diff --git a/README.md b/README.md index 6d035b8..aa6e3f4 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ menu categories: | **Tabletop** | 21 | Backgammon, Chess, Checkers, Go, Othello, Settlers of Catan, Ticket to Ride, Risk, Monopoly, Blokus, Labyrinth, Mahjong, Stratego, Battleship, Mastermind, Connect 4, Forbidden Island, Azul, Chinese Checkers, Mexican Train, Parchisi | | **Cards** | 18 | Cribbage, Gin Rummy, Rummikub, Canasta, Hearts, Uno, Phase 10, Skip-Bo, Go Fish, Old Maid, Nerts, Dominion, Splendor, Freecell, Solitaire Tour, Spire Climb, Zahtzee, Farkle | | **Casino** | 9 | Blackjack, Texas Hold 'Em, Baccarat, Pai Gow Poker, Video Poker, Craps, Roulette, Bingo, Slot Machines | -| **Word** | 15 | Wordle Race, Scrabble, Boggle, Ghost, Word Ladder, Word Search, Hangman, Spelling Bee, Sudoku, Mini Crossword, Tectonic, Bookwork, Kiitos, Tri-Ominoes, Jumble | +| **Word** | 15 | Wordle Race, Scrabble, Boggle, Ghost, Word Ladder, Word Search, Hangman, Spelling Bee, Sudoku, Mini Crossword, Tectonic, Bookworm, Kiitos, Tri-Ominoes, Jumble | | **Logic & Puzzle** | 14 | 2048, Rush Hour, Hexsweeper, Jell-o Monsters, Shift, Mahjong Match, Jewel Quest, Zuma, Bejeweled Blitz, Mini Motorways, Dot Link, Katamino, Genius Square, Block Fighter | | **Arcade, Console & PC** | 2 | Colorado Defense, Star Control | diff --git a/data/bookwork.json b/data/bookwork.json index b9eb391..d6e61dc 100644 --- a/data/bookwork.json +++ b/data/bookwork.json @@ -1,5 +1,12 @@ { "playerBaseHp": 100, + "steer": { + "wordBoostK": 1.0, + "classThreshold": 2, + "classDamp": 0.35, + "classBoost": 1.8, + "vowelBand": [0.30, 0.45] + }, "milestones": [ { "afterLevel": 5, "maxHpBonus": 10, "unlock": "potion" }, { "afterLevel": 10, "maxHpBonus": 10 }, diff --git a/src/data/gamesRegistry.js b/src/data/gamesRegistry.js index 76805c8..c359188 100644 --- a/src/data/gamesRegistry.js +++ b/src/data/gamesRegistry.js @@ -99,7 +99,7 @@ registerGame({ slug: 'ginrummy', name: 'Gin Rummy', category: 'cards', cardGame: registerGame({ slug: 'risk', name: 'Risk', category: 'tabletop', minPlayers: 2, maxPlayers: 6, minOpponents: 1, maxOpponents: 5, defaultOpponents: 3, hasTutorial: true, iconFrame: 54, defaultPlayfield: 'combat' }); registerGame({ slug: 'geniussquare', name: 'Genius Square', category: 'logic', minPlayers: 1, maxPlayers: 2, minOpponents: 1, maxOpponents: 1, iconFrame: 70 }); registerGame({ slug: 'katamino', name: 'Katamino', category: 'logic', minPlayers: 1, maxPlayers: 1, minOpponents: 0, maxOpponents: 0, iconFrame: 71 }); -registerGame({ slug: 'bookwork', name: 'Bookwork', category: 'word', minPlayers: 1, maxPlayers: 1, minOpponents: 0, maxOpponents: 0, iconFrame: 72 }); +registerGame({ slug: 'bookwork', name: 'Bookworm', category: 'word', minPlayers: 1, maxPlayers: 1, minOpponents: 0, maxOpponents: 0, iconFrame: 72 }); registerGame({ slug: 'paigow', name: 'Pai Gow Poker', category: 'casino', cardGame: true, minPlayers: 1, maxPlayers: 5, minOpponents: 0, maxOpponents: 4, defaultOpponents: 4, iconFrame: 73 }); registerGame({ slug: 'spireclimb', name: 'Spire Climb', category: 'cards', cardGame: true, minPlayers: 1, maxPlayers: 1, minOpponents: 0, maxOpponents: 0, hasTutorial: true, iconFrame: 74 }); registerGame({ slug: 'azul', name: 'Azul', category: 'tabletop', minPlayers: 2, maxPlayers: 4, minOpponents: 1, maxOpponents: 3, defaultOpponents: 3, hasTutorial: true, iconFrame: 75, defaultPlayfield: 'cherry' }); diff --git a/src/games/bookwork/BookworkGame.js b/src/games/bookwork/BookworkGame.js index f444c4a..5b92e50 100644 --- a/src/games/bookwork/BookworkGame.js +++ b/src/games/bookwork/BookworkGame.js @@ -12,6 +12,7 @@ import { computeMaxHp, isPotionUnlocked, } from './BookworkLogic.js'; import { getAttackDamage, getSpecialTile } from './BookworkAI.js'; +import { makeSteeredGrid, refillSteered, parseWordList } from './BookworkSteering.js'; const CELL = 96; const GRID_W = CELL * GRID_SIZE; @@ -39,7 +40,7 @@ export default class BookworkGame extends Phaser.Scene { constructor() { super('BookworkGame'); } init(data) { - this.gameDef = data.game ?? { slug: 'bookwork', name: 'Bookwork' }; + this.gameDef = data.game ?? { slug: 'bookwork', name: 'Bookworm' }; this.config = { playerBaseHp: 100, milestones: [], levels: [] }; this.bank = []; this.roster = []; @@ -56,6 +57,8 @@ export default class BookworkGame extends Phaser.Scene { this.bgTex = null; // Battle state this.grid = null; + this.wordSet = null; // steering dictionary (ENABLE words 3–15); null → unsteered + this.steerOpts = {}; this.tileObjs = null; this.selection = []; this.selGraphics = null; @@ -85,8 +88,16 @@ export default class BookworkGame extends Phaser.Scene { const raw = this.cache.json.get('bookwork'); if (raw) this.config = raw; + this.steerOpts = this.config.steer ?? {}; this.bank = (this.config.levels ?? []).slice().sort((a, b) => a.level - b.level); + // Load the steering dictionary (same ENABLE list the word validator uses). + // Non-fatal: if it fails, boards fall back to plain weighted-random. + try { + const res = await fetch('data/wordlists/enable1.txt'); + if (res.ok) this.wordSet = parseWordList(await res.text()); + } catch (_) { this.wordSet = null; } + try { const res = await fetch('data/opponents.json'); const json = await res.json(); @@ -279,7 +290,7 @@ export default class BookworkGame extends Phaser.Scene { this.clearLayer(); const cx = GAME_WIDTH / 2; - const title = this.add.text(cx, 84, 'BOOKWORK', { + const title = this.add.text(cx, 84, 'BOOKWORM', { fontFamily: 'Righteous', fontSize: '64px', color: COLORS.goldHex, }).setOrigin(0.5); const sub = this.add.text(cx, 138, 'Spell words from the letter grid to battle your way through 20 opponents.', { @@ -523,7 +534,7 @@ export default class BookworkGame extends Phaser.Scene { this.potionUnlocked = isPotionUnlocked(this.config, this.levelsCompleted); this.potionUsed = false; this.turnPhase = 'player'; - this.grid = makeGrid(); + this.grid = this.wordSet ? makeSteeredGrid(Math.random, this.wordSet, this.steerOpts) : makeGrid(); this.selection = []; this.drawBattleUI(); @@ -832,7 +843,9 @@ export default class BookworkGame extends Phaser.Scene { } // Refill grid - this.grid = clearAndRefill(this.grid, cells); + this.grid = this.wordSet + ? refillSteered(this.grid, cells, Math.random, this.wordSet, this.steerOpts) + : clearAndRefill(this.grid, cells); await this.animateRefill(cells); this.redrawAllTiles(); diff --git a/src/games/bookwork/BookworkLogic.js b/src/games/bookwork/BookworkLogic.js index 14238d0..e95cfad 100644 --- a/src/games/bookwork/BookworkLogic.js +++ b/src/games/bookwork/BookworkLogic.js @@ -1,7 +1,7 @@ export const GRID_SIZE = 5; // Weighted letter pool tuned for playability (vowel-rich, rare letters suppressed) -const LETTER_WEIGHTS = { +export const LETTER_WEIGHTS = { A:9, B:2, C:3, D:4, E:13, F:2, G:2, H:3, I:8, J:1, K:2, L:4, M:3, N:6, O:7, P:2, R:6, S:5, T:7, U:4, V:2, W:2, X:1, Y:3, Z:1, diff --git a/tools/verifyBookwork.js b/tools/verifyBookwork.js index ef2e4b3..e4ce238 100644 --- a/tools/verifyBookwork.js +++ b/tools/verifyBookwork.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -// verifyBookwork.js — engine tests for Bookwork +// verifyBookwork.js — engine tests for Bookworm import { readFileSync } from 'node:fs'; import {