Have got win and failures working
This commit is contained in:
parent
d9fa154a01
commit
a8e42da6fd
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1
site/assets/data/shuffle_pattern.json
Normal file
1
site/assets/data/shuffle_pattern.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -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 = "";
|
||||||
|
|
18
site/assets/scripts/controllers/overlay.js
Normal file
18
site/assets/scripts/controllers/overlay.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 = " "
|
||||||
|
this.nextPuzzleButtonTarget.classList.add("hide");
|
||||||
|
|
||||||
window.dispatchEvent(new CustomEvent("resetKeyColors"));
|
window.dispatchEvent(new CustomEvent("resetKeyColors"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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"> </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">←</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">↵</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>
|
Loading…
Reference in a new issue