Update textris draft

This commit is contained in:
Trevor Slocum 2019-11-21 07:37:27 -08:00
parent 48abd89324
commit 09b22d132e
1 changed files with 80 additions and 21 deletions

View File

@ -1,13 +1,16 @@
---
title: "Textris - Creating a terminal-based Tetris clone in Go - Part 1: Pieces and playfield"
title: "Textris - Part 1: Pieces and playfield - Creating a terminal-based Tetris clone"
#date: 2019-11-26T01:42:18-07:00
categories: [tutorial]
draft: true
---
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).
Game pieces (minos) are generated and added to a playfield (matrix).
## Disclaimer
Tetris is a registered trademark of the Tetris Holding, LLC.
Rocket Nine Labs is in no way affiliated with Tetris Holding, LLC.
@ -17,13 +20,16 @@ Rocket Nine Labs is in no way affiliated with Tetris Holding, LLC.
* [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
Game pieces are called "Minos" because they are [polyominos](https://en.wikipedia.org/wiki/Polyomino).
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.
The number of blocks a mino has is also known as its rank.
| I | O | T | J | L | S | Z |
@ -33,7 +39,7 @@ The number of blocks a mino has is also known as its rank.
## 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:
We will store Minos as slices of points.
```go
type Point struct {
@ -45,15 +51,6 @@ 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} }
// Neighborhood returns the Von Neumann neighborhood of a point
func (p Point) Neighborhood() Mino {
return Mino{
{p.X - 1, p.Y},
{p.X, p.Y - 1},
{p.X + 1, p.Y},
{p.X, p.Y + 1}}
}
type Mino []Point
var minoT = Mino{{0, 0}, {1, 0}, {2, 0}, {1, 1}}
@ -66,7 +63,7 @@ This allows us to play with any size of mino.
### Comparing and sorting
To compare minos more easily, we will use their string representation.
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.
@ -236,7 +233,19 @@ func (m Mino) Canonical() Mino {
### Generating new minos
WIP
Neighborhood returns the [Von Neumann neighborhood](https://en.wikipedia.org/wiki/Von_Neumann_neighborhood) of a point.
```go
func (p Point) Neighborhood() []Point {
return []Point{
{p.X - 1, p.Y},
{p.X, p.Y - 1},
{p.X + 1, p.Y},
{p.X, p.Y + 1}}
}
```
NewPoints calculates the neighborhood of each point of a mino and returns only new points.
```go
func (m Mino) HasPoint(p Point) bool {
@ -249,22 +258,26 @@ func (m Mino) HasPoint(p Point) bool {
return false
}
func (m Mino) newPoints() Mino {
var newMino Mino
func (m Mino) NewPoints() []Point {
var newPoints []Point
for _, p := range m {
for _, np := range p.Neighborhood() {
if !m.HasPoint(np) {
newMino = append(newMino, np)
newPoints = append(newPoints, np)
}
}
}
return newMino
return newPoints
}
```
func (m Mino) newMinos() []Mino {
points := m.newPoints()
NewMinos returns a new mino for every new neighborhood point of a supplied mino.
```go
func (m Mino) NewMinos() []Mino {
points := m.NewPoints()
minos := make([]Mino, len(points))
for i, p := range points {
@ -275,13 +288,56 @@ func (m Mino) newMinos() []Mino {
}
```
Generate procedurally generates minos of a supplied rank.
```go
func Generate(rank int) ([]Mino, error) {
switch {
case rank < 0:
return nil, errors.New("invalid rank")
case rank == 0:
return []Mino{}, nil
case rank == 1:
return []Mino{monomino()}, nil
default:
r, err := Generate(rank - 1)
if err != nil {
return nil, err
}
var (
minos []Mino
s string
found = make(map[string]bool)
)
for _, mino := range r {
for _, newMino := range mino.NewMinos() {
s = newMino.Canonical().String()
if found[s] {
continue
}
minos = append(minos, newMino.Canonical())
found[s] = true
}
}
return minos, nil
}
}
func monomino() Mino {
return Mino{{0, 0}}
}
```
# Matrix
The matrix is typically 10 blocks wide and 20 blocks high.
## Data model
A block represents the contents of a single X-Y coordinate on the matrix.
A block is an integer representing the contents of a single X-Y coordinate on the matrix.
```go
type Block int
@ -301,6 +357,9 @@ const (
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.
```go
type Matrix struct {
W int // Width