/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Baldi = Container.expand(function () {
var self = Container.call(this);
var baldiGraphics = self.attachAsset('baldiShape', {
anchorX: 0.5,
anchorY: 0.5
});
self.baseSpeed = 2;
self.speed = self.baseSpeed;
self.targetX = 0;
self.targetY = 0;
self.angerLevel = 0;
self.lastRulerSound = 0;
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
self.update = function () {
// Move towards player
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
// Normalize direction
dx = dx / distance;
dy = dy / distance;
// Apply movement with current speed
self.x += dx * self.speed;
self.y += dy * self.speed;
// Play ruler slap sound periodically
var currentTime = Date.now();
if (currentTime - self.lastRulerSound > 1500 / (1 + self.angerLevel * 0.2)) {
LK.getSound('ruler').play();
self.lastRulerSound = currentTime;
}
}
};
self.increaseAnger = function () {
self.angerLevel++;
self.speed = self.baseSpeed + self.angerLevel * 0.5;
tween(baldiGraphics, {
tint: 0xff0000
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(baldiGraphics, {
tint: 0xffffff
}, {
duration: 500,
easing: tween.easeIn
});
}
});
};
return self;
});
var Door = Container.expand(function () {
var self = Container.call(this);
var doorGraphics = self.attachAsset('doorShape', {
anchorX: 0.5,
anchorY: 0.5
});
self.isLocked = false;
self.isExit = false;
self.setAsExit = function () {
self.isExit = true;
self.isLocked = true;
doorGraphics.destroy();
var exitDoorGraphics = self.attachAsset('exitDoorShape', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
};
return self;
});
var Notebook = Container.expand(function () {
var self = Container.call(this);
var notebookGraphics = self.attachAsset('notebookShape', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
self.problems = [];
self.generateProblems = function () {
self.problems = [];
for (var i = 0; i < 3; i++) {
var difficulty = game.notebooksCollected;
var problem;
if (difficulty < 2) {
// Simple addition problems
var a = Math.floor(Math.random() * 10) + 1;
var b = Math.floor(Math.random() * 10) + 1;
problem = {
question: a + " + " + b + " = ?",
answer: a + b
};
} else if (difficulty < 4) {
// Medium difficulty problems (addition/subtraction)
var a = Math.floor(Math.random() * 20) + 1;
var b = Math.floor(Math.random() * 10) + 1;
var isAddition = Math.random() > 0.5;
if (isAddition) {
problem = {
question: a + " + " + b + " = ?",
answer: a + b
};
} else {
// Ensure a > b for subtraction
if (a < b) {
var temp = a;
a = b;
b = temp;
}
problem = {
question: a + " - " + b + " = ?",
answer: a - b
};
}
} else {
// Hard problems (multiplication, division)
if (Math.random() > 0.5) {
// Multiplication
var a = Math.floor(Math.random() * 12) + 1;
var b = Math.floor(Math.random() * 12) + 1;
problem = {
question: a + " × " + b + " = ?",
answer: a * b
};
} else {
// Division (make sure it's clean division)
var b = Math.floor(Math.random() * 10) + 1;
var result = Math.floor(Math.random() * 10) + 1;
var a = b * result;
problem = {
question: a + " ÷ " + b + " = ?",
answer: result
};
}
}
// Add occasional trick questions in later notebooks
if (difficulty > 3 && Math.random() < 0.2) {
problem = {
question: Math.random() < 0.5 ? "What is Baldi's favorite number?" : "What sound does a bee make?",
answer: "IMPOSSIBLE",
isTrick: true
};
}
self.problems.push(problem);
}
return self.problems;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('playerShape', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 7;
self.maxStamina = 100;
self.stamina = self.maxStamina;
self.isRunning = false;
self.lastFootstepTime = 0;
self.move = function (dx, dy) {
var moveSpeed = self.speed;
if (self.isRunning && self.stamina > 0) {
moveSpeed *= 1.8;
self.stamina -= 0.8;
if (self.stamina < 0) {
self.stamina = 0;
}
} else {
// Recover stamina when not running
if (!self.isRunning && self.stamina < self.maxStamina) {
self.stamina += 0.3;
if (self.stamina > self.maxStamina) {
self.stamina = self.maxStamina;
}
}
}
// Normalize diagonal movement
if (dx !== 0 && dy !== 0) {
var length = Math.sqrt(dx * dx + dy * dy);
dx = dx / length;
dy = dy / length;
}
// Apply movement
var newX = self.x + dx * moveSpeed;
var newY = self.y + dy * moveSpeed;
// Wall collision will be checked in the game.update method
self.x = newX;
self.y = newY;
// Play footstep sound periodically while moving
if (dx !== 0 || dy !== 0) {
var currentTime = Date.now();
if (currentTime - self.lastFootstepTime > (self.isRunning ? 250 : 350)) {
LK.getSound('footstep').play();
self.lastFootstepTime = currentTime;
}
}
};
self.collidesWith = function (obj) {
return self.intersects(obj);
};
return self;
});
var Principal = Container.expand(function () {
var self = Container.call(this);
var principalGraphics = self.attachAsset('principalShape', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3;
self.patrolPoints = [];
self.currentPatrolIndex = 0;
self.waitCounter = 0;
self.waitTime = 60; // frames to wait at each patrol point
self.isChasing = false;
self.chasedPlayer = null;
self.detentionDuration = 180; // 3 seconds at 60fps
self.setPatrolPoints = function (points) {
self.patrolPoints = points;
if (points.length > 0) {
self.x = points[0].x;
self.y = points[0].y;
}
return self;
};
self.update = function () {
if (self.isChasing && self.chasedPlayer) {
// Chase the running player
var dx = self.chasedPlayer.x - self.x;
var dy = self.chasedPlayer.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 40) {
// Caught the player - put in detention
self.isChasing = false;
game.putPlayerInDetention();
return;
}
// Move towards player
if (distance > 5) {
dx = dx / distance;
dy = dy / distance;
self.x += dx * (self.speed * 1.2); // Slightly faster when chasing
self.y += dy * (self.speed * 1.2);
}
return;
}
// Regular patrol behavior
if (self.patrolPoints.length === 0) {
return;
}
if (self.waitCounter > 0) {
self.waitCounter--;
return;
}
var targetPoint = self.patrolPoints[self.currentPatrolIndex];
var dx = targetPoint.x - self.x;
var dy = targetPoint.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
// Reached patrol point, wait and then move to next
self.waitCounter = self.waitTime;
self.currentPatrolIndex = (self.currentPatrolIndex + 1) % self.patrolPoints.length;
} else {
// Move towards patrol point
dx = dx / distance;
dy = dy / distance;
self.x += dx * self.speed;
self.y += dy * self.speed;
}
};
self.startChasing = function (player) {
self.isChasing = true;
self.chasedPlayer = player;
LK.getSound('detention').play();
};
self.stopChasing = function () {
self.isChasing = false;
self.chasedPlayer = null;
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphics = self.attachAsset('wallShape', {
anchorX: 0.5,
anchorY: 0.5
});
self.checkCollision = function (player) {
return self.intersects(player);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x333333
});
/****
* Game Code
****/
// Game state variables
game.player = null;
game.baldi = null;
game.principal = null;
game.notebooks = [];
game.walls = [];
game.doors = [];
game.notebooksCollected = 0;
game.totalNotebooks = 7;
game.isShowingProblem = false;
game.currentNotebook = null;
game.currentProblemIndex = 0;
game.gameState = "playing"; // playing, detention, mathProblem, gameOver, win
game.detentionTimer = 0;
game.movementDirection = {
x: 0,
y: 0
};
game.runButtonPressed = false;
game.exitDoor = null;
// UI Elements
var staminaBarBg = LK.getAsset('staminaBarBg', {
anchorX: 0,
anchorY: 0,
x: 30,
y: 30
});
var staminaBarFg = LK.getAsset('staminaBarFg', {
anchorX: 0,
anchorY: 0,
x: 30,
y: 30,
width: 400 // Will be updated based on stamina
});
var notebookCountText = new Text2('0/' + game.totalNotebooks + ' Notebooks', {
size: 40,
fill: 0xFFFFFF
});
notebookCountText.anchor.set(0, 0);
var problemText = new Text2('', {
size: 60,
fill: 0xFFFFFF
});
problemText.anchor.set(0.5, 0.5);
var answerInput = new Text2('', {
size: 50,
fill: 0xFFFFFF
});
answerInput.anchor.set(0.5, 0.5);
var promptText = new Text2('Tap to enter your answer', {
size: 30,
fill: 0xAAAAAA
});
promptText.anchor.set(0.5, 0.5);
var currentInputText = '';
// Initialize map
function initializeMap() {
// Create school walls
var mapWidth = 2000;
var mapHeight = 2600;
var wallThickness = 40;
// Outer walls
createWall(mapWidth / 2, wallThickness / 2, mapWidth, wallThickness); // Top
createWall(mapWidth / 2, mapHeight - wallThickness / 2, mapWidth, wallThickness); // Bottom
createWall(wallThickness / 2, mapHeight / 2, wallThickness, mapHeight); // Left
createWall(mapWidth - wallThickness / 2, mapHeight / 2, wallThickness, mapHeight); // Right
// Inner walls - horizontal
createWall(mapWidth / 4, mapHeight / 4, mapWidth / 2, wallThickness);
createWall(mapWidth * 3 / 4, mapHeight / 4, mapWidth / 2, wallThickness);
createWall(mapWidth / 4, mapHeight / 2, mapWidth / 2, wallThickness);
createWall(mapWidth * 3 / 4, mapHeight / 2, mapWidth / 2, wallThickness);
createWall(mapWidth / 4, mapHeight * 3 / 4, mapWidth / 2, wallThickness);
createWall(mapWidth * 3 / 4, mapHeight * 3 / 4, mapWidth / 2, wallThickness);
// Inner walls - vertical
createWall(mapWidth / 4, mapHeight * 3 / 8, wallThickness, mapHeight / 4);
createWall(mapWidth / 2, mapHeight * 3 / 8, wallThickness, mapHeight / 4);
createWall(mapWidth * 3 / 4, mapHeight * 3 / 8, wallThickness, mapHeight / 4);
createWall(mapWidth / 4, mapHeight * 7 / 8, wallThickness, mapHeight / 4);
createWall(mapWidth / 2, mapHeight * 7 / 8, wallThickness, mapHeight / 4);
createWall(mapWidth * 3 / 4, mapHeight * 7 / 8, wallThickness, mapHeight / 4);
// Create doors
createDoor(mapWidth / 2, mapHeight / 4 + 80);
createDoor(mapWidth * 3 / 4, mapHeight / 4 + 80);
createDoor(mapWidth / 4, mapHeight / 2 + 80);
createDoor(mapWidth / 2, mapHeight / 2 + 80);
createDoor(mapWidth * 3 / 4, mapHeight * 3 / 4 - 80);
// Exit door
game.exitDoor = createDoor(mapWidth - 120, mapHeight / 2);
game.exitDoor.setAsExit();
// Create notebooks
createNotebook(mapWidth / 4 - 100, mapHeight / 4 - 100);
createNotebook(mapWidth * 3 / 4 + 100, mapHeight / 4 - 100);
createNotebook(mapWidth / 4 - 100, mapHeight / 2 + 100);
createNotebook(mapWidth / 2 + 100, mapHeight / 2 - 100);
createNotebook(mapWidth * 3 / 4 + 100, mapHeight * 3 / 4 - 100);
createNotebook(mapWidth / 4 - 100, mapHeight * 3 / 4 + 100);
createNotebook(mapWidth * 3 / 4 + 100, mapHeight - 200);
// Create the player
game.player = new Player();
game.player.x = 120;
game.player.y = 120;
game.addChild(game.player);
// Create Baldi
game.baldi = new Baldi();
game.baldi.x = mapWidth / 2;
game.baldi.y = mapHeight / 2;
game.addChild(game.baldi);
// Create Principal
game.principal = new Principal();
game.principal.setPatrolPoints([{
x: mapWidth / 4,
y: mapHeight / 4
}, {
x: mapWidth * 3 / 4,
y: mapHeight / 4
}, {
x: mapWidth * 3 / 4,
y: mapHeight * 3 / 4
}, {
x: mapWidth / 4,
y: mapHeight * 3 / 4
}]);
game.addChild(game.principal);
}
function createWall(x, y, width, height) {
var wall = new Wall();
wall.x = x;
wall.y = y;
wall.width = width;
wall.height = height;
game.walls.push(wall);
game.addChild(wall);
return wall;
}
function createDoor(x, y) {
var door = new Door();
door.x = x;
door.y = y;
game.doors.push(door);
game.addChild(door);
return door;
}
function createNotebook(x, y) {
var notebook = new Notebook();
notebook.x = x;
notebook.y = y;
notebook.generateProblems();
game.notebooks.push(notebook);
game.addChild(notebook);
return notebook;
}
function setupUI() {
// Add stamina bar to GUI
LK.gui.addChild(staminaBarBg);
LK.gui.addChild(staminaBarFg);
// Add notebook counter
notebookCountText.x = 30;
notebookCountText.y = 80;
LK.gui.addChild(notebookCountText);
// Set up problem UI (not visible initially)
problemText.x = 1024;
problemText.y = 1200;
LK.gui.addChild(problemText);
answerInput.x = 1024;
answerInput.y = 1300;
LK.gui.addChild(answerInput);
promptText.x = 1024;
promptText.y = 1400;
LK.gui.addChild(promptText);
// Hide problem UI initially
toggleProblemUI(false);
}
function toggleProblemUI(show) {
problemText.visible = show;
answerInput.visible = show;
promptText.visible = show;
game.isShowingProblem = show;
}
function showNextProblem() {
if (!game.currentNotebook) {
return;
}
var problems = game.currentNotebook.problems;
if (game.currentProblemIndex >= problems.length) {
// All problems answered, collect notebook
collectNotebook();
return;
}
var problem = problems[game.currentProblemIndex];
problemText.setText(problem.question);
answerInput.setText("");
currentInputText = "";
toggleProblemUI(true);
}
function collectNotebook() {
if (!game.currentNotebook) {
return;
}
game.currentNotebook.collected = true;
game.notebooksCollected++;
notebookCountText.setText(game.notebooksCollected + '/' + game.totalNotebooks + ' Notebooks');
LK.getSound('collect').play();
// Check if all notebooks are collected
if (game.notebooksCollected >= game.totalNotebooks) {
// Unlock exit
game.exitDoor.isLocked = false;
tween(game.exitDoor, {
tint: 0x00ff00
}, {
duration: 1000,
easing: tween.easeOut
});
}
toggleProblemUI(false);
game.gameState = "playing";
game.currentNotebook = null;
}
function submitAnswer() {
if (!game.currentNotebook) {
return;
}
var problems = game.currentNotebook.problems;
var problem = problems[game.currentProblemIndex];
var userAnswer = currentInputText.trim();
var isCorrect = false;
if (problem.isTrick) {
// Trick questions always wrong
isCorrect = false;
} else {
// Regular math problems
var numAnswer = parseInt(userAnswer);
isCorrect = !isNaN(numAnswer) && numAnswer === problem.answer;
}
if (isCorrect) {
LK.getSound('correct').play();
} else {
LK.getSound('wrong').play();
game.baldi.increaseAnger();
}
game.currentProblemIndex++;
LK.setTimeout(showNextProblem, 500);
}
function handlePlayerInput() {
if (game.gameState === "mathProblem") {
// Handle numeric input for math problems
return;
}
if (game.gameState === "detention") {
// No movement in detention
game.movementDirection.x = 0;
game.movementDirection.y = 0;
return;
}
// Apply movement
if (game.movementDirection.x !== 0 || game.movementDirection.y !== 0) {
game.player.move(game.movementDirection.x, game.movementDirection.y);
}
// Update player running state
game.player.isRunning = game.runButtonPressed && game.player.stamina > 0;
}
function checkCollisions() {
// Only check collisions if playing
if (game.gameState !== "playing") {
return;
}
// Wall collisions
for (var i = 0; i < game.walls.length; i++) {
var wall = game.walls[i];
if (game.player.collidesWith(wall)) {
// Simple bounce back
game.player.x -= game.movementDirection.x * game.player.speed * 1.5;
game.player.y -= game.movementDirection.y * game.player.speed * 1.5;
}
}
// Door interactions
for (var i = 0; i < game.doors.length; i++) {
var door = game.doors[i];
if (game.player.collidesWith(door)) {
if (door.isExit && !door.isLocked) {
// Win condition
LK.showYouWin();
return;
}
}
}
// Notebook collection
for (var i = 0; i < game.notebooks.length; i++) {
var notebook = game.notebooks[i];
if (!notebook.collected && game.player.collidesWith(notebook)) {
// Start math problem
game.currentNotebook = notebook;
game.currentProblemIndex = 0;
game.gameState = "mathProblem";
showNextProblem();
return;
}
}
// Baldi collision - game over
if (game.player.collidesWith(game.baldi)) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
// Principal detection
if (game.player.isRunning && !game.principal.isChasing && distanceBetween(game.player, game.principal) < 300) {
// Principal spots player running
game.principal.startChasing(game.player);
}
}
function putPlayerInDetention() {
game.gameState = "detention";
game.detentionTimer = 180; // 3 seconds at 60fps
LK.getSound('detention').play();
// Flash player
tween(game.player, {
alpha: 0.5
}, {
duration: 250,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(game.player, {
alpha: 1
}, {
duration: 250,
easing: tween.easeIn
});
}
});
}
function updateDetention() {
if (game.gameState !== "detention") {
return;
}
game.detentionTimer--;
if (game.detentionTimer <= 0) {
game.gameState = "playing";
game.principal.stopChasing();
}
}
function updateNPCs() {
// Update Baldi
game.baldi.setTarget(game.player.x, game.player.y);
game.baldi.update();
// Update Principal
game.principal.update();
}
function updateUI() {
// Update stamina bar
var staminaPercent = game.player.stamina / game.player.maxStamina;
staminaBarFg.width = 400 * staminaPercent;
// Update stamina bar color based on level
if (staminaPercent > 0.6) {
staminaBarFg.tint = 0x00ff00;
} else if (staminaPercent > 0.3) {
staminaBarFg.tint = 0xffff00;
} else {
staminaBarFg.tint = 0xff0000;
}
}
function distanceBetween(obj1, obj2) {
var dx = obj1.x - obj2.x;
var dy = obj1.y - obj2.y;
return Math.sqrt(dx * dx + dy * dy);
}
// Initialize game
initializeMap();
setupUI();
LK.playMusic('gameMusic');
// Set up input handlers
game.down = function (x, y, obj) {
// Handle input for math problems
if (game.gameState === "mathProblem") {
// Check if player tapped on a number
var gridSize = 120;
var startX = 804; // Start position for number grid (center of screen - grid width/2)
var startY = 1450;
// Calculate which button was pressed
var buttonX = Math.floor((x - startX) / gridSize);
var buttonY = Math.floor((y - startY) / gridSize);
if (buttonX >= 0 && buttonX < 3 && buttonY >= 0 && buttonY < 4) {
// Number buttons 1-9 and special buttons
var buttonValue = buttonX + buttonY * 3 + 1;
if (buttonValue <= 9) {
// Numbers 1-9
currentInputText += buttonValue;
} else if (buttonValue === 10) {
// Button 10 is "0"
currentInputText += "0";
} else if (buttonValue === 11) {
// Button 11 is "Backspace"
if (currentInputText.length > 0) {
currentInputText = currentInputText.substring(0, currentInputText.length - 1);
}
} else if (buttonValue === 12) {
// Button 12 is "Enter/Submit"
submitAnswer();
return;
}
answerInput.setText(currentInputText);
}
return;
}
// Handle movement controls
var centerX = 1024;
var centerY = 2200;
var dx = x - centerX;
var dy = y - centerY;
// Check if this is the run button
if (y < 2000 && x > 1700) {
game.runButtonPressed = true;
return;
}
// Determine movement direction based on touch position
var angle = Math.atan2(dy, dx);
// Normalize to 8 directions
game.movementDirection.x = 0;
game.movementDirection.y = 0;
if (angle < -Math.PI * 7 / 8 || angle > Math.PI * 7 / 8) {
game.movementDirection.x = -1; // Left
} else if (angle < -Math.PI * 5 / 8) {
game.movementDirection.x = -1; // Up-left
game.movementDirection.y = -1;
} else if (angle < -Math.PI * 3 / 8) {
game.movementDirection.y = -1; // Up
} else if (angle < -Math.PI * 1 / 8) {
game.movementDirection.x = 1; // Up-right
game.movementDirection.y = -1;
} else if (angle < Math.PI * 1 / 8) {
game.movementDirection.x = 1; // Right
} else if (angle < Math.PI * 3 / 8) {
game.movementDirection.x = 1; // Down-right
game.movementDirection.y = 1;
} else if (angle < Math.PI * 5 / 8) {
game.movementDirection.y = 1; // Down
} else if (angle < Math.PI * 7 / 8) {
game.movementDirection.x = -1; // Down-left
game.movementDirection.y = 1;
}
};
game.up = function (x, y, obj) {
// Check if this is the run button being released
if (y < 2000 && x > 1700) {
game.runButtonPressed = false;
return;
}
// Stop movement when touch is released
game.movementDirection.x = 0;
game.movementDirection.y = 0;
};
game.move = function (x, y, obj) {
// Update movement direction if the player is dragging
if (game.movementDirection.x !== 0 || game.movementDirection.y !== 0) {
game.down(x, y, obj);
}
};
// Main game update function
game.update = function () {
handlePlayerInput();
checkCollisions();
updateDetention();
updateNPCs();
updateUI();
// Draw number pad if showing math problem
if (game.isShowingProblem) {
// This would normally be done using actual UI elements,
// but for simplicity, we'll handle it in the update function
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Baldi = Container.expand(function () {
var self = Container.call(this);
var baldiGraphics = self.attachAsset('baldiShape', {
anchorX: 0.5,
anchorY: 0.5
});
self.baseSpeed = 2;
self.speed = self.baseSpeed;
self.targetX = 0;
self.targetY = 0;
self.angerLevel = 0;
self.lastRulerSound = 0;
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
self.update = function () {
// Move towards player
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
// Normalize direction
dx = dx / distance;
dy = dy / distance;
// Apply movement with current speed
self.x += dx * self.speed;
self.y += dy * self.speed;
// Play ruler slap sound periodically
var currentTime = Date.now();
if (currentTime - self.lastRulerSound > 1500 / (1 + self.angerLevel * 0.2)) {
LK.getSound('ruler').play();
self.lastRulerSound = currentTime;
}
}
};
self.increaseAnger = function () {
self.angerLevel++;
self.speed = self.baseSpeed + self.angerLevel * 0.5;
tween(baldiGraphics, {
tint: 0xff0000
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(baldiGraphics, {
tint: 0xffffff
}, {
duration: 500,
easing: tween.easeIn
});
}
});
};
return self;
});
var Door = Container.expand(function () {
var self = Container.call(this);
var doorGraphics = self.attachAsset('doorShape', {
anchorX: 0.5,
anchorY: 0.5
});
self.isLocked = false;
self.isExit = false;
self.setAsExit = function () {
self.isExit = true;
self.isLocked = true;
doorGraphics.destroy();
var exitDoorGraphics = self.attachAsset('exitDoorShape', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
};
return self;
});
var Notebook = Container.expand(function () {
var self = Container.call(this);
var notebookGraphics = self.attachAsset('notebookShape', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
self.problems = [];
self.generateProblems = function () {
self.problems = [];
for (var i = 0; i < 3; i++) {
var difficulty = game.notebooksCollected;
var problem;
if (difficulty < 2) {
// Simple addition problems
var a = Math.floor(Math.random() * 10) + 1;
var b = Math.floor(Math.random() * 10) + 1;
problem = {
question: a + " + " + b + " = ?",
answer: a + b
};
} else if (difficulty < 4) {
// Medium difficulty problems (addition/subtraction)
var a = Math.floor(Math.random() * 20) + 1;
var b = Math.floor(Math.random() * 10) + 1;
var isAddition = Math.random() > 0.5;
if (isAddition) {
problem = {
question: a + " + " + b + " = ?",
answer: a + b
};
} else {
// Ensure a > b for subtraction
if (a < b) {
var temp = a;
a = b;
b = temp;
}
problem = {
question: a + " - " + b + " = ?",
answer: a - b
};
}
} else {
// Hard problems (multiplication, division)
if (Math.random() > 0.5) {
// Multiplication
var a = Math.floor(Math.random() * 12) + 1;
var b = Math.floor(Math.random() * 12) + 1;
problem = {
question: a + " × " + b + " = ?",
answer: a * b
};
} else {
// Division (make sure it's clean division)
var b = Math.floor(Math.random() * 10) + 1;
var result = Math.floor(Math.random() * 10) + 1;
var a = b * result;
problem = {
question: a + " ÷ " + b + " = ?",
answer: result
};
}
}
// Add occasional trick questions in later notebooks
if (difficulty > 3 && Math.random() < 0.2) {
problem = {
question: Math.random() < 0.5 ? "What is Baldi's favorite number?" : "What sound does a bee make?",
answer: "IMPOSSIBLE",
isTrick: true
};
}
self.problems.push(problem);
}
return self.problems;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('playerShape', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 7;
self.maxStamina = 100;
self.stamina = self.maxStamina;
self.isRunning = false;
self.lastFootstepTime = 0;
self.move = function (dx, dy) {
var moveSpeed = self.speed;
if (self.isRunning && self.stamina > 0) {
moveSpeed *= 1.8;
self.stamina -= 0.8;
if (self.stamina < 0) {
self.stamina = 0;
}
} else {
// Recover stamina when not running
if (!self.isRunning && self.stamina < self.maxStamina) {
self.stamina += 0.3;
if (self.stamina > self.maxStamina) {
self.stamina = self.maxStamina;
}
}
}
// Normalize diagonal movement
if (dx !== 0 && dy !== 0) {
var length = Math.sqrt(dx * dx + dy * dy);
dx = dx / length;
dy = dy / length;
}
// Apply movement
var newX = self.x + dx * moveSpeed;
var newY = self.y + dy * moveSpeed;
// Wall collision will be checked in the game.update method
self.x = newX;
self.y = newY;
// Play footstep sound periodically while moving
if (dx !== 0 || dy !== 0) {
var currentTime = Date.now();
if (currentTime - self.lastFootstepTime > (self.isRunning ? 250 : 350)) {
LK.getSound('footstep').play();
self.lastFootstepTime = currentTime;
}
}
};
self.collidesWith = function (obj) {
return self.intersects(obj);
};
return self;
});
var Principal = Container.expand(function () {
var self = Container.call(this);
var principalGraphics = self.attachAsset('principalShape', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3;
self.patrolPoints = [];
self.currentPatrolIndex = 0;
self.waitCounter = 0;
self.waitTime = 60; // frames to wait at each patrol point
self.isChasing = false;
self.chasedPlayer = null;
self.detentionDuration = 180; // 3 seconds at 60fps
self.setPatrolPoints = function (points) {
self.patrolPoints = points;
if (points.length > 0) {
self.x = points[0].x;
self.y = points[0].y;
}
return self;
};
self.update = function () {
if (self.isChasing && self.chasedPlayer) {
// Chase the running player
var dx = self.chasedPlayer.x - self.x;
var dy = self.chasedPlayer.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 40) {
// Caught the player - put in detention
self.isChasing = false;
game.putPlayerInDetention();
return;
}
// Move towards player
if (distance > 5) {
dx = dx / distance;
dy = dy / distance;
self.x += dx * (self.speed * 1.2); // Slightly faster when chasing
self.y += dy * (self.speed * 1.2);
}
return;
}
// Regular patrol behavior
if (self.patrolPoints.length === 0) {
return;
}
if (self.waitCounter > 0) {
self.waitCounter--;
return;
}
var targetPoint = self.patrolPoints[self.currentPatrolIndex];
var dx = targetPoint.x - self.x;
var dy = targetPoint.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
// Reached patrol point, wait and then move to next
self.waitCounter = self.waitTime;
self.currentPatrolIndex = (self.currentPatrolIndex + 1) % self.patrolPoints.length;
} else {
// Move towards patrol point
dx = dx / distance;
dy = dy / distance;
self.x += dx * self.speed;
self.y += dy * self.speed;
}
};
self.startChasing = function (player) {
self.isChasing = true;
self.chasedPlayer = player;
LK.getSound('detention').play();
};
self.stopChasing = function () {
self.isChasing = false;
self.chasedPlayer = null;
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphics = self.attachAsset('wallShape', {
anchorX: 0.5,
anchorY: 0.5
});
self.checkCollision = function (player) {
return self.intersects(player);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x333333
});
/****
* Game Code
****/
// Game state variables
game.player = null;
game.baldi = null;
game.principal = null;
game.notebooks = [];
game.walls = [];
game.doors = [];
game.notebooksCollected = 0;
game.totalNotebooks = 7;
game.isShowingProblem = false;
game.currentNotebook = null;
game.currentProblemIndex = 0;
game.gameState = "playing"; // playing, detention, mathProblem, gameOver, win
game.detentionTimer = 0;
game.movementDirection = {
x: 0,
y: 0
};
game.runButtonPressed = false;
game.exitDoor = null;
// UI Elements
var staminaBarBg = LK.getAsset('staminaBarBg', {
anchorX: 0,
anchorY: 0,
x: 30,
y: 30
});
var staminaBarFg = LK.getAsset('staminaBarFg', {
anchorX: 0,
anchorY: 0,
x: 30,
y: 30,
width: 400 // Will be updated based on stamina
});
var notebookCountText = new Text2('0/' + game.totalNotebooks + ' Notebooks', {
size: 40,
fill: 0xFFFFFF
});
notebookCountText.anchor.set(0, 0);
var problemText = new Text2('', {
size: 60,
fill: 0xFFFFFF
});
problemText.anchor.set(0.5, 0.5);
var answerInput = new Text2('', {
size: 50,
fill: 0xFFFFFF
});
answerInput.anchor.set(0.5, 0.5);
var promptText = new Text2('Tap to enter your answer', {
size: 30,
fill: 0xAAAAAA
});
promptText.anchor.set(0.5, 0.5);
var currentInputText = '';
// Initialize map
function initializeMap() {
// Create school walls
var mapWidth = 2000;
var mapHeight = 2600;
var wallThickness = 40;
// Outer walls
createWall(mapWidth / 2, wallThickness / 2, mapWidth, wallThickness); // Top
createWall(mapWidth / 2, mapHeight - wallThickness / 2, mapWidth, wallThickness); // Bottom
createWall(wallThickness / 2, mapHeight / 2, wallThickness, mapHeight); // Left
createWall(mapWidth - wallThickness / 2, mapHeight / 2, wallThickness, mapHeight); // Right
// Inner walls - horizontal
createWall(mapWidth / 4, mapHeight / 4, mapWidth / 2, wallThickness);
createWall(mapWidth * 3 / 4, mapHeight / 4, mapWidth / 2, wallThickness);
createWall(mapWidth / 4, mapHeight / 2, mapWidth / 2, wallThickness);
createWall(mapWidth * 3 / 4, mapHeight / 2, mapWidth / 2, wallThickness);
createWall(mapWidth / 4, mapHeight * 3 / 4, mapWidth / 2, wallThickness);
createWall(mapWidth * 3 / 4, mapHeight * 3 / 4, mapWidth / 2, wallThickness);
// Inner walls - vertical
createWall(mapWidth / 4, mapHeight * 3 / 8, wallThickness, mapHeight / 4);
createWall(mapWidth / 2, mapHeight * 3 / 8, wallThickness, mapHeight / 4);
createWall(mapWidth * 3 / 4, mapHeight * 3 / 8, wallThickness, mapHeight / 4);
createWall(mapWidth / 4, mapHeight * 7 / 8, wallThickness, mapHeight / 4);
createWall(mapWidth / 2, mapHeight * 7 / 8, wallThickness, mapHeight / 4);
createWall(mapWidth * 3 / 4, mapHeight * 7 / 8, wallThickness, mapHeight / 4);
// Create doors
createDoor(mapWidth / 2, mapHeight / 4 + 80);
createDoor(mapWidth * 3 / 4, mapHeight / 4 + 80);
createDoor(mapWidth / 4, mapHeight / 2 + 80);
createDoor(mapWidth / 2, mapHeight / 2 + 80);
createDoor(mapWidth * 3 / 4, mapHeight * 3 / 4 - 80);
// Exit door
game.exitDoor = createDoor(mapWidth - 120, mapHeight / 2);
game.exitDoor.setAsExit();
// Create notebooks
createNotebook(mapWidth / 4 - 100, mapHeight / 4 - 100);
createNotebook(mapWidth * 3 / 4 + 100, mapHeight / 4 - 100);
createNotebook(mapWidth / 4 - 100, mapHeight / 2 + 100);
createNotebook(mapWidth / 2 + 100, mapHeight / 2 - 100);
createNotebook(mapWidth * 3 / 4 + 100, mapHeight * 3 / 4 - 100);
createNotebook(mapWidth / 4 - 100, mapHeight * 3 / 4 + 100);
createNotebook(mapWidth * 3 / 4 + 100, mapHeight - 200);
// Create the player
game.player = new Player();
game.player.x = 120;
game.player.y = 120;
game.addChild(game.player);
// Create Baldi
game.baldi = new Baldi();
game.baldi.x = mapWidth / 2;
game.baldi.y = mapHeight / 2;
game.addChild(game.baldi);
// Create Principal
game.principal = new Principal();
game.principal.setPatrolPoints([{
x: mapWidth / 4,
y: mapHeight / 4
}, {
x: mapWidth * 3 / 4,
y: mapHeight / 4
}, {
x: mapWidth * 3 / 4,
y: mapHeight * 3 / 4
}, {
x: mapWidth / 4,
y: mapHeight * 3 / 4
}]);
game.addChild(game.principal);
}
function createWall(x, y, width, height) {
var wall = new Wall();
wall.x = x;
wall.y = y;
wall.width = width;
wall.height = height;
game.walls.push(wall);
game.addChild(wall);
return wall;
}
function createDoor(x, y) {
var door = new Door();
door.x = x;
door.y = y;
game.doors.push(door);
game.addChild(door);
return door;
}
function createNotebook(x, y) {
var notebook = new Notebook();
notebook.x = x;
notebook.y = y;
notebook.generateProblems();
game.notebooks.push(notebook);
game.addChild(notebook);
return notebook;
}
function setupUI() {
// Add stamina bar to GUI
LK.gui.addChild(staminaBarBg);
LK.gui.addChild(staminaBarFg);
// Add notebook counter
notebookCountText.x = 30;
notebookCountText.y = 80;
LK.gui.addChild(notebookCountText);
// Set up problem UI (not visible initially)
problemText.x = 1024;
problemText.y = 1200;
LK.gui.addChild(problemText);
answerInput.x = 1024;
answerInput.y = 1300;
LK.gui.addChild(answerInput);
promptText.x = 1024;
promptText.y = 1400;
LK.gui.addChild(promptText);
// Hide problem UI initially
toggleProblemUI(false);
}
function toggleProblemUI(show) {
problemText.visible = show;
answerInput.visible = show;
promptText.visible = show;
game.isShowingProblem = show;
}
function showNextProblem() {
if (!game.currentNotebook) {
return;
}
var problems = game.currentNotebook.problems;
if (game.currentProblemIndex >= problems.length) {
// All problems answered, collect notebook
collectNotebook();
return;
}
var problem = problems[game.currentProblemIndex];
problemText.setText(problem.question);
answerInput.setText("");
currentInputText = "";
toggleProblemUI(true);
}
function collectNotebook() {
if (!game.currentNotebook) {
return;
}
game.currentNotebook.collected = true;
game.notebooksCollected++;
notebookCountText.setText(game.notebooksCollected + '/' + game.totalNotebooks + ' Notebooks');
LK.getSound('collect').play();
// Check if all notebooks are collected
if (game.notebooksCollected >= game.totalNotebooks) {
// Unlock exit
game.exitDoor.isLocked = false;
tween(game.exitDoor, {
tint: 0x00ff00
}, {
duration: 1000,
easing: tween.easeOut
});
}
toggleProblemUI(false);
game.gameState = "playing";
game.currentNotebook = null;
}
function submitAnswer() {
if (!game.currentNotebook) {
return;
}
var problems = game.currentNotebook.problems;
var problem = problems[game.currentProblemIndex];
var userAnswer = currentInputText.trim();
var isCorrect = false;
if (problem.isTrick) {
// Trick questions always wrong
isCorrect = false;
} else {
// Regular math problems
var numAnswer = parseInt(userAnswer);
isCorrect = !isNaN(numAnswer) && numAnswer === problem.answer;
}
if (isCorrect) {
LK.getSound('correct').play();
} else {
LK.getSound('wrong').play();
game.baldi.increaseAnger();
}
game.currentProblemIndex++;
LK.setTimeout(showNextProblem, 500);
}
function handlePlayerInput() {
if (game.gameState === "mathProblem") {
// Handle numeric input for math problems
return;
}
if (game.gameState === "detention") {
// No movement in detention
game.movementDirection.x = 0;
game.movementDirection.y = 0;
return;
}
// Apply movement
if (game.movementDirection.x !== 0 || game.movementDirection.y !== 0) {
game.player.move(game.movementDirection.x, game.movementDirection.y);
}
// Update player running state
game.player.isRunning = game.runButtonPressed && game.player.stamina > 0;
}
function checkCollisions() {
// Only check collisions if playing
if (game.gameState !== "playing") {
return;
}
// Wall collisions
for (var i = 0; i < game.walls.length; i++) {
var wall = game.walls[i];
if (game.player.collidesWith(wall)) {
// Simple bounce back
game.player.x -= game.movementDirection.x * game.player.speed * 1.5;
game.player.y -= game.movementDirection.y * game.player.speed * 1.5;
}
}
// Door interactions
for (var i = 0; i < game.doors.length; i++) {
var door = game.doors[i];
if (game.player.collidesWith(door)) {
if (door.isExit && !door.isLocked) {
// Win condition
LK.showYouWin();
return;
}
}
}
// Notebook collection
for (var i = 0; i < game.notebooks.length; i++) {
var notebook = game.notebooks[i];
if (!notebook.collected && game.player.collidesWith(notebook)) {
// Start math problem
game.currentNotebook = notebook;
game.currentProblemIndex = 0;
game.gameState = "mathProblem";
showNextProblem();
return;
}
}
// Baldi collision - game over
if (game.player.collidesWith(game.baldi)) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
// Principal detection
if (game.player.isRunning && !game.principal.isChasing && distanceBetween(game.player, game.principal) < 300) {
// Principal spots player running
game.principal.startChasing(game.player);
}
}
function putPlayerInDetention() {
game.gameState = "detention";
game.detentionTimer = 180; // 3 seconds at 60fps
LK.getSound('detention').play();
// Flash player
tween(game.player, {
alpha: 0.5
}, {
duration: 250,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(game.player, {
alpha: 1
}, {
duration: 250,
easing: tween.easeIn
});
}
});
}
function updateDetention() {
if (game.gameState !== "detention") {
return;
}
game.detentionTimer--;
if (game.detentionTimer <= 0) {
game.gameState = "playing";
game.principal.stopChasing();
}
}
function updateNPCs() {
// Update Baldi
game.baldi.setTarget(game.player.x, game.player.y);
game.baldi.update();
// Update Principal
game.principal.update();
}
function updateUI() {
// Update stamina bar
var staminaPercent = game.player.stamina / game.player.maxStamina;
staminaBarFg.width = 400 * staminaPercent;
// Update stamina bar color based on level
if (staminaPercent > 0.6) {
staminaBarFg.tint = 0x00ff00;
} else if (staminaPercent > 0.3) {
staminaBarFg.tint = 0xffff00;
} else {
staminaBarFg.tint = 0xff0000;
}
}
function distanceBetween(obj1, obj2) {
var dx = obj1.x - obj2.x;
var dy = obj1.y - obj2.y;
return Math.sqrt(dx * dx + dy * dy);
}
// Initialize game
initializeMap();
setupUI();
LK.playMusic('gameMusic');
// Set up input handlers
game.down = function (x, y, obj) {
// Handle input for math problems
if (game.gameState === "mathProblem") {
// Check if player tapped on a number
var gridSize = 120;
var startX = 804; // Start position for number grid (center of screen - grid width/2)
var startY = 1450;
// Calculate which button was pressed
var buttonX = Math.floor((x - startX) / gridSize);
var buttonY = Math.floor((y - startY) / gridSize);
if (buttonX >= 0 && buttonX < 3 && buttonY >= 0 && buttonY < 4) {
// Number buttons 1-9 and special buttons
var buttonValue = buttonX + buttonY * 3 + 1;
if (buttonValue <= 9) {
// Numbers 1-9
currentInputText += buttonValue;
} else if (buttonValue === 10) {
// Button 10 is "0"
currentInputText += "0";
} else if (buttonValue === 11) {
// Button 11 is "Backspace"
if (currentInputText.length > 0) {
currentInputText = currentInputText.substring(0, currentInputText.length - 1);
}
} else if (buttonValue === 12) {
// Button 12 is "Enter/Submit"
submitAnswer();
return;
}
answerInput.setText(currentInputText);
}
return;
}
// Handle movement controls
var centerX = 1024;
var centerY = 2200;
var dx = x - centerX;
var dy = y - centerY;
// Check if this is the run button
if (y < 2000 && x > 1700) {
game.runButtonPressed = true;
return;
}
// Determine movement direction based on touch position
var angle = Math.atan2(dy, dx);
// Normalize to 8 directions
game.movementDirection.x = 0;
game.movementDirection.y = 0;
if (angle < -Math.PI * 7 / 8 || angle > Math.PI * 7 / 8) {
game.movementDirection.x = -1; // Left
} else if (angle < -Math.PI * 5 / 8) {
game.movementDirection.x = -1; // Up-left
game.movementDirection.y = -1;
} else if (angle < -Math.PI * 3 / 8) {
game.movementDirection.y = -1; // Up
} else if (angle < -Math.PI * 1 / 8) {
game.movementDirection.x = 1; // Up-right
game.movementDirection.y = -1;
} else if (angle < Math.PI * 1 / 8) {
game.movementDirection.x = 1; // Right
} else if (angle < Math.PI * 3 / 8) {
game.movementDirection.x = 1; // Down-right
game.movementDirection.y = 1;
} else if (angle < Math.PI * 5 / 8) {
game.movementDirection.y = 1; // Down
} else if (angle < Math.PI * 7 / 8) {
game.movementDirection.x = -1; // Down-left
game.movementDirection.y = 1;
}
};
game.up = function (x, y, obj) {
// Check if this is the run button being released
if (y < 2000 && x > 1700) {
game.runButtonPressed = false;
return;
}
// Stop movement when touch is released
game.movementDirection.x = 0;
game.movementDirection.y = 0;
};
game.move = function (x, y, obj) {
// Update movement direction if the player is dragging
if (game.movementDirection.x !== 0 || game.movementDirection.y !== 0) {
game.down(x, y, obj);
}
};
// Main game update function
game.update = function () {
handlePlayerInput();
checkCollisions();
updateDetention();
updateNPCs();
updateUI();
// Draw number pad if showing math problem
if (game.isShowingProblem) {
// This would normally be done using actual UI elements,
// but for simplicity, we'll handle it in the update function
}
};