diff --git a/.gitignore b/.gitignore
index a1d5a715..0ac58eae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ package-lock.json
package.json
.hugo_build.lock
+hugo.exe
diff --git a/README.md b/README.md
index ae016daa..7f5636b4 100644
--- a/README.md
+++ b/README.md
@@ -6,5 +6,4 @@ A platform for the various events, resources and workshops that the Learning Bra
* [Game Dev Series](https://learning.mcpt.ca/game-dev/)
* [Game Jam](https://learning.mcpt.ca/game-jam/)
-* [LyonHacks](https://learning.mcpt.ca/lyon-hacks/)
-* [Mentorship](https://learning.mcpt.ca/mentorship/)
+* [LyonHacks](https://learning.mcpt.ca/lyon-hacks/)
\ No newline at end of file
diff --git a/content/_index.md b/content/_index.md
index ad51f685..b6684ee1 100644
--- a/content/_index.md
+++ b/content/_index.md
@@ -6,12 +6,22 @@ title = "Home"
---
The Learning Branch is here to help you through the ICS curriculum while also expanding your world of coding knowledge in an interactive, informative, and (most importantly) fun way!
-{{% notice info "Next Event: Game Series - Part II" %}}
-Join us on **Thursday, December 2nd** at **4:15pm** for our **Game Dev** series, as you’ll build your very own tower defense game! Get ready for an hour of interactive learning suited for all skill levels, preparing you for our keynote Game Jam during the Winter Break!
+{{% notice info "Next Event: Holiday Game Jam" %}}
+Starting **December 27th** at noon, dive into our Game Jam, the finale of our 2021 school year, where **you** and up to **3 others** will have the chance to build games for up to **$100** in **prizes**! Spanning **96 hours**, get ready for fun activities, creative workshops, and more!
+
+More info is available on our blog post [here](game-jam).
+{{% /notice %}}
+
+{{% notice tip "Make sure to sign up!" %}}
+
+Make sure you've signed up [here](https://mcpt.ca/gamejam) so that you will be able to participate!
+
+Signups close on **December 26th**, at **11:59 PM!**
+
+{{< countdown "That's in: " "Dec 27 2021 00:00:00 EST" "" >}}
{{% /notice %}}
### Jump To:
* [Game Series](game-dev)
* [Game Jam](game-jam)
-* [LyonHacks](lyon-hacks)
-* [Mentorship](mentorship)
+* [LyonHacks](lyon-hacks)
\ No newline at end of file
diff --git a/content/game-dev/leaderboard.md b/content/game-dev/leaderboard.md
index 28f71da1..4fdc4077 100644
--- a/content/game-dev/leaderboard.md
+++ b/content/game-dev/leaderboard.md
@@ -10,7 +10,8 @@ weight = 1
{{% notice info %}}
Participate to earn **GAME DEV POINTS**, which will award you with a *surprise* during the [Game Jam](/game-jam)!
-Each participation will be worth **200** points, with winners of the *special challenges* earning an extra **100** points per challenge.
+Each participation will be worth **200** points, with winners of the *special challenges* earning bonus points.
+
{{% /notice %}}
{{< users-table >}}
diff --git a/content/game-dev/part-iii/MakingLevels.files/APIs.zip b/content/game-dev/part-iii/MakingLevels.files/APIs.zip
new file mode 100644
index 00000000..67521481
Binary files /dev/null and b/content/game-dev/part-iii/MakingLevels.files/APIs.zip differ
diff --git a/content/game-dev/part-iii/MakingLevels.md b/content/game-dev/part-iii/MakingLevels.md
new file mode 100644
index 00000000..f05381ef
--- /dev/null
+++ b/content/game-dev/part-iii/MakingLevels.md
@@ -0,0 +1,76 @@
++++
+title = "Making Levels"
+weight = 5
++++
+
+---
+
+{{%attachments style="blue" title="Powerups Template Code" /%}}
+
+### Making Levels
+
+Now that we are in the 3rd session, some levels in the game are long overdue. Now that we are more familiar with the concept of arrays and arraylists, let’s see how we can implement them
+
+{{% notice info "Key Concepts" %}}
+
+1. How do we represent a level in code? And how do we store information for it?
+2. What happens when a level is being played, and when a level isn't being played?
+3. How can we generate levels easily?
+
+{{% /notice %}}
+
+##### Representing a level in code
+
+Before we get to coding anything, we must understand what a level actually is. In bloons tower defense, levels are best defined by the balloon wave that you must pop. For example, in level 1, there are weak balloons that you must pop. In level 2, the balloons are stronger and more numerous. This trend continues as you move to higher and higher levels.
+
+We already have 1 default level that starts immediately when you open the game. What we need to do now is make many distinct levels, and have some control over when each level starts.
+
+##### Making many levels.
+
+As you might have seen from previous lessons, a single balloon is represented by an array of floats, which is a list of values that contain information about the position and state of the balloon. These float arrays were then stored in a larger, master array, which contained all the “balloons” for the default level.
+
+You can thing of it kind of like a folder/file system
+
+
+
+Now, you could say we need a larger “folder” to store many level folders that contain balloon data. In other words, we need a bigger container that will store each level
+
+
+
+To do this, we can make some new arrays similar to the default level, and put all of those arrays into a larger array, called levels. We can represent this structure using a 3D array.
+
+```Java
+ArrayList> levels = new
+ArrayList< ArrayList< float[]>>();
+/*Main “levels folder” //A Level folder //List of balloon
+which can store many which can store information that
+levels many balloons defines a balloon
+*/
+```
+
+We also need to control what can happen when a level is and isn't being played. For that, we have a boolean isPlaying variable, which disables certain features when false. For example, if a person is not playing yet, the code for _tower tracking_ and _projectile creation_ are essentially turned off, which makes sense since there are no balloons to turn to and shoot new projectiles at. The isPlaying variable is turned to true when the “next level” button pressed, and false when there are no more balloons in the level.
+
+##### Levels API
+
+Now, a way to quickly create balloons for a level would be very nice if you wanted to make a playable game. For that, we have another, very simple API which you can use in the createWaves function in Balloons.pde.
+
+```java
+void createWaves(){
+//1
+createLevels(numberOfLevels);
+
+//2
+createBalloons(
+ levelTheBalloonsAreFor,
+ numberOfBalloons,
+ delayOfTheFirstBalloon,
+ delayBetweenBalloons,
+ speedOfTheBalloons,
+ hP
+ );
+}
+```
+
+The first function, **createLevels()**, will help determine how many levels there are in your game. Once you have called/used this function, you can call the **createBalloons()** function to make balloons for a specific level. First, you specify the level you want to assign balloons to (starting from 0, since we are dealing with arrays!). Then you specify the number of balloons and the first balloon’s delay, which essentially controls when this collection of balloons will appear on the screen. Then you can tell the function the delay between the balloons, which controls how spaced apart they are, and also the speed and hp of each balloon.
+
+And there you go! Now you can create your own levels!
diff --git a/content/game-dev/part-iii/PathCreationAPI.files/APIs.zip b/content/game-dev/part-iii/PathCreationAPI.files/APIs.zip
new file mode 100644
index 00000000..67521481
Binary files /dev/null and b/content/game-dev/part-iii/PathCreationAPI.files/APIs.zip differ
diff --git a/content/game-dev/part-iii/PathCreationAPI.md b/content/game-dev/part-iii/PathCreationAPI.md
new file mode 100644
index 00000000..9a5d458a
--- /dev/null
+++ b/content/game-dev/part-iii/PathCreationAPI.md
@@ -0,0 +1,80 @@
++++
+title = "Path Creation API"
+weight = 5
++++
+
+---
+
+{{%attachments style="blue" title="Powerups Template Code" /%}}
+
+### Path Creation API
+
+Before this, balloons have moved in straight lines. That’s cool and all, but what if we wanted to implement some maps like this?
+
+
+
+Obviously, straight lines are not going to cut it. Instead, we need a way to implement **curves**, and we need to have some tools that will make it easy for us to smoothly connect them. As a result, we developed an API (a set of useful functions) that will help us do this.
+
+```java
+//1
+addLine(startPointX,startPointY,endPointX,endPointY);
+
+//2
+addArc(startPointX,startPointY,centerPointX,centerPointY, angleOfRotation);
+
+//3
+addSmoothLine(lineLength);
+
+//4
+addSmoothArc(distanceAwayFromPathTip, angleOfRotation);
+```
+
+{{% expand "addLine(startPointX, startPointY, endPointX, endPointY);" "false" %}}
+This is the most straightforward function. Just define a line with 2 points - a start and end point - and it will draw it for you.
+
+Eg. addLine(0,200,300,200); gives:
+
+{{% /expand %}}
+
+{{% expand "addArc(startPointX, startPointY, centerPointX, centerPointY, angleOfRotation);" "false" %}}
+
+To draw a circular path segment using this function, you must first define where the segment starts - startPoint. Then, you define the center point of the arc (centerPoint), which is the point from where the arc is curved around. Finally you have an angle of rotation, which determines how much curve is in the arc.
+
+Eg. addArc(0,200,200,100,-radians(90)); gives:
+
+
+**Note**: The _radians()_ function simply converts angles from degrees to radians, which is perferred by Processing. Also, the point you see in the center of the arc is just for understanding - it will not appear when you are making your own path.
+{{% /expand %}}
+
+{{% expand "addSmoothLine(lineLength);" "false" %}}
+
+This function is used to extend the paths of arcs in a smooth way. The lineLength argument (or input) is used to describe how far the arc should be extended
+
+Eg.
+
+addArc(0,200,200,100,-radians(90));
+
+addSmoothLine(200);
+
+gives:
+
+{{% /expand %}}
+
+{{% expand "addSmoothArc(distanceAwayFromPathTip, angleOfRotation);" "false" %}}
+
+This function draws a smooth arc from an already existing line or arc. To understand it, you must imagine an arrow that starts from the path’s end, and points in a direction so that it is a +90 degree rotation from the path.
+
+
+
+{{% notice info "Processing Angles"%}}
+Remember that Processing angles start from east and increase counterclockwise. This should be taken into account when imagining the arrows.
+{{%/notice%}}
+
+_distanceAwayFromPathTip_ describes how far away the **center** of the arc should be from the tip of the path, along the line described by the red arrow. If you would like to have the center of the arc be in the opposite direction of the arrow, simply put a negative value.
+
+//addSmoothArc(100,radians(105)); and addSmoothArc(-100,-radians(105)); respectively give:
+
+
+As you can see, the addSmoothCurve() function can be used with other arcs to make more complex curves. You can also put many addSmoothCurve()’s together to make interesting shapes.
+
+{{% /expand %}}
diff --git a/content/game-dev/part-iii/_index.md b/content/game-dev/part-iii/_index.md
index 858df945..046a9e78 100644
--- a/content/game-dev/part-iii/_index.md
+++ b/content/game-dev/part-iii/_index.md
@@ -1,5 +1,6 @@
+++
chapter = true
+alwaysopen = false
title = "Part III"
pre = "3. "
weight = 3
@@ -8,8 +9,10 @@ weight = 3
### Game Series
# Part III
-{{% notice info "Coming to WLMAC on December 16th, 2021!" %}}
+{{% notice info %}}
*Premiere, Interactive, Tower-Defense Game Workshop for Programmers of All Levels...*
{{% /notice %}}
-
\ No newline at end of file
+This event has ended on **December 16th, 2021**. You can find a recording of the event [here](https://drive.google.com/file/d/1Jo2fXD_-bBMKOeHWMH0GFvbqgpJ_fwa9/view)!
+
+
diff --git a/content/game-dev/part-iii/challenge.md b/content/game-dev/part-iii/challenge.md
new file mode 100644
index 00000000..1de7bf1e
--- /dev/null
+++ b/content/game-dev/part-iii/challenge.md
@@ -0,0 +1,29 @@
++++
+title = "Challenges"
+weight = 7
++++
+---
+## Challenge Prompt
+
+Congratulations on making it to the end of the series!
+
+To celebrate, as well as help you prepare for the Game Jam, we'd like to test your game development skills and see how well you can make this game _your own_!
+
+{{% notice info "Challenge" %}}
+Create your own spin-off of the game! This is more or less an open-ended question, you are free to do whatever you want.
+
+The first place winner will earn **400** points, with second place earning **375**, third place earning **350**, etc... with a minimum of **100** points being awarded for successfully attempting the challenge, **on top** of the **200** points for participation.
+
+Placements will be determined by a combination of creativity, number of features implemented / changed, as well as _balancing_ of the game; making sure the game is not too easy or too hard.
+
+We suggest any of the following:
+* Creating a new path
+* Creating a new power-up
+* Creating a unique set of upgrades
+* Modifying balloon waves / creating new waves for progression
+
+Submit a GitHub / Google Drive link to the form that will be provided during the workshop to earn points!
+
+**Full Source Code (For reference):** https://github.com/mcpt/game-dev/tree/main/PartThree
+{{% /notice %}}
+
diff --git a/content/game-dev/part-iii/currency.md b/content/game-dev/part-iii/currency.md
new file mode 100644
index 00000000..1a495985
--- /dev/null
+++ b/content/game-dev/part-iii/currency.md
@@ -0,0 +1,33 @@
++++
+title = "More Currency"
+weight = 2
++++
+
+---
+
+### Currency for Removing Towers
+
+Sometimes, we make mistakes. Maybe you accidentally sour a relationship with a close friend, play the wrong chord in a concert after practicing for months, or you drop your lovingly handcrafted bridge for your ISP in a puddle. While we can’t help you with any of those things in this workshop, we can help you when you change your mind on keeping a tower on the board.
+
+Recall that we used two ArrayLists to store the data for our towers: one to store the position as a PVector and the other to store data of the tower as an integer array.
+
+```Java
+//notice that we could have just included the PVectors in the towerData array as x and y values
+//we decided to keep it like this since we are building off our previous workshops
+ArrayList towers; // Towers that are placed down
+ArrayList towerData;
+```
+
+To remove a tower from the board, we can simply remove the values at that specified index. Since our towers are 1-indexed, we can just take out the (i-1)th tower from our ArrayList storing the position and the ArrayList storing the tower data. When removing, we set a location to be the remove button, known as `removeLocation`. This will remove the tower and give a certain amount of currency when removing it. In our code, we decided that your current balance would go up by the level of upgrade multiplied by the tower’s price divided by two. The following is our method for checking where to remove.
+
+```Java
+void removeCheck() {
+ if((removeLocation.x - 35 <= mouseX && mouseX <= removeLocation.x + 35 && removeLocation.y - 12 <= mouseY && mouseY <= removeLocation.y + 12) && mousePressed && towerClicked != -1) {
+ int[] temp = towerData.get(towerClicked);
+ currentBalance += temp[upgrade] * towerPrice[temp[projectileType]] / 2;
+ int temp1 = towerClicked; towerClicked = -1;
+ towerData.remove(temp1); towers.remove(temp1);
+ }
+}
+
+```
diff --git a/content/game-dev/part-iii/introduction.files/AllTemplates.zip b/content/game-dev/part-iii/introduction.files/AllTemplates.zip
new file mode 100644
index 00000000..2ce0403c
Binary files /dev/null and b/content/game-dev/part-iii/introduction.files/AllTemplates.zip differ
diff --git a/content/game-dev/part-iii/introduction.md b/content/game-dev/part-iii/introduction.md
new file mode 100644
index 00000000..dee40b8d
--- /dev/null
+++ b/content/game-dev/part-iii/introduction.md
@@ -0,0 +1,77 @@
++++
+title = "Introduction"
+weight = 1
++++
+
+---
+
+## Game Dev - Season IV
+
+Welcome to the return of MCPT's Game Dev Series! Over the course of 3 workshops, we have something for everyone, whether you’re a beginner or an experienced coder. Inspired by **Bloons Tower Defense**, you will learn how to code your very own tower-defense game in Processing!
+
+{{% notice tip "Challenges" %}}
+Earn up to **400** bonus points with our open-ended challenge! Stay tuned for more info!
+{{% /notice %}}
+
+### Demo
+
+{{< p5js-src "/content/p5-js-demo/part3.js" >}}
+
+{{% expand "What is a tower-defense game?" %}}
+{{% notice info %}}
+
+A **tower-defense game** is a type of strategy game where players will place down "towers", usually with a form of in-game currency that will defend against a set of enemies. If you fail to defeat the enemies, you will typically lose health - when you reach zero, it's **Game Over.**
+
+In **Bloons Tower Defense**, the towers are monkeys, and the enemies are balloons, which spawn in rounds and travel along a pre-determined path. Your goal as a player is to strategically spend your currency while placing towers in effective locations, using their special abilities to win the game!
+
+In this workshop, we will cover the fundamental aspects of a tower-defense game, and in future workshops, we will cover special abilities and ways that you can customize your game to make it your own!
+
+{{% /notice %}}
+{{% /expand %}}
+
+Everything will be made step-by-step allowing you to learn and see the progress of the game. The speed of the workshop is not set and will be altered at a moment's notice in order, so don’t worry if you don't get everything immediately!
+
+This website is interactive and holds all the content that we will be going over. Flip along and test code just as we do! This site will also be accessible at any time for you to look back and review content.
+
+### Additional Info
+
+{{% expand "What you will learn" %}}
+{{% notice info %}}
+In the third part of this workshop, you will finish up the game, adding more intricate paths, as well as two finalizing features: power-ups and tower upgrades!
+This includes:
+* How to use a pre-built library to create circular and disjoint paths
+* How to use nested ArrayLists to create multiple waves
+* How to modify our code for towers and projectiles to create upgrades
+* How to create spikes and a new interface for custom power-ups
+ {{% /notice %}}
+ {{% /expand %}}
+
+
+{{% expand "What you will need" %}}
+{{% notice info %}}
+
+You will need to download [Processing 4.0](https://processing.org/download) from https://processing.org/download, or have Processing 3 or newer installed.
+
+In our workshop, we will be adding code to several **templates**, which you can download below.
+
+{{% /notice %}}
+
+{{%attachments style="blue" title="All Templates" pattern=".*zip" /%}}
+
+{{% /expand %}}
+
+
+{{% expand "The Game Jam" %}}
+{{% notice tip %}}
+
+Ultimately, the Game Dev Series will build up to a week-long Game Jam during the Winter Break. During the Game Jam, you'll develop and create the best game you can!
+
+There will be many prizes and awards so make sure to participate!
+
+In addition, by participating in the Game Dev Series and completing challenges, you will earn points which will help you win awards during the Game Jam.
+
+PS. This is the last chance to get extra points before our Winter Game Jam, so be sure to try the challenges!
+
+{{% /notice %}}
+{{% /expand %}}
+
diff --git a/content/game-dev/part-iii/powerups.files/Powerups_Template.zip b/content/game-dev/part-iii/powerups.files/Powerups_Template.zip
new file mode 100644
index 00000000..3d57f423
Binary files /dev/null and b/content/game-dev/part-iii/powerups.files/Powerups_Template.zip differ
diff --git a/content/game-dev/part-iii/powerups.md b/content/game-dev/part-iii/powerups.md
new file mode 100644
index 00000000..6d516e79
--- /dev/null
+++ b/content/game-dev/part-iii/powerups.md
@@ -0,0 +1,95 @@
++++
+title = "Powerups"
+weight = 6
++++
+
+---
+
+{{%attachments style="blue" title="Powerups Template Code" /%}}
+
+### Road Spikes
+One feature in tower defense games is road spikes which are intended for use in urgent situations, where balloons are approaching the end of the path and need to be popped quickly. The implementation of road spikes is similar to towers, but the drag-and-drop interface was slightly different, since spikes are placed ***on*** the path rather than off, and they do not shoot anything, but rather sit on the path until a balloon comes by.
+
+The first modification to the drag-and-drop that needs to be made is to ensure that spikes are placed on the path. Recall beforehand that we checked to see if towers were *not* on the path by ensuring that its distance from the path is greater than the path's radius. Similarly, we will check to see if the distance of the spike to the path is ***less than*** the radius.
+
+{{% expand "See code for checking spike drop locations" "false" %}}
+```java
+// Checks if the location of the spike is on the path
+boolean legalSpikeDrop() {
+ PVector heldLocation = spikeLocation;
+ return shortestDist(heldLocation) <= PATH_RADIUS;
+}
+```
+{{% /expand %}}
+
+Next, we need to head to the `Balloons.pde` file to have every balloon check if they are within the range of a spike.
+
+{{% expand "See code for checking spike pops" "false" %}}
+```java
+PVector position = getLocation(balloon[distanceTravelled]); // Get the current location of the balloon
+if (balloonSpikeCollision(position)) { // Check for a collision between the balloon and any spike
+ handleBalloonPop(); // Award player for popping the balloon
+ balloons.remove(i); // Balloon has been popped! Remove it from the list
+ i--; // Obligatory index fixing due to ArrayList indexing
+ continue;
+}
+
+boolean balloonSpikeCollision(PVector position) {
+ for (int i = 0; i < spikeLocations.size(); i++) {
+ PVector spikeLocation = spikeLocations.get(i);
+ if (dist(position.x, position.y, spikeLocation.x, spikeLocation.y) <= PATH_RADIUS) { // See if there is a collision between the spike and the balloon
+ spikeData.set(i, spikeData.get(i) - 1);
+ return true; // // Spike has popped the balloon!
+ }
+ }
+ return false;
+}
+```
+{{% /expand %}}
+
+### Balloon Slowdowns
+The function of the balloon slowdown is to temporarily decrease the moving speeds of all the balloons to half their original speed. To do this, we will keep a `slowdownAmount` variable which will be multiplied into the speed of every balloon at all times.
+
+```java
+float travelSpeed = balloon[speed] * slowdownAmount; // Slow down the balloon if the slowdown powerup is engaged
+balloon[distanceTravelled] += travelSpeed; // Increases the balloon's total steps by the speed
+```
+
+Initially the `slowdownAmount` variable will be set to `1` to effect no change on the balloons' speeds. In the `handleSlowdownPress()` method, we will set `slowdownAmount` to `0.5` in order to multiply the speeds of all balloons by half.
+
+{{% expand "See code for handleSlowdownPress()" "false" %}}
+```java
+void handleSlowdownPress() {
+ if (withinSlowdownBounds() && powerupCount[slowdown] > 0 && slowdownAmount == 1) {
+ powerupCount[slowdown]--;
+ slowdownAmount = 0.5;
+ slowdownRemaining = slowdownLength * 60;
+ }
+}
+```
+{{% /expand %}}
+
+To make sure slowdowns are temporary, we will set a `slowdownRemaining` variable to keep track of how many frames of slowdown is left. Since the Processing frame-rate is 60 frames per second, one second will go by in 60 frames. Whenever the slowdown button is pressed, we need to refill `slowdownRemaining` with 60 times the length of a slowdown session in seconds. At each frame that goes by, `slowdownRemaining` will be decreased by 1. Once it reaches 0, the slowdown effect will be cancelled.
+
+{{% expand "See code for handling each frame of slowdown" "false" %}}
+```java
+void handleSlowdown() {
+ if (slowdownRemaining > 0) {
+ slowdownRemaining--;
+
+ if (slowdownRemaining == 0) { // Once slowdown has ended...
+ slowdownAmount = 1; // Revert to original speed
+ }
+ }
+}
+```
+{{% /expand %}}
+
+### Tower Speed Boosts
+This powerup will give every tower a speed boost for a limited amount of time. More specifically, the cooldown time for each tower will be reduced, allowing towers to shoot projectiles more quickly. Similar to the slowdown powerup, we will keep track of a `speedBoostAmount` which will be multiplied into the cooldowns of every tower on the field. We will modify the `Projectiles.pde` file to account for the speedboost value.
+
+```java
+data[cooldownRemaining] = (int)(data[maxCooldown] * speedBoostAmount); // Resets the cooldown accounting for the speedBoostAmount factor.
+```
+
+Make sure to cast the cooldown value into an integer since the `data` array stores integers rather than floating point numbers. Other than this slight change, all other parts of the powerup's implementation are identical to the slowdown powerup.
\ No newline at end of file
diff --git a/content/game-dev/part-iii/upgrades.files/UpgradesTemplate.zip b/content/game-dev/part-iii/upgrades.files/UpgradesTemplate.zip
new file mode 100644
index 00000000..ea0bf1ca
Binary files /dev/null and b/content/game-dev/part-iii/upgrades.files/UpgradesTemplate.zip differ
diff --git a/content/game-dev/part-iii/upgrades.md b/content/game-dev/part-iii/upgrades.md
new file mode 100644
index 00000000..1fae8b9a
--- /dev/null
+++ b/content/game-dev/part-iii/upgrades.md
@@ -0,0 +1,193 @@
++++
+title = "Tower Upgrades"
+weight = 2
++++
+
+---
+
+{{%attachments style="orange" title="Upgrades Template" /%}}
+
+### Introduction
+Personally, the part I enjoyed most about Bloons Tower Defence is trying out each path for each tower to see what they all do. Now, in Part III, we’ve finally implemented upgrades. We’ll be teaching you how to edit the different values that you may want to use when making your own upgrade paths.
+
+### Framework for Upgrades
+
+Before making upgrades, we need to add a state that stores the value of the tower currently clicked. Think back to BTD and how clicking on a tower will show the upgrades that you can get. For our game, we want to have a similar thing so that we don’t end up upgrading all towers at the same time.
+
+To do this, let’s add a new variable called `towerClicked`. If we set no tower to -1, then our tower will have a default value of -1 and will also become -1 when we click on anything that is not a tower. To check. Here is the implementation of `towerClicked`:
+
+{{% expand "See code" "false" %}}
+```Java
+int towerClicked = -1; //no tower clicked
+void towerClickCheck() {
+ if (mousePressed) {
+ towerClicked = -1;
+ }
+ for (int i = 0; i < towers.size(); i++) {
+ float xPos = towers.get(i).x, yPos = towers.get(i).y;
+ if(pointRectCollision(mouseX, mouseY, xPos, yPos, towerSize) && mousePressed) {
+ // Drawing the tower range visually
+ towerClicked = i; //clicked the ith tower, from indices 0 to towers.size() - 1
+ }
+ }
+}
+```
+{{% /expand %}}
+
+
+Now, let’s add an extra set of values to our `towerData` integer arrays that represent our tower’s current upgrade level. Every time we upgrade our current tower, we increment that tower’s upgrade index in its respective integer array in `towerData`.
+
+{{% expand "See code" "false" %}}
+```Java
+final int cooldownRemaining = 0, maxCooldown = 1, towerVision = 2, projectileType = 3, upgrade = 4;
+//initial values of 1 for upgrade since they are level 1
+int[] makeTowerData(int towerID) {
+ if (towerID == def) {
+ return new int[] {
+ 10, // Cooldown between next projectile
+ 10, // Max cooldown
+ towerVisions[def], // Tower Vision
+ 0, // Projectile ID
+ 1
+ };
+ } else if (towerID == eight) {
+ return new int[] {
+ 25, // Cooldown between next projectile
+ 25, // Max cooldown
+ towerVisions[eight], // Tower Vision
+ 1, // Projectile ID
+ 1
+ };
+ } else if (towerID == slow) {
+ return new int[] {
+ 35,
+ 35,
+ towerVisions[slow], // Tower Vision
+ 2,
+ 1
+ };
+ }
+ return new int[] {}; //filler since we need to return something
+}
+```
+{{% /expand %}}
+
+To do this, we need to create an `upgradeCheck()` method that checks if you click the upgrade button. To check for upgrades, we just need to check if there is a valid tower under `towerClicked` and if we click on the button that will represent our upgrade. If we do click on it, then we’ll take the value of the towerData at `towerClicked` and edit the values there. We increment the index representing upgrades, which will affect the types of upgrades we do. For this workshop, we decided that upgrades would cost half the amount of the tower cost, but you can play around with this value.
+
+{{% expand "See code" "false" %}}
+```Java
+if (currentBalance >= towerPrice[temp[projectileType]] / 2) {
+ temp[upgrade]++; currentBalance -= towerPrice[temp[projectileType]] / 2 ;
+}
+```
+{{% /expand %}}
+
+### Different Upgrades
+
+For upgrades, our default tower first increases its attack speed and then increases its damage by one every upgrade after that. For our eight shot tower, it first increases its range, then the number of shots from 8 to 16, then increases the damage. For our slow tower, the first upgrade will increase the slow from 70% of the balloon’s original speed to 50%, and then it will increase the range for every update after that. While some of the updates are easily done by editing values in `towerData`, other values require for us to go into `Projectile.pde` and make a few edits.
+
+To increase the attack speed, we just need to reduce the `maxCooldown` of the tower in the towerData. Increasing the range just requires us to increase our value at `towerVision`. Here is the implementation:
+
+{{% expand "See code" "false" %}}
+```Java
+int[] temp = towerData.get(towerClicked);
+temp[maxCooldown] = 8; //increases attack speed
+temp[towerVision] += 50; //increases range
+```
+{{% /expand %}}
+
+Increasing damage, number of shots, and slow reduction is a bit harder. Let’s look at damage first. To make things simpler, we decided to create variables to represent the default damage of our towers without any upgrades.
+
+{{% expand "See code" "false" %}}
+```Java
+int defdmg = 6, eightdmg = 4, slowdmg = 1;
+```
+{{% /expand %}}
+
+Notice how every time we want to shoot a projectile, we create a new amount of damage for that projectile that represents how much damage that projectile does. Usually it’s just `damage = defdmg`, but let’s change this up a bit to make it better. Recall that we have a value that represents our upgrade level in `towerData`. For our situation, since it is always adding more damage including and after the 2nd upgrade, we can set a new equation representing what our new value for damage should be:
+
+{{% expand "See code" "false" %}}
+```Java
+if (data[upgrade] >= 3) { //notice that it’s 3 since we are level 3 at the second upgrade
+ damage = defdmg + data[upgrade] - 2; //we subtract 2 because at level 3, we want to add 1 damage
+}
+```
+{{% /expand %}}
+
+The same logic can be applied to our number of shots and slow percent. We can set a default number of shots and slow percent, and edit those values depending on the upgrade level. The implementation is shown below:
+
+{{% expand "See code" "false" %}}
+```Java
+//eight shot implementation
+int shots = 8;
+int curShots = shots;
+if (data[upgrade] >= 3) {
+ curShots = shots + 8;
+}
+//slowing implementation
+float slowPercent = 0.7;
+float slowNum = slowPercent;
+if (data[upgrade] >= 2) {
+ slowNum -= 0.2;
+}
+```
+{{% /expand %}}
+
+Here is our final upgrade check, including checking whether you click the button and upgrading things accordingly:
+
+{{% expand "See code" "false" %}}
+```Java
+void upgradeCheck() {
+ if((upgradeLocation.x - 43 <= mouseX && mouseX <= upgradeLocation.x + 43 && upgradeLocation.y - 12 <= mouseY && mouseY <= upgradeLocation.y + 12) && mousePressed && towerClicked != -1) {
+ int[] temp = towerData.get(towerClicked);
+ if (currentBalance >= towerPrice[temp[projectileType]] / 2) {
+ temp[upgrade]++; currentBalance -= towerPrice[temp[projectileType]] / 2 ;
+ if (temp[projectileType] == 0) {
+ if (temp[upgrade] == 2) { //first upgrade
+ temp[maxCooldown] = 8; //increases attack speed
+ }
+ } else if (temp[projectileType] == 1) {
+ if (temp[upgrade] == 2) { //second upgrade
+ temp[towerVision] += 50;
+ }
+ } else if (temp[projectileType] == 2) {
+ if (temp[upgrade] > 2) {
+ temp[towerVision] += 50;
+ }
+ }
+ towerData.set(towerClicked, temp);
+ println("tower number: " + (towerClicked + 1) + ", upgrade level: " + temp[upgrade]);
+ }
+ }
+}
+```
+{{% /expand %}}
+
+
+### Getting a Tower’s Damage from Projectile Type
+{{% expand "See code" "false" %}}
+```java
+int dmgFromProjectileType(int type, int[] temp){
+ if(type==0) {
+ int ret = defdmg;
+ if (temp[upgrade] >= 3) {
+ ret += temp[upgrade] - 2;
+ }
+ return ret;
+ }
+ else if(type==1) {
+ int ret = eightdmg;
+ if (temp[upgrade] >= 4) {
+ ret += temp[upgrade] - 3;
+ }
+ return ret;
+ }
+ else if(type==2) {
+ return slowdmg;
+ }
+ return 0;
+}
+{{% /expand %}}
+In this return method, we calculate the damage of a tower based on the projectile type and the tower’s current level, then return it. We take in two parameters, `type` which is the projectile’s type, as well as the temp array which is the tower’s data. We have three types of projectiles, so we will create three if/else if blocks. The first block will be `type==0`, which means that it is a “default” projectile. The `ret` variable will store the tower’s damage, and it is initialized as the default projectile’s level 1 damage. We can calculate the updated damage by adding 1 point of damage for every level that the tower is above 3. We can then return the tower’s damage by returning the `ret` variable.
+This is similar for the projectile that shoots in 8 directions, and the one that slows. For `type==1`, we set ret to the default level 1 damage for this projectile `eightdmg`, add damage based on the level, and then return the updated damage value.
+For the last type of projectile that slows balloons, the if block is even simpler since the upgrades do not affect the damage, but instead change the amount it slows by. This means that we can set ret to the original damage value and then return it. Outside this method, we have a `return 0;` statement because the method needs a ‘default’ return statement. This statement won’t be reached in our code, since type can only be 0,1 or 2.
diff --git a/content/game-dev/part-iii/userInterface.md b/content/game-dev/part-iii/userInterface.md
new file mode 100644
index 00000000..03133765
--- /dev/null
+++ b/content/game-dev/part-iii/userInterface.md
@@ -0,0 +1,74 @@
++++
+title = "User Interface"
+weight = 4
++++
+
+---
+
+### Drawing Upgrade & Remove Button
+In these two methods, we will draw the buttons for upgrading and removing towers. You’ll notice that these two methods are very similar, since they do similar things.
+
+{{% expand "See code for drawing remove button " "false" %}}
+```java
+PVector removeLocation = new PVector(255, 470);
+void drawRemove() {
+ strokeWeight(1);
+ stroke(#deac9e);
+ fill(#FF6961);
+ rectMode(CENTER);
+ rect(removeLocation.x, removeLocation.y, 70, 24,5);
+ textSize(16);
+ fill(#ffffff);
+ text("Remove", removeLocation.x - 30, removeLocation.y+4);
+}
+```
+{{% /expand %}}
+
+{{% expand "See code for drawing upgrades button " "false" %}}
+```java
+PVector upgradeLocation = new PVector(145, 470);
+void drawUpgrade() {
+ strokeWeight(0);
+ stroke(0);
+ fill(#C364FF);
+ rectMode(CENTER);
+ rect(upgradeLocation.x, upgradeLocation.y, 86, 24,5);
+ textSize(16);
+ fill(255);
+ int[] temp = towerData.get(towerClicked);
+ text("Buy: $" + towerPrice[temp[projectileType]] / 2, upgradeLocation.x-40, upgradeLocation.y+4);
+}
+```
+{{% /expand %}}
+Both of these methods draw a rectangle with text. We use strokeWeight() and stroke() to change borders around the rectangles, and fill() to fill the rectangle with colour. The PVector upgradeLocation and removeLocation represent the centre of the upgrade and remove buttons respectively. We use a PVector to have a ‘centre’ coordinate to make it easier to check if the user is pressing the button (in upgradeCheck() and removeCheck() above. For the remove button, we just have to write some text to indicate that this button is for removing towers, in this case we just write “Remove” on the button. For upgrades, we also display the price of the current upgrade. This means that we have to get the cost of the upgrade, which is calculated by `towerPrice[temp[projectileType]] / 2`. Finally, we can use text()to write this to the user on the button.
+### Creating a tower UI
+Now that we have all the methods in place to upgrade and remove towers, we can draw the user interface. This UI will contain the upgrade and remove button, but also tell the user what level the tower is, as well as the damage and range of the tower when it is clicked.
+
+{{% expand "See code for drawing tower UI" "false" %}}
+```java
+// draw the tower UI - includes the remove option
+void drawTowerUI(){
+ if(towerClicked != -1) {
+ //draw outer box for upgrades
+ int[] temp = towerData.get(towerClicked);
+ stroke(#add558);
+ strokeWeight(1);
+ fill(#E7EAB5);
+ rect(200,450,216,80,3);
+ fill(#444941);
+ text("Current Level: " + temp[upgrade],98,426);
+ text("range: "+ temp[towerVision],104,446);
+ text("damage: "+ (dmgFromProjectileType(temp[projectileType], temp)),204,446);
+ strokeWeight(2);
+ stroke(#a8a89d,200);
+ line(100,453,295,453);
+
+ drawUpgrade();
+ upgradeCheck();
+ drawRemove();
+ removeCheck();
+ }
+}
+```
+{{% /expand %}}
+We start this method by reusing the towerClicked variable, which stores the towerID of the tower the user just clicked. If it is not equal to -1, that means that some tower is being clicked right now, so we can display the tower UI. We will get the tower data of this specific tower from the ArrayList of towers, and then store it in a local temp[] array. This will allow for easier use, since we will need to access the level, range, and damage of the tower. We draw the rectangle which will be the background for the tower UI, then the text indicating the tower’s stats. For the level and range stats, we can take it directly from the temp array since both of those values are stored. To display the damage of the projectile, we call the `dmgFromProjectileType()` method (explained above), then display that value. To end off this method, we draw a line to separate the stats and the buttons, then we can call the drawUpgrade() and drawRemove() methods to draw the upgrade and remove buttons respectively. Finally, the upgradeCheck() and removeCheck() methods are called to make the buttons actually work.
diff --git a/content/game-jam/_index.md b/content/game-jam/_index.md
index f621ca41..d136a8e7 100644
--- a/content/game-jam/_index.md
+++ b/content/game-jam/_index.md
@@ -6,4 +6,40 @@ weight = 5
# Game Jam
---
-Coming Soon!
\ No newline at end of file
+
+
+Introducing Mackenzie’s first-ever **Holiday Game Jam!**
+
+Work together with up to **4** of your friends over the course of **96 hours** to create a **video game** just for glory and for the chance to win prizes!
+
+
+
+##### **What is a game jam?**
+---
+The **Holiday Game Jam** is both a competitive and social event where teams build and design video games and then **pitch** their ideas to the group. It’s a great opportunity to make new friends, create something cool, and learn a thing or two! Whether you’re proficient with game development or are trying it out for the first time, we have something for everyone.
+
+New to game dev? No worries! We’ll be running many different **workshops** over the course of the **Holiday Game Jam**, topics include how to create Tetris controls or program jump physics! If you ever feel tired or want to take a quick break from the competition we’ll also be hosting many fun activities and games for you to participate in! And for those who participated in MCPT’s Game Dev series, your points will carry over into this event.
+
+Didn’t get the chance to check out our game dev series? Visit [our site](/game-dev) to check out our lessons.
+
+##### **Dates and Times**
+---
+The competition will run from **December 27th** at noon to **December 31st** at noon. Teams will have the full 96 hours to design a game and pitch. Note that you may only start working on your project once the competition window begins on December 27th at noon. A full itinerary will be released closer to the event date.
+
+{{< countdown "Event starting in: " "Dec 27 2021 12:00:00 EST" "" >}}
+
+
+##### **Registration**
+---
+Sign up in **teams of up to 4** by **December 26th** at midnight. There is no registration fee; all students currently attending William Lyon Mackenzie can participate!
+
+Register with your student email through the following link:
+> https://mcpt.ca/gamejam
+
+##### **Judging and Prizes**
+---
+There will be a **theme** for the event, which will be announced during our opening ceremony. Creativity counts - original ideas count even more. For a full list of judging categories, rules, and criteria, visit our [Devpost](https://holiday-game-jam.devpost.com/) page.
diff --git a/content/mentorship/_index.md b/content/mentorship/_index.md
index 90c9f36d..84b234cc 100644
--- a/content/mentorship/_index.md
+++ b/content/mentorship/_index.md
@@ -2,6 +2,7 @@
chapter = true
title = "Mentorship"
weight = 10
+draft = true
+++
# Mentorship Program
diff --git a/content/p5-js-demo/part3.js b/content/p5-js-demo/part3.js
new file mode 100644
index 00000000..8f8964f7
--- /dev/null
+++ b/content/p5-js-demo/part3.js
@@ -0,0 +1,1602 @@
+
+// Program main method
+function setup() {
+ initializeFields();
+ createCanvas(800, 500);
+
+ initDragAndDrop();
+ initPath();
+ createWaves();
+}
+let started;
+let frames = 0;
+
+function draw() {
+ frames++;
+ background(color(0xad, 0xd5, 0x58));
+
+ if(!started) {
+ const sz = 40 + Math.sin(frames / 15);
+ textSize(sz);
+ textAlign(CENTER, CENTER)
+
+ rectMode(CENTER);
+ noStroke();
+ fill(color(0xed, 0xd7, 0x60));
+ rect(400, 250, sz * 9, sz * 2, 50);
+ fill(color(0xfd, 0xe7, 0x70));
+ rect(400, 250, sz * 9 - 10, sz * 2 - 10, 50);
+ fill(color(0x4C, 0x67, 0x10));
+ text("click to start! ", 408, 255);
+ return;
+ }
+ textAlign(LEFT, BASELINE)
+ background(color(0xad, 0xd5, 0x58));
+ drawPath();
+ // Draw all the towers that have been placed down before
+ drawAllTowers();
+ handleProjectiles();
+ drawTrash();
+ drawSelectedTowers();
+ dragAndDropInstructions();
+ drawCurrentSpikeIcon();
+ displayPowerups();
+ drawAllSpikes();
+ handleSlowdown();
+ handleSpeedBoost();
+ if (playingLevel) {
+ drawBalloons();
+ }
+ drawHealthBar();
+ drawBalanceDisplay();
+ drawNextLevelButton();
+ drawTowerUI();
+ /*
+ //upgrading towers implementation
+ drawUpgrade();
+ upgradeCheck();
+
+ //removing towers implementation
+ drawRemove();
+ removeCheck();
+ */
+ towerClickCheck();
+ drawRange();
+ if (health <= 0) {
+ drawLostAnimation();
+ }
+}
+
+// Whenever the user drags the mouse, update the x and y values of the tower
+function mouseDragged() {
+ if (currentlyDragging != notDragging) {
+ dragAndDropLocations[currentlyDragging] = new p5.Vector(mouseX + difX, mouseY + difY);
+ }
+ if (spikeHeld) {
+ spikeLocation = new p5.Vector(mouseX + difX, mouseY + difY);
+ }
+}
+
+// Whenever the user initially presses down on the mouse
+function mousePressed() {
+ started = true;
+ for (var i = 0; i < towerCount; i++) {
+ handlePickUp(i);
+ }
+ handleSpikePickUp();
+ handleSlowdownPress();
+ handleSpeedBoostPress();
+ handleNextLevel();
+}
+
+// Whenever the user releases their mouse
+function mouseReleased() {
+ if (currentlyDragging != notDragging) {
+ handleDrop(currentlyDragging);
+ }
+ currentlyDragging = notDragging;
+ if (spikeHeld) {
+ handleSpikeDrop();
+ }
+}
+
+var levels;
+
+var balloons;
+
+var distanceTravelled, delay, speed, maxHP, hp, slowed, ID;
+
+// Radius of the balloon
+var balloonRadius;
+
+var levelNum;
+
+var playingLevel;
+
+/*
+Encompasses: Displaying Balloons, Waves & Sending Balloons, Balloon Reaching End of Path
+*/
+function createWaves() {
+ createLevels(2);
+ // (level balloons are for, number of balloons, first balloon delay, delay between the sequence of balloons, speed, hp)
+ createBalloons(0, 5, 0, 20, 1, 20);
+ createBalloons(0, 100, 30, 20, 2, 60);
+ createBalloons(0, 1, 2020, 0, 0.6, 1000);
+ createBalloons(1, 5, 0, 20, 1, 100);
+}
+
+function createLevels(num) {
+ for (var i = 0; i < num; i++) {
+ levels.push([]);
+ }
+}
+
+function createBalloons(level, numBalloons, delay, delayInBetween, speed, hp) {
+ for (var i = 0; i < numBalloons; i++) {
+ console.log(level + " " + (delay + i * delayInBetween));
+ levels[level].push( [ 0, delay + i * delayInBetween, speed, hp, hp, 0, levels[level].length ]);
+ }
+}
+
+// Displays and moves balloons
+function updatePositions(balloon) {
+ // Only when balloonProps[1] is 0 (the delay) will the balloons start moving.
+ if (balloon[delay] < 0) {
+ var position = getLocation(balloon[distanceTravelled]);
+ // Slow down the balloon if the slowdown powerup is engaged
+ var travelSpeed = balloon[speed] * slowdownAmount;
+ // Increases the balloon's total steps by the speed
+ balloon[distanceTravelled] += travelSpeed;
+ // Drawing of ballon
+ ellipseMode(CENTER);
+ strokeWeight(0);
+ stroke(0);
+ fill(0);
+ // draw healthbar outline
+ stroke(0, 0, 0);
+ strokeWeight(0);
+ rectMode(CORNER);
+ fill(color(0x83, 0x00, 0x00));
+ var hbLength = 35, hbWidth = 6;
+ rect(position.x - hbLength / 2, position.y - (balloonRadius), hbLength, hbWidth);
+ // draw mini healthbar
+ noStroke();
+ fill(color(0xFF, 0x31, 0x31));
+ // the healthbar that changes based on hp
+ rect(position.x - hbLength / 2, position.y - (balloonRadius), hbLength * (balloon[hp] / balloon[maxHP]), hbWidth);
+ noFill();
+ // write text
+ stroke(0, 0, 0);
+ textSize(14);
+ fill(255, 255, 255);
+ strokeWeight(0);
+ text("Health: " + health, 670, 462);
+ fill(color(0xf3, 0xcd, 0x64));
+ if (balloon[slowed] == 1) {
+ fill(color(0xC1, 0x9D, 0x40));
+ }
+ ellipse(position.x, position.y, balloonRadius, balloonRadius);
+ } else {
+ balloon[delay]--;
+ }
+}
+
+function drawBalloons() {
+ balloons = levels[levelNum];
+ for (var i = 0; i < balloons.length; i++) {
+ var balloon = balloons[i];
+ updatePositions(balloon);
+ var position = getLocation(balloon[distanceTravelled]);
+ if (balloonSpikeCollision(position)) {
+ handleBalloonPop();
+ balloons.splice(i, 1);
+ i--;
+ continue;
+ }
+ if (balloon[hp] <= 0) {
+ handleBalloonPop();
+ balloons.splice(i, 1);
+ i--;
+ continue;
+ }
+ if (balloon[distanceTravelled] >= pathLength) {
+ // Removing the balloon from the list
+ balloons.splice(i, 1);
+ // Lost a life.
+ health--;
+ // Must decrease this counter variable, since the "next" balloon would be skipped
+ i--;
+ // When you remove a balloon from the list, all the indexes of the balloons "higher-up" in the list will decrement by 1
+ }
+ }
+ if (balloons.length == 0 && playingLevel) {
+ playingLevel = false;
+ handleWaveReward(levelNum + 1);
+ }
+}
+
+// variable to track user's health
+var health;
+
+var heart;
+
+// ------- HP SYSTEM --------
+/*
+ Heath-related variables:
+ int health: The player's total health.
+ This number decreases if balloons pass the end of the path (offscreen), currentely 11 since there are 11 balloons.
+ PImage heart: the heart icon to display with the healthbar.
+ */
+function loadHeartIcon() {
+ heart = loadImage("https://raw.githubusercontent.com/mcpt/game-dev/main/PartThree/data/heart.png");
+}
+
+// method to draw a healthbar at the bottom right of the screen
+function drawHealthBar() {
+ // draw healthbar outline
+ stroke(0, 0, 0);
+ strokeWeight(0);
+ fill(color(0x83, 0x00, 0x00));
+ rectMode(CENTER);
+ rect(721, 455, 132, 20);
+ var trueHealth = max(health, 0);
+ // draw healthbar
+ noStroke();
+ rectMode(CORNER);
+ fill(color(0xFF, 0x31, 0x31));
+ // the healthbar that changes based on hp
+ rect(655, 445.5, trueHealth * 12, 20);
+ rectMode(CENTER);
+ noFill();
+ // write text
+ stroke(0, 0, 0);
+ textSize(14);
+ fill(255, 255, 255);
+ text("Health: " + trueHealth, 670, 462);
+ // put the heart.png image on screen
+ imageMode(CENTER);
+ image(heart, 650, 456);
+ noFill();
+}
+
+// Next level Button
+function pointRectCollision(x1, y1, x2, y2, sizeX, sizeY) {
+ // --X Distance-- --Y Distance--
+ return (Math.abs(x2 - x1) <= sizeX / 2) && (Math.abs(y2 - y1) <= sizeY / 2);
+}
+
+function handleNextLevel() {
+ var center = new p5.Vector(100, 400);
+ var lengths = new p5.Vector(100, 100);
+ if (!playingLevel && pointRectCollision(mouseX, mouseY, center.x, center.y, lengths.x, lengths.y) && levelNum < levels.length - 1) {
+ playingLevel = true;
+ levelNum++;
+ }
+}
+
+function drawNextLevelButton() {
+ var center = new p5.Vector(60, 425);
+ var lengths = new p5.Vector(100, 70);
+ fill(0, 150, 0);
+ if (playingLevel) {
+ fill(0, 150, 0, 100);
+ }
+ rect(center.x, center.y, lengths.x, lengths.y, 10);
+ fill(255);
+ text("Next Level", center.x - 28, center.y + 4);
+}
+
+// Give the user $750 of starting balance
+var currentBalance;
+
+// Money earned by popping a balloon
+var rewardPerBalloon;
+
+// base money earned per wave
+var baseRewardPerWave;
+
+/**
+ * Currency system for tower defense
+ * - Rewards player for popping balloon
+ * - Keeps track of balance
+ * - Checks for sufficient funds when purchasing tower
+ */
+// Current amount of money owned by the player
+function handleBalloonPop() {
+ // Reward the player for popping the balloon
+ increaseBalance(rewardPerBalloon);
+}
+
+function increaseBalance(amount) {
+ // Increase the current balance by the amount given
+ currentBalance += amount;
+}
+
+// method to give user money for completing a wave
+function handleWaveReward(waveNum) {
+ increaseBalance(baseRewardPerWave * waveNum);
+}
+
+/**
+ * Checks to see if there is sufficient balance for purchasing a certain item
+ * Parameter "cost" is the cost of the tower to be purchased
+ */
+function hasSufficientFunds(cost) {
+ if (currentBalance < cost) {
+ // Not enough money to purchase the tower
+ return false;
+ } else {
+ // Enough money to purchase the tower
+ return true;
+ }
+}
+
+/**
+ * Purchases a tower
+ * Parameter "cost" is the cost of the tower to be purchased
+ */
+function purchaseTower(cost) {
+ currentBalance -= cost;
+}
+
+// Checks to see if the user is attempting to purchase/pick up a tower but has insufficient funds
+function attemptingToPurchaseTowerWithoutFunds(towerID) {
+ if (mouseIsPressed && withinBounds(towerID) && !hasSufficientFunds(towerPrice[towerID])) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// Displays the user's current balance on the screen
+function drawBalanceDisplay() {
+ // If the user is attempting to purchase a tower without funds, warn them with red display text
+ var error = false;
+ for (var i = 0; i < towerCount; i++) {
+ if (attemptingToPurchaseTowerWithoutFunds(i)) {
+ error = true;
+ }
+ }
+ if (error) {
+ fill(towerErrorColour);
+ } else {
+ // Black text
+ fill(0);
+ }
+ text("Current Balance: $" + currentBalance, 336, 65);
+}
+
+// -1 = not holding any tower, 0 = within default, 1 = within eight, 2 = within slow
+var currentlyDragging;
+
+var notDragging;
+
+var def, eight, slow;
+
+var towerCount;
+
+var difX, difY, count, towerClicked;
+
+var held;
+
+var towerPrice;
+
+var towerColours;
+
+// Constant, "copy" array to store where the towers are supposed to be
+var originalLocations;
+
+// Where the currently dragged towers are
+var dragAndDropLocations;
+
+// Towers that are placed down
+var towers;
+
+var towerSize;
+
+// Colour to display when user purchases tower without sufficient funds
+var towerErrorColour;
+
+// these variables are the trash bin coordinates
+var trashX1, trashY1, trashX2, trashY2;
+
+/*
+Encompasses: Displaying Towers, Drag & Drop, Discarding Towers, Rotating Towers, Tower Validity Checking
+ */
+// -------- CODE FOR DRAG & DROP ----------------------
+// final color
+function initDragAndDrop() {
+ difX = 0;
+ difY = 0;
+ trashX1 = 525;
+ trashY1 = 30;
+ trashX2 = 775;
+ trashY2 = 120;
+ count = 0;
+ towers = [];
+ towerData = [];
+ spikeLocations = [];
+ spikeData = [];
+}
+
+// Use point to rectangle collision detection to check for mouse being within bounds of pick-up box
+function pointRectCollision(x1, y1, x2, y2, size) {
+ // --X Distance-- --Y Distance--
+ return (Math.abs(x2 - x1) <= size / 2) && (Math.abs(y2 - y1) <= size / 2);
+}
+
+function withinBounds(towerID) {
+ var towerLocation = dragAndDropLocations[towerID];
+ return pointRectCollision(mouseX, mouseY, towerLocation.x, towerLocation.y, towerSize);
+}
+
+// check if you drop in trash
+function trashDrop(towerID) {
+ var location = dragAndDropLocations[towerID];
+ if (location.x >= trashX1 && location.x <= trashX2 && location.y >= trashY1 && location.y <= trashY2)
+ return true;
+ return false;
+}
+
+// -------Methods Used for further interaction-------
+function handleDrop(towerID) {
+ // Instructions to check for valid drop area will go here
+ if (trashDrop(towerID)) {
+ dragAndDropLocations[towerID] = originalLocations[towerID];
+ held[towerID] = false;
+ print("Dropped object in trash.");
+ } else if (legalDrop(towerID)) {
+ towers.push(dragAndDropLocations[towerID].copy());
+ towerData.push(makeTowerData(towerID));
+ dragAndDropLocations[towerID] = originalLocations[towerID];
+ held[towerID] = false;
+ purchaseTower(towerPrice[towerID]);
+ print("Dropped for the " + (++count) + "th time.");
+ }
+}
+
+// Will be called whenever a tower is picked up
+function handlePickUp(pickedUpTowerID) {
+ if (withinBounds(pickedUpTowerID) && hasSufficientFunds(towerPrice[pickedUpTowerID])) {
+ currentlyDragging = pickedUpTowerID;
+ held[currentlyDragging] = true;
+ var location = dragAndDropLocations[pickedUpTowerID];
+ // Calculate the offset values (the mouse pointer may not be in the direct centre of the tower)
+ difX = parseInt(location.x) - mouseX;
+ difY = parseInt(location.y) - mouseY;
+ }
+ print("Object picked up.");
+}
+
+function drawTrash() {
+ rectMode(CORNERS);
+ noStroke();
+ fill(color(0x4C, 0x67, 0x10));
+ rect(trashX1, trashY1, trashX2, trashY2);
+ fill(255, 255, 255);
+ stroke(255, 255, 255);
+}
+
+function dragAndDropInstructions() {
+ fill(color(0x4C, 0x67, 0x10));
+ textSize(12);
+ text("Pick up tower from here!", 620, 20);
+ text("You can't place towers on the path of the balloons!", 200, 20);
+ text("Place a tower into the surrounding area to put it in the trash.", 200, 40);
+ text("Mouse X: " + mouseX + "\nMouse Y: " + mouseY + "\nMouse held: " + mouseIsPressed + "\nTower Held: " + currentlyDragging, 15, 20);
+}
+
+// -------- CODE FOR PATH COLLISION DETECTION ---------
+function pointDistToLine(start, end, point) {
+ // Code from https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
+ // i.e. |w-v|^2 - avoid a sqrt
+ var l2 = (start.x - end.x) * (start.x - end.x) + (start.y - end.y) * (start.y - end.y);
+ // v == w case
+ if (l2 == 0.0)
+ return dist(end.x, end.y, point.x, point.y);
+ var t = max(0, min(1, p5.Vector.sub(point, start).dot(p5.Vector.sub(end, start)) / l2));
+ // Projection falls on the segment
+ var projection = p5.Vector.add(start, p5.Vector.mult(p5.Vector.sub(end, start), t));
+ return dist(point.x, point.y, projection.x, projection.y);
+}
+
+function pointDistToArc(start, center, end, arcData, point) {
+ if (Math.abs(arcData.y) < radians(360)) {
+ var towerAngles = new Array(2);
+ towerAngles[0] = atan2(point.y - center.y, point.x - center.x) - arcData.x;
+ if (towerAngles[0] < 0) {
+ towerAngles[1] = towerAngles[0] + radians(360);
+ } else if (towerAngles[0] > 0) {
+ towerAngles[1] = towerAngles[0] - radians(360);
+ } else {
+ towerAngles[1] = 0;
+ }
+ for (var _ in towerAngles) {
+ var t = towerAngles[_] / arcData.y;
+ if (t >= 0 && t <= 1) {
+ return Math.abs(dist(point.x, point.y, center.x, center.y) - arcData.z);
+ }
+ }
+ }
+ return min(dist(point.x, point.y, start.x, start.y), dist(point.x, point.y, end.x, end.y));
+}
+
+function shortestDist(point) {
+ var answer = Number.MAX_VALUE;
+ var distance = Number.MAX_VALUE;
+ for (var i = 0; i < pathSegments.length; i++) {
+ var pathSegment = pathSegments[i];
+ if (pathSegment.length == 2) {
+ var startPoint = pathSegment[start];
+ var endPoint = pathSegment[end];
+ distance = pointDistToLine(startPoint, endPoint, point);
+ } else {
+ var centerPoint = pathSegment[centerArc];
+ var arcData = pathSegment[arcValues];
+ if (dist(point.x, point.y, centerPoint.x, centerPoint.y) < arcData.z + 30) {
+ var startPoint = pathSegment[startArc];
+ var endPoint = pathSegment[endArc];
+ distance = pointDistToArc(startPoint, centerPoint, endPoint, arcData, point);
+ }
+ }
+ answer = min(answer, distance);
+ }
+ return answer;
+}
+
+// Will return if a drop is legal by looking at the shortest distance between the rectangle center and the path.
+function legalDrop(towerID) {
+ var heldLocation = dragAndDropLocations[towerID];
+ // checking if this tower overlaps any of the already placed towers
+ for (var i = 0; i < towers.length; i++) {
+ var towerLocation = towers[i];
+ if (pointRectCollision(heldLocation.x, heldLocation.y, towerLocation.x, towerLocation.y, towerSize))
+ return false;
+ }
+ return shortestDist(heldLocation) > PATH_RADIUS;
+}
+
+// Checks if the location of the spike is on the path
+function legalSpikeDrop() {
+ var heldLocation = spikeLocation;
+ return shortestDist(heldLocation) <= PATH_RADIUS;
+}
+
+var framesSinceLost;
+
+function drawLostAnimation() {
+ framesSinceLost++;
+ var alpha = 166 * framesSinceLost / 80;
+ if (alpha > 166)
+ alpha = 166;
+ fill(127, alpha);
+ rectMode(CORNER);
+ noStroke();
+ rect(0, 0, 800, 500);
+ var textAlpha = 255 * (framesSinceLost - 80) / 80;
+ if (textAlpha > 255)
+ ;
+ textAlpha = 255;
+ fill(255, textAlpha);
+ textSize(70);
+ text("YOU LOST...", 265, 260);
+}
+
+// ------- CODE FOR THE PATH
+var pathSegments;
+
+var start, end;
+
+var startArc, centerArc, endArc, arcValues;
+
+var PATH_RADIUS;
+
+var pathLength;
+
+/*
+Encompasses: The Path for Balloons, Balloon Movement
+ */
+function initPoints() {
+ addLine(0, 100, 300, 100);
+ addSmoothArc(50, PI);
+ addSmoothLine(70);
+ addSmoothArc(-50, -PI);
+ addSmoothLine(100);
+ addSmoothArc(-50, -PI / 2);
+ addSmoothArc(75, PI / 3);
+ addSmoothArc(125, PI / 2);
+ addSmoothLine(40);
+ addSmoothArc(100, PI / 2);
+}
+
+function addLine(startX, startY, endX, endY) {
+ pathSegments.push([]);
+ // If the line should continue from the existing path
+ if (startX === -1 && startY === -1) {
+ var pathSegment = pathSegments[pathSegments.length - 2];
+ // If the previous path segment was a line
+ if (pathSegment.length === 2) {
+ startX = pathSegment[end].x;
+ startY = pathSegment[end].y;
+ } else // If the previous path segment was an arc
+ {
+ startX = pathSegment[endArc].x;
+ startY = pathSegment[endArc].y;
+ }
+ }
+ pathSegments[pathSegments.length - 1].push(new p5.Vector(startX, startY));
+ pathSegments[pathSegments.length - 1].push(new p5.Vector(endX, endY));
+}
+
+function addArc(x, y, centerX, centerY, displacementAngle) {
+ pathSegments.push([]);
+ // If the line should continue from the existing path
+ if (x == -1 && y == -1) {
+ var pathSegment = pathSegments[pathSegments.length - 2];
+ // If the previous path segment was a line
+ if (pathSegment.length == 2) {
+ x = pathSegment[end].x;
+ y = pathSegment[end].y;
+ } else // If the previous path segment was an arc
+ {
+ x = pathSegment[endArc].x;
+ y = pathSegment[endArc].y;
+ }
+ }
+ // Starting angle
+ var startingAngle = atan2(y - centerY, x - centerX);
+ // radius of the arc
+ var radius = dist(x, y, centerX, centerY);
+ // Angle that will determine where the end coordinates are for the arc
+ var finalAngle = startingAngle + displacementAngle;
+ pathSegments[pathSegments.length - 1].push(new p5.Vector(x, y));
+ pathSegments[pathSegments.length - 1].push(new p5.Vector(centerX, centerY));
+ pathSegments[pathSegments.length - 1].push(new p5.Vector(centerX + radius * Math.cos(finalAngle), centerY + radius * Math.sin(finalAngle)));
+ pathSegments[pathSegments.length - 1].push(new p5.Vector(startingAngle, displacementAngle, radius));
+}
+
+function addSmoothArc(distanceAway, displacementAngle) {
+ var endPoint;
+ var directionVector;
+ var pathSegment = pathSegments[pathSegments.length - 1];
+ if (pathSegment.length == 2) {
+ var startPoint = pathSegment[start];
+ endPoint = pathSegment[end];
+ var scaleFactor = dist(startPoint.x, startPoint.y, endPoint.x, endPoint.y);
+ directionVector = new p5.Vector(-(endPoint.y - startPoint.y) * distanceAway / scaleFactor, (endPoint.x - startPoint.x) * distanceAway / scaleFactor);
+ } else {
+ var centerPoint = pathSegment[centerArc];
+ endPoint = pathSegment[endArc];
+ var scaleFactor = dist(centerPoint.x, centerPoint.y, endPoint.x, endPoint.y) * pathSegment[arcValues].y / Math.abs(pathSegment[arcValues].y);
+ directionVector = new p5.Vector((centerPoint.x - endPoint.x) * distanceAway / scaleFactor, (centerPoint.y - endPoint.y) * distanceAway / scaleFactor);
+ }
+ addArc(-1, -1, endPoint.x + directionVector.x, endPoint.y + directionVector.y, displacementAngle);
+}
+
+function addSmoothLine(steps) {
+ var pathSegment = pathSegments[pathSegments.length - 1];
+ var centerPoint = pathSegment[centerArc];
+ var endPoint = pathSegment[endArc];
+ var scaleFactor = dist(centerPoint.x, centerPoint.y, endPoint.x, endPoint.y) * pathSegment[arcValues].y / Math.abs(pathSegment[arcValues].y);
+ var directionVector = new p5.Vector(-(endPoint.y - centerPoint.y) / scaleFactor, (endPoint.x - centerPoint.x) / scaleFactor);
+ directionVector = p5.Vector.mult(directionVector, steps);
+ addLine(-1, -1, endPoint.x + directionVector.x, endPoint.y + directionVector.y);
+}
+
+function initPath() {
+ print("iyadfuisadyfgyuasdgf")
+ initPoints();
+ for (var i = 0; i < pathSegments.length; i++) {
+ var pathSegment = pathSegments[i];
+ print(pathSegment)
+ var point1 = pathSegment[0];
+ var point2 = pathSegment[1];
+ if (pathSegment.length === 4) {
+ pathLength += Math.abs(pathSegment[arcValues].y * pathSegment[arcValues].z);
+ } else {
+ pathLength += dist(point1.x, point1.y, point2.x, point2.y);
+ }
+ }
+}
+
+function drawPath() {
+ noFill();
+ stroke(color(0x4C, 0x67, 0x10));
+ strokeWeight(PATH_RADIUS * 2 + 1);
+ ellipseMode(CENTER);
+ for (var i = 0; i < pathSegments.length; i++) {
+ var pathSegment = pathSegments[i];
+ var point2 = pathSegment[end];
+ if (pathSegment.length == 2) {
+ var point1 = pathSegment[start];
+ line(point1.x, point1.y, point2.x, point2.y);
+ } else {
+ var arcData = pathSegment[arcValues];
+ var angle1;
+ var angle2;
+ if (arcData.y <= 0) {
+ angle1 = arcData.x + arcData.y;
+ angle2 = arcData.x;
+ } else {
+ angle2 = arcData.x + arcData.y;
+ angle1 = arcData.x;
+ }
+ arc(point2.x, point2.y, arcData.z * 2, arcData.z * 2, angle1, angle2);
+ }
+ }
+ stroke(color(0x7b, 0x9d, 0x32));
+ strokeWeight(PATH_RADIUS * 2);
+ for (var i = 0; i < pathSegments.length; i++) {
+ var pathSegment = pathSegments[i];
+ var point2 = pathSegment[end];
+ if (pathSegment.length == 2) {
+ var point1 = pathSegment[start];
+ line(point1.x, point1.y, point2.x, point2.y);
+ } else {
+ var arcData = pathSegment[arcValues];
+ var angle1;
+ var angle2;
+ if (arcData.y <= 0) {
+ angle1 = arcData.x + arcData.y;
+ angle2 = arcData.x;
+ } else {
+ angle2 = arcData.x + arcData.y;
+ angle1 = arcData.x;
+ }
+ arc(point2.x, point2.y, arcData.z * 2, arcData.z * 2, angle1, angle2);
+ }
+ }
+}
+
+var dp;
+
+// GIVEN TO PARTICIPANTS BY DEFAULT
+function getLocation(travelDistance) {
+ var memoized = dp[travelDistance]
+ if (memoized !== undefined) {
+ return memoized;
+ }
+ var originalDist = travelDistance;
+ var distance;
+ var point1;
+ var point2;
+ for (var i = 0; i < pathSegments.length; i++) {
+ var pathSegment = pathSegments[i];
+ point1 = pathSegment[0];
+ point2 = pathSegment[1];
+ distance = dist(point1.x, point1.y, point2.x, point2.y);
+ if (pathSegment.length == 4) {
+ distance = Math.abs(pathSegment[arcValues].y * pathSegment[arcValues].z);
+ }
+ if (distance <= 0.0000001 || travelDistance >= distance) {
+ travelDistance -= distance;
+ } else {
+ // In between two pathSegments
+ var x;
+ var y;
+ if (pathSegment.length == 2) {
+ var xDist = point2.x - point1.x;
+ var yDist = point2.y - point1.y;
+ var travelProgress = travelDistance / distance;
+ x = point1.x + xDist * travelProgress;
+ y = point1.y + yDist * travelProgress;
+ } else {
+ var arcData = pathSegment[arcValues];
+ // initial angle //radius
+ var angle = arcData.x + ((1 / arcData.z) * travelDistance * arcData.y / Math.abs(arcData.y));
+ x = point2.x + arcData.z * Math.cos(angle);
+ y = point2.y + arcData.z * Math.sin(angle);
+ }
+ dp[originalDist] = new p5.Vector(x, y);
+ return new p5.Vector(x, y);
+ }
+ }
+ // At end of path
+ var lastPathSegment = pathSegments[pathSegments.length - 1];
+ if (lastPathSegment.length == 2) {
+ dp[originalDist] = lastPathSegment[end];
+ return lastPathSegment[end];
+ } else {
+ dp[originalDist] = lastPathSegment[endArc];
+ return lastPathSegment[endArc];
+ }
+}
+
+// Amount of each powerup remaining
+var powerupCount;
+
+var spikes, slowdown, speedboost;
+
+// Amount of balloons the cluster of spikes will pop before disappearing
+var spikePierce;
+
+// Amount of time that a slowdown session lasts for in seconds
+var slowdownLength;
+
+// Amount of time that a speed boost session lasts for in seconds
+var speedBoostLength;
+
+// The factor to multiply all balloon speeds by
+var slowdownAmount;
+
+var slowdownRemaining;
+
+var slowdownLocation;
+
+// Factor to multiply all tower cooldowns by
+var speedBoostAmount;
+
+var speedBoostRemaining;
+
+var speedBoostLocation;
+
+// Image for path spikes
+var spikeIcon;
+
+// Location of spike for drag and drop
+var spikeLocation;
+
+var spikeLocations;
+
+var spikeData;
+
+var originalSpikeLocation;
+
+var spikeHeld;
+
+/**
+ * All powerups including
+ * - Path spikes
+ * - Slow Time
+ * - Damage/Speed boost for towers
+ */
+/**
+ * Reimplementation of Drag and Drop for path spikes *
+ */
+function withinSpikeBounds() {
+ return pointRectCollision(mouseX, mouseY, spikeLocation.x, spikeLocation.y, 45);
+}
+
+function spikeTrashDrop() {
+ var location = spikeLocation;
+ if (location.x >= trashX1 && location.x <= trashX2 && location.y >= trashY1 && location.y <= trashY2)
+ return true;
+ return false;
+}
+
+function handleSpikePickUp() {
+ if (withinSpikeBounds() && powerupCount[spikes] > 0) {
+ spikeHeld = true;
+ var location = spikeLocation;
+ // Calculate the offset values (the mouse pointer may not be in the direct centre of the tower)
+ difX = parseInt(location.x) - mouseX;
+ difY = parseInt(location.y) - mouseY;
+ }
+}
+
+function handleSpikeDrop() {
+ if (spikeTrashDrop()) {
+ spikeLocation = originalSpikeLocation;
+ print("Spike object in trash.");
+ } else if (legalSpikeDrop()) {
+ // Decrease remaining spikes by 1
+ powerupCount[spikes]--;
+ spikeLocations.push(spikeLocation.copy());
+ spikeData.push(spikePierce);
+ spikeLocation = originalSpikeLocation;
+ print("Spike Dropped on Path");
+ }
+ spikeHeld = false;
+}
+
+function loadSpikeIcon() {
+ spikeIcon = loadImage("https://raw.githubusercontent.com/mcpt/game-dev/main/PartThree/data/spikes.png");
+}
+
+function drawSpikeIcon(location, colour) {
+ ellipseMode(RADIUS);
+ fill(colour);
+ ellipse(location.x, location.y, 20, 20);
+ imageMode(CENTER);
+ image(spikeIcon, location.x, location.y);
+}
+
+function drawSpikeIcon(location) {
+ imageMode(CENTER);
+ image(spikeIcon, location.x, location.y);
+}
+
+function drawAllSpikes() {
+ for (var i = 0; i < spikeLocations.length; i++) {
+ if (spikeData[i] <= 0) {
+ spikeData.splice(i, 1);
+ spikeLocations.splice(i, 1);
+ i--;
+ } else {
+ drawSpikeIcon(spikeLocations[i]);
+ }
+ }
+}
+
+function drawCurrentSpikeIcon() {
+ if (legalSpikeDrop() || spikeLocation == originalSpikeLocation) {
+ drawSpikeIcon(spikeLocation, color(0xFF, 0xFF, 0xFF));
+ } else {
+ drawSpikeIcon(spikeLocation, color(0xF0, 0x00, 0x00));
+ }
+}
+
+function balloonSpikeCollision(position) {
+ for (var i = 0; i < spikeLocations.length; i++) {
+ var spikeLocation = spikeLocations[i];
+ if (dist(position.x, position.y, spikeLocation.x, spikeLocation.y) <= PATH_RADIUS) {
+ spikeData[i] = spikeData[i] - 1;
+ // // Spike has popped the balloon!
+ return true;
+ }
+ }
+ return false;
+}
+
+function displayPowerups() {
+ fill(255);
+ text("Slowdowns remaining: " + powerupCount[slowdown], 655, 184);
+ text("Speed Boosts remaining: " + powerupCount[speedboost], 635, 234);
+ var displayColour;
+ /**
+ * Slowdown
+ */
+ if (mouseIsPressed && withinSlowdownBounds() && powerupCount[slowdown] <= 0 && slowdownRemaining <= 0) {
+ // Display using red error colour
+ displayColour = color(0xF0, 0x00, 0x00);
+ } else if (slowdownRemaining > 0) {
+ // Display blue colour for slowdown in progress
+ displayColour = color(0x81, 0xE5, 0xFF);
+ } else {
+ // Display using white colour
+ displayColour = color(0xFF, 0xFF, 0xFF);
+ }
+ fill(displayColour);
+ ellipseMode(RADIUS);
+ ellipse(slowdownLocation.x, slowdownLocation.y, 20, 20);
+ /**
+ * Speed Boosts
+ */
+ if (mouseIsPressed && withinSpeedBoostBounds() && powerupCount[speedboost] <= 0 && speedBoostRemaining <= 0) {
+ // Display using red error colour
+ displayColour = color(0xF0, 0x00, 0x00);
+ } else if (speedBoostRemaining > 0) {
+ // Display blue colour for slowdown in progress
+ displayColour = color(0x81, 0xE5, 0xFF);
+ } else {
+ // Display using white colour
+ displayColour = color(0xFF, 0xFF, 0xFF);
+ }
+ fill(displayColour);
+ ellipse(speedBoostLocation.x, speedBoostLocation.y, 20, 20);
+ /**
+ * Spikes
+ */
+ if (mouseIsPressed && withinSpikeBounds() && powerupCount[spikes] <= 0) {
+ // Display using red error colour
+ displayColour = color(0xF0, 0x00, 0x00);
+ } else {
+ // Display using white colour
+ displayColour = color(0xFF, 0xFF, 0xFF);
+ }
+ fill(displayColour);
+ text("Spikes remaining: " + powerupCount[spikes], 625, 146);
+ drawSpikeIcon(originalSpikeLocation, displayColour);
+}
+
+function withinSlowdownBounds() {
+ return pointRectCollision(mouseX, mouseY, slowdownLocation.x, slowdownLocation.y, 45);
+}
+
+function handleSlowdownPress() {
+ if (withinSlowdownBounds() && powerupCount[slowdown] > 0 && slowdownAmount == 1) {
+ powerupCount[slowdown]--;
+ slowdownAmount = 0.5;
+ slowdownRemaining = slowdownLength * 60;
+ }
+}
+
+function handleSlowdown() {
+ if (slowdownRemaining > 0) {
+ slowdownRemaining--;
+ if (slowdownRemaining == 0) {
+ // Revert to original speed
+ slowdownAmount = 1;
+ }
+ }
+}
+
+/**
+ * Speed Boost Powerup
+ */
+function withinSpeedBoostBounds() {
+ return pointRectCollision(mouseX, mouseY, speedBoostLocation.x, speedBoostLocation.y, 45);
+}
+
+function handleSpeedBoostPress() {
+ if (withinSpeedBoostBounds() && powerupCount[speedboost] > 0 && speedBoostAmount == 1) {
+ powerupCount[speedboost]--;
+ speedBoostAmount = 0.4;
+ speedBoostRemaining = speedBoostLength * 60;
+ }
+}
+
+function handleSpeedBoost() {
+ if (speedBoostRemaining > 0) {
+ speedBoostRemaining--;
+ if (speedBoostRemaining == 0) {
+ // Revert to original speed
+ speedBoostAmount = 1;
+ }
+ }
+}
+
+// Stores the location of each projectile and how fast it should move each frame
+var center, velocity;
+
+// Stores additional projectile data (unrelated to motion)
+var projectileData;
+
+// Stores a list of balloons that each projectile has hit, so it doesn't hit the same balloon twice
+var balloonsHit;
+
+// Constants to make accessing the projectileData array more convenient
+var damage, pierce, angle, currDistTravelled, maxDistTravelled, thickness, dmgType;
+
+var projectileRadius;
+
+// changed values to help upgrades
+var defdmg, eightdmg, slowdmg;
+
+var shots;
+
+var slowPercent;
+
+// -------------- TEMPLATE CODE BEGINS ---------------- (Participants will NOT need to code anything below this line)
+// For Participants: The HashSet data structure is like an ArrayList, but can tell you whether it contains a value or not very quickly
+// The downside of HashSets is that there is no order or indexes, so you can't use it like a normal list
+// Think of it like throwing items into an unorganized bin
+// Adds a new projectile
+function createProjectile(centre, vel, damage, pierce, maxDistTravelled, thickness, dmgType) {
+ // Adds an empty set to the balloonsHit structure - this represents the current projectile, not having hit any balloons yet.
+ balloonsHit.push([]);
+ // Adds the starting location of the projectile as the current location
+ center.push(centre);
+ // Adds the velocity of the projectile to the list
+ velocity.push(vel);
+ var angle = atan2(vel.y, vel.x);
+ projectileData.push( [ damage, pierce, angle, 0, maxDistTravelled, thickness, dmgType ]);
+}
+
+// Checks the distance from a point to a projectile using the pointDistToLine() method coded earlier
+function distToProjectile(projectileID, point) {
+ var data = projectileData[projectileID];
+ var width = Math.cos(data[angle]), height = Math.sin(data[angle]);
+ var displacement = new p5.Vector(width, height).mult(projectileRadius);
+ var start = p5.Vector.add(center[projectileID], displacement), end = p5.Vector.sub(center[projectileID], displacement);
+ return pointDistToLine(start, end, point);
+}
+
+// Checks if a projectile is ready to be removed (is it off screen? has it already reached its maximum pierce? has it exceeded the maximum distance it needs to travel?)
+function dead(projectileID) {
+ var data = projectileData[projectileID];
+ return offScreen(projectileID) || data[pierce] == 0 || data[currDistTravelled] > data[maxDistTravelled];
+}
+
+// Checks if a projectile is off-screen
+function offScreen(projectileID) {
+ return center[projectileID].x < 0 || center[projectileID].x > 800 || center[projectileID].y < 0 || center[projectileID].y > 500;
+}
+
+// Displays a projectile and handles movement & collision via their respective methods
+function drawProjectile(projectileID) {
+ var data = projectileData[projectileID];
+ stroke(255);
+ strokeWeight(data[thickness]);
+ var width = Math.cos(data[angle]), height = Math.sin(data[angle]);
+ var displacement = new p5.Vector(width, height).mult(projectileRadius);
+ var start = p5.Vector.add(center[projectileID], displacement), end = p5.Vector.sub(center[projectileID], displacement);
+ line(start.x, start.y, end.x, end.y);
+ handleProjectileMovement(projectileID);
+ handleCollision(projectileID);
+}
+
+// Updates projectile locations
+function handleProjectileMovement(projectileID) {
+ // Adds the velocity to the current position
+ var nextLocation = p5.Vector.add(center[projectileID], velocity[projectileID]);
+ // Updates the current position
+ center[projectileID] = nextLocation;
+ var data = projectileData[projectileID];
+ // Tracks the current distance travelled, so that if it exceeds the maximum projectile range, it disappears
+ data[currDistTravelled] += velocity[projectileID].mag();
+}
+
+// Checks collision with balloons
+function handleCollision(projectileID) {
+ var data = projectileData[projectileID];
+ for (var b in balloons) {
+ var balloon = balloons[b];
+ // If the balloon hasn't entered yet, don't count it
+ if (balloon[delay] > 0)
+ continue;
+ var position = getLocation(balloon[distanceTravelled]);
+ if (distToProjectile(projectileID, position) <= balloonRadius / 2 + data[thickness] / 2) {
+ // Already hit the balloon / already used up its max pierce
+ if (data[pierce] === 0 || balloonsHit[projectileID].includes(parseInt(balloon[ID])))
+ continue;
+ // Lowers the pierce by 1 after hitting the balloon
+ data[pierce]--;
+ // Adds the projectile to the set of already hit balloons
+ balloonsHit[projectileID].push(parseInt(balloon[ID]));
+ hitBalloon(projectileID, balloon);
+ }
+ }
+}
+
+// -------------- TEMPLATE CODE ENDS ---------------- (Participants will NOT need to code anything above this line)
+// Code that is called when a projectile hits a balloon
+function hitBalloon(projectileID, balloonData) {
+ var data = projectileData[projectileID];
+ // Deals damage
+ balloonData[hp] -= data[damage];
+ if (data[dmgType] == slow && balloonData[slowed] == 0) {
+ // Slows down the balloon
+ var slowNum = slowPercent;
+ if (data[upgrade] >= 2) {
+ slowNum -= 0.2;
+ }
+ balloonData[speed] *= slowNum;
+ balloonData[slowed] = 1;
+ }
+}
+
+// Tracks the tower that is closest to the end, within the vision of the tower
+function track(towerLocation, vision) {
+ var maxDist = 0;
+ var location = undefined;
+ for (var b in levels[levelNum]) {
+ var balloon = levels[levelNum][b];
+ var balloonLocation = getLocation(balloon[distanceTravelled]);
+ // Checks if the tower can see the balloon
+ if (dist(balloonLocation.x, balloonLocation.y, towerLocation.x, towerLocation.y) <= vision) {
+ // If the balloon has travelled further than the previously stored one, it is now the new fastest
+ if (balloon[distanceTravelled] > maxDist) {
+ location = balloonLocation;
+ maxDist = balloon[distanceTravelled];
+ }
+ }
+ }
+ return location;
+}
+
+// Handles all projectile creation
+function handleProjectiles() {
+ if (playingLevel) {
+ for (var i = 0; i < towers.length; i++) {
+ var location = towers[i];
+ var data = towerData[i];
+ data[cooldownRemaining]--;
+ var balloon = track(location, data[towerVision]);
+ // Cooldown is 0 and there is a balloon that the tower tracks shoots a projectile
+ if (data[cooldownRemaining] <= 0 && balloon !== undefined) {
+ // Resets the cooldown
+ data[cooldownRemaining] = parseInt((data[maxCooldown] * speedBoostAmount));
+ var toMouse = new p5.Vector(balloon.x - location.x, balloon.y - location.y);
+ if (data[projectileType] == def) {
+ var speed = 24, damage = defdmg, pierce = 1, thickness = 2, maxTravelDist = 500;
+ if (data[upgrade] >= 3) {
+ damage = defdmg + data[upgrade] - 2;
+ }
+ var unitVector = p5.Vector.div(toMouse, toMouse.mag());
+ var velocity_ = p5.Vector.mult(unitVector, speed);
+ createProjectile(location, velocity_, damage, pierce, maxTravelDist, thickness, def);
+ // Default type
+ } else if (data[projectileType] == eight) {
+ // Spread in 8
+ var curShots = shots;
+ if (data[upgrade] >= 3) {
+ curShots = shots + 8;
+ }
+ for (var j = 0; j < curShots; j++) {
+ var speed = 18, damage = eightdmg, pierce = 2, thickness = 2, maxTravelDist = 150;
+ var angle = (PI * 2) * j / curShots;
+ var unitVector = p5.Vector.div(toMouse, toMouse.mag());
+ if (data[upgrade] >= 4) {
+ damage = eightdmg + data[upgrade] - 3;
+ }
+ var velocity_ = p5.Vector.mult(unitVector, speed).rotate(angle);
+ createProjectile(location, velocity_, damage, pierce, maxTravelDist, thickness, eight);
+ }
+ } else if (data[projectileType] == slow) {
+ // glue gunner - slows balloons
+ // slow-ish speed, low damage, high pierce, low range
+ var speed = 15, damage = slowdmg, pierce = 7, thickness = 4, maxTravelDist = 220;
+ var unitVector = p5.Vector.div(toMouse, toMouse.mag());
+ var velocity_ = p5.Vector.mult(unitVector, speed);
+ createProjectile(location, velocity_, damage, pierce, maxTravelDist, thickness, slow);
+ }
+ }
+ }
+ }
+ // Displays projectiles and removes those which need to be removed
+ for (var projectileID = 0; projectileID < projectileData.length; projectileID++) {
+ drawProjectile(projectileID);
+ if (dead(projectileID)) {
+ projectileData.splice(projectileID, 1);
+ center.splice(projectileID, 1);
+ velocity.splice(projectileID, 1);
+ balloonsHit.splice(projectileID, 1);
+ projectileID--;
+ }
+ }
+}
+
+var removeLocation;
+
+function drawRemove() {
+ strokeWeight(1);
+ stroke(color(0xde, 0xac, 0x9e));
+ fill(color(0xFF, 0x69, 0x61));
+ rectMode(CENTER);
+ rect(removeLocation.x, removeLocation.y, 70, 24, 5);
+ textSize(16);
+ fill(color(0xff, 0xff, 0xff));
+ text("Remove", removeLocation.x - 30, removeLocation.y + 4);
+}
+
+function removeCheck() {
+ if ((removeLocation.x - 35 <= mouseX && mouseX <= removeLocation.x + 35 && removeLocation.y - 12 <= mouseY && mouseY <= removeLocation.y + 12) && mouseIsPressed && towerClicked != -1) {
+ var temp = towerData[towerClicked];
+ currentBalance += temp[upgrade] * towerPrice[temp[projectileType]] / 2;
+ var temp1 = towerClicked;
+ towerClicked = -1;
+ towerData.splice(temp1, 1);
+ towers.splice(temp1, 1);
+ }
+}
+
+var cooldownRemaining, maxCooldown, towerVision, projectileType, upgrade;
+
+var towerData;
+
+var towerVisions;
+
+/*
+Encompasses: Displaying Towers & Tower Data (for projectiles)
+*/
+function makeTowerData(towerID) {
+ if (towerID == def) {
+ return [ // Cooldown between next projectile
+ 10, // Max cooldown
+ 10, // Tower Vision
+ towerVisions[def], // Projectile ID
+ 0, 1 ];
+ } else if (towerID == eight) {
+ return [ // Cooldown between next projectile
+ 25, // Max cooldown
+ 25, // Tower Vision
+ towerVisions[eight], // Projectile ID
+ 1, 1 ];
+ } else if (towerID == slow) {
+ return [ 35, 35, // Tower Vision
+ towerVisions[slow], 2, 1 ];
+ }
+ // filler since we need to return something
+ return [];
+}
+
+// --------------------------------------------------
+// Draw a simple tower at a specified location
+function drawTowerIcon(xPos, yPos, colour) {
+ strokeWeight(0);
+ stroke(0);
+ fill(colour);
+ rectMode(CENTER);
+ // Draw a simple rectangle as the tower
+ rect(xPos, yPos, towerSize, towerSize);
+}
+
+// Draws a tower that rotates to face the targetLocation
+function drawTowerWithRotation(xPos, yPos, colour, targetLocation) {
+ push();
+ translate(xPos, yPos);
+ // Angle calculation
+ var slope = (targetLocation.y - yPos) / (targetLocation.x - xPos);
+ var angle = atan(slope);
+ rotate(angle);
+ strokeWeight(0);
+ fill(colour);
+ rectMode(CENTER);
+ // Draw a simple rectangle as the tower
+ rect(0, 0, towerSize, towerSize);
+ pop();
+}
+
+function drawAllTowers() {
+ for (var i = 0; i < towers.length; i++) {
+ var xPos = towers[i].x, yPos = towers[i].y;
+ var data = towerData[i];
+ var towerType = data[projectileType];
+ var track_ = undefined;
+ if (playingLevel) {
+ track_ = track(towers[i], data[towerVision]);
+ }
+ if (track_ === undefined) {
+ drawTowerIcon(xPos, yPos, towerColours[towerType]);
+ } else {
+ drawTowerWithRotation(xPos, yPos, towerColours[towerType], new p5.Vector(track_.x, track_.y));
+ }
+ if (pointRectCollision(mouseX, mouseY, xPos, yPos, towerSize)) {
+ // Drawing the tower range visually
+ fill(127, 80);
+ stroke(127);
+ strokeWeight(4);
+ ellipseMode(RADIUS);
+ ellipse(xPos, yPos, data[towerVision], data[towerVision]);
+ }
+ fill(color(0x4C, 0x67, 0x10));
+ textSize(12);
+ strokeWeight(0);
+ text("Tower " + (i + 1), xPos - 30, yPos - 20);
+ }
+}
+
+function drawSelectedTowers() {
+ // Note that more than one tower can be dragged at a time
+ for (var towerID = 0; towerID < towerCount; towerID++) {
+ if (held[towerID]) {
+ var location = dragAndDropLocations[towerID];
+ if (!legalDrop(towerID)) {
+ drawTowerIcon(location.x, location.y, color(0xFF, 0x00, 0x00));
+ } else {
+ drawTowerIcon(location.x, location.y, towerColours[towerID]);
+ }
+ // Drawing the tower range of the selected towers
+ fill(127, 80);
+ stroke(127);
+ strokeWeight(4);
+ ellipseMode(RADIUS);
+ ellipse(location.x, location.y, towerVisions[towerID], towerVisions[towerID]);
+ }
+ }
+ // Draws the default towers
+ for (var towerType = 0; towerType < towerCount; towerType++) {
+ var location = originalLocations[towerType];
+ if (attemptingToPurchaseTowerWithoutFunds(towerType)) {
+ drawTowerIcon(location.x, location.y, towerErrorColour);
+ } else
+ drawTowerIcon(location.x, location.y, towerColours[towerType]);
+ fill(255);
+ textSize(14);
+ var textOffsetX = -15, textOffsetY = 26;
+ // displays the prices of towers
+ text("$" + towerPrice[towerType], location.x + textOffsetX, location.y + textOffsetY);
+ }
+}
+
+// To upgrade towers, click them and their radius will show around them. Click the upgrade button to upgrade the tpower to the next level
+var upgradeLocation;
+
+function towerClickCheck() {
+ if (mouseIsPressed) {
+ towerClicked = -1;
+ }
+ for (var i = 0; i < towers.length; i++) {
+ var xPos = towers[i].x, yPos = towers[i].y;
+ if (pointRectCollision(mouseX, mouseY, xPos, yPos, towerSize) && mouseIsPressed) {
+ // Drawing the tower range visually
+ towerClicked = i;
+ }
+ }
+}
+
+function drawRange() {
+ if (towerClicked != -1) {
+ var xPos = towers[towerClicked].x, yPos = towers[towerClicked].y;
+ var data = towerData[towerClicked];
+ fill(127, 80);
+ stroke(127);
+ strokeWeight(4);
+ ellipseMode(RADIUS);
+ ellipse(xPos, yPos, data[towerVision], data[towerVision]);
+ }
+}
+
+// method to get damage numbers from the type of tower's projectile
+function dmgFromProjectileType(type, temp) {
+ if (type == 0) {
+ var ret = defdmg;
+ if (temp[upgrade] >= 3) {
+ ret += temp[upgrade] - 2;
+ }
+ return ret;
+ } else if (type == 1) {
+ var ret = eightdmg;
+ if (temp[upgrade] >= 4) {
+ ret += temp[upgrade] - 3;
+ }
+ return ret;
+ } else if (type == 2) {
+ return slowdmg;
+ }
+ return 0;
+}
+
+// draw the tower UI - includes the remove option
+function drawTowerUI() {
+ if (towerClicked != -1) {
+ // draw outer box for upgrades
+ var temp = towerData[towerClicked];
+ stroke(color(0xad, 0xd5, 0x58));
+ strokeWeight(1);
+ fill(color(0xE7, 0xEA, 0xB5));
+ rect(200, 450, 216, 80, 3);
+ fill(color(0x44, 0x49, 0x41));
+ strokeWeight(0);
+ text("Current Level: " + temp[upgrade], 98, 426);
+ text("range: " + temp[towerVision], 104, 446);
+ text("damage: " + (dmgFromProjectileType(temp[projectileType], temp)), 204, 446);
+ strokeWeight(2);
+ stroke(color(0xa8, 0xa8, 0x9d, 200));
+ line(100, 453, 295, 453);
+ drawUpgrade();
+ upgradeCheck();
+ drawRemove();
+ removeCheck();
+ }
+}
+
+// EDIT THIS FOR UI FOR UPGRADES
+function drawUpgrade() {
+ strokeWeight(0);
+ stroke(0);
+ fill(color(0xC3, 0x64, 0xFF));
+ rectMode(CENTER);
+ rect(upgradeLocation.x, upgradeLocation.y, 86, 24, 5);
+ textSize(16);
+ fill(255);
+ var temp = towerData[towerClicked];
+ strokeWeight(0);
+ text("Buy: $" + towerPrice[temp[projectileType]] / 2, upgradeLocation.x - 40, upgradeLocation.y + 4);
+}
+
+function upgradeCheck() {
+ if ((upgradeLocation.x - 43 <= mouseX && mouseX <= upgradeLocation.x + 43 && upgradeLocation.y - 12 <= mouseY && mouseY <= upgradeLocation.y + 12) && mouseIsPressed && towerClicked != -1) {
+ var temp = towerData[towerClicked];
+ if (currentBalance >= towerPrice[temp[projectileType]] / 2) {
+ temp[upgrade]++;
+ currentBalance -= towerPrice[temp[projectileType]] / 2;
+ if (temp[projectileType] == 0) {
+ if (temp[upgrade] == 2) {
+ // first upgrade
+ // increases attack speed
+ temp[maxCooldown] = 8;
+ }
+ } else if (temp[projectileType] == 1) {
+ if (temp[upgrade] == 2) {
+ // second upgrade
+ temp[towerVision] += 50;
+ }
+ } else if (temp[projectileType] == 2) {
+ if (temp[upgrade] > 2) {
+ temp[towerVision] += 50;
+ }
+ }
+ towerData[towerClicked] = temp;
+ print("tower number: " + (towerClicked + 1) + ", upgrade level: " + temp[upgrade]);
+ }
+ }
+}
+
+function initializeFields() {
+ levels = [];
+ balloons = null;
+ distanceTravelled = 0;
+ delay = 1;
+ speed = 2;
+ maxHP = 3;
+ hp = 4;
+ slowed = 5;
+ ID = 6;
+ balloonRadius = 25;
+ levelNum = -1;
+ playingLevel = false;
+ health = 11;
+ currentBalance = 100000;
+ rewardPerBalloon = 15;
+ baseRewardPerWave = 10;
+ currentlyDragging = -1;
+ notDragging = -1;
+ def = 0;
+ eight = 1;
+ slow = 2;
+ towerCount = 3;
+ difX = 0;
+ difY = 0;
+ count = 0;
+ towerClicked = -1;
+ held = [ false, false, false ];
+ towerPrice = [ 100, 200, 200 ];
+ towerColours = [ color(0x7b, 0x9d, 0x32), color(0xF0, 0x98, 0xD7), color(0x82, 0xE5, 0xF7) ];
+ originalLocations = [ new p5.Vector(650, 50), new p5.Vector(700, 50), new p5.Vector(750, 50) ];
+ dragAndDropLocations = [ new p5.Vector(650, 50), new p5.Vector(700, 50), new p5.Vector(750, 50) ];
+ towers = null;
+ towerSize = 25;
+ towerErrorColour = color(0xE3, 0x07, 0x07);
+ trashX1 = 0;
+ trashY1 = 0;
+ trashX2 = 0;
+ trashY2 = 0;
+ framesSinceLost = 0;
+ pathSegments = []
+ start = 0;
+ end = 1;
+ startArc = 0;
+ centerArc = 1;
+ endArc = 2;
+ arcValues = 3;
+ PATH_RADIUS = 20;
+ pathLength = 0;
+ dp = [];
+ powerupCount = [ 5, 3, 3 ];
+ spikes = 0;
+ slowdown = 1;
+ speedboost = 2;
+ spikePierce = 3;
+ slowdownLength = 7;
+ speedBoostLength = 7;
+ slowdownAmount = 1;
+ slowdownRemaining = 0;
+ slowdownLocation = new p5.Vector(763, 208);
+ speedBoostAmount = 1;
+ speedBoostRemaining = 0;
+ speedBoostLocation = new p5.Vector(763, 258);
+ spikeLocation = new p5.Vector(763, 150);
+ spikeLocations = null;
+ spikeData = null;
+ originalSpikeLocation = new p5.Vector(763, 150);
+ spikeHeld = false;
+ center = [];
+ velocity = [];
+ projectileData = [];
+ balloonsHit = [];
+ damage = 0;
+ pierce = 1;
+ angle = 2;
+ currDistTravelled = 3;
+ maxDistTravelled = 4;
+ thickness = 5;
+ dmgType = 6;
+ projectileRadius = 11;
+ defdmg = 6;
+ eightdmg = 4;
+ slowdmg = 1;
+ shots = 8;
+ slowPercent = 0.7;
+ removeLocation = new p5.Vector(255, 470);
+ cooldownRemaining = 0;
+ maxCooldown = 1;
+ towerVision = 2;
+ projectileType = 3;
+ upgrade = 4;
+ towerData = null;
+ towerVisions = [ 200, 100, 100 ];
+ upgradeLocation = new p5.Vector(145, 470);
+}
+
+function preload() {
+ loadHeartIcon();
+ loadSpikeIcon();
+// TODO: put method calls that load from files into this method
+// I found the following calls that you should move here:
+// - on line 210: heart = loadImage("heart.png")
+// - on line 861: spikeIcon = loadImage("spikes.png")
+// (note that line numbers are from your Processing code)
+}
+
diff --git a/data/leaderboard.yaml b/data/leaderboard.yaml
deleted file mode 100644
index 9ac7550d..00000000
--- a/data/leaderboard.yaml
+++ /dev/null
@@ -1,394 +0,0 @@
-### Template: ###
-# - name: Name
-# rank: 1
-# scores:
-# - part: i
-# points: 200
-# bonus: 100
-# status: first-solve full-score # status for the table cell
-# - part: ii:
-# points: -9999
-# status: failed-score
-# - part: iii
-# total: 300
-# status: disqualified # status for the row
-#
-# status:
-# options: first-solve, full-score, partial-score, failed-score, disqualified
-# can space separate mutliple statuses (each has their own style)
-# default: full-score
-# (option names were chosen to match dmoj)
-#
-# full-score -> green bolded text
-# partial-score -> green text
-# failed-score -> red bolded text
-# first-solve -> highlights the box green
-# disqualified -> highlights the box red
-# The "part" key exists for human readability. Its value has no impact on the leaderboard.
-
-- name: Luka Jovanovic
- email: luka.jovanovic@student.tdsb.on.ca
- rank: 1
- scores:
- - part: i
- points: 200
- bonus: 100
- status: first-solve full-score
- - part: ii
- points: 200
- bonus: 300
- status: first-solve full-score
- - part: iii
- total: 800
-- name: Michael C
- email: michael.chen7@student.tdsb.on.ca
- rank: 2
- scores:
- - part: i
- points: 200
- bonus: 90
- status: first-solve full-score
- - part: ii
- points: 200
- bonus: 260
- status: first-solve full-score
- - part: iii
- total: 750
-- name: BattleMage_
- email: leyang.zou@student.tdsb.on.ca
- rank: 3
- scores:
- - part: i
- points: 200
- bonus: 100
- status: first-solve full-score
- - part: ii
- points: 200
- bonus: 240
- status: first-solve full-score
- - part: iii
- total: 740
-- name: Maplefin
- email: james.huynh2@student.tdsb.on.ca
- rank: 4
- scores:
- - part: i
- points: 200
- bonus: 60
- status: first-solve full-score
- - part: ii
- points: 200
- bonus: 220
- status: first-solve full-score
- - part: iii
- total: 680
-- name: AZron
- email: aaron.zhu@student.tdsb.on.ca
- rank: 5
- scores:
- - part: i
- points: 200
- bonus: 100
- status: first-solve full-score
- - part: ii
- points: 200
- bonus: 160
- status: first-solve full-score
- - part: iii
- total: 660
-- name: RedWiz
- email: saileshvijayaragavan.badri@student.tdsb.on.ca
- rank: 5
- scores:
- - part: i
- points: 200
- bonus: 60
- status: first-solve full-score
- - part: ii
- points: 200
- bonus: 200
- status: first-solve full-score
- - part: iii
- total: 660
-- name: Chelsea Wong
- email: chelsea.wong@student.tdsb.on.ca
- rank: 7
- scores:
- - part: i
- points: 200
- bonus: 100
- status: first-solve full-score
- - part: ii
- points: 200
- bonus: 140
- status: first-solve full-score
- - part: iii
- total: 640
-- name: alexa
- email: alyn.huang@student.tdsb.on.ca
- rank: 7
- scores:
- - part: i
- points: 200
- bonus: 60
- status: first-solve full-score
- - part: ii
- points: 200
- bonus: 180
- status: first-solve full-score
- - part: iii
- total: 640
-- name: 3xp3rtz
- email: caleb.chue@student.tdsb.on.ca
- rank: 8
- scores:
- - part: i
- - part: ii
- points: 200
- bonus: 280
- status: first-solve full-score
- - part: iii
- total: 580
-- name: Lost_Cactus
- email: sean.zhao2@student.tdsb.on.ca
- rank: 10
- scores:
- - part: i
- points: 200
- bonus: 50
- status: first-solve full-score
- - part: ii
- points: 200
- bonus: 120
- status: first-solve full-score
- - part: iii
- total: 570
-- name: lukas142434
- email: lukas.li@student.tdsb.on.ca
- rank: 11
- scores:
- - part: i
- points: 200
- bonus: 20
- status: first-solve full-score
- - part: ii
- points: 200
- bonus: 100
- status: first-solve full-score
- - part: iii
- total: 520
-- name: Felix
- email: felix.zhao2@student.tdsb.on.ca
- rank: 12
- scores:
- - part: i
- points: 200
- bonus: 10
- status: first-solve full-score
- - part: ii
- points: 200
- bonus: 100
- status: first-solve full-score
- - part: iii
- total: 510
-- name: Sarah Zhao
- email: sarah.zhao4@student.tdsb.on.ca
- rank: 13
- scores:
- - part: i
- points: 200
- bonus: 10
- status: first-solve full-score
- - part: ii
- points: 200
- - part: iii
- total: 410
-- name: Formoon28
- email: ethan.xu@student.tdsb.on.ca
- rank: 14
- scores:
- - part: i
- points: 200
- - part: ii
- points: 200
- - part: iii
- total: 400
-- name: Arjun
- email: arjun.jindal@student.tdsb.on.ca
- rank: 14
- scores:
- - part: i
- points: 200
- - part: ii
- points: 200
- - part: iii
- total: 400
-- name: Anthony Zanchetta
- email: anthony.zanchetta@student.tdsb.on.ca
- rank: 14
- scores:
- - part: i
- points: 200
- - part: ii
- points: 200
- - part: iii
- total: 400
-- name: Aidan Wang
- email: aidan.wang2@student.tdsb.on.ca
- rank: 14
- scores:
- - part: i
- points: 200
- - part: ii
- points: 200
- - part: iii
- total: 400
-- name: MuteMini
- email: min.kang@student.tdsb.on.ca
- rank: 18
- scores:
- - part: i
- points: 200
- bonus: 100
- status: first-solve full-score
- - part: ii
- - part: iii
- total: 300
-- name: MarsFlat
- email: shane.chen@student.tdsb.on.ca
- rank: 18
- scores:
- - part: i
- points: 200
- bonus: 100
- status: first-solve full-score
- - part: ii
- - part: iii
- total: 300
-- name: CodeBeginner
- email: joshua.persaud4@student.tdsb.on.ca
- rank: 18
- scores:
- - part: i
- - part: ii
- points: 200
- bonus: 100
- status: first-solve full-score
- - part: iii
- total: 300
-- name: Ryan Atlas
- email: ryan.atlas@student.tdsb.on.ca
- rank: 21
- scores:
- - part: i
- points: 200
- bonus: 90
- status: first-solve full-score
- - part: ii
- - part: iii
- total: 290
-- name: C.W.
- email: christina.wang4@student.tdsb.on.ca
- rank: 22
- scores:
- - part: i
- points: 200
- bonus: 20
- status: first-solve full-score
- - part: ii
- - part: iii
- total: 220
-- name: Minh
- email: minh.phan3@student.tdsb.on.ca
- rank: 23
- scores:
- - part: i
- points: 200
- bonus: 10
- status: first-solve full-score
- - part: ii
- - part: iii
- total: 210
-- name: Galit T.
- email: galit.tauber@student.tdsb.on.ca
- rank: 23
- scores:
- - part: i
- points: 200
- bonus: 10
- status: first-solve full-score
- - part: ii
- - part: iii
- total: 210
-- name: Ella R
- email: ella.richmond@student.tdsb.on.ca
- rank: 25
- scores:
- - part: i
- points: 200
- - part: ii
- - part: iii
- total: 200
-- name: Paul Lee
- email: paul.lee@student.tdsb.on.ca
- rank: 25
- scores:
- - part: i
- points: 200
- - part: ii
- - part: iii
- total: 200
-- name: Leo Liu
- email: leo.liu4@student.tdsb.on.ca
- rank: 25
- scores:
- - part: i
- points: 200
- - part: ii
- - part: iii
- total: 200
-- name: roma
- email: roman.shteflyuk@student.tdsb.on.ca
- rank: 25
- scores:
- - part: i
- points: 200
- - part: ii
- - part: iii
- total: 200
-- name: Ivy Zhuang
- email: ivy.zhuang@student.tdsb.on.ca
- rank: 25
- scores:
- - part: i
- points: 200
- - part: ii
- - part: iii
- total: 200
-- name: ryan gill
- email: ryan.gill2@student.tdsb.on.ca
- rank: 25
- scores:
- - part: i
- points: 200
- - part: ii
- - part: iii
- total: 200
-- name: deggy
- email: danya.cheng@student.tdsb.on.ca
- rank: 25
- scores:
- - part: i
- points: 200
- - part: ii
- - part: iii
- total: 200
-- name: Brian Song
- email: brian.song@student.tdsb.on.ca
- rank: 25
- scores:
- - part: i
- - part: ii
- points: 200
- - part: iii
- total: 200
diff --git a/layouts/shortcodes/countdown.html b/layouts/shortcodes/countdown.html
new file mode 100644
index 00000000..ad4f7c4a
--- /dev/null
+++ b/layouts/shortcodes/countdown.html
@@ -0,0 +1,23 @@
+
+
+
\ No newline at end of file
diff --git a/layouts/shortcodes/users-table.html b/layouts/shortcodes/users-table.html
index 6eb22658..ec1c677b 100644
--- a/layouts/shortcodes/users-table.html
+++ b/layouts/shortcodes/users-table.html
@@ -10,20 +10,71 @@
Total Points
- {{ range .Site.Data.leaderboard }}
-
-
{{ .rank }}
-
- {{ .name }}
-
- {{ range .scores }}
-
- {{ .points }}
- {{ if .bonus }} +{{ .bonus }}{{ end }}
-
- {{ end }}
-
{{ .total }}
-
- {{ end }}
+
+
diff --git a/static/img/ArrayVisual1.png b/static/img/ArrayVisual1.png
new file mode 100644
index 00000000..72feb1fd
Binary files /dev/null and b/static/img/ArrayVisual1.png differ
diff --git a/static/img/ArrayVisual2.png b/static/img/ArrayVisual2.png
new file mode 100644
index 00000000..5af9b736
Binary files /dev/null and b/static/img/ArrayVisual2.png differ
diff --git a/static/img/PathAPI/AddArc.png b/static/img/PathAPI/AddArc.png
new file mode 100644
index 00000000..0bc12f4c
Binary files /dev/null and b/static/img/PathAPI/AddArc.png differ
diff --git a/static/img/PathAPI/AddLine.png b/static/img/PathAPI/AddLine.png
new file mode 100644
index 00000000..d29d1d0e
Binary files /dev/null and b/static/img/PathAPI/AddLine.png differ
diff --git a/static/img/PathAPI/AddSmoothArcAfter.png b/static/img/PathAPI/AddSmoothArcAfter.png
new file mode 100644
index 00000000..686636f6
Binary files /dev/null and b/static/img/PathAPI/AddSmoothArcAfter.png differ
diff --git a/static/img/PathAPI/AddSmoothArcBefore.png b/static/img/PathAPI/AddSmoothArcBefore.png
new file mode 100644
index 00000000..253e7af6
Binary files /dev/null and b/static/img/PathAPI/AddSmoothArcBefore.png differ
diff --git a/static/img/PathAPI/AddSmoothLine.png b/static/img/PathAPI/AddSmoothLine.png
new file mode 100644
index 00000000..176a63c3
Binary files /dev/null and b/static/img/PathAPI/AddSmoothLine.png differ
diff --git a/static/img/PathAPI/RealPathExamples.png b/static/img/PathAPI/RealPathExamples.png
new file mode 100644
index 00000000..ca556195
Binary files /dev/null and b/static/img/PathAPI/RealPathExamples.png differ
diff --git a/static/img/game_jam_banner.png b/static/img/game_jam_banner.png
new file mode 100644
index 00000000..e97bb8ab
Binary files /dev/null and b/static/img/game_jam_banner.png differ
diff --git a/static/img/game_jam_logo.png b/static/img/game_jam_logo.png
new file mode 100644
index 00000000..0da70f4c
Binary files /dev/null and b/static/img/game_jam_logo.png differ