final
This commit is contained in:
commit
353c78607d
4 changed files with 859 additions and 0 deletions
3
README.md
Executable file
3
README.md
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
# js-tic-tac-toe
|
||||
|
||||
Tic-Tac-Toe game made in ES6 JS, with fully functional AI player to play against
|
||||
95
assets/css/style.css
Executable file
95
assets/css/style.css
Executable file
|
|
@ -0,0 +1,95 @@
|
|||
body {
|
||||
text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5);
|
||||
box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5);
|
||||
}
|
||||
|
||||
.cover-container {
|
||||
max-width: 42em;
|
||||
}
|
||||
|
||||
.col-md-6 {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#gridspace-background {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#gridspace {
|
||||
height: 510px;
|
||||
width: 510px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.griditem {
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.nav-masthead .nav-link {
|
||||
color: rgba(255, 255, 255, .5);
|
||||
}
|
||||
|
||||
.nav-masthead .nav-link:hover,
|
||||
.nav-masthead .nav-link:focus {
|
||||
border-bottom-color: rgba(255, 255, 255, .25);
|
||||
}
|
||||
|
||||
.nav-masthead .nav-link + .nav-link {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
#darkenScreen {
|
||||
background-color: #000c;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: black;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
#closebutton {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
#closebutton > :first-child {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card-body > .nav > .nav-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card-body > .nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
input {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: rgba(0, 0, 0, .5);
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: white;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: rgba(0, 0, 0, .5);
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
#divider {
|
||||
height: 2px;
|
||||
background-color: rgba(0, 0, 0, .25);
|
||||
border-radius: 40%;
|
||||
}
|
||||
725
assets/js/main.js
Executable file
725
assets/js/main.js
Executable file
|
|
@ -0,0 +1,725 @@
|
|||
const style = (() => {
|
||||
// for setting multiple CSS attributes on an element
|
||||
Element.prototype.setAttributes = function (attributes) {
|
||||
Object.keys(attributes).forEach(key => this.setAttribute(key, attributes[key]));
|
||||
}
|
||||
|
||||
// highlights nav button based on mouse hover
|
||||
const navButtons = document.querySelectorAll(".nav-link");
|
||||
function makeActive() {
|
||||
this.setAttribute('class', this.getAttribute("class") + ' active');
|
||||
}
|
||||
function deactivateButton() {
|
||||
this.setAttribute('class', "nav-link fw-bold");
|
||||
}
|
||||
|
||||
// assigns listeners to nav buttons
|
||||
navButtons.forEach((button) => {
|
||||
button.onmouseenter = makeActive;
|
||||
button.onmouseleave = deactivateButton;
|
||||
});
|
||||
|
||||
// resizes background if user adjusts zoom level
|
||||
function resizeBody () {
|
||||
const childHeight = document.querySelector(".cover-container").offsetHeight;
|
||||
if (window.innerHeight < childHeight) {
|
||||
document.querySelector("body").setAttribute("style", "height: " + childHeight +"px;");
|
||||
}
|
||||
else {
|
||||
document.querySelector("body").setAttribute("style", "height: " + window.innerHeight +"px;");
|
||||
}
|
||||
}
|
||||
|
||||
// calls on page load
|
||||
resizeBody();
|
||||
|
||||
// calls when user changes window size
|
||||
window.onresize = resizeBody;
|
||||
|
||||
// changes greeting based on passed in name
|
||||
function changeGreeting(name) {
|
||||
document.querySelector("#name").textContent = "Welcome " + name + "!";
|
||||
}
|
||||
|
||||
// creates a modal window to change player name and sign
|
||||
function namePopup(callback1, callback2) {
|
||||
// makes divs
|
||||
const darkenScreen = document.createElement('div');
|
||||
const popupCard = document.createElement('div');
|
||||
const cardBody = document.createElement('div');
|
||||
const closeButton = document.createElement('div');
|
||||
const closeButtonContainer = document.createElementNS("http://www.w3.org/2000/svg","svg");
|
||||
const closeButtonSVGPath = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||
const cardTitle = document.createElement('h5');
|
||||
const nameInput = document.createElement('input');
|
||||
const signInput = document.createElement('select');
|
||||
const xs = document.createElement('option');
|
||||
const os = document.createElement('option');
|
||||
const okayButton = document.createElement('div');
|
||||
const wrapperul = document.createElement('ul');
|
||||
const wrapperli = document.createElement('li');
|
||||
|
||||
// sets attributes
|
||||
(() => {
|
||||
darkenScreen.setAttributes({id: 'darkenScreen', style: "height: " + document.querySelector('body').offsetHeight + "px"});
|
||||
popupCard.setAttributes({class: "card", style: "width: 18rem"});
|
||||
cardBody.setAttribute('class', "card-body");
|
||||
closeButton.setAttribute('id', "closebutton");
|
||||
closeButtonContainer.setAttributes({style: "width:24px;height:24px", "viewBox": "0 0 24 24"});
|
||||
closeButtonSVGPath.setAttributes({fill: "#6c757d", d: "M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"});
|
||||
cardTitle.setAttribute('class', "card-text");
|
||||
cardTitle.textContent = "Your name:";
|
||||
nameInput.setAttributes({type: 'text', class: "mb-3"});
|
||||
signInput.setAttribute('class', "mb-3");
|
||||
xs.setAttribute('value', 'x');
|
||||
os.setAttribute('value', 'o');
|
||||
xs.textContent = "X's";
|
||||
os.textContent = "O's";
|
||||
okayButton.setAttribute('class', "nav-link fw-bold active");
|
||||
okayButton.textContent = "Ok";
|
||||
wrapperul.setAttribute('class', "nav nav-pills");
|
||||
wrapperli.setAttribute('class', "nav-item");
|
||||
})();
|
||||
|
||||
// appends divs to DOM
|
||||
(() => {
|
||||
document.querySelector('body').appendChild(darkenScreen);
|
||||
darkenScreen.appendChild(popupCard);
|
||||
popupCard.appendChild(cardBody);
|
||||
cardBody.appendChild(closeButton);
|
||||
closeButton.appendChild(closeButtonContainer);
|
||||
closeButtonContainer.appendChild(closeButtonSVGPath);
|
||||
cardBody.appendChild(cardTitle);
|
||||
cardBody.appendChild(nameInput);
|
||||
cardBody.appendChild(document.createElement("br"));
|
||||
cardBody.appendChild(signInput);
|
||||
signInput.appendChild(xs);
|
||||
signInput.appendChild(os);
|
||||
cardBody.appendChild(wrapperul);
|
||||
wrapperul.appendChild(wrapperli);
|
||||
wrapperli.appendChild(okayButton);
|
||||
})();
|
||||
|
||||
|
||||
// focuses name field
|
||||
nameInput.focus();
|
||||
|
||||
// closes window when called
|
||||
function closeWindow() {
|
||||
document.querySelector('body').removeChild(darkenScreen);
|
||||
}
|
||||
|
||||
// close window if close button pushed - without updating name
|
||||
closeButtonContainer.onclick = closeWindow;
|
||||
|
||||
// changes the name variable to a filtered string of the input value
|
||||
function inputComplete() {
|
||||
// regex
|
||||
filteredInput = nameInput.value.replace(/[^a-zA-Z]+/g, '');
|
||||
// checks for non-empty string (actual value) before proceeding
|
||||
if (filteredInput != "") {
|
||||
// changes name and sign
|
||||
callback1(filteredInput, signInput.value);
|
||||
// updates AI sign
|
||||
if (signInput.value === 'x') {
|
||||
callback2('AI', 'o');
|
||||
}
|
||||
else {
|
||||
callback2('AI', 'x');
|
||||
}
|
||||
// closes window
|
||||
closeWindow();
|
||||
}
|
||||
else {
|
||||
alert("Not a valid name!");
|
||||
}
|
||||
}
|
||||
// creates event listeners on modal window
|
||||
(() => {
|
||||
// updates name when OK button clicked
|
||||
wrapperli.onclick = inputComplete;
|
||||
|
||||
// updates name when Enter keystroke detected
|
||||
document.addEventListener("keydown", function(event) {
|
||||
if (event.code === 'Enter') {
|
||||
event.preventDefault();
|
||||
inputComplete();
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
const winnerPopup = (name, tie = false) => {
|
||||
// makes divs
|
||||
const darkenScreen = document.createElement('div');
|
||||
const popupCard = document.createElement('div');
|
||||
const cardBody = document.createElement('div');
|
||||
const cardTitle = document.createElement('h5');
|
||||
const newButton = document.createElement('div');
|
||||
const wrapperul = document.createElement('ul');
|
||||
const wrapperli = document.createElement('li');
|
||||
|
||||
// sets attributes
|
||||
(() => {
|
||||
darkenScreen.setAttributes({id: 'darkenScreen', style: "height: " + document.querySelector('body').offsetHeight + "px"});
|
||||
popupCard.setAttributes({class: "card", style: "width: 18rem"});
|
||||
cardBody.setAttribute('class', "card-body");
|
||||
cardTitle.setAttribute('class', "card-text");
|
||||
if (tie) {
|
||||
cardTitle.innerHTML = "It's a tie!";
|
||||
}
|
||||
else {
|
||||
if (name === "AI") {
|
||||
cardTitle.innerHTML = "Sorry " + user.getName() + "! <br />You lost!";
|
||||
}
|
||||
else {
|
||||
cardTitle.innerHTML = "Congratulations " + name + "! <br />You won!";
|
||||
}
|
||||
}
|
||||
newButton.setAttribute('class', "nav-link fw-bold active");
|
||||
newButton.textContent = "New";
|
||||
wrapperul.setAttribute('class', "nav nav-pills");
|
||||
wrapperli.setAttribute('class', "nav-item");
|
||||
})();
|
||||
|
||||
// appends divs to DOM
|
||||
(() => {
|
||||
document.querySelector('body').appendChild(darkenScreen);
|
||||
darkenScreen.appendChild(popupCard);
|
||||
popupCard.appendChild(cardBody);
|
||||
cardBody.appendChild(cardTitle);
|
||||
cardBody.appendChild(wrapperul);
|
||||
wrapperul.appendChild(wrapperli);
|
||||
wrapperli.appendChild(newButton)
|
||||
})();
|
||||
newButton.onclick = () => {
|
||||
location.reload();
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
changeGreeting,
|
||||
namePopup,
|
||||
winnerPopup
|
||||
}
|
||||
})();
|
||||
|
||||
const gameBoard = (() => {
|
||||
// creates gameArray[][] containing each position
|
||||
let gameArray = new Array(3);
|
||||
for(let arrayPosition = 0; arrayPosition < gameArray.length; arrayPosition++) {
|
||||
gameArray[arrayPosition] = new Array(3);
|
||||
}
|
||||
|
||||
// checks for wins
|
||||
const check = (player) => {
|
||||
// purely background var to allow the program to not freeze
|
||||
let gameConcluded = false;
|
||||
// get player's sign
|
||||
const sign = player.getSign();
|
||||
|
||||
//get player's name
|
||||
const name = player.getName();
|
||||
|
||||
// checks for wins horizontally
|
||||
for (yloc = 0; yloc < 3; yloc++) {
|
||||
let finder = 0;
|
||||
for (xloc = 0; xloc < 3; xloc ++) {
|
||||
if (gameArray[yloc][xloc] === sign) {
|
||||
finder++;
|
||||
}
|
||||
}
|
||||
if (finder === 3) {
|
||||
gameConcluded = true;
|
||||
style.winnerPopup(name);
|
||||
}
|
||||
}
|
||||
|
||||
// checks for wins vertically
|
||||
for (xloc = 0; xloc < 3; xloc++) {
|
||||
let finder = 0;
|
||||
for (yloc = 0; yloc < 3; yloc ++) {
|
||||
if (gameArray[yloc][xloc] === sign) {
|
||||
finder++;
|
||||
}
|
||||
}
|
||||
if (finder === 3) {
|
||||
gameConcluded = true;
|
||||
style.winnerPopup(name);
|
||||
}
|
||||
}
|
||||
|
||||
// checks for wins from top left to bottom right
|
||||
(() => {
|
||||
let finder = 0;
|
||||
for (posDiag = 0; posDiag < 3; posDiag++) {
|
||||
if (gameArray[posDiag][posDiag] === sign) {
|
||||
finder++;
|
||||
}
|
||||
}
|
||||
if (finder === 3) {
|
||||
gameConcluded = true;
|
||||
style.winnerPopup(name);
|
||||
}
|
||||
})();
|
||||
|
||||
// checks for wins from top right to bottom left
|
||||
(() => {
|
||||
let finder = 0;
|
||||
for (yloc = 0; yloc < 3; yloc++) {
|
||||
xloc = Math.abs(yloc - 2);
|
||||
if (gameArray[yloc][xloc] === sign) {
|
||||
finder++;
|
||||
}
|
||||
}
|
||||
if (finder === 3) {
|
||||
gameConcluded = true;
|
||||
style.winnerPopup(name);
|
||||
}
|
||||
})();
|
||||
|
||||
// checks for ties
|
||||
(() => {
|
||||
let counter = 0;
|
||||
for (yloc = 0; yloc < 3; yloc++) {
|
||||
for (xloc = 0; xloc < 3; xloc++) {
|
||||
if (gameArray[yloc][xloc] != undefined) {
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (counter === 9) {
|
||||
gameConcluded = true;
|
||||
style.winnerPopup(player, true);
|
||||
}
|
||||
})();
|
||||
|
||||
// return the var to move to prevent AI move after game completion
|
||||
return gameConcluded;
|
||||
}
|
||||
|
||||
// sets validity boolean to check if AI move has already been made
|
||||
let AIMoveCompleted = false;
|
||||
|
||||
// when a move is made
|
||||
const move = (player, square = undefined) => {
|
||||
// if called with a square passed - that is, for player move or AI final move
|
||||
if (square != undefined) {
|
||||
// get player's sign
|
||||
const sign = player.getSign();
|
||||
// determine grid position based on square id
|
||||
const yloc = Number(square.getAttribute('id').charAt(0));
|
||||
const xloc = Number(square.getAttribute('id').charAt(1));
|
||||
// if the position is empty, append to array and DOM
|
||||
if (gameArray[yloc][xloc] === undefined) {
|
||||
// assign sign to array position
|
||||
gameArray[yloc][xloc] = player.getSign();;
|
||||
// make SVG elements
|
||||
const SVGBox = document.createElementNS("http://www.w3.org/2000/svg","svg");
|
||||
SVGBox.setAttributes({style: "width:168px; height:168px", "viewBox": "0 0 24 24"});
|
||||
const SVGPath = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||
// assign correct SVG path based on player's sign
|
||||
if (sign == "x") {
|
||||
SVGPath.setAttributes({fill: "black", d: "M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"});
|
||||
}
|
||||
else {
|
||||
SVGPath.setAttributes({fill: "black", d: "M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"});
|
||||
}
|
||||
// append to DOM
|
||||
square.appendChild(SVGBox);
|
||||
SVGBox.appendChild(SVGPath);
|
||||
|
||||
// check player win conditions
|
||||
let finished = check(player);
|
||||
|
||||
// only do AI move if check determines game is NOT done
|
||||
// have to do this or it lags the program
|
||||
if (finished === false) {
|
||||
// call if AI move has NOT been done - otherwise infinite recursion
|
||||
if (AIMoveCompleted === false) {
|
||||
// call ai player move with undefined square
|
||||
move(aiplayer);
|
||||
// check ai player win conditions
|
||||
check(aiplayer);
|
||||
// set AIMoveCompleted boolean to false
|
||||
AIMoveCompleted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// when square is undefined (called for AI logic)
|
||||
else {
|
||||
(() => {
|
||||
// get human and AI players' signs
|
||||
let userSign = "x";
|
||||
let AISign = player.getSign();
|
||||
if (AISign === "x") {
|
||||
userSign = "o";
|
||||
}
|
||||
|
||||
// make the actual move in the game after calculations
|
||||
function setSquare (y,x) {
|
||||
const square = document.getElementById("" + y + x);
|
||||
AIMoveCompleted = true;
|
||||
move(player, square);
|
||||
}
|
||||
|
||||
// at the twos stage
|
||||
// check for two of the same sign in a row on a grid
|
||||
function dubsChecker (sign) {
|
||||
// selects proper square and makes move, then sets validity boolean to false
|
||||
|
||||
// HORIZONTAL
|
||||
(() => {
|
||||
let row = 3;
|
||||
let column = 3;
|
||||
horizontalSearcher:
|
||||
for (yloc = 0; yloc < 3; yloc++) {
|
||||
let finder = 0;
|
||||
for (xloc = 0; xloc < 3; xloc ++) {
|
||||
if (gameArray[yloc][xloc] === sign) {
|
||||
finder++;
|
||||
}
|
||||
else {
|
||||
column = xloc;
|
||||
}
|
||||
}
|
||||
// returns the offending row and breaks loop
|
||||
if (finder === 2) {
|
||||
row = yloc;
|
||||
break horizontalSearcher;
|
||||
}
|
||||
}
|
||||
// checks for non-dummy row value then moves on blank square
|
||||
if (row != 3) {
|
||||
if (gameArray[row][column] === undefined) {
|
||||
setSquare(row,column);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
if (AIMoveCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// VERTICAL
|
||||
(() => {
|
||||
let column = 3;
|
||||
let row = 3;
|
||||
verticalSearcher:
|
||||
for (xloc = 0; xloc < 3; xloc++) {
|
||||
let finder = 0;
|
||||
for (yloc = 0; yloc < 3; yloc ++) {
|
||||
if (gameArray[yloc][xloc] === sign) {
|
||||
finder++;
|
||||
}
|
||||
else {
|
||||
row = yloc;
|
||||
}
|
||||
}
|
||||
// returns the offending column and breaks loop
|
||||
if (finder === 2) {
|
||||
column = xloc;
|
||||
break verticalSearcher;
|
||||
}
|
||||
}
|
||||
// checks for non-dummy column value then moves on blank square
|
||||
if (column != 3) {
|
||||
if (gameArray[row][column] === undefined) {
|
||||
setSquare(row,column);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
if (AIMoveCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// L-R DIAGONAL
|
||||
(() => {
|
||||
let finder = 0;
|
||||
let diag = 3;
|
||||
for (posDiag = 0; posDiag < 3; posDiag++) {
|
||||
if (gameArray[posDiag][posDiag] === sign) {
|
||||
finder++;
|
||||
}
|
||||
else {
|
||||
diag = posDiag;
|
||||
}
|
||||
}
|
||||
if (finder === 2) {
|
||||
if (gameArray[diag][diag] === undefined) {
|
||||
setSquare(diag,diag);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
if (AIMoveCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// R-L DIAGONAL
|
||||
(() => {
|
||||
let finder = 0;
|
||||
let row = 3;
|
||||
let column = 3;
|
||||
for (yloc = 0; yloc < 3; yloc++) {
|
||||
xloc = Math.abs(yloc - 2);
|
||||
if (gameArray[yloc][xloc] === sign) {
|
||||
finder++;
|
||||
}
|
||||
else {
|
||||
row = yloc;
|
||||
column = xloc;
|
||||
}
|
||||
}
|
||||
if (finder === 2) {
|
||||
if (gameArray[row][column] === undefined) {
|
||||
setSquare(row,column);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
// first check opponent's (user's) and block to prevent a 3
|
||||
dubsChecker(userSign);
|
||||
|
||||
if (AIMoveCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// then check own (ai's) and go for gold
|
||||
dubsChecker(AISign);
|
||||
|
||||
if (AIMoveCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// at the ones stage
|
||||
function singlesChecker () {
|
||||
let row = 3;
|
||||
let column = 3;
|
||||
|
||||
// find a single one that looks like the ai's sign
|
||||
(() => {
|
||||
for (yloc = 0; yloc < 3; yloc++) {
|
||||
for (xloc = 0; xloc < 3; xloc++) {
|
||||
if (gameArray[yloc][xloc] === AISign) {
|
||||
row = yloc;
|
||||
column = xloc;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// create an array of possible move options
|
||||
let coordArray = [];
|
||||
|
||||
// find all empty squares and add them to the array
|
||||
// gross way - if statements
|
||||
|
||||
// checks for non-dummy value if one exists
|
||||
// NOTE: one if statement (checking for row alone) faster than two (checking for row and column)
|
||||
if (row > 3) {
|
||||
// creates a -1/0/+1 int for neighbor calculations
|
||||
for (i = -1; i <= 1; i++) {
|
||||
// checks +1/0/-1 place of column value
|
||||
if (0 <= column + i <= 2) {
|
||||
// checks for open square
|
||||
if (gameArray[row][column + i] === undefined) {
|
||||
tempVar = column + i;
|
||||
// pushes to array
|
||||
coordArray.push({
|
||||
row: row,
|
||||
column: tempVar
|
||||
});
|
||||
}
|
||||
}
|
||||
// do the same thing for the rows at the column value
|
||||
if (0 <= row + i <= 2) {
|
||||
if (gameArray[row + i][column] === undefined) {
|
||||
tempVar = row + i;
|
||||
coordArray.push({
|
||||
row: tempVar,
|
||||
column: column
|
||||
});
|
||||
}
|
||||
}
|
||||
// do that but horizontally
|
||||
if (0 <= row + i <= 2 && 0 <= column + i <= 2) {
|
||||
if (gameArray[row + i][column + i] === undefined) {
|
||||
yloc = row + i;
|
||||
xloc = column + i;
|
||||
coordArray.push({
|
||||
row: yloc,
|
||||
column: xloc
|
||||
});
|
||||
}
|
||||
if (gameArray[row - i][column + i] === undefined) {
|
||||
yloc = row - i;
|
||||
xloc = column + i;
|
||||
coordArray.push({
|
||||
row: yloc,
|
||||
column: xloc
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the array actually has some coordinates after the last check
|
||||
if (coordArray.length < 0) {
|
||||
// get random choice
|
||||
// floor removes decimals
|
||||
// random gets number between 0 and 1
|
||||
// multiplied by the array length
|
||||
let rand = Math.floor(Math.random() * coordArray.length);
|
||||
|
||||
// make move at random based on values in coordinate array
|
||||
setSquare(coordArray[rand].row, coordArray[rand].column);
|
||||
}
|
||||
}
|
||||
|
||||
// check if ai already has a square down, make adjacent move
|
||||
singlesChecker();
|
||||
|
||||
if (AIMoveCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// at this point, the ai has no squares on the board
|
||||
function middleChecker() {
|
||||
if (gameArray[1][1] === undefined) {
|
||||
setSquare(1,1);
|
||||
}
|
||||
}
|
||||
|
||||
// pick the middle square if available
|
||||
middleChecker();
|
||||
|
||||
if (AIMoveCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if middle is not available, pick square at random
|
||||
function randomSquare() {
|
||||
// random value from 0 - 3 for x value
|
||||
const x = Math.floor(Math.random() * 3);
|
||||
// different random value from 0 - 3 for y value
|
||||
const y = Math.floor(Math.random() * 3);
|
||||
// check if square at values is available, move if so
|
||||
if (gameArray[y] === undefined) {
|
||||
console.log(y);
|
||||
setSquare(y, x);
|
||||
}
|
||||
else if (gameArray[y][x] === undefined) {
|
||||
setSquare(y, x);
|
||||
}
|
||||
}
|
||||
|
||||
if (AIMoveCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// keep doing it until an open square is found
|
||||
while (AIMoveCompleted === false) {
|
||||
randomSquare();
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
// refreshes the page
|
||||
const newGrid = () => {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
// initializes grid
|
||||
const initGrid = () => {
|
||||
// makes square based on location coordinates and puts those location coordinates into the id of the square
|
||||
function makeSquare (yloc, xloc) {
|
||||
let gridElement = document.createElement('div');
|
||||
gridElement.setAttribute('class', 'griditem');
|
||||
gridElement.onclick = function() {
|
||||
move(user, this);
|
||||
};
|
||||
document.querySelector("#gridspace").appendChild(gridElement);
|
||||
gridElement.setAttribute('id', "" + yloc + "" + xloc + "");
|
||||
}
|
||||
|
||||
// create array of y positions
|
||||
let varArray = [];
|
||||
|
||||
// for each y location
|
||||
for (yloc = 0; yloc < 3; yloc++) {
|
||||
// make each array entry a function that loops thrice and calls makeSquare[][] each time
|
||||
varArray[yloc] = () => {
|
||||
for (xloc=0;xloc<3;xloc++) {
|
||||
makeSquare(yloc,xloc);
|
||||
}
|
||||
}
|
||||
// calls above function
|
||||
varArray[yloc]();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
newGrid,
|
||||
initGrid
|
||||
}
|
||||
})();
|
||||
|
||||
const player = (name, sign) => {
|
||||
// assigns player name to passed in name
|
||||
let playerName = name;
|
||||
|
||||
// assigns player sign to passed in sign
|
||||
let playerSign = sign;
|
||||
|
||||
// makes greeting with player name
|
||||
style.changeGreeting(playerName);
|
||||
|
||||
function updateInfo (newName, newSign) {
|
||||
playerName = newName;
|
||||
playerSign = newSign;
|
||||
style.changeGreeting(playerName);
|
||||
}
|
||||
|
||||
// returns player name
|
||||
function getName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
// returns player sign
|
||||
function getSign() {
|
||||
return playerSign;
|
||||
}
|
||||
|
||||
return {
|
||||
updateInfo,
|
||||
getName,
|
||||
getSign
|
||||
}
|
||||
};
|
||||
|
||||
// creates a user player with default values
|
||||
const user = player("John Doe", 'x');
|
||||
|
||||
// creates an AI player with default values
|
||||
const aiplayer = player("AI", 'o');
|
||||
|
||||
// initializes grid spaces
|
||||
gameBoard.initGrid();
|
||||
|
||||
// reloads page when new button pressed
|
||||
document.querySelector("#newButton").onclick = gameBoard.newGrid;
|
||||
|
||||
// changes player info when name button pressed
|
||||
document.querySelector("#nameButton").onclick = () => {
|
||||
style.namePopup(user.updateInfo, aiplayer.updateInfo);
|
||||
}
|
||||
|
||||
// immediately queries the user for their information to replace default values
|
||||
style.namePopup(user.updateInfo, aiplayer.updateInfo);
|
||||
36
index.html
Executable file
36
index.html
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<meta name="Description" content="Enter your description here"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.1.0/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
<title>Tic-Tac-Toe</title>
|
||||
</head>
|
||||
<body class="d-flex flex-column text-center text-white bg-dark">
|
||||
<div class="cover-container d-flex p-3 mx-auto flex-column">
|
||||
<header class="mb-3">
|
||||
<div>
|
||||
<h3 class="float-md-start mb-0">Tic-Tac-Toe</h3>
|
||||
<nav class="nav nav-masthead justify-content-center float-md-end">
|
||||
<ul class="nav nav-pills">
|
||||
<li class="nav-item"><a href="#" class="nav-link fw-bold" id="newButton">New</a></li>
|
||||
<li class="nav-item"><a href="#" class="nav-link fw-bold" id="nameButton">Change Name</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<div id="name" class="mb-3 fw-bold"></div>
|
||||
<div class="bg-light border rounded-3 p-5 d-flex" id="gridspace-background">
|
||||
<div class="text-black" id="gridspace">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.9.2/umd/popper.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.1.0/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue