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
|
||||
|
||||
- correctly calculate uiHeight with multi-line clues
|
||||
- timer
|
||||
- reveal grid/word
|
||||
- .puz checksums
|
||||
|
||||
+8
-3
@@ -13,6 +13,11 @@ import (
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func getTermWidth() int {
|
||||
w, _, _ := term.GetSize(int(os.Stdin.Fd()))
|
||||
return w
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
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.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
@@ -108,13 +113,13 @@ func main() {
|
||||
state.SelectedCell = -1
|
||||
fmt.Printf("\r\x1b[%dA", uiHeight)
|
||||
fmt.Print("\x1b[J")
|
||||
state.RenderUI(os.Stdout)
|
||||
state.RenderUI(os.Stdout, getTermWidth())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\r\x1b[%dA", uiHeight)
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return &State{
|
||||
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 {
|
||||
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
|
||||
@@ -258,12 +299,16 @@ func (state *State) RenderUI(w io.Writer) int {
|
||||
fmt.Fprint(w, state.RenderPuzzle()+"\r\n")
|
||||
uiHeight += state.Puzzle.Height + 3
|
||||
if state.SelectedClue != nil {
|
||||
directionStr := "down"
|
||||
if state.SelectedClue.Direction == puz.DirectionAcross {
|
||||
fmt.Fprintf(w, "%d-across: %s\r\n\r\n", state.SelectedClue.Number, state.SelectedClue.Clue)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%d-down: %s\r\n\r\n", state.SelectedClue.Number, state.SelectedClue.Clue)
|
||||
directionStr = "across"
|
||||
}
|
||||
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)
|
||||
uiHeight += 1
|
||||
|
||||
@@ -3,6 +3,7 @@ package puz
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -16,6 +17,7 @@ type iPuzClue struct {
|
||||
Clue string `json:"clue"`
|
||||
}
|
||||
type iPuzFile struct {
|
||||
Kind []string `json:"kind"`
|
||||
Title string `json:"title"`
|
||||
Author string `json:"author"`
|
||||
Copyright string `json:"copyright"`
|
||||
@@ -32,6 +34,12 @@ func ParseIPuz(file []byte) (*Puzzle, error) {
|
||||
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.Title = parsed.Title
|
||||
|
||||
+4
-3
@@ -39,7 +39,8 @@ func main() {
|
||||
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.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
@@ -71,14 +72,14 @@ func main() {
|
||||
state.SelectedCell = -1
|
||||
fmt.Fprintf(s, "\r\x1b[%dA", uiHeight)
|
||||
fmt.Fprint(s, "\x1b[J")
|
||||
state.RenderUI(s)
|
||||
state.RenderUI(s, pty.Window.Width)
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Fprintf(s, "\r\x1b[%dA", uiHeight)
|
||||
fmt.Fprint(s, "\x1b[J")
|
||||
|
||||
uiHeight = state.RenderUI(s)
|
||||
uiHeight = state.RenderUI(s, pty.Window.Width)
|
||||
}
|
||||
|
||||
log.Printf("%s disconnected\n", s.RemoteAddr().String())
|
||||
|
||||
Reference in new issue
Block a user