/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // CountryMarker: Player, AI, or Country 3 marker for claiming territory var CountryMarker = Container.expand(function () { var self = Container.call(this); // 1 = player, 2 = AI, 3 = Country 3 (green) self.owner = 1; var markerAsset = null; self.setOwner = function (owner) { self.owner = owner; self.removeChildren(); if (self.owner === 1) { markerAsset = self.attachAsset('playerMarker', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.owner === 2) { markerAsset = self.attachAsset('aiMarker', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.owner === 3) { markerAsset = self.attachAsset('markerCountry3', { anchorX: 0.5, anchorY: 0.5 }); } }; // For AI, target cell to move towards self.targetCell = null; return self; }); // GridCell: Represents a single region on the map var GridCell = Container.expand(function () { var self = Container.call(this); // State: 0 = neutral, 1 = player, 2 = AI self.state = 0; self.row = 0; self.col = 0; // Attach neutral asset by default var cellAsset = self.attachAsset('cellNeutral', { anchorX: 0.5, anchorY: 0.5 }); // Update cell appearance based on state self.setState = function (newState) { self.state = newState; // Remove previous asset self.removeChildren(); if (self.state === 0) { cellAsset = self.attachAsset('cellNeutral', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.state === 1) { cellAsset = self.attachAsset('cellPlayer', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.state === 2) { cellAsset = self.attachAsset('cellAI', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.state === 3) { cellAsset = self.attachAsset('cellCountry3', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.state === 4) { cellAsset = self.attachAsset('cellCountry4', { anchorX: 0.5, anchorY: 0.5 }); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ // No title, no description // Always backgroundColor is black backgroundColor: 0x000000 }); /**** * Game Code ****/ // Grid settings // Country markers (player and AI) // Grid cell states // Additional colors for more countries var GRID_ROWS = 9; var GRID_COLS = 7; var CELL_SIZE = 180; var GRID_OFFSET_X = (2048 - GRID_COLS * CELL_SIZE) / 2; var GRID_OFFSET_Y = 200; // Game state var grid = []; var playerMarker = null; var aiMarker = null; var playerCells = 0; var aiCells = 0; var neutralCells = 0; var country3Cells = 0; var dragMarker = null; var lastPlayerCell = null; var lastAIUpdateTick = 0; var aiMoveInterval = 60; // AI moves every 60 ticks (~1s) var gameEnded = false; // Score display var scoreTxt = new Text2('0 - 0 - 0', { size: 110, fill: "#222" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Player money var playerMoney = 5; var moneyTxt = new Text2('💰 ' + playerMoney, { size: 90, fill: 0x2ECC40 }); moneyTxt.anchor.set(1, 0); // anchor right/top LK.gui.topRight.addChild(moneyTxt); moneyTxt.x = 0; // right edge of gui.topRight moneyTxt.y = 0; // top edge of gui.topRight // Helper: Get cell at grid position function getCell(row, col) { if (row < 0 || row >= GRID_ROWS || col < 0 || col >= GRID_COLS) { return null; } return grid[row][col]; // Green (country 3) AI move if (!gameEnded && LK.ticks - lastCountry3UpdateTick > country3MoveInterval) { lastCountry3UpdateTick = LK.ticks; var greenTarget = country3ChooseTarget(); if (greenTarget) { // Animate green marker to target tween(country3Marker, { x: greenTarget.x, y: greenTarget.y }, { duration: 200, easing: tween.easeInOut, onFinish: function onFinish() { // Claim cell if (greenTarget.state === 1 || greenTarget.state === 2) { // Player or AI loses cell greenTarget.setState(3); // If player marker is here, snap it back to last owned cell var dx = playerMarker.x - greenTarget.x; var dy = playerMarker.y - greenTarget.y; if (Math.sqrt(dx * dx + dy * dy) < 10) { // Find any player cell to snap to var found = false; for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { if (grid[r][c].state === 1) { lastPlayerCell = grid[r][c]; tween(playerMarker, { x: lastPlayerCell.x, y: lastPlayerCell.y }, { duration: 120, easing: tween.easeIn }); found = true; break; } } if (found) { break; } } } } else { greenTarget.setState(3); } lastCountry3Cell = greenTarget; countCells(); updateScore(); checkEndGame(); } }); } } } ; // Helper: Get cell at (x, y) game coordinates function getCellAtPosition(x, y) { var col = Math.floor((x - GRID_OFFSET_X) / CELL_SIZE); var row = Math.floor((y - GRID_OFFSET_Y) / CELL_SIZE); return getCell(row, col); } // Helper: Get adjacent cells (up, down, left, right) function getAdjacentCells(cell) { var adj = []; var dirs = [{ dr: -1, dc: 0 }, { dr: 1, dc: 0 }, { dr: 0, dc: -1 }, { dr: 0, dc: 1 }]; for (var i = 0; i < dirs.length; i++) { var ncell = getCell(cell.row + dirs[i].dr, cell.col + dirs[i].dc); if (ncell) { adj.push(ncell); } } return adj; } // Helper: Count cells by state function countCells() { playerCells = 0; aiCells = 0; neutralCells = 0; country3Cells = 0; for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { var cell = grid[r][c]; if (cell.state === 0) { neutralCells++; } else if (cell.state === 1) { playerCells++; } else if (cell.state === 2) { aiCells++; } else if (cell.state === 3) { country3Cells++; } } } } // Helper: Update score display function updateScore() { scoreTxt.setText(playerCells + " - " + aiCells + " - " + country3Cells); // Win if player has 32 or more cells if (!gameEnded && playerCells >= 32) { gameEnded = true; LK.showYouWin(); } } // Helper: Check win/lose function checkEndGame() { if (gameEnded) { return; } var total = GRID_ROWS * GRID_COLS; if (playerCells > total / 2) { gameEnded = true; LK.showYouWin(); } else if (aiCells > total / 2 || playerCells === 0) { gameEnded = true; LK.showGameOver(); } } // Initialize grid grid = []; for (var r = 0; r < GRID_ROWS; r++) { var rowArr = []; for (var c = 0; c < GRID_COLS; c++) { var cell = new GridCell(); cell.row = r; cell.col = c; cell.x = GRID_OFFSET_X + c * CELL_SIZE + CELL_SIZE / 2; cell.y = GRID_OFFSET_Y + r * CELL_SIZE + CELL_SIZE / 2; cell.setState(0); game.addChild(cell); rowArr.push(cell); } grid.push(rowArr); } // Set initial player, AI, and Country 3 (green) territory (center cells) var playerStart = getCell(Math.floor(GRID_ROWS / 2), 1); playerStart.setState(1); var aiStart = getCell(Math.floor(GRID_ROWS / 2), GRID_COLS - 2); aiStart.setState(2); var country3Start = getCell(Math.floor(GRID_ROWS / 2), Math.floor(GRID_COLS / 2)); country3Start.setState(3); // Place player marker playerMarker = new CountryMarker(); playerMarker.setOwner(1); playerMarker.x = playerStart.x; playerMarker.y = playerStart.y; game.addChild(playerMarker); lastPlayerCell = playerStart; // Place AI marker aiMarker = new CountryMarker(); aiMarker.setOwner(2); aiMarker.x = aiStart.x; aiMarker.y = aiStart.y; game.addChild(aiMarker); // Place Country 3 (green) marker (now with AI movement/attack) country3Marker = new CountryMarker(); country3Marker.setOwner(3); country3Marker.x = country3Start.x; country3Marker.y = country3Start.y; game.addChild(country3Marker); lastCountry3Cell = country3Start; // Country 3 (green) AI variables var lastCountry3UpdateTick = 0; var country3MoveInterval = 60; // green AI moves every 60 ticks (~1s) // Initial cell counts countCells(); updateScore(); // Dragging logic for player marker game.down = function (x, y, obj) { // Only allow drag if touch/click is on player marker var dx = x - playerMarker.x; var dy = y - playerMarker.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 80) { dragMarker = playerMarker; } }; game.up = function (x, y, obj) { if (dragMarker === playerMarker) { // Snap to nearest cell var cell = getCellAtPosition(playerMarker.x, playerMarker.y); if (cell && (cell.state === 0 || cell.state === 2 || cell.state === 3)) { // Allow move to any cell adjacent to ANY player cell var isAdj = false; for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { if (grid[r][c].state === 1) { var adj = getAdjacentCells(grid[r][c]); for (var i = 0; i < adj.length; i++) { if (adj[i] === cell) { isAdj = true; break; } } if (isAdj) { break; } } } if (isAdj) { break; } } if (isAdj) { // Only allow attack if player has money if (playerMoney > 0) { playerMoney--; moneyTxt.setText('💰 ' + playerMoney); // Claim cell cell.setState(1); lastPlayerCell = cell; // Animate marker to cell center tween(playerMarker, { x: cell.x, y: cell.y }, { duration: 180, easing: tween.easeOut }); countCells(); updateScore(); checkEndGame(); } else { // Not enough money, snap back tween(playerMarker, { x: lastPlayerCell.x, y: lastPlayerCell.y }, { duration: 120, easing: tween.easeIn }); } } else { // Not adjacent, snap back tween(playerMarker, { x: lastPlayerCell.x, y: lastPlayerCell.y }, { duration: 120, easing: tween.easeIn }); } } else { // Not a valid cell, snap back tween(playerMarker, { x: lastPlayerCell.x, y: lastPlayerCell.y }, { duration: 120, easing: tween.easeIn }); } } dragMarker = null; }; game.move = function (x, y, obj) { if (dragMarker === playerMarker) { // Restrict movement to grid area var px = Math.max(GRID_OFFSET_X + 60, Math.min(x, GRID_OFFSET_X + GRID_COLS * CELL_SIZE - 60)); var py = Math.max(GRID_OFFSET_Y + 60, Math.min(y, GRID_OFFSET_Y + GRID_ROWS * CELL_SIZE - 60)); playerMarker.x = px; playerMarker.y = py; } }; // AI logic: smarter targeting: prioritize attacking player cells with most adjacent AI cells, fallback to neutral, then green (country 3) function aiChooseTarget() { // Find all AI cells var aiCellsArr = []; for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { if (grid[r][c].state === 2) { aiCellsArr.push(grid[r][c]); } } } // For each AI cell, check adjacent for neutral, player, or green cells var playerCandidates = []; var neutralCandidates = []; var greenCandidates = []; for (var i = 0; i < aiCellsArr.length; i++) { var adj = getAdjacentCells(aiCellsArr[i]); for (var j = 0; j < adj.length; j++) { var cell = adj[j]; if (cell.state === 1) { // Count how many AI cells are adjacent to this player cell var adjToPlayer = getAdjacentCells(cell); var aiAdjCount = 0; for (var k = 0; k < adjToPlayer.length; k++) { if (adjToPlayer[k].state === 2) { aiAdjCount++; } } playerCandidates.push({ cell: cell, aiAdjCount: aiAdjCount }); } else if (cell.state === 0) { neutralCandidates.push(cell); } else if (cell.state === 3) { greenCandidates.push(cell); } } } // Prefer player cells with most adjacent AI cells (easier to defend/expand) if (playerCandidates.length > 0) { // Sort descending by aiAdjCount playerCandidates.sort(function (a, b) { return b.aiAdjCount - a.aiAdjCount; }); // Pick one of the best (if multiple have same max) var maxAdj = playerCandidates[0].aiAdjCount; var best = []; for (var i = 0; i < playerCandidates.length; i++) { if (playerCandidates[i].aiAdjCount === maxAdj) { best.push(playerCandidates[i].cell); } } var idx = Math.floor(Math.random() * best.length); return best[idx]; } // Otherwise, prefer neutral cells if (neutralCandidates.length > 0) { var idx = Math.floor(Math.random() * neutralCandidates.length); return neutralCandidates[idx]; } // Otherwise, attack green (country 3) if (greenCandidates.length > 0) { var idx = Math.floor(Math.random() * greenCandidates.length); return greenCandidates[idx]; } return null; } // Green (country 3) AI logic: smarter targeting: prioritize attacking player/AI cells with most adjacent green cells, fallback to neutral function country3ChooseTarget() { // Find all green cells var greenCellsArr = []; for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { if (grid[r][c].state === 3) { greenCellsArr.push(grid[r][c]); } } } // For each green cell, check adjacent for neutral, player, or AI cells (never yellow) var playerCandidates = []; var aiCandidates = []; var neutralCandidates = []; for (var i = 0; i < greenCellsArr.length; i++) { var adj = getAdjacentCells(greenCellsArr[i]); for (var j = 0; j < adj.length; j++) { var cell = adj[j]; if (cell.state === 1) { // Count how many green cells are adjacent to this player cell var adjToPlayer = getAdjacentCells(cell); var greenAdjCount = 0; for (var k = 0; k < adjToPlayer.length; k++) { if (adjToPlayer[k].state === 3) { greenAdjCount++; } } playerCandidates.push({ cell: cell, greenAdjCount: greenAdjCount }); } else if (cell.state === 2) { // Count how many green cells are adjacent to this AI cell var adjToAI = getAdjacentCells(cell); var greenAdjCount = 0; for (var k = 0; k < adjToAI.length; k++) { if (adjToAI[k].state === 3) { greenAdjCount++; } } aiCandidates.push({ cell: cell, greenAdjCount: greenAdjCount }); } else if (cell.state === 0) { neutralCandidates.push(cell); } } } // Prefer player cells with most adjacent green cells if (playerCandidates.length > 0) { playerCandidates.sort(function (a, b) { return b.greenAdjCount - a.greenAdjCount; }); var maxAdj = playerCandidates[0].greenAdjCount; var best = []; for (var i = 0; i < playerCandidates.length; i++) { if (playerCandidates[i].greenAdjCount === maxAdj) { best.push(playerCandidates[i].cell); } } var idx = Math.floor(Math.random() * best.length); return best[idx]; } // Otherwise, prefer AI cells with most adjacent green cells if (aiCandidates.length > 0) { aiCandidates.sort(function (a, b) { return b.greenAdjCount - a.greenAdjCount; }); var maxAdj = aiCandidates[0].greenAdjCount; var best = []; for (var i = 0; i < aiCandidates.length; i++) { if (aiCandidates[i].greenAdjCount === maxAdj) { best.push(aiCandidates[i].cell); } } var idx = Math.floor(Math.random() * best.length); return best[idx]; } // Otherwise, prefer neutral cells if (neutralCandidates.length > 0) { var idx = Math.floor(Math.random() * neutralCandidates.length); return neutralCandidates[idx]; } return null; } // Main game update loop game.update = function () { if (gameEnded) { return; } // Give player money for each cell owned at the start of their turn (max 10) if (LK.ticks % 60 === 0 && !gameEnded) { var newMoney = 0; for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { if (grid[r][c].state === 1) { newMoney++; } } } playerMoney += newMoney; if (playerMoney > 100) { playerMoney = 10; } moneyTxt.setText('💰 ' + playerMoney); } // AI move if (LK.ticks - lastAIUpdateTick > aiMoveInterval) { lastAIUpdateTick = LK.ticks; var target = aiChooseTarget(); if (target) { // Animate AI marker to target tween(aiMarker, { x: target.x, y: target.y }, { duration: 200, easing: tween.easeInOut, onFinish: function onFinish() { // Claim cell if (target.state === 1) { // Player loses cell target.setState(2); // If player marker is here, snap it back to last owned cell var dx = playerMarker.x - target.x; var dy = playerMarker.y - target.y; if (Math.sqrt(dx * dx + dy * dy) < 10) { // Find any player cell to snap to var found = false; for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { if (grid[r][c].state === 1) { lastPlayerCell = grid[r][c]; tween(playerMarker, { x: lastPlayerCell.x, y: lastPlayerCell.y }, { duration: 120, easing: tween.easeIn }); found = true; break; } } if (found) { break; } } } } else { target.setState(2); } countCells(); updateScore(); checkEndGame(); } }); } } // Green (country 3) AI move if (!gameEnded && LK.ticks - lastCountry3UpdateTick > country3MoveInterval) { lastCountry3UpdateTick = LK.ticks; var greenTarget = country3ChooseTarget(); if (greenTarget) { // Animate green marker to target tween(country3Marker, { x: greenTarget.x, y: greenTarget.y }, { duration: 200, easing: tween.easeInOut, onFinish: function onFinish() { // Claim cell if (greenTarget.state === 1 || greenTarget.state === 2) { // Player or AI loses cell greenTarget.setState(3); // If player marker is here, snap it back to last owned cell var dx = playerMarker.x - greenTarget.x; var dy = playerMarker.y - greenTarget.y; if (Math.sqrt(dx * dx + dy * dy) < 10) { // Find any player cell to snap to var found = false; for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { if (grid[r][c].state === 1) { lastPlayerCell = grid[r][c]; tween(playerMarker, { x: lastPlayerCell.x, y: lastPlayerCell.y }, { duration: 120, easing: tween.easeIn }); found = true; break; } } if (found) { break; } } } } else { greenTarget.setState(3); } lastCountry3Cell = greenTarget; countCells(); updateScore(); checkEndGame(); } }); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// CountryMarker: Player, AI, or Country 3 marker for claiming territory
var CountryMarker = Container.expand(function () {
var self = Container.call(this);
// 1 = player, 2 = AI, 3 = Country 3 (green)
self.owner = 1;
var markerAsset = null;
self.setOwner = function (owner) {
self.owner = owner;
self.removeChildren();
if (self.owner === 1) {
markerAsset = self.attachAsset('playerMarker', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.owner === 2) {
markerAsset = self.attachAsset('aiMarker', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.owner === 3) {
markerAsset = self.attachAsset('markerCountry3', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
// For AI, target cell to move towards
self.targetCell = null;
return self;
});
// GridCell: Represents a single region on the map
var GridCell = Container.expand(function () {
var self = Container.call(this);
// State: 0 = neutral, 1 = player, 2 = AI
self.state = 0;
self.row = 0;
self.col = 0;
// Attach neutral asset by default
var cellAsset = self.attachAsset('cellNeutral', {
anchorX: 0.5,
anchorY: 0.5
});
// Update cell appearance based on state
self.setState = function (newState) {
self.state = newState;
// Remove previous asset
self.removeChildren();
if (self.state === 0) {
cellAsset = self.attachAsset('cellNeutral', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.state === 1) {
cellAsset = self.attachAsset('cellPlayer', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.state === 2) {
cellAsset = self.attachAsset('cellAI', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.state === 3) {
cellAsset = self.attachAsset('cellCountry3', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.state === 4) {
cellAsset = self.attachAsset('cellCountry4', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
// No title, no description
// Always backgroundColor is black
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Grid settings
// Country markers (player and AI)
// Grid cell states
// Additional colors for more countries
var GRID_ROWS = 9;
var GRID_COLS = 7;
var CELL_SIZE = 180;
var GRID_OFFSET_X = (2048 - GRID_COLS * CELL_SIZE) / 2;
var GRID_OFFSET_Y = 200;
// Game state
var grid = [];
var playerMarker = null;
var aiMarker = null;
var playerCells = 0;
var aiCells = 0;
var neutralCells = 0;
var country3Cells = 0;
var dragMarker = null;
var lastPlayerCell = null;
var lastAIUpdateTick = 0;
var aiMoveInterval = 60; // AI moves every 60 ticks (~1s)
var gameEnded = false;
// Score display
var scoreTxt = new Text2('0 - 0 - 0', {
size: 110,
fill: "#222"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Player money
var playerMoney = 5;
var moneyTxt = new Text2('💰 ' + playerMoney, {
size: 90,
fill: 0x2ECC40
});
moneyTxt.anchor.set(1, 0); // anchor right/top
LK.gui.topRight.addChild(moneyTxt);
moneyTxt.x = 0; // right edge of gui.topRight
moneyTxt.y = 0; // top edge of gui.topRight
// Helper: Get cell at grid position
function getCell(row, col) {
if (row < 0 || row >= GRID_ROWS || col < 0 || col >= GRID_COLS) {
return null;
}
return grid[row][col];
// Green (country 3) AI move
if (!gameEnded && LK.ticks - lastCountry3UpdateTick > country3MoveInterval) {
lastCountry3UpdateTick = LK.ticks;
var greenTarget = country3ChooseTarget();
if (greenTarget) {
// Animate green marker to target
tween(country3Marker, {
x: greenTarget.x,
y: greenTarget.y
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Claim cell
if (greenTarget.state === 1 || greenTarget.state === 2) {
// Player or AI loses cell
greenTarget.setState(3);
// If player marker is here, snap it back to last owned cell
var dx = playerMarker.x - greenTarget.x;
var dy = playerMarker.y - greenTarget.y;
if (Math.sqrt(dx * dx + dy * dy) < 10) {
// Find any player cell to snap to
var found = false;
for (var r = 0; r < GRID_ROWS; r++) {
for (var c = 0; c < GRID_COLS; c++) {
if (grid[r][c].state === 1) {
lastPlayerCell = grid[r][c];
tween(playerMarker, {
x: lastPlayerCell.x,
y: lastPlayerCell.y
}, {
duration: 120,
easing: tween.easeIn
});
found = true;
break;
}
}
if (found) {
break;
}
}
}
} else {
greenTarget.setState(3);
}
lastCountry3Cell = greenTarget;
countCells();
updateScore();
checkEndGame();
}
});
}
}
}
;
// Helper: Get cell at (x, y) game coordinates
function getCellAtPosition(x, y) {
var col = Math.floor((x - GRID_OFFSET_X) / CELL_SIZE);
var row = Math.floor((y - GRID_OFFSET_Y) / CELL_SIZE);
return getCell(row, col);
}
// Helper: Get adjacent cells (up, down, left, right)
function getAdjacentCells(cell) {
var adj = [];
var dirs = [{
dr: -1,
dc: 0
}, {
dr: 1,
dc: 0
}, {
dr: 0,
dc: -1
}, {
dr: 0,
dc: 1
}];
for (var i = 0; i < dirs.length; i++) {
var ncell = getCell(cell.row + dirs[i].dr, cell.col + dirs[i].dc);
if (ncell) {
adj.push(ncell);
}
}
return adj;
}
// Helper: Count cells by state
function countCells() {
playerCells = 0;
aiCells = 0;
neutralCells = 0;
country3Cells = 0;
for (var r = 0; r < GRID_ROWS; r++) {
for (var c = 0; c < GRID_COLS; c++) {
var cell = grid[r][c];
if (cell.state === 0) {
neutralCells++;
} else if (cell.state === 1) {
playerCells++;
} else if (cell.state === 2) {
aiCells++;
} else if (cell.state === 3) {
country3Cells++;
}
}
}
}
// Helper: Update score display
function updateScore() {
scoreTxt.setText(playerCells + " - " + aiCells + " - " + country3Cells);
// Win if player has 32 or more cells
if (!gameEnded && playerCells >= 32) {
gameEnded = true;
LK.showYouWin();
}
}
// Helper: Check win/lose
function checkEndGame() {
if (gameEnded) {
return;
}
var total = GRID_ROWS * GRID_COLS;
if (playerCells > total / 2) {
gameEnded = true;
LK.showYouWin();
} else if (aiCells > total / 2 || playerCells === 0) {
gameEnded = true;
LK.showGameOver();
}
}
// Initialize grid
grid = [];
for (var r = 0; r < GRID_ROWS; r++) {
var rowArr = [];
for (var c = 0; c < GRID_COLS; c++) {
var cell = new GridCell();
cell.row = r;
cell.col = c;
cell.x = GRID_OFFSET_X + c * CELL_SIZE + CELL_SIZE / 2;
cell.y = GRID_OFFSET_Y + r * CELL_SIZE + CELL_SIZE / 2;
cell.setState(0);
game.addChild(cell);
rowArr.push(cell);
}
grid.push(rowArr);
}
// Set initial player, AI, and Country 3 (green) territory (center cells)
var playerStart = getCell(Math.floor(GRID_ROWS / 2), 1);
playerStart.setState(1);
var aiStart = getCell(Math.floor(GRID_ROWS / 2), GRID_COLS - 2);
aiStart.setState(2);
var country3Start = getCell(Math.floor(GRID_ROWS / 2), Math.floor(GRID_COLS / 2));
country3Start.setState(3);
// Place player marker
playerMarker = new CountryMarker();
playerMarker.setOwner(1);
playerMarker.x = playerStart.x;
playerMarker.y = playerStart.y;
game.addChild(playerMarker);
lastPlayerCell = playerStart;
// Place AI marker
aiMarker = new CountryMarker();
aiMarker.setOwner(2);
aiMarker.x = aiStart.x;
aiMarker.y = aiStart.y;
game.addChild(aiMarker);
// Place Country 3 (green) marker (now with AI movement/attack)
country3Marker = new CountryMarker();
country3Marker.setOwner(3);
country3Marker.x = country3Start.x;
country3Marker.y = country3Start.y;
game.addChild(country3Marker);
lastCountry3Cell = country3Start;
// Country 3 (green) AI variables
var lastCountry3UpdateTick = 0;
var country3MoveInterval = 60; // green AI moves every 60 ticks (~1s)
// Initial cell counts
countCells();
updateScore();
// Dragging logic for player marker
game.down = function (x, y, obj) {
// Only allow drag if touch/click is on player marker
var dx = x - playerMarker.x;
var dy = y - playerMarker.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 80) {
dragMarker = playerMarker;
}
};
game.up = function (x, y, obj) {
if (dragMarker === playerMarker) {
// Snap to nearest cell
var cell = getCellAtPosition(playerMarker.x, playerMarker.y);
if (cell && (cell.state === 0 || cell.state === 2 || cell.state === 3)) {
// Allow move to any cell adjacent to ANY player cell
var isAdj = false;
for (var r = 0; r < GRID_ROWS; r++) {
for (var c = 0; c < GRID_COLS; c++) {
if (grid[r][c].state === 1) {
var adj = getAdjacentCells(grid[r][c]);
for (var i = 0; i < adj.length; i++) {
if (adj[i] === cell) {
isAdj = true;
break;
}
}
if (isAdj) {
break;
}
}
}
if (isAdj) {
break;
}
}
if (isAdj) {
// Only allow attack if player has money
if (playerMoney > 0) {
playerMoney--;
moneyTxt.setText('💰 ' + playerMoney);
// Claim cell
cell.setState(1);
lastPlayerCell = cell;
// Animate marker to cell center
tween(playerMarker, {
x: cell.x,
y: cell.y
}, {
duration: 180,
easing: tween.easeOut
});
countCells();
updateScore();
checkEndGame();
} else {
// Not enough money, snap back
tween(playerMarker, {
x: lastPlayerCell.x,
y: lastPlayerCell.y
}, {
duration: 120,
easing: tween.easeIn
});
}
} else {
// Not adjacent, snap back
tween(playerMarker, {
x: lastPlayerCell.x,
y: lastPlayerCell.y
}, {
duration: 120,
easing: tween.easeIn
});
}
} else {
// Not a valid cell, snap back
tween(playerMarker, {
x: lastPlayerCell.x,
y: lastPlayerCell.y
}, {
duration: 120,
easing: tween.easeIn
});
}
}
dragMarker = null;
};
game.move = function (x, y, obj) {
if (dragMarker === playerMarker) {
// Restrict movement to grid area
var px = Math.max(GRID_OFFSET_X + 60, Math.min(x, GRID_OFFSET_X + GRID_COLS * CELL_SIZE - 60));
var py = Math.max(GRID_OFFSET_Y + 60, Math.min(y, GRID_OFFSET_Y + GRID_ROWS * CELL_SIZE - 60));
playerMarker.x = px;
playerMarker.y = py;
}
};
// AI logic: smarter targeting: prioritize attacking player cells with most adjacent AI cells, fallback to neutral, then green (country 3)
function aiChooseTarget() {
// Find all AI cells
var aiCellsArr = [];
for (var r = 0; r < GRID_ROWS; r++) {
for (var c = 0; c < GRID_COLS; c++) {
if (grid[r][c].state === 2) {
aiCellsArr.push(grid[r][c]);
}
}
}
// For each AI cell, check adjacent for neutral, player, or green cells
var playerCandidates = [];
var neutralCandidates = [];
var greenCandidates = [];
for (var i = 0; i < aiCellsArr.length; i++) {
var adj = getAdjacentCells(aiCellsArr[i]);
for (var j = 0; j < adj.length; j++) {
var cell = adj[j];
if (cell.state === 1) {
// Count how many AI cells are adjacent to this player cell
var adjToPlayer = getAdjacentCells(cell);
var aiAdjCount = 0;
for (var k = 0; k < adjToPlayer.length; k++) {
if (adjToPlayer[k].state === 2) {
aiAdjCount++;
}
}
playerCandidates.push({
cell: cell,
aiAdjCount: aiAdjCount
});
} else if (cell.state === 0) {
neutralCandidates.push(cell);
} else if (cell.state === 3) {
greenCandidates.push(cell);
}
}
}
// Prefer player cells with most adjacent AI cells (easier to defend/expand)
if (playerCandidates.length > 0) {
// Sort descending by aiAdjCount
playerCandidates.sort(function (a, b) {
return b.aiAdjCount - a.aiAdjCount;
});
// Pick one of the best (if multiple have same max)
var maxAdj = playerCandidates[0].aiAdjCount;
var best = [];
for (var i = 0; i < playerCandidates.length; i++) {
if (playerCandidates[i].aiAdjCount === maxAdj) {
best.push(playerCandidates[i].cell);
}
}
var idx = Math.floor(Math.random() * best.length);
return best[idx];
}
// Otherwise, prefer neutral cells
if (neutralCandidates.length > 0) {
var idx = Math.floor(Math.random() * neutralCandidates.length);
return neutralCandidates[idx];
}
// Otherwise, attack green (country 3)
if (greenCandidates.length > 0) {
var idx = Math.floor(Math.random() * greenCandidates.length);
return greenCandidates[idx];
}
return null;
}
// Green (country 3) AI logic: smarter targeting: prioritize attacking player/AI cells with most adjacent green cells, fallback to neutral
function country3ChooseTarget() {
// Find all green cells
var greenCellsArr = [];
for (var r = 0; r < GRID_ROWS; r++) {
for (var c = 0; c < GRID_COLS; c++) {
if (grid[r][c].state === 3) {
greenCellsArr.push(grid[r][c]);
}
}
}
// For each green cell, check adjacent for neutral, player, or AI cells (never yellow)
var playerCandidates = [];
var aiCandidates = [];
var neutralCandidates = [];
for (var i = 0; i < greenCellsArr.length; i++) {
var adj = getAdjacentCells(greenCellsArr[i]);
for (var j = 0; j < adj.length; j++) {
var cell = adj[j];
if (cell.state === 1) {
// Count how many green cells are adjacent to this player cell
var adjToPlayer = getAdjacentCells(cell);
var greenAdjCount = 0;
for (var k = 0; k < adjToPlayer.length; k++) {
if (adjToPlayer[k].state === 3) {
greenAdjCount++;
}
}
playerCandidates.push({
cell: cell,
greenAdjCount: greenAdjCount
});
} else if (cell.state === 2) {
// Count how many green cells are adjacent to this AI cell
var adjToAI = getAdjacentCells(cell);
var greenAdjCount = 0;
for (var k = 0; k < adjToAI.length; k++) {
if (adjToAI[k].state === 3) {
greenAdjCount++;
}
}
aiCandidates.push({
cell: cell,
greenAdjCount: greenAdjCount
});
} else if (cell.state === 0) {
neutralCandidates.push(cell);
}
}
}
// Prefer player cells with most adjacent green cells
if (playerCandidates.length > 0) {
playerCandidates.sort(function (a, b) {
return b.greenAdjCount - a.greenAdjCount;
});
var maxAdj = playerCandidates[0].greenAdjCount;
var best = [];
for (var i = 0; i < playerCandidates.length; i++) {
if (playerCandidates[i].greenAdjCount === maxAdj) {
best.push(playerCandidates[i].cell);
}
}
var idx = Math.floor(Math.random() * best.length);
return best[idx];
}
// Otherwise, prefer AI cells with most adjacent green cells
if (aiCandidates.length > 0) {
aiCandidates.sort(function (a, b) {
return b.greenAdjCount - a.greenAdjCount;
});
var maxAdj = aiCandidates[0].greenAdjCount;
var best = [];
for (var i = 0; i < aiCandidates.length; i++) {
if (aiCandidates[i].greenAdjCount === maxAdj) {
best.push(aiCandidates[i].cell);
}
}
var idx = Math.floor(Math.random() * best.length);
return best[idx];
}
// Otherwise, prefer neutral cells
if (neutralCandidates.length > 0) {
var idx = Math.floor(Math.random() * neutralCandidates.length);
return neutralCandidates[idx];
}
return null;
}
// Main game update loop
game.update = function () {
if (gameEnded) {
return;
}
// Give player money for each cell owned at the start of their turn (max 10)
if (LK.ticks % 60 === 0 && !gameEnded) {
var newMoney = 0;
for (var r = 0; r < GRID_ROWS; r++) {
for (var c = 0; c < GRID_COLS; c++) {
if (grid[r][c].state === 1) {
newMoney++;
}
}
}
playerMoney += newMoney;
if (playerMoney > 100) {
playerMoney = 10;
}
moneyTxt.setText('💰 ' + playerMoney);
}
// AI move
if (LK.ticks - lastAIUpdateTick > aiMoveInterval) {
lastAIUpdateTick = LK.ticks;
var target = aiChooseTarget();
if (target) {
// Animate AI marker to target
tween(aiMarker, {
x: target.x,
y: target.y
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Claim cell
if (target.state === 1) {
// Player loses cell
target.setState(2);
// If player marker is here, snap it back to last owned cell
var dx = playerMarker.x - target.x;
var dy = playerMarker.y - target.y;
if (Math.sqrt(dx * dx + dy * dy) < 10) {
// Find any player cell to snap to
var found = false;
for (var r = 0; r < GRID_ROWS; r++) {
for (var c = 0; c < GRID_COLS; c++) {
if (grid[r][c].state === 1) {
lastPlayerCell = grid[r][c];
tween(playerMarker, {
x: lastPlayerCell.x,
y: lastPlayerCell.y
}, {
duration: 120,
easing: tween.easeIn
});
found = true;
break;
}
}
if (found) {
break;
}
}
}
} else {
target.setState(2);
}
countCells();
updateScore();
checkEndGame();
}
});
}
}
// Green (country 3) AI move
if (!gameEnded && LK.ticks - lastCountry3UpdateTick > country3MoveInterval) {
lastCountry3UpdateTick = LK.ticks;
var greenTarget = country3ChooseTarget();
if (greenTarget) {
// Animate green marker to target
tween(country3Marker, {
x: greenTarget.x,
y: greenTarget.y
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Claim cell
if (greenTarget.state === 1 || greenTarget.state === 2) {
// Player or AI loses cell
greenTarget.setState(3);
// If player marker is here, snap it back to last owned cell
var dx = playerMarker.x - greenTarget.x;
var dy = playerMarker.y - greenTarget.y;
if (Math.sqrt(dx * dx + dy * dy) < 10) {
// Find any player cell to snap to
var found = false;
for (var r = 0; r < GRID_ROWS; r++) {
for (var c = 0; c < GRID_COLS; c++) {
if (grid[r][c].state === 1) {
lastPlayerCell = grid[r][c];
tween(playerMarker, {
x: lastPlayerCell.x,
y: lastPlayerCell.y
}, {
duration: 120,
easing: tween.easeIn
});
found = true;
break;
}
}
if (found) {
break;
}
}
}
} else {
greenTarget.setState(3);
}
lastCountry3Cell = greenTarget;
countCells();
updateScore();
checkEndGame();
}
});
}
}
};