Have got win and failures working

This commit is contained in:
Leon Mika 2025-01-25 11:30:04 +11:00
parent d9fa154a01
commit a8e42da6fd
10 changed files with 255 additions and 28 deletions

View file

@ -2,21 +2,32 @@ package main
import ( import (
"bufio" "bufio"
"bytes"
"encoding/json" "encoding/json"
"flag" "flag"
"log" "log"
"math/rand/v2"
"os" "os"
"path/filepath"
"sort" "sort"
"time"
) )
type wordList struct { type wordList struct {
Words map[int][]string `json:"words"` Words map[int][]string `json:"words"`
} }
type shufflePattern struct {
Index map[int][]int `json:"index"`
}
func main() { func main() {
dictFile := flag.String("dict", "", "dictionary of word to prep") dictFile := flag.String("dict", "./dict/en_GB.dic", "dictionary of word to prep")
outDir := flag.String("out", "./site/assets/data", "output directory")
flag.Parse() flag.Parse()
r := rand.New(rand.NewPCG(uint64(time.Now().UnixNano()), uint64(time.Now().UnixNano())))
words := wordList{ words := wordList{
Words: make(map[int][]string), Words: make(map[int][]string),
} }
@ -29,11 +40,42 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
for _, word := range words.Words { for k, word := range words.Words {
log.Printf("Found %d words of length %v", len(word), k)
sort.Strings(word) sort.Strings(word)
} }
if err := json.NewEncoder(os.Stdout).Encode(words); err != nil { var wordData bytes.Buffer
if err := json.NewEncoder(&wordData).Encode(words); err != nil {
log.Fatal(err)
}
if err := os.WriteFile(filepath.Join(*outDir, "words.json"), wordData.Bytes(), 0644); err != nil {
log.Fatal(err)
}
// Generate a shuffle pattern
shp := shufflePattern{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
}
for x := 4; x < r.IntN(8)+4; x++ {
r.Shuffle(len(pattern), func(i, j int) {
pattern[i], pattern[j] = pattern[j], pattern[i]
})
}
// TODO: shuffle
shp.Index[k] = pattern
}
var patternData bytes.Buffer
if err := json.NewEncoder(&patternData).Encode(shp); err != nil {
log.Fatal(err)
}
if err := os.WriteFile(filepath.Join(*outDir, "shuffle_pattern.json"), patternData.Bytes(), 0644); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

File diff suppressed because one or more lines are too long

View file

@ -33,6 +33,16 @@ export default class extends Controller {
this.playfieldOutlet.tappedKey(key); this.playfieldOutlet.tappedKey(key);
} }
tapEnter(ev) {
ev.preventDefault();
this.playfieldOutlet.enterGuess();
}
tapBackspace(ev) {
ev.preventDefault();
this.playfieldOutlet.tappedBackspace();
}
resetKeyColors(ev) { resetKeyColors(ev) {
for (let keyElement of this.keyTargets) { for (let keyElement of this.keyTargets) {
keyElement.classList.value = ""; keyElement.classList.value = "";

View file

@ -0,0 +1,18 @@
import { Controller } from "https://unpkg.com/@hotwired/stimulus@v3.2.2/dist/stimulus.js"
export default class extends Controller {
static targets = ["message"];
showMessage(msg) {
this.messageTarget.innerText = msg;
this.element.classList.add("show");
if (this._waitTimer) {
clearTimeout(this._waitTimer);
}
this._waitTimer = setTimeout(() => {
this.element.classList.remove("show");
}, 3000);
}
}

View file

@ -5,7 +5,8 @@ import { WordSource } from "../models/words.js";
export default class extends Controller { export default class extends Controller {
static targets = ["row"]; static targets = ["row", "playfield", "topMessage", "nextPuzzleButton"];
static outlets = ["overlay"];
async connect() { async connect() {
this._wordSource = new WordSource(); this._wordSource = new WordSource();
@ -64,20 +65,30 @@ export default class extends Controller {
switch (results.guessResult) { switch (results.guessResult) {
case GUESS_RESULT.FOUL: case GUESS_RESULT.FOUL:
console.log("not a word!"); this.overlayOutlet.showMessage("Not a valid word.");
rowElem.replaceWith(this._buildPlayfieldRow(this._gameController.wordLength())); rowElem.replaceWith(this._buildPlayfieldRow(this._gameController.wordLength()));
this._activeLetter = 0; this._activeLetter = 0;
break; break;
case GUESS_RESULT.MISS: case GUESS_RESULT.MISS:
console.log("try again!");
this._colorizeRow(rowElem, results); this._colorizeRow(rowElem, results);
this._activeRowIndex += 1; this._activeRowIndex += 1;
if (this._activeRowIndex >= this._gameController.guesses()) {
this.topMessageTarget.innerText = this._gameController.currentWord().toUpperCase();
this.nextPuzzleButtonTarget.classList.remove("hide");
} else {
this._activeLetter = 0; this._activeLetter = 0;
}
break; break;
case GUESS_RESULT.WIN: case GUESS_RESULT.WIN:
console.log("CORRECT!"); this._colorizeRow(rowElem, results);
this.topMessageTarget.innerText = "Hooray! You did it.";
this.nextPuzzleButtonTarget.classList.remove("hide");
/*
if (this._gameController.nextWord()) { if (this._gameController.nextWord()) {
this._buildPlayfield(); this._buildPlayfield();
} else { } else {
@ -85,10 +96,20 @@ export default class extends Controller {
this._activeRowIndex = -1; this._activeRowIndex = -1;
this._colorizeRow(rowElem, results); this._colorizeRow(rowElem, results);
} }
*/
break; break;
} }
} }
async loadNextPuzzle(ev) {
ev.preventDefault();
if (await this._gameController.nextWord()) {
this._buildPlayfield();
} else {
this.overlayOutlet.showMessage("No more words available.");
}
}
_buildPlayfield() { _buildPlayfield() {
let rows = this._gameController.guesses(); let rows = this._gameController.guesses();
let wordLength = this._gameController.wordLength(); let wordLength = this._gameController.wordLength();
@ -101,7 +122,10 @@ export default class extends Controller {
newRows.push(this._buildPlayfieldRow(wordLength)); newRows.push(this._buildPlayfieldRow(wordLength));
} }
this.element.replaceChildren.apply(this.element, newRows); this.playfieldTarget.replaceChildren.apply(this.playfieldTarget, newRows);
this.topMessageTarget.innerHTML = "&nbsp;"
this.nextPuzzleButtonTarget.classList.add("hide");
window.dispatchEvent(new CustomEvent("resetKeyColors")); window.dispatchEvent(new CustomEvent("resetKeyColors"));
} }

View file

@ -2,9 +2,11 @@ import { Application, Controller } from "https://unpkg.com/@hotwired/stimulus@v3
import PlayfieldController from "./controllers/playfield.js"; import PlayfieldController from "./controllers/playfield.js";
import KeyboardController from "./controllers/keyboard.js"; import KeyboardController from "./controllers/keyboard.js";
import OverlayController from "./controllers/overlay.js";
window.Stimulus = Application.start(); window.Stimulus = Application.start();
Stimulus.register("playfield", PlayfieldController); Stimulus.register("playfield", PlayfieldController);
Stimulus.register("keyboard", KeyboardController); Stimulus.register("keyboard", KeyboardController);
Stimulus.register("overlay", OverlayController);

View file

@ -32,6 +32,11 @@ export class GameController {
return this._guesses; return this._guesses;
} }
currentWord() {
this._checkHasStarted();
return this._currentWord;
}
async nextWord() { async nextWord() {
this._currentWord = await this._wordSource.getCurrentWord(); this._currentWord = await this._wordSource.getCurrentWord();
this._guesses = 5; this._guesses = 5;

View file

@ -20,6 +20,7 @@ function binSearch(list, word) {
export class WordSource { export class WordSource {
constructor() { constructor() {
this._wordData = null; this._wordData = null;
this._pattern = null;
this._currentWord = null; this._currentWord = null;
} }
@ -38,7 +39,9 @@ export class WordSource {
} }
let words = await this._fetchAllWordsIfNecessary(); let words = await this._fetchAllWordsIfNecessary();
this._currentWord = words.words["4"][7]; let idx = this._pattern.index["4"][7];
this._currentWord = words.words["4"][idx];
return this._currentWord; return this._currentWord;
} }
@ -57,8 +60,8 @@ export class WordSource {
return this._wordData; return this._wordData;
} }
let res = await fetch("/assets/data/words.json"); this._wordData = await (await fetch("/assets/data/words.json")).json();
this._wordData = await res.json(); this._pattern = await (await fetch("/assets/data/shuffle_pattern.json")).json();
return this._wordData; return this._wordData;
} }
} }

View file

@ -1,15 +1,90 @@
:root {
--color-no-letter-bg: #666;
--color-no-letter-fg: #fff;
--color-has-letter-bg: #dd4;
--color-has-letter-fg: #000;
--color-right-letter-bg: #4b4;
--color-right-letter-fg: #fff;
}
body {
height: 100vh;
display: flex;
flex-direction: column;
}
main {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
}
main > div:first-child {
flex-grow: 1;
}
div.playfield {
text-align: center;
font-size: 1.5rem;
}
div.playfield div[data-playfield-target="topMessage"] {
font-weight: bold;
}
div.playfield div[data-playfield-target="playfield"] {
margin-block: 1rem;
font-size: 3rem;
}
div.keyboard > div {
display: flex;
justify-content: space-around;
}
div.keyboard button {
width: 9vw;
border-radius: 2px;
height: 4rem;
text-transform: uppercase;
background: #bbb;
color: #000;
border: solid 1px #444;
}
div.keyboard > div:nth-child(2) {
margin-inline: 2vw;
}
div.keyboard > div:nth-child(3) {
margin-inline: 4vw;
}
button[data-keyboard-target="key"].right-pos { button[data-keyboard-target="key"].right-pos {
background: green; background: var(--color-right-letter-bg);
color: var(--color-right-letter-fg);
} }
button[data-keyboard-target="key"].right-char { button[data-keyboard-target="key"].right-char {
background: yellow; background: var(--color-has-letter-bg);
color: var(--color-has-letter-fg);
} }
button[data-keyboard-target="key"].miss { button[data-keyboard-target="key"].miss {
background: grey; background: var(--color-no-letter-bg);
color: var(--color-no-letter-fg);
} }
div.playfield div.row {
display: flex;
justify-content: center;
}
div.playfield div.row span { div.playfield div.row span {
display: inline-block; display: inline-block;
@ -17,16 +92,54 @@ div.playfield div.row span {
height: 1.1em; height: 1.1em;
width: 1.1em; width: 1.1em;
line-height: 1.1em;
margin: 5px;
} }
div.playfield div.row span.right-pos { div.playfield div.row span.right-pos {
background: green; background: var(--color-right-letter-bg);
color: var(--color-right-letter-fg);
} }
div.playfield div.row span.right-char { div.playfield div.row span.right-char {
background: yellow; background: var(--color-has-letter-bg);
color: var(--color-has-letter-fg);
} }
div.playfield div.row span.miss { div.playfield div.row span.miss {
background: grey; background: var(--color-no-letter-bg);
color: var(--color-no-letter-fg);
}
div.overlay {
position: fixed;
bottom: 25%;
left: 10%;
right: 10%;
z-index: 10;
display: none;
justify-content: center;
}
div.overlay.show {
display: flex;
}
div.overlay-message {
font-size: 2rem;
line-height: 2.2rem;
text-align: center;
color: white;
background-color: rgba(0, 0, 0, 70%);
border-radius: 5px;
padding-block: 12px;
padding-inline: 20px;
}
.hide {
display: none;
} }

View file

@ -3,6 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
<link href="/assets/styles/main.css" rel="stylesheet"> <link href="/assets/styles/main.css" rel="stylesheet">
<title>Wordle Clone</title> <title>Wordle Clone</title>
</head> </head>
@ -12,11 +13,15 @@
</header> </header>
<main> <main>
<div data-controller="playfield" class="playfield"> <div class="playfield" data-controller="playfield"
data-playfield-overlay-outlet=".overlay">
<div data-playfield-target="topMessage">&nbsp;</div>
<div data-playfield-target="playfield"></div>
<button data-playfield-target="nextPuzzleButton" class="hide" data-action="playfield#loadNextPuzzle">Next Puzzle</button>
</div> </div>
<div class="keyboard" data-controller="keyboard"
<div data-controller="keyboard"
data-keyboard-playfield-outlet=".playfield" data-keyboard-playfield-outlet=".playfield"
data-action=" data-action="
keydown@window->keyboard#onKeyPress keydown@window->keyboard#onKeyPress
@ -47,6 +52,7 @@
<button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="l">l</button> <button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="l">l</button>
</div> </div>
<div> <div>
<button data-keyboard-target="key" data-action="keyboard#tapBackspace">&larr;</button>
<button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="z">z</button> <button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="z">z</button>
<button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="x">x</button> <button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="x">x</button>
<button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="c">c</button> <button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="c">c</button>
@ -54,8 +60,7 @@
<button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="b">b</button> <button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="b">b</button>
<button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="n">n</button> <button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="n">n</button>
<button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="m">m</button> <button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="m">m</button>
<button data-keyboard-target="key" data-action="keyboard#tapEnter">enter</button> <button data-keyboard-target="key" data-action="keyboard#tapEnter">&crarr;</button>
<button data-keyboard-target="key" data-action="keyboard#tapBackspace">back</button>
</div> </div>
</div> </div>
@ -70,6 +75,10 @@
</template> </template>
</main> </main>
<div class="overlay" data-controller="overlay">
<div class="overlay-message" data-overlay-target="message">I am the overlay</div>
</div>
<script src="/assets/scripts/main.js" type="module"></script> <script src="/assets/scripts/main.js" type="module"></script>
</body> </body>
</html> </html>