Greasy Fork 支持简体中文。

Github's Game of Life

Plays Conways' Game of Life with user's Github activity

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