Develop a game with p5.js (part 3)

In this third and last part of the series on how to develop a game with p5.js we will implement the actual game logic: eat enemies, get bigger and faster and game over handling. You will find the full code at the end of the article and you can play the game here.

New state

First of all let’s add the missing variables to complete our game.

  • We will add a boolean variable to store the information whether the game has ended.
  • As we want our player to get faster and faster with every bite, we need a variable to store the current player’s velocity
let direction = null;
let velocity = 1;
let gameOver = false;

Eating enemies / collision detection

This is the hardest part. But when we move our player around we need a way to find out whether we catched an enemy. Touching the enemy is good enough in our game, so we will need some sort of collision detection!

As all our game objects are circles, the circle collision detection is our best friend. It is pretty easy to understand and also easy to code. But it requires some understanding of the Pythagoras theorem (find the distance between two points). You can read this great article here for a detailed explanation.

In short we first find out the distance of two points using the Pythagoras theorem and then check if we we meet the condition for a collision.

I tried to visualize this here, so if the circles get closer and closer, the distance gets smaller. Once the distance is smaller than the sum of the two radiuses, we have a collision.

Let’s now add this to our game!

distance=\sqrt{(distanceX^2)+(distanceY^2)}

Which can be written in JavaScript using the built in functions sqrt and pow.

  let distance = sqrt(pow(playerPositionX -enemyPositionX ,2) + pow(playerPositionY - enemyPositionY, 2));

Now that we have the distance, we can check for collision, this is simple:

let radii = 10 + playerWeight / 2;
let collisionDetected = distance <= radii;

The radii variable may be a bit surprising: this is just adding up the radius of the player and the enemy. The circle function accepts the diameter, we have a diameter of 20 for the enemy (we should use a constant for this..) the diameter of the player is equal to it’s weight we take 10 for the enemy and half the weight for the player.

Now that we know that we have a collision, we need to update the variables to reflect the new reality. We want to make everything faster, bigger and find a new position for the enemy.

if (collisionDetected) {
    playerWeight += 2;
    velocity += 1;
    enemyPositionX = random(500);
    enemyPositionY = random(500);
}

Where to put the code? We can simply add it to the draw function. As we know, it is called repeatedly.

function draw() {
  
  // get distance of the two circles using Pythagoras
  let distance = sqrt(pow(playerPositionX -enemyPositionX ,2) + pow(playerPositionY - enemyPositionY, 2));
  // get the sum of the two radii (note we pass the diameter to the circle function, so we need to half it!)
  let radii = 10 + playerWeight / 2;
  // check if the distance is smaller than the sum
  let collisionDetected = distance <= radii;
  
  if (collisionDetected) {
    playerWeight += 2;
    velocity += 1;
    enemyPositionX = random(500);
    enemyPositionY = random(500);
  }
  ... rest of the function 

Almost done

You can play around with the updates to the game world. Why not make it even faster, or add way more weight each time you eat an enemy? I found the configuration above to be quite challenging, but still playable.

But one thing is missing: if we hit the border of the playfield, we need to stop the game. In addition we want to print the current weight somewhere on the screen so that we can compare highscores with our friends!

Print the Score

p5.js comes with a handy function to draw text, we just use that one. We can do this as the last thing in our draw function:

  // current score
  fill(60,200,120);
  textSize(20);
  text("your current weight " + playerWeight + " kg",20,20);
}

Hit a Wall

This is another part where we need some sort of collision detection. If we touch the left, right, bottom or top edge the game is over.

// Hit a wall?
  if (playerPositionX < 0 || playerPositionX > 500 || playerPositionY < 0 || playerPositionY > 500) {
   gameOver = true; 
  }

And last but not least, we need to stop the game:

// Game over?
if (gameOver) {
  fill(200,50,40);
  textSize(20);
  text("GAME OVER", 200,250); 
  return; // just skip everything else
}

The End

We created a very simple game with just a few lines of code. I really like p5.js and their editor. My highscore:

What is yours? 😉

Full Code

let playerWeight = 10;
let playerPositionX = 300;
let playerPositionY = 300;
let enemyPositionX = 0;
let enemyPositionY = 0;
let direction = null;
let velocity = 1;
let gameOver = false;

function setup() {
  // setup the random initial location of the enemy
  enemyPositionX = random(500);
  enemyPositionY = random(500);
  createCanvas(500, 500);
}

function draw() {
  // Game over?
  if (gameOver) {
    fill(200,50,40);
    textSize(20);
    text("GAME OVER", 200,250); 
    return; // just skip everything else
  }
  
  // Hit a wall?
  if (playerPositionX < 0 || playerPositionX > 500 || playerPositionY < 0 || playerPositionY > 500) {
   gameOver = true; 
  }
  
  // get distance of the two circles using Pythagoras
  let distance = sqrt(pow(playerPositionX -enemyPositionX ,2) + pow(playerPositionY - enemyPositionY, 2));
  // get the sum of the two radii (note we pass the diameter to the circle function, so we need to half it!)
  let radii = 10 + playerWeight / 2;
  // check if the distance is smaller than the sum
  let collisionDetected = distance <= radii;
  
  if (collisionDetected) {
    playerWeight += 2;
    velocity += 1;
    enemyPositionX = random(500);
    enemyPositionY = random(500);
  }
  
  // Update the world
  if (direction == 'up') {
    playerPositionY = playerPositionY - velocity; 
  }
  if (direction == 'left') {
    playerPositionX = playerPositionX - velocity; 
  }
  if (direction == 'right') {
    playerPositionX = playerPositionX + velocity; 
  }
  if (direction == 'down') {
    playerPositionY = playerPositionY + velocity; 
  }
  
  // draw a black background
  background(0);

  // define the fill color for the player
  fill(255, 120, 90);
  // draw a circle (our player)
  circle(playerPositionX, playerPositionY, playerWeight);

  // define the fill color of the enemy
  fill(0, 255, 0);
  // draw the enemy as a circle
  circle(enemyPositionX, enemyPositionY, 20);
  
  // current score
  fill(60,200,120);
  textSize(20);
  text("your current weight " + playerWeight + " kg",20,20);
}

function keyPressed() {
  if (key == 'w') {
    direction = 'up';
  }
  if (key == 'a') {
    direction = 'left';
  }
  if (key == 's') {
    direction = 'down';
  }
  if (key == 'd') {
    direction = 'right';
  }
}

Records in Java (JEP 359, preview)

Records in Java look very promising to me and a great addition to the language. The ceremony of creating pure data holder classes will be well know to any Java developer.

Sometimes we just create a class to transport some data from A to B. Most of the times we don’t even care about object equality or an efficient hash code algorithm. Nonetheless, the amount of code you (usually) generate is quite a bit. I’d go so far to say that the need to create classes for everything sometimes takes me out of the flow.

Take this simple example:

public class Person {
  private String name;
  private String firstname;
  private String address;
 
  public Person(String name, String firstname, String address) {
    this.name = name;
    this.firstname = firstname;
    this.address = address;
  }

  public String getName() {
    return this.name;
  }
  
  public void setName(String name) {
    this.name = name;
  }

  // ... all the other getters and setters, hashCode() and equals(), toString()

With records we would be able to declare a pure data holder structure way easier. And the best thing: it takes care about equality, hash code generation and a proper string representation (toString())

record Person(String name, String firstname, String address) {};
Image result for meme unbelievable

Yep, that one line expresses the same thing as the class above.

As a seasoned Java developer you may know that you can declare a class locally as part of a method. I don’t think this is common practice, but with records I could imagine that we can solve several problems in a very elegant way by just declaring a local record.

Try it out today

You can download early access builds from https://jdk.java.net/14/ (just unzip it somewhere you find it again). No comfort version, without any IDE – it’s fun to use these commands from time to time. Just keep in mind to enable the preview features:

Compile

c:\Dev\jdk-14\bin\javac -source 14 –enable-preview YourClass.java

Run

c:\Dev\jdk-14\bin\java –enable-preview YourClass

Personal opinion

I like it. But I feel like I’m saying this for every new Java feature. I do have some concerns about pattern matching and switch expressions as you can read in my other posts. But I don’t see a lot of trouble with this one coming!

Develop a game with p5.js (part 2)

Welcome back to the second part on how to develop a game with p5.js. Today we are going to add some interactions to our game. Wouldn’t be a real game if we couldn’t move around something, right?

Now that we already have setup most variables that we need and the scene is set, the rest comes quite easy.

keyPressed()

We just need to add a third function to our code which listens to keyboard input. Whenever some key is pressed that we are interested in, we keep the information in a variable so that we can use that to update our game world.

In other words:

  • if we press “W”, we want to move up
  • if we press “S” we want to move down
  • if we press “A” we want to move left
  • if we press “D” we want to move right

First we add a new variable to our little application:

let enemyPositionX = 0;
let enemyPositionY = 0;
let direction = null;

And then we add our keyboard listener code:

function keyPressed() {
  if (key == 'w') {
    direction = 'up';
  }
  if (key == 'a') {
    direction = 'left';
  }
  if (key == 's') {
    direction = 'down';
  }
  if (key == 'd') {
    direction = 'right';
  }
}

With this we now basically have all the information we need. Now it’s time to move something on the screen!

Image result for excited meme

It may seem a bit weird what we are going to do now, but just think of the keyboard event handler that we just wrote to be completely independent of the draw function. Remember, the draw function is called over and over again automatically by p5.js. So the missing part is to interpret the direction we just got and start moving the player!

function draw() {
  
  // Update the world
  if (direction == 'up') {
    playerPositionY = playerPositionY - 1; 
  }
  if (direction == 'left') {
    playerPositionX = playerPositionX - 1; 
  }
  if (direction == 'right') {
    playerPositionX = playerPositionX + 1; 
  }
  if (direction == 'down') {
    playerPositionY = playerPositionY + 1; 
  }

What we are doing here is pretty simple:

  • every time p5.js runs our draw function we look at the choosen direction and move the player by one pixel
  • This happens by updating the global variable which stores the current player’s location.
  • Then the render logic we wrote in part 1 comes into play (clearing background, drawing player, drawing enemy)
  • You can make the player faster by increasing the amount of pixels it moves per update.

Code

Find the complete running code for your reference

let playerWeight = 10;
let playerPositionX = 300;
let playerPositionY = 300;
let enemyPositionX = 0;
let enemyPositionY = 0;
let direction = null;

function setup() {
  // setup the random initial location of the enemy
  enemyPositionX = random(500);
  enemyPositionY = random(500);
  createCanvas(500, 500);
}

function draw() {
  
  // Update the world
  if (direction == 'up') {
    playerPositionY = playerPositionY - 1; 
  }
  if (direction == 'left') {
    playerPositionX = playerPositionX - 1; 
  }
  if (direction == 'right') {
    playerPositionX = playerPositionX + 1; 
  }
  if (direction == 'down') {
    playerPositionY = playerPositionY + 1; 
  }
  
  // draw a black background
  background(0);

  // define the fill color for the player
  fill(255, 120, 90);
  // draw a circle (our player)
  circle(playerPositionX, playerPositionY, playerWeight);

  // define the fill color of the enemy
  fill(0, 255, 0);
  // draw the enemy as a circle
  circle(enemyPositionX, enemyPositionY, 20);
}

function keyPressed() {
  if (key == 'w') {
    direction = 'up';
  }
  if (key == 'a') {
    direction = 'left';
  }
  if (key == 's') {
    direction = 'down';
  }
  if (key == 'd') {
    direction = 'right';
  }
}

Develop a game with p5.js (part 1)

p5.js is a great JavaScript library to create little games that run directly in your browser. They even offer an online editor, so you can really just get started.

Find out more about p5.js here: https://p5js.org/
Or directly access the online editor: https://editor.p5js.org/

In this article I will show you a game we implemented as part of a programmer training for kids. Let’s go through it step by step!

Image result for im ready meme

Editor

Before we write one line of code I’d like to give you a quick introcution on the editor. Just open https://editor.p5js.org/ and the editor will open up instantly in your favorite browser.

You can write code on the left side and see the result on the right side, it’s that easy. if you hit the *play* button on the top bar it will actually execute the code. If you hit the button without any modification, it will render a grey 400px by 400px square in the preview area on the right side.

Maybe you want to create an account or login via Github or Google. This way you can save your game / application / sketch online.

Canvas

Before we can draw anything on the screen at the location we actually want, we need to understand the canvas and the coordinate system a bit better. Imagine the canvas as a squared paper, each square representing a single pixel. If I would ask you to draw a rect of 3 squares width and 4 squares height on the top left corner, that would be pretty easy.

In the end p5.js works the same way but you just code your instructions and p5.js executes them.

  • First we create our canvas (e.g. createCanvas(400,400)
  • And then we tell p5j.s to draw something on it
function draw() {
  background(220);
  rect(0,0,30,40);
}

In the example above we are now drawing a rectangle beginning at the top left corner. Play around with these numbers to get familiar with the coordinate system. What would you need to do to render a square exactly in the middle of the canvas?

Let’s have a look at the rect() function, it takes several parameters, but we will focus only on the most important ones for now: where do we want to place the rectangle, and what is it’s dimension.

rect(x, y, w, h)

xThe X coordinate of the rect, from left to right, at which point do we want to start drawing the rect
y
The Y coordinate of the rect, from top to bottom, at which point do we want to start drawing the rect
wThe width of the rectangle in pixels
hThe height of the rectangle in pixels

See https://p5js.org/reference/#/p5/rect to learn more about the rect() function. The documentation is fantastic and provides good examples.

Let’s assume our canvas is 400 by 400 pixels and we want to draw a rectangle of 50 by 50 pixels – perfectly centered.

Our first impulse might be to write: rect(200 ,200 ,50 ,50);

Not so bad, but if we really want to center the rect we need to correct the x and y parameters slightly (basically moving our rectangle a 25px left and up). Now we get the result we wanted!

The Game

What is the game about? You are a hungry little ball that want’s to eat other balls. Whenever you eat a new ball you not only get heavier but also faster. Caution: if you leave the playfield, you loose!

We will divide the game programming into three different parts:

  1. setup the scene (rendering the player and the enemy
  2. keyboard support and moving our player
  3. collision detection and finalization

Setup the Scene

State

Our game will need some state. In our simple game we will use some global variables as our state. For example we might want to store the player’s location on the screen, so that we can later update it and render it. Altough later on we will add a bit more state, to get started we need the following state:

playerPositionX
The x coordinate of the player
playerPositionY
The y coordinate of the player
playerWeight The current weight of the player
enemyPositionX The x coordinate of the enemy
enemyPositionY The y coordinate of the player

As you can see the new sketch already contains two function where we can put our code:

setup()

The setup function is used to initialize our game. Here will setup our canvas (on which we will paint our game) and initialize some game variables like the player’s initial position on the screen. When we start the game, this function is called first.

draw()

The draw function is repeatadly invoked by p5.js to render the current state. So here we will actually draw our scene / the current state to the canvas.

Code

let playerWeight  = 10;
let playerPositionX = 300;
let playerPositionY = 300;
let enemyPositionX  = 0;
let enemyPositionY   = 0;
function setup() {
  // setup the random initial location of the enemy
  enemyPositionX  = random(500);
  enemyPositionY   = random(500);
  createCanvas(500, 500);
}

function draw() {
  // draw a black background
  background(0);
  
  // define the fill color for the player
  fill(255,120,90);
  // draw a circle (our player)
  circle(playerPositionX, playerPositionY, playerWeight);
  
  // define the fill color of the enemy
  fill(0,255,0);
  // draw the enemy as a circle
  circle(enemyPositionX , enemyPositionY  , 20);
}

Run the code and you should see something like below in the preview area.

  • We used the setup() function to initialize our canvas and set the initial location of our enemy. random(500) produces a random value between 0 and 500
  • In the draw() function we tell p5.js line by line what do do!
  • First draw a black background. You can actually pass more values to the function to define the color. Maybe try background(255, 0, 0) and you will get a red background. You can actually mix your own color by defining how much red green and blue (RGB) you want in your color. Learn more about the RGB colors; https://www.w3schools.com/colors/colors_rgb.asp
  • Then we ask p5.js to use another fill for its pen (again we can set any RGB color code)
  • After having set the color, we draw a circle at the given location. The third parameter is the diameter
  • Same thing for the enemy, first use another fill color
  • Then draw the circle at the enemy’s location

Switch Expressions in Java (JEP 361)

With Java 14 we will get a new feature that simplifies the switch statement. It will provide a new and clearer way to write switch statements and in addition allows us to use switch as an expression yielding a value.

New Syntax

Forget about break! No fall through anymore – simple and concise.

switch (day) {
    case MONDAY             -> System.out.println("Fully motivated!");
    case TUESDAY, WEDNESDAY -> System.ouFt.println("Get this done!");
    case THURSDAY, FRIDAY   -> System.out.println("Almost done!");
    case SATURDAY, SUNDAY   -> System.out.println("Party time!");
}

Today you would write something like below. Don’t miss the breaks!

switch (day) {
   case MONDAY:
       System.out.println("Fully motivated!");
       break;
   case TUESDAY:
   case WEDNESDAY: 
        System.ouFt.println("Let's get this done!");
        break;
   case THURSDAY:
   case FRIDAY:   
       System.out.println("Almost done!");
       break;
   case SATURDAY:
   case SUNDAY:
       System.out.println("Party time!");
}

Switch Expressions

This is really cool. Now we can asisgn the result of a switch expression to a variable.

String workingDay = switch(day) {
    case 1,2,3,4,5 -> "yes";
    case 6,7 -> "no";
    default -> "don't know";
};

This is easy to read and allows us to simplify our code. Maybe until now you used nested conditional operators (!!) or you introduced a little helper method with return statements.

If you need an actual block to calculate the result of the expression, there is a new keyword you can use for that purpose: yield

String workingDay = switch(day) {
    case 1,2,3,4,5 -> "yes";
    case 6,7 -> "no";
    default -> {
       String defaultAnswer = calculateDefaultAnswer();
       yield defaultAnswer;
    }
};

You can also use the legacy switch syntax and form expressions using the yield keyword.

int result = switch (s) {
    case "Foo": 
        yield 1;
    case "Bar":
        yield 2;
    default:
        System.out.println("Neither Foo nor Bar, hmmm...");
        yield 0;
};

It’s an expression baby

Image result for coding meme

Yes, you can drive your co worker crazy by building complex and inlined switch expressions. It works – I tried it.

public static boolean isWorkingDay(String day) {        
    if ("yes".equals(switch (day) { case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" -> "yes"; case "Saturday", "Sunday" -> "no"; default -> "dont' know";})) {
        return true;
    } else {
        return false;
    }
}

Try it out today

You can download early access builds from https://jdk.java.net/14/ (just unzip it somewhere you find it again). With Java 14 this is not a preview feature anymore, so you can just start to use the new switch expressions.

Personal opinion

In short: I really like it. I think it makes a lot of sense. Having the privilege to also teach classes in programming I can say that the intention of the programmer when using the switch statement was always a different one – always.

In addition using switch as an expression provides new ways also for a seasoned programmer to simplify their code. Have you ever seen those nested tenary conditional operators?

One downside I see is developers thinking that it is cool to inline switch expressions and combine them to more complex expressions that no one will ever touch again.

Image result for i can do it meme

Pattern Matching for instanceof (JEP 305)

Finally with Java 14 we get a (preview) feature I had on my wishlist for a long time. It eliminates the need to first test an object via instanceof operator and then (and I asusme it is the 90% case) cast it to exact that class in order to do something with it.

Here is the example directly taken from the JEP site:

if (obj instanceof String s) {
    // can use s here
} else {
    // can't use s here
}

Before we we had to write something like:

if (obj instanceof String) {
    String s = (String) obj;
} else {
    
}

But it gets even better

The binding variable that is introduced by the instanceof operator is not only accessible in the actual true block, but also in the logical extension of the boolean expression. I don’t know how to describe it in a better way.

if (obj instanceof String s && s.contains("hello")) {
  System.out.println("It contains the magic word!");
}

On the other hand this will not work and if you think it through a bit it also makes sense.

if (obj instanceof String s || s.contains("hello")) { // won't work!
  System.out.println("It contains the magic word!");
}

Try it out today

You can download early access builds from https://jdk.java.net/14/ (just unzip it somewhere you find it again). No comfort version, without any IDE – it’s fun to use these commands from time to time.

Code

package ch.christianmenz.java14;

public class PatternMatching {
    public static void main(String[] args) {
        Object obj = "hello world";
        if (obj instanceof String s) {
            System.out.println(s.length());
          }
    }
}

Compilation

C:\Users\chris\Documents\Java14\src>c:\Dev\jdk-14\bin\javac -d . -source 14 –enable-preview ch\christianmenz\java14\PatternMatching.java

It is important that you pass the source and the –enable-preview parameters.

Running

Running is also quite simple (note you don’t have to use pacakges as I did, I just did it for the nostalgic feeling).

C:\Users\chris\Documents\Java14\src>c:\Dev\jdk-14\bin\java –enable-preview -cp . ch.christianmenz.java14.PatternMatching

Extra: without manual compilation

If you read about this Java 11 feature, you might wonder if you could also use these preview features: yes you can! Just note you have to pass a double dash this time for the source parameter (I guess because of reasons ;-)).

C:\Users\chris\Documents\Java14\src>c:\Dev\jdk-14\bin\java –enable-preview –source 14 ch\christianmenz\java14\PatternMatching.java

Personal opinion

I’m a bit ambivalent about this feature. I certainly like that some boilerplate code can be eliminated. On the other hand the use of this language construct in combination with more complex expressions might lead to more misunderstandings and harder to understand software. Also keep an eye on this draft JEP, which aims to extend pattern matching to the switch statement: https://openjdk.java.net/jeps/8213076

Launch Java Programs Without Compilation (JEP 330)

Since Java 11 we can run Java programs directly from the command line without the need to manually compile them first. Frankly speaking I don’t know if this is a feature I will ever use in my daily work as a developer. But it could be useful to learn Java and provides an easy way to just get started.

Best read http://openjdk.java.net/jeps/330 to learn more about the motivation behind this feautre.

Running .java File

Simply run java HelloWorld.java on your command line and it will compile the class into memory and run it from there.

  
public class HelloWorld {

    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

Passing Parameters

You can pass parameters as you normally would when starting your Java program:
java HelloWorld.java Chris

  
public class HelloWorld {

    public static void main(String[] args) {
        System.out.println("Hello " + args[0]);
    }
}

Different Filename

Maybe you don’t want to name your file HelloWorld.java but just hello.j. This is also possible but you need to pass an additional parameter. java –source 11 hello.j

In the example above I just renamed the file and ran it again.

Shebang Files

In addition to just launching Java files direclty you can also add a shebang to the file and execute it directly as a script on Unix like systems (of course you can also use Cygwin or some similar tool on Windows). I just used the Git Bash for the example:

#!java --source 11  
public class HelloWorld {

    public static void main(String[] args) {
        System.out.println("Hello " + args[0]);
    }
}