mirror of
https://github.com/tiennm99/nntv.git
synced 2026-10-16 02:15:39 +00:00
feat: apply pixel-art design assets across sprites, tiles, hud, and scenes
This commit is contained in:
1 parent
6a1b3ec572
commit
fb2f72f85b
14 files changed
+1207
-59
No files matched your search
@@ -1,4 +1,13 @@
|
||||
<script>
|
||||
import Pixel from '../lib/pixel/Pixel.svelte';
|
||||
import {
|
||||
TILE_EMPTY, TILE_EMPTY_PAL,
|
||||
TILE_WALL, TILE_WALL_PAL,
|
||||
TILE_GOAL, TILE_GOAL_PAL,
|
||||
TILE_LIT, TILE_LIT_PAL,
|
||||
TILE_PREVIEW, TILE_PREVIEW_PAL,
|
||||
} from '../lib/pixel/art-tiles.js';
|
||||
|
||||
let { cells = [], rows = 6, cols = 6, cellSize = 50, previewCells = new Set(),
|
||||
detectedCell = null, oncellclick } = $props();
|
||||
|
||||
@@ -13,6 +22,14 @@
|
||||
function isDetected(cell) {
|
||||
return detectedCell && detectedCell.row === cell.row && detectedCell.col === cell.col;
|
||||
}
|
||||
|
||||
// Resolve the tile art/palette for a cell. Priority: lit > goal > wall > empty.
|
||||
function tileFor(cell) {
|
||||
if (cell.isLight) return { art: TILE_LIT, pal: TILE_LIT_PAL };
|
||||
if (cell.isGoal) return { art: TILE_GOAL, pal: TILE_GOAL_PAL };
|
||||
if (cell.isWall) return { art: TILE_WALL, pal: TILE_WALL_PAL };
|
||||
return { art: TILE_EMPTY, pal: TILE_EMPTY_PAL };
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -22,18 +39,25 @@
|
||||
style="grid-template-columns: repeat({cols}, {cellSize}px); grid-template-rows: repeat({rows}, {cellSize}px);"
|
||||
>
|
||||
{#each cells as cell (cell.row * cols + cell.col)}
|
||||
{@const tile = tileFor(cell)}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions a11y_interactive_supports_focus -->
|
||||
<div
|
||||
tabindex="-1"
|
||||
class="cell"
|
||||
class:wall={cell.isWall}
|
||||
class:goal={cell.isGoal}
|
||||
class:lit={cell.isLight}
|
||||
class:preview={previewCells.has(`${cell.row},${cell.col}`) && !cell.isLight}
|
||||
class:detected-flash={isDetected(cell)}
|
||||
role="gridcell"
|
||||
aria-label={cellLabel(cell)}
|
||||
onclick={() => oncellclick?.(cell.row, cell.col)}
|
||||
></div>
|
||||
>
|
||||
<Pixel art={tile.art} palette={tile.pal} width={cellSize} height={cellSize} />
|
||||
{#if previewCells.has(`${cell.row},${cell.col}`) && !cell.isLight}
|
||||
<div class="preview-overlay">
|
||||
<Pixel art={TILE_PREVIEW} palette={TILE_PREVIEW_PAL} width={cellSize} height={cellSize} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -44,22 +68,23 @@
|
||||
position: relative;
|
||||
}
|
||||
.cell {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--grid-empty);
|
||||
border: 1px solid var(--grid-border);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
.cell.wall { background: var(--grid-wall); cursor: default; }
|
||||
.cell.goal { background: var(--grid-goal); }
|
||||
.cell.lit { background: var(--grid-lit); }
|
||||
.cell.preview { background: rgba(255, 234, 0, 0.15); border-color: rgba(255, 234, 0, 0.3); }
|
||||
.cell.wall { cursor: default; }
|
||||
.preview-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.cell.detected-flash {
|
||||
animation: cell-flash 0.4s ease-out;
|
||||
}
|
||||
@keyframes cell-flash {
|
||||
0% { background: #ff0000; }
|
||||
100% { background: var(--grid-lit); }
|
||||
0% { filter: brightness(2) hue-rotate(-60deg); }
|
||||
100% { filter: none; }
|
||||
}
|
||||
</style>
|
||||
@@ -2,6 +2,9 @@
|
||||
import { getText } from '../lib/localization.js';
|
||||
import { isMuted, setMuted, playClick } from '../lib/audio.js';
|
||||
import Button from './Button.svelte';
|
||||
import Pixel from '../lib/pixel/Pixel.svelte';
|
||||
import { HEART_ART, HEART_ART_EMPTY, HEART_PAL,
|
||||
ICON_UNDO, ICON_EYE, ICON_PAUSE, ICON_PAL } from '../lib/pixel/art-ui.js';
|
||||
let { lives = 3, level = 1, turns = 0, showPreview = false, canUndo = false,
|
||||
ontogglepreview, onpause, onmenu, onundo, onshowcontrols } = $props();
|
||||
let muted = $state(isMuted());
|
||||
@@ -11,20 +14,33 @@
|
||||
setMuted(muted);
|
||||
if (!muted) playClick();
|
||||
}
|
||||
|
||||
const MAX_LIVES = 3;
|
||||
let hearts = $derived(Array.from({ length: MAX_LIVES }, (_, i) => i < lives));
|
||||
</script>
|
||||
|
||||
<div class="hud">
|
||||
<div class="hud-left">
|
||||
<span>{getText('lives')}{lives}</span>
|
||||
<div class="lives" aria-label="{getText('lives')}{lives}">
|
||||
{#each hearts as full}
|
||||
<Pixel art={full ? HEART_ART : HEART_ART_EMPTY} palette={HEART_PAL} width={20} height={20} />
|
||||
{/each}
|
||||
</div>
|
||||
<span class="turns">Turns: {turns}</span>
|
||||
</div>
|
||||
<div class="hud-right">
|
||||
<span>{getText('level')}{level}</span>
|
||||
<Button text={canUndo ? 'Z' : '-'} onclick={onundo} small disabled={!canUndo} />
|
||||
<span class="level-label">{getText('level')}{level}</span>
|
||||
<button class="icon-btn" onclick={onundo} disabled={!canUndo} aria-label="Undo">
|
||||
<Pixel art={ICON_UNDO} palette={ICON_PAL} width={20} height={20} />
|
||||
</button>
|
||||
<Button text={muted ? 'MUTE' : 'SND'} onclick={toggleMute} small />
|
||||
<Button text={showPreview ? 'V:ON' : 'V:OFF'} onclick={ontogglepreview} small />
|
||||
<button class="icon-btn" class:active={showPreview} onclick={ontogglepreview} aria-label="Toggle preview">
|
||||
<Pixel art={ICON_EYE} palette={ICON_PAL} width={20} height={20} />
|
||||
</button>
|
||||
<Button text="?" onclick={onshowcontrols} small />
|
||||
<Button text={getText('pause')} onclick={onpause} small />
|
||||
<button class="icon-btn" onclick={onpause} aria-label={getText('pause')}>
|
||||
<Pixel art={ICON_PAUSE} palette={ICON_PAL} width={20} height={20} />
|
||||
</button>
|
||||
<Button text={getText('menu')} onclick={onmenu} small />
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,5 +59,25 @@
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.lives {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
align-items: center;
|
||||
}
|
||||
.turns { color: var(--text-secondary); font: var(--font-small); }
|
||||
.level-label { font: var(--font-ui); color: var(--text-primary); }
|
||||
.icon-btn {
|
||||
background: var(--btn-default);
|
||||
border: 2px solid var(--btn-border);
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.icon-btn:hover:not(:disabled) { background: var(--btn-hover); }
|
||||
.icon-btn:disabled { opacity: 0.4; cursor: default; }
|
||||
.icon-btn.active { background: var(--btn-hover); border-color: var(--text-accent); }
|
||||
</style>
|
||||
@@ -1,10 +1,19 @@
|
||||
<script>
|
||||
import Pixel from '../lib/pixel/Pixel.svelte';
|
||||
import { GUARD_SPRITES } from '../lib/pixel/art-characters.js';
|
||||
|
||||
let { guard, cellSize = 50 } = $props();
|
||||
let top = $derived(guard.row * cellSize + cellSize / 2);
|
||||
let left = $derived(guard.col * cellSize + cellSize / 2);
|
||||
let size = $derived(Math.floor(cellSize / 2));
|
||||
let size = $derived(Math.floor(cellSize * 0.95));
|
||||
let rotation = $derived(guard.direction * 90);
|
||||
let hasDirection = $derived(guard.type === 'rotating' || guard.type === 'patrolling' || guard.type === 'chaser');
|
||||
let hasDirection = $derived(
|
||||
guard.type === 'rotating' || guard.type === 'patrolling' || guard.type === 'chaser'
|
||||
);
|
||||
let sprite = $derived(GUARD_SPRITES[guard.type]);
|
||||
let activePal = $derived(
|
||||
guard.type === 'blinking' && !guard.isOn ? sprite.palOff : sprite.pal
|
||||
);
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -13,6 +22,9 @@
|
||||
class:alert={guard.type === 'chaser' && guard.isChasing}
|
||||
style="top: {top}px; left: {left}px; width: {size}px; height: {size}px;"
|
||||
>
|
||||
{#if sprite}
|
||||
<Pixel art={sprite.art} palette={activePal} width={size} height={size} />
|
||||
{/if}
|
||||
{#if hasDirection}
|
||||
<div class="direction-indicator" style="transform: rotate({rotation}deg);"></div>
|
||||
{/if}
|
||||
@@ -21,28 +33,26 @@
|
||||
<style>
|
||||
.guard {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: top 100ms linear, left 100ms linear;
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.guard.static { background: var(--guard-static); }
|
||||
.guard.rotating { background: var(--guard-rotating); }
|
||||
.guard.blinking { background: var(--guard-blinking); }
|
||||
.guard.blinking.off { background: var(--guard-blinking-off); }
|
||||
.guard.patrolling { background: var(--guard-patrolling); }
|
||||
.guard.chaser { background: var(--guard-chaser, #ff6622); }
|
||||
.guard.chaser.alert { background: #ff2200; box-shadow: 0 0 8px #ff2200; }
|
||||
.guard.mirror { background: var(--guard-mirror); border-radius: 4px; transform: translate(-50%, -50%) rotate(45deg); }
|
||||
.guard.alert {
|
||||
filter: drop-shadow(0 0 6px #ff2200);
|
||||
}
|
||||
.guard.off {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.direction-indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
top: 6%;
|
||||
left: 50%;
|
||||
width: 2px;
|
||||
height: 50%;
|
||||
background: white;
|
||||
width: 3px;
|
||||
height: 40%;
|
||||
background: var(--cream, #fff4d6);
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.8);
|
||||
transform-origin: bottom center;
|
||||
translate: -50% 0;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
<script>
|
||||
import Pixel from '../lib/pixel/Pixel.svelte';
|
||||
import { RABBIT_ART, RABBIT_PAL } from '../lib/pixel/art-characters.js';
|
||||
let { row = 0, col = 0, cellSize = 50, shake = false } = $props();
|
||||
let top = $derived(row * cellSize + cellSize / 2);
|
||||
let left = $derived(col * cellSize + cellSize / 2);
|
||||
let size = $derived(Math.floor(cellSize / 1.5));
|
||||
let size = $derived(Math.floor(cellSize * 0.9));
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="player"
|
||||
class:shake
|
||||
style="top: {top}px; left: {left}px; width: {size}px; height: {size}px;"
|
||||
></div>
|
||||
>
|
||||
<Pixel art={RABBIT_ART} palette={RABBIT_PAL} width={size} height={size} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.player {
|
||||
position: absolute;
|
||||
background: var(--player-color);
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: top 100ms linear, left 100ms linear;
|
||||
z-index: 10;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { fade } from 'svelte/transition';
|
||||
import { getText } from '../lib/localization.js';
|
||||
import Button from './Button.svelte';
|
||||
import Pixel from '../lib/pixel/Pixel.svelte';
|
||||
import { ICON_UNDO, ICON_REDO, ICON_EYE, ICON_ARROW, ICON_PAL } from '../lib/pixel/art-ui.js';
|
||||
let { onclose } = $props();
|
||||
</script>
|
||||
|
||||
@@ -9,11 +11,25 @@
|
||||
<div class="popup">
|
||||
<h2>{getText('controlsTitle')}</h2>
|
||||
<div class="controls-list">
|
||||
<div class="control-row"><kbd>Arrow Keys</kbd><span>/ WASD — {getText('controlMove')}</span></div>
|
||||
<div class="control-row"><kbd>Space</kbd><span>— {getText('controlWait')}</span></div>
|
||||
<div class="control-row"><kbd>V</kbd><span>— {getText('controlPreview')}</span></div>
|
||||
<div class="control-row"><kbd>Z</kbd><span>— {getText('controlUndo')}</span></div>
|
||||
<div class="control-row"><kbd>Y</kbd><span>— {getText('controlRedo')}</span></div>
|
||||
<div class="control-row">
|
||||
<span class="icon"><Pixel art={ICON_ARROW} palette={ICON_PAL} width={20} height={20} /></span>
|
||||
<kbd>Arrow Keys</kbd><span>/ WASD — {getText('controlMove')}</span>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<kbd>Space</kbd><span>— {getText('controlWait')}</span>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<span class="icon"><Pixel art={ICON_EYE} palette={ICON_PAL} width={20} height={20} /></span>
|
||||
<kbd>V</kbd><span>— {getText('controlPreview')}</span>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<span class="icon"><Pixel art={ICON_UNDO} palette={ICON_PAL} width={20} height={20} /></span>
|
||||
<kbd>Z</kbd><span>— {getText('controlUndo')}</span>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<span class="icon"><Pixel art={ICON_REDO} palette={ICON_PAL} width={20} height={20} /></span>
|
||||
<kbd>Y</kbd><span>— {getText('controlRedo')}</span>
|
||||
</div>
|
||||
<div class="control-row"><kbd>Click</kbd><span>/ Tap — {getText('controlTap')}</span></div>
|
||||
<div class="control-row"><kbd>Swipe</kbd><span>— {getText('controlSwipe')}</span></div>
|
||||
</div>
|
||||
@@ -40,7 +56,7 @@
|
||||
background: var(--bg-panel);
|
||||
padding: 24px 32px;
|
||||
border-radius: 8px;
|
||||
max-width: 360px;
|
||||
max-width: 400px;
|
||||
}
|
||||
h2 {
|
||||
font: var(--font-heading);
|
||||
@@ -60,6 +76,11 @@
|
||||
font: var(--font-small);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.icon {
|
||||
display: inline-flex;
|
||||
width: 24px;
|
||||
justify-content: center;
|
||||
}
|
||||
kbd {
|
||||
background: var(--btn-default);
|
||||
border: 1px solid var(--btn-border);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<script>
|
||||
// Pixel-art renderer — strings+palette → SVG <rect>s with run-length merge.
|
||||
// `art`: array of equal-length strings. `palette`: { [char]: hexColor }. '.' / ' ' = transparent.
|
||||
let { art, palette, scale = 6, bg = null, width = null, height = null, pixelated = true, style = '' } = $props();
|
||||
|
||||
let rows = $derived(art?.length ?? 0);
|
||||
let cols = $derived(art?.[0]?.length ?? 0);
|
||||
|
||||
let rects = $derived.by(() => {
|
||||
if (!art || !palette) return [];
|
||||
const out = [];
|
||||
for (let r = 0; r < rows; r++) {
|
||||
let c = 0;
|
||||
const row = art[r];
|
||||
while (c < cols) {
|
||||
const ch = row[c];
|
||||
if (ch === '.' || ch === ' ') { c++; continue; }
|
||||
let run = 1;
|
||||
while (c + run < cols && row[c + run] === ch) run++;
|
||||
const color = palette[ch];
|
||||
if (color) out.push({ x: c, y: r, w: run, color });
|
||||
c += run;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
});
|
||||
|
||||
let w = $derived(width ?? cols * scale);
|
||||
let h = $derived(height ?? rows * scale);
|
||||
</script>
|
||||
|
||||
<svg
|
||||
width={w}
|
||||
height={h}
|
||||
viewBox="0 0 {cols} {rows}"
|
||||
shape-rendering="crispEdges"
|
||||
style="display:block; image-rendering: {pixelated ? 'pixelated' : 'auto'}; {style}"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
{#if bg}<rect x="0" y="0" width={cols} height={rows} fill={bg} />{/if}
|
||||
{#each rects as r}
|
||||
<rect x={r.x} y={r.y} width={r.w} height="1" fill={r.color} />
|
||||
{/each}
|
||||
</svg>
|
||||
@@ -0,0 +1,330 @@
|
||||
// 32×32 pixel-art sprites for player, guards (veggies), and princess.
|
||||
// Primary colors mirror theme.css guard colors for gameplay readability.
|
||||
import { NNTV } from './palette.js';
|
||||
|
||||
export const RABBIT_ART = [
|
||||
'................................',
|
||||
'................................',
|
||||
'.......KK........KKKK...........',
|
||||
'......KFFK......KFFFFK..........',
|
||||
'......KFFK......KFFFFK..........',
|
||||
'......KFCK......KFFCFK..........',
|
||||
'......KFCK......KFFCFK..........',
|
||||
'.......KFK......KFFFK...........',
|
||||
'.......KKKKKKKKKFFFFK...........',
|
||||
'......KFFFFFFFFFFFFFFK..........',
|
||||
'.....KFFFFFFFFFFFFFFFFK.........',
|
||||
'....KFSSSSSSSSSSSSSSSSFK........',
|
||||
'...KFSSSSSSSSSSSSSSSSSSFK.......',
|
||||
'...KFSKKKKSSSSSSSSKKKKSFK.......',
|
||||
'...KFSKWWKSSSSSSSSKWWKSFK.......',
|
||||
'...KFSKWEKSSSSSSSSKWEKSFK.......',
|
||||
'...KFSKKKKSSSSSSSSKKKKSFK.......',
|
||||
'...KFSSSSSSSKRRKSSSSSSSFK.......',
|
||||
'...KFSSSSSSKRRRRKSSSSSSFK.......',
|
||||
'....KFSSSSSSSRRSSSSSSSFK........',
|
||||
'.....KFSSSSSSSSSSSSSSFK.........',
|
||||
'......KFFSSSSSSSSSSFFK..........',
|
||||
'.......KKFFSSSSSSFFKK...........',
|
||||
'.........KFKKKKKKFK.............',
|
||||
'.........KFK....KFK.............',
|
||||
'........KFFK....KFFK............',
|
||||
'.......KFMMK....KMMFK...........',
|
||||
'.......KMMMK....KMMMK...........',
|
||||
'........KKK......KKK............',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
];
|
||||
export const RABBIT_PAL = {
|
||||
K: NNTV.ink, F: NNTV.furLight, C: NNTV.furMid,
|
||||
S: NNTV.cloth, W: NNTV.eyeShine, E: NNTV.ink,
|
||||
R: NNTV.scarfRed, M: NNTV.furMid,
|
||||
};
|
||||
|
||||
export const TOMATO_ART = [
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
'..............GGG...............',
|
||||
'.............GLLLG..............',
|
||||
'............GLDDLG..............',
|
||||
'...........GLDLLDLG.............',
|
||||
'..........GGLLLLLLGG............',
|
||||
'.........KKKKKKKKKKKK...........',
|
||||
'.......KKRRRRRRRRRRRRKK.........',
|
||||
'......KRRHHHHRRRRHHHHRRK........',
|
||||
'.....KRHHHHHHRRRRHHHHHRRK.......',
|
||||
'....KRRHWWHHRRRRRRHWWHRRRK......',
|
||||
'....KRRHWWHHRRRRRRHWWHRRRK......',
|
||||
'...KRRRHHHHRRRRRRRRHHHHRRRK.....',
|
||||
'...KRRRRRRRRRRRDDRRRRRRRRRRK....',
|
||||
'...KRRRRRRRRRRDDDDRRRRRRRRRK....',
|
||||
'...KRRRRRRRRRRRRRRRRRRRRRRRK....',
|
||||
'...KRRRRHHRRRRRRRRRRRRHHRRRK....',
|
||||
'....KRRRHHHRRRRRRRRRRHHHRRK.....',
|
||||
'....KRRRRHHHHRRRRRRHHHHRRRK.....',
|
||||
'.....KRRRRRHHHHHHHHHHRRRRRK.....',
|
||||
'......KRRRRRRRRRRRRRRRRRRK......',
|
||||
'.......KKRRRRRRRRRRRRRRKK.......',
|
||||
'.........KKRRRRRRRRRRKK.........',
|
||||
'...........KKKKKKKKKK...........',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
];
|
||||
export const TOMATO_PAL = {
|
||||
K: NNTV.ink, R: NNTV.tomatoRed, H: NNTV.tomatoDark,
|
||||
W: NNTV.eyeShine, D: NNTV.cream, G: NNTV.leafGreen, L: NNTV.leafDark,
|
||||
};
|
||||
|
||||
export const BLUEBERRY_ART = [
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
'..............GGG...............',
|
||||
'.............GGSG...............',
|
||||
'..............GGG...............',
|
||||
'.........KKKKKKKKKKKK...........',
|
||||
'.......KKBBBBBBBBBBBBKK.........',
|
||||
'......KBBBLLLBBBBLLLBBBK........',
|
||||
'.....KBBLLLLBBBBBBLLLLBBK.......',
|
||||
'....KBBBLLLLBBBBBBBLLLLBBBK.....',
|
||||
'....KBBBBBBBBBBBBBBBBBBBBBK.....',
|
||||
'....KBBBBBBBBBBBBBBBBBBBBBK.....',
|
||||
'...KBBBBHHHBBBBBBBBBHHHBBBBK....',
|
||||
'...KBBBHWWHBBBBBBBBHWWHBBBBK....',
|
||||
'...KBBBHWWHBBBBBBBBHWWHBBBBK....',
|
||||
'...KBBBBHHHBBBBBBBBBHHHBBBBK....',
|
||||
'...KBBBBBBBBBBBBBBBBBBBBBBBK....',
|
||||
'...KBBBBBBBBBBBBBBBBBBBBBBBK....',
|
||||
'...KBBBBBBBBBBNNNBBBBBBBBBBK....',
|
||||
'....KBBBBBBBBBNNNBBBBBBBBBK.....',
|
||||
'....KBBBBBBBBBBNBBBBBBBBBBK.....',
|
||||
'....KBBBBBBBBBBBBBBBBBBBBBK.....',
|
||||
'.....KBBBBBBBBBBBBBBBBBBBK......',
|
||||
'......KBBBBBBBBBBBBBBBBBK.......',
|
||||
'.......KKBBBBBBBBBBBBBKK........',
|
||||
'.........KKBBBBBBBBBKK..........',
|
||||
'...........KKKKKKKKK............',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
];
|
||||
export const BLUEBERRY_PAL = {
|
||||
K: NNTV.ink, B: NNTV.guardRotating, L: NNTV.cream,
|
||||
H: NNTV.blueberryDark, W: NNTV.eyeShine, N: NNTV.cream,
|
||||
G: NNTV.leafGreen, S: NNTV.leafDark,
|
||||
};
|
||||
|
||||
export const CORN_ART = [
|
||||
'................................',
|
||||
'................................',
|
||||
'...........GGG....GGG...........',
|
||||
'..........GHHHG..GHHHG..........',
|
||||
'..........GHHHGGGHHHG...........',
|
||||
'...........GHHHGHHHG............',
|
||||
'............KGGKGGK.............',
|
||||
'..........KKCCCCCCCCKK..........',
|
||||
'........KKCCYCCYCCYCCKK.........',
|
||||
'........KCYYCCYCCYYCCYCK........',
|
||||
'.......KCCYYCCYCCYYCCYCCK.......',
|
||||
'.......KCYYYYCCYCCYYYYYCK.......',
|
||||
'.......KCCHHCCYYCCCHHCCCCK......',
|
||||
'.......KCHWWHCCYCCHWWHCCCK......',
|
||||
'.......KCHWWHCCYCCHWWHCCCK......',
|
||||
'.......KCCHHCCYCCCHHCCCCCK......',
|
||||
'.......KCYYYYCCYCCYYYYCCCK......',
|
||||
'.......KCCYYCCYCCCCYCCYCCK......',
|
||||
'.......KCYYYCYYCYCYYCCYYCK......',
|
||||
'.......KCCYCCYCCCYCCYYYCCK......',
|
||||
'........KCYYYNNNNCYYYCCCK.......',
|
||||
'........KCCYCNNNNCCYCCYCK.......',
|
||||
'........KCYYCCNNCCCYCYYCK.......',
|
||||
'........KCCYYCCCCYYYCCCCK.......',
|
||||
'.........KCCYYYYYCYYYCCK........',
|
||||
'.........KKCCCCCCCCCCCKK........',
|
||||
'...........KKCCCCCCCKK..........',
|
||||
'.............KKKKKKKK...........',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
];
|
||||
export const CORN_PAL = {
|
||||
K: NNTV.ink, C: NNTV.guardBlinking, Y: NNTV.cornDark,
|
||||
H: NNTV.cornDark, W: NNTV.eyeShine, N: NNTV.ink, G: NNTV.cornHusk,
|
||||
};
|
||||
export const CORN_OFF_PAL = { ...CORN_PAL, C: NNTV.guardBlinkingOff, Y: '#5a4410' };
|
||||
|
||||
export const EGGPLANT_ART = [
|
||||
'................................',
|
||||
'................................',
|
||||
'..............GG................',
|
||||
'.............GLLG...............',
|
||||
'............GLLLG...............',
|
||||
'...........GLLLLG...............',
|
||||
'...........KLLLLK...............',
|
||||
'..........KKKLLKKK..............',
|
||||
'.........KKPPPPPPKK.............',
|
||||
'........KPPHHPPPPPPK............',
|
||||
'.......KPPPHPPPPPHPPK...........',
|
||||
'......KPPPPPPPPPPPPPPK..........',
|
||||
'......KPPPPPPPPPPPPPPK..........',
|
||||
'.....KPPHHPPPPPPPPHHPPK.........',
|
||||
'.....KPHWWHPPPPPPHWWHPK.........',
|
||||
'.....KPHWWHPPPPPPHWWHPK.........',
|
||||
'.....KPPHHPPPPPPPPHHPPK.........',
|
||||
'.....KPPPPPPPPPPPPPPPPK.........',
|
||||
'.....KPPPPPPPPPPPPPPPPK.........',
|
||||
'......KPPPPPPNNNNPPPPK..........',
|
||||
'......KPPPPPPNNNNPPPPK..........',
|
||||
'......KPPPPPPPPPPPPPPK..........',
|
||||
'.......KPPPPPPPPPPPPK...........',
|
||||
'.......KPPPPPPPPPPPPK...........',
|
||||
'........KPPPPPPPPPPK............',
|
||||
'........KKPPPPPPPPKK............',
|
||||
'.........KKPPPPPPKK.............',
|
||||
'..........KKPPPPKK..............',
|
||||
'...........KKKKKK...............',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
];
|
||||
export const EGGPLANT_PAL = {
|
||||
K: NNTV.ink, P: NNTV.guardPatrolling, H: NNTV.eggplantDark,
|
||||
W: NNTV.eyeShine, N: NNTV.cream, G: NNTV.leafGreen, L: NNTV.leafDark,
|
||||
};
|
||||
|
||||
export const LETTUCE_ART = [
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
'..............KKK...............',
|
||||
'............KKMMMKK.............',
|
||||
'..........KKMMMMMMMKK...........',
|
||||
'.........KMMMMLLLMMMMK..........',
|
||||
'........KMMLLLLLLLLMMMK.........',
|
||||
'.......KMMLLLLLLLLLLLMMK........',
|
||||
'......KMMLLLMMMMMMMLLLMMK.......',
|
||||
'.....KMMLLMMLLLLLLLMMLLMMK......',
|
||||
'....KMMLLMLLLLLLLLLLLMLLMMK.....',
|
||||
'....KMMLMLLLHHLLLLHHLLMLMMK.....',
|
||||
'...KMMLLMLHWWHLLHWWHLMLLMMK.....',
|
||||
'...KMLLMMLHWWHLLHWWHLMMLLMK.....',
|
||||
'...KMLLMMLLHHLLLLHHLLMMLLMK.....',
|
||||
'....KMMLMLLLLLLLLLLLLMLMMK......',
|
||||
'....KMMLLMLLLNNNNNLLLMLLMMK.....',
|
||||
'.....KMMLLMLLNNNNNLLMLLMMK......',
|
||||
'......KMMLLMMLLLLLMMLLMMK.......',
|
||||
'.......KMMLLLMMMMMMMLLMMK.......',
|
||||
'........KMMLLLLLLLLLLMMK........',
|
||||
'.........KMMLLLLLLLLMMK.........',
|
||||
'..........KKMMMMMMMMKK..........',
|
||||
'............KKMMMMKK............',
|
||||
'..............KKKK..............',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
];
|
||||
export const LETTUCE_PAL = {
|
||||
K: NNTV.ink, L: NNTV.guardMirror, M: NNTV.lettuceDark,
|
||||
H: NNTV.lettuceDark, W: NNTV.eyeShine, N: NNTV.cream,
|
||||
};
|
||||
|
||||
export const PUMPKIN_ART = [
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
'..............SS................',
|
||||
'.............SSSS...............',
|
||||
'.............SSSS...............',
|
||||
'...........GGGSSGGG.............',
|
||||
'...........KGGSSGGK.............',
|
||||
'.........KKPPKKKKPPKK...........',
|
||||
'........KPPOOPPPPOOPPK..........',
|
||||
'.......KPPOOOPPPPOOOPPK.........',
|
||||
'......KPPOOOPPPPPPOOOPPK........',
|
||||
'.....KPOOOPPPPPPPPPPOOOPK.......',
|
||||
'.....KPOOPPPPPPPPPPPPOOPK.......',
|
||||
'....KPPOOPHHPPPPPPHHPPOOPPK.....',
|
||||
'....KPOOPPHWHPPPPPHWHPPOOOPK....',
|
||||
'....KPOOPPHWHPPPPPHWHPPOOOPK....',
|
||||
'....KPOOPPPHHPPPPPPHHPPPOOPK....',
|
||||
'....KPOOPPPPPPPPPPPPPPPPOOPK....',
|
||||
'....KPOOPPPPKKPPPPKKPPPPOOPK....',
|
||||
'....KPPOOPPKNNKPPKNNKPPPOOPK....',
|
||||
'.....KPOOPKNNNNKKNNNNKPPOOPK....',
|
||||
'.....KPPOOPKNNNNNNNNKPPOOPPK....',
|
||||
'......KPOOOPKKNNNNKKPOOOPK......',
|
||||
'.......KPOOOOPPPPPPOOOOPK.......',
|
||||
'........KPPOOOOOOOOOOPPK........',
|
||||
'.........KKPPOOOOOOPPKK.........',
|
||||
'...........KKPPPPPPKK...........',
|
||||
'.............KKKKKK.............',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
];
|
||||
export const PUMPKIN_PAL = {
|
||||
K: NNTV.ink, P: NNTV.guardChaser, O: NNTV.pumpkinDark,
|
||||
H: NNTV.eyeRed, W: NNTV.cream, N: NNTV.ink,
|
||||
G: NNTV.leafGreen, S: NNTV.leafDark,
|
||||
};
|
||||
|
||||
export const PRINCESS_ART = [
|
||||
'................................',
|
||||
'................................',
|
||||
'............SSSSS...............',
|
||||
'...........SLLLLLS..............',
|
||||
'..........SLLHLLLLS.............',
|
||||
'..........SLLLHLLLS.............',
|
||||
'.........SSLLLLHLLSS............',
|
||||
'........KKKGGGGGGGKK............',
|
||||
'.......KYYYYCCCCCYYYK...........',
|
||||
'......KYYCCCDCCDCCCYYK..........',
|
||||
'......KYCCCCCDCDCCCCYK..........',
|
||||
'......KYCCCCCCCCCCCCYK..........',
|
||||
'......KYCCHHCCCCCHHCYK..........',
|
||||
'......KYCHWWHCCCHWWHCK..........',
|
||||
'......KYCHWEHCCCHWEHCK..........',
|
||||
'......KYCCHHCCCCCHHCCK..........',
|
||||
'......KYCCCCCCKKCCCCCK..........',
|
||||
'......KYCCCCCKPPKCCCCK..........',
|
||||
'......KYCCCCCKKKKCCCCK..........',
|
||||
'......KYCCCCCCCCCCCCCK..........',
|
||||
'.......KYCCCCCCCCCCCK...........',
|
||||
'........KYCCCCCCCCCK............',
|
||||
'.........KYCCCCCCCK.............',
|
||||
'..........KYCCCCCK..............',
|
||||
'...........KYCCCK...............',
|
||||
'............KYCK................',
|
||||
'.............KK.................',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
'................................',
|
||||
];
|
||||
export const PRINCESS_PAL = {
|
||||
K: NNTV.ink, C: NNTV.carrot, D: NNTV.cream, H: NNTV.carrotDark,
|
||||
W: NNTV.eyeShine, E: NNTV.ink, P: NNTV.scarfRed,
|
||||
Y: NNTV.carrotDark, L: NNTV.leafGreen, S: NNTV.leafDark, G: NNTV.gold,
|
||||
};
|
||||
|
||||
// Map game guard types → sprite data. Blinking uses on/off variants.
|
||||
export const GUARD_SPRITES = {
|
||||
static: { art: TOMATO_ART, pal: TOMATO_PAL },
|
||||
rotating: { art: BLUEBERRY_ART, pal: BLUEBERRY_PAL },
|
||||
blinking: { art: CORN_ART, pal: CORN_PAL, palOff: CORN_OFF_PAL },
|
||||
patrolling: { art: EGGPLANT_ART, pal: EGGPLANT_PAL },
|
||||
mirror: { art: LETTUCE_ART, pal: LETTUCE_PAL },
|
||||
chaser: { art: PUMPKIN_ART, pal: PUMPKIN_PAL },
|
||||
};
|
||||
@@ -0,0 +1,214 @@
|
||||
// 80×N pixel-art backdrops for each act (Levels 1-2, 3-4, 5-6, 7-8, 9-11, 12).
|
||||
import { NNTV } from './palette.js';
|
||||
|
||||
// Normalize helper — ensure every row is exactly 80 chars.
|
||||
const norm = (rows) => rows.map(r => (r.length >= 80 ? r.slice(0, 80) : r.padEnd(80, r.at(-1) || '.')));
|
||||
|
||||
// ── ACT 1: GARDEN ──────────────────────────────────────────────────────
|
||||
export const BG_GARDEN = norm([
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmm........smmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmm....OOOOO....smmmmmmmmmmmmmmmmmmmmmm..s..s...mmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmm...OO..........OOmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmm..OO............OOmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmm..O..............Omm.s.mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmm..O..............Ommmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmm..OO..........OOmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmm..OOOOOOOOOOOOmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmm........smmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'..LL......LL......LL......LL......LL......LL......LL......LL......LL......LL...',
|
||||
'.LLLL....LLLL....LLLL....LLLL....LLLL....LLLL....LLLL....LLLL....LLLL....LLLL...',
|
||||
'.LDDL....LDDL....LDDL....LDDL....LDDL....LDDL....LDDL....LDDL....LDDL....LDDL...',
|
||||
'gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg',
|
||||
'GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG',
|
||||
'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
|
||||
'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
|
||||
'...CC..........CC..........CC..........CC..........CC..........CC..........CC..',
|
||||
'..CCCC........CCCC........CCCC........CCCC........CCCC........CCCC........CCCC.',
|
||||
'..CCCC........CCCC........CCCC........CCCC........CCCC........CCCC........CCCC.',
|
||||
'..CCCC........CCCC........CCCC........CCCC........CCCC........CCCC........CCCC.',
|
||||
'..OOOO........OOOO........OOOO........OOOO........OOOO........OOOO........OOOO.',
|
||||
'..OOOO........OOOO........OOOO........OOOO........OOOO........OOOO........OOOO.',
|
||||
'..ooooo......ooooo........oooo........ooooo.......oooo.......ooooo........ooo..',
|
||||
'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
|
||||
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||
]);
|
||||
export const BG_GARDEN_PAL = {
|
||||
m: NNTV.midnight, s: NNTV.moonlight, O: NNTV.cream, o: NNTV.moonGlow,
|
||||
L: NNTV.leafDark, D: NNTV.mossGreen, g: NNTV.grass, G: NNTV.grassDark,
|
||||
d: NNTV.dirt, C: NNTV.leafGreen, f: NNTV.dirtDark,
|
||||
};
|
||||
|
||||
// ── ACT 2: GARDEN WALLS ────────────────────────────────────────────────
|
||||
export const BG_WALLS = norm([
|
||||
'ppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp',
|
||||
'ppppppppppppppppp..sppppppppppppppppppppppppps.ppppppppppppppppppp..spppppppppp',
|
||||
'pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp',
|
||||
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
'bBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBB',
|
||||
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
'bbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbb',
|
||||
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
'bBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBB',
|
||||
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
'bbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbb',
|
||||
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
'bBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBB',
|
||||
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
'bbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbb',
|
||||
'..vvbbb.bbbvvbbbb..vvbb.bbbvv..bbbvvb..bbbbvvb..vvbbbb..vvbbbbb..vvbbbb..vvbbbbb',
|
||||
'vvllvvvvbbbllvvvbbllvvbbbbllv..bllvvbbvvllvvbvvvllvbvvvvllvbvvvllvvbbvvllvvvbbbb',
|
||||
'lllllvvvvvlllllvvvlllvvvvllll.lllllvllllllvllllllvllllllvllllllvlllllllvllllllv',
|
||||
'gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg',
|
||||
'GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG',
|
||||
'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
|
||||
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||
]);
|
||||
export const BG_WALLS_PAL = {
|
||||
p: NNTV.midnight, s: NNTV.cream, b: NNTV.brick, B: NNTV.brickDark,
|
||||
v: NNTV.leafDark, l: NNTV.leafGreen, g: NNTV.grass, G: NNTV.grassDark,
|
||||
d: NNTV.dirt, f: NNTV.dirtDark,
|
||||
};
|
||||
|
||||
// ── ACT 3: FORTRESS ────────────────────────────────────────────────────
|
||||
export const BG_FORTRESS = norm([
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmsmmmmmmmmmmmsmmmmmmmmmmmmmmmmmmmmmsmmmmmmmmmmmmmmsmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'sssssssssssssssssssssssssssssssssssssssss.....ssssssssssssssssssssssssssssssssss',
|
||||
's.....ss.....ss.....ss.....ss.....ss...ss.....ss...ss.....ss.....ss.....ss....ss',
|
||||
's.....ss.....ss.....ss.....ss.....ss...ss.....ss...ss.....ss.....ss.....ss....ss',
|
||||
'SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS',
|
||||
'sSSsSSssSsSSssSSsSSssSsSSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSS',
|
||||
'SssSSsSSssSsSSssSSsSSssSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSS',
|
||||
'sSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSssSsSS',
|
||||
'SssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSS',
|
||||
'sSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSssSsSS',
|
||||
'ssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSss',
|
||||
'sSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSssSsSS',
|
||||
'ssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSss',
|
||||
'sSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSssSsSS',
|
||||
'..........ff..................................ff...........................ff..',
|
||||
'.........fFFf................................fFFf.........................fFFf.',
|
||||
'........fFFFFf..............................fFFFFf........................fFFFFf',
|
||||
'.........ffff................................ffff..........................ffff',
|
||||
'..........kk..................................kk...........................kk..',
|
||||
'ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss',
|
||||
'SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS',
|
||||
'ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd.',
|
||||
]);
|
||||
export const BG_FORTRESS_PAL = {
|
||||
m: NNTV.midnight, s: NNTV.stone, S: NNTV.stoneDark,
|
||||
f: NNTV.guardBlinking, F: NNTV.pumpkinOrange, k: NNTV.ink, d: NNTV.dirtDark,
|
||||
};
|
||||
|
||||
// ── ACT 4: UNDERGROUND ─────────────────────────────────────────────────
|
||||
export const BG_UNDERGROUND = norm([
|
||||
'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
|
||||
'dDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDddd',
|
||||
'dddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDd',
|
||||
'dDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDddd',
|
||||
'ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss',
|
||||
'sSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSs',
|
||||
'sSmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs',
|
||||
'sSmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs',
|
||||
'sSmmmmmmfmmmmmmmmmmmmmmmmmmmmmmmfmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs',
|
||||
'sSmmmmmFFmmmmmmmmmmmmmmmmmmmmmmFFmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs',
|
||||
'sSmmmmmffmmmmmmmmmmmmmmmmmmmmmmffmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs',
|
||||
'sSmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs',
|
||||
'sSmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs',
|
||||
'sSmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs',
|
||||
'sSmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs',
|
||||
'sSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSs',
|
||||
'ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss',
|
||||
'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
|
||||
'dDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDddd',
|
||||
'dddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDd',
|
||||
]);
|
||||
export const BG_UNDERGROUND_PAL = {
|
||||
d: NNTV.dirt, D: NNTV.dirtDark, s: NNTV.stoneDark, S: NNTV.stoneDark,
|
||||
m: NNTV.midnight, f: NNTV.guardBlinking, F: NNTV.pumpkinOrange,
|
||||
};
|
||||
|
||||
// ── ACT 5: PALACE ──────────────────────────────────────────────────────
|
||||
export const BG_PALACE = norm([
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmsmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmmm',
|
||||
'mmGCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCGmm',
|
||||
'mGC..........pp.......................pp.......................pp............CGm',
|
||||
'mGC.........pPPp.....................pPPp.....................pPPp...........CGm',
|
||||
'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm',
|
||||
'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm',
|
||||
'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm',
|
||||
'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm',
|
||||
'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm',
|
||||
'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm',
|
||||
'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm',
|
||||
'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm',
|
||||
'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm',
|
||||
'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm',
|
||||
'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm',
|
||||
'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm',
|
||||
'mGCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCGm',
|
||||
'mmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmm.',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
]);
|
||||
export const BG_PALACE_PAL = {
|
||||
m: NNTV.midnight, s: NNTV.cream, G: NNTV.gold, C: NNTV.goldDark,
|
||||
p: NNTV.moonlight, P: NNTV.silver,
|
||||
};
|
||||
|
||||
// ── ACT 6: CHAMBER ─────────────────────────────────────────────────────
|
||||
export const BG_CHAMBER = norm([
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmyyyyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmyyyYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmyyyYYYYYYymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmyyyyYYYCCYYYymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmyyyyYYYYCCCCYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmmmyyyyYYYYYYCCCCCYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmmmyyyyYYYYYYYYCCCCCYYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmmmyyyyYYYYYYYYYYCCCCYYYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmmmyyyyYYYYYYYYYYYYCCCCYYYYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmmmyyyyyYYYYYYYYYYYYYCCYYYYYYYYyyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmmmyyyyYYYYYYYYYYYYYYYYCCYYYYYYYYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmmmyyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmmyyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYyyymmmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmmmyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYyymmmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmmyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYyymmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmmmyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYymmmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmmyyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYyymmmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmmyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYyymmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmmyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYymmmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmmyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYymmmmmmmmmmmmmmmmmmmmmm',
|
||||
'mmmyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYymmmmmmmmmmmmmmmmmmmmmm',
|
||||
]);
|
||||
export const BG_CHAMBER_PAL = {
|
||||
m: NNTV.shadow, y: NNTV.guardBlinkingOff, Y: NNTV.gridLit, C: NNTV.carrot,
|
||||
};
|
||||
|
||||
// Map level (1-indexed) → act scene data. Keeps UI code simple.
|
||||
export const SCENE_BY_LEVEL = [
|
||||
// 1-2 garden, 3-4 walls, 5-6 fortress, 7-8 underground, 9-11 palace, 12 chamber
|
||||
BG_GARDEN, BG_GARDEN, BG_WALLS, BG_WALLS, BG_FORTRESS, BG_FORTRESS,
|
||||
BG_UNDERGROUND, BG_UNDERGROUND, BG_PALACE, BG_PALACE, BG_PALACE, BG_CHAMBER,
|
||||
];
|
||||
export const SCENE_PAL_BY_LEVEL = [
|
||||
BG_GARDEN_PAL, BG_GARDEN_PAL, BG_WALLS_PAL, BG_WALLS_PAL,
|
||||
BG_FORTRESS_PAL, BG_FORTRESS_PAL, BG_UNDERGROUND_PAL, BG_UNDERGROUND_PAL,
|
||||
BG_PALACE_PAL, BG_PALACE_PAL, BG_PALACE_PAL, BG_CHAMBER_PAL,
|
||||
];
|
||||
|
||||
export function sceneForLevel(level) {
|
||||
const idx = Math.max(0, Math.min(SCENE_BY_LEVEL.length - 1, level - 1));
|
||||
return { art: SCENE_BY_LEVEL[idx], pal: SCENE_PAL_BY_LEVEL[idx] };
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// 16×16 pixel-art tiles for the game board.
|
||||
import { NNTV } from './palette.js';
|
||||
|
||||
export const TILE_EMPTY = [
|
||||
'aaaaaaaaaaaaaaaa',
|
||||
'abbbbbbbbbbbbbba',
|
||||
'abccccccccccccba',
|
||||
'abcccccccccccccb',
|
||||
'abcccdddccccdccb',
|
||||
'abccdcccccccccbb',
|
||||
'abcccccccccccccb',
|
||||
'abcccccccccddcbb',
|
||||
'abccdddcccccccbb',
|
||||
'abccccccccccccbb',
|
||||
'abccccdcccccccbb',
|
||||
'abcccccccccccccb',
|
||||
'abccccccccdccccb',
|
||||
'abbbbbbbbbbbbbbb',
|
||||
'aaaaaaaaaaaaaaaa',
|
||||
'aaaaaaaaaaaaaaaa',
|
||||
];
|
||||
export const TILE_EMPTY_PAL = { a: NNTV.gridBorder, b: NNTV.twilight, c: NNTV.gridEmpty, d: NNTV.dusk };
|
||||
|
||||
export const TILE_WALL = [
|
||||
'kkkkkkkkkkkkkkkk',
|
||||
'kssssssstsssssrk',
|
||||
'ksllllllstlllsrk',
|
||||
'kslmmmmlstlmmsrk',
|
||||
'ksllllllstlllsrk',
|
||||
'ksssssssstssssrk',
|
||||
'ktttttttttttttrk',
|
||||
'kssssstsssssssrk',
|
||||
'kslmmstlmmmlssrk',
|
||||
'kslllstllllllsrk',
|
||||
'kssssstsssssssrk',
|
||||
'ksssssstssssssrk',
|
||||
'kslmmlstmmlssrkk',
|
||||
'ksllllstlllssrkk',
|
||||
'kssssstsssssrrkk',
|
||||
'kkkkkkkkkkkkkkkk',
|
||||
];
|
||||
export const TILE_WALL_PAL = {
|
||||
k: NNTV.ink, s: NNTV.stone, l: NNTV.stoneLight,
|
||||
m: NNTV.stoneDark, r: NNTV.stoneDark, t: NNTV.ink,
|
||||
};
|
||||
|
||||
export const TILE_GOAL = [
|
||||
'kkkkkkkkkkkkkkkk',
|
||||
'kggggggggggggggk',
|
||||
'kghhghhhghhhgggk',
|
||||
'kgghghhhghhhhggk',
|
||||
'kgggghhghhhhhggk',
|
||||
'kgghghhhghhhhggk',
|
||||
'kggghhhhhhhhhggk',
|
||||
'kggghhwhwhhhhggk',
|
||||
'kggghhhhhwhhhggk',
|
||||
'kggghwhhhhhhhggk',
|
||||
'kgghghhhwhhhhggk',
|
||||
'kggghhhhhhhhhggk',
|
||||
'kgghghhhhhhhhggk',
|
||||
'kggghwhhhhwhhggk',
|
||||
'kggggggggggggggk',
|
||||
'kkkkkkkkkkkkkkkk',
|
||||
];
|
||||
export const TILE_GOAL_PAL = { k: NNTV.ink, g: NNTV.gridGoal, h: '#00e763', w: '#7affb0' };
|
||||
|
||||
export const TILE_LIT = [
|
||||
'yyyyyyyyyyyyyyyy',
|
||||
'ywwwwwwwwwwwwwwy',
|
||||
'ywyyyyyyyyyyyywy',
|
||||
'ywyyfffffffffywy',
|
||||
'ywyfyyyyyyyyfywy',
|
||||
'ywyfyffffffyfywy',
|
||||
'ywyfyfyyyyyfyfwy',
|
||||
'ywyfyfyffyyfyfwy',
|
||||
'ywyfyfyffyyfyfwy',
|
||||
'ywyfyfyyyyyfyfwy',
|
||||
'ywyfyffffffyfywy',
|
||||
'ywyfyyyyyyyyfywy',
|
||||
'ywyyfffffffffywy',
|
||||
'ywyyyyyyyyyyyywy',
|
||||
'ywwwwwwwwwwwwwwy',
|
||||
'yyyyyyyyyyyyyyyy',
|
||||
];
|
||||
export const TILE_LIT_PAL = { y: NNTV.gridLit, w: '#fff88a', f: NNTV.gridLit };
|
||||
|
||||
export const TILE_MIRROR = [
|
||||
'........kk......',
|
||||
'.......kmmk.....',
|
||||
'......kmllmk....',
|
||||
'.....kmllllmk...',
|
||||
'....kmllwwllmk..',
|
||||
'...kmllwllwllmk.',
|
||||
'..kmllwlllwllmk.',
|
||||
'.kmllwlllllwllmk',
|
||||
'kmlwlllllllwllmk',
|
||||
'.kmllwlllllwllmk',
|
||||
'..kmllwlllwllmk.',
|
||||
'...kmllwwwllmk..',
|
||||
'....kmllllmk....',
|
||||
'.....kmllmk.....',
|
||||
'......kmmk......',
|
||||
'.......kk.......',
|
||||
];
|
||||
export const TILE_MIRROR_PAL = { k: NNTV.ink, l: NNTV.guardMirror, m: NNTV.lettuceDark, w: NNTV.cream };
|
||||
|
||||
export const TILE_PREVIEW = [
|
||||
'yyyy........yyyy',
|
||||
'y..y........y..y',
|
||||
'y..y........y..y',
|
||||
'yyyy........yyyy',
|
||||
'................',
|
||||
'................',
|
||||
'................',
|
||||
'................',
|
||||
'................',
|
||||
'................',
|
||||
'................',
|
||||
'................',
|
||||
'yyyy........yyyy',
|
||||
'y..y........y..y',
|
||||
'y..y........y..y',
|
||||
'yyyy........yyyy',
|
||||
];
|
||||
export const TILE_PREVIEW_PAL = { y: NNTV.gridLit };
|
||||
@@ -0,0 +1,194 @@
|
||||
// Pixel-art UI elements — hearts, logo, moon, icons.
|
||||
import { NNTV } from './palette.js';
|
||||
|
||||
export const HEART_ART = [
|
||||
'................',
|
||||
'..kkkk....kkkk..',
|
||||
'.krrrhk..krrrhk.',
|
||||
'krrwrrrkkrrrrhk.',
|
||||
'krwrrrrrrrrrrhk.',
|
||||
'krrrrrrrrrrrrhk.',
|
||||
'krrrrrrrrrrrhhk.',
|
||||
'krrrrrrrrrrrhhk.',
|
||||
'.krrrrrrrrrrhk..',
|
||||
'.khrrrrrrrrhhk..',
|
||||
'..khrrrrrrhhk...',
|
||||
'...khrrrrhhk....',
|
||||
'....khrrhhk.....',
|
||||
'.....khhhk......',
|
||||
'......khk.......',
|
||||
'.......k........',
|
||||
];
|
||||
export const HEART_ART_EMPTY = HEART_ART.map(r => r.replace(/[rwh]/g, 'e'));
|
||||
export const HEART_PAL = {
|
||||
k: NNTV.ink, r: NNTV.scarfRed, h: '#8a1a22',
|
||||
w: NNTV.cream, e: NNTV.dusk,
|
||||
};
|
||||
|
||||
export const MOON_ART = [
|
||||
'........kkkkk.......',
|
||||
'......kkggghhk......',
|
||||
'.....kgggghhhhk.....',
|
||||
'....kggghhhhhhhk....',
|
||||
'...kggghhhhhhhhhk...',
|
||||
'...kgghhhhhhhhhhk...',
|
||||
'..kggghhhhhhhhhhhk..',
|
||||
'..kgghhhhhhhhhhhhk..',
|
||||
'..kgghhhhhhhhhhhhk..',
|
||||
'..kgghhhhhhhhhhhhk..',
|
||||
'..kgghhhhhhhhhhhhk..',
|
||||
'..kgghhhhhhhhhhhhk..',
|
||||
'..kggghhhhhhhhhhhk..',
|
||||
'...kgghhhhhhhhhhk...',
|
||||
'...kggghhhhhhhhhk...',
|
||||
'....kggghhhhhhhk....',
|
||||
'.....kgggghhhhk.....',
|
||||
'......kkggghhk......',
|
||||
'........kkkkk.......',
|
||||
'....................',
|
||||
];
|
||||
export const MOON_PAL = { k: NNTV.ink, g: NNTV.cream, h: NNTV.moonGlow };
|
||||
|
||||
// 16-wide icons — 'k' outline, 'w' fill.
|
||||
const padIcon = (s) => s.split('\n').filter(Boolean).map(r => r.padEnd(16, '.').slice(0, 16));
|
||||
export const ICON_PAL = { k: NNTV.ink, w: NNTV.moonlight, a: NNTV.scarfRed };
|
||||
|
||||
export const ICON_UNDO = padIcon(`
|
||||
................
|
||||
................
|
||||
.....wwww.......
|
||||
....w....w......
|
||||
...w......w.....
|
||||
..w...wwww.w....
|
||||
..w..w.....w....
|
||||
..w..w..........
|
||||
.wwww.w.........
|
||||
.wwwww..........
|
||||
..www...........
|
||||
...w............
|
||||
................
|
||||
................
|
||||
................
|
||||
................`);
|
||||
|
||||
export const ICON_REDO = padIcon(`
|
||||
................
|
||||
................
|
||||
.......wwww.....
|
||||
......w....w....
|
||||
.....w......w...
|
||||
....w.wwww...w..
|
||||
....w.....w..w..
|
||||
..........w..w..
|
||||
.........w.wwww.
|
||||
..........wwwww.
|
||||
...........www..
|
||||
............w...
|
||||
................
|
||||
................
|
||||
................
|
||||
................`);
|
||||
|
||||
export const ICON_EYE = padIcon(`
|
||||
................
|
||||
................
|
||||
...wwwwwwwww....
|
||||
..w.........w...
|
||||
.w...wwwww...w..
|
||||
w...w.....w...w.
|
||||
w...w.www.w...w.
|
||||
w...w.www.w...w.
|
||||
w...w.....w...w.
|
||||
.w...wwwww...w..
|
||||
..w.........w...
|
||||
...wwwwwwwww....
|
||||
................
|
||||
................
|
||||
................
|
||||
................`);
|
||||
|
||||
export const ICON_PAUSE = padIcon(`
|
||||
................
|
||||
...ww.....ww....
|
||||
...ww.....ww....
|
||||
...ww.....ww....
|
||||
...ww.....ww....
|
||||
...ww.....ww....
|
||||
...ww.....ww....
|
||||
...ww.....ww....
|
||||
...ww.....ww....
|
||||
...ww.....ww....
|
||||
...ww.....ww....
|
||||
...ww.....ww....
|
||||
................
|
||||
................
|
||||
................
|
||||
................`);
|
||||
|
||||
export const ICON_SETTINGS = padIcon(`
|
||||
................
|
||||
.....wwww.......
|
||||
....w....w......
|
||||
...w..ww..w.....
|
||||
..w..wwww..w....
|
||||
..w..wwww..w....
|
||||
.w....ww....w...
|
||||
.w..........w...
|
||||
.w..........w...
|
||||
.w....ww....w...
|
||||
..w..wwww..w....
|
||||
..w..wwww..w....
|
||||
...w..ww..w.....
|
||||
....w....w......
|
||||
.....wwww.......
|
||||
................`);
|
||||
|
||||
export const ICON_LANG = padIcon(`
|
||||
................
|
||||
.....wwww.......
|
||||
....wwwwww......
|
||||
...ww....ww.....
|
||||
..ww..ww..ww....
|
||||
.ww..w..w..ww...
|
||||
.w..ww..ww..w...
|
||||
.w..ww..ww..w...
|
||||
.w..ww..ww..w...
|
||||
.ww..w..w..ww...
|
||||
..ww..ww..ww....
|
||||
...ww....ww.....
|
||||
....wwwwww......
|
||||
.....wwww.......
|
||||
................
|
||||
................`);
|
||||
|
||||
export const ICON_ARROW = padIcon(`
|
||||
................
|
||||
................
|
||||
.......w........
|
||||
......ww........
|
||||
.....www........
|
||||
....wwww........
|
||||
...wwwww........
|
||||
..wwwwwwwwww....
|
||||
...wwwww........
|
||||
....wwww........
|
||||
.....www........
|
||||
......ww........
|
||||
.......w........
|
||||
................
|
||||
................
|
||||
................`);
|
||||
|
||||
// Simple "NNTV" wordmark — compact 36×9 to fit menu header.
|
||||
export const LOGO_ART = [
|
||||
'....................................',
|
||||
'.k..k.k...k.kkkk.k...k..kkk.k...k...',
|
||||
'.kk.k.kk..k.k.k..kk..k.k....kk..k...',
|
||||
'.k.kk.k.k.k.k.k..k.k.k.k....k.k.k...',
|
||||
'.k..k.k..kk.k.k..k..kk.k....k..kk...',
|
||||
'.k..k.k...k.kkkk.k...k..kkk.k...k...',
|
||||
'....................................',
|
||||
'....................................',
|
||||
'....................................',
|
||||
];
|
||||
export const LOGO_PAL = { k: NNTV.moonlight };
|
||||
@@ -0,0 +1,80 @@
|
||||
// NNTV pixel-art palette — shared across sprite/tile/scene art.
|
||||
// Semantic guard colors mirror theme.css variables to preserve gameplay readability.
|
||||
|
||||
export const NNTV = {
|
||||
// Gameplay-critical (must match theme.css guard colors)
|
||||
guardStatic: '#ff4444',
|
||||
guardRotating: '#4488ff',
|
||||
guardBlinking: '#ffdd44',
|
||||
guardBlinkingOff: '#887722',
|
||||
guardPatrolling: '#bb44ff',
|
||||
guardMirror: '#44ddaa',
|
||||
guardChaser: '#ff6622',
|
||||
gridEmpty: '#1a1a2e',
|
||||
gridWall: '#4a4a5e',
|
||||
gridGoal: '#00c853',
|
||||
gridLit: '#ffea00',
|
||||
gridBorder: '#2a2a3e',
|
||||
playerInk: '#111111',
|
||||
|
||||
// Extended palette — atmosphere & pixel shading
|
||||
midnight: '#0a0a1a',
|
||||
twilight: '#141428',
|
||||
dusk: '#1f1b3a',
|
||||
plum: '#2d2150',
|
||||
moonlight: '#e8e4ff',
|
||||
silver: '#9a9ac0',
|
||||
shadow: '#05050f',
|
||||
ink: '#0b0a18',
|
||||
cream: '#fff4d6',
|
||||
moonGlow: '#ffe88a',
|
||||
carrot: '#ff8844',
|
||||
carrotDark: '#cc5a1f',
|
||||
leafGreen: '#3ea85c',
|
||||
leafDark: '#1e6b38',
|
||||
|
||||
// Rabbit fur/cloth
|
||||
furLight: '#f5eeff',
|
||||
furMid: '#b8b0d0',
|
||||
furShadow: '#6a607a',
|
||||
cloth: '#1a1a2e',
|
||||
clothShadow: '#05050f',
|
||||
eyeShine: '#ffffff',
|
||||
eyeRed: '#ff3355',
|
||||
scarfRed: '#c73e3a',
|
||||
|
||||
// Veggie tones
|
||||
tomatoRed: '#e23a3a',
|
||||
tomatoDark: '#991f1f',
|
||||
tomatoLeaf: '#3ea85c',
|
||||
eggplantPurp: '#8a3fc9',
|
||||
eggplantDark: '#4a1a6e',
|
||||
cornYellow: '#ffd84a',
|
||||
cornDark: '#b88a1a',
|
||||
cornHusk: '#6aa84a',
|
||||
lettuceGreen: '#8fd66a',
|
||||
lettuceDark: '#3e7a3e',
|
||||
pumpkinOrange: '#ff7722',
|
||||
pumpkinDark: '#aa4411',
|
||||
blueberry: '#4a7acc',
|
||||
blueberryDark: '#2a4a8c',
|
||||
onionPurp: '#a968c9',
|
||||
onionDark: '#5a2a7a',
|
||||
|
||||
// Tile tones
|
||||
stone: '#5a5a6e',
|
||||
stoneDark: '#2a2a3e',
|
||||
stoneLight: '#7a7a9a',
|
||||
dirt: '#4a3a2a',
|
||||
dirtDark: '#2a1a1a',
|
||||
grass: '#3e7a3e',
|
||||
grassDark: '#2a5a2a',
|
||||
grassLight: '#5aaa5a',
|
||||
mossGreen: '#2a5a3a',
|
||||
brick: '#6a3a2a',
|
||||
brickDark: '#3a1a0a',
|
||||
wood: '#7a5a2a',
|
||||
woodDark: '#3a2a1a',
|
||||
gold: '#ffcc44',
|
||||
goldDark: '#aa7722',
|
||||
};
|
||||
+26
-1
@@ -17,6 +17,8 @@
|
||||
import LevelCompletePopup from '../components/LevelCompletePopup.svelte';
|
||||
import PauseMenu from '../components/PauseMenu.svelte';
|
||||
import ControlsOverlay from '../components/controls-overlay.svelte';
|
||||
import Pixel from '../lib/pixel/Pixel.svelte';
|
||||
import { sceneForLevel } from '../lib/pixel/art-scenes.js';
|
||||
|
||||
let { navigate, level = 1, lives = 3 } = $props();
|
||||
|
||||
@@ -64,6 +66,7 @@
|
||||
? turnManager.previewNextTurn(grid, player, guards)
|
||||
: new Set()));
|
||||
let canUndo = $derived((renderVersion, history.canUndo()));
|
||||
let scene = $derived(sceneForLevel(currentLevel));
|
||||
|
||||
// Initialize level
|
||||
function initLevel() {
|
||||
@@ -266,6 +269,9 @@
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="game-scene" class:flash={showFlash}
|
||||
ontouchstart={onTouchStart} ontouchend={onTouchEnd}>
|
||||
<div class="scene-backdrop">
|
||||
<Pixel art={scene.art} palette={scene.pal} width={1024} height={384} />
|
||||
</div>
|
||||
<GameHud
|
||||
lives={livesRemaining}
|
||||
level={currentLevel}
|
||||
@@ -337,6 +343,17 @@
|
||||
display: flex; flex-direction: column;
|
||||
background: var(--bg-dark);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.scene-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0.22;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
.game-scene.flash {
|
||||
animation: level-flash 0.4s ease-out;
|
||||
@@ -351,8 +368,16 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.board-container {
|
||||
position: relative;
|
||||
background: rgba(10, 10, 26, 0.55);
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 0 24px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
.board-container { position: relative; }
|
||||
.final-message {
|
||||
position: absolute;
|
||||
top: 60px; left: 50%;
|
||||
|
||||
@@ -2,29 +2,57 @@
|
||||
import { getText } from '../lib/localization.js';
|
||||
import { LEVELS } from '../lib/levels/levels.js';
|
||||
import Button from '../components/Button.svelte';
|
||||
import Pixel from '../lib/pixel/Pixel.svelte';
|
||||
import { sceneForLevel } from '../lib/pixel/art-scenes.js';
|
||||
let { navigate, level = 1, lives = 3 } = $props();
|
||||
|
||||
let levelData = $derived(LEVELS[level - 1]);
|
||||
let levelName = $derived(levelData?.name || `Level ${level}`);
|
||||
let storyText = $derived(levelData?.storyKey ? getText(levelData.storyKey) : '');
|
||||
let scene = $derived(sceneForLevel(level));
|
||||
</script>
|
||||
|
||||
<div class="intro">
|
||||
<span class="level-num">{getText('level')}{level}</span>
|
||||
<h1>{levelName}</h1>
|
||||
{#if storyText}
|
||||
<p class="story">{storyText}</p>
|
||||
{/if}
|
||||
<Button text={getText('continue')} onclick={() => navigate('Game', { level, lives })} />
|
||||
<div class="backdrop">
|
||||
<Pixel art={scene.art} palette={scene.pal} width={1024} height={480} />
|
||||
</div>
|
||||
<div class="foreground">
|
||||
<span class="level-num">{getText('level')}{level}</span>
|
||||
<h1>{levelName}</h1>
|
||||
{#if storyText}
|
||||
<p class="story">{storyText}</p>
|
||||
{/if}
|
||||
<Button text={getText('continue')} onclick={() => navigate('Game', { level, lives })} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.intro {
|
||||
width: 100%; height: 100%;
|
||||
position: relative;
|
||||
background: var(--bg-dark);
|
||||
overflow: hidden;
|
||||
}
|
||||
.backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0.55;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.foreground {
|
||||
position: relative;
|
||||
width: 100%; height: 100%;
|
||||
display: flex; flex-direction: column;
|
||||
justify-content: center; align-items: center;
|
||||
gap: 20px;
|
||||
background: var(--bg-dark);
|
||||
background: linear-gradient(180deg,
|
||||
rgba(10,10,26,0.65) 0%,
|
||||
rgba(10,10,26,0.3) 40%,
|
||||
rgba(10,10,26,0.3) 60%,
|
||||
rgba(10,10,26,0.75) 100%);
|
||||
}
|
||||
.level-num {
|
||||
font: var(--font-small);
|
||||
@@ -33,6 +61,7 @@
|
||||
h1 {
|
||||
font: var(--font-title);
|
||||
color: var(--text-title);
|
||||
text-shadow: 0 0 12px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.story {
|
||||
font: var(--font-body);
|
||||
@@ -41,5 +70,6 @@
|
||||
max-width: 70%;
|
||||
line-height: 1.6;
|
||||
white-space: pre-line;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
</style>
|
||||
@@ -1,13 +1,15 @@
|
||||
<script>
|
||||
import { getText } from '../lib/localization.js';
|
||||
import Button from '../components/Button.svelte';
|
||||
import Pixel from '../lib/pixel/Pixel.svelte';
|
||||
import { MOON_ART, MOON_PAL } from '../lib/pixel/art-ui.js';
|
||||
import { RABBIT_ART, RABBIT_PAL } from '../lib/pixel/art-characters.js';
|
||||
let { navigate } = $props();
|
||||
|
||||
// Generate starfield dots
|
||||
const stars = Array.from({ length: 40 }, () => ({
|
||||
x: Math.random() * 100,
|
||||
y: Math.random() * 100,
|
||||
opacity: 0.1 + Math.random() * 0.4,
|
||||
opacity: 0.1 + Math.random() * 0.5,
|
||||
size: 1 + Math.floor(Math.random() * 2),
|
||||
}));
|
||||
</script>
|
||||
@@ -20,8 +22,14 @@
|
||||
></div>
|
||||
{/each}
|
||||
|
||||
<div class="moon">
|
||||
<Pixel art={MOON_ART} palette={MOON_PAL} width={100} height={100} />
|
||||
</div>
|
||||
|
||||
<h1>{getText('gameTitle')}</h1>
|
||||
<div class="ninja-icon"></div>
|
||||
<div class="hero">
|
||||
<Pixel art={RABBIT_ART} palette={RABBIT_PAL} width={96} height={96} />
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<Button text={getText('startGame')} onclick={() => navigate('StoryIntro')} />
|
||||
@@ -45,19 +53,23 @@
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.moon {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
right: 60px;
|
||||
filter: drop-shadow(0 0 24px rgba(255, 232, 138, 0.3));
|
||||
}
|
||||
h1 {
|
||||
font: var(--font-title);
|
||||
color: var(--text-title);
|
||||
margin-bottom: 16px;
|
||||
z-index: 1;
|
||||
text-shadow: 0 0 12px rgba(187, 134, 252, 0.4);
|
||||
}
|
||||
.ninja-icon {
|
||||
width: 36px; height: 36px;
|
||||
background: var(--player-color);
|
||||
border-radius: 50%;
|
||||
margin-bottom: 32px;
|
||||
box-shadow: 0 0 20px rgba(83, 52, 131, 0.3);
|
||||
.hero {
|
||||
margin-bottom: 24px;
|
||||
z-index: 1;
|
||||
filter: drop-shadow(0 4px 12px rgba(83, 52, 131, 0.6));
|
||||
}
|
||||
.buttons {
|
||||
display: flex; flex-direction: column;
|
||||
|
||||
Reference in new issue
Block a user