feat: refactor gameplay logic from main.go to a state machine handler.
This commit is contained in:
parent
75b4981566
commit
29225b7007
2 changed files with 139 additions and 110 deletions
130
game/state/handlers/gameplay.go
Normal file
130
game/state/handlers/gameplay.go
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"tetris/assets"
|
||||||
|
"tetris/engine/audio"
|
||||||
|
"tetris/engine/core"
|
||||||
|
"tetris/engine/render"
|
||||||
|
"tetris/game"
|
||||||
|
"tetris/game/draw"
|
||||||
|
"tetris/game/state"
|
||||||
|
|
||||||
|
rl "github.com/gen2brain/raylib-go/raylib"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GamePlay struct {
|
||||||
|
shape game.Shape
|
||||||
|
shape_pos core.Vec2i8
|
||||||
|
score game.Score
|
||||||
|
dropTimer core.IntervalTimer
|
||||||
|
moveTimer core.IntervalTimer
|
||||||
|
grid game.Grid
|
||||||
|
nextShape game.Shape
|
||||||
|
shapeQueue *game.ShapeQueue
|
||||||
|
r draw.Renderer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGamePlay() *GamePlay {
|
||||||
|
return &GamePlay{
|
||||||
|
dropTimer: core.NewIntervalTimer(0.3),
|
||||||
|
moveTimer: core.NewIntervalTimer(0.1),
|
||||||
|
r: draw.Renderer{
|
||||||
|
Theme: &draw.Theme{
|
||||||
|
FrameBG: color.RGBA{R: 30, G: 30, B: 46, A: 255},
|
||||||
|
FrameBorder: color.RGBA{R: 242, G: 205, B: 205, A: 255},
|
||||||
|
TextHeader: color.RGBA{R: 242, G: 205, B: 205, A: 255},
|
||||||
|
Text: color.RGBA{R: 205, G: 214, B: 244, A: 255},
|
||||||
|
GridBackground: color.RGBA{R: 17, G: 17, B: 27, A: 255},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *GamePlay) Enter() {
|
||||||
|
gp.shapeQueue = game.NewShapeQueue()
|
||||||
|
gp.nextShape = gp.shapeQueue.Next()
|
||||||
|
gp.SpawnShape()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (GamePlay) Exit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *GamePlay) SpawnShape() {
|
||||||
|
gp.shape = gp.nextShape
|
||||||
|
gp.nextShape = gp.shapeQueue.Next()
|
||||||
|
gp.shape_pos = core.Vec2i8{X: 4, Y: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *GamePlay) LockShape() {
|
||||||
|
audio.Play(assets.SFX_SHAPE_LOCKED)
|
||||||
|
|
||||||
|
for _, block := range gp.shape.Coordinates() {
|
||||||
|
block = gp.shape_pos.Add(block)
|
||||||
|
// Check bounds
|
||||||
|
if block.X < 0 || block.X > int8(gp.grid.Width()) || block.Y < 0 || block.Y > int8(gp.grid.Height()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gp.grid.Set(byte(block.X), byte(block.Y), gp.shape.GetBlock())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp *GamePlay) Update(fsm state.Transitioner, delta float32) {
|
||||||
|
if rl.IsKeyPressed(rl.KeyDown) {
|
||||||
|
gp.dropTimer.SetInterval(0.05)
|
||||||
|
} else if rl.IsKeyReleased(rl.KeyDown) {
|
||||||
|
gp.dropTimer.SetInterval(0.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rl.IsKeyPressed(rl.KeyUp) {
|
||||||
|
rotated := gp.shape.RotateCW()
|
||||||
|
if !game.CheckShapeCollision(gp.shape_pos, &rotated, &gp.grid) {
|
||||||
|
gp.shape = rotated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if gp.moveTimer.UpdateReset(delta) && (rl.IsKeyDown(rl.KeyLeft) || rl.IsKeyDown(rl.KeyRight)) {
|
||||||
|
new_pos := gp.shape_pos
|
||||||
|
if rl.IsKeyDown(rl.KeyLeft) {
|
||||||
|
new_pos.X -= 1
|
||||||
|
} else {
|
||||||
|
new_pos.X += 1
|
||||||
|
}
|
||||||
|
if !game.CheckShapeCollision(new_pos, &gp.shape, &gp.grid) {
|
||||||
|
gp.shape_pos.X = new_pos.X
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if gp.dropTimer.UpdateReset(delta) {
|
||||||
|
new_pos := gp.shape_pos
|
||||||
|
new_pos.Y += 1
|
||||||
|
|
||||||
|
// Update position if it does not collide
|
||||||
|
if game.CheckShapeCollision(new_pos, &gp.shape, &gp.grid) {
|
||||||
|
gp.LockShape()
|
||||||
|
num_rows := gp.grid.ClearFullRows()
|
||||||
|
if num_rows > 0 {
|
||||||
|
audio.Play(assets.SFX_ROW_CLEARED)
|
||||||
|
gp.score.Lines(num_rows)
|
||||||
|
}
|
||||||
|
gp.SpawnShape()
|
||||||
|
} else {
|
||||||
|
gp.shape_pos = new_pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gp GamePlay) Render() {
|
||||||
|
render.Begin(gp.r.Theme.GridBackground)
|
||||||
|
gp.r.DrawGrid(rl.NewVector2(25, 25), gp.grid)
|
||||||
|
draw.DrawShape(rl.NewVector2(25, 25), gp.shape_pos, gp.shape)
|
||||||
|
gp.r.DrawFrame(rl.RectangleInt32{X: 400, Y: 25, Width: 250, Height: 100})
|
||||||
|
gp.r.DrawHeaderText(410, 30, "Score")
|
||||||
|
gp.r.DrawText(410, 65, fmt.Sprintf("%.7d", gp.score))
|
||||||
|
gp.r.DrawFrame(rl.RectangleInt32{X: 400, Y: 150, Width: 250, Height: 200})
|
||||||
|
gp.r.DrawHeaderText(410, 155, "Next")
|
||||||
|
draw.DrawShape(rl.NewVector2(450, 150), core.NewVec2[int8](1, 3), gp.nextShape)
|
||||||
|
render.End()
|
||||||
|
}
|
||||||
119
main.go
119
main.go
|
|
@ -1,120 +1,17 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"image/color"
|
|
||||||
|
|
||||||
"tetris/assets"
|
"tetris/assets"
|
||||||
"tetris/engine/audio"
|
"tetris/engine/audio"
|
||||||
"tetris/engine/core"
|
|
||||||
"tetris/engine/graphics"
|
"tetris/engine/graphics"
|
||||||
"tetris/engine/render"
|
"tetris/engine/render"
|
||||||
"tetris/game"
|
"tetris/game/state/handlers"
|
||||||
"tetris/game/draw"
|
"tetris/game/state/machine"
|
||||||
|
|
||||||
rl "github.com/gen2brain/raylib-go/raylib"
|
rl "github.com/gen2brain/raylib-go/raylib"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
shape game.Shape
|
|
||||||
shape_pos core.Vec2i8
|
|
||||||
score game.Score
|
|
||||||
dropTimer = core.NewIntervalTimer(0.3)
|
|
||||||
moveTimer = core.NewIntervalTimer(0.1)
|
|
||||||
grid = game.Grid{}
|
|
||||||
nextShape = game.NewShape(game.SHAPE_O)
|
|
||||||
shapeQueue = game.NewShapeQueue()
|
|
||||||
|
|
||||||
r = draw.Renderer{
|
|
||||||
Theme: &draw.Theme{
|
|
||||||
FrameBG: color.RGBA{R: 30, G: 30, B: 46, A: 255},
|
|
||||||
FrameBorder: color.RGBA{R: 242, G: 205, B: 205, A: 255},
|
|
||||||
TextHeader: color.RGBA{R: 242, G: 205, B: 205, A: 255},
|
|
||||||
Text: color.RGBA{R: 205, G: 214, B: 244, A: 255},
|
|
||||||
GridBackground: color.RGBA{R: 17, G: 17, B: 27, A: 255},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func SpawnShape() {
|
|
||||||
shape = nextShape
|
|
||||||
nextShape = shapeQueue.Next()
|
|
||||||
shape_pos = core.Vec2i8{X: 4, Y: 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func LockShape() {
|
|
||||||
audio.Play(assets.SFX_SHAPE_LOCKED)
|
|
||||||
|
|
||||||
for _, block := range shape.Coordinates() {
|
|
||||||
block = shape_pos.Add(block)
|
|
||||||
// Check bounds
|
|
||||||
if block.X < 0 || block.X > int8(grid.Width()) || block.Y < 0 || block.Y > int8(grid.Height()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
grid.Set(byte(block.X), byte(block.Y), shape.GetBlock())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Update(delta float32) {
|
|
||||||
if rl.IsKeyPressed(rl.KeyDown) {
|
|
||||||
dropTimer.SetInterval(0.05)
|
|
||||||
} else if rl.IsKeyReleased(rl.KeyDown) {
|
|
||||||
dropTimer.SetInterval(0.3)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rl.IsKeyPressed(rl.KeyUp) {
|
|
||||||
rotated := shape.RotateCW()
|
|
||||||
if !game.CheckShapeCollision(shape_pos, &rotated, &grid) {
|
|
||||||
shape = rotated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if moveTimer.UpdateReset(delta) && (rl.IsKeyDown(rl.KeyLeft) || rl.IsKeyDown(rl.KeyRight)) {
|
|
||||||
new_pos := shape_pos
|
|
||||||
if rl.IsKeyDown(rl.KeyLeft) {
|
|
||||||
new_pos.X -= 1
|
|
||||||
} else {
|
|
||||||
new_pos.X += 1
|
|
||||||
}
|
|
||||||
if !game.CheckShapeCollision(new_pos, &shape, &grid) {
|
|
||||||
shape_pos.X = new_pos.X
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if dropTimer.UpdateReset(delta) {
|
|
||||||
new_pos := shape_pos
|
|
||||||
new_pos.Y += 1
|
|
||||||
|
|
||||||
// Update position if it does not collide
|
|
||||||
if game.CheckShapeCollision(new_pos, &shape, &grid) {
|
|
||||||
LockShape()
|
|
||||||
num_rows := grid.ClearFullRows()
|
|
||||||
if num_rows > 0 {
|
|
||||||
audio.Play(assets.SFX_ROW_CLEARED)
|
|
||||||
score.Lines(num_rows)
|
|
||||||
}
|
|
||||||
SpawnShape()
|
|
||||||
} else {
|
|
||||||
shape_pos = new_pos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Render() {
|
|
||||||
render.Begin(r.Theme.GridBackground)
|
|
||||||
r.DrawGrid(rl.NewVector2(25, 25), grid)
|
|
||||||
draw.DrawShape(rl.NewVector2(25, 25), shape_pos, shape)
|
|
||||||
r.DrawFrame(rl.RectangleInt32{X: 400, Y: 25, Width: 250, Height: 100})
|
|
||||||
r.DrawHeaderText(410, 30, "Score")
|
|
||||||
r.DrawText(410, 65, fmt.Sprintf("%.7d", score))
|
|
||||||
r.DrawFrame(rl.RectangleInt32{X: 400, Y: 150, Width: 250, Height: 200})
|
|
||||||
r.DrawHeaderText(410, 155, "Next")
|
|
||||||
draw.DrawShape(rl.NewVector2(450, 150), core.NewVec2[int8](1, 3), nextShape)
|
|
||||||
render.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Set random blocks to test
|
|
||||||
render.Init(render.Config{
|
render.Init(render.Config{
|
||||||
Title: "Tetris",
|
Title: "Tetris",
|
||||||
WindowWidth: 685,
|
WindowWidth: 685,
|
||||||
|
|
@ -136,13 +33,15 @@ func main() {
|
||||||
render.SetTexture(texture)
|
render.SetTexture(texture)
|
||||||
render.SetFont(&assets.Font)
|
render.SetFont(&assets.Font)
|
||||||
|
|
||||||
nextShape = shapeQueue.Next()
|
// Setup state machine.
|
||||||
|
fsm := machine.New()
|
||||||
SpawnShape()
|
fsm.Register("gameplay", handlers.NewGamePlay())
|
||||||
|
fsm.Start("gameplay")
|
||||||
|
|
||||||
|
// Enter game loop
|
||||||
for !rl.WindowShouldClose() {
|
for !rl.WindowShouldClose() {
|
||||||
audio.Update()
|
audio.Update()
|
||||||
Update(rl.GetFrameTime())
|
fsm.Update(rl.GetFrameTime())
|
||||||
Render()
|
fsm.Render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue