mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 18:58:54 +01:00
example/blocks: Refactoring
This commit is contained in:
parent
20e12a7ea9
commit
8a7687687f
@ -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())
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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{})
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user