Add history

This commit is contained in:
Madison Rye Progress
2026-03-18 13:49:25 -07:00
parent 1bf7e0c828
commit 4a3daa764d
2 changed files with 193 additions and 0 deletions

136
state/history.go Normal file
View File

@ -0,0 +1,136 @@
package state
import "strconv"
// History returns the gameplay history for replays
func (s *State) History() string {
return s.history
}
func UnmarshalAll(history string) *State {
s := Unmarshal(history)
for {
if !s.Step() {
break
}
}
return s
}
func Unmarshal(history string) *State {
return &State{
history: history,
historyIndex: 0,
cursor: &Point{0, 0},
}
}
func (s *State) Step() bool {
if s.historyIndex >= len(s.history) {
return false
}
switch s.history[s.historyIndex] {
case 'g':
s.historyStart()
case 'i':
s.historyInitSection()
case 'm':
s.mark(*s.cursor)
case 'f':
s.flag(*s.cursor)
case 'c':
s.clear(*s.cursor, true)
case 'r':
s.cursorCellRight()
case 'l':
s.cursorCellLeft()
case 'u':
s.cursorCellUp()
case 'd':
s.cursorCellDown()
case 'R':
s.cursorSectionRight()
case 'L':
s.cursorSectionLeft()
case 'U':
s.cursorSectionUp()
case 'D':
s.cursorSectionDown()
}
s.historyIndex++
return true
}
func (s *State) historyStart() {
peek := s.historyIndex + 1
p, peek := s.historyPoint(peek)
s.sectionSize = p.X
s.cellsPerSection = p.Y
s.cells = newField(s.sectionSize * s.cellsPerSection)
s.sections = newField(s.sectionSize)
s.cursor = &Point{0, 0}
s.historyIndex = peek
}
func (s *State) historyInitSection() {
peek := s.historyIndex + 1
p, peek := s.historyPoint(peek)
segment := []byte(s.history[peek : peek+s.cellsPerSection*s.cellsPerSection])
for i, c := range segment {
curr := Point{
p.X*s.cellsPerSection + (i % s.cellsPerSection),
p.Y*s.cellsPerSection + (i / s.cellsPerSection),
}
s.cells.clear(curr, true)
switch c {
case 'x':
s.cells.vivify(curr)
case 'o':
s.cells.kill(curr)
default:
break
}
peek++
}
s.historyIndex = peek
}
func (s *State) historyPoint(index int) (Point, int) {
var x, y string
// Advance past paren
index++
for {
switch s.history[index] {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
x += string(s.history[index])
index++
continue
}
break
}
// Advance past comma
index++
for {
switch s.history[index] {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
y += string(s.history[index])
index++
continue
}
break
}
pX, err := strconv.Atoi(x)
if err != nil {
panic(err)
}
pY, err := strconv.Atoi(y)
if err != nil {
panic(err)
}
return Point{pX, pY}, index
}

57
state/history_test.go Normal file
View File

@ -0,0 +1,57 @@
package state
import (
"regexp"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestHistory(t *testing.T) {
Convey("Given a game's history", t, func() {
Convey("You can maintain a history of all actions", func() {
s := New(2, 2)
s.Flag()
s.Mark()
s.Clear()
s.CursorSectionRight()
s.CursorSectionLeft()
s.CursorSectionDown()
s.CursorSectionUp()
s.CursorCellRight()
s.CursorCellLeft()
s.CursorCellDown()
s.CursorCellUp()
matches, err := regexp.Match(`g\(2,2\)(i\(\d,\d\)[xo]{4}){4}fmcRLDUrldu`, []byte(s.History()))
So(err, ShouldBeNil)
So(matches, ShouldBeTrue)
})
Convey("You can load a game from its saved history", func() {
history := "g(2,2)i(0,0)oxoxi(1,0)xoxoi(0,1)ooooi(1,1)xxxxfRmDcLUrldu"
s := UnmarshalAll(history)
Convey("It sets the history", func() {
So(s.History(), ShouldEqual, history)
})
Convey("It correctly sets the field sizes", func() {
So(s.sectionSize, ShouldEqual, 2)
So(s.cellsPerSection, ShouldEqual, 2)
So(len(s.cells.cells), ShouldEqual, 16)
So(len(s.sections.cells), ShouldEqual, 4)
})
Convey("The cursor should be set to the appropriate coordinates after all movement", func() {
So(s.cursor, ShouldResemble, &Point{0, 0})
})
Convey("The board should be appropriately marked and filled", func() {
So(s.String(), ShouldEqual, "X. \n \no .\n....\n")
})
})
})
}