Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's 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 it's URL and the proper URL extention.

+ 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

Save Automatically?

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

              
                <h1>&middot;&middot;I&middot;&middot;C&middot;&middot;D&middot;&middot;O&middot;&middot;T&middot;&middot;S&middot;&middot;</h1>
<h2>&ndash;&nbsp;&ndash;&nbsp;and&nbsp;&ndash;&nbsp;&ndash;&nbsp;lines&nbsp;&ndash;&nbsp;&ndash;</h2>

<!-- Development history details at bottom of HTML code. -->

<p class="description">This game is for 2-7 players who will take turns selecting lines to form boxes. Scoring is 1 point per box pinned. Line colors have no distinction other than one is horizontal and one is vertical, and the alternating colors felt visually appealing.</p>

<form name="icdots">
  <div class="row">
    <label><span class="label-text">board size:</span>
      <input type="number" 
             min="2" max="7" value="3" 
             class="input-size board-size" 
             title="board size can be between 2 and 7, inclusive." />
    </label><br/>
  </div>
  
  <div class="row">
    <label><span class="label-text">players <small>(names optional)</small>:</span></label>
    <div class="buttons-up-down">
      <input type="button" class="players-count players-count-down" value="-" />
      <input type="button" class="players-count players-count-up" value="+" />
    </div>
    <div class="tooltip-container">
      <tooltip class="tooltip tooltip-small">
        <span class="tooltip-open">?</span>
        <span class="tooltip-close">X</span>
        <span class="tooltip-content">
          number of players should not exceed the board's size. 
          board size can be between 2 and 7, inclusive.
        </span>
      </tooltip>
    </div>
  </div>
  
  <div class="row">
    <div class="player-names-list">
      <input type="text" class="player-names" />
      <input type="text" class="player-names" />
    </div>
  </div>
  <div class="row">
      <div class="start-it-container">
      <button type="button" class="start-it-button" title="Click to Begin Game">Begin Game</button>
    </div>
  </div>
</form>

<aside>
  <p>My interactive version of a game inspired by a Codewars kata</p>
  <ul>
    <li>
      My 'React with TypeScript' Version of this same game:<br/>
      <a href="https://codepen.io/KeithDC/details/OzdoLv/">https://codepen.io/KeithDC/details/OzdoLv/</a>
    </li>
    <li>
      Original Codewars Kata:<br/>
      <a href="https://www.codewars.com/kata/58fdcc51b4f81a0b1e00003e/train/javascript">Pigs in a Pen (dots-connect-lines game)</a>
    </li>
    <li>
      Wikipedia (Game: Dots and Boxes):<br/>
      <a href="https://en.wikipedia.org/wiki/Dots_and_Boxes">https://en.wikipedia.org/wiki/Dots_and_Boxes</a>
    </li>
  </ul>
  <h3>@DONE: (2018-01)</h3>
  <ul>
    <li><a href="https://codepen.io/KeithDC/pen/OzdoLv/">Forked this Pen and completed 'React with TypeScript' version</a>.</li>
  </ul>
  <h3>Development Time</h3>
  <p>
    Conversion from 'kata style' to 'interactive style'.<br/>
    [2018-01-13 - Saturday] --> [2018-01-21 - Sunday]
  </p>
</aside>

<div class="game-board-mask"></div>
<div class="game-board">
  <div class="welcome">Pick a line (gap)... Any line (gap)!</div>
  <div class="main-board"></div>
  <div class="main-board-stats"></div>
  <div class="main-board-footer">
    <div class="footer-left">
      <label>Board Background: 
        <select class="main-board-background">
          <option value="rgba(50,205,205,.5)">default</option>
          <option value="aqua">aqua</option>
          <option value="aquamarine">aquamarine</option>
          <option value="black">black</option>
          <option value="chartreuse">chartreuse</option>
          <option value="darkblue">dark blue</option>
          <option value="darkgreen">dark green</option>
          <option value="darkred">dark red</option>
          <option value="lightskyblue">light sky blue</option>
          <option value="lightgreen">light green</option>
          <option value="lime">lime</option>
          <option value="limegreen">lime green</option>
          <option value="midnightblue">midnight blue</option>
          <option value="mistyrose">misty rose</option>
          <option value="palegreen">pale green</option>
          <option value="purple">purple</option>
          <option value="skyblue">sky blue</option>
          <option value="tan">tan</option>
          <option value="turquoise">turquoise</option>
          <option value="wheat">wheat</option>
        </select>
      </label>
    </div>
    <div class="footer-right">
      <button class="main-board-close" type="button" title="Exit Game">Exit Game</button>
    </div>
  </div>
</div>

<svg class="hidden" width="50" height="50"><circle cx="4" cy="25" r="4" stroke="green" stroke-width="0" fill="yellow"></circle><circle cx="46" cy="25" r="4" stroke="green" stroke-width="0" fill="yellow"></circle><line x1="8" y1="25" x2="42" y2="25" style="stroke:rgb(255,0,0); stroke-width:2;" stroke-dasharray="5,5"></line></svg>

<!--  
  // History

  2018-01-11 - Thursday

  @6:40 PM - Codewars -> The Line Game (Box 'em In)
  @8:15 PM - Back to looking for a better online Coding Notebook.

  @12:30 AM
    - Coding Notes have finally found a home!
    - RightNote -> Export to Webbook -> Copy to kdcinfo.com (public)
  @4:55 AM - Codewars Kata - Game: Dots and Boxes
    - Still trying to figure out the calculations to get each 4 box outlines.
      Once I figure out the calculation for each quadrant,
      Just check to see if there are 3 of them in the list; if so, add 4th.
    - I "think" I'm gonna have to do a recursive function;
      On each pass to see if any boxes have 3 walls,
      filling one in could close in one before or above it.
      Just keep a track if any new lines were added to linesPlayed; if not, no more loopback.


  // [2018-01-17] Completed algorithms and braided player progression (2 and 3 players).
  // -This may also play with one person...? Albeit would be nice to add in a simulator.

  2018-01-16 - Tuesday

  The final hours; Started about 30% in 
    (from what I did and could use from my Codewars kata submission).

  @10:00 PM - 3 hours
    - Figured out the algorithms (pattern) for finding the boxes from line selections.
    - Well, it's about 90-95% proofed out, but I can visualize the rest, 
      so it's just academic (or would that be, fundamental(s)?).

  @11:05 PM - 1 hour
    - Started back on Dots and Boxes.
      - Just looked around and noticed I hadn't put my mouse back...
      Guess (direct) coding doesn't take a lot of mouse work.
        Direct* = Not having to look stuff up and test stuff out. Just coding.

  @11:45 PM
    - Ready to begin again...

  @12:35 AM - 0.75 hour
    - Finished transpiling my Open Office Calc data into JavaScript variables:

  @2:30 AM - 2 hours
    - Making good (notable) progress. :)
    - Got it mostly working.
      Moving up the tested line numbers; up to 3 and it doesn't look right.
      boxRightNum should be 1, not 3 (when line picked is 3 on a 2x2 box grid).

  @5:55 AM - 3.50 hours
    - Think I've got it. "Think", being the operative word here.
    - Adjusted each cardinal; had to split 'bottom' check/calc.
    - Next issue: No errors thrown on my using `array[colName]` instead of `array['colName']`.
      - Found because it was breaking the loop when multiple boxes were found.

  @7:40 AM - 1.75 hours
    - Next issue -- Gotta score and adjust move.
      Then end/start game.
      Then include names.
      Issue is scoring happens each turn; not just on box pin.

  @8:55 AM - 1.25 hours
    - Scoring stuff done.
    - Tested 2 and 3 players with 2 filled boxes.

  = 13.25 hours

  === ~20-24 hours

  [2018-01-17] - Let's make it playable!

  @7:20 PM
    - Failing silently (or Codepen showing error at end of file):
      // Last time was my not quoting object keys. This time was because
      // I didn't close the multiline comment at the end of the file ( `* /` )

  [2018-01-18] - Let's create the board!
    - Finished with all 'preChecks'. We have the data to initialize a board.
    - Can now actually create a game... Now we just need a board to play on.
      Game.prototype.createBoard = function() {
        // We have our game instance and all its info regarding board size and players.
        // How to draw the board...?
      }

  [2018-01-19] - Board is coming along nicely!
    - Created SVG's for dot connectors (hor and ver hotspots).
    - Got all nodes laid out.
    - Got SVGs lined up nicely.
    - Formatted player names
      - Got setup for player turns with 'active' class.
      - Added scores and prepped for dynamic updates.
    - Got Hor and Ver event handlers working.
      - To pass the index, I added a CSS class name to each node: node-n
      - To execute the listener I call:
        addEventListener('click', this.play.bind(this), false)

    - @2:00 AM - Fixed: Issue with Hor and Ver hotspot overlaps.
        Had to refigure the HTML / CSS (aligned the dot squares over them.)
    - Updated 'open' SVGs to have gaps. Added yellow highlight on hover.

  [2018-01-20] - Game play is completed. Ready for play.
    - Integrated plays with board.
        - Open vs Clicked lines.
        - Player scores.
        - Player numbers in pinned boxes.
        - Change 'active' class when player turn changes.
    - Allow player to change game board background color.
    - Technically completion was [2018-01-21] @ ~2:30 AM PST

  [2018-01-21]
    - Forked Pen and started on 'React with TypeScript' version.
      https://codepen.io/KeithDC/pen/OzdoLv/
      ETA: [2018-01-26]

  @TODO:
    - 
-->
              
            
!

CSS

              
                body {
  position: relative;
}
h1,
h2 {
  text-align: center;
}
h1 {
  margin: 1em auto 0.25em;
}
h2 {
  color: darkgray;
  font-size: 14px;
  font-weight: normal;
  margin: 0 auto 1.5em;
}
h3 {
  margin: 0.5em auto;
}
p.description {
  display: block;
  font-size: 12px;
  line-height: 1.4;
  margin: 0 auto 1rem;
  max-width: 434px;
  outline: 0px solid green;
  padding: 0;
  position: relative;
}
.row {
  position: relative;
}
aside {
  font-size: 12px;
  line-height: 1.4;
  margin: 0 auto;
  outline: 1px solid lightblue;
  padding: 1em;
  max-width: 400px;
}
aside li {
  border-bottom: 1px dotted lightgray;
  margin: 5px 0;
  width: 95%;
}
form[name="icdots"] {
  border: 3px solid lightblue;
    border-radius: 2px;
  box-shadow: 3px 3px green;
  margin: 0 auto 15px;
  padding: 15px;
  min-width: 250px;
  max-width: 400px;
  width: 80%;
}
label {
  display: inline-block;
  font-size: 14px;
  position: relative;
}
  label .label-text {
    display: inline-block;
    width: 145px;
  }
  label .input-size {
    padding-left: 5px;
    width: 35px;
  }

.start-it-container {
  margin: 0.75em auto 0;
  text-align: center;
}
  .start-it-button {
    background-color: #ddeeff;
    cursor: pointer;
  }
.buttons-up-down {
  display: inline-block;
  height: 30px;
  outline: 0px solid red;
  padding: 10px 0 0;
}
  .players-count {
    background-color: #ddeeff;
    border-radius: 50%;
    cursor: pointer;
    font-size: 16px;
    outline: 0px solid red;
    padding: 0;
    width: 20px;
  }
  .tooltip-container {
    display: inline-block;
    outline: 0px solid blue;
    position: relative;
  }
  tooltip {
    display: inline-block;
    margin: 0 0 0 2px;
    position: absolute;
      left: 0px;
      top: -30px;
      z-index: 0;
  }
  .tooltip-small {
    border: 1px solid rgba(200,75,25,0.75);
      border-radius: 5px;
    color: rgba(200,75,25,0.75);
    cursor: pointer;
    display: inline-block;
    font-size: 14px;
    height: 15px;
    line-height: 1.1;
    opacity: 0.8;
    padding: 1px;
    text-align: center;
    transition: all 0.1s ease-in;
    width: 15px;
  }
  .tooltip-big {
    background-color: #ffffff;
    border: 2px dashed rgba(150,0,0,0.5);
      border-radius: 7px;
    font-size: 14px;
    opacity: 1;
    padding: 3px;
    top: -50px;
    transition: opacity 0.1s, border 2s ease-in;
  }
  .tooltip-small .tooltip-open,
  .tooltip-big .tooltip-close {
    background-color: #ffffff;
    color: rgba(200,75,25,0.75);
    cursor: pointer;
    font-size: 14px;
    height: 15px;
    text-align: center;
    text-transform: lowercase;
    width: 15px;
  }
    .tooltip-small .tooltip-open {
      border: 0px solid rgba(200,75,25,0.75);
        border-radius: 5px;
      display: inline-block;
      line-height: 1.1;
      padding: 0;
    }
    .tooltip-big .tooltip-close {
      border-left: 1px solid maroon;
      border-bottom: 1px solid maroon;
        border-radius: 2px;
      font-family: monospace;
      line-height: 1.0;
      position: absolute;
        top: 1px;
        right: 1px;
        z-index: 3;
    }
  .tooltip-small .tooltip-close {
    display: none;
  }
  .tooltip-big .tooltip-open {
    display: none;
  }
  .tooltip-small .tooltip-content {
    opacity: 0;
  }
  .tooltip-big .tooltip-content {
    border: 2px dashed rgba(150,0,0,0);
    opacity: 1;
    transition: opacity 1s, opacity 0.25s ease-in;
  }
.player-names-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
}
.player-names {
  display: inline-block;
  margin: 5px auto 0;
  position: relative;
    z-index: 1;
  width: 100px;
}

/* Utility Classes */
.hide,
.hidden {
  display: none;
}
code,
pre,
main,
textarea {
  border: 1px solid green;
  display: inline-block;
  font-size: 12px;
  line-height: 1.8;
  min-width: 400px;
  width: 95%;
}
.bold {
  font-weight: bold;
}

/* Responsive */

@media (max-width: 495px) {
  .tooltip-big {
    width: 155px;
  }
}
@media (max-width: 470px) {
  .tooltip-big {
    width: 130px;
  }
}
@media (max-width: 435px) {
  .tooltip-big {
    width: 105px;
  }
}
@media (max-width: 400px) {
  .tooltip-big {
    width: 80px;
  }
}
@media (min-width: 496px) {
  .tooltip-big {
    width: 180px;
  }
}

.game-board-mask {
  background-color: rgba(235, 245, 235, 0.9);
/*   background-color: rgba(0, 50, 50, 0.1); */
  display: none;
  height: 100%;
  position: fixed;
    top: 0;
    left: 0;
    z-index: 99;
  width: 100%;
}
.game-board {
  background-color: #ffffff;
  border: 2px solid rgba(0,150,50,1);
    border-radius: 4px;
  display: none;
  min-height: 300px;
  min-width: 300px;
  padding: 1em 2em;
  position: fixed;
    top: 50%;
    left: 50%;
    z-index: 100;
  text-align: center;
  transform: translate(-50%, -50%);
}
.welcome {
  color: darkgreen;
  font-size: 16px;
  font-weight: bold;
  margin: 1em 0.5em;
}
.main-board {
  background-color: rgba(50,205,205,.5);
/*   background-color: rgba(200, 225, 255, 0.75); */
  border: 3px solid lightgray;
    border-radius: 10px;
  display: flex;
  flex-wrap: wrap;
  font-family: monospace;
  margin: 1em auto;
  min-height: 200px;
  min-width: 200px;
  position: relative;
  width: 225px; /*2=225;5=495;7=675px;*/
}
  .mb-node {
    color: green;
    display: inline-block;
    font-size: 2em;
    height: 40px;
    line-height: 1.5;
    vertical-align: middle;
    width: 45px;
  }
    .mb-lh {
      position: relative;
      outline: 0px dotted rgba(55,55,225,.5);
    }
    .mb-lv {
      /* content: '\2506'; */ /*2506 2195 2502*/
      outline: 0px dashed rgba(255,55,25,.5);
      position: relative;
    }
    .mb-lh.open:hover,
    .mb-lv.open:hover {
      cursor: pointer;
      outline: 1px solid yellow;
    }
    .div-svgH {
      background-repeat: no-repeat;
      height: 40px;
/*       pointer-events: none; */
      position: absolute;
        top: 1px;
        left: -27px;
      outline: 0px dotted rgba(55,55,225,.5);
      width: 97px;
    }
    .div-svgV {
      background-repeat: no-repeat;
      height: 97px;
/*       pointer-events: none; */
      position: absolute;
        top: -23px;
        left: 2px;
      outline: 0px dotted rgba(55,55,225,.5);
      width: 40px;
    }
    .mb-dot {
      cursor: default;
      outline: 0px solid green;
      position: relative;
        z-index: 110;
    }
      .mb-dot::after {
        content: '\2022'; /*25c9*/
        opacity: 0.1;
      }
    .mb-space {
      /* content: ' '; */
    }
    .mb-play {
      /* content: '1'; */
    }

.main-board-stats {
  font-size: 12px;
}
  .main-board-stats div {
    border: 2px solid rgba(0,150,50,1);
      border-radius: 10px;
    border-top: 0;
    margin: 0.5em auto;
    padding: 0.25em 0.75em;
  }
  .main-board-stats span {
    border: 1px solid lightblue;
    display: inline-block;
    margin: 0.5em;
    padding: 0.5em 0.5em 0.5em 1.0em;
    position: relative;
    white-space: nowrap;
  }
  .main-board-stats span playernum {
    color: darkgray;
    font-family: monospace;
    font-size: 12px;
/*     outline: 1px solid red; */
    position: absolute;
      left: 0;
      top: 0;
  }
  .main-board-stats span.active {
    border: 3px solid lightgreen;
  }
  .main-board-stats span.active playernum {
    color: red;
    font-weight: bold;
  }
.main-board-close {
  /*background-color: rgba(200, 225, 255, 0.75); */
  background-color: #ddeeff;
  cursor: pointer;
}
.main-board-footer {
  margin: 0;
}
.main-board-footer .footer-left,
.main-board-footer .footer-right {
  display: inline-block;
  outline: 0px solid blue;
  padding: 0.5em 0;
  vertical-align: bottom;
  width: 47%;
}
.main-board-footer .footer-left select {
  margin: 10px 0 0;
  padding: 3px;
}
              
            
!

JS

              
                ;console.clear();

/* My interactive version of a game inspired by a Codewars kata.
  // Wikipedia: https://en.wikipedia.org/wiki/Dots_and_Boxes

    The Game:
    1. Board is between 2 and 7 (represented as 1 row of boxes).
    2. Number of players is between 2 and [Board] size.
    3. Players take turns.
    4. If a player fills in a box, they go again.
*/

// @TODO: Need better State Mgmt than global vars
   // Perhaps localStorage. May do in React version.

let letsPlay1 = {},
    SVGLib = {};

// SVG injection thanks to: https://stackoverflow.com/a/21626701/638153

const mySVGHClicked = "<svg xmlns='http://www.w3.org/2000/svg' width='98' height='40'><circle cx='4' cy='20' r='4' stroke='green' stroke-width='0' fill='yellow'></circle><circle cx='94' cy='20' r='4' stroke='green' stroke-width='0' fill='yellow'></circle><line x1='8' y1='20' x2='90' y2='20' style='stroke:rgb(255,0,0); stroke-width:1;'></line></svg>";
SVGLib['mySVGHClicked64'] = window.btoa(mySVGHClicked);

const mySVGVClicked = "<svg xmlns='http://www.w3.org/2000/svg' width='40' height='90'><circle cx='20' cy='4' r='4' stroke='green' stroke-width='0' fill='yellow'></circle><circle cx='20' cy='94' r='4' stroke='green' stroke-width='0' fill='yellow'></circle><line x1='20' y1='8' x2='20' y2='80' style='stroke:rgb(0,0,255); stroke-width:1;'></line></svg>";
SVGLib['mySVGVClicked64'] = window.btoa(mySVGVClicked);

const mySVGHOpen = "<svg xmlns='http://www.w3.org/2000/svg' width='98' height='40'><circle cx='4' cy='20' r='4' stroke='green' stroke-width='0' fill='yellow'></circle><circle cx='94' cy='20' r='4' stroke='green' stroke-width='0' fill='yellow'></circle><line x1='8' y1='20' x2='28' y2='20' style='stroke:rgb(255,100,100); stroke-width:1;' stroke-dasharray='3,3'></line><line x1='70' y1='20' x2='90' y2='20' style='stroke:rgb(255,100,100); stroke-width:1;' stroke-dasharray='3,3'></line></svg>";
SVGLib['mySVGHOpen64'] = window.btoa(mySVGHOpen);

const mySVGVOpen = "<svg xmlns='http://www.w3.org/2000/svg' width='40' height='90'><circle cx='20' cy='4' r='4' stroke='green' stroke-width='0' fill='yellow'></circle><circle cx='20' cy='94' r='4' stroke='green' stroke-width='0' fill='yellow'></circle><line x1='20' y1='8' x2='20' y2='28' style='stroke:rgb(100,100,255); stroke-width:1;' stroke-dasharray='3,3'></line><line x1='20' y1='60' x2='20' y2='80' style='stroke:rgb(100,100,255); stroke-width:1;' stroke-dasharray='3,3'></line></svg>";
SVGLib['mySVGVOpen64'] = window.btoa(mySVGVOpen);

function Game(board, playerNames) {
  this.boardSize = this.isGoodType(board, Number) ? board : 2;     // 2 - 7
  this.playerNames = this.isGoodType(playerNames, Array, String) ? playerNames : [];
  this.numOfPlayers = this.playerNames.length; // 2 - [board]
  this.numOfBoxes = 1 << this.boardSize; // A.k.a., Math.pow(2, this.boardSize);
  this.linesPlayed = [];
  this.boxesPinned = [];
  this.numOfLines = (4 + ((this.boardSize-1)*3)) + ((this.boardSize-1) * (3 + ((this.boardSize-1)*2)));
  this.allBoxNodes = ((this.boardSize * 2) + 1) * ((this.boardSize * 2) + 1);
  this.players = Array.from(Array(this.numOfPlayers)).map( e => 0);
  this.curPlayer = 1;
  this.curLine = 0;
  this.curLineDir = '';
  // this.playListener = ()=>{};
  this.play = this.play.bind(this);
}

Game.prototype.createBoard = function() {
  const ourBoard = document.querySelector('.game-board'),
        mainBoard = ourBoard.querySelector('.main-board');

  // PUT PLAYER NAMES ON BOARD

  let displayPlayerNames = '';

  this.playerNames.forEach( (n,i) => {
    if (i === 0) {
      displayPlayerNames += '<span class="pname-' + i + ' active">' +
        '<playernum>' + (i + 1) + '</playernum><b>' + n + '</b> [ <score>0</score> ]</span> ';
    } else {
      displayPlayerNames += '<span class="pname-' + i + '">' +
        '<playernum>' + (i + 1) + '</playernum><b>' + n + '</b> [ <score>0</score> ]</span> ';
    }
  });

  ourBoard.querySelector('.main-board-stats').innerHTML = "<div>" + displayPlayerNames + "</div>";

  // console.log('mainBoard 1: ', mainBoard, mainBoard.children[0]);
  // console.log('mainBoard 2: ', mainBoard.children.length, mainBoard.children);

  // CLEAR ALL BOARD ELEMENTS IF SET PREVIOUSLY
  while (mainBoard.children[0]) {
    mainBoard.removeChild(mainBoard.children[0]);
  }
  mainBoard.style.width = (45 * ((this.boardSize * 2) + 1)).toString() + 'px';
  ourBoard.previousElementSibling.style.display = 'block';
  ourBoard.style.display = 'block';

  // We have our game instance and all its info regarding board size and players.
  // How to draw the board...?
      // Decided to go with a simple custom modal - 2-DIV approach; 1 mask.

  // Types of lines:
     // Light dashed - Clickable
     // Solid        - Clicked

  var createDiv = (idx, cssClass, clickLine) => {

    // This function is called from (right below) within a loop of "allBoxNodes".
    // `clickLine` is only counted and passed with horizontal and vertical clickable lines.

    let newElementOut = document.createElement('div'),
        newElementIn = document.createElement('div');
    // var tmpPlay = this.play.bind(this);
    // this.playListener = tmpPlay;

    if (cssClass.indexOf('lh') > 0) {
      newElementIn.setAttribute('class', 'div-svgH node-' + clickLine);
      newElementIn.style.backgroundImage = "url('data:image/svg+xml;base64," + SVGLib['mySVGHOpen64'] + "')";
      // newElementIn.addEventListener('click', e => {
        // console.log('clicked: ', clickLine); this.play(clickLine); });
      newElementOut.addEventListener('click', this.play); // console.log('clicked: ', e); // playListener
    }
    if (cssClass.indexOf('lv') > 0) {
      newElementIn.setAttribute('class', 'div-svgV node-' + clickLine);
      newElementIn.style.backgroundImage = "url('data:image/svg+xml;base64," + SVGLib['mySVGVOpen64'] + "')";
      // newElementIn.addEventListener('click', e => {
        // console.log('clicked: ', clickLine); this.play(clickLine); });
      newElementOut.addEventListener('click', this.play); // console.log('clicked: ', e); // playListener
    }
    newElementOut.setAttribute('class', cssClass);
    newElementOut.appendChild(newElementIn);
    return newElementOut;
  }

  let gridRow = 0,
      gridCol = 0,
      curClickNode = 1;

  for (let i = 0; i < this.allBoxNodes; i++) {

    if (gridRow % 2 === 0 && gridCol % 2 === 0) {
      mainBoard.appendChild(createDiv(i, 'mb-node mb-dot'));
    } else if (gridRow % 2 === 0 && gridCol % 2 !== 0) {
      mainBoard.appendChild(createDiv(i, 'mb-node mb-lh open', curClickNode)); // .play() is 1-based
      curClickNode++;
    } else if (gridRow % 2 !== 0 && gridCol % 2 === 0) {
      mainBoard.appendChild(createDiv(i, 'mb-node mb-lv open', curClickNode));
      curClickNode++;
    } else if (gridRow % 2 !== 0 && gridCol % 2 !== 0) {
      mainBoard.appendChild(createDiv(i, 'mb-node mb-space'));
    } else if (false) {
      mainBoard.appendChild(createDiv(i, 'mb-node mb-play'));
    }
    gridCol = ((i > 0) && ((i+1) % ((this.boardSize * 2) + 1) === 0)) ? 0 : gridCol + 1;
    gridRow = (gridCol === 0) ? gridRow + 1 : gridRow;
  }
}

Game.prototype.removeBoard = function() {
  const ourBoard = document.querySelector('.game-board');
  ourBoard.querySelector('.main-board')
  ourBoard.querySelector('.main-board-stats').innerHTML = "";
  ourBoard.previousElementSibling.style.display = 'none';
  ourBoard.style.display = 'none';
}

Game.prototype.isGoodType = function(param, type, subtype) {
  if (param.constructor === type && type === Array) {
    for (let i = 0; i < param.length; i++) {
      // Check elements inside Array for proper subtypes.
      if (param[i].constructor !== subtype) {
        throw new Error('Array subtype failed: [' + i + '] ' + 
                        param + ' -- ' + 
                        param[i].constructor + ' !== ' + subtype);
        return false;
      }
    }
  }
  // Ensure the element's type is as expected.
  if (param.constructor === type) {
    return true;
  } else {
    throw new Error('Input param type failed: ' + param.constructor + ' !== ' + type);
    return false;
  }
}

Game.prototype.play = function(e) {

  // See if play is good or not
  // If good, run check for sides === 4 (successful pins)
  // All 4-sided boxes that do not exist in [boxesPinned]
  // If found, allow to play again.
  // Did my first 'purposeful' recursive function

  // const linePlay = e; // MouseEvent(.target)
  // const linePlay = parseInt(Array.from(e.target.classList).filter(e=>e.indexOf('node-')===0)[0].split('-')[1], 10);
  const classListArr = Array.from(e.target.classList),
        classListNode = classListArr.filter(e=>e.indexOf('node-')===0),
          lineNum = classListNode[0].split('-')[1],
          linePlay = parseInt(lineNum, 10),
        classListDir = classListArr.filter(e=>e.indexOf('div-svg')===0),
          nodeDir = classListDir[0].split('svg')[1];

  // classListArr     // ["div-svgH", "node-9"]
  // classListNode    // ["node-9"]
  // lineNum typeof   // "9" "string"
  // linePlay typeof  //  9  "number"
  // classListDir     // ["div-svgH"]
  // nodeDir          // "H"
  // this.boardSize // 3
  // this.curPlayer // 1
  // linePlay       // 9

  this.gotABoxArr = []; // If non-empty, player pinned a box; Let player go again.

  // Check the play

  if (!this.isGoodType(linePlay, Number)) {
    // Cannot 'assume' value provided was indeed a number.
    throw new Error("Not a number? Not a Number!!");

  } else if (linePlay >= 1 && linePlay <= this.numOfLines && this.linesPlayed.indexOf(linePlay) === -1) {
    // GOOD PLAY: Good check directly after Number check and before others; Good checks come before bad.
    this.setLinePlayed(linePlay); // linesPlayed[] used in: checkIfBoxed(), and here in play()
    this.setCurLine(linePlay);    // Line in Play: Let's set the play line so other methods can access it.
    this.setCurLineDir(nodeDir);  // Line in Play's pointing direction: "H" | "V"
    this.tryPlay();

  } else if (this.linesPlayed.indexOf(linePlay) >= 0) {
    // already played (a.k.a., "How'd you do that?")
    throw new Error("How'd you do that? (...The UI should've disabled that option.)");

  } else if (linePlay < 0 || linePlay > this.numOfLines) {
    // play out of bounds (again with the, "How'd you do that?")
    throw new Error("Intruder alert! (...and, How'd you accomplish an out-of-bounds?)");

  } else {
    // No play (a.k.a., What else is there?)
    throw new Error("Wait... what happened? What'd you do?");
  }

  if (this.linesPlayed.length === this.numOfLines) {
    alert('Good game!');
    // Disable board // Enable 'Play Again' button
  } else  if (this.gotABoxArr.length === 0) { // If no boxes were filled; move to next player.
    this.changePlayer();
  }
  // console.log('Player\'s Turn: ', this.curPlayer);
  // console.log('Current Score: ', this.players);
  // console.log('boxesPinned: ', this.boxesPinned);

  // Scoreboard should automagically already be auto-updated with new stats
  // Wait, that's a SPA. This is JavaScript. Can JavaScript do declarative?
}

Game.prototype.setLinePlayed = function(newLinePlayed) {
  const expected = Number;
  if (newLinePlayed.constructor === expected) {
    this.linesPlayed.push(newLinePlayed);
    // Does .push() indicate a mutation of the var? Should I make a copy, push, and assign?
    // Although this method is supposed to act as a 'setter', so not sure how that works.
  } else {
    console.log('Error: Expected: ' + expected + ', Got: ', newLinePlayed.constructor)
  }
}

Game.prototype.setCurLine = function(linePlay) {
  this.curLine = linePlay; // The 'linePlay' param has already been vetted.
}

Game.prototype.setCurLineDir = function(linePlayDir) {
  this.curLineDir = linePlayDir;
}

Game.prototype.tryPlay = function() {

  const borderBoxes = this.getAdjacentBoxes();  // Array of boxes adjacent to selected line.

  borderBoxes.forEach( boxNum => {
    if (this.checkIfBoxed(boxNum)) {    // Run 'boxed in' (pinned) check.
                                        // If you're in here, you scored! Good job!!
      this.gotABoxArr.push(boxNum);     // Will capture if 1 or 2 boxes were pinned.
      this.players[this.curPlayer-1]++; // Scores (boxes pinned per player)
      this.boxesPinned.push({'box': boxNum, 'p': this.curPlayer });
      // ^ Array of objects: [{'box': 1, 'p': 2 }]

      // this.curPlayer   // 1
      // this.gotABoxArr   // [1]
      // this.boxesPinned // [{box: 1, p: 1}]
      // this.players     // [1, 0]
    }
  });

  // SET DASHED LINE TO SOLID
  this.updateCurLine();

  // PUT PLAYER NUM IN PINNED SQUARE
  this.updateBoxes();

  // UPDATE PLAYER SCORES
  this.updateScores();
}

Game.prototype.changePlayer = function() {
  const playerNodes = document.querySelectorAll('.main-board-stats div span');

  // Change CSS class name for `active` - (old) Current User
  playerNodes[this.curPlayer - 1].classList.remove('active');

  this.curPlayer = (this.curPlayer === this.numOfPlayers) ? 1 : this.curPlayer + 1;

  // Change CSS class name for `active` - (new) Current User
  playerNodes[this.curPlayer - 1].classList.add('active');
}

Game.prototype.updateScores = function() {
  // Update all scores
  // 
  let playerNodes = document.querySelectorAll('.main-board-stats div span'),
      playerNodeNum = '0';
  playerNodes.forEach( e => {
      // Not trusting the `i` to be consistently in order,
      // I'm instead grabbing each player's number from the node's class name.
    playerNodeNum = e.className.match(/pname-(\d+)/)[1];
      // playerNodeNum = e.classList.filter(e=>e.indexOf('pname-')===0)[0].split('-')[1],
      // I couldn't see filtering each classList on each playerNode. Hopefully `.match()` is quicker.
    e.getElementsByTagName('score')[0].innerHTML = this.players[playerNodeNum];
  });
}

Game.prototype.updateBoxes = function() {
  // Update boxes with player numbers
  // 
  let boxesNode = document.querySelectorAll('.mb-space');
  this.boxesPinned.forEach( (e, i) => {
    boxesNode[e.box - 1].innerHTML = e.p;
  });
}

Game.prototype.updateCurLine = function() {
  /** SET DASHED LINE TO SOLID
   *    (and remove listener)
   *    (and remove cursor/outline styling (.open))
   */
  let getSVG = document.querySelector('.node-' + this.curLine);

  // console.log('playing: ', this.curLine, this.curLineDir, getSVG);

  if (this.curLineDir.toUpperCase() === 'H') {
    getSVG.style.backgroundImage = "url('data:image/svg+xml;base64," + SVGLib['mySVGHClicked64'] + "')";
    getSVG.parentNode.classList.remove('open');
    getSVG.parentNode.removeEventListener('click', this.play); // playListener

    // console.log('getSVG.parentNode: ', getSVG.parentNode);

  } else if (this.curLineDir.toUpperCase() === 'V') {
    getSVG.style.backgroundImage = "url('data:image/svg+xml;base64," + SVGLib['mySVGVClicked64'] + "')";
    getSVG.parentNode.classList.remove('open');
    getSVG.parentNode.removeEventListener('click', this.play); // playListener

    // console.log('getSVG.parentNode: ', getSVG.parentNode);
  }
}

/*  // 1st line
    =(5  + (5 * 2) + 1) - (( (11 * 1) + 5) - D3)
    =IF( (V4 >= 1) AND (V4 <= 5); (5 * R5) + V4; "False")

    // 6th line (left of 1st box)
    =(5 + (5 * 2) + 1) - (( (11 * 1) + 5) - C4)
    =(T4 >= 5 + 1) AND (T4 <= 5 + 5)

    =IF( (boxBaseLineNum >= 1) AND (boxBaseLineNum <= 5); (5 * R5) + boxBaseLineNum; "False")
*/
Game.prototype.getAdjacentBoxes = function() {
  /**
  *  Get the box number to each side of the selected line.
  *
  *  In the case of the first and last rows, there is always only 1 box below or above it.
  */
  let boxedArray = [],
      lastRowFirstLine = this.numOfLines - this.boardSize + 1,
      lastRowFirstBox = (this.curLine >= lastRowFirstLine) ? (
        (this.boardSize * this.boardSize) - (this.numOfLines - this.curLine)
      ) : 0;

  if (this.curLine <= this.boardSize) { // First Row
    boxedArray.push(this.curLine);
    return boxedArray;
  }

  if (lastRowFirstBox > 0) {            // Last Row
    boxedArray.push(lastRowFirstBox);
    return boxedArray;
  }

  const boxRowBase = (this.boardSize * 2) + this.boardSize + 1, // =(5+(5*2)+1) // Base box row size; 5-board = [16]
        boxRowSize = (this.boardSize * 2) + 1, // =(5*2)+1 // Line count for each row; 5-board = [11]
        boxRowNum = (this.curLIne <= boxRowBase) ? 0 : Math.ceil((this.curLine - this.boardSize) / boxRowSize) - 1,
                       // CEILING(C4/((5*2)+1);1) // 0, 1, ..., boardSize (each row)
        boxBaseLineNum = boxRowBase - (( (boxRowSize * (boxRowNum + 1)) + this.boardSize) - this.curLine),
                       // Individual number 1-(16) [16]
                       // =( 5 + (5 * 2) + 1) - (( (11 * 1) + 5) - C4)
                       // =(5+(5*2)+1)-(((11*1)+5)-C4) // Each row, when being calc'd as a base, has 1-(16)
        boxNumBase = (this.boardSize * boxRowNum) + boxBaseLineNum,
                       // =IF ( ( T4 >= 5 + 1 ) AND ( T4 <= 5 + 5 ); ( 5 * R5 ) + T4 ; "False")
        // boxBelowTrue = (boxBaseLineNum >= 1) && (boxBaseLineNum <= this.boardSize),
                       // V4 = boxBaseLineNum // =(V4 >= 1) AND (V4 <= 5) // Nice stat, but not currently used.

        boxLeftNum =  (boxBaseLineNum >= (this.boardSize + 2) && 
                       boxBaseLineNum <= (this.boardSize + this.boardSize + 1)) ?
                                          (this.boardSize * boxRowNum) + (boxBaseLineNum - this.boardSize - 1) : 0,
                       // =IF ( V4 >= 5 + 2 ) AND ( V4 <= 5 + 5 + 2 )
                       // ( 5 * R11 ) + ( T10 - 5 )

        boxRightNum = (boxBaseLineNum >= (this.boardSize + 1) && 
                       boxBaseLineNum <= (this.boardSize + this.boardSize)) ? 
                                          (this.boardSize * boxRowNum) + (boxBaseLineNum - this.boardSize) : 0,
                       // =IF ( V4 >= 5 + 1 ) AND ( V4 <= 5 + 5 )

        boxAboveNum = (boxBaseLineNum >= ((this.boardSize * 2) + 2) && 
                       boxBaseLineNum <= ((this.boardSize * 3) + 1)) ?
                                          (this.boardSize * boxRowNum) + (boxBaseLineNum - boxRowSize) : 0;
                       // =IF ( V4 >= ((5 * 2) + 1) + 1) AND ( V4 <= ((5 * 3) + 1))
  // End of constant assignments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .^

  let boxBelowNum = 0;

  // Had restriction on bottm boxes due to initially developing for the top row.
  // In that top row, the bottom tier (6 and 7 in a 2x2) also have a bottom box,
  // but that bottom box tolerance had yet to be developed; calculation adopted.

  if (boxBaseLineNum >= 1 && boxBaseLineNum <= this.boardSize) {
    boxBelowNum = (this.boardSize * boxRowNum) + boxBaseLineNum;
  } else if (boxBaseLineNum > boxRowSize && (boxRowNum + 1) < this.boardSize) {
    boxBelowNum = (this.boardSize * boxRowNum) + (boxBaseLineNum - boxRowSize + this.boardSize);
  }                    // =IF ( (V4 >= 1) AND (V4 <= 5); V4 * R5; "False") // Box number or 0 (for none)

  // console.log('above: ', boxBaseLineNum, boxRowNum, boxRowSize);

  if (boxBelowNum > 0) boxedArray.push(boxBelowNum);
  if (boxLeftNum > 0) boxedArray.push(boxLeftNum);
  if (boxRightNum > 0) boxedArray.push(boxRightNum);
  if (boxAboveNum > 0) boxedArray.push(boxAboveNum);

  //   console.log('boxRowBase', boxRowBase);         // 16
  //   console.log('boxRowSize', boxRowSize);         // 11
  //   console.log('boxRowNum', boxRowNum);           // 3
  //   console.log('boxBaseLineNum', boxBaseLineNum); // 16
  //   console.log('boxNumBase', boxNumBase);         // 31

  //   console.log('boxAboveNum', boxAboveNum);       // 20
  //   console.log('boxBelowNum', boxBelowNum);       // 25

  //   console.log('boxLeftNum', boxLeftNum);         // 0
  //   console.log('boxRightNum', boxRightNum);       // 0

  //   console.log('boxedArray', boxedArray);         // [25, 20]

  return boxedArray;
  // return {
  //   'boxBelowNum': boxBelowNum,
  //   'boxLeftNum': boxLeftNum,
  //   'boxRightNum': boxRightNum,
  //   'boxAboveNum': boxAboveNum
  // };
}

Game.prototype.checkIfBoxed = function(boxNum) {
  let thisBox = [],
      notFound = 0,
      borderLines = this.get4BoxLines(boxNum);

  // TOP
  if (this.linesPlayed.indexOf(borderLines['top']) >= 0) {
    thisBox.push(borderLines['top']);
  } else { notFound = 'top'; }

  // LEFT
  if (this.linesPlayed.indexOf(borderLines['left']) >= 0) {
    thisBox.push(borderLines['left']);
  } else { notFound = 'left'; }

  // RIGHT
  if (this.linesPlayed.indexOf(borderLines['right']) >= 0) {
    thisBox.push(borderLines['right']);
  } else { notFound = 'right'; }

  // BOTTOM BOX
  if (this.linesPlayed.indexOf(borderLines['bottom']) >= 0) {
    thisBox.push(borderLines['bottom']);
  } else { notFound = 'bottom'; }

  // console.log('checkIfBoxed', boxNum, '|', thisBox, notFound, '|', borderLines['top'], this.linesPlayed);

  return (thisBox.length === 4) ? true : false;
}

Game.prototype.get4BoxLines = function(boxNum) {
  let boardR = Math.ceil(boxNum / this.boardSize) - 1,
      boardC = boxNum % this.boardSize === 0 ? this.boardSize : boxNum % this.boardSize,
      base = (((this.boardSize * 3) + 1 - this.boardSize) * boardR),
      top    = base + boardC,                               // =(((box5*3)+1-box5)*box5r2)+box5c1
      left   = base + this.boardSize + boardC,             // =(((box5*3)+1-box5)*box5r2)+box5+box5c1
      right  = base + this.boardSize + boardC + 1,         // =(((box5*3)+1-box5)*box5r2)+box5+box5c1+1
      bottom = base + ((this.boardSize * 2) + 1) + boardC; // =(((box5*3)+1-box5)*box5r2)+((box5*2)+1)+box5c1
  // console.log('get4BoxLines', boxNum, boardR, boardC, '|', top, left, right, bottom);
  return {
    'top': top,
    'left': left,
    'right': right,
    'bottom': bottom
  };
}

// const original = [1, 3, 4],
//       gameResults = [1, 3, 4, 6],
//       game = new Game(2);
// const original = [ 20, 21, 24, 26, 33, 34, 35, 38, 39 ],
//       gameResults = [20, 21, 24, 25, 26, 29, 30, 33, 34, 35, 38, 39],
//       game = new Game(4);

// console.log(game.play(original), "Should return '[" + gameResults + "]'");

// const letsPlay1 = new Game(5, 3, ['Keith', 'Seliena', 'Tiffany']);
// letsPlay1.play(49); // ^ board, players, playerNames
// letsPlay1.play(48); // Player 2
// letsPlay1.play(55); // Player 3
// letsPlay1.play(54); // Player 1
// letsPlay1.play(60); // Player 2
// letsPlay1.play(59); // Player 2
// letsPlay1.play(53); // Player 3
// ng1.play(2);

function adjustPlayerNames(dir, bsize) {
  // <Number>bsize is only used to accommodate board size decrements

  let playerNamesArr = document.getElementsByClassName('player-names');

  // Reset 'number of players' tooltip hint (activated when attempting to go too high/low).
  // clearHint();

  if (dir === 'up') {
    if (parseInt(document.getElementsByClassName('board-size')[0].value, 10) > playerNamesArr.length) {
      clearHint();
      // add one
      let tmpInput = document.createElement('input');
      tmpInput.type = "text";
      tmpInput.className = "player-names";
      playerNamesArr[0].parentNode.appendChild(tmpInput);
    } else {
      if (document.getElementsByTagName('tooltip')[0].classList.contains('tooltip-small')) {
        highlightHint(); // Maxed out. Highlight Tooltip for hint.
      }
    }

  } else if (dir === 'down') {
    // remove one
    if (playerNamesArr.length > 2) {
      clearHint();
      playerNamesArr[0].parentNode.removeChild(playerNamesArr[playerNamesArr.length-1]);
    } else {
      if (document.getElementsByTagName('tooltip')[0].classList.contains('tooltip-small')) {
        highlightHint(); // Min'd out. Highlight Tooltip for hint.
      }
    }

  } else if (dir === 'max') {
    clearHint();
    while (playerNamesArr[0] && playerNamesArr.length > bsize) {
      playerNamesArr[0].remove();
    }
  }
}

function highlightHint() {
  // document.getElementsByTagName('tooltip')[0].style.fontWeight = 'bold';
  document.querySelector('.tooltip-open').classList.add('bold');
  document.querySelector('.tooltip-open').style.color = 'red';
  document.querySelector('.tooltip-open').style.boxShadow = '1px 1px 2px 2px yellowgreen';
}

function dehighlightHint() {
  // document.getElementsByTagName('tooltip')[0].style.fontWeight = 'bold';
  document.querySelector('.tooltip-open').classList.remove('bold');
  document.querySelector('.tooltip-open').style.color = 'rgba(200,75,25,0.75)';
  document.querySelector('.tooltip-open').style.boxShadow = 'none';
}

function showHint() {
  // document.querySelector('.tooltip-content').style.display = 'inline-block';
  // console.log('cl: ', document.getElementsByTagName('tooltip')[0]);
  if (document.getElementsByTagName('tooltip')[0].classList.contains('tooltip-small')) {
    document.querySelector('.tooltip').style.zIndex = 2;
    document.getElementsByTagName('tooltip')[0].classList.remove('tooltip-small');
    document.getElementsByTagName('tooltip')[0].classList.add('tooltip-big');
  }
}

function clearHint() {
  dehighlightHint();
  let thisTip = document.getElementsByTagName('tooltip');

  // document.querySelector('.tooltip-content').style.display = 'none';
  document.querySelector('.tooltip').style.zIndex = 0;
  thisTip[0].classList.remove('tooltip-big');
  thisTip[0].classList.add('tooltip-small');
  // document.getElementsByClassName('tooltip-close')[0].innerHTML = '?';
  // console.log('click ttc cH', thisTip[0]);
}

function getName(curNames) {
  const rNames = ['Paper', 'Steckle', 'Lunarcey', 'Hydraple', 'D-31', 'Superr Duperr', 
                  'Fortwone', 'Atlassed', 'Everwood', 'Oak', 'Abovert', 'Lethion',
                  'Elmentts', 'Qemple', 'Curry Ahn', 'Parry Over', 'Peezee', 
                  'Central', 'Sentral', 'Cyglue', 'Get-a-Grip', 'Down Up', 
                  'Xissors', 'Yoo Doo', 'Marsent', 'Tuthedoreon Ish'],
        getPick = () => Math.floor(Math.random() * rNames.length);

  let tmpRun = 0;
  let keepCnt = 0;
  let tmpPick = '';
  const getNameTry = () => {
    if (keepCnt > 7)
      return;
    else
      keepCnt++;

    tmpPick = rNames[getPick()];
    if (curNames.includes(tmpPick)) {
      // console.log('getNameTry again: ', keepCnt, tmpPick, curNames);
      getNameTry();
    }
  }
  getNameTry();
  // console.log('GOT Pick: ', tmpPick);
  return tmpPick;
}

function preChecks() {
  let iBoardSize = document.querySelector('.board-size').value,
      iNumPlayers = document.getElementsByClassName('player-names');

  // @TODO: Need error-handling
  // (can pass back to the game board for feedback)

  if (isNaN(parseInt(iBoardSize, 10))) {
      throw new Error('Board size can only be a number (between 2 and 7, inclusive).');
  } else {
    // So far, so good.
    let iBoard = parseInt(iBoardSize, 10),
        iPlayers = iNumPlayers.length;

    if (iBoard < 2 || iBoard > 7) {
      throw new Error('Board size can only be between 2 and 7, inclusive.');
    } else {
      // So far, so good, good. (We can trust iBoard to be a number between 2 and 7.)
      if (iNumPlayers.length < 2 || iNumPlayers.length > iBoard) {
        throw new Error('Number of players can only be between 2 and the size of the board, inclusive.');
      } else {
        // So far, so good, good... good. (Number of names [iNumPlayers] is a good count.)
        let playerNamesArr = Array.from(iNumPlayers),
            curNames = [],
            regCheck = /^[a-z0-9\s_\-']+$/i,
            nameErrors = [];
        // Names can only contain A-Z 0-9 \W _ - '

        for (let ii = 0; ii < playerNamesArr.length; ii++) {
          if (playerNamesArr[ii].value.length !== 0) {
            // A name was entered for this field #
            if (regCheck.test(playerNamesArr[ii].value)) {
              // Passed our RegExp check
              curNames[ii] = playerNamesArr[ii].value;
            } else {
              // An invalid character? Log it, and assign something else.
              curNames[ii] = getName(curNames);
              nameErrors.push({
                playerNum: ii, 
                nameOld: playerNamesArr[ii].value, 
                nameNew: curNames[ii]
              });
            }
          } else {
            curNames[ii] = getName(curNames);
          }
          // /[a-z0-9\w]/gi.test(t1)   // My pitiful first-thought attempt
          // /^[a-z0-9\W]+$/i.test(t1) // https://stackoverflow.com/a/389022/638153
        }
        // console.log('preChecks Complete: ', curNames, nameErrors);
        letsPlay(iBoard, curNames, nameErrors);
        // iNumPlayers
        // And now we're really good! Silver-like!
        /*
          this.boardSize = this.isGoodType(board, Number) ? board : 2;       // 2 - 7
          this.numOfPlayers = this.isGoodType(players, Number) ? players : 2; // 2 - [board]
          this.playerNames = this.isGoodType(playerNames, Array, String) ? playerNames : [];
          this.numOfBoxes = 1 << this.boardSize; // A.k.a., Math.pow(2, this.boardSize);
          this.linesPlayed = [];
          this.boxesPinned = [];
          this.numOfLines = (4 + ((this.boardSize-1)*3)) + ((this.boardSize-1) * (3 + ((this.boardSize-1)*2)));
          this.players = Array.from(Array(this.numOfPlayers)).map( e => 0);
          this.curPlayer = 1;
          this.curLine = 0;
        */
      }
    }
  }
}

function letsPlay(lpBoardSize, lpCurNames, lpNameErrors) {

  // @TODO: Create a queue so multiple games can be played.

  letsPlay1 = new Game(lpBoardSize, lpCurNames);
  letsPlay1.createBoard();
  
  // This will get called when a <div> on the board is clicked.
  // letsPlay1.play(49); // ^ board, playerNames
}
function clearGame() {
  letsPlay1.removeBoard();
  letsPlay1 = {};
}

window.onload = function() {
  document.querySelector('.start-it-button').addEventListener('click', () => {
    clearHint();
    preChecks();
    // Check all inputs
    // Assign inputs to variables
    // Add functional buttons as needed.
  });
  document.querySelector('.main-board-close').addEventListener('click', () => {
    clearGame(letsPlay1);
  });
  document.getElementsByClassName('board-size')[0].addEventListener('change', (e) => {
    adjustPlayerNames('max', parseInt(e.target.value, 10));
  });
  document.getElementsByClassName('players-count-up')[0].addEventListener('click', () => {
    adjustPlayerNames('up');
  });
  document.getElementsByClassName('players-count-down')[0].addEventListener('click', () => {
    adjustPlayerNames('down');
  });
  document.querySelector('.tooltip .tooltip-open').addEventListener('click', () => {
    showHint();
  });
  document.querySelector('.tooltip .tooltip-close').addEventListener('click', () => {
    clearHint();
  });
  document.getElementsByClassName('player-names')[0].addEventListener('change', (e) => {
    clearHint();
  });
  document.getElementsByClassName('player-names')[1].addEventListener('change', (e) => {
    clearHint();
  });
  document.querySelector('.main-board-background').addEventListener('change', (e) => {
    document.querySelector('.main-board').style.backgroundColor = e.target.value;
  })
  document.addEventListener('keydown', function(e) {
    // [event.keyCode] is deprecated.
    // [event.key] is its replacement.
    // [event.code] is not supported well (2017-11-07).
    if (e.key.toLowerCase() === 'escape') { // ESCAPE
      if (Object.keys(letsPlay1).length !== 0 || letsPlay1.constructor !== Object) {
        if (window.confirm("Do you really want to quit?")) { 
          clearGame(letsPlay1);
        }
      }
    }
  });
}

              
            
!
999px

Console