Examples
Game of Life
About
Conway's Game of Life.
quillo run life.qwe --watch then open http://localhost:8080
The board is a list of rows of 0s and 1s in a component's state, and
next makes a generation from the one before: a plain function, which runs
the same on the server and in the browser. While the game runs, the render
asks for a timer with every(1000 / speed, step); pausing stops asking, which
stops it. Click a cell to flip it.
Run it
$ quillo run life.qwe --watchSource
life.qwe
let W = 48let H = 32// Patterns, as rows of text: `#` is a live cell.let patterns = [ { name: "Glider gun", top: 4, left: 4, rows: [ "........................#...........", "......................#.#...........", "............##......##............##", "...........#...#....##............##", "##........#.....#...##..............", "##........#...#.##....#.#...........", "..........#.....#.......#...........", "...........#...#....................", "............##......................", ] }, { name: "Gliders", top: 2, left: 2, rows: [ ".#.......#.......#.......#", "..#.......#.......#.......#", "###.....###.....###.....###", ] }, { name: "Pulsar", top: 9, left: 17, rows: [ "..###...###..", ".............", "#....#.#....#", "#....#.#....#", "#....#.#....#", "..###...###..", ".............", "..###...###..", "#....#.#....#", "#....#.#....#", "#....#.#....#", ".............", "..###...###..", ] }, { name: "R-pentomino", top: 14, left: 22, rows: [ ".##", "##.", ".#.", ] },]fn empty() = range(0, H) | map((_) => range(0, W) | map((_) => 0))// The pattern's live cells, the rest dead.fn board_of(p: map) = range(0, H) | map((y) => range(0, W) | map((x) => { let row = p.rows[y - p.top] let inside = y >= p.top && y - p.top < len(p.rows) && x >= p.left && x - p.left < len(row ?? "") inside && row[x - p.left] == "#" ? 1 : 0 }))fn randomized() = range(0, H) | map((_) => range(0, W) | map((_) => random() < 0.25 ? 1 : 0))// Cells off one edge come back on the other.fn cell(board: list, y: int, x: int) = board[(y + H) % H][(x + W) % W]fn next(board: list) = range(0, H) | map((y) => range(0, W) | map((x) => { let n = cell(board, y - 1, x - 1) + cell(board, y - 1, x) + cell(board, y - 1, x + 1) + cell(board, y, x - 1) + cell(board, y, x + 1) + cell(board, y + 1, x - 1) + cell(board, y + 1, x) + cell(board, y + 1, x + 1) n == 3 || (n == 2 && board[y][x] == 1) ? 1 : 0 }))fn flip(board: list, y: int, x: int) = range(0, H) | map((r) => r != y ? board[r] : range(0, W) | map((c) => c == x ? 1 - board[r][c] : board[r][c]))component Life() { state board = board_of(first(patterns)) state running = false state speed = 8 // generations per second state generation = 0 let step = () => { board = next(board) generation += 1 } if running { every(1000 / speed, step) } let population = board | map((row) => sum(row)) | sum <section.life> <div.controls> <button.primary @click={() => { running = !running }}>{running ? "Pause" : "Play"}</button> <button @click={step} disabled={running}>Step</button> <label.speed> Speed <input type="range" min="1" max="100" value={speed} @input={(e) => { speed = int(e.value) ?? speed }}/> <span.per-second>{speed}/s</span> </label> <span.stats>Generation <b.generation>{generation}</b> · <b.population>{population}</b> alive</span> </div> <div.board style={`grid-template-columns: repeat(${W}, 1fr)`}> {for y in range(0, H) => for x in range(0, W) => <i class={board[y][x] == 1 && "on"} @click={() => { board = flip(board, y, x) }}></i>} </div> <div.controls> {for p in patterns => <button key={p.name} @click={() => { board = board_of(p) generation = 0 }}>{p.name}</button>} <button @click={() => { board = randomized() generation = 0 }}>Random</button> <button @click={() => { board = empty() generation = 0 running = false }}>Clear</button> </div> </section>}let style = css`body { font: 15px/1.4 system-ui, sans-serif; max-width: 52rem; margin: 2rem auto; padding: 0 1rem; color: #222; }.controls { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin: .75rem 0; }button { font: inherit; padding: .3rem .8rem; border-radius: 6px; border: 1px solid #ccc; background: white; cursor: pointer; }button.primary { background: #222; color: white; border-color: #222; min-width: 5rem; }button.active { border-color: #222; font-weight: 600; }button:disabled { opacity: .4; cursor: default; }.speed { display: inline-flex; align-items: center; gap: .5rem; color: #555; }.speed input { width: 9rem; accent-color: #1a7f37; }.per-second { min-width: 2.5rem; font-variant-numeric: tabular-nums; }.stats { margin-left: auto; color: #666; font-variant-numeric: tabular-nums; }.board { display: grid; gap: 1px; background: #e8e8e8; border: 1px solid #e8e8e8; border-radius: 4px; overflow: hidden; }.board i { aspect-ratio: 1; background: white; cursor: pointer; }.board i.on { background: #1a7f37; }@media (prefers-color-scheme: dark) { body { background: #161616; color: #ddd; } button { background: #222; color: #ddd; border-color: #444; } button.primary { background: #ddd; color: #111; } .board { background: #2a2a2a; border-color: #2a2a2a; } .board i { background: #161616; } .board i.on { background: #3fb950; }}`route GET / = <html> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>Game of Life</title> <style>{style}</style> </head> <body> <h1>Game of Life</h1> <Life/> </body> </html>Thread
Thread 0 posts
On Game of Life: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.