Compare commits
No commits in common. "master" and "gh-pages" have entirely different histories.
26 changed files with 2 additions and 14106 deletions
3
.babelrc
3
.babelrc
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"presets": ["@babel/preset-env"]
|
||||
}
|
34
.github/workflows/main.yml
vendored
34
.github/workflows/main.yml
vendored
|
@ -1,34 +0,0 @@
|
|||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: CI
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# Runs a single command using the runners shell
|
||||
- name: Install Deps
|
||||
run: npm install
|
||||
- name: Test
|
||||
run: npm run test
|
||||
- name: Build
|
||||
run: npm run build
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./dist
|
11
README.md
11
README.md
|
@ -1,11 +0,0 @@
|
|||
# Game of Life
|
||||
*It's not really about the Game of Life*
|
||||
|
||||
A weekend project with the following goals:
|
||||
- [x] implement webpack build with babel
|
||||
- [x] develop a simple Conway's Game of Life app without framework reliance
|
||||
- [x] seed Game of Life with GitHub contribution calendars just for fun
|
||||
- [x] implement a GitHub Actions workflow
|
||||
|
||||
![Screenshot of Game of Life being seeded with creator's contribution calendar](./SeedScreenshot.png)
|
||||
![Screenshot of same Game of Life running a few generations later](./RunningScreenshot.png)
|
Binary file not shown.
Before Width: | Height: | Size: 97 KiB |
Binary file not shown.
Before Width: | Height: | Size: 96 KiB |
1
app.bundle.js
Normal file
1
app.bundle.js
Normal file
File diff suppressed because one or more lines are too long
21
index.html
21
index.html
|
@ -1,20 +1 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Game of Life</title>
|
||||
</head>
|
||||
<body>
|
||||
<aside id="controls">
|
||||
<h1>Game of Life</h1>
|
||||
<!-- insert form for fetch seed data -->
|
||||
<!-- insert controls for animation -->
|
||||
</aside>
|
||||
<main>
|
||||
<p>Enter valid GitHub user handle above to seed with contribution calendar</p>
|
||||
<p>Touch below to toggle seed cells alive or dead</p>
|
||||
<canvas id="game-field" width="500" height="300"></canvas>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Game of Life</title></head><body><aside id="controls"><h1>Game of Life</h1></aside><main><p>Enter valid GitHub user handle above to seed with contribution calendar</p><p>Touch below to toggle seed cells alive or dead</p><canvas id="game-field" width="500" height="300"></canvas></main><script src="app.bundle.js"></script></body></html>
|
12659
package-lock.json
generated
12659
package-lock.json
generated
File diff suppressed because it is too large
Load diff
41
package.json
41
package.json
|
@ -1,41 +0,0 @@
|
|||
{
|
||||
"name": "game-of-life",
|
||||
"version": "1.0.0",
|
||||
"homepage": "https://sorrelbri.github.io/game-of-life",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "webpack --mode=production --config webpack.prod.js",
|
||||
"test": "jest",
|
||||
"start": "webpack-dev-server --open --config webpack.dev.js",
|
||||
"cleanup": "rm -rf node_modules/gh-pages/.cache",
|
||||
"deploy": "npm run cleanup && gh-pages -d dist"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.9.6",
|
||||
"@babel/preset-env": "^7.9.6",
|
||||
"babel-jest": "^26.0.1",
|
||||
"babel-loader": "^8.1.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"css-loader": "^3.5.3",
|
||||
"gh-pages": "^2.2.0",
|
||||
"html-loader": "^1.1.0",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
"jest": "^26.0.1",
|
||||
"style-loader": "^1.2.1",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-merge": "^4.2.2"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
".*": "<rootDir>/node_modules/babel-jest"
|
||||
},
|
||||
"moduleFileExtensions": [
|
||||
"js"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
const { Stream } = require("../utils");
|
||||
|
||||
class Cell {
|
||||
constructor(living = false, liveNeighbors = 0) {
|
||||
this.living = living;
|
||||
this.liveNeighbors = liveNeighbors;
|
||||
}
|
||||
toggleLiving() {
|
||||
this.living = !this.living;
|
||||
}
|
||||
addLiveNeighbor() {
|
||||
this.liveNeighbors++;
|
||||
}
|
||||
setLiving() {
|
||||
if (this.living && this.liveNeighbors !== 2 && this.liveNeighbors !== 3) {
|
||||
this.liveNeighbors = 0;
|
||||
return (this.living = false);
|
||||
}
|
||||
if (this.liveNeighbors === 3) {
|
||||
this.liveNeighbors = 0;
|
||||
return (this.living = true);
|
||||
}
|
||||
this.liveNeighbors = 0;
|
||||
}
|
||||
}
|
||||
|
||||
class CellStream extends Stream {
|
||||
constructor(head, next) {
|
||||
super(head, next);
|
||||
}
|
||||
get living() {
|
||||
return this.head.living;
|
||||
}
|
||||
get liveNeighbors() {
|
||||
return this.head.liveNeighbors;
|
||||
}
|
||||
set liveNeighbors(liveNeighbors) {
|
||||
this.head.liveNeighbors = liveNeighbors;
|
||||
}
|
||||
addLiveNeighbor() {
|
||||
this.head.addLiveNeighbor();
|
||||
}
|
||||
setLiving() {
|
||||
this.head.setLiving();
|
||||
}
|
||||
toggleLiving() {
|
||||
this.head.toggleLiving();
|
||||
}
|
||||
}
|
||||
|
||||
const cellStream = (living = false, liveNeighbors = 0) => {
|
||||
return new CellStream(new Cell(living, liveNeighbors), function () {
|
||||
this.head.setLiving();
|
||||
return this;
|
||||
});
|
||||
};
|
||||
|
||||
// as a stream -> cellStream = Stream(Cell, () => Cell(cellStream.isLiving()))
|
||||
// in this case GameField = { [x-y]: cellStream }
|
||||
// communicating with neighbors = filter for (Boolean(Cell.living)) -> Cell neighbors.addLivingNeighbor
|
||||
// controlling whether to call or not: filter for (Boolean(Cell.living) || Boolean(cell.liveNeighbors)) -> cellStream.next
|
||||
|
||||
module.exports = {
|
||||
Cell,
|
||||
cellStream,
|
||||
};
|
|
@ -1,104 +0,0 @@
|
|||
const html = require("./controls.html");
|
||||
const css = require("../styles/controls.css");
|
||||
const { getCalendar } = require("../utils/index");
|
||||
|
||||
const init = (gameField) => {
|
||||
const controlsEl = document.getElementById("controls");
|
||||
controlsEl.innerHTML += html;
|
||||
const rateEl = document.getElementById("rate");
|
||||
const forwardEl = document.getElementById("forward");
|
||||
const playEl = document.getElementById("play");
|
||||
const resetEl = document.getElementById("reset");
|
||||
const clearEl = document.getElementById("clear");
|
||||
const canvasEl = document.getElementById("game-field");
|
||||
const calendarFormEl = document.getElementById("calendar-form");
|
||||
const controls = {
|
||||
interval: null,
|
||||
play() {
|
||||
const gameLoop = () => gameField.advance();
|
||||
this.interval = setInterval(gameLoop, 1000 / this.rate);
|
||||
},
|
||||
pause() {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
gameField = gameField.clear();
|
||||
},
|
||||
reset() {
|
||||
this.pause();
|
||||
gameField = gameField.reset();
|
||||
},
|
||||
seed(weeks) {
|
||||
if (weeks.length) {
|
||||
gameField = gameField.seed(weeks);
|
||||
}
|
||||
},
|
||||
forward() {
|
||||
gameField.advance();
|
||||
},
|
||||
rate: 10,
|
||||
updateField(x, y) {
|
||||
gameField = gameField.toggleCell(x, y);
|
||||
},
|
||||
updateRate(rate) {
|
||||
controls.rate = rate;
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval);
|
||||
this.play();
|
||||
}
|
||||
},
|
||||
};
|
||||
rateEl.addEventListener("change", (e) => {
|
||||
e.preventDefault();
|
||||
controls.updateRate(e.target.value);
|
||||
});
|
||||
forwardEl.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
controls.forward();
|
||||
});
|
||||
playEl.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
if (controls.interval) {
|
||||
controls.pause();
|
||||
return (forwardEl.disabled = false);
|
||||
}
|
||||
controls.play();
|
||||
forwardEl.disabled = true;
|
||||
});
|
||||
resetEl.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
controls.reset();
|
||||
});
|
||||
clearEl.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
controls.clear();
|
||||
});
|
||||
canvasEl.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
if (controls.interval) return;
|
||||
const { offsetX, offsetY } = e;
|
||||
controls.updateField(offsetX, offsetY);
|
||||
});
|
||||
calendarFormEl.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const user = e.target[0].value;
|
||||
getCalendar(user)
|
||||
.then((data) => {
|
||||
return data.map((week) => {
|
||||
return week.contributionDays.map((day) => day.contributionCount);
|
||||
});
|
||||
})
|
||||
.then((weeks) => controls.seed(weeks))
|
||||
.catch((e) => {
|
||||
calendarFormEl.elements[0].value = "enter valid handle";
|
||||
});
|
||||
});
|
||||
return controls;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
init,
|
||||
};
|
|
@ -1,85 +0,0 @@
|
|||
const { cellStream } = require("./Cell");
|
||||
const { Stream, getNeighbors } = require("../utils");
|
||||
|
||||
class GameField {
|
||||
constructor({ fieldArray = [], fieldMap = {} }) {
|
||||
// seed = [ [] ]
|
||||
this.map = {};
|
||||
fieldArray.forEach((subArray, majorIndex) =>
|
||||
subArray.forEach((value, minorIndex) => {
|
||||
if (value > 0) {
|
||||
this.map[`${majorIndex},${minorIndex}`] = cellStream(true, 0);
|
||||
}
|
||||
})
|
||||
);
|
||||
Object.entries(fieldMap).forEach(
|
||||
([key, [live, neighbors]]) =>
|
||||
(this.map[key] = cellStream(live, neighbors))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FieldStream extends Stream {
|
||||
constructor(head, next) {
|
||||
super(head, next);
|
||||
}
|
||||
get map() {
|
||||
return this.head.map;
|
||||
}
|
||||
addLiveNeighbor(key) {
|
||||
if (this.map[key] === undefined) {
|
||||
this.map[key] = cellStream(false);
|
||||
}
|
||||
this.map[key].addLiveNeighbor();
|
||||
}
|
||||
}
|
||||
|
||||
const seedMap = (map, [key, seed]) => {
|
||||
map[key] = seed;
|
||||
return map;
|
||||
};
|
||||
const isLiving = ([key, cell]) => cell.living === true;
|
||||
const incrementLiveNeighbors = (field) => ([key]) =>
|
||||
getNeighbors(key).forEach((neighbor) => field.addLiveNeighbor(neighbor));
|
||||
const makeSeed = ([key, cell]) => [key, [cell.living, cell.liveNeighbors]];
|
||||
const makeSeedNextGen = ([key, cell]) => {
|
||||
cell.setLiving();
|
||||
return [key, [cell.living, 0]];
|
||||
};
|
||||
|
||||
const fieldStream = ({ fieldArray, fieldMap }) => {
|
||||
return new FieldStream(new GameField({ fieldArray, fieldMap }), function () {
|
||||
// calculate liveNeighbors for all cells on first next call
|
||||
Object.entries(this.map)
|
||||
.filter(isLiving)
|
||||
.forEach(incrementLiveNeighbors(this));
|
||||
// generate seed for next Stream with liveNeighbors
|
||||
const mapWithLiveNeighbors = Object.entries(this.map)
|
||||
.map(makeSeed)
|
||||
.reduce(seedMap, {});
|
||||
// return next stream
|
||||
return new FieldStream(
|
||||
new GameField({ fieldMap: mapWithLiveNeighbors }),
|
||||
function () {
|
||||
// determine living cells for next generation
|
||||
const nextGeneration = Object.entries(this.map)
|
||||
.map(makeSeedNextGen)
|
||||
.reduce(seedMap, {});
|
||||
// seed next Stream
|
||||
return fieldStream({ fieldMap: nextGeneration });
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// wrapper for fieldStream
|
||||
// -- .reset => instantiates new fieldStream
|
||||
// -- .toggle(cell) => manually toggles cell state
|
||||
|
||||
// instantiate table (orientation of major and minor axis dependent on viewport)
|
||||
// const gameFields = new Array(1).fill(new GameField({}));
|
||||
|
||||
module.exports = {
|
||||
GameField,
|
||||
fieldStream,
|
||||
};
|
|
@ -1,71 +0,0 @@
|
|||
const css = require("../styles/gameField.css");
|
||||
const { fieldStream } = require("./GameField");
|
||||
|
||||
const canvasEl = document.getElementById("game-field");
|
||||
const canvas2D = canvasEl.getContext("2d");
|
||||
canvas2D.fillStyle = "white";
|
||||
|
||||
const parseSeed = (seed) => {
|
||||
if (seed && Array.isArray(seed)) {
|
||||
return {
|
||||
fieldArray: seed,
|
||||
};
|
||||
}
|
||||
return seed;
|
||||
};
|
||||
|
||||
const fieldView = (seed) => {
|
||||
seed = parseSeed(seed);
|
||||
const field = fieldStream(seed);
|
||||
const view = {
|
||||
draw(x, y) {
|
||||
const { scale, offset } = this.dimension;
|
||||
canvas2D.fillRect(x * scale + offset, y * scale + offset, scale, scale);
|
||||
},
|
||||
dimension: { x0: 0, y0: 0, x1: 500, y1: 300, scale: 6, offset: 100 },
|
||||
field,
|
||||
updateView() {
|
||||
canvas2D.clearRect(0, 0, this.dimension.x1, this.dimension.y1);
|
||||
Object.entries(this.field.map)
|
||||
.filter(([key, cell]) => cell.living)
|
||||
.map(([key]) => key.split(","))
|
||||
.forEach(([x, y]) => this.draw(x, y));
|
||||
},
|
||||
clear() {
|
||||
return fieldView({});
|
||||
},
|
||||
reset() {
|
||||
const newField = fieldView(seed);
|
||||
newField.updateView();
|
||||
return newField;
|
||||
},
|
||||
seed(seed) {
|
||||
return fieldView(seed);
|
||||
},
|
||||
advance() {
|
||||
this.field = this.field.next.next;
|
||||
this.updateView();
|
||||
},
|
||||
toggleCell(x, y) {
|
||||
const { scale, offset } = this.dimension;
|
||||
x = Math.floor((x - offset) / scale);
|
||||
y = Math.floor((y - offset) / scale);
|
||||
const fieldMap = Object.entries(this.field.map)
|
||||
.map(([key, cell]) => [key, cell.living])
|
||||
.reduce((map, [key, living]) => {
|
||||
map[key] = [living];
|
||||
return map;
|
||||
}, {});
|
||||
fieldMap[`${x},${y}`] = !!fieldMap[`${x},${y}`]
|
||||
? [!fieldMap[`${x},${y}`][0]]
|
||||
: [true];
|
||||
return new fieldView({ fieldMap });
|
||||
},
|
||||
};
|
||||
view.updateView();
|
||||
return view;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
fieldView,
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
<div class="playControls">
|
||||
<button id="clear">⏹️</button>
|
||||
<button id="reset">🔄️</button>
|
||||
<button id="play">⏯️</button>
|
||||
<button id="forward">⏩️</button>
|
||||
<input
|
||||
type="range"
|
||||
id="rate"
|
||||
min="1"
|
||||
max="20"
|
||||
value="10"
|
||||
/>
|
||||
</div>
|
||||
<form action="/" id="calendar-form">
|
||||
<input type="text" value="GitHub handle"/>
|
||||
<input type="submit" value="seed" />
|
||||
</form>
|
|
@ -1,7 +0,0 @@
|
|||
import reset from "./styles/reset.css";
|
||||
import css from "./styles/style.css";
|
||||
const { fieldView } = require("./components/GameFieldTable");
|
||||
const { init } = require("./components/Controls");
|
||||
(() => console.log("hello world!"))();
|
||||
window.game = fieldView([]);
|
||||
window.controls = init(game);
|
|
@ -1,51 +0,0 @@
|
|||
aside {
|
||||
background: #944;
|
||||
justify-content: space-evenly;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
position: float;
|
||||
top: 0;
|
||||
border-bottom: solid 1em #522;
|
||||
}
|
||||
|
||||
aside * {
|
||||
background: inherit;
|
||||
color: #dff;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 130%;
|
||||
padding: 1em;
|
||||
|
||||
}
|
||||
|
||||
div.playControls {
|
||||
min-width: 30vw;
|
||||
display: grid;
|
||||
border-radius: 1vh;
|
||||
border: solid .5vh;
|
||||
grid-template-rows: 2fr 1fr;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
gap: 1vh;
|
||||
grid-template-areas: "buttons buttons buttons buttons" "slider slider slider slider";
|
||||
padding: 1vh;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
background: #ccc;
|
||||
border-radius: .5vh;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background: #999;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
grid-area: slider;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 0.5em;
|
||||
padding: 0.5em
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
canvas {
|
||||
background: #222;
|
||||
color: white;
|
||||
border: 0.5vmin solid #aaa;
|
||||
fill: white;
|
||||
margin: 0 3em 3em 3em;
|
||||
}
|
|
@ -1,361 +0,0 @@
|
|||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0-modified | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
input[type=search]::-webkit-search-cancel-button,
|
||||
input[type=search]::-webkit-search-decoration,
|
||||
input[type=search]::-webkit-search-results-button,
|
||||
input[type=search]::-webkit-search-results-decoration {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
input[type=search] {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
-webkit-box-sizing: content-box;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
vertical-align: top;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
video {
|
||||
display: inline-block;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent modern browsers from displaying `audio` without controls.
|
||||
* Remove excess height in iOS 5 devices.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
|
||||
* Known issue: no IE 6 support.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
|
||||
* `em` units.
|
||||
* 2. Prevent iOS text size adjust after orientation change, without disabling
|
||||
* user zoom.
|
||||
*/
|
||||
|
||||
html {
|
||||
font-size: 100%; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `outline` inconsistency between Chrome and other browsers.
|
||||
*/
|
||||
|
||||
a:focus {
|
||||
outline: thin dotted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improve readability when focused and also mouse hovered in all browsers.
|
||||
*/
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
|
||||
* 2. Improve image quality when scaled in IE 7.
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0; /* 1 */
|
||||
-ms-interpolation-mode: bicubic; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct margin displayed oddly in IE 6/7.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Define consistent border, margin, and padding.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct color not being inherited in IE 6/7/8/9.
|
||||
* 2. Correct text not wrapping in Firefox 3.
|
||||
* 3. Correct alignment displayed oddly in IE 6/7.
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0;
|
||||
white-space: normal; /* 2 */
|
||||
*margin-left: -7px; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct font size not being inherited in all browsers.
|
||||
* 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
|
||||
* and Chrome.
|
||||
* 3. Improve appearance and consistency in all browsers.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-size: 100%; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
vertical-align: baseline; /* 3 */
|
||||
*vertical-align: middle; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Firefox 3+ setting `line-height` on `input` using `!important` in
|
||||
* the UA stylesheet.
|
||||
*/
|
||||
|
||||
button,
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
||||
* All other form control elements do not inherit `text-transform` values.
|
||||
* Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
|
||||
* Correct `select` style inheritance in Firefox 4+ and Opera.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
||||
* and `video` controls.
|
||||
* 2. Correct inability to style clickable `input` types in iOS.
|
||||
* 3. Improve usability and consistency of cursor style between image-type
|
||||
* `input` and others.
|
||||
* 4. Remove inner spacing in IE 7 without affecting normal text inputs.
|
||||
* Known issue: inner spacing remains in IE 6.
|
||||
*/
|
||||
|
||||
button,
|
||||
html input[type="button"], /* 1 */
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
cursor: pointer; /* 3 */
|
||||
*overflow: visible; /* 4 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-set default cursor for disabled elements.
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
html input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Address box sizing set to content-box in IE 8/9.
|
||||
* 2. Remove excess padding in IE 8/9.
|
||||
* 3. Remove excess padding in IE 7.
|
||||
* Known issue: excess padding remains in IE 6.
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
*height: 13px; /* 3 */
|
||||
*width: 13px; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
|
||||
* 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
|
||||
* (include `-moz` to future-proof).
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and search cancel button in Safari 5 and Chrome
|
||||
* on OS X.
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and border in Firefox 3+.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove default vertical scrollbar in IE 6/7/8/9.
|
||||
* 2. Improve readability and alignment in all browsers.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto; /* 1 */
|
||||
vertical-align: top; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove most spacing between table cells.
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
color: #222;
|
||||
}
|
||||
|
||||
|
||||
::-moz-selection {
|
||||
background: #b3d4fc;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #b3d4fc;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.chromeframe {
|
||||
margin: 0.2em 0;
|
||||
background: #ccc;
|
||||
color: #000;
|
||||
padding: 0.2em 0;
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
* {
|
||||
background: #333;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main, aside {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
main {
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
main p {
|
||||
padding: 1em;
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
import { Cell, cellStream } from "../components/Cell";
|
||||
|
||||
describe("Cell functionality", () => {
|
||||
test("dispatch toggleLiving state should mark living cell dead", () => {
|
||||
const cell = new Cell(true);
|
||||
cell.toggleLiving();
|
||||
expect(cell.living).toEqual(false);
|
||||
});
|
||||
test("dispatch toggleLiving state should mark dead cell living", () => {
|
||||
const cell = new Cell();
|
||||
cell.toggleLiving();
|
||||
expect(cell.living).toEqual(true);
|
||||
});
|
||||
test("dispatch add Live Neighbor should increment live neighbors property", () => {
|
||||
const cell = new Cell();
|
||||
cell.addLiveNeighbor();
|
||||
expect(cell.liveNeighbors).toEqual(1);
|
||||
});
|
||||
|
||||
const livingFromLivingStates = new Array(8)
|
||||
.fill()
|
||||
.map((_, i) => (i === 2 || i === 3 ? true : false));
|
||||
|
||||
livingFromLivingStates.forEach((state, liveNeighbors) => {
|
||||
test(`dispatch setLiving on live cell with ${liveNeighbors} neighbors should result in living = ${state}`, () => {
|
||||
const cell = new Cell(true);
|
||||
new Array(liveNeighbors).fill().forEach((_) => cell.addLiveNeighbor());
|
||||
cell.setLiving();
|
||||
expect(cell.living).toEqual(state);
|
||||
});
|
||||
});
|
||||
|
||||
const livingFromDeadStates = new Array(8)
|
||||
.fill()
|
||||
.map((_, i) => (i === 3 ? true : false));
|
||||
|
||||
livingFromDeadStates.forEach((state, liveNeighbors) => {
|
||||
test(`dispatch setLiving on dead cell with ${liveNeighbors} neighbors should result in living = ${state}`, () => {
|
||||
const cell = new Cell();
|
||||
new Array(liveNeighbors).fill().forEach((_) => cell.addLiveNeighbor());
|
||||
cell.setLiving();
|
||||
expect(cell.living).toEqual(state);
|
||||
});
|
||||
});
|
||||
|
||||
const cellStreamNextCalls = [
|
||||
[2, false],
|
||||
[3, true],
|
||||
[0, false],
|
||||
[5, false],
|
||||
];
|
||||
|
||||
cellStreamNextCalls.forEach(([liveNeighbors, livingResult]) => {
|
||||
test(`cellStream advances cell state for ${liveNeighbors} live Neighbors (from dead cell)`, () => {
|
||||
const cell = cellStream(false, liveNeighbors).next;
|
||||
expect(cell.living).toEqual(livingResult);
|
||||
});
|
||||
});
|
||||
|
||||
[[2, true], ...cellStreamNextCalls.slice(1)].forEach(
|
||||
([liveNeighbors, livingResult]) => {
|
||||
test(`cellStream advances cell state for ${liveNeighbors} live Neighbors (from living cell)`, () => {
|
||||
const cell = cellStream(true, liveNeighbors).next;
|
||||
expect(cell.living).toEqual(livingResult);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
|
@ -1,368 +0,0 @@
|
|||
import { GameField, fieldStream } from "../components/GameField";
|
||||
|
||||
describe("Game Field seeds living Cells with array", () => {
|
||||
const fieldArray = [
|
||||
[0, 1, 0],
|
||||
[1, 0, 1],
|
||||
];
|
||||
|
||||
const fieldMap = {
|
||||
"0,0": [true, 0],
|
||||
"0,2": [true, 0],
|
||||
"1,1": [true, 0],
|
||||
};
|
||||
const gameArraySeed = new GameField({ fieldArray });
|
||||
const gameMapSeed = new GameField({ fieldMap });
|
||||
const streamArraySeed = fieldStream({ fieldArray });
|
||||
const streamMapSeed = fieldStream({ fieldMap });
|
||||
|
||||
["0,1", "1,0", "1,2"].forEach((key) => {
|
||||
test(`Array seed: ${key} should equal living Cell`, () => {
|
||||
expect(gameArraySeed.map[key].living).toEqual(true);
|
||||
});
|
||||
|
||||
test(`Map seed: ${key} should equal undefined`, () => {
|
||||
expect(gameMapSeed.map[key]).toEqual(undefined);
|
||||
});
|
||||
|
||||
test(`Stream array seed: ${key} should equal living Cell`, () => {
|
||||
expect(streamArraySeed.map[key].living).toEqual(true);
|
||||
});
|
||||
|
||||
test(`Stream map seed: ${key} should equal undefined`, () => {
|
||||
expect(streamMapSeed.map[key]).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
["0,0", "0,2", "1,1"].forEach((key) => {
|
||||
test(`Array seed: ${key} should equal undefined`, () => {
|
||||
expect(gameArraySeed.map[key]).toEqual(undefined);
|
||||
});
|
||||
|
||||
test(`Map seed: ${key} should equal living Cell`, () => {
|
||||
expect(gameMapSeed.map[key].living).toEqual(true);
|
||||
});
|
||||
|
||||
test(`Stream array seed: ${key} should equal undefined`, () => {
|
||||
expect(streamArraySeed.map[key]).toEqual(undefined);
|
||||
});
|
||||
|
||||
test(`Stream map seed: ${key} should equal living Cell`, () => {
|
||||
expect(streamMapSeed.map[key].living).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("fieldStream.next calculates liveNeighbors", () => {
|
||||
const fieldArray = [
|
||||
[0, 1, 0],
|
||||
[1, 0, 1],
|
||||
];
|
||||
const testStream = fieldStream({ fieldArray });
|
||||
[
|
||||
["-1,0", 1],
|
||||
["-1,1", 1],
|
||||
["-1,2", 1],
|
||||
["0,-1", 1],
|
||||
["0,0", 2],
|
||||
["0,1", 2],
|
||||
["0,2", 2],
|
||||
["0,3", 1],
|
||||
["1,-1", 1],
|
||||
["1,0", 1],
|
||||
["1,1", 3],
|
||||
["1,2", 1],
|
||||
["1,3", 1],
|
||||
["2,-1", 1],
|
||||
["2,0", 1],
|
||||
["2,1", 2],
|
||||
["2,2", 1],
|
||||
["2,3", 1],
|
||||
].forEach(([key, liveNeighbors]) => {
|
||||
test(`after .next ${key} in 1st should have ${liveNeighbors} liveNeighbors`, () => {
|
||||
expect(testStream.next.map[key].liveNeighbors).toEqual(liveNeighbors);
|
||||
});
|
||||
});
|
||||
|
||||
const fieldMap = {
|
||||
"0,1": [true, 0],
|
||||
"0,2": [true, 0],
|
||||
"1,0": [true, 0],
|
||||
"1,1": [true, 0],
|
||||
"1,3": [true, 0],
|
||||
"2,1": [true, 0],
|
||||
"2,2": [true, 0],
|
||||
};
|
||||
const testStream2 = fieldStream({ fieldMap });
|
||||
[
|
||||
["-1,0", 1],
|
||||
["-1,1", 2],
|
||||
["-1,2", 2],
|
||||
["-1,3", 1],
|
||||
["0,-1", 1],
|
||||
["0,0", 3],
|
||||
["0,1", 3],
|
||||
["0,2", 3],
|
||||
["0,3", 2],
|
||||
["0,4", 1],
|
||||
["1,-1", 1],
|
||||
["1,0", 3],
|
||||
["1,1", 5],
|
||||
["1,2", 6],
|
||||
["1,3", 2],
|
||||
["1,4", 1],
|
||||
["2,-1", 1],
|
||||
["2,0", 3],
|
||||
["2,1", 3],
|
||||
["2,2", 3],
|
||||
["2,3", 2],
|
||||
["2,4", 1],
|
||||
["3,0", 1],
|
||||
["3,1", 2],
|
||||
["3,2", 2],
|
||||
["3,3", 1],
|
||||
].forEach(([key, liveNeighbors]) => {
|
||||
test(`after .next ${key} in 2nd should have ${liveNeighbors} liveNeighbors`, () => {
|
||||
expect(testStream2.next.map[key].liveNeighbors).toEqual(liveNeighbors);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("fieldStream.next tests still lifes", () => {
|
||||
const blockArray = [
|
||||
[1, 1],
|
||||
[1, 1],
|
||||
];
|
||||
|
||||
const streamBlock = fieldStream({ fieldArray: blockArray });
|
||||
[
|
||||
["-1,-1", false],
|
||||
["-1,0", false],
|
||||
["-1,1", false],
|
||||
["-1,2", false],
|
||||
["0,-1", false],
|
||||
["0,0", true],
|
||||
["0,1", true],
|
||||
["0,2", false],
|
||||
["1,-1", false],
|
||||
["1,0", true],
|
||||
["1,1", true],
|
||||
["1,2", false],
|
||||
["2,-1", false],
|
||||
["2,0", false],
|
||||
["2,1", false],
|
||||
["2,2", false],
|
||||
].forEach(([key, live]) => {
|
||||
test(`after one generation of Block, ${key} alive: ${live}`, () => {
|
||||
expect(streamBlock.next.next.map[key].living).toEqual(live);
|
||||
});
|
||||
});
|
||||
|
||||
const beehiveMap = {
|
||||
"0,1": [true, 0],
|
||||
"0,2": [true, 0],
|
||||
"1,0": [true, 0],
|
||||
"1,3": [true, 0],
|
||||
"2,1": [true, 0],
|
||||
"2,2": [true, 0],
|
||||
};
|
||||
const streamBeehive = fieldStream({ fieldMap: beehiveMap });
|
||||
[
|
||||
["-1,0", false],
|
||||
["-1,1", false],
|
||||
["-1,2", false],
|
||||
["-1,3", false],
|
||||
["0,-1", false],
|
||||
["0,0", false],
|
||||
["0,1", true],
|
||||
["0,2", true],
|
||||
["0,3", false],
|
||||
["0,4", false],
|
||||
["1,-1", false],
|
||||
["1,0", true],
|
||||
["1,1", false],
|
||||
["1,2", false],
|
||||
["1,3", true],
|
||||
["1,4", false],
|
||||
["2,-1", false],
|
||||
["2,0", false],
|
||||
["2,1", true],
|
||||
["2,2", true],
|
||||
["2,3", false],
|
||||
["2,4", false],
|
||||
["3,0", false],
|
||||
["3,1", false],
|
||||
["3,2", false],
|
||||
["3,3", false],
|
||||
].forEach(([key, live]) => {
|
||||
test(`after one generation of Beehive, ${key} alive: ${live}`, () => {
|
||||
expect(streamBeehive.next.next.map[key].living).toEqual(live);
|
||||
});
|
||||
});
|
||||
|
||||
const boatArray = [
|
||||
[1, 1, 0],
|
||||
[1, 0, 1],
|
||||
[0, 1, 0],
|
||||
];
|
||||
const streamBoat = fieldStream({ fieldArray: boatArray });
|
||||
[
|
||||
["-1,-1", false],
|
||||
["-1,0", false],
|
||||
["-1,1", false],
|
||||
["-1,2", false],
|
||||
["0,-1", false],
|
||||
["0,0", true],
|
||||
["0,1", true],
|
||||
["0,2", false],
|
||||
["1,-1", false],
|
||||
["1,0", true],
|
||||
["1,1", false],
|
||||
["1,2", true],
|
||||
["1,3", false],
|
||||
["2,-1", false],
|
||||
["2,0", false],
|
||||
["2,1", true],
|
||||
["2,2", false],
|
||||
["2,3", false],
|
||||
["3,0", false],
|
||||
["3,1", false],
|
||||
["3,2", false],
|
||||
].forEach(([key, live]) => {
|
||||
test(`after one generation of Beehive, ${key} alive: ${live}`, () => {
|
||||
expect(streamBoat.next.next.map[key].living).toEqual(live);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("fieldStream.next tests oscillators, spaceships", () => {
|
||||
const blinkerArray = [
|
||||
[0, 1, 0],
|
||||
[0, 1, 0],
|
||||
[0, 1, 0],
|
||||
];
|
||||
const streamBlinker = fieldStream({ fieldArray: blinkerArray });
|
||||
[
|
||||
["0,0", false],
|
||||
["0,1", false],
|
||||
["0,2", false],
|
||||
["1,0", true],
|
||||
["1,1", true],
|
||||
["1,2", true],
|
||||
["2,0", false],
|
||||
["2,1", false],
|
||||
["2,2", false],
|
||||
].forEach(([key, live]) => {
|
||||
test(`after one generation of blinker, ${key} alive: ${live}`, () => {
|
||||
expect(streamBlinker.next.next.map[key].living).toEqual(live);
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
["0,0", false],
|
||||
["0,1", true],
|
||||
["0,2", false],
|
||||
["1,0", false],
|
||||
["1,1", true],
|
||||
["1,2", false],
|
||||
["2,0", false],
|
||||
["2,1", true],
|
||||
["2,2", false],
|
||||
].forEach(([key, live]) => {
|
||||
test(`after two generations of blinker, ${key} alive: ${live}`, () => {
|
||||
expect(streamBlinker.next.next.next.next.map[key].living).toEqual(live);
|
||||
});
|
||||
});
|
||||
|
||||
const gliderArray = [
|
||||
[0, 1, 0],
|
||||
[0, 0, 1],
|
||||
[1, 1, 1],
|
||||
];
|
||||
const streamGlider = fieldStream({ fieldArray: gliderArray });
|
||||
|
||||
[
|
||||
["0,0", false],
|
||||
["0,1", false],
|
||||
["0,2", false],
|
||||
["1,0", true],
|
||||
["1,1", false],
|
||||
["1,2", true],
|
||||
["2,0", false],
|
||||
["2,1", true],
|
||||
["2,2", true],
|
||||
["3,0", false],
|
||||
["3,1", true],
|
||||
["3,2", false],
|
||||
].forEach(([key, live]) => {
|
||||
test(`after one generation of glider, ${key} alive: ${live}`, () => {
|
||||
expect(streamGlider.next.next.map[key].living).toEqual(live);
|
||||
});
|
||||
});
|
||||
[
|
||||
["0,0", false],
|
||||
["0,1", false],
|
||||
["0,2", false],
|
||||
["1,0", false],
|
||||
["1,1", false],
|
||||
["1,2", true],
|
||||
["2,0", true],
|
||||
["2,1", false],
|
||||
["2,2", true],
|
||||
["3,0", false],
|
||||
["3,1", true],
|
||||
["3,2", true],
|
||||
].forEach(([key, live]) => {
|
||||
test(`after two generations of glider, ${key} alive: ${live}`, () => {
|
||||
expect(streamGlider.next.next.next.next.map[key].living).toEqual(live);
|
||||
});
|
||||
});
|
||||
[
|
||||
["0,0", false],
|
||||
["0,1", false],
|
||||
["0,2", false],
|
||||
["0,3", false],
|
||||
["1,0", false],
|
||||
["1,1", true],
|
||||
["1,2", false],
|
||||
["1,3", false],
|
||||
["2,0", false],
|
||||
["2,1", false],
|
||||
["2,2", true],
|
||||
["2,3", true],
|
||||
["3,0", false],
|
||||
["3,1", true],
|
||||
["3,2", true],
|
||||
["3,3", false],
|
||||
].forEach(([key, live]) => {
|
||||
test(`after three generations of glider, ${key} alive: ${live}`, () => {
|
||||
expect(
|
||||
streamGlider.next.next.next.next.next.next.map[key].living
|
||||
).toEqual(live);
|
||||
});
|
||||
});
|
||||
[
|
||||
["0,0", false],
|
||||
["0,1", false],
|
||||
["0,2", false],
|
||||
["0,3", false],
|
||||
["1,0", false],
|
||||
["1,1", false],
|
||||
["1,2", true],
|
||||
["1,3", false],
|
||||
["2,0", false],
|
||||
["2,1", false],
|
||||
["2,2", false],
|
||||
["2,3", true],
|
||||
["3,0", false],
|
||||
["3,1", true],
|
||||
["3,2", true],
|
||||
["3,3", true],
|
||||
].forEach(([key, live]) => {
|
||||
test(`after four generations of glider, ${key} alive: ${live}`, () => {
|
||||
expect(
|
||||
streamGlider.next.next.next.next.next.next.next.next.map[key].living
|
||||
).toEqual(live);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,47 +0,0 @@
|
|||
const options = {
|
||||
method: "get",
|
||||
};
|
||||
|
||||
const getCalendar = (user) => {
|
||||
return fetch(
|
||||
`https://appkeychain.herokuapp.com/git/calendar/${user}`,
|
||||
options
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.catch((e) => e);
|
||||
};
|
||||
|
||||
class Stream {
|
||||
constructor(head, next) {
|
||||
this.head = head;
|
||||
this.tail = next;
|
||||
this.memo = false;
|
||||
}
|
||||
get next() {
|
||||
if (!this.memo) {
|
||||
this.tail = this.tail();
|
||||
this.memo = true;
|
||||
}
|
||||
return this.tail;
|
||||
}
|
||||
}
|
||||
|
||||
const getNeighbors = (key) => {
|
||||
const [x, y] = key.split(",").map((str) => parseInt(str));
|
||||
return [
|
||||
[x - 1, y - 1],
|
||||
[x - 1, y],
|
||||
[x - 1, y + 1],
|
||||
[x, y - 1],
|
||||
[x, y + 1],
|
||||
[x + 1, y - 1],
|
||||
[x + 1, y],
|
||||
[x + 1, y + 1],
|
||||
].map((arr) => arr.join(","));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
Stream,
|
||||
getNeighbors,
|
||||
getCalendar,
|
||||
};
|
|
@ -1,43 +0,0 @@
|
|||
const path = require("path");
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
app: "./src/index.js",
|
||||
},
|
||||
plugins: [
|
||||
// new CleanWebpackPlugin(['dist/*']) for < v2 versions of CleanWebpackPlugin
|
||||
new CleanWebpackPlugin(),
|
||||
new HtmlWebpackPlugin({
|
||||
template: "index.html",
|
||||
}),
|
||||
],
|
||||
output: {
|
||||
filename: "[name].bundle.js",
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
chunkFilename: "[name].bundle.js",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.html$/i,
|
||||
loader: "html-loader",
|
||||
options: {
|
||||
attributes: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
const merge = require("webpack-merge");
|
||||
const common = require("./webpack.common.js");
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: "development",
|
||||
devtool: "inline-source-map",
|
||||
devServer: {
|
||||
contentBase: "./dist",
|
||||
},
|
||||
});
|
|
@ -1,6 +0,0 @@
|
|||
const merge = require("webpack-merge");
|
||||
const common = require("./webpack.common.js");
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: "production",
|
||||
});
|
Loading…
Reference in a new issue