feat: add tetris shell (Tetris game TUI)
Full-screen Tetris game using Bubbletea with title screen, ghost piece, lock delay, NES-style scoring, configurable difficulty (easy/normal/hard), and honeypot event logging. Bumps version to 0.17.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
101
internal/shell/tetris/data.go
Normal file
101
internal/shell/tetris/data.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package tetris
|
||||
|
||||
import "github.com/charmbracelet/lipgloss"
|
||||
|
||||
// pieceType identifies a tetromino (0–6).
|
||||
type pieceType int
|
||||
|
||||
const (
|
||||
pieceI pieceType = iota
|
||||
pieceO
|
||||
pieceT
|
||||
pieceS
|
||||
pieceZ
|
||||
pieceJ
|
||||
pieceL
|
||||
)
|
||||
|
||||
const numPieceTypes = 7
|
||||
|
||||
// Standard Tetris colors.
|
||||
var pieceColors = [numPieceTypes]lipgloss.Color{
|
||||
lipgloss.Color("#00FFFF"), // I — cyan
|
||||
lipgloss.Color("#FFFF00"), // O — yellow
|
||||
lipgloss.Color("#AA00FF"), // T — purple
|
||||
lipgloss.Color("#00FF00"), // S — green
|
||||
lipgloss.Color("#FF0000"), // Z — red
|
||||
lipgloss.Color("#0000FF"), // J — blue
|
||||
lipgloss.Color("#FF8800"), // L — orange
|
||||
}
|
||||
|
||||
// Each piece has 4 rotations, each rotation is a list of (row, col) offsets
|
||||
// relative to the piece origin.
|
||||
type rotation [4][2]int
|
||||
|
||||
var pieces = [numPieceTypes][4]rotation{
|
||||
// I
|
||||
{
|
||||
{[2]int{0, 0}, [2]int{0, 1}, [2]int{0, 2}, [2]int{0, 3}},
|
||||
{[2]int{0, 0}, [2]int{1, 0}, [2]int{2, 0}, [2]int{3, 0}},
|
||||
{[2]int{0, 0}, [2]int{0, 1}, [2]int{0, 2}, [2]int{0, 3}},
|
||||
{[2]int{0, 0}, [2]int{1, 0}, [2]int{2, 0}, [2]int{3, 0}},
|
||||
},
|
||||
// O
|
||||
{
|
||||
{[2]int{0, 0}, [2]int{0, 1}, [2]int{1, 0}, [2]int{1, 1}},
|
||||
{[2]int{0, 0}, [2]int{0, 1}, [2]int{1, 0}, [2]int{1, 1}},
|
||||
{[2]int{0, 0}, [2]int{0, 1}, [2]int{1, 0}, [2]int{1, 1}},
|
||||
{[2]int{0, 0}, [2]int{0, 1}, [2]int{1, 0}, [2]int{1, 1}},
|
||||
},
|
||||
// T
|
||||
{
|
||||
{[2]int{0, 0}, [2]int{0, 1}, [2]int{0, 2}, [2]int{1, 1}},
|
||||
{[2]int{0, 0}, [2]int{1, 0}, [2]int{2, 0}, [2]int{1, 1}},
|
||||
{[2]int{0, 1}, [2]int{1, 0}, [2]int{1, 1}, [2]int{1, 2}},
|
||||
{[2]int{0, 1}, [2]int{1, 0}, [2]int{1, 1}, [2]int{2, 1}},
|
||||
},
|
||||
// S
|
||||
{
|
||||
{[2]int{0, 1}, [2]int{0, 2}, [2]int{1, 0}, [2]int{1, 1}},
|
||||
{[2]int{0, 0}, [2]int{1, 0}, [2]int{1, 1}, [2]int{2, 1}},
|
||||
{[2]int{0, 1}, [2]int{0, 2}, [2]int{1, 0}, [2]int{1, 1}},
|
||||
{[2]int{0, 0}, [2]int{1, 0}, [2]int{1, 1}, [2]int{2, 1}},
|
||||
},
|
||||
// Z
|
||||
{
|
||||
{[2]int{0, 0}, [2]int{0, 1}, [2]int{1, 1}, [2]int{1, 2}},
|
||||
{[2]int{0, 1}, [2]int{1, 0}, [2]int{1, 1}, [2]int{2, 0}},
|
||||
{[2]int{0, 0}, [2]int{0, 1}, [2]int{1, 1}, [2]int{1, 2}},
|
||||
{[2]int{0, 1}, [2]int{1, 0}, [2]int{1, 1}, [2]int{2, 0}},
|
||||
},
|
||||
// J
|
||||
{
|
||||
{[2]int{0, 0}, [2]int{1, 0}, [2]int{1, 1}, [2]int{1, 2}},
|
||||
{[2]int{0, 0}, [2]int{0, 1}, [2]int{1, 0}, [2]int{2, 0}},
|
||||
{[2]int{0, 0}, [2]int{0, 1}, [2]int{0, 2}, [2]int{1, 2}},
|
||||
{[2]int{0, 0}, [2]int{1, 0}, [2]int{2, 0}, [2]int{2, 1}},
|
||||
},
|
||||
// L
|
||||
{
|
||||
{[2]int{0, 2}, [2]int{1, 0}, [2]int{1, 1}, [2]int{1, 2}},
|
||||
{[2]int{0, 0}, [2]int{1, 0}, [2]int{2, 0}, [2]int{2, 1}},
|
||||
{[2]int{0, 0}, [2]int{0, 1}, [2]int{0, 2}, [2]int{1, 0}},
|
||||
{[2]int{0, 0}, [2]int{0, 1}, [2]int{1, 1}, [2]int{2, 1}},
|
||||
},
|
||||
}
|
||||
|
||||
// spawnCol returns the starting column for a piece, centering it on the board.
|
||||
func spawnCol(pt pieceType, rot int) int {
|
||||
shape := pieces[pt][rot]
|
||||
minC, maxC := shape[0][1], shape[0][1]
|
||||
for _, off := range shape {
|
||||
if off[1] < minC {
|
||||
minC = off[1]
|
||||
}
|
||||
if off[1] > maxC {
|
||||
maxC = off[1]
|
||||
}
|
||||
}
|
||||
width := maxC - minC + 1
|
||||
return (boardCols - width) / 2
|
||||
}
|
||||
Reference in New Issue
Block a user