Add history
This commit is contained in:
136
state/history.go
Normal file
136
state/history.go
Normal 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
57
state/history_test.go
Normal 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")
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user