Update Tetris tutorials

This commit is contained in:
Trevor Slocum 2020-08-29 09:42:25 -07:00
parent 0896e453c9
commit 09858990a0
2 changed files with 111 additions and 10 deletions

View File

@ -62,6 +62,7 @@ so we will store minos as slices of points.
*Example coordinate positions in 10x10 playfield*
{{< highlight go >}}
// Point is an X,Y coordinate.
type Point struct {
X, Y int
}
@ -71,6 +72,7 @@ func (p Point) Rotate180() Point { return Point{-p.X, -p.Y} }
func (p Point) Rotate270() Point { return Point{-p.Y, p.X} }
func (p Point) Reflect() Point { return Point{-p.X, p.Y} }
// Mino is a set of Points.
type Mino []Point
var exampleMino = Mino{{0, 0}, {1, 0}, {2, 0}, {1, 1}} // T piece
@ -116,6 +118,7 @@ func (m Mino) String() string {
return b.String()
}
// Render returns a visual representation of a Mino.
func (m Mino) Render() string {
var (
w, h = m.Size()
@ -163,6 +166,7 @@ translated to `(0, 0), (1, 0), (2, 0), (1, 1)`:
*Translating a mino to `(0,0)`*
{{< highlight go >}}
// minCoords returns the lowest coordinate of a Mino.
func (m Mino) minCoords() (int, int) {
minx := m[0].X
miny := m[0].Y
@ -179,6 +183,7 @@ func (m Mino) minCoords() (int, int) {
return minx, miny
}
// Origin returns a translated Mino located at 0,0 and with positive coordinates only.
func (m Mino) Origin() Mino {
minx, miny := m.minCoords()
@ -205,6 +210,7 @@ XXX X
Flatten calculates the flattest side of a mino and returns a flattened mino.
{{< highlight go >}}
// Size returns the dimensions of a Mino.
func (m Mino) Size() (int, int) {
var x, y int
for _, p := range m {
@ -219,6 +225,7 @@ func (m Mino) Size() (int, int) {
return x + 1, y + 1
}
// Flatten calculates the flattest side of a Mino and returns a flattened Mino.
func (m Mino) Flatten() Mino {
var (
w, h = m.Size()
@ -281,6 +288,7 @@ XXX -> XX XXX XX
*Variations of a mino*
{{< highlight go >}}
// Variations returns the three other rotations of a Mino.
func (m Mino) Variations() []Mino {
v := make([]Mino, 3)
for i := 0; i < 3; i++ {
@ -300,6 +308,7 @@ func (m Mino) Variations() []Mino {
Canonical returns a flattened mino translated to `0,0`.
{{< highlight go >}}
// Canonical returns a flattened Mino translated to 0,0.
func (m Mino) Canonical() Mino {
var (
ms = m.Origin().String()
@ -339,6 +348,7 @@ X -> XX -> XXX XX -> XXXX XX XXX XXX XXX XX XX
Neighborhood returns the [Von Neumann neighborhood](https://en.wikipedia.org/wiki/Von_Neumann_neighborhood) of a point.
{{< highlight go >}}
//Neighborhood returns the Von Neumann neighborhood of a Point.
func (p Point) Neighborhood() []Point {
return []Point{
{p.X - 1, p.Y},
@ -351,6 +361,7 @@ func (p Point) Neighborhood() []Point {
NewPoints calculates the neighborhood of each point of a mino and returns only the new points.
{{< highlight go >}}
// Neighborhood returns the Von Neumann neighborhood of a Point.
func (m Mino) HasPoint(p Point) bool {
for _, mp := range m {
if mp == p {
@ -361,6 +372,7 @@ func (m Mino) HasPoint(p Point) bool {
return false
}
// NewPoints calculates the neighborhood of each Point of a Mino and returns only the new Points.
func (m Mino) NewPoints() []Point {
var newPoints []Point
@ -381,6 +393,7 @@ func (m Mino) NewPoints() []Point {
NewMinos returns a new mino for every new neighborhood point of a supplied mino.
{{< highlight go >}}
// NewMinos returns a new Mino for every new neighborhood Point of a supplied Mino.
func (m Mino) NewMinos() []Mino {
points := m.NewPoints()
@ -401,10 +414,12 @@ We generate minos for the rank below the requested rank and iterate over the
variations of each mino, saving and returning all unique variations.
{{< highlight go >}}
// monomino returns a single-block Mino.
func monomino() Mino {
return Mino{{0, 0}}
}
// Generate procedurally generates Minos of a supplied rank.
func Generate(rank int) ([]Mino, error) {
switch {
case rank < 0:
@ -443,4 +458,4 @@ func Generate(rank int) ([]Mino, error) {
# Up next: The Matrix
In [part two](/post/tetris-2/) we will create a [matrix](https://tetris.wiki/Playfield) to hold our minos and implement [SRS rotation](https://tetris.wiki/SRS).
In [part two](/post/tetris-2/) we create a [Matrix](https://tetris.wiki/Playfield) to hold our Minos and implement [SRS rotation](https://tetris.wiki/SRS).

View File

@ -65,6 +65,7 @@ A block is an integer representing the contents of a single X-Y Point (see part
We will use four of these blocks to build each tetromino.
{{< highlight go >}}
// Block represents the content of a single location within a Matrix.
type Block int
const (
@ -86,6 +87,7 @@ The matrix has a width, height and buffer height.
The buffer is additional space above the visible playfield.
{{< highlight go >}}
// Matrix is a 2D grid of Blocks.
type Matrix struct {
W int // Width
H int // Height
@ -94,12 +96,13 @@ type Matrix struct {
M []Block // Contents
}
func NewMatrix(w int, h int, b int) *Matrix {
// NewMatrix returns a new Matrix.
func NewMatrix(width int, height int, buffer int) *Matrix {
m := Matrix{
W: w,
H: h,
B: b,
M: make([]Block, w*(h+b)),
W: width,
H: height,
B: buffer,
M: make([]Block, width*(height+buffer)),
}
return &m
@ -109,14 +112,16 @@ func NewMatrix(w int, h int, b int) *Matrix {
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.
{{< highlight go >}}
func I(x int, y int, w int) int {
if x < 0 || x >= w || y < 0 {
// 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 * w) + x
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)
@ -126,3 +131,84 @@ func (m *Matrix) Block(x int, y int) Block {
return m.M[index]
}
{{< / highlight >}}
To display the Matrix, a `Render` function must be implemented as we did in part 1.
{{< highlight go >}}
// 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()
}
{{< / highlight >}}
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.
{{< highlight go >}}
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())
}
{{< / highlight >}}
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](https://gitlab.com/tslocum/cview).
This will be covered in part three.
## Rotation
*Coming soon*
See [Piece](https://gitlab.com/tslocum/netris/-/blob/master/pkg/mino/piece.go).
# Up next: The User Interface
In part three we will implement a terminal-based user interface with support for colors using [cview](https://gitlab.com/tslocum/cview).