Documentation
¶
Index ¶
- Variables
- func ForceErrorDuringPuzzleLoad(t *testing.T, err error)
- func ForceErrorDuringSettingsLoad(t *testing.T, err error)
- func ForceErrorDuringSettingsSave(t *testing.T, err error)
- func ForceErrorDuringStateLoad(t *testing.T, err error)
- func ForceErrorDuringStateSave(t *testing.T, err error)
- func ForcePuzzleToBeLoaded(t *testing.T, filename string)
- func GetAllChannels(conn redis.Conn) ([]model.Channel, error)
- func GetEvents(pool *redis.Pool, registry *pubsub.Registry) http.HandlerFunc
- func NewEventSubscription(t *testing.T, registry *pubsub.Registry, channel string) <-chan pubsub.Event
- func NewRedisConnection(t *testing.T, pool *redis.Pool) redis.Conn
- func NewTestRouter(t *testing.T) (chi.Router, *redis.Pool, *pubsub.Registry)
- func ParseAnswer(answer string) ([]string, error)
- func ParseClue(clue string) (int, string, error)
- func ParseXWordInfoClue(s string) (int, string, error)
- func ReadLength(in io.Reader, length uint16) ([]byte, error)
- func ReadUntil(in io.Reader, delimiter byte) ([]byte, error)
- func RegisterRoutes(router chi.Router, pool *redis.Pool)
- func RegisterRoutesWithRegistry(r chi.Router, pool *redis.Pool, registry *pubsub.Registry)
- func SetSettings(conn redis.Conn, channel string, settings Settings) error
- func SetState(conn redis.Conn, channel string, state State) error
- func SettingsKey(name string) string
- func ShowClue(registry *pubsub.Registry) http.HandlerFunc
- func StateKey(name string) string
- func ToggleStatus(pool *redis.Pool, registry *pubsub.Registry) http.HandlerFunc
- func UpdateAnswer(pool *redis.Pool, registry *pubsub.Registry) http.HandlerFunc
- func UpdatePuzzle(pool *redis.Pool, registry *pubsub.Registry) http.HandlerFunc
- func UpdateSetting(pool *redis.Pool, registry *pubsub.Registry) http.HandlerFunc
- type CRC
- type ClueVisibility
- type PuzFile
- func (f *PuzFile) Convert() (*Puzzle, error)
- func (f *PuzFile) GlobalChecksum() uint16
- func (f *PuzFile) HeaderChecksum() uint16
- func (f *PuzFile) MaskedChecksum() [8]byte
- func (f *PuzFile) StringsChecksum(crc CRC) CRC
- func (f *PuzFile) Unscramble(key int) bool
- func (f *PuzFile) Version() (int, int)
- type PuzFileExtension
- type Puzzle
- func LoadFromEncodedPuzFile(encoded string) (*Puzzle, error)
- func LoadFromNewYorkTimes(date string) (*Puzzle, error)
- func LoadFromPuzFileURL(url string) (*Puzzle, error)
- func LoadFromWallStreetJournal(date string) (*Puzzle, error)
- func LoadPuzFile(in io.Reader) (*Puzzle, error)
- func LoadTestPuzzle(t *testing.T, filename string) *Puzzle
- func ParseXWordInfoResponse(in io.Reader) (*Puzzle, error)
- type Settings
- type State
- type XWordInfoPuzzle
Constants ¶
This section is empty.
Variables ¶
var MagicNumber = []byte("ACROSS&DOWN\000")
var StateTTL = 4 * time.Hour
StateTTL determines how long a particular crossword's solve state should remain in redis in the absence of any activity.
var UnshiftTables = [10]map[byte]byte{
{
'A': 'A', 'B': 'B', 'C': 'C', 'D': 'D', 'E': 'E', 'F': 'F', 'G': 'G',
'H': 'H', 'I': 'I', 'J': 'J', 'K': 'K', 'L': 'L', 'M': 'M', 'N': 'N',
'O': 'O', 'P': 'P', 'Q': 'Q', 'R': 'R', 'S': 'S', 'T': 'T', 'U': 'U',
'V': 'V', 'W': 'W', 'X': 'X', 'Y': 'Y', 'Z': 'Z',
},
{
'A': 'Z', 'B': 'A', 'C': 'B', 'D': 'C', 'E': 'D', 'F': 'E', 'G': 'F',
'H': 'G', 'I': 'H', 'J': 'I', 'K': 'J', 'L': 'K', 'M': 'L', 'N': 'M',
'O': 'N', 'P': 'O', 'Q': 'P', 'R': 'Q', 'S': 'R', 'T': 'S', 'U': 'T',
'V': 'U', 'W': 'V', 'X': 'W', 'Y': 'X', 'Z': 'Y',
},
{
'A': 'Y', 'B': 'Z', 'C': 'A', 'D': 'B', 'E': 'C', 'F': 'D', 'G': 'E',
'H': 'F', 'I': 'G', 'J': 'H', 'K': 'I', 'L': 'J', 'M': 'K', 'N': 'L',
'O': 'M', 'P': 'N', 'Q': 'O', 'R': 'P', 'S': 'Q', 'T': 'R', 'U': 'S',
'V': 'T', 'W': 'U', 'X': 'V', 'Y': 'W', 'Z': 'X',
},
{
'A': 'X', 'B': 'Y', 'C': 'Z', 'D': 'A', 'E': 'B', 'F': 'C', 'G': 'D',
'H': 'E', 'I': 'F', 'J': 'G', 'K': 'H', 'L': 'I', 'M': 'J', 'N': 'K',
'O': 'L', 'P': 'M', 'Q': 'N', 'R': 'O', 'S': 'P', 'T': 'Q', 'U': 'R',
'V': 'S', 'W': 'T', 'X': 'U', 'Y': 'V', 'Z': 'W',
},
{
'A': 'W', 'B': 'X', 'C': 'Y', 'D': 'Z', 'E': 'A', 'F': 'B', 'G': 'C',
'H': 'D', 'I': 'E', 'J': 'F', 'K': 'G', 'L': 'H', 'M': 'I', 'N': 'J',
'O': 'K', 'P': 'L', 'Q': 'M', 'R': 'N', 'S': 'O', 'T': 'P', 'U': 'Q',
'V': 'R', 'W': 'S', 'X': 'T', 'Y': 'U', 'Z': 'V',
},
{
'A': 'V', 'B': 'W', 'C': 'X', 'D': 'Y', 'E': 'Z', 'F': 'A', 'G': 'B',
'H': 'C', 'I': 'D', 'J': 'E', 'K': 'F', 'L': 'G', 'M': 'H', 'N': 'I',
'O': 'J', 'P': 'K', 'Q': 'L', 'R': 'M', 'S': 'N', 'T': 'O', 'U': 'P',
'V': 'Q', 'W': 'R', 'X': 'S', 'Y': 'T', 'Z': 'U',
},
{
'A': 'U', 'B': 'V', 'C': 'W', 'D': 'X', 'E': 'Y', 'F': 'Z', 'G': 'A',
'H': 'B', 'I': 'C', 'J': 'D', 'K': 'E', 'L': 'F', 'M': 'G', 'N': 'H',
'O': 'I', 'P': 'J', 'Q': 'K', 'R': 'L', 'S': 'M', 'T': 'N', 'U': 'O',
'V': 'P', 'W': 'Q', 'X': 'R', 'Y': 'S', 'Z': 'T',
},
{
'A': 'T', 'B': 'U', 'C': 'V', 'D': 'W', 'E': 'X', 'F': 'Y', 'G': 'Z',
'H': 'A', 'I': 'B', 'J': 'C', 'K': 'D', 'L': 'E', 'M': 'F', 'N': 'G',
'O': 'H', 'P': 'I', 'Q': 'J', 'R': 'K', 'S': 'L', 'T': 'M', 'U': 'N',
'V': 'O', 'W': 'P', 'X': 'Q', 'Y': 'R', 'Z': 'S',
},
{
'A': 'S', 'B': 'T', 'C': 'U', 'D': 'V', 'E': 'W', 'F': 'X', 'G': 'Y',
'H': 'Z', 'I': 'A', 'J': 'B', 'K': 'C', 'L': 'D', 'M': 'E', 'N': 'F',
'O': 'G', 'P': 'H', 'Q': 'I', 'R': 'J', 'S': 'K', 'T': 'L', 'U': 'M',
'V': 'N', 'W': 'O', 'X': 'P', 'Y': 'Q', 'Z': 'R',
},
{
'A': 'R', 'B': 'S', 'C': 'T', 'D': 'U', 'E': 'V', 'F': 'W', 'G': 'X',
'H': 'Y', 'I': 'Z', 'J': 'A', 'K': 'B', 'L': 'C', 'M': 'D', 'N': 'E',
'O': 'F', 'P': 'G', 'Q': 'H', 'R': 'I', 'S': 'J', 'T': 'K', 'U': 'L',
'V': 'M', 'W': 'N', 'X': 'O', 'Y': 'P', 'Z': 'Q',
},
}
These tables help to perform unshift operations quickly by precomputing the 10 possible unshift operations we might have to do.
var XWordInfoHeaders = map[string]string{
"Referer": "https://www.xwordinfo.com/JSON",
}
Functions ¶
func ForceErrorDuringPuzzleLoad ¶
ForceErrorDuringLoad sets up an error to be returned when an attempt is made to load a puzzle.
func ForceErrorDuringSettingsLoad ¶
ForceErrorDuringSettingsLoad sets up an error to be returned when an attempt is made to load settings.
func ForceErrorDuringSettingsSave ¶
ForceErrorDuringSettingsSave sets up an error to be returned when an attempt is made to save settings.
func ForceErrorDuringStateLoad ¶
ForceErrorDuringStateLoad sets up an error to be returned when an attempt is made to load state.
func ForceErrorDuringStateSave ¶
ForceErrorDuringStateSave sets up an error to be returned when an attempt is made to save state.
func ForcePuzzleToBeLoaded ¶
ForcePuzzleToBeLoaded sets up a cached version of a puzzle using a file from the testdata directory.
func GetAllChannels ¶
GetAllChannels returns a slice of model.Channel instances for each crossword that contains state in the database. If there are no active channels then an empty slice is returned. This method does not update the expiration times of any state instance.
func GetEvents ¶
GetEvents establishes an event stream with a client. An event stream is server side event stream (SSE) with a client's browser that allows one way communication from the server to the client. Clients that call into this handler will keep an open connection open to the server waiting to receive events as JSON objects. The server can send events to all clients of a channel using the pubsub.Registry's Publish method.
func NewEventSubscription ¶
func NewEventSubscription(t *testing.T, registry *pubsub.Registry, channel string) <-chan pubsub.Event
NewEventSubscription will return a channel of events that are subscribed to the specified channel. The subscription will be configured to automatically unsubscribe when the test completes.
func NewRedisConnection ¶
NewRedisConnection will return a connection to the provided connection pool. The returned connection will be configured to automatically close when the test completes.
func NewTestRouter ¶
NewTestRouter will return a router configured with a redis pool and pubsub registry and wired together along with all of the routes for a spelling bee puzzle.
func ParseAnswer ¶
ParseAnswer parses an answer string into a list of cell values. The answer string is parsed in such a way to look for cell values that contain multiple characters (aka a rebus). It does this by looking for parenthesized groups of letters. For example the string "(red)velvet" would be interpreted as ["red", "v", "e", "l", "v", "e", "t"] and fit as the answer for a 7 cell clue.
Additionally if an answer contains a "." character anywhere that particular cell will be left empty. This allows strings like "....s" to be entered to indicate that the answer is known to be plural, but the other letters aren't known yet. Within a rebus cell "." characters are kept as-is.
Whitespace within answers is removed and ignored. This makes it more natural to specify answers like "red velvet cake".
func ParseClue ¶
ParseClue parses the identifier of a clue into its number and direction. If the clue cannot be parsed for some reason then an error will be returned.
func ParseXWordInfoClue ¶
ParseXWordInfoClue parses the text of a clue from the New York Times into its clue number and clue text.
func SetSettings ¶
SetSettings will write settings for the provided channel name. If the settings can't be properly written then an error will be returned.
func SetState ¶
SetState writes the state for a channel's crossword solve to redis. If the state can't be property written then an error will be returned.
func SettingsKey ¶
SettingsKey returns the key that should be used in redis to store a particular channel's settings.
func ShowClue ¶
func ShowClue(registry *pubsub.Registry) http.HandlerFunc
ShowClue sends an event to all clients of a channel requesting that they update their view to make the specified clue visible. If the specified clue isn't structured as a proper clue number and direction than an error will be returned.
func StateKey ¶
StateKey returns the key that should be used in redis to store a particular crossword solve's state.
func ToggleStatus ¶
ToggleStatus changes the status of the current crossword solve to a new status. This effectively toggles between the solving and paused statuses as long as the solve is in a state that can be paused or resumed.
func UpdateAnswer ¶
UpdateAnswer applies an answer to a given clue in the current crossword solve.
func UpdatePuzzle ¶
UpdatePuzzle changes the crossword puzzle that's currently being solved for a channel.
func UpdateSetting ¶
UpdateSetting changes a specified crossword setting to a new value.
Types ¶
type ClueVisibility ¶
type ClueVisibility int
ClueVisibility is an enumeration representing which clues should be shown.
const ( AllCluesVisible ClueVisibility = iota NoCluesVisible OnlyDownCluesVisible OnlyAcrossCluesVisible )
func (ClueVisibility) MarshalJSON ¶
func (v ClueVisibility) MarshalJSON() ([]byte, error)
func (ClueVisibility) String ¶
func (v ClueVisibility) String() string
func (*ClueVisibility) UnmarshalJSON ¶
func (v *ClueVisibility) UnmarshalJSON(bs []byte) error
type PuzFile ¶
type PuzFile struct { Header struct { GlobalChecksum uint16 MagicNumber [12]byte // ACROSS&DOWN\000 (null terminated) HeaderChecksum uint16 MaskedChecksum [8]byte Version [4]byte // e.g. 1.2\0 (null terminated) UnscrambledChecksum uint16 Width uint8 Height uint8 NumClues uint16 UnknownBitmask uint16 ScrambledTag uint16 // contains filtered or unexported fields } Solution []byte Cells []byte Title []byte Author []byte Copyright []byte Clues [][]byte Notes []byte Extensions map[string]*PuzFileExtension }
PuzFile represents the binary file format of a .puz file that contains a crossword puzzle.
In short it contains a fixed size header that outlines the various checksums and dimensions of the puzzle followed by the variable length portions of the puzzle such as the solution and clues. Lastly the file can contain a set of extension sections to the file format to represent things like rebus squares, circled squares, etc.
Of note is that the file format itself doesn't actually contain clue numbers, they must be derived from the structure of the black cells within the puzzle's grid.
Details on the file format can be found at:
https://code.google.com/archive/p/puz/wikis/FileFormat.wiki
Implementations of parsers can be found at:
https://github.com/alexdej/puzpy https://github.com/kobolabs/puz
func (*PuzFile) Convert ¶
Convert takes the in-memory representation of a .puz file and converts it into a Puzzle object.
func (*PuzFile) GlobalChecksum ¶
func (*PuzFile) HeaderChecksum ¶
func (*PuzFile) MaskedChecksum ¶
func (*PuzFile) StringsChecksum ¶
func (*PuzFile) Unscramble ¶
Unscramble attempts to undo a scramble operation with the provided key. If the operation succeeds then the solution will be updated and true will be returned.
type PuzFileExtension ¶
type PuzFileExtension struct { Header struct { Code [4]byte Length uint16 Checksum uint16 } Data []byte }
PuzFileExtension represents the contents of an extension within a .puz file. An extension contains a fixed size header followed by variable length data that is interpreted according to which type of extension it is.
func (*PuzFileExtension) Checksum ¶
func (e *PuzFileExtension) Checksum() uint16
type Puzzle ¶
type Puzzle struct { // The number of rows in the crossword grid. Rows int `json:"rows"` // The number of columns in the crossword grid. Cols int `json:"cols"` // The title of the crossword. Title string `json:"title"` // The publisher of the crossword. Publisher string `json:"publisher"` // The date that the crossword was published. PublishedDate time.Time `json:"published"` // The name of the author(s) of the crossword. Author string `json:"author"` // The cells of the crossword as a 2D list, entries are the letter (or letters // in the case of a rebus) that belong in the cell. If a cell cannot be // inputted into then it will contain the empty string. The lists are first // indexed by the row coordinate of the cell and then by the column coordinate // of the cell. Cells [][]string `json:"cells,omitempty"` // The block attribute for each of the cells in the crossword as a 2D list. // Cells that cannot be inputted into will contain an entry of true, all other // cells will contain an entry of false. Like cells the 2D list is first // indexed by the row coordinate of the cell and then by the column // coordinate. CellBlocks [][]bool `json:"cell_blocks"` // The clue numbers for each of the cells in the crossword as a 2D list. // Cells that cannot be inputted into or that don't have a clue number will // contain an entry of 0. Like cells the 2D list is first indexed by the row // coordinate of the cell and then by the column coordinate. CellClueNumbers [][]int `json:"cell_clue_numbers"` // Whether or not a cell contains a circle for all of the cells in the // crossword as a 2D list. Cells that should have a circle rendered in them // appear as true and those that shouldn't have a circle rendered in them will // appear as false. Like cells the 2D list is first indexed by the row // coordinate of the cell and then by the column coordinate. CellCircles [][]bool `json:"cell_circles"` // The clues for the across answers indexed by the clue number. CluesAcross map[int]string `json:"clues_across"` // The clues for the down answers indexed by the clue number. CluesDown map[int]string `json:"clues_down"` // The notes for the clues of this crossword. Often there is something // visually done when the crossword is published in a newspaper but that can't // be done online. These notes describe the visual change so that the // crossword can be solved online. Notes string `json:"notes"` }
Puzzle represents a crossword puzzle. The puzzle is comprised of a grid which has dimensions (rows x cols) and demonstrates which cells of the crossword are available for placing letters into and which are not. Additionally the puzzle contains a set of clues organized by number and whether or not they fill in cells going across or down. Lastly a puzzle has various bits of interesting metadata such as the publication that the crossword is from, the date that it was published as well as the author(s).
func LoadFromEncodedPuzFile ¶
LoadFromEncodedPuzFile will base64 decode the input and then attempt to load the resulting binary as a .puz file into a Puzzle object.
func LoadFromNewYorkTimes ¶
LoadFromNewYorkTimes loads a crossword puzzle from the New York Times for a particular date.
This method uses the xwordinfo.com JSON API to load a New York Times crossword puzzle. While organized slightly differently from the XPF API the returned data is mostly the same. Documentation for the JSON API can be found here: https://www.xwordinfo.com/JSON/
If the puzzle cannot be loaded or parsed then an error is returned.
func LoadFromPuzFileURL ¶
LoadFromPuzFileURL will take a URL and retrieve it and load it into a Puzzle object.
If the URL cannot be retrieved or the puzzle parsed then an error is returned.
func LoadFromWallStreetJournal ¶
LoadFromWallStreetJournal loads a crossword puzzle from the Wall Street Journal for a particular date.
This method downloads a .puz file and loads it into a Puzzle object. We do this in particular on the server side instead of within the client because the herbach.dnsalias.com site unfortunately is only HTTP and we can't load resources from a non-HTTPS site in the browser.
If the puzzle cannot be loaded or parsed then an error is returned.
func LoadPuzFile ¶
LoadPuzFile parses a binary .puz file into a Puzzle object.
func LoadTestPuzzle ¶
LoadTestPuzzle loads a puzzle from the testdata directory.
func ParseXWordInfoResponse ¶
ParseXWordInfoResponse converts a JSON response from xwordinfo.com into a puzzle object.
func (*Puzzle) GetAnswerCoordinates ¶
GetAnswerCoordinates returns the min/max x/y coordinates for a clue. If the clue doesn't exist then an error is returned.
func (*Puzzle) WithoutSolution ¶
WithoutSolution returns a copy of the puzzle that has the solution cells missing. This makes it suitable to pass to a client that shouldn't know the answers to the puzzle.
type Settings ¶
type Settings struct { // When enabled only correct answers will be filled into the puzzle grid. OnlyAllowCorrectAnswers bool `json:"only_allow_correct_answers"` // Which clues should be shown to users. Can be all of the clues, none of the // clues, only across clues or only down clues. CluesToShow ClueVisibility `json:"clues_to_show"` // What font size should the clues be rendered with. ClueFontSize model.FontSize `json:"clue_font_size"` // Whether or not notes field should shown. ShowNotes bool `json:"show_notes"` }
Settings represents the optional behaviors that can be enabled or disabled by a streamer for their channel's crossword solves.
type State ¶
type State struct { // The status of the channel's crossword solve. Status model.Status `json:"status"` // The crossword puzzle that's being solved. May not always be present, for // example when the state is being serialized to be sent to the browser. Puzzle *Puzzle `json:"puzzle,omitempty"` // The currently filled in cells of the crossword. Cells [][]string `json:"cells"` // Whether or not an across clue with a given clue number has had an answer // filled in. AcrossCluesFilled map[int]bool `json:"across_clues_filled"` // Whether or not a down clue with a given clue number has had an answer // filled in. DownCluesFilled map[int]bool `json:"down_clues_filled"` // The time that we last started or resumed solving the puzzle. If the // channel has not yet started solving the puzzle or is in a non-playing state // this will be nil. LastStartTime *time.Time `json:"last_start_time,omitempty"` // The total time spent on solving the puzzle up to the last start time. TotalSolveDuration model.Duration `json:"total_solve_duration"` }
State represents the state of an active channel that is attempting to solve a crossword.
func GetState ¶
GetState loads the state for a crossword solve from redis. If the state can't be loaded then an error will be returned. If there is no state, then the zero value will be returned. After a state is read, its expiration time is automatically updated.
func NewState ¶
NewState creates a new crossword puzzle state that has been properly initialized with the puzzle corresponding to the provided filename.
func (*State) ApplyAnswer ¶
ApplyAnswer applies an answer for a clue to the state. If the clue cannot be identified or the answer doesn't fit property (too short or too long) then an error will be returned. If the onlyCorrect parameter is true then only correct cells will be permitted and an error is returned if any part of the answer is incorrect or would remove a correct cell.
func (*State) ClearIncorrectCells ¶
ClearIncorrectCells will look at each filled in cell of the crossword and clear it if it is filled in with an incorrect answer. The AcrossCluesFilled and DownCluesFilled fields will also be updated to indicate any clues that are now unanswered due to cleared cells.
func (*State) UpdateFilledClues ¶
UpdateFilledClues looks at each clue in the puzzle and determines if a complete answer has been provided for the clue, if so then the corresponding entry in AcrossCluesFilled or DownCluesFilled will be set to true. This method doesn't check that the provided answer is correct, just that one is present.
type XWordInfoPuzzle ¶
type XWordInfoPuzzle struct { Title string `json:"title"` Author string `json:"author"` Publisher string `json:"publisher"` Date string `json:"date"` Notepad string `json:"notepad"` JNotes string `json:"jnotes"` Size struct { Rows int `json:"rows"` Cols int `json:"cols"` } `json:"size"` Grid []string `json:"grid"` GridNums []int `json:"gridnums"` Circles []int `json:"circles"` Clues struct { Across []string `json:"across"` Down []string `json:"down"` } `json:"clues"` Answers struct { Across []string `json:"across"` Down []string `json:"down"` } `json:"answers"` }
XWordInfoPuzzle is a representation of the response from the xwordinfo.com JSON API when querying for a puzzle.