diff --git a/src/pages/ants/index.tsx b/src/pages/ants/index.tsx index 04e093c..8664341 100644 --- a/src/pages/ants/index.tsx +++ b/src/pages/ants/index.tsx @@ -7,6 +7,7 @@ import { useEffect, useRef, useState } from "react" export const meta: RouteMeta = { title: 'Ants', + image: './screen.png' } export default function () { @@ -45,9 +46,9 @@ export default function () { ant: [0xcc, 0xcc, 0xcc, 0xff], antAndFood: [0xee, 0x44, 0xee, 0xff], food: [0, 0x80, 0, 0xff], - pheromoneToFood: [0x20, 0xff, 0x20, 0xff], + pheromoneToFood: [0x20, 0xee, 0x20, 0xff], pheromoneToHill: [0xa0, 0x20, 0x20, 0x80], - pheromoneBoth: [0x70, 0xff, 0x20, 0xff], + pheromoneBoth: [0xa0, 0xee, 0x20, 0xff], anthill: [0x80, 0, 0, 0xff], void: [0, 0, 0, 0xff], } diff --git a/src/pages/ants/screen.png b/src/pages/ants/screen.png new file mode 100644 index 0000000..a093d68 Binary files /dev/null and b/src/pages/ants/screen.png differ diff --git a/src/pages/artisan-td-calc/index.tsx b/src/pages/artisan-td-calc/index.tsx deleted file mode 100644 index 9b546bf..0000000 --- a/src/pages/artisan-td-calc/index.tsx +++ /dev/null @@ -1,329 +0,0 @@ -import { useMemo, useState, type CSSProperties } from "react" -import styles from './styles.module.css' -import { Head } from "~/components/Head" -import type { RouteMeta } from "~/router" - -export const meta: RouteMeta = { - title: 'Artisan TD calculator', -} - -const SIDE = 8 -const TURNS = 15 - -enum State { - OFF, - ON, - INSIDE, -} - -const makeGrid = (value: State = State.OFF) => Array.from({ length: SIDE }, () => Array.from({ length: SIDE }, () => value)) - -export default function Lightning() { - const [grid, setGrid] = useState(makeGrid) - - const computed = useMemo(() => computeInside(grid), [grid]) - - - return ( -
- - -

Click on the grid below to draw your farm towers:

- { - const newGrid = structuredClone(grid) - newGrid[y][x] = value - setGrid(newGrid) - }} - /> - -
- ) -} - -function Results({ grid }: { grid: State[][] }) { - const summary = useMemo(() => counts(grid), [grid]) - - const cost = summary[State.ON] * 20 - const perTurn = summary[State.INSIDE] * 10 - - const rows = { - total: Array.from({ length: TURNS }, (_, i) => Math.round(perTurn * (i + 1) - cost)), - totalSell: Array.from({ length: TURNS }, (_, i) => Math.round(perTurn * (i + 1) - cost / 2)), - bank: Array.from({ length: TURNS }, (_, i) => Math.round(i * cost * 0.2)), - bankCompound: Array.from({ length: TURNS }, (_, i) => Math.round(cost * 1.2 ** i - cost)), - both: Array.from({ length: TURNS }).reduce((arr, _, i) => (arr.push(Math.round((i === 0 ? 0 : ((arr[i - 1] + cost / 2) * 1.2)) + perTurn - cost / 2)), arr), []), - } as const - - return ( - <> - - - - - - - - - - - - - - - - - - - - - - - -
Initial cost{cost}
Sale price{cost / 2}
Per turn{perTurn}
Walls{summary[State.ON]}
Fields{summary[State.INSIDE]}
- - - - - {Array.from({ length: TURNS }, (_, i) => ( - - ))} - - - - {Object.entries(rows).map(([key, values]) => { - const det = details[key as keyof typeof details] - return ( - - - - {values.map((v, i) => ( - - ))} - - ) - })} - -
{i + 1}
-

{det.title}

-

{det.subtitle}

- - {det.formula} - -
{v}
- - ) -} - -function DisplayGrid({ grid, onToggle }: { grid: State[][], onToggle: (x: number, y: number, value: State) => void }) { - return ( -
- {grid.map((row, y) => ( - row.map((cell, x) => ( -
onToggle(x, y, cell === State.ON ? State.OFF : State.ON)} - > - {cell === State.INSIDE ? '🌾' : cell === State.ON ? '🚜' : ''} -
- )) - ))} -
- ) -} - -function computeInside(grid: State[][]): State[][] { - const next = makeGrid(State.INSIDE) - - // copy walls, list entry points - const queue: [number, number][] = [] - for (let y = 0; y < SIDE; y++) { - for (let x = 0; x < SIDE; x++) { - if (grid[y][x] === State.ON) { - next[y][x] = grid[y][x] - } else if (y === 0 || x === 0 || y === SIDE - 1 || x === SIDE - 1) { - next[y][x] = State.OFF - queue.push([x, y]) - } - } - } - - // flood fill - const neighbors = [ - [-1, 0], - [1, 0], - [0, -1], - [0, 1], - ] - while (queue.length) { - const [x, y] = queue.shift()! - for (const [dx, dy] of neighbors) { - const nx = x + dx - const ny = y + dy - if (nx < 0 || nx >= SIDE || ny < 0 || ny >= SIDE) continue - if (grid[ny][nx] !== State.OFF) continue - if (next[ny][nx] === State.OFF) continue - next[ny][nx] = State.OFF - queue.push([nx, ny]) - } - } - - return next -} - -function counts(grid: State[][]): Record { - const result = { - [State.OFF]: 0, - [State.ON]: 0, - [State.INSIDE]: 0, - } - for (const row of grid) { - for (const cell of row) { - result[cell]++ - } - } - return result -} - -const details = { - total: { - title: 'Total gains', - subtitle: 'Total earnings from all fields after N turns.', - formula: ( - - f - {'('} - - n - - {')'} - = - fields - × - 10 - × - n - - - cost - - ) - }, - totalSell: { - title: 'Total after sale', - subtitle: 'Total earnings from all fields after N turns,\nafter having sold the farms.', - formula: ( - - f - {'('} - - n - - {')'} - = - fields - × - 10 - × - n - - - cost - / - 2 - - ) - }, - both: { - title: 'Both', - subtitle: 'Total earnings from all fields after N turns,\nearnings are accumulated in the bank and not spent.\nGains are only realized at the end.', - formula: ( - - f - {'('} - - n - - {')'} - = - fields - × - 10 - + - f - {'('} - - n - - - 1 - - {')'} - × - 1.2 - - ) - }, - bank: { - title: 'Bank', - subtitle: 'Initial cost of the farms is kept frozen in the bank instead,\nsurplus is spent every turn.', - formula: ( - - f - {'('} - - n - - {')'} - = - cost - × - 0.2 - × - n - - ) - }, - bankCompound: { - title: 'Bank (accumulate)', - subtitle: 'Initial cost of the farms is kept in the bank,\nearnings accumulate every turn and are not spent.\nGains are only realized at the end.', - formula: ( - - f - {'('} - - n - - {')'} - = - cost - × - {'('} - - - 1.2 - n - - - - 1 - - {')'} - - ) - } -} as const - - -declare global { - namespace JSX { - interface IntrinsicElements { - math: React.DetailedHTMLProps & { display?: 'block' | 'inline' }, HTMLElement> - mrow: React.DetailedHTMLProps, HTMLElement> - mi: React.DetailedHTMLProps, HTMLElement> - mn: React.DetailedHTMLProps, HTMLElement> - mo: React.DetailedHTMLProps & { fence?: boolean }, HTMLElement> - msup: React.DetailedHTMLProps, HTMLElement> - munderover: React.DetailedHTMLProps, HTMLElement> - } - } -} \ No newline at end of file diff --git a/src/pages/artisan-td-calc/styles.module.css b/src/pages/artisan-td-calc/styles.module.css deleted file mode 100644 index 4aa92af..0000000 --- a/src/pages/artisan-td-calc/styles.module.css +++ /dev/null @@ -1,85 +0,0 @@ -.main { - padding: 1em; - - height: 100dvh; - width: 100dvw; - - >* { - position: relative; - z-index: 1; - } -} - -.grid { - display: grid; - grid-template-columns: repeat(var(--side), 100px); - grid-template-rows: repeat(var(--side), 1fr); -} - -.cell { - aspect-ratio: 1; - border: 1px solid currentColor; - text-align: center; - font-size: 50px; - cursor: pointer; - - &[data-state="on"] { - background-color: gray; - } - - &[data-state="off"] { - background-color: black; - - &:hover { - background-color: #333; - } - } - - &[data-state="in"] { - background-color: green; - } -} - -.table { - margin-bottom: 1em; - border-collapse: collapse; - - th, - td { - border: 1px solid currentColor; - } - - th:empty { - border: none; - } - - th, - td { - padding: 0.5em; - } - - td, - thead th { - min-width: calc(3ch + 1em); - font-variant-numeric: tabular-nums; - text-align: right; - } - - th[scope="row"] { - [data-title] { - margin: 0; - } - - [data-subtitle] { - font-weight: normal; - margin: 0; - white-space: pre-wrap; - font-size: smaller; - } - - math { - margin: 0.5em; - opacity: 0.5; - } - } -} \ No newline at end of file diff --git a/src/router.ts b/src/router.ts index 8543006..5092443 100644 --- a/src/router.ts +++ b/src/router.ts @@ -9,8 +9,9 @@ import perlin_ripples_image from "./pages/perlin-ripples/screen.png" import paint_worklet_image from "./pages/paint-worklet/screen.png" import lightning_image from "./pages/lightning/screen.png" import bird_inverse_kinematics_image from "./pages/bird-inverse-kinematics/screen.png" +import ants_image from "./pages/ants/screen.png" -export type Routes = "wave-function-collapse" | "spider-inverse-kinematics" | "quad-tree" | "pong-pang" | "perlin-ripples" | "paint-worklet" | "modern-modal" | "lightning" | "fragment-portal" | "bird-inverse-kinematics" | "artisan-td-calc" | "ants" +export type Routes = "wave-function-collapse" | "spider-inverse-kinematics" | "quad-tree" | "pong-pang" | "perlin-ripples" | "paint-worklet" | "modern-modal" | "lightning" | "fragment-portal" | "bird-inverse-kinematics" | "ants" export type RouteMeta = { title: string @@ -138,23 +139,14 @@ export const ROUTES = { firstAdded: 1717339261000 }, }, - "artisan-td-calc": { - Component: lazy(() => import("./pages/artisan-td-calc/index.tsx")), - meta: { - title: 'Artisan TD calculator', - }, - git: { - lastModified: 1723588686000, - firstAdded: 1723588686000 - }, - }, "ants": { Component: lazy(() => import("./pages/ants/index.tsx")), meta: { title: 'Ants', + image: ants_image }, git: { - lastModified: 1728127860000, + lastModified: 1728128584000, firstAdded: 1727995709000 }, }