diff --git a/config.toml b/config.toml index 01789fa..b426910 100644 --- a/config.toml +++ b/config.toml @@ -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"] diff --git a/content/post/tetris-1.md b/content/post/tetris-1.md index 75b2804..020fb25 100644 --- a/content/post/tetris-1.md +++ b/content/post/tetris-1.md @@ -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](#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) +Game pieces are called "minos" because they are [polyominos](https://en.wikipedia.org/wiki/Polyomino). +This tutorial series will focus on the seven one-sided [terominos](https://en.wikipedia.org/wiki/Tetromino), +where each piece has four blocks. -# Minos +``` + XX X X X XX XX +XXXX XX XXX XXX XXX XX XX -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. + 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 | -| --- | --- | --- | --- | --- | --- | --- | -|
|
████
██|
██
█|
███
█|
███
█|
███
██|
██
██| - -## Data model +### Mino 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 - -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. +# Stay tuned... -{{< 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). diff --git a/content/post/tetris-2.md b/content/post/tetris-2.md new file mode 100644 index 0000000..5be3f4e --- /dev/null +++ b/content/post/tetris-2.md @@ -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 >}} diff --git a/update.sh b/update.sh new file mode 100755 index 0000000..4775d24 --- /dev/null +++ b/update.sh @@ -0,0 +1,2 @@ +#!/bin/bash +git submodule update --merge --remote
██