From 8a7687687fbdf7226bc1c687c6a38317352df4ef Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Mon, 22 Jan 2018 15:12:17 +0900 Subject: [PATCH] example/blocks: Refactoring --- examples/blocks/blocks/field.go | 102 +++++++------- examples/blocks/blocks/game.go | 36 ++--- examples/blocks/blocks/gamepad.go | 47 ++++--- examples/blocks/blocks/gamepadscene.go | 23 ++-- examples/blocks/blocks/gamescene.go | 33 +++-- examples/blocks/blocks/input.go | 48 +++---- examples/blocks/blocks/piece.go | 176 +++++++++++++------------ examples/blocks/blocks/scenemanager.go | 42 +++--- examples/blocks/blocks/titlescene.go | 17 ++- examples/blocks/main.go | 2 +- 10 files changed, 273 insertions(+), 253 deletions(-) diff --git a/examples/blocks/blocks/field.go b/examples/blocks/blocks/field.go index f19770416..e4153b10a 100644 --- a/examples/blocks/blocks/field.go +++ b/examples/blocks/blocks/field.go @@ -22,16 +22,15 @@ import ( const maxFlushCount = 20 +// Field represents a game field with block states. type Field struct { - blocks [fieldBlockNumX][fieldBlockNumY]BlockType - flushCount int - onEndFlushing func(int) -} - -func NewField() *Field { - return &Field{} + blocks [fieldBlockNumX][fieldBlockNumY]BlockType + flushCount int + onEndFlushAnimating func(int) } +// IsBlocked returns a boolean value indicating whether +// there is a block at position (x, y) on the field. func (f *Field) IsBlocked(x, y int) bool { if x < 0 || fieldBlockNumX <= x { return true @@ -45,49 +44,58 @@ func (f *Field) IsBlocked(x, y int) bool { return f.blocks[x][y] != BlockTypeNone } -func (f *Field) collides(piece *Piece, x, y int, angle Angle) bool { - return piece.collides(f, x, y, angle) -} - +// MovePieceToLeft tries to move the piece to the left +// and returns the piece's next x position. func (f *Field) MovePieceToLeft(piece *Piece, x, y int, angle Angle) int { - if f.collides(piece, x-1, y, angle) { + if piece.collides(f, x-1, y, angle) { return x } return x - 1 } +// MovePieceToRight tries to move the piece to the right +// and returns the piece's next x position. func (f *Field) MovePieceToRight(piece *Piece, x, y int, angle Angle) int { - if f.collides(piece, x+1, y, angle) { + if piece.collides(f, x+1, y, angle) { return x } return x + 1 } +// PieceDroppable returns a boolean value indicating whether +// the piece at (x, y) with the given angle can drop. func (f *Field) PieceDroppable(piece *Piece, x, y int, angle Angle) bool { - return !f.collides(piece, x, y+1, angle) + return !piece.collides(f, x, y+1, angle) } +// DropPiece tries to drop the piece to the right +// and returns the piece's next y position. func (f *Field) DropPiece(piece *Piece, x, y int, angle Angle) int { - if f.collides(piece, x, y+1, angle) { + if piece.collides(f, x, y+1, angle) { return y } return y + 1 } +// RotatePieceRight tries to rotate the piece to the right +// and returns the piece's next angle. func (f *Field) RotatePieceRight(piece *Piece, x, y int, angle Angle) Angle { - if f.collides(piece, x, y, angle.RotateRight()) { + if piece.collides(f, x, y, angle.RotateRight()) { return angle } return angle.RotateRight() } +// RotatePieceLeft tries to rotate the piece to the left +// and returns the piece's next angle. func (f *Field) RotatePieceLeft(piece *Piece, x, y int, angle Angle) Angle { - if f.collides(piece, x, y, angle.RotateLeft()) { + if piece.collides(f, x, y, angle.RotateLeft()) { return angle } return angle.RotateLeft() } +// AbsorbPiece absorbs the piece at (x, y) with the given angle into the field. func (f *Field) AbsorbPiece(piece *Piece, x, y int, angle Angle) { piece.AbsorbInto(f, x, y, angle) if f.flushable() { @@ -95,14 +103,20 @@ func (f *Field) AbsorbPiece(piece *Piece, x, y int, angle Angle) { } } -func (f *Field) Flushing() bool { +// IsFlushAnimating returns a boolean value indicating +// whether there is a flush animation. +func (f *Field) IsFlushAnimating() bool { return 0 < f.flushCount } -func (f *Field) SetEndFlushing(fn func(lines int)) { - f.onEndFlushing = fn +// SetEndFlushAnimating sets a callback fired on the end of flush animation. +// The callback argument is the number of flushed lines. +func (f *Field) SetEndFlushAnimating(fn func(lines int)) { + f.onEndFlushAnimating = fn } +// flushable returns a boolean value indicating whether +// there is a flushable line in the field. func (f *Field) flushable() bool { for j := fieldBlockNumY - 1; 0 <= j; j-- { if f.flushableLine(j) { @@ -112,6 +126,8 @@ func (f *Field) flushable() bool { return false } +// flushableLine returns a boolean value indicating whether +// the line j is flushabled or not. func (f *Field) flushableLine(j int) bool { for i := 0; i < fieldBlockNumX; i++ { if f.blocks[i][j] == BlockTypeNone { @@ -125,7 +141,7 @@ func (f *Field) setBlock(x, y int, blockType BlockType) { f.blocks[x][y] = blockType } -func (f *Field) endFlushing() int { +func (f *Field) endFlushAnimating() int { flushedLineNum := 0 for j := fieldBlockNumY - 1; 0 <= j; j-- { if f.flushLine(j + flushedLineNum) { @@ -135,6 +151,11 @@ func (f *Field) endFlushing() int { return flushedLineNum } +// flushLine flushes the line j if possible, and if the line is flushed, +// the other lines above the line go down. +// +// flushLine returns a boolean value indicating whether +// the line is actually flushed. func (f *Field) flushLine(j int) bool { for i := 0; i < fieldBlockNumX; i++ { if f.blocks[i][j] == BlockTypeNone { @@ -153,14 +174,17 @@ func (f *Field) flushLine(j int) bool { } func (f *Field) Update() { - if 0 <= f.flushCount { - f.flushCount-- - if f.flushCount == 0 { - l := f.endFlushing() - if f.onEndFlushing != nil { - f.onEndFlushing(l) - } - } + if f.flushCount == 0 { + return + } + + f.flushCount-- + if f.flushCount > 0 { + return + } + + if f.onEndFlushAnimating != nil { + f.onEndFlushAnimating(f.endFlushAnimating()) } } @@ -171,12 +195,7 @@ func min(a, b float64) float64 { return a } -func (f *Field) flushingColor() ebiten.ColorM { - c := f.flushCount - if c < 0 { - c = 0 - } - rate := float64(c) / maxFlushCount +func flushingColor(rate float64) ebiten.ColorM { clr := ebiten.ColorM{} alpha := min(1, rate*2) clr.Scale(1, 1, 1, alpha) @@ -186,23 +205,16 @@ func (f *Field) flushingColor() ebiten.ColorM { } func (f *Field) Draw(r *ebiten.Image, x, y int) { - blocks := make([][]BlockType, len(f.blocks)) - flushingBlocks := make([][]BlockType, len(f.blocks)) - for i := 0; i < fieldBlockNumX; i++ { - blocks[i] = make([]BlockType, fieldBlockNumY) - flushingBlocks[i] = make([]BlockType, fieldBlockNumY) - } + fc := flushingColor(float64(f.flushCount) / maxFlushCount) for j := 0; j < fieldBlockNumY; j++ { if f.flushableLine(j) { for i := 0; i < fieldBlockNumX; i++ { - flushingBlocks[i][j] = f.blocks[i][j] + drawBlock(r, f.blocks[i][j], i*blockWidth+x, j*blockHeight+y, fc) } } else { for i := 0; i < fieldBlockNumX; i++ { - blocks[i][j] = f.blocks[i][j] + drawBlock(r, f.blocks[i][j], i*blockWidth+x, j*blockHeight+y, ebiten.ColorM{}) } } } - drawBlocks(r, blocks, x, y, ebiten.ColorM{}) - drawBlocks(r, flushingBlocks, x, y, f.flushingColor()) } diff --git a/examples/blocks/blocks/game.go b/examples/blocks/blocks/game.go index 382234e8a..9f5152ffd 100644 --- a/examples/blocks/blocks/game.go +++ b/examples/blocks/blocks/game.go @@ -17,41 +17,33 @@ package blocks import ( - "sync" - "github.com/hajimehoshi/ebiten" ) -const ScreenWidth = 256 -const ScreenHeight = 240 - -type GameState struct { - SceneManager *SceneManager - Input *Input -} +const ( + ScreenWidth = 256 + ScreenHeight = 240 +) type Game struct { - once sync.Once sceneManager *SceneManager input Input } -func NewGame() *Game { - return &Game{ - sceneManager: NewSceneManager(NewTitleScene()), +func (g *Game) Update(r *ebiten.Image) error { + if g.sceneManager == nil { + g.sceneManager = &SceneManager{} + g.sceneManager.GoTo(&TitleScene{}) } -} -func (game *Game) Update(r *ebiten.Image) error { - game.input.Update() - if err := game.sceneManager.Update(&GameState{ - SceneManager: game.sceneManager, - Input: &game.input, - }); err != nil { + g.input.Update() + if err := g.sceneManager.Update(&g.input); err != nil { return err } - if !ebiten.IsRunningSlowly() { - game.sceneManager.Draw(r) + if ebiten.IsRunningSlowly() { + return nil } + + g.sceneManager.Draw(r) return nil } diff --git a/examples/blocks/blocks/gamepad.go b/examples/blocks/blocks/gamepad.go index b5527a23e..43f0a3917 100644 --- a/examples/blocks/blocks/gamepad.go +++ b/examples/blocks/blocks/gamepad.go @@ -22,16 +22,24 @@ import ( "github.com/hajimehoshi/ebiten" ) -type abstractButton int +type virtualGamepadButton int const ( - abstractButtonLeft abstractButton = iota - abstractButtonRight - abstractButtonDown - abstractButtonButtonA - abstractButtonButtonB + virtualGamepadButtonLeft virtualGamepadButton = iota + virtualGamepadButtonRight + virtualGamepadButtonDown + virtualGamepadButtonButtonA + virtualGamepadButtonButtonB ) +var virtualGamepadButtons = []virtualGamepadButton{ + virtualGamepadButtonLeft, + virtualGamepadButtonRight, + virtualGamepadButtonDown, + virtualGamepadButtonButtonA, + virtualGamepadButtonButtonB, +} + const threshold = 0.75 type axis struct { @@ -40,19 +48,19 @@ type axis struct { } type gamepadConfig struct { - current abstractButton - buttons map[abstractButton]ebiten.GamepadButton - axes map[abstractButton]axis + current virtualGamepadButton + buttons map[virtualGamepadButton]ebiten.GamepadButton + axes map[virtualGamepadButton]axis assignedButtons map[ebiten.GamepadButton]struct{} assignedAxes map[axis]struct{} } func (c *gamepadConfig) initializeIfNeeded() { if c.buttons == nil { - c.buttons = map[abstractButton]ebiten.GamepadButton{} + c.buttons = map[virtualGamepadButton]ebiten.GamepadButton{} } if c.axes == nil { - c.axes = map[abstractButton]axis{} + c.axes = map[virtualGamepadButton]axis{} } if c.assignedButtons == nil { c.assignedButtons = map[ebiten.GamepadButton]struct{}{} @@ -69,27 +77,29 @@ func (c *gamepadConfig) Reset() { c.assignedAxes = nil } -func (c *gamepadConfig) Scan(index int, b abstractButton) bool { +// Scan scans the current input state and assigns the given virtual gamepad button b +// to the current (pysical) pressed buttons of the gamepad. +func (c *gamepadConfig) Scan(gamepadID int, b virtualGamepadButton) bool { c.initializeIfNeeded() delete(c.buttons, b) delete(c.axes, b) - ebn := ebiten.GamepadButton(ebiten.GamepadButtonNum(index)) + ebn := ebiten.GamepadButton(ebiten.GamepadButtonNum(gamepadID)) for eb := ebiten.GamepadButton(0); eb < ebn; eb++ { if _, ok := c.assignedButtons[eb]; ok { continue } - if ebiten.IsGamepadButtonPressed(index, eb) { + if ebiten.IsGamepadButtonPressed(gamepadID, eb) { c.buttons[b] = eb c.assignedButtons[eb] = struct{}{} return true } } - an := ebiten.GamepadAxisNum(index) + an := ebiten.GamepadAxisNum(gamepadID) for a := 0; a < an; a++ { - v := ebiten.GamepadAxis(index, a) + v := ebiten.GamepadAxis(gamepadID, a) // Check |v| < 1.0 because // 1) there is a bug that a button returns an axis value wrongly // and the value may be over 1. @@ -114,7 +124,7 @@ func (c *gamepadConfig) Scan(index int, b abstractButton) bool { return false } -func (c *gamepadConfig) IsButtonPressed(id int, b abstractButton) bool { +func (c *gamepadConfig) IsButtonPressed(b virtualGamepadButton) bool { c.initializeIfNeeded() bb, ok := c.buttons[b] @@ -133,7 +143,8 @@ func (c *gamepadConfig) IsButtonPressed(id int, b abstractButton) bool { return false } -func (c *gamepadConfig) Name(b abstractButton) string { +// Name returns the button's name. +func (c *gamepadConfig) ButtonName(b virtualGamepadButton) string { c.initializeIfNeeded() bb, ok := c.buttons[b] diff --git a/examples/blocks/blocks/gamepadscene.go b/examples/blocks/blocks/gamepadscene.go index c301d4bff..2b2517713 100644 --- a/examples/blocks/blocks/gamepadscene.go +++ b/examples/blocks/blocks/gamepadscene.go @@ -31,25 +31,21 @@ type GamepadScene struct { buttonStates []string } -func NewGamepadScene() *GamepadScene { - return &GamepadScene{} -} - func (s *GamepadScene) Update(state *GameState) error { if s.currentIndex == 0 { state.Input.gamepadConfig.Reset() } if state.Input.StateForKey(ebiten.KeyEscape) == 1 { state.Input.gamepadConfig.Reset() - state.SceneManager.GoTo(NewTitleScene()) + state.SceneManager.GoTo(&TitleScene{}) } if s.buttonStates == nil { - s.buttonStates = make([]string, len(gamepadAbstractButtons)) + s.buttonStates = make([]string, len(virtualGamepadButtons)) } - for i, b := range gamepadAbstractButtons { + for i, b := range virtualGamepadButtons { if i < s.currentIndex { - s.buttonStates[i] = strings.ToUpper(state.Input.gamepadConfig.Name(b)) + s.buttonStates[i] = strings.ToUpper(state.Input.gamepadConfig.ButtonName(b)) continue } if s.currentIndex == i { @@ -62,15 +58,16 @@ func (s *GamepadScene) Update(state *GameState) error { if 0 < s.countAfterSetting { s.countAfterSetting-- if s.countAfterSetting <= 0 { - state.SceneManager.GoTo(NewTitleScene()) + state.SceneManager.GoTo(&TitleScene{}) } return nil } - b := gamepadAbstractButtons[s.currentIndex] - if state.Input.gamepadConfig.Scan(0, b) { + b := virtualGamepadButtons[s.currentIndex] + const gamepadID = 0 + if state.Input.gamepadConfig.Scan(gamepadID, b) { s.currentIndex++ - if s.currentIndex == len(gamepadAbstractButtons) { + if s.currentIndex == len(virtualGamepadButtons) { s.countAfterSetting = ebiten.FPS } } @@ -102,7 +99,7 @@ ROTATE RIGHT: %s %s` msg := "" - if s.currentIndex == len(gamepadAbstractButtons) { + if s.currentIndex == len(virtualGamepadButtons) { msg = "OK!" } str := fmt.Sprintf(f, s.buttonStates[0], s.buttonStates[1], s.buttonStates[2], s.buttonStates[3], s.buttonStates[4], msg) diff --git a/examples/blocks/blocks/gamescene.go b/examples/blocks/blocks/gamescene.go index cadfc38d4..5a9f8ed0f 100644 --- a/examples/blocks/blocks/gamescene.go +++ b/examples/blocks/blocks/gamescene.go @@ -78,20 +78,25 @@ func init() { // Windows imageWindows, _ = ebiten.NewImage(ScreenWidth, ScreenHeight, ebiten.FilterNearest) + // Windows: Field x, y := fieldWindowPosition() drawWindow(imageWindows, x, y, fieldWidth, fieldHeight) + // Windows: Next x, y = nextWindowLabelPosition() common.ArcadeFont.DrawTextWithShadow(imageWindows, "NEXT", x, y, 1, fontColor) x, y = nextWindowPosition() drawWindow(imageWindows, x, y, 5*blockWidth, 5*blockHeight) + // Windows: Score x, y = scoreTextBoxPosition() drawTextBox(imageWindows, "SCORE", x, y, textBoxWidth()) + // Windows: Level x, y = levelTextBoxPosition() drawTextBox(imageWindows, "LEVEL", x, y, textBoxWidth()) + // Windows: Lines x, y = linesTextBoxPosition() drawTextBox(imageWindows, "LINES", x, y, textBoxWidth()) @@ -138,7 +143,7 @@ type GameScene struct { func NewGameScene() *GameScene { return &GameScene{ - field: NewField(), + field: &Field{}, rand: rand.New(rand.NewSource(time.Now().UnixNano())), } } @@ -168,8 +173,10 @@ func (s *GameScene) drawBackground(r *ebiten.Image) { r.DrawImage(imageGameBG, op) } -const fieldWidth = blockWidth * fieldBlockNumX -const fieldHeight = blockHeight * fieldBlockNumY +const ( + fieldWidth = blockWidth * fieldBlockNumX + fieldHeight = blockHeight * fieldBlockNumY +) func (s *GameScene) choosePiece() *Piece { num := int(BlockTypeMax) @@ -213,7 +220,7 @@ func (s *GameScene) Update(state *GameState) error { if s.gameover { // TODO: Gamepad key? if state.Input.StateForKey(ebiten.KeySpace) == 1 { - state.SceneManager.GoTo(NewTitleScene()) + state.SceneManager.GoTo(&TitleScene{}) } return nil } @@ -233,14 +240,14 @@ func (s *GameScene) Update(state *GameState) error { angle := s.currentPieceAngle // Move piece by user input. - if !s.field.Flushing() { + if !s.field.IsFlushAnimating() { piece := s.currentPiece x := s.currentPieceX y := s.currentPieceY - if state.Input.IsRotateRightTrigger() { + if state.Input.IsRotateRightJustPressed() { s.currentPieceAngle = s.field.RotatePieceRight(piece, x, y, angle) moved = angle != s.currentPieceAngle - } else if state.Input.IsRotateLeftTrigger() { + } else if state.Input.IsRotateLeftJustPressed() { s.currentPieceAngle = s.field.RotatePieceLeft(piece, x, y, angle) moved = angle != s.currentPieceAngle } else if l := state.Input.StateForLeft(); l == 1 || (10 <= l && l%2 == 0) { @@ -259,7 +266,7 @@ func (s *GameScene) Update(state *GameState) error { } // Drop the current piece with gravity. - if !s.field.Flushing() { + if !s.field.IsFlushAnimating() { angle := s.currentPieceAngle s.currentPieceYCarry += 2*s.level() + 1 const maxCarry = 60 @@ -269,7 +276,7 @@ func (s *GameScene) Update(state *GameState) error { } } - if !s.field.Flushing() && !s.field.PieceDroppable(piece, s.currentPieceX, s.currentPieceY, angle) { + if !s.field.IsFlushAnimating() && !s.field.PieceDroppable(piece, s.currentPieceX, s.currentPieceY, angle) { if 0 < state.Input.StateForDown() { s.landingCount += 10 } else { @@ -277,8 +284,8 @@ func (s *GameScene) Update(state *GameState) error { } if maxLandingCount <= s.landingCount { s.field.AbsorbPiece(piece, s.currentPieceX, s.currentPieceY, angle) - if s.field.Flushing() { - s.field.SetEndFlushing(func(lines int) { + if s.field.IsFlushAnimating() { + s.field.SetEndFlushAnimating(func(lines int) { s.lines += lines if 0 < lines { s.addScore(lines) @@ -298,7 +305,7 @@ func (s *GameScene) goNextPiece() { s.initCurrentPiece(s.nextPiece) s.nextPiece = s.choosePiece() s.landingCount = 0 - if s.currentPiece.Collides(s.field, s.currentPieceX, s.currentPieceY, s.currentPieceAngle) { + if s.currentPiece.collides(s.field, s.currentPieceX, s.currentPieceY, s.currentPieceAngle) { s.gameover = true } } @@ -323,7 +330,7 @@ func (s *GameScene) Draw(r *ebiten.Image) { // Draw blocks fieldX, fieldY := fieldWindowPosition() s.field.Draw(r, fieldX, fieldY) - if s.currentPiece != nil && !s.field.Flushing() { + if s.currentPiece != nil && !s.field.IsFlushAnimating() { x := fieldX + s.currentPieceX*blockWidth y := fieldY + s.currentPieceY*blockHeight s.currentPiece.Draw(r, x, y, s.currentPieceAngle) diff --git a/examples/blocks/blocks/input.go b/examples/blocks/blocks/input.go index b2a20dcf8..09878b90a 100644 --- a/examples/blocks/blocks/input.go +++ b/examples/blocks/blocks/input.go @@ -20,19 +20,11 @@ import ( "github.com/hajimehoshi/ebiten" ) -var gamepadAbstractButtons = []abstractButton{ - abstractButtonLeft, - abstractButtonRight, - abstractButtonDown, - abstractButtonButtonA, - abstractButtonButtonB, -} - type Input struct { - keyStates map[ebiten.Key]int - gamepadButtonStates map[ebiten.GamepadButton]int - gamepadAbstractButtonStates map[abstractButton]int - gamepadConfig gamepadConfig + keyStates map[ebiten.Key]int + gamepadButtonStates map[ebiten.GamepadButton]int + virtualGamepadButtonStates map[virtualGamepadButton]int + gamepadConfig gamepadConfig } func (i *Input) StateForKey(key ebiten.Key) int { @@ -49,11 +41,11 @@ func (i *Input) StateForGamepadButton(b ebiten.GamepadButton) int { return i.gamepadButtonStates[b] } -func (i *Input) stateForGamepadAbstractButton(b abstractButton) int { - if i.gamepadAbstractButtonStates == nil { +func (i *Input) stateForVirtualGamepadButton(b virtualGamepadButton) int { + if i.virtualGamepadButtonStates == nil { return 0 } - return i.gamepadAbstractButtonStates[b] + return i.virtualGamepadButtonStates[b] } func (i *Input) Update() { @@ -80,30 +72,30 @@ func (i *Input) Update() { i.gamepadButtonStates[b]++ } - if i.gamepadAbstractButtonStates == nil { - i.gamepadAbstractButtonStates = map[abstractButton]int{} + if i.virtualGamepadButtonStates == nil { + i.virtualGamepadButtonStates = map[virtualGamepadButton]int{} } - for _, b := range gamepadAbstractButtons { - if !i.gamepadConfig.IsButtonPressed(gamepadID, b) { - i.gamepadAbstractButtonStates[b] = 0 + for _, b := range virtualGamepadButtons { + if !i.gamepadConfig.IsButtonPressed(b) { + i.virtualGamepadButtonStates[b] = 0 continue } - i.gamepadAbstractButtonStates[b]++ + i.virtualGamepadButtonStates[b]++ } } -func (i *Input) IsRotateRightTrigger() bool { +func (i *Input) IsRotateRightJustPressed() bool { if i.StateForKey(ebiten.KeySpace) == 1 || i.StateForKey(ebiten.KeyX) == 1 { return true } - return i.stateForGamepadAbstractButton(abstractButtonButtonB) == 1 + return i.stateForVirtualGamepadButton(virtualGamepadButtonButtonB) == 1 } -func (i *Input) IsRotateLeftTrigger() bool { +func (i *Input) IsRotateLeftJustPressed() bool { if i.StateForKey(ebiten.KeyZ) == 1 { return true } - return i.stateForGamepadAbstractButton(abstractButtonButtonA) == 1 + return i.stateForVirtualGamepadButton(virtualGamepadButtonButtonA) == 1 } func (i *Input) StateForLeft() int { @@ -111,7 +103,7 @@ func (i *Input) StateForLeft() int { if 0 < v { return v } - return i.stateForGamepadAbstractButton(abstractButtonLeft) + return i.stateForVirtualGamepadButton(virtualGamepadButtonLeft) } func (i *Input) StateForRight() int { @@ -119,7 +111,7 @@ func (i *Input) StateForRight() int { if 0 < v { return v } - return i.stateForGamepadAbstractButton(abstractButtonRight) + return i.stateForVirtualGamepadButton(virtualGamepadButtonRight) } func (i *Input) StateForDown() int { @@ -127,5 +119,5 @@ func (i *Input) StateForDown() int { if 0 < v { return v } - return i.stateForGamepadAbstractButton(abstractButtonDown) + return i.stateForVirtualGamepadButton(virtualGamepadButtonDown) } diff --git a/examples/blocks/blocks/piece.go b/examples/blocks/blocks/piece.go index d191cf38a..d0f84d0ea 100644 --- a/examples/blocks/blocks/piece.go +++ b/examples/blocks/blocks/piece.go @@ -75,77 +75,86 @@ type Piece struct { blocks [][]bool } -func toBlocks(ints [][]int) [][]bool { - blocks := make([][]bool, len(ints)) - for j, row := range ints { +func transpose(bs [][]bool) [][]bool { + blocks := make([][]bool, len(bs)) + for j, row := range bs { blocks[j] = make([]bool, len(row)) } // Tranpose the argument matrix. - for i, col := range ints { + for i, col := range bs { for j, v := range col { - blocks[j][i] = v != 0 + blocks[j][i] = v } } return blocks } -var Pieces = map[BlockType]*Piece{ - BlockType1: { - blockType: BlockType1, - blocks: toBlocks([][]int{ - {0, 0, 0, 0}, - {1, 1, 1, 1}, - {0, 0, 0, 0}, - {0, 0, 0, 0}, - }), - }, - BlockType2: { - blockType: BlockType2, - blocks: toBlocks([][]int{ - {1, 0, 0}, - {1, 1, 1}, - {0, 0, 0}, - }), - }, - BlockType3: { - blockType: BlockType3, - blocks: toBlocks([][]int{ - {0, 1, 0}, - {1, 1, 1}, - {0, 0, 0}, - }), - }, - BlockType4: { - blockType: BlockType4, - blocks: toBlocks([][]int{ - {0, 0, 1}, - {1, 1, 1}, - {0, 0, 0}, - }), - }, - BlockType5: { - blockType: BlockType5, - blocks: toBlocks([][]int{ - {1, 1, 0}, - {0, 1, 1}, - {0, 0, 0}, - }), - }, - BlockType6: { - blockType: BlockType6, - blocks: toBlocks([][]int{ - {0, 1, 1}, - {1, 1, 0}, - {0, 0, 0}, - }), - }, - BlockType7: { - blockType: BlockType7, - blocks: toBlocks([][]int{ - {1, 1}, - {1, 1}, - }), - }, +// Pieces is the set of all the possible pieces. +var Pieces map[BlockType]*Piece + +func init() { + const ( + f = false + t = true + ) + Pieces = map[BlockType]*Piece{ + BlockType1: { + blockType: BlockType1, + blocks: transpose([][]bool{ + {f, f, f, f}, + {t, t, t, t}, + {f, f, f, f}, + {f, f, f, f}, + }), + }, + BlockType2: { + blockType: BlockType2, + blocks: transpose([][]bool{ + {t, f, f}, + {t, t, t}, + {f, f, f}, + }), + }, + BlockType3: { + blockType: BlockType3, + blocks: transpose([][]bool{ + {f, t, f}, + {t, t, t}, + {f, f, f}, + }), + }, + BlockType4: { + blockType: BlockType4, + blocks: transpose([][]bool{ + {f, f, t}, + {t, t, t}, + {f, f, f}, + }), + }, + BlockType5: { + blockType: BlockType5, + blocks: transpose([][]bool{ + {t, t, f}, + {f, t, t}, + {f, f, f}, + }), + }, + BlockType6: { + blockType: BlockType6, + blocks: transpose([][]bool{ + {f, t, t}, + {t, t, f}, + {f, f, f}, + }), + }, + BlockType7: { + blockType: BlockType7, + blocks: transpose([][]bool{ + {t, t}, + {t, t}, + }), + }, + } } const ( @@ -155,23 +164,19 @@ const ( fieldBlockNumY = 20 ) -func drawBlocks(r *ebiten.Image, blocks [][]BlockType, x, y int, clr ebiten.ColorM) { +func drawBlock(r *ebiten.Image, block BlockType, x, y int, clr ebiten.ColorM) { + if block == BlockTypeNone { + return + } + op := &ebiten.DrawImageOptions{} op.ColorM = clr - for j := 0; j < len(blocks[0]); j++ { - for i := 0; i < len(blocks); i++ { - op.GeoM.Reset() - op.GeoM.Translate(float64(i*blockWidth+x), float64(j*blockHeight+y)) - block := blocks[i][j] - if block == BlockTypeNone { - continue - } - x := (int(block) - 1) * blockWidth - src := image.Rect(x, 0, x+blockWidth, blockHeight) - op.SourceRect = &src - r.DrawImage(imageBlocks, op) - } - } + op.GeoM.Translate(float64(x), float64(y)) + + srcX := (int(block) - 1) * blockWidth + src := image.Rect(srcX, 0, srcX+blockWidth, blockHeight) + op.SourceRect = &src + r.DrawImage(imageBlocks, op) } func (p *Piece) InitialPosition() (int, int) { @@ -190,6 +195,8 @@ Loop: return x, y } +// isBlocked returns a boolean value indicating whether +// there is a block at the position (x, y) of the piece with the given angle. func (p *Piece) isBlocked(i, j int, angle Angle) bool { size := len(p.blocks) i2, j2 := i, j @@ -208,6 +215,8 @@ func (p *Piece) isBlocked(i, j int, angle Angle) bool { return p.blocks[i2][j2] } +// collides returns a boolean value indicating whether +// the piece at (x, y) with the given angle would collide with the field's blocks. func (p *Piece) collides(field *Field, x, y int, angle Angle) bool { size := len(p.blocks) for i := 0; i < size; i++ { @@ -220,10 +229,6 @@ func (p *Piece) collides(field *Field, x, y int, angle Angle) bool { return false } -func (p *Piece) Collides(field *Field, x, y int, angle Angle) bool { - return p.collides(field, x, y, angle) -} - func (p *Piece) AbsorbInto(field *Field, x, y int, angle Angle) { size := len(p.blocks) for i := 0; i < size; i++ { @@ -242,15 +247,12 @@ func (p *Piece) DrawAtCenter(r *ebiten.Image, x, y, width, height int, angle Ang } func (p *Piece) Draw(r *ebiten.Image, x, y int, angle Angle) { - size := len(p.blocks) - blocks := make([][]BlockType, size) for i := range p.blocks { - blocks[i] = make([]BlockType, size) - for j := range blocks[i] { + for j := range p.blocks[i] { if p.isBlocked(i, j, angle) { - blocks[i][j] = p.blockType + drawBlock(r, p.blockType, i*blockWidth+x, j*blockHeight+y, ebiten.ColorM{}) } } } - drawBlocks(r, blocks, x, y, ebiten.ColorM{}) + } diff --git a/examples/blocks/blocks/scenemanager.go b/examples/blocks/blocks/scenemanager.go index 08d67d59e..281d623b9 100644 --- a/examples/blocks/blocks/scenemanager.go +++ b/examples/blocks/blocks/scenemanager.go @@ -43,31 +43,35 @@ type SceneManager struct { transitionCount int } -func NewSceneManager(initScene Scene) *SceneManager { - return &SceneManager{ - current: initScene, - transitionCount: -1, - } +type GameState struct { + SceneManager *SceneManager + Input *Input } -func (s *SceneManager) Update(state *GameState) error { - if s.transitionCount == -1 { - return s.current.Update(state) +func (s *SceneManager) Update(input *Input) error { + if s.transitionCount == 0 { + return s.current.Update(&GameState{ + SceneManager: s, + Input: input, + }) } - s.transitionCount++ - if transitionMaxCount <= s.transitionCount { - s.current = s.next - s.next = nil - s.transitionCount = -1 + + s.transitionCount-- + if s.transitionCount > 0 { + return nil } + + s.current = s.next + s.next = nil return nil } func (s *SceneManager) Draw(r *ebiten.Image) { - if s.transitionCount == -1 { + if s.transitionCount == 0 { s.current.Draw(r) return } + transitionFrom.Clear() s.current.Draw(transitionFrom) @@ -76,13 +80,17 @@ func (s *SceneManager) Draw(r *ebiten.Image) { r.DrawImage(transitionFrom, nil) - alpha := float64(s.transitionCount) / float64(transitionMaxCount) + alpha := 1 - float64(s.transitionCount)/float64(transitionMaxCount) op := &ebiten.DrawImageOptions{} op.ColorM.Scale(1, 1, 1, alpha) r.DrawImage(transitionTo, op) } func (s *SceneManager) GoTo(scene Scene) { - s.next = scene - s.transitionCount = 0 + if s.current == nil { + s.current = scene + } else { + s.next = scene + s.transitionCount = transitionMaxCount + } } diff --git a/examples/blocks/blocks/titlescene.go b/examples/blocks/blocks/titlescene.go index 8120eee7d..65ba314f7 100644 --- a/examples/blocks/blocks/titlescene.go +++ b/examples/blocks/blocks/titlescene.go @@ -38,13 +38,9 @@ type TitleScene struct { count int } -func NewTitleScene() *TitleScene { - return &TitleScene{} -} - func anyGamepadAbstractButtonPressed(i *Input) bool { - for _, b := range gamepadAbstractButtons { - if i.gamepadConfig.IsButtonPressed(0, b) { + for _, b := range virtualGamepadButtons { + if i.gamepadConfig.IsButtonPressed(b) { return true } } @@ -71,8 +67,11 @@ func (s *TitleScene) Update(state *GameState) error { state.SceneManager.GoTo(NewGameScene()) return nil } + + // If 'abstract' gamepad buttons are not set and any gamepad buttons are pressed, + // go to the gamepad configuration scene. if anyGamepadButtonPressed(state.Input) { - state.SceneManager.GoTo(NewGamepadScene()) + state.SceneManager.GoTo(&GamepadScene{}) return nil } return nil @@ -93,7 +92,7 @@ func (s *TitleScene) drawTitleBackground(r *ebiten.Image, c int) { op := &ebiten.DrawImageOptions{} for i := 0; i < (ScreenWidth/w+1)*(ScreenHeight/h+2); i++ { op.GeoM.Reset() - dx := (-c / 4) % w + dx := -(c / 4) % w dy := (c / 4) % h dstX := (i%(ScreenWidth/w+1))*w + dx dstY := (i/(ScreenWidth/w+1)-1)*h + dy @@ -103,7 +102,7 @@ func (s *TitleScene) drawTitleBackground(r *ebiten.Image, c int) { } func drawLogo(r *ebiten.Image, str string) { - scale := 4 + const scale = 4 textWidth := common.ArcadeFont.TextWidth(str) * scale x := (ScreenWidth - textWidth) / 2 y := 32 diff --git a/examples/blocks/main.go b/examples/blocks/main.go index fc249dd66..d8777260e 100644 --- a/examples/blocks/main.go +++ b/examples/blocks/main.go @@ -41,7 +41,7 @@ func main() { defer pprof.StopCPUProfile() } - game := blocks.NewGame() + game := &blocks.Game{} update := game.Update if err := ebiten.Run(update, blocks.ScreenWidth, blocks.ScreenHeight, 2, "Blocks (Ebiten Demo)"); err != nil { log.Fatal(err)