Moved to an easier dictionary and added invalid word guesses back
All checks were successful
/ publish (push) Successful in 1m19s

Also added a spinner
This commit is contained in:
Leon Mika 2025-04-05 09:33:22 +11:00
parent 36b079681c
commit 566f55ed12
7 changed files with 105 additions and 57 deletions

View file

@ -23,13 +23,11 @@ const maxWordLength = 7
var validWordRegex = regexp.MustCompile(`^[a-z]+$`) var validWordRegex = regexp.MustCompile(`^[a-z]+$`)
type wordList struct { type dataStruct struct {
Words map[int][]string `json:"words"` VersionID string `json:"versionId"`
} GuessWords map[int][]string `json:"guessWords"`
OtherWords map[int][]string `json:"otherWords"`
type shufflePattern struct { ShufflePattern map[int][]int `json:"shufflePattern"`
ID string `json:"id"`
Index map[int][]int `json:"index"`
} }
func main() { func main() {
@ -39,49 +37,52 @@ func main() {
r := rand.New(rand.NewPCG(uint64(time.Now().UnixNano()), uint64(time.Now().UnixNano()))) r := rand.New(rand.NewPCG(uint64(time.Now().UnixNano()), uint64(time.Now().UnixNano())))
words := wordList{ data := dataStruct{
Words: make(map[int][]string), VersionID: gonanoid.MustID(12),
GuessWords: make(map[int][]string),
OtherWords: make(map[int][]string),
ShufflePattern: make(map[int][]int),
} }
wordSet := make(map[string]bool) guessWords := make(map[string]bool)
if err := scanSuitableWords(*dictFile, func(word string) { otherWords := make(map[string]bool)
if err := scanSuitableWords(*dictFile, func(easy bool, word string) {
w := strings.TrimSpace(word) w := strings.TrimSpace(word)
if !validWordRegex.MatchString(w) { if !validWordRegex.MatchString(w) {
return return
} }
if len(w) >= 4 && len(w) <= maxWordLength { if len(w) >= 4 && len(w) <= maxWordLength {
wordSet[word] = true if easy {
guessWords[word] = true
} else {
otherWords[word] = true
}
} }
}); err != nil { }); err != nil {
log.Fatal(err) log.Fatal(err)
} }
for w := range wordSet { for w := range guessWords {
words.Words[len(w)] = append(words.Words[len(w)], w) data.GuessWords[len(w)] = append(data.GuessWords[len(w)], w)
}
for w := range otherWords {
data.OtherWords[len(w)] = append(data.OtherWords[len(w)], w)
} }
for k, word := range words.Words { for k, word := range data.GuessWords {
log.Printf("Found %d words of length %v", len(word), k) log.Printf("Found %d guess words of length %v", len(word), k)
sort.Strings(word)
}
for k, word := range data.OtherWords {
log.Printf("Found %d other words of length %v", len(word), k)
sort.Strings(word) sort.Strings(word)
} }
var wordData bytes.Buffer for k := range data.GuessWords {
if err := json.NewEncoder(&wordData).Encode(words); err != nil { pattern := make([]int, len(data.GuessWords[k]))
log.Fatal(err) for i := range data.GuessWords[k] {
}
if err := os.WriteFile(filepath.Join(*outDir, "words.json"), wordData.Bytes(), 0644); err != nil {
log.Fatal(err)
}
// Generate a shuffle pattern
shp := shufflePattern{
ID: gonanoid.MustID(12),
Index: make(map[int][]int),
}
for k := range words.Words {
pattern := make([]int, len(words.Words[k]))
for i := range words.Words[k] {
pattern[i] = i pattern[i] = i
} }
@ -92,19 +93,19 @@ func main() {
} }
// TODO: shuffle // TODO: shuffle
shp.Index[k] = pattern data.ShufflePattern[k] = pattern
} }
var patternData bytes.Buffer var wordData bytes.Buffer
if err := json.NewEncoder(&patternData).Encode(shp); err != nil { if err := json.NewEncoder(&wordData).Encode(data); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := os.WriteFile(filepath.Join(*outDir, "shuffle_pattern.json"), patternData.Bytes(), 0644); err != nil { if err := os.WriteFile(filepath.Join(*outDir, "data.json"), wordData.Bytes(), 0644); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
func scanSuitableWords(dictDir string, withWord func(word string)) error { func scanSuitableWords(dictDir string, withWord func(easy bool, word string)) error {
if err := scanSuitableWordsFromWordListHTML(filepath.Join(dictDir, "oxford-word-list.htm"), withWord); err != nil { if err := scanSuitableWordsFromWordListHTML(filepath.Join(dictDir, "oxford-word-list.htm"), withWord); err != nil {
return err return err
} }
@ -120,7 +121,7 @@ func scanSuitableWords(dictDir string, withWord func(word string)) error {
return nil return nil
} }
func scanSuitableWordsFromWordListHTML(dictFile string, withWord func(word string)) error { func scanSuitableWordsFromWordListHTML(dictFile string, withWord func(easy bool, word string)) error {
f, err := os.Open(dictFile) f, err := os.Open(dictFile)
if err != nil { if err != nil {
return err return err
@ -133,12 +134,12 @@ func scanSuitableWordsFromWordListHTML(dictFile string, withWord func(word strin
} }
dom.Find("table.t tbody tr td.t:nth-child(2)").Each(func(i int, s *goquery.Selection) { dom.Find("table.t tbody tr td.t:nth-child(2)").Each(func(i int, s *goquery.Selection) {
withWord(s.Text()) withWord(true, s.Text())
}) })
return nil return nil
} }
func scanSuitableWordsFromOxford3000(dictFile string, withWord func(word string)) error { func scanSuitableWordsFromOxford3000(dictFile string, withWord func(easy bool, word string)) error {
f, err := os.Open(dictFile) f, err := os.Open(dictFile)
if err != nil { if err != nil {
return err return err
@ -147,20 +148,20 @@ func scanSuitableWordsFromOxford3000(dictFile string, withWord func(word string)
scanner := bufio.NewScanner(f) scanner := bufio.NewScanner(f)
for scanner.Scan() { for scanner.Scan() {
withWord(scanner.Text()) withWord(true, scanner.Text())
} }
return scanner.Err() return scanner.Err()
} }
func scanSuitableWordsFromEnGB(dictFile, affFile string, withWord func(word string)) error { func scanSuitableWordsFromEnGB(dictFile, affFile string, withWord func(easy bool, word string)) error {
words, err := script.Exec(fmt.Sprintf("unmunch '%v' '%v'", dictFile, affFile)).String() words, err := script.Exec(fmt.Sprintf("unmunch '%v' '%v'", dictFile, affFile)).String()
if err != nil { if err != nil {
return err return err
} }
for _, word := range strings.Split(words, "\n") { for _, word := range strings.Split(words, "\n") {
withWord(word) withWord(false, word)
} }
return nil return nil
} }

File diff suppressed because one or more lines are too long

View file

@ -5,7 +5,7 @@ import { WordSource } from "../models/words.js";
export default class extends Controller { export default class extends Controller {
static targets = ["row", "playfield", "topMessage", "nextPuzzleButtons"]; static targets = ["row", "playfield", "topMessage", "nextPuzzleButtons", "loader"];
static outlets = ["overlay"]; static outlets = ["overlay"];
async connect() { async connect() {
@ -15,6 +15,8 @@ export default class extends Controller {
await this._gameController.start(); await this._gameController.start();
this._buildPlayfield(); this._buildPlayfield();
this.loaderTarget.classList.add("hide");
} }
tappedKey(key) { tappedKey(key) {
@ -109,11 +111,16 @@ export default class extends Controller {
async loadNextPuzzle(ev) { async loadNextPuzzle(ev) {
ev.preventDefault(); ev.preventDefault();
this.loaderTarget.classList.remove("hide");
if (await this._gameController.nextWord()) { if (await this._gameController.nextWord()) {
this._buildPlayfield(); this._buildPlayfield();
} else { } else {
this.overlayOutlet.showMessage("No more words available."); this.overlayOutlet.showMessage("No more words available.");
} }
this.loaderTarget.classList.add("hide");
} }
_showWin() { _showWin() {

View file

@ -149,6 +149,18 @@ export class GameController {
let misses = {}; let misses = {};
let hits = {}; let hits = {};
if (!this._wordSource.isWord(guess)) {
hits = {};
for (let i = 0; i < guess.length; i++) {
hits[guess[i]] = MARKERS.ATTEMPTED;
}
return {
hits: hits,
guessResult: GUESS_RESULT.FOUL,
};
}
for (let i = 0; i < guess.length; i++) { for (let i = 0; i < guess.length; i++) {
if (guess[i] == this._currentWord[i]) { if (guess[i] == this._currentWord[i]) {
markers[i] = MARKERS.RIGHT_POS; markers[i] = MARKERS.RIGHT_POS;

View file

@ -19,38 +19,38 @@ function binSearch(list, word) {
export class WordSource { export class WordSource {
constructor() { constructor() {
this._dataVersion = null;
this._wordData = null; this._wordData = null;
this._otherWords = null;
this._pattern = null; this._pattern = null;
// this._currentWord = null;
// this._progressionState=
} }
isWord(word) { isWord(word) {
let list = this._wordData.words[word.length.toString()]; if (!this._wordData || !this._otherWords) {
if (!list) {
return false; return false;
} }
return binSearch(list, word); return binSearch(this._wordData[word.length.toString()], word) ||
binSearch(this._otherWords[word.length.toString()], word);
} }
async needToResetProgression(prog) { async needToResetProgression(prog) {
await this._fetchAllWordsIfNecessary(); await this._fetchAllWordsIfNecessary();
return !prog || !prog.shuffleId || this._pattern.id !== prog.shuffleId; return !prog || !prog.shuffleId || this._dataVersion !== prog.shuffleId;
} }
async getPattenShuffleID() { async getPattenShuffleID() {
return this._pattern.id; return this._dataVersion;
} }
async getCurrentWord(prog) { async getCurrentWord(prog) {
let words = await this._fetchAllWordsIfNecessary(); await this._fetchAllWordsIfNecessary();
let wordLengthKey = prog.wordLength + ""; let wordLengthKey = prog.wordLength + "";
let wordIndex = prog.wordIndex[wordLengthKey]; let wordIndex = prog.wordIndex[wordLengthKey];
let idx = this._pattern.index[wordLengthKey][wordIndex]; let idx = this._pattern[wordLengthKey][wordIndex];
return words.words[wordLengthKey][idx]; return this._wordData[wordLengthKey][idx];
} }
async _fetchAllWordsIfNecessary() { async _fetchAllWordsIfNecessary() {
@ -58,8 +58,11 @@ export class WordSource {
return this._wordData; return this._wordData;
} }
this._wordData = await (await fetch("/assets/data/words.json")).json(); let data = await (await fetch("/assets/data/data.json")).json();
this._pattern = await (await fetch("/assets/data/shuffle_pattern.json")).json();
return this._wordData; this._dataVersion = data["versionId"];
this._wordData = data["guessWords"];
this._otherWords = data["otherWords"];
this._pattern = data["shufflePattern"];
} }
} }

View file

@ -218,3 +218,25 @@ div.overlay-message {
.hide { .hide {
display: none !important; display: none !important;
} }
.loader {
margin-top: 6rem;
width: 48px;
height: 48px;
border: 5px solid #FFF;
border-bottom-color: var(--color-no-letter-bg);
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View file

@ -21,6 +21,8 @@
<div data-playfield-target="topMessage">&nbsp;</div> <div data-playfield-target="topMessage">&nbsp;</div>
<div data-playfield-target="playfield"></div> <div data-playfield-target="playfield"></div>
<span data-playfield-target="loader" class="loader"></span>
<div data-playfield-target="nextPuzzleButtons" class="hide"> <div data-playfield-target="nextPuzzleButtons" class="hide">
<button class="secondary" data-action="playfield#loadDef">Define</button> <button class="secondary" data-action="playfield#loadDef">Define</button>
<button data-action="playfield#loadNextPuzzle">Next Puzzle</button> <button data-action="playfield#loadNextPuzzle">Next Puzzle</button>