// Bilder für die Community-Posts (How to play, Realms). Werte kommen aus Engine und look.ts, damit die Bilder // stimmen, wenn sich Regeln oder Freischaltungen ändern. ?page=howto | realms import '../../src/client/style.css' import './community.css' import {h} from '../../src/client/dom.ts' import {UNIT_NAME} from '../../src/client/texts.ts' import {spriteUrl} from '../../src/engine/app/board/atlas.ts' import { bannerPreview, pieceSpriteName, } from '../../src/engine/app/board/BoardView.ts' import {TEAM_HEX} from '../../src/engine/core/avatars.ts' import { UNIT_DEFS, UNIT_TYPES, type UnitType, } from '../../src/engine/core/units.ts' import { BANNER_OPTIONS, COLORS, FACTION_OPTIONS, THEME_OPTIONS, type Unlock, unlockText, } from '../../src/shared/look.ts' const sheet = document.getElementById('sheet') as HTMLElement const COLOR = 'purple' /** Normale Kosten (ohne Gesetz des Tages) */ const COST: Record = { HIVEMOTHER: 'free', HIVEWORKER: 'free', DRONE: '10 gold', HIVESCOUT: '15 gold', } const RULE: Record = { HIVEMOTHER: 'Only once. Up to 5 cells per line.', HIVEWORKER: 'The 8 cells around it, own land.', DRONE: '2 cells per line, own land. Pays 2×.', HIVESCOUT: '2 cells per diagonal, any land.', } /** Beanspruchte Felder auf freiem Grasland – direkt aus den Engine-Daten (Strahlen bzw. Ring) */ function claimed(unit: UnitType, n: number): Set { const def = UNIT_DEFS[unit] const mid = (n - 1) / 2 const out = new Set() for (const [dx, dy] of def.directions) { const steps = def.shape === 'ring' ? 1 : def.budget for (let s = 1; s <= steps; s++) { const x = mid + dx * s const y = mid + dy * s if (x >= 0 && y >= 0 && x < n && y < n) out.add(y * n + x) } } return out } function pattern(unit: UnitType): HTMLElement { const n = unit === 'HIVEMOTHER' ? 11 : 5 const size = unit === 'HIVEMOTHER' ? 12 : 26 const grid = h('div.pattern') grid.style.gridTemplateColumns = `repeat(${n}, ${size}px)` grid.style.gridAutoRows = `${size}px` const claim = claimed(unit, n) for (let i = 0; i < n * n; i++) { const c = h('span.c') c.style.backgroundImage = `url(${spriteUrl(`grass${(i * 7) % 6}` as 'grass0')})` if (claim.has(i)) c.classList.add('claim') if (i === (n * n - 1) / 2) { const piece = img(pieceSpriteName(unit, COLOR, 'kingdom')) // Burg auf dem großen Raster dreifach groß, sonst kaum zu erkennen if (n > 5) piece.classList.add('big') c.append(piece) } grid.append(c) } return grid } function img(name: string): HTMLImageElement { const el = h('img') el.src = name.startsWith('/') ? name : spriteUrl(name as 'grass0') el.alt = '' return el } function howto(): void { // Reihenfolge wie man spielt: Burg, Bauer, Ritter, Läufer const order: UnitType[] = ['HIVEMOTHER', 'HIVEWORKER', 'DRONE', 'HIVESCOUT'] if (order.some(u => !UNIT_TYPES.includes(u))) throw new Error('Einheiten') sheet.append( h( 'header.title', h('h1', 'How to play'), h('p', 'Ninelands · a new 9×9 map every day'), ), h( 'section.units', ...order.map(u => h( 'div.card', h('h2', UNIT_NAME[u], h('small', COST[u])), h('p', RULE[u]), pattern(u), ), ), ), h( 'section.card', h('h2', 'Claimed cells pay gold'), h( 'div.row', h('span.gold-item', img('ore'), 'Ore ', h('b', '20')), h('span.gold-item', img('herbs'), 'Herbs ', h('b', '20')), h( 'span.gold-item', img('grass0'), 'Pasture ', h('b', '5'), h('small', ' (7 by a lake)'), ), ), h( 'p', 'Claimed land can’t be built on, so plan ahead. Claim half of a land to rule it.', ), ), h( 'section.card.stars-card', h('span.stars', '★'), ' gold goal ', h('span.stars', '★'), ' lands goal ', h('span.stars', '★'), ' both in one game', ), h( 'p.foot', 'Only your first game counts · each map has its own law · new map 00:00 UTC', ), ) } const NAMES: Record = { kingdom: 'Kingdom', orc: 'Orcs', pirate: 'Pirates', classic: 'Classic', autumn: 'Autumn', winter: 'Winter', night: 'Night', winterfest: 'Winterfest', } const lock = (u: Unlock) => u.kind === 'free' ? h('span.lock', 'free') : h('span.lock', unlockText(u)) function realms(): void { const peoples = h('div.peoples') FACTION_OPTIONS.forEach((f, row) => { peoples.append(h('span.name', NAMES[f.id] ?? f.id, lock(f.unlock))) for (const u of UNIT_TYPES) { // Jede Zeile in einer anderen Farbe – zeigt Völker und Farben zugleich const color = COLORS[(row + 1) % COLORS.length]?.id ?? 'red' peoples.append(img(pieceSpriteName(u, color, f.id))) } }) const tiles = h( 'div.tiles', ...THEME_OPTIONS.map(t => { const patch = h('div.patch') for (let i = 0; i < 9; i++) { const s = h('span') const file = `grass${(i * 5) % 6}.png` s.style.backgroundImage = `url(/sprites/themes/${t.id}/${file})` patch.append(s) } patch.append(img(pieceSpriteName('HIVEMOTHER', COLOR, 'kingdom'))) return h('div.tile', patch, NAMES[t.id] ?? t.id, lock(t.unlock)) }), ) const banners = h( 'div.banners', ...BANNER_OPTIONS.map(b => h( 'div', bannerPreview(b.id, TEAM_HEX[COLOR]), `${b.id[0]?.toUpperCase()}${b.id.slice(1)}`, lock(b.unlock), ), ), ) sheet.append( h( 'header.title', h('h1', 'Choose your realm'), h( 'p', 'Tap 🎨 Realm on the daily post · 4 colours free · same rules for all', ), ), h( 'section.card', h('h2', 'Factions', h('small', 'collect stars')), peoples, ), h('section.card', h('h2', 'Boards', h('small', 'keep your streak')), tiles), h( 'section.card', h('h2', 'Banners', h('small', 'a new one every 10 ★')), banners, ), h( 'p.foot', 'It’s all about looking good while you win. Which faction are you playing?', ), ) } const page = new URLSearchParams(location.search).get('page') if (page === 'realms') realms() else howto()