/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // BoardCell: Represents a single cell in the Tic Tac Toe grid var BoardCell = Container.expand(function () { var self = Container.call(this); // Cell state: 0 = empty, 1 = player (X), 2 = AI (O) self.state = 0; self.row = 0; self.col = 0; // Draw cell background (box) var cellBg = self.attachAsset('cellBg', { anchorX: 0.5, anchorY: 0.5 }); self.cellBg = cellBg; // X and O graphics (hidden by default) var xMark = self.attachAsset('xMark', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); self.xMark = xMark; var oMark = self.attachAsset('oMark', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); self.oMark = oMark; // Highlight overlay (for winning line) var highlight = self.attachAsset('highlight', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); self.highlight = highlight; // Set cell state and update graphics self.setState = function (state) { self.state = state; if (state === 1) { self.xMark.alpha = 1; self.oMark.alpha = 0; } else if (state === 2) { self.xMark.alpha = 0; self.oMark.alpha = 1; } else { self.xMark.alpha = 0; self.oMark.alpha = 0; } }; // Show highlight overlay self.showHighlight = function () { self.highlight.alpha = 0.5; }; // Hide highlight overlay self.hideHighlight = function () { self.highlight.alpha = 0; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Add the dark arcade background // --- Arcade Neon Grid Background --- // --- Game Variables --- // Neon grid background: large dark rectangle, grid lines will be drawn with additional assets below // Neon horizontal grid line (thin, bright cyan) // Neon vertical grid line (thin, bright cyan) var arcadeBg = LK.getAsset('arcadeBg', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChild(arcadeBg); // Add neon grid lines (2 vertical, 2 horizontal to make 3x3 grid) var gridLineThickness = 8; var gridColor = 0x00fff7; var gridLines = []; // Vertical lines (after each column except last) for (var i = 1; i < boardSize; i++) { var vLine = LK.getAsset('neonVLine', { anchorX: 0.5, anchorY: 0, x: boardOriginX + i * cellSize, y: boardOriginY, alpha: 0.7 }); game.addChild(vLine); gridLines.push(vLine); } // Horizontal lines (after each row except last) for (var i = 1; i < boardSize; i++) { var hLine = LK.getAsset('neonHLine', { anchorX: 0, anchorY: 0.5, x: boardOriginX, y: boardOriginY + i * cellSize, alpha: 0.7 }); game.addChild(hLine); gridLines.push(hLine); } // --- Asset Initialization --- // Cell background (light gray box) // X mark (red) // O mark (blue ellipse) // Highlight overlay (yellow, semi-transparent) var boardSize = 3; var cellSize = 480; // px (for 3x3 grid, fits well in 2048x2732) var board = []; // 2D array of BoardCell var boardState = []; // 2D array of 0 (empty), 1 (player), 2 (AI) var currentPlayer = 1; // 1 = player, 2 = AI var gameActive = true; var winningLine = null; // Array of [row, col] for winning cells // UI elements var statusText = null; var playAgainBtn = null; // --- Board Setup --- // Center the board var boardOriginX = Math.floor((2048 - cellSize * boardSize) / 2); var boardOriginY = Math.floor((2732 - cellSize * boardSize) / 2); // Create board cells for (var row = 0; row < boardSize; row++) { board[row] = []; boardState[row] = []; for (var col = 0; col < boardSize; col++) { var cell = new BoardCell(); cell.x = boardOriginX + col * cellSize + cellSize / 2; cell.y = boardOriginY + row * cellSize + cellSize / 2; cell.row = row; cell.col = col; cell.setState(0); cell.hideHighlight(); board[row][col] = cell; boardState[row][col] = 0; game.addChild(cell); // Cell tap handler cell.down = function (cellRef) { return function (x, y, obj) { if (!gameActive) return; if (currentPlayer !== 1) return; if (boardState[cellRef.row][cellRef.col] !== 0) return; playerMove(cellRef.row, cellRef.col); }; }(cell); } } // --- Status Text --- statusText = new Text2('Your turn', { size: 120, fill: 0xFFFFFF }); statusText.anchor.set(0.5, 0); LK.gui.top.addChild(statusText); // --- Play Again Button --- playAgainBtn = new Text2('Play Again', { size: 100, fill: 0xFFFFFF }); playAgainBtn.anchor.set(0.5, 0.5); playAgainBtn.x = 2048 / 2; playAgainBtn.y = 2732 - 350; playAgainBtn.visible = false; playAgainBtn.bg = LK.getAsset('playAgainBg', { width: 600, height: 180, color: 0x2a6edb, anchorX: 0.5, anchorY: 0.5 }); playAgainBtn.bg.y = 0; playAgainBtn.bg.x = 0; playAgainBtn.addChild(playAgainBtn.bg); playAgainBtn.setChildIndex(playAgainBtn.bg, 0); game.addChild(playAgainBtn); // Play Again tap handler playAgainBtn.down = function (x, y, obj) { if (!gameActive) { resetGame(); } }; // --- Game Logic --- function playerMove(row, col) { if (!gameActive) return; if (boardState[row][col] !== 0) return; boardState[row][col] = 1; board[row][col].setState(1); currentPlayer = 2; updateStatus(); checkGameEnd(); if (gameActive) { LK.setTimeout(aiMove, 400); } } function aiMove() { if (!gameActive) return; // Find best move for AI (O) var move = findBestAIMove(); if (move) { boardState[move.row][move.col] = 2; board[move.row][move.col].setState(2); currentPlayer = 1; updateStatus(); checkGameEnd(); } } function updateStatus() { if (!gameActive) return; if (currentPlayer === 1) { statusText.setText('Your turn'); } else { statusText.setText('AI\'s turn'); } } function checkGameEnd() { var result = checkWinner(); if (result.winner) { gameActive = false; highlightWinningLine(result.line); if (result.winner === 1) { statusText.setText('You win!'); } else if (result.winner === 2) { statusText.setText('AI wins!'); } showPlayAgain(); } else if (isBoardFull()) { gameActive = false; statusText.setText('Draw!'); showPlayAgain(); } } function showPlayAgain() { playAgainBtn.visible = true; } function hidePlayAgain() { playAgainBtn.visible = false; } function resetGame() { // Reset board state for (var row = 0; row < boardSize; row++) { for (var col = 0; col < boardSize; col++) { boardState[row][col] = 0; board[row][col].setState(0); board[row][col].hideHighlight(); } } currentPlayer = 1; gameActive = true; winningLine = null; statusText.setText('Your turn'); hidePlayAgain(); } // --- AI Logic --- function findBestAIMove() { // 1. Win if possible for (var row = 0; row < boardSize; row++) { for (var col = 0; col < boardSize; col++) { if (boardState[row][col] === 0) { boardState[row][col] = 2; if (checkWinner().winner === 2) { boardState[row][col] = 0; return { row: row, col: col }; } boardState[row][col] = 0; } } } // 2. Block player win for (var row = 0; row < boardSize; row++) { for (var col = 0; col < boardSize; col++) { if (boardState[row][col] === 0) { boardState[row][col] = 1; if (checkWinner().winner === 1) { boardState[row][col] = 0; return { row: row, col: col }; } boardState[row][col] = 0; } } } // 3. Take center if available if (boardState[1][1] === 0) { return { row: 1, col: 1 }; } // 4. Take a corner if available var corners = [{ row: 0, col: 0 }, { row: 0, col: 2 }, { row: 2, col: 0 }, { row: 2, col: 2 }]; for (var i = 0; i < corners.length; i++) { var c = corners[i]; if (boardState[c.row][c.col] === 0) { return { row: c.row, col: c.col }; } } // 5. Take any side var sides = [{ row: 0, col: 1 }, { row: 1, col: 0 }, { row: 1, col: 2 }, { row: 2, col: 1 }]; for (var i = 0; i < sides.length; i++) { var s = sides[i]; if (boardState[s.row][s.col] === 0) { return { row: s.row, col: s.col }; } } // 6. No moves left return null; } // --- Win/Draw Detection --- function checkWinner() { // Rows, columns, diagonals for (var i = 0; i < boardSize; i++) { // Row if (boardState[i][0] !== 0 && boardState[i][0] === boardState[i][1] && boardState[i][1] === boardState[i][2]) { return { winner: boardState[i][0], line: [[i, 0], [i, 1], [i, 2]] }; } // Column if (boardState[0][i] !== 0 && boardState[0][i] === boardState[1][i] && boardState[1][i] === boardState[2][i]) { return { winner: boardState[0][i], line: [[0, i], [1, i], [2, i]] }; } } // Diagonal \ if (boardState[0][0] !== 0 && boardState[0][0] === boardState[1][1] && boardState[1][1] === boardState[2][2]) { return { winner: boardState[0][0], line: [[0, 0], [1, 1], [2, 2]] }; } // Diagonal / if (boardState[0][2] !== 0 && boardState[0][2] === boardState[1][1] && boardState[1][1] === boardState[2][0]) { return { winner: boardState[0][2], line: [[0, 2], [1, 1], [2, 0]] }; } return { winner: 0, line: null }; } function isBoardFull() { for (var row = 0; row < boardSize; row++) { for (var col = 0; col < boardSize; col++) { if (boardState[row][col] === 0) return false; } } return true; } function highlightWinningLine(line) { if (!line) return; for (var i = 0; i < line.length; i++) { var rc = line[i]; board[rc[0]][rc[1]].showHighlight(); } } // --- Prevent interaction in top-left 100x100 px (no cells are placed there) --- // --- No need for dragging or move handlers --- // --- No music, sound, or pause --- // --- No background beyond white --- // --- No keyboard controls --- // --- No dynamic resizing/orientation code --- // --- No leaderboard, game over, or win popups (handled by LK) --- // --- End of game code ---
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// BoardCell: Represents a single cell in the Tic Tac Toe grid
var BoardCell = Container.expand(function () {
var self = Container.call(this);
// Cell state: 0 = empty, 1 = player (X), 2 = AI (O)
self.state = 0;
self.row = 0;
self.col = 0;
// Draw cell background (box)
var cellBg = self.attachAsset('cellBg', {
anchorX: 0.5,
anchorY: 0.5
});
self.cellBg = cellBg;
// X and O graphics (hidden by default)
var xMark = self.attachAsset('xMark', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
self.xMark = xMark;
var oMark = self.attachAsset('oMark', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
self.oMark = oMark;
// Highlight overlay (for winning line)
var highlight = self.attachAsset('highlight', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
self.highlight = highlight;
// Set cell state and update graphics
self.setState = function (state) {
self.state = state;
if (state === 1) {
self.xMark.alpha = 1;
self.oMark.alpha = 0;
} else if (state === 2) {
self.xMark.alpha = 0;
self.oMark.alpha = 1;
} else {
self.xMark.alpha = 0;
self.oMark.alpha = 0;
}
};
// Show highlight overlay
self.showHighlight = function () {
self.highlight.alpha = 0.5;
};
// Hide highlight overlay
self.hideHighlight = function () {
self.highlight.alpha = 0;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Add the dark arcade background
// --- Arcade Neon Grid Background ---
// --- Game Variables ---
// Neon grid background: large dark rectangle, grid lines will be drawn with additional assets below
// Neon horizontal grid line (thin, bright cyan)
// Neon vertical grid line (thin, bright cyan)
var arcadeBg = LK.getAsset('arcadeBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(arcadeBg);
// Add neon grid lines (2 vertical, 2 horizontal to make 3x3 grid)
var gridLineThickness = 8;
var gridColor = 0x00fff7;
var gridLines = [];
// Vertical lines (after each column except last)
for (var i = 1; i < boardSize; i++) {
var vLine = LK.getAsset('neonVLine', {
anchorX: 0.5,
anchorY: 0,
x: boardOriginX + i * cellSize,
y: boardOriginY,
alpha: 0.7
});
game.addChild(vLine);
gridLines.push(vLine);
}
// Horizontal lines (after each row except last)
for (var i = 1; i < boardSize; i++) {
var hLine = LK.getAsset('neonHLine', {
anchorX: 0,
anchorY: 0.5,
x: boardOriginX,
y: boardOriginY + i * cellSize,
alpha: 0.7
});
game.addChild(hLine);
gridLines.push(hLine);
}
// --- Asset Initialization ---
// Cell background (light gray box)
// X mark (red)
// O mark (blue ellipse)
// Highlight overlay (yellow, semi-transparent)
var boardSize = 3;
var cellSize = 480; // px (for 3x3 grid, fits well in 2048x2732)
var board = []; // 2D array of BoardCell
var boardState = []; // 2D array of 0 (empty), 1 (player), 2 (AI)
var currentPlayer = 1; // 1 = player, 2 = AI
var gameActive = true;
var winningLine = null; // Array of [row, col] for winning cells
// UI elements
var statusText = null;
var playAgainBtn = null;
// --- Board Setup ---
// Center the board
var boardOriginX = Math.floor((2048 - cellSize * boardSize) / 2);
var boardOriginY = Math.floor((2732 - cellSize * boardSize) / 2);
// Create board cells
for (var row = 0; row < boardSize; row++) {
board[row] = [];
boardState[row] = [];
for (var col = 0; col < boardSize; col++) {
var cell = new BoardCell();
cell.x = boardOriginX + col * cellSize + cellSize / 2;
cell.y = boardOriginY + row * cellSize + cellSize / 2;
cell.row = row;
cell.col = col;
cell.setState(0);
cell.hideHighlight();
board[row][col] = cell;
boardState[row][col] = 0;
game.addChild(cell);
// Cell tap handler
cell.down = function (cellRef) {
return function (x, y, obj) {
if (!gameActive) return;
if (currentPlayer !== 1) return;
if (boardState[cellRef.row][cellRef.col] !== 0) return;
playerMove(cellRef.row, cellRef.col);
};
}(cell);
}
}
// --- Status Text ---
statusText = new Text2('Your turn', {
size: 120,
fill: 0xFFFFFF
});
statusText.anchor.set(0.5, 0);
LK.gui.top.addChild(statusText);
// --- Play Again Button ---
playAgainBtn = new Text2('Play Again', {
size: 100,
fill: 0xFFFFFF
});
playAgainBtn.anchor.set(0.5, 0.5);
playAgainBtn.x = 2048 / 2;
playAgainBtn.y = 2732 - 350;
playAgainBtn.visible = false;
playAgainBtn.bg = LK.getAsset('playAgainBg', {
width: 600,
height: 180,
color: 0x2a6edb,
anchorX: 0.5,
anchorY: 0.5
});
playAgainBtn.bg.y = 0;
playAgainBtn.bg.x = 0;
playAgainBtn.addChild(playAgainBtn.bg);
playAgainBtn.setChildIndex(playAgainBtn.bg, 0);
game.addChild(playAgainBtn);
// Play Again tap handler
playAgainBtn.down = function (x, y, obj) {
if (!gameActive) {
resetGame();
}
};
// --- Game Logic ---
function playerMove(row, col) {
if (!gameActive) return;
if (boardState[row][col] !== 0) return;
boardState[row][col] = 1;
board[row][col].setState(1);
currentPlayer = 2;
updateStatus();
checkGameEnd();
if (gameActive) {
LK.setTimeout(aiMove, 400);
}
}
function aiMove() {
if (!gameActive) return;
// Find best move for AI (O)
var move = findBestAIMove();
if (move) {
boardState[move.row][move.col] = 2;
board[move.row][move.col].setState(2);
currentPlayer = 1;
updateStatus();
checkGameEnd();
}
}
function updateStatus() {
if (!gameActive) return;
if (currentPlayer === 1) {
statusText.setText('Your turn');
} else {
statusText.setText('AI\'s turn');
}
}
function checkGameEnd() {
var result = checkWinner();
if (result.winner) {
gameActive = false;
highlightWinningLine(result.line);
if (result.winner === 1) {
statusText.setText('You win!');
} else if (result.winner === 2) {
statusText.setText('AI wins!');
}
showPlayAgain();
} else if (isBoardFull()) {
gameActive = false;
statusText.setText('Draw!');
showPlayAgain();
}
}
function showPlayAgain() {
playAgainBtn.visible = true;
}
function hidePlayAgain() {
playAgainBtn.visible = false;
}
function resetGame() {
// Reset board state
for (var row = 0; row < boardSize; row++) {
for (var col = 0; col < boardSize; col++) {
boardState[row][col] = 0;
board[row][col].setState(0);
board[row][col].hideHighlight();
}
}
currentPlayer = 1;
gameActive = true;
winningLine = null;
statusText.setText('Your turn');
hidePlayAgain();
}
// --- AI Logic ---
function findBestAIMove() {
// 1. Win if possible
for (var row = 0; row < boardSize; row++) {
for (var col = 0; col < boardSize; col++) {
if (boardState[row][col] === 0) {
boardState[row][col] = 2;
if (checkWinner().winner === 2) {
boardState[row][col] = 0;
return {
row: row,
col: col
};
}
boardState[row][col] = 0;
}
}
}
// 2. Block player win
for (var row = 0; row < boardSize; row++) {
for (var col = 0; col < boardSize; col++) {
if (boardState[row][col] === 0) {
boardState[row][col] = 1;
if (checkWinner().winner === 1) {
boardState[row][col] = 0;
return {
row: row,
col: col
};
}
boardState[row][col] = 0;
}
}
}
// 3. Take center if available
if (boardState[1][1] === 0) {
return {
row: 1,
col: 1
};
}
// 4. Take a corner if available
var corners = [{
row: 0,
col: 0
}, {
row: 0,
col: 2
}, {
row: 2,
col: 0
}, {
row: 2,
col: 2
}];
for (var i = 0; i < corners.length; i++) {
var c = corners[i];
if (boardState[c.row][c.col] === 0) {
return {
row: c.row,
col: c.col
};
}
}
// 5. Take any side
var sides = [{
row: 0,
col: 1
}, {
row: 1,
col: 0
}, {
row: 1,
col: 2
}, {
row: 2,
col: 1
}];
for (var i = 0; i < sides.length; i++) {
var s = sides[i];
if (boardState[s.row][s.col] === 0) {
return {
row: s.row,
col: s.col
};
}
}
// 6. No moves left
return null;
}
// --- Win/Draw Detection ---
function checkWinner() {
// Rows, columns, diagonals
for (var i = 0; i < boardSize; i++) {
// Row
if (boardState[i][0] !== 0 && boardState[i][0] === boardState[i][1] && boardState[i][1] === boardState[i][2]) {
return {
winner: boardState[i][0],
line: [[i, 0], [i, 1], [i, 2]]
};
}
// Column
if (boardState[0][i] !== 0 && boardState[0][i] === boardState[1][i] && boardState[1][i] === boardState[2][i]) {
return {
winner: boardState[0][i],
line: [[0, i], [1, i], [2, i]]
};
}
}
// Diagonal \
if (boardState[0][0] !== 0 && boardState[0][0] === boardState[1][1] && boardState[1][1] === boardState[2][2]) {
return {
winner: boardState[0][0],
line: [[0, 0], [1, 1], [2, 2]]
};
}
// Diagonal /
if (boardState[0][2] !== 0 && boardState[0][2] === boardState[1][1] && boardState[1][1] === boardState[2][0]) {
return {
winner: boardState[0][2],
line: [[0, 2], [1, 1], [2, 0]]
};
}
return {
winner: 0,
line: null
};
}
function isBoardFull() {
for (var row = 0; row < boardSize; row++) {
for (var col = 0; col < boardSize; col++) {
if (boardState[row][col] === 0) return false;
}
}
return true;
}
function highlightWinningLine(line) {
if (!line) return;
for (var i = 0; i < line.length; i++) {
var rc = line[i];
board[rc[0]][rc[1]].showHighlight();
}
}
// --- Prevent interaction in top-left 100x100 px (no cells are placed there) ---
// --- No need for dragging or move handlers ---
// --- No music, sound, or pause ---
// --- No background beyond white ---
// --- No keyboard controls ---
// --- No dynamic resizing/orientation code ---
// --- No leaderboard, game over, or win popups (handled by LK) ---
// --- End of game code ---