For the midterm project, I decided on making a scuba diving game where the player will be encounter sea treasures and sea monsters in a continuous stream. The player will accumulate points by colliding with gold coins but when a collision is made with a sea monster, the game will end.
The game is in three states: the start, playing and game over states. The start state consist of the game name and instructions printed out on the screen. After the clicking the mouse, you enter a playing state. The task is collect as many coins as possible while avoiding contact with the sea monsters. The game will end when you collide with a sea monster. There are levels in the game which influence the speed at which the sea monsters move. The higher up you go, the faster they move. To move the next level, you must go through a series of 6 backgrounds. You change backgrounds by moving to the right now of the game window.
The diver, monster fishes and coins were implemented with sprites from the internet. The various modes have sounds attached embedded in them. There is also a game play sound the loops when you’re playing the game. The score and the level are displayed at the top left and top right positions.
To make the coins spin, I used a sprite sheet to animate them; displaying one image at a time in a suitable frame count.
Challenges:
Implementing a continuous scrolling background was a big challenge. To resolve that challenge I decided to use a set of related backgrounds and display the continuous when the player reaches the rightmost of the game window. This creates the illusion that he’s move through various scenes underwater.
Integrating the sounds and making them work with the various game modes also required a bit of smart engineering. I decided to use boolean flags that signal when to play and stop a sound.
Embedded Sketch:
`
Reflections:
I particularly happy with the current version of the game. I intend to update it further and make the diver be able to fire water shots at the monsters. I tried controlling the diver with hand gestures but the outcome hasn’t really effective, it just wasn’t right for this game. This version of the game doesn’t have a high level of interactivity aside controlling the diver but I think to has the right kind of interactivity that makes the game enjoyable. I plan to explore more ways to make it interactive.
My midterm project is an extension of the “Ivy’s Hardest Game” game, where I have incorporated additional features. Unlike the original game, my version of the game, titled “ChaseMe”, is a time-based game, where John Wick, a fictional Hollywood character, is the primary character, while other Marvel characters like Thanos, Monolith, Howard and so on are supplementary characters. Here, the main character is being chased by the secondary characters mentioned above, so the target is to help Wick survive as long as possible. In case, the secondary characters catch him, the game is over and all the progress vanishes.
Moreover, I took a tiny bit of inspiration from “Adventure Trap 2” – a game famous for its hard levels and hidden traps. In simple words, no matter what you do, no matter what the player does, the character dies in this game. Thus, I wanted to implement such features; consequently, it is relatively challenging to go beyond level 4 in this game (Good luck with this!). With this idea in mind, I have displayed the number of levels on the top-left corner.
Code
The entire project is divided into four JavaScript files for organizational purposes (character.js, game.js, keyboard_keys.js and driver_file.js). Most of the functions are present inside the “game.js” file, character.js file contains blueprints of superclass and subclasses. In the same way, “keyboard_keys.js” files contain functions that keep track of arrow keys pressed and released during the game, whereas the driver_file.js file is the driving code that consists of all the global variables as well as setup() and draw() functions.
The core feature of the game is the use of Object Oriented Programming (OOP) and related features like abstraction, encapsulation and modularity. There is a superclass titled “Character” from which two subclasses — MyCharacter and SuppCharacter — are derived.
Here, the MyCharacter consists of image dimension and collision attributes in addition to the attributes of superclass; similarly, it constitutes of five methods as mentioned below:
up_collision_check() It checks if the primary character has collided with secondary characters or not — if yes, class attributes are updated accordingly.
movement() It loops through the list of secondary characters and checks collision with them and sets attributes accordingly.
border_check()
It keeps the primary character within the canvas.
update()
It updates the location of the primary character and calls border check and movement method in the process.
display() It displays the primary character (image) and updates its position as the character moves.
Similarly, the SuppCharacter subclass initializes additional attributes (particularly, acceleration and velocity vectors) and includes three methods as mentioned below:
border_check() It makes sure secondary characters stay within the canvas — when each character reaches the border, its velocity is reversed.
update() It updates the (x, y) coordinates of the character, checks border collision and uses an algorithm so that each supplementary character follows the primary character with the help of vector methods.
display() method
This method calls the update() method and displays the image of the secondary character.
Above mentioned are the three classes of the project. The entire function is based on these classes and additional features are implemented over them to shape the project.
The fundamental phases of this game can be divided into the following game stages (or called “gamePhase”) in the code. I have made various changes and included additional features in each of these stages.
“Start” phase
“Playing” phase
“Complete” phase
In the start phase, the game’s interface is shown to the user. It displays an interface background (an image loaded into the system), shows the game instructions (how to play the game) and consists of a “Start” button, which should be clicked on to start the game.
This stage relies on “start_screen()” and “instruction_text()” functions. The “start_screen()” function displays the background image, sets up the “start” button and ensures the game progresses to the next stage when clicked on. In addition, when the cursor is brought over the button, hover effect is visible on the screen. Similarly, the “instruction_text()” function displays a set of texts on how to play the game on the right corner of the screen. Once, the “start” button is clicked, “gamePhase” changes to “playing”, thus new functions are called and the game interface changes.
The second phase is the “playing” phase, which is the most code-intensive stage of the game. This is where a player plays the game, progresses to different levels and completes the game (it can be tiring though). Once the game starts, the “game_screen()” function is called. It displays the primary character and loops through the supplementary characters’ lists and displays all the secondary characters on the screen. Inside this function, “timer()” function is called.
function timer()
{
// Co-ordinates of timer text
let x_pos = -160;
let y_pos = 25;
// Timer Functionality Text
textAlign(CENTER, CENTER);
stroke(5);
textSize(25);
fill("white");
text("Time Remaining: " + timer_len, canvas_w + x_pos, y_pos); // Displaying Timer
text("Level: " + level + " of " + num_levels, canvas_w/2 + 2.5 * x_pos, y_pos); // Displaying Level
// A second passed using modulus function
// Decrease time and increase score
if (frameCount % 60 == 0 && timer_len > 0)
{
timer_len--;
score++;
}
// If the character has survived until the end of the level, call next_level_screen() function
if (isGameOver == false && timer_len <= 0)
{
next_level_screen();
}
}
The “timer()” function displays the timer and level of the game on the screen. The idea behind the countdown timer is to decrease the value of the “timer_len” global variable every second, which is done using the “modulus” operator — i.e. in an interval of every 60 frames, the timer_len variable’s value is decreased once. This way, it appears as if the timer is in countdown mode. Additionally, while decreasing the value of the timer_len variable, the value of the “score” variable is increased too. This function also ensures the game proceeds to the next level if the timer_len is “0” and isGameOver boolean is “false”, which implies that the game should go on — this is done by calling the “next_level_screen()” function.
The “next_level_screen()” function depends on two helper functions “level_completion_setting()” and “next_level_settings()”. These functions update appropriate global variables like isGameOver, gamePhase and score. The next_level_settings() function adds an increasing number of normal characters to the game as it progresses. Also, a new character called “big-boss” is introduced after level 3 in the game (and two of them after level 6), which is done by the next_level_settings() function.
The next_level_screen() function checks if it’s the end of the level — if there are more levels in the game, it shows a button (which is done using the “myButton()” function described later) that can be clicked to move to the next level.
While playing the game, if the primary character collides with any other characters, the isGameOver variable changes to true and using the collision check method of the “MyCharacter” subclass, the gameOver_screen() function is invoked. This function again displays a game over the screen along with a button that can be clicked to restart the game. In order to do so, reset() function is called, which resets different global variables and lists to initial states for a fresh start.
function reset_game()
{
// Reset isGameOver boolean and change the gamephase
isGameOver = false;
gamePhase = "playing";
score = 0;
level = 1;
timer_len = timer_len_1; // Reset the timer to 30 seconds
supp_char_list = []; // Reset the list of secondary characters
add_supp_char(); // Secondary characteres added manually in reset
textFont("calibri"); // For Timer Font (Otherwise it changes to Assassin Font)
add_soundtrack();
// Placing the primary character at the middle of the canvas
my_char.location.x = (canvas_w/2 - my_char_size/3);
my_char.location.y = (canvas_w/2 - my_char_size*3);
}
Similarly, the third phase of the game is the “complete” phase. If all the levels are completed, the “game_complete_screen()” function is called. It displays a congratulatory message and provides the player with a button to restart the game – once again, myButton() and reset() functions are used respectively to restart the game.
Additional Functionalities
The game consists of different sound effects to aid the overall gameplay. Using the “add_soundtrack()” function, background music is added to the game, which spans for the entirety of the game (to do so, p5.js’s inbuilt loop() function is used). This function is called inside the “setup()” function as well as the “reset()” function — this way, music does not overlap and it can be stopped whenever the game over screen is to be displayed.
In the same way, a collision sound is a part of the game, which is played when secondary characters catch the primary character successfully.
I spent a significant amount of time brainstorming possible algorithms and game designs for this project. Consequently, I came up with different ideas that I am particularly proud of. For instance, the chasing feature inside the update method of the “SuppCharacter” subclass. Initially, I was planning on implementing Dijkstra’s Algorithm to find the shortest path between characters, but the concept of vector implementation seemed more reasonable. I used some ideas from this source to complete this function. The idea here is to create a vector that points to the position of the primary character. Then, the (vector) location of the chasing characters is subtracted from the vector created above. The resultant vector has a strong magnitude or pull; thus, it is mapped by a particular scale (in this case, 0.1) as a result, the movement is more realistic. Finally, the vector of the primary character is assigned to the acceleration vector, which is used to update the velocity vector; the velocity vector in turn updates the location vector. This way, it appears as if supplementary characters are chasing the primary character. Essentially, it is using basic mechanics to find the shortest path.
update()
{
// Create location vector for main character (dynamic in nature)
let main_vec = createVector(my_char.location.x, my_char.location.y);
main_vec.sub(this.location); // Subtract location of this.location from main_vec
main_vec.setMag(supp_char_mag); // Set the magnitude of the vector - used as 'Pull'
this.acc = main_vec; // Set acceleration to main_vec
this.location.add(this.velocity); // Update location
this.velocity.add(this.acc); // Similary for velocity
this.velocity.limit(supp_vel_limit); // Set max velocity limit
this.border_check();
}
At first, I used p5.js’ inbuilt createButton() function to take user input. However, in the later phase of game design, I manually created a myButton() function that takes certain parameters and creates a button for the user. This function displays the required message to the user (based on the message parameter), creates different buttons (based on the button_text parameter) and enables unique hover effects (based on the color parameter). This way, using one function, unique buttons are created throughout the game. Moreover, the use of the “type” parameter ensures the implementation of certain features under certain conditions only.
function myButton(img_name, rect_x, rect_y, rect_w, rect_h, message, button_text, color, type)
{
image(img_name, 0, 0, canvas_w, canvas_h); // Load the image based on parameter
textSize(50);
textFont(assassin_font);
fill("white");
stroke(1);
text(message, canvas_w/2, canvas_h/2 - 100);
// Your Score Text -- for all pages except the first one
if (type != "start")
{
textSize(30);
textFont("calibri");
fill("red");
text("Your Score: " + score + " seconds", canvas_w/2, canvas_h/2 - 10);
}
// Play Again Button
let isWithinRectangle = (mouseX < (rect_x + rect_w) && mouseX > (rect_w) && mouseY < (rect_y + rect_h) && mouseY > (rect_y))
// Hover Effect
if (isWithinRectangle)
{
if (type != "start")
fill(color);
else
fill("grey");
text_size = 55;
}
else
{
if (type != "start")
fill("white");
else
fill(143,148,123);
text_size = 50;
}
// Border rectangle for Start Button
stroke(5);
rect(rect_x, rect_y, rect_w, rect_h, 20);
// Font setting for start page
if (type == "start")
{
textAlign(CENTER);
textFont("Helvetica");
fill("white");
}
else
textFont(corleone_font);
stroke(5);
textSize(text_size);
text(button_text, rect_x + 100, rect_y + 30); // Button text
return isWithinRectangle; // Return if cursor is within the rectangle or not
}
Similarly, I am proud of the way the overall project is organized. For instance, there are separate “add_supp_char()” and “add_big_boss()” functions in the driver_file.js file, where I have adjusted the x-coordinates of new characters so that no supplementary character overlap with the primary character. Also, this design lets me call the functions in each level separately, thus the number of supplementary characters can be increased after each level; also, big-boss characters can be added after certain levels due to this game design. Thus, I am happy with the game design.
Problems I encountered
The section that took most of my time was designing the functionality where secondary characters follow the primary character. I was trying to implement this feature without using ‘vectors’; however, it was tedious and failed in many circumstances. Thus, I switched the location, velocity and acceleration attributes of the classes mentioned above, which allowed me to track the path of the character’s movement. This way, I was able to implement this feature. Similarly, increasing the level of difficulty required a worthwhile time investment as sometimes the levels were too difficult or supremely easy. I solved this problem by introducing supp_vel_increment and supp_char_mag variables which let me increase the secondary character’s velocity and pull force manually inside different functions or methods.
Reflection
Overall, I am incredibly happy with the way the project turned out. Designing a tentative plan was definitely helpful as I could build upon different ideas. In future iterations of this project, I would love to include the following changes:
The use of Computer Vision (web camera or microphone) to control the movement of the primary character. As of now, the character’s movement is sensitive given the increasing level of difficulty, thus proper algorithms and modes of user interactivity can enhance the game.
A setting page at the beginning of the game, which lets the user select the number of levels or number of characters.
For my midterm, I made a Where’s Waldo? game where the player has to search for the Waldo character on the canvas and click on him to win. Waldo is randomly generated on the canvas every time you play, and he’s in a crowded background that you have to navigate through and look closely in order to find him. It’s relatively simple; a digital version of one of my favorite picture books from childhood.
I added sound effects that play whenever you hover over specific things or people on the canvas, which I thought added another fun dimension to my game.
For example, if you hover your cursor over the ocean part of the scene, you can hear a water splash sound effect, or if you hover your cursor over the crowd of children running, you can hear a laughter sound effect.
Embedded sketch:
My game has 5 screens: the title screen, instruction screen, gameplay screen, win screen and lose screen. They are navigated via the draw function, which keeps track of the game’s state and calls functions whenever you insert the game state somewhere.
I think adding more rounds would have definitely improved my game, but it took me a while to figure out how to sort everything out and debug my code– so much so that I didn’t have enough time to figure out how to embed another round into my sketch (and trust me, I tried).
I also would have liked the game to be more interactive in terms of key pressing and mouse clicking as a form of navigation. My game is fairly simple, as all you need to do is click on Waldo to win.
Keeping track of everything was a nightmare until the professor helped me out with the game states in function draw, and this made it easier for me to trigger certain functions without having to continuously navigate through the code and jumble things up.
Advice from my peers also really helped me configure the game as well!
Phew! That was one hell of a ride for sure. For my midterm project, I have created a shooting game, with a forestry theme. The name (Man Vs Tree) is inspired by the Netflix show (Man Vs Bee) starred by the famous Mr.Bean lol. Basically the project started off as an idea of creating a whole ecosystem of different enemies to our environment and catalysts to global warming. However as challenges grew and came in different forms, I finally decided to implement the current theme, which is simple yet meaningful. Man Vs Tree is about a player, which is an apple tree, protecting its forest against loggers by attacking them with apples. As more enemies die, they spawn power-ups as ozone health. The enemy loggers shoot dynamites and axes at the tree, and once all lives are finished, the tree dies and the game is lost.
Code
The coding for the game was indeed a great hustle. A lot of time was spent in thinking about the mechanics of different aspects such as collisions, arrays, level implementation, speed optimization, and many more concepts. Even more time was spent testing the game and making sure there were as few bugs as possible and that all the logic implemented was working as it was supposed to. Below is a snippet of the game class, showing the display method of the game class.
display_game() {
//condition for displaying actual gameplay when game has started
if (this.gameStarted && this.gameOver == false && this.gameWon == false) {
image(this.bg_img, 0, 0, 600, 600);
this.player.display();
//loop for displaying all enemies in enemy array
for (let i = 0; i < this.enemies.length; i++) {
this.enemies[i].display();
}
//loop for displaying all projectiles in enemyprojectiles array
for (let i = 0; i < this.enemyProjectiles.length; i++) {
this.enemyProjectiles[i].display();
//logic for player and enemyprojectile collision detection, player then takes damage
if (this.enemyProjectiles[i].used == false && this.enemyProjectiles[i].collision(this.player)) {
this.player.hit(10);
}
}
//loops through enemyprojectiles array and pops any projectiles which have already collided
for (let i = 0; i < this.enemyProjectiles.length; i++) {
if (this.enemyProjectiles[i].used == true) {
this.enemyProjectiles.splice(i, 1);
break;
}
}
//loops through enemy array and pops any enemies with health less than zero
for (let i = 0; i < this.enemies.length; i++) {
if (this.enemies[i].health <= 0) {
this.enemies.splice(i, 1);
break;
//logic for score reduction if enemy reaches beyond screen height, and pops enemy from array
} else if (this.enemies[i].y > 650) {
this.enemies.splice(i, 1);
this.score -= 10;
break;
}
}
//loops through playerprojectiles array and displays them
for (let i = 0; i < this.playerProjectiles.length; i++) {
this.playerProjectiles[i].display();
//detects collision btn playerprojectiles and enemies, then enemy takes damage
for (let j = 0; j < this.enemies.length; j++) {
//Check if the projectile collides with any enemy
if (
this.playerProjectiles[i].used == false &&
this.playerProjectiles[i].collision(this.enemies[j])
) {
this.enemies[j].hit(10);
}
}
}
//loops through ozone powerups array, displays them, and detects collision with player
for (let i = 0; i < this.ozone.length; i++) {
this.ozone[i].display();
if (
this.ozone[i].used == false &&
this.ozone[i].collision(this.player)
) {
this.ozone[i].give_health();
}
}
//pops ozone powerup from array once collected by player
for (let i = 0; i < this.ozone.length; i++) {
if (this.ozone[i].used == true) {
this.ozone.splice(i, 1);
break;
}
}
this.show_score();
//logic controlling time in the game
if (frameCount % 60 == 0) {
this.timer += 1;
}
Reflections / Improvements
Overall , I am proud of myself and the general implementation of the code. However, of course there could further improvements that could be made such as having variety of enemies, more visual effects such as floods maybe or rain. Codewise, there was quite some hardcoding which could’ve been implemented more elegantly, and made the game more efficient and easy to understand. Watching that progress starting from scratch to coming across those bugs that you would spend hours, sometimes days on, and seeing the final version of the game gives me great joy, one I can not describe.
For our midterm project I wanted to recreate a game that I used to play as a kid. The game is called Purble Place and it has three game options within it.
I decided to recreate this one. The goal of the game is recreate the cake displayed in the small window at the top left. I believe that you have a maximum number of times you get the game wrong before you lose.
I started by brain storming the aspects of the game that I wanted to recreate and the classes I would use (I’m not sure why the images are blurry, could be because they are screenshots)
I then started to implement the game in p5js. I wanted to keep track of the functionalities that each draft of the game has and maybe what are the issues and what is to be done next. This would make it easier to remember what each draft does incase I need to go back and use something from it. To do this I added comments at the top of each draft outlining those things.
In the first draft I drew the cake(created a cake class to do this) and tried to add each aspect by clicking within a certain range.
In the second draft I tried to move the cake
In the third draft I made a class that creates new cakes after the current one leaves the window
In the forth draft I added graphics and implement the button clicking within the class. I also added the score at the top left
In the forth I added a conveyer belt.
to do:
remove placeholder graphics and add actual graphics
add more options for each aspect of the cake (more colors…)
display the expected cake(the cake that the user needs to try and mimic)
add audio
keep track of how many cakes were produced
figure out when to stop and start the game (timer?)
i wanted to keep going with the idea of being trapped. so i decided to make a text that signifies that. The point of the project that i made was it being a cry for help that only shows for flashes of time as if the ball is trying to tell people its message but it’s not that clear so can only see it if you focus. it is clearly in prison since we can see the prison bars around it.
Process
I made a code creating diffrent variables for mu prisoner ( the ball) and the x and y.
I then used the pre load to input the font i wanted to use which i downloaded for the web
I then imputed the text to the code.
I used my past project in order to create the prison cell .
I then created a class for my bouncing ball.
let myBouncingBall;
let x= 0
let y=0
let spacing = 20;
function preload() {
font = loadFont("3D.TTF");
}
function setup() {
createCanvas(400, 400);
myBouncingBall = new BouncingBall();
text ("FREE ME PLEASE!!!", 30,100)
}
function draw() {
background(54);
textFont(font);
textSize(50);
noStroke();
text("FREE ME PLEASE!!!", 30, 100)
stroke(56);
if (random(7) < 0.9) {
noStroke();
(x, y, random(8, 12));
fill(random(255), random(0), random(170));
} else {
stroke('#6cd8eb');
line(x + spacing, y + spacing, x, y);
}
x = x + spacing;
if (x > width) {
x = 0;
y = y + spacing;
}
for (let lineX = 0; lineX <= 600; lineX += 9) {
line(lineX, 0, lineX, height);
}
ellipse (width/2, height/2, 154, 154)
myBouncingBall.move();
myBouncingBall.collision();
myBouncingBall.draw();
}
class BouncingBall {
constructor() {
this.xPos = width / 2;
this.yPos = random(100, 300);
this.xSpeed = 4;
this.ySpeed = 7;
}
move() {
this.xPos += this.xSpeed;
this.yPos += this.ySpeed;
}
collision() {
if (this.xPos <= 150 || this.xPos >= (width - 150)) {
this.xSpeed = -this.xSpeed;
}
if (this.yPos <= 150 || this.yPos >= (height - 150)) {
this.ySpeed = -this.ySpeed;
}
}
draw() {
circle(this.xPos, this.yPos, 30);
}
}
Improvements
this project could have been easier on the eyes since it kind of hurts if you look at it for a long period of time. i would have liked the ball not to lag when you click it sometimes.
For this project, I have created an 8-bit style Tetris game. The general concept of the game is similar to the original game, but there are two additional features in this game. First, the game sometimes generates a random block that does not look like a traditional Tetris block. The block may consist of more than 5 blocks and some empty spaces. This is a challenge that this game gives to the players. Secondly, there is a bomb block that removes all blocks. If the bomb is in row n, it will clear all blocks from row n-2 to n+2, removing 5 lines total. This feature is added because randomized blocks may be too difficult for the users. For additional concepts, please refer to my previous posts.
Code
**note: I will only discuss codes that are new or different from the previous post I made on Tetris. I recommend reading the previous first and coming back.**
For update 2, I started off by fixing these one minor bug:
Block cannot be moved as soon as it reaches the bottom.
This happened because the code generates new block as soon as the block touches the ground (when block cannot go further down). In order to solve this, I added a condition which the code will wait for the new keyboard input if it cannot go further down for a specific amount of time. If no key input is given in that time range, the code will then generate the block. Bolded code shows the new lines that I have added.
if (millis() - counterBlockDown > 1000 / this.speed) {
fixBlock = true;
counterBlockDown = millis();
}
if (fixBlock) {
//Once block cannot be moved down further, update game field
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (this.shapeArr[i][j] == 1) board[this.y + i][this.x + j] = 1;
}
}
//generate new block
generateBlock = true;
fixBlock = false;
}
}
Not to mention, there are tons of new features that I added in this update:
Soft drop
//soft drop
if (keyIsDown(DOWN_ARROW)) {
if (myBlock != null) myBlock.speed = blockSpeed * 3;
} else {
if (myBlock != null) myBlock.speed = blockSpeed;
}
When down arrow key is pressed, the block will move down with the speed that is 3 times its initial speed. Not to mention, block’s speed is in variable because initial speed will increase as level goes up.
Hard drop
When spacebar key is pressed, the block will instantly move down to the bottom. Notice that I used 32 for keyCode because spacebar is not mapped in p5js.
//Hard drop when spacebar pressed
if (keyCode == 32) {
if (myBlock != null) {
let yPos = myBlock.y;
while (myBlock.validMove(0, 1, 0)) {
myBlock.y = yPos;
yPos++;
}
hardDropMusic.play();
}
}
For this, additional calculation is done to find the lowest possible position for the block. The program will essentially start from the lowest position and see if the block can fit there. If block cannot be fitted, the block will moved 1 unit up to see if it fits there. This process repeated until the block finally fits.
Rotation
rotate() {
for (let i = 0; i < 2; i++) {
for (let j = i; j < 4 - i - 1; j++) {
// Swap elements of each cycle
// in clockwise direction
let temp = this.shapeArr[i][j];
this.shapeArr[i][j] = this.shapeArr[3 - j][i];
this.shapeArr[3 - j][i] = this.shapeArr[3 - i][3 - j];
this.shapeArr[3 - i][3 - j] = this.shapeArr[j][3 - i];
this.shapeArr[j][3 - i] = temp;
}
}
}
//rotate block with condition check
rotateBlock() {
if (!this.validMove(0, 0, 1)) {
for (let i = 0; i < 3; i++) this.rotate();
}
}
In order to rotate blocks, there must be additional condition that will check if the rotation is a valid movement at the given position of the block. Bolded code shows the new condition I have added to do this.
//Checks if block can be moved in a given direction
validMove(dX, dY, dR) {
if (dR == 0) {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) { if (this.y + i >= 0 && this.x + j >= 0) {
if (
/*ignores all empty spaces. 1s must always be within the
boundary of game field and must not overlap with non-empty,
or 1s, when moved*/
this.shapeArr[i][j] != 0 &&
(board[this.y + i + dY][this.x + j + dX] != 0 ||
this.y + i + dY >= boardHeight ||
this.x + j + dX < 0 || this.x + j + dX >= boardWidth)
) {
if (this.shapeArr[i][j] == 2){
this.explode();
}
return false;
}
}
}
}
} else {
this.rotate();
if (this.y >= 0) {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) { if ( /*ignores all empty spaces. 1s must always be within the boundary of game field and must not overlap with non-empty, or 1s, when moved*/ this.shapeArr[i][j] == 1 && (board[this.y + i][this.x + j] != 0 || this.y + i >= boardHeight ||
this.x + j < 0 || this.x + j >= boardWidth)
) {
return false;
}
if (this.shapeArr[i][j] == 2) return false; //bomb cannot be rotated
}
}
} else return false;
}
return true;
}
validMove() will first check if the block is being rotated or translated. If block is being rotated, i.e. third parameter is 1, it will first rotate the block and check if the block is not overlapped with other blocks and is within the width and height of the game field. If block satisfies all conditions, validMove() returns true. Else, it returns false. Since validMove() actually rotates block to check condition, rotateBlock() will rotate block 3 more times to reset the block status if no rotation can be made.
Random block
As level goes up, the game will start to generate random a 4 by 4 block that may contain empty spaces like such:
Each time a random block is generated, the block will have randomized shape. This feature was added to increase the difficulty of the game and make the game more interesting/different every time.
let blockType = random(0, 10);
if (blockType <= 11 - level * 0.5)
myBlock = new block(5, -4, random(blockTypeArr), blockSpeed);
else myBlock = new block(5, -4, random(specialTypeArr), blockSpeed);
This is a updated condition for generating block. blockType is a random number between 0 and 10. Regular blocks will be generated if blockType is less or equal to 11- level * 0.5 and random blocks or bomb (will be discussed in the next section) will be generated else. Note that the probability of getting random blocks or bomb increases as level goes up. For this reason, random block and bomb will only appear from level 3.
Bomb
Bomb is a special type of block that will remove blocks nearby. If block is at row n, it will remove all blocks from row n-2 to row n+2.
else if (_type == "bomb") {
for (let i = 0; i < 4; i++) {
shapeArr[3][2] = 2;
}
}
/*explode bomb block. if bomb is at (x,y), it will destroy every row
from x-2 to x+2*/
explode() {
let go = true;
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (this.shapeArr[i][j] == 2 && go) {
go = false;
for (let k = this.y + i - 2; k <= this.y + i + 2; k++) {
if (k >= 0 && k < boardHeight) {
for (let l = 0; l < boardWidth; l++) {
board[k][l] = -1;
}
}
}
}
}
}
}
Block object now has new type called bomb. Chance of generating random block and bomb is both 50%.
Line Clearing
Every time a game field is updated, the program will look for any completed lines. If there is a completed, the game field will be updated. The update will make game field to hold value -1 instead of 1.
//Check if there is a line cleared
function lineClear() {
for (let i = 0; i < boardHeight; i++) {
if (board[i][0] == 1) {
for (let j = 0; j < boardWidth; j++) {
if (board[i][j] == 1) {
if (j == boardWidth - 1) {
for (let k = 0; k < boardWidth; k++) board[i][k] = "-1";
counterLineClear = millis();
}
} else break;
}
}
}
}
Because the game filed can now hold -1, I added new condition to make sure the game can display -1s as a grey block. Then, the game filed will start to remove grey blocks line by line at each frame. This code is shown as bold code below.
//visually display the gameboard.
function displayBoard() {
blockImg = blockImg.get(0, 0, 20, 20);
push();
translate(25, 30);
for (let i = 0; i < boardHeight; i++) {
for (let j = 0; j < boardWidth; j++) {
if (board[i][j] == 1) image(blockImg, blockSize * j, blockSize *
i);
else if (board[i][j] == -1) { image(greyBlockImg, blockSize * j,
blockSize * i);
removeLine(i);
}
}
}
pop();
}
function removeLine(index) { if (millis() - counterLineClear > 400) {
for (let i = index; i > 1; i--) {
for (let j = 0; j < boardWidth; j++) {
board[i][j] = board[i - 1][j];
}
}
lineClearMusic.play();
lines++;
score += scorePerLine;
counterLineClear = millis();
}
}
Score, level, and line cleared counting
For each line cleared, the game will update score, level, and line cleared displayed on the right side. For each line cleared, the score will increase more as level goes up. Level increases by 1 for each 10 lines cleared. As mentioned above, block speed will also increase as level goes up.
A condition that checks if game is over. If the position of non-empty block unit is below game field boundary, the game is over.
else {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (this.y + i < 0 && this.shapeArr[i][j] == 1) {
gameOver = true;
break;
}
}
}
...
This condition is within blockDown() method of the block object.
Sound effect
Last feature that was added to this game is sound effect for background music, the block is moved and soft/hard dropped, and game over. Code will not be demonstrated because the codes for sound effect is scattered throughout the entire program.
Future Improvements
Overall, I am very satisfied with I have made for this project, but here are some things that I would love to fix in the future:
I realized when this program is played with chrome browser, the sound effect will slow down as time progresses. I found out this is the problem with the chrome itself.
Add settings page so players can change sound effects.
Due to the time limit, I failed to include a feature which shows next 2 blocks that will show up in the game. I would love to add this feature in the future.
Include hold feature so user can hold the block and use it later when needed.
The rotation of the blocks is not centered because rotation is done as if I rotating a matrix.
This week, we learned to work with text and visualizing data on p5js. We had the choice of working with either data visualization or text effects. I have always been fascinated by typography and so I decided to work with text effects.
Implementing Perlin Noise with Text:
I like how the Perlin noise function helps create random distortions and it’s something I wanted to integrate with the assignment. I also dug a bit into the various text effects in the Reference section of p5js.
I learned more about text to point from this Coding Train tutorial:
Although the Coding Train’s video covered a lot more than using text to point, I used just this one functionality since I found it easier to grasp and thought it would create an interesting visual.
In the final result, the text ‘IM QM’ is formed by tiny rectangles tracing around a custom font (which I uploaded) and the rectangles move and change color with the movement of the mouse using the noise() function.
Going Forward:
Typographies are fascinating and creating typography with code is something I find very powerful because we are no longer limited to hands-on skills for creating something beautiful. Beyond this assignment, I wish to keep experimenting with various other text effects and create something more interactive.
Originally, I was going to do a game similar to i-Spy, but I wasn’t happy with that idea. So I decided to stick to a Studio Ghibli theme and do something like related to the movie Spirited Away. In the movie, there is a character called No Face who goes to the bathhouse and eats a lot of food. So I thought it would be a fun idea to recreate that by making it rain food and have the player try to catch the raining food by controlling No Face. The things I need to for this game to work are:
Mouse click to catch the food and make it disappear
A counter to keep track of the score
Game states (title screen, game playing, and game over)
Restart and exit game buttons
Food “raining”
The character No Face for the player to control
Adding sound effects and background music
There might be more things I need to add, but this is the general idea. So far I have playing mode, the character, and the food raining. I still need to work on how No Face will catch the food, the counter, and the different states of the game.
Code Highlight
// From The Coding Train Flappy Bird Coding Challenge:
// https://editor.p5js.org/codingtrain/sketches/FtBc2BhHM
for(let i = addFoods.length - 1; i >= 0; i--) {
addFoods[i].rainingFood();
addFoods[i].show();
}
// A new food is pushed every time the frameCount is divisible by 50 or 100
if(frameCount % 50 == 0) {
addFoods.push(new Food());
} else if(frameCount % 100 == 0) {
addFoods.push(new Food());
}
// print(frameCount);
I used Object Oriented Programming for the food so I can add food images to the canvas based on the frame count. This code snippet I got from a Coding Train video, which was super helpful for my game.
for (let i = 0; i < 5; i++) {
foods[i] = loadImage("Assets/food" + i + ".png")
}
Another part that I want to highlight is this chunk of code, which is my favorite hack. It’s such a smart and efficient idea to loop through the images by using the same name for the food images.
Future Directions
Since this is a progress check, I still have a lot to work on. For instance, making this an actual game where the food can be eaten and there’s an end. It will be a challenge, but hopefully I can figure it out. I was thinking of adding another object that can injure the character and take away points or lives, but I’m going to stick with figuring out how to catch the food first.
This week’s assignment required us to implement the notion of object-oriented programming, which includes utilizing classes and arrays. Given how beneficial and ambitious it could be, I was excited to delve into this coding section. My intention was to focus on implementing the concept more than forcing my usual out-of-the-box idea that might jeopardize my understanding of it. I decided to present an art-motivated sketch representing faded COVID-like cells (green shades) all over the canvas representing how this prominent virus has been a part of our lives and will slowly fade away.
code:
//initiate variables
var circ;
function setup() {
createCanvas(600, 600);
//setup a circle with attributes with color range randomness (greenish)
circ = new Circ(width, width, 0, 0, [random(35), random(110), random(35)], 0);
noLoop();
}
function draw() {
background(circ.color);
//translate origin of canvas
translate(width / 4, height / 4);
circ.createChildren();
circ.show();
}
class Circ {
//set constructors and default positioning
constructor(circW, circWPar, circXPar, circYPar, color, circNo) {
this.circNo = circNo;
this.circW = circW;
this.circXPar = circXPar;
this.circYPar = circYPar;
if (circNo == 1) {
this.x = random(-width + circW, width);
this.y = random(-height + circW, height);
} else {
this.x = random(-circWPar / 3 + circW, circWPar / 3, -circW);
this.y = random(-circWPar / 3 + circW, circWPar / 3, -circW);
}
this.color = color;
this.children = [];
}
createChildren() {
//set amount of circles and how recursive
if (this.circW > 15) {
for (var i = 0; i < 2000; i++) {
var color = [this.color[0] + random(110), this.color[1] + random(120)];
var circ = new Circ(
this.circW * random(0.02, 0.6),
this.circW,
this.x,
this.y,
color,
this.circNo + 1
);
if (i == 0) {
this.children.push(circ);
} else {
for (var j = 0; j < this.children.length; j++) {
//keep values + using absuloute function
if (
abs(circ.x - this.children[j].x) <
circ.circW / 2 + this.children[j].circW / 2 &&
abs(circ.y - this.children[j].y) <
circ.circW / 2 + this.children[j].circW / 2
) {
break;
} else if (j === this.children.length - 1) {
this.children.push(circ);
}
}
}
}
}
}
show() {
noStroke();
fill(this.color);
translate(this.circXPar, this.circYPar);
circle(this.x, this.y, this.circW, this.circW, this.circW / 20);
for (var i = 0; i < this.children.length; i++) {
push();
this.children[i].createChildren();
this.children[i].show();
pop();
}
}
}
method:
It was essential to find a way to take advantage of how beneficial the functionality of classes and arrays are, so I ended up classifying circles and assigning constructors to particular colors, sizes, and recurrences. I proceeded by saving the outcomes on the array, which was beneficial to have different results on each sketch run.
sketch:
The following pictures preview different art sketch runs from the program, all possessing a green tint with various shades:
future improvements:
Presenting an animation, perhaps a pulsing effect would be fun, indicating how ‘alive’ the cells are. Maybe also making them interactive with the user’s mouse.