mirror of
https://github.com/cjdenio/crossword.git
synced 2026-10-03 11:06:19 +00:00
handle line breaks in clues
This commit is contained in:
5 files changed
+70
-12
No files matched your search
@@ -10,7 +10,6 @@ Very silly terminal-based crossword solver. It can play .puz / .ipuz files from
|
|||||||
|
|
||||||
todo
|
todo
|
||||||
|
|
||||||
- correctly calculate uiHeight with multi-line clues
|
|
||||||
- timer
|
- timer
|
||||||
- reveal grid/word
|
- reveal grid/word
|
||||||
- .puz checksums
|
- .puz checksums
|
||||||
|
|||||||
+8
-3
@@ -13,6 +13,11 @@ import (
|
|||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getTermWidth() int {
|
||||||
|
w, _, _ := term.GetSize(int(os.Stdin.Fd()))
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@@ -72,7 +77,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uiHeight := state.RenderUI(os.Stdout)
|
uiHeight := state.RenderUI(os.Stdout, getTermWidth())
|
||||||
|
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
@@ -108,13 +113,13 @@ func main() {
|
|||||||
state.SelectedCell = -1
|
state.SelectedCell = -1
|
||||||
fmt.Printf("\r\x1b[%dA", uiHeight)
|
fmt.Printf("\r\x1b[%dA", uiHeight)
|
||||||
fmt.Print("\x1b[J")
|
fmt.Print("\x1b[J")
|
||||||
state.RenderUI(os.Stdout)
|
state.RenderUI(os.Stdout, getTermWidth())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\r\x1b[%dA", uiHeight)
|
fmt.Printf("\r\x1b[%dA", uiHeight)
|
||||||
fmt.Print("\x1b[J")
|
fmt.Print("\x1b[J")
|
||||||
|
|
||||||
uiHeight = state.RenderUI(os.Stdout)
|
uiHeight = state.RenderUI(os.Stdout, getTermWidth())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+50
-5
@@ -32,6 +32,47 @@ type State struct {
|
|||||||
RebusConfirmation bool
|
RebusConfirmation bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RenderWithLineWrap(s string, width int) (out string, lines int) {
|
||||||
|
builder := strings.Builder{}
|
||||||
|
runes := []rune(s)
|
||||||
|
|
||||||
|
counter := 0
|
||||||
|
lines = 1
|
||||||
|
|
||||||
|
for i := 0; i < len(runes); i++ {
|
||||||
|
char := runes[i]
|
||||||
|
|
||||||
|
if char == '\n' {
|
||||||
|
builder.WriteString("\r\n")
|
||||||
|
lines++
|
||||||
|
counter = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if char == '\r' && i < len(runes)-1 && runes[i+1] == '\n' {
|
||||||
|
builder.WriteString("\r\n")
|
||||||
|
lines++
|
||||||
|
counter = 0
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.WriteRune(char)
|
||||||
|
counter++
|
||||||
|
|
||||||
|
if counter >= width && i < len(runes)-1 {
|
||||||
|
lines++
|
||||||
|
builder.WriteString("\r\n")
|
||||||
|
counter = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.WriteString("\r\n")
|
||||||
|
|
||||||
|
out = builder.String()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func NewState(puzzle *puz.Puzzle) *State {
|
func NewState(puzzle *puz.Puzzle) *State {
|
||||||
return &State{
|
return &State{
|
||||||
Puzzle: puzzle,
|
Puzzle: puzzle,
|
||||||
@@ -243,7 +284,7 @@ func (state *State) CheckWord(clue *puz.Clue) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *State) RenderUI(w io.Writer) int {
|
func (state *State) RenderUI(w io.Writer, width int) int {
|
||||||
if state.RebusConfirmation {
|
if state.RebusConfirmation {
|
||||||
fmt.Fprint(w, "This puzzle contains a rebus. This tool does not support rebuses.\r\nPress 'y' to continue anyway, or press any other key to exit.\r\n")
|
fmt.Fprint(w, "This puzzle contains a rebus. This tool does not support rebuses.\r\nPress 'y' to continue anyway, or press any other key to exit.\r\n")
|
||||||
return 2
|
return 2
|
||||||
@@ -258,12 +299,16 @@ func (state *State) RenderUI(w io.Writer) int {
|
|||||||
fmt.Fprint(w, state.RenderPuzzle()+"\r\n")
|
fmt.Fprint(w, state.RenderPuzzle()+"\r\n")
|
||||||
uiHeight += state.Puzzle.Height + 3
|
uiHeight += state.Puzzle.Height + 3
|
||||||
if state.SelectedClue != nil {
|
if state.SelectedClue != nil {
|
||||||
|
directionStr := "down"
|
||||||
if state.SelectedClue.Direction == puz.DirectionAcross {
|
if state.SelectedClue.Direction == puz.DirectionAcross {
|
||||||
fmt.Fprintf(w, "%d-across: %s\r\n\r\n", state.SelectedClue.Number, state.SelectedClue.Clue)
|
directionStr = "across"
|
||||||
} else {
|
|
||||||
fmt.Fprintf(w, "%d-down: %s\r\n\r\n", state.SelectedClue.Number, state.SelectedClue.Clue)
|
|
||||||
}
|
}
|
||||||
uiHeight += 2
|
|
||||||
|
clueStr, clueLines := RenderWithLineWrap(fmt.Sprintf("%d-%s: %s", state.SelectedClue.Number, directionStr, state.SelectedClue.Clue), width)
|
||||||
|
w.Write([]byte(clueStr))
|
||||||
|
uiHeight += clueLines
|
||||||
|
io.WriteString(w, "\r\n")
|
||||||
|
uiHeight += 1
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "%s\r\n", state.Puzzle.Copyright)
|
fmt.Fprintf(w, "%s\r\n", state.Puzzle.Copyright)
|
||||||
uiHeight += 1
|
uiHeight += 1
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package puz
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -16,6 +17,7 @@ type iPuzClue struct {
|
|||||||
Clue string `json:"clue"`
|
Clue string `json:"clue"`
|
||||||
}
|
}
|
||||||
type iPuzFile struct {
|
type iPuzFile struct {
|
||||||
|
Kind []string `json:"kind"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Author string `json:"author"`
|
Author string `json:"author"`
|
||||||
Copyright string `json:"copyright"`
|
Copyright string `json:"copyright"`
|
||||||
@@ -32,6 +34,12 @@ func ParseIPuz(file []byte) (*Puzzle, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(parsed.Kind) == 0 || !slices.ContainsFunc(parsed.Kind, func(k string) bool {
|
||||||
|
return strings.HasPrefix(k, "http://ipuz.org/crossword")
|
||||||
|
}) {
|
||||||
|
return nil, errors.New("ipuz file is not a crossword")
|
||||||
|
}
|
||||||
|
|
||||||
puzzle := new(Puzzle)
|
puzzle := new(Puzzle)
|
||||||
|
|
||||||
puzzle.Title = parsed.Title
|
puzzle.Title = parsed.Title
|
||||||
|
|||||||
+4
-3
@@ -39,7 +39,8 @@ func main() {
|
|||||||
io.WriteString(s, "\x1b[?25h")
|
io.WriteString(s, "\x1b[?25h")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
uiHeight := state.RenderUI(s)
|
pty, _, _ := s.Pty() // TODO: actually handle terminal size changes
|
||||||
|
uiHeight := state.RenderUI(s, pty.Window.Width)
|
||||||
|
|
||||||
scanner := bufio.NewScanner(s)
|
scanner := bufio.NewScanner(s)
|
||||||
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
@@ -71,14 +72,14 @@ func main() {
|
|||||||
state.SelectedCell = -1
|
state.SelectedCell = -1
|
||||||
fmt.Fprintf(s, "\r\x1b[%dA", uiHeight)
|
fmt.Fprintf(s, "\r\x1b[%dA", uiHeight)
|
||||||
fmt.Fprint(s, "\x1b[J")
|
fmt.Fprint(s, "\x1b[J")
|
||||||
state.RenderUI(s)
|
state.RenderUI(s, pty.Window.Width)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(s, "\r\x1b[%dA", uiHeight)
|
fmt.Fprintf(s, "\r\x1b[%dA", uiHeight)
|
||||||
fmt.Fprint(s, "\x1b[J")
|
fmt.Fprint(s, "\x1b[J")
|
||||||
|
|
||||||
uiHeight = state.RenderUI(s)
|
uiHeight = state.RenderUI(s, pty.Window.Width)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("%s disconnected\n", s.RemoteAddr().String())
|
log.Printf("%s disconnected\n", s.RemoteAddr().String())
|
||||||
|
|||||||
Reference in new issue
Block a user