User prompt
Add a small “⚙️” settings button on the top-right of the game screen. When tapped, open a simple popup with this title: “Change Difficulty” Inside the popup, show 3 buttons: • 🟢 Easy • ⚪ Normal • 🔴 Hard When the player taps one of these buttons: • Set variable difficulty to “easy”, “normal”, or “hard” • Immediately update enemy speed based on this: • Easy → enemySpeed = baseSpeed × 0.5 • Normal → enemySpeed = baseSpeed × 1.0 • Hard → enemySpeed = baseSpeed × 1.5 • Update the on-screen text that shows the current difficulty Do not pause the game. Just apply the change directly.
User prompt
Add a small “⚙️” settings button on the top-right corner of the game screen. When the player taps it, pause the game and open a small popup menu labeled: “Change Difficulty” Inside the popup, show 3 buttons: • 🟢 Easy • ⚪ Normal • 🔴 Hard When the player taps a difficulty button: • Immediately update the difficulty variable • Adjust enemy speed: • Easy → enemySpeed = baseSpeed × 0.5 • Normal → enemySpeed = baseSpeed × 1.0 • Hard → enemySpeed = baseSpeed × 1.5 • Update the on-screen difficulty label accordingly (e.g., “Difficulty: Hard”) • Resume the game after a short delay (e.g., 1 second) Also: • Ensure this menu can be opened and closed anytime during gameplay • If possible, dim the background slightly when the menu is open
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'startBtn.txt.style.fill = "#fff";' Line Number: 999
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'easyBtn.txt.style.fill = menuDifficulty === "Easy" ? "#222" : "#fff";' Line Number: 963
User prompt
Add three large touch buttons in the center of the screen: • 🟢 Easy • ⚪ Normal • 🔴 Hard When a player taps one of these buttons: • Set a variable difficulty to "easy", "normal", or "hard" • Set the enemy speed as follows: • Easy → enemySpeed = baseSpeed * 0.5 • Normal → enemySpeed = baseSpeed * 1.0 • Hard → enemySpeed = baseSpeed * 1.5 • Show a new button below: “Start Game” When “Start Game” is tapped: • Hide the menu • Start the actual game • Display the selected difficulty on the main game screen (e.g. “Difficulty: Normal”) Also: • On the game UI, show the difficulty label near the score or hearts • Make sure the enemy speed stays consistent based on that selection throughout the game Optional: • Use different background music for each difficulty level: • Easy → calmTrack • Normal → normalTrack • Hard → intenseTrack
User prompt
Reduce the movement speed of the enemy AI slightly. Ghosts should still chase the player, but with a slower speed (e.g. 20% slower than current). Ensure it still feels challenging, but not overwhelming. Optional: Add difficulty levels where Easy = slow ghosts, Hard = fast ghosts.
User prompt
When the game starts, play the music named "1" in a loop until the game ends. It should not restart on every life loss.
User prompt
Player noktalara dokunduğunda dotbeep çal
User prompt
Sound hatalarını düzrlt
User prompt
When the player loses a life (e.g., by touching a ghost), play the sound effect named loseLife. The sound should play only once at the moment the life is lost. If loseLife is not available, use a fallback sound like fail or error. Example trigger:
User prompt
When the player collects a dot or powerdot, play the sound named dotbeep. If dotbeep is not available, use the default “beep” sound instead. Trigger the sound only once per collection using the onCollect event.
User prompt
Dotbeep sound player dot
User prompt
When the player collects a small dot or a powerdot, play the sound effect named dotbeep. The sound should play only once per collection, not in a loop.
User prompt
Play a sound effect every time the player collects a small dot or a powerdot. Use a light “beep” for small dots and a more noticeable “power-up” sound for powerdots. The sound should only play once at the moment of collection, not loop continuously.
User prompt
Ghostbody biraz büyüt
User prompt
Ghostbody yellow
User prompt
After a powerdot (large yellow dot) is collected, it should respawn after 10 seconds at a random valid location, not the same one. The respawn location should be chosen from a list of predefined suitable spots (e.g., maze corners or large empty tiles). At any time, there should be 4 active powerdots. A powerdot should not respawn in the same position twice in a row.
User prompt
After the powerdot (large yellow dot) is collected, it should respawn at the same location after 10 seconds. The powerdot should keep respawning and only disappear when collected by the player. Apply this behavior to all powerdots in the game.
User prompt
Make the ghost characters (ghostbody) check the player’s position every 0.5 seconds and move toward the player using the shortest valid path. If blocked, choose a random open direction. If a ghost touches the player, reduce one life. Ghosts should actively follow the player, not move randomly.
User prompt
Ghostbody serbest gezmeli
User prompt
Ghostbody playerı takip etmeli
User prompt
Hepsini toplayınca diğer levele geç. Yeni level tasarla
User prompt
Powerdot ghostbody nin biraz uzak olmalı
User prompt
Powerdot ghostbody e 4 nokta geride olmalı
User prompt
Powerdot ghostbody e yakın olmalı
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// --- Dot (Pellet) ---
var Dot = Container.expand(function () {
var self = Container.call(this);
var dotAsset = self.attachAsset('dot', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = dotAsset.width / 2;
return self;
});
// --- Ghost ---
var Ghost = Container.expand(function () {
var self = Container.call(this);
// Attach ghost asset (color set later)
var ghostAsset = self.attachAsset('ghostBody', {
anchorX: 0.5,
anchorY: 0.5
});
// Ghost color
self.color = 0xffff00;
ghostAsset.tint = self.color;
// Movement speed
self.speed = 6.4; // 20% slower than previous (was 8)
// Direction
self.dir = {
x: 0,
y: 0
};
// Scatter/Chase mode
self.mode = 'chase'; // or 'scatter' or 'frightened'
self.frightenedTicks = 0;
// For collision
self.radius = ghostAsset.width / 2;
// Ghost AI type: 'blinky', 'pinky', 'inky', 'clyde'
self.ghostType = 'blinky';
// Set color
self.setColor = function (color) {
self.color = color;
ghostAsset.tint = color;
};
// Set type
self.setType = function (type) {
self.ghostType = type;
};
// Set mode
self.setMode = function (mode) {
self.mode = mode;
if (mode === 'frightened') {
ghostAsset.tint = 0x2222ff;
} else {
ghostAsset.tint = self.color;
}
};
// Move to tile
self.moveToTile = function (tile) {
self.x = tile.x * tileSize + tileSize / 2 + mazeOffsetX;
self.y = tile.y * tileSize + tileSize / 2 + mazeOffsetY;
};
// Get current tile
self.getTile = function () {
return {
x: Math.floor((self.x - mazeOffsetX) / tileSize),
y: Math.floor((self.y - mazeOffsetY) / tileSize)
};
};
// AI: get target tile
self.getTargetTile = function () {
// If player exists, target the player's current tile (chase mode)
if (typeof player !== "undefined" && player && typeof player.getTile === "function") {
var targetTile = player.getTile();
return {
x: targetTile.x,
y: targetTile.y
};
}
// Fallback: random movement as before
var curTile = self.getTile();
var dirs = [{
x: 0,
y: -1
},
// up
{
x: 1,
y: 0
},
// right
{
x: 0,
y: 1
},
// down
{
x: -1,
y: 0
} // left
];
// Try to keep going in the same direction if possible
if (canMove(curTile.x, curTile.y, self.dir.x, self.dir.y)) {
return {
x: curTile.x + self.dir.x,
y: curTile.y + self.dir.y
};
}
// Otherwise, pick a random valid direction
var options = [];
for (var i = 0; i < dirs.length; i++) {
var d = dirs[i];
if (canMove(curTile.x, curTile.y, d.x, d.y)) {
options.push(d);
}
}
if (options.length > 0) {
var idx = Math.floor(Math.random() * options.length);
var d = options[idx];
return {
x: curTile.x + d.x,
y: curTile.y + d.y
};
}
// If stuck, stay in place
return curTile;
};
// AI: choose direction
self.chooseDir = function () {
var curTile = self.getTile();
var target = self.getTargetTile();
// All possible directions
var dirs = [{
x: 0,
y: -1
},
// up
{
x: 1,
y: 0
},
// right
{
x: 0,
y: 1
},
// down
{
x: -1,
y: 0
} // left
];
// Don't reverse
var opp = {
x: -self.dir.x,
y: -self.dir.y
};
// For frightened, pick random
if (self.mode === 'frightened') {
var options = [];
for (var i = 0; i < dirs.length; i++) {
var d = dirs[i];
if ((d.x !== opp.x || d.y !== opp.y) && canMove(curTile.x, curTile.y, d.x, d.y)) {
options.push(d);
}
}
if (options.length > 0) {
var idx = Math.floor(Math.random() * options.length);
return options[idx];
}
return self.dir;
}
// Otherwise, pick direction that minimizes distance to target
var minDist = 99999,
bestDir = self.dir;
for (var i = 0; i < dirs.length; i++) {
var d = dirs[i];
if ((d.x !== opp.x || d.y !== opp.y) && canMove(curTile.x, curTile.y, d.x, d.y)) {
var nx = curTile.x + d.x,
ny = curTile.y + d.y;
var dist = (target.x - nx) * (target.x - nx) + (target.y - ny) * (target.y - ny);
if (dist < minDist) {
minDist = dist;
bestDir = d;
}
}
}
return bestDir;
};
// Update per tick
self.update = function () {
// If in tunnel, wrap
if (self.x < mazeOffsetX - tileSize / 2) {
self.x = mazeOffsetX + (mazeCols - 1) * tileSize + tileSize / 2;
}
if (self.x > mazeOffsetX + (mazeCols - 1) * tileSize + tileSize / 2) {
self.x = mazeOffsetX - tileSize / 2;
}
// Frightened mode timer
if (self.mode === 'frightened') {
self.frightenedTicks--;
if (self.frightenedTicks <= 0) {
self.setMode('chase');
}
}
// At center of tile, choose new dir
var curTile = self.getTile();
var cx = curTile.x * tileSize + tileSize / 2 + mazeOffsetX;
var cy = curTile.y * tileSize + tileSize / 2 + mazeOffsetY;
var dist = Math.abs(self.x - cx) + Math.abs(self.y - cy);
if (dist < 2) {
// Always choose direction based on AI (chase, scatter, frightened)
var newDir = self.chooseDir();
self.dir = newDir;
// Snap to center
self.x = cx;
self.y = cy;
}
// Move if possible
if (canMove(curTile.x, curTile.y, self.dir.x, self.dir.y)) {
self.x += self.dir.x * self.speed;
self.y += self.dir.y * self.speed;
}
};
return self;
});
// --- Player (Pac) ---
var Player = Container.expand(function () {
var self = Container.call(this);
// Attach yellow circle asset for player
var playerAsset = self.attachAsset('playerCircle', {
anchorX: 0.5,
anchorY: 0.5
});
// Movement speed (pixels per tick)
self.speed = 10;
// Current direction: {x: -1, y:0} etc.
self.dir = {
x: 0,
y: 0
};
self.nextDir = {
x: 0,
y: 0
};
// For smooth movement
self.targetTile = null;
// For mouth animation
self.mouthOpen = true;
self.mouthTween = null;
// For collision
self.radius = playerAsset.width / 2;
// Animate mouth (simple scaleX)
function animateMouth() {
if (self.mouthTween) tween.stop(playerAsset, {
scaleX: true
});
playerAsset.scaleX = 1;
self.mouthTween = tween(playerAsset, {
scaleX: 0.7
}, {
duration: 120,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(playerAsset, {
scaleX: 1
}, {
duration: 120,
easing: tween.easeInOut,
onFinish: animateMouth
});
}
});
}
animateMouth();
// Set direction
self.setDir = function (dx, dy) {
self.nextDir = {
x: dx,
y: dy
};
};
// Move to tile
self.moveToTile = function (tile) {
self.x = tile.x * tileSize + tileSize / 2 + mazeOffsetX;
self.y = tile.y * tileSize + tileSize / 2 + mazeOffsetY;
};
// Get current tile
self.getTile = function () {
return {
x: Math.floor((self.x - mazeOffsetX) / tileSize),
y: Math.floor((self.y - mazeOffsetY) / tileSize)
};
};
// Update per tick
self.update = function () {
// If in tunnel, wrap
if (self.x < mazeOffsetX - tileSize / 2) {
self.x = mazeOffsetX + (mazeCols - 1) * tileSize + tileSize / 2;
}
if (self.x > mazeOffsetX + (mazeCols - 1) * tileSize + tileSize / 2) {
self.x = mazeOffsetX - tileSize / 2;
}
// Try to turn if possible
var curTile = self.getTile();
if (canMove(curTile.x, curTile.y, self.nextDir.x, self.nextDir.y)) {
self.dir = {
x: self.nextDir.x,
y: self.nextDir.y
};
}
// Move if possible
if (canMove(curTile.x, curTile.y, self.dir.x, self.dir.y)) {
self.x += self.dir.x * self.speed;
self.y += self.dir.y * self.speed;
} else {
// Snap to center of tile
self.moveToTile(curTile);
}
};
return self;
});
// --- PowerDot (Power Pellet) ---
var PowerDot = Container.expand(function () {
var self = Container.call(this);
var pdAsset = self.attachAsset('powerDot', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = pdAsset.width / 2;
return self;
});
// --- Wall ---
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallAsset = self.attachAsset('wallBlock', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Example: A new, more open map with a different wall and dot pattern
// --- Maze Layout ---
// 0: empty, 1: wall, 2: dot, 3: power dot, 4: player start, 5: ghost start, 6: tunnel
// Simple 19x21 maze (Pac-Man like, but smaller for MVP)
var mazeData = [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1], [1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1], [1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1], [1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 1, 1, 5, 1, 1, 1, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1], [1, 2, 2, 2, 1, 2, 2, 2, 1, 4, 1, 2, 2, 2, 1, 2, 2, 2, 1], [1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1], [1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1], [1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1], [1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1], [1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1], [1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]];
var mazeRows = mazeData.length;
var mazeCols = mazeData[0].length;
// --- Maze Rendering ---
var tileSize = 90;
var mazeWidth = mazeCols * tileSize;
var mazeHeight = mazeRows * tileSize;
var mazeOffsetX = Math.floor((2048 - mazeWidth) / 2);
var mazeOffsetY = Math.floor((2732 - mazeHeight) / 2);
// --- Asset Initialization ---
// --- Game State ---
var player = null;
var ghosts = [];
var dots = [];
var powerDots = [];
var walls = [];
var lives = 3;
var level = 1;
var score = 0;
var gameActive = true;
var frightenedTicksMax = 360; // 6 seconds at 60fps
// --- Difficulty Levels ---
var difficulty = "Normal"; // Options: "Easy", "Normal", "Hard"
// You can change this value to "Easy" or "Hard" to test
function getGhostSpeed() {
if (difficulty === "Easy") return 5.2; // 35% slower than original
if (difficulty === "Hard") return 8.8; // 10% faster than original
return 6.4; // Normal (20% slower than original)
}
// --- GUI ---
var scoreTxt = new Text2('0', {
size: 100,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var livesTxt = new Text2('♥♥♥', {
size: 80,
fill: 0xFF4444
});
livesTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(livesTxt);
livesTxt.y = 110;
// Show difficulty in GUI (optional)
var diffTxt = new Text2('Difficulty: ' + difficulty, {
size: 60,
fill: "#fff"
});
diffTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(diffTxt);
diffTxt.y = 200;
// --- Settings Button (⚙️) ---
var settingsBtn = new Container();
var settingsBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.1,
scaleY: 1.1,
tint: 0x222222
});
settingsBtn.addChild(settingsBg);
var settingsTxt = new Text2("⚙️", {
size: 80,
fill: "#fff"
});
settingsTxt.anchor.set(0.5, 0.5);
settingsBtn.addChild(settingsTxt);
// Place in top-right, with margin
settingsBtn.x = 2048 - 100;
settingsBtn.y = 100;
LK.gui.topRight.addChild(settingsBtn);
settingsBtn.zIndex = 1000; // ensure on top
// --- Helper: canMove ---
function canMove(x, y, dx, dy) {
var nx = x + dx,
ny = y + dy;
if (nx < 0) nx = mazeCols - 1;
if (nx >= mazeCols) nx = 0;
if (ny < 0 || ny >= mazeRows) return false;
if (mazeData[ny][nx] === 1) return false;
return true;
}
// --- Helper: reset maze ---
function resetMaze() {
// Remove old
for (var i = 0; i < dots.length; i++) dots[i].destroy();
for (var i = 0; i < powerDots.length; i++) powerDots[i].destroy();
for (var i = 0; i < walls.length; i++) walls[i].destroy();
dots = [];
powerDots = [];
walls = [];
// Place maze
for (var y = 0; y < mazeRows; y++) {
for (var x = 0; x < mazeCols; x++) {
var v = mazeData[y][x];
var px = x * tileSize + tileSize / 2 + mazeOffsetX;
var py = y * tileSize + tileSize / 2 + mazeOffsetY;
if (v === 1) {
var wall = new Wall();
wall.x = px;
wall.y = py;
game.addChild(wall);
walls.push(wall);
} else if (v === 2) {
var dot = new Dot();
dot.x = px;
dot.y = py;
game.addChild(dot);
dots.push(dot);
} else if (v === 3) {
var pd = new PowerDot();
pd.x = px;
pd.y = py;
game.addChild(pd);
powerDots.push(pd);
}
}
}
}
// --- Helper: reset player and ghosts ---
function resetActors() {
// Remove old
if (player) player.destroy();
for (var i = 0; i < ghosts.length; i++) ghosts[i].destroy();
ghosts = [];
// Place player
// Start player at the first open black area (not a wall) from the top, scanning left to right
var foundStart = false;
for (var y = 0; y < mazeRows && !foundStart; y++) {
for (var x = 0; x < mazeCols && !foundStart; x++) {
if (mazeData[y][x] !== 1) {
// not a wall
player = new Player();
player.x = x * tileSize + tileSize / 2 + mazeOffsetX;
player.y = y * tileSize + tileSize / 2 + mazeOffsetY;
game.addChild(player);
foundStart = true;
}
}
}
// Fallback in case all are walls (should never happen)
if (!player) {
player = new Player();
player.x = 1 * tileSize + tileSize / 2 + mazeOffsetX;
player.y = 1 * tileSize + tileSize / 2 + mazeOffsetY;
game.addChild(player);
}
// Place ghosts
var ghostColors = [0xffff00, 0xffff00, 0xffff00, 0xffff00];
var ghostTypes = ['blinky', 'pinky', 'inky', 'clyde'];
var ghostCount = 4;
var gidx = 0;
for (var y = 0; y < mazeRows; y++) {
for (var x = 0; x < mazeCols; x++) {
if (mazeData[y][x] === 5 && gidx < ghostCount) {
var ghost = new Ghost();
ghost.x = x * tileSize + tileSize / 2 + mazeOffsetX;
ghost.y = y * tileSize + tileSize / 2 + mazeOffsetY;
ghost.setColor(ghostColors[gidx]);
ghost.setType(ghostTypes[gidx]);
ghost.speed = getGhostSpeed(); // Set speed based on difficulty
ghost.dir = {
x: 0,
y: -1
};
game.addChild(ghost);
ghosts.push(ghost);
gidx++;
}
}
}
}
// --- Helper: update GUI ---
function updateGUI() {
scoreTxt.setText(score);
var lstr = '';
for (var i = 0; i < lives; i++) lstr += '♥';
livesTxt.setText(lstr);
}
// --- Helper: start level ---
function startLevel() {
resetMaze();
resetActors();
updateGUI();
gameActive = true;
}
// --- Helper: lose life ---
function loseLife() {
lives--;
updateGUI();
// Play loseLife sound if available, otherwise fallback to fail or error
var loseLifeSound = LK.getSound('loseLife');
if (loseLifeSound && typeof loseLifeSound.play === "function") {
loseLifeSound.play();
} else {
var failSound = LK.getSound('fail');
if (failSound && typeof failSound.play === "function") {
failSound.play();
} else {
var errorSound = LK.getSound('error');
if (errorSound && typeof errorSound.play === "function") errorSound.play();
}
}
if (lives <= 0) {
gameActive = false;
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
// Reset player/ghosts only
resetActors();
}
// --- Helper: next level ---
function nextLevel() {
level++;
// Increase ghost speed a bit, but keep within difficulty bounds
for (var i = 0; i < ghosts.length; i++) {
ghosts[i].speed = getGhostSpeed() + (level - 1) * 0.5; // Slightly increase per level, but base on difficulty
}
startLevel();
}
// --- Helper: frighten ghosts ---
function frightenGhosts() {
for (var i = 0; i < ghosts.length; i++) {
ghosts[i].setMode('frightened');
ghosts[i].frightenedTicks = frightenedTicksMax;
}
}
// --- Helper: check collision (circle) ---
function circlesCollide(a, b) {
var dx = a.x - b.x,
dy = a.y - b.y;
var r = a.radius + b.radius;
return dx * dx + dy * dy < r * r;
}
// --- Input Handling (swipe) ---
var touchStart = null;
game.down = function (x, y, obj) {
touchStart = {
x: x,
y: y
};
};
game.up = function (x, y, obj) {
if (!touchStart) return;
var dx = x - touchStart.x,
dy = y - touchStart.y;
if (Math.abs(dx) > Math.abs(dy)) {
if (dx > 30) player.setDir(1, 0);else if (dx < -30) player.setDir(-1, 0);
} else {
if (dy > 30) player.setDir(0, 1);else if (dy < -30) player.setDir(0, -1);
}
touchStart = null;
};
// --- Main Update Loop ---
// --- Ghost Pathfinding Timer ---
// Ghosts will check the player's position and update their path every 0.5 seconds (30 ticks)
var ghostPathTimer = 0;
game.update = function () {
if (!gameActive) return;
// Update player
if (player) player.update();
// --- Ghost Pathfinding: update every 0.5s (30 ticks) ---
ghostPathTimer++;
if (ghostPathTimer >= 30) {
ghostPathTimer = 0;
for (var gi = 0; gi < ghosts.length; gi++) {
var ghost = ghosts[gi];
// Only chase if not frightened
if (ghost.mode !== 'frightened') {
// Find shortest path to player using BFS
var startTile = ghost.getTile();
var targetTile = player.getTile();
var queue = [];
var visited = [];
for (var y = 0; y < mazeRows; y++) {
visited[y] = [];
for (var x = 0; x < mazeCols; x++) {
visited[y][x] = false;
}
}
queue.push({
x: startTile.x,
y: startTile.y,
path: []
});
visited[startTile.y][startTile.x] = true;
var found = false;
var path = [];
var dirs = [{
x: 0,
y: -1
},
// up
{
x: 1,
y: 0
},
// right
{
x: 0,
y: 1
},
// down
{
x: -1,
y: 0
} // left
];
while (queue.length > 0 && !found) {
var node = queue.shift();
if (node.x === targetTile.x && node.y === targetTile.y) {
path = node.path;
found = true;
break;
}
for (var d = 0; d < dirs.length; d++) {
var nx = node.x + dirs[d].x;
var ny = node.y + dirs[d].y;
// Wrap horizontally (tunnel)
if (nx < 0) nx = mazeCols - 1;
if (nx >= mazeCols) nx = 0;
if (ny < 0 || ny >= mazeRows) continue;
if (mazeData[ny][nx] === 1) continue; // wall
if (visited[ny][nx]) continue;
visited[ny][nx] = true;
queue.push({
x: nx,
y: ny,
path: node.path.concat([{
x: nx,
y: ny
}])
});
}
}
// If found, set direction toward first step in path
if (found && path.length > 0) {
var nextStep = path[0];
var dx = nextStep.x - startTile.x;
var dy = nextStep.y - startTile.y;
// Handle tunnel wrap
if (dx > 1) dx = -1;
if (dx < -1) dx = 1;
ghost.dir = {
x: dx,
y: dy
};
} else {
// If blocked, pick a random open direction
var curTile = ghost.getTile();
var options = [];
for (var d = 0; d < dirs.length; d++) {
var nx = curTile.x + dirs[d].x;
var ny = curTile.y + dirs[d].y;
if (nx < 0) nx = mazeCols - 1;
if (nx >= mazeCols) nx = 0;
if (ny < 0 || ny >= mazeRows) continue;
if (mazeData[ny][nx] === 1) continue;
options.push(dirs[d]);
}
if (options.length > 0) {
var idx = Math.floor(Math.random() * options.length);
ghost.dir = {
x: options[idx].x,
y: options[idx].y
};
}
}
}
}
}
// Update ghosts
for (var i = 0; i < ghosts.length; i++) ghosts[i].update();
// --- Collisions: Player <-> Dots ---
for (var i = dots.length - 1; i >= 0; i--) {
if (circlesCollide(player, dots[i])) {
score += 10;
dots[i].destroy();
dots.splice(i, 1);
updateGUI();
// Play dotBeep if available, otherwise play beep
var dotBeepSound = LK.getSound('dotBeep');
if (dotBeepSound && typeof dotBeepSound.play === "function") {
dotBeepSound.play();
} else {
var beepSound = LK.getSound('beep');
if (beepSound && typeof beepSound.play === "function") beepSound.play();
}
}
}
// --- Collisions: Player <-> PowerDots ---
// Find the blue powerDot (assume only one, at most)
var bluePowerDot = null;
for (var i = 0; i < powerDots.length; i++) {
// The blue powerDot is the one with color 0x00ffff (from asset definition)
// We can check by checking the attached asset's tint or just assume the first is blue if only one
// For robustness, check radius (blue is 40, others could be different)
if (powerDots[i].radius === 20) {
// 40/2 = 20
bluePowerDot = powerDots[i];
break;
}
}
// Find the red powerDot (assume only one, at most)
var redPowerDot = null;
for (var i = 0; i < powerDots.length; i++) {
if (powerDots[i] !== bluePowerDot) {
redPowerDot = powerDots[i];
break;
}
}
// Now handle collisions for all powerDots
for (var i = powerDots.length - 1; i >= 0; i--) {
if (circlesCollide(player, powerDots[i])) {
score += 50;
// Store respawn position before destroying
var respawnX = powerDots[i].x;
var respawnY = powerDots[i].y;
powerDots[i].destroy();
powerDots.splice(i, 1);
updateGUI();
frightenGhosts();
// Play dotBeep if available, otherwise play beep
var dotBeepSound = LK.getSound('dotBeep');
if (dotBeepSound && typeof dotBeepSound.play === "function") {
dotBeepSound.play();
} else {
var beepSound = LK.getSound('beep');
if (beepSound && typeof beepSound.play === "function") beepSound.play();
}
// Respawn powerDot at same location after 10 seconds (600 ticks)
(function (x, y) {
LK.setTimeout(function () {
// Only respawn if the game is still active and not already a powerDot at this location
if (gameActive) {
// Check if a powerDot already exists at this location (avoid duplicates)
var exists = false;
for (var j = 0; j < powerDots.length; j++) {
if (Math.abs(powerDots[j].x - x) < 1 && Math.abs(powerDots[j].y - y) < 1) {
exists = true;
break;
}
}
if (!exists) {
var pd = new PowerDot();
pd.x = x;
pd.y = y;
game.addChild(pd);
powerDots.push(pd);
}
}
}, 10000); // 10 seconds
})(respawnX, respawnY);
}
}
// --- Collisions: Player <-> Ghosts ---
for (var i = 0; i < ghosts.length; i++) {
if (circlesCollide(player, ghosts[i])) {
if (ghosts[i].mode === 'frightened') {
score += 200;
ghosts[i].setMode('chase');
ghosts[i].moveToTile({
x: 9,
y: 6
}); // send to ghost start
updateGUI();
} else {
loseLife();
return;
}
}
}
// --- Win Condition: all dots eaten ---
if (dots.length === 0 && powerDots.length === 0) {
gameActive = false;
LK.effects.flashScreen(0x00ff00, 1000);
LK.showYouWin();
// Change mazeData for new level
if (level === 1) {
// Level 2: new maze layout, more open and with more power dots
mazeData = [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1], [1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1], [1, 2, 1, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 1, 2, 1], [1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 1, 1, 5, 1, 1, 1, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1], [1, 2, 2, 2, 1, 2, 2, 2, 1, 4, 1, 2, 2, 2, 1, 2, 2, 2, 1], [1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1], [1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1], [1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1], [1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1], [1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1], [1, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]];
mazeRows = mazeData.length;
mazeCols = mazeData[0].length;
mazeWidth = mazeCols * tileSize;
mazeHeight = mazeRows * tileSize;
mazeOffsetX = Math.floor((2048 - mazeWidth) / 2);
mazeOffsetY = Math.floor((2732 - mazeHeight) / 2);
}
nextLevel();
}
};
// --- Difficulty Selection Menu ---
var menuContainer = new Container();
game.addChild(menuContainer);
// Center of screen
var centerX = 2048 / 2;
var centerY = 2732 / 2;
// Button style
function makeButton(label, y, selected) {
var btn = new Container();
var bg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 2.2,
tint: selected ? 0x44ff44 : 0x222222
});
btn.addChild(bg);
var txt = new Text2(label, {
size: 120,
fill: selected ? "#222" : "#fff"
});
txt.anchor.set(0.5, 0.5);
btn.addChild(txt);
btn.x = centerX;
btn.y = y;
btn.bg = bg;
btn.txt = txt;
return btn;
}
// Track selected difficulty
var menuDifficulty = "Normal";
var easyBtn = makeButton("Easy", centerY - 220, false);
var normalBtn = makeButton("⚪ Normal", centerY, true);
var hardBtn = makeButton("Hard", centerY + 220, false);
menuContainer.addChild(easyBtn);
menuContainer.addChild(normalBtn);
menuContainer.addChild(hardBtn);
var startBtn = null;
// Button tap logic
function updateMenuButtons() {
easyBtn.bg.tint = menuDifficulty === "Easy" ? 0x44ff44 : 0x222222;
if (typeof easyBtn.txt.setFill === "function") {
easyBtn.txt.setFill(menuDifficulty === "Easy" ? "#222" : "#fff");
}
normalBtn.bg.tint = menuDifficulty === "Normal" ? 0x44ff44 : 0x222222;
if (typeof normalBtn.txt.setFill === "function") {
normalBtn.txt.setFill(menuDifficulty === "Normal" ? "#222" : "#fff");
}
hardBtn.bg.tint = menuDifficulty === "Hard" ? 0x44ff44 : 0x222222;
if (typeof hardBtn.txt.setFill === "function") {
hardBtn.txt.setFill(menuDifficulty === "Hard" ? "#222" : "#fff");
}
}
easyBtn.down = function (x, y, obj) {
menuDifficulty = "Easy";
updateMenuButtons();
if (!startBtn) showStartBtn();
};
normalBtn.down = function (x, y, obj) {
menuDifficulty = "Normal";
updateMenuButtons();
if (!startBtn) showStartBtn();
};
hardBtn.down = function (x, y, obj) {
menuDifficulty = "Hard";
updateMenuButtons();
if (!startBtn) showStartBtn();
};
function showStartBtn() {
startBtn = makeButton("Start Game", centerY + 500, false);
startBtn.bg.tint = 0x0080ff;
if (typeof startBtn.txt.setFill === "function") {
startBtn.txt.setFill("#fff");
}
menuContainer.addChild(startBtn);
startBtn.down = function (x, y, obj) {
// Set global difficulty and enemy speed
if (menuDifficulty === "Easy") {
difficulty = "Easy";
} else if (menuDifficulty === "Hard") {
difficulty = "Hard";
} else {
difficulty = "Normal";
}
// Set music per difficulty
if (difficulty === "Easy") {
LK.playMusic('calmTrack', {
loop: true
});
} else if (difficulty === "Hard") {
LK.playMusic('intenseTrack', {
loop: true
});
} else {
LK.playMusic('normalTrack', {
loop: true
});
}
// Hide menu
menuContainer.visible = false;
// Update difficulty label
diffTxt.setText('Difficulty: ' + difficulty);
// Start game
startLevel();
};
}
// Only show menu at start
gameActive = false;
// Hide GUI elements until game starts
scoreTxt.visible = false;
livesTxt.visible = false;
diffTxt.visible = false;
// Show GUI when game starts
var oldStartLevel = startLevel;
startLevel = function startLevel() {
scoreTxt.visible = true;
livesTxt.visible = true;
diffTxt.visible = true;
oldStartLevel();
};
// --- Settings Popup for In-Game Difficulty Change ---
var settingsPopup = new Container();
settingsPopup.visible = false;
game.addChild(settingsPopup);
// Dim background
var dimBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 30,
scaleY: 40,
tint: 0x000000
});
dimBg.alpha = 0.45;
dimBg.x = centerX;
dimBg.y = centerY;
settingsPopup.addChild(dimBg);
// Popup box
var popupBox = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 7,
scaleY: 7,
tint: 0x222222
});
popupBox.x = centerX;
popupBox.y = centerY;
settingsPopup.addChild(popupBox);
// Popup label
var popupLabel = new Text2("Change Difficulty", {
size: 90,
fill: "#fff"
});
popupLabel.anchor.set(0.5, 0.5);
popupLabel.x = centerX;
popupLabel.y = centerY - 260;
settingsPopup.addChild(popupLabel);
// Popup buttons
function makePopupBtn(label, y, selected) {
var btn = new Container();
var bg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 2.2,
tint: selected ? 0x44ff44 : 0x222222
});
btn.addChild(bg);
var txt = new Text2(label, {
size: 120,
fill: selected ? "#222" : "#fff"
});
txt.anchor.set(0.5, 0.5);
btn.addChild(txt);
btn.x = centerX;
btn.y = y;
btn.bg = bg;
btn.txt = txt;
return btn;
}
var popupEasyBtn = makePopupBtn("Easy", centerY - 100, false);
var popupNormalBtn = makePopupBtn("⚪ Normal", centerY + 60, true);
var popupHardBtn = makePopupBtn("Hard", centerY + 220, false);
settingsPopup.addChild(popupEasyBtn);
settingsPopup.addChild(popupNormalBtn);
settingsPopup.addChild(popupHardBtn);
// Helper to update popup button visuals
function updatePopupBtns() {
popupEasyBtn.bg.tint = difficulty === "Easy" ? 0x44ff44 : 0x222222;
if (typeof popupEasyBtn.txt.setFill === "function") {
popupEasyBtn.txt.setFill(difficulty === "Easy" ? "#222" : "#fff");
}
popupNormalBtn.bg.tint = difficulty === "Normal" ? 0x44ff44 : 0x222222;
if (typeof popupNormalBtn.txt.setFill === "function") {
popupNormalBtn.txt.setFill(difficulty === "Normal" ? "#222" : "#fff");
}
popupHardBtn.bg.tint = difficulty === "Hard" ? 0x44ff44 : 0x222222;
if (typeof popupHardBtn.txt.setFill === "function") {
popupHardBtn.txt.setFill(difficulty === "Hard" ? "#222" : "#fff");
}
}
// Popup logic: change difficulty and apply instantly (no pause/resume)
function popupSetDifficulty(newDiff) {
if (difficulty === newDiff) return;
difficulty = newDiff;
diffTxt.setText('Difficulty: ' + difficulty);
// Update ghost speed immediately
for (var i = 0; i < ghosts.length; i++) {
ghosts[i].speed = getGhostSpeed();
}
// Change music if needed
if (difficulty === "Easy") {
LK.playMusic('calmTrack', {
loop: true
});
} else if (difficulty === "Hard") {
LK.playMusic('intenseTrack', {
loop: true
});
} else {
LK.playMusic('normalTrack', {
loop: true
});
}
updatePopupBtns();
settingsPopup.visible = false;
}
// Button handlers
popupEasyBtn.down = function () {
popupSetDifficulty("Easy");
};
popupNormalBtn.down = function () {
popupSetDifficulty("Normal");
};
popupHardBtn.down = function () {
popupSetDifficulty("Hard");
};
// Settings button handler
settingsBtn.down = function () {
if (!gameActive) return;
settingsPopup.visible = true;
updatePopupBtns();
};
// Allow closing popup by tapping dim background (does not change difficulty)
dimBg.down = function () {
settingsPopup.visible = false;
}; ===================================================================
--- original.js
+++ change.js
@@ -353,12 +353,12 @@
/****
* Game Code
****/
-// Simple 19x21 maze (Pac-Man like, but smaller for MVP)
-// 0: empty, 1: wall, 2: dot, 3: power dot, 4: player start, 5: ghost start, 6: tunnel
-// --- Maze Layout ---
// Example: A new, more open map with a different wall and dot pattern
+// --- Maze Layout ---
+// 0: empty, 1: wall, 2: dot, 3: power dot, 4: player start, 5: ghost start, 6: tunnel
+// Simple 19x21 maze (Pac-Man like, but smaller for MVP)
var mazeData = [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1], [1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1], [1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1], [1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 1, 1, 5, 1, 1, 1, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1], [1, 2, 2, 2, 1, 2, 2, 2, 1, 4, 1, 2, 2, 2, 1, 2, 2, 2, 1], [1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1], [1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1], [1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1], [1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1], [1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1], [1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1], [1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]];
var mazeRows = mazeData.length;
var mazeCols = mazeData[0].length;
// --- Maze Rendering ---
@@ -1049,9 +1049,9 @@
if (typeof popupHardBtn.txt.setFill === "function") {
popupHardBtn.txt.setFill(difficulty === "Hard" ? "#222" : "#fff");
}
}
-// Popup logic: change difficulty and resume after 1s
+// Popup logic: change difficulty and apply instantly (no pause/resume)
function popupSetDifficulty(newDiff) {
if (difficulty === newDiff) return;
difficulty = newDiff;
diffTxt.setText('Difficulty: ' + difficulty);
@@ -1073,13 +1073,9 @@
loop: true
});
}
updatePopupBtns();
- // Resume game after 1s
- LK.setTimeout(function () {
- settingsPopup.visible = false;
- gameActive = true;
- }, 1000);
+ settingsPopup.visible = false;
}
// Button handlers
popupEasyBtn.down = function () {
popupSetDifficulty("Easy");
@@ -1094,11 +1090,9 @@
settingsBtn.down = function () {
if (!gameActive) return;
settingsPopup.visible = true;
updatePopupBtns();
- gameActive = false;
};
// Allow closing popup by tapping dim background (does not change difficulty)
dimBg.down = function () {
settingsPopup.visible = false;
- gameActive = true;
};
\ No newline at end of file