/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var GridCell = Container.expand(function (row, col) {
var self = Container.call(this);
var cellBg = self.attachAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5
});
self.row = row;
self.col = col;
self.symbol = null;
self.symbolGraphic = null;
self.down = function (x, y, obj) {
if (gameState === 'playing' && self.symbol === null && !waitingForAI) {
self.placeSymbol(playerSymbol);
LK.getSound('placeSymbol').play();
if (checkWin(gameBoard, playerSymbol)) {
gameState = 'playerWin';
showGameResult('You Win!');
LK.getSound('gameWin').play();
return;
}
if (checkDraw(gameBoard)) {
gameState = 'draw';
showGameResult('Draw!');
LK.getSound('gameDraw').play();
return;
}
waitingForAI = true;
LK.setTimeout(function () {
makeAIMove();
waitingForAI = false;
}, 500);
}
};
self.placeSymbol = function (symbol) {
if (self.symbol !== null) {
return;
}
self.symbol = symbol;
gameBoard[self.row][self.col] = symbol;
var assetName = symbol === 'X' ? 'xSymbol' : 'oSymbol';
self.symbolGraphic = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1
});
tween(self.symbolGraphic, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.bounceOut
});
};
return self;
});
var MenuButton = Container.expand(function (text, difficulty) {
var self = Container.call(this);
var buttonBg = self.attachAsset('buttonBackground', {
anchorX: 0.5,
anchorY: 0.5
});
var buttonText = new Text2(text, {
size: 60,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
self.difficulty = difficulty;
self.isHovered = false;
self.down = function (x, y, obj) {
tween(buttonBg, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
currentDifficulty = self.difficulty;
gameState = 'playing';
initializeGame();
};
self.up = function (x, y, obj) {
tween(buttonBg, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
};
return self;
});
var TutorialOverlay = Container.expand(function (message) {
var self = Container.call(this);
var overlay = self.attachAsset('tutorialOverlay', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
var messageText = new Text2(message, {
size: 80,
fill: 0xFFFFFF
});
messageText.anchor.set(0.5, 0.5);
self.addChild(messageText);
self.down = function (x, y, obj) {
self.destroy();
tutorialStep++;
showNextTutorial();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
var gameState = 'menu'; // menu, playing, playerWin, aiWin, draw
var currentDifficulty = 'EASY';
var gameBoard = [];
var gridCells = [];
var playerSymbol = 'X';
var aiSymbol = 'O';
var waitingForAI = false;
var tutorialStep = 0;
var tutorialOverlay = null;
// Menu elements
var menuButtons = [];
var gameGrid = null;
var resultText = null;
var playAgainButton = null;
// Tutorial messages
var tutorialMessages = ["Welcome to Tic-Tac-Toe!\nTap to continue...", "Goal: Get 3 symbols in a row\n(horizontal, vertical, or diagonal)", "You are X, AI is O\nYou go first - tap any cell!", "Watch how the AI responds\nto your moves!", "Good luck! Tap to start playing..."];
function initializeMenu() {
gameState = 'menu';
// Clear existing elements
if (gameGrid) {
gameGrid.destroy();
gameGrid = null;
}
if (resultText) {
resultText.destroy();
resultText = null;
}
if (playAgainButton) {
playAgainButton.destroy();
playAgainButton = null;
}
// Create menu buttons
var difficulties = ['BEGINNER', 'EASY', 'MEDIUM', 'HARD'];
var buttonSpacing = 200;
var startY = 1366 - difficulties.length * buttonSpacing / 2;
for (var i = 0; i < difficulties.length; i++) {
var button = new MenuButton(difficulties[i], difficulties[i]);
button.x = 1024;
button.y = startY + i * buttonSpacing;
menuButtons.push(button);
game.addChild(button);
}
// Title
var titleText = new Text2('Tic-Tac-Toe Master', {
size: 120,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 400;
game.addChild(titleText);
}
function initializeGame() {
// Clear menu
for (var i = 0; i < menuButtons.length; i++) {
menuButtons[i].destroy();
}
menuButtons = [];
// Random symbol assignment
if (Math.random() < 0.5) {
playerSymbol = 'X';
aiSymbol = 'O';
} else {
playerSymbol = 'O';
aiSymbol = 'X';
}
// Initialize game board
gameBoard = [[null, null, null], [null, null, null], [null, null, null]];
// Create grid
createGrid();
// Show tutorial for beginner mode
if (currentDifficulty === 'BEGINNER') {
tutorialStep = 0;
showNextTutorial();
}
}
function createGrid() {
gameGrid = new Container();
game.addChild(gameGrid);
// Center the grid
gameGrid.x = 1024;
gameGrid.y = 1366;
// Create grid background
var gridBg = gameGrid.attachAsset('gridBackground', {
anchorX: 0.5,
anchorY: 0.5
});
// Create cells
gridCells = [];
for (var row = 0; row < 3; row++) {
gridCells[row] = [];
for (var col = 0; col < 3; col++) {
var cell = new GridCell(row, col);
cell.x = (col - 1) * 200;
cell.y = (row - 1) * 200;
gridCells[row][col] = cell;
gameGrid.addChild(cell);
}
}
}
function showNextTutorial() {
if (currentDifficulty !== 'BEGINNER' || tutorialStep >= tutorialMessages.length) {
return;
}
tutorialOverlay = new TutorialOverlay(tutorialMessages[tutorialStep]);
tutorialOverlay.x = 1024;
tutorialOverlay.y = 1366;
game.addChild(tutorialOverlay);
}
function makeAIMove() {
var availableMoves = [];
// Find available moves
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
if (gameBoard[row][col] === null) {
availableMoves.push({
row: row,
col: col
});
}
}
}
if (availableMoves.length === 0) {
return;
}
var move = null;
switch (currentDifficulty) {
case 'BEGINNER':
case 'EASY':
// Random move
move = availableMoves[Math.floor(Math.random() * availableMoves.length)];
break;
case 'MEDIUM':
// Try to win, then block, then random
move = findWinningMove(gameBoard, aiSymbol) || findWinningMove(gameBoard, playerSymbol) || availableMoves[Math.floor(Math.random() * availableMoves.length)];
break;
case 'HARD':
// Strategic AI
move = getBestMove(gameBoard, aiSymbol);
break;
}
if (move) {
gridCells[move.row][move.col].placeSymbol(aiSymbol);
if (checkWin(gameBoard, aiSymbol)) {
gameState = 'aiWin';
showGameResult('AI Wins!');
LK.getSound('gameLose').play();
} else if (checkDraw(gameBoard)) {
gameState = 'draw';
showGameResult('Draw!');
LK.getSound('gameDraw').play();
}
}
}
function findWinningMove(board, symbol) {
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
if (board[row][col] === null) {
board[row][col] = symbol;
if (checkWin(board, symbol)) {
board[row][col] = null;
return {
row: row,
col: col
};
}
board[row][col] = null;
}
}
}
return null;
}
function getBestMove(board, symbol) {
var bestScore = -Infinity;
var bestMove = null;
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
if (board[row][col] === null) {
board[row][col] = symbol;
var score = minimax(board, 0, false, symbol);
board[row][col] = null;
if (score > bestScore) {
bestScore = score;
bestMove = {
row: row,
col: col
};
}
}
}
}
return bestMove;
}
function minimax(board, depth, isMaximizing, aiSym) {
var playerSym = aiSym === 'X' ? 'O' : 'X';
if (checkWin(board, aiSym)) {
return 10 - depth;
}
if (checkWin(board, playerSym)) {
return depth - 10;
}
if (checkDraw(board)) {
return 0;
}
if (isMaximizing) {
var bestScore = -Infinity;
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
if (board[row][col] === null) {
board[row][col] = aiSym;
var score = minimax(board, depth + 1, false, aiSym);
board[row][col] = null;
bestScore = Math.max(score, bestScore);
}
}
}
return bestScore;
} else {
var bestScore = Infinity;
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
if (board[row][col] === null) {
board[row][col] = playerSym;
var score = minimax(board, depth + 1, true, aiSym);
board[row][col] = null;
bestScore = Math.min(score, bestScore);
}
}
}
return bestScore;
}
}
function checkWin(board, symbol) {
// Check rows
for (var row = 0; row < 3; row++) {
if (board[row][0] === symbol && board[row][1] === symbol && board[row][2] === symbol) {
return true;
}
}
// Check columns
for (var col = 0; col < 3; col++) {
if (board[0][col] === symbol && board[1][col] === symbol && board[2][col] === symbol) {
return true;
}
}
// Check diagonals
if (board[0][0] === symbol && board[1][1] === symbol && board[2][2] === symbol) {
return true;
}
if (board[0][2] === symbol && board[1][1] === symbol && board[2][0] === symbol) {
return true;
}
return false;
}
function checkDraw(board) {
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
if (board[row][col] === null) {
return false;
}
}
}
return true;
}
function showGameResult(message) {
resultText = new Text2(message, {
size: 100,
fill: 0xFFFFFF
});
resultText.anchor.set(0.5, 0.5);
resultText.x = 1024;
resultText.y = 800;
game.addChild(resultText);
// Play again button
playAgainButton = new MenuButton('Play Again', 'playAgain');
playAgainButton.x = 1024;
playAgainButton.y = 1000;
playAgainButton.down = function () {
initializeMenu();
};
game.addChild(playAgainButton);
}
// Initialize menu on start
initializeMenu();
game.update = function () {
// Main game loop - most logic handled in event handlers
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var GridCell = Container.expand(function (row, col) {
var self = Container.call(this);
var cellBg = self.attachAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5
});
self.row = row;
self.col = col;
self.symbol = null;
self.symbolGraphic = null;
self.down = function (x, y, obj) {
if (gameState === 'playing' && self.symbol === null && !waitingForAI) {
self.placeSymbol(playerSymbol);
LK.getSound('placeSymbol').play();
if (checkWin(gameBoard, playerSymbol)) {
gameState = 'playerWin';
showGameResult('You Win!');
LK.getSound('gameWin').play();
return;
}
if (checkDraw(gameBoard)) {
gameState = 'draw';
showGameResult('Draw!');
LK.getSound('gameDraw').play();
return;
}
waitingForAI = true;
LK.setTimeout(function () {
makeAIMove();
waitingForAI = false;
}, 500);
}
};
self.placeSymbol = function (symbol) {
if (self.symbol !== null) {
return;
}
self.symbol = symbol;
gameBoard[self.row][self.col] = symbol;
var assetName = symbol === 'X' ? 'xSymbol' : 'oSymbol';
self.symbolGraphic = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1
});
tween(self.symbolGraphic, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.bounceOut
});
};
return self;
});
var MenuButton = Container.expand(function (text, difficulty) {
var self = Container.call(this);
var buttonBg = self.attachAsset('buttonBackground', {
anchorX: 0.5,
anchorY: 0.5
});
var buttonText = new Text2(text, {
size: 60,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
self.difficulty = difficulty;
self.isHovered = false;
self.down = function (x, y, obj) {
tween(buttonBg, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
currentDifficulty = self.difficulty;
gameState = 'playing';
initializeGame();
};
self.up = function (x, y, obj) {
tween(buttonBg, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
};
return self;
});
var TutorialOverlay = Container.expand(function (message) {
var self = Container.call(this);
var overlay = self.attachAsset('tutorialOverlay', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
var messageText = new Text2(message, {
size: 80,
fill: 0xFFFFFF
});
messageText.anchor.set(0.5, 0.5);
self.addChild(messageText);
self.down = function (x, y, obj) {
self.destroy();
tutorialStep++;
showNextTutorial();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
var gameState = 'menu'; // menu, playing, playerWin, aiWin, draw
var currentDifficulty = 'EASY';
var gameBoard = [];
var gridCells = [];
var playerSymbol = 'X';
var aiSymbol = 'O';
var waitingForAI = false;
var tutorialStep = 0;
var tutorialOverlay = null;
// Menu elements
var menuButtons = [];
var gameGrid = null;
var resultText = null;
var playAgainButton = null;
// Tutorial messages
var tutorialMessages = ["Welcome to Tic-Tac-Toe!\nTap to continue...", "Goal: Get 3 symbols in a row\n(horizontal, vertical, or diagonal)", "You are X, AI is O\nYou go first - tap any cell!", "Watch how the AI responds\nto your moves!", "Good luck! Tap to start playing..."];
function initializeMenu() {
gameState = 'menu';
// Clear existing elements
if (gameGrid) {
gameGrid.destroy();
gameGrid = null;
}
if (resultText) {
resultText.destroy();
resultText = null;
}
if (playAgainButton) {
playAgainButton.destroy();
playAgainButton = null;
}
// Create menu buttons
var difficulties = ['BEGINNER', 'EASY', 'MEDIUM', 'HARD'];
var buttonSpacing = 200;
var startY = 1366 - difficulties.length * buttonSpacing / 2;
for (var i = 0; i < difficulties.length; i++) {
var button = new MenuButton(difficulties[i], difficulties[i]);
button.x = 1024;
button.y = startY + i * buttonSpacing;
menuButtons.push(button);
game.addChild(button);
}
// Title
var titleText = new Text2('Tic-Tac-Toe Master', {
size: 120,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 400;
game.addChild(titleText);
}
function initializeGame() {
// Clear menu
for (var i = 0; i < menuButtons.length; i++) {
menuButtons[i].destroy();
}
menuButtons = [];
// Random symbol assignment
if (Math.random() < 0.5) {
playerSymbol = 'X';
aiSymbol = 'O';
} else {
playerSymbol = 'O';
aiSymbol = 'X';
}
// Initialize game board
gameBoard = [[null, null, null], [null, null, null], [null, null, null]];
// Create grid
createGrid();
// Show tutorial for beginner mode
if (currentDifficulty === 'BEGINNER') {
tutorialStep = 0;
showNextTutorial();
}
}
function createGrid() {
gameGrid = new Container();
game.addChild(gameGrid);
// Center the grid
gameGrid.x = 1024;
gameGrid.y = 1366;
// Create grid background
var gridBg = gameGrid.attachAsset('gridBackground', {
anchorX: 0.5,
anchorY: 0.5
});
// Create cells
gridCells = [];
for (var row = 0; row < 3; row++) {
gridCells[row] = [];
for (var col = 0; col < 3; col++) {
var cell = new GridCell(row, col);
cell.x = (col - 1) * 200;
cell.y = (row - 1) * 200;
gridCells[row][col] = cell;
gameGrid.addChild(cell);
}
}
}
function showNextTutorial() {
if (currentDifficulty !== 'BEGINNER' || tutorialStep >= tutorialMessages.length) {
return;
}
tutorialOverlay = new TutorialOverlay(tutorialMessages[tutorialStep]);
tutorialOverlay.x = 1024;
tutorialOverlay.y = 1366;
game.addChild(tutorialOverlay);
}
function makeAIMove() {
var availableMoves = [];
// Find available moves
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
if (gameBoard[row][col] === null) {
availableMoves.push({
row: row,
col: col
});
}
}
}
if (availableMoves.length === 0) {
return;
}
var move = null;
switch (currentDifficulty) {
case 'BEGINNER':
case 'EASY':
// Random move
move = availableMoves[Math.floor(Math.random() * availableMoves.length)];
break;
case 'MEDIUM':
// Try to win, then block, then random
move = findWinningMove(gameBoard, aiSymbol) || findWinningMove(gameBoard, playerSymbol) || availableMoves[Math.floor(Math.random() * availableMoves.length)];
break;
case 'HARD':
// Strategic AI
move = getBestMove(gameBoard, aiSymbol);
break;
}
if (move) {
gridCells[move.row][move.col].placeSymbol(aiSymbol);
if (checkWin(gameBoard, aiSymbol)) {
gameState = 'aiWin';
showGameResult('AI Wins!');
LK.getSound('gameLose').play();
} else if (checkDraw(gameBoard)) {
gameState = 'draw';
showGameResult('Draw!');
LK.getSound('gameDraw').play();
}
}
}
function findWinningMove(board, symbol) {
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
if (board[row][col] === null) {
board[row][col] = symbol;
if (checkWin(board, symbol)) {
board[row][col] = null;
return {
row: row,
col: col
};
}
board[row][col] = null;
}
}
}
return null;
}
function getBestMove(board, symbol) {
var bestScore = -Infinity;
var bestMove = null;
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
if (board[row][col] === null) {
board[row][col] = symbol;
var score = minimax(board, 0, false, symbol);
board[row][col] = null;
if (score > bestScore) {
bestScore = score;
bestMove = {
row: row,
col: col
};
}
}
}
}
return bestMove;
}
function minimax(board, depth, isMaximizing, aiSym) {
var playerSym = aiSym === 'X' ? 'O' : 'X';
if (checkWin(board, aiSym)) {
return 10 - depth;
}
if (checkWin(board, playerSym)) {
return depth - 10;
}
if (checkDraw(board)) {
return 0;
}
if (isMaximizing) {
var bestScore = -Infinity;
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
if (board[row][col] === null) {
board[row][col] = aiSym;
var score = minimax(board, depth + 1, false, aiSym);
board[row][col] = null;
bestScore = Math.max(score, bestScore);
}
}
}
return bestScore;
} else {
var bestScore = Infinity;
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
if (board[row][col] === null) {
board[row][col] = playerSym;
var score = minimax(board, depth + 1, true, aiSym);
board[row][col] = null;
bestScore = Math.min(score, bestScore);
}
}
}
return bestScore;
}
}
function checkWin(board, symbol) {
// Check rows
for (var row = 0; row < 3; row++) {
if (board[row][0] === symbol && board[row][1] === symbol && board[row][2] === symbol) {
return true;
}
}
// Check columns
for (var col = 0; col < 3; col++) {
if (board[0][col] === symbol && board[1][col] === symbol && board[2][col] === symbol) {
return true;
}
}
// Check diagonals
if (board[0][0] === symbol && board[1][1] === symbol && board[2][2] === symbol) {
return true;
}
if (board[0][2] === symbol && board[1][1] === symbol && board[2][0] === symbol) {
return true;
}
return false;
}
function checkDraw(board) {
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
if (board[row][col] === null) {
return false;
}
}
}
return true;
}
function showGameResult(message) {
resultText = new Text2(message, {
size: 100,
fill: 0xFFFFFF
});
resultText.anchor.set(0.5, 0.5);
resultText.x = 1024;
resultText.y = 800;
game.addChild(resultText);
// Play again button
playAgainButton = new MenuButton('Play Again', 'playAgain');
playAgainButton.x = 1024;
playAgainButton.y = 1000;
playAgainButton.down = function () {
initializeMenu();
};
game.addChild(playAgainButton);
}
// Initialize menu on start
initializeMenu();
game.update = function () {
// Main game loop - most logic handled in event handlers
};