package main import ( "math/rand" "time" ) const WIDTH = 10 const HEIGHT = 24 type Tetris struct { bucket [][]int block, nextBlock *Block ticker *time.Ticker score, lines int // player variables rotation int column, row int renderer Renderer rand *rand.Rand } func NewTetris(renderer Renderer) *Tetris { t := Tetris{} t.rand = rand.New(rand.NewSource(time.Now().UnixNano())) t.block = t.randomBlock() t.nextBlock = t.randomBlock() t.bucket = initBucket() t.renderer = renderer var speed = time.Duration(650-t.lines) * time.Millisecond t.ticker = time.NewTicker(speed) t.KeyboardHandling() return &t } func (t *Tetris) NewGame() { t.score = 0 t.lines = 0 t.bucket = initBucket() t.newBlock() } func (t *Tetris) newBlock() { // clear preview t.draw(WIDTH, 0, t.nextBlock, 0, "") clearedLines := t.sweepBucket() t.redrawBucket() // stats bonus := []int{0, 100, 300, 700, 1500} t.lines += clearedLines t.score += bonus[clearedLines] t.renderer.Score(t.score, t.lines) // show preview t.block = t.nextBlock t.nextBlock = t.randomBlock() t.draw(WIDTH, 0, t.nextBlock, 0, t.nextBlock.Color) var speed = time.Duration(650-t.lines) * time.Millisecond t.ticker.Stop() t.ticker = time.NewTicker(speed) go func() { for { select { // case <-gameover: // break case <-t.ticker.C: t.fall() } } }() t.column = 3 t.row = 0 t.rotation = 0 if !t.move(0, 0, t.rotation) { t.ticker.Stop() t.gameOver() } } func (t *Tetris) move(deltaColumn, deltaRow, newRotation int) bool { canMove := t.testBlock(deltaColumn, deltaRow, t.block, newRotation) if canMove { // clear previous block t.draw(t.column, t.row, t.block, t.rotation, "") t.column += deltaColumn t.row += deltaRow t.rotation = newRotation // draw block t.draw(t.column, t.row, t.block, t.rotation, t.block.Color) } return canMove } func indexOf(blocks *[]Block, block *Block) int { for index, b := range *blocks { if b.Shape == block.Shape { return index } } return -1 } func (t *Tetris) fall() bool { if !t.move(0, 1, t.rotation) { // block lands t.setBuffer(indexOf(&blocks, t.block), t.column, t.row, t.block, t.rotation) t.newBlock() return false } return true } func (t *Tetris) drop() { // TODO drop hangs if done like this, juist non-animated for loop for now, wait until Go 1.15 for ticker.Restart() // t.ticker.Stop() // t.ticker = time.NewTicker(10 * time.Millisecond) for t.fall() { } t.score += (HEIGHT - t.row) t.renderer.Score(t.score, t.lines) } func (t *Tetris) draw(column, row int, block *Block, rotation int, color string) { coords := t.coords(block, rotation, column, row) for _, coord := range coords { t.renderer.Draw(coord.x, coord.y, color) } } func initBucket() [][]int { bucket := [][]int{} for y := 0; y < HEIGHT; y++ { row := []int{} for x := 0; x < WIDTH; x++ { row = append(row, -1) } bucket = append(bucket, row) } return bucket } func (t *Tetris) setBuffer(set, column, row int, block *Block, rotation int) { for _, coord := range t.coords(block, rotation, column, row) { t.bucket[coord.y][coord.x] = set } } // testBlocks tests if a block can be placed and returns true if so, false otherwise func (t *Tetris) testBlock(deltaColumn, deltaRow int, block *Block, rotation int) bool { for _, coord := range t.coords(block, rotation, t.column+deltaColumn, t.row+deltaRow) { if coord.x < 0 || coord.x >= WIDTH || coord.y >= HEIGHT || t.bucket[coord.y][coord.x] >= 0 { return false } } return true } func (t *Tetris) coords(block *Block, rotation, column, row int) []Coord { coords := []Coord{} for _, coord := range block.Coords[rotation] { coords = append(coords, Coord{coord.x + column, coord.y + row}) } return coords } func (t *Tetris) randomBlock() *Block { b := t.rand.Intn(len(blocks)) return &blocks[b] } func (t *Tetris) sweepBucket() int { linesCleared := 0 for rowIndex, row := range t.bucket { full := true for _, cell := range row { if cell < 0 { full = false break } } if full { t.bucket = append(t.bucket[:rowIndex], t.bucket[rowIndex+1:]...) row := []int{} for x := 0; x < WIDTH; x++ { row = append(row, -1) } t.bucket = append([][]int{row}, t.bucket...) linesCleared++ } } return linesCleared } func (t *Tetris) redrawBucket() { for y, row := range t.bucket { for x, cell := range row { color := "" if cell >= 0 { color = blocks[cell].Color } t.renderer.Draw(x, y, color) } } } func (t *Tetris) gameOver() { t.renderer.GameOver() t.NewGame() } func main() { renderer := NewBrowserRenderer() tetris := NewTetris(renderer) tetris.NewGame() select {} }