Skip to content

Instantly share code, notes, and snippets.

@markogresak
Last active February 10, 2016 18:28
Show Gist options
  • Save markogresak/d9b8a60ececb434d8376 to your computer and use it in GitHub Desktop.
Save markogresak/d9b8a60ececb434d8376 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Color Wars</title>
<style>
body {
font-family: sans-serif;
}
.outer-wrapper {
display: flex;
justify-content: center;
}
.main-wrapper {
width: 500px;
height: 500px;
}
.wrapper.end-text {
width: 500px;
height: 500px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
flex-flow: column;
display: none;
z-index: 9999;
position: absolute;
top: 0;
font-size: 150px;
font-weight: bold;
color: #fff;
-webkit-text-stroke: 1px black;
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
}
.end-text .iterations {
font-size: 35px;
}
.main-wrapper.done .end-text {
display: flex;
}
.frequencies {
height: 500px;
width: 200px;
padding-left: 20px;
}
.frequency {
margin-top: 10px;
}
.frequency .color {
width: 16px;
height: 16px;
margin-right: 10px;
display: inline-block;
vertical-align: bottom;
}
.frequency.end {
text-decoration: line-through;
}
.input-wrapper {
margin-top: 20px;
}
.input-wrapper .size-n {
display: inline-block;
width: 200px;
}
input[name="size-n"] {
width: 200px;
}
.start-button {
padding: 5px 20px;
font-size: 20px;
border: none;
border-radius: 4px;
float: right;
}
</style>
</head>
<body>
<div class="outer-wrapper">
<div class="main-wrapper">
<canvas id="canvas" width="500" height="500"></canvas>
<div class="wrapper end-text">
<div>END</div>
<div class="iterations">
Iterations: <span class="iterations-count">0</span>
</div>
</div>
<div class="input-wrapper">
<label for="size-n" class="size-n">Select matrix size n (<label class="selected-size">30</label>):</label>
<input type="range" name="size-n" id="size-n" value="30" min="10" max="250" step="5">
<button class="start-button" type="button" name="start-button">Start</button>
</div>
</div>
<div class="frequencies"></div>
</div>
<script src="https://code.jquery.com/jquery-2.2.0.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chance/0.8.0/chance.min.js" charset="utf-8"></script>
<script src="main.js" charset="utf-8"></script>
</body>
</html>
/* @flow */
/* global chance */
'use strict';
const colors = ['#7c078e', '#ec4a48', '#fffc58', '#1fa2d1', '#80cf0c', '#472245', '#01998a', '#ce1736', '#f75830', '#0c0841'];
const weights = [0.3, 0.05, 0.05, 0.1, 0.1, 0.05, 0.125, 0.075, 0.05, 0.1];
const randomColor = () => chance.weighted(colors, weights);
const initialFrequencies = colors.map(() => 0);
class ColorField {
constructor(n, a, neighbourMask) {
this.n = n;
this.length = n * n;
this.a = a || new Array(this.length);
this.neighbourMask = neighbourMask || this.calcNeighbourMask();
this.frequencies = initialFrequencies;
}
calcNeighbourMask() {
const n = this.n;
const neighbourMask = new Array(this.length);
for (let i = 0, index = 0; i < n; i++) {
for (let j = 0; j < n; j++, index++) {
let ind = 0;
for (let ii = i - 1; ii <= i + 1; ii++) {
for (let jj = j - 1; jj <= j + 1; jj++) {
if ((ii !== i || jj !== j) && (ii >= 0 && ii < n) && (jj >= 0 && jj < n)) {
if (!neighbourMask[index]) {
neighbourMask[index] = [];
}
neighbourMask[index][ind++] = this.index(ii, jj);
}
}
}
neighbourMask[index] = neighbourMask[index].slice(0, ind);
}
}
return neighbourMask;
}
isAllSame() {
const el = this.a[0];
for (let i = 1; i < this.length; i++) {
if (this.a[i] !== el) {
return false;
}
}
return true;
}
nextIteration() {
const oldA = this.a.slice();
this.frequencies = initialFrequencies.slice();
for (let i = 0; i < this.length; i++) {
const nextColor = oldA[chance.pick(this.neighbourMask[i])];
this.a[i] = nextColor;
this.frequencies[colors.indexOf(nextColor)]++;
}
}
render(ctx, canvas) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const elementSize = Math.ceil(Math.min(canvas.width, canvas.height) / this.n);
for (let i = 0; i < this.n; i++) {
for (let j = 0; j < this.n; j++) {
ctx.fillStyle = this.a[this.index(i, j)];
ctx.fillRect(j * elementSize, i * elementSize, elementSize, elementSize);
}
}
}
index(i, j) {
return j + this.n * i;
}
static generate(n) {
const a = [];
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
a.push(randomColor());
}
}
return new ColorField(n, a);
}
}
function generateFrequencyElement(color, frequency, percent) {
const valueEl = $('<span>').addClass('value').text(frequency);
const percentEl = $('<span>').addClass('percent').text(percent.toFixed(2) + '%');
const frequencyEl = $('<div>').addClass('frequency').append(
$('<span>').addClass('color').css('background', color),
valueEl,
$(document.createTextNode(' (')),
percentEl,
$(document.createTextNode(')'))
);
return {
valueEl: valueEl,
precentEl: percentEl,
frequencyEl: frequencyEl,
done: false
};
}
function initFrequencies(field, frequenciesWrapper) {
const frequencyElements = field.frequencies.map((f, i) => generateFrequencyElement(colors[i], f, f / field.length * 100));
frequenciesWrapper.empty().append(frequencyElements.map(e => e.frequencyEl));
return frequencyElements;
}
function updateFrequencies(field, frequencyElements) {
field.frequencies.forEach((f, i) => {
const e = frequencyElements[i];
if (e.done) {
return;
}
e.done = f === 0;
e.valueEl.text(f);
e.precentEl.text((f / field.length * 100).toFixed(2) + '%');
e.frequencyEl.toggleClass('end', e.done);
});
}
$(function () {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.fillRect(0, 0, canvas.width, canvas.height);
const frequenciesWrapper = $('.frequencies');
let frameId;
function start(n) {
$('.main-wrapper').removeClass('done');
if (frameId) {
cancelAnimationFrame(frameId);
}
const field = ColorField.generate(n);
field.render(ctx, canvas);
const frequencyElements = initFrequencies(field, frequenciesWrapper);
let updateCounter = 0;
function update() {
if (!field.isAllSame()) {
frameId = requestAnimationFrame(update);
field.nextIteration();
field.render(ctx, canvas);
if (updateCounter++ % 5 === 0) {
updateFrequencies(field, frequencyElements);
}
} else {
field.render(ctx, canvas);
updateFrequencies(field, frequencyElements);
$('.iterations-count').text(updateCounter);
$('.main-wrapper').addClass('done');
}
}
update();
}
const sizeInputEl = $('#size-n');
const selectedSizeEl = $('.selected-size');
sizeInputEl.on('input', () => selectedSizeEl.text(sizeInputEl.val()));
// sizeInputEl.on('input', () => start(parseInt(sizeInputEl.val(), 10)));
$('.start-button').click(() => start(parseInt(sizeInputEl.val(), 10)));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment