Skip to content

Commit

Permalink
Add controls for GOL
Browse files Browse the repository at this point in the history
  • Loading branch information
rradczewski committed Jul 28, 2020
1 parent 62c0db1 commit 37dd357
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 98 deletions.
32 changes: 25 additions & 7 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,25 @@
<div id="gameCanvasOverlay"></div>

<div class="jumbotron-gol-controls">
<button class="btn btn-link" id="jumbotron-gol-control-speed-slower">
<i class="fas fa-fast-backward"></i>
</button>
<button class="btn btn-link" id="jumbotron-gol-control-speed-reset">
<i class="fas fa-tachometer-alt"></i>
</button>
<button class="btn btn-link" id="jumbotron-gol-control-speed-faster">
<i class="fas fa-fast-forward"></i>
</button>
<button class="btn btn-link" id="jumbotron-gol-control-shuffle">
<i class="fas fa-random"></i>
</button>
<button class="btn btn-link" id="jumbotron-gol-control-pause">
<i class="fas fa-pause"></i>
<i class="fas fa-play"></i>
</button>
</div>
<div class="jumbotron-gol-content container">
<h1 class="text-center col-12 display-lg-1 display-4">Coderetreat</h1>
<h1 class="text-center col-12 display-lg-1 display-5">Coderetreat</h1>
<h2 class="text-center col-12 col-lg-6 offset-lg-3">
A free full-day workshop to practice professional software development
</h2>
Expand All @@ -27,7 +39,8 @@ <h2 class="text-center col-12 col-lg-6 offset-lg-3">
<div class="container">
<div class="card-deck">
<div class="card">
<img src="{% link /assets/home-gdcr.jpg %}" class="card-img-top card-img-tinted" alt="A photo of a hangout session with lots of coderetreat sessions going on at the same time in different places of the world" />
<img src="{% link /assets/home-gdcr.jpg %}" class="card-img-top card-img-tinted"
alt="A photo of a hangout session with lots of coderetreat sessions going on at the same time in different places of the world" />
<div class="card-body">
<h5 class="card-title">Global Day of Coderetreat</h5>
<p class="card-text">
Expand All @@ -37,11 +50,13 @@ <h5 class="card-title">Global Day of Coderetreat</h5>
<a href="#" class="btn btn-primary">Find an event in your area</a>
</div>
<div class="card-footer py-1">
<span class="small">Photo by <a href="https://twitter.com/racingDeveloper/status/665529557961830400">@racingDeveloper</a></span>
<span class="small">Photo by <a
href="https://twitter.com/racingDeveloper/status/665529557961830400">@racingDeveloper</a></span>
</div>
</div>
<div class="card">
<img src="{% link /assets/home-what-is-a-coderetreat.jpg %}" class="card-img-top card-img-tinted" alt="a poster of the rules of game of life, put up on a window in Berlin" />
<img src="{% link /assets/home-what-is-a-coderetreat.jpg %}" class="card-img-top card-img-tinted"
alt="a poster of the rules of game of life, put up on a window in Berlin" />
<div class="card-body">
<h5 class="card-title">What is a coderetreat?</h5>
<p class="card-text">
Expand All @@ -51,11 +66,13 @@ <h5 class="card-title">What is a coderetreat?</h5>
<a href="#" class="btn btn-primary">Learn more</a>
</div>
<div class="card-footer py-1">
<span class="small">Photo by <a href="https://twitter.com/martinklose/status/931791467642793984/photo/1">@martinKlose</a></span>
<span class="small">Photo by <a
href="https://twitter.com/martinklose/status/931791467642793984/photo/1">@martinKlose</a></span>
</div>
</div>
<div class="card">
<img src="{% link /assets/home-getting-started.jpg %}" class="card-img-top card-img-tinted" alt="a laptop next to a notepad and a glass of water" />
<img src="{% link /assets/home-getting-started.jpg %}" class="card-img-top card-img-tinted"
alt="a laptop next to a notepad and a glass of water" />
<div class="card-body">
<h5 class="card-title">Getting started</h5>
<p class="card-text">
Expand All @@ -65,7 +82,8 @@ <h5 class="card-title">Getting started</h5>
<a href="#" class="btn btn-primary">To the guide</a>
</div>
<div class="card-footer py-1">
<span class="small">Photo by <a href="https://pixabay.com/de/photos/laptop-notebook-start-computer-849798/">StartupStockPhotos</a></span>
<span class="small">Photo by <a
href="https://pixabay.com/de/photos/laptop-notebook-start-computer-849798/">StartupStockPhotos</a></span>
</div>
</div>
</div>
Expand Down
178 changes: 88 additions & 90 deletions js/gameCanvas.js
Original file line number Diff line number Diff line change
@@ -1,102 +1,100 @@
import * as PIXI from "pixi.js";
import { computeNextGeneration } from "./gameOfLife/computeNextGeneration";

const CELL_SIZE = 20;
const TICK_EVERY_MS = 3000;
const ALPHA_DELTA = 0.01;
const SEED_THRESHOLD = 0.7;

const view = document.getElementById("gameCanvas");
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

const app = new PIXI.Application({
view,
resizeTo: view.parentElement,
antialias: true,
autoDensity: true,
resolution: 2,
autoStart: !reducedMotion
});

document.querySelector(`#jumbotron-gol-control-pause ${reducedMotion ? ".fa-pause" : ".fa-play"}`).classList.add("d-none");
document.querySelector("#jumbotron-gol-control-pause").addEventListener("click", (e) => {
e.preventDefault();
const shouldStart = !app.ticker.started;
if(shouldStart) {
app.start();
} else {
app.stop();
}
document.querySelector("#jumbotron-gol-control-pause [data-icon=pause]").classList.toggle("d-none");
document.querySelector("#jumbotron-gol-control-pause [data-icon=play]").classList.toggle("d-none");
})
import { GolApp } from "./gameOfLife/golApp";
import qs from "qs";
import seedrandom from "seedrandom";

const DEFAULT_OPTIONS = {
seed: (Math.random() * 10000) | 0,
autoStart: !window.matchMedia("(prefers-reduced-motion: reduce)").matches,
};

const createApp = ({ view, seed, autoStart }, opts) => {
history.pushState({}, "", "?" + qs.stringify({ seed }));

let { width, height } = app.screen;
let gridX = (width / CELL_SIZE) | 0;
let gridY = (height / CELL_SIZE) | 0;
const random = seedrandom(seed);

let grid = Array(gridY)
.fill(0)
.map(() =>
Array(gridX)
.fill(0)
.map(() => (Math.random() > SEED_THRESHOLD ? 1 : 0))
return new GolApp(
{
view,
resizeTo: view.parentElement,
antialias: true,
autoDensity: true,
resolution: 2,
autoStart,
},
{ ...opts, random }
);

let graphics = grid.map((row, y) =>
row.map((_, x) => {
let point = new PIXI.Graphics();
point.interactive = true;
point.x = x * CELL_SIZE;
point.y = y * CELL_SIZE;
point.alpha = !reducedMotion ? 0 : grid[y][x];
point.alphaDelta = grid[y][x] == 1 ? ALPHA_DELTA : -ALPHA_DELTA;
point.beginFill(0xffffff);
point.drawCircle(CELL_SIZE / 2, CELL_SIZE / 2, (CELL_SIZE - 2) / 2);
app.stage.addChild(point);
return point;
})
);

const drawGeneration = (delta) => {
for (let y = 0; y < grid.length; y++) {
for (let x = 0; x < grid[0].length; x++) {
let point = graphics[y][x];
point.alpha = Math.max(
0,
Math.min(1, point.alpha + point.alphaDelta * delta)
);
}
}
};

const updateAlphaDelta = () => {
for (let y = 0; y < grid.length; y++) {
for (let x = 0; x < grid[0].length; x++) {
let point = graphics[y][x];
point.alphaDelta = grid[y][x] == 1 ? ALPHA_DELTA : -ALPHA_DELTA;
}
}
const options = {
view: document.getElementById("gameCanvas"),
...DEFAULT_OPTIONS,
...qs.parse(window.location.search, { ignoreQueryPrefix: true }),
};

let secondsPassed = 0;
let nextSecond = 1;
let app = createApp(options);

drawGeneration(0);
updateAlphaDelta();
let timeInMs = 0;
document
.querySelector("#jumbotron-gol-control-shuffle")
.addEventListener("click", (e) => {
e.preventDefault();
const wasStarted = app.started;
app.destroy();

if(reducedMotion) app.render();
const oldCanvas = document.querySelector("#gameCanvas");
const newCanvas = oldCanvas.cloneNode();
oldCanvas.replaceWith(newCanvas);

// Listen for frame updates
app.ticker.add((delta) => {
timeInMs += app.ticker.elapsedMS;
if (timeInMs > TICK_EVERY_MS) {
timeInMs = timeInMs % TICK_EVERY_MS;
grid = computeNextGeneration(grid);
updateAlphaDelta();
}
drawGeneration(delta);
});
setTimeout(() => {
app = createApp(
{
...options,
view: newCanvas,
autoStart: wasStarted,
seed: (Math.random() * 10000) | 0,
},
{ tickEveryMs: app.tickEveryMs, alphaDelta: app.alphaDelta }
);
}, 0);
});

document
.querySelector("#jumbotron-gol-control-speed-slower")
.addEventListener("click", (e) => {
e.preventDefault();
app.slower();
});
document
.querySelector("#jumbotron-gol-control-speed-reset")
.addEventListener("click", (e) => {
e.preventDefault();
app.resetSpeed();
});
document
.querySelector("#jumbotron-gol-control-speed-faster")
.addEventListener("click", (e) => {
e.preventDefault();
app.faster();
});

document
.querySelector(
`#jumbotron-gol-control-pause ${!app.started ? ".fa-pause" : ".fa-play"}`
)
.classList.add("d-none");
document
.querySelector("#jumbotron-gol-control-pause")
.addEventListener("click", (e) => {
e.preventDefault();
const shouldStart = !app.started;
if (shouldStart) {
app.start();
} else {
app.stop();
}
document
.querySelector("#jumbotron-gol-control-pause [data-icon=pause]")
.classList.toggle("d-none");
document
.querySelector("#jumbotron-gol-control-pause [data-icon=play]")
.classList.toggle("d-none");
});
129 changes: 129 additions & 0 deletions js/gameOfLife/golApp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import * as PIXI from "pixi.js";
import { computeNextGeneration } from "./computeNextGeneration";

const CELL_SIZE = 20;
const TICK_EVERY_MS = 3000;
const ALPHA_DELTA = 0.01;
const SEED_THRESHOLD = 0.7;

const GRID_X = 100;
const GRID_Y = 100;

export class GolApp {
constructor(pixiOpts, { random, tickEveryMs = TICK_EVERY_MS, alphaDelta = ALPHA_DELTA }) {
this.tickEveryMs = tickEveryMs;
this.alphaDelta = alphaDelta;

this.pixiApp = new PIXI.Application(pixiOpts);

this.grid = Array(GRID_Y)
.fill(0)
.map(() =>
Array(GRID_X)
.fill(0)
.map(() => (random() > SEED_THRESHOLD ? 1 : 0))
);

let { width, height } = this.pixiApp.screen;

let gridX = Math.min(GRID_X, (width / CELL_SIZE) | 0);
let gridY = Math.min(GRID_Y, (height / CELL_SIZE) | 0);

this.secondsPassed = 0;
this.nextSecond = 1;

this.graphics = Array(gridY)
.fill(0)
.map((_, y) =>
Array(gridX)
.fill(0)
.map((_, x) => {
let point = new PIXI.Graphics();
point.interactive = true;
point.x = x * CELL_SIZE;
point.y = y * CELL_SIZE;
point.alpha = pixiOpts.autoStart ? 0 : this.grid[y][x];
point.alphaDelta =
this.grid[y][x] == 1 ? this.alphaDelta : -this.alphaDelta;
point.beginFill(0xffffff);
point.drawCircle(CELL_SIZE / 2, CELL_SIZE / 2, (CELL_SIZE - 2) / 2);
this.pixiApp.stage.addChild(point);
return point;
})
);

this.drawGeneration(0);
this.updateAlphaDelta();

let timeInMs = 0;

if (!pixiOpts.autoStart) this.pixiApp.render();

// Listen for frame updates
this.pixiApp.ticker.add((delta) => {
timeInMs += this.pixiApp.ticker.elapsedMS;
if (timeInMs > this.tickEveryMs) {
timeInMs = timeInMs % this.tickEveryMs;
this.grid = computeNextGeneration(this.grid);
this.updateAlphaDelta();
}
this.drawGeneration(delta);
});
}

faster() {
this.tickEveryMs /= 2;
this.alphaDelta *= 2;
}

slower() {
this.tickEveryMs *= 2;
this.alphaDelta /= 2;
}

resetSpeed() {
this.tickEveryMs = TICK_EVERY_MS;
this.alphaDelta = ALPHA_DELTA;
}

start() {
return this.pixiApp.start();
}

stop() {
return this.pixiApp.stop();
}

get started() {
return this.pixiApp?.ticker?.started;
}

render() {
return this.pixiApp.render();
}

destroy() {
return this.pixiApp.destroy();
}

drawGeneration(delta) {
for (let y = 0; y < this.graphics.length; y++) {
for (let x = 0; x < this.graphics[0].length; x++) {
let point = this.graphics[y][x];
point.alpha = Math.max(
0,
Math.min(1, point.alpha + point.alphaDelta * delta)
);
}
}
}

updateAlphaDelta() {
for (let y = 0; y < this.graphics.length; y++) {
for (let x = 0; x < this.graphics[0].length; x++) {
let point = this.graphics[y][x];
point.alphaDelta = this.grid[y][x] == 1 ? this.alphaDelta : -this.alphaDelta;
}
}
}
}
Loading

0 comments on commit 37dd357

Please sign in to comment.