Hardly an initial commit. The AI can effectively play either X or O. Options for allowing the human player to go first and to switch AI algorithms are supported in the UI.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
tictactoe/
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>p5.js example</title>
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.column {
|
||||
float: left;
|
||||
}
|
||||
|
||||
/* Clear floats after the columns */
|
||||
.row:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function resetBoard(){
|
||||
gameBoard = [
|
||||
['', '', ''],
|
||||
['', '', ''],
|
||||
['', '', '']
|
||||
];
|
||||
|
||||
gameOver = false;
|
||||
currentPlayer = 0;
|
||||
fill(220);
|
||||
rect(0,0,width,height);
|
||||
document.getElementById("statusLabel").innerHTML = "";
|
||||
redraw();
|
||||
}
|
||||
|
||||
function setPruning(){
|
||||
usePruning = document.getElementById("usePruning").checked;
|
||||
print(usePruning);
|
||||
}
|
||||
|
||||
function update(){
|
||||
humanFirst = document.getElementById("humanFirst").checked;
|
||||
}
|
||||
</script>
|
||||
<script src="p5.min.js"></script>
|
||||
<script src="sketch.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="sketch-holder" class="column"></div>
|
||||
<div class="column">
|
||||
<label id="statusLabel"></label></br>
|
||||
<input type="checkbox" id="usePruning" onclick="setPruning()" unchecked></input><label for="usePruning">Use Pruning Alogrithm</label>
|
||||
</br><input type="checkbox" id="humanFirst" onclick="update()" unchecked></input><label for="humanFirst">Human makes the first move</label>
|
||||
</br><button id="resetButton" onclick="resetBoard()">Reset Board</button>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,268 @@
|
||||
var cellWidth = 0;
|
||||
var gameOver = false;
|
||||
var humanFirst;
|
||||
var usePruning;
|
||||
var gameBoard = [
|
||||
['', '', ''],
|
||||
['', '', ''],
|
||||
['', '', '']
|
||||
];
|
||||
var currentPlayer = 0;
|
||||
var players = ['X', 'O'];
|
||||
var scores = {
|
||||
X: 1,
|
||||
Tie: 0,
|
||||
O: -1
|
||||
};
|
||||
var eh = true;
|
||||
|
||||
function setup() {
|
||||
var canvas = createCanvas(500, 500);
|
||||
cellWidth = floor(width / 3);
|
||||
canvas.parent('sketch-holder');
|
||||
humanFirst = document.getElementById("humanFirst").checked;
|
||||
usePruning = document.getElementById("usePruning").checked;
|
||||
}
|
||||
|
||||
function draw() {
|
||||
background(220);
|
||||
|
||||
strokeWeight(4);
|
||||
line(0, height / 3, width, height / 3);
|
||||
line(0, height - height / 3, width, height - height / 3);
|
||||
line(width / 3, 0, width / 3, height);
|
||||
line(width - width / 3, 0, width - width / 3, height);
|
||||
|
||||
if(!humanFirst) nextTurn();
|
||||
|
||||
noLoop();
|
||||
}
|
||||
|
||||
function nextTurn() {
|
||||
let bestScore = players[currentPlayer] == 'X' ? -Infinity : Infinity;
|
||||
let move;
|
||||
|
||||
for(let i = 0; i < 3; i++)
|
||||
for(let j = 0; j < 3; j++) {
|
||||
if(gameBoard[i][j] == ''){
|
||||
gameBoard[i][j] = players[currentPlayer];
|
||||
let score;
|
||||
|
||||
if(!usePruning) score = minimax(gameBoard, !(players[currentPlayer] == 'X'));
|
||||
else score = minimaxPrune(gameBoard, -Infinity, Infinity, !(players[currentPlayer] == 'X'));
|
||||
gameBoard[i][j] = '';
|
||||
|
||||
if(players[currentPlayer] == 'X') {
|
||||
if(score > bestScore){
|
||||
bestScore = score;
|
||||
|
||||
move = {i, j};
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(score < bestScore){
|
||||
bestScore = score;
|
||||
|
||||
move = {i, j};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gameBoard[move.i][move.j] = players[currentPlayer];
|
||||
drawPlayer(move.j, move.i, cellWidth, players[currentPlayer]);
|
||||
currentPlayer = (currentPlayer + 1) % players.length;
|
||||
}
|
||||
|
||||
function minimaxPrune(board, alpha, beta, isMaximizing){
|
||||
let result = checkWinner(board);
|
||||
|
||||
if(result != null) {
|
||||
return scores[result];
|
||||
}
|
||||
|
||||
if(isMaximizing){
|
||||
let bestScore = -Infinity;
|
||||
|
||||
for(let i = 0; i < 3; i++)
|
||||
for(let j = 0; j < 3; j++) {
|
||||
if(board[i][j] == ''){
|
||||
board[i][j] = 'X';
|
||||
let score = minimaxPrune(board, alpha, beta, false);
|
||||
board[i][j] = '';
|
||||
|
||||
bestScore = max(score, bestScore);
|
||||
alpha = max(alpha, bestScore);
|
||||
|
||||
if(beta <= alpha)
|
||||
return bestScore;
|
||||
}
|
||||
}
|
||||
return bestScore;
|
||||
}
|
||||
else {
|
||||
let bestScore = Infinity;
|
||||
|
||||
for(let i = 0; i < 3; i++)
|
||||
for(let j = 0; j < 3; j++) {
|
||||
if(board[i][j] == ''){
|
||||
board[i][j] = 'O';
|
||||
let score = minimaxPrune(board, alpha, beta, true);
|
||||
board[i][j] = '';
|
||||
|
||||
bestScore = min(score, bestScore);
|
||||
beta = min(beta, bestScore);
|
||||
|
||||
if(beta <= alpha)
|
||||
return bestScore;
|
||||
}
|
||||
}
|
||||
return bestScore;
|
||||
}
|
||||
}
|
||||
|
||||
function minimax(board, isMaximizing){
|
||||
let result = checkWinner(board);
|
||||
|
||||
if(result != null) {
|
||||
return scores[result];
|
||||
}
|
||||
|
||||
if(isMaximizing){
|
||||
let bestScore = -Infinity;
|
||||
|
||||
for(let i = 0; i < 3; i++)
|
||||
for(let j = 0; j < 3; j++) {
|
||||
if(board[i][j] == ''){
|
||||
board[i][j] = 'X';
|
||||
let score = minimax(board, false);
|
||||
board[i][j] = '';
|
||||
|
||||
bestScore = max(score, bestScore);
|
||||
}
|
||||
}
|
||||
return bestScore;
|
||||
}
|
||||
else {
|
||||
let bestScore = Infinity;
|
||||
|
||||
for(let i = 0; i < 3; i++)
|
||||
for(let j = 0; j < 3; j++) {
|
||||
if(board[i][j] == ''){
|
||||
board[i][j] = 'O';
|
||||
let score = minimax(board, true);
|
||||
board[i][j] = '';
|
||||
|
||||
bestScore = min(score, bestScore);
|
||||
}
|
||||
}
|
||||
return bestScore;
|
||||
}
|
||||
}
|
||||
|
||||
function checkWinner(board, showEnd = false){
|
||||
let winner = null;
|
||||
|
||||
for(let i = 0; i < 3; i++){
|
||||
if(board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ''){
|
||||
winner = board[i][0];
|
||||
let y = 0;
|
||||
|
||||
if(i == 0) y = cellWidth / 2;
|
||||
else y = cellWidth * i + cellWidth / 2;
|
||||
|
||||
if(showEnd) line(0, y, width, y);
|
||||
}
|
||||
}
|
||||
|
||||
for(let i = 0; i < 3; i++){
|
||||
if(board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ''){
|
||||
winner = board[0][i];
|
||||
let x = 0;
|
||||
|
||||
if(i == 0) x = cellWidth / 2;
|
||||
else x = cellWidth * i + cellWidth / 2;
|
||||
|
||||
if(showEnd) line(x, 0, x, width);
|
||||
}
|
||||
}
|
||||
|
||||
if(board[0][0] != '' && board[0][0] == board[1][1] && board[2][2] == board[0][0]){
|
||||
winner = board[0][0];
|
||||
if(showEnd) line(0, 0, width, width);
|
||||
}
|
||||
else if(board[2][0] != '' && board[2][0] == board[1][1] && board[0][2] == board[2][0]){
|
||||
winner = board[2][0];
|
||||
if(showEnd) line(width, 0, 0, width);
|
||||
}
|
||||
|
||||
let openMoves = 0;
|
||||
|
||||
for(let i = 0; i < 3; i++)
|
||||
for(let j = 0; j < 3; j++)
|
||||
if(board[i][j] == '')
|
||||
openMoves++;
|
||||
|
||||
if(!winner && openMoves == 0)
|
||||
return "Tie";
|
||||
|
||||
return winner;
|
||||
}
|
||||
|
||||
function mouseClicked(){
|
||||
if(gameOver) {
|
||||
print("Game over!");
|
||||
return;
|
||||
}
|
||||
|
||||
let x = floor(mouseX / cellWidth);
|
||||
let y = floor(mouseY / cellWidth);
|
||||
|
||||
if(gameBoard[y][x] == ''){
|
||||
drawPlayer(x, y, cellWidth, players[currentPlayer]);
|
||||
gameBoard[y][x] = players[currentPlayer];
|
||||
|
||||
let result = checkWinner(gameBoard, true);
|
||||
|
||||
if(result != null) {
|
||||
showWinner(result);
|
||||
return;
|
||||
}
|
||||
|
||||
currentPlayer = (currentPlayer + 1) % players.length;
|
||||
|
||||
nextTurn();
|
||||
|
||||
result = checkWinner(gameBoard, true);
|
||||
|
||||
if(result != null)
|
||||
showWinner(result);
|
||||
}
|
||||
}
|
||||
|
||||
function showWinner(player) {
|
||||
gameOver = true;
|
||||
|
||||
if(player == "Tie")
|
||||
document.getElementById("statusLabel").innerHTML = `<h1>Its a tie. (Pruning? ${usePruning})</h1>`;
|
||||
else
|
||||
document.getElementById("statusLabel").innerHTML = `<h1>Game over ${player} wins! (Pruning? ${usePruning})</h1>`;
|
||||
}
|
||||
|
||||
function drawPlayer(x, y, w, player) {
|
||||
strokeWeight(2);
|
||||
|
||||
if(player == 'O'){
|
||||
fill(220);
|
||||
ellipse(w / 2 + w * x, w / 2 + w * y, floor(width / 4));
|
||||
}
|
||||
else if (player == 'X'){
|
||||
let newX = w * x;
|
||||
let newY = w * y;
|
||||
let trim = w * 0.90;
|
||||
line(newX + trim * 0.15, newY + trim * 0.15, newX + trim, newY + trim);
|
||||
line(newX + trim * 0.15, newY + trim, newX + trim, newY + trim * 0.15);
|
||||
}
|
||||
|
||||
strokeWeight(10);
|
||||
}
|
||||
Reference in New Issue
Block a user