Jelly Blobs of Doom

For this assignment, we'll implement a delightful game that Alice Zhou and Jacqueline Young created for CS 111 in Python, and Alice adapted to JavaScript. I rewrote and refactored the game, but most of the credit goes to Alice, and I'm grateful for her work on this.

The instructions for the game are quite simple:

  • Move the mouse to control the blue blob
  • Eat blobs that are smaller than you in order to grow
  • Avoid bigger blogs or you will shrink

Goals

The educational goals for this assignment are primarily to use OOP and inheritance in your implementation.

There are three entities that you will have to implement:

  1. Blobs
  2. Enemies
  3. the Player

Both Enemies and the Player are subtypes of Blobs.

In addition to implementing these three objects, you'll define a mouse-movement event handler and you'll work with jQuery animations.

Reference Solution

Here's the obfuscated reference solution

Here's the obfuscated reference solution w/ debugging turned on The debugging version is exactly the same code but because debug=true is in the URL, the behavior is slightly different. Enemies go from edge to edge and don't disappear, rather than start and ending off-screen.

Feel free to copy the HTML and CSS files. Clear out the code in the obfuscated files. You will implement these files:

  • Blob.js implements the Blob class
  • Player.js implements the Player class, which inherits from Blob
  • Enemy.js implements the Enemy class, which also inherits from Blob
  • game.js attaches event handlers, puts all the pieces together, and implements the game.

Your solution will also load two files that I supply, namely:

TL;DR

Warning, this is another difficult assignment. But I have faith that you'll be able to do it, and, besides, it's a fun game. In total, my solution has about 270 lines in the four JavaScript files, including blank lines and comments.

Another note: this assignment description is long, and sometimes suffers from the TL;DR phenomenon. I understand that you're eager to get started coding, particularly since you know there's a lot of coding, but time spent reading and mentally preparing will pay off with less time spent coding. I'll tell you what my wife tells my son when he's eager to start cooking: first read the whole recipe because sometimes earlier steps make more sense when you know where the recipe is going. Then, you can start cooking, reading the recipe again step by step, while you cook. So, with this long recipe, I suggest you read it all first, and then read it again, step by step, as you code.

Blobs

Blobs have instance variables to keep track of

  • their color (though it never changes)
  • their radius (the player's size dynamically changes, but enemy sizes don't)
  • the x and y location of their center (changes for both subclasses)
  • the DOM element that corresponds to the blob.

The DOM element is quite simple:

<div class="circle"></div>

The CSS is where all the magic occurs:

.circle {
    border-radius: 50%;
    position: absolute;
    /* all the following will be overridden in real blobs */
    background-color: gray;
    width: 200px;
    height: 200px;
    top: 0px;
    left: 0px;
}

Because the border-radius is 50% (of the width of the DIV), the DIV will be a circle. The position:absolute will position the DIV on the page (you could have a smaller playing area, but we'll use the whole document).

I implemented the constructor to have two arguments, color and radius; I also implemented the following methods. Some might be unnecessary, but I wanted to be complete.

  • addToGame which adds the blob to some container (usually the body)
  • setDOM creates a DOM element (the div described above) and stores it in an instance variable
  • setColor which sets the color instance variable and also needs to update the DOM element's background-color property.
  • setRadius which sets the appropriate instance variable(s) and also needs to update the DOM element's width, height, left and top properties.
  • getColor returns the current color.
  • getDOM returns the DOM element stored in the instance variable
  • getDiameter returns the value
  • getRadius returns the value
  • getX and getY return the x,y coordinates of the center
  • setX and setY change the x,y coordinates of the center and also update the position of the DOM element by setting left or top.

You'll notice that there's no setDiameter method. Setting the diameter to any odd number means that the radius is not an integer. (If you set the diameter to 3, the radius is 1.5.) Non-integers can be ugly and awkward sometimes, particularly when we are synchronizing them with CSS. So I eliminated that method. You can implement it if you want, but stick to even integer values.

Object Location

The math involved with the object's (x,y) location and the DOM element's CSS positioning (left,top) is not complicated, but students often get confused, so I often end up drawing the following figure:

the relationship of left, x and radius
The relationship of left & top, x & y and the blob's radius. The teal box is the playing area, and the blob has a radius of 50px. The blob is positioned at coordinates (350,150) relative to the playing area. Consequently, left is 300 and top is 100.

The following should always be true:

left+radius == x
top+radius == y

Given that figure, ask yourself:

  • if the caller uses setX to change the blob's location, what values have to change and how?
  • if the caller uses setRadius to make the blob bigger, while keeping the center (x,y) unchanged (that is, the blob doesn't move), what values have to change and how?
  • if the animation moves the blob by changinging the left and invokes your progress callback, what values have to change and how?

Checking Location

Since getting x, top and all those values correct and in sync can be difficult, I've provided the following method that will return a string letting you know whether you got the basic invariants right:

    location() {
        let x = this.getX();
        let y = this.getY();
        let left = parseInt(this.getDOM().css('left'),10);
        let top = parseInt(this.getDOM().css('top'),10);
        let r = this.getRadius();
        let xok = (left+r==x) ? "X OK" : "X WRONG";
        let yok = (top+r==y) ? "Y OK" : "Y WRONG";
        return `radius ${r} center (${x},${y}) w/ DOM elt (${left},${top}): ${xok}, ${yok}`;
    }

You can include this with your other methods for Blob objects.

I suggest you call this regularly when you are testing your blobs and enemies. You can test it easily from the JS console, like this:

> b1 = new Blob('red', 200);
Object { elt: {…}, radius: 200, color: "red", x: 0, y: 0 }

> b1.location();
"radius 200 center (0,0) w/ DOM elt (-200,-200): X OK, Y OK"

> b1.setX(150);
Object { elt: {…}, radius: 200, color: "red", x: 150, y: 0 }

> b1.location();
"radius 200 center (150,0) w/ DOM elt (50,-200): X OK, Y OK"

Intersection

I will give you the following code, which determines whether two circular Blobs intersect. It uses the Pythagorean theorem to compute the distance between their centers (d), and then compares that to the sum of the two radiuses (r1+r2). Essentially, we want to return true if and only if:

d < r1+r2

However, the Pythagorean theorem requires us to compute d as the square root of the sum of two squares. The square root function is relatively expensive (try computing one by hand, without a calculator). So, we use a trick: we square both sides of that inequality, and we return true if and only if:

d^2 < (r1+r2)^2

Here's the code, using the new method syntax:

function isNum(val) {
    if( typeof val === 'number' ) {
        return val;
    } else {
        throw new Error('value is not a number');
    }
}

class Blob {
    //
    intersects (other) {
        // six uses of the 'isNum' function to make sure all values are defined
        const dx = isNum(this.getX()) - isNum(other.getX());
        const dy = isNum(this.getY()) - isNum(other.getY());
        const r1 = isNum(this.getRadius());
        const r2 = isNum(other.getRadius());

        // finally, some real computation
        const distance_squared = (dx * dx + dy * dy);

        const rsum = r1+r2;
        const isCloser = (distance_squared <= rsum*rsum);
        return isCloser;
    }
}

Note: I've defined a function isNum that checks that its argument is a number, since a common error is to have a broken coordinate or radius, which makes the intersection code fail. This catches that error. You're welcome to use isNum in your own code.

Testing Blobs

The constructor for a Blob should initialize the color and radius. It can set the x and y of the center to zero; those will change later. It will also create the DOM element that corresponds to this blob and it will add it to the game, using the addToGame method to add the DOM element to the "body". (It would be better modularity to keep addToGame separate, but combining them makes testing and debugging easier, and that's worth doing here.)

The following code, executed in the JS console, should give you a small, red quarter circle in the upper left corner of your page, and a small green blob below, just touching, and a small orange blog to its right, just touching.

b1 = new Blob("red", 50);

b2 = new Blob("green", 50);
b2.setY(100);

b3 = new Blob("orange", 50);
b3.setX(100);

Like this:

small red blob

Here are some of the instance variables:

>> b1
Object { elt: {}, radius: 50, color: "red", x: 0, y: 0 }

>> b2
Object { elt: {}, radius: 50, color: "green", x: 0, y: 100 }

>> b3 
Object { elt: {}, radius: 50, color: "orange", x: 100, y: 0 }

The details of the elt are not show, but that's the DOM element (the DIV.circle) corresponding to this blob. If the DOM element doesn't show up, check the element CSS.

Be sure to liberally use the location() method that I provided to check these methods.

You should check the working of blobs thoroughly before going on to implement Player and Enemy.

  • Check that if you set the radius, the DOM element grows/shrinks correctly.
  • Check that if you set the radius, the getter methods for both getRadius and getDiameter report the new value. That is, if you set the radius to 100, getDiameter should return 200.
  • Check that if you set the X or Y location, the DOM element moves on the screen and the getter methods are correctly updated.

Many students get confused about how x, y, top, left, radius and diameter are all related. See object location.

The testing.js file has some useful testing functions, particularly testDOM(), testCalculations() and a number of others. See testing

Player Object

There will only be one instance of the Player object, but we'll define it as a class anyhow. (Maybe there will be a multi-player version someday...). Its constructor can have the same arguments as Blob. The player will inherit all the behavior of Blobs, plus the following methods:

  • move which takes an x,y location and moves the DIV so that the center is in the new location.
  • grow which increases the radius by growRadius pixels.
  • shrink which decreases the radius by shrinkRadius pixels
  • collide which is invoked when a collision happens: that is, when an enemy says "I got you". It takes the enemy blob as an argument and either grows or shrinks the player as appropriate.

The grow and shrink methods also compare the new size to some limits. If the limit is exceeded, the game ends, with the player winning or losing.

Here are some key values:

var winningRadius = window.innerHeight/4; // bigger than this wins
var losingRadius = 4;                     // smaller than this loses
var growRadius = 10;                      // grow by this many pixels
var shrinkRadius = 3;                     // shrink by this many pixels

Testing the Player object is much like testing the blobs, except there are a few new methods. Consider the following tests in the JS console. If you're not sure what they should do, just ask:

var thePlayer = new Player('blue', 15);
thePlayer.move(200,300);
thePlayer.grow();  // or .shrink()

To check the collision handling, just create an Enemy, like this:

bigBad = new Enemy();
bigBad.setRadius(50);     // bigger than thePlayer
thePlayer.collide(bigBad);   // big Enemy got me, I gotta shrink

Note that this code happens after the intersection checking, so it doesn't matter where the bigBad is; it only matters that it's bigger than the Player blob.

We can also check eating small prey:

smallPrey = new Enemy();
smallPrey.setRadius(10);     // smaller than thePlayer
thePlayer.collide(smallPrey);  // ate this small Enemy; I will grow

The enemy should also disappear when it's eaten, because the player will invoke the Enemy's remove method.

The Player Object

Your Player class will be named Player, so the name of the global variable must be different. Remember that classes and instances are different things, so they need to be stored in different variables.

You'll notice above that I consistently named my global variable thePlayer like this:

var thePlayer = new Player('blue', 15);

I recommend you do the same. Your Player.js file will define the Player class and declare the global variable, creating an instance of the class.

While this isn't ideal modularity, it's not bad and makes testing a little easier — a worthwhile tradeoff.

Enemies

Enemies have some interesting and complex behavior, since they are autonomous.

The constructor of an enemy takes no arguments and does the following:

  • gives it a random color and a random radius

An earlier version of these directions also said that the constructor should specify the Enemy's direction and location, but it's easier to put that off and make the user do that.

For the random color and direction, use the functions in random.js, which I supplied, and you should load without editing the file, but you ought to read the file.

The enemy blobs always move in a straight line directly across the window, so if they come in from the left, they go in a horizontal line and exit on the right. Their y (top) coordinate never changes. (You can imagine many fun improvements and variations on this game.) Choose their direction or starting side randomly among the four possibilities.

Enemy blobs can collide with the player. (As noted above, the player object is stored in a global variable defined in Player.js.)

Oddly and asymmetrically, it's the enemies who check for collisions, not the player. In other words, when an enemy moves, it checks to see if it has collided with the player, but when the player moves, it doesn't check to see if it has collided with any enemies. This rule, odd as it is, has the advantage that an enemy only has to check one other blob for collisions (namely the player), while the player would have to check against every enemy. This rule means that the player code doesn't have to worry about all the enemies (the human player does).

In the finished game, enemies start and end off-screen, but that can make debugging hard, so I suggest that, at first, you have them start and end at the edges of the screen. Once their motion is all debugged, you can have them start and end off-screen. (That's the difference between the regular and "debug" mode in my solutions, above.)

Furthermore, enemies can only collide with the player once. This means, for example, that if the enemy moves over the player in ten steps, that doesn't count as ten collisions. To implement this rule, an enemy will have to keep track of whether it has collided with the player, and if it has already collided, skip any further collision checking.

Whenever an enemy moves, it does the following:

  • updates the values of X and Y from the DOM object. As we'll see later, jQuery animations modify the CSS properties, and our code will have to extract the values of top and left in order to determine the center of the blob, so that the intersects algorithm above can be used.
  • it then uses the intersects method on itself and thePlayer. If that indicates that an collision has occurred, it
  • invokes thePlayer's collide method, described above.

Setting initial coordinates is a bit tricky, but it breaks down into two or four straightforward cases:

  • If the blob is entering from the top (or bottom) moving vertically, choose its X coordinate randomly between the left and right edge of the window (0 and window.innerWidth). The Y coordinate is chosen so that the blob is just at the edge of the screen. So, zero for the top edge and window.innerHeight if it's entering from the bottom edge.
  • if the blob is entering from the left (or right) moving horizontally, choose its Y coordinate randomly between the top and bottom edge of the window (0 and window.innerHeight). The X coordinate is chosen so that the blob is at the edge of the screen, so zero for the left edge and window.innerWith for the right edge.

The enemy has a remove method that stops the animation and removes the DOM element from the document. This method is used by the player when it eats a smaller blob or when the animation is done. (The player testing code above shows how to test this: just create big and small enemies instead of just blobs.)

The enemy has a start method that starts its animation going. So, in a moment, we'll turn to animation to learn more. First, though, here are some values you might want:

var minRadius = 4;                   // random size >= this
var maxRadius = window.innerWidth/8; // random size <= this
var enemyDuration = 5000;              // time to cross the page

Here's a complete list of the methods of the methods I implemented for my Enemies objects:

  • collide which is invoked when a collision happens and it records that the enemy has collided with the player and informs the player of the fact. That is, it invokes the player's collide method (see above). Remember that the player object is stored in a global variable that is declared and initialized in Player.js.
  • updateLocation which updates the X and Y location of the center from the top/left CSS values. This is helpful when the animation moves the enemy and you need to update the location before testing for intersection with the player.
  • maybeCollide which checks for a collision. It's invoked during the animation of the movement of the enemy. The method first updates its location. Next it checks to see if this enemy has collided with the Player in the past and if so, skips any further processing. If it hasn't collided in the past, it checks to see if there is an intersection (using the intersects method above) and if so, invokes the collide method that we just discussed.
  • setSide(side) which takes one argument, a string indicating which side of the screen the enemy is entering from. The argument is one of "top", "right", "bottom", and "left". The method sets the initial X,Y coordinates of the enemy, based on the side it enters from. It also records the side, as that makes the start method easier.
  • start which starts the jQuery animation of this enemy moving across the board to its final X/Y value.
  • remove which stops the animation and removes this enemy from the board

Many people are confused by the difference between maybeCollide and collide. See collision methods below.

Testing the enemies is a little tricky because the enemies are always moving. But we can test the setup. We can also test what happens during a single step ("frame") of the animation. Setup is like this:

e1 = new Enemy();
e1.setSide('top');

The enemy should have a random starting location on the top edge of the board. You can test the other three sides similarly.

Testing updateLocation

The animation will modify the CSS top or left values to move the enemy. Let's similate one step of that. In the code below, I've gotten the DOM element and modified its CSS (this is what the animation will do).

e1 = new Enemy();
Object { elt: {…}, radius: 17, color: "Bisque", x: 0, y: 0, collided: false }

e1.setRadius(100);
Object { elt: {…}, radius: 100, color: "Bisque", x: 0, y: 0, collided: false }

e1.location();
"radius 100 center (0,0) w/ DOM elt (-100,-100): X OK, Y OK"
d1 = e1.getDOM()
Object { 0: div.circle
, length: 1 }

d1.css('top', 100).css('left', 200);
Object { 0: div.circle, length: 1 }

e1.location();
"radius 100 center (0,0) w/ DOM elt (200,100): X WRONG, Y WRONG"
e1.updateLocation();
undefined
e1.location();
"radius 100 center (300,200) w/ DOM elt (200,100): X OK, Y OK"

This code places the enemy at the upper left corner of the page, showing just a quarter of it. Moving the DOM element means the x,y values are wrong, as the location method shows. After the updateLocation method runs, the X and Y location of the enemy should have the correct values, which the location method confirms.

Remember that the jQuery .css method can be used to either set the css value: $(elt).css('top',newVal) or it can be used to get the current value: $(elt).css('top'). The latter, however, returns the value as a string with 'px' at the end, like '50px'. To parse that string to extract the value as a number that you can do arithmetic with, you can use the builtin JavaScript parseInt function:

parseInt('50px', 10) // returns 50 as a number

Testing Intersection

Next, to test intersection, let's create a Player that doesn't intersect, test that, and then move the Player so that it does.

e1 = new Enemy();
e1.setRadius(100);
e1.getDOM().css('top',0).css('left',0)
e1.updateLocation();
thePlayer = new Player('blue',15);
thePlayer.move(300,300);
e1.maybeCollide(); // doesn't collide
thePlayer.move(200,100); // on the right edge of the enemy, definite collision
e1.maybeCollide(); // does collide, player shrinks
e1.maybeCollide(); // second collision doesn't count

Once all that works, you're ready to start putting the animation together.

jQuery Animation

Animation in jQuery is very cool and powerful. You can read a ton about the animate method on the jQuery site.

I used a version of the jQuery animate method that looks like this:

$(elt).animate({left: 500}, options);

That method starts moving the element by adjusting its left CSS property until it is 500px, regardless of its starting value. Obviously, you won't use exactly that code, but that example should get you going.

The options argument in a JS object literal that can have many properties. I used three:

  1. duration a time in milliseconds for the animation to take. Smaller numbers mean the enemies move faster and the game is more challenging.
  2. progress a function that jQuery invokes at every step of the animation. This is a good time to update the location and check for collisions. If you want to use a method of an object, remember that you'll need a value for this. Arrow functions might be useful here.
  3. complete a function that jQuery invokes when the animation is done. Again, if this is a method, you might find an arrow function to be useful.

You can stop an ongoing animation by using the jQuery .stop() method:

$(sel).stop();

Animation and our Model

The jQuery animation happens separately from our JavaScript code. The jQuery animation incrementally updates the top or left CSS values of the DOM object (the div.circle that we discussed above). By default, the jQuery animation does not update anything in our Blob object: no instance variables are modified.

Let's take a concrete example to explain what is going on. Suppose we have a blob with a radius of 10. It starts out nestled snugly in the upper left corner of the screen, so the CSS looks like top:0px;left:0px and the Blob object has instance variables recording that the center of the blob is at (10,10).

We then start an animation where the blob is moving diagonally across the screen. After a while, the CSS values will be top:500px;left:500px but the Blob object's instance variables still have the center at (10,10). That's because the animation only updates the CSS values we asked it to, nothing else.

If we want it to update something else, namely the blob's center, we can use the progress callback. (This is information flowing from the animation back to our JS code.) Here's an example of the idea, from the testProgress testing function in testing.js:

function testProgress() {
    $(".circle").remove();      // remove any prior blobs
    testBlob = new Enemy();
    testBlob.setX(100);
    testBlob.setY(100);
    $(testBlob.getDOM())
        .animate({ left: 500 },
                 { duration: 3000,
                   progress: function () {
                       testBlob.updateLocation();
                       console.log("x is now ",testBlob.getX());
                 }});                                  
}

Of course, the code above is not sufficiently modular or generic. You should define a method that does the right thing here. Again, you might find arrow functions useful, since you need to a function with the correct value of this for the progress callback.

Testing

In my experience, the most common student errors came in

  • getting the blob model correct: the correct center x,y and radius
  • updating the blob model from the animation, as described in the previous section
  • computing the intersection of two blobs. Once the model is correct, this is usually a straightforward use of the code I supplied.

To make this easier, I am supplying some testing code in

https://cs.wellesley.edu/~cs204/assignments/jelly/testing.js

The reference solution loads that file. You should try the testing functions in the reference solution and compare them with your own.

The functions testDOM and testCalculations will help you test your Blob implementation. These work without any obvious stuff on the screen, so you'll use them in the JS console. The next few functions display blobs on the page, so there have a visual component, and there are screenshots below.

The testIntersection function puts two blobs on the screen, just touching. You can move the green one relative to the red one, so that sometimes they intersect and sometimes they don't. Read the examples.

The testProgress function allows you to test your own progress callback, as described above.

Here are screen shots of the various testing functions working with my solution. The testBlobDisplay* functions create and display a green blob in a red box, so you can test the basic display and sizing constructor and methods. Note that there's no visual output from testBlobDisplay4() so I've omitted that, but you should stil run it for the console.log statements. There are also functions to test the blob intersection, which are the last two pictures. In the first one, the blobs just overlap by a pixel or so. In the second, they have been moved just a little and don't intersect. Your code should work the same. Again, try these in the reference solution to see how they should work, then try them in your own solution.

screenshot from testBlobDisplay1()
screenshot from testBlobDisplay1()
screenshot from testBlobDisplay2()
screenshot from testBlobDisplay2()
screenshot from testBlobDisplay3()
screenshot from testBlobDisplay3()
screenshot from testBlobDisplay5()
screenshot from testBlobDisplay5()
screenshot from testIntersect1()
screenshot testIntersect1()
screenshot from testIntersect2()
screenshot testIntersect2()

Mouse Movement

One of the exciting things that players do is have their movement determined by the user's mouse. This is surprisingly easy. We just have to set up an event handler for an event called mousemove. That event happens, you guessed it, every time the mouse moves. As with all events, the handler is invoked with an event object that captures important information about the event. Here, the relevant information is the location of the mouse, which are in properties clientX and clientY.

So, code like:

$(document).on('mousemove', function (evt) { ...evt.clientX,evt.clientY... });

Your event handler should update the blob representing the player and that should indirectly update the blue div.circle on the screen. I suggest the move() method you defined.

Random Stuff

You'll need a bunch of code for generating random values and random colors. I've provided some useful functions for you in https://cs.wellesley.edu/~cs204/assignments/jelly/random.js

Again, that's loaded by the reference solution. You should do the same and use the functions in it.

Automatic stuff

To keep the enemies coming as long as the game is played, we need to generate a new one every so often. To do that, I used setInterval, which we also learned (will learn) in the context of automatic slide shows:

var intervalID = setInterval(function () { ... }, delay_in_milliseconds);

It's really just another callback: we write a function, hand it to the browser and the browser invokes it at the appropriate time(s). In this case, every N milliseconds, where we specify the N.

Recall that the return value from setInterval, which I stored in intervalID above, can be used to stop the interval, using clearInterval. You'll find that useful in stopping the game:

clearInterval(intervalID);

I suggest you define a function called launchEnemy that does all the work to launch a single enemy. Test this in the console to make sure it works. Later, you can use setInterval and launchEnemy to automatically launch one every second.

Starting the Game

The home page is fairly simple: there is some explanatory text and one button. The button starts the game by invoking the startGame() function.

The startGame function does the following:

  1. Removes all the other info stuff from the document, clearing the slate
  2. Uses setInterval() and launchEnemy() to start generating a new enemy every 1 second.
  3. create a new player object, setting the global variable thePlayer, and putting the player in the center ofthe board.
  4. set up the mouse movement event handler for the player.

I made the player be blue (and 'blue' is not a possible random color) and initially it is 15 pixels in radius.

Stopping the Game

You should define a function, stopGame(result) to stop the game. It'll be used by the player to stop the game when it is won or lost. That function should:

  • stop the generation of new enemies
  • stop the animation of all current enemies
  • announce the result, which is an argument

The best place to invoke the stopGame(result) function is in the Player code that grows or shrinks the player, because that is the time that the game is either won or lost.

After the game ends, the human playing it can reload the page to play again. There's no "replay" button.

Hints

There are a number of hints I routinely give out. This covers most of them. Feel free to ask if you're confused about anything.

Element CSS

Many people have managed to create a blob and add it to the screen, such that they can see it in the Chrome Inspector, but the blobs are invisible. A complete active blob will have all of the following CSS properties:

position:absolute;
border-radius: 50%;
left: ...;
top: ...;
width: ...;
height: ...;
background-color: ...;

Remember that CSS's block model wants to know the width and height of the rectangular region occupied by the blob and the absolute positioning needs to know the left and top. The intersection code needs to know the center coordinates and radius. There's an obvious relationship between the two, but keeping these in sync is tricky. Here's a screenshot that may help, where I've put the mouse over a blob I added to the screen using the testIntersect function:

The CSS for a circular blob with center (221,261) and radius 100

Putting Elements Off-stage

Putting overflow:hidden on the body will keep the body from becoming wider or taller when there is an enemy coming in from the right or the bottom.

Collision Methods

The naming of the three collision methods can be confusing. Here's one way to think about them:

  • Enemy.maybeCollide: called constantly during animations. checks for collisions with the player. Think of it as didIgetThePlayer?
  • Enemy.collide: called when an actual collision happens. Think of it as IgotThem!
  • Player.collide: called from Enemy.collide when an actual collision happens. Think of it as youGotMe

Modularity

This assignment is hard enough, so don't worry about packaging things up in IIFE modules.

Getting Started

You're welcome to use some of my files, just not the obfuscated .js files:

cd ~/public_html/cs204-assignments
mkdir jelly
cp ~cs204/pub/assignments/jelly/solution/jBlobs.html jelly
cp ~cs204/pub/assignments/jelly/solution/jBlobs.css jelly

How to turn this in

Do all your work in a new folder called jelly; turn in a Gradescope item.

Tutors/Graders

You can use the password to view the solution

Here's the version with debugging turned on: solution w/ debugging

Time and Work

The following link has been updated for Fall 2023.

Finally, when you have completed the assignment, make sure you fill out the Time and Work Fall 2023 That report is required.