Add toggleable rules panel to Tents game
- Introduce a slide-in "How to Play" panel listing the four Tents rules, kept in sync with the engine's own rule comment so the UI can't drift. - Add a Rules button in the HUD parked where the panel's Close button will appear, so opening reads as one control morphing rather than two controls. - Animate the panel open/close and swap visibility/interactivity between the Rules and Close buttons to avoid stacking both in the same spot.
This commit is contained in:
parent
9d82073e5a
commit
e3c764a981
|
|
@ -13,6 +13,16 @@ import {
|
|||
|
||||
const FONT_D = 'Righteous';
|
||||
const FONT_B = '"Julius Sans One"';
|
||||
const RULES_W = 420;
|
||||
|
||||
// The same four rules the engine enforces (TentsLogic.js rule comment) —
|
||||
// kept here as plain strings so the panel can never drift from the model.
|
||||
const RULES_TEXT = [
|
||||
'Every tent must sit next to a tree — up, down, left, or right (no diagonals).',
|
||||
"It's a one-to-one pairing: each tree gets exactly one tent, and each tent belongs to exactly one tree.",
|
||||
'Tents can never touch another tent, not even corner to corner.',
|
||||
'The numbers along the top and side are the exact tent count for that column and row — no more, no less.',
|
||||
];
|
||||
|
||||
// ── Color helpers (day → night lerps drive the whole scene) ─────────────────
|
||||
|
||||
|
|
@ -75,6 +85,7 @@ export default class TentsGame extends Phaser.Scene {
|
|||
this.sky = { t: 0 }; // 0 = midday, 1 = starry night
|
||||
this.won = false;
|
||||
this.winPanelShown = false;
|
||||
this.rulesOpen = false;
|
||||
this.difficulty = 'porch';
|
||||
this.hover = null; // {c, r}
|
||||
this.embers = [];
|
||||
|
|
@ -107,6 +118,7 @@ export default class TentsGame extends Phaser.Scene {
|
|||
|
||||
this.buildHud();
|
||||
this.hudLayer.setVisible(false);
|
||||
this.buildRulesPanel();
|
||||
this.buildSelectScreen();
|
||||
|
||||
this.events.on('shutdown', () => {
|
||||
|
|
@ -209,7 +221,14 @@ export default class TentsGame extends Phaser.Scene {
|
|||
this.btnReset = mk('Reset Tents', 700, () => this.resetTents(), 'ghost');
|
||||
this.btnMenu = mk('Menu', 780, () => this.scene.start('GameMenu'), 'ghost');
|
||||
|
||||
this.hudLayer.add([this.btnNew, this.btnAnswer, this.btnReset, this.btnMenu]);
|
||||
// Parked at the same screen position the rules panel's Close button sits
|
||||
// at once open (GAME_WIDTH - RULES_W/2, GAME_HEIGHT - 80), so opening the
|
||||
// panel reads as this button "becoming" Close rather than two separate
|
||||
// controls.
|
||||
this.btnRules = new Button(this, GAME_WIDTH - RULES_W / 2, GAME_HEIGHT - 80, 'Rules',
|
||||
() => this.toggleRules(), { width: 200, height: 56, variant: 'ghost' }).setDepth(31);
|
||||
|
||||
this.hudLayer.add([this.btnNew, this.btnAnswer, this.btnReset, this.btnMenu, this.btnRules]);
|
||||
}
|
||||
|
||||
makeText(str, x, y, font, size, color) {
|
||||
|
|
@ -219,6 +238,68 @@ export default class TentsGame extends Phaser.Scene {
|
|||
}).setOrigin(0, 0).setDepth(30);
|
||||
}
|
||||
|
||||
// ── Rules panel (toggled in-play; parked off-screen until opened) ──────────
|
||||
|
||||
buildRulesPanel() {
|
||||
const panel = this.add.container(GAME_WIDTH, 0).setDepth(35);
|
||||
|
||||
const bg = this.add.graphics();
|
||||
bg.fillStyle(0x101024, 0.95);
|
||||
bg.fillRect(0, 0, RULES_W, GAME_HEIGHT);
|
||||
bg.lineStyle(3, 0xffc46a, 0.9);
|
||||
bg.lineBetween(0, 0, 0, GAME_HEIGHT);
|
||||
panel.add(bg);
|
||||
|
||||
// Pushed down ~100px so the title doesn't sit under the soundtrack
|
||||
// controls (MusicPlayer's skip/mute buttons + track name) in the
|
||||
// top-right corner, which render above this panel's depth.
|
||||
panel.add(this.add.text(RULES_W / 2, 150, 'How to Play', {
|
||||
fontFamily: FONT_D, fontSize: '36px', color: '#ffd76a',
|
||||
stroke: '#3a2a14', strokeThickness: 6,
|
||||
}).setOrigin(0.5));
|
||||
|
||||
let y = 218;
|
||||
RULES_TEXT.forEach((line, i) => {
|
||||
panel.add(this.add.text(24, y, `${i + 1}.`, {
|
||||
fontFamily: FONT_D, fontSize: '22px', color: '#ffd76a',
|
||||
}).setOrigin(0, 0));
|
||||
const body = this.add.text(58, y, line, {
|
||||
fontFamily: FONT_B, fontSize: '20px', color: '#fdf6e3',
|
||||
wordWrap: { width: RULES_W - 82 }, lineSpacing: 4,
|
||||
}).setOrigin(0, 0);
|
||||
panel.add(body);
|
||||
y += body.height + 34;
|
||||
});
|
||||
|
||||
const closeBtn = new Button(this, RULES_W / 2, GAME_HEIGHT - 80, 'Close',
|
||||
() => this.toggleRules(), { width: 200, height: 56, variant: 'ghost' });
|
||||
panel.add(closeBtn);
|
||||
|
||||
this.rulesPanel = panel;
|
||||
}
|
||||
|
||||
toggleRules() {
|
||||
this.rulesOpen = !this.rulesOpen;
|
||||
this.tweens.add({
|
||||
targets: this.rulesPanel,
|
||||
x: this.rulesOpen ? GAME_WIDTH - RULES_W : GAME_WIDTH,
|
||||
duration: 220,
|
||||
ease: 'Quad.Out',
|
||||
});
|
||||
// The panel's own Close button sits in this same screen spot once open,
|
||||
// so hide (and un-click) the Rules button rather than stacking both.
|
||||
this.btnRules.setVisible(!this.rulesOpen);
|
||||
if (this.rulesOpen) {
|
||||
this.btnRules.disableInteractive();
|
||||
} else {
|
||||
this.btnRules.setInteractive({
|
||||
useHandCursor: true,
|
||||
hitArea: new Phaser.Geom.Rectangle(0, 0, 200, 56),
|
||||
hitAreaCallback: Phaser.Geom.Rectangle.Contains,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ── Select screen ──────────────────────────────────────────────────────────
|
||||
|
||||
buildSelectScreen() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue