/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Simple directional button class for on-screen controls var DirButton = Container.expand(function () { var self = Container.call(this); // Background shape self.bg = self.attachAsset('buttonBg', { anchorX: 0.5, anchorY: 0.5 }); // Label self.label = new Text2('', { size: 120, fill: 0x222222 }); self.label.anchor.set(0.5, 0.5); self.addChild(self.label); // Set label text self.setLabel = function (txt) { self.label.setText(txt); }; // Touch feedback (optional: simple scale animation) self.down = function (x, y, obj) { // This will be overridden by the main code }; self.up = function (x, y, obj) { // Optional: visual feedback }; return self; }); /**** * Initialize Game ****/ // --- GAME UPDATE LOOP (not needed for this MVP) --- // --- GAME RESET HANDLING --- // LK will reset the game state automatically on game over or win var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Player "camera" class (not a display object, just for logic) // MazeCell: For possible future expansion, but not used directly in this MVP // --- ASSETS --- // Button background (large, rounded box) // Wall segment (vertical) // Wall segment (horizontal) // Floor // Exit door var Player = function Player() { var self = {}; self.x = 1; // Start position (column) self.y = 1; // Start position (row) self.dir = 0; // 0: up, 1: right, 2: down, 3: left // Directions: [dx, dy] self.dirs = [[0, -1], // up [1, 0], // right [0, 1], // down [-1, 0] // left ]; self.move = function (forward) { var d = self.dir; if (!forward) d = (d + 2) % 4; var nx = self.x + self.dirs[d][0]; var ny = self.y + self.dirs[d][1]; if (maze.isOpen(nx, ny)) { self.x = nx; self.y = ny; return true; } return false; }; self.turn = function (right) { if (right) { self.dir = (self.dir + 1) % 4; } else { self.dir = (self.dir + 3) % 4; } }; return self; }; // Maze class: handles maze data and logic var Maze = function Maze(w, h) { var self = {}; self.w = w; self.h = h; // 0: wall, 1: open self.grid = []; // Simple hardcoded maze for MVP (7x7) // 0: wall, 1: open, E: exit // S = start (1,1), E = exit (5,5) self.template = [[0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 0, 1, 1, 0], [0, 0, 1, 0, 1, 0, 0], [0, 0, 1, 1, 1, 0, 0], [0, 1, 0, 0, 1, 0, 0], [0, 1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0]]; self.exit = { x: 5, y: 5 }; self.isOpen = function (x, y) { if (x < 0 || y < 0 || x >= self.w || y >= self.h) return false; return self.template[y][x] === 1 || x === self.exit.x && y === self.exit.y; }; self.isExit = function (x, y) { return x === self.exit.x && y === self.exit.y; }; return self; }; // --- MAZE LOGIC --- var maze = new Maze(7, 7); // Place player 20 steps away from the exit at game start var player = new Player(); player.x = 1; player.y = 1; player.dir = 0; // --- VIEWPORT CONSTANTS --- var viewCenterX = 2048 / 2; var viewCenterY = 1100; // Place "camera" view in upper half var wallColor = 0x444488; var floorColor = 0xcccccc; var exitColor = 0x44cc44; // --- RENDERED OBJECTS --- var renderObjs = []; // All objects currently rendered for the "view" var youWinText = null; // Helper: check if a move is a wrong turn (turning into a wall) function isWrongTurn(dir) { // Only applies to left/right turns if (dir !== 'left' && dir !== 'right') return false; // Simulate the turn var testDir = player.dir; if (dir === 'left') testDir = (testDir + 3) % 4; if (dir === 'right') testDir = (testDir + 1) % 4; var dx = player.dirs[testDir][0]; var dy = player.dirs[testDir][1]; var nx = player.x + dx; var ny = player.y + dy; // If the cell in front after turn is a wall, it's a wrong turn return !maze.isOpen(nx, ny); } // --- RENDERING LOGIC --- // Simulate a simple 3D effect by drawing rectangles for walls/floor at different depths // We'll render up to 3 steps ahead function clearView() { for (var i = 0; i < renderObjs.length; ++i) { renderObjs[i].destroy(); } renderObjs.length = 0; if (youWinText) { youWinText.destroy(); youWinText = null; } } function renderView() { clearView(); // For each depth (0 = current cell, 1 = one ahead, 2 = two ahead, 3 = three ahead) for (var depth = 0; depth <= 3; ++depth) { // Compute cell in front of player var dx = player.dirs[player.dir][0]; var dy = player.dirs[player.dir][1]; var px = player.x + dx * depth; var py = player.y + dy * depth; // If out of bounds, stop if (px < 0 || py < 0 || px >= maze.w || py >= maze.h) break; // Floor: always draw var floorW = 1200 - depth * 300; var floorH = 300 - depth * 40; var floorY = viewCenterY + 300 + depth * 60; var floorX = viewCenterX; var floor = LK.getAsset('floor', { anchorX: 0.5, anchorY: 0.5, width: floorW, height: floorH }); floor.x = floorX; floor.y = floorY; renderObjs.push(game.addChild(floor)); // If this is the exit, draw exit door at the end if (maze.isExit(px, py)) { var exitW = 400 - depth * 100; var exitH = 600 - depth * 120; var exitY = viewCenterY - 100 + depth * 60; var exit = LK.getAsset('exit', { anchorX: 0.5, anchorY: 0.5, width: exitW, height: exitH }); exit.x = viewCenterX; exit.y = exitY; renderObjs.push(game.addChild(exit)); break; } // If wall ahead, draw wall if (!maze.isOpen(px, py)) { // Draw front wall var wallW = 1200 - depth * 300; var wallH = 600 - depth * 120; var wallY = viewCenterY - 100 + depth * 60; var wall = LK.getAsset('wallH', { anchorX: 0.5, anchorY: 0.5, width: wallW, height: wallH }); wall.x = viewCenterX; wall.y = wallY; renderObjs.push(game.addChild(wall)); break; } // Draw left/right walls if present // Left: (dir-1), Right: (dir+1) for (var side = 0; side < 2; ++side) { var sideDir = (player.dir + (side === 0 ? 3 : 1)) % 4; var sdx = player.dirs[sideDir][0]; var sdy = player.dirs[sideDir][1]; var sx = px + sdx; var sy = py + sdy; if (!maze.isOpen(sx, sy)) { // Draw side wall as a vertical rectangle var wallW = 120 - depth * 20; var wallH = 600 - depth * 120; var wallY = viewCenterY - 100 + depth * 60; var wallX = viewCenterX + (side === 0 ? -1 : 1) * (500 - depth * 120); var wall = LK.getAsset('wallV', { anchorX: 0.5, anchorY: 0.5, width: wallW, height: wallH }); wall.x = wallX; wall.y = wallY; renderObjs.push(game.addChild(wall)); } } } } // --- GUI: Directional Buttons --- var btnSize = 220; var btnMargin = 40; var btnYOffset = 370; // move all buttons down from center (slightly lower) // Center of the GUI (not game world) var centerX = 0; var centerY = 0; // We'll use LK.gui.center as the parent for all buttons, and position them relative to its center var btnForward = new DirButton(); btnForward.setLabel('▲'); btnForward.x = 0; btnForward.y = -btnSize - btnMargin + btnYOffset; btnForward.bg.width = btnSize; btnForward.bg.height = btnSize; btnForward.label.setText('▲'); LK.gui.center.addChild(btnForward); var btnBack = new DirButton(); btnBack.setLabel('▼'); btnBack.x = 0; btnBack.y = btnSize + btnMargin + btnYOffset; btnBack.bg.width = btnSize; btnBack.bg.height = btnSize; btnBack.label.setText('▼'); LK.gui.center.addChild(btnBack); var btnLeft = new DirButton(); btnLeft.setLabel('◀'); btnLeft.x = -btnSize - btnMargin; btnLeft.y = btnYOffset; btnLeft.bg.width = btnSize; btnLeft.bg.height = btnSize; btnLeft.label.setText('◀'); LK.gui.center.addChild(btnLeft); var btnRight = new DirButton(); btnRight.setLabel('▶'); btnRight.x = btnSize + btnMargin; btnRight.y = btnYOffset; btnRight.bg.width = btnSize; btnRight.bg.height = btnSize; btnRight.label.setText('▶'); LK.gui.center.addChild(btnRight); // --- GUI: Step Counter --- var stepCount = 0; var stepText = new Text2('Steps: 0', { size: 90, fill: 0xFFFFFF }); stepText.anchor.set(0.5, 0); LK.gui.top.addChild(stepText); // --- GUI: Distance to Exit Tracker --- function calcDistanceToExit() { // Manhattan distance (no diagonal moves) return Math.abs(player.x - maze.exit.x) + Math.abs(player.y - maze.exit.y); } var distText = new Text2('To Exit: ' + calcDistanceToExit(), { size: 90, fill: 0x44CC44 }); distText.anchor.set(0.5, 0); distText.y = stepText.height + 10; LK.gui.top.addChild(distText); // --- BUTTON EVENTS --- function handleMove(dir) { if (youWinText) return; // Don't move after win var moved = false; if (dir === 'forward') { moved = player.move(true); } else if (dir === 'back') { moved = player.move(false); } else if (dir === 'left') { // Check for wrong turn before turning isWrongTurn('left'); player.turn(false); moved = true; } else if (dir === 'right') { isWrongTurn('right'); player.turn(true); moved = true; } if (moved) { stepCount += 1; stepText.setText('Steps: ' + stepCount); distText.setText('To Exit: ' + calcDistanceToExit()); renderView(); checkWin(); } } // Touch events for buttons btnForward.down = function (x, y, obj) { handleMove('forward'); }; btnBack.down = function (x, y, obj) { handleMove('back'); }; btnLeft.down = function (x, y, obj) { handleMove('left'); }; btnRight.down = function (x, y, obj) { handleMove('right'); }; // --- WIN CONDITION --- function checkWin() { if (maze.isExit(player.x, player.y)) { // Show "You Win" text in the center youWinText = new Text2('You Win!', { size: 200, fill: 0x44CC44 }); youWinText.anchor.set(0.5, 0.5); youWinText.x = 2048 / 2; youWinText.y = 1100; game.addChild(youWinText); // Show win popup (handled by LK) LK.showYouWin(); } } // --- INITIALIZE GAME STATE --- renderView(); stepCount = 0; stepText.setText('Steps: 0'); distText.setText('To Exit: ' + calcDistanceToExit());
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Simple directional button class for on-screen controls
var DirButton = Container.expand(function () {
var self = Container.call(this);
// Background shape
self.bg = self.attachAsset('buttonBg', {
anchorX: 0.5,
anchorY: 0.5
});
// Label
self.label = new Text2('', {
size: 120,
fill: 0x222222
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
// Set label text
self.setLabel = function (txt) {
self.label.setText(txt);
};
// Touch feedback (optional: simple scale animation)
self.down = function (x, y, obj) {
// This will be overridden by the main code
};
self.up = function (x, y, obj) {
// Optional: visual feedback
};
return self;
});
/****
* Initialize Game
****/
// --- GAME UPDATE LOOP (not needed for this MVP) ---
// --- GAME RESET HANDLING ---
// LK will reset the game state automatically on game over or win
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Player "camera" class (not a display object, just for logic)
// MazeCell: For possible future expansion, but not used directly in this MVP
// --- ASSETS ---
// Button background (large, rounded box)
// Wall segment (vertical)
// Wall segment (horizontal)
// Floor
// Exit door
var Player = function Player() {
var self = {};
self.x = 1; // Start position (column)
self.y = 1; // Start position (row)
self.dir = 0; // 0: up, 1: right, 2: down, 3: left
// Directions: [dx, dy]
self.dirs = [[0, -1],
// up
[1, 0],
// right
[0, 1],
// down
[-1, 0] // left
];
self.move = function (forward) {
var d = self.dir;
if (!forward) d = (d + 2) % 4;
var nx = self.x + self.dirs[d][0];
var ny = self.y + self.dirs[d][1];
if (maze.isOpen(nx, ny)) {
self.x = nx;
self.y = ny;
return true;
}
return false;
};
self.turn = function (right) {
if (right) {
self.dir = (self.dir + 1) % 4;
} else {
self.dir = (self.dir + 3) % 4;
}
};
return self;
};
// Maze class: handles maze data and logic
var Maze = function Maze(w, h) {
var self = {};
self.w = w;
self.h = h;
// 0: wall, 1: open
self.grid = [];
// Simple hardcoded maze for MVP (7x7)
// 0: wall, 1: open, E: exit
// S = start (1,1), E = exit (5,5)
self.template = [[0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 0, 1, 1, 0], [0, 0, 1, 0, 1, 0, 0], [0, 0, 1, 1, 1, 0, 0], [0, 1, 0, 0, 1, 0, 0], [0, 1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0]];
self.exit = {
x: 5,
y: 5
};
self.isOpen = function (x, y) {
if (x < 0 || y < 0 || x >= self.w || y >= self.h) return false;
return self.template[y][x] === 1 || x === self.exit.x && y === self.exit.y;
};
self.isExit = function (x, y) {
return x === self.exit.x && y === self.exit.y;
};
return self;
};
// --- MAZE LOGIC ---
var maze = new Maze(7, 7);
// Place player 20 steps away from the exit at game start
var player = new Player();
player.x = 1;
player.y = 1;
player.dir = 0;
// --- VIEWPORT CONSTANTS ---
var viewCenterX = 2048 / 2;
var viewCenterY = 1100; // Place "camera" view in upper half
var wallColor = 0x444488;
var floorColor = 0xcccccc;
var exitColor = 0x44cc44;
// --- RENDERED OBJECTS ---
var renderObjs = []; // All objects currently rendered for the "view"
var youWinText = null;
// Helper: check if a move is a wrong turn (turning into a wall)
function isWrongTurn(dir) {
// Only applies to left/right turns
if (dir !== 'left' && dir !== 'right') return false;
// Simulate the turn
var testDir = player.dir;
if (dir === 'left') testDir = (testDir + 3) % 4;
if (dir === 'right') testDir = (testDir + 1) % 4;
var dx = player.dirs[testDir][0];
var dy = player.dirs[testDir][1];
var nx = player.x + dx;
var ny = player.y + dy;
// If the cell in front after turn is a wall, it's a wrong turn
return !maze.isOpen(nx, ny);
}
// --- RENDERING LOGIC ---
// Simulate a simple 3D effect by drawing rectangles for walls/floor at different depths
// We'll render up to 3 steps ahead
function clearView() {
for (var i = 0; i < renderObjs.length; ++i) {
renderObjs[i].destroy();
}
renderObjs.length = 0;
if (youWinText) {
youWinText.destroy();
youWinText = null;
}
}
function renderView() {
clearView();
// For each depth (0 = current cell, 1 = one ahead, 2 = two ahead, 3 = three ahead)
for (var depth = 0; depth <= 3; ++depth) {
// Compute cell in front of player
var dx = player.dirs[player.dir][0];
var dy = player.dirs[player.dir][1];
var px = player.x + dx * depth;
var py = player.y + dy * depth;
// If out of bounds, stop
if (px < 0 || py < 0 || px >= maze.w || py >= maze.h) break;
// Floor: always draw
var floorW = 1200 - depth * 300;
var floorH = 300 - depth * 40;
var floorY = viewCenterY + 300 + depth * 60;
var floorX = viewCenterX;
var floor = LK.getAsset('floor', {
anchorX: 0.5,
anchorY: 0.5,
width: floorW,
height: floorH
});
floor.x = floorX;
floor.y = floorY;
renderObjs.push(game.addChild(floor));
// If this is the exit, draw exit door at the end
if (maze.isExit(px, py)) {
var exitW = 400 - depth * 100;
var exitH = 600 - depth * 120;
var exitY = viewCenterY - 100 + depth * 60;
var exit = LK.getAsset('exit', {
anchorX: 0.5,
anchorY: 0.5,
width: exitW,
height: exitH
});
exit.x = viewCenterX;
exit.y = exitY;
renderObjs.push(game.addChild(exit));
break;
}
// If wall ahead, draw wall
if (!maze.isOpen(px, py)) {
// Draw front wall
var wallW = 1200 - depth * 300;
var wallH = 600 - depth * 120;
var wallY = viewCenterY - 100 + depth * 60;
var wall = LK.getAsset('wallH', {
anchorX: 0.5,
anchorY: 0.5,
width: wallW,
height: wallH
});
wall.x = viewCenterX;
wall.y = wallY;
renderObjs.push(game.addChild(wall));
break;
}
// Draw left/right walls if present
// Left: (dir-1), Right: (dir+1)
for (var side = 0; side < 2; ++side) {
var sideDir = (player.dir + (side === 0 ? 3 : 1)) % 4;
var sdx = player.dirs[sideDir][0];
var sdy = player.dirs[sideDir][1];
var sx = px + sdx;
var sy = py + sdy;
if (!maze.isOpen(sx, sy)) {
// Draw side wall as a vertical rectangle
var wallW = 120 - depth * 20;
var wallH = 600 - depth * 120;
var wallY = viewCenterY - 100 + depth * 60;
var wallX = viewCenterX + (side === 0 ? -1 : 1) * (500 - depth * 120);
var wall = LK.getAsset('wallV', {
anchorX: 0.5,
anchorY: 0.5,
width: wallW,
height: wallH
});
wall.x = wallX;
wall.y = wallY;
renderObjs.push(game.addChild(wall));
}
}
}
}
// --- GUI: Directional Buttons ---
var btnSize = 220;
var btnMargin = 40;
var btnYOffset = 370; // move all buttons down from center (slightly lower)
// Center of the GUI (not game world)
var centerX = 0;
var centerY = 0;
// We'll use LK.gui.center as the parent for all buttons, and position them relative to its center
var btnForward = new DirButton();
btnForward.setLabel('▲');
btnForward.x = 0;
btnForward.y = -btnSize - btnMargin + btnYOffset;
btnForward.bg.width = btnSize;
btnForward.bg.height = btnSize;
btnForward.label.setText('▲');
LK.gui.center.addChild(btnForward);
var btnBack = new DirButton();
btnBack.setLabel('▼');
btnBack.x = 0;
btnBack.y = btnSize + btnMargin + btnYOffset;
btnBack.bg.width = btnSize;
btnBack.bg.height = btnSize;
btnBack.label.setText('▼');
LK.gui.center.addChild(btnBack);
var btnLeft = new DirButton();
btnLeft.setLabel('◀');
btnLeft.x = -btnSize - btnMargin;
btnLeft.y = btnYOffset;
btnLeft.bg.width = btnSize;
btnLeft.bg.height = btnSize;
btnLeft.label.setText('◀');
LK.gui.center.addChild(btnLeft);
var btnRight = new DirButton();
btnRight.setLabel('▶');
btnRight.x = btnSize + btnMargin;
btnRight.y = btnYOffset;
btnRight.bg.width = btnSize;
btnRight.bg.height = btnSize;
btnRight.label.setText('▶');
LK.gui.center.addChild(btnRight);
// --- GUI: Step Counter ---
var stepCount = 0;
var stepText = new Text2('Steps: 0', {
size: 90,
fill: 0xFFFFFF
});
stepText.anchor.set(0.5, 0);
LK.gui.top.addChild(stepText);
// --- GUI: Distance to Exit Tracker ---
function calcDistanceToExit() {
// Manhattan distance (no diagonal moves)
return Math.abs(player.x - maze.exit.x) + Math.abs(player.y - maze.exit.y);
}
var distText = new Text2('To Exit: ' + calcDistanceToExit(), {
size: 90,
fill: 0x44CC44
});
distText.anchor.set(0.5, 0);
distText.y = stepText.height + 10;
LK.gui.top.addChild(distText);
// --- BUTTON EVENTS ---
function handleMove(dir) {
if (youWinText) return; // Don't move after win
var moved = false;
if (dir === 'forward') {
moved = player.move(true);
} else if (dir === 'back') {
moved = player.move(false);
} else if (dir === 'left') {
// Check for wrong turn before turning
isWrongTurn('left');
player.turn(false);
moved = true;
} else if (dir === 'right') {
isWrongTurn('right');
player.turn(true);
moved = true;
}
if (moved) {
stepCount += 1;
stepText.setText('Steps: ' + stepCount);
distText.setText('To Exit: ' + calcDistanceToExit());
renderView();
checkWin();
}
}
// Touch events for buttons
btnForward.down = function (x, y, obj) {
handleMove('forward');
};
btnBack.down = function (x, y, obj) {
handleMove('back');
};
btnLeft.down = function (x, y, obj) {
handleMove('left');
};
btnRight.down = function (x, y, obj) {
handleMove('right');
};
// --- WIN CONDITION ---
function checkWin() {
if (maze.isExit(player.x, player.y)) {
// Show "You Win" text in the center
youWinText = new Text2('You Win!', {
size: 200,
fill: 0x44CC44
});
youWinText.anchor.set(0.5, 0.5);
youWinText.x = 2048 / 2;
youWinText.y = 1100;
game.addChild(youWinText);
// Show win popup (handled by LK)
LK.showYouWin();
}
}
// --- INITIALIZE GAME STATE ---
renderView();
stepCount = 0;
stepText.setText('Steps: 0');
distText.setText('To Exit: ' + calcDistanceToExit());