This time, I have prepared this page where a user may select what AI bots will be run in the game of Connect Four. The entire repository is here.
Code
index.html:
<!DOCTYPE html>
<html>
<head>
<title>AI vs. AI playing Connect 4</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel='stylesheet' type="text/css" href='css/connect4.css' />
</head>
<body>
<form>
<div class="paramDivs">
<h3>X engine parameters</h3>
<table>
<tr><td><h5>Search algorithm for X (beginning) AI:</h5></td><td></td></tr>
<tr><td> <label for="xEngineId">Choose X engine:</label> </td>
<td>
<select name="xEngine" id="xEngineId">
<option value="alpha-beta">Alpha-beta pruning</option>
<option value="negamax" >Negamax</option>
<option value="pvs" >PVS (Principal Variation Search)</option>
</select>
</td>
</tr>
<tr><td><h5>Search depth for X AI:</h5></td><td></td></tr>
<tr><td> <label for="xEngineDepthId">Choose X engine depth :</label> </td>
<td>
<select name="xEngineDepth" id="xEngineDepthId">
<option value="1" >Depth 1 </option>
<option value="2" >Depth 2 </option>
<option value="3" >Depth 3 </option>
<option value="4" >Depth 4 </option>
<option value="5" >Depth 5 </option>
<option value="6" >Depth 6 </option>
<option value="7" >Depth 7 </option>
<option value="8" selected>Depth 8 </option>
<option value="9" >Depth 9 </option>
<option value="10" >Depth 10</option>
</select>
</td>
</tr>
</table>
</div>
<div class="paramDivs">
<h3>O engine parameters</h3>
<table>
<tr><td><h5>Search algorithm for O (following) AI:</h5></td><td></td></tr>
<tr><td> <label for="oEngineId">Choose O engine:</label> </td>
<td>
<select name="oEngine" id="oEngineId">
<option value="alpha-beta">Alpha-beta pruning</option>
<option value="negamax" >Negamax</option>
<option value="pvs" >PVS (Principal Variation Search)</option>
</select>
</td>
</tr>
<tr><td><h5>Search depth for X AI:</h5></td><td></td></tr>
<tr><td> <label for="oEngineDepthId">Choose O engine depth :</label> </td>
<td>
<select name="oEngineDepth" id="oEngineDepthId">
<option value="1" >Depth 1 </option>
<option value="2" >Depth 2 </option>
<option value="3" >Depth 3 </option>
<option value="4" >Depth 4 </option>
<option value="5" >Depth 5 </option>
<option value="6" >Depth 6 </option>
<option value="7" >Depth 7 </option>
<option value="8" selected>Depth 8 </option>
<option value="9" >Depth 9 </option>
<option value="10" >Depth 10</option>
</select>
</td>
</tr>
</table>
</div>
</form>
<div class="paramDivs">
<button type="button" onclick="runMatch();">RUN MATCH!</button>
</div>
<div class="paramDivs">
<button type="button" onclick="clearOutput();">Clear output</button>
</div>
<pre id='output-text-div'></pre>
<div class="paramDivs">
<button type="button" onclick="haltMatch();">Halt match</button>
</div>
<div class="paramDivs">
<button type="button" onclick="clearOutput();">Clear output</button>
</div>
<script src='js/connect4ai.js'></script>
<script src="js/MatchRunner.js"></script>
<script>
function clearOutput() {
document.getElementById("output-text-div").innerHTML = "";
}
let matchRunner;
function haltMatch() {
if (matchRunner) {
matchRunner.halt();
matchRunner = null;
}
}
function runMatch() {
matchRunner =
new MatchRunner(
getXEngine(),
getOEngine(),
getXEngineDepth(),
getOEngineDepth(),
document.getElementById("output-text-div"));
document.getElementById("output-text-div").innerHTML = "";
matchRunner.start();
}
const heuristicFunction = new ConnectFourHeuristicFunction();
const output = document.getElementById('output-text-div');
function nameToEngine(name) {
switch (name) {
case "alpha-beta":
return new ConnectFourAlphaBetaPruningSearchEngine(
heuristicFunction);
case "negamax":
return new ConnectFourNegamaxSearchEngine(
heuristicFunction);
case "pvs":
return new ConnectFourPrincipalVariationSearchEngine(
heuristicFunction);
default:
throw "Should not get here.";
}
}
function getXEngine() {
const selection = document.getElementById("xEngineId");
return nameToEngine(selection.value);
}
function getOEngine() {
const selection = document.getElementById("oEngineId");
return nameToEngine(selection.value);
}
function getXEngineDepth() {
const selection = document.getElementById("xEngineDepthId");
return Number(selection.value);
}
function getOEngineDepth() {
const selection = document.getElementById("oEngineDepthId");
return Number(selection.value);
}
</script>
</body>
</html>
js/MatchRunner.js:
class MatchRunner {
static #X = 1;
static #O = 0;
#engineX;
#engineO;
#engineDepthX;
#engineDepthO;
#outputDiv;
#currentEngineDepth;
#currentEngineTurn;
#currentTurnNumber = MatchRunner.#X;
#keepRunning = false;
#currentState = new ConnectFourBoard();
#totalDurationX = 0;
#totalDurationO = 0;
constructor() {
this.#engineX = arguments[0];
this.#engineO = arguments[1];
this.#engineDepthX = arguments[2];
this.#engineDepthO = arguments[3];
this.#outputDiv = arguments[4];
this.#currentEngineTurn = this.#engineX;
this.#currentEngineDepth = this.#engineDepthX;
}
start() {
this.#keepRunning = true;
this.clearOutput();
this.#gameLoop();
}
halt() {
this.#keepRunning = false;
this.#outputDiv.innerHTML += "Match halted! Stopping prematurely...";
}
clearOutput() {
this.#outputDiv.innerHTML = "";
}
getTotalDurationX() {
return this.#totalDurationX;
}
getTotalDurationO() {
return this.#totalDurationO;
}
#gameLoop() {
if (!this.#keepRunning) {
window.scrollTo(0, document.body.scrollHeight);
return;
}
const startTime = this.#millis();
this.#currentState =
this.#currentEngineTurn.search(
this.#currentState,
this.#currentEngineDepth,
this.#getPlayerEnum());
const endTime = this.#millis();
const duration = endTime - startTime;
if (this.#currentEngineTurn === this.#engineX) {
this.#totalDurationX += duration;
} else {
this.#totalDurationO += duration;
}
this.#outputDiv.innerHTML += this.#currentState.toString();
this.#outputDiv.innerHTML += "<br/>";
const turnEngineName = this.#getCurrentEngineName();
const turnNumeral = this.#getTurnNumberString();;
this.#outputDiv.innerHTML +=
this.#currentEngineTurn === this.#engineX ?
turnEngineName
+ " made the "
+ turnNumeral
+ " turn, duration: "
+ (endTime - startTime)
+ " milliseconds.<br/>"
:
turnEngineName
+ " made the "
+ turnNumeral
+ " turn, duration: "
+ (endTime - startTime)
+ " milliseconds.<br/>";
this.#currentTurnNumber++;
if (this.#currentState.isTie()) {
this.#keepRunning = false;
this.#outputDiv.innerHTML += "RESULT: It's a tie.<br/>";
this.#outputDiv.innerHTML += this.#getDurationReport();
window.scrollTo(0, document.body.scrollHeight);
return;
}
if (this.#currentEngineTurn === this.#engineX) {
if (this.#currentState.isWinningFor(MatchRunner.#X)) {
this.#keepRunning = false;
this.#outputDiv.innerHTML +=
"RESULT: "
+ this.#currentEngineTurn.getName()
+ " (X) won.<br/>";
this.#outputDiv.innerHTML += this.#getDurationReport();
window.scrollTo(0, document.body.scrollHeight);
return;
}
} else {
if (this.#currentState.isWinningFor(MatchRunner.#O)) {
this.#keepRunning = false;
this.#outputDiv.innerHTML +=
"RESULT: "
+ this.#currentEngineTurn.getName()
+ " (O) won.<br/>";
this.#outputDiv.innerHTML += this.#getDurationReport();
window.scrollTo(0, document.body.scrollHeight);
return;
}
}
this.#flipCurrentEngineTurn();
this.#flipDepth();
window.scrollTo(0, document.body.scrollHeight);
window.requestAnimationFrame(() => this.#gameLoop());
}
#getPlayerEnum() {
if (this.#currentEngineTurn === this.#engineX) {
return MatchRunner.#X;
} else {
return MatchRunner.#O;
}
}
#getDurationReport() {
return this.#engineX.getName()
+ " (X) total duration: "
+ this.#totalDurationX
+ " milliseconds.<br/>"
+ this.#engineO.getName()
+ " (O) total duration: "
+ this.#totalDurationO
+ " milliseconds.<br/>";
}
#flipCurrentEngineTurn() {
if (this.#currentEngineTurn === this.#engineX) {
this.#currentEngineTurn = this.#engineO;
} else {
this.#currentEngineTurn = this.#engineX;
}
}
#flipDepth() {
if (this.#currentEngineDepth === this.#engineDepthX) {
this.#currentEngineDepth = this.#engineDepthO;
} else {
this.#currentEngineDepth = this.#engineDepthX;
}
}
#getTurnNumberString() {
const turn = this.#currentTurnNumber;
if (turn % 10 === 1) {
return turn + "st";
}
if (turn %10 === 2) {
return turn + "nd";
}
if (turn % 10 === 3) {
return turn + "rd";
}
return turn + "th";
}
#millis() {
return new Date().valueOf();
}
#getCurrentEngineName() {
if (this.#currentEngineTurn === this.#engineX) {
return this.#engineX.getName() + " (X)";
}
return this.#engineO.getName() + " (O)";
}
}
Critique request
As always, I would love to hear constructive critisism on my work.
#
? That's a code convention I've never seen before. \$\endgroup\$_
, but I am very much not aware of the coding conventions here. At least now I understand the meaning. Thanks. \$\endgroup\$