example/blocks: Refactoring

This commit is contained in:
Hajime Hoshi 2018-01-22 15:12:17 +09:00
parent 20e12a7ea9
commit 8a7687687f
10 changed files with 273 additions and 253 deletions

View File

@ -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())
}

View File

@ -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
}

View File

@ -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]

View File

@ -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)

View File

@ -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)

View File

@ -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)
}

View File

@ -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{})
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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)