This is a simple working example of the popular game Connect Four. It is a 2 player game where the goal is to form a horizontal, diagonal, or vertical line of four of one's own discs.
I have two identical versions in both the VueJs 2 and Angular5 frameworks.
- Enter names for Player 1 and Player 2.
- Press the play button.
- Click on the column of the top row with the arrow icon to drop your chip.
- Keep repeating step 3, alternating between players until you get 4 in a row either in diagonal, horizontal, or vertical positions.
- Once a winner has been made, a dialog box will appear asking you to play again.
- Press OK to clear the board.
- Press Cancel to go back to the home page.
Components remain consistent with both frameworks, the only difference is the associated HTML and CSS files. In Vue, the HTML and CSS are all together in one .vue file, while in Angular they are separated, but remain in the same component folder.
The App component is responsible for holding the logo and the "true/false" state of the Start and Table components.
The Start component is responsible for collecting the names of Player 1 and Player 2.
The Peg component is the actual chip that retrieves the prop, "color", to initialize with the color "red" or "black".
The Table component is responsible for creating the actual board/table of the game. It also shows the player name with the color of chip for each player and who's turn it is. Within this component, there are two buttons, one to reset the board and the other to navigate back to the Start component.
The naive approach to check for a win condition is to scan the entire row, column, diagonal left and diagonal right every time a chip is dropped. That is a-lot of iteration and can slow down your application, especially if you add more rows and columns.
The way this application works is similar to how a Linked List works. Every cell is created with an object holding a pointer to the adjacent cells around it and the value of which player it belongs to.
An empty cell is represented with the player value NULL, and if the position is pointing outside of the table then the pointer itself is set to NULL
createTable() {
let table = [];
let totalRows = 6;
let totalCols = 7;
for (let row = 0; row < totalRows; row++) {
table.push([]);
for (let col = 0; col < totalCols; col++) {
table[row].push({})
}
}
for (let row = 0; row < totalRows; row++) {
for (let col = 0; col < totalCols; col++) {
table[row][col].left = col > 0 ? table[row][col - 1] : null;
table[row][col].right = col < totalCols - 1 ? table[row][col + 1] : null;
table[row][col].bottom = row < totalRows - 1 ? table[row + 1][col] : null;
table[row][col].top = row > 0 ? table[row - 1][col] : null;
table[row][col].topLeft = row > 0 && col > 0 ? table[row - 1][col - 1] : null;
table[row][col].topRight = row > 0 && col < totalCols - 1 ? table[row - 1][col + 1] : null;
table[row][col].bottomLeft = row < totalRows - 1 && col > 0 ? table[row + 1][col - 1] : null;
table[row][col].bottomRight = row < totalRows - 1 && col < totalCols - 1 ? table[row + 1][col + 1] : null;
table[row][col].player = null;
}
}
return table
}
Once the current player chip is detected on, for example, Left, then it keeps going in that direction, keeping count the number of times it matches the current player value until it reaches NULL or if player value does not match.
Condition Expression:
currentCell.left == null
orcurrentCell.left.player != currentCell.player
If condition expression is met then it goes back to the first cell where the initial win condition is checked and goes the opposite direction in this case right. Once the condition expression in the opposite direction is met, it checks the count and if count < 4
returns false, failing the win condition. If count > 3
returns true, evaluating a win for the player.
checkWin(player, cell) {
let count = 0;
if (it(player, cell, 'left', 'right')) {
return true
}
if (it(player, cell, 'top', 'bottom')) {
return true
}
if (it(player, cell, 'topLeft', 'bottomRight')) {
return true
}
if (it(player, cell, 'topRight', 'bottomLeft')) {
return true
}
// Evalutes win as false and switches player
this.nextPlayer();
function it(p, c, pos, opPos) {
if (count === 4) {
return true
}
if (!c || !c.player || c.player.name !== p) {
if (opPos === null) {
count = 0;
return false
}
return it(p, cell[opPos], opPos, null)
} else {
count++;
return it(p, c[pos], pos, opPos)
}
}
},
This way of checking for win condition is very efficient and does not care whether there are 1,000 or 1,000,000 rows and columns.