mirror of
https://github.com/cjdenio/crossword.git
synced 2026-10-03 11:06:19 +00:00
212 lines
4.9 KiB
Go
212 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"slices"
|
|
"unicode"
|
|
|
|
"github.com/cjdenio/crossword/puz"
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
func main() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
fmt.Print("!!! PROGRAM CRASHED !!! Error:\r\n", r, "\r\n")
|
|
}
|
|
}()
|
|
|
|
debugMode := flag.Bool("debug", false, "")
|
|
flag.Parse()
|
|
|
|
filename := flag.Arg(0)
|
|
file, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
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)
|
|
}
|
|
|
|
fmt.Print("\x1b[?25l")
|
|
|
|
defer func() {
|
|
fmt.Print("\x1b[?25h")
|
|
}()
|
|
termState, err := term.MakeRaw(int(os.Stdin.Fd()))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
defer term.Restore(int(os.Stdin.Fd()), termState)
|
|
|
|
state := State{
|
|
Puzzle: puzzle,
|
|
PuzzleState: []rune(puzzle.State),
|
|
SelectedClue: puzzle.Clues[0],
|
|
SelectedCell: puzzle.Clues[0].Cells[0],
|
|
}
|
|
if debugMode != nil {
|
|
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)
|
|
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
|
if len(data) == 0 {
|
|
return 0, nil, bufio.ErrFinalToken
|
|
}
|
|
|
|
if data[0] == 0x1b {
|
|
if len(data) < 3 {
|
|
return 0, nil, nil
|
|
}
|
|
|
|
return 3, data[0:3], nil
|
|
}
|
|
|
|
return 1, data[0:1], nil
|
|
})
|
|
|
|
for {
|
|
scanner.Scan()
|
|
buffer := scanner.Bytes()
|
|
|
|
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
|
|
fmt.Printf("\r\x1b[%dA", uiHeight)
|
|
fmt.Print("\x1b[J")
|
|
state.RenderUI()
|
|
return
|
|
}
|
|
|
|
if buffer[0] == ' ' {
|
|
switch state.SelectedClue.Direction {
|
|
case puz.DirectionAcross:
|
|
if state.Puzzle.Cells[state.SelectedCell][1] != nil {
|
|
state.SelectedClue = state.Puzzle.Cells[state.SelectedCell][1]
|
|
}
|
|
case puz.DirectionDown:
|
|
if state.Puzzle.Cells[state.SelectedCell][0] != nil {
|
|
state.SelectedClue = state.Puzzle.Cells[state.SelectedCell][0]
|
|
}
|
|
}
|
|
}
|
|
|
|
if buffer[0] >= 0x61 && buffer[0] <= 0x7a {
|
|
cellWasFilled := state.PuzzleState[state.SelectedCell] != '-'
|
|
state.PuzzleState[state.SelectedCell] = unicode.ToUpper(rune(buffer[0]))
|
|
|
|
i := slices.Index(state.SelectedClue.Cells, state.SelectedCell)
|
|
|
|
if !cellWasFilled {
|
|
// jump to next unfilled cell in clue
|
|
for x := i + 1; x < len(state.SelectedClue.Cells); x++ {
|
|
if state.PuzzleState[state.SelectedClue.Cells[x]] == '-' {
|
|
state.SelectedCell = state.SelectedClue.Cells[x]
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
// jump to next cell
|
|
if i < len(state.SelectedClue.Cells)-1 {
|
|
state.SelectedCell = state.SelectedClue.Cells[i+1]
|
|
}
|
|
}
|
|
|
|
if state.GridFilled() {
|
|
if state.CheckPuzzle() {
|
|
state.SolveState = 2
|
|
} else {
|
|
state.SolveState = 1
|
|
}
|
|
} else {
|
|
state.SolveState = 0
|
|
}
|
|
}
|
|
|
|
if buffer[0] == 0x7f {
|
|
// is there a filled cell underneath the cursor?
|
|
if state.PuzzleState[state.SelectedCell] != '-' {
|
|
state.PuzzleState[state.SelectedCell] = '-'
|
|
} else {
|
|
i := slices.Index(state.SelectedClue.Cells, state.SelectedCell)
|
|
if i > 0 {
|
|
state.PuzzleState[state.SelectedClue.Cells[i-1]] = '-' // clear the previous cell
|
|
state.SelectedCell = state.SelectedClue.Cells[i-1]
|
|
}
|
|
}
|
|
}
|
|
|
|
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 {
|
|
state.SelectedClue = state.Puzzle.Cells[state.SelectedCell][0]
|
|
} else if (buffer[2] == 65 || buffer[2] == 66) && state.SelectedClue != nil && state.SelectedClue.Direction == puz.DirectionAcross {
|
|
state.SelectedClue = state.Puzzle.Cells[state.SelectedCell][1]
|
|
} else {
|
|
switch buffer[2] {
|
|
case 68: // left
|
|
state.MoveCursor(3)
|
|
case 67: // right
|
|
state.MoveCursor(1)
|
|
case 65: // up
|
|
state.MoveCursor(0)
|
|
case 66: // down
|
|
state.MoveCursor(2)
|
|
}
|
|
|
|
switch state.SelectedClue.Direction {
|
|
case puz.DirectionAcross:
|
|
state.SelectedClue = state.Puzzle.Cells[state.SelectedCell][0]
|
|
case puz.DirectionDown:
|
|
state.SelectedClue = state.Puzzle.Cells[state.SelectedCell][1]
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Printf("\r\x1b[%dA", uiHeight)
|
|
fmt.Print("\x1b[J")
|
|
|
|
uiHeight = state.RenderUI()
|
|
}
|
|
}
|