1
0
Fork 0

Compare commits

..

No commits in common. "main" and "1.2.0" have entirely different histories.
main ... 1.2.0

14 changed files with 19 additions and 170 deletions

View file

@ -65,8 +65,7 @@ go build tetris.go
## Roadmap
- [x] Line clear animations
- [x] ghost piece
- [ ] Hard drop
- [ ] Hard drop and ghost piece
- [ ] Level/speed progression and pause
- [ ] High score persistence
- [ ] Custom key bindings

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

View file

@ -1,7 +1,7 @@
package graphics
import rl "github.com/gen2brain/raylib-go/raylib"
func LoadImageFromMemory(typ string, data []byte) *rl.Image {
return rl.LoadImageFromMemory(typ, data, int32(len(data)))
}
package graphics
import rl "github.com/gen2brain/raylib-go/raylib"
func LoadImageFromMemory(typ string, data []byte) *rl.Image {
return rl.LoadImageFromMemory(typ, data, int32(len(data)))
}

View file

@ -16,22 +16,12 @@ const (
BLOCK_ORANGE
BLOCK_MAGENTA
BLOCK_TEAL
BLOCK_GHOST
)
func (c Block) Tile() graphics.Tile {
if c >= 9 {
if c >= 8 {
c = BLOCK_EMPTY
}
if c == BLOCK_GHOST {
return graphics.Tile{
Size: assets.BLOCK_TILE_SIZE,
X: byte(7),
Y: byte(1),
}
}
return graphics.Tile{
Size: assets.BLOCK_TILE_SIZE,
X: byte(c),

View file

@ -9,13 +9,11 @@ import (
type Config struct {
fd *os.File
SoundVolume byte `json:"sound_volume"`
GhostPiece bool `json:"ghost_piece"`
}
func DefaultConfig(fd *os.File) *Config {
cfg := Config{
SoundVolume: 255,
GhostPiece: false,
}
cfg.fd = fd
return &cfg

View file

@ -14,10 +14,8 @@ type Shape interface {
}
func DrawShape(offset rl.Vector2, pos core.Vec2i8, shape Shape) {
DrawShapeTile(offset, pos, shape, shape.Tile())
}
tile := shape.Tile()
func DrawShapeTile(offset rl.Vector2, pos core.Vec2i8, shape Shape, tile graphics.Tile) {
for _, coord := range shape.Coordinates() {
block := pos.Add(coord)
grid.DrawBlock(offset, byte(block.X), byte(block.Y), tile)

View file

@ -1,39 +0,0 @@
package game
import (
"tetris/engine/core"
"tetris/game/draw"
rl "github.com/gen2brain/raylib-go/raylib"
)
type GhostShape struct {
posY int8 // only need Y coordinate
}
func (gs *GhostShape) UpdatePos(shape *Shape, pos core.Vec2i8, grid *Grid) {
for gs.posY = pos.Y; pos.Y <= int8(grid.Height()); pos.Y++ {
if CheckShapeCollision(pos, shape, grid) {
gs.posY = pos.Y - 1
break
}
}
}
func (gs *GhostShape) Draw(offset rl.Vector2, shape *Shape, pos core.Vec2i8) {
draw.DrawShapeTile(offset, core.NewVec2(pos.X, gs.posY), shape, BLOCK_GHOST.Tile())
}
func UpdateGhostShapeY(shape *Shape, pos core.Vec2i8, grid *Grid) int8 {
for ; pos.Y <= int8(grid.Height()); pos.Y++ {
if CheckShapeCollision(pos, shape, grid) {
pos.Y = pos.Y - 1
break
}
}
return pos.Y
}
func DrawGhost(offset rl.Vector2, shape *Shape, pos core.Vec2i8, ghost_y int8) {
draw.DrawShapeTile(offset, core.NewVec2(pos.X, ghost_y), shape, BLOCK_GHOST.Tile())
}

View file

@ -32,15 +32,15 @@ func (lca *LineClearAnimation) SetLines(rows []byte) {
}
}
func (lca *LineClearAnimation) Update(grid *Grid) bool {
func (lca *LineClearAnimation) Update(grid *Grid) {
if lca.Completed() {
return false
return
}
if lca.leftIndex < 0 || lca.rightIndex > GRID_WIDTH {
grid.MoveLinesDown(lca.lines...)
lca.Reset()
return true
return
}
for _, lineIndex := range lca.lines {
@ -50,8 +50,6 @@ func (lca *LineClearAnimation) Update(grid *Grid) bool {
lca.leftIndex--
lca.rightIndex++
return false
}
func (lca LineClearAnimation) Completed() bool {

View file

@ -5,7 +5,7 @@ package game
const SCORE_MAX Score = 9999999
// Score is a running total of points earned.
// It's a distinct type (over uint32) to prevent mixing raw integers with scores.
// It’s a distinct type (over uint32) to prevent mixing raw integers with scores.
type Score uint32
// Lines adds points for clearing a number of lines at once.

View file

@ -58,10 +58,6 @@ func (s Shape) Coordinates() [4]core.Vec2i8 {
}
func (s Shape) RotateCW() Shape {
if s.typ == SHAPE_O {
return s
}
rotated := [3]core.Vec2i8{}
for i, block := range s.coordinates {
@ -75,10 +71,6 @@ func (s Shape) RotateCW() Shape {
}
func (s Shape) RotateCCW() Shape {
if s.typ == SHAPE_O {
return s
}
rotated := [3]core.Vec2i8{}
for i, block := range s.coordinates {

View file

@ -18,8 +18,6 @@ import (
type GamePlay struct {
shape game.Shape
shape_pos core.Vec2i8
ghost game.GhostShape
ghost_needs_update bool
score game.Score
dropTimer core.IntervalTimer
moveTimer core.IntervalTimer
@ -68,7 +66,6 @@ func (gp *GamePlay) SpawnShape() {
gp.shape = gp.nextShape
gp.nextShape = gp.shapeQueue.Next()
gp.shape_pos = core.Vec2i8{X: 4, Y: 0}
gp.ghost_needs_update = true
}
func (gp *GamePlay) LockShape() {
@ -93,9 +90,7 @@ func (gp *GamePlay) Update(fsm state.Transitioner, delta float32) {
if !gp.lineClearAnimation.Completed() {
if gp.lineClearTimer.UpdateReset(delta) {
if gp.lineClearAnimation.Update(&gp.grid) {
gp.ghost_needs_update = true
}
gp.lineClearAnimation.Update(&gp.grid)
}
return
}
@ -104,7 +99,6 @@ func (gp *GamePlay) Update(fsm state.Transitioner, delta float32) {
rotated := gp.shape.RotateCW()
if !game.CheckShapeCollision(gp.shape_pos, &rotated, &gp.grid) {
gp.shape = rotated
gp.ghost_needs_update = true
}
}
@ -117,7 +111,6 @@ func (gp *GamePlay) Update(fsm state.Transitioner, delta float32) {
}
if !game.CheckShapeCollision(new_pos, &gp.shape, &gp.grid) {
gp.shape_pos.X = new_pos.X
gp.ghost_needs_update = true
}
}
@ -143,24 +136,12 @@ func (gp *GamePlay) Update(fsm state.Transitioner, delta float32) {
gp.shape_pos = new_pos
}
}
if gp.ghost_needs_update {
if game.Config.GhostPiece {
gp.ghost.UpdatePos(&gp.shape, gp.shape_pos, &gp.grid)
}
gp.ghost_needs_update = false
}
}
func (gp GamePlay) Render() {
offset := rl.NewVector2(25, 25)
render.Begin(gp.r.Theme.GridBackground)
gp.r.DrawGrid(offset, gp.grid)
draw.DrawShape(offset, gp.shape_pos, gp.shape)
if game.Config.GhostPiece && gp.lineClearAnimation.Completed() {
gp.ghost.Draw(offset, &gp.shape, gp.shape_pos)
}
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))

View file

@ -16,9 +16,8 @@ import (
)
type OptionsMenu struct {
sndVolume *widgets.Slider
ghostBlock *widgets.Checkbox
list *layouts.ListBox
sndVolume *widgets.Slider
list *layouts.ListBox
}
func soundVolumeChanged(widget *widgets.Slider) {
@ -34,15 +33,10 @@ func soundVolumeChanged(widget *widgets.Slider) {
func NewOptionsMenu() *OptionsMenu {
sndVolume := widgets.NewSlider(0, 255, 10).WithOnChange(soundVolumeChanged)
ghostPiece := widgets.NewCheckbox(game.Config.GhostPiece, func(value bool) {
game.Config.GhostPiece = value
game.Config.Save()
})
return &OptionsMenu{
sndVolume: sndVolume,
list: layouts.NewListBox([]ui.InputWidget{
layouts.NewInputControl(widgets.NewLabel("Sound Volume", 16), sndVolume),
layouts.NewInputControl(widgets.NewLabel("Show ghost piece", 16), ghostPiece),
}).Spacing(10).OnSelect(uievents.MenuSelect),
}
}

View file

@ -1,62 +0,0 @@
package widgets
import (
"tetris/engine/core"
"tetris/engine/input"
"tetris/engine/render"
"tetris/game/ui/base"
rl "github.com/gen2brain/raylib-go/raylib"
)
type CheckboxAction func(value bool)
type Checkbox struct {
*base.WithFocus
action CheckboxAction
value bool
}
func NewCheckbox(value bool, action CheckboxAction) *Checkbox {
return &Checkbox{
WithFocus: &base.WithFocus{},
value: value,
action: action,
}
}
func (cb Checkbox) Size() core.Vec2i {
return core.NewVec2(24, 24)
}
func (cb *Checkbox) HandleInput() {
if input.KeyPressedWithRepeat(rl.KeyEnter) {
cb.value = !cb.value
cb.action(cb.value)
}
}
func (cb Checkbox) Draw(x, y int32) {
size := cb.Size()
space := int32(2)
color := rl.White
if cb.HasFocus() {
color = rl.Red
}
render.DrawRectOutlineBorder(rl.RectangleInt32{
X: x,
Y: y,
Width: int32(size.X),
Height: int32(size.Y),
}, 4, color)
if cb.value {
render.DrawRectBorder(rl.RectangleInt32{
X: x + space,
Y: y + space,
Width: int32(size.X) - (space * 2),
Height: int32(size.Y) - (space * 2),
}, color, 0, rl.Blank)
}
}