Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div class="container">
  <div id="view" class="conway">
    <h1>Conway's Game of Life+</h1>
    <div id="controls">
      <button id="play" type="button" name="button">Play</button>
      <button id="step" type="button" name="button">Step</button>
      <button id="clear" type="button" name="button">Clear</button>
      <button id="rand" type="button" name="button">Rand</button>
      <br>
      <label>Pen Color
        <input id="penColor" type="color" name="favcolor" value="#000000">
      </label>
      <br>
      <label>Pen Size: 1
        <input id="penSize" type="range" min="1" max="10" value="5" /> 10</label>
      <br>
      <label>Speed: 0
        <input id="speed" type="range" min="0" max="180" value="180" /> 180</label>
      <br>
      <label>Width: 5
        <input id="width" type="range" min="5" max="300" value="128" /> 300</label>
      <br>
      <label>Height: 5
        <input id="height" type="range" min="5" max="300" value="128" /> 300</label>
    </div>
    <canvas id="canvas"></canvas>
  </div>
</div>
              
            
!

CSS

              
                .container {
  text-align: center;
  font-family: Sans-Serif;
}

.conway {
  background: #eee;
  display: inline-block;
  padding: 10px;
  border-radius: 5px;
}

.conway h1 {
  margin: 0 0 10px 0;
}

.conway #controls {
  margin-bottom: 10px;
}

.conway canvas {
  width: 475px;
  height: 475px;
  border: black solid 1px;
  background: white;
  cursor: crosshair;
  image-rendering: -moz-crisp-edges;
  image-rendering: -o-crisp-edges;
  image-rendering: -webkit-optimize-contrast;
  -ms-interpolation-mode: nearest-neighbor;
  image-rendering: pixelated;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
              
            
!

JS

              
                /**
 * The model for each cell
 * @params {int} params.x
 * @params {int} params.y
 * @params {obj} params.color {r,g,b,a}
 * @params {Grid} params.grid
 */
function Cell(params) {
  var cell = this;
  cell.x = params.x;
  cell.y = params.y;
  cell.color = params.color;
  cell.live = false;
  cell.grid = params.grid;
  cell.liveNeighborColors = null;
}

Cell.AGE_UNIT = 255 * 0.1;

/**
 * counts nearby living neighbors and stores their colors
 * @return {[type]} [description]
 */
Cell.prototype.examine = function() {
  var cell = this;
  cell.liveNeighborColors = cell.getNeighbors().map(function(neighbor) {
    return neighbor.live ? neighbor.getColor() : null;
  }).filter(function(neighbor) {
    // Clear out any nulls
    return neighbor;
  });
};

/**
 * Update cell's live status and color based on their neighbors
 * @note Should be called after all cells are 'examined'
 */
Cell.prototype.update = function() {
  var cell = this,
    liveNeighborCount = cell.liveNeighborColors.length;

  if (cell.live) {
    if (liveNeighborCount <= 1 || liveNeighborCount >= 4) {
      cell.kill();
    } else {
      cell.age();
    }
  } else if (liveNeighborCount === 3) {
    cell.inheritColorFromNeighbors();
    cell.makeAlive();
  }
};

Cell.prototype.makeAlive = function() {
  this.live = true;
};

Cell.prototype.kill = function() {
  this.live = false;
};

Cell.prototype.randomize = function() {
  this.live = Math.random() < 0.4;
  this.color.r = Math.random() * 255;
  this.color.g = Math.random() * 255;
  this.color.b = Math.random() * 255;
  this.color.a = 255 * 0.1;
};

/**
 * Gets color values from neighbors
 * @assume That there are only three live neighbors
 */
Cell.prototype.inheritColorFromNeighbors = function() {
  var cell = this;
  cell.color.a = Cell.AGE_UNIT;
  // channels are backwards so we can pop off in order of rgb
  var channels = ['b', 'g', 'r'];
  cell.liveNeighborColors.forEach(function(color) {
    var channel = channels.pop();
    cell.color[channel] = color[channel];
  });
};

/**
 * Increment the age of the cell. Capped at max color value;
 */
Cell.prototype.age = function() {
  var newAge = this.color.a + Cell.AGE_UNIT;
  this.color.a = newAge > 255 ? 255 : newAge;
};

/**
 * @return {array} Neighbors starting on left and going in clockwise order
 */
Cell.prototype.getNeighbors = function() {
  var cell = this,
    grid = cell.grid,
    onTop = cell.y === 0,
    onBottom = cell.y === (grid.height - 1),
    onLeft = cell.x === 0,
    onRight = cell.x === (grid.width - 1);
  return [
    (onLeft) ? null : grid.rows[cell.y][cell.x - 1],
    (onLeft || onTop) ? null : grid.rows[cell.y - 1][cell.x - 1],
    (onTop) ? null : grid.rows[cell.y - 1][cell.x],
    (onRight || onTop) ? null : grid.rows[cell.y - 1][cell.x + 1],
    (onRight) ? null : grid.rows[cell.y][cell.x + 1],
    (onBottom || onRight) ? null : grid.rows[cell.y + 1][cell.x + 1],
    (onBottom) ? null : grid.rows[cell.y + 1][cell.x],
    (onBottom || onLeft) ? null : grid.rows[cell.y + 1][cell.x - 1]
  ].filter(function(neighbor) {
    // Clear out any nulls
    return neighbor;
  });
};

/**
 * @return {obj} A duplicate of the cell's color
 */
Cell.prototype.getColor = function() {
  return {
    r: this.color.r,
    g: this.color.g,
    b: this.color.b,
    a: this.color.a
  };
};

/**
 * Conway's game of life
 * Extended from http://jsfiddle.net/blesh/n9S8R/light/
 * @param {int} gridWidth
 * @param {int} gridHeight
 */
function Conway(gridWidth, gridHeight) {
  var conway = this;
  conway.grid = new Grid(gridWidth, gridHeight);
  var controlsElem = document.getElementById('controls');
  var canvasElem = document.getElementById('canvas');
  conway.view = new View(controlsElem, canvasElem);
  conway.viewModel = new ViewModel(conway.view, conway.grid);
};

/**
 * Model for the game board and necessary logic to simulate the game
 * @param {int} w
 * @param {int} h
 */
function Grid(w, h) {
  var grid = this;
  grid.width = w;
  grid.height = h;
  grid.running = false;
  grid.rows = null;

  /**
   * Change the grid size
   * @param {number} size.width (optional)
   * @param {number} size.height (optional)
   */
  grid.changeSize = function(size) {
    grid.width = size.width || grid.width;
    grid.height = size.height || grid.height;
    grid.reset();
  };

  /**
   * Initialize the correct number of cells and rows.
   * Shrinks or grows the grid according to current width/height
   */
  grid.reset = function() {
    var x, y, row;
    grid.rows = grid.rows || [];

    for (y = 0; y < grid.height; y++) {
      row = grid.rows[y] || [];

      // Add on any additional width cells
      for (x = row.length; x < grid.width; x++) {
        row.push(new Cell({
          x: x,
          y: y,
          color: {
            r: 255,
            g: 255,
            b: 255,
            a: 255
          },
          grid: grid
        }));
      }
      // Check if any cells outside the width need to be removed
      if (row.length > grid.width) {
        row.splice(grid.width);
      }

      grid.rows[y] = row;
    }

    // Check if any cells outside the height need to be removed
    if (grid.rows.length > grid.height) {
      grid.rows.splice(grid.height);
    }
  };

  /**
   * Utility to run through all of the cells
   * @param {Function} fn function(Cell, x, y)
   */
  grid.traverse = function(fn) {
    var x, y;
    for (y = 0; y < grid.height; y++) {
      for (x = 0; x < grid.width; x++) {
        fn(grid.rows[y][x], x, y);
      }
    }
  };

  /**
   * Utility to run through all the cells from (x1, y1)
   * up to (x2, y2)
   * @param {obj} bounds {x1,y1,x2,y2}
   * @param {Function} fn function(cell, x, y)
   */
  grid.traverseBounds = function(bounds, fn) {
    var x, y;
    for (y = bounds.y1; y < bounds.y2; y++) {
      for (x = bounds.x1; x < bounds.x2; x++) {
        if (grid.rows[y] && grid.rows[y][x]) {
          fn(grid.rows[y][x], x, y);
        }
      }
    }
  };

  /**
   * Simulates a single step in the game
   * @return {[type]} [description]
   */
  grid.step = function() {
    grid.traverse(function(cell) {
      cell.examine();
    });

    grid.traverse(function(cell) {
      cell.update();
    });
  };

  /**
   * Kills off all cells
   */
  grid.clear = function() {
    grid.traverse(function(cell) {
      cell.kill();
    });
  };

  /**
   * Randomly turns on cells and changes their color
   */
  grid.randomize = function() {
    grid.traverse(function(cell) {
      cell.randomize();
    });
  };

  // call it right away to initialize the grid
  grid.reset();
  grid.randomize();
}

// Startup the game
window.conway = new Conway(128, 128);

/**
 * Stores view elements
 * @param {htmlElement} controlsElem
 * @param {htmlCanvas} canvasElem
 */
function View(controlsElem, canvasElem) {
  var view = this;
  view.playButton = controlsElem.querySelector('#play');
  view.stepButton = controlsElem.querySelector('#step');
  view.clearButton = controlsElem.querySelector('#clear');
  view.randButton = controlsElem.querySelector('#rand');
  view.penColorInput = controlsElem.querySelector('#penColor');
  view.penSizeRange = controlsElem.querySelector('#penSize');
  view.speedRange = controlsElem.querySelector('#speed');
  view.widthRange = controlsElem.querySelector('#width');
  view.heightRange = controlsElem.querySelector('#height');
  view.canvas = canvasElem;
}

/**
 * Creates bindings between view and model
 * @param {View} view
 * @param {Grid} grid
 */
function ViewModel(view, grid) {
  var table = view.table;
  var speedRange = view.speedRange;
  var viewModel = this;

  /**
   * Updates the current view
   */
  viewModel.update = function() {
    var context = view.canvas.getContext('2d'),
      imageData, pixels,
      x, y, i, color;

    imageData = context.createImageData(grid.width, grid.height);
    pixels = imageData.data;
    grid.traverse(function(cell) {
      if (cell.live) {
        i = (cell.y * grid.width + cell.x) * 4;
        pixels[i + 0] = cell.color.r; //red
        pixels[i + 1] = cell.color.g; //green
        pixels[i + 2] = cell.color.b; //blue
        pixels[i + 3] = cell.color.a; //alpha
      }
    });

    context.putImageData(imageData, 0, 0);
  };

  /**
   * updates the grid, and then update the view
   */
  viewModel.tick = function() {
    grid.step();
    viewModel.update();
  };

  // Shim requestAnimationFrame
  var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(fn) {
    setTimeout(fn, 1000 / 60);
  };

  // The animation loop
  var frame = 0;

  viewModel.animate = function() {
    var speed = +speedRange.max - +speedRange.value;
    var runStep = speed === 0 || speed < 180 && (frame++ % speed) === 0;
    if (grid.running && runStep) {
      viewModel.tick();
    }
    requestAnimationFrame(viewModel.animate);
  }
  requestAnimationFrame(viewModel.animate);

  /**
   * Setup initial bindings
   */
  viewModel.init = function() {
    setupCanvashandlers();
    setupControlHandlers();
  };

  function setupCanvashandlers() {
    var canvas = document.getElementById('canvas'),
      context = canvas.getContext('2d');
    canvas.width = grid.width;
    canvas.height = grid.height;

    function getPos(canvas, evt) {
      var rect = canvas.getBoundingClientRect();
      return {
        x: Math.floor((evt.clientX - rect.left) / (rect.right - rect.left) * canvas.width),
        y: Math.floor((evt.clientY - rect.top) / (rect.bottom - rect.top) * canvas.height)
      };
    }

    var mouseDown = false;
    canvas.onmousedown = function(evt) {
      mouseDown = true;
      drawSquare(evt, true);
    };
    canvas.onmouseup = function(evt) {
      mouseDown = false;
    };
    canvas.onmousemove = function(evt) {
      if (!mouseDown) return;
      drawSquare(evt);
    };

    function drawSquare(evt, canKill) {
      var pos = getPos(canvas, evt);

      var clickedCell = grid.rows[pos.y][pos.x];
      // Kill the entire square of cells if you can kill
      // and the middle cell is alive
      var killingCells = clickedCell.live && canKill;

      var penSize = Number.parseInt(view.penSizeRange.value);
      var hPenSize = penSize / 2;
      // Shifts square up and left one if size is even
      grid.traverseBounds({
        x1: pos.x - Math.floor(hPenSize),
        y1: pos.y - Math.floor(hPenSize),
        x2: pos.x + Math.ceil(hPenSize),
        y2: pos.y + Math.ceil(hPenSize)
      }, function(cell) {
        cell.color = hexToRgb(view.penColorInput.value);

        if (killingCells) {
          cell.kill();
        } else {
          cell.makeAlive();
        }
      });

      viewModel.update();
    }
  }

  function hexToRgb(hex) {
    var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? {
      r: parseInt(result[1], 16),
      g: parseInt(result[2], 16),
      b: parseInt(result[3], 16),
      a: 255 * 0.1
    } : null;
  }

  function setupControlHandlers() {
    view.widthRange.addEventListener('input', function() {
      var width = Number.parseInt(view.widthRange.value);
      grid.changeSize({
        width: width
      });
      view.canvas.width = width;
      viewModel.update();
    });
    view.heightRange.addEventListener('input', function() {
      var height = Number.parseInt(view.heightRange.value);
      grid.changeSize({
        height: height
      });
      view.canvas.height = height;
      viewModel.update();
    });
    view.playButton.onclick = function() {
      grid.running = !grid.running;
      view.playButton.firstChild.data = grid.running ? 'Stop' : 'Play';
    };
    view.stepButton.onclick = function() {
      viewModel.tick();
    };
    view.clearButton.onclick = function() {
      grid.clear();
      viewModel.update();
    };
    view.randButton.onclick = function() {
      grid.randomize();
      viewModel.update();
    };
  }

  viewModel.init();
  viewModel.update();
}
              
            
!
999px

Console