mirror of
https://codeberg.org/scip/golsky.git
synced 2025-12-16 12:10:58 +01:00
Added RLE parser by N.Hoffmann and incorporated it onto my gol.
This commit is contained in:
65
main.go
65
main.go
@@ -10,7 +10,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/repr"
|
||||
"github.com/tlinden/gameoflife/rle"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
@@ -40,12 +41,13 @@ type Game struct {
|
||||
ScreenWidth, ScreenHeight int
|
||||
Generations int // Stats
|
||||
Black, White, Grey, Beige color.RGBA
|
||||
TPG int // ticks per generation/game speed, 1==max
|
||||
TicksElapsed int // tick counter for game speed
|
||||
Debug, Paused, Empty, Invert bool // game modi
|
||||
ShowEvolution, NoGrid, RunOneStep bool // flags
|
||||
Rule *Rule // which rule to use, default: B3/S23
|
||||
Tiles Images // pre-computed tiles for dead and alife cells
|
||||
TPG int // ticks per generation/game speed, 1==max
|
||||
TicksElapsed int // tick counter for game speed
|
||||
Debug, Paused, Empty, Invert bool // game modi
|
||||
ShowEvolution, NoGrid, RunOneStep bool // flags
|
||||
Rule *Rule // which rule to use, default: B3/S23
|
||||
Tiles Images // pre-computed tiles for dead and alife cells
|
||||
RLE *rle.RLE // loaded GOL pattern from RLE file
|
||||
}
|
||||
|
||||
func (game *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||
@@ -294,6 +296,7 @@ func (game *Game) Draw(screen *ebiten.Image) {
|
||||
}
|
||||
}
|
||||
|
||||
// returns current memory usage in MB
|
||||
func GetMem() float64 {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
@@ -301,6 +304,28 @@ func GetMem() float64 {
|
||||
return float64(m.Alloc) / 1024 / 1024
|
||||
}
|
||||
|
||||
// load a pre-computed pattern from RLE file
|
||||
func (game *Game) InitPattern() {
|
||||
if game.RLE != nil {
|
||||
startX := (game.Width / 2) - (game.RLE.Width / 2)
|
||||
startY := (game.Height / 2) - (game.RLE.Height / 2)
|
||||
var y, x int
|
||||
|
||||
for rowIndex, patternRow := range game.RLE.Pattern {
|
||||
for colIndex := range patternRow {
|
||||
if game.RLE.Pattern[rowIndex][colIndex] > 0 {
|
||||
x = colIndex + startX
|
||||
y = rowIndex + startY
|
||||
|
||||
game.History.Data[y][x] = 1
|
||||
game.Grids[0].Data[y][x] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// initialize playing field/grid
|
||||
func (game *Game) InitGrid() {
|
||||
grid := &Grid{Data: make([][]int, game.Height)}
|
||||
gridb := &Grid{Data: make([][]int, game.Height)}
|
||||
@@ -310,6 +335,7 @@ func (game *Game) InitGrid() {
|
||||
grid.Data[y] = make([]int, game.Width)
|
||||
gridb.Data[y] = make([]int, game.Width)
|
||||
history.Data[y] = make([]int, game.Width)
|
||||
|
||||
if !game.Empty {
|
||||
for x := 0; x < game.Width; x++ {
|
||||
if rand.Intn(game.Density) == 1 {
|
||||
@@ -370,6 +396,7 @@ func (game *Game) Init() {
|
||||
game.ScreenHeight = game.Cellsize * game.Height
|
||||
|
||||
game.InitGrid()
|
||||
game.InitPattern()
|
||||
game.InitTiles()
|
||||
|
||||
game.Index = 0
|
||||
@@ -401,6 +428,7 @@ func main() {
|
||||
game := &Game{}
|
||||
showversion := false
|
||||
var rule string
|
||||
var rlefile string
|
||||
|
||||
pflag.IntVarP(&game.Width, "width", "W", 40, "grid width in cells")
|
||||
pflag.IntVarP(&game.Height, "height", "H", 40, "grid height in cells")
|
||||
@@ -409,6 +437,7 @@ func main() {
|
||||
pflag.IntVarP(&game.TPG, "ticks-per-generation", "t", 10, "game speed: the higher the slower (default: 10)")
|
||||
|
||||
pflag.StringVarP(&rule, "rule", "r", "B3/S23", "game rule")
|
||||
pflag.StringVarP(&rlefile, "rlefile", "f", "", "RLE pattern file")
|
||||
|
||||
pflag.BoolVarP(&showversion, "version", "v", false, "show version")
|
||||
pflag.BoolVarP(&game.Paused, "paused", "p", false, "do not start simulation (use space to start)")
|
||||
@@ -427,7 +456,27 @@ func main() {
|
||||
|
||||
game.Rule = ParseGameRule(rule)
|
||||
|
||||
repr.Print(game.TPG)
|
||||
if rlefile != "" {
|
||||
content, err := os.ReadFile(rlefile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
parsedRle, err := rle.Parse(string(content))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load RLE pattern file: %s", err)
|
||||
}
|
||||
|
||||
if parsedRle.Width > game.Width || parsedRle.Height > game.Height {
|
||||
log.Fatal("loaded RLE pattern is too large for game grid, adjust width+height")
|
||||
}
|
||||
|
||||
game.RLE = &parsedRle
|
||||
|
||||
// RLE needs an empty grid
|
||||
game.Empty = true
|
||||
}
|
||||
|
||||
game.Init()
|
||||
|
||||
ebiten.SetWindowSize(game.ScreenWidth, game.ScreenHeight)
|
||||
|
||||
159
rle/pattern_parser.go
Normal file
159
rle/pattern_parser.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package rle
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type TokenType string
|
||||
|
||||
type Token struct {
|
||||
Type TokenType
|
||||
Literal string
|
||||
}
|
||||
|
||||
const (
|
||||
RUN_COUNT = "RUN_COUNT"
|
||||
DEAD_CELL = "DEAD_CELL"
|
||||
ALIVE_CELL = "ALIVE_CELL"
|
||||
EOL = "EOL"
|
||||
EOP = "EOP"
|
||||
)
|
||||
|
||||
type Lexer struct {
|
||||
input string
|
||||
position int
|
||||
readPosition int
|
||||
char byte
|
||||
}
|
||||
|
||||
func NewLexer(input string) *Lexer {
|
||||
l := &Lexer{input: input}
|
||||
l.readChar()
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Lexer) NextToken() Token {
|
||||
var tok Token
|
||||
|
||||
l.skipWhitespace()
|
||||
|
||||
switch l.char {
|
||||
case '$':
|
||||
tok = newToken(EOL, l.char)
|
||||
case '!':
|
||||
tok = newToken(EOP, l.char)
|
||||
case 'b':
|
||||
tok = newToken(DEAD_CELL, l.char)
|
||||
case 'o':
|
||||
tok = newToken(ALIVE_CELL, l.char)
|
||||
default:
|
||||
if isDigit(l.char) {
|
||||
tok.Type = RUN_COUNT
|
||||
tok.Literal = l.readNumber()
|
||||
return tok
|
||||
}
|
||||
}
|
||||
|
||||
l.readChar()
|
||||
return tok
|
||||
}
|
||||
|
||||
func newToken(tokenType TokenType, char byte) Token {
|
||||
return Token{Type: tokenType, Literal: string(char)}
|
||||
}
|
||||
|
||||
type PatternParser struct {
|
||||
lexer *Lexer
|
||||
currentToken Token
|
||||
peekToken Token
|
||||
}
|
||||
|
||||
func NewParser(lexer *Lexer) *PatternParser {
|
||||
p := &PatternParser{
|
||||
lexer: lexer,
|
||||
}
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (pp *PatternParser) ParsePattern(width, height int) [][]int {
|
||||
result := make([][]int, height)
|
||||
|
||||
row := make([]int, width)
|
||||
var rowIndex int
|
||||
var colIndex int
|
||||
for {
|
||||
switch pp.currentToken.Type {
|
||||
case RUN_COUNT:
|
||||
count, _ := strconv.Atoi(pp.currentToken.Literal)
|
||||
for i := 0; i < count; i++ {
|
||||
switch pp.peekToken.Type {
|
||||
case ALIVE_CELL:
|
||||
row[rowIndex+i] = 1
|
||||
case DEAD_CELL:
|
||||
row[rowIndex+i] = 0
|
||||
case EOL:
|
||||
result[colIndex] = row
|
||||
row = make([]int, width)
|
||||
rowIndex = -1
|
||||
colIndex++
|
||||
}
|
||||
}
|
||||
|
||||
if pp.peekToken.Type != EOL {
|
||||
rowIndex += count - 1
|
||||
}
|
||||
pp.nextToken()
|
||||
case ALIVE_CELL:
|
||||
row[rowIndex] = 1
|
||||
case DEAD_CELL:
|
||||
row[rowIndex] = 0
|
||||
case EOL:
|
||||
result[colIndex] = row
|
||||
row = make([]int, width)
|
||||
rowIndex = -1
|
||||
colIndex++
|
||||
case EOP:
|
||||
result[colIndex] = row
|
||||
return result
|
||||
}
|
||||
rowIndex++
|
||||
pp.nextToken()
|
||||
}
|
||||
}
|
||||
|
||||
func (pp *PatternParser) nextToken() {
|
||||
pp.currentToken = pp.peekToken
|
||||
pp.peekToken = pp.lexer.NextToken()
|
||||
}
|
||||
|
||||
func isDigit(char byte) bool {
|
||||
return '0' <= char && char <= '9'
|
||||
}
|
||||
|
||||
func (l *Lexer) readChar() {
|
||||
if l.readPosition >= len(l.input) {
|
||||
l.char = 0
|
||||
} else {
|
||||
l.char = l.input[l.readPosition]
|
||||
}
|
||||
l.position = l.readPosition
|
||||
l.readPosition++
|
||||
}
|
||||
|
||||
func (l *Lexer) readNumber() string {
|
||||
position := l.position
|
||||
for isDigit(l.char) {
|
||||
l.readChar()
|
||||
}
|
||||
|
||||
return l.input[position:l.position]
|
||||
}
|
||||
|
||||
func (l *Lexer) skipWhitespace() {
|
||||
for l.char == ' ' || l.char == '\t' || l.char == '\n' || l.char == '\r' {
|
||||
l.readChar()
|
||||
}
|
||||
}
|
||||
163
rle/pattern_parser_test.go
Normal file
163
rle/pattern_parser_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package rle
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNextToken(t *testing.T) {
|
||||
input := "bo$2bo$3o!"
|
||||
|
||||
tests := []struct {
|
||||
expectedType TokenType
|
||||
expectedLiteral string
|
||||
}{
|
||||
{DEAD_CELL, "b"},
|
||||
{ALIVE_CELL, "o"},
|
||||
{EOL, "$"},
|
||||
{RUN_COUNT, "2"},
|
||||
{DEAD_CELL, "b"},
|
||||
{ALIVE_CELL, "o"},
|
||||
{EOL, "$"},
|
||||
{RUN_COUNT, "3"},
|
||||
{ALIVE_CELL, "o"},
|
||||
{EOP, "!"},
|
||||
}
|
||||
|
||||
l := NewLexer(input)
|
||||
|
||||
for _, test := range tests {
|
||||
token := l.NextToken()
|
||||
|
||||
if token.Type != test.expectedType {
|
||||
t.Errorf("Token typ not correct")
|
||||
}
|
||||
|
||||
if token.Literal != test.expectedLiteral {
|
||||
t.Errorf("Literal not correct")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePattern(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected [][]int
|
||||
width int
|
||||
height int
|
||||
}{
|
||||
{
|
||||
input: "bo$2bo$3o!",
|
||||
expected: [][]int{
|
||||
{0, 1, 0},
|
||||
{0, 0, 1},
|
||||
{1, 1, 1},
|
||||
},
|
||||
width: 3,
|
||||
height: 3,
|
||||
},
|
||||
{
|
||||
input: `24bo$22bobo$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o$2o8bo3bob2o4b
|
||||
obo$10bo5bo7bo$11bo3bo$12b2o!`,
|
||||
expected: [][]int{
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
|
||||
{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
},
|
||||
width: 36,
|
||||
height: 9,
|
||||
},
|
||||
{
|
||||
input: `20b2o$
|
||||
20b2o4$
|
||||
9b2o$
|
||||
8bo2bo10b2o$
|
||||
9b2o11bo$
|
||||
22bo12bo$
|
||||
23bo10bobo$
|
||||
34bobo$
|
||||
35bo7$
|
||||
32bo2bo$
|
||||
33b3o$
|
||||
2o38b2o$
|
||||
2o38b2o$
|
||||
6b3o$
|
||||
6bo2bo7$
|
||||
6bo$
|
||||
5bobo$
|
||||
5bobo10bo$
|
||||
6bo12bo$
|
||||
19bo11b2o$
|
||||
18b2o10bo2bo$
|
||||
31b2o4$
|
||||
20b2o$
|
||||
20b2o!`,
|
||||
expected: [][]int{
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0},
|
||||
{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
|
||||
{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
|
||||
{0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
},
|
||||
width: 42,
|
||||
height: 42,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
l := NewLexer(test.input)
|
||||
pp := NewParser(l)
|
||||
result := pp.ParsePattern(test.width, test.height)
|
||||
|
||||
if !reflect.DeepEqual(result, test.expected) {
|
||||
t.Fatalf(
|
||||
"Patterns do not match.\nExpected: %v\nGot: %v",
|
||||
test.expected,
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
100
rle/rle.go
Normal file
100
rle/rle.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// source: https://github.com/nhoffmann/life by N.Hoffmann 2020.
|
||||
package rle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RLE struct {
|
||||
Rule string // rule
|
||||
Width int // x
|
||||
Height int // y
|
||||
Pattern [][]int // The actual pattern
|
||||
|
||||
inputLines []string
|
||||
headerLineIndex int
|
||||
patternLineIndex int
|
||||
}
|
||||
|
||||
func Parse(input string) (RLE, error) {
|
||||
rle := RLE{
|
||||
inputLines: strings.Split(input, "\n"),
|
||||
}
|
||||
|
||||
rle.partitionFile()
|
||||
|
||||
err := rle.parseComments()
|
||||
if err != nil {
|
||||
return RLE{}, err
|
||||
}
|
||||
err = rle.parseHeader()
|
||||
if err != nil {
|
||||
return RLE{}, err
|
||||
}
|
||||
err = rle.parsePattern()
|
||||
if err != nil {
|
||||
return RLE{}, err
|
||||
}
|
||||
|
||||
return rle, nil
|
||||
}
|
||||
|
||||
func (rle *RLE) partitionFile() error {
|
||||
for index, line := range rle.inputLines {
|
||||
cleanLine := removeWhitespace(line)
|
||||
if strings.HasPrefix(cleanLine, "x=") {
|
||||
rle.headerLineIndex = index
|
||||
rle.patternLineIndex = index + 1
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Invlaid input: Header is missing")
|
||||
}
|
||||
|
||||
func (rle *RLE) parseComments() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rle *RLE) parseHeader() (err error) {
|
||||
headerLine := removeWhitespace(rle.inputLines[rle.headerLineIndex])
|
||||
|
||||
headerElements := strings.SplitN(headerLine, ",", 3)
|
||||
|
||||
rle.Width, err = strconv.Atoi(strings.TrimPrefix(headerElements[0], "x="))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rle.Height, err = strconv.Atoi(strings.TrimPrefix(headerElements[1], "y="))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rle.Pattern = make([][]int, rle.Width)
|
||||
|
||||
// check wehter a rule is present, since it's optional
|
||||
if len(headerElements) == 3 {
|
||||
rle.Rule = strings.TrimPrefix(headerElements[2], "rule=")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rle *RLE) parsePattern() error {
|
||||
patternString := strings.Join(rle.inputLines[rle.patternLineIndex:], "")
|
||||
|
||||
l := NewLexer(patternString)
|
||||
pp := NewParser(l)
|
||||
|
||||
rle.Pattern = pp.ParsePattern(rle.Width, rle.Height)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeWhitespace(input string) string {
|
||||
re := regexp.MustCompile(` *\t*\r*\n*`)
|
||||
return re.ReplaceAllString(input, "")
|
||||
}
|
||||
84
rle/rle_test.go
Normal file
84
rle/rle_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package rle
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRLE(t *testing.T) {
|
||||
t.Run("Parse", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expectedPattern [][]int
|
||||
expectedComment string
|
||||
expectedWidth int
|
||||
expectedHeight int
|
||||
expectedRule string
|
||||
}{
|
||||
{
|
||||
input: `#C This is a glider.
|
||||
x = 3, y = 3
|
||||
bo$2bo$3o!`,
|
||||
expectedPattern: [][]int{
|
||||
{0, 1, 0},
|
||||
{0, 0, 1},
|
||||
{1, 1, 1},
|
||||
},
|
||||
expectedWidth: 3,
|
||||
expectedHeight: 3,
|
||||
expectedRule: "",
|
||||
},
|
||||
{
|
||||
input: `#N Gosper glider gun
|
||||
#C This was the first gun discovered.
|
||||
#C As its name suggests, it was discovered by Bill Gosper.
|
||||
x = 36, y = 9, rule = B3/S23
|
||||
24bo$22bobo$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o$2o8bo3bob2o4b
|
||||
obo$10bo5bo7bo$11bo3bo$12b2o!`,
|
||||
expectedPattern: [][]int{
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
|
||||
{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
},
|
||||
expectedWidth: 36,
|
||||
expectedHeight: 9,
|
||||
expectedRule: "B3/S23",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
rle, err := Parse(test.input)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if rle.Width != test.expectedWidth {
|
||||
t.Errorf("Width dos not match")
|
||||
}
|
||||
|
||||
if rle.Height != test.expectedHeight {
|
||||
t.Errorf("Height does not match")
|
||||
}
|
||||
|
||||
if rle.Rule != test.expectedRule {
|
||||
t.Errorf("Rule does not match")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(rle.Pattern, test.expectedPattern) {
|
||||
t.Errorf(
|
||||
"Patterns do not match.\nExpected: %v\nGot: %v",
|
||||
test.expectedPattern,
|
||||
rle.Pattern,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
3
sample-rles/64P2H1V0.rle
Normal file
3
sample-rles/64P2H1V0.rle
Normal file
@@ -0,0 +1,3 @@
|
||||
x = 8, y = 31, rule = B3/S23
|
||||
o$4o$2b2o$5bo$2b4o$6bo$2bo2b3o$4b3o$5bo$ob3o$2o2bo$b3o$bo$3bo$bobo$4bo$bobo$
|
||||
3bo$bo$b3o$2o2bo$ob3o$5bo$4b3o$2bo2b3o$6bo$2b4o$5bo$2b2o$4o$o!
|
||||
8
sample-rles/p39piheptominohasslerdimer.rle
Normal file
8
sample-rles/p39piheptominohasslerdimer.rle
Normal file
@@ -0,0 +1,8 @@
|
||||
#N p39piheptominohasslerdimer.rle
|
||||
#C https://conwaylife.com/wiki/P39_pi-heptomino_hassler
|
||||
#C https://www.conwaylife.com/patterns/p39piheptominohasslerdimer.rle
|
||||
x = 51, y = 30, rule = B3/S23
|
||||
9b2o$8bobo$8bo$3bob2ob2o$3b2obo$6bo$6b2o27b2o$35b2o4$22b2o3b2o$22bobo
|
||||
2b2o$10b3o10bo$2obo6bobo25bobo6b2obo$ob2o6bobo25bobo6bob2o$27bo10b3o$
|
||||
22b2o2bobo$22b2o3b2o4$14b2o$14b2o27b2o$44bo$44bob2o$41b2ob2obo$42bo$
|
||||
40bobo$40b2o!
|
||||
13
sample-rles/weekender.rle
Normal file
13
sample-rles/weekender.rle
Normal file
@@ -0,0 +1,13 @@
|
||||
#N 244p7h3v0.rle
|
||||
#C https://conwaylife.com/wiki/232P7H3V0
|
||||
#C https://www.conwaylife.com/patterns/244p7h3v0.rle
|
||||
x = 51, y = 52, rule = B3/S23
|
||||
19b3o9b3o$18bo3bo7bo3bo$17bobo3bo5bo3bobo$17bo3b2o7b2o3bo$17b3o3bo5bo
|
||||
3b3o$16bo3b2ob3ob3ob2o3bo$16b2o2bo3b2ob2o3bo2b2o$15b3o3b5ob5o3b3o$23bo
|
||||
5bo$20bo11bo$15bo4bo11bo4bo$15bo4b4o5b4o4bo$19bo4bo3bo4bo$18b2ob3o5b3o
|
||||
b2o$18b2obo3bobo3bob2o$14b3o7b2ob2o7b3o$13bo3b2o4bobobobo4b2o3bo$12bo
|
||||
3bo19bo3bo$12bo9b3o3b3o9bo$16bo6b2o3b2o6bo$11bo12bo3bo12bo$11bo2b2o5bo
|
||||
9bo5b2o2bo$12b2o8bo7bo8b2o$10bo12bo5bo12bo$9b3o29b3o$8b2o2bo27bo2b2o$
|
||||
11b2o27b2o$11bo29bo2$8bo35bo$9b2o31b2o$7bo2bo31bo2bo$6bo39bo$5b2o39b2o
|
||||
$4b4o37b4o$3bo45bo$3b3o41b3o$2bo47bo$4b2o41b2o$6bo39bo$4b2o41b2o$5bo
|
||||
41bo$4bo43bo$4bo43bo$2b2o$2obo$o$2o$bo3bo$4bo$o2bo$o!
|
||||
Reference in New Issue
Block a user