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 lineClearTimer core.IntervalTimer grid game.Grid nextShape game.Shape shapeQueue *game.ShapeQueue r draw.Renderer lineClearAnimation game.LineClearAnimation } func NewGamePlay() *GamePlay { return &GamePlay{ dropTimer: core.NewIntervalTimer(0.3), moveTimer: core.NewIntervalTimer(0.1), lineClearTimer: core.NewIntervalTimer(0.05), 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.grid = game.Grid{} gp.score = 0 gp.dropTimer.SetInterval(0.3) gp.shapeQueue = game.NewShapeQueue() gp.nextShape = gp.shapeQueue.Next() gp.SpawnShape() gp.lineClearAnimation.Reset() audio.PlayLooped(assets.SFX_MUSIC) } func (GamePlay) Exit() { audio.Stop(assets.SFX_MUSIC) } 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 !gp.lineClearAnimation.Completed() { if gp.lineClearTimer.UpdateReset(delta) { gp.lineClearAnimation.Update(&gp.grid) } return } 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() gp.SpawnShape() lines := gp.grid.FullLines() if len(lines) > 0 { gp.lineClearAnimation.SetLines(lines) gp.score.Lines(byte(len(lines))) audio.Play(assets.SFX_ROW_CLEARED) } else { if game.CheckShapeCollision(gp.shape_pos, &gp.shape, &gp.grid) { fsm.Switch("gameover") } } } 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() }