diff options
| author | Rohit T P <tprohit9@gmail.com> | 2024-09-16 04:27:19 +0530 |
|---|---|---|
| committer | Rohit T P <tprohit9@gmail.com> | 2024-09-16 04:27:19 +0530 |
| commit | bbbb09325b08a8bf5ab9ef8632cd4bbf1337a324 (patch) | |
| tree | 87495ff9e08ec2506424881aa060adc8dca9533f | |
Initial commit
| -rw-r--r-- | enemy.js | 66 | ||||
| -rw-r--r-- | index.html | 47 | ||||
| -rw-r--r-- | index.js | 35 | ||||
| -rw-r--r-- | logic.js | 199 | ||||
| -rw-r--r-- | maze.js | 72 | ||||
| -rw-r--r-- | render.js | 57 |
6 files changed, 476 insertions, 0 deletions
diff --git a/enemy.js b/enemy.js new file mode 100644 index 0000000..f91cab4 --- /dev/null +++ b/enemy.js @@ -0,0 +1,66 @@ +class Enemy { + constructor(x, y) { + this.x = x; + this.y = y; + } + + getPossibleMoves(maze, playerPositions) { + const moves = []; + const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]; + + for (let [dx, dy] of directions) { + let newX = this.x + dx; + let newY = this.y + dy; + + while ( + newX >= 0 && newX < maze[0].length && + newY >= 0 && newY < maze.length && + maze[newY][newX] === 0 && + !playerPositions.some(([px, py]) => px === newX && py === newY) + ) { + moves.push([newX, newY]); + newX += dx; + newY += dy; + } + } + + return moves; + } + + move(newX, newY) { + this.x = newX; + this.y = newY; + } + + makeMove(maze, playerPositions) { + const possibleMoves = this.getPossibleMoves(maze, playerPositions); + if (possibleMoves.length === 0) { + return false; // No moves available, game over + } + + // Intelligent move selection + const bestMove = this.findBestMove(possibleMoves, maze, playerPositions); + this.move(bestMove[0], bestMove[1]); + return true; + } + + findBestMove(possibleMoves, maze, playerPositions) { + let bestMove = null; + let maxFutureMoves = -1; + + for (const move of possibleMoves) { + const futureMoves = this.countFutureMoves(move[0], move[1], maze, playerPositions); + if (futureMoves > maxFutureMoves) { + maxFutureMoves = futureMoves; + bestMove = move; + } + } + + return bestMove; + } + + countFutureMoves(x, y, maze, playerPositions) { + const tempEnemy = new Enemy(x, y); + return tempEnemy.getPossibleMoves(maze, playerPositions).length; + } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..e22f49f --- /dev/null +++ b/index.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Maze Trapper Game</title> + <style> + body { + font-family: sans-serif; + } + canvas { + border: 1px solid black; + } + + .main-container { + display: flex; + flex-direction: row; + justify-content: space-evenly; + flex-wrap: wrap; + align-content: center; + } + </style> + <script src="enemy.js" defer></script> + <script src="maze.js" defer></script> + <script src="logic.js" defer></script> + <script src="render.js" defer></script> + <script src="index.js" defer></script> +</head> +<body> +<h1 style="text-align: center;font-size: xxx-large;">CatchUp</h1> +<div class="main-container"> + <canvas id="gameCanvas" width="500" height="500"></canvas> + <div> + <label for="hardness">Hardness</label> + <input type="range" min="0.1" max="1" value="0.5" step="0.1" id="hardness"> + <p style="max-width: 40vw"> + You are the blue player. You objective is to trap the red player in the maze. + If you manage to get to a state where the red player has nowhere to go, you win. + Use the arrow keys to move the blue player. Use space bar to switch between the blue players. + </p> + <p style="font-size: large">Current Moves: <b id="moves"></b></p> + <p style="font-size: large">Moves Left: <b id="movesLeft"></b></p> + <p style="font-size: large">Game State: <b id="state"></b></p> + </div> +</div> +</body> +</html> diff --git a/index.js b/index.js new file mode 100644 index 0000000..b5ad00e --- /dev/null +++ b/index.js @@ -0,0 +1,35 @@ +const moves = document.getElementById("moves"); +const movesLeft = document.getElementById("movesLeft"); +const state = document.getElementById("state"); +const hardness = document.getElementById("hardness"); + +let game, renderer; + +function setUpGame(){ + const hardnessValue = parseFloat(hardness.value); + const grid = hardnessValue < 0.5 ? 10 : 15; + game = new MazeGame(grid, grid, hardnessValue); + renderer = new MazeRenderer(game, 'gameCanvas'); + game.initializeGame(); + renderer.drawMaze(); +} + +function showGameState(){ + const { moves: movesMade, moveLimit, gameWon, gameOver } = game.getGameState(); + moves.innerText = movesMade; + movesLeft.innerText = String(moveLimit - movesMade); + state.innerText = gameWon ? 'Game Won' : gameOver ? 'Game Over' : 'On Going'; +} + +document.addEventListener('keydown', (event) => { + renderer.handleKeyPress(event); + showGameState(); +}); + +hardness.addEventListener('change', () => { + setUpGame(); + showGameState(); +}); + +setUpGame(); +showGameState(); diff --git a/logic.js b/logic.js new file mode 100644 index 0000000..c85cf78 --- /dev/null +++ b/logic.js @@ -0,0 +1,199 @@ +class MazeGame { + constructor(width, height, difficulty = 0.5) { + this.width = width; + this.height = height; + this.difficulty = Math.max(0, Math.min(1, difficulty)); + this.maze = []; + this.playerPositions = []; + this.enemy = null; + this.moveLimit = 0; + this.moves = 0; + this.selectedPlayer = 0; + this.gameOver = false; + this.gameWon = false; + } + + initializeGame() { + const mazeGenerator = new MazeGenerator(this.width, this.height, this.difficulty); + this.maze = mazeGenerator.generate(); + this.placePieces(); + this.setDifficulty(this.difficulty); + this.moves = 0; + this.gameOver = false; + this.gameWon = false; + } + + placePieces() { + const emptySpaces = this.getEmptySpaces(); + + // Calculate minimum and maximum distances based on difficulty + const maxDimension = Math.max(this.width, this.height); + const minDistance = Math.floor(maxDimension * (0.2 + this.difficulty * 0.3)); // 20% to 50% of max dimension + const maxDistance = Math.floor(maxDimension * (0.5 + this.difficulty * 0.3)); // 50% to 80% of max dimension + + // Place first player + const player1 = this.getRandomPosition(emptySpaces); + this.playerPositions.push(player1); + this.removePosition(emptySpaces, player1); + + // Place second player + const player2 = this.findPositionWithinRange(emptySpaces, player1, minDistance, maxDistance); + this.playerPositions.push(player2); + this.removePosition(emptySpaces, player2); + + // Place enemy + let enemyPos; + if (this.difficulty < 0.5) { + // For lower difficulties, place the enemy farther from players + enemyPos = this.findFarthestPosition(emptySpaces, this.playerPositions); + } else { + // For higher difficulties, place the enemy closer to the center of the players + const centerX = (player1[0] + player2[0]) / 2; + const centerY = (player1[1] + player2[1]) / 2; + enemyPos = this.findClosestPosition(emptySpaces, [centerX, centerY]); + } + this.enemy = new Enemy(enemyPos[0], enemyPos[1]); + } + + getEmptySpaces() { + const emptySpaces = []; + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + if (this.maze[y][x] === 0) { + emptySpaces.push([x, y]); + } + } + } + return emptySpaces; + } + + getRandomPosition(positions) { + return positions[Math.floor(Math.random() * positions.length)]; + } + + removePosition(positions, pos) { + const index = positions.findIndex(p => p[0] === pos[0] && p[1] === pos[1]); + if (index !== -1) { + positions.splice(index, 1); + } + } + + findPositionWithinRange(positions, referencePos, minDistance, maxDistance) { + const validPositions = positions.filter(pos => { + const distance = this.manhattanDistance(pos, referencePos); + return distance >= minDistance && distance <= maxDistance; + }); + return this.getRandomPosition(validPositions) || this.getRandomPosition(positions); + } + + findFarthestPosition(positions, referencePositions) { + let farthestPos = null; + let maxMinDistance = -1; + + for (const pos of positions) { + const minDistance = Math.min(...referencePositions.map(refPos => this.manhattanDistance(pos, refPos))); + if (minDistance > maxMinDistance) { + maxMinDistance = minDistance; + farthestPos = pos; + } + } + + return farthestPos || this.getRandomPosition(positions); + } + + findClosestPosition(positions, referencePos) { + let closestPos = null; + let minDistance = Infinity; + + for (const pos of positions) { + const distance = this.manhattanDistance(pos, referencePos); + if (distance < minDistance) { + minDistance = distance; + closestPos = pos; + } + } + + return closestPos || this.getRandomPosition(positions); + } + + manhattanDistance(pos1, pos2) { + return Math.abs(pos1[0] - pos2[0]) + Math.abs(pos1[1] - pos2[1]); + } + + setDifficulty(difficulty) { + this.difficulty = Math.max(0, Math.min(1, difficulty)); + this.moveLimit = Math.floor(30 + (1 - this.difficulty) * 30); // 30 to 60 moves based on difficulty + } + + checkFailState() { + return this.moves >= this.moveLimit; + } + + movePlayer(dx, dy) { + if (this.gameOver) return false; + + let [x, y] = this.playerPositions[this.selectedPlayer]; + let newX = x + dx; + let newY = y + dy; + + while (this.isValidMove(newX, newY)) { + x = newX; + y = newY; + newX += dx; + newY += dy; + } + + if (x !== this.playerPositions[this.selectedPlayer][0] || y !== this.playerPositions[this.selectedPlayer][1]) { + this.playerPositions[this.selectedPlayer] = [x, y]; + this.moves++; + + if (this.checkFailState()) { + this.gameOver = true; + return false; + } + + const enemyMoved = this.enemy.makeMove(this.maze, this.playerPositions); + if (!enemyMoved) { + this.gameOver = true; + this.gameWon = true; + } + + return true; + } + + return false; // Player didn't move + } + + isValidMove(x, y) { + // Check if the move is within the maze bounds and not a wall + if (x < 0 || x >= this.width || y < 0 || y >= this.height || this.maze[y][x] === 1) { + return false; + } + + // Check if the move collides with the other player + const otherPlayerIndex = 1 - this.selectedPlayer; + if (x === this.playerPositions[otherPlayerIndex][0] && y === this.playerPositions[otherPlayerIndex][1]) { + return false; + } + + // Check if the move collides with the enemy + return !(x === this.enemy.x && y === this.enemy.y); + } + + switchPlayer() { + this.selectedPlayer = 1 - this.selectedPlayer; + } + + getGameState() { + return { + maze: this.maze, + playerPositions: this.playerPositions, + enemyPosition: [this.enemy.x, this.enemy.y], + selectedPlayer: this.selectedPlayer, + moves: this.moves, + moveLimit: this.moveLimit, + gameOver: this.gameOver, + gameWon: this.gameWon + }; + } +} @@ -0,0 +1,72 @@ +class MazeGenerator { + constructor(width, height, difficulty) { + this.width = width; + this.height = height; + this.difficulty = difficulty + this.maze = []; + } + + generate() { + this.initializeMaze(); + this.carvePassagesFrom(Math.floor(Math.random() * this.width), Math.floor(Math.random() * this.height)); + this.addLoops(); + this.addDeadEnds(); + return this.maze; + } + + initializeMaze() { + for (let y = 0; y < this.height; y++) { + this.maze[y] = Array(this.width).fill(1); // 1 represents a wall + } + } + + carvePassagesFrom(x, y) { + const directions = [[0, -1], [1, 0], [0, 1], [-1, 0]]; + directions.sort(() => Math.random() - 0.5); + + for (let [dx, dy] of directions) { + let nx = x + dx * 2, ny = y + dy * 2; + if (nx >= 0 && ny >= 0 && nx < this.width && ny < this.height && this.maze[ny][nx] === 1) { + this.maze[y + dy][x + dx] = 0; + this.maze[ny][nx] = 0; + this.carvePassagesFrom(nx, ny); + } + } + } + + addLoops() { + const loopCount = Math.floor(this.width * this.height * this.difficulty * 0.1); + for (let i = 0; i < loopCount; i++) { + let x = Math.floor(Math.random() * (this.width - 2)) + 1; + let y = Math.floor(Math.random() * (this.height - 2)) + 1; + if (this.maze[y][x] === 1 && this.countAdjacentPassages(x, y) >= 2) { + this.maze[y][x] = 0; + } + } + } + + addDeadEnds() { + const deadEndCount = Math.floor(this.width * this.height * (1 - this.difficulty) * 0.1); + for (let i = 0; i < deadEndCount; i++) { + let x = Math.floor(Math.random() * (this.width - 2)) + 1; + let y = Math.floor(Math.random() * (this.height - 2)) + 1; + if (this.maze[y][x] === 0 && this.countAdjacentWalls(x, y) === 3) { + this.maze[y][x] = 1; + } + } + } + + countAdjacentPassages(x, y) { + return this.getAdjacentCells(x, y).filter(([ax, ay]) => this.maze[ay][ax] === 0).length; + } + + countAdjacentWalls(x, y) { + return this.getAdjacentCells(x, y).filter(([ax, ay]) => this.maze[ay][ax] === 1).length; + } + + getAdjacentCells(x, y) { + return [ + [x, y - 1], [x + 1, y], [x, y + 1], [x - 1, y] + ].filter(([ax, ay]) => ax >= 0 && ay >= 0 && ax < this.width && ay < this.height); + } +} diff --git a/render.js b/render.js new file mode 100644 index 0000000..4eb3f24 --- /dev/null +++ b/render.js @@ -0,0 +1,57 @@ +class MazeRenderer { + constructor(game, canvasId) { + this.game = game; + this.canvas = document.getElementById(canvasId); + this.ctx = this.canvas.getContext('2d'); + this.cellSize = 0; + } + + drawMaze() { + this.cellSize = Math.min(this.canvas.width / this.game.width, this.canvas.height / this.game.height); + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + + // Draw maze + for (let y = 0; y < this.game.height; y++) { + for (let x = 0; x < this.game.width; x++) { + if (this.game.maze[y][x] === 1) { + this.ctx.fillStyle = 'black'; + this.ctx.fillRect(x * this.cellSize, y * this.cellSize, this.cellSize, this.cellSize); + } + } + } + + // Draw players + this.ctx.fillStyle = 'blue'; + this.game.playerPositions.forEach(([x, y]) => { + this.ctx.beginPath(); + this.ctx.arc((x + 0.5) * this.cellSize, (y + 0.5) * this.cellSize, this.cellSize / 3, 0, Math.PI * 2); + this.ctx.fill(); + }); + + // Draw enemy + this.ctx.fillStyle = 'red'; + this.ctx.beginPath(); + this.ctx.arc((this.game.enemy.x + 0.5) * this.cellSize, (this.game.enemy.y + 0.5) * this.cellSize, this.cellSize / 3, 0, Math.PI * 2); + this.ctx.fill(); + + // Highlight selected player + this.ctx.strokeStyle = 'yellow'; + this.ctx.lineWidth = 3; + const [x, y] = this.game.playerPositions[this.game.selectedPlayer]; + this.ctx.strokeRect(x * this.cellSize, y * this.cellSize, this.cellSize, this.cellSize); + } + + handleKeyPress(event) { + let moved = false; + switch(event.key) { + case 'ArrowUp': moved = this.game.movePlayer(0, -1); break; + case 'ArrowDown': moved = this.game.movePlayer(0, 1); break; + case 'ArrowLeft': moved = this.game.movePlayer(-1, 0); break; + case 'ArrowRight': moved = this.game.movePlayer(1, 0); break; + case ' ': this.game.switchPlayer(); moved = true; break; // Space bar to switch players + } + if (moved) { + this.drawMaze(); + } + } +} |
