I am practically brand new to coding and this is my first "real" project I've been attempting. It's an attempt at a vanilla JS game with selectable layout and win conditions (partially implemented). ATM it only has a vertical win condition included as I didn't want too much cluttering of the code (horizontal and diagonal would look more so the same).
SCRIPT.JS
const mainDiv = document.querySelector('[data-screen]')
class TicTacToe {
constructor(div) {
this.board = div.querySelector('[data-board]')
}
//most of the initial variables here
varUpdater = () => {
this.pauseButton = this.board.querySelector('[data-pause-button]')
this.pauseButton.addEventListener('click', this.setUp)
this.pauseScreen = this.board.querySelector('[data-pause]')
this.pauseText = this.board.querySelector('[data-pause-text]')
this.playerOne = "x"
this.playerTwo = "o"
this.currentPlayer = this.playerOne
this.winCondition = 3 //adjusts win condition
this.rows = 5 // these values adjust layout
this.columns = 5
}
setUp = () => {
this.pauseMenuSetUp()
this.varUpdater()
this.cellSetUp()
this.start()
}
pauseMenuSetUp = () => {
this.board.innerHTML = `
<div class="pause-screen show" data-pause>
<div data-pause-text></div>
<button data-pause-button>Start</button>
</div>
`
}
//Sets up cell layout and css style based on given parameters
cellSetUp = () => {
this.board.style.cssText = `grid-template-columns: repeat(${this.columns}, auto);`
this.colSeperatorDiv = document.createElement('div')
this.cellElement = document.createElement('div')
this.cellElement.setAttribute('class', 'cell')
for (let i = 0; i < this.rows; i++){
this.cellElement.setAttribute('data-cell', i)
this.colSeperatorDiv.appendChild(this.cellElement.cloneNode(true))
}
for (let i = 0; i < this.columns;i++){
this.colSeperatorDiv.setAttribute('data-seperator', i)
this.board.appendChild(this.colSeperatorDiv.cloneNode(true))
}
}
//Clears cell attributes and adds event listener before removing pause screen
start = () => {
this.board.querySelectorAll('[data-cell]').forEach(cell => {
cell.classList.remove('x', 'o')
cell.removeEventListener('click', this.clickHandle)
cell.addEventListener('click', this.clickHandle, { once: true })
})
this.pauseScreen.classList.remove('show')
}
//Adds symbol based on cell target
clickHandle = (cell) => {
this.cell = cell.target
this.cell.classList.add(this.currentPlayer)
this.checkBoard()
}
//Decides if it should select next player or conclude game
checkBoard = () => {
this.columnIndex = parseInt(this.cell.getAttribute('data-cell'))
this.rowIndex = parseInt(this.cell.parentElement.getAttribute('data-seperator'))
if (this.isConcluded() == true) {
this.pauseText.innerHTML = `${this.currentPlayer} wins!`
this.pauseScreen.classList.add('show')
} else if (this.isConcluded() === 'tie') {
this.pauseText.innerHTML = `Tie!`
this.pauseScreen.classList.add('show')
} else {
this.playerSelect()
}
}
//Selects next player
playerSelect = () => {
this.currentPlayer !== this.playerTwo ? this.currentPlayer = this.playerTwo
: this.currentPlayer = this.playerOne;
}
//Checks for game conclusion. Does NOT check for horizontal and diagonal conditions atm
isConcluded = () => {
return this.verticalScore(0, this.columnIndex) >= this.winCondition ? true
: Array.from(this.board.querySelectorAll('[data-cell]')).every(c => c.classList.contains(this.playerOne) || c.classList.contains(this.playerTwo)) ? 'tie'
: false
}
//Goes down the column using recursion to add up score,
//Diagonal/horizontal win condition would use the rowIndex as well
verticalScore = (score, col) => {
return this.cell.parentElement.querySelector(`[data-cell="${col}"]`) == null ? score += this.reverseDirectionScore(0, this.columnIndex - 1)
: this.cell.parentElement.querySelector(`[data-cell="${col}"]`).classList.contains(this.currentPlayer) ? score += this.verticalScore(1, col + 1)
: score += this.reverseDirectionScore(0, this.columnIndex - 1);
}
//Goes up column using recursion to return a score
reverseDirectionScore = (rScore, rCol) => {
return this.cell.parentElement.querySelector(`[data-cell="${rCol}"]`) == null ? rScore
: this.cell.parentElement.querySelector(`[data-cell="${rCol}"]`).classList.contains(this.currentPlayer) ? rScore += this.reverseDirectionScore(1, rCol - 1)
: rScore
}
}
const newBoard = new TicTacToe(mainDiv)
newBoard.setUp()
attaches to this HTML page
<!DOCTYPE html>
<html>
<head>
<title>Tic-Tac-Toe</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div data-screen>
<div class="board" data-board>
</div>
</div>
<script src="/script.js" async defer></script>
</body>
</html>
Codepen: https://codepen.io/sarbastu/pen/XWjZgey
The game is functioning as intended, but I have a bunch of doubt on the way it was written particularly with possible overuse of querySelector/data-attributes. A few particular questions:
- Was the choice to make the game a template object pointless/more damaging than otherwise?
- Does this code at all represent decently implemented OOP style?
- How is the use of querySelector (mainly the use of it in the last 3 game conclusion check functions)? Should I have attached them to a variable first? Is there anything I could have done better?
Do to my complete lack of experience I have no idea if I am at all approaching OOP or programming in general correctly. Any criticism/feedback is greatly appreciated.