summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRohit T P <tprohit9@gmail.com>2024-09-16 04:27:19 +0530
committerRohit T P <tprohit9@gmail.com>2024-09-16 04:27:19 +0530
commitbbbb09325b08a8bf5ab9ef8632cd4bbf1337a324 (patch)
tree87495ff9e08ec2506424881aa060adc8dca9533f
Initial commit
-rw-r--r--enemy.js66
-rw-r--r--index.html47
-rw-r--r--index.js35
-rw-r--r--logic.js199
-rw-r--r--maze.js72
-rw-r--r--render.js57
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:&nbsp;<b id="moves"></b></p>
+ <p style="font-size: large">Moves Left:&nbsp;<b id="movesLeft"></b></p>
+ <p style="font-size: large">Game State:&nbsp;<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
+ };
+ }
+}
diff --git a/maze.js b/maze.js
new file mode 100644
index 0000000..96cf5f1
--- /dev/null
+++ b/maze.js
@@ -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();
+ }
+ }
+}