Dots and Boxes

In this example below you will see how to do a Dots and Boxes with some HTML / CSS and Javascript

Thumbnail
This awesome code was written by benlorantfy, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright benlorantfy ©
  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>Dots and Boxes</title>
  
  
  
      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>

  <canvas></canvas>
  <script src='https://wzrd.in/standalone/raf@latest'></script>
<script src='https://www.gstatic.com/firebasejs/4.9.1/firebase.js'></script>

  

    <script  src="js/index.js"></script>




</body>

</html>

/*Downloaded from https://www.codeseek.co/benlorantfy/dots-and-boxes-vWPbGy */
html, body {
  padding: 0;
  margin: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

/*Downloaded from https://www.codeseek.co/benlorantfy/dots-and-boxes-vWPbGy */
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

/**
 * A class that abstracts the firebase api so we can specify our
 * own api, using the adapter design pattern. 
 *
 * Minimze this if you don't care
 */
var FireQuery = function () {
  function FireQuery(ref) {
    _classCallCheck(this, FireQuery);

    this.ref = ref;
    this.cancelled = false;
    this.numCalls = 0;
  }

  _createClass(FireQuery, [{
    key: 'orderByChild',
    value: function orderByChild(child) {
      this.ref = this.ref.orderByChild(child);
      return this; // Return this so we can chain them
    }
  }, {
    key: 'limitToFirst',
    value: function limitToFirst(n) {
      this.ref = this.ref.limitToFirst(n);
      return this;
    }
  }, {
    key: 'off',
    value: function off() {
      this.cancelled = true;
      this.ref.off('child_added');
    }
  }, {
    key: 'twice',
    value: function twice(callback) {
      var _this = this;

      this.ref.on('child_added', function () {
        if (_this.numCalls < 2 && !_this.cancelled) {
          _this.numCalls++;
          callback.apply(undefined, arguments);
        } else if (!_this.cancelled) {
          _this.ref.off('child_added');
        }
      });

      return this;
    }
  }]);

  return FireQuery;
}();

var Server = function () {
  function Server() {
    _classCallCheck(this, Server);

    this.joined = false;
  }

  _createClass(Server, [{
    key: 'setup',
    value: function setup() {
      var _this2 = this;

      var config = {
        apiKey: "AIzaSyARzEfIvAiWvjGv2khFL0koGbLpcZE0U2k",
        authDomain: "ben-codepen.firebaseapp.com",
        databaseURL: "https://ben-codepen.firebaseio.com",
        projectId: "ben-codepen",
        storageBucket: "ben-codepen.appspot.com",
        messagingSenderId: "141282204522"
      };
      firebase.initializeApp(config);
      this.db = firebase.database();
      firebase.auth().signInAnonymously().then(function (user) {
        _this2.user = user;

        _this2.db.ref('/dots-and-boxes/lobby/' + _this2.user.uid).update({
          timestamp: firebase.database.ServerValue.TIMESTAMP
        }).then(function () {
          _this2.joinGame();
        });
      });
    }
  }, {
    key: 'joinGame',
    value: function joinGame() {
      var _this3 = this;

      // 1. find player who has been waiting the longest
      // 2. try to create a game with that player
      //    - firebase should only allow one game
      // 3. if failure, try again later
      // note: at any point, we want to cancel this process if another player
      // adds this player to their game

      // Get's a ref to the dots and boxes lobby, which is
      // where all the unmatched players sit
      var ref = this.db.ref('/dots-and-boxes/lobby');

      // Look for the oldest two players in the lobby. We
      // need two players because one of them might be the
      // current player
      var qry = new FireQuery(ref).orderByChild('timestamp').limitToFirst(2).twice(function (player) {
        // We don't care if the player is the current player,
        // we're trying to find the oldest player that isn't the current player
        if (player.key === _this3.user.uid) return;

        // As soon as we find a player that isn't the current player,
        // we can cancel the qry. This is for the case that the two oldest
        // players aren't this player, in which case we only want one of them
        if (player.key !== _this3.user.uid) {
          qry.off();
        }

        // The following code should only execute once with the oldest
        // player that isn't the current player
        var foeId = player.key;
        _this3.db.ref('/dots-and-boxes/games/' + _this3.user.uid + '-' + foeId).set({
          lines: []
        }).then(function () {
          console.log('wazzup'); // a
        }).catch(function () {
          console.log('damn son'); // b
        });
      });

      // let qry = ref
      //   // We want to get the players waiting the longest
      //   .orderByChild('timestamp')
      //   // We need to look for the first 2 people in the lobby, because the person waiting the longest
      //   // could be the current player
      //   .limitToFirst(2) 
      //   .once('child_added', (player) => {
      //     console.log(player.key);
      //     // ref.off('child_added');//////
      //     // if (player.key === this.user.uid) return;
      //     ////
      //     // console.log(player.key )
      //     // const playerId = player.key;
      //     // this.db.ref(`/dots-and-boxes/games/${this.user.uid}-${playerId}`).set({
      //     //   lines: []
      //     // }).then(() => {
      //     //   console.log('wazzup'); //
      //     // })
      //   });
    }
  }]);

  return Server;
}();

var Game = function Game() {
  _classCallCheck(this, Game);

  this.currentLine = [];
  this.width = 4;
  this.height = 4;
  this.lines = [{ player: 1, x1: 0, y1: 0, x2: 1, y2: 0 }, { player: 2, x1: 1, y1: 1, x2: 1, y2: 2 }];
};

var GameController = function () {
  function GameController(_ref) {
    var _this4 = this;

    var game = _ref.game,
        window = _ref.window,
        server = _ref.server;

    _classCallCheck(this, GameController);

    var canvas = window.document.getElementsByTagName('canvas')[0];
    var width = window.innerWidth;
    var height = window.innerHeight;
    this.game = game;
    this.window = window;

    // Start the drawer
    this.drawer = new Drawer({ game: game, canvas: canvas, width: width, height: height });

    // Setup the events
    this.watchMouseDown();
    this.watchMouseMove();

    // Resize game on window resize
    window.addEventListener("resize", function () {
      _this4.drawer.resize(window.innerWidth, window.innerHeight);
    });

    // Start the game loop
    raf(function () {
      return _this4.gameLoop();
    });

    // Setup the server
    server.setup();
  }

  _createClass(GameController, [{
    key: 'gameLoop',
    value: function gameLoop() {
      var _this5 = this;

      var mouseWasPressedDown = this.mousedown && !this.oldMouseDown;
      var mouseWasLetGo = !this.mousedown && this.oldMouseDown;
      var mouseWasMoved = this.oldMouseX !== this.mouseX || this.oldMouseY !== this.mouseY;
      var mouseWasMovedWhilePressedDown = mouseWasMoved && this.mousedown && this.oldMouseDown;

      if (mouseWasMovedWhilePressedDown) {
        this.game.currentLine.push({ x: this.drawer.toGridX(this.mouseX), y: this.drawer.toGridY(this.mouseY) });
      }

      if (mouseWasPressedDown) {
        this.dragStartX = this.mouseX;
        this.dragStartY = this.mouseY;
      }

      if (mouseWasLetGo) {
        this.game.currentLine = [];
        this.dragEndX = this.mouseX;
        this.dragEndY = this.mouseY;
        this.createLine();
      }

      this.oldMouseDown = this.mousedown;
      this.oldMouseX = this.mouseX;
      this.oldMouseY = this.mouseY;
      raf(function () {
        return _this5.gameLoop();
      });
    }
  }, {
    key: 'createLine',
    value: function createLine() {
      var x1 = this.drawer.toRoundedGridX(this.dragStartX);
      var y1 = this.drawer.toRoundedGridY(this.dragStartY);
      var x2 = this.drawer.toRoundedGridX(this.dragEndX);
      var y2 = this.drawer.toRoundedGridY(this.dragEndY);

      // Validate the line
      var isHorizontal = x1 === x2;
      var isVertical = y1 === y2;
      var isNotDiagonal = isHorizontal || isVertical;
      var verticalNeighbours = x2 === x1 + 1 || x2 === x1 - 1;
      var horizontalNeighbours = y2 === y1 + 1 || y2 === y1 - 1;
      var areNeighbours = verticalNeighbours || horizontalNeighbours;
      var withinHorizontalBounds = x1 >= 0 && x2 >= 0 && x1 < this.game.width && x2 < this.game.width;
      var withinVerticalBounds = y1 >= 0 && y2 >= 0 && y1 < this.game.height && y2 < this.game.height;
      var isValid = isNotDiagonal && areNeighbours && withinHorizontalBounds && withinVerticalBounds;
      if (isValid) {
        var line = {
          player: 1,
          x1: x1,
          x2: x2,
          y1: y1,
          y2: y2
        };
        this.game.lines.push(line);
      }
    }
  }, {
    key: 'watchMouseDown',
    value: function watchMouseDown() {
      var _this6 = this;

      this.oldMouseDown = this.mousedown = false;
      this.window.addEventListener("mousedown", function () {
        return _this6.mousedown = true;
      });
      this.window.addEventListener("mouseup", function () {
        return _this6.mousedown = false;
      });
      this.window.addEventListener("mouseout", function () {
        return _this6.mousedown = false;
      });
    }
  }, {
    key: 'watchMouseMove',
    value: function watchMouseMove() {
      var _this7 = this;

      this.window.addEventListener("mousemove", function (e) {
        _this7.mouseX = e.pageX;
        _this7.mouseY = e.pageY;
      });
    }
  }]);

  return GameController;
}();

var Drawer = function () {
  function Drawer(_ref2) {
    var _this8 = this;

    var game = _ref2.game,
        canvas = _ref2.canvas,
        width = _ref2.width,
        height = _ref2.height;

    _classCallCheck(this, Drawer);

    var context = canvas.getContext('2d');
    this.canvas = canvas;
    this.game = game;
    this.ctx = context;
    this.dotColor = "black";
    this.dotSize = 4;
    this.dotSpacing = 50;
    this.p1Color = "red";
    this.p2Color = "blue";
    this.resize(width, height);
    raf(function () {
      return _this8.draw();
    });
  }

  _createClass(Drawer, [{
    key: 'clear',
    value: function clear() {
      this.ctx.fillStyle = "white";
      this.ctx.beginPath();
      this.ctx.rect(0, 0, this.width, this.height);
      this.ctx.closePath();
      this.ctx.fill();
    }
  }, {
    key: 'draw',
    value: function draw() {
      var _this9 = this;

      this.clear();
      this.drawLines();
      this.drawDots();
      this.drawCurrentLine();
      raf(function () {
        return _this9.draw();
      });
    }
  }, {
    key: 'drawDots',
    value: function drawDots() {
      var padding = this.dotSpacing;
      var gridLeft = this.gridLeft;
      var gridTop = this.gridTop;
      for (var i = 0; i < this.game.width; i++) {
        for (var j = 0; j < this.game.height; j++) {
          this.ctx.beginPath();
          this.ctx.arc(gridLeft + i * padding, gridTop + j * padding, this.dotSize, 0, 2 * Math.PI, false);
          this.ctx.fillStyle = this.dotColor;
          this.ctx.fill();
        }
      }
    }
  }, {
    key: 'drawLines',
    value: function drawLines() {
      var padding = this.dotSpacing;
      var gridLeft = this.gridLeft;
      var gridTop = this.gridTop;
      for (var i = 0; i < this.game.lines.length; i++) {
        var line = this.game.lines[i];
        this.ctx.beginPath();
        this.ctx.moveTo(this.toGlobalX(line.x1), this.toGlobalY(line.y1));
        this.ctx.lineTo(this.toGlobalX(line.x2), this.toGlobalY(line.y2));
        if (line.player === 1) this.ctx.strokeStyle = this.p1Color;
        if (line.player === 2) this.ctx.strokeStyle = this.p2Color;
        this.ctx.stroke();
      }
    }
  }, {
    key: 'drawCurrentLine',
    value: function drawCurrentLine() {
      var _this10 = this;

      this.ctx.beginPath();
      this.game.currentLine.forEach(function (p, i) {
        if (i === 0) {
          _this10.ctx.moveTo(_this10.toGlobalX(p.x), _this10.toGlobalY(p.y));
        } else {
          _this10.ctx.lineTo(_this10.toGlobalX(p.x), _this10.toGlobalY(p.y));
        }
      });

      this.ctx.strokeStyle = this.p1Color;
      this.ctx.stroke();
    }
  }, {
    key: 'toRoundedGridX',
    value: function toRoundedGridX(x) {
      return Math.round(this.toGridX(x));
    }
  }, {
    key: 'toRoundedGridY',
    value: function toRoundedGridY(y) {
      return Math.round(this.toGridY(y));
    }
  }, {
    key: 'toGridX',
    value: function toGridX(x) {
      return (x - this.gridLeft) / this.dotSpacing;
    }
  }, {
    key: 'toGridY',
    value: function toGridY(y) {
      return (y - this.gridTop) / this.dotSpacing;
    }
  }, {
    key: 'toGlobalX',
    value: function toGlobalX(x) {
      return this.gridLeft + this.dotSpacing * x;
    }
  }, {
    key: 'toGlobalY',
    value: function toGlobalY(y) {
      return this.gridTop + this.dotSpacing * y;
    }
  }, {
    key: 'resize',
    value: function resize(newWidth, newHeight) {
      this.width = newWidth;
      this.height = newHeight;
      this.gridLeft = this.width / 2 - this.dotSpacing * this.game.width / 2;
      this.gridTop = this.height / 2 - this.dotSpacing * this.game.height / 2;
      this.canvas.setAttribute("width", newWidth);
      this.canvas.setAttribute("height", newHeight);
    }
  }]);

  return Drawer;
}();

var Main = function Main(window) {
  _classCallCheck(this, Main);

  var game = new Game();
  var server = new Server();
  var controller = new GameController({ game: game, window: window, server: server });
};

new Main(window);

Comments