205 lines
4.1 KiB
Go
205 lines
4.1 KiB
Go
package state
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
)
|
|
|
|
// History returns the gameplay history for replays
|
|
func (s *State) History() string {
|
|
return s.history
|
|
}
|
|
|
|
func UnmarshalAll(history string) (*State, error) {
|
|
s := Unmarshal(history)
|
|
for {
|
|
res, err := s.Step()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !res {
|
|
break
|
|
}
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func Unmarshal(history string) *State {
|
|
return &State{
|
|
history: history,
|
|
historyIndex: 0,
|
|
cursor: &Point{0, 0},
|
|
}
|
|
}
|
|
|
|
func (s *State) Step() (bool, error) {
|
|
if s.historyIndex >= len(s.history) {
|
|
return false, nil
|
|
}
|
|
|
|
switch s.history[s.historyIndex] {
|
|
case 'i', 'm', 'f', 'c', 'r', 'l', 'u', 'd', 'R', 'L', 'U', 'D':
|
|
err := s.beforeAct()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
switch s.history[s.historyIndex] {
|
|
case 'g':
|
|
if s.initialized {
|
|
return false, fmt.Errorf("initialization step in invalid location (index %d)", s.historyIndex)
|
|
}
|
|
err := s.historyStart()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
case 'i':
|
|
err := s.historyInitSection()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
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()
|
|
case 't': // TODO for now, this is just sugar. Will be a timestamp for events completed.
|
|
for {
|
|
s.historyIndex++
|
|
if s.historyIndex >= len(s.history) || s.history[s.historyIndex] == ')' {
|
|
break
|
|
}
|
|
}
|
|
case ' ', '\n', '\t':
|
|
break
|
|
case '#':
|
|
for {
|
|
s.historyIndex++
|
|
if s.historyIndex >= len(s.history) || s.history[s.historyIndex] == '\n' {
|
|
break
|
|
}
|
|
}
|
|
default:
|
|
return false, fmt.Errorf("invalid step in history: %s (index %d)", string(s.history[s.historyIndex]), s.historyIndex)
|
|
}
|
|
s.historyIndex++
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (s *State) beforeAct() error {
|
|
if !s.initialized {
|
|
return fmt.Errorf("tried to act on an uninitialized state (index %d)", s.historyIndex)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *State) historyStart() error {
|
|
peek := s.historyIndex + 1
|
|
p, peek, err := s.historyPoint(peek)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.sectionSize = p.X
|
|
s.cellsPerSection = p.Y
|
|
s.cells = newField(s.size())
|
|
s.sections = newField(s.sectionSize)
|
|
s.cursor = &Point{0, 0}
|
|
s.rowHeaders = make([]header, s.size())
|
|
s.colHeaders = make([]header, s.size())
|
|
s.historyIndex = peek
|
|
s.score.Blackout = make([]bool, s.size())
|
|
s.initialized = true
|
|
return nil
|
|
}
|
|
|
|
func (s *State) historyInitSection() error {
|
|
peek := s.historyIndex + 1
|
|
p, peek, err := s.historyPoint(peek)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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
|
|
return nil
|
|
}
|
|
|
|
func (s *State) historyPoint(index int) (Point, int, error) {
|
|
var x, y string
|
|
|
|
// Advance past opening paren
|
|
index++
|
|
for {
|
|
if index >= len(s.history) {
|
|
return Point{}, 0, fmt.Errorf("point.X never ended? (index %d)", index)
|
|
}
|
|
switch s.history[index] {
|
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
x += string(s.history[index])
|
|
index++
|
|
continue
|
|
case ',':
|
|
index++
|
|
default:
|
|
return Point{}, 0, fmt.Errorf("invalid character in point.X: %s (index %d)", string(s.history[index]), index)
|
|
}
|
|
break
|
|
}
|
|
|
|
for {
|
|
if index >= len(s.history) {
|
|
return Point{}, 0, fmt.Errorf("point.Y never ended? (index %d)", index)
|
|
}
|
|
switch s.history[index] {
|
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
y += string(s.history[index])
|
|
index++
|
|
continue
|
|
case ')':
|
|
break
|
|
default:
|
|
return Point{}, 0, fmt.Errorf("invalid character in point.Y: %s (index %d)", string(s.history[index]), index)
|
|
}
|
|
break
|
|
}
|
|
|
|
pX, _ := strconv.Atoi(x)
|
|
pY, _ := strconv.Atoi(y)
|
|
return Point{pX, pY}, index, nil
|
|
}
|