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:
parent
d5e34b0685
commit
52f0ba3ec3
17
README.md
17
README.md
|
@ -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)
|
|
@ -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>
|
|
@ -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
193
script.js
|
@ -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",
|
||||
}}
|
||||
);
|
74
style.css
74
style.css
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue