Implement Scrabble move generator (DAWG) with English and Russian rules
A Go library that returns every legal play ranked by score and scores or validates plays, using the Appel-Jacobson DAWG algorithm over github.com/iliadenisov/dafsa v1.1.0. - DAWG move generation (across / down / both), full tournament scoring with a per-tile breakdown; public Solver: GenerateMoves (ranked), ScorePlay, ValidatePlay. - Rulesets: English Scrabble, Russian Scrabble, Эрудит (parameterizable Ruleset). - cmd/builddict (build the DAWG from the dictionaries submodule), cmd/stress (self-play benchmark), selfplay engine; brute-force test oracle. - A GADDAG was implemented, benchmarked and removed (the DAWG was smaller and faster for a scoring solver); see RESULTS.md and ALGORITHM.md.
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
// Package encoding defines the compact byte conventions shared by the board, rack,
|
||||
// move output and (for letters) the dictionary graph.
|
||||
//
|
||||
// One uniform "symbol byte" is used everywhere:
|
||||
//
|
||||
// bits 0..5 the alphabet letter index plus one (1..63); 0 means "empty / no tile"
|
||||
// bit 6 reserved (unused)
|
||||
// bit 7 Blank — the tile is a blank standing for that letter; it scores 0
|
||||
//
|
||||
// The +1 offset lets 0 mean an empty board square. The same byte represents a board
|
||||
// cell, a placed tile and a rack tile; the graph stores raw letter indexes (without the
|
||||
// +1).
|
||||
package encoding
|
||||
|
||||
const (
|
||||
// Blank flags a tile as a blank standing for its letter; a blank scores 0.
|
||||
Blank byte = 0x80
|
||||
|
||||
// Empty is the value of an unoccupied board square.
|
||||
Empty byte = 0x00
|
||||
|
||||
letterBits byte = 0x3f // low 6 bits: letter index + 1
|
||||
)
|
||||
|
||||
// Cell builds the byte for a tile of the given alphabet letter index. When blank is
|
||||
// true the tile is marked as a blank (it scores 0).
|
||||
func Cell(letter byte, blank bool) byte {
|
||||
c := (letter + 1) & letterBits
|
||||
if blank {
|
||||
c |= Blank
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// IsEmpty reports whether a board cell is unoccupied.
|
||||
func IsEmpty(cell byte) bool { return cell&letterBits == 0 }
|
||||
|
||||
// Letter returns the alphabet letter index of a non-empty cell or tile byte. The
|
||||
// result is meaningless for an empty cell.
|
||||
func Letter(cell byte) byte { return (cell & letterBits) - 1 }
|
||||
|
||||
// IsBlank reports whether a cell or tile byte is a blank (scores 0).
|
||||
func IsBlank(cell byte) bool { return cell&Blank != 0 }
|
||||
@@ -0,0 +1,39 @@
|
||||
package encoding
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCellRoundTrip(t *testing.T) {
|
||||
for letter := range byte(26) {
|
||||
c := Cell(letter, false)
|
||||
if IsEmpty(c) {
|
||||
t.Errorf("Cell(%d,false) reports empty", letter)
|
||||
}
|
||||
if IsBlank(c) {
|
||||
t.Errorf("Cell(%d,false) reports blank", letter)
|
||||
}
|
||||
if got := Letter(c); got != letter {
|
||||
t.Errorf("Letter(Cell(%d,false)) = %d", letter, got)
|
||||
}
|
||||
|
||||
b := Cell(letter, true)
|
||||
if !IsBlank(b) {
|
||||
t.Errorf("Cell(%d,true) not blank", letter)
|
||||
}
|
||||
if got := Letter(b); got != letter {
|
||||
t.Errorf("Letter(Cell(%d,true)) = %d, want %d", letter, got, letter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
if !IsEmpty(Empty) {
|
||||
t.Error("IsEmpty(Empty) = false")
|
||||
}
|
||||
if IsEmpty(Cell(0, false)) {
|
||||
t.Error("IsEmpty(Cell('a')) = true")
|
||||
}
|
||||
// 'a' (index 0) must not collide with empty.
|
||||
if Cell(0, false) == Empty {
|
||||
t.Error("Cell('a') collides with Empty")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user