xxxxxxxxxx
641
let mazes = [];
let currentMazeIndex = 0;
let player;
let cellSize;
let mazeColumns = 19;
let mazeRows = 19;
let startTime;
let timeLimit = 60 * 1000;
let dungeonBackground;
let coinGIF;
let dungeonMaster;
let dungeonMasterAnimation = [];
let frameWidth, frameHeight;
let teleporter;
let keysPressed = {};
let gameState = 'INTRO';
let score = 0;
let dungeonIntroBg;
let ancientFont;
let gameOverBg, winningBg, howtoplayBG;
let introMusic, playingMusic, gameOverMusic, gameWinningMusic,collectCoinSound, teleporterSound;
let message = '';
let messageTimeout;
let howtoplay = false;
function preload() {
dungeonBackground = loadImage("mazes.jpeg");
coinGIF = loadImage("coin.gif");
dungeonMaster = loadImage("sprite.png");
teleporter = loadImage("portal.gif");
dungeonIntroBg = loadImage("dungeongif.gif");
ancientFont = loadFont("AncientMedium.ttf");
gameOverBg = loadImage("endgame.gif");
winningBg = loadImage("winningstate.png")
introMusic = loadSound("introMusic.mp3");
playingMusic = loadSound("playingMusic.mp3");
gameOverMusic = loadSound("gameOverMusic.mp3");
gameWinningMusic = loadSound("gamewinning.mp3");
collectCoinSound = loadSound("collectcoin.mp3");
teleporterSound = loadSound("teleportsound.mp3");
howtoplayBG = loadImage("howtoplay.png");
}
function setup() {
createCanvas(800, 800);
calculateCellSize();
// cellSize = width / mazeColumns;
noStroke();
textAlign(CENTER, CENTER);
textSize(16);
frameRate(10);
let animations = setupPlayerAnimations();
player = new Player(1, 1, animations);
mazes.push(new Maze(mazeRows, mazeColumns));
}
// Defines a function to set up player animations from a sprite sheet.
function setupPlayerAnimations() {
// Initialize an object to hold arrays of animation frames for each direction.
let animations = { down: [], left: [], right: [], up: [] };
let directions = ['up', 'left', 'down', 'right'];
frameWidth = dungeonMaster.width / 9;
frameHeight = dungeonMaster.height / 4;
// Nested loops to iterate through each direction and each frame within that direction.
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 9; j++) {
let img = dungeonMaster.get(j * frameWidth, i * frameHeight, frameWidth, frameHeight);
animations[directions[i]].push(img);
}
}
// Returns the populated animations object.
return animations;
}
class Maze {
constructor(rows, cols) {
this.rows = rows;
this.cols = cols;
// Track total coins in the maze
this.totalCoins = 0;
// Track coins collected by the player
this.coinsCollected = 0;
this.grid = this.generateRandomMaze();
}
generateRandomMaze() {
// Initializes a maze with all walls ('#') in a grid of 'rows' by 'cols'.
let maze = new Array(this.rows);
for (let y = 0; y < this.rows; y++) {
maze[y] = new Array(this.cols).fill('#');
}
// Function to recursively carve paths (' ') in the maze from a given starting point (x, y).
const carvePath = (x, y) => {
// Possible movement directions.
const directions = [[1, 0], [-1, 0], [0, 1], [0, -1]];
// Randomize the order of directions for varied path carving.
this.shuffle(directions);
directions.forEach(([dx, dy]) => {
// Calculate new position, skipping one cell to create walls.
// Check if new position is valid and unvisited ('#'), then carve a path and recurse.
const nx = x + 2 * dx, ny = y + 2 * dy;
if (nx > 0 && nx < this.cols - 1 && ny > 0 && ny < this.rows - 1 && maze[ny][nx] === '#') {
// Carve path at new position.
maze[ny][nx] = ' ';
// Carve path between old and new position.
maze[y + dy][x + dx] = ' ';
carvePath(nx, ny);
}
});
};
// Select random starting position within maze boundaries and ensure it's an odd cell for path carving.
const startX = 2 * Math.floor(Math.random() * Math.floor((this.cols - 2) / 4)) + 1;
const startY = 2 * Math.floor(Math.random() * Math.floor((this.rows - 2) / 4)) + 1;
maze[startY][startX] = ' ';
carvePath(startX, startY);
// Randomly determine the number of coins and teleporters to place in the maze.
let coinCount = Math.floor(Math.random() * (12 - 8 + 1)) + 8; // 8 to 12 coins
let teleporterCount = Math.floor(Math.random() * (2 - 1 + 1)) + 1; // 1 to 2 teleporters
maze[this.rows - 2][this.cols - 2] = 'E';
this.addRandomItems(maze, 'C', coinCount);
this.addRandomItems(maze, 'T', teleporterCount);
return maze;
}
// Function to randomly shuffle the elements of an array in place.
shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
// Function to add a specified number of items into random positions within the maze.
addRandomItems(maze, item, count) {
let placed = 0;
while (placed < count) {
// Generate random coordinates within the maze boundaries, excluding the outer walls.
let ry = Math.floor(Math.random() * (this.rows - 2)) + 1;
let rx = Math.floor(Math.random() * (this.cols - 2)) + 1;
if (maze[ry][rx] === ' ') {
// Place the item at the random position.
maze[ry][rx] = item;
placed++;
if (item === 'C') {
// Increment total coins if a coin is placed
this.totalCoins++;
}
}
}
}
draw(x, y, cellSize) {
// Calculate the starting X-coordinate to center the maze horizontally
let offsetX = max(0, (width - (this.cols * cellSize)) / 2);
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.cols; j++) {
let cell = this.grid[i][j];
// Draw walls
if (cell === '#') {
fill('rgba(50, 50, 50, 0.7)');
rect((j * cellSize) + offsetX, i * cellSize, cellSize, cellSize);
// Draw Exit
} else if (cell === 'E') {
fill('brown');
rect((j * cellSize) + offsetX, i * cellSize, cellSize, cellSize);
// Draw path
} else {
fill('rgba(100, 100, 100, 0.3)');
rect((j * cellSize) + offsetX, i * cellSize, cellSize, cellSize);
}
if (cell === 'T') {
// Draw teleporter
image(teleporter, (j * cellSize) + offsetX, i * cellSize, cellSize, cellSize);
} else if (cell === 'C') {
// Draw coin
image(coinGIF, (j * cellSize) + offsetX, i * cellSize, cellSize, cellSize);
}
}
}
}
}
class Player {
constructor(startX, startY, animationFrames) {
this.position = createVector(startX, startY);
this.animationFrames = animationFrames;
// Default direction
this.currentDirection = 'down';
this.currentFrame = 0;
// To check if direction has changed
this.lastDirection = '';
}
updateDirection(direction) {
// Check if direction has changed or it's the first movement in any direction
if (this.currentDirection !== direction || this.lastDirection === '') {
this.currentDirection = direction;
// Remember the last direction
this.lastDirection = direction;
// Reset frame at the start of new direction animation
this.currentFrame = 0;
}
}
startMoving(direction) {
// Only update direction if it has changed
if (this.currentDirection !== direction) {
this.updateDirection(direction);
} else {
// Increment the frame within the same direction
this.currentFrame++;
// Loop back to the first frame if we reach the end of the animation frames
if (this.currentFrame >= this.animationFrames[this.currentDirection].length) {
this.currentFrame = 0;
}
}
}
// New method to stop the animation
stopMoving() {
this.isMoving = false;
// Reset frame to show idle animation frame
this.currentFrame = 0;
}
move(direction, maze) {
let dx = 0;
let dy = 0;
switch (direction) {
case 'up': dy = -1; break;
case 'down': dy = 1; break;
case 'left': dx = -1; break;
case 'right': dx = 1; break;
}
let newX = this.position.x + dx;
let newY = this.position.y + dy;
if (newX >= 0 && newX < maze.cols && newY >= 0 && newY < maze.rows) {
let cell = maze.grid[newY][newX];
// Check if the cell is not a wall
if (cell !== '#') {
this.position.x = newX;
this.position.y = newY;
// If the player moves into a coin or teleporter, return that cell value
return cell;
}
}
// Return an empty string if no significant interaction occurs
return '';
}
draw(cellSize, offsetX) {
// Add a glow effect around the player when drawing
// Start a new drawing state
push();
// Calculate the position of the player with the horizontal offset
let playerX = (this.position.x * cellSize) + cellSize / 2 + offsetX;
let playerY = this.position.y * cellSize + cellSize / 2;
translate(playerX, playerY);
// Add a glow effect by setting a shadow on the drawing context
drawingContext.shadowBlur = 15;
// Color of the glow
drawingContext.shadowColor = 'white';
// Determine the current animation frame
let frames = this.animationFrames[this.currentDirection];
if (!frames) {
console.error('Invalid direction or frames not loaded:', this.currentDirection);
// Make sure to restore the drawing state even if there's an error
pop();
// Exit the draw function early to avoid errors
return;
}
// Calculate the index of the current frame for the animation
this.currentFrame = (this.currentFrame + (this.isMoving ? 1 : 0)) % frames.length;
// Draw the current frame of the animation
image(frames[floor(this.currentFrame)], -cellSize / 2, -cellSize / 2, cellSize, cellSize);
// Restore original drawing state
pop();
}
resetDirection(newDirection = 'down') {
this.currentDirection = newDirection;
this.currentFrame = 0;
}
}
function draw() {
// Loop the intro music
if (gameState === 'INTRO') {
if (!introMusic.isPlaying()) {
playingMusic.stop();
gameOverMusic.stop();
gameWinningMusic.stop();
introMusic.loop();
}
drawIntroScreen();
}
// Loop the playing music
else if (gameState === 'PLAYING') {
if (!playingMusic.isPlaying()) {
introMusic.stop();
gameOverMusic.stop();
gameWinningMusic.stop();
playingMusic.loop();
}
playGame();
}
// Play the game over music
else if (gameState === 'GAME_OVER') {
if (!gameOverMusic.isPlaying()) {
introMusic.stop();
playingMusic.stop();
gameWinningMusic.stop();
gameOverMusic.play();
}
drawGameOverScreen();
}
// Play winning music
else if (gameState == "WINNING"){
if (!gameWinningMusic.isPlaying()){
introMusic.stop();
playingMusic.stop();
gameOverMusic.stop();
gameWinningMusic.play();
}
drawWinningScreen();
}
// Play the same intro music
else if (gameState == "HowToPlay"){
if (!introMusic.isPlaying()) {
playingMusic.stop();
gameOverMusic.stop();
gameWinningMusic.stop();
introMusic.loop();
}
drawHowToPlay();
}
}
function drawIntroScreen() {
background(dungeonIntroBg);
fill(255);
textFont('Times New Roman');
textSize(20);
text('Press f/F for fullscreen mode.', width/6,height/10);
textFont(ancientFont);
fill(255);
textSize(75);
text('Maze Dungeon', width / 2, height / 3);
textSize(45);
text('Find the gold coins and escape the dungeon!', width / 2, height / 2);
// Draw Play Button
drawDungeonButton(width / 2 - 75, height / 2 + 150, 150, 50, 'Play');
// Draw How to Play Button
drawDungeonButton(width / 2 - 75, height / 2 + 225, 150, 50, 'How to Play');
}
function drawDungeonButton(x, y, w, h, buttonText) {
// Dark wood/brown color
fill(139, 69, 19);
rect(x, y, w, h, 10);
// Gold-like color for the text
fill(255, 215, 0);
textSize(30);
textAlign(CENTER, CENTER);
text(buttonText, x + w / 2, y + h / 2);
}
function mouseClicked() {
if (gameState === 'INTRO') {
// Corrected button click area check for the 'Play' button
if (mouseX >= width / 2 - 75 && mouseX <= width / 2 + 75 && mouseY >= height / 2 + 150 && mouseY <= height / 2 + 200) {
gameState = 'PLAYING';
resetGame();
}
// Corrected button click area check for the 'How to Play' button
else if (mouseX >= width / 2 - 75 && mouseX <= width / 2 + 75 && mouseY >= height / 2 + 225 && mouseY <= height / 2 + 275) {
console.log('Display how to play the game...');
gameState = 'HowToPlay';
}
} else if (gameState == 'HowToPlay'){
// Corrected button click area check for the 'Return' button
if (mouseX >= width / 2 - 75 && mouseX <= width / 2 + 75 && mouseY >= height / 2 + 270 && mouseY <= height / 2 + 320){
gameState = 'INTRO';
}
}
else if (gameState === 'GAME_OVER') {
gameState = 'INTRO';
}
else if (gameState === 'WINNING'){
gameState = 'INTRO';
}
}
function drawLimitedVision(playerX,playerY,visibilityRadius) {
// Draw a radial gradient from transparent to dark
let radialGradient = drawingContext.createRadialGradient(playerX, playerY, 0, playerX, playerY, visibilityRadius);
// Completely transparent at the center
radialGradient.addColorStop(0, 'rgba(0,0,0,0)');
// More opaque towards the edges
radialGradient.addColorStop(1, 'rgba(0,0,0,0.8)');
drawingContext.fillStyle = radialGradient;
drawingContext.fillRect(0, 0, width, height);
}
function playGame() {
background(dungeonBackground);
let currentTime = millis();
let mazeX = max(0, (width - (mazeColumns * cellSize)) / 2);
mazes[currentMazeIndex].draw(mazeX, 0, cellSize);
// Calculate the player's position with the horizontal offset
let playerX = (player.position.x * cellSize) + cellSize / 2 + mazeX;
let playerY = player.position.y * cellSize + cellSize / 2;
let visibilityRadius = 120;
// Draw limited vision effect
drawLimitedVision(playerX,playerY,visibilityRadius);
player.draw(cellSize, mazeX);
drawTimer(currentTime);
drawScore();
if (currentTime - startTime > timeLimit) {
gameState = 'GAME_OVER';
}
printMessage();
}
function resetGame() {
score = 0;
mazes = [new Maze(mazeRows, mazeColumns)];
currentMazeIndex = 0;
player.position.set(1, 1);
player.resetDirection();
startTime = millis();
}
function drawGameOverScreen() {
background(gameOverBg);
textFont(ancientFont);
fill(255, 0, 0);
textSize(70);
text("Game Over!", width / 2, height / 4 );
textSize(55);
fill(255);
text("Click to return to the main menu", width / 2, height / 2 + height / 4);
}
function drawWinningScreen() {
background(winningBg);
textFont(ancientFont);
fill(255, 0, 0);
textSize(70);
text("Congratulations!", width / 2, height / 4 - 60);
text("You filled the treasure chest!", width / 2, height / 4);
textSize(55);
fill(255);
text("Click to return to the main menu", width / 2, height / 2 + height / 4);
}
function drawHowToPlay(){
background(howtoplayBG);
textFont(ancientFont);
fill(255, 0, 0);
textSize(70);
drawDungeonButton(width / 2 - 75, height / 2 + 270, 150, 50, 'Return');
}
function drawTimer(currentTime) {
let timeLeft = (startTime + timeLimit - currentTime) / 1000;
if (timeLeft < 0) {
// Ensure time left doesn't go negative
timeLeft = 0;
}
fill(255);
stroke(0);
strokeWeight(2);
textSize(20);
// Bold text for better readability
textStyle(BOLD);
// Background for the timer for better visibility
// Semi-transparent dark background
fill(0, 102, 153, 200);
// Rounded rectangle for the timer background
rect(width / 2 - 60, 10, 120, 40, 20);
// Set fill again in case the rect overrides it
fill(255);
noStroke();
// Draw the text in the middle of the background
text(`Time: ${timeLeft.toFixed(1)}`, width / 2, 30);
}
function keyPressed() {
// Fullscreen toggle with 'F' key
if (key === 'f' || key === 'F') {
let fs = fullscreen();
fullscreen(!fs);
}
// Only process keyboard input if the game is in the 'PLAYING' state
if (gameState === 'PLAYING') {
let direction = '';
if (keyCode === UP_ARROW) direction = 'up';
else if (keyCode === DOWN_ARROW) direction = 'down';
else if (keyCode === RIGHT_ARROW) direction = 'right';
else if (keyCode === LEFT_ARROW) direction = 'left';
if (direction) {
let interaction = player.move(direction, mazes[currentMazeIndex]);
player.startMoving(direction);
switch (interaction) {
case 'C': // Coin collected
collectCoinSound.play();
score++;
// Increment coins collected
mazes[currentMazeIndex].coinsCollected++;
// Remove the coin from the grid
mazes[currentMazeIndex].grid[player.position.y][player.position.x] = ' ';
console.log(`Score: ${score}`);
break;
case 'T': // Teleporter used
teleporterSound.play();
nextMaze();
break;
case 'E': // Exit door used
// Check if the player has collected at least 70% of the coins
if (mazes[currentMazeIndex].coinsCollected >= Math.ceil(mazes[currentMazeIndex].totalCoins * 0.7)) {
nextMaze();
}
else {
showMessage("Collect at least 70% of the coins to use the Exit door!");
}
}
}
return false;
}
}
function keyReleased() {
if (gameState == 'PLAYING'){
// This will stop the animation when any key is released.
player.stopMoving();
// Prevent default behavior
return false;
}
}
function drawScore() {
// Style adjustments for a dungeon theme
fill(128, 0, 0);
stroke(0);
strokeWeight(4);
textSize(22);
textStyle(BOLD);
// Simulate a banner or parchment
rect(width - 160, 5, 150, 50, 10); // Rounded rectangle for parchment/banner look
fill(255, 215, 0); // Gold color for the score to stand out as if it's inscribed
noStroke();
text(`Score: ${score}`, width - 85, 30);
if (score == 20){
gameState = 'WINNING';
}
}
function nextMaze() {
// Generate a new maze and add it to the list
let newMaze = new Maze(mazeRows, mazeColumns);
mazes.push(newMaze);
// Update the current maze index to the newly created maze
currentMazeIndex = mazes.length - 1;
// Reset player position for the new maze
player.position.set(1, 1);
// Optionally reset the player's direction
player.resetDirection();
// Reset timer for the new maze
startTime = millis();
mazes[currentMazeIndex].coinsCollected = 0;
}
function showMessage(text, duration = 3000) {
message = text;
clearTimeout(messageTimeout); // Clear existing message timeout
messageTimeout = setTimeout(() => {
message = ''; // Clear message after the duration
}, duration);
}
function printMessage(){
if (message !== '') {
fill(255, 255, 0); // Choose a text color that stands out
textSize(30);
textAlign(CENTER, CENTER);
text(message, width / 2, height/2); // Display the message at the top or center
}
}
function calculateCellSize() {
cellSize = min(width, height) / max(mazeColumns, mazeRows);
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
calculateCellSize();
}