Update Tetris tutorials
This commit is contained in:
parent
0896e453c9
commit
09858990a0
|
@ -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).
|
||||
|
|
|
@ -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).
|
||||
|
|
Loading…
Reference in New Issue