/****
* 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());