Majority done, more info in body

outputs user stats on win/draw and readable user-friendly errors in console
should've commited earlier to have a better history, but what am i gonna do about it now
This commit is contained in:
NetMan 2024-01-30 17:42:13 +01:00
parent d5e34b0685
commit 52f0ba3ec3
6 changed files with 333 additions and 1 deletions

View File

@ -1,3 +1,18 @@
# odin-tic-tac-toe
Tic Tac Toe
Tic Tac Toe
using IIFE to prevent unwanted user-forced changes, using addEventListeners
utilizing pubsub, and splitting functions into parts used by others for code readability :)
## todo:
* display points, turn, etc - info in GUI
* "setup screen" on first startup
* reset everything btn
* localstorage or other similar implementation to save progress and players
* edit player settings - name, marker
# preview:
![preview](image.png)

BIN
image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -6,9 +6,13 @@
<title>Document</title>
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="style.css">
<script src="pubsub.js" defer></script>
<script src="script.js" defer></script>
</head>
<body>
<div id="center-contain">
<div id="main-game"></div>
</div>
</body>
</html>

46
pubsub.js Normal file
View File

@ -0,0 +1,46 @@
// PubSub from: https://gist.github.com/fatihacet/1290216
var pubsub = {};
(function(q) {
var topics = {}, subUid = -1;
q.subscribe = function(topic, func) {
if (!topics[topic]) {
topics[topic] = [];
}
var token = (++subUid).toString();
topics[topic].push({
token: token,
func: func
});
return token;
};
q.publish = function(topic, args) {
if (!topics[topic]) {
return false;
}
setTimeout(function() {
var subscribers = topics[topic],
len = subscribers ? subscribers.length : 0;
while (len--) {
subscribers[len].func(topic, args);
}
}, 0);
return true;
};
q.unsubscribe = function(token) {
for (var m in topics) {
if (topics[m]) {
for (var i = 0, j = topics[m].length; i < j; i++) {
if (topics[m][i].token === token) {
topics[m].splice(i, 1);
return token;
}
}
}
}
return false;
};
}(pubsub));

193
script.js
View File

@ -0,0 +1,193 @@
"use strict";
function initiateGame(playersInput) {
const game = (function() {
const mainGameDiv = document.querySelector("#main-game");
let gameboard = new Array(9);
gameboard.fill(null);
// -----------------
function checkWin() {
const combinations = [
"123", "456", "789",
"147", "258", "369",
"159", "357",
];
let combinationsValidate = {};
const allEqualInArray = arr => arr.every( v => v === arr[0] )
const player = activePlayer.player;
let win = false;
combinations.forEach(combination => {
combinationsValidate[combination] = [];
combination.split('').forEach(box => {
combinationsValidate[combination].push(gameboard[+box - 1] == player.marker);
})
if (allEqualInArray(combinationsValidate[combination]) && combinationsValidate[combination][0]) {
player.addPoint();
console.log(`${JSON.stringify({name:player.name, marker:player.marker, points:player.points})} won`);
pubsub.publish("winTrue");
return;
}
});
if (!win) {
if (gameboard.find(field => field === null) === null) {
pubsub.publish("continueGame");
} else {
pubsub.publish("draw");
}
}
}
// -----------------
function addDismissivePopup(popup, popupContent) {
function dismissPopup() {
popup.remove();
}
popup.addEventListener("click", function(event) {
if (this !== event.target) return;
dismissPopup();
}, false);
function dismissPopupKey(event) {
if (event.key.toLowerCase() == "escape") {
popup.remove();
document.body.removeEventListener("keydown", dismissPopupKey);
}
}
const dismissBtn = document.createElement("button");
dismissBtn.setAttribute("type", "button");
dismissBtn.classList.add("dismiss-button");
dismissBtn.textContent = "Dismiss";
dismissBtn.addEventListener("click", dismissPopup);
document.body.addEventListener("keydown", dismissPopupKey);
return dismissBtn;
}
function generatePopup(dismissive) {
const popup = document.createElement("div");
popup.classList.add("popup");
const popupContent = document.createElement("div");
popupContent.classList.add("popup-content");
popup.append(popupContent);
document.body.append(popup);
if (dismissive) {
const dismissBtn = addDismissivePopup(popup, popupContent);
return {popup, popupContent, dismissBtn};
}
return {popup, popupContent};
}
function popupWin() {
const {popup, popupContent, dismissBtn} = generatePopup(true);
popupContent.textContent = `${activePlayer.player.name} (${activePlayer.player.marker}) won!`;
popupContent.append(dismissBtn);
}
function popupDraw() {
const {popup, popupContent, dismissBtn} = generatePopup(true);
popupContent.textContent = `It's a draw!`;
popupContent.append(dismissBtn);
}
// -----------------
function clearController() {
while(mainGameDiv.lastElementChild) {
mainGameDiv.lastElementChild.remove();
}
}
function displayController() {
clearController();
let boxes = [];
for (let i = 0; i < 9; i++) {
let boxDiv = document.createElement("div");
boxDiv.classList.add("box");
boxDiv.setAttribute("data-id", i);
boxDiv.textContent = gameboard[boxDiv.getAttribute("data-id")];
boxDiv.addEventListener("click", function(event) {
changeField(boxDiv.getAttribute("data-id"));
});
boxes.push(boxDiv);
}
boxes.forEach(box => mainGameDiv.appendChild(box));
}
// -----------------
class Player {
constructor(name, marker) {
this.name = name;
this.marker = marker;
this.points = 0;
}
changeName(newName) {
this.name = newName;
}
changeMarker(newMarker) {
this.marker = newMarker;
}
addPoint() {
this.points++;
}
}
let players = {
player1: new Player(playersInput.player1.name, playersInput.player1.marker),
player2: new Player(playersInput.player2.name, playersInput.player2.marker),
}
let activePlayer = Object.create({
change() {
this.player == players.player1
? this.player = players.player2
: this.player = players.player1;
},
set(player) {
if (player instanceof Player) {
this.player = player;
} else {
throw new Error("No such player exists");
}
}
});
// -----------------
function changeField(field) {
if (gameboard[field] === null) {
gameboard[field] = activePlayer.player.marker;
pubsub.publish("fieldChange");
} else if (gameboard[field] === undefined) {
console.warn("No such field exists");
} else {
console.warn("Value already set");
}
}
// -----------------
pubsub.subscribe("fieldChange", displayController);
pubsub.subscribe("fieldChange", checkWin);
pubsub.subscribe("continueGame", () => {activePlayer.change()});
pubsub.subscribe("draw", popupDraw);
pubsub.subscribe("winTrue", popupWin);
pubsub.subscribe("draw", () => {gameboard.fill(null);})
pubsub.subscribe("winTrue", () => {gameboard.fill(null);})
displayController();
activePlayer.change()
})();
}
initiateGame(
{player1: {
name: "Oskar",
marker: "O",
},
player2: {
name: "NetMan",
marker: "X",
}}
);

View File

@ -0,0 +1,74 @@
:root {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
html, body {
min-height: 100dvh;
}
#center-contain {
display: flex;
justify-content: center;
align-items: center;
min-height: 100dvh;
}
#main-game {
display: grid;
grid-template: repeat(3, 80px) / repeat(3, 80px);
padding: 10px;
gap: 10px;
}
.box {
border: 1px solid #000;
box-sizing: border-box;
background-color: #e2e2e2;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
font-size: 3rem;
transition: 0.1s background-color ease-in-out,
0.1s transform ease-in-out;
}
.box:hover {
background-color: #f8f8f8;
transform: scale(1.03);
}
.box:active {
background-color: #efefef;
transform: scale(0.99);
}
.popup {
background: #00000044;
backdrop-filter: blur(6px);
display: flex;
justify-content: center;
align-items: center;
min-height: 100dvh;
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.popup-content {
border: 2px solid #000000aa;
padding: 25px;
border-radius: 10px;
background: #ccc;
color: #000;
}
.dismiss-button {
padding: 5px;
border-radius: 8px;
background-color: #fff;
color: #000;
border: 1px solid #000;
cursor: pointer;
}