xxxxxxxxxx
355
// Intro To IM - Fall 2023
// Final Project - Baige - Horse Race
// Diana Alibekova
// initialization of variables
let gameState = 'start';
let circleX = 200; //initial x position of the horse
let obstacles = []; //array to store the obstacles
let bonusCircles = []; //array to store the bonus circles
let xPositions = []; //array to store the x positions where the obstacles and bonus stars appear
let obstacleSpeed = 2; // Initial speed of obstacles
let speedIncrement = 0.4; //the amount by which the speed of the obstacles increase
let score = 0; //initial score
let wonGame = false;
let stopOnce = false;
let startimage;
// preloading the images and sound before the setup starts, so all the audio-visuals are prepared before the game runs
function preload() {
startimage = loadImage('images/startimage.png');
grass = loadImage('images/grass.png');
playimage = loadImage('images/playimage.png');
endimage = loadImage('images/endimage.png');
horse = loadImage('images/horse.png');
obstacle_1 = loadImage('images/snake.png');
star = loadImage('images/star.png')
dombyra = loadSound("dombyra.mp3");
bonussound = loadSound("bonussound.mp3");
negative_beep = loadSound("negative_beeps.mp3");
level_win = loadSound("level-win.mp3");
}
function setup() {
createCanvas(windowWidth, windowHeight); //full screen canvas
restartGame();
// positions of obstacles and bonus circles by x coordinate where they will appear
xPositions = [windowWidth * (3 / 12), windowWidth * (5 / 12), windowWidth * (7 / 12), windowWidth * (9 / 12)];
circleX = windowWidth*(5/12); //initial horse position
dombyra.play(); //the traditional song played
}
// resetting the horse position, clearing the obstacles, the bonus stars and the score everytime the game restarts
function restartGame() {
wonGame = false;
gameState = 'start';
circleX = windowWidth*(5/12);
obstacles = []; // Clearing the obstacles array
score = 0; // Resetting the score
obstacleSpeed = 1;
bonusCircles = [];
}
function draw() {
// three game states: start, play and end
// the game states code followed the class example https://editor.p5js.org/mangtronix/sketches/lwALEq10U
if (gameState == 'start') {
stopOnce = false;
background(220);
drawInstructions();
} else if (gameState == 'playing') {
background(220);
drawGame();
} else if (gameState == 'end') {
//The sleep function was based on the code on the website: https://stackoverflow.com/questions/67221313/how-to-wait-in-p5-js
if (stopOnce == false) {
//freezing the frame for a second, so the collision with the snake is visible
sleep(1000).then(function() {
background(220);
drawEndScreen();
})
} else {
background(220);
drawEndScreen();
}
}
}
// the function of drawing instructions during the 'start' state
function drawInstructions() {
print('drawing instructions');
background(255);
textSize(20);
startimage.resize(windowWidth, windowHeight); //making responsible image that resizes according the window size
image(startimage, 0, 0); //displaying the image with instructions
//The serial communication code followed the class example: https://editor.p5js.org/mangtronix/sketches/s67XC0zT4
if (!serialActive) {
text("Press Space Bar to select Serial Port", 20, 30);}
}
// the function of drawing game during the 'playing' state
function drawGame() {
background('#FFFFFF');
playimage.resize(windowWidth, windowHeight); //making responsible image that resizes according the window size
image(playimage, 0, 0); //displaying the playing image with four lines
fill(0);
let circleRadius = min(windowWidth, windowHeight) * 0.04; //making responsible circle radius of the horse for the window size
// displaying the image with its coordinates and size
image(horse, circleX - circleRadius, windowHeight * (2 / 3) - circleRadius, circleRadius * 2, circleRadius * 3);
// Creating obstacles randomly on one of x positions in array every 40 frames
if (frameCount % 40 === 0) {
let randomIndex = floor(random(xPositions.length));
let randomX = xPositions[randomIndex];
let obstacle = new Obstacle(randomX);
obstacles.push(obstacle);
}
// increasing the speed of the obstacles by 0.4 every 2000 frames
if (frameCount % 4000 === 0) {
obstacleSpeed += speedIncrement; // Increasing the speed
}
// Updating and displaying obstacles
for (let i = obstacles.length - 1; i >= 0; i--) {
obstacles[i].speed = obstacleSpeed; // Updating obstacle speed
obstacles[i].update();
obstacles[i].display();
for (let i = obstacles.length - 1; i >= 0; i--) {
obstacles[i].update();
obstacles[i].display();
// Removing obstacles when they reach the bottom of the screen
if (obstacles[i].offScreen()) {
obstacles.splice(i, 1);
}
}
// for loop accessing each obstacle in the array and calcilating the distance between the horse and obstacle as well as minimum distance for collision to occur
for (let i = 0; i < obstacles.length; i++) {
let obstacle = obstacles[i];
let distance = dist(circleX, windowHeight * (2 / 3), obstacle.x, obstacle.y);
let minDistance = obstacle.radius + (circleRadius / 2);
// if the horse collides with the obstacles, then the game is over, transitting to end game state and playing the winning sound
if (distance < minDistance) {
wonGame = false;
// transitting to end game state
gameState = 'end';
level_win.play();
// if the horse doesn't collide with the obstacles, for each of the obstacles passed through the window height, the score is incremented.
} else if (obstacle.y > windowHeight && !obstacle.passed) {
obstacle.passed = true;
score++;
}
}
}
// Creating bonus circles randomly on one of x positions in array every 350 frames
if (frameCount % 350 === 0) {
let randomIndex = floor(random(xPositions.length));
let randomX = xPositions[randomIndex];
let bonusCircle = new BonusCircle(randomX);
bonusCircles.push(bonusCircle);
}
// Updating and displaying bonus circles
for (let i = bonusCircles.length - 1; i >= 0; i--) {
bonusCircles[i].speed = obstacleSpeed;
bonusCircles[i].update();
bonusCircles[i].display();
let distance = dist(circleX, windowHeight * (2 / 3), this.x, this.y);
let minDistance = this.radius + (circleRadius / 2);
if (distance < minDistance) {
// Collision occurred
score += 10; // Incrementing the score by 10
bonussound.play();
bonusCircles.splice(i, 1);
return true; // Signal collision
} else {
bonusCircles.splice(i, 1);
return false; // No collision
}
// // here I experimented and created the checking collision function and for every collision with the bonus circles.
// if (bonusCircles[i].checkCollision()) {
// // Removing the bonus circle upon collision
// bonusCircles.splice(i, 1);
// } else if (bonusCircles[i].offScreen()) {
// // Removing the bonus circle if it's off-screen
// bonusCircles.splice(i, 1);
// }
}
fill(0);
textSize(30);
textStyle(BOLD);
text(`Score: ${score}`, windowWidth*0.08, windowHeight*0.05);
// }
}
// starting serial communication
function keyPressed() {
if (key == " ") {
// important to have in order to start the serial connection!!
setUpSerial();
}
}
// the function of drawing end screen in end state
function drawEndScreen() {
stopOnce = true;
if (wonGame) {
background('blue');
text('You WON!!', 100, 200);
} else {
endimage.resize(windowWidth, windowHeight);
image(endimage, 0, 0);
textSize(150);
textAlign(CENTER, CENTER);
textStyle(BOLD);
fill('#012495');
text(`${score}`, windowWidth*0.8, windowHeight*0.65);
}
}
// serial communication followed the class example: https://editor.p5js.org/mangtronix/sketches/s67XC0zT4
// This function will be called by the web-serial library with each new *line* of data.
function readSerial(data) {
//reading from Arduino
if (data != null) {
//making sure that there is a message and splitting that
let fromArduino = split(trim(data), ",");
// if the right length, then proceed
if (fromArduino.length == 1) {
// changing the x position of the horse based on the data received
if (data === '1') {
circleX = windowWidth*(3/12);
} else if (data === '2') {
circleX = windowWidth*(5/12);
} else if (data === '3') {
circleX = windowWidth*(7/12);
} else if (data === '4') {
circleX = windowWidth*(9/12);
}
//transitting through the states if data '0' is received
else if (data === '0') {
if (gameState == 'start') {
gameState = 'playing';
} else if (gameState == 'playing') {
// if (key == ' ') {
// wonGame = true;
gameState = 'end';
// }
} else if (gameState == 'end') {
restartGame();
}
}
}
//////////////////////////////////
//SEND TO ARDUINO HERE (handshake)
//////////////////////////////////
// let sendToArduino = circleX + "\n";
// writeSerial(sendToArduino);
}
}
class Obstacle {
constructor(x) {
this.x = x;
this.y = 0;
this.speed = obstacleSpeed;
this.radius = min(windowWidth, windowHeight) * 0.03;
}
update() {
this.y += this.speed;
}
display() {
image(obstacle_1, this.x, this.y, this.radius * 4, this.radius * 4);
}
offScreen() {
return this.y > windowHeight + this.radius;
}
}
class BonusCircle {
constructor(x) {
this.x = x;
this.y = 0;
this.speed = obstacleSpeed;
this.radius = min(windowWidth, windowHeight) * 0.03;
}
update() {
this.y += this.speed;
}
display() {
image(star, this.x, this.y, this.radius * 2.5, this.radius * 2.5);
}
offScreen() {
return this.y > windowHeight + this.radius;
}
// checkCollision() {
// let distance = dist(circleX, windowHeight * (2 / 3), this.x, this.y);
// let circleRadius = min(windowWidth, windowHeight) * 0.04;
// let minDistance = this.radius + (circleRadius / 2);
// if (distance < minDistance) {
// // Collision occurred
// score += 10; // Incrementing the score by 10
// bonussound.play();
// return true; // Signal collision
// }
// return false; // No collision
// }
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
xPositions = [windowWidth * (3 / 12), windowWidth * (5 / 12), windowWidth * (7 / 12), windowWidth * (9 / 12)];
}
// pressing 4 to toggle the fullscreen
function keyTyped() {
if (key === 'f') {
toggleFullscreen();
}
}
// Toggle fullscreen state
function toggleFullscreen() {
let fs = fullscreen(); // Get the current state
fullscreen(!fs); // Flip it!
}
// the 'delay' or 'sleep' function to freeze the picture for a second was learned from the website https://stackoverflow.com/questions/67221313/how-to-wait-in-p5-js
function sleep(millisecondsDuration)
{
return new Promise((resolve) => {
setTimeout(resolve, millisecondsDuration);
})
}