-
Notifications
You must be signed in to change notification settings - Fork 7
/
github-game-of-life.user.js
273 lines (273 loc) · 10.4 KB
/
github-game-of-life.user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
// ==UserScript==
// @name Github's Game of Life
// @namespace https://github.com/ryanml
// @description Plays Conways' Game of Life with user's Github activity
// @include https://github.com/*
// @version 1
// @grant GM_addStyle
// ==/UserScript==
(function() {
// Strict mode
'use strict';
// Constant hex values
const INACTIVE_HEX = '#eeeeee';
const ACTIVE_HEX_ARR = ['#d6e685', '#8cc665', '#44a340', '#1e6823'];
// Interval by default is 200ms
var IT_INTERVAL = 200;
// Gets the <rect> wrapper tag <g> element
var loop;
var columns = document.getElementsByTagName('g');
var colDepth = 7;
var colorize = false;
var generationCount = 0;
var liveCellNum = 0;
var play = false;
var fillColumnGaps = fillGaps();
var ui = buildUI();
var grid = buildGrid();
var originalState = buildGrid();
var fillGrid = fillGrid();
// Builds grid of appropriate length
function buildGrid() {
var grid = [];
for (var col = 0; col < columns.length - 1; col++) {
grid.push([]);
}
return grid;
}
// Resets grid to original state
function resetGrid() {
for (var x = 0; x < originalState.length; x++) {
for (var y = 0; y < originalState[x].length; y++) {
grid[x][y] = originalState[x][y][1];
document.getElementById(x + ',' + y).setAttribute('fill', originalState[x][y][0]);
}
}
updateLiveCellCount();
document.getElementById('gol-info').innerHTML = '';
document.getElementById('gcc').innerHTML = (generationCount = 0);
}
// Fills grid with initial states
function fillGrid() {
for (var y = 0; y < colDepth; y++) {
for (var k = 1; k < columns.length; k++) {
var x = k - 1;
var cell = columns[k].children[y];
cell.addEventListener('click', clickUpdateCell);
cell.id = x + ',' + y;
// If cell is default color (Not filled) push 0 to the grid, else 1
var fill = cell.getAttribute('fill');
var active = fill == INACTIVE_HEX ? 0 : 1;
originalState[x].push([fill, active]);
grid[x].push(active);
updateLiveCellCount();
}
}
}
// Click event function for play/pause button. Starts and stops execution of the algorithm
function controlSim() {
if (!play) {
this.id = 'pause';
this.innerHTML = 'Pause';
document.getElementById('gol-info').innerHTML = '';
play = true;
loop = setInterval(checkGrid, IT_INTERVAL);
}
else {
this.id = 'play';
this.innerHTML = 'Play';
play = false;
clearInterval(loop);
}
}
// Applies one sweep of the algorithm to the grid
function step() {
if (!play) {
checkGrid();
}
}
// Sets all cells to dead (0)
function clearGrid() {
for (var x = 0; x < grid.length; x++) {
for (var y = 0; y < grid[x].length; y++) {
updateCellAt(x, y, grid[x][y] = 0);
}
}
updateLiveCellCount();
document.getElementById('gcc').innerHTML = (generationCount = 0);
}
// Updates the interval on change of the range input
function updateInterval() {
IT_INTERVAL = this.value == 0 ? ((this.value + 1) * 10) : (this.value * 10);
// If animation is playing, set new interval loop
if (play) {
clearInterval(loop);
loop = setInterval(checkGrid, IT_INTERVAL);
}
document.getElementById('icc').innerHTML = IT_INTERVAL;
}
// Returns the number of live cells in the grid
function updateLiveCellCount() {
liveCellNum = 0;
for (var x = 0; x < grid.length; x++) {
for (var y = 0; y < grid[x].length; y++) {
if (grid[x][y] == 1) {
liveCellNum++;
}
}
}
document.getElementById('lcc').innerHTML = liveCellNum;
}
// Checks if all cells are dead, displays message
function checkForCellDeaths() {
// Check for no cells
if (liveCellNum == 0) {
// If the simulation is being run it needs to stop
if (play) {
document.getElementById('pause').click();
}
document.getElementById('gol-info').innerHTML = ' - Mass Death! All your cells have died.';
}
}
// Loops through grid and applies Conway's algorithm to cells
function checkGrid() {
for (var x = 0; x < grid.length; x++) {
for (var y = 0; y < grid[x].length; y++) {
var isAlive = grid[x][y] == 1 ? true : false;
var nC = getNumNeighbors(x, y);
if (isAlive && nC < 2) {
grid[x][y] = 0;
}
else if (isAlive && (nC == 2 || nC == 3)) {
grid[x][y] = 1;
}
else if (isAlive && nC > 3) {
grid[x][y] = 0;
}
else if (!isAlive && nC == 3) {
grid[x][y] = 1;
}
updateCellAt(x, y, grid[x][y]);
}
}
updateLiveCellCount();
checkForCellDeaths();
document.getElementById('gcc').innerHTML = ++generationCount;
}
// Checks neighbors
function getNumNeighbors(x, y) {
// All possible coordinates of neighbors
var fullCoords = [[x-1,y-1],[x,y-1],[x+1,y-1],[x+1,y],[x+1,y+1],[x,y+1],[x-1,y+1],[x-1,y]];
var neighborCells = [];
// Checks to make sure the coordinates aren't out of bounds, if not, push to neighborCells
for (var f = 0; f < fullCoords.length; f++) {
if (fullCoords[f][0] >= 0 && fullCoords[f][0] <= (grid.length - 1)
&& fullCoords[f][1] >= 0 && fullCoords[f][1] <= colDepth - 1) {
neighborCells.push(grid[fullCoords[f][0]][fullCoords[f][1]]);
}
}
// Adds neighBorCell values via reduce, each live cell is represented by 1
return neighborCells.reduce((c, p) => c + p);
}
// Updates the <rect> markup at given coordinates
function updateCellAt(x, y, newState) {
var cell = document.getElementById(x + ',' + y);
var stateHex = newState == 0 ? INACTIVE_HEX : genRandomHex();
cell.setAttribute('fill', stateHex);
}
// Given a click event on the cell, sets grid at cell to opposite stateHex
function clickUpdateCell() {
var slc = this.id.split(',');
var x = slc[0], y = slc[1];
grid[x][y] = grid[x][y] == 0 ? 1 : 0;
updateCellAt(x, y, grid[x][y]);
updateLiveCellCount();
}
// Generates/gets the appropriate random hex value
function genRandomHex() {
var chars = 'ABCDEF0123456789';
var hex = '#';
if (!colorize) {
return ACTIVE_HEX_ARR[Math.floor(Math.random() * ACTIVE_HEX_ARR.length)];
}
else {
for (var n = 0; n < 6; n++) {
hex += chars[Math.floor(Math.random() * chars.length)];
}
return hex;
}
}
// Fills gaps in the markup
function fillGaps() {
// Gets the needed number of cells and most recent y value for first row
var fCol = columns[1];
var fNodes = fCol.children;
var fCellNo = (colDepth - fNodes.length);
var fCellY = fNodes[0].getAttribute('y');
var nextfCellY = parseInt(fCellY) - 13;
// Gets the needed number of cells and most recent y value for last row
var lCol = columns[columns.length - 1];
var lNodes = lCol.children;
var lCellNo = (colDepth - lNodes.length);
var lCellY = lNodes[lNodes.length - 1].getAttribute('y');
var nextlCellY = parseInt(lCellY) + 13;
for (var f = 0; f < fCellNo; f++) {
fCol.innerHTML = ('<rect class="day" width="11" height="11" y="' + nextfCellY + '" fill="' + INACTIVE_HEX + '"></rect>' + fCol.innerHTML);
nextfCellY -= 13;
}
for (var l = 0; l < lCellNo; l++) {
lCol.innerHTML += '<rect class="day" width="11" height="11" y="' + nextlCellY + '" fill="' + INACTIVE_HEX + '"></rect>';
nextlCellY += 13;
}
}
// Sets colorize variable on change
function setColorize() {
colorize = this.checked ? true : false;
}
// Builds UI and adds it to the document.
function buildUI() {
// Appends needed <style> to <head>
GM_addStyle(" .calendar-graph.days-selected rect.day { opacity: 1 !important; } " +
" .gol-span { display: inline-block; width: 125px; margin: 0px 6px; font-size: 90%; } " +
" .gol-button { margin: 0px 3px; width: 50px; height: 35px; border-radius: 5px; color: #ffffff; font-weight:bold; font-size: 11px; } " +
" .gol-button:focus { outline: none; } " +
" #play { background: #66ff33; border: 2px solid #208000; } " +
" #pause { background: #ff4d4d; border: 2px solid #cc0000; } " +
" #step { background: #0066ff; border: 2px solid #003380; } " +
" #clear { background: #e6e600; border: 2px solid #b3b300; } " +
" #reset { background: #ff9900; border: 2px solid #cc7a00; } " +
" #gol-range-span { width: 190px; } " +
" #gol-range-lbl { margin-right: 5px; } " +
" #gol-range { vertical-align:middle; width: 100px; } ");
// Contributions tab will be the parent div
var overTab = document.getElementsByClassName('mb-5')[0];
overTab.style = 'padding-bottom:0px !important';
// Control panel container
var golCont = document.createElement('div');
golCont.className = 'boxed-group flush';
golCont.style = 'margin-bottom:0px';
var markUp = "<h3>Github's Game of Life Control Panel <span id='gol-info' style='color:#ff0000'></span></h3>" +
"<div class='boxed-group-inner' style='padding:10px'>" +
"<button class='gol-button' id='play'>Play</button>" +
"<button class='gol-button' id='step'>Step</button>" +
"<button class='gol-button' id='clear'>Clear</button>" +
"<button class='gol-button' id='reset'>Reset</button>" +
"<span class='gol-span'><strong>Live Cell Count: </strong><span id='lcc'></span></span>" +
"<span class='gol-span' style='width:105px'><strong>Generation: </strong><span id='gcc'>0</span></span>" +
"<span class='gol-span' id='gol-range-span'>" +
"<span id='gol-range-lbl'><strong>Int (ms): </strong><span id='icc'>200</span></span>" +
"<input type='range' id='gol-range' value='20'/>" +
"</span>" +
"<input type='checkbox' id='color-check' style='vertical-align:middle'/>" +
"</div>";
golCont.innerHTML = markUp;
overTab.appendChild(golCont);
// Add events
document.getElementById('play').addEventListener('click', controlSim);
document.getElementById('step').addEventListener('click', step);
document.getElementById('clear').addEventListener('click', clearGrid);
document.getElementById('gol-range').addEventListener('change', updateInterval);
document.getElementById('color-check').addEventListener('change', setColorize);
document.getElementById('reset').addEventListener('click', resetGrid);
}
})();