-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
77201a6
commit 0785367
Showing
4 changed files
with
367 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
#headText { | ||
justify-content: center; | ||
} | ||
|
||
body { | ||
margin: 0; | ||
padding: 0; | ||
color: #3e0249; | ||
font-family: sans-serif; | ||
} | ||
|
||
* { | ||
box-sizing: border-box; | ||
} | ||
|
||
h1 { | ||
font-size: 54px; | ||
text-transform: uppercase; | ||
} | ||
|
||
.container { | ||
padding: 40px; | ||
height: 100vh; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
flex-direction: column; | ||
} | ||
|
||
#gameBoard { | ||
width: 450px; | ||
display: flex; | ||
flex-wrap: wrap; | ||
margin-top: 40px; | ||
|
||
} | ||
|
||
.box { | ||
height: 150px; | ||
width: 150px; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
color: #3e0249; | ||
font-size: 150px; | ||
} | ||
button { | ||
padding: 10px 20px; | ||
border-radius: 10px; | ||
background-color: #3e0249; | ||
color: white; | ||
border-color: #3e0249; | ||
font-size: 18px; | ||
transition: 200ms transform; | ||
} | ||
button:hover { | ||
cursor: pointer; | ||
transform: translateY(-2px); | ||
} | ||
|
||
.box0, .box1, .box2, .box3, .box4, .box5{ | ||
border-bottom: solid 3px #3e0249 ; | ||
} | ||
|
||
.box0, .box1, .box3, .box4{ | ||
border-right: solid 3px #3e0249; | ||
} | ||
|
||
.box6, .box7 { | ||
border-right:solid 3px #3e0249; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Tic-tac-toe</title> | ||
<link rel="stylesheet" href="AI.css"> | ||
</head> | ||
<body> | ||
<div class="container"> | ||
<h1 id="headText">Let's play<br>O starts</h1> | ||
<button id="Reset">Restart</button> | ||
<div id="gameBoard"> | ||
<div class="box box0" id="0"></div> | ||
<div class="box box1" id="1"></div> | ||
<div class="box box2" id="2"></div> | ||
<div class="box box3" id="3"></div> | ||
<div class="box box4" id="4"></div> | ||
<div class="box box5" id="5"></div> | ||
<div class="box box6" id="6"></div> | ||
<div class="box box7" id="7"></div> | ||
<div class="box box8" id="8"></div> | ||
</div> | ||
</div> | ||
<!--<audio id="sounds" loop= "false" volume = "60"> | ||
<source src="Sound/whoosh.aac" id="select" type="audio/wav"> | ||
</audio> --> | ||
|
||
|
||
|
||
|
||
<script src="AI.js" defer></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
const headText = document.getElementById('headText') | ||
const boxes = document.getElementsByClassName('box'); | ||
const Reset = document.getElementById('Reset') | ||
|
||
var emptyBoxes; | ||
const player1 = 'O'; | ||
const player2 = 'X'; | ||
const victories = [ | ||
[0, 1, 2], | ||
[3, 4, 5], | ||
[6, 7, 8], | ||
[0, 3, 6], | ||
[1, 4, 7], | ||
[2, 5, 8], | ||
[0, 4, 8], | ||
[6, 4, 2] | ||
]; | ||
|
||
const positions = { | ||
1: "Top", | ||
2: "Mid", | ||
3: "Bottom", | ||
4: "Right", | ||
5: "Mid", | ||
6: "Left", | ||
7: "Diagonal", | ||
8: "Diagonal" | ||
} | ||
|
||
var Player = { | ||
1: [], | ||
2: [] | ||
}; | ||
|
||
|
||
createGame(); | ||
Restart(); | ||
|
||
function createGame() { | ||
|
||
emptyBoxes = Array.from(Array(9).keys()); | ||
for (let i = 0; i < boxes.length; i++) { | ||
boxes[i].addEventListener('click', turnClick, false); | ||
} | ||
} | ||
|
||
function turnClick(square) { | ||
if (typeof emptyBoxes[square.target.id] == 'number') { | ||
turn(square.target.id, player1, 1) | ||
if (!checkWinner(emptyBoxes, player1) && !checkTie()) turn(bestSpot(), player2, 2); | ||
} | ||
} | ||
|
||
function turn(squareId, currentPlayer, i) { | ||
emptyBoxes[squareId] = currentPlayer; | ||
Player[i].push(squareId) | ||
document.getElementById(squareId).innerText = currentPlayer; | ||
let gameWon = checkWinner(emptyBoxes, currentPlayer) | ||
if (gameWon) gameOver(gameWon) | ||
} | ||
|
||
function checkWinner(board, currentPlayer) { | ||
let validPlays = board.reduce((acc, el, ind) => | ||
(el === currentPlayer) ? acc.concat(ind) : acc, []); | ||
//console.log(validPlays) | ||
let gameWon = null; | ||
for (let [index, win] of victories.entries()) { | ||
if (win.every(elem => validPlays.indexOf(elem) > -1)) { | ||
gameWon = {index: index, currentPlayer: currentPlayer}; | ||
//console.log(gameWon) | ||
break; | ||
} | ||
} | ||
return gameWon; | ||
} | ||
|
||
function gameOver(gameWon) { | ||
for (let index of victories[gameWon.index]) { | ||
document.getElementById(index).style.backgroundColor = | ||
gameWon.currentPlayer == player1 ? "blue" : "red"; | ||
} | ||
for (var i = 0; i < boxes.length; i++) { | ||
boxes[i].removeEventListener('click', turnClick, false); | ||
} | ||
declareWinner(gameWon.currentPlayer == player1 ? "You win!" : "You lose."); | ||
} | ||
|
||
function declareWinner(winner) { | ||
headText.innerText = winner; | ||
} | ||
|
||
function emptySquares() { | ||
return emptyBoxes.filter(s => typeof s == 'number'); | ||
} | ||
|
||
function bestSpot() { | ||
return minimax(emptyBoxes, player2).index; | ||
} | ||
|
||
function checkTie() { | ||
if (emptySquares().length == 0) { | ||
for (var i = 0; i < boxes.length; i++) { | ||
boxes[i].removeEventListener('click', turnClick, false); | ||
} | ||
declareWinner("Tie Game!") | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
function minimax(newBoard, currentPlayer) { | ||
var availSpots = emptySquares(); | ||
|
||
if (checkWinner(newBoard, player1)) { | ||
return {score: -10}; | ||
} else if (checkWinner(newBoard, player2)) { | ||
return {score: 10}; | ||
} else if (availSpots.length === 0) { | ||
return {score: 0}; | ||
} | ||
var moves = []; | ||
for (var i = 0; i < availSpots.length; i++) { | ||
var move = {}; | ||
move.index = newBoard[availSpots[i]]; | ||
newBoard[availSpots[i]] = currentPlayer; | ||
|
||
if (currentPlayer == player2) { | ||
var result = minimax(newBoard, player1); | ||
move.score = result.score; | ||
} else { | ||
var result = minimax(newBoard, player2); | ||
move.score = result.score; | ||
} | ||
|
||
newBoard[availSpots[i]] = move.index; | ||
|
||
moves.push(move); | ||
//console.log(moves) | ||
} | ||
|
||
var bestMove; | ||
if(currentPlayer === player2) { | ||
var bestScore = -Infinity; | ||
for(var i = 0; i < moves.length; i++) { | ||
if (moves[i].score > bestScore) { | ||
bestScore = moves[i].score; | ||
bestMove = i; | ||
} | ||
} | ||
} else { | ||
var bestScore = Infinity; | ||
for(var i = 0; i < moves.length; i++) { | ||
if (moves[i].score < bestScore) { | ||
bestScore = moves[i].score; | ||
bestMove = i; | ||
} | ||
} | ||
} | ||
|
||
return moves[bestMove]; | ||
} | ||
|
||
function Restart() { | ||
Reset.addEventListener('click', function(){ | ||
headText.style.color = "#3e0249" | ||
for(i = 0; i < boxes.length; i++){ | ||
document.getElementById(`${i}`).innerText = ""; | ||
document.getElementById(`${i}`).style.backgroundColor = '#fff' | ||
} | ||
Player = { | ||
1: [], | ||
2: [] | ||
} | ||
currentPlayer = player1; | ||
headText.innerText = "Let's play again!" | ||
setTimeout(() => { | ||
headText.innerText = "You start"},1500); | ||
createGame() | ||
}) | ||
}; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
Code explained. | ||
|
||
createGame() - Line 240 will create an array emptyBoxes with keys(numbers) for each of the divs grabbed by the variable boxes, and will add the eventlistener click to all of them, and if clicked execute turnClick() on that element | ||
|
||
turnClick(square) line 248 will pass an argument square which represent the div selected, and use the id of this div to check of that in emptyBoxes is still a number and not X or O. | ||
If it is still a number that means that boxes hasnt been used yet, and will execute the method turn(square.target.id, player1, 1) | ||
..... | ||
|
||
function turn(squareId, player, i) line 255 the squareId is the id number of the selected box, player1 is O, and i is 1 (to pass information to the object Player[1]). | ||
It will write the player (O or X ) in the emptyBoxes[squrareId (for example 0)] | ||
Will also push that id number to the Player[1] object | ||
Write the O in the html innertext of that div. | ||
and check if the game has been won by checkWin(emptyBoxes, player), if yes, then execute gameOver | ||
|
||
function checkWinner(board,player) line 263. Create a variable validPlays from passing the reduce method on 'board' which in this example is the argument passed above as 'emptyBoxes' and player which in this case will be player1 (O) | ||
The reduce method will take as arguments board.reduce((a, e, i) - The accumulator, current value(if its a number or O or X), and index of each box. | ||
(e === player) ? a.concat(i) : a, []) which means is the current value equal to O,X ? If yes, then pass the index to the accumulator, for now say the box clicke was the first one, index 0, so plays will have the value 0. | ||
It will also iterate through the victories array using a 'for of' loop passing two parameters for (let [index, win] of victories.entries()), index is the index and win is every array containing 3 combinations. | ||
|
||
if (win.every(elem => validPlays.indexOf(elem) > -1)) if the sequence of numbers in validPlays is found in any array of combinations from victories. It passes the values to gameWon as an object containing the index that matched the victories array and player O or X . | ||
|
||
function gameOver(gameWon) line 278 | ||
will use a for of loop to iterate through the gameWon index ex 0 and iterate through each cell changing the colors. | ||
--- minimax | ||
|
||
function bestSpot() line 297 | ||
will run the minimax function passing emptyboxes as arguments, and player2 | ||
|
||
function minimax(newBoard,player) line 312 newboard is emptyBoxes and player is player2 X | ||
variable availSpots will run the emptySquares function to get only the numbers from emptyBoxes, because what is not a number is already taken by O or X | ||
It will run the method checkWinner in a 'if' statement passing as arguments(newBoard, player1) newBoard is emptyBoxes, if it returns true minimax will return a score of -10. | ||
another 'if' statement will run checkWinner passing newBoard and player2, if it returns true, minimax will return a score of 10. | ||
|
||
It creates a variable moves, and starts a for loop throught the availSpots. | ||
|
||
Creates an empty object named move and creates a key named index and passes the newBoard or '(emptyBoxes)' first available number from availSpots to move.index (move.index = newBoard[availSpots[i]]). If we had selected the first box to O then the first available spot will be the number 1, which will be passed to move.index. Then it will assign the player, which is X to this free spot.(line 326) | ||
line 328 a ''if" statement checking if the player is player2, if yes then use a variable ''result' and assign to this variable the return from minimax, passing newBoard and player1 as arguments. Keep in mind that the first free spot of newBoard (emptyBoxes) has been selected and assigned to player2(X). Then it will assign the result (-10 10 or 0) to the object move in a newly created key named score. It will repeat this untill i reaches the length of availSpots. It will push to the array moves all thousands of possible combinations. | ||
|
||
In order to select the best move, line 342. Check if the player is player2 if so set the bestScore to -infinity ( or anyother big negative number). | ||
Use a for loop to iterate through the moves array, and if the key score of moves[i] is higher than - infinity this is the best move. | ||
Else if the player is player1 set the bestScore to infinity and do the same for loop if the score is lower than bestScore taht is the best move, since we are looking to lower the score for player1. | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|