-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
211 lines (180 loc) · 6.75 KB
/
Copy pathscript.js
File metadata and controls
211 lines (180 loc) · 6.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
// Stores and manages the 3x3 board data
const gameBoard = (function () {
let boardArray = [
["□", "□", "□"],
["□", "□", "□"],
["□", "□", "□"],
];
// Returns the raw 2D array
const getBoardArray = function () {
return boardArray;
};
// Returns all cells as objects with their content and coordinates
const getBoardContents = function () {
return boardArray.flatMap((row, rowIndex) => {
return row.map((element, columnIndex) => {
return {
cellContent: element,
row: rowIndex,
column: columnIndex,
};
});
});
};
// Clears every cell back to empty
const resetBoard = function () {
boardArray.forEach((row) => row.fill("□"));
};
// Counts how many cells are still empty (□)
const getRemainingCells = function () {
return boardArray.flat().filter((item) => item === "□").length;
};
// Places the current player's marker on the chosen cell
// Flip Y axis so 0,0 is bottom-left instead (array row 0 is top, so we invert)
const updateBoard = function (cell, currentPlayer) {
boardArray[2 - cell[1]][cell[0]] = currentPlayer;
};
return {
getBoardArray,
getBoardContents,
getRemainingCells,
updateBoard,
resetBoard,
};
})();
// Creates a player with a name and marker (X or O)
function createPlayer(name, marker) {
const getName = () => name;
const getMarker = () => marker;
return { getName, getMarker };
}
const player1 = createPlayer("Player1", "X");
const player2 = createPlayer("Player2", "O");
// Controls the flow of the game: turns, win checking, outcome
const gameController = (function (gameBoard, player1, player2) {
let boardArray = gameBoard.getBoardArray();
// Randomly decide who goes first
let currentPlayer = Math.random() >= 0.5 ? player1 : player2;
let gameOutcome = {
gameStatus: "Ongoing",
gameVictor: undefined,
};
// Places the current player's marker on the board
const playTurn = (chosenCell) => {
gameBoard.updateBoard(chosenCell, currentPlayer.getMarker());
};
// Returns the current player's marker string
const getCurrentTurn = () => currentPlayer.getMarker();
// Swaps to the other player
const switchPlayerTurn = () => {
currentPlayer = currentPlayer === player1 ? player2 : player1;
};
// Checks for a win or draw and updates gameOutcome
const setGameOutcome = () => {
// Check if any row is all X or all O
const rowWin = boardArray.some((row) => {
return row.every((cell) => cell === "X") || row.every((cell) => cell === "O");
});
// Check if any column is all X or all O
const colWin = boardArray[0]
.map(
(_, colIndex) => boardArray.map((row) => row[colIndex]),
)
.some(
(col) => col.every((cell) => cell === "X") || col.every((cell) => cell === "O"),
);
// Grab the main diagonal (top-left to bottom-right) and check it
const mainDiagonal = boardArray.map((row, rowIndex) => {
return row.filter((cell, columnIndex) => {
return columnIndex === rowIndex;
});
});
const diagonalWin =
mainDiagonal.flat().every((cell) => cell === "X") || mainDiagonal.flat().every((cell) => cell === "O");
if (rowWin || colWin || diagonalWin) {
gameOutcome.gameStatus = "Victor";
gameOutcome.gameVictor = getCurrentTurn();
} else if (gameBoard.getRemainingCells() == 0) {
gameOutcome.gameStatus = "Draw";
}
};
const getGameOutcome = () => gameOutcome;
// Resets the board, turn, and outcome so a new game can start
const resetGame = () => {
gameBoard.resetBoard();
currentPlayer = Math.random() >= 0.5 ? player1 : player2;
gameOutcome = { gameStatus: "Ongoing", gameVictor: undefined };
};
return {
getCurrentTurn,
switchPlayerTurn,
setGameOutcome,
getGameOutcome,
playTurn,
resetGame,
};
})(gameBoard, player1, player2);
// Handles everything the player sees and interacts with in the DOM
const displayController = (function (gameBoard, gameController) {
// Grab all 9 cells and the single subheading element from the DOM
const cells = document.querySelectorAll(".cell");
const statusEl = document.querySelector("#status");
// Map each DOM cell (index 0–8) to its value in boardArray and display it
const renderBoard = () => {
cells.forEach((cell, index) => {
const r = Math.floor(index / 3); // visual row (0 = top)
const c = index % 3; // visual column (0 = left)
const value = gameBoard.getBoardArray()[2 - r][c];
cell.textContent = value === "□" ? "" : value;
// Add a marker class so CSS can colour X and O differently
cell.classList.toggle("cell-x", value === "X");
cell.classList.toggle("cell-o", value === "O");
});
};
// Update the single subheading: only the text changes, the element never moves
const updateStatus = () => {
const outcome = gameController.getGameOutcome();
const marker = gameController.getCurrentTurn();
// Add game-over class to the board so CSS can tint the cells
document.querySelector("#board").classList.toggle("game-over", outcome.gameStatus !== "Ongoing");
// Match the subheading colour to the relevant player's marker colour
statusEl.classList.toggle("cell-x", marker === "X" && outcome.gameStatus !== "Draw");
statusEl.classList.toggle("cell-o", marker === "O" && outcome.gameStatus !== "Draw");
if (outcome.gameStatus === "Victor") {
statusEl.textContent = `PLAYER ${outcome.gameVictor} WINS!`;
} else if (outcome.gameStatus === "Draw") {
statusEl.textContent = "IT'S A TIE!";
} else {
statusEl.textContent = `PLAYER ${marker}'s TURN!`;
}
};
// Runs when a cell is clicked
const handleClick = (index) => {
const c = index % 3;
const r = Math.floor(index / 3);
// Do nothing if the cell is already taken or the game has ended
if (gameBoard.getBoardArray()[2 - r][c] !== "□") return;
if (gameController.getGameOutcome().gameStatus !== "Ongoing") return;
gameController.playTurn([c, r]);
gameController.setGameOutcome();
renderBoard();
// Only switch turns if the game is still going
if (gameController.getGameOutcome().gameStatus === "Ongoing") {
gameController.switchPlayerTurn();
}
updateStatus();
};
// Wire up a click listener on each cell
cells.forEach((cell, index) => {
cell.addEventListener("click", () => handleClick(index));
});
// Reset the game and refresh the board and status when Reset is clicked
document.querySelector("#reset").addEventListener("click", () => {
gameController.resetGame();
renderBoard();
updateStatus();
});
// Draw the empty board and set the initial turn message on page load
renderBoard();
updateStatus();
})(gameBoard, gameController);