works better
This commit is contained in:
		
							
								
								
									
										461
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										461
									
								
								index.html
									
									
									
									
									
								
							@ -67,6 +67,74 @@
 | 
				
			|||||||
            color: #ff4444 !important;
 | 
					            color: #ff4444 !important;
 | 
				
			||||||
            text-shadow: 0 0 10px rgba(255, 68, 68, 0.5) !important;
 | 
					            text-shadow: 0 0 10px rgba(255, 68, 68, 0.5) !important;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .game-over-screen {
 | 
				
			||||||
 | 
					            position: fixed;
 | 
				
			||||||
 | 
					            top: 0;
 | 
				
			||||||
 | 
					            left: 0;
 | 
				
			||||||
 | 
					            width: 100vw;
 | 
				
			||||||
 | 
					            height: 100vh;
 | 
				
			||||||
 | 
					            background: rgba(0, 0, 0, 0.9);
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            justify-content: center;
 | 
				
			||||||
 | 
					            align-items: center;
 | 
				
			||||||
 | 
					            z-index: 1000;
 | 
				
			||||||
 | 
					            backdrop-filter: blur(10px);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .game-over-content {
 | 
				
			||||||
 | 
					            text-align: center;
 | 
				
			||||||
 | 
					            color: white;
 | 
				
			||||||
 | 
					            font-family: 'Courier New', monospace;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .game-over-content h1 {
 | 
				
			||||||
 | 
					            font-size: 4rem;
 | 
				
			||||||
 | 
					            color: #ff4444;
 | 
				
			||||||
 | 
					            text-shadow: 0 0 20px rgba(255, 68, 68, 0.8);
 | 
				
			||||||
 | 
					            margin-bottom: 30px;
 | 
				
			||||||
 | 
					            animation: pulse 2s infinite;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        @keyframes pulse {
 | 
				
			||||||
 | 
					            0%, 100% { opacity: 1; }
 | 
				
			||||||
 | 
					            50% { opacity: 0.7; }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .score-display {
 | 
				
			||||||
 | 
					            font-size: 1.5rem;
 | 
				
			||||||
 | 
					            margin-bottom: 40px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .score-display div {
 | 
				
			||||||
 | 
					            margin: 10px 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .score-display span {
 | 
				
			||||||
 | 
					            color: #00ff88;
 | 
				
			||||||
 | 
					            text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .restart-btn {
 | 
				
			||||||
 | 
					            background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%);
 | 
				
			||||||
 | 
					            border: none;
 | 
				
			||||||
 | 
					            color: white;
 | 
				
			||||||
 | 
					            padding: 15px 30px;
 | 
				
			||||||
 | 
					            font-size: 1.2rem;
 | 
				
			||||||
 | 
					            font-family: 'Courier New', monospace;
 | 
				
			||||||
 | 
					            font-weight: bold;
 | 
				
			||||||
 | 
					            border-radius: 10px;
 | 
				
			||||||
 | 
					            cursor: pointer;
 | 
				
			||||||
 | 
					            transition: all 0.3s ease;
 | 
				
			||||||
 | 
					            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
 | 
				
			||||||
 | 
					            box-shadow: 0 4px 15px rgba(255, 68, 68, 0.3);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .restart-btn:hover {
 | 
				
			||||||
 | 
					            background: linear-gradient(135deg, #ff6666 0%, #ff0000 100%);
 | 
				
			||||||
 | 
					            transform: translateY(-2px);
 | 
				
			||||||
 | 
					            box-shadow: 0 6px 20px rgba(255, 68, 68, 0.5);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    </style>
 | 
					    </style>
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
@ -80,6 +148,17 @@
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    <div id="gameOverScreen" class="game-over-screen" style="display: none;">
 | 
				
			||||||
 | 
					        <div class="game-over-content">
 | 
				
			||||||
 | 
					            <h1>GAME OVER</h1>
 | 
				
			||||||
 | 
					            <div class="score-display">
 | 
				
			||||||
 | 
					                <div>Final Score: <span id="finalScore">0</span></div>
 | 
				
			||||||
 | 
					                <div>High Score: <span id="highScore">0</span></div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <button id="restartButton" class="restart-btn">RESTART GAME</button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
        const canvas = document.getElementById('gameCanvas');
 | 
					        const canvas = document.getElementById('gameCanvas');
 | 
				
			||||||
        const ctx = canvas.getContext('2d');
 | 
					        const ctx = canvas.getContext('2d');
 | 
				
			||||||
@ -107,7 +186,13 @@
 | 
				
			|||||||
            camera: { x: 0, y: 0 },
 | 
					            camera: { x: 0, y: 0 },
 | 
				
			||||||
            tileSize: 32,
 | 
					            tileSize: 32,
 | 
				
			||||||
            mapWidth: 50,
 | 
					            mapWidth: 50,
 | 
				
			||||||
            mapHeight: 40
 | 
					            mapHeight: 40,
 | 
				
			||||||
 | 
					            pathfindingCache: new Map(),
 | 
				
			||||||
 | 
					            maxCacheSize: 100,
 | 
				
			||||||
 | 
					            pathfindingQueue: [],
 | 
				
			||||||
 | 
					            maxPathfindingPerFrame: 2,
 | 
				
			||||||
 | 
					            gameOver: false,
 | 
				
			||||||
 | 
					            highScore: localStorage.getItem('zombieHighScore') || 0
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Sprite loader
 | 
					        // Sprite loader
 | 
				
			||||||
@ -267,8 +352,8 @@
 | 
				
			|||||||
            for (let y = 0; y < game.mapHeight; y++) {
 | 
					            for (let y = 0; y < game.mapHeight; y++) {
 | 
				
			||||||
                const row = [];
 | 
					                const row = [];
 | 
				
			||||||
                for (let x = 0; x < game.mapWidth; x++) {
 | 
					                for (let x = 0; x < game.mapWidth; x++) {
 | 
				
			||||||
                    // Border walls
 | 
					                    // No border walls - zombies can enter from all sides
 | 
				
			||||||
                    if (x === 0 || x === game.mapWidth - 1 || y === 0 || y === game.mapHeight - 1) {
 | 
					                    if (false) {
 | 
				
			||||||
                        row.push(TILE_TYPES.WALL);
 | 
					                        row.push(TILE_TYPES.WALL);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    // Buildings in clusters
 | 
					                    // Buildings in clusters
 | 
				
			||||||
@ -335,6 +420,161 @@
 | 
				
			|||||||
            return game.map[tileY][tileX];
 | 
					            return game.map[tileY][tileX];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        // Pathfinding system
 | 
				
			||||||
 | 
					        class PathNode {
 | 
				
			||||||
 | 
					            constructor(x, y) {
 | 
				
			||||||
 | 
					                this.x = x;
 | 
				
			||||||
 | 
					                this.y = y;
 | 
				
			||||||
 | 
					                this.g = 0; // Cost from start
 | 
				
			||||||
 | 
					                this.h = 0; // Heuristic cost to goal
 | 
				
			||||||
 | 
					                this.f = 0; // Total cost (g + h)
 | 
				
			||||||
 | 
					                this.parent = null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        function manhattanDistance(a, b) {
 | 
				
			||||||
 | 
					            return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        function getNeighbors(node) {
 | 
				
			||||||
 | 
					            const neighbors = [];
 | 
				
			||||||
 | 
					            const directions = [
 | 
				
			||||||
 | 
					                {x: 0, y: -1}, {x: 1, y: 0}, {x: 0, y: 1}, {x: -1, y: 0}, // Cardinal
 | 
				
			||||||
 | 
					                {x: -1, y: -1}, {x: 1, y: -1}, {x: 1, y: 1}, {x: -1, y: 1} // Diagonal
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            for (let dir of directions) {
 | 
				
			||||||
 | 
					                const newX = node.x + dir.x;
 | 
				
			||||||
 | 
					                const newY = node.y + dir.y;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Check bounds
 | 
				
			||||||
 | 
					                if (newX >= 0 && newX < game.mapWidth && newY >= 0 && newY < game.mapHeight) {
 | 
				
			||||||
 | 
					                    // Check if tile is walkable
 | 
				
			||||||
 | 
					                    if (isWalkable(game.map[newY][newX])) {
 | 
				
			||||||
 | 
					                        // For diagonal movement, also check adjacent tiles to prevent corner cutting
 | 
				
			||||||
 | 
					                        if (dir.x !== 0 && dir.y !== 0) {
 | 
				
			||||||
 | 
					                            if (isWalkable(game.map[node.y][newX]) && isWalkable(game.map[newY][node.x])) {
 | 
				
			||||||
 | 
					                                neighbors.push(new PathNode(newX, newY));
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            neighbors.push(new PathNode(newX, newY));
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return neighbors;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        function findPath(startX, startY, goalX, goalY) {
 | 
				
			||||||
 | 
					            // Convert world coordinates to tile coordinates
 | 
				
			||||||
 | 
					            const startTileX = Math.floor(startX / game.tileSize);
 | 
				
			||||||
 | 
					            const startTileY = Math.floor(startY / game.tileSize);
 | 
				
			||||||
 | 
					            const goalTileX = Math.floor(goalX / game.tileSize);
 | 
				
			||||||
 | 
					            const goalTileY = Math.floor(goalY / game.tileSize);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Check cache first (quantize coordinates for better cache hits)
 | 
				
			||||||
 | 
					            const quantizedStartX = Math.floor(startTileX / 2) * 2;
 | 
				
			||||||
 | 
					            const quantizedStartY = Math.floor(startTileY / 2) * 2;
 | 
				
			||||||
 | 
					            const quantizedGoalX = Math.floor(goalTileX / 2) * 2;
 | 
				
			||||||
 | 
					            const quantizedGoalY = Math.floor(goalTileY / 2) * 2;
 | 
				
			||||||
 | 
					            const cacheKey = `${quantizedStartX},${quantizedStartY}-${quantizedGoalX},${quantizedGoalY}`;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (game.pathfindingCache.has(cacheKey)) {
 | 
				
			||||||
 | 
					                return game.pathfindingCache.get(cacheKey);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Check if start and goal are valid
 | 
				
			||||||
 | 
					            if (!isWalkable(game.map[startTileY]?.[startTileX]) || 
 | 
				
			||||||
 | 
					                !isWalkable(game.map[goalTileY]?.[goalTileX])) {
 | 
				
			||||||
 | 
					                return [];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            const openSet = [];
 | 
				
			||||||
 | 
					            const closedSet = new Set();
 | 
				
			||||||
 | 
					            const startNode = new PathNode(startTileX, startTileY);
 | 
				
			||||||
 | 
					            const goalNode = new PathNode(goalTileX, goalTileY);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            startNode.h = manhattanDistance(startNode, goalNode);
 | 
				
			||||||
 | 
					            startNode.f = startNode.h;
 | 
				
			||||||
 | 
					            openSet.push(startNode);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            while (openSet.length > 0) {
 | 
				
			||||||
 | 
					                // Find node with lowest f cost
 | 
				
			||||||
 | 
					                let currentIndex = 0;
 | 
				
			||||||
 | 
					                for (let i = 1; i < openSet.length; i++) {
 | 
				
			||||||
 | 
					                    if (openSet[i].f < openSet[currentIndex].f) {
 | 
				
			||||||
 | 
					                        currentIndex = i;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                const current = openSet.splice(currentIndex, 1)[0];
 | 
				
			||||||
 | 
					                const currentKey = `${current.x},${current.y}`;
 | 
				
			||||||
 | 
					                closedSet.add(currentKey);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Check if we reached the goal
 | 
				
			||||||
 | 
					                if (current.x === goalNode.x && current.y === goalNode.y) {
 | 
				
			||||||
 | 
					                    const path = [];
 | 
				
			||||||
 | 
					                    let node = current;
 | 
				
			||||||
 | 
					                    while (node) {
 | 
				
			||||||
 | 
					                        path.unshift({
 | 
				
			||||||
 | 
					                            x: node.x * game.tileSize + game.tileSize / 2,
 | 
				
			||||||
 | 
					                            y: node.y * game.tileSize + game.tileSize / 2
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                        node = node.parent;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Cache the result
 | 
				
			||||||
 | 
					                    if (game.pathfindingCache.size >= game.maxCacheSize) {
 | 
				
			||||||
 | 
					                        // Remove oldest entry
 | 
				
			||||||
 | 
					                        const firstKey = game.pathfindingCache.keys().next().value;
 | 
				
			||||||
 | 
					                        game.pathfindingCache.delete(firstKey);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    game.pathfindingCache.set(cacheKey, path);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    return path;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                const neighbors = getNeighbors(current);
 | 
				
			||||||
 | 
					                for (let neighbor of neighbors) {
 | 
				
			||||||
 | 
					                    const neighborKey = `${neighbor.x},${neighbor.y}`;
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if (closedSet.has(neighborKey)) {
 | 
				
			||||||
 | 
					                        continue;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Calculate movement cost (diagonal costs more)
 | 
				
			||||||
 | 
					                    const isDiagonal = Math.abs(neighbor.x - current.x) + Math.abs(neighbor.y - current.y) === 2;
 | 
				
			||||||
 | 
					                    const movementCost = isDiagonal ? 1.4 : 1.0;
 | 
				
			||||||
 | 
					                    const tentativeG = current.g + movementCost;
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    let existingNode = openSet.find(node => node.x === neighbor.x && node.y === neighbor.y);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if (!existingNode) {
 | 
				
			||||||
 | 
					                        neighbor.g = tentativeG;
 | 
				
			||||||
 | 
					                        neighbor.h = manhattanDistance(neighbor, goalNode);
 | 
				
			||||||
 | 
					                        neighbor.f = neighbor.g + neighbor.h;
 | 
				
			||||||
 | 
					                        neighbor.parent = current;
 | 
				
			||||||
 | 
					                        openSet.push(neighbor);
 | 
				
			||||||
 | 
					                    } else if (tentativeG < existingNode.g) {
 | 
				
			||||||
 | 
					                        existingNode.g = tentativeG;
 | 
				
			||||||
 | 
					                        existingNode.f = existingNode.g + existingNode.h;
 | 
				
			||||||
 | 
					                        existingNode.parent = current;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Cache empty result
 | 
				
			||||||
 | 
					            const emptyPath = [];
 | 
				
			||||||
 | 
					            if (game.pathfindingCache.size >= game.maxCacheSize) {
 | 
				
			||||||
 | 
					                const firstKey = game.pathfindingCache.keys().next().value;
 | 
				
			||||||
 | 
					                game.pathfindingCache.delete(firstKey);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            game.pathfindingCache.set(cacheKey, emptyPath);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return emptyPath; // No path found
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        class Player {
 | 
					        class Player {
 | 
				
			||||||
            constructor(x, y) {
 | 
					            constructor(x, y) {
 | 
				
			||||||
                this.x = x;
 | 
					                this.x = x;
 | 
				
			||||||
@ -379,17 +619,15 @@
 | 
				
			|||||||
                
 | 
					                
 | 
				
			||||||
                // Shooting
 | 
					                // Shooting
 | 
				
			||||||
                const now = Date.now();
 | 
					                const now = Date.now();
 | 
				
			||||||
                if (this.burstCount > 0 && now - this.burstTimer > this.weapon.fireRate) {
 | 
					                // Handle burst firing (remaining shots after initial click)
 | 
				
			||||||
 | 
					                if (this.burstCount > 1 && now - this.burstTimer >= this.weapon.fireRate) {
 | 
				
			||||||
                    this.shoot();
 | 
					                    this.shoot();
 | 
				
			||||||
                    this.burstCount--;
 | 
					                    this.burstCount--;
 | 
				
			||||||
                    this.burstTimer = now;
 | 
					                    this.burstTimer = now;
 | 
				
			||||||
                } else if ((game.mouse.down && this.weapon.autoFire) || this.burstCount > 0) {
 | 
					 | 
				
			||||||
                    if (now - this.lastShot > this.weapon.fireRate) {
 | 
					 | 
				
			||||||
                        if (this.burstCount === 0) {
 | 
					 | 
				
			||||||
                            this.burstCount = this.weapon.burstCount;
 | 
					 | 
				
			||||||
                            this.burstTimer = now;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                // Handle automatic weapons (hold to fire)
 | 
				
			||||||
 | 
					                else if (game.mouse.down && this.weapon.autoFire && now - this.lastShot >= this.weapon.fireRate) {
 | 
				
			||||||
 | 
					                    this.shoot();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                // Check weapon pickups
 | 
					                // Check weapon pickups
 | 
				
			||||||
@ -450,10 +688,22 @@
 | 
				
			|||||||
                this.hp = 50 + Math.random() * 50;
 | 
					                this.hp = 50 + Math.random() * 50;
 | 
				
			||||||
                this.maxHp = this.hp;
 | 
					                this.maxHp = this.hp;
 | 
				
			||||||
                this.sprite = ['zombie.png', 'zombie2.png', 'zombie3.png', 'zombie4.png'][Math.floor(Math.random() * 4)];
 | 
					                this.sprite = ['zombie.png', 'zombie2.png', 'zombie3.png', 'zombie4.png'][Math.floor(Math.random() * 4)];
 | 
				
			||||||
 | 
					                this.path = [];
 | 
				
			||||||
 | 
					                this.pathIndex = 0;
 | 
				
			||||||
 | 
					                this.lastPathUpdate = 0;
 | 
				
			||||||
 | 
					                this.pathUpdateInterval = 1000 + Math.random() * 500; // Update path every 1-1.5 seconds
 | 
				
			||||||
 | 
					                this.lastPlayerX = null;
 | 
				
			||||||
 | 
					                this.lastPlayerY = null;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            update() {
 | 
					            update() {
 | 
				
			||||||
                // Move towards player with collision detection
 | 
					                const now = Date.now();
 | 
				
			||||||
 | 
					                const distanceToPlayer = Math.sqrt(
 | 
				
			||||||
 | 
					                    Math.pow(game.player.x - this.x, 2) + Math.pow(game.player.y - this.y, 2)
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // If very close to player, use direct movement for responsiveness
 | 
				
			||||||
 | 
					                if (distanceToPlayer < 64) {
 | 
				
			||||||
                    const dx = game.player.x - this.x;
 | 
					                    const dx = game.player.x - this.x;
 | 
				
			||||||
                    const dy = game.player.y - this.y;
 | 
					                    const dy = game.player.y - this.y;
 | 
				
			||||||
                    const distance = Math.sqrt(dx * dx + dy * dy);
 | 
					                    const distance = Math.sqrt(dx * dx + dy * dy);
 | 
				
			||||||
@ -474,6 +724,79 @@
 | 
				
			|||||||
                            this.y = newY;
 | 
					                            this.y = newY;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Update path periodically or if no path exists
 | 
				
			||||||
 | 
					                if (now - this.lastPathUpdate > this.pathUpdateInterval || this.path.length === 0) {
 | 
				
			||||||
 | 
					                    // Only recalculate if player has moved significantly or path is old
 | 
				
			||||||
 | 
					                    const playerMoved = this.lastPlayerX && this.lastPlayerY && 
 | 
				
			||||||
 | 
					                        (Math.abs(game.player.x - this.lastPlayerX) > 64 || 
 | 
				
			||||||
 | 
					                         Math.abs(game.player.y - this.lastPlayerY) > 64);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if (!this.lastPlayerX || playerMoved || this.path.length === 0 || 
 | 
				
			||||||
 | 
					                        now - this.lastPathUpdate > this.pathUpdateInterval) {
 | 
				
			||||||
 | 
					                        this.path = findPath(this.x, this.y, game.player.x, game.player.y);
 | 
				
			||||||
 | 
					                        this.pathIndex = 0;
 | 
				
			||||||
 | 
					                        this.lastPathUpdate = now;
 | 
				
			||||||
 | 
					                        this.lastPlayerX = game.player.x;
 | 
				
			||||||
 | 
					                        this.lastPlayerY = game.player.y;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Follow the path
 | 
				
			||||||
 | 
					                if (this.path.length > 0 && this.pathIndex < this.path.length) {
 | 
				
			||||||
 | 
					                    const target = this.path[this.pathIndex];
 | 
				
			||||||
 | 
					                    const dx = target.x - this.x;
 | 
				
			||||||
 | 
					                    const dy = target.y - this.y;
 | 
				
			||||||
 | 
					                    const distance = Math.sqrt(dx * dx + dy * dy);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // If we're close enough to the current waypoint, move to the next one
 | 
				
			||||||
 | 
					                    if (distance < 16) {
 | 
				
			||||||
 | 
					                        this.pathIndex++;
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Move towards current waypoint
 | 
				
			||||||
 | 
					                    if (distance > 0) {
 | 
				
			||||||
 | 
					                        let newX = this.x + (dx / distance) * this.speed;
 | 
				
			||||||
 | 
					                        let newY = this.y + (dy / distance) * this.speed;
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        // Check collision for X movement
 | 
				
			||||||
 | 
					                        if (isWalkable(getTileAt(newX - this.width/2, this.y)) && 
 | 
				
			||||||
 | 
					                            isWalkable(getTileAt(newX + this.width/2, this.y))) {
 | 
				
			||||||
 | 
					                            this.x = newX;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        // Check collision for Y movement
 | 
				
			||||||
 | 
					                        if (isWalkable(getTileAt(this.x, newY - this.height/2)) && 
 | 
				
			||||||
 | 
					                            isWalkable(getTileAt(this.x, newY + this.height/2))) {
 | 
				
			||||||
 | 
					                            this.y = newY;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // Fallback to direct movement if no path found
 | 
				
			||||||
 | 
					                    const dx = game.player.x - this.x;
 | 
				
			||||||
 | 
					                    const dy = game.player.y - this.y;
 | 
				
			||||||
 | 
					                    const distance = Math.sqrt(dx * dx + dy * dy);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if (distance > 0) {
 | 
				
			||||||
 | 
					                        let newX = this.x + (dx / distance) * this.speed * 0.5; // Slower when no path
 | 
				
			||||||
 | 
					                        let newY = this.y + (dy / distance) * this.speed * 0.5;
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        // Check collision for X movement
 | 
				
			||||||
 | 
					                        if (isWalkable(getTileAt(newX - this.width/2, this.y)) && 
 | 
				
			||||||
 | 
					                            isWalkable(getTileAt(newX + this.width/2, this.y))) {
 | 
				
			||||||
 | 
					                            this.x = newX;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        // Check collision for Y movement
 | 
				
			||||||
 | 
					                        if (isWalkable(getTileAt(this.x, newY - this.height/2)) && 
 | 
				
			||||||
 | 
					                            isWalkable(getTileAt(this.x, newY + this.height/2))) {
 | 
				
			||||||
 | 
					                            this.y = newY;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            takeDamage(damage) {
 | 
					            takeDamage(damage) {
 | 
				
			||||||
@ -596,29 +919,31 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        function spawnZombie() {
 | 
					        function spawnZombie() {
 | 
				
			||||||
            const side = Math.floor(Math.random() * 4);
 | 
					 | 
				
			||||||
            let x, y;
 | 
					            let x, y;
 | 
				
			||||||
            const mapPixelWidth = game.mapWidth * game.tileSize;
 | 
					            let attempts = 0;
 | 
				
			||||||
            const mapPixelHeight = game.mapHeight * game.tileSize;
 | 
					            const maxAttempts = 50;
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            switch(side) {
 | 
					            // Keep trying to find a walkable spawn position
 | 
				
			||||||
                case 0: // Top
 | 
					            do {
 | 
				
			||||||
                    x = Math.random() * mapPixelWidth;
 | 
					                x = Math.random() * (game.mapWidth * game.tileSize);
 | 
				
			||||||
                    y = -32;
 | 
					                y = Math.random() * (game.mapHeight * game.tileSize);
 | 
				
			||||||
                    break;
 | 
					                attempts++;
 | 
				
			||||||
                case 1: // Right
 | 
					            } while (!isWalkable(getTileAt(x, y)) && attempts < maxAttempts);
 | 
				
			||||||
                    x = mapPixelWidth + 32;
 | 
					            
 | 
				
			||||||
                    y = Math.random() * mapPixelHeight;
 | 
					            // If we couldn't find a walkable tile, spawn on a road or floor tile
 | 
				
			||||||
                    break;
 | 
					            if (attempts >= maxAttempts) {
 | 
				
			||||||
                case 2: // Bottom
 | 
					                // Find any walkable tile
 | 
				
			||||||
                    x = Math.random() * mapPixelWidth;
 | 
					                for (let ty = 0; ty < game.mapHeight; ty++) {
 | 
				
			||||||
                    y = mapPixelHeight + 32;
 | 
					                    for (let tx = 0; tx < game.mapWidth; tx++) {
 | 
				
			||||||
                    break;
 | 
					                        if (isWalkable(game.map[ty][tx])) {
 | 
				
			||||||
                case 3: // Left
 | 
					                            x = tx * game.tileSize + game.tileSize / 2;
 | 
				
			||||||
                    x = -32;
 | 
					                            y = ty * game.tileSize + game.tileSize / 2;
 | 
				
			||||||
                    y = Math.random() * mapPixelHeight;
 | 
					 | 
				
			||||||
                            break;
 | 
					                            break;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    if (isWalkable(getTileAt(x, y))) break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            game.zombies.push(new Zombie(x, y));
 | 
					            game.zombies.push(new Zombie(x, y));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -627,6 +952,7 @@
 | 
				
			|||||||
            // Bullet-zombie collisions
 | 
					            // Bullet-zombie collisions
 | 
				
			||||||
            for (let i = game.bullets.length - 1; i >= 0; i--) {
 | 
					            for (let i = game.bullets.length - 1; i >= 0; i--) {
 | 
				
			||||||
                const bullet = game.bullets[i];
 | 
					                const bullet = game.bullets[i];
 | 
				
			||||||
 | 
					                let bulletHit = false;
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                for (let j = game.zombies.length - 1; j >= 0; j--) {
 | 
					                for (let j = game.zombies.length - 1; j >= 0; j--) {
 | 
				
			||||||
                    const zombie = game.zombies[j];
 | 
					                    const zombie = game.zombies[j];
 | 
				
			||||||
@ -650,10 +976,14 @@
 | 
				
			|||||||
                            
 | 
					                            
 | 
				
			||||||
                            game.zombies.splice(j, 1);
 | 
					                            game.zombies.splice(j, 1);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        game.bullets.splice(i, 1);
 | 
					                        bulletHit = true;
 | 
				
			||||||
                        break;
 | 
					                        break;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (bulletHit) {
 | 
				
			||||||
 | 
					                    game.bullets.splice(i, 1);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // Player-zombie collisions
 | 
					            // Player-zombie collisions
 | 
				
			||||||
@ -672,15 +1002,8 @@
 | 
				
			|||||||
                        updateUI();
 | 
					                        updateUI();
 | 
				
			||||||
                        
 | 
					                        
 | 
				
			||||||
                        if (game.player.health <= 0) {
 | 
					                        if (game.player.health <= 0) {
 | 
				
			||||||
                            // Game over - restart
 | 
					                            // Game over
 | 
				
			||||||
                            game.player.health = game.player.maxHealth;
 | 
					                            showGameOver();
 | 
				
			||||||
                            game.player.x = canvas.width / 2;
 | 
					 | 
				
			||||||
                            game.player.y = canvas.height / 2;
 | 
					 | 
				
			||||||
                            game.zombies = [];
 | 
					 | 
				
			||||||
                            game.bullets = [];
 | 
					 | 
				
			||||||
                            game.weaponDrops = [];
 | 
					 | 
				
			||||||
                            game.score = 0;
 | 
					 | 
				
			||||||
                            updateUI();
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -695,6 +1018,49 @@
 | 
				
			|||||||
            document.getElementById('score').textContent = game.score;
 | 
					            document.getElementById('score').textContent = game.score;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        function showGameOver() {
 | 
				
			||||||
 | 
					            game.gameOver = true;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Update high score
 | 
				
			||||||
 | 
					            if (game.score > game.highScore) {
 | 
				
			||||||
 | 
					                game.highScore = game.score;
 | 
				
			||||||
 | 
					                localStorage.setItem('zombieHighScore', game.highScore);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Update game over screen
 | 
				
			||||||
 | 
					            document.getElementById('finalScore').textContent = game.score;
 | 
				
			||||||
 | 
					            document.getElementById('highScore').textContent = game.highScore;
 | 
				
			||||||
 | 
					            document.getElementById('gameOverScreen').style.display = 'flex';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        function restartGame() {
 | 
				
			||||||
 | 
					            // Hide game over screen
 | 
				
			||||||
 | 
					            document.getElementById('gameOverScreen').style.display = 'none';
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Reset game state
 | 
				
			||||||
 | 
					            game.gameOver = false;
 | 
				
			||||||
 | 
					            game.score = 0;
 | 
				
			||||||
 | 
					            game.zombies = [];
 | 
				
			||||||
 | 
					            game.bullets = [];
 | 
				
			||||||
 | 
					            game.weaponDrops = [];
 | 
				
			||||||
 | 
					            game.pathfindingCache.clear();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Reset player
 | 
				
			||||||
 | 
					            const centerX = Math.floor(game.mapWidth / 2) * game.tileSize;
 | 
				
			||||||
 | 
					            const centerY = Math.floor(game.mapHeight / 2) * game.tileSize;
 | 
				
			||||||
 | 
					            game.player.health = game.player.maxHealth;
 | 
				
			||||||
 | 
					            game.player.x = centerX;
 | 
				
			||||||
 | 
					            game.player.y = centerY;
 | 
				
			||||||
 | 
					            game.player.weapon = { ...weaponTypes.pistol };
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Spawn initial zombies
 | 
				
			||||||
 | 
					            for (let i = 0; i < 5; i++) {
 | 
				
			||||||
 | 
					                spawnZombie();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            updateUI();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        function drawMap() {
 | 
					        function drawMap() {
 | 
				
			||||||
            // Pre-rendered tile canvases for performance
 | 
					            // Pre-rendered tile canvases for performance
 | 
				
			||||||
            if (!game.tileCanvases) {
 | 
					            if (!game.tileCanvases) {
 | 
				
			||||||
@ -794,6 +1160,8 @@
 | 
				
			|||||||
            // Draw map
 | 
					            // Draw map
 | 
				
			||||||
            drawMap();
 | 
					            drawMap();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
 | 
					            // Only update game if not game over
 | 
				
			||||||
 | 
					            if (!game.gameOver) {
 | 
				
			||||||
                // Update
 | 
					                // Update
 | 
				
			||||||
                game.player.update();
 | 
					                game.player.update();
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
@ -807,6 +1175,7 @@
 | 
				
			|||||||
                if (Math.random() < 0.01 + game.score * 0.00005) {
 | 
					                if (Math.random() < 0.01 + game.score * 0.00005) {
 | 
				
			||||||
                    spawnZombie();
 | 
					                    spawnZombie();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // Draw
 | 
					            // Draw
 | 
				
			||||||
            game.zombies.forEach(zombie => zombie.draw());
 | 
					            game.zombies.forEach(zombie => zombie.draw());
 | 
				
			||||||
@ -852,9 +1221,12 @@
 | 
				
			|||||||
        
 | 
					        
 | 
				
			||||||
        canvas.addEventListener('mousedown', (e) => {
 | 
					        canvas.addEventListener('mousedown', (e) => {
 | 
				
			||||||
            game.mouse.down = true;
 | 
					            game.mouse.down = true;
 | 
				
			||||||
            if (!game.player.weapon.autoFire) {
 | 
					            const now = Date.now();
 | 
				
			||||||
                game.player.burstCount = game.player.weapon.burstCount;
 | 
					            // Always shoot on click for non-auto weapons if fire rate allows
 | 
				
			||||||
                game.player.burstTimer = Date.now();
 | 
					            if (!game.player.weapon.autoFire && now - game.player.lastShot >= game.player.weapon.fireRate) {
 | 
				
			||||||
 | 
					                game.player.shoot(); // Shoot immediately on click
 | 
				
			||||||
 | 
					                game.player.burstCount = game.player.weapon.burstCount - 1; // Set remaining burst shots
 | 
				
			||||||
 | 
					                game.player.burstTimer = now;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
@ -862,6 +1234,9 @@
 | 
				
			|||||||
            game.mouse.down = false;
 | 
					            game.mouse.down = false;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        // Restart button event listener
 | 
				
			||||||
 | 
					        document.getElementById('restartButton').addEventListener('click', restartGame);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        // Start loading sprites
 | 
					        // Start loading sprites
 | 
				
			||||||
        loadSprites();
 | 
					        loadSprites();
 | 
				
			||||||
    </script>
 | 
					    </script>
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user