logo

I code dreams

gotris

Download
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 {}
}