224 lines
6.3 KiB
TypeScript
224 lines
6.3 KiB
TypeScript
// 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<UnitType, string> = {
|
||
HIVEMOTHER: 'free',
|
||
HIVEWORKER: 'free',
|
||
DRONE: '10 gold',
|
||
HIVESCOUT: '15 gold',
|
||
}
|
||
const RULE: Record<UnitType, string> = {
|
||
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<number> {
|
||
const def = UNIT_DEFS[unit]
|
||
const mid = (n - 1) / 2
|
||
const out = new Set<number>()
|
||
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', 'Gold for the cell you build on'),
|
||
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 in a lake land)'),
|
||
),
|
||
),
|
||
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<string, string> = {
|
||
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()
|