Update Tetris part 1

This commit is contained in:
Trevor Slocum 2020-01-18 08:48:50 -08:00
parent 470d1df32f
commit 481a343f09
4 changed files with 140 additions and 109 deletions

View File

@ -27,13 +27,13 @@ googleAnalytics = "" # Enable Google Analytics by entering your tracking id
mathjax = false # Enable MathJax
mathjaxPath = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.6/MathJax.js" # Specify MathJax path
mathjaxConfig = "TeX-AMS-MML_HTMLorMML" # Specify MathJax config
highlightColor = "#e22d30" # Override highlight color
highlightColor = "#0b5693" # Override highlight color
customCSS = ["css/custom.css"] # Include custom CSS files
customJS = ["js/custom.js"] # Include custom JS files
[Params.sidebar]
home = "right" # Configure layout for home page
list = "left" # Configure layout for list pages
list = "right" # Configure layout for list pages
single = false # Configure layout for single pages
# Enable widgets in given order
widgets = ["social", "recent"]

View File

@ -1,47 +1,41 @@
---
title: "Textris - Creating a terminal-based Tetris clone - Part 1: Pieces and playfield"
#date: 2019-11-26T01:42:18-07:00
title: "Terminal-based Tetris - Part 1: Procedural polyomino generation"
date: 2020-01-18T08:30:00-07:00
categories: [tutorial]
draft: true
aliases:
- /post/textris-1/
---
This is the first in a series of tutorials on creating a [terminal-based](https://en.wikipedia.org/wiki/Text-based_user_interface) [Tetris](https://en.wikipedia.org/wiki/Tetris) clone with [Go](https://golang.org).
This is the first part of a series of tutorials on creating a [terminal-based](https://en.wikipedia.org/wiki/Text-based_user_interface)
[Tetris](https://en.wikipedia.org/wiki/Tetris) clone with [Go](https://golang.org).
Game pieces (minos) are generated and added to a playfield (matrix).
For a complete implementation of a Tetris clone in Go, see [netris](https://git.sr.ht/~tslocum/netris).
## Disclaimer
# Disclaimer
Tetris is a registered trademark of the Tetris Holding, LLC.
Rocket Nine Labs is in no way affiliated with Tetris Holding, LLC.
## Contents
* [Minos](#minos)
* [Data model](#data-model)
* [Generation](#generation)
* [Comparing and sorting](#comparing-and-sorting)
* [Generating new minos](#generating-new-minos)
* [Matrix](#matrix)
* [Data model](#data-model)
# Minos
## Minos
Game pieces are called "minos" because they are [polyominos](https://en.wikipedia.org/wiki/Polyomino).
This tutorial focuses on the seven one-sided [terominos](https://en.wikipedia.org/wiki/Tetromino), where each piece has four blocks.
This tutorial series will focus on the seven one-sided [terominos](https://en.wikipedia.org/wiki/Tetromino),
where each piece has four blocks.
```
XX X X X XX XX
XXXX XX XXX XXX XXX XX XX
I O T J L S Z
```
The number of blocks a mino has is also known as its rank.
| I | O | T | J | L | S | Z |
| --- | --- | --- | --- | --- | --- | --- |
| <pre><br>████</pre> | <pre>██<br>██</pre> | <pre><br>███</pre> | <pre><br>███</pre> | <pre><br>███</pre> | <pre> ██<br>██</pre> | <pre>██<br> ██</pre> |
### Mino data model
## Data model
Tetris is played on an [X-Y grid](https://en.wikipedia.org/wiki/Cartesian_coordinate_system).
We will store Minos as slices of points.
Tetris is played on an [X-Y grid](https://en.wikipedia.org/wiki/Cartesian_coordinate_system),
so we will store minos as slices of points.
{{< highlight go >}}
type Point struct {
@ -55,19 +49,20 @@ func (p Point) Reflect() Point { return Point{-p.X, p.Y} }
type Mino []Point
var minoT = Mino{{0, 0}, {1, 0}, {2, 0}, {1, 1}}
var exampleMino = Mino{{0, 0}, {1, 0}, {2, 0}, {1, 1}} // T piece
{{< / highlight >}}
## Generation
### Generating minos
Instead of hard-coding each piece into our game, let's procedurally generate them.
This allows us to play with any size of mino.
### Comparing and sorting
### Sorting and comparing minos
To compare minos efficiently while generating, we will compare their string representation.
We will define a String method which sorts the coordinates before printing.
This allows us to compare duplicate minos just by checking their string values.
To compare minos efficiently while generating, we will define a String method
which sorts a mino's coordinates before printing them.
This will allow us to identify duplicate minos by comparing their string values.
{{< highlight go >}}
func (m Mino) Len() int { return len(m) }
@ -98,7 +93,11 @@ func (m Mino) String() string {
}
{{< / highlight >}}
Origin returns a translated mino located at 0,0 and with positive coordinates only.
Origin returns a translated mino located at `0,0` and with positive coordinates
only.
A mino with the coordinates `(-3, -1), (-2, -1), (-1, -1), (-2, 0)` would be
translated to `(0, 0), (1, 0), (2, 0), (1, 1)`.
{{< highlight go >}}
func (m Mino) minCoords() (int, int) {
@ -130,9 +129,10 @@ func (m Mino) Origin() Mino {
}
{{< / highlight >}}
Another transformation is applied not only to help identify duplicate minos, but also to retrieve their initial rotation, as [pieces should spawn flat-side down](https://tetris.wiki/Super_Rotation_System).
Another transformation is applied not only to help identify duplicate minos,
but also to retrieve their initial rotation, as [pieces should spawn flat-side down](https://tetris.wiki/Super_Rotation_System).
The flattest side is calculated and a flattened mino is returned.
Flatten calculates the flattest side of a mino and returns a flattened mino.
{{< highlight go >}}
func (m Mino) Flatten() Mino {
@ -178,9 +178,8 @@ func (m Mino) Flatten() Mino {
}
newMino := make(Mino, len(m))
copy(newMino, m)
for i := 0; i < len(m); i++ {
newMino[i] = rotateFunc(newMino[i])
newMino[i] = rotateFunc(m[i])
}
return newMino
@ -206,7 +205,7 @@ func (m Mino) Variations() []Mino {
}
{{< / highlight >}}
Canonical returns a flattened mino translated to 0,0.
Canonical returns a flattened mino translated to `0,0`.
{{< highlight go >}}
func (m Mino) Canonical() Mino {
@ -233,7 +232,10 @@ func (m Mino) Canonical() Mino {
}
{{< / highlight >}}
### Generating new minos
### Generating additional minos
Starting with a monomino (a mino with a single point: `0,0`), we will generate
additional minos by adding neighboring points.
Neighborhood returns the [Von Neumann neighborhood](https://en.wikipedia.org/wiki/Von_Neumann_neighborhood) of a point.
@ -292,8 +294,13 @@ func (m Mino) NewMinos() []Mino {
}
{{< / highlight >}}
### Generating unique minos
Generate procedurally generates minos of a supplied rank.
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 >}}
func Generate(rank int) ([]Mino, error) {
switch {
@ -335,73 +342,6 @@ func monomino() Mino {
}
{{< / highlight >}}
# Matrix
# Stay tuned...
The matrix is typically 10 blocks wide and 20 blocks high.
## Data model
A block is an integer representing the contents of a single X-Y coordinate on the matrix.
{{< highlight go >}}
type Block int
const (
BlockNone Block = iota
BlockSolidBlue
BlockSolidCyan
BlockSolidRed
BlockSolidYellow
BlockSolidMagenta
BlockSolidGreen
BlockSolidOrange
)
{{< / highlight >}}
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.
{{< highlight go >}}
type Matrix struct {
W int // Width
H in // Height
B int // Buffer height
M []Block // Contents
}
func NewMatrix(w int, h int, b int) *Matrix {
m := Matrix{
W: w,
H: h,
B: b,
M: make([]Block, w*(h+b)),
}
return &m
}
{{< / highlight >}}
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 {
log.Panicf("failed to retrieve index for %d,%d width %d: invalid coordinates", x, y, w)
}
return (y * w) + x
}
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]
}
{{< / highlight >}}
In part two we will create a [matrix](https://tetris.wiki/Playfield) to hold our minos and implement [SRS rotation](https://tetris.wiki/SRS).

89
content/post/tetris-2.md Normal file
View File

@ -0,0 +1,89 @@
---
title: "Terminal-based Tetris - Part 2: The matrix"
#date: 2019-11-26T01:42:18-07:00
categories: [tutorial]
draft: true
aliases:
- /post/textris-2/
---
This is the second part of a series of tutorials on creating a [terminal-based](https://en.wikipedia.org/wiki/Text-based_user_interface) [Tetris](https://en.wikipedia.org/wiki/Tetris) clone with [Go](https://golang.org).
For a complete implementation of a Tetris clone in Go, see [netris](https://git.sr.ht/~tslocum/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
The matrix is typically 10 blocks wide and 20 blocks high.
### Matrix data model
A block is an integer representing the contents of a single X-Y coordinate on the matrix.
{{< highlight go >}}
type Block int
const (
BlockNone Block = iota
BlockSolidBlue
BlockSolidCyan
BlockSolidRed
BlockSolidYellow
BlockSolidMagenta
BlockSolidGreen
BlockSolidOrange
)
{{< / highlight >}}
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.
{{< highlight go >}}
type Matrix struct {
W int // Width
H in // Height
B int // Buffer height
M []Block // Contents
}
func NewMatrix(w int, h int, b int) *Matrix {
m := Matrix{
W: w,
H: h,
B: b,
M: make([]Block, w*(h+b)),
}
return &m
}
{{< / highlight >}}
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 {
log.Panicf("failed to retrieve index for %d,%d width %d: invalid coordinates", x, y, w)
}
return (y * w) + x
}
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]
}
{{< / highlight >}}

2
update.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
git submodule update --merge --remote