<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The Accountant - UCLA Anderson Adventure</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #2D68C4 0%, #1a1a2e 100%);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden;
}
.game-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.header {
color: #FFD100;
text-align: center;
padding: 10px;
}
.header h1 {
font-size: 28px;
text-shadow: 3px 3px 0 #2D68C4;
margin-bottom: 5px;
font-weight: 800;
}
.header .subtitle {
color: #fff;
font-size: 12px;
opacity: 0.9;
}
.stats-bar {
display: flex;
gap: 30px;
color: #fff;
font-size: 13px;
background: rgba(45, 104, 196, 0.8);
padding: 12px 25px;
border-radius: 10px;
border: 2px solid #FFD100;
}
.stat {
display: flex;
align-items: center;
gap: 8px;
}
.stat-icon {
font-size: 18px;
}
.stat-value {
font-weight: bold;
color: #FFD100;
}
#gameCanvas {
border: 4px solid #FFD100;
border-radius: 10px;
box-shadow: 0 0 40px rgba(255, 209, 0, 0.3);
}
.controls {
color: #87CEEB;
font-size: 11px;
text-align: center;
margin-top: 10px;
}
.question-display {
background: linear-gradient(135deg, #2D68C4 0%, #1e3a5f 100%);
border: 3px solid #FFD100;
border-radius: 10px;
padding: 15px 25px;
color: #fff;
font-size: 13px;
max-width: 800px;
text-align: center;
line-height: 1.6;
}
.question-label {
color: #FFD100;
font-size: 10px;
margin-bottom: 5px;
font-weight: bold;
letter-spacing: 1px;
}
/* Overlays */
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.95);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
}
.overlay.hidden {
display: none;
}
.menu {
text-align: center;
color: #fff;
max-width: 600px;
padding: 20px;
}
.menu h1 {
color: #FFD100;
font-size: 42px;
text-shadow: 4px 4px 0 #2D68C4;
margin-bottom: 5px;
}
.menu .game-subtitle {
color: #2D68C4;
font-size: 18px;
font-weight: bold;
background: #FFD100;
padding: 5px 20px;
border-radius: 20px;
display: inline-block;
margin-bottom: 20px;
}
.ucla-logo {
font-size: 24px;
color: #FFD100;
margin: 10px 0;
letter-spacing: 8px;
font-weight: 900;
}
.menu-character {
margin: 20px 0;
position: relative;
height: 120px;
}
.student-preview {
animation: walk 0.6s infinite;
}
@keyframes walk {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.instructions {
background: rgba(45, 104, 196, 0.3);
padding: 20px;
border-radius: 10px;
margin: 20px 0;
font-size: 12px;
line-height: 2;
text-align: left;
border: 2px solid rgba(255, 209, 0, 0.3);
}
.instructions h3 {
color: #FFD100;
margin-bottom: 10px;
text-align: center;
}
.key {
background: #FFD100;
color: #2D68C4;
padding: 4px 10px;
border-radius: 4px;
font-size: 11px;
font-weight: bold;
}
.start-btn {
background: linear-gradient(135deg, #FFD100 0%, #FFC107 100%);
color: #2D68C4;
border: none;
padding: 18px 50px;
font-size: 18px;
font-weight: bold;
cursor: pointer;
border-radius: 30px;
margin-top: 20px;
transition: transform 0.2s, box-shadow 0.2s;
text-transform: uppercase;
letter-spacing: 2px;
}
.start-btn:hover {
transform: scale(1.1);
box-shadow: 0 0 40px rgba(255, 209, 0, 0.6);
}
.level-select {
margin-top: 25px;
}
.level-select span {
color: #FFD100;
font-size: 13px;
font-weight: bold;
}
.level-btns {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 12px;
flex-wrap: wrap;
}
.level-btn {
background: rgba(45, 104, 196, 0.5);
color: #fff;
border: 2px solid #FFD100;
padding: 12px 18px;
font-size: 11px;
cursor: pointer;
border-radius: 10px;
transition: all 0.2s;
}
.level-btn:hover, .level-btn.active {
background: #FFD100;
color: #2D68C4;
font-weight: bold;
}
.level-btn .location {
font-size: 9px;
opacity: 0.8;
display: block;
margin-top: 3px;
}
/* End Screen */
.end-screen h2 {
font-size: 32px;
margin-bottom: 20px;
}
.end-screen.win h2 {
color: #FFD100;
}
.end-screen.lose h2 {
color: #f44336;
}
.final-stats {
font-size: 16px;
line-height: 2.5;
margin: 20px 0;
}
.concept-review {
background: rgba(45, 104, 196, 0.3);
padding: 15px;
border-radius: 10px;
margin: 20px auto;
font-size: 11px;
text-align: left;
max-height: 200px;
max-width: 500px;
overflow-y: auto;
border: 2px solid rgba(255, 209, 0, 0.3);
}
.concept-review h4 {
color: #FFD100;
margin-bottom: 10px;
text-align: center;
}
.concept-item {
margin: 8px 0;
padding: 10px;
background: rgba(0,0,0,0.3);
border-radius: 5px;
}
.concept-item.correct {
border-left: 4px solid #4caf50;
}
.concept-item.wrong {
border-left: 4px solid #f44336;
}
.bruins-cheer {
color: #FFD100;
font-size: 14px;
margin-top: 15px;
animation: pulse 1s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
</style>
</head>
<body>
<!-- Start Menu -->
<div class="overlay" id="startMenu">
<div class="menu">
<div class="ucla-logo">UCLA</div>
<h1>THE ACCOUNTANT</h1>
<div class="game-subtitle">Anderson School of Management</div>
<div class="menu-character">
<canvas id="previewCanvas" width="80" height="120" class="student-preview"></canvas>
</div>
<div class="instructions">
<h3>π YOUR MISSION</h3>
<p>Help our Anderson MBA student navigate across UCLA campus while mastering accounting concepts!</p>
<br>
<p><span class="key">β</span> <span class="key">β</span> or <span class="key">A</span> <span class="key">D</span> β Run across campus</p>
<p><span class="key">SPACE</span> or <span class="key">W</span> β Jump over obstacles</p>
<p>π’ <strong>Collect GREEN answers</strong> β Correct! (+100 pts)</p>
<p>π΄ <strong>Avoid RED answers</strong> β Wrong! (-50 pts, lose a life)</p>
<p>β <strong>Grab coffee cups</strong> β Bonus energy!</p>
</div>
<div class="level-select">
<span>πΊοΈ SELECT YOUR ROUTE:</span>
<div class="level-btns">
<button class="level-btn active" data-level="returns">
Module 1: Returns
<span class="location">π Royce Hall β Powell</span>
</button>
<button class="level-btn" data-level="warranty">
Module 2: Warranty
<span class="location">π Bruin Plaza β Ackerman</span>
</button>
<button class="level-btn" data-level="baddebt">
Module 3: Bad Debt
<span class="location">π Janss Steps β Kerckhoff</span>
</button>
<button class="level-btn" data-level="ratios">
Module 4: Ratios
<span class="location">π Anderson β Pauley</span>
</button>
</div>
</div>
<button class="start-btn" id="startBtn">π» GO BRUINS!</button>
<div class="bruins-cheer">8-CLAP READY!</div>
</div>
</div>
<!-- End Screen -->
<div class="overlay hidden" id="endScreen">
<div class="menu end-screen" id="endContent"></div>
</div>
<!-- Game -->
<div class="game-wrapper">
<div class="header">
<h1>π» THE ACCOUNTANT</h1>
<div class="subtitle">UCLA Anderson School of Management</div>
</div>
<div class="stats-bar">
<div class="stat"><span class="stat-icon">β€οΈ</span> <span id="lives" class="stat-value">3</span></div>
<div class="stat"><span class="stat-icon">β</span> <span id="score" class="stat-value">0</span></div>
<div class="stat"><span class="stat-icon">π</span> <span id="level" class="stat-value">Royce Hall</span></div>
<div class="stat"><span class="stat-icon">β</span> <span id="correct" class="stat-value">0</span>/<span id="total" class="stat-value">0</span></div>
</div>
<canvas id="gameCanvas" width="900" height="450"></canvas>
<div class="question-display">
<div class="question-label">π ACCOUNTING CHALLENGE</div>
<div id="currentQuestion">Navigate to the correct answer!</div>
</div>
<div class="controls">
[β β or A D] Move [SPACE or W] Jump [P] Pause
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const previewCanvas = document.getElementById('previewCanvas');
const previewCtx = previewCanvas.getContext('2d');
// Game State
let gameRunning = false;
let gamePaused = false;
let currentLevel = 'returns';
let score = 0;
let lives = 3;
let correctAnswers = 0;
let totalQuestions = 0;
let answeredQuestions = [];
let currentQuestionIndex = 0;
let frameCount = 0;
// UCLA Colors
const UCLA_BLUE = '#2D68C4';
const UCLA_GOLD = '#FFD100';
// Questions Database
const questions = {
returns: [
{ q: "When you ESTIMATE returns from a CASH sale, what happens to Revenue?", correct: "Revenue DECREASES", wrong: ["Revenue increases", "No effect", "Revenue doubles"] },
{ q: "Estimated returns from CREDIT sales creates what account?", correct: "Allowance for Returns", wrong: ["Refund Liability", "Cash Account", "COGS"] },
{ q: "When an ACTUAL return happens, what's the income statement effect?", correct: "NO income effect", wrong: ["β Revenue again", "β Expense", "β Net Income"] },
{ q: "Net Revenue = Gross Revenue minus what?", correct: "Returns & Allowances", wrong: ["COGS", "Bad Debt", "Taxes"] },
{ q: "Refund Liability is created for returns from...", correct: "CASH sales", wrong: ["Credit sales", "All sales", "No sales"] },
{ q: "Allowance for Returns is what type of account?", correct: "Contra A/R", wrong: ["Liability", "Expense", "Revenue"] }
],
warranty: [
{ q: "When should warranty expense be recorded?", correct: "When product SOLD", wrong: ["When claim made", "Year end", "When paid"] },
{ q: "Recording warranty creates what liability?", correct: "Warranty Reserve", wrong: ["A/P", "Refund Liability", "Unearned Rev"] },
{ q: "When warranty claim is SATISFIED, net income...", correct: "NO EFFECT", wrong: ["Decreases", "Increases", "Varies"] },
{ q: "Warranty expense is classified as...", correct: "COGS", wrong: ["Op. Expense", "Other Exp", "Revenue β"] },
{ q: "Warranty T-account: Beg + Expense - ___ = End", correct: "Claims Satisfied", wrong: ["Write-offs", "Returns", "Bad Debt"] },
{ q: "Satisfying a warranty claim affects...", correct: "Balance Sheet only", wrong: ["Income Stmt only", "Both", "Neither"] }
],
baddebt: [
{ q: "AFDA stands for Allowance for...", correct: "Doubtful Accounts", wrong: ["Direct Assets", "Debt Adjusted", "Deferred Accts"] },
{ q: "AFDA is what type of account?", correct: "Contra Asset", wrong: ["Liability", "Expense", "Asset"] },
{ q: "Net A/R = Gross A/R minus...", correct: "AFDA", wrong: ["Bad Debt Exp", "Write-offs", "Returns"] },
{ q: "When you WRITE OFF an account, Net A/R...", correct: "Stays SAME", wrong: ["Decreases", "Increases", "Goes to zero"] },
{ q: "Bad Debt Expense recorded when?", correct: "Same period as sale", wrong: ["At write-off", "Year end", "When default"] },
{ q: "Write-off: β Gross A/R and β ___", correct: "AFDA", wrong: ["Bad Debt Exp", "Net A/R", "Revenue"] }
],
ratios: [
{ q: "Gross Margin = (Net Rev - COGS) / ___", correct: "Net Revenue", wrong: ["Gross Revenue", "Total Assets", "COGS"] },
{ q: "Current Ratio = Current Assets / ___", correct: "Current Liabilities", wrong: ["Total Assets", "Total Liab", "Equity"] },
{ q: "Quick Ratio excludes ___ from Current Assets", correct: "Inventory", wrong: ["Cash", "A/R", "Prepaid"] },
{ q: "ROA = Net Income / ___", correct: "Total Assets", wrong: ["Revenue", "Equity", "Curr Assets"] },
{ q: "Current β but Quick β means company is...", correct: "Building inventory", wrong: ["Losing cash", "Paying debt", "β Sales"] },
{ q: "Asset Turnover measures...", correct: "Rev per $ assets", wrong: ["Profit per $", "Liquidity", "Debt ratio"] }
]
};
// Campus locations per level
const campusLocations = {
returns: ['Royce Hall', 'Powell Library', 'Janss Steps', 'Dickson Plaza', 'Kaufman Hall', 'Anderson School'],
warranty: ['Bruin Bear', 'Ackerman Union', 'Kerckhoff Hall', 'Moore Hall', 'Bunche Hall', 'Anderson School'],
baddebt: ['Janss Steps', 'Humanities', 'Broad Art', 'Fowler Museum', 'Sculpture Garden', 'Anderson School'],
ratios: ['Anderson School', 'Pauley Pavilion', 'Drake Stadium', 'Sunset Rec', 'Intramural Field', 'Bruin Walk']
};
// Player
const player = {
x: 100,
y: 300,
width: 45,
height: 60,
velocityX: 0,
velocityY: 0,
speed: 6,
jumpPower: -16,
onGround: false,
facingRight: true,
walkFrame: 0
};
const gravity = 0.9;
const friction = 0.85;
let platforms = [];
let coins = [];
let decorations = [];
let buildings = [];
let cameraX = 0;
const worldWidth = 4000;
const keys = {};
// Draw the UCLA student character
function drawStudent(context, x, y, facingRight, walking, scale = 1) {
context.save();
context.translate(x, y);
context.scale(scale, scale);
if (!facingRight) {
context.scale(-1, 1);
context.translate(-45, 0);
}
// Shadow
context.fillStyle = 'rgba(0,0,0,0.2)';
context.beginPath();
context.ellipse(22, 58, 15, 5, 0, 0, Math.PI * 2);
context.fill();
// Legs (khaki pants)
const legOffset = walking ? Math.sin(frameCount * 0.3) * 5 : 0;
context.fillStyle = '#C4A574';
context.fillRect(12, 42, 9, 18 + legOffset);
context.fillRect(24, 42, 9, 18 - legOffset);
// Shoes (brown loafers)
context.fillStyle = '#5D4037';
context.fillRect(10, 58 + legOffset, 13, 6);
context.fillRect(22, 58 - legOffset, 13, 6);
// Body - Collared shirt (white/light blue)
context.fillStyle = '#E3F2FD';
context.beginPath();
context.moveTo(10, 25);
context.lineTo(35, 25);
context.lineTo(37, 44);
context.lineTo(8, 44);
context.closePath();
context.fill();
// Collar
context.fillStyle = '#BBDEFB';
context.beginPath();
context.moveTo(15, 22);
context.lineTo(22, 28);
context.lineTo(22, 22);
context.closePath();
context.fill();
context.beginPath();
context.moveTo(30, 22);
context.lineTo(23, 28);
context.lineTo(23, 22);
context.closePath();
context.fill();
// Quarter zip (UCLA Blue)
context.fillStyle = UCLA_BLUE;
context.beginPath();
context.moveTo(8, 26);
context.lineTo(37, 26);
context.lineTo(40, 44);
context.lineTo(5, 44);
context.closePath();
context.fill();
// Quarter zip - zipper line
context.strokeStyle = UCLA_GOLD;
context.lineWidth = 2;
context.beginPath();
context.moveTo(22, 26);
context.lineTo(22, 38);
context.stroke();
// Zipper pull
context.fillStyle = UCLA_GOLD;
context.fillRect(20, 36, 5, 4);
// UCLA text on chest
context.fillStyle = UCLA_GOLD;
context.font = 'bold 6px Arial';
context.fillText('UCLA', 13, 35);
// Arms
const armSwing = walking ? Math.sin(frameCount * 0.3) * 8 : 0;
context.fillStyle = UCLA_BLUE;
// Left arm
context.save();
context.translate(8, 28);
context.rotate((-15 + armSwing) * Math.PI / 180);
context.fillRect(-3, 0, 8, 18);
context.restore();
// Right arm
context.save();
context.translate(37, 28);
context.rotate((15 - armSwing) * Math.PI / 180);
context.fillRect(-5, 0, 8, 18);
context.restore();
// Hands
context.fillStyle = '#FDBF6F';
context.beginPath();
context.arc(5, 46 - armSwing/2, 4, 0, Math.PI * 2);
context.arc(40, 46 + armSwing/2, 4, 0, Math.PI * 2);
context.fill();
// Neck
context.fillStyle = '#FDBF6F';
context.fillRect(18, 18, 9, 7);
// Head
context.fillStyle = '#FDBF6F';
context.beginPath();
context.arc(22, 12, 12, 0, Math.PI * 2);
context.fill();
// Hair (neat business school hair)
context.fillStyle = '#3E2723';
context.beginPath();
context.arc(22, 9, 11, Math.PI, 0);
context.fill();
context.fillRect(11, 5, 22, 6);
// Side hair
context.fillRect(10, 8, 3, 8);
context.fillRect(32, 8, 3, 8);
// Eyes
context.fillStyle = '#fff';
context.beginPath();
context.arc(18, 11, 3, 0, Math.PI * 2);
context.arc(27, 11, 3, 0, Math.PI * 2);
context.fill();
context.fillStyle = '#1A237E';
context.beginPath();
context.arc(18, 11, 1.5, 0, Math.PI * 2);
context.arc(27, 11, 1.5, 0, Math.PI * 2);
context.fill();
// Friendly smile
context.strokeStyle = '#5D4037';
context.lineWidth = 1.5;
context.beginPath();
context.arc(22, 14, 5, 0.1 * Math.PI, 0.9 * Math.PI);
context.stroke();
// Eyebrows
context.strokeStyle = '#3E2723';
context.lineWidth = 1.5;
context.beginPath();
context.moveTo(15, 7);
context.lineTo(20, 8);
context.moveTo(25, 8);
context.lineTo(30, 7);
context.stroke();
// Backpack straps visible
context.fillStyle = '#1565C0';
context.fillRect(8, 26, 3, 15);
context.fillRect(34, 26, 3, 15);
context.restore();
}
// Draw preview character
function drawPreview() {
previewCtx.clearRect(0, 0, 80, 120);
drawStudent(previewCtx, 17, 30, true, true, 1.3);
}
// Animate preview
function animatePreview() {
frameCount++;
drawPreview();
requestAnimationFrame(animatePreview);
}
animatePreview();
// Initialize level
function initLevel() {
const levelQuestions = questions[currentLevel];
totalQuestions = levelQuestions.length;
currentQuestionIndex = 0;
correctAnswers = 0;
answeredQuestions = [];
player.x = 100;
player.y = 300;
player.velocityX = 0;
player.velocityY = 0;
cameraX = 0;
// Platforms
platforms = [];
// Main ground (brick path)
platforms.push({ x: 0, y: 380, width: worldWidth, height: 70, type: 'ground' });
// Question platforms
let xPos = 400;
levelQuestions.forEach((q, i) => {
const yVariation = Math.sin(i * 0.8) * 40;
platforms.push({ x: xPos, y: 280 + yVariation, width: 140, height: 25, type: 'platform' });
platforms.push({ x: xPos + 180, y: 250 + yVariation, width: 120, height: 25, type: 'platform' });
platforms.push({ x: xPos + 340, y: 290 + yVariation, width: 130, height: 25, type: 'platform' });
xPos += 550;
});
// End platform
platforms.push({ x: worldWidth - 250, y: 280, width: 200, height: 25, type: 'end' });
// Buildings (background decorations)
buildings = [];
const buildingTypes = ['royce', 'powell', 'anderson', 'pauley', 'ackerman'];
for (let i = 0; i < 15; i++) {
buildings.push({
x: i * 300 + Math.random() * 100,
type: buildingTypes[i % buildingTypes.length],
scale: 0.8 + Math.random() * 0.4
});
}
// Trees and decorations
decorations = [];
for (let i = 0; i < 40; i++) {
decorations.push({
x: Math.random() * worldWidth,
y: 330 + Math.random() * 40,
type: Math.random() > 0.5 ? 'tree' : 'bush',
scale: 0.7 + Math.random() * 0.6
});
}
// Lamp posts
for (let i = 0; i < 20; i++) {
decorations.push({
x: i * 200 + 150,
y: 300,
type: 'lamp'
});
}
generateCoinsForQuestion(0);
updateUI();
}
function generateCoinsForQuestion(qIndex) {
if (qIndex >= questions[currentLevel].length) return;
coins = [];
const q = questions[currentLevel][qIndex];
const baseX = 400 + qIndex * 550;
const allAnswers = [q.correct, ...q.wrong].sort(() => Math.random() - 0.5);
allAnswers.forEach((answer, i) => {
coins.push({
x: baseX + 30 + i * 140,
y: 180 + Math.sin(i) * 30,
width: 110,
height: 35,
text: answer,
isCorrect: answer === q.correct,
collected: false,
bobOffset: Math.random() * Math.PI * 2
});
});
document.getElementById('currentQuestion').textContent = q.q;
// Update location
const locations = campusLocations[currentLevel];
document.getElementById('level').textContent = locations[Math.min(qIndex, locations.length - 1)];
}
function gameLoop() {
if (!gameRunning || gamePaused) {
requestAnimationFrame(gameLoop);
return;
}
frameCount++;
update();
render();
requestAnimationFrame(gameLoop);
}
function update() {
// Input
if (keys['ArrowLeft'] || keys['KeyA']) {
player.velocityX = -player.speed;
player.facingRight = false;
} else if (keys['ArrowRight'] || keys['KeyD']) {
player.velocityX = player.speed;
player.facingRight = true;
} else {
player.velocityX *= friction;
}
if ((keys['Space'] || keys['ArrowUp'] || keys['KeyW']) && player.onGround) {
player.velocityY = player.jumpPower;
player.onGround = false;
}
// Physics
player.velocityY += gravity;
player.x += player.velocityX;
player.y += player.velocityY;
// Platform collision
player.onGround = false;
platforms.forEach(platform => {
if (player.x < platform.x + platform.width &&
player.x + player.width > platform.x &&
player.y + player.height > platform.y &&
player.y + player.height < platform.y + platform.height + 20 &&
player.velocityY > 0) {
player.y = platform.y - player.height;
player.velocityY = 0;
player.onGround = true;
}
});
// World bounds
if (player.x < 0) player.x = 0;
if (player.x > worldWidth - player.width) player.x = worldWidth - player.width;
if (player.y > canvas.height) {
player.x = Math.max(100, cameraX + 100);
player.y = 200;
player.velocityY = 0;
}
// Camera
const targetCameraX = player.x - canvas.width / 3;
cameraX += (targetCameraX - cameraX) * 0.08;
cameraX = Math.max(0, Math.min(cameraX, worldWidth - canvas.width));
// Coin collision
coins.forEach(coin => {
if (!coin.collected &&
player.x < coin.x + coin.width &&
player.x + player.width > coin.x &&
player.y < coin.y + coin.height &&
player.y + player.height > coin.y) {
coin.collected = true;
if (coin.isCorrect) {
score += 100;
correctAnswers++;
answeredQuestions.push({
q: questions[currentLevel][currentQuestionIndex].q,
correct: true,
answer: coin.text
});
currentQuestionIndex++;
if (currentQuestionIndex < questions[currentLevel].length) {
generateCoinsForQuestion(currentQuestionIndex);
} else {
endGame(true);
}
} else {
score = Math.max(0, score - 50);
lives--;
answeredQuestions.push({
q: questions[currentLevel][currentQuestionIndex].q,
correct: false,
answer: coin.text,
rightAnswer: questions[currentLevel][currentQuestionIndex].correct
});
if (lives <= 0) {
endGame(false);
} else {
setTimeout(() => generateCoinsForQuestion(currentQuestionIndex), 500);
}
}
updateUI();
}
});
}
function render() {
// Sky gradient (California blue sky)
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, '#87CEEB');
gradient.addColorStop(0.6, '#B0E0E6');
gradient.addColorStop(1, '#E0F7FA');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Sun
ctx.fillStyle = '#FFD700';
ctx.beginPath();
ctx.arc(750 - cameraX * 0.1, 60, 40, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'rgba(255, 215, 0, 0.3)';
ctx.beginPath();
ctx.arc(750 - cameraX * 0.1, 60, 55, 0, Math.PI * 2);
ctx.fill();
// Distant mountains/hills (Westwood hills)
ctx.fillStyle = '#90A4AE';
ctx.beginPath();
ctx.moveTo(0, 250);
for (let i = 0; i < canvas.width + 100; i += 50) {
ctx.lineTo(i, 220 + Math.sin((i + cameraX * 0.2) * 0.01) * 30);
}
ctx.lineTo(canvas.width, 380);
ctx.lineTo(0, 380);
ctx.fill();
ctx.save();
ctx.translate(-cameraX, 0);
// Background buildings
buildings.forEach(building => {
drawBuilding(building.x, building.type, building.scale);
});
// Trees and bushes
decorations.forEach(dec => {
if (dec.type === 'tree') {
drawTree(dec.x, dec.y, dec.scale);
} else if (dec.type === 'bush') {
drawBush(dec.x, dec.y, dec.scale);
} else if (dec.type === 'lamp') {
drawLampPost(dec.x, dec.y);
}
});
// Platforms
platforms.forEach(platform => {
if (platform.type === 'ground') {
// Brick path
ctx.fillStyle = '#D7CCC8';
ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
// Brick pattern
ctx.fillStyle = '#A1887F';
for (let bx = platform.x; bx < platform.x + platform.width; bx += 30) {
for (let by = platform.y; by < platform.y + platform.height; by += 15) {
const offset = (Math.floor((by - platform.y) / 15) % 2) * 15;
ctx.fillRect(bx + offset, by, 28, 13);
}
}
// Grass edge
ctx.fillStyle = '#4CAF50';
ctx.fillRect(platform.x, platform.y - 8, platform.width, 10);
} else if (platform.type === 'end') {
// Finish area - Anderson School entrance
ctx.fillStyle = UCLA_BLUE;
ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
// Building
ctx.fillStyle = '#E8D5B7';
ctx.fillRect(platform.x + 20, platform.y - 100, 160, 100);
// Entrance
ctx.fillStyle = UCLA_BLUE;
ctx.fillRect(platform.x + 80, platform.y - 60, 40, 60);
// Sign
ctx.fillStyle = UCLA_GOLD;
ctx.fillRect(platform.x + 50, platform.y - 90, 100, 20);
ctx.fillStyle = UCLA_BLUE;
ctx.font = 'bold 10px Arial';
ctx.textAlign = 'center';
ctx.fillText('ANDERSON', platform.x + 100, platform.y - 76);
// Flag
ctx.fillStyle = '#8B4513';
ctx.fillRect(platform.x + 180, platform.y - 130, 5, 130);
ctx.fillStyle = UCLA_BLUE;
ctx.fillRect(platform.x + 185, platform.y - 130, 35, 25);
ctx.fillStyle = UCLA_GOLD;
ctx.font = 'bold 8px Arial';
ctx.fillText('UCLA', platform.x + 202, platform.y - 114);
} else {
// Regular platforms (stone benches/steps)
ctx.fillStyle = '#BDBDBD';
ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
ctx.fillStyle = '#9E9E9E';
ctx.fillRect(platform.x, platform.y, platform.width, 5);
ctx.fillStyle = '#757575';
ctx.fillRect(platform.x + 5, platform.y + 5, platform.width - 10, 3);
}
});
// Coins (answer bubbles)
coins.forEach(coin => {
if (!coin.collected) {
const bob = Math.sin(frameCount * 0.05 + coin.bobOffset) * 5;
// Glow
ctx.shadowColor = coin.isCorrect ? '#4CAF50' : '#f44336';
ctx.shadowBlur = 15;
// Bubble
ctx.fillStyle = coin.isCorrect ? '#4CAF50' : '#e53935';
ctx.beginPath();
ctx.roundRect(coin.x, coin.y + bob, coin.width, coin.height, 12);
ctx.fill();
// Border
ctx.strokeStyle = coin.isCorrect ? '#2E7D32' : '#B71C1C';
ctx.lineWidth = 3;
ctx.stroke();
ctx.shadowBlur = 0;
// Icon
ctx.font = '14px Arial';
ctx.fillStyle = '#fff';
ctx.textAlign = 'left';
ctx.fillText(coin.isCorrect ? 'β' : 'β', coin.x + 8, coin.y + bob + 24);
// Text
ctx.fillStyle = '#fff';
ctx.font = 'bold 10px Arial';
ctx.textAlign = 'center';
ctx.fillText(coin.text, coin.x + coin.width/2 + 8, coin.y + bob + 23, coin.width - 30);
}
});
// Player
const isWalking = Math.abs(player.velocityX) > 0.5;
drawStudent(ctx, player.x, player.y, player.facingRight, isWalking);
ctx.restore();
// HUD
ctx.fillStyle = 'rgba(45, 104, 196, 0.85)';
ctx.fillRect(10, 10, 200, 30);
ctx.strokeStyle = UCLA_GOLD;
ctx.lineWidth = 2;
ctx.strokeRect(10, 10, 200, 30);
ctx.fillStyle = '#fff';
ctx.font = 'bold 12px Arial';
ctx.textAlign = 'left';
ctx.fillText(`π Question ${currentQuestionIndex + 1} of ${totalQuestions}`, 20, 30);
}
function drawBuilding(x, type, scale) {
ctx.save();
ctx.translate(x, 200);
ctx.scale(scale, scale);
if (type === 'royce') {
// Royce Hall style
ctx.fillStyle = '#D4A574';
ctx.fillRect(0, 50, 150, 130);
// Towers
ctx.fillRect(0, 0, 35, 180);
ctx.fillRect(115, 0, 35, 180);
// Windows
ctx.fillStyle = '#5D4037';
for (let i = 0; i < 3; i++) {
ctx.fillRect(10, 20 + i * 50, 15, 30);
ctx.fillRect(125, 20 + i * 50, 15, 30);
}
// Arches
ctx.fillStyle = '#3E2723';
for (let i = 0; i < 4; i++) {
ctx.beginPath();
ctx.arc(25 + i * 35, 150, 12, Math.PI, 0);
ctx.fill();
}
} else if (type === 'powell') {
// Powell Library style
ctx.fillStyle = '#E8D5B7';
ctx.fillRect(0, 60, 120, 120);
// Dome
ctx.beginPath();
ctx.arc(60, 60, 40, Math.PI, 0);
ctx.fill();
// Columns
ctx.fillStyle = '#D7CCC8';
for (let i = 0; i < 4; i++) {
ctx.fillRect(15 + i * 30, 90, 10, 90);
}
} else if (type === 'anderson') {
// Anderson School
ctx.fillStyle = '#CFD8DC';
ctx.fillRect(0, 50, 140, 130);
ctx.fillStyle = UCLA_BLUE;
ctx.fillRect(50, 120, 40, 60);
// Glass windows
ctx.fillStyle = '#81D4FA';
ctx.fillRect(10, 60, 50, 50);
ctx.fillRect(80, 60, 50, 50);
} else if (type === 'pauley') {
// Pauley Pavilion
ctx.fillStyle = '#90A4AE';
ctx.beginPath();
ctx.moveTo(0, 180);
ctx.lineTo(75, 40);
ctx.lineTo(150, 180);
ctx.fill();
ctx.fillStyle = UCLA_BLUE;
ctx.fillRect(55, 120, 40, 60);
} else {
// Generic building
ctx.fillStyle = '#BDBDBD';
ctx.fillRect(0, 80, 100, 100);
}
ctx.restore();
}
function drawTree(x, y, scale) {
ctx.save();
ctx.translate(x, y);
ctx.scale(scale, scale);
// Trunk
ctx.fillStyle = '#5D4037';
ctx.fillRect(-5, 0, 10, 40);
// Foliage (palm tree style for UCLA)
ctx.fillStyle = '#2E7D32';
for (let i = 0; i < 6; i++) {
ctx.save();
ctx.translate(0, -10);
ctx.rotate((i * 60 - 150) * Math.PI / 180);
ctx.beginPath();
ctx.ellipse(0, -30, 8, 35, 0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
ctx.restore();
}
function drawBush(x, y, scale) {
ctx.save();
ctx.translate(x, y);
ctx.scale(scale, scale);
ctx.fillStyle = '#388E3C';
ctx.beginPath();
ctx.arc(0, 0, 15, 0, Math.PI * 2);
ctx.arc(12, -5, 12, 0, Math.PI * 2);
ctx.arc(-10, -3, 10, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function drawLampPost(x, y) {
// Post
ctx.fillStyle = '#37474F';
ctx.fillRect(x - 3, y, 6, 80);
// Lamp
ctx.fillStyle = '#455A64';
ctx.fillRect(x - 12, y - 5, 24, 15);
// Light glow (if near player or always on)
ctx.fillStyle = 'rgba(255, 235, 59, 0.3)';
ctx.beginPath();
ctx.arc(x, y + 15, 25, 0, Math.PI * 2);
ctx.fill();
}
function updateUI() {
document.getElementById('lives').textContent = 'β€οΈ'.repeat(Math.max(0, lives));
document.getElementById('score').textContent = score;
document.getElementById('correct').textContent = correctAnswers;
document.getElementById('total').textContent = totalQuestions;
}
function endGame(won) {
gameRunning = false;
const endScreen = document.getElementById('endScreen');
const endContent = document.getElementById('endContent');
endContent.className = `menu end-screen ${won ? 'win' : 'lose'}`;
let reviewHTML = '';
answeredQuestions.forEach(q => {
reviewHTML += `<div class="concept-item ${q.correct ? 'correct' : 'wrong'}">
<strong>${q.correct ? 'β' : 'β'}</strong> ${q.q}<br>
<small>Your answer: ${q.answer}</small>
${!q.correct ? `<br><small style="color: #4CAF50;">Correct: ${q.rightAnswer}</small>` : ''}
</div>`;
});
endContent.innerHTML = `
<div class="ucla-logo">${won ? 'π UCLA π' : 'UCLA'}</div>
<h2>${won ? 'LEVEL COMPLETE!' : 'GAME OVER'}</h2>
<canvas id="endCharCanvas" width="80" height="100" style="margin: 10px auto; display: block;"></canvas>
<div class="final-stats">
<div>Final Score: <span style="color: ${UCLA_GOLD}">${score}</span></div>
<div>Correct: <span style="color: #4CAF50">${correctAnswers}/${totalQuestions}</span></div>
<div>Accuracy: <span style="color: #87CEEB">${Math.round((correctAnswers / Math.max(1, answeredQuestions.length)) * 100)}%</span></div>
</div>
${answeredQuestions.length > 0 ? `
<div class="concept-review">
<h4>π Concept Review</h4>
${reviewHTML}
</div>` : ''}
<button class="start-btn" onclick="restartGame()">π» PLAY AGAIN</button>
<button class="level-btn" style="margin-top: 10px; margin-left: 10px;" onclick="backToMenu()">MAIN MENU</button>
<div class="bruins-cheer">${won ? '8 CLAP! GO BRUINS! π' : 'Try again, Bruin!'}</div>
`;
endScreen.classList.remove('hidden');
// Draw character on end screen
setTimeout(() => {
const endCanvas = document.getElementById('endCharCanvas');
if (endCanvas) {
const endCtx = endCanvas.getContext('2d');
drawStudent(endCtx, 17, 20, true, false, 1.2);
}
}, 100);
}
function restartGame() {
document.getElementById('endScreen').classList.add('hidden');
lives = 3;
score = 0;
initLevel();
gameRunning = true;
}
function backToMenu() {
document.getElementById('endScreen').classList.add('hidden');
document.getElementById('startMenu').classList.remove('hidden');
lives = 3;
score = 0;
}
// Event Listeners
document.addEventListener('keydown', e => {
keys[e.code] = true;
if (e.code === 'KeyP') gamePaused = !gamePaused;
if (['Space', 'ArrowUp', 'ArrowDown'].includes(e.code)) e.preventDefault();
});
document.addEventListener('keyup', e => {
keys[e.code] = false;
});
document.querySelectorAll('.level-btn[data-level]').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.level-btn[data-level]').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentLevel = btn.dataset.level;
});
});
document.getElementById('startBtn').addEventListener('click', () => {
document.getElementById('startMenu').classList.add('hidden');
lives = 3;
score = 0;
initLevel();
gameRunning = true;
});
// Polyfill
if (!ctx.roundRect) {
CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
this.beginPath();
this.moveTo(x + r, y);
this.arcTo(x + w, y, x + w, y + h, r);
this.arcTo(x + w, y + h, x, y + h, r);
this.arcTo(x, y + h, x, y, r);
this.arcTo(x, y, x + w, y, r);
this.closePath();
return this;
};
}
gameLoop();
</script>
</body>
</html>