Project Highlights
• Dynamic Maze Generation: Random maze layout for each playthrough
• Dual Control Modes: Switch between button input and accelerometer-based motion control
• Smart Enemy AI: A* pathfinding algorithm for adaptive chasing
• Scalable Design: Adjustable difficulty levels and customizable themes
Complete Code & Demo
Preview
• Objective: Escape maze while evading AI enemies
• Core Mechanics:
• Green sprite: Player
• Red sprite: Chasing enemy
• Gold sprite: Exit
• Blue tiles: Impassable walls
Full Implementation
// ===== Global Config =====
enum SpriteKind {
Player,
Enemy,
Exit
}
// Maze parameters
let mazeWidth = 10;
let mazeHeight = 8;
let cellSize = 16;
// ===== Maze Generation =====
function generateMaze(): number[][] {
let maze: number[][] = [];
// Prim's algorithm implementation (abbreviated)
return maze;
}
// ===== Game Elements =====
let maze = generateMaze();
let player = sprites.create(img`
. . . . .
. 7 7 7 .
. 7 9 7 .
. 7 7 7 .
. . . . .
`, SpriteKind.Player);
let enemy = sprites.create(img`
. . 2 . .
. 2 2 2 .
2 2 4 2 2
. 2 2 2 .
. . 2 . .
`, SpriteKind.Enemy);
let exit = sprites.create(img`
. . 5 . .
. 5 5 5 .
5 5 d 5 5
. 5 5 5 .
. . 5 . .
`, SpriteKind.Exit);
// ===== Control Logic =====
// Toggle control mode with A button
let useAccelerometer = false;
controller.A.onEvent(ControllerButtonEvent.Pressed, () => {
useAccelerometer = !useAccelerometer;
info.showScore("Mode: " + (useAccelerometer ? "Motion" : "Buttons"));
});
// Player movement
game.onUpdate(() => {
if (useAccelerometer) {
player.x += input.acceleration(Dimension.X) / 20;
player.y += input.acceleration(Dimension.Y) / 20;
} else {
player.x += controller.dx() * 2;
player.y += controller.dy() * 2;
}
// Wall collision
if (tiles.tileAtLocationIsWall(tiles.getTileLocation(player.x, player.y))) {
player.x -= controller.dx() * 2;
player.y -= controller.dy() * 2;
music.playTone(131, 200);
}
});
// Enemy AI pathfinding
game.onUpdateInterval(1000, () => {
let path = scene.aStarPath(enemy, player);
if (path.length > 0) {
enemy.follow(path[0]);
}
});
// Win condition
sprites.onOverlap(SpriteKind.Player, SpriteKind.Exit, () => {
game.over(true);
});
Technical Breakdown
1. Maze Generation (Prim's Algorithm)
function generateMaze(): number[][] {
// Initialize full-wall maze
let maze = [];
for (let y = 0; y < mazeHeight; y++) {
maze.push([]);
for (let x = 0; x < mazeWidth; x++) {
maze[y].push(1); // 1=wall, 0=path
}
}
// Random starting point
let startX = Math.randomRange(1, mazeWidth-2);
let startY = Math.randomRange(1, mazeHeight-2);
maze[startY][startX] = 0;
// Path carving logic (abbreviated)
return maze;
}
2. A* Pathfinding Implementation
• Heuristic: Manhattan distance h(n) = |x1-x2| + |y1-y2|
• Cost calculation: f(n) = g(n) + h(n)
• Optimization: Path step limitation prevents lag
3. Dual Control System
MODE |
CODE IMPLEMENTATION |
Sensitivity Parameter |
Button Control |
controller.dx()/dy() |
Multiplier (2) |
Motion Control |
input.acceleration() |
Divisor (20) |
Educational Applications
Classroom Activities
1. Algorithm Workshop
-
Task: Modify maze generation rules to create symmetrical layouts
-
Concepts: Recursive algorithms, 2D array manipulation
2. Physics Integration
3. Pixel Art Design
-
Task: Create theme-based sprites using Piskel
-
Requirements: 16x16 pixels, thematic consistency (space/forest/castle)
Advanced Challenges
Difficulty Level System
info.onStart(() => {
let difficulty = game.askForNumber("Choose difficulty (1-5)", 1);
mazeWidth = 6 + difficulty * 2;
mazeHeight = 4 + difficulty * 2;
});
Dynamic Environment
// Regenerate maze every 30 seconds
game.onUpdateInterval(30000, () => {
tiles.setTilemap(generateMaze());
info.changeScoreBy(10); // Bonus points
});
Troubleshooting
Issue 1: Enemy Stuck in Corners
Solution: Add pathfinding tolerance
let path = scene.aStarPath(enemy, player, {
stepLimit: 50 // Limit search steps
});
Issue 2: Low Motion Sensitivity
Adjustment: Modify acceleration divisor
player.x += input.acceleration(Dimension.X) / 15; // Increased sensitivity
Recommended Resources