xxxxxxxxxx
777
let rocky, moodMeter, timer, buttons = [];
let foods = [];
let foodImages = []; // array to hold the food images
let feedCount = 0; // to track feeding attempts
let maxFeeds = 2; // max number of times Rocky can be fed
let currentFoodScreen = false; // variable to track if we're on the food selection screen
let foodPreferences = {}; // stores random mood effects for each food
let musicPreferences = {}; // stores mood effects for music tracks
let foodFeedback = ""; // to show Rocky's reaction to the selected food
let foodFeedbackTimeout; // variable to hold timeout ID for clearing food feedback
let relaxFeedbackTimeout; // variable to hold timeout ID for clearing relax feedback
let playing = false; // to track if Rocky is playing
let playInterval; // variable to hold the interval ID for mood increase
let relaxing = false; // to track is Rocky is relaxing
let obstacles = []; // to hold obstacles
let obstacleSpeed = 9; // speed of obstacles
let relaxTracks = []; // array to hold music tracks
let selectedTrack = null; // to store the selected track
let relaxFeedback = ""; // to store feedback for the selected track
let relaxDisabled = false; // disable the relax button after it's used
let isRelaxing = false; // check if Rocky is relaxing
let currentTrack = null; // to store current track
let trackButtons = []; // array to hold track buttons
let gameState = 'start'; // variable to store possible game states: 'start', 'instructions', 'playing', 'gameOver'
// Variables to store different media
let rockyImageMeh, rockyImageHappyish, rockyImageHappy, rockyImageSadish, rockyImageSad, rockImage;
let obstacleImage;
let buttonImage;
let backButton;
let hitSound;
let introImage, instructionsImage, endImage; // Declare images for different screens
// Preloading media
function preload() {
backgroundMusic = loadSound('/sounds/bg-music.mp3');
rockyImage = rockyImageMeh = loadImage('/images/rocky-meh.png');
rockyImageHappyish = loadImage('/images/rocky-happyish.png');
rockyImageHappy = loadImage('/images/rocky-happy.png');
rockyImageSadish = loadImage('/images/rocky-sadish.png');
rockyImageSad = loadImage('/images/rocky-sad.png');
mainBGImage = loadImage('/images/garden.png');
playImage = loadImage('/images/garden-topview.png');
relaxImage = loadImage('/images/relax-background.png');
obstacleImage = loadImage('/images/dodgeball.png');
foodImages.push(loadImage('/images/pebbles.png'));
foodImages.push(loadImage('/images/sand.png'));
foodImages.push(loadImage('/images/lava.png'));
foodImages.push(loadImage('/images/clay.png'));
foodImages.push(loadImage('/images/granite.png'));
buttonImage = loadImage('/images/button.png');
hitSound = loadSound('/sounds/hit-sound.mp3');
backButtonImage = loadImage('/images/back-button.png');
backButtonImage = loadImage('/images/back-button.png');
relaxTracks.push(loadSound('/sounds/lullaby-track.mp3'));
relaxTracks.push(loadSound('/sounds/funk-track.mp3'));
relaxTracks.push(loadSound('/sounds/lofi-track.mp3'));
introImage = loadImage('/images/intro-screen.png');
instructionsImage = loadImage('/images/instructions-screen.png');
endImage = loadImage('/images/endscreen-background.png');
relaxTracks.push(loadSound('/sounds/hit-sound.mp3'));
relaxTracks.push(loadSound('/sounds/hit-sound.mp3'));
relaxTracks.push(loadSound('/sounds/hit-sound.mp3'));
}
function setup() {
createCanvas(windowWidth, windowHeight);
// Play and loop the background music
backgroundMusic.loop();
textFont('Comic Sans MS');
rocky = new Rocky(); // create an instance of Rocky
moodMeter = new MoodMeter(); // create an instance of MoodMeter
timer = new Timer(100); // create an instance of timer with 100 seconds
let buttonWidth = 100;
let buttonSpacing = 50;
// Center the first button
let startX = (windowWidth - (buttonWidth * 3 + buttonSpacing * 2)) / 2;
// Add buttons with updated x-coordinates
buttons.push(new Button('Feed', startX, rocky.y+110));
buttons.push(new Button('Play', startX + buttonWidth + buttonSpacing, rocky.y+110));
buttons.push(new Button('Relax', startX + 2 * (buttonWidth + buttonSpacing), rocky.y+110)); // Placeholder for music
// Add track buttons
trackButtons.push(new Button('Lullaby', startX, rocky.y-130));
trackButtons.push(new Button('Funk', startX + buttonWidth + buttonSpacing, rocky.y-130));
trackButtons.push(new Button('Lo-fi', startX + 2 * (buttonWidth + buttonSpacing), rocky.y-130));
// Update food options to something rocks might eat
foods = ['Pebbles', 'Sand', 'Lava', 'Clay', 'Granite'];
randomizeFoodPreferences(); // Randomize mood effects for food
randomizeMusicPreferences(); // Randomize mood effects for music
// Create back button for food/play/relax screens
backButton = new BackButton();
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
rocky.x = windowWidth / 2; // re-center Rocky horizontally
rocky.y = windowHeight / 2 + 100; // re-center Rocky vertically
if (relaxing){
rocky.y = windowHeight * 0.60;
}
// Re-center buttons
let buttonWidth = 100;
let buttonSpacing = 50;
let startX = (windowWidth - (buttonWidth * 3 + buttonSpacing * 2)) / 2; // Recalculate starting x for buttons
buttons[0].x = startX; // Update the x-coordinate for 'Feed' button
buttons[0].y = buttons [1].y = buttons[2].y = rocky.y + 110;
buttons[1].x = startX + buttonWidth + buttonSpacing; // Update the x-coordinate for 'Play' button
buttons[2].x = startX + 2 * (buttonWidth + buttonSpacing); // Update the x-coordinate for 'Relax' button
trackButtons[0].x = startX; // Update the x-coordinate for 'Lullaby' button
trackButtons[0].y = trackButtons[1].y = trackButtons[2].y = rocky.y-130;
trackButtons[1].x = startX + buttonWidth + buttonSpacing; // Update the x-coordinate for 'Funk' button
trackButtons[2].x = startX + 2 * (buttonWidth + buttonSpacing); // Update the x-coordinate for 'Lo-Fi' button
}
function draw() {
if (timer.timeRemaining>=100){
playing = false;
}
// print(playing);
if (gameState === 'start') {
// Display the start screen image
image(introImage, 0, 0, width, height);
} else if (gameState === 'instructions') {
// Display the instructions image
rocky.mood = 50; // Reset Rocky's mood
image(instructionsImage, 0, 0, width, height);
} else if (gameState === 'playing') {
// The existing game logic goes here
if (playing){
background(playImage);
}
else if (relaxing) {
background(relaxImage); // Display relax background when relaxing
}
else{
background(mainBGImage);
}
if (rocky.mood > 100){
rocky.mood = 100;
}
if (currentFoodScreen) {
if (backgroundMusic.isPaused()){
backgroundMusic.play();
}
displayFoods(); // Show food selection
backButton.display(); // Display back button in food selection mode
moodMeter.display(rocky.mood);
timer.display();
}
else if (playing) {
if (backgroundMusic.isPaused()){
backgroundMusic.play();
}
// Play mode
timer.display();
rocky.update();
rocky.display();
moodMeter.display(rocky.mood);
// Generate obstacles
if (frameCount % 40 == 0) {
obstacles.push(new Obstacle());
}
for (let i = obstacles.length - 1; i >= 0; i--) {
obstacles[i].update();
obstacles[i].display();
// Check for collision
if (obstacles[i].hits(rocky)) {
hitSound.play(); // Play hit sound on collision
rocky.mood -= 10; // Decrease mood on collision
obstacles.splice(i, 1); // Remove obstacle after collision
continue; // Skip to the next iteration to avoid calling offscreen() on a spliced element
}
// Remove obstacles that go off-screen
if (obstacles[i].offscreen()) {
obstacles.splice(i, 1);
}
}
// Check if mood or timer runs out to end the game
if (rocky.mood <= 0 || timer.isTimeUp()) {
gameState='gameOver';
playing = false;
resetGame();
}
if (rocky.mood <= 0) {
gameState='gameOver';
playing = false;
resetGame();
}
backButton.display(); // Display back button in play mode
fill(0);
textSize(20);
}
else if (relaxing){
backgroundMusic.pause();
rocky.y = windowHeight * 0.67;
rocky.display();
timer.display();
moodMeter.display(rocky.mood);
startRelaxMode();
}
else {
if (backgroundMusic.isPaused()){
backgroundMusic.play();
}
rocky.display();
moodMeter.display(rocky.mood);
timer.display();
for (let button of buttons) {
button.display();
}
// if (timer.isTimeUp()) {
// endGame();
// }
if (relaxFeedback){
fill(0);
textSize(23);
textAlign(CENTER); // Center the feedback text horizontally
text(relaxFeedback, windowWidth / 2, rocky.y + 80); // Display Rocky's feedback
}
if (foodFeedback) {
fill(0);
textSize(23);
textAlign(CENTER); // Center the feedback text horizontally
text(foodFeedback, windowWidth / 2, rocky.y + 80); // Display Rocky's feedback
}
}
if (rocky.mood <= 0 || timer.isTimeUp()) {
gameState='gameOver';
playing = false;
resetGame();
}
}
else if (gameState === 'gameOver') {
playing = false;
relaxing = false;
currentFoodScreen = false;
// Display the final mood of Rocky and "Game Over"
image(endImage, 0, 0, windowWidth, windowHeight); // Background image scaled to window size
push();
fill('#39521b')
// Display "Game Over" text at the top (responsive size)
textSize(windowWidth * 0.1); // Scale the size based on windowWidth
textAlign(CENTER);
text("Game Over!", windowWidth / 2, windowHeight * 0.16); // "Game Over" positioned at 10% of windowHeight
// Display Rocky's image based on his mood state in the center (responsive size)
let rockySize = 300; // Make Rocky's image size relative to windowWidth
image(rockyImage, (windowWidth / 2) - (rockySize * 1.5 / 2), (windowHeight / 2) - (rockySize / 2), rockySize*1.5, rockySize); // Rocky's image centered
// Display the mood note based on Rocky's final mood (responsive size)
textSize(windowWidth * 0.04); // Scale the text size based on windowWidth
let moodMessage = '';
// print(rocky.mood);
if (rocky.mood >= 0 && rocky.mood <= 20) {
moodMessage = "Rocky's feeling pretty upset now :( \nHe probably needs some time to himself.";
} else if (rocky.mood > 20 && rocky.mood <= 40) {
moodMessage = "Rocky isn't feeling his best anymore :(";
} else if (rocky.mood > 40 && rocky.mood <= 60) {
moodMessage = "Rocky's feeling pretty meh \nbut he hopes to feel better soon!";
} else if (rocky.mood > 60 && rocky.mood <= 80) {
moodMessage = "Rocky's in much better spirits now. Yay!";
} else if (rocky.mood > 80 && rocky.mood <= 100) {
moodMessage = "Wow, you're really good at this!\n You're now officially Rocky's favorite human!";
}
// Display the mood message below Rocky's image (responsive size)
textSize(31);
text(moodMessage, windowWidth / 2, windowHeight / 2 + rockySize / 1.4); // Message below the image
textSize(27);
fill(0);
text("Click anywhere to restart!", windowWidth / 2, windowHeight - windowHeight*0.07);
pop();
}
}
function randomizeFoodPreferences() {
let shuffledFoods = shuffle([10, 10, -10, -10, 0]); // Two increase, two decrease, one neutral
for (let i = 0; i < foods.length; i++) {
foodPreferences[foods[i]] = shuffledFoods[i];
}
}
function randomizeMusicPreferences() {
let shuffledTracks = shuffle([10, 0, -10]); // One increases, one decreases, one is neutral
for (let i = 0; i < relaxTracks.length; i++) {
musicPreferences[i] = shuffledTracks[i]; // Map tracks to mood effects
}
}
function displayFoods() {
// Display Rocky at the center
rocky.display();
let centerX = rocky.x - rocky.size/8; // Center of the screen (same as Rocky's x)
let centerY = rocky.y - rocky.size/8; // Center of the screen (same as Rocky's y)
let radius = 170; // Distance from the center to where the foods will be displayed
let angleStep = TWO_PI / foods.length; // Angle between each food item
for (let i = 0; i < foods.length; i++) {
let angle = i * angleStep; // Calculate angle for each food item
let foodX = centerX + radius * cos(angle); // X-coordinate based on angle
let foodY = centerY + radius * sin(angle); // Y-coordinate based on angle
image(foodImages[i], foodX - 40, foodY - 40, 110, 110); // Display food images
fill(0);
textAlign(CENTER); // Center the text below each food item
text(foods[i], foodX+20, foodY + 80); // Display food names under the images
}
}
function mousePressed() {
if (gameState === 'start') {
// From start screen to instructions
gameState = 'instructions';
} else if (gameState === 'instructions') {
// From instructions to the actual game
gameState = 'playing';
} else if (gameState === 'gameOver') {
resetGame(); // Reset the game state when game over screen is clicked
gameState = 'start';
}
if (relaxing) {
// Check for mouse clicks on each button
for (let i = 0; i < trackButtons.length; i++) {
if (trackButtons[i].isClicked()) {
playRelaxTrack(i); // Play the selected track and adjust Rocky's mood
}
}
}
if (backButton.isClicked() && !isRelaxing) {
currentFoodScreen = false; // Go back to main screen
playing = false; // Stop playing mode
relaxing = false;
// Clear any existing interval before starting a new one
clearInterval(playInterval);
return; // Exit if back button is clicked
}
if (currentFoodScreen) {
playing = false;
let centerX = rocky.x - rocky.size/8; // Center of the screen (same as Rocky's x)
let centerY = rocky.y - rocky.size/8; // Center of the screen (same as Rocky's y)
let radius = 170; // Distance from the center to where the foods will be displayed
let angleStep = TWO_PI / foods.length; // Angle between each food item
for (let i = 0; i < foods.length; i++) {
let angle = i * angleStep; // Calculate angle for each food item
let foodX = centerX + radius * cos(angle); // X-coordinate based on angle
let foodY = centerY + radius * sin(angle); // Y-coordinate based on angle
if (dist(mouseX, mouseY, foodX, foodY) < 40) { // Use the food's actual position
rocky.feed(foods[i]);
setFoodFeedback(foods[i]); // Set feedback after Rocky is fed
currentFoodScreen = false;
feedCount++; // Increment the feeding count
return; // Exit after selecting food
}
}
} else {
for (let button of buttons) {
if (button.isClicked()) {
if (button.label == 'Feed' && feedCount < maxFeeds) {
currentFoodScreen = true; // Go to food selection
// Clear any existing interval before starting a new one
clearInterval(playInterval);
playing = false;
} else if (button.label == 'Play') {
playing = true; // Start play mode
startPlayMode();
} else if (button.label == 'Relax' && !relaxDisabled) {
relaxing = true;
// Clear any existing interval before starting a new one
clearInterval(playInterval);
playing = false;
startRelaxMode();
}
}
}
}
}
function setFoodFeedback(food) {
let moodEffect = foodPreferences[food];
rocky.mood += moodEffect; // Update Rocky's mood immediately
// Set food feedback based on mood effect
if (moodEffect > 0) {
foodFeedback = `Rocky likes ${food}!`;
} else if (moodEffect < 0) {
foodFeedback = `Rocky doesn't like ${food}!`;
} else {
foodFeedback = `Rocky is okay with ${food}.`;
}
// Clear any previous timeout to avoid overlap
if (foodFeedbackTimeout) {
clearTimeout(foodFeedbackTimeout);
}
// Clear the feedback after 3 seconds
foodFeedbackTimeout = setTimeout(() => {
foodFeedback = "";
}, 3000);
}
// Rocky class
class Rocky {
constructor() {
this.mood = 50;
this.x = windowWidth / 2;
this.y = windowHeight / 2 + 100;
this.size = 100; // The size of Rocky (matches the previous ellipse size)
this.speed = 5; // Speed for moving up and down
}
display() {
// fill(150);
// ellipse(this.x, this.y, 100, 100); // Rocky as an ellipse
if (this.mood >=80){
rockyImage = rockyImageHappy;
}
else if (this.mood >=60){
rockyImage = rockyImageHappyish;
}
else if (this.mood >=40){
rockyImage = rockyImageMeh;
}
else if (this.mood >=20){
rockyImage = rockyImageSadish;
}
else {
rockyImage = rockyImageSad;
}
image(rockyImage, this.x - this.size / 2 - 25, this.y - this.size / 2 , this.size+40, this.size); // Display the image with Rocky's coordinates
}
update() {
if (keyIsDown(UP_ARROW)) {
this.y -= this.speed; // Move up
}
if (keyIsDown(DOWN_ARROW)) {
this.y += this.speed; // Move down
}
// Make sure Rocky stays within the canvas boundaries
this.y = constrain(this.y, 50, windowHeight - 50);
}
feed(food) {
// Mood is updated when feedback is set in setFoodFeedback function
}
}
class MoodMeter {
constructor() {
this.width = 220; // Adjust as needed
this.height = 30; // Adjust as needed
}
display(mood) {
let x = 50;
let y = 50; // Position near the top
let cornerRadius = 10; // Adjust to your preference for rounding
// Background of the mood meter (rounded rectangle)
fill(200); // Light gray color
stroke(0); // Black border
strokeWeight(2);
rect(x, y, this.width, this.height, cornerRadius); // Rounded corners
// Map Rocky's mood to a value between 0 and 1
let moodPercent = map(mood, 0, 100, 0, 1);
// Define colors for the gradient
let redColor = color(255, 0, 0); // Red
let yellowColor = color(255, 165, 0); // Orange/Yellow
let greenColor = color(0, 255, 0); // Green
// Interpolate between red and yellow for the lower half, yellow and green for the upper half
let moodColor;
if (moodPercent < 0.5) {
moodColor = lerpColor(redColor, yellowColor, moodPercent * 2); // From red to yellow
} else {
moodColor = lerpColor(yellowColor, greenColor, (moodPercent - 0.5) * 2); // From yellow to green
}
// Mood level inside the meter
let moodWidth = map(mood, 0, 100, 0, this.width); // Adjust based on mood range
// Display the mood meter with the interpolated color
fill(moodColor);
noStroke();
rect(x+1, y+1, moodWidth-1, this.height-2, cornerRadius); // Rounded rectangle for mood
// Optional: Display the numerical mood value as text inside the bar
noStroke();
fill(0); // Black text
textSize(16);
textAlign(CENTER, CENTER);
text(`Mood: ${mood}`, x + this.width / 2, y + this.height / 2);
}
}
// Button class
class Button {
constructor(label, x, y) {
this.label = label;
this.x = x;
this.y = y;
this.width = 120;
this.height = 70;
}
display() {
fill(feedCount >= maxFeeds && this.label == 'Feed' ? 150 : 200);
image(buttonImage, this.x, this.y, this.width, this.height);
fill(0);
textAlign(CENTER, CENTER);
textSize(28);
text(this.label, this.x + this.width / 2, this.y + this.height / 2);
}
isClicked() {
return mouseX > this.x && mouseX < this.x + this.width &&
mouseY > this.y && mouseY < this.y + this.height;
}
}
// Obstacle class for the play mode
class Obstacle {
constructor() {
this.x = windowWidth;
this.size = random(60,75); // Size of obstacle
// this.y = random(0, windowHeight - this.size); // Random y-position
this.y = rocky.y; // Spawn the obstacle at Rocky's current y position
}
display() {
// fill(255, 0, 0);
// rect(this.x, this.y, this.size, this.size);
image(obstacleImage, this.x, this.y, this.size, this.size);
}
update() {
this.x -= obstacleSpeed; // Move the obstacle to the left
}
offscreen() {
return this.x < -this.size; // Check if the obstacle has moved off-screen
}
hits(rocky) {
let d = dist(this.x, this.y, rocky.x, rocky.y);
return d < this.size / 2 + 50; // Check for collision with Rocky
}
}
class Timer {
constructor(duration) {
this.timeRemaining = duration;
}
display() {
fill(0);
textSize(20);
text("Time: " + this.timeRemaining, windowWidth - 100, 50);
if (frameCount % 60 == 0 && this.timeRemaining > 0) {
this.timeRemaining--;
}
}
isTimeUp() {
return this.timeRemaining <= 0;
}
}
class BackButton {
constructor() {
this.x = 100;
this.y = 100;
this.size = 80;
}
display() {
// Display the back button image at the specified location and size
image(backButtonImage, this.x, this.y, this.size, this.size);
}
isClicked() {
rocky.x = windowWidth / 2;
rocky.y = windowHeight / 2 + 100;
return mouseX > this.x && mouseX < this.x + this.size &&
mouseY > this.y && mouseY < this.y + this.size;// Check if the ellipse is clicked
}
}
function startPlayMode() {
if (gameState !== 'gameOver'){
// Clear any existing interval before starting a new one
clearInterval(playInterval);
// Set an interval to increase Rocky's mood by 2 every 5 seconds while in play mode
playInterval = setInterval(() => {
rocky.mood += 4;
rocky.mood = constrain(rocky.mood, 0, 100); // Constrain mood to a maximum of 100
}, 5000); // Every 5 seconds
}
}
function startRelaxMode() {
relaxing = true;
playing = false;
displayTrackButtons(); // Display buttons for selecting tracks
backButton.display(); // Show back button to return to main screen
}
// Function to display track buttons
function displayTrackButtons() {
// Display each track button
for (let trackButton of trackButtons) {
trackButton.display();
}
}
// Function to play the selected track and show feedback
function playRelaxTrack(trackIndex) {
if (isRelaxing) {
return; // If a track is already playing, prevent any other interaction
}
isRelaxing = true; // Set to true once a track starts playing
selectedTrack = trackIndex;
relaxTracks[trackIndex].play(); // Play the selected track
setTimeout(function() {
relaxTracks[trackIndex].stop(); // Stop the track after 10 seconds
showRelaxFeedback(trackIndex);
relaxing = false; // Return to the main game state
relaxDisabled = true; // Disable the Relax button after it's used
isRelaxing = false;
rocky.x = windowWidth / 2;
rocky.y = windowHeight / 2 + 100;
}, 10000); // Play for 10 seconds
}
// Function to show feedback based on Rocky's preferences
function showRelaxFeedback(trackIndex) {
let moodEffect = musicPreferences[trackIndex]; // Get mood effect for the selected track
rocky.mood += moodEffect; // Adjust Rocky's mood accordingly
// Set relax feedback based on the mood effect of the track
if (moodEffect > 0) {
relaxFeedback = `Rocky likes this track!`;
} else if (moodEffect === 0) {
relaxFeedback = `Rocky is okay with this track.`;
} else {
relaxFeedback = `Rocky doesn't like this track!`;
}
// Clear any previous timeout to avoid overlap
if (relaxFeedbackTimeout) {
clearTimeout(relaxFeedbackTimeout);
}
// Clear the feedback after 3 seconds
relaxFeedbackTimeout = setTimeout(() => {
relaxFeedback = "";
}, 3000);
// Clear any previous timeout to avoid overlap
}
function resetGame() {
// Reset all necessary variables to their initial values
clearInterval(playInterval);
timer.timeRemaining=100; // Reset the timer to the initial value
feedCount = 0; // Reset the feeding count
currentFoodScreen = false; // Reset the food screen state
playing = false; // Reset playing state
relaxing = false; // Reset relaxing state
isRelaxing = false; // Reset relaxing check
relaxDisabled = false; // Reset variable to disable relax state
obstacles = []; // Clear the obstacles array
// Optionally, randomize food preferences again
randomizeFoodPreferences();
randomizeMusicPreferences();
// Set the gameState back to 'start'
// gameState = 'start';
// Ensure feedback message is cleared
foodFeedback = "";
relaxFeedback = "";
}
function keyTyped() {
if (key === 'f' || key ==='F') {
toggleFullscreen();
}
// return false;
}
// Toggle fullscreen state. Must be called in response
// to a user event (i.e. keyboard, mouse click)
function toggleFullscreen() {
let fs = fullscreen(); // Get the current state
fullscreen(!fs); // Flip it!
}