Afra Binjerais – Week 8a reading response

In my reflection, when reading “attractive things work better” I understood how design is more than just about making things work well or look good. It’s about bringing these aspects together to enhance our experiences with technology, as our emotional reactions to a design are as important as its functionality. This broader view makes me appreciate how both practicality and our emotional responses matter in design.

When reading, I stumbled across a certain idea, where I was thinking that the color of a smartphone is a big deal when people decide what to buy. In general, black or white phones might look professional or classic, but a phone in a bright color like blue or red can make one feel excited or bold. These colors can attract customers who want to show their personality through their devices.

Personally, my phone is blue because that’s my favorite color. When I was choosing, the blue phone immediately stood out to me.

This experience shows how crucial emotional aspects, like color preferences, are in design. They can make technology more enjoyable and meaningful for us every day. By considering both function and emotion, design can really enrich our daily technology use, making it more satisfying and personal.

Week 8a: Attractive Things and Margaret Hamilton Reflection

While reading, I noticed that the author was creating a symbiosis between the design’s appearance, utility, and the emotional response associated with it. It was interesting to see how the author defined the balance between these aspects, and I believe it is a healthy way for them to exist. I have always thought that utility should take precedence over design, which I believe is a practical approach. However, the author appears to infer that good effects in design can improve usability and problem solving. Although I believe in the value of good design, here’s another perspective: if we have a problem and are designing a tool to help us solve it, why go the extra mile to make it attractive or visually appealing? While answering this question, I came to the conclusion that an aesthetically pleasing design is the result of properly implementing all of the necessary functionality in the tool.

I think it can be best explained with the modern kitchen designs. Consider the trend of open-plan kitchens that blend effortlessly with living spaces. This design choice is not purely aesthetic; it stems from the functionality of wanting a kitchen that supports not just cooking, but also social interaction and entertainment. The central kitchen island often serves multiple purposes: it is a prep area, a dining table, and a social hub, all in one. Its design—sleek, with clean lines and often featuring visually appealing materials like marble or polished wood—enhances the kitchen’s utility by making it a more inviting space. The aesthetics of the island, from its material to its positioning, are integrated with its function, creating a space that is both beautiful and highly functional. By contemplating this — approach to kitchen design, I mean that aesthetics and utility go hand in hand.

Thinking about the document’s ideas in a broader context, I’m drawn to look into how they relate to digital interfaces and even services. In a society increasingly mediated by screens, the emotional impact of design aesthetics on usability is even more important. I’ve experienced websites and apps whose gorgeous design increased my patience while navigating difficult functionality. This notion is supported by the document’s explanation that ‘Positive affect broadens the thought processes making it more easily distractible,’ which validates my experiences with digital interfaces. The emotional impact of design aesthetics on usability is critical, especially in our increasingly screen-mediated society. However, the document’s emphasis on the universal benefits of good effect in design fails to account for individual and cultural variations in aesthetic tastes. This error makes me think about the worldwide nature of design work nowadays. Products and interfaces developed in one cultural context may not elicit the same emotional responses in another, thereby affecting usability across user groups. This intricacy adds to the difficulty of attaining truly universal design—a goal that appears to be becoming increasingly relevant in our interconnected society.

Now about Margaret Hamilton, I believe her story shows how individual determination and intellectual bravery can redefine the possible, even against the backdrop of societal and professional norms. In a time when the professional landscape marked differently for women, bringing her daughter to work at the MIT lab is a powerful moment. It shows us how she balanced being a mom and working on the Apollo missions at a time when most people didn’t expect women to do such jobs. This story is special because it’s about more than just making software for the moon landing. It’s also about Hamilton showing that women can be both caring mothers and brilliant scientists. She didn’t let the rules of her time stop her from doing great things in technology. It also made me think about how personal life and big achievements can mix together. Hamilton’s story is really inspiring because it shows that, how anyone can break barriers and make a big difference, no matter what others might expect.

USB Sticks, Water Taps and Cars – The balance between Beauty and Usability

Often times we can see beautiful designs which overwhelm us, which don’t really have any practicality or are not usable at all. At the same time we can often see practical and useful designs being ugly and really not pleasing to the human eye. Let me show you some examples:

Disco Truck? What do discos and cement-trucks have in common? Looks cool though.

Sushi Chairs….Okay they have the usability of a chair, I’ll give them that but the design man, it doesn’t add anything.

There are many other examples like this which we can see in our everyday life. A really good one is USB Sticks. I always have a problem with USB sticks, I try to put the stick into the port, doesn’t go in, okay, let’s try the other side, doesn’t go in again, wait what? I try the first side again, IT WORKS, how? magic! Jokes aside, USB sticks are just very weird, at least for me.

A design I really really like is Water Taps with sensors. They just save so much time, are much more hygienic and much better for the environment. Triple the benefit!!

Cars! Cars are a thing I love. As a young person that is transitioning from a teenager to a young man in his twenties, everyday I get more and more interested into cars. Let me show you my favorite design in a car. It offers amazing design and at the same time, it is one of the most aerodynamic cars in the world.

Say hi to the McLaren Speedtail. The speedtail is a perfect example of what happens when we combine beauty and usability (and a lot of money).

All in all, as the heading says, the best combination of beauty and usability (usually when they’re both balanced) will result in something we like to call “Good Design”.

 

Midterm Project – “Wanderer” – Redha Al Hammad

For my midterm project I created a simple interactive piece which focuses on the importance of mindfulness, slowing down and taking a break. The piece features a character that can be controlled by the user to walk left and right with the arrow keys. By walking (or ‘wandering’), the user progresses through a series of different scenic environments ranging from grass fields to mountain ranges. The user is then prompted to ‘think’ at given points (improper functionality explained later) before finally returning home. I have sequenced the images (sourced from craftpix) to convey the passage of time with the exception of the last image which I edited in Lightroom to create a ‘blue hour’ color palette. The link to the sketch is below. For the best experience, open your window in full screen:

https://editor.p5js.org/redhaalhammad/sketches/H_1B-Ts-1

Edits for the final frame

In terms of technical application, I am happy that I was able to incorporate an intuitive transition from background to background for the user through my updateBackground functions. I found it challenging to wrap my head around how exactly to include this functionally. An issue that I had early on was that the background would always change to the next image regardless of whether the user walked off-screen to the left or right. I was able to resolve this by adding an else if statement and simply subtracting 1 rather than adding 1. I feel that doing so helped create an immersive environment for the user as it more accurately reflected the development of the character’s ‘wandering’ journey. The source code for the background transitions is included below:

function updateBackground() {
  // Cycle through the backgrounds
  currentBackground = (currentBackground % 14) + 1;
}
function updateBackground2() {
  // Cycle through the backgrounds
  currentBackground = (currentBackground % 14) - 1;
}

Building upon this, I feel that another strength of this project is its continuity which applies to both aesthetic and narrative. While I initially wanted a basic silhouette sprite sheet to make the experience more universal and relatable, the pixelated design style of the character matches with the similar style of the background images. Additionally, the visual aesthetic of the background images is consistent despite being sourced from different asset folders on craftpix. In terms of narrative, I was conscious, as mentioned previously, of sequencing the images to reflect both the passage of time but also a sense of space. While I do not repeat images (except for the scene of the character’s home), I consciously chose to include scenes featuring mountains as the character nears returning home in correspondence to the scene of the mountain which appears at the beginning of the journey. The intention behind this was to subtly present to the user that the journey is nearing its end as (based on the sequencing) they can infer that the character’s home is located near mountains.

Unfortunately, I have several issues in this project which I repeatedly tried to resolve but ultimately could not figure out. The first, which will be apparent to users unless they engage with the piece in the p5 editor while the window is in full screen, is that I could not properly situate my character and text into a fixed position relative to the backgrounds. This is likely due to the fact that I used ‘innerWidth’ and ‘innerHeight’ for the canvas dimensions with background images that do not take up this entire space. I tried to place the y-position of my character relative to the height of the images (using summer1 as a reference) but that did not accomplish the adaptive positioning that I wanted it to.

Another technical shortcoming was my inability to successfully add a simple audio track to help create a sense of atmosphere. Despite being a straightforward incorporation which I am familiar with, I was unable to successfully have an audio track play once. When the audio did play, it would be called to play every frame and eventually cause the sketch to crash. I looked to the examples provided in the class notes, researched the references on the p5 website and asked more experienced colleagues but could still not figure out how to do it.

Finally, an issue that I am deeply upset about was the lack of functionality in the ‘press ENTER to think’ prompt. To begin with, I was able to get the ‘think’ prompt to work momentarily. However, when it was working, my sprite sheet was not entirely functional as it would move across the screen without being animated. I suspect that the ‘keyPressed’/’keyCode’ functions were interfering with one another but I could figure out how to resolve it. I am especially upset that this element did not work as I feel that it would have elevated my project on many levels. First, it would have simply added another level of interactivity besides the basic movement, thus making the piece more engaging. Second, I feel that it very succinctly relates to the intention behind this work by prompting the user to physically stop progressing and to focus on the digital scenery in front of them. Moreover, the text which that appeared on-screen when this element was functional (still in the source code) added a much-needed personal touch and sense of character to the work which I feel is lacking currently.

Midterm – Sara Al Mehairi

Concept oVERVIEW

Amazon
Face in a Book
Teacher Superstore

 

 

 

 

 

I couldn’t recall the exact moment of picking up a copy of “Diary of a Wimpy Kid,” but it has simply always been a part of my childhood. First and foremost, allow me to introduce the author of this masterpiece, Jeff Kinney. Kinney’s unique style breaks the norms of traditional literature/books. Unlike typical novels, his books use a diary format with personal writing and illustrations. The use of lined paper, childlike font, and unconventional chapter structure sets his work apart and thats what makes it so memorable. (source)

That being said, Inspired by the “Diary of a Wimpy Kid” books, I wanted my project to bring back those nostalgic feelings, with simple drawings and everything in black & white. My goal was to involve the user in the diary and make the project interactive, allowing them to feel like they were part of the story. So, I titled it “Diary of an NYUAD Kid” to grasp that mix of memories & relatable experiences. In creating my project, I initially planned to develop four games inspired by various elements of the “Diary of a Wimpy Kid” series, with an NYUAD twist. However, I narrowed down my focus to three main games: “Elevator Rush,” “Cheese Touch,” and “My Diary.”

Game Details

1. Menu

Despite my initial attempts, I encountered challenges in embedding the menu screen above. At some point, I managed to make it work with a few bugs, but ultimately, it didn’t function as intended (discussed in detail in another section below). Further, the design of the main menu page draws inspiration from the cover page of the “Diary of a Wimpy Kid” books. Clicking the “i” button reveals instructions for each game.

2. Elevator Rush

Elevator Rush” is a game born from the frustration of waiting for elevators, especially during busy times like the 10-minute rush between classes in the C2 Building. You know, maybe the delays are intentional to nudge students towards taking the stairs, was this their plan all along? In the game, you control the elevator using the UP & DOWN keys, hurrying to pick up students so they’re not late for class. Every time a passenger gets picked up, a sound plays to signify success. The background music is the classic “elevator music” sourced from YouTube. With every student you pick up, you earn one point, but if you miss a student, you lose a point. To add difficulty, students appear and disappear quickly, and they are NOT patient. The game ends when the time runs out or if your score drops to -3. Upon game over, a screen pops up with the option to click to restart.

3. Cheese Touch

In the “Cheese Touch” game, inspired by the popular game played at Westmore Middle School in “Diary of a Wimpy Kid,” players aim to gather as many candies as possible while avoiding the dreaded Cheese Touch. In the original story, having the Cheese Touch makes someone a social outcast until they pass it on to someone else by touching them (source). Using the LEFT and RIGHT keys, the player must navigate the area while trying to gather candies and avoid the Cheese Touch. Additionally, when a player successfully collects candy, a cheerful audio plays to signify their success (+1 point). Conversely, if a player encounters the Cheese Touch, a sticky audio plays to indicate their loss (-1 point). The game continues until the time runs out or if a player’s score drops to -3, indicating they’ve had too many encounters with the Cheese Touch. Upon game over, a screen pops up with the option to click to restart.

4. My Diary

In the final somewhat game, titled “My Diary,” I wanted to capture the idea of doodling and scribbling found in “Diary of a Wimpy Kid.” This option allows users to paint on a canvas using colors inspired by the books. They can also change the brush size, erase the canvas, and save their artwork as a PNG file. To enhance the experience, each button plays a sound when clicked. Moreover, the save button triggers a different audio to signify that the image has been saved successfully. To further simulate the feel of real paper, I incorporated the sound of scribbles each time the user draws on the canvas. The main idea behind this “game” was to use audio cues to create a realistic experience for the users. Below are some of the images I have saved during the debugging process:

Visuals, Audios, & Resources

1. Menu

 

 

 

 

 

 

Background: by me using Procreate
Penguin Logo source

2. Elevator Rush

 

 

 

 

 

 

 

 

 

 

Background music source
Remaining audio source
Background: by me using Procreate, inspired by NYUAD
Characters: Diary of a Wimpy Kid

Chatgpt debugging: draw function (passenger spawn interval, passenger spawn, time) & Passenger class

3. Cheese Touch

 

 

 

 

 

 

 

 

Audio source
Cheese image source
Candy image source
Face image source
Game over image:
by me using Procreate

Chatgpt debugging: obstacle & candy detection, draw function, audio errors (replay/pause)

4. My Diary

 

 

 

 

 

 

All audio source
Background: by me using Procreate
Colors: inspired by the “Diary of a Wimpy Kid” books

Chatgpt debugging: button effect & draw function

Challenges & Areas of improvement

One of my biggest challenges was attempting to merge all three games into a single JavaScript file, given that I have worked on them separately.  Despite my best attempts, the complexities of combining multiple game modes within one file led to organizational/functional issues that remained unresolved. At some point it functioned with some issues, but my attempts to fix these problems led to further complications.

function draw() {
  if (scene == "main") {
    drawMenu();
  } else if (scene == "game 1") {
    drawGame1();
  } else if (scene == "game 2") {
    drawGame2();
  } else if (scene == "game 3") {
    drawGame3();
  }
}

For “Elevator Rush,” I encountered several challenges, particularly in managing the spawning of passengers at specific intervals and ensuring they appeared on random floors, excluding the floor where the elevator was located (it did not look visually appealing, as if the passenger was already in the elevator, caused some quick flashes). Implementing this required generating random floor numbers while avoiding duplication with the elevator’s current floor. Additionally, I had to adjust the spawn intervals to  balance between keeping the game challenging and preventing overwhelming/underwhelming spawns.

//spawn passengers
  if (millis() - lastSpawnTime > spawnInterval) {
    let floorToSpawn = floor(random(4));
    if (floorToSpawn !== currentFloor) {
      passengers.push(new Passenger(floorToSpawn));
      lastSpawnTime = millis();
    }
  }

Another challenge came about when measuring the game screen and drawing assets in Procreate. Despite using Procreate’s grid assist feature, ensuring the correct proportions for each floor was pretty tricky. Moreover, this caused the elevator to appear either too small or too large on certain floors, hence I adjusted the elevator’s dimensions until it fit within each floor’s layout.

//draw elevator
    let elevatorWidth = 55; 
    let elevatorHeight = floorHeight - 2; 
    let elevatorX = width / 2; 
    image(elevatorImage, elevatorX - elevatorWidth / 2, elevatorY - elevatorHeight / 2, elevatorWidth, elevatorHeight);

As for “Cheese Touch,” one of the challenges was precisely detecting the cheese touch and candy, which relied heavily on precise x and y coordinate calculations, leading to many trials & errors to create accurate collision detection.

collects(candy) {
  return (
    this.x < candy.x + candy.w &&
    this.x + this.w > candy.x &&
    this.y < candy.y + candy.h &&
    this.y + this.h > candy.y
  );
}

hits(obstacle) {
  return (
    this.x < obstacle.x + obstacle.w &&
    this.x + this.w > obstacle.x &&
    this.y < obstacle.y + obstacle.h &&
    this.y + this.h > obstacle.y
  );
}

Additionally, initial attempts to use a notepad background encountered a persistent issue where the screen froze upon game restart attempts, despite multiple efforts to solve the problem through redraws. This issue likely stemmed from an error in managing the background image or memory management concerns. Consequently, I resorted to using a white background instead.

 

 

 

 

 

 

Overall, I faced many challenges and made some mistakes along the way. Looking back, I realize there are ways I could have done better. For example, I worked on each game mode separately, but when I tried to put them all together, they didn’t work well. Perhaps it wasn’t the best idea to start working in separate JavaScript files. In terms of the games, I also could have added extra obstacles to make each game more challenging (due to the simplicity, I resorted to creating more than one game.). For instance, in the “Cheese Touch” game, I could have added bonus elements that had power-ups. And in all the games, I could have included a leaderboard, using CSV files, to track score.

Conclusion

All in all, developing this project based on “Diary of a Wimpy Kid” presented a lot of challenges…integrating the menu screen with other screens was difficult, and attempts to merge all games into one JavaScript file were unfortunately unsuccessful. However, I managed to incorporate at least one shape, one image, one sound, on-screen text, and Object-Oriented Programming into the project. The menu screen initially provided instructions when the “i” button was clicked, but there were issues directing to the games (yet, each game did have a start & restart option). After completing each game experience, a restart option was available by clicking the screen without restarting the sketch, except for the last game, where users needed to click “erase.” Safe to say that I’m proud of my project, the visuals, and the menu design for being accurate & fulfilling my vision, especially in the elevator game, which initially seemed ambitious.

Rama’s Midterm: Tetris

The Idea

For my Midterm I had my take on the Tetris game which I’ve been a fan of for years. I want to maintain the arcade/video game feel to it so I kept that in mind while creating my game. It consists of various components including shapes, grids, timers, and user input handling. The game aims to control the falling tetrominoes, rotating and moving them to form complete horizontal lines to clear rows. Once 5 rows have been cleared, the game levels up and the tetrominoes fall faster giving the player less time to find a good fit to clear the rows.

How It Works and Highlights

The project leverages object-oriented programming principles to organize code into manageable classes such as Tetris, Timer, and T-Grid. This modular approach enhances code readability and maintainability. The game mechanics are well-implemented, with smooth tetromino movement, collision detection, and row-clearing functionality. The user interface is intuitive, providing clear visual feedback through colorful shapes and text. The inclusion of background music and sound effects enhances the overall gaming experience. I created the background image and the first-page using elements on Canva.

displayGrid(pg, x, y, w, h, pallette) {
    var nx = this.tGrid.nx;
    var ny = this.tGrid.ny;
    var cw = w / nx;
    var ch = h / ny;
    
    // Render background
    for (var gy = 0; gy < ny; gy++) {
        for (var gx = 0; gx < nx; gx++) {
            var cx = x + gx * cw;
            var cy = y + gy * ch;
            pg.stroke(210);
            if ((gx & 1) == 1) {
                pg.fill(250);
            } else {
                pg.fill(240);
            }
            pg.rect(cx, cy, cw, ch);
        }
    }
    
    // Render foreground (tetrominoes)
    for (var gy = 0; gy < ny; gy++) {
        for (var gx = 0; gx < nx; gx++) {
            var cx = x + gx * cw;
            var cy = y + gy * ch;
            var valGrid = this.tGrid.getGridVal(gx, gy);
            if (valGrid > 0) {
                pg.stroke(0);
                var rgb = pallette[valGrid % pallette.length];
                pg.fill(rgb[0], rgb[1], rgb[2]);
                pg.rect(cx, cy, cw, ch);
            }
        }
    }
    
    // Render active tetromino shape
    var ks = this.tGrid.shapeSize;
    var kr = ceil(this.tGrid.shapeSize / 2.0);
    for (var ky = 0; ky < ks; ky++) {
        for (var kx = 0; kx < ks; kx++) {
            var gx = this.tGrid.sx + kx - kr;
            var gy = this.tGrid.sy + ky - kr;
            var cx = x + gx * cw;
            var cy = y + gy * ch;
            var valShape = this.tGrid.getShapeVal(kx, ky);
            if (valShape != 0) {
                pg.stroke(0);
                var rgb = pallette[valShape % pallette.length];
                pg.fill(rgb[0], rgb[1], rgb[2]);
                pg.rect(cx, cy, cw, ch);
            }
        }
    }
}

One really cool part of the code is how it draws the game grid. It splits the screen into smaller squares to represent each cell of the grid. Then, it fills these squares with colors to show the background, the falling shapes, and the shapes that have already landed. It does this by going through each cell of the grid and deciding what color it should be based on the game’s state. This method makes sure everything looks neat and organized on the screen, giving players a clear view of the game.

Areas for Improvement and Challenges

One area for improvement could be enhancing the visual appeal of the game by adding animations for tetromino movements and row clearing. Additionally, implementing more advanced gameplay features such as different game modes, power-ups, or multiplayer functionality could increase player engagement. Some challenges were adding a sound effect once every tetromino lands but I had several issues with it, also I was not able to get the tetromino count to stop once the game was over.

Design Inspiration

I took inspiration from EA’s Tetris mobile app game, here’s how it looks:

And here’s mine:

Credits

Sound Track: https://www.youtube.com/watch?v=NmCCQxVBfyM

Main Menu page: https://www.canva.com/

Code :https://www.youtube.com/@easywebsify

Additional Code Assistance: Chat GPT.

Final Sketch

 

Midterm Project – Shaikha AlKaabi

 

When I first started working on my midterm project, I really wanted to make a game about planting flowers. It sounded fun and I thought it would be something different. But as I kept working on it and thinking more about how it would work, it started to feel less like a game and more like a simulator for planting. It wasn’t as fun as I had hoped it would be, and that was really important to me. So, I decided to change it.

I switched my idea to “Back and Forth.” At first, I thought about making a volleyball game. It seemed like a good idea because it was about moving back and forth, which is what I wanted. But the more I thought about it, the more I realized that making it feel realistic and fun at the same time was going to be really hard. So, I started looking for something similar but a bit simpler, and that’s when I landed on a ping-pong game. Ping-pong still had that back and forth action which I liked, but it felt more doable and still really fun. That’s how I ended up with the idea for my project.


Challenges:

The tricky  part was figuring out how to make the ball start from the center of the screen. But then I remembered the width /2 and height/2 that we used to center things and I used that to position the ball at the center whenever it went out of the screen:

reset() { // Reset the ball’s position and speed this.x = width / 2; this.y = height / 2; this.xSpeed = random([-5, 5]); // Increased speed this.ySpeed = random([-5, 5]); // Increased speed }

Overall, I’m quite happy with my game.

let leftPaddle;
let rightPaddle;
let ball;
let leftScore = 0;
let rightScore = 0;
let gameState = "start";
let tableColor;
let startScreenImage;
let gameOverSound;
let paddleHitSound;

function preload() {
  startScreenImage = loadImage('background-image.jpg');
  gameOverSound = loadSound('game over.mp3');
  paddleHitSound = loadSound('paddle.mp3');
}

function setup() {
  createCanvas(800, 400);
  textAlign(CENTER, CENTER);
  textSize(40);
  leftPaddle = new Paddle(true);
  rightPaddle = new Paddle(false);
  ball = new Ball();
  tableColor = color(138, 43, 226);
}

function draw() {
  background(tableColor);
  drawNet();
  if (gameState === "start") {
    showStartScreen();
  } else if (gameState === "play") {
    leftPaddle.show();
    rightPaddle.show();
    ball.show();
    leftPaddle.update();
    rightPaddle.update();
    ball.update();
    ball.checkPaddleCollision(leftPaddle);
    ball.checkPaddleCollision(rightPaddle);
    ball.checkScore();
    drawScore();
  } else if (gameState === "gameover") {
    showGameOverScreen();
  }
}

function keyPressed() {
  // Start the game when ENTER is pressed
  if (keyCode === ENTER && gameState === "start") {
    gameState = "play";
  }
  // Reset the game when ENTER is pressed after game over
  else if (keyCode === ENTER && gameState === "gameover") {
    gameState = "start";
    leftScore = 0;
    rightScore = 0;
    ball.reset();
  }

  // Control the right paddle with arrow keys
  if (keyCode === UP_ARROW) {
    rightPaddle.ySpeed = -5;
  } else if (keyCode === DOWN_ARROW) {
    rightPaddle.ySpeed = 5;
  }

  // Control the left paddle with 'W' and 'S' keys
  if (key === 'w' || key === 'W') {
    leftPaddle.ySpeed = -5;
  } else if (key === 's' || key === 'S') {
    leftPaddle.ySpeed = 5;
  }
}

function keyReleased() {
  // Stop the left paddle when 'W' or 'S' key is released
  if (key === 'w' || key === 'W' || key === 's' || key === 'S') {
    leftPaddle.ySpeed = 0;
  }

  // Stop the right paddle when arrow keys are released
  if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
    rightPaddle.ySpeed = 0;
  }
}


function keyReleased() {
  // Stop the left paddle when 'W' or 'S' key is released
  if (key === 'W' || key === 'S') {
    leftPaddle.ySpeed = 0;
  }

  // Stop the right paddle when arrow keys are released
  if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
    rightPaddle.ySpeed = 0;
  }
}

function drawNet() {
  // Draw the net in the middle of the table
  stroke(255);
  strokeWeight(2);
  for (let i = 0; i < height; i += 20) {
    line(width / 2, i, width / 2, i + 10);
  }
}

function drawScore() {
  // Display the scores below the player names with text indicating it is the score
  fill(255);
  textSize(20);
  textFont("Courier New");
  textAlign(CENTER, CENTER);
  
  // Player 1 score
  stroke(0)
  text("Player 1", width / 4, 90);
  text("Score: " + leftScore, width / 4, 110);

  // Player 2 score
  stroke(0)
  text("Player 2", (3 * width) / 4, 90);
  text("Score: " + rightScore, (3 * width) / 4, 110);
}


function showStartScreen() {
  // Display the start screen with instructions
  image(startScreenImage, 0, 0, width, height);
  fill(138, 43, 226);
  stroke(255); // Outline color
  strokeWeight(2); // Outline weight
  textFont("Courier New");
  textStyle(BOLD);
  textSize(40);
  text("Back and Forth", width / 2, height / 2 - 100);
  textSize(20);
  text("Press ENTER to Start", width / 2, height / 2 + 50);
  textSize(15);
  text("Press 'W' to move the left paddle up and 'S' to move it down", width / 2, height / 2 + 125);
  textSize(15);
  text("Press Upward-Arrow to move the right paddle up and Down-Arrow to move it down", width / 2, height / 2 + 150);
}

function showGameOverScreen() {
  // Display the game over screen with the winning player's name
  let winner = leftScore > rightScore ? "Player 1" : "Player 2";
  fill(255);
  stroke(0); // Outline color
  strokeWeight(5); // Outline weight
  textFont("Courier New");
  textSize(40);
  text(winner + " Wins!", width / 2, height / 2 - 40);
  textSize(20);
  text("Press ENTER to Play Again", width / 2, height / 2 + 40);
}

class Paddle {
  constructor(isLeft) {
    this.w = 10;
    this.h = 80;
    this.y = height / 2 - this.h / 2;
    if (isLeft) {
      this.x = 20;
    } else {
      this.x = width - 30;
    }
    this.ySpeed = 0;
  }

  show() {
    // Draw the paddle
    fill(255);
    rect(this.x, this.y, this.w, this.h);
  }

  update() {
    // Update the paddle's position based on key inputs
    this.y += this.ySpeed;
    this.y = constrain(this.y, 0, height - this.h);
  }
}

class Ball {
  constructor() {
    this.reset();
  }

  reset() {
    // Reset the ball's position and speed
    this.x = width / 2;
    this.y = height / 2;
    this.xSpeed = random([-5, 5]); // Increased speed
    this.ySpeed = random([-5, 5]); // Increased speed
  }

  show() {
    // Draw the ball
    fill(255);
    ellipse(this.x, this.y, 10, 10);
  }

  update() {
    // Update the ball's position
    this.x += this.xSpeed;
    this.y += this.ySpeed;
    if (this.y < 0 || this.y > height) {
      this.ySpeed *= -1;
    }
  }

  checkPaddleCollision(paddle) {
    // Check for collision with paddles and change direction
    if (this.x - 5 < paddle.x + paddle.w && this.x + 5 > paddle.x && this.y - 5 < paddle.y + paddle.h && this.y + 5 > paddle.y) {
      this.xSpeed *= -1;
      paddleHitSound.play();
    }
  }

  checkScore() {
    // Check for scoring and end game condition
    if (this.x < 0) {
      rightScore++;
      this.reset();
      gameOverSound.play();
    } else if (this.x > width) {
      leftScore++;
      this.reset();
      gameOverSound.play();
    }
    if (leftScore >= 5 || rightScore >= 5) {
      gameState = "gameover";
    }
  }
}

 

Week 6: Midterm Project – Save the Butterfly

Concept 

As I shared in last week’s blog post, I wanted my project to be a gamified story. I also wanted to center it around a butterfly, an extension of a theme I have been committed to for the past few weeks. Additionally, the narrative created would convey my own desire to reunite with my family – a goal that I hope to achieve eventually in my lifetime. This could be seen in the final scene unlocked if the player passes the levels successfully. The butterfly returns to her family of four, which is the number of members in my own family. The storyline and flow of the game go like this:

    1. A butterfly finds herself lost in the city on a rainy day. She flutters through the window of the main player’s room.
    2. the player is prompted to help the butterfly find her way back to her home and family, going through game levels in the city and forest and avoiding (jumping over) obstacles to preserve their health. Here the player has a chance to replenish their health by collecting potions.
    3. If the player manages to successfully complete the mission, they unlock the final scene, in which the butterfly is finally reunited with her family.
    4. If the player loses, they are prompted to restart the game.

In the making of this, I emphasized the animations for an increased focus on the narrative component over the game one. I spent a lot of time playing with different parameters and finding ways to control time and the movement of sprites in response to changes in their environment. The storyboarding I had done last week greatly aided in visualizing how I wanted the animations to eventually look.

Implementation and Parts I am Most Proud of

In terms of execution, I implemented everything as a class based on the rough UML structure I sketched out in my last blog. The main Gameclass had methods for each level and its attributes were objects instantiated from the Player, Butterfly, EnemyPotion, and HealthBar classes. Certain classes were also abstracted from parent classes using inheritance (such as the Playerclass inheriting from a parent Sprite class that has basic attributes and methods shared between all its child classes). Each level/scene is separated by overlays, where the user is prompted to click anywhere on the screen to continue to the next stage of the experience. In terms of assets, all sounds, images, and fonts were sourced from the following open-source community/free platforms:

  1. https://freesound.org/
  2. https://opengameart.org/
  3. https://www.free-stock-music.com/
  4. https://www.dafont.com/

For the mechanics of the game, the Player sprite is placed at the lower left corner of the screen. Its main movement, jumping, is only triggered when the player presses the Space bar. Jumping occurs by setting the vertical velocity to a certain jump power attribute. As the player falls down, the player’s velocity is incremented by an acceleration due to gravity attribute. The player also has the ability to double jump once while in air, which comes in handy if a flock of enemies is headed its way. In terms of the collision detection mechanism, a collision occurs when the distance between the center of the player and that of an enemy object is less than the sum of their respective radii (minus a certain amount to account for the free pixels in the sprite images). Below is the code for the collision detection mechanism, which is a Player class method:

  detectCollision(obj, offset=30) {
    if (!obj.collided) {
      // get distance between the center of the character and that of the enemy object
      let objHeight = obj.h;
      let objWidth = obj.w;
      let playerWidth = this.w;
      let playerHeight = this.h;
      
      // update height and width based on resized parameters if the player/object was resized 
      if (obj.resize) {
        objHeight = obj.resize_y;
        objWidth = obj.resize_x;
      }

      if (this.resize) {
        playerWidth = this.resize_x;
        playerHeight = this.resize_y;
      }
      let centerX = this.x + playerWidth / 2;
      let centerY = this.y + playerHeight / 2;

      let d = dist(
        centerX,
        centerY,
        obj.x + objWidth / 2,
        obj.y + objHeight / 2
      );
      
      // collision detected
      // distance is less than the sum of objects' radii
      // minus a distance to account for free pixels in the sprite images

      if (d < playerWidth / 2 + objWidth / 2 - offset) {
        if (!obj.potion) { // lose health if the object is an enemy
          loseHealth.play();
          this.currHealth -= obj.damage; 
          
        } else if (obj.potion && this.currHealth < game.healthBar.maxHealth) { // regain health if the object is a potion 
          gainHealth.play();
          this.currHealth += 1;
        }
        obj.collided = true; // set object collided to true
        return true; // return true if collision is detected 
      }
    } else {
      return false; // return false if collision is not detected 
    }
  }
}

An implementation highlight that I think is worth noting is the use of the smoothing algorithm in simulating the butterfly’s movement in the first scene as she makes her way from the window to the desk. This was implemented as part of the Butterfly class.

move(targetX, targetY) {
   // move butterfly toward destination -- smoothing algorithm
   if (this.moving) {
     this.x += (targetX - this.x) * 0.009;
     this.y += (targetY - this.y) * 0.007;
   }

There are quite a few things that I am proud of in the implementation of this project. The first is the emotional feel of the overall experience. I made sure that the combination of animations created, the sounds chosen, the font, and the color palettes – going from night to day and from city to nature – created a wholesome backdrop to the game engulfed within. I also am proud that I made sure to bring to life the storyboard from last week’s blog. Finally, more than anything, I loved working on the animated start and final scenes (code shown below) and how the flow from animation to instructions to game and then to animation again (with transition overlays in between) turned out to be quite seamless.

  firstScene() {
    // show background of the scene 
    image(room[this.firstSceneAnimationStep], 0, 0, width, height);
    // change the background based on frameCount to show animated changes in the player's room
    if (frameCount % 7 == 0) {
      this.firstSceneAnimationStep = (this.firstSceneAnimationStep + 1) % 6;
    }
    // show player
    this.player.show();
    // show butterfly, passing in rotation paramaters 
    this.butterfly.show(100, 170);
    
    // player faces the butterfly once it is 40 pixels from the point (100, 170)
    if (dist(this.butterfly.x, this.butterfly.y, 100, 170) < 40) {
      this.player.dir = 1; 
      // move player toward butterfly once it lands on the desk
      if (
        dist(this.player.x, this.player.y, this.butterfly.x, this.butterfly.y) >
        50
      )
        this.player.x -= 0.6;
      else { // once the player is close to the butterfly, display instructions overlay 
        this.gameMode = 1;
        let text = `This poor butterfly seems to be far away from home! 
You have to help her find her way back to her family!

The first step on your journey is to go through the city. 
Beware the obstacles on your way. 

Press the Space bar to jump. 
Collect potions to replenish your health. 

Click anywhere on the screen if you are 
ready to embark on the journey!`;
        twinkleSound.play(); // play twinkle sound
        this.overlay(text);
      }
    }
  }

  finalScene() {
    // display the flower field background
    image(flowerField, 0, 0, width, height);
    this.player.dir = 3; // change direction so that the player' front side is facing the flower field
    this.levelButterfly.dir = 3;

    // resize the butterfly and player to show advancing movement 
    if (frameCount % 10 == 0) {
      this.player.resize_x -= 4;
      this.player.resize_y = this.player.resize_x / 0.75;
    }
    if (frameCount % 15 == 0) {
      this.levelButterfly.resize_x -= 1.5;
      this.levelButterfly.resize_y = this.levelButterfly.resize_x * 2;
    }


    this.resizeObject(this.player);
    this.resizeObject(this.levelButterfly);
    
    // show background butterflies 
    for (let i = 0; i < 4; i++) {
      this.butterflyFamily[i].show();
    }
    
    // stop the animation once the player's y position is less than 
    // 255 pixels 
    if (this.player.y <= 225) {
      this.player.moving = false;
      this.levelButterfly.moving = false;
      // change into overlay, prompting the player to restart the game
      this.gameMode = 5;
      let text = `Click anywhere to restart the game!`;
      twinkleSound.play();
      this.overlay(text);
      noLoop();
    }
    
    // move player and butterfly diagonally across the screen to move 
    // upward through the field 
    this.player.moveDiagonally();
    this.levelButterfly.moveDiagonally();
  }
Challenges Encountered and Proposed Improvements

One of the challenges I encountered was during the implementation of the final scene animation, where the main player and the butterfly had to be iteratively resized to create the animation of moving into the distance. I found that using the resize() method consecutively blurred the images and I, thus, had to find another way to resize them. After some googling, I found a way to resize the image by creating a resized image object and copying the pixels into the resized image as a way to avoid calling the resize() method:

resizeObject(obj) {
  // scale with copy -- 
https://stackoverflow.com/questions/72368646/images-blur-with-resize-with-p5-js
  
  // create an image object with the resized parameters
  let resizedImg = createImage(int(obj.resize_x), int(obj.resize_y));
  
  // get the image to resize from the object's sprites array 
  let srcImg = obj.sprites[obj.dir][obj.step];
  
  // copy the pixels from the source image to the resized image 
  resizedImg.copy(
    srcImg,
    0,
    0,
    srcImg.width,
    srcImg.height,
    0,
    0,
    obj.resize_x,
    obj.resize_y
  );
  
  // rotate object if needed and display the resized image
  if (obj.rotated) {
    push();
    translate(obj.x, obj.y);
    rotate(radians(obj.rotationAngle));
    image(resizedImg, 0, 0);
    pop();
  } else { 
    image(resizedImg, obj.x, obj.y);
  }
}

Another challenge was the length of the Enemy arrays created in the constructor() of the game class for each level as it modulated the duration of each level. The more enemies there are in a level, the longer its duration, as the condition for termination was when a particular level Enemy array became empty. However, I found that the more enemies there were in a level, the more slow and laggy the movements became, possibly due to the number of objects that had to be drawn on the screen. I attempted to fix this by ensuring that objects are only drawn when they are within the screen bounds as their locations are initialized randomly off-screen. While this helped a little, the problem remained. So a future improvement could be to look into this further and perhaps choose lighter/smaller sprites to display or have a different initialization mechanism.

Additionally, here are a few other ideas to elevate the current version a little more:

  • Add some more levels, perhaps in between the city and the forest (e.g. a suburban in-between area/ or a highway).
  • Add different types of potions with varying degrees of health replenishment (the stronger the potion, the rarer it is). This should be accompanied by an increase in the difficulty of the game, e.g. more enemy sprites, faster enemy sprite movements, or an increase in the damage attribute of certain sprites.
  • Add some feedback, such as a jitter, when a collision occurs with an enemy object. An extension of this would be the possibility of annihilating an enemy if the Player sprite jumps on its head (the same way Gombas are annihilated in Super Mario).
Final Sketch

Midterm Project Presentation/Documentation – Jihad Jammal

Concept:

Taking a fresh and creative approach to the beloved Snake game format, my concept introduces players to a comically troublesome dog, embarking on a homework-eating spree. This idea reinvents the classic game mechanic, where instead of a snake that grows with each item consumed, we have a dog that enlarges with every piece of homework it swallows. This creative twist not only injects a dose of humor into the gameplay but also layers in strategic depth and a ticking clock element, with a 30-second time limit creating a sense of urgency.

The concept of this game particularly appeals to me due to my personal fascination with those almost mythical stories we’ve all heard growing up—tales of pets, especially dogs, eating homework and becoming the convenient scapegoat for undone assignments. There’s something universally relatable and humorously nostalgic about the idea, regardless of how fanciful these excuses might have seemed. I was inspired to bring these whimsical narratives to life through gameplay, transforming an age-old excuse into an interactive and entertaining experience.

By integrating this playful storyline with the mechanics of the growing dog within a limited timeframe, I aim to capture not just the essence of these stories but also their inherent humor and the frantic, often comical, attempts to salvage what’s left of the homework before it’s too late. It’s a nod to those shared experiences, a blend of reality and exaggeration, that many of us can chuckle at in hindsight.

Embed sketch:

Include code snippets and one or more images

 

function draw() {
    switch (gameState) {
        case "start":
            drawStartScreen();
            break;
        case "play":
        // Start the looped sound if it's not already playing and the game state just switched to "play"
      if (!gameLoopSound.isPlaying()) {
        gameLoopSound.loop();
        gameLoopSound.setVolume(0.5); // Set volume to 50%
      }
            drawPlayScreen();
            // Calculate time left here
            let timePassed = (millis() - timerStartTime) / 1000;
            let timeLeft = max(30 - timePassed, 0);
            
            // Display the timer
            fill(0);
            noStroke();
            textSize(16);
            textAlign(RIGHT, BOTTOM);
            text("Time left: " + timeLeft.toFixed(1), width - 10, height - 10);

            if (timeLeft <= 0) {
                gameState = "gameover";
              if (gameLoopSound.isPlaying()) {
          gameLoopSound.stop();
        }
            }

            // Check if the dog collides with the square and handle scoring
            checkCollisionsAndScore();

            // Display the current score
            displayScore();

            // Handle player movement
            handleMovement();
            break;
        case "instructions":
            drawInstructionsScreen();
            break;
        case "credits":
            drawCreditsScreen();
            break;
        case "gameover":
            drawGameOverScreen();
            break;
    }
}

 

 

Describe how your project works and what parts you’re proud of (e.g. good technical decisions, good game design):

My approach to this project was focused on crafting an engaging and enjoyable game loop that balanced challenge and playability. I was acutely aware that the dog’s speed couldn’t start off too fast, as this would alienate new players, nor could it become too slow as the timer neared its end, as this would sap the excitement from the gameplay. Achieving this balance was critical in keeping players engaged and encouraging them to improve over time. Additionally, I aimed to create a cohesive and immersive game environment. From the main menu’s view of the backyard to the transition into a bird’s-eye view of the entire backyard upon starting the game, players are drawn into a world that feels both expansive and detailed. This seamless movement into the game’s space was designed to make players feel as if they’ve stepped into the backyard themselves, observing the chaos from above.

Furthermore, I’m particularly proud of how I tackled the game’s technical and design challenges. After some experimentation, I managed to modularize many of the game’s features, breaking them down into separate functions that could be easily managed and updated. This was a significant departure from my initial approach, which had all the functionalities crammed into the draw function—a classic case of spaghetti code. This early version was overwhelming and led to analysis paralysis, making debugging a tedious chore. However, by compartmentalizing these features, I not only streamlined the development process but also significantly improved the game’s performance and my ability to debug and expand on the game. This decision towards modular design has been a game-changer, allowing for cleaner code, easier maintenance, and the flexibility to add new features or tweak existing ones with minimal hassle. It’s a technical achievement that has had a profound impact on both the development experience and the overall quality of the game.

Below is some of the modularized code:

function displayGameInfo() {
    let timePassed = (millis() - timerStartTime) / 1000;
    let timeLeft = max(30 - timePassed, 0);
    fill(0);
    noStroke();
    textSize(16);
    textAlign(RIGHT, BOTTOM);
    text("Time left: " + timeLeft.toFixed(1), width - 10, height - 10);

    checkCollisionsAndScore();
    displayScore();
    handleMovement();
}

function checkCollisionsAndScore() {
let hit = rectRectCollision(dog.x - dog.img.width / 2 * dog.scale, dog.y - dog.img.height / 2 * dog.scale, dog.img.width * dog.scale, dog.img.height * dog.scale, whiteSquare.x, whiteSquare.y, whiteSquare.size, whiteSquare.size);
  if (hit) {
    dog.growAndSlow();
    whiteSquare.respawn();
    score++;

    // Play collision sound at 30% volume
    collisionSound.setVolume(0.3); // Set volume to 30%
    collisionSound.play();
  }
}

function displayScore() {
    fill(0);
    stroke(255);
    strokeWeight(2);
    textSize(16);
    textAlign(RIGHT, TOP);
    text("Score: " + score, width - 10, 10);
}

function handleMovement() {
    if (keyIsDown(LEFT_ARROW)) dog.move(-1, 0);
    if (keyIsDown(RIGHT_ARROW)) dog.move(1, 0);
    if (keyIsDown(UP_ARROW)) dog.move(0, -1);
    if (keyIsDown(DOWN_ARROW)) dog.move(0, 1);
}

function draw() {
    switch (gameState) {
        case "start":
            drawStartScreen();
            break;
        case "play":
        // Start the looped sound if it's not already playing and the game state just switched to "play"
      if (!gameLoopSound.isPlaying()) {
        gameLoopSound.loop();
        gameLoopSound.setVolume(0.5); // Set volume to 50%
      }
            drawPlayScreen();
            // Calculate time left here
            let timePassed = (millis() - timerStartTime) / 1000;
            let timeLeft = max(30 - timePassed, 0);
            
            // Display the timer
            fill(0);
            noStroke();
            textSize(16);
            textAlign(RIGHT, BOTTOM);
            text("Time left: " + timeLeft.toFixed(1), width - 10, height - 10);

            if (timeLeft <= 0) {
                gameState = "gameover";
              if (gameLoopSound.isPlaying()) {
          gameLoopSound.stop();
        }
            }

            // Check if the dog collides with the square and handle scoring
            checkCollisionsAndScore();

            // Display the current score
            displayScore();

            // Handle player movement
            handleMovement();
            break;
        case "instructions":
            drawInstructionsScreen();
            break;
        case "credits":
            drawCreditsScreen();
            break;
        case "gameover":
            drawGameOverScreen();
            break;
    }
}

 

Describe some areas for improvement and problems that you ran into (resolved or otherwise):

Reflecting on the project, I see a few avenues for enhancement that could elevate the gameplay experience and address some challenges encountered along the way. In future iterations, I’d love to delve deeper into refining the game’s core loop. Introducing an in-game store where players can buy stat or ability upgrades seems like an exciting direction. Coupling this with the addition of random power-ups, like speed boosts, time extensions, and score multipliers appearing throughout the 30-second gameplay window, would inject more depth and variability into the strategies players might employ. Given the tight time frame players are working with, these elements could introduce pivotal decision-making moments that are both thrilling and consequential, potentially transforming an average round into an extraordinary one.

On the technical side, one particular hurdle I had to navigate involved the game’s scalability, specifically related to the dog’s growing image. As the dog expanded, it reached a point where it would cause the site or the p5.js environment to crash. Despite my efforts, pinning down the precise cause was challenging, leading me to theorize that it stemmed from an excessive strain on resources at any given moment. Under the pressure of deadlines, I opted for a temporary workaround by imposing a maximum size limit on the dog’s growth. This solution, while effective in preventing crashes, is admittedly more of a band-aid than a cure. Moving forward, dedicating time to resolve this issue comprehensively would not only enhance performance but also ensure that the gameplay’s immersive experience remains uninterrupted and fluid for all players.

Credits:

Sounds:

  • https://pixabay.com/sound-effects/dog-barking-70772/
  • https://pixabay.com/sound-effects/crunchy-paper-33625/
  • https://pixabay.com/sound-effects/merx-market-song-33936/

Art:

  • https://www.freepik.com/free-vector/sticker-template-dog-cartoon-character_20496955.htm#fromView=search&page=1&position=44&uuid=59df1125-5f43-4d72-9319-72464f25d11c
  • https://www.pinterest.com/pin/4433299622478035/
  • https://www.freepik.com/free-vector/scene-backyard-with-fence_24552372.htm
  • https://www.freepik.com/free-vector/seamless-textured-grass-natural-grass-pattern_11930799.htm#query=cartoon%20grass%20texture&position=0&from_view=keyword&track=ais&uuid=7951681d-d20d-4bad-8ce4-d50239a26e77

Code Assistance:

Chatgpt – https://chat.openai.com/

 

Full Code:

let dog; // This will be an instance of MovableImage
let bgImage; // This variable will hold your background image
let startScreenImage; // This variable will hold your start screen image
let gameState = "start"; // "start" for the start screen, "play" for the gameplay
let whiteSquare; // Instance of WhiteSquare
let customFont; // Variable for the custom font
let score = 0; // Tracks the number of times the dog collides with the white square
let hwImage; // This will hold the image of the homework paper
let timerStartTime;
// Global variables for button dimensions
let buttonWidth = 100;
let buttonHeight = 40;
let gameLoopSound;
let collisionSound;


class WhiteSquare {
constructor() {
    this.size = 50; // This can be adjusted based on the actual size of your image
    this.x = 0;
    this.y = 0;
    this.respawn();
  }

  display() {
    // Use the homework image instead of a white rectangle
    image(hwImage, this.x, this.y, this.size, this.size);
  }

  respawn() {
    // Cap the additional distance increase once the score reaches 32
    let cappedScore = Math.min(score, 32);
    let additionalDistance = cappedScore * 5; // The distance increase caps when the score reaches 32
    let minDistance = 100 + additionalDistance; // Base min distance + additional based on capped score

    // Attempt to respawn the square until it's far enough from the dog
    do {
      this.x = random(this.size, width - this.size);
      this.y = random(this.size, height - this.size);
    } while (this.isTooCloseToDog(minDistance));
  }

  isTooCloseToDog(minDistance) {
    if (dog && dog.x !== undefined && dog.y !== undefined) {
      let distance = dist(this.x, this.y, dog.x, dog.y);
      return distance < minDistance;
    }
    return false; // Default to false if dog is not initialized
  }
}


class MovableImage {
  constructor(image, x, y, scale = 1) {
    this.img = image;
    this.x = x;
    this.y = y;
    this.scale = scale; // Added a scale property
    this.speed = 5; // Initial speed - starting with a reasonable speed
  }

  display() {
    // Check if the image is within the bounds of the canvas
    this.x = constrain(this.x, 0, width);
    this.y = constrain(this.y, 0, height);
    
    push();
    translate(this.x, this.y);
    scale(this.scale); // Apply the scaling factor
    image(this.img, 0, 0);
    pop();
  }

  move(stepX, stepY) {
    this.x += stepX * this.speed;
    this.y += stepY * this.speed;
  }

  // Function to set the scale and adjust speed
 growAndSlow() {
   
const maxScale = 0.25; // Adjust this value as needed for balance and performance

  // Only grow if below the maximum scale limit
  if (this.scale < maxScale) {
    this.scale *= 1.025; // Grow by 2.5%
  }

    // Only reduce speed if the score is less than 13
    if (score < 13) {
      this.speed = max(1, this.speed * 0.95); // Reduce speed by 5%, no less than 1
    }
 }

}

function preload() {
  dogImage = loadImage('images/dog1.png');
  bgImage = loadImage('background2.jpg');
  startScreenImage = loadImage('backyard.jpeg');
  customFont = loadFont('MadimiOne-Regular.ttf'); // Load the custom font
  hwImage = loadImage('HW.png'); // Load the image of the homework paper
  buttonSound = loadSound('woof.mp3'); // Make sure the path to your mp3 is correct
    gameLoopSound = loadSound('game_loop_2.mp3'); // Adjust path as needed
    collisionSound = loadSound('paper.mp3'); // Adjust path as needed




}

function setup() {
  createCanvas(400, 400);
  dog = new MovableImage(dogImage, width / 2, height / 2, 0.009);
  whiteSquare = new WhiteSquare();

  imageMode(CENTER);
  textFont(customFont); // Set the custom font for all text
  
  
  // Define button properties
  buttonWidth = 100;
  buttonHeight = 40;
  restartButtonX = width / 2 - buttonWidth / 2;
  restartButtonY = height / 2 + 20;
  menuButtonX = width / 2 - buttonWidth / 2;
  menuButtonY = height / 2 + 70; // Positioned below the restart button

}

function rectRectCollision(x1, y1, w1, h1, x2, y2, w2, h2) {
  return x1 < x2 + w2 &&
         x1 + w1 > x2 &&
         y1 < y2 + h2 &&
         y1 + h1 > y2;
}

function isMouseOverButton(x, y) {
  return mouseX >= x && mouseX <= x + buttonWidth &&
         mouseY >= y && mouseY <= y + buttonHeight;
}

function drawButton(label, x, y) {
  fill(100); // Button color
  stroke(0); // Button outline color
  strokeWeight(2); // Button outline weight
  rect(x, y, buttonWidth, buttonHeight, 5); // Draw the button with rounded corners
  noStroke(); // Disable outline for the text
  fill(255); // Text color
  textSize(16); // Text size
  textAlign(CENTER, CENTER);
  text(label, x + buttonWidth / 2, y + buttonHeight / 2); // Draw the text centered on the button
}

function mousePressed() {
    let playButtonX = width / 2 - buttonWidth / 2;
    let playButtonY = height / 2 + 20;
    let instructionsButtonX = width / 2 - buttonWidth / 2;
    let instructionsButtonY = playButtonY + 70; // Positioned below the "Play" button
    let creditsButtonX = width / 2 - buttonWidth / 2;
    let creditsButtonY = instructionsButtonY + 50; // Positioned below the "Instructions" button

    if (gameState === "start") {
        if (isMouseOverButton(playButtonX, playButtonY)) {
            gameState = "play";
            timerStartTime = millis();
            score = 0;
            dog = new MovableImage(dogImage, width / 2, height / 2, 0.009);
            whiteSquare = new WhiteSquare();
        } else if (isMouseOverButton(instructionsButtonX, instructionsButtonY)) {
            gameState = "instructions";
        } else if (isMouseOverButton(creditsButtonX, creditsButtonY)) {
            gameState = "credits";
        }
    } else if ((gameState === "instructions" || gameState === "credits") && isMouseOverButton(menuButtonX, menuButtonY)) {
        gameState = "start";
    } else if (gameState === "gameover") {
        if (isMouseOverButton(menuButtonX, menuButtonY)) {
            gameState = "start";
        }
        let restartButtonX = width / 2 - buttonWidth / 2;
        let restartButtonY = playButtonY;
        if (isMouseOverButton(restartButtonX, restartButtonY)) {
            gameState = "play";
            timerStartTime = millis();
            score = 0;
            dog = new MovableImage(dogImage, width / 2, height / 2, 0.009);
            whiteSquare = new WhiteSquare();
        }
    }
    if (isMouseOverButton(playButtonX, playButtonY) || 
      isMouseOverButton(instructionsButtonX, instructionsButtonY) || 
      isMouseOverButton(creditsButtonX, creditsButtonY) || 
      isMouseOverButton(menuButtonX, menuButtonY) || 
      isMouseOverButton(restartButtonX, restartButtonY)) {
    buttonSound.play();
  }
}

function drawGameOverScreen() {
  background(0); // You can change the background color
  fill(255);
  textSize(32);
  textAlign(CENTER, CENTER);
  text("Game Over!", width / 2, height / 2 - 60);
  textSize(24);
  text("Your Score: " + score, width / 2, height / 2 - 20);
  

  // Draw the "Main Menu" button
  drawButton("Main Menu", menuButtonX, menuButtonY);
  
    // Draw the "Restart" button
  drawButton("Restart", restartButtonX, restartButtonY);
}


// Global variables for new button positions (adjust the Y positions as needed)
let instructionsButtonY = 267.5; // Positioned below the "Play" button
let creditsButtonY = 315; // Positioned below the "Instructions" button

function drawBackgroundAndTitle() {
    image(startScreenImage, 200, 200, width, height); // Repeated background and title setup
    textSize(30);
    textAlign(CENTER, CENTER);
    stroke(0); // Black outline
    strokeWeight(4); // Thickness of the outline
    fill(255); // White fill for the text
    text("The Dog ate my Homework!", width / 2, height / 2 - 20);
}

function drawStartScreen() {
    drawBackgroundAndTitle();
    drawButton("Instructions", width / 2 - buttonWidth / 2, instructionsButtonY);
    drawButton("Credits", width / 2 - buttonWidth / 2, creditsButtonY);
    drawButton("Play", width / 2 - buttonWidth / 2, height / 2 + 20);
}

function drawPlayScreen() {
    image(bgImage, 200, 200, width, height);
    dog.display();
    whiteSquare.display();
    displayGameInfo();
}

function drawInstructionsScreen() {
    image(startScreenImage, 200, 200, width, height); // Repeated background and title setup
    fill(255);
    textSize(16);
    stroke(0); // Black outline
    strokeWeight(4); // Thickness of the outline
    textAlign(CENTER, CENTER);
    text("The aim of the game is to eat as much of your owner's homework in 30 seconds before you get caught. Use the arrow keys to move around.", width / 8, height / 5, 300, 200); // Adjust the box size if needed
    drawButton("Main Menu", menuButtonX, menuButtonY);
}

function drawCreditsScreen() {
   image(startScreenImage, 200, 200, width, height); // Repeated background and title setup
    fill(255);
    textSize(16);
    stroke(0); // Black outline
    strokeWeight(4); // Thickness of the outline
    textAlign(CENTER, CENTER);
    text("Credits:\nGame Design: Jihad\nProgramming: Jihad, Chatgpt \nArtwork: brgfx, babysofja  \nSound: Pixabay",
      width / 8, height / 5, 300, 200); // The text box's width and height
    drawButton("Main Menu", menuButtonX, menuButtonY); // Adjust the Y position to place it below the credits text
}


function displayGameInfo() {
    let timePassed = (millis() - timerStartTime) / 1000;
    let timeLeft = max(30 - timePassed, 0);
    fill(0);
    noStroke();
    textSize(16);
    textAlign(RIGHT, BOTTOM);
    text("Time left: " + timeLeft.toFixed(1), width - 10, height - 10);

    checkCollisionsAndScore();
    displayScore();
    handleMovement();
}

function checkCollisionsAndScore() {
let hit = rectRectCollision(dog.x - dog.img.width / 2 * dog.scale, dog.y - dog.img.height / 2 * dog.scale, dog.img.width * dog.scale, dog.img.height * dog.scale, whiteSquare.x, whiteSquare.y, whiteSquare.size, whiteSquare.size);
  if (hit) {
    dog.growAndSlow();
    whiteSquare.respawn();
    score++;

    // Play collision sound at 30% volume
    collisionSound.setVolume(0.3); // Set volume to 30%
    collisionSound.play();
  }
}

function displayScore() {
    fill(0);
    stroke(255);
    strokeWeight(2);
    textSize(16);
    textAlign(RIGHT, TOP);
    text("Score: " + score, width - 10, 10);
}

function handleMovement() {
    if (keyIsDown(LEFT_ARROW)) dog.move(-1, 0);
    if (keyIsDown(RIGHT_ARROW)) dog.move(1, 0);
    if (keyIsDown(UP_ARROW)) dog.move(0, -1);
    if (keyIsDown(DOWN_ARROW)) dog.move(0, 1);
}

function draw() {
    switch (gameState) {
        case "start":
            drawStartScreen();
            break;
        case "play":
        // Start the looped sound if it's not already playing and the game state just switched to "play"
      if (!gameLoopSound.isPlaying()) {
        gameLoopSound.loop();
        gameLoopSound.setVolume(0.5); // Set volume to 50%
      }
            drawPlayScreen();
            // Calculate time left here
            let timePassed = (millis() - timerStartTime) / 1000;
            let timeLeft = max(30 - timePassed, 0);
            
            // Display the timer
            fill(0);
            noStroke();
            textSize(16);
            textAlign(RIGHT, BOTTOM);
            text("Time left: " + timeLeft.toFixed(1), width - 10, height - 10);

            if (timeLeft <= 0) {
                gameState = "gameover";
              if (gameLoopSound.isPlaying()) {
          gameLoopSound.stop();
        }
            }

            // Check if the dog collides with the square and handle scoring
            checkCollisionsAndScore();

            // Display the current score
            displayScore();

            // Handle player movement
            handleMovement();
            break;
        case "instructions":
            drawInstructionsScreen();
            break;
        case "credits":
            drawCreditsScreen();
            break;
        case "gameover":
            drawGameOverScreen();
            break;
    }
}

 

Midterm Project: Dungeon Maze

Concept

This dungeon-based maze game’s premise centers on an exciting and dynamic challenge where players are sent into several mazes that are produced by an algorithm, each with its own layout and set of coins and teleporters.

The game’s main goal, set against an engrossing dungeon backdrop, requires players to maneuver tactically through the maze, collecting gold coins while racing against the clock to locate the exit. The game uses teleporters as a game-changing element to enhance depth and complexity. Players can quickly go to different mazes, which resets the maze’s layout and offers unexpected twists. This makes the ‘Dungeon Master’ change their strategy.

In addition, an interesting limited vision feature simulates the real difficulty of traversing a dimly lit dungeon by preventing players from seeing beyond their immediate surroundings and forcing them to depend solely on memory and spatial awareness in order to advance. A fascinating and unpredictable journey that tests players’ intelligence and flexibility through the mix of random maze creation and other mechanisms like timers and limited vision, making every playthrough a unique experience.

Sketch

Snapshots of the Game

Elements of Pride

The choices made in the design of features like the drawLimitedVision effect, the generateRandomMaze function, and the adaptive design for fullscreen and windowed modes are a demonstration of good game designs and good technical decisions.

generateRandomMaze() {
    let maze = new Array(this.rows);
    for (let y = 0; y < this.rows; y++) {
      maze[y] = new Array(this.cols).fill('#');
    }
    const carvePath = (x, y) => {
      const directions = [[1, 0], [-1, 0], [0, 1], [0, -1]];
      this.shuffle(directions);

      directions.forEach(([dx, dy]) => {
        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] === '#') {
          maze[ny][nx] = ' ';
          maze[y + dy][x + dx] = ' ';
          carvePath(nx, ny);
        }
      });
    };

    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);
    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;
  }

The generateRandomMaze function is a cornerstone of the game’s dynamic environment, creating a new, solvable maze for each session or level. The function uses a recursive backtracking algorithm, it selects a random starting point within the maze and carves out paths. This is done by moving two cells at a time in a chosen direction (to avoid creating isolated cells) and removing walls between cells to create a path. This process repeats recursively for each new cell reached that has all its surrounding cells intact.

Once the maze is generated, the function strategically places coins (‘C’) and teleporters (‘T’) within the maze. Coins act as collectibles, and teleporters serve as transport points to new mazes. An exit (‘E’) is placed at a predefined location, typically far from the start, ensuring that players must navigate through the maze to find it.

function drawLimitedVision(playerX,playerY,visibilityRadius) {

  // Draw a radial gradient from transparent to dark
  let radialGradient = drawingContext.createRadialGradient(playerX, playerY, 0, playerX, playerY, visibilityRadius);
  radialGradient.addColorStop(0, 'rgba(0,0,0,0)'); // Completely transparent at the center
  radialGradient.addColorStop(1, 'rgba(0,0,0,0.8)'); // More opaque towards the edges

  drawingContext.fillStyle = radialGradient;
  drawingContext.fillRect(0, 0, width, height);
}

The drawLimitedVision function enhances the game’s atmosphere by simulating a limited field of vision for the player, similar to carrying a light source in a dark dungeon. The function creates a radial gradient centered on the player’s current position. This gradient transitions from transparent (at the center) to opaque (at the edges) obscures parts of the maze not immediately near the player.

function calculateCellSize() {
  cellSize = min(width, height) / max(mazeColumns, mazeRows);
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  calculateCellSize();
}

The code includes mechanisms to adapt to fullscreen and windowed modes. The calculateCellSize function dynamically calculates the size of each maze cell based on the current dimensions of the game window. This ensures that the maze scales correctly when switching between fullscreen and windowed modes, maintaining consistent gameplay and visual quality. The windowResized function listens for changes in the window size (including toggling fullscreen mode) and adjusts the canvas size accordingly.

Difficulties

One of the trickier parts of developing the game was actually implementing the limited vision functionality. Developing a smooth, radial-gradient of light that would dynamically follow the player’s motions and provide vision around them while keeping the remainder of the maze in complete darkness was the challenge. It took several tries to get this effect just right so that it seemed natural and didn’t interfere with gameplay by being too dark or changing too quickly.

There were further difficulties when trying to get the game to smoothly switch between fullscreen and regular window modes. Flexible design was necessary to make sure that the maze, player character, and user interface (UI) components all scaled and remained proportionate across a range of screen sizes and aspect ratios. This flexibility was essential to maintaining the game’s playability and graphic quality in any viewing mode.

Improvements

A possible area for enhancement is the visual design and user interface. For example, adding more complex visuals to the player’s character, treasures, and maze walls might greatly improve the game’s visual attractiveness. Furthermore, adding more variation to the maze layouts and obstacles—like traps or enemies—could add new and unique difficulties and amplify the action.

The project could also benefit from a more advanced level progression system, where the difficulty gradually increases through levels, possibly by enlarging the maze, decreasing the time limit, or increasing the required percentage of coins to collect before exiting.

 

Full Code