HW03 - james-bern/CS136 GitHub Wiki

Enter the Gungeon

README (Before Lab)

Vector2

  • i wrote a class called Vector2, which can be used to represent a 2D mathematical point or vector
    • e.g., in high-school geometry, $(0.0, 0.0)^T$ was the origin of the plane, and $(1.0, 0.0)^T$ was the $x$-axis
  • here is a simplified version of the class:
class Vector2 {
    // // INSTANCE VARIABLES
    double x;
    double y;

    // // toString() is a special Java method that is called automatically by System.out.print*
    public String toString() {
        return "(" + x + ", " + y + ")";
    }

    // // CONSTRUCTORS
    // Option A: default constructor (instance variables cleared to zero automatically)
    Vector2() {}
    // Option B: specify x and y separately
    Vector2(double x, double y) {
        this.x = x;
        this.y = y;
    }
    // Option C: copy constructor (copy x and y from some other Vector2 instance/object)
    Vector2(Vector2 other) {
        this.x = other.x; 
        this.y = other.x;
    }


    // // INSTANCE METHODS (called on an instance of the Vector2 class)
    // this + other
    Vector2 add(Vector2 other) {
        return new Vector2(this.x + other.x, this.y + other.y);
    }
    // this - other
    Vector2 minus(Vector2 other) {
        return new Vector2(this.x - other.x, this.y - other.y);
    }


    // // STATIC METHODS (NOT called on an instance of the Vector2 class)
    // distance between points a and b
    static Vector2 distanceBetween(Vector2 a, Vector2 b) {
        Vector2 v = b.minus(a);
        return Math.sqrt(v.x * v.x + v.y * v.y);
    }
}

examples

$$\mathbf{a} = \begin{bmatrix}1.0\\2.0\end{bmatrix}$$
Vector2 a = new Vector2(1.0, 2.0);
// This is the same thing but with more typing.
Vector2 a = new Vector2();
a.x = 1.0;
a.y = 2.0;
$$\displaylines{\mathbf{a} = \begin{bmatrix}1.0\\2.0\end{bmatrix}\\\ \mathbf{b} = \begin{bmatrix}3.0\\4.0\end{bmatrix}\\\ \mathbf{c} = \mathbf{a} + \mathbf{b}}$$
Vector2 a = new Vector2(1.0, 2.0);
Vector2 b = new Vector2(3.0, 4.0);
Vector2 c = a.plus(b);
// This is the same thing but with more typing.
Vector2 a = new Vector2();
a.x = 1.0;
a.y = 2.0;
Vector2 b = new Vector2();
b.x = 3.0;
b.y = 4.0;
Vector2 c = new Vector2();
c.x = a.x + b.x;
c.y = a.y + b.y;
$$\displaylines{\mathbf{a} = \begin{bmatrix}1.0\\2.0\end{bmatrix}\\\ \mathbf{b} = \begin{bmatrix}3.0\\4.0\end{bmatrix}\\\ d\text{ is the distance between point }\mathbf{a}\text{ to point }\mathbf{b}}$$
Vector2 a = new Vector2(1.0, 2.0);
Vector2 b = new Vector2(3.0, 4.0);
double d = Vector2.distanceBetween(a, b);

Java Memory Model

primtives

  • primitive types include bool int, double, and char
    • int a; is "an int"

example

int a = 1; // a = 1;        a is an int
int b = a; // a = 1, b = 1; a and b are DIFFERENT ints
           //               (which happen to have the same value)
++a;       // a = 2, b = 1;

image

non-primtives (instances/objects of classess)

  • in Java, everything else (i.e., instances/objects of classes) are non-primitive types
    • you create a new instance/object of a class using the new keyword, which returns a reference to that instance/object
    • Vector2 a; is "a reference to an instance of the Vector2 class"
    • in diagrams, we often draw references as arrows

example

Vector2 a = new Vector2(0.0, 0.0); // a -> (0.0, 0.0)
Vector2 b = a;                     // a, b -> (0.0, 0.0);
                                   // a and b are references to the SAME instance of the Vector2 class
                                   // there is only 1 instance because we only called new once!
a.x = 1.0;                         // a, b -> (1.0, 0.0)

image

Circle-Circle Intersection

  • Consider two circles A and B.
    • Circle A has center position $\mathbf{p}_A$ and radius $r_A$.
    • Circle B has center position $\mathbf{p}_B$ and radius $r_B$.
    • The two circles intersect if the distance between $\mathbf{p}_A$ and $\mathbf{p}_B$ is less than $r_A + r_B.$

Skim the Documentation for Vector2, Vector3, and App

Specification

  • First, read through the Starter Code very carefully, and play the game. Figure out how the game works by poking around, making small changes to the code (for example, red -> green) and seeing what happens.

    • This could easily take 1-2 hours.
  • To get a B:

    • ✅ Make the game have two turrets firing at the player instead of one.
      • BEFORE (NOTE: Non-turret code left out for clarity; It is still actually there!)
        class HW03 {
            Turret turret;
        
            void setup() {
                turret = new Turret();
                turret.color = new Vector3(Vector3.red);
                ...
                turret.position = new Vector2(0.0, 0.0);
            }
        
            void loop() {
                if (turret.framesSinceFired++ == 32) {
                    turret.framesSinceFired = 0;
        
                    // fire bullet
                    ... // SO MUCH CODE
                }
                drawCircle(turret.position, turret.radius, turret.color);
            }
        }
      • AFTER (NOTE: Non-turret code left out for clarity; It is still actually there!)
        class HW03 {
            Turret[] turrets;
            
            void setup() { // called once at the beginning of the game
                turrets = new Turret[2];
                for (int turretIndex = 0; turretIndex < turrets.length; ++turretIndex) {
                    turrets[turretIndex] = new Turret();
                    turrets[turretIndex].color = new Vector3(Vector3.red);
                    ...
                }
                turrets[0].position = new Vector2(-40.0, 0.0);
                turrets[1].position = new Vector2( 40.0, 0.0);
            }
        
            void loop() { // called over and over, once per frame
                for (int turretIndex = 0; turretIndex < turrets.length; ++turretIndex) {
                    if (turrets[turretIndex].framesSinceFired++ == 32) {
                       turrets[turretIndex].framesSinceFired = 0;
        
                       // fire bullet
                       ... // SO MUCH CODE
                    }
                    drawCircle(turret.position, turret.radius, turret.color); 
                }
            }
        }
      • 🚨 The turrets must be stored in an array like Turret[] turrets;.
        • 🚨 The turret array must be initialized in setup() using a for loop to avoid repeating code.
          • Some of the initialization (like position) won't make sense to put in the for loop. That's okay, you can put those things after the for loop. Just try to avoid massive repetition.
        • 🚨 The turret array must be updated in loop() using a for loop to avoid repeating code.
    • ✅ Press I, J, K, or L to make the player fire a bullets up, left, down, or right.
      • AFTER (NOTE: Non-bullet-firing code left out for clarity; It is still actually there!)
        class HW03 {
            // First, decide what arguments this function should take.
            // Then, move the code marked SO MUCH CODE (everything under `// fire bullet`) into the body of this function.
            // Then, "hook it up" to the arguments (`bullet.position = position;`, etc.).
            // NOTE: I show how `position` would be done below.
            //       You will have to figure out the other arguments yourself.
            void fireBullet(Vector2 position, ...) {
                for (int bulletIndex = 0; bulletIndex < bullets.length; ++bulletIndex) {
                    if (!bullets[bulletIndex].alive) { // write to first empty ("dead") slot in bullets array
                        bullets[bulletIndex].position = new Vector2(position);
                        ...
        
                        break;
                    }
                } 
            }
            
            void setup() { // called once at the beginning of the game
                ...
            }
        
            void loop() { // called over and over, once per frame
                // player
                ...
                if (keyPressed('I')) { fireBullet(player.position, ...); }
                if (keyPressed('J')) { fireBullet(player.position, ...); }
                if (keyPressed('K')) { fireBullet(player.position, ...); }
                if (keyPressed('L')) { fireBullet(player.position, ...); }
        
                // turrets
                for (int turretIndex = 0; turretIndex < turrets.length; ++turretIndex) {
                    if (turrets[turretIndex].framesSinceFired++ == 32) {
                       turrets[turretIndex].framesSinceFired = 0;
                       fireBullet(turrets[turretIndex].position, ...);
                    }
                    drawCircle(turrets[turretIndex].position, turrets[turretIndex].radius, turrets[turretIndex].color); 
                }
        
                // bullets
                ...
            }
        }
      • 🚨 Both the turrets and the player should fire bullets using a single function something like void fireBullet(...) { ... }, which will need to take the bullet's type (Bullet.TYPE_PLAYER or BULLET.TYPE_TURRET) as one of its arguments. It will also need to take the bullet's position and velocity.
        • 🚨 All fired bullets must be written to the same bullets array that was provided in the starter code.
      • ✅ These "player bullets" must be the same color as the player.
  • To get an A:

    • ✅ Implement circle-circle collision detection.

      • ✅ If the player gets hit by a turret bullet, the game must reset.
        • ✨ Just call reset(); 🙂👍
      • ✅ If the turret gets hit by a player bullet, that bullet must disappear.
        • You can just set bullet.alive = false; 🙂👍
      • 🚨 The circle-circle intersection math must show up in exactly one place in the code (NOT two places).
        • ✨ Perhaps inside a function like boolean circleCircleIntersection(...) { ...};
          • If you do write a function, keep it simple and general-purpose! Make it take positions and radii and arguments. 🙂👍
      • ✅ Turrets have health and die after getting hit a few times.
        • 🚨 Dead turrets should not be drawn.
        • 🚨 Dead turrets should not collide with bullets.
        • 🚨 Dead turrets should not fire bullets (though bullets a turret fired before dying can be kept alive).
    • ✅ Clean up your code.

      • 🚨 Delete commented out code.
        • 🚨 There should NOT be the line Turret turret; still in your code when you submit it. 😬
      • 🚨 Make sure variable names are reasonable and consistent.
      • 🚨 Make sure indentation is reasonable and consistent.
    • ✅ (Optional but Recommended) After you've finished the for loop version of initializing the turrets, you can write a (non-default) constructor Turret(Vector2 position) which sets its position to the argument position and also sets its radius, color, etc. to default values (the stuff that used to be in the body of the loop). Once you do this, your code for initialization turrets will look like this:

      turrets = new Turret[2];
      turrets[0] = new Turret(new Vector2(-40.0, 0.0));
      turrets[1] = new Turret(new Vector2( 40.0, 0.0));
  • To get an S:

    • ✅ Implement everything above.
    • ✅ Make the game good. 🙂
      • ✨ Some ideas...
        • When turret gets hit it should flash different colors.
        • More weapons.
        • Particle effects.
        • Make the turret chase you around.
        • Turrets that shoot turrets.
        • You have a pet.
        • Turrets have health bars.
        • You can dive roll like in enter the gungeon.
        • Obstacles (walls, etc.)
        • Enemies are smart (A* search, etc.)
        • ???

Submission Instructions

  • Upload your code file to GradeScope by the due date.
  • Live demo your code to Jim or a Grading TA in Lab.
    • 🚨 You must do this in order to receive points.
    • If you are trying for an S, you must demo to Jim.

Starter Code

  1. Create a folder called HW03
  2. Create a new file in that folder called Cow.java and copy and paste the contents of Cow.java inside
    • Click link above
    • Click icon with the two squares at the top right of the file to copy it
  3. Create a new file in that folder called HW03.java and copy and paste the starter code below inside
  4. Open both Cow.java and HW03.java in DrJava
  5. Click on HW03.java
  6. Compile and Run

HW03.java

// NOTE: Read and compile and run and understand this file before you start working!

import java.util.*;

class HW03 extends App {
    // NOTE: please ignore this particular use of the keyword static
    // (static nested class); it is NOT what we talked about in lecture
    // feel free to delete this comment after you finish reading it
    static class Player {
        Vector3 color;
        double radius;
        Vector2 position;
    }
    
    static class Turret {
        Vector3 color;
        double radius;
        Vector2 position;
        int framesSinceFired;
    }
    
    static class Bullet {
        Vector3 color;
        double radius;
        Vector2 position;
        Vector2 velocity;
        boolean alive;
        int age;
        int type;
        
        static final int TYPE_PLAYER = 0;
        static final int TYPE_TURRET = 1;
    }
    
    ////////////////////////////////////////////////////////////////////////////
    
    Player player;
    
    Turret turret; // TODO: Replace this line with "Turret[] turrets;"
    
    // bullets is a big, fixed-size array of Bullet objects (slots)
    // initially all Bullet objects are "not live" ("dead"),
    // which means they are not being updated or drawn
    Bullet[] bullets;
    
    ////////////////////////////////////////////////////////////////////////////

    // TODO: Write a new function "void fireBullet(...) { ... }"

    ////////////////////////////////////////////////////////////////////////////
    
    void setup() { // called once at the beginning of the game
        player = new Player();
        player.color = new Vector3(Vector3.cyan);
        player.radius = 4.0;
        player.position = new Vector2(0.0, -40.0);
        
        turret = new Turret();
        turret.color = new Vector3(Vector3.red);
        turret.radius = 8.0;
        turret.position = new Vector2(0.0, 0.0);
        
        bullets = new Bullet[256];
        for (int bulletIndex = 0; bulletIndex < bullets.length; ++bulletIndex) {
            bullets[bulletIndex] = new Bullet();
        }
    }
    
    void loop() { // called over and over, once per frame
        // player
        if (keyHeld('W')) { player.position.y += 1.0; }
        if (keyHeld('A')) { player.position.x -= 1.0; }
        if (keyHeld('S')) { player.position.y -= 1.0; }
        if (keyHeld('D')) { player.position.x += 1.0; }
        drawCircle(player.position, player.radius, player.color);
        
        // turret
        if (turret.framesSinceFired++ == 32) {
            turret.framesSinceFired = 0;
            
            // fire bullet
            for (int bulletIndex = 0; bulletIndex < bullets.length; ++bulletIndex) {
                if (!bullets[bulletIndex].alive) { // write to first unused ("dead") slot in bullets array
                    bullets[bulletIndex].position = new Vector2(turret.position); // copy constructor (instead of just referring to the same Vector2 object)
                    bullets[bulletIndex].velocity = Vector2.directionVectorFrom(turret.position, player.position);
                    bullets[bulletIndex].radius = 2.0;
                    bullets[bulletIndex].color = new Vector3(Vector3.yellow);
                    bullets[bulletIndex].alive = true;
                    bullets[bulletIndex].age = 0;
                    bullets[bulletIndex].type = Bullet.TYPE_TURRET;
                    
                    break;
                }
            }
        }
        drawCircle(turret.position, turret.radius, turret.color);
        
        // bullets
        for (int bulletIndex = 0; bulletIndex < bullets.length; ++bulletIndex) {            
            if (!bullets[bulletIndex].alive) { continue; } // skip dead bullets
            
            // kill bullets that are too old (they're probably off-screen anyway)
            if (bullets[bulletIndex].age++ > 128) {
                bullets[bulletIndex].alive = false;
            }
            
            // "physics"
            bullets[bulletIndex].position = bullets[bulletIndex].position.plus(bullets[bulletIndex].velocity);
            
            // draw
            drawCircle(bullets[bulletIndex].position, bullets[bulletIndex].radius, bullets[bulletIndex].color);
        }
    }
    
    public static void main(String[] arguments) {
        App app = new HW03();
        app.setWindowBackgroundColor(0.0, 0.0, 0.0);
        app.setWindowSizeInWorldUnits(128.0, 128.0);
        app.setWindowCenterInWorldUnits(0.0, 0.0);
        app.setWindowHeightInPixels(512);
        app.setWindowTopLeftCornerInPixels(64, 64);
        app.run();
    }
}
⚠️ **GitHub.com Fallback** ⚠️