/**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/**** 
* Classes
****/ 
var BoostButton = Container.expand(function () {
	var self = Container.call(this);
	var buttonSprite = self.attachAsset('attackButton', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 2.5,
		scaleY: 2.5
	});
	// Change tint to blue to differentiate from attack button
	buttonSprite.tint = 0x3366ff;
	// Add text to indicate boost function
	var boostText = new Text2('BOOST', {
		size: 40,
		fill: 0xFFFFFF
	});
	boostText.anchor.set(0.5, 0.5);
	self.addChild(boostText);
	self.pressed = false;
	self.active = false;
	self.cooldown = false;
	self.down = function (x, y, obj) {
		if (self.cooldown) return;
		self.pressed = true;
		buttonSprite.alpha = 0.7;
		self.active = true;
		// Apply boost effect
		PLAYER_MOVE_SPEED *= 3;
	};
	self.up = function (x, y, obj) {
		self.pressed = false;
		if (self.active) {
			// Reset speed to normal
			PLAYER_MOVE_SPEED = PLAYER_MOVE_SPEED / 3;
			self.active = false;
			// Set cooldown
			self.cooldown = true;
			buttonSprite.alpha = 0.3;
			// Reset cooldown after 5 seconds
			LK.setTimeout(function () {
				self.cooldown = false;
				buttonSprite.alpha = 1;
			}, 5000);
		}
	};
	return self;
});
var ControlButton = Container.expand(function (direction) {
	var self = Container.call(this);
	var buttonSprite = self.attachAsset('controlButton', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 2.5,
		scaleY: 2.5
	});
	var arrowSprite = self.attachAsset('buttonArrow', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 2.0,
		scaleY: 2.0
	});
	// Set arrow direction based on button type
	if (direction === 'up') {
		arrowSprite.rotation = 0;
	} else if (direction === 'right') {
		arrowSprite.rotation = Math.PI / 2;
	} else if (direction === 'down') {
		arrowSprite.rotation = Math.PI;
	} else if (direction === 'left') {
		arrowSprite.rotation = Math.PI * 1.5;
	} else if (direction === 'attack') {
		arrowSprite.visible = false;
		buttonSprite = self.attachAsset('attackButton', {
			anchorX: 0.5,
			anchorY: 0.5,
			scaleX: 3.5,
			scaleY: 3.5
		});
	}
	self.direction = direction;
	self.pressed = false;
	self.updateCooldown = function (ratio) {
		if (self.direction === 'attack') {
			// Update the button alpha based on cooldown ratio (0 to 1)
			buttonSprite.alpha = 0.3 + ratio * 0.7;
		}
	};
	self.down = function (x, y, obj) {
		self.pressed = true;
		buttonSprite.alpha = 0.7;
		if (self.direction === 'attack' && canAttack) {
			// Trigger attack immediately when the attack button is pressed
			attackAction();
		}
	};
	self.up = function (x, y, obj) {
		self.pressed = false;
		if (self.direction === 'attack') {
			if (!canAttack) {
				buttonSprite.alpha = 0.3; // Show as disabled
			} else {
				buttonSprite.alpha = 1;
			}
		} else {
			buttonSprite.alpha = 1;
		}
	};
	return self;
});
var Gate = Container.expand(function () {
	var self = Container.call(this);
	// Create gate visual using existing wall asset but with a different color
	var gateSprite = self.attachAsset('wall', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	// Make the gate distinct with a green tint
	gateSprite.tint = 0x00FF00;
	self.mapX = 0;
	self.mapY = 0;
	// Pulse animation to make gate more visible
	var _animateGate = function animateGate() {
		tween(gateSprite, {
			alpha: 0.7,
			scaleX: 1.1,
			scaleY: 1.1
		}, {
			duration: 1000,
			onFinish: function onFinish() {
				tween(gateSprite, {
					alpha: 1,
					scaleX: 1.0,
					scaleY: 1.0
				}, {
					duration: 1000,
					onFinish: _animateGate
				});
			}
		});
	};
	// Start the pulsing animation
	_animateGate();
	return self;
});
var MapCell = Container.expand(function () {
	var self = Container.call(this);
	self.type = 0; // 0 = floor, 1 = wall
	self.monster = null;
	self.treasure = null;
	self.gate = null;
	self.setType = function (type) {
		self.type = type;
		self.updateVisual();
	};
	self.updateVisual = function () {
		self.removeChildren();
		if (self.type === 1) {
			self.attachAsset('mapWall', {
				anchorX: 0,
				anchorY: 0
			});
		} else {
			self.attachAsset('mapFloor', {
				anchorX: 0,
				anchorY: 0
			});
		}
	};
	self.addMonster = function () {
		if (self.type === 0 && !self.monster && !self.treasure) {
			self.monster = true;
			return true;
		}
		return false;
	};
	self.addTreasure = function () {
		if (self.type === 0 && !self.monster && !self.treasure) {
			self.treasure = true;
			return true;
		}
		return false;
	};
	self.removeMonster = function () {
		self.monster = null;
	};
	self.removeTreasure = function () {
		self.treasure = null;
	};
	self.addGate = function () {
		if (self.type === 0 && !self.monster && !self.treasure && !self.gate) {
			self.gate = true;
			return true;
		}
		return false;
	};
	self.removeGate = function () {
		self.gate = null;
	};
	return self;
});
var Monster = Container.expand(function () {
	var self = Container.call(this);
	var monsterSprite = self.attachAsset('monster', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.mapX = 0;
	self.mapY = 0;
	self.health = 3;
	self.takeDamage = function () {
		self.health -= 1;
		LK.getSound('hit').play();
		// Visual feedback for hit
		tween(monsterSprite, {
			alpha: 0.2
		}, {
			duration: 100,
			onFinish: function onFinish() {
				tween(monsterSprite, {
					alpha: 1
				}, {
					duration: 100
				});
			}
		});
		return self.health <= 0;
	};
	return self;
});
var Projectile = Container.expand(function () {
	var self = Container.call(this);
	var projectileSprite = self.attachAsset('projectile', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 0.5,
		scaleY: 0.5
	});
	self.speed = 10; // Speed of horizontal movement
	self.startX = 0; // Starting X position on screen
	self.targetX = 0; // How far to move
	self.visible = false; // Start invisible
	self.active = false; // Projectile state
	self.isClone = false; // Track if this is a mirrored clone
	// Method to flip the projectile sprite horizontally
	self.setMirrored = function (mirrored) {
		self.isClone = mirrored;
		if (mirrored) {
			// Flip the sprite horizontally by setting negative scale
			projectileSprite.scaleX = -0.5;
		} else {
			projectileSprite.scaleX = 0.5;
		}
	};
	self.update = function (deltaTime) {
		if (!self.active) {
			return false;
		}
		// Move projectile based on speed direction
		self.x -= self.speed;
		// Create pulsating effect for better visibility
		if (LK.ticks % 20 === 0) {
			tween(projectileSprite, {
				scaleX: self.isClone ? -0.6 : 0.6,
				scaleY: 0.6
			}, {
				duration: 200,
				onFinish: function onFinish() {
					tween(projectileSprite, {
						scaleX: self.isClone ? -0.5 : 0.5,
						scaleY: 0.5
					}, {
						duration: 200
					});
				}
			});
		}
		// Return true if projectile has gone off either side of screen
		if (self.speed > 0) {
			// For projectiles moving left to right (clones), check right boundary
			return self.x > 2048 + 100;
		} else {
			// For normal projectiles moving right to left, check left boundary
			return self.x < -100;
		}
	};
	self.fire = function (screenX, screenY) {
		// Position at right side of screen
		self.x = 2048 + 100; // Start off-screen to the right
		self.y = screenY || 2732 - 400; // Position at bottom of screen instead of middle
		self.visible = true;
		self.active = true;
	};
	return self;
});
var RaycastStrip = Container.expand(function () {
	var self = Container.call(this);
	var wall = self.attachAsset('wall', {
		anchorX: 0,
		anchorY: 0
	});
	var ceiling = self.attachAsset('ceiling', {
		anchorX: 0,
		anchorY: 0
	});
	var floor = self.attachAsset('floor', {
		anchorX: 0,
		anchorY: 0
	});
	self.updateStrip = function (stripWidth, wallHeight, stripIdx, wallType, distance) {
		// Wall setup
		wall.width = stripWidth;
		wall.height = wallHeight;
		wall.y = (2732 - wallHeight) / 2;
		// Ceiling setup
		ceiling.width = stripWidth;
		ceiling.height = wall.y;
		ceiling.y = 0;
		// Floor setup
		floor.width = stripWidth;
		floor.height = ceiling.height;
		floor.y = wall.y + wallHeight;
		// Adjust positions
		self.x = stripIdx * stripWidth;
		// Add distance shading effect
		var shade = Math.max(0.3, 1 - distance / 10);
		wall.alpha = shade;
		ceiling.alpha = shade * 0.7;
		floor.alpha = shade * 0.8;
	};
	return self;
});
var Treasure = Container.expand(function () {
	var self = Container.call(this);
	var treasureSprite = self.attachAsset('treasure', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.mapX = 0;
	self.mapY = 0;
	self.value = 1;
	// Animate the treasure to make it more appealing
	var _animateTreasure = function animateTreasure() {
		// Animation removed to stop spinning
	};
	// No longer calling animation
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x111111
});
/**** 
* Game Code
****/ 
// Game constants
var MAP_SIZE = 16;
var CELL_SIZE = 20;
var MINI_MAP_SCALE = 1;
var STRIP_WIDTH = 32; // Increased strip width to reduce ray count
var NUM_RAYS = Math.ceil(2048 / STRIP_WIDTH);
var FOV = Math.PI / 3; // 60 degrees field of view
var HALF_FOV = FOV / 2;
var PLAYER_MOVE_SPEED = 0.002; // Reduced from 0.005 to make movement slower
var PLAYER_TURN_SPEED = 0.002; // Reduced from 0.005 to make turning slower
var WALL_HEIGHT_FACTOR = 600;
var MAX_RENDER_DISTANCE = 16;
var MONSTER_COUNT = 5;
var TREASURE_COUNT = 10;
// Game state
var map = [];
var player = {
	x: 1.5,
	y: 1.5,
	dir: 0,
	health: 5,
	score: 0,
	level: 1
};
var controls = {
	forward: false,
	backward: false,
	left: false,
	right: false,
	attack: false,
	boost: false
};
var monsters = [];
var treasures = [];
var projectiles = [];
var gate = null;
var lastTime = Date.now();
var canAttack = true;
var attackCooldown = 3000; // 3 second cooldown
// UI elements
var miniMap;
var rayCastView;
var healthText;
var scoreText;
var levelText;
var monsterText;
var controlButtons = {};
var playerMarker;
// Setup game
function setupGame() {
	// Create the rayCast view container
	rayCastView = new Container();
	game.addChild(rayCastView);
	// Create raycast strips
	for (var i = 0; i < NUM_RAYS; i++) {
		var strip = new RaycastStrip();
		rayCastView.addChild(strip);
	}
	// Create minimap container
	miniMap = new Container();
	miniMap.x = (2048 - MAP_SIZE * CELL_SIZE * MINI_MAP_SCALE) / 2; // Center horizontally
	miniMap.y = 20; // Keep at top
	game.addChild(miniMap);
	// Generate map
	generateMap();
	// Create player marker
	playerMarker = game.addChild(LK.getAsset('player', {
		anchorX: 0.5,
		anchorY: 0.5
	}));
	// Create UI elements
	createUI();
	// Create control buttons
	createControlButtons();
	// Start background music
	LK.playMusic('dungeon');
}
function generateMap() {
	// Clear existing map
	miniMap.removeChildren();
	map = [];
	// Remove existing monsters and treasures
	for (var i = 0; i < monsters.length; i++) {
		monsters[i].destroy();
	}
	monsters = [];
	for (var i = 0; i < treasures.length; i++) {
		treasures[i].destroy();
	}
	treasures = [];
	// Clear projectiles
	for (var i = 0; i < projectiles.length; i++) {
		projectiles[i].destroy();
	}
	projectiles = [];
	// Generate base map with borders
	for (var y = 0; y < MAP_SIZE; y++) {
		map[y] = [];
		for (var x = 0; x < MAP_SIZE; x++) {
			var cell = new MapCell();
			cell.x = x * CELL_SIZE * MINI_MAP_SCALE;
			cell.y = y * CELL_SIZE * MINI_MAP_SCALE;
			// Create outer walls
			if (x === 0 || y === 0 || x === MAP_SIZE - 1 || y === MAP_SIZE - 1) {
				cell.setType(1); // Wall
			} else {
				// Random interior walls based on level difficulty
				var wallChance = 0.2 + player.level * 0.03;
				if (Math.random() < wallChance && !(x === 1 && y === 1)) {
					// Ensure starting position is clear
					cell.setType(1); // Wall
				} else {
					cell.setType(0); // Floor
				}
			}
			map[y][x] = cell;
			miniMap.addChild(cell);
		}
	}
	// Check map connectivity and fix walled-off areas
	ensureMapConnectivity();
	// Create monsters
	var monstersToPlace = MONSTER_COUNT + Math.floor(player.level * 0.5);
	for (var i = 0; i < monstersToPlace; i++) {
		placeMonster();
	}
	// Create treasures
	var treasuresToPlace = TREASURE_COUNT;
	for (var i = 0; i < treasuresToPlace; i++) {
		placeTreasure();
	}
	// Reset player position
	player.x = 1.5;
	player.y = 1.5;
	player.dir = 0;
	// Place exit gate
	gate = placeGate();
}
function placeMonster() {
	// Find a random empty cell
	var x, y;
	var attempts = 0;
	do {
		x = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
		y = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
		attempts++;
		// Make sure it's not too close to the player
		var distToPlayer = Math.sqrt(Math.pow(x - player.x, 2) + Math.pow(y - player.y, 2));
		if (attempts > 100) {
			break;
		} // Prevent infinite loop
	} while (map[y][x].type !== 0 || map[y][x].monster || map[y][x].treasure || distToPlayer < 3);
	if (attempts <= 100) {
		map[y][x].addMonster();
		var monster = new Monster();
		monster.mapX = x;
		monster.mapY = y;
		monster.health = 2 + Math.floor(player.level / 3); // Monsters get tougher with level
		monsters.push(monster);
		game.addChild(monster);
	}
}
function placeTreasure() {
	// Find a random empty cell
	var x, y;
	var attempts = 0;
	do {
		x = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
		y = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
		attempts++;
		if (attempts > 100) {
			break;
		} // Prevent infinite loop
	} while (map[y][x].type !== 0 || map[y][x].monster || map[y][x].treasure || map[y][x].gate);
	if (attempts <= 100) {
		map[y][x].addTreasure();
		var treasure = new Treasure();
		treasure.mapX = x;
		treasure.mapY = y;
		treasure.value = 1 + Math.floor(Math.random() * player.level);
		treasures.push(treasure);
		game.addChild(treasure);
	}
}
function placeGate() {
	// Place gate in a random valid location
	var x, y;
	var attempts = 0;
	var validPositions = [];
	// Collect all valid positions first
	for (var i = 0; i < 100; i++) {
		x = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
		y = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
		// Check if the cell is suitable
		if (map[y][x].type === 0 && !map[y][x].monster && !map[y][x].treasure && !map[y][x].gate) {
			// Make sure it's not too close to the player (at least 2 cells away)
			var distToPlayer = Math.sqrt(Math.pow(x - player.x, 2) + Math.pow(y - player.y, 2));
			if (distToPlayer > 2) {
				// Add to valid positions
				validPositions.push({
					x: x,
					y: y
				});
			}
		}
	}
	// If we found valid spots, choose one randomly
	if (validPositions.length > 0) {
		// Pick a random position from the valid ones
		var randomIndex = Math.floor(Math.random() * validPositions.length);
		var chosenPos = validPositions[randomIndex];
		var gateX = chosenPos.x;
		var gateY = chosenPos.y;
		// Place the gate
		map[gateY][gateX].addGate();
		var gate = new Gate();
		gate.mapX = gateX;
		gate.mapY = gateY;
		game.addChild(gate);
		return gate;
	}
	return null;
}
function createUI() {
	// Health display
	healthText = new Text2('Health: ' + player.health, {
		size: 40,
		fill: 0xFF5555
	});
	healthText.anchor.set(0, 0);
	LK.gui.topRight.addChild(healthText);
	healthText.x = -200;
	healthText.y = 20;
	// Score display
	scoreText = new Text2('Score: ' + player.score, {
		size: 40,
		fill: 0xFFFF55
	});
	scoreText.anchor.set(0, 0);
	LK.gui.topRight.addChild(scoreText);
	scoreText.x = -200;
	scoreText.y = 80;
	// Level display
	levelText = new Text2('Level: ' + player.level, {
		size: 40,
		fill: 0x55FF55
	});
	levelText.anchor.set(0, 0);
	LK.gui.topRight.addChild(levelText);
	levelText.x = -200;
	levelText.y = 140;
	// Monster counter display
	monsterText = new Text2('Monsters: 0', {
		size: 40,
		fill: 0xFF9955
	});
	monsterText.anchor.set(0, 0);
	LK.gui.topRight.addChild(monsterText);
	monsterText.x = -200;
	monsterText.y = 200;
	// Update UI displays
	updateUI();
}
function updateUI() {
	healthText.setText('Health: ' + player.health);
	scoreText.setText('Score: ' + player.score);
	levelText.setText('Level: ' + player.level);
	monsterText.setText('Monsters: ' + monsters.length);
	// Update score in LK system
	LK.setScore(player.score);
}
function createControlButtons() {
	// Create directional buttons with increased size and more centered position
	controlButtons.up = new ControlButton('up');
	controlButtons.up.x = 400;
	controlButtons.up.y = 2732 - 700;
	game.addChild(controlButtons.up);
	controlButtons.right = new ControlButton('right');
	controlButtons.right.x = 650;
	controlButtons.right.y = 2732 - 500;
	game.addChild(controlButtons.right);
	controlButtons.down = new ControlButton('down');
	controlButtons.down.x = 400;
	controlButtons.down.y = 2732 - 300;
	game.addChild(controlButtons.down);
	controlButtons.left = new ControlButton('left');
	controlButtons.left.x = 150;
	controlButtons.left.y = 2732 - 500;
	game.addChild(controlButtons.left);
	// Create attack button more centered on the right side
	controlButtons.attack = new ControlButton('attack');
	controlButtons.attack.x = 2048 - 400;
	controlButtons.attack.y = 2732 - 500;
	game.addChild(controlButtons.attack);
	// Create boost button in the middle, moved a little to the right
	controlButtons.boost = new BoostButton();
	controlButtons.boost.x = 2048 / 2 + 150;
	controlButtons.boost.y = 2732 - 500;
	game.addChild(controlButtons.boost);
}
function rayCasting() {
	var rayAngle, distToWall, rayDirX, rayDirY, mapCheckX, mapCheckY;
	var distX, distY;
	var rayStartX = player.x;
	var rayStartY = player.y;
	for (var rayIdx = 0; rayIdx < NUM_RAYS; rayIdx++) {
		// Calculate ray angle (center ray + offset based on ray index)
		rayAngle = player.dir - HALF_FOV + rayIdx / NUM_RAYS * FOV;
		// Get direction vector
		rayDirX = Math.cos(rayAngle);
		rayDirY = Math.sin(rayAngle);
		// Distance to wall
		distToWall = 0;
		hitWall = false;
		// Step size for ray casting
		var stepSizeX = Math.abs(1 / rayDirX);
		var stepSizeY = Math.abs(1 / rayDirY);
		// Which block we're checking
		mapCheckX = Math.floor(rayStartX);
		mapCheckY = Math.floor(rayStartY);
		// Length of ray from current position to next x or y-side
		var sideDistX, sideDistY;
		// Direction to step in x or y direction (either +1 or -1)
		var stepX = rayDirX >= 0 ? 1 : -1;
		var stepY = rayDirY >= 0 ? 1 : -1;
		// Calculate distance to first x and y side
		if (rayDirX < 0) {
			sideDistX = (rayStartX - mapCheckX) * stepSizeX;
		} else {
			sideDistX = (mapCheckX + 1.0 - rayStartX) * stepSizeX;
		}
		if (rayDirY < 0) {
			sideDistY = (rayStartY - mapCheckY) * stepSizeY;
		} else {
			sideDistY = (mapCheckY + 1.0 - rayStartY) * stepSizeY;
		}
		// Perform DDA (Digital Differential Analysis)
		var hit = false;
		var side = 0; // 0 for x-side, 1 for y-side
		var maxDistance = MAX_RENDER_DISTANCE;
		while (!hit && distToWall < maxDistance) {
			// Jump to next map square
			if (sideDistX < sideDistY) {
				sideDistX += stepSizeX;
				mapCheckX += stepX;
				side = 0;
			} else {
				sideDistY += stepSizeY;
				mapCheckY += stepY;
				side = 1;
			}
			// Check if ray has hit a wall
			if (mapCheckX < 0 || mapCheckX >= MAP_SIZE || mapCheckY < 0 || mapCheckY >= MAP_SIZE || !map[mapCheckY] || !map[mapCheckY][mapCheckX]) {
				hit = true;
				distToWall = maxDistance;
			} else if (map[mapCheckY][mapCheckX].type === 1) {
				hit = true;
				// Calculate exact distance to avoid fisheye effect
				if (side === 0) {
					distToWall = (mapCheckX - rayStartX + (1 - stepX) / 2) / rayDirX;
				} else {
					distToWall = (mapCheckY - rayStartY + (1 - stepY) / 2) / rayDirY;
				}
			}
		}
		// Calculate height of wall based on distance
		var wallHeight = Math.min(2732, WALL_HEIGHT_FACTOR / distToWall);
		// Update the strip
		var strip = rayCastView.children[rayIdx];
		strip.updateStrip(STRIP_WIDTH, wallHeight, rayIdx, 1, distToWall);
	}
	// Render monsters and treasures
	renderEntities();
}
function renderEntities() {
	// First, hide all entities
	for (var i = 0; i < monsters.length; i++) {
		monsters[i].visible = false;
	}
	for (var i = 0; i < treasures.length; i++) {
		treasures[i].visible = false;
	}
	// Hide gate initially
	if (gate) {
		gate.visible = false;
	}
	// Calculate entity positions relative to player
	for (var i = 0; i < monsters.length; i++) {
		var monster = monsters[i];
		// Vector from player to monster
		var dx = monster.mapX - player.x;
		var dy = monster.mapY - player.y;
		// Distance to monster
		var dist = Math.sqrt(dx * dx + dy * dy);
		// Angle between player's direction and monster
		var angle = Math.atan2(dy, dx) - player.dir;
		// Normalize angle
		while (angle < -Math.PI) {
			angle += Math.PI * 2;
		}
		while (angle > Math.PI) {
			angle -= Math.PI * 2;
		}
		// Check if monster is in field of view
		if (Math.abs(angle) < HALF_FOV && dist < MAX_RENDER_DISTANCE) {
			// Check if there's a wall between player and monster
			var rayDirX = Math.cos(player.dir + angle);
			var rayDirY = Math.sin(player.dir + angle);
			var rayHit = castRayToPoint(player.x, player.y, monster.mapX, monster.mapY);
			if (!rayHit.hit || rayHit.dist > dist - 0.5) {
				// Monster is visible
				monster.visible = true;
				// Calculate screen position
				var screenX = (0.5 + angle / FOV) * 2048;
				// Calculate height based on distance
				var height = WALL_HEIGHT_FACTOR / dist;
				// Position monster in the middle of the screen
				monster.x = screenX;
				monster.y = 2732 / 2;
				// Scale monster based on distance
				var scale = height / 100;
				monster.scale.set(scale, scale);
			}
		}
	}
	// Render treasures with the same logic
	for (var i = 0; i < treasures.length; i++) {
		var treasure = treasures[i];
		var dx = treasure.mapX - player.x;
		var dy = treasure.mapY - player.y;
		var dist = Math.sqrt(dx * dx + dy * dy);
		var angle = Math.atan2(dy, dx) - player.dir;
		while (angle < -Math.PI) {
			angle += Math.PI * 2;
		}
		while (angle > Math.PI) {
			angle -= Math.PI * 2;
		}
		if (Math.abs(angle) < HALF_FOV && dist < MAX_RENDER_DISTANCE) {
			var rayHit = castRayToPoint(player.x, player.y, treasure.mapX, treasure.mapY);
			if (!rayHit.hit || rayHit.dist > dist - 0.5) {
				treasure.visible = true;
				var screenX = (0.5 + angle / FOV) * 2048;
				var height = WALL_HEIGHT_FACTOR / dist;
				treasure.x = screenX;
				treasure.y = 2732 / 2;
				var scale = height / 100;
				treasure.scale.set(scale, scale);
			}
		}
	}
	// Render gate with the same logic as treasures and monsters
	if (gate) {
		var dx = gate.mapX - player.x;
		var dy = gate.mapY - player.y;
		var dist = Math.sqrt(dx * dx + dy * dy);
		var angle = Math.atan2(dy, dx) - player.dir;
		while (angle < -Math.PI) {
			angle += Math.PI * 2;
		}
		while (angle > Math.PI) {
			angle -= Math.PI * 2;
		}
		if (Math.abs(angle) < HALF_FOV && dist < MAX_RENDER_DISTANCE) {
			var rayHit = castRayToPoint(player.x, player.y, gate.mapX, gate.mapY);
			if (!rayHit.hit || rayHit.dist > dist - 0.5) {
				gate.visible = true;
				var screenX = (0.5 + angle / FOV) * 2048;
				var height = WALL_HEIGHT_FACTOR / dist;
				gate.x = screenX;
				gate.y = 2732 / 2;
				var scale = height / 100;
				gate.scale.set(scale, scale);
			}
		}
	}
}
function castRayToPoint(startX, startY, targetX, targetY) {
	var rayDirX = targetX - startX;
	var rayDirY = targetY - startY;
	var distance = Math.sqrt(rayDirX * rayDirX + rayDirY * rayDirY);
	rayDirX /= distance;
	rayDirY /= distance;
	var mapCheckX = Math.floor(startX);
	var mapCheckY = Math.floor(startY);
	var stepSizeX = Math.abs(1 / rayDirX);
	var stepSizeY = Math.abs(1 / rayDirY);
	var stepX = rayDirX >= 0 ? 1 : -1;
	var stepY = rayDirY >= 0 ? 1 : -1;
	var sideDistX, sideDistY;
	if (rayDirX < 0) {
		sideDistX = (startX - mapCheckX) * stepSizeX;
	} else {
		sideDistX = (mapCheckX + 1.0 - startX) * stepSizeX;
	}
	if (rayDirY < 0) {
		sideDistY = (startY - mapCheckY) * stepSizeY;
	} else {
		sideDistY = (mapCheckY + 1.0 - startY) * stepSizeY;
	}
	var hit = false;
	var side = 0;
	var distToWall = 0;
	while (!hit && distToWall < distance) {
		if (sideDistX < sideDistY) {
			sideDistX += stepSizeX;
			mapCheckX += stepX;
			side = 0;
			distToWall = sideDistX - stepSizeX;
		} else {
			sideDistY += stepSizeY;
			mapCheckY += stepY;
			side = 1;
			distToWall = sideDistY - stepSizeY;
		}
		if (mapCheckX < 0 || mapCheckX >= MAP_SIZE || mapCheckY < 0 || mapCheckY >= MAP_SIZE || !map[mapCheckY] || !map[mapCheckY][mapCheckX]) {
			break;
		} else if (map[mapCheckY][mapCheckX].type === 1) {
			hit = true;
		}
	}
	return {
		hit: hit,
		dist: distToWall
	};
}
function updateControls() {
	// Read from control buttons
	controls.forward = controlButtons.up.pressed;
	controls.backward = controlButtons.down.pressed;
	controls.left = controlButtons.left.pressed;
	controls.right = controlButtons.right.pressed;
	controls.attack = controlButtons.attack.pressed;
	controls.boost = controlButtons.boost && controlButtons.boost.active;
}
function updatePlayerMovement(deltaTime) {
	var moveSpeed = PLAYER_MOVE_SPEED * deltaTime;
	var turnSpeed = PLAYER_TURN_SPEED * deltaTime;
	var dx = 0,
		dy = 0;
	var didMove = false;
	// Track player's last position to detect state changes
	if (player.lastX === undefined) {
		player.lastX = player.x;
	}
	if (player.lastY === undefined) {
		player.lastY = player.y;
	}
	// Handle rotation
	if (controls.left) {
		player.dir -= turnSpeed;
		while (player.dir < 0) {
			player.dir += Math.PI * 2;
		}
	}
	if (controls.right) {
		player.dir += turnSpeed;
		while (player.dir >= Math.PI * 2) {
			player.dir -= Math.PI * 2;
		}
	}
	// Handle movement
	if (controls.forward) {
		dx += Math.cos(player.dir) * moveSpeed;
		dy += Math.sin(player.dir) * moveSpeed;
		didMove = true;
	}
	if (controls.backward) {
		dx -= Math.cos(player.dir) * moveSpeed;
		dy -= Math.sin(player.dir) * moveSpeed;
		didMove = true;
	}
	// Collision detection
	var newX = player.x + dx;
	var newY = player.y + dy;
	var cellX = Math.floor(newX);
	var cellY = Math.floor(newY);
	// Check if we can move to the new position
	if (cellX >= 0 && cellX < MAP_SIZE && cellY >= 0 && cellY < MAP_SIZE && map[cellY] && map[cellY][cellX]) {
		if (map[cellY][cellX].type === 0) {
			player.x = newX;
			player.y = newY;
			if (didMove && LK.ticks % 20 === 0) {
				LK.getSound('walk').play();
			}
		}
	}
	// Only attempt to attack if attack button is pressed and we can attack
	if (controls.attack && canAttack) {
		attackAction();
	}
	// Check for collisions with monsters
	checkMonsterCollisions();
	// Check for collisions with treasures
	checkTreasureCollisions();
	// Check for gate collision
	checkGateCollision();
	// Update player marker on minimap
	updateMiniMap();
	// Update player's last position
	player.lastX = player.x;
	player.lastY = player.y;
}
function attackAction() {
	// Check if attack is on cooldown
	if (!canAttack) {
		return;
	}
	// Set attack on cooldown
	canAttack = false;
	// Play attack sound
	LK.getSound('attack').play();
	// Create main projectile from right side
	var projectile = new Projectile();
	// Set scale for appropriate size
	var scale = 1.0;
	projectile.scale.set(scale, scale);
	// Set this as the original, non-mirrored projectile
	projectile.setMirrored(false);
	// Initialize and fire the projectile from right to left
	projectile.fire(2048 + 100, 2732 / 2);
	// Add visual pulse effect to make projectile more visible
	tween(projectile, {
		alpha: 0.7,
		scaleX: scale * 1.2,
		scaleY: scale * 1.2
	}, {
		duration: 500,
		onFinish: function onFinish() {
			tween(projectile, {
				alpha: 1,
				scaleX: scale,
				scaleY: scale
			}, {
				duration: 500
			});
		}
	});
	// Add to the game
	projectiles.push(projectile);
	game.addChild(projectile);
	// Create clone projectile on opposite side (left side)
	var cloneProjectile = new Projectile();
	cloneProjectile.scale.set(scale, scale);
	// Set this as the mirrored clone
	cloneProjectile.setMirrored(true);
	// Make clone start on the left side
	cloneProjectile.x = -100;
	cloneProjectile.y = 2732 / 2;
	cloneProjectile.visible = true;
	cloneProjectile.active = true;
	// Reverse speed to make it move right to left
	cloneProjectile.speed = -cloneProjectile.speed;
	// Add visual pulse effect to clone
	tween(cloneProjectile, {
		alpha: 0.7,
		scaleX: scale * 1.2,
		scaleY: scale * 1.2
	}, {
		duration: 500,
		onFinish: function onFinish() {
			tween(cloneProjectile, {
				alpha: 1,
				scaleX: scale,
				scaleY: scale
			}, {
				duration: 500
			});
		}
	});
	// Add to the game
	projectiles.push(cloneProjectile);
	game.addChild(cloneProjectile);
	// Start cooldown animation for attack button
	var attackButton = controlButtons.attack;
	attackButton.updateCooldown(0);
	// Create a tween for the cooldown visual effect
	tween(attackButton, {
		_cooldownProgress: 1
	}, {
		duration: attackCooldown,
		onFinish: function onFinish() {
			canAttack = true;
			attackButton.updateCooldown(1);
		}
	});
	// Update button visual during cooldown
	var _cooldownTick = function cooldownTick(progress) {
		attackButton.updateCooldown(progress);
		if (progress < 1) {
			LK.setTimeout(function () {
				_cooldownTick(progress + 0.05);
			}, attackCooldown / 20);
		}
	};
	_cooldownTick(0);
	// Reset cooldown after specified time
	LK.setTimeout(function () {
		canAttack = true;
	}, attackCooldown);
}
function updateProjectiles(deltaTime) {
	for (var i = projectiles.length - 1; i >= 0; i--) {
		var projectile = projectiles[i];
		// Update projectile position
		var remove = projectile.update(deltaTime);
		// Check if projectile has gone off screen or should be removed
		if (remove) {
			// Remove projectile
			projectile.destroy();
			projectiles.splice(i, 1);
			continue;
		}
		// Check if projectile hits a monster
		var hitMonster = false;
		var hitMonsterIndex = -1;
		for (var j = 0; j < monsters.length; j++) {
			var monster = monsters[j];
			// Calculate distance between player and monster for range check
			var distToMonster = Math.sqrt(Math.pow(monster.mapX - player.x, 2) + Math.pow(monster.mapY - player.y, 2));
			// Only allow hits if monster is within range (5 map units) - stricter range check
			var inRange = distToMonster <= 2.5;
			// Simple screen space collision check with added distance constraint
			if (monster.visible && inRange && Math.abs(projectile.x - monster.x) < 100 && Math.abs(projectile.y - monster.y) < 100) {
				hitMonster = true;
				hitMonsterIndex = j;
				break;
			}
		}
		// Handle monster hit
		if (hitMonster && hitMonsterIndex !== -1) {
			var monster = monsters[hitMonsterIndex];
			var killed = monster.takeDamage();
			if (killed) {
				// Remove monster
				map[Math.floor(monster.mapY)][Math.floor(monster.mapX)].removeMonster();
				monster.destroy();
				monsters.splice(hitMonsterIndex, 1);
				// Increase score
				player.score += 10;
				updateUI();
				// Check for level completion
				checkLevelCompletion();
			}
			// Remove projectile on hit
			projectile.destroy();
			projectiles.splice(i, 1);
		}
	}
}
function checkMonsterCollisions() {
	for (var i = 0; i < monsters.length; i++) {
		var monster = monsters[i];
		var dx = monster.mapX - player.x;
		var dy = monster.mapY - player.y;
		var dist = Math.sqrt(dx * dx + dy * dy);
		if (dist < 0.5) {
			// Player hit by monster
			player.health--;
			updateUI();
			// Visual feedback
			LK.effects.flashScreen(0xff0000, 300);
			// Play sound
			LK.getSound('hit').play();
			// Push player back slightly
			player.x -= dx * 0.3;
			player.y -= dy * 0.3;
			// Check game over
			if (player.health <= 0) {
				LK.showGameOver();
			}
			break;
		}
	}
}
function checkTreasureCollisions() {
	for (var i = treasures.length - 1; i >= 0; i--) {
		var treasure = treasures[i];
		var dx = treasure.mapX - player.x;
		var dy = treasure.mapY - player.y;
		var dist = Math.sqrt(dx * dx + dy * dy);
		if (dist < 0.5) {
			// Collect treasure
			player.score += treasure.value * 5;
			updateUI();
			// Play sound
			LK.getSound('collect').play();
			// Remove treasure
			map[Math.floor(treasure.mapY)][Math.floor(treasure.mapX)].removeTreasure();
			treasure.destroy();
			treasures.splice(i, 1);
			// Check level completion
			checkLevelCompletion();
		}
	}
}
function checkGateCollision() {
	if (!gate) {
		return;
	}
	var dx = gate.mapX - player.x;
	var dy = gate.mapY - player.y;
	var dist = Math.sqrt(dx * dx + dy * dy);
	if (dist < 0.7) {
		// Check if all monsters need to be defeated first
		if (monsters.length > 0) {
			// Display message that monsters need to be defeated
			var warningText = new Text2('Defeat all monsters\nbefore exiting!', {
				size: 60,
				fill: 0xFF5555
			});
			warningText.anchor.set(0.5, 0.5);
			warningText.x = 2048 / 2;
			warningText.y = 2732 / 2;
			game.addChild(warningText);
			// Remove text after a few seconds
			LK.setTimeout(function () {
				warningText.destroy();
			}, 2000);
			return;
		}
		// Player reached the gate - complete level
		// Play collect sound
		LK.getSound('collect').play();
		// Add points for completing level
		player.score += 50 * player.level;
		// Remove gate from map
		map[Math.floor(gate.mapY)][Math.floor(gate.mapX)].removeGate();
		gate.destroy();
		gate = null;
		// Level up
		player.level++;
		storage.level = player.level;
		// Restore health
		player.health = Math.min(player.health + 2, 5);
		// Update UI
		updateUI();
		// Show level complete with gate messaging
		var levelCompleteText = new Text2('Level ' + (player.level - 1) + ' Complete!\nYou found the exit!', {
			size: 80,
			fill: 0x00FF55
		});
		levelCompleteText.anchor.set(0.5, 0.5);
		levelCompleteText.x = 2048 / 2;
		levelCompleteText.y = 2732 / 2;
		game.addChild(levelCompleteText);
		// Generate new level after a delay
		LK.setTimeout(function () {
			levelCompleteText.destroy();
			generateMap();
		}, 2000);
	}
}
function checkLevelCompletion() {
	// Level is only considered "complete" if all monsters are defeated
	// This allows the gate to activate
	if (monsters.length === 0) {
		// If gate is not visible, make it pulse more dramatically to draw attention
		if (gate) {
			tween(gate, {
				alpha: 0.2
			}, {
				duration: 300,
				onFinish: function onFinish() {
					tween(gate, {
						alpha: 1
					}, {
						duration: 300
					});
				}
			});
			// Show hint text
			var gateHintText = new Text2('Find the exit gate!', {
				size: 60,
				fill: 0x00FF55
			});
			gateHintText.anchor.set(0.5, 0.5);
			gateHintText.x = 2048 / 2;
			gateHintText.y = 200;
			game.addChild(gateHintText);
			// Remove hint after a few seconds
			LK.setTimeout(function () {
				gateHintText.destroy();
			}, 3000);
		}
	} else {
		// Show hint text about defeating monsters first
		if (gate && gate.visible && LK.ticks % 300 === 0) {
			var monsterHintText = new Text2('Defeat all monsters to activate the exit!', {
				size: 60,
				fill: 0xFF5555
			});
			monsterHintText.anchor.set(0.5, 0.5);
			monsterHintText.x = 2048 / 2;
			monsterHintText.y = 200;
			game.addChild(monsterHintText);
			// Remove hint after a few seconds
			LK.setTimeout(function () {
				monsterHintText.destroy();
			}, 3000);
		}
	}
}
function updateMiniMap() {
	// Update player marker position on minimap
	playerMarker.x = miniMap.x + player.x * CELL_SIZE * MINI_MAP_SCALE;
	playerMarker.y = miniMap.y + player.y * CELL_SIZE * MINI_MAP_SCALE;
	// Draw player direction indicator
	var dirX = Math.cos(player.dir) * 15;
	var dirY = Math.sin(player.dir) * 15;
}
// Game update method
game.update = function () {
	var currentTime = Date.now();
	var deltaTime = currentTime - lastTime;
	lastTime = currentTime;
	// Update controls
	updateControls();
	// Update player
	updatePlayerMovement(deltaTime);
	// Update monsters (only move every few ticks to make movement slower)
	if (LK.ticks % 10 === 0) {
		for (var i = 0; i < monsters.length; i++) {
			MonsterAI.moveTowardsPlayer(monsters[i], player.x, player.y, map);
		}
	}
	// Update projectiles
	updateProjectiles(deltaTime);
	// Update raycast view
	rayCasting();
};
// Game initialization
setupGame();
// Event handlers
game.down = function (x, y, obj) {
	// Handle general screen press
};
game.up = function (x, y, obj) {
	// Handle general screen release
};
game.move = function (x, y, obj) {
	// Handle general screen move
};
var MonsterAI = {
	moveTowardsPlayer: function moveTowardsPlayer(monster, playerX, playerY, map) {
		// Get monster's map position
		var monsterX = monster.mapX;
		var monsterY = monster.mapY;
		// Vector from monster to player
		var dx = playerX - monsterX;
		var dy = playerY - monsterY;
		// Only move if monster is within a certain distance to the player
		var dist = Math.sqrt(dx * dx + dy * dy);
		if (dist > 8) {
			return;
		} // Don't move if too far away
		// Normalize direction vector
		var length = Math.sqrt(dx * dx + dy * dy);
		if (length > 0) {
			dx /= length;
			dy /= length;
		}
		// Movement speed (slower than player)
		var moveSpeed = 0.05; // Increased for smoother animation
		// Calculate potential new position
		var newX = monsterX + dx * moveSpeed;
		var newY = monsterY + dy * moveSpeed;
		// Round to get map cell coordinates
		var cellX = Math.floor(newX);
		var cellY = Math.floor(newY);
		// Check if new position is valid (not a wall or another monster)
		if (cellX >= 0 && cellX < MAP_SIZE && cellY >= 0 && cellY < MAP_SIZE) {
			if (map[cellY][cellX].type === 0 && !map[cellY][cellX].monster) {
				// Update map cell references
				map[Math.floor(monsterY)][Math.floor(monsterX)].removeMonster();
				map[cellY][cellX].addMonster();
				// Update monster position with smooth animation
				monster.mapX = newX;
				monster.mapY = newY;
				// Calculate screen position for the moved monster
				var monsterToPlayerDX = playerX - newX;
				var monsterToPlayerDY = playerY - newY;
				var monsterDist = Math.sqrt(monsterToPlayerDX * monsterToPlayerDX + monsterToPlayerDY * monsterToPlayerDY);
				var monsterAngle = Math.atan2(monsterToPlayerDY, monsterToPlayerDX) - Math.atan2(dy, dx);
				// Position monster in the middle of the screen with updated coordinates
				monster.x = 2048 / 2;
				monster.y = 2732 / 2;
				// Scale monster based on distance
				var scale = WALL_HEIGHT_FACTOR / (monsterDist * 100);
				monster.scale.set(scale, scale);
			}
		}
	}
};
function ensureMapConnectivity() {
	// Flood fill from player starting position to check accessibility
	var visited = [];
	for (var y = 0; y < MAP_SIZE; y++) {
		visited[y] = [];
		for (var x = 0; x < MAP_SIZE; x++) {
			visited[y][x] = false;
		}
	}
	var queue = [];
	// Start from player position (1,1)
	queue.push({
		x: 1,
		y: 1
	});
	visited[1][1] = true;
	// Perform flood fill
	while (queue.length > 0) {
		var current = queue.shift();
		var x = current.x;
		var y = current.y;
		// Check all four neighbors
		var neighbors = [{
			x: x + 1,
			y: y
		}, {
			x: x - 1,
			y: y
		}, {
			x: x,
			y: y + 1
		}, {
			x: x,
			y: y - 1
		}];
		for (var i = 0; i < neighbors.length; i++) {
			var nx = neighbors[i].x;
			var ny = neighbors[i].y;
			// Check if neighbor is valid and not visited
			if (nx >= 0 && nx < MAP_SIZE && ny >= 0 && ny < MAP_SIZE && map[ny][nx].type === 0 && !visited[ny][nx]) {
				visited[ny][nx] = true;
				queue.push({
					x: nx,
					y: ny
				});
			}
		}
	}
	// Check for unreachable areas and create paths to them
	for (var y = 1; y < MAP_SIZE - 1; y++) {
		for (var x = 1; x < MAP_SIZE - 1; x++) {
			// If it's a floor tile but wasn't visited, it's unreachable
			if (map[y][x].type === 0 && !visited[y][x]) {
				// Find the nearest accessible cell
				var nearestX = -1;
				var nearestY = -1;
				var minDist = MAP_SIZE * MAP_SIZE;
				for (var cy = 1; cy < MAP_SIZE - 1; cy++) {
					for (var cx = 1; cx < MAP_SIZE - 1; cx++) {
						if (visited[cy][cx]) {
							var dist = (cx - x) * (cx - x) + (cy - y) * (cy - y);
							if (dist < minDist) {
								minDist = dist;
								nearestX = cx;
								nearestY = cy;
							}
						}
					}
				}
				// Create a path from the unreachable area to the nearest accessible area
				if (nearestX !== -1) {
					createPath(x, y, nearestX, nearestY);
					// Update visited map by doing a mini flood fill from this newly connected point
					var pathQueue = [{
						x: x,
						y: y
					}];
					visited[y][x] = true;
					while (pathQueue.length > 0) {
						var current = pathQueue.shift();
						var px = current.x;
						var py = current.y;
						var pathNeighbors = [{
							x: px + 1,
							y: py
						}, {
							x: px - 1,
							y: py
						}, {
							x: px,
							y: py + 1
						}, {
							x: px,
							y: py - 1
						}];
						for (var j = 0; j < pathNeighbors.length; j++) {
							var nx = pathNeighbors[j].x;
							var ny = pathNeighbors[j].y;
							if (nx >= 0 && nx < MAP_SIZE && ny >= 0 && ny < MAP_SIZE && map[ny][nx].type === 0 && !visited[ny][nx]) {
								visited[ny][nx] = true;
								pathQueue.push({
									x: nx,
									y: ny
								});
							}
						}
					}
				}
			}
		}
	}
}
function createPath(startX, startY, endX, endY) {
	// Determine direction (horizontal or vertical first)
	if (Math.random() < 0.5) {
		// Horizontal first, then vertical
		var x = startX;
		while (x !== endX) {
			x += x < endX ? 1 : -1;
			if (map[startY][x].type === 1) {
				map[startY][x].setType(0); // Convert wall to floor
			}
		}
		var y = startY;
		while (y !== endY) {
			y += y < endY ? 1 : -1;
			if (map[y][endX].type === 1) {
				map[y][endX].setType(0); // Convert wall to floor
			}
		}
	} else {
		// Vertical first, then horizontal
		var y = startY;
		while (y !== endY) {
			y += y < endY ? 1 : -1;
			if (map[y][startX].type === 1) {
				map[y][startX].setType(0); // Convert wall to floor
			}
		}
		var x = startX;
		while (x !== endX) {
			x += x < endX ? 1 : -1;
			if (map[endY][x].type === 1) {
				map[endY][x].setType(0); // Convert wall to floor
			}
		}
	}
} /**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/**** 
* Classes
****/ 
var BoostButton = Container.expand(function () {
	var self = Container.call(this);
	var buttonSprite = self.attachAsset('attackButton', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 2.5,
		scaleY: 2.5
	});
	// Change tint to blue to differentiate from attack button
	buttonSprite.tint = 0x3366ff;
	// Add text to indicate boost function
	var boostText = new Text2('BOOST', {
		size: 40,
		fill: 0xFFFFFF
	});
	boostText.anchor.set(0.5, 0.5);
	self.addChild(boostText);
	self.pressed = false;
	self.active = false;
	self.cooldown = false;
	self.down = function (x, y, obj) {
		if (self.cooldown) return;
		self.pressed = true;
		buttonSprite.alpha = 0.7;
		self.active = true;
		// Apply boost effect
		PLAYER_MOVE_SPEED *= 3;
	};
	self.up = function (x, y, obj) {
		self.pressed = false;
		if (self.active) {
			// Reset speed to normal
			PLAYER_MOVE_SPEED = PLAYER_MOVE_SPEED / 3;
			self.active = false;
			// Set cooldown
			self.cooldown = true;
			buttonSprite.alpha = 0.3;
			// Reset cooldown after 5 seconds
			LK.setTimeout(function () {
				self.cooldown = false;
				buttonSprite.alpha = 1;
			}, 5000);
		}
	};
	return self;
});
var ControlButton = Container.expand(function (direction) {
	var self = Container.call(this);
	var buttonSprite = self.attachAsset('controlButton', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 2.5,
		scaleY: 2.5
	});
	var arrowSprite = self.attachAsset('buttonArrow', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 2.0,
		scaleY: 2.0
	});
	// Set arrow direction based on button type
	if (direction === 'up') {
		arrowSprite.rotation = 0;
	} else if (direction === 'right') {
		arrowSprite.rotation = Math.PI / 2;
	} else if (direction === 'down') {
		arrowSprite.rotation = Math.PI;
	} else if (direction === 'left') {
		arrowSprite.rotation = Math.PI * 1.5;
	} else if (direction === 'attack') {
		arrowSprite.visible = false;
		buttonSprite = self.attachAsset('attackButton', {
			anchorX: 0.5,
			anchorY: 0.5,
			scaleX: 3.5,
			scaleY: 3.5
		});
	}
	self.direction = direction;
	self.pressed = false;
	self.updateCooldown = function (ratio) {
		if (self.direction === 'attack') {
			// Update the button alpha based on cooldown ratio (0 to 1)
			buttonSprite.alpha = 0.3 + ratio * 0.7;
		}
	};
	self.down = function (x, y, obj) {
		self.pressed = true;
		buttonSprite.alpha = 0.7;
		if (self.direction === 'attack' && canAttack) {
			// Trigger attack immediately when the attack button is pressed
			attackAction();
		}
	};
	self.up = function (x, y, obj) {
		self.pressed = false;
		if (self.direction === 'attack') {
			if (!canAttack) {
				buttonSprite.alpha = 0.3; // Show as disabled
			} else {
				buttonSprite.alpha = 1;
			}
		} else {
			buttonSprite.alpha = 1;
		}
	};
	return self;
});
var Gate = Container.expand(function () {
	var self = Container.call(this);
	// Create gate visual using existing wall asset but with a different color
	var gateSprite = self.attachAsset('wall', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	// Make the gate distinct with a green tint
	gateSprite.tint = 0x00FF00;
	self.mapX = 0;
	self.mapY = 0;
	// Pulse animation to make gate more visible
	var _animateGate = function animateGate() {
		tween(gateSprite, {
			alpha: 0.7,
			scaleX: 1.1,
			scaleY: 1.1
		}, {
			duration: 1000,
			onFinish: function onFinish() {
				tween(gateSprite, {
					alpha: 1,
					scaleX: 1.0,
					scaleY: 1.0
				}, {
					duration: 1000,
					onFinish: _animateGate
				});
			}
		});
	};
	// Start the pulsing animation
	_animateGate();
	return self;
});
var MapCell = Container.expand(function () {
	var self = Container.call(this);
	self.type = 0; // 0 = floor, 1 = wall
	self.monster = null;
	self.treasure = null;
	self.gate = null;
	self.setType = function (type) {
		self.type = type;
		self.updateVisual();
	};
	self.updateVisual = function () {
		self.removeChildren();
		if (self.type === 1) {
			self.attachAsset('mapWall', {
				anchorX: 0,
				anchorY: 0
			});
		} else {
			self.attachAsset('mapFloor', {
				anchorX: 0,
				anchorY: 0
			});
		}
	};
	self.addMonster = function () {
		if (self.type === 0 && !self.monster && !self.treasure) {
			self.monster = true;
			return true;
		}
		return false;
	};
	self.addTreasure = function () {
		if (self.type === 0 && !self.monster && !self.treasure) {
			self.treasure = true;
			return true;
		}
		return false;
	};
	self.removeMonster = function () {
		self.monster = null;
	};
	self.removeTreasure = function () {
		self.treasure = null;
	};
	self.addGate = function () {
		if (self.type === 0 && !self.monster && !self.treasure && !self.gate) {
			self.gate = true;
			return true;
		}
		return false;
	};
	self.removeGate = function () {
		self.gate = null;
	};
	return self;
});
var Monster = Container.expand(function () {
	var self = Container.call(this);
	var monsterSprite = self.attachAsset('monster', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.mapX = 0;
	self.mapY = 0;
	self.health = 3;
	self.takeDamage = function () {
		self.health -= 1;
		LK.getSound('hit').play();
		// Visual feedback for hit
		tween(monsterSprite, {
			alpha: 0.2
		}, {
			duration: 100,
			onFinish: function onFinish() {
				tween(monsterSprite, {
					alpha: 1
				}, {
					duration: 100
				});
			}
		});
		return self.health <= 0;
	};
	return self;
});
var Projectile = Container.expand(function () {
	var self = Container.call(this);
	var projectileSprite = self.attachAsset('projectile', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 0.5,
		scaleY: 0.5
	});
	self.speed = 10; // Speed of horizontal movement
	self.startX = 0; // Starting X position on screen
	self.targetX = 0; // How far to move
	self.visible = false; // Start invisible
	self.active = false; // Projectile state
	self.isClone = false; // Track if this is a mirrored clone
	// Method to flip the projectile sprite horizontally
	self.setMirrored = function (mirrored) {
		self.isClone = mirrored;
		if (mirrored) {
			// Flip the sprite horizontally by setting negative scale
			projectileSprite.scaleX = -0.5;
		} else {
			projectileSprite.scaleX = 0.5;
		}
	};
	self.update = function (deltaTime) {
		if (!self.active) {
			return false;
		}
		// Move projectile based on speed direction
		self.x -= self.speed;
		// Create pulsating effect for better visibility
		if (LK.ticks % 20 === 0) {
			tween(projectileSprite, {
				scaleX: self.isClone ? -0.6 : 0.6,
				scaleY: 0.6
			}, {
				duration: 200,
				onFinish: function onFinish() {
					tween(projectileSprite, {
						scaleX: self.isClone ? -0.5 : 0.5,
						scaleY: 0.5
					}, {
						duration: 200
					});
				}
			});
		}
		// Return true if projectile has gone off either side of screen
		if (self.speed > 0) {
			// For projectiles moving left to right (clones), check right boundary
			return self.x > 2048 + 100;
		} else {
			// For normal projectiles moving right to left, check left boundary
			return self.x < -100;
		}
	};
	self.fire = function (screenX, screenY) {
		// Position at right side of screen
		self.x = 2048 + 100; // Start off-screen to the right
		self.y = screenY || 2732 - 400; // Position at bottom of screen instead of middle
		self.visible = true;
		self.active = true;
	};
	return self;
});
var RaycastStrip = Container.expand(function () {
	var self = Container.call(this);
	var wall = self.attachAsset('wall', {
		anchorX: 0,
		anchorY: 0
	});
	var ceiling = self.attachAsset('ceiling', {
		anchorX: 0,
		anchorY: 0
	});
	var floor = self.attachAsset('floor', {
		anchorX: 0,
		anchorY: 0
	});
	self.updateStrip = function (stripWidth, wallHeight, stripIdx, wallType, distance) {
		// Wall setup
		wall.width = stripWidth;
		wall.height = wallHeight;
		wall.y = (2732 - wallHeight) / 2;
		// Ceiling setup
		ceiling.width = stripWidth;
		ceiling.height = wall.y;
		ceiling.y = 0;
		// Floor setup
		floor.width = stripWidth;
		floor.height = ceiling.height;
		floor.y = wall.y + wallHeight;
		// Adjust positions
		self.x = stripIdx * stripWidth;
		// Add distance shading effect
		var shade = Math.max(0.3, 1 - distance / 10);
		wall.alpha = shade;
		ceiling.alpha = shade * 0.7;
		floor.alpha = shade * 0.8;
	};
	return self;
});
var Treasure = Container.expand(function () {
	var self = Container.call(this);
	var treasureSprite = self.attachAsset('treasure', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.mapX = 0;
	self.mapY = 0;
	self.value = 1;
	// Animate the treasure to make it more appealing
	var _animateTreasure = function animateTreasure() {
		// Animation removed to stop spinning
	};
	// No longer calling animation
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x111111
});
/**** 
* Game Code
****/ 
// Game constants
var MAP_SIZE = 16;
var CELL_SIZE = 20;
var MINI_MAP_SCALE = 1;
var STRIP_WIDTH = 32; // Increased strip width to reduce ray count
var NUM_RAYS = Math.ceil(2048 / STRIP_WIDTH);
var FOV = Math.PI / 3; // 60 degrees field of view
var HALF_FOV = FOV / 2;
var PLAYER_MOVE_SPEED = 0.002; // Reduced from 0.005 to make movement slower
var PLAYER_TURN_SPEED = 0.002; // Reduced from 0.005 to make turning slower
var WALL_HEIGHT_FACTOR = 600;
var MAX_RENDER_DISTANCE = 16;
var MONSTER_COUNT = 5;
var TREASURE_COUNT = 10;
// Game state
var map = [];
var player = {
	x: 1.5,
	y: 1.5,
	dir: 0,
	health: 5,
	score: 0,
	level: 1
};
var controls = {
	forward: false,
	backward: false,
	left: false,
	right: false,
	attack: false,
	boost: false
};
var monsters = [];
var treasures = [];
var projectiles = [];
var gate = null;
var lastTime = Date.now();
var canAttack = true;
var attackCooldown = 3000; // 3 second cooldown
// UI elements
var miniMap;
var rayCastView;
var healthText;
var scoreText;
var levelText;
var monsterText;
var controlButtons = {};
var playerMarker;
// Setup game
function setupGame() {
	// Create the rayCast view container
	rayCastView = new Container();
	game.addChild(rayCastView);
	// Create raycast strips
	for (var i = 0; i < NUM_RAYS; i++) {
		var strip = new RaycastStrip();
		rayCastView.addChild(strip);
	}
	// Create minimap container
	miniMap = new Container();
	miniMap.x = (2048 - MAP_SIZE * CELL_SIZE * MINI_MAP_SCALE) / 2; // Center horizontally
	miniMap.y = 20; // Keep at top
	game.addChild(miniMap);
	// Generate map
	generateMap();
	// Create player marker
	playerMarker = game.addChild(LK.getAsset('player', {
		anchorX: 0.5,
		anchorY: 0.5
	}));
	// Create UI elements
	createUI();
	// Create control buttons
	createControlButtons();
	// Start background music
	LK.playMusic('dungeon');
}
function generateMap() {
	// Clear existing map
	miniMap.removeChildren();
	map = [];
	// Remove existing monsters and treasures
	for (var i = 0; i < monsters.length; i++) {
		monsters[i].destroy();
	}
	monsters = [];
	for (var i = 0; i < treasures.length; i++) {
		treasures[i].destroy();
	}
	treasures = [];
	// Clear projectiles
	for (var i = 0; i < projectiles.length; i++) {
		projectiles[i].destroy();
	}
	projectiles = [];
	// Generate base map with borders
	for (var y = 0; y < MAP_SIZE; y++) {
		map[y] = [];
		for (var x = 0; x < MAP_SIZE; x++) {
			var cell = new MapCell();
			cell.x = x * CELL_SIZE * MINI_MAP_SCALE;
			cell.y = y * CELL_SIZE * MINI_MAP_SCALE;
			// Create outer walls
			if (x === 0 || y === 0 || x === MAP_SIZE - 1 || y === MAP_SIZE - 1) {
				cell.setType(1); // Wall
			} else {
				// Random interior walls based on level difficulty
				var wallChance = 0.2 + player.level * 0.03;
				if (Math.random() < wallChance && !(x === 1 && y === 1)) {
					// Ensure starting position is clear
					cell.setType(1); // Wall
				} else {
					cell.setType(0); // Floor
				}
			}
			map[y][x] = cell;
			miniMap.addChild(cell);
		}
	}
	// Check map connectivity and fix walled-off areas
	ensureMapConnectivity();
	// Create monsters
	var monstersToPlace = MONSTER_COUNT + Math.floor(player.level * 0.5);
	for (var i = 0; i < monstersToPlace; i++) {
		placeMonster();
	}
	// Create treasures
	var treasuresToPlace = TREASURE_COUNT;
	for (var i = 0; i < treasuresToPlace; i++) {
		placeTreasure();
	}
	// Reset player position
	player.x = 1.5;
	player.y = 1.5;
	player.dir = 0;
	// Place exit gate
	gate = placeGate();
}
function placeMonster() {
	// Find a random empty cell
	var x, y;
	var attempts = 0;
	do {
		x = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
		y = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
		attempts++;
		// Make sure it's not too close to the player
		var distToPlayer = Math.sqrt(Math.pow(x - player.x, 2) + Math.pow(y - player.y, 2));
		if (attempts > 100) {
			break;
		} // Prevent infinite loop
	} while (map[y][x].type !== 0 || map[y][x].monster || map[y][x].treasure || distToPlayer < 3);
	if (attempts <= 100) {
		map[y][x].addMonster();
		var monster = new Monster();
		monster.mapX = x;
		monster.mapY = y;
		monster.health = 2 + Math.floor(player.level / 3); // Monsters get tougher with level
		monsters.push(monster);
		game.addChild(monster);
	}
}
function placeTreasure() {
	// Find a random empty cell
	var x, y;
	var attempts = 0;
	do {
		x = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
		y = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
		attempts++;
		if (attempts > 100) {
			break;
		} // Prevent infinite loop
	} while (map[y][x].type !== 0 || map[y][x].monster || map[y][x].treasure || map[y][x].gate);
	if (attempts <= 100) {
		map[y][x].addTreasure();
		var treasure = new Treasure();
		treasure.mapX = x;
		treasure.mapY = y;
		treasure.value = 1 + Math.floor(Math.random() * player.level);
		treasures.push(treasure);
		game.addChild(treasure);
	}
}
function placeGate() {
	// Place gate in a random valid location
	var x, y;
	var attempts = 0;
	var validPositions = [];
	// Collect all valid positions first
	for (var i = 0; i < 100; i++) {
		x = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
		y = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
		// Check if the cell is suitable
		if (map[y][x].type === 0 && !map[y][x].monster && !map[y][x].treasure && !map[y][x].gate) {
			// Make sure it's not too close to the player (at least 2 cells away)
			var distToPlayer = Math.sqrt(Math.pow(x - player.x, 2) + Math.pow(y - player.y, 2));
			if (distToPlayer > 2) {
				// Add to valid positions
				validPositions.push({
					x: x,
					y: y
				});
			}
		}
	}
	// If we found valid spots, choose one randomly
	if (validPositions.length > 0) {
		// Pick a random position from the valid ones
		var randomIndex = Math.floor(Math.random() * validPositions.length);
		var chosenPos = validPositions[randomIndex];
		var gateX = chosenPos.x;
		var gateY = chosenPos.y;
		// Place the gate
		map[gateY][gateX].addGate();
		var gate = new Gate();
		gate.mapX = gateX;
		gate.mapY = gateY;
		game.addChild(gate);
		return gate;
	}
	return null;
}
function createUI() {
	// Health display
	healthText = new Text2('Health: ' + player.health, {
		size: 40,
		fill: 0xFF5555
	});
	healthText.anchor.set(0, 0);
	LK.gui.topRight.addChild(healthText);
	healthText.x = -200;
	healthText.y = 20;
	// Score display
	scoreText = new Text2('Score: ' + player.score, {
		size: 40,
		fill: 0xFFFF55
	});
	scoreText.anchor.set(0, 0);
	LK.gui.topRight.addChild(scoreText);
	scoreText.x = -200;
	scoreText.y = 80;
	// Level display
	levelText = new Text2('Level: ' + player.level, {
		size: 40,
		fill: 0x55FF55
	});
	levelText.anchor.set(0, 0);
	LK.gui.topRight.addChild(levelText);
	levelText.x = -200;
	levelText.y = 140;
	// Monster counter display
	monsterText = new Text2('Monsters: 0', {
		size: 40,
		fill: 0xFF9955
	});
	monsterText.anchor.set(0, 0);
	LK.gui.topRight.addChild(monsterText);
	monsterText.x = -200;
	monsterText.y = 200;
	// Update UI displays
	updateUI();
}
function updateUI() {
	healthText.setText('Health: ' + player.health);
	scoreText.setText('Score: ' + player.score);
	levelText.setText('Level: ' + player.level);
	monsterText.setText('Monsters: ' + monsters.length);
	// Update score in LK system
	LK.setScore(player.score);
}
function createControlButtons() {
	// Create directional buttons with increased size and more centered position
	controlButtons.up = new ControlButton('up');
	controlButtons.up.x = 400;
	controlButtons.up.y = 2732 - 700;
	game.addChild(controlButtons.up);
	controlButtons.right = new ControlButton('right');
	controlButtons.right.x = 650;
	controlButtons.right.y = 2732 - 500;
	game.addChild(controlButtons.right);
	controlButtons.down = new ControlButton('down');
	controlButtons.down.x = 400;
	controlButtons.down.y = 2732 - 300;
	game.addChild(controlButtons.down);
	controlButtons.left = new ControlButton('left');
	controlButtons.left.x = 150;
	controlButtons.left.y = 2732 - 500;
	game.addChild(controlButtons.left);
	// Create attack button more centered on the right side
	controlButtons.attack = new ControlButton('attack');
	controlButtons.attack.x = 2048 - 400;
	controlButtons.attack.y = 2732 - 500;
	game.addChild(controlButtons.attack);
	// Create boost button in the middle, moved a little to the right
	controlButtons.boost = new BoostButton();
	controlButtons.boost.x = 2048 / 2 + 150;
	controlButtons.boost.y = 2732 - 500;
	game.addChild(controlButtons.boost);
}
function rayCasting() {
	var rayAngle, distToWall, rayDirX, rayDirY, mapCheckX, mapCheckY;
	var distX, distY;
	var rayStartX = player.x;
	var rayStartY = player.y;
	for (var rayIdx = 0; rayIdx < NUM_RAYS; rayIdx++) {
		// Calculate ray angle (center ray + offset based on ray index)
		rayAngle = player.dir - HALF_FOV + rayIdx / NUM_RAYS * FOV;
		// Get direction vector
		rayDirX = Math.cos(rayAngle);
		rayDirY = Math.sin(rayAngle);
		// Distance to wall
		distToWall = 0;
		hitWall = false;
		// Step size for ray casting
		var stepSizeX = Math.abs(1 / rayDirX);
		var stepSizeY = Math.abs(1 / rayDirY);
		// Which block we're checking
		mapCheckX = Math.floor(rayStartX);
		mapCheckY = Math.floor(rayStartY);
		// Length of ray from current position to next x or y-side
		var sideDistX, sideDistY;
		// Direction to step in x or y direction (either +1 or -1)
		var stepX = rayDirX >= 0 ? 1 : -1;
		var stepY = rayDirY >= 0 ? 1 : -1;
		// Calculate distance to first x and y side
		if (rayDirX < 0) {
			sideDistX = (rayStartX - mapCheckX) * stepSizeX;
		} else {
			sideDistX = (mapCheckX + 1.0 - rayStartX) * stepSizeX;
		}
		if (rayDirY < 0) {
			sideDistY = (rayStartY - mapCheckY) * stepSizeY;
		} else {
			sideDistY = (mapCheckY + 1.0 - rayStartY) * stepSizeY;
		}
		// Perform DDA (Digital Differential Analysis)
		var hit = false;
		var side = 0; // 0 for x-side, 1 for y-side
		var maxDistance = MAX_RENDER_DISTANCE;
		while (!hit && distToWall < maxDistance) {
			// Jump to next map square
			if (sideDistX < sideDistY) {
				sideDistX += stepSizeX;
				mapCheckX += stepX;
				side = 0;
			} else {
				sideDistY += stepSizeY;
				mapCheckY += stepY;
				side = 1;
			}
			// Check if ray has hit a wall
			if (mapCheckX < 0 || mapCheckX >= MAP_SIZE || mapCheckY < 0 || mapCheckY >= MAP_SIZE || !map[mapCheckY] || !map[mapCheckY][mapCheckX]) {
				hit = true;
				distToWall = maxDistance;
			} else if (map[mapCheckY][mapCheckX].type === 1) {
				hit = true;
				// Calculate exact distance to avoid fisheye effect
				if (side === 0) {
					distToWall = (mapCheckX - rayStartX + (1 - stepX) / 2) / rayDirX;
				} else {
					distToWall = (mapCheckY - rayStartY + (1 - stepY) / 2) / rayDirY;
				}
			}
		}
		// Calculate height of wall based on distance
		var wallHeight = Math.min(2732, WALL_HEIGHT_FACTOR / distToWall);
		// Update the strip
		var strip = rayCastView.children[rayIdx];
		strip.updateStrip(STRIP_WIDTH, wallHeight, rayIdx, 1, distToWall);
	}
	// Render monsters and treasures
	renderEntities();
}
function renderEntities() {
	// First, hide all entities
	for (var i = 0; i < monsters.length; i++) {
		monsters[i].visible = false;
	}
	for (var i = 0; i < treasures.length; i++) {
		treasures[i].visible = false;
	}
	// Hide gate initially
	if (gate) {
		gate.visible = false;
	}
	// Calculate entity positions relative to player
	for (var i = 0; i < monsters.length; i++) {
		var monster = monsters[i];
		// Vector from player to monster
		var dx = monster.mapX - player.x;
		var dy = monster.mapY - player.y;
		// Distance to monster
		var dist = Math.sqrt(dx * dx + dy * dy);
		// Angle between player's direction and monster
		var angle = Math.atan2(dy, dx) - player.dir;
		// Normalize angle
		while (angle < -Math.PI) {
			angle += Math.PI * 2;
		}
		while (angle > Math.PI) {
			angle -= Math.PI * 2;
		}
		// Check if monster is in field of view
		if (Math.abs(angle) < HALF_FOV && dist < MAX_RENDER_DISTANCE) {
			// Check if there's a wall between player and monster
			var rayDirX = Math.cos(player.dir + angle);
			var rayDirY = Math.sin(player.dir + angle);
			var rayHit = castRayToPoint(player.x, player.y, monster.mapX, monster.mapY);
			if (!rayHit.hit || rayHit.dist > dist - 0.5) {
				// Monster is visible
				monster.visible = true;
				// Calculate screen position
				var screenX = (0.5 + angle / FOV) * 2048;
				// Calculate height based on distance
				var height = WALL_HEIGHT_FACTOR / dist;
				// Position monster in the middle of the screen
				monster.x = screenX;
				monster.y = 2732 / 2;
				// Scale monster based on distance
				var scale = height / 100;
				monster.scale.set(scale, scale);
			}
		}
	}
	// Render treasures with the same logic
	for (var i = 0; i < treasures.length; i++) {
		var treasure = treasures[i];
		var dx = treasure.mapX - player.x;
		var dy = treasure.mapY - player.y;
		var dist = Math.sqrt(dx * dx + dy * dy);
		var angle = Math.atan2(dy, dx) - player.dir;
		while (angle < -Math.PI) {
			angle += Math.PI * 2;
		}
		while (angle > Math.PI) {
			angle -= Math.PI * 2;
		}
		if (Math.abs(angle) < HALF_FOV && dist < MAX_RENDER_DISTANCE) {
			var rayHit = castRayToPoint(player.x, player.y, treasure.mapX, treasure.mapY);
			if (!rayHit.hit || rayHit.dist > dist - 0.5) {
				treasure.visible = true;
				var screenX = (0.5 + angle / FOV) * 2048;
				var height = WALL_HEIGHT_FACTOR / dist;
				treasure.x = screenX;
				treasure.y = 2732 / 2;
				var scale = height / 100;
				treasure.scale.set(scale, scale);
			}
		}
	}
	// Render gate with the same logic as treasures and monsters
	if (gate) {
		var dx = gate.mapX - player.x;
		var dy = gate.mapY - player.y;
		var dist = Math.sqrt(dx * dx + dy * dy);
		var angle = Math.atan2(dy, dx) - player.dir;
		while (angle < -Math.PI) {
			angle += Math.PI * 2;
		}
		while (angle > Math.PI) {
			angle -= Math.PI * 2;
		}
		if (Math.abs(angle) < HALF_FOV && dist < MAX_RENDER_DISTANCE) {
			var rayHit = castRayToPoint(player.x, player.y, gate.mapX, gate.mapY);
			if (!rayHit.hit || rayHit.dist > dist - 0.5) {
				gate.visible = true;
				var screenX = (0.5 + angle / FOV) * 2048;
				var height = WALL_HEIGHT_FACTOR / dist;
				gate.x = screenX;
				gate.y = 2732 / 2;
				var scale = height / 100;
				gate.scale.set(scale, scale);
			}
		}
	}
}
function castRayToPoint(startX, startY, targetX, targetY) {
	var rayDirX = targetX - startX;
	var rayDirY = targetY - startY;
	var distance = Math.sqrt(rayDirX * rayDirX + rayDirY * rayDirY);
	rayDirX /= distance;
	rayDirY /= distance;
	var mapCheckX = Math.floor(startX);
	var mapCheckY = Math.floor(startY);
	var stepSizeX = Math.abs(1 / rayDirX);
	var stepSizeY = Math.abs(1 / rayDirY);
	var stepX = rayDirX >= 0 ? 1 : -1;
	var stepY = rayDirY >= 0 ? 1 : -1;
	var sideDistX, sideDistY;
	if (rayDirX < 0) {
		sideDistX = (startX - mapCheckX) * stepSizeX;
	} else {
		sideDistX = (mapCheckX + 1.0 - startX) * stepSizeX;
	}
	if (rayDirY < 0) {
		sideDistY = (startY - mapCheckY) * stepSizeY;
	} else {
		sideDistY = (mapCheckY + 1.0 - startY) * stepSizeY;
	}
	var hit = false;
	var side = 0;
	var distToWall = 0;
	while (!hit && distToWall < distance) {
		if (sideDistX < sideDistY) {
			sideDistX += stepSizeX;
			mapCheckX += stepX;
			side = 0;
			distToWall = sideDistX - stepSizeX;
		} else {
			sideDistY += stepSizeY;
			mapCheckY += stepY;
			side = 1;
			distToWall = sideDistY - stepSizeY;
		}
		if (mapCheckX < 0 || mapCheckX >= MAP_SIZE || mapCheckY < 0 || mapCheckY >= MAP_SIZE || !map[mapCheckY] || !map[mapCheckY][mapCheckX]) {
			break;
		} else if (map[mapCheckY][mapCheckX].type === 1) {
			hit = true;
		}
	}
	return {
		hit: hit,
		dist: distToWall
	};
}
function updateControls() {
	// Read from control buttons
	controls.forward = controlButtons.up.pressed;
	controls.backward = controlButtons.down.pressed;
	controls.left = controlButtons.left.pressed;
	controls.right = controlButtons.right.pressed;
	controls.attack = controlButtons.attack.pressed;
	controls.boost = controlButtons.boost && controlButtons.boost.active;
}
function updatePlayerMovement(deltaTime) {
	var moveSpeed = PLAYER_MOVE_SPEED * deltaTime;
	var turnSpeed = PLAYER_TURN_SPEED * deltaTime;
	var dx = 0,
		dy = 0;
	var didMove = false;
	// Track player's last position to detect state changes
	if (player.lastX === undefined) {
		player.lastX = player.x;
	}
	if (player.lastY === undefined) {
		player.lastY = player.y;
	}
	// Handle rotation
	if (controls.left) {
		player.dir -= turnSpeed;
		while (player.dir < 0) {
			player.dir += Math.PI * 2;
		}
	}
	if (controls.right) {
		player.dir += turnSpeed;
		while (player.dir >= Math.PI * 2) {
			player.dir -= Math.PI * 2;
		}
	}
	// Handle movement
	if (controls.forward) {
		dx += Math.cos(player.dir) * moveSpeed;
		dy += Math.sin(player.dir) * moveSpeed;
		didMove = true;
	}
	if (controls.backward) {
		dx -= Math.cos(player.dir) * moveSpeed;
		dy -= Math.sin(player.dir) * moveSpeed;
		didMove = true;
	}
	// Collision detection
	var newX = player.x + dx;
	var newY = player.y + dy;
	var cellX = Math.floor(newX);
	var cellY = Math.floor(newY);
	// Check if we can move to the new position
	if (cellX >= 0 && cellX < MAP_SIZE && cellY >= 0 && cellY < MAP_SIZE && map[cellY] && map[cellY][cellX]) {
		if (map[cellY][cellX].type === 0) {
			player.x = newX;
			player.y = newY;
			if (didMove && LK.ticks % 20 === 0) {
				LK.getSound('walk').play();
			}
		}
	}
	// Only attempt to attack if attack button is pressed and we can attack
	if (controls.attack && canAttack) {
		attackAction();
	}
	// Check for collisions with monsters
	checkMonsterCollisions();
	// Check for collisions with treasures
	checkTreasureCollisions();
	// Check for gate collision
	checkGateCollision();
	// Update player marker on minimap
	updateMiniMap();
	// Update player's last position
	player.lastX = player.x;
	player.lastY = player.y;
}
function attackAction() {
	// Check if attack is on cooldown
	if (!canAttack) {
		return;
	}
	// Set attack on cooldown
	canAttack = false;
	// Play attack sound
	LK.getSound('attack').play();
	// Create main projectile from right side
	var projectile = new Projectile();
	// Set scale for appropriate size
	var scale = 1.0;
	projectile.scale.set(scale, scale);
	// Set this as the original, non-mirrored projectile
	projectile.setMirrored(false);
	// Initialize and fire the projectile from right to left
	projectile.fire(2048 + 100, 2732 / 2);
	// Add visual pulse effect to make projectile more visible
	tween(projectile, {
		alpha: 0.7,
		scaleX: scale * 1.2,
		scaleY: scale * 1.2
	}, {
		duration: 500,
		onFinish: function onFinish() {
			tween(projectile, {
				alpha: 1,
				scaleX: scale,
				scaleY: scale
			}, {
				duration: 500
			});
		}
	});
	// Add to the game
	projectiles.push(projectile);
	game.addChild(projectile);
	// Create clone projectile on opposite side (left side)
	var cloneProjectile = new Projectile();
	cloneProjectile.scale.set(scale, scale);
	// Set this as the mirrored clone
	cloneProjectile.setMirrored(true);
	// Make clone start on the left side
	cloneProjectile.x = -100;
	cloneProjectile.y = 2732 / 2;
	cloneProjectile.visible = true;
	cloneProjectile.active = true;
	// Reverse speed to make it move right to left
	cloneProjectile.speed = -cloneProjectile.speed;
	// Add visual pulse effect to clone
	tween(cloneProjectile, {
		alpha: 0.7,
		scaleX: scale * 1.2,
		scaleY: scale * 1.2
	}, {
		duration: 500,
		onFinish: function onFinish() {
			tween(cloneProjectile, {
				alpha: 1,
				scaleX: scale,
				scaleY: scale
			}, {
				duration: 500
			});
		}
	});
	// Add to the game
	projectiles.push(cloneProjectile);
	game.addChild(cloneProjectile);
	// Start cooldown animation for attack button
	var attackButton = controlButtons.attack;
	attackButton.updateCooldown(0);
	// Create a tween for the cooldown visual effect
	tween(attackButton, {
		_cooldownProgress: 1
	}, {
		duration: attackCooldown,
		onFinish: function onFinish() {
			canAttack = true;
			attackButton.updateCooldown(1);
		}
	});
	// Update button visual during cooldown
	var _cooldownTick = function cooldownTick(progress) {
		attackButton.updateCooldown(progress);
		if (progress < 1) {
			LK.setTimeout(function () {
				_cooldownTick(progress + 0.05);
			}, attackCooldown / 20);
		}
	};
	_cooldownTick(0);
	// Reset cooldown after specified time
	LK.setTimeout(function () {
		canAttack = true;
	}, attackCooldown);
}
function updateProjectiles(deltaTime) {
	for (var i = projectiles.length - 1; i >= 0; i--) {
		var projectile = projectiles[i];
		// Update projectile position
		var remove = projectile.update(deltaTime);
		// Check if projectile has gone off screen or should be removed
		if (remove) {
			// Remove projectile
			projectile.destroy();
			projectiles.splice(i, 1);
			continue;
		}
		// Check if projectile hits a monster
		var hitMonster = false;
		var hitMonsterIndex = -1;
		for (var j = 0; j < monsters.length; j++) {
			var monster = monsters[j];
			// Calculate distance between player and monster for range check
			var distToMonster = Math.sqrt(Math.pow(monster.mapX - player.x, 2) + Math.pow(monster.mapY - player.y, 2));
			// Only allow hits if monster is within range (5 map units) - stricter range check
			var inRange = distToMonster <= 2.5;
			// Simple screen space collision check with added distance constraint
			if (monster.visible && inRange && Math.abs(projectile.x - monster.x) < 100 && Math.abs(projectile.y - monster.y) < 100) {
				hitMonster = true;
				hitMonsterIndex = j;
				break;
			}
		}
		// Handle monster hit
		if (hitMonster && hitMonsterIndex !== -1) {
			var monster = monsters[hitMonsterIndex];
			var killed = monster.takeDamage();
			if (killed) {
				// Remove monster
				map[Math.floor(monster.mapY)][Math.floor(monster.mapX)].removeMonster();
				monster.destroy();
				monsters.splice(hitMonsterIndex, 1);
				// Increase score
				player.score += 10;
				updateUI();
				// Check for level completion
				checkLevelCompletion();
			}
			// Remove projectile on hit
			projectile.destroy();
			projectiles.splice(i, 1);
		}
	}
}
function checkMonsterCollisions() {
	for (var i = 0; i < monsters.length; i++) {
		var monster = monsters[i];
		var dx = monster.mapX - player.x;
		var dy = monster.mapY - player.y;
		var dist = Math.sqrt(dx * dx + dy * dy);
		if (dist < 0.5) {
			// Player hit by monster
			player.health--;
			updateUI();
			// Visual feedback
			LK.effects.flashScreen(0xff0000, 300);
			// Play sound
			LK.getSound('hit').play();
			// Push player back slightly
			player.x -= dx * 0.3;
			player.y -= dy * 0.3;
			// Check game over
			if (player.health <= 0) {
				LK.showGameOver();
			}
			break;
		}
	}
}
function checkTreasureCollisions() {
	for (var i = treasures.length - 1; i >= 0; i--) {
		var treasure = treasures[i];
		var dx = treasure.mapX - player.x;
		var dy = treasure.mapY - player.y;
		var dist = Math.sqrt(dx * dx + dy * dy);
		if (dist < 0.5) {
			// Collect treasure
			player.score += treasure.value * 5;
			updateUI();
			// Play sound
			LK.getSound('collect').play();
			// Remove treasure
			map[Math.floor(treasure.mapY)][Math.floor(treasure.mapX)].removeTreasure();
			treasure.destroy();
			treasures.splice(i, 1);
			// Check level completion
			checkLevelCompletion();
		}
	}
}
function checkGateCollision() {
	if (!gate) {
		return;
	}
	var dx = gate.mapX - player.x;
	var dy = gate.mapY - player.y;
	var dist = Math.sqrt(dx * dx + dy * dy);
	if (dist < 0.7) {
		// Check if all monsters need to be defeated first
		if (monsters.length > 0) {
			// Display message that monsters need to be defeated
			var warningText = new Text2('Defeat all monsters\nbefore exiting!', {
				size: 60,
				fill: 0xFF5555
			});
			warningText.anchor.set(0.5, 0.5);
			warningText.x = 2048 / 2;
			warningText.y = 2732 / 2;
			game.addChild(warningText);
			// Remove text after a few seconds
			LK.setTimeout(function () {
				warningText.destroy();
			}, 2000);
			return;
		}
		// Player reached the gate - complete level
		// Play collect sound
		LK.getSound('collect').play();
		// Add points for completing level
		player.score += 50 * player.level;
		// Remove gate from map
		map[Math.floor(gate.mapY)][Math.floor(gate.mapX)].removeGate();
		gate.destroy();
		gate = null;
		// Level up
		player.level++;
		storage.level = player.level;
		// Restore health
		player.health = Math.min(player.health + 2, 5);
		// Update UI
		updateUI();
		// Show level complete with gate messaging
		var levelCompleteText = new Text2('Level ' + (player.level - 1) + ' Complete!\nYou found the exit!', {
			size: 80,
			fill: 0x00FF55
		});
		levelCompleteText.anchor.set(0.5, 0.5);
		levelCompleteText.x = 2048 / 2;
		levelCompleteText.y = 2732 / 2;
		game.addChild(levelCompleteText);
		// Generate new level after a delay
		LK.setTimeout(function () {
			levelCompleteText.destroy();
			generateMap();
		}, 2000);
	}
}
function checkLevelCompletion() {
	// Level is only considered "complete" if all monsters are defeated
	// This allows the gate to activate
	if (monsters.length === 0) {
		// If gate is not visible, make it pulse more dramatically to draw attention
		if (gate) {
			tween(gate, {
				alpha: 0.2
			}, {
				duration: 300,
				onFinish: function onFinish() {
					tween(gate, {
						alpha: 1
					}, {
						duration: 300
					});
				}
			});
			// Show hint text
			var gateHintText = new Text2('Find the exit gate!', {
				size: 60,
				fill: 0x00FF55
			});
			gateHintText.anchor.set(0.5, 0.5);
			gateHintText.x = 2048 / 2;
			gateHintText.y = 200;
			game.addChild(gateHintText);
			// Remove hint after a few seconds
			LK.setTimeout(function () {
				gateHintText.destroy();
			}, 3000);
		}
	} else {
		// Show hint text about defeating monsters first
		if (gate && gate.visible && LK.ticks % 300 === 0) {
			var monsterHintText = new Text2('Defeat all monsters to activate the exit!', {
				size: 60,
				fill: 0xFF5555
			});
			monsterHintText.anchor.set(0.5, 0.5);
			monsterHintText.x = 2048 / 2;
			monsterHintText.y = 200;
			game.addChild(monsterHintText);
			// Remove hint after a few seconds
			LK.setTimeout(function () {
				monsterHintText.destroy();
			}, 3000);
		}
	}
}
function updateMiniMap() {
	// Update player marker position on minimap
	playerMarker.x = miniMap.x + player.x * CELL_SIZE * MINI_MAP_SCALE;
	playerMarker.y = miniMap.y + player.y * CELL_SIZE * MINI_MAP_SCALE;
	// Draw player direction indicator
	var dirX = Math.cos(player.dir) * 15;
	var dirY = Math.sin(player.dir) * 15;
}
// Game update method
game.update = function () {
	var currentTime = Date.now();
	var deltaTime = currentTime - lastTime;
	lastTime = currentTime;
	// Update controls
	updateControls();
	// Update player
	updatePlayerMovement(deltaTime);
	// Update monsters (only move every few ticks to make movement slower)
	if (LK.ticks % 10 === 0) {
		for (var i = 0; i < monsters.length; i++) {
			MonsterAI.moveTowardsPlayer(monsters[i], player.x, player.y, map);
		}
	}
	// Update projectiles
	updateProjectiles(deltaTime);
	// Update raycast view
	rayCasting();
};
// Game initialization
setupGame();
// Event handlers
game.down = function (x, y, obj) {
	// Handle general screen press
};
game.up = function (x, y, obj) {
	// Handle general screen release
};
game.move = function (x, y, obj) {
	// Handle general screen move
};
var MonsterAI = {
	moveTowardsPlayer: function moveTowardsPlayer(monster, playerX, playerY, map) {
		// Get monster's map position
		var monsterX = monster.mapX;
		var monsterY = monster.mapY;
		// Vector from monster to player
		var dx = playerX - monsterX;
		var dy = playerY - monsterY;
		// Only move if monster is within a certain distance to the player
		var dist = Math.sqrt(dx * dx + dy * dy);
		if (dist > 8) {
			return;
		} // Don't move if too far away
		// Normalize direction vector
		var length = Math.sqrt(dx * dx + dy * dy);
		if (length > 0) {
			dx /= length;
			dy /= length;
		}
		// Movement speed (slower than player)
		var moveSpeed = 0.05; // Increased for smoother animation
		// Calculate potential new position
		var newX = monsterX + dx * moveSpeed;
		var newY = monsterY + dy * moveSpeed;
		// Round to get map cell coordinates
		var cellX = Math.floor(newX);
		var cellY = Math.floor(newY);
		// Check if new position is valid (not a wall or another monster)
		if (cellX >= 0 && cellX < MAP_SIZE && cellY >= 0 && cellY < MAP_SIZE) {
			if (map[cellY][cellX].type === 0 && !map[cellY][cellX].monster) {
				// Update map cell references
				map[Math.floor(monsterY)][Math.floor(monsterX)].removeMonster();
				map[cellY][cellX].addMonster();
				// Update monster position with smooth animation
				monster.mapX = newX;
				monster.mapY = newY;
				// Calculate screen position for the moved monster
				var monsterToPlayerDX = playerX - newX;
				var monsterToPlayerDY = playerY - newY;
				var monsterDist = Math.sqrt(monsterToPlayerDX * monsterToPlayerDX + monsterToPlayerDY * monsterToPlayerDY);
				var monsterAngle = Math.atan2(monsterToPlayerDY, monsterToPlayerDX) - Math.atan2(dy, dx);
				// Position monster in the middle of the screen with updated coordinates
				monster.x = 2048 / 2;
				monster.y = 2732 / 2;
				// Scale monster based on distance
				var scale = WALL_HEIGHT_FACTOR / (monsterDist * 100);
				monster.scale.set(scale, scale);
			}
		}
	}
};
function ensureMapConnectivity() {
	// Flood fill from player starting position to check accessibility
	var visited = [];
	for (var y = 0; y < MAP_SIZE; y++) {
		visited[y] = [];
		for (var x = 0; x < MAP_SIZE; x++) {
			visited[y][x] = false;
		}
	}
	var queue = [];
	// Start from player position (1,1)
	queue.push({
		x: 1,
		y: 1
	});
	visited[1][1] = true;
	// Perform flood fill
	while (queue.length > 0) {
		var current = queue.shift();
		var x = current.x;
		var y = current.y;
		// Check all four neighbors
		var neighbors = [{
			x: x + 1,
			y: y
		}, {
			x: x - 1,
			y: y
		}, {
			x: x,
			y: y + 1
		}, {
			x: x,
			y: y - 1
		}];
		for (var i = 0; i < neighbors.length; i++) {
			var nx = neighbors[i].x;
			var ny = neighbors[i].y;
			// Check if neighbor is valid and not visited
			if (nx >= 0 && nx < MAP_SIZE && ny >= 0 && ny < MAP_SIZE && map[ny][nx].type === 0 && !visited[ny][nx]) {
				visited[ny][nx] = true;
				queue.push({
					x: nx,
					y: ny
				});
			}
		}
	}
	// Check for unreachable areas and create paths to them
	for (var y = 1; y < MAP_SIZE - 1; y++) {
		for (var x = 1; x < MAP_SIZE - 1; x++) {
			// If it's a floor tile but wasn't visited, it's unreachable
			if (map[y][x].type === 0 && !visited[y][x]) {
				// Find the nearest accessible cell
				var nearestX = -1;
				var nearestY = -1;
				var minDist = MAP_SIZE * MAP_SIZE;
				for (var cy = 1; cy < MAP_SIZE - 1; cy++) {
					for (var cx = 1; cx < MAP_SIZE - 1; cx++) {
						if (visited[cy][cx]) {
							var dist = (cx - x) * (cx - x) + (cy - y) * (cy - y);
							if (dist < minDist) {
								minDist = dist;
								nearestX = cx;
								nearestY = cy;
							}
						}
					}
				}
				// Create a path from the unreachable area to the nearest accessible area
				if (nearestX !== -1) {
					createPath(x, y, nearestX, nearestY);
					// Update visited map by doing a mini flood fill from this newly connected point
					var pathQueue = [{
						x: x,
						y: y
					}];
					visited[y][x] = true;
					while (pathQueue.length > 0) {
						var current = pathQueue.shift();
						var px = current.x;
						var py = current.y;
						var pathNeighbors = [{
							x: px + 1,
							y: py
						}, {
							x: px - 1,
							y: py
						}, {
							x: px,
							y: py + 1
						}, {
							x: px,
							y: py - 1
						}];
						for (var j = 0; j < pathNeighbors.length; j++) {
							var nx = pathNeighbors[j].x;
							var ny = pathNeighbors[j].y;
							if (nx >= 0 && nx < MAP_SIZE && ny >= 0 && ny < MAP_SIZE && map[ny][nx].type === 0 && !visited[ny][nx]) {
								visited[ny][nx] = true;
								pathQueue.push({
									x: nx,
									y: ny
								});
							}
						}
					}
				}
			}
		}
	}
}
function createPath(startX, startY, endX, endY) {
	// Determine direction (horizontal or vertical first)
	if (Math.random() < 0.5) {
		// Horizontal first, then vertical
		var x = startX;
		while (x !== endX) {
			x += x < endX ? 1 : -1;
			if (map[startY][x].type === 1) {
				map[startY][x].setType(0); // Convert wall to floor
			}
		}
		var y = startY;
		while (y !== endY) {
			y += y < endY ? 1 : -1;
			if (map[y][endX].type === 1) {
				map[y][endX].setType(0); // Convert wall to floor
			}
		}
	} else {
		// Vertical first, then horizontal
		var y = startY;
		while (y !== endY) {
			y += y < endY ? 1 : -1;
			if (map[y][startX].type === 1) {
				map[y][startX].setType(0); // Convert wall to floor
			}
		}
		var x = startX;
		while (x !== endX) {
			x += x < endX ? 1 : -1;
			if (map[endY][x].type === 1) {
				map[endY][x].setType(0); // Convert wall to floor
			}
		}
	}
}