diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/1-tic-tac-toe.iml b/.idea/1-tic-tac-toe.iml new file mode 100644 index 0000000..0c8867d --- /dev/null +++ b/.idea/1-tic-tac-toe.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..fac583c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/index.js b/index.js index 7553909..f2e65c4 100644 --- a/index.js +++ b/index.js @@ -2,65 +2,234 @@ const CROSS = 'X'; const ZERO = 'O'; const EMPTY = ' '; +const RED = '#F00'; + const container = document.getElementById('fieldWrapper'); +let field; +let currentSymbol; +let winner; +let ai; +let successfulMovesCount; + +const isGameOver = () => winner || successfulMovesCount === field.size * field.size; + +function Cell(content, x, y) { + this.content = content; + this.x = x; + this.y = y; +} + +function Field(size) { + this.source = []; + this.size = size; + + this.init = function () { + for (let row = 0; row < this.size; row++) { + this.source.push([]); + for (let col = 0; col < this.size; col++) { + this.source[row].push(null); + } + } + }; + + this.init(); + + this.getRow = function (index) { + return this.source[index]; + } + + this.getColumn = function (index) { + let column = []; + for (let row = 0; row < this.size; row++) { + column[row] = this.source[row][index]; + } + return column; + } + + this.getDiagonal = function (row, col) { + let diagonal = []; + + if (row === col) { + diagonal.push([]); + for (let rowIndex = 0; rowIndex < this.size; rowIndex++) { + diagonal[diagonal.length - 1][rowIndex] = this.source[rowIndex][rowIndex]; + } + } + + if (row + col === this.size - 1) { + diagonal.push([]); + for (let rowIndex = 0; rowIndex < this.size; rowIndex++) { + diagonal[diagonal.length - 1][rowIndex] = this.source[rowIndex][this.size - rowIndex - 1]; + } + } + + return diagonal; + } +} + +function AI() { + this.makeSimpleMove = function () { + let emptyCells = getEmptyCells(); + let selectedCell = emptyCells[Math.floor(Math.random() * emptyCells.length)]; + clickOnCell(selectedCell.x, selectedCell.y); + } + + function getEmptyCells() { + let emptyCells = []; + field.source.forEach(row => emptyCells.push(...row.filter(cell => cell.content === EMPTY))); + return emptyCells; + } +} + startGame(); addResetListener(); -function startGame () { - renderGrid(3); +function startGame() { + let dimension = +prompt('Введите размерность поля', 3); + + createField(dimension); + ai = new AI(); + currentSymbol = CROSS; + winner = null; + successfulMovesCount = 0; + renderGrid(dimension); } -function renderGrid (dimension) { +function createField(dimension) { + field = new Field(dimension); + for (let row = 0; row < field.size; row++) { + for (let col = 0; col < field.size; col++) { + field.source[row][col] = new Cell(EMPTY, row, col); + } + } +} + +function renderGrid(dimension) { container.innerHTML = ''; - for (let i = 0; i < dimension; i++) { + for (let rowIndex = 0; rowIndex < dimension; rowIndex++) { const row = document.createElement('tr'); - for (let j = 0; j < dimension; j++) { + for (let colIndex = 0; colIndex < dimension; colIndex++) { const cell = document.createElement('td'); - cell.textContent = EMPTY; - cell.addEventListener('click', () => cellClickHandler(i, j)); + cell.textContent = field.source[rowIndex][colIndex].content; + cell.addEventListener('click', () => cellClickHandler(rowIndex, colIndex)); row.appendChild(cell); } container.appendChild(row); } } -function cellClickHandler (row, col) { - // Пиши код тут +function updateGame(row, col) { + if (isGameOver()) { + return; + } + + field.source[row][col].content = currentSymbol; + renderSymbolInCell(currentSymbol, row, col); + + if (tryFindWinner(row, col) || isGameOver()) { + winner = currentSymbol; + announceWinner(); + } + + switchCurrentSymbol(); + successfulMovesCount++; +} + +function tryFindWinner(rowIndex, colIndex) { + let symbol = field.source[rowIndex][colIndex].content; + let winningLine = findWinningLine(symbol, + field.getRow(rowIndex), + field.getColumn(colIndex), + ...field.getDiagonal(rowIndex, colIndex)); + + if (winningLine) { + paintCells(winningLine, RED); + return true; + } + + return false; +} + +function findWinningLine(symbol, ...lines) { + let winningLine; + for (let line of lines) { + if (line.every(cell => cell.content === symbol)) { + winningLine = line; + break; + } + } + + return winningLine; +} + +function paintCells(cellsInfo, color) { + cellsInfo.forEach(cell => findCell(cell.x, cell.y).style.backgroundColor = color); +} + +function announceWinner() { + switch (winner) { + case CROSS: + alert('Крестики победили'); + break; + case ZERO: + alert('Нолики победили'); + break; + default: + alert('Победила дружба'); + break; + } +} + +function cellClickHandler(row, col) { + tryMakeMove(row, col); + if (successfulMovesCount % 2 !== 0) { + ai.makeSimpleMove(); + } +} + +function tryMakeMove(row, col) { + if (isGameOver() || field.source[row][col].content !== EMPTY) { + return; + } + console.log(`Clicked on cell: ${row}, ${col}`); + updateGame(row, col); +} - /* Пользоваться методом для размещения символа в клетке так: - renderSymbolInCell(ZERO, row, col); - */ +function switchCurrentSymbol() { + currentSymbol = currentSymbol === CROSS ? ZERO : CROSS; } -function renderSymbolInCell (symbol, row, col, color = '#333') { +function renderSymbolInCell(symbol, row, col, color = '#333') { const targetCell = findCell(row, col); targetCell.textContent = symbol; targetCell.style.color = color; } -function findCell (row, col) { +function findCell(row, col) { const targetRow = container.querySelectorAll('tr')[row]; return targetRow.querySelectorAll('td')[col]; } -function addResetListener () { +function addResetListener() { const resetButton = document.getElementById('reset'); resetButton.addEventListener('click', resetClickHandler); } -function resetClickHandler () { +function resetClickHandler() { console.log('reset!'); + startGame(); } /* Test Function */ + /* Победа первого игрока */ -function testWin () { +function testWin() { clickOnCell(0, 2); clickOnCell(0, 0); clickOnCell(2, 0); @@ -71,7 +240,7 @@ function testWin () { } /* Ничья */ -function testDraw () { +function testDraw() { clickOnCell(2, 0); clickOnCell(1, 0); clickOnCell(1, 1); @@ -84,6 +253,6 @@ function testDraw () { clickOnCell(2, 2); } -function clickOnCell (row, col) { +function clickOnCell(row, col) { findCell(row, col).click(); }