See projekt võimaldab mängida klassikalist Tic Tac Toe otse Google Sheetsi tabelis. Kasutajad saavad reaalajas teha käike, kasutades tabeli lahtrite interaktiivsust. Projekt sisaldab automatiseeritud loogikat, mis määrab võitja ja takistab kehtetuid käike.
Kuidas alustada?
Projekti loomiseks kasutasin juhend –> Spreadsheet Dev’is
Teeme uus Google Sheet leht, ja ümbernimetame “Board”

Lisame Google Sheetsi tabelit 3×3 (B2:D4) nimega “Board” ja väli (G2:J2 ühendatud lahter) sõnumite jaoks nimega “Message”

Lisame nuppu “Reset game” ja määrame skripti “resetGame”

Kuidas töötab kood?
Kui mängija valib lahtri, käivitub onSelectionChange(e)
, mis:
- Lähtestab teate
- Kontrollib, kas lahtri valimine on lubatud (
checkSelection
) - Kui kõik on korras, lisab lahtrisse “X” või “O” (
play
)
function onSelectionChange(e) {
// Сбрасываем сообщение
displayMessage("");
// Проверяем, правильна ли выбрана ячейка
var isSelectionValid = checkSelection(e.range);
if(isSelectionValid)
play(e.range); // Если да, запускаем игру
}
Lahtri kontroll (checkSelection
):
- Lahter peab olema mängulaua sees (3×3 ala)
- Saab valida ainult ühe lahtri
- Lahter peab olema tühi
- Kui midagi on valesti, kuvatakse veateade
function checkSelection(range) {
// Проверяем, не вышел ли выбор за пределы игрового поля (ячейки 2-4 по строкам и столбцам)
if(range.rowStart < 2 || range.rowEnd > 4 || range.columnStart < 2 || range.columnEnd > 4) {
error("Select a cell inside the board.");
return false;
}
// Проверяем, выбрана ли только одна ячейка
if(range.rowStart != range.rowEnd || range.columnStart != range.columnEnd) {
error("Select a single cell.");
return false;
}
// Проверяем, пустая ли выбранная ячейка
var cell = SpreadsheetApp
.getActive()
.getSheetByName("Board")
.getRange(range.rowStart, range.columnStart);
var cellValue = cell.getValue();
if(cellValue != "") {
error("Select an empty cell.");
return false;
}
return true;
}
Funktsioon play(range)
:
- Määrab, kelle kord on (mängija 1 või 2)
- Lisab valitud lahtrisse “X” või “O”
- Kontrollib, kas keegi on võitnud (
hasPlayerWonGame
) - Kui keegi võidab, kuvatakse teade võitja kohta. Kui kõik lahtrid on täis, on viik. Kui mäng jätkub, näidatakse, kelle kord on järgmine
function play(range) {
var player = whoseTurnIsIt(); // Определяем, чей ход
// Записываем "X" или "O" в выбранную ячейку в зависимости от хода игрока
SpreadsheetApp
.getActive()
.getSheetByName("Board")
.getRange(range.rowStart, range.columnStart)
.setValue(player == "1" ? "X" : "O");
// Проверяем, выиграл ли игрок
if(hasPlayerWonGame()) {
displayMessage("Player " + player + " has won!");
} else if(areAllSquaresTaken()) {
displayMessage("Game is a draw!"); // Проверяем ничью
} else {
displayMessage("Player " + (player == "1" ? "2's " : "1's ") + "turn.");
}
}
Mängu oleku kontrollimise funktsioonid:
whoseTurnIsIt()
– määrab, kelle kord on, lugedes täidetud lahtrite arvu
function whoseTurnIsIt() {
// Определяем, чей ход: если заполненных ячеек четное количество, то 1-й игрок, иначе 2-й
return numSquaresTaken() % 2 == 0 ? 1 : 2;
}
numSquaresTaken()
– loeb, mitu lahtrit on hõivatud
function numSquaresTaken() {
// Подсчитываем количество занятых клеток (с "X" или "O")
var board = SpreadsheetApp.getActive().getRangeByName("Board").getValues();
var numChars = 0;
board.forEach(function(row) {
row.forEach(function(col) {
if(col == "X" || col == "O")
numChars++;
});
});
return numChars;
}
areAllSquaresTaken()
– kontrollib, kas kõik 9 lahtrit on täis (viigi korral)
function areAllSquaresTaken() {
// Проверяем, заполнены ли все 9 ячеек игрового поля
return numSquaresTaken() === 9;
}
hasPlayerWonGame()
– kontrollib võidukombinatsioone
function hasPlayerWonGame() {
// Получаем текущее состояние игрового поля
var board = SpreadsheetApp.getActive().getRangeByName("Board").getValues();
// Проверяем выигрышные комбинации (по строкам, столбцам и диагоналям)
if(
(board[0][0] === board[0][1] && board[0][1] === board[0][2] && board[0][0] != "") ||
(board[1][0] === board[1][1] && board[1][1] === board[1][2] && board[1][0] != "") ||
(board[2][0] === board[2][1] && board[2][1] === board[2][2] && board[2][0] != "") ||
(board[0][0] === board[1][0] && board[1][0] === board[2][0] && board[0][0] != "") ||
(board[0][1] === board[1][1] && board[1][1] === board[2][1] && board[0][1] != "") ||
(board[0][2] === board[1][2] && board[1][2] === board[2][2] && board[0][2] != "") ||
(board[0][0] === board[1][1] && board[1][1] === board[2][2] && board[0][0] != "") ||
(board[2][0] === board[1][1] && board[1][1] === board[0][2] && board[2][0] != "")
) {
return true;
}
}
Funktsioon resetGame()
– puhastab mängulaua ja alustab uut mängu esimese mängijaga
function resetGame() {
var spreadsheet = SpreadsheetApp.getActive();
// Очищаем игровое поле
spreadsheet.getRangeByName("Board").setValue("");
// Уведомляем, что первый ход за игроком 1
displayMessage("Player 1's turn");
// Активируем ячейку за пределами игрового поля (чтобы избежать случайного хода)
spreadsheet.getSheetByName("Board").getRange(1,1).activate();
}
Tulemus


Originaal kood
function onSelectionChange(e) {
// Сбрасываем сообщение
displayMessage("");
// Проверяем, правильна ли выбрана ячейка
var isSelectionValid = checkSelection(e.range);
if(isSelectionValid)
play(e.range); // Если да, запускаем игру
}
function checkSelection(range) {
// Проверяем, не вышел ли выбор за пределы игрового поля (ячейки 2-4 по строкам и столбцам)
if(range.rowStart < 2 || range.rowEnd > 4 || range.columnStart < 2 || range.columnEnd > 4) {
error("Select a cell inside the board.");
return false;
}
// Проверяем, выбрана ли только одна ячейка
if(range.rowStart != range.rowEnd || range.columnStart != range.columnEnd) {
error("Select a single cell.");
return false;
}
// Проверяем, пустая ли выбранная ячейка
var cell = SpreadsheetApp
.getActive()
.getSheetByName("Board")
.getRange(range.rowStart, range.columnStart);
var cellValue = cell.getValue();
// Проверяем, что ячейка не пуста (не "X" и не "O")
if(cellValue != "") {
error("Select an empty cell.");
return false;
}
return true;
}
function displayMessage(message) {
// Выводим сообщение в ячейку "Message"
SpreadsheetApp.getActive().getRangeByName("Message").setValue(message);
}
function error(message) {
// Выводим сообщение об ошибке
displayMessage("⚠️ " + message);
}
function play(range) {
var player = whoseTurnIsIt(); // Определяем, чей ход
// Записываем "X" или "O" в выбранную ячейку в зависимости от хода игрока
SpreadsheetApp
.getActive()
.getSheetByName("Board")
.getRange(range.rowStart, range.columnStart)
.setValue(player == "1" ? "X" : "O");
// Проверяем, выиграл ли игрок
if(hasPlayerWonGame()) {
displayMessage("Player " + player + " has won!");
} else if(areAllSquaresTaken()) {
displayMessage("Game is a draw!"); // Проверяем ничью
} else {
displayMessage("Player " + (player == "1" ? "2's " : "1's ") + "turn.");
}
}
function whoseTurnIsIt() {
// Определяем, чей ход: если заполненных ячеек четное количество, то 1-й игрок, иначе 2-й
return numSquaresTaken() % 2 == 0 ? 1 : 2;
}
function areAllSquaresTaken() {
// Проверяем, заполнены ли все 9 ячеек игрового поля
return numSquaresTaken() === 9;
}
function numSquaresTaken() {
// Подсчитываем количество занятых клеток (с "X" или "O")
var board = SpreadsheetApp.getActive().getRangeByName("Board").getValues();
var numChars = 0;
board.forEach(function(row) {
row.forEach(function(col) {
if(col == "X" || col == "O")
numChars++;
});
});
return numChars;
}
function hasPlayerWonGame() {
// Получаем текущее состояние игрового поля
var board = SpreadsheetApp.getActive().getRangeByName("Board").getValues();
// Проверяем выигрышные комбинации (по строкам, столбцам и диагоналям)
if(
(board[0][0] === board[0][1] && board[0][1] === board[0][2] && board[0][0] != "") ||
(board[1][0] === board[1][1] && board[1][1] === board[1][2] && board[1][0] != "") ||
(board[2][0] === board[2][1] && board[2][1] === board[2][2] && board[2][0] != "") ||
(board[0][0] === board[1][0] && board[1][0] === board[2][0] && board[0][0] != "") ||
(board[0][1] === board[1][1] && board[1][1] === board[2][1] && board[0][1] != "") ||
(board[0][2] === board[1][2] && board[1][2] === board[2][2] && board[0][2] != "") ||
(board[0][0] === board[1][1] && board[1][1] === board[2][2] && board[0][0] != "") ||
(board[2][0] === board[1][1] && board[1][1] === board[0][2] && board[2][0] != "")
) {
return true;
}
}
function resetGame() {
var spreadsheet = SpreadsheetApp.getActive();
// Очищаем игровое поле
spreadsheet.getRangeByName("Board").setValue("");
// Уведомляем, что первый ход за игроком 1
displayMessage("Player 1's turn");
// Активируем ячейку за пределами игрового поля (чтобы избежать случайного хода)
spreadsheet.getSheetByName("Board").getRange(1,1).activate();
}
Minu koodis tehtud muudatused
Otsustasin lisada koodile uusi funktsioone, nagu värskendatav skooritabel ja lahtrite esiletõstmine võidu korral, et muuta Tic Tac Toe mängu veelgi põnevamaks ja interaktiivsemaks
Lisame Google Sheetsi tabelit 3×2 ja anname nimed tühjadele lahtristele “Player1Wins”, “Player2Wins” ja “Draw”
Lisame nuppu “Reset score” ja määrame skripti “resetScoreboard”

Lisatud funktsioon highlightWinningCells(cells)
:
- Toetab võidukombinatsiooniga lahtrite esiletõstmist
- Arvutab võidulahtrite koordinaadid, mis asuvad “Board” vahemikus
- Muudab nende tausta roheliseks (#90EE90)
function highlightWinningCells(cells) {
var sheet = SpreadsheetApp.getActive().getSheetByName("Board");
// Получаем диапазон "Board" и его диапазон строк и столбцов
var range = sheet.getRange("Board");
var startRow = range.getRow(); // Начальная строка диапазона
var startCol = range.getColumn(); // Начальный столбец диапазона
// Применяем фоновый цвет только для ячеек внутри диапазона "Board"
cells.forEach(function(cell) {
// Рассчитываем реальные строки и столбцы относительно диапазона Board
var row = startRow + cell[0]; // Начальная строка + индекс строки победы
var col = startCol + cell[1]; // Начальный столбец + индекс столбца победы
// Устанавливаем фон для ячеек
sheet.getRange(row, col).setBackground("#90EE90"); // Зеленый цвет для победных ячеек
});
}
Lisatud funktsioon updateScoreboard(winner)
:
- Suurendab võitnud mängija (Player1Wins või Player2Wins) skoori
- Suurendab viigi skoori (Draws), kui mäng lõppeb viigiga
function updateScoreboard(winner) {
var spreadsheet = SpreadsheetApp.getActive();
// Обновление счетов игроков в зависимости от результата
if (winner == "1") {
var player1Wins = spreadsheet.getRangeByName("Player1Wins").getValue();
spreadsheet.getRangeByName("Player1Wins").setValue(player1Wins + 1);
} else if (winner == "2") {
var player2Wins = spreadsheet.getRangeByName("Player2Wins").getValue();
spreadsheet.getRangeByName("Player2Wins").setValue(player2Wins + 1);
} else if (winner == "draw") {
var draws = spreadsheet.getRangeByName("Draws").getValue();
spreadsheet.getRangeByName("Draws").setValue(draws + 1);
}
}
Lisatud funktsioon resetScoreboard()
:
- Nullib mängijate võidud ja viigid
function resetScoreboard() {
var spreadsheet = SpreadsheetApp.getActive();
// Сброс всех счетчиков (побед 1-го игрока, побед 2-го и ничьих)
spreadsheet.getRangeByName("Player1Wins").setValue(0);
spreadsheet.getRangeByName("Player2Wins").setValue(0);
spreadsheet.getRangeByName("Draws").setValue(0);
}
Täiendatud funktsioon play(range)
:
- Kutsutakse välja
highlightWinningCells(winningCells)
, kui keegi on võitnud - Uuendab skoori, kutsudes välja
updateScoreboard(player)
võiupdateScoreboard("draw")
, sõltuvalt mängu tulemuseks
// Проверяем, выиграл ли игрок
var winningCells = hasPlayerWonGame();
if (winningCells) {
displayMessage("Player " + player + " has won!");
highlightWinningCells(winningCells);
updateScoreboard(player);
} else if (areAllSquaresTaken()) {
displayMessage("Game is a draw!"); // Проверяем ничью
updateScoreboard("draw");
} else {
displayMessage("Player " + (player == "1" ? "2's " : "1's ") + "turn.");
}
}
Täiendatud funktsioon resetGame()
:
- Puhastab mänguvälja (setValue(“”))
- Taastab mänguvälja tausta (setBackground(“#EFEFEF”))
- Uuendab sõnumit mängu alguses
function resetGame() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName("Board");
// Очищаем игровое поле
sheet.getRange("Board").setValue("");
// Восстановить начальный цвет фона всех ячеек в диапазоне Board
sheet.getRange("Board").setBackground("#efefef");
// Уведомляем, что первый ход за игроком 1
displayMessage("Player 1's turn");
// Активируем ячейку за пределами игрового поля (чтобы избежать случайного хода)
sheet.getRange(1, 1).activate();
}
Tulemus


Kood minu muudatusega
function onSelectionChange(e) {
// Сбрасываем сообщение
displayMessage("");
// Проверяем, правильна ли выбрана ячейка
var isSelectionValid = checkSelection(e.range);
if(isSelectionValid)
play(e.range); // Если да, запускаем игру
}
function checkSelection(range) {
// Проверяем, не вышел ли выбор за пределы игрового поля (ячейки 2-4 по строкам и столбцам)
if(range.rowStart < 2 || range.rowEnd > 4 || range.columnStart < 2 || range.columnEnd > 4) {
error("Select a cell inside the board.")
return false;
}
// Проверяем, выбрана ли только одна ячейка
if(range.rowStart != range.rowEnd || range.columnStart != range.columnEnd) {
error("Select a single cell.")
return false;
}
// Проверяем, пустая ли выбранная ячейка
var cell = SpreadsheetApp
.getActive()
.getSheetByName("Board")
.getRange(range.rowStart, range.columnStart);
var cellValue = cell.getValue();
// Проверяем, что ячейка не пуста (не "X" и не "O")
if(cellValue != "" && cellValue !=null) {
error("Select an empty cell.")
return false;
}
return true;
}
function displayMessage(message) {
// Выводим сообщение в ячейку "Message"
SpreadsheetApp.getActive().getRangeByName("Message").setValue(message);
}
function error(message) {
// Выводим сообщение об ошибке
displayMessage("⚠️ " + message);
}
function play(range) {
var player = whoseTurnIsIt(); // Определяем, чей ход
// Записываем "X" или "O" в выбранную ячейку в зависимости от хода игрока
SpreadsheetApp
.getActive()
.getSheetByName("Board")
.getRange(range.rowStart, range.columnStart)
.setValue(player == "1" ? "X" : "O");
// Проверяем, выиграл ли игрок
var winningCells = hasPlayerWonGame();
if (winningCells) {
displayMessage("Player " + player + " has won!");
highlightWinningCells(winningCells);
updateScoreboard(player);
} else if (areAllSquaresTaken()) {
displayMessage("Game is a draw!"); // Проверяем ничью
updateScoreboard("draw");
} else {
displayMessage("Player " + (player == "1" ? "2's " : "1's ") + "turn.");
}
}
function whoseTurnIsIt() {
// Определяем, чей ход: если заполненных ячеек четное количество, то 1-й игрок, иначе 2-й
return numSquaresTaken() % 2 == 0 ? 1 : 2;
}
function areAllSquaresTaken() {
// Проверяем, заполнены ли все 9 ячеек игрового поля
return numSquaresTaken() === 9;
}
function numSquaresTaken() {
// Подсчитываем количество занятых клеток (с "X" или "O")
var board = SpreadsheetApp.getActive().getRangeByName("Board").getValues();
var numChars = 0;
board.forEach(function(row) {
row.forEach(function(col) {
if(col == "X" || col == "O")
numChars++;
});
});
return numChars;
}
function hasPlayerWonGame() {
// Получаем текущее состояние игрового поля
var board = SpreadsheetApp.getActive().getRangeByName("Board").getValues();
// Проверяем выигрышные комбинации (по строкам, столбцам и диагоналям)
for (var i = 0; i < 3; i++) {
// Горизонтальные линии
if (board[i][0] !== "" && board[i][0] === board[i][1] && board[i][1] === board[i][2]) {
return [[i, 0], [i, 1], [i, 2]]; // Возвращаем индексы
}
// Вертикальные линии
if (board[0][i] !== "" && board[0][i] === board[1][i] && board[1][i] === board[2][i]) {
return [[0, i], [1, i], [2, i]]; // Возвращаем индексы
}
}
// Диагонали
if (board[0][0] !== "" && board[0][0] === board[1][1] && board[1][1] === board[2][2]) {
return [[0, 0], [1, 1], [2, 2]]; // Возвращаем индексы
}
if (board[0][2] !== "" && board[0][2] === board[1][1] && board[1][1] === board[2][0]) {
return [[0, 2], [1, 1], [2, 0]]; // Возвращаем индексы
}
return false;
}
function resetGame() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName("Board");
// Очищаем игровое поле
sheet.getRange("Board").setValue("");
// Восстановить начальный цвет фона всех ячеек в диапазоне Board
sheet.getRange("Board").setBackground("#efefef");
// Уведомляем, что первый ход за игроком 1
displayMessage("Player 1's turn");
// Активируем ячейку за пределами игрового поля (чтобы избежать случайного хода)
sheet.getRange(1, 1).activate();
}
function updateScoreboard(winner) {
var spreadsheet = SpreadsheetApp.getActive();
// Обновление счетов игроков в зависимости от результата
if (winner == "1") {
var player1Wins = spreadsheet.getRangeByName("Player1Wins").getValue();
spreadsheet.getRangeByName("Player1Wins").setValue(player1Wins + 1);
} else if (winner == "2") {
var player2Wins = spreadsheet.getRangeByName("Player2Wins").getValue();
spreadsheet.getRangeByName("Player2Wins").setValue(player2Wins + 1);
} else if (winner == "draw") {
var draws = spreadsheet.getRangeByName("Draws").getValue();
spreadsheet.getRangeByName("Draws").setValue(draws + 1);
}
}
function resetScoreboard() {
var spreadsheet = SpreadsheetApp.getActive();
// Сброс всех счетчиков (побед 1-го игрока, побед 2-го и ничьих)
spreadsheet.getRangeByName("Player1Wins").setValue(0);
spreadsheet.getRangeByName("Player2Wins").setValue(0);
spreadsheet.getRangeByName("Draws").setValue(0);
}
function highlightWinningCells(cells) {
var sheet = SpreadsheetApp.getActive().getSheetByName("Board");
// Получаем диапазон "Board" и его диапазон строк и столбцов
var range = sheet.getRange("Board");
var startRow = range.getRow(); // Начальная строка диапазона
var startCol = range.getColumn(); // Начальный столбец диапазона
// Применение фона только для ячеек внутри диапазона "Board"
cells.forEach(function(cell) {
// Рассчитываем реальные строки и столбцы относительно диапазона Board
var row = startRow + cell[0]; // Начальная строка + индекс строки победы
var col = startCol + cell[1]; // Начальный столбец + индекс столбца победы
// Устанавливаем фон для ячеек
sheet.getRange(row, col).setBackground("#90EE90"); // Зеленый цвет
});
}