Terminal-based Tetris - Part 2: The matrix
This is the second part of a series of tutorials on creating a terminal-based Tetris clone with Go.
Code for this tutorial is available on GitLab.
go get code.rocket9labs.com/tslocum/terminal-tetris-tutorial/part-2 # Download and install
~/go/bin/part-2 # Run
For a complete implementation of a Tetris clone in Go, see netris.
Disclaimer
Tetris is a registered trademark of the Tetris Holding, LLC.
Rocket Nine Labs is in no way affiliated with Tetris Holding, LLC.
Matrix
In this part of the series, we will learn what the matrix is and how we could implement it in Go.
The matrix is a playfield which holds tetrominos. It is typically 10 blocks wide and 20 blocks high.
Initially empty, tetrominos will appear just outside of view at the top of the matrix, and will fall into place one by one.
<! !> <! !>
<! !> <! !>
<! !> <! !>
<! !> <! !>
<! 3,15 !> <! X !>
<! !> <! !>
<! !> <! !>
<! !> <! !>
<! !> <! !>
<! !> <! !>
<! !> <! !>
<! !> <! !>
<! 1,7 !> <! X !>
<! !> <! !>
<! 4,5 !> <! X !>
<! !> <! !>
<! !> <! !>
<! 5,2 !> <! X !>
<! !> <! !>
<! 2,0 !> <! X !>
<!==========!> <!==========!>
\/\/\/\/\/ \/\/\/\/\/
Example coordinate positions and blocks in 10x20 playfield
Matrix data model
A block is an integer representing the contents of a single X-Y Point (see part 1) on the matrix. We will use four of these blocks to build each tetromino.
// Block represents the content of a single location within a Matrix.
type Block int
const (
BlockNone Block = iota
BlockSolidBlue
BlockSolidCyan
BlockSolidRed
BlockSolidYellow
BlockSolidMagenta
BlockSolidGreen
BlockSolidOrange
)
The matrix will be stored as a slice of blocks. The zero-value of Block is a blank space.
The matrix has a width, height and buffer height. The buffer is additional space above the visible playfield.
// Matrix is a 2D grid of Blocks.
type Matrix struct {
W int // Width
H int // Height
B int // Buffer height
M []Block // Contents
}
// NewMatrix returns a new Matrix.
func NewMatrix(width int, height int, buffer int) *Matrix {
m := Matrix{
W: width,
H: height,
B: buffer,
M: make([]Block, width*(height+buffer)),
}
return &m
}
To retrieve the contents of a point, we calculate its index by multiplying the Y coordinate with the matrix width and adding the X coordinate.
// I returns the slice index position of the specified coordinates.
func I(x int, y int, width int) int {
if x < 0 || x >= width || y < 0 {
log.Panicf("failed to retrieve index for %d,%d width %d: invalid coordinates", x, y, w)
}
return (y * width) + x
}
// Block returns the block at the specified coordinates, or BlockNone.
func (m *Matrix) Block(x int, y int) Block {
if y >= m.H+m.B {
log.Panicf("failed to retrieve block at %d,%d: invalid y coordinate", x, y)
}
index := I(x, y, m.W)
return m.M[index]
}
To display the Matrix, a Render
function must be implemented as we did in part 1.
// Render returns a visual representation of a Matrix.
func (m *Matrix) Render() string {
var b strings.Builder
for y := m.H - 1; y >= 0; y-- {
b.WriteRune('<')
b.WriteRune('!')
for x := 0; x < m.W; x++ {
block := m.Block(x, y)
if block != BlockNone {
b.WriteRune('X')
} else {
b.WriteRune(' ')
}
}
b.WriteRune('!')
b.WriteRune('>')
b.WriteRune('\n')
}
// Bottom border
b.WriteRune('<')
b.WriteRune('!')
for x := 0; x < m.W; x++ {
b.WriteRune('=')
}
b.WriteRune('!')
b.WriteRune('>')
b.WriteRune('\n')
b.WriteString(` \/\/\/\/\/`)
b.WriteRune('\n')
return b.String()
}
Let’s create a new Matrix and add some blocks to it. The resulting output should match the Matrix at the top of this tutorial.
func main() {
matrix := NewMatrix(10, 20, 20)
matrix.M[I(2, 0, matrix.W)] = BlockSolidBlue
matrix.M[I(5, 2, matrix.W)] = BlockSolidCyan
matrix.M[I(4, 5, matrix.W)] = BlockSolidRed
matrix.M[I(1, 7, matrix.W)] = BlockSolidYellow
matrix.M[I(3, 15, matrix.W)] = BlockSolidMagenta
fmt.Print(matrix.Render())
}
We are currently rendering the matrix as text without any color information. To properly support colors, and to allow more advanced interface elements, we will instead use a terminal-based user interface library, cview. This will be covered in part three.
Rotation
Coming soon
See Piece.
Up next: The User Interface
In part three we will implement a terminal-based user interface with support for colors using cview.