Last active
February 10, 2016 18:28
-
-
Save markogresak/d9b8a60ececb434d8376 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* @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