diff --git a/README.md b/README.md index c0b4b97..8f0618f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ todo -- When pressing enter, skip filled words -- Shift-enter -- check puzzle when grid is filled - fix bug with that one 6x6 puzzle - correctly calculate uiHeight with multi-line clues - timer diff --git a/game.go b/game.go index 652c75e..9fee202 100644 --- a/game.go +++ b/game.go @@ -1,6 +1,8 @@ package main import ( + "encoding/json" + "errors" "fmt" "slices" "strings" @@ -68,6 +70,16 @@ func (state *State) MoveCursor(direction int) { } } +func (state *State) ClueFilled(clue *puz.Clue) bool { + for _, cell := range clue.Cells { + if state.PuzzleState[cell] == '-' { + return false + } + } + + return true +} + func (state *State) FirstUnfilledCellForClue(clue *puz.Clue) int { for _, cell := range clue.Cells { if state.PuzzleState[cell] == '-' { @@ -90,7 +102,7 @@ func (state *State) NextWord() { continue } - if foundSelectedClue && clue.Direction == state.SelectedClue.Direction { + if foundSelectedClue && clue.Direction == state.SelectedClue.Direction && !state.ClueFilled(clue) { state.SelectedClue = clue state.SelectedCell = state.FirstUnfilledCellForClue(clue) return @@ -98,7 +110,62 @@ func (state *State) NextWord() { } for _, clue := range state.Puzzle.Clues { - if clue.Direction != state.SelectedClue.Direction { + if clue.Direction != state.SelectedClue.Direction && !state.ClueFilled(clue) { + state.SelectedClue = clue + state.SelectedCell = state.FirstUnfilledCellForClue(clue) + return + } + } + + for _, clue := range state.Puzzle.Clues { + if clue == state.SelectedClue { + return + } else if clue.Direction == state.SelectedClue.Direction && !state.ClueFilled(clue) { + state.SelectedClue = clue + state.SelectedCell = state.FirstUnfilledCellForClue(clue) + return + } + } +} + +func (state *State) PreviousWord() { + if state.SelectedClue == nil { + return + } + + foundSelectedClue := false + + for i := len(state.Puzzle.Clues) - 1; i >= 0; i-- { + clue := state.Puzzle.Clues[i] + + if !foundSelectedClue && clue == state.SelectedClue { + foundSelectedClue = true + continue + } + + if foundSelectedClue && clue.Direction == state.SelectedClue.Direction && !state.ClueFilled(clue) { + state.SelectedClue = clue + state.SelectedCell = state.FirstUnfilledCellForClue(clue) + return + } + } + + for i := len(state.Puzzle.Clues) - 1; i >= 0; i-- { + clue := state.Puzzle.Clues[i] + + if clue.Direction != state.SelectedClue.Direction && !state.ClueFilled(clue) { + state.SelectedClue = clue + state.SelectedCell = state.FirstUnfilledCellForClue(clue) + return + } + } + + for i := len(state.Puzzle.Clues) - 1; i >= 0; i-- { + clue := state.Puzzle.Clues[i] + + if clue == state.SelectedClue { + return + } else if clue.Direction == state.SelectedClue.Direction && !state.ClueFilled(clue) { state.SelectedClue = clue state.SelectedCell = state.FirstUnfilledCellForClue(clue) return @@ -242,3 +309,36 @@ func RenderPuzzle(puzzle []rune, width, height int, selectedClueCells []int, sel return b.String() } + +type SaveFile struct { + State string `json:"state"` +} + +func (state *State) CreateSaveFile() ([]byte, error) { + return json.Marshal(SaveFile{ + State: string(state.PuzzleState), + }) +} + +func (state *State) LoadSaveFile(f []byte) error { + var save SaveFile + err := json.Unmarshal(f, &save) + if err != nil { + return err + } + + // verify the save file matches the shape of the puzzle + if len(save.State) != state.Puzzle.Width*state.Puzzle.Height { + return errors.New("save is invalid") + } + + for i, cell := range state.Puzzle.Solution { + if (save.State[i] == '.' && cell != '.') || (save.State[i] != '.' && cell == '.') { + return errors.New("save is invalid") + } + } + + state.PuzzleState = []rune(save.State) + + return nil +} diff --git a/main.go b/main.go index 63ed657..f48b015 100644 --- a/main.go +++ b/main.go @@ -29,6 +29,11 @@ func main() { log.Fatal(err) } + fileInfo, err := os.Stat(filename) + if err != nil { + log.Fatal(err) + } + puzzle, err := puz.ParsePuz(file) if err != nil { log.Fatal(err) @@ -56,6 +61,15 @@ func main() { state.DebugMode = *debugMode } + // try to load a saveFile + saveFile, err := os.ReadFile(filename + ".save") + if err == nil { + err = state.LoadSaveFile(saveFile) + if state.DebugMode && err != nil { + state.LastKeySequence = fmt.Sprintf("failed to read savefile: %s", err) + } + } + uiHeight := state.RenderUI() scanner := bufio.NewScanner(os.Stdin) @@ -82,6 +96,13 @@ func main() { state.LastKeySequence = fmt.Sprintf("%v", buffer) if buffer[0] == 3 { + saveFile, err := state.CreateSaveFile() + if err == nil { + err = os.WriteFile(filename+".save", saveFile, fileInfo.Mode()) + if err != nil && state.DebugMode { + state.LastKeySequence = fmt.Sprintf("failed to write savefile: %s", err) + } + } state.SelectedClue = nil state.SelectedCell = -1 state.Goodbye = true @@ -149,9 +170,12 @@ func main() { } } - if buffer[0] == 0x0d { + if buffer[0] == '\r' { state.NextWord() } + if buffer[0] == '~' { + state.PreviousWord() + } if string(buffer[0:2]) == "\x1b[" { if (buffer[2] == 68 || buffer[2] == 67) && state.SelectedClue != nil && state.SelectedClue.Direction == puz.DirectionDown {