Creators – Flying Over Dynamic Terrain

 

This week we creating a game where a tiny plane flies over dynamically generated terrain picking up as many boxes as possible. The player scores a point for every box, but that also makes the plane fly faster, making the game more challenging.

Perlin Noise

Normal random-number generators produce values that jump around all over the place. Perlin noise is different. It is also random, but it varies smoothly. This makes it great for mimicking the sort of randomness we see in nature.

1D Perlin Noise Landscape

Our smooth and changing landscape is generated using a one-dimensional Perlin noise value generated by the P5.js function noise(xoff).

We start with a loop that goes across from 0 -> width across the screen. We’re looking to generate a set of [xy] points that define our shape.

We use these values:

  1. xstart: A starting value for xoff
  2. xinc: An amount to increment xoff by for every new location
  3. ymin: The smallest y value we want for our landscape – something a little below the top of the screen
  4. ymax: The largest y value we want for our landscape – something a little above the bottom of the screen

Each call to noise() generates a single value in the range 0-1. We use the P5.js function map() to change this value in the range 0-1 into a value in the range ymin-ymax.

Changing the size of xinc controls how choppy or smooth the landscape is. We tune it to a value that gives approximately two peaks and two valleys across the screen, and looks right for our game.

Moving the Landscape

Moving the landscape is achieved by changing the starting value of xoff (aka. xstart) each time we update the screen. By making it a little larger each time, the effect is that the landscape seems to scroll from right to left.

Other Parts of the Game

The other parts of the game are very standard. We define a simple plane shape (drawn using rect() calls)  that can move up or down in response to the arrow keys.

We define “cargo” containers that are randomly generated on the surface of the landscape and move right-to-left at the same speed.

The cargo containers have an active property that is false if they move beyond the left-edge of the screen or get sufficiently close to the plane to be “picked up”.

We added a function to the landscape class (Ground.js) that checks for a given [x, y] location to see if that point us under the ground by checking what the height of the landscape is at that x value. If the plane is below the ground we consider it crashed.

We added a simple scoring mechanism that tracks how many boxes were collected and makes the plane move faster (really – the ground scroll faster) every time a box is collected.

Download

The files for this week can be found on our GitHub repository.

Explorers Week 7 – Truchet Tiles

Hello everyone

Nice big room last week as we had Advancers and Explorers in the one room. As I said on Saturday, we will continue to combine the both groups for the next two weeks.

We are starting Stop/Start Animation this coming week and if you could bring some or all of the following it would be helpful.

PDF notes of the session can be found here CDA-S7-Week_07-TruchetTiles.pdf

 

See you next week,

Martha

Julie, Ruaidhrí and Eoin McG

Hackers – code for our first prototype remote-controlled robot

Here is a video of the first tests of our remote-controlled robot with 2-wheel drive.

The control for this is based on the calculations we described in an earlier post: https://coderdojoathenry.org/2019/02/24/hackers-how-to-control-a-robots-wheel-motors-based-on-joystick-movements/

Here is the code:

// Code by Luke Madden, CoderDojo Athenry, with some comments added by Michael.
// This code controls a robot with 2-wheel drive, based on movements of a joystick.

// These are the motor H bridge control pins
#define in1 8
#define in2 9
#define in3 10
#define in4 11

// These hold values read from channels of the LemonRX receiver
int ch1;
int ch2; // not currently used
int ch3;

// These are the min and max values we read on each channel when we move the joystick
int joymin = 950;
int joymax = 1950;

// X and Y are joystick values in range -1 to +1
float X;
float Y;

// M1 and M2 are values for Motors 1 and 2, in range -1 to +1
int M1;
int M2; 

void setup() {
  pinMode(5, INPUT);
  pinMode(6, INPUT);
  pinMode(7, INPUT);

  Serial.begin(9600);
}

void loop() {

  // read pulse width values from each channel of lemonRX
  ch1 = pulseIn(5, HIGH, 25000);
  ch2 = pulseIn(6, HIGH, 25000);
  ch3 = pulseIn(7, HIGH, 25000);

  // Convert them to floats in range -1 to 1: map uses int, so set it to int in range -1000 to 1000 and then divide by 1000.0
  X = map(ch1, joymin, joymax, -1000, 1000)/1000.0;
  Y = map(ch3, joymin, joymax, -1000, 1000)/-1000.0;

  // This is the fomula for how much power to send to each motor
  // Motor values should be in range -255 to 255, not -1 to 1, so multiply by 255
  M1 = (X + Y) * 255;
  M2 = (X - Y) * 255;

  // Our fomula can end up with values greater than 255, so constrain them to this range
  M1 = constrain(M1, -255, 255);
  M2 = constrain(M2, -255, 255);

  // Call our function to actually drive the motors
  drive(M1,M2);

  // print out for debugging
  Serial.print("Channels: C1=\t"); // Print the value of
  Serial.print(ch1);        // each channel
  Serial.print("\t M1=\t");
  Serial.print(M1);
  Serial.print("\t M2=\t");
  Serial.print(M2);
  Serial.print("\t C3:\t");
  Serial.println(ch3);

  // this delay seems to help reading joystick
  delay(300);
}

void drive(int M1, int M2) {
  // drive both motors at speeds M1, M2 in range -255, 255
  if (M1 > 0) {
    analogWrite(in1, M1);
    analogWrite(in2, 0);
  }
  else {
    analogWrite(in1, 0);
    analogWrite(in2, -M1);
  }

  if (M2 > 0) {
    analogWrite(in3, M2);
    analogWrite(in4, 0);
  }
  else {
    analogWrite(in3, 0);
    analogWrite(in4, -M2);
  }
}

Creators: Shootah Part 6 – Enemy Shooting Back

alien

This week we did our last iteration on Shootah and added a Bomb to be dropped by the enemy to try to hit the player.

Bomb Class

We copied the existing Bullet class and called it Bomb instead. The main changes we had to make were:

  1. Changing the speed to a negative number so that it moved down instead of moving up (as the Bullet does)
  2. Changed the check for the top of the screen to one for the bottom of the screen instead (to account for the fact it moves down)
  3. Changed the check in its hit() function so that the Bomb doesn’t interact with the Enemy when it’s dropped, but will be marked as inactive it if hits anything else.
  4. Changed the colour of the bomb to red

Manages Bombs

It happened that the code we had already written to manage bullets was already perfect for managing bombs as well.

We used Visual Studio Code’s built-in capability to automatically rename symbols to:

  1. Rename the bullets array to projectiles
  2. Rename the manageBullets() function to manageProjectiles()

This was enough to have bombs move, draw and be removed when it becomes inactive.

Dropping Bombs

We added a new function to Ememy called shoot(). In that function we generated a random number from one to two hundred. We then dropped a bomb every time that number was less than five (we tuned this small number to get a good rate of bomb drops). This meant that the enemy dropped a bomb at random intervals, to make it impossible for the player to anticipate.

Download

The files for this week can be found on our GitHub repository.

Week 5, Explorers – Pen Commands

Hello Everyone,

A special welcome to all our new members that came to us this week, I hope you enjoyed yourself and hope to see you back again soon.

Thank you all for coming again this week. This week we looked at pen commands, we have not done this before in the Explorers group so it was new for everyone.

pencommand

We also created some variables which we set as sliders which again is something we had not done before with this group.

 

sliders

 

 

 

 

 

 

 

 

 

Here are the full Pdf version of my notes from this weeks session. CDA-S8 Week 5-Pen Command.pdf

Martha

Creators: Shootah Part 5 – Edges

spaceship.png

This week we extended our colliders so that we could used them to prevent the player going off the edges of the screen. We used it to show how software design needs to evolve.

Colliders

Our colliders were designed to be connected to an object with three things:

  1. A property  containing the x-coordinate of its location
  2. A property y containing the y-coordinate of its location
  3. A function hit() which was called if the attached collider touched another collider

Something to Connect To

We had colliders already attached to our:

  1. Enemy
  2. Bullets

but we didn’t have anything to attach to that could represent the side of the screen.

We created a new class called Static() with nothing more than the x, y  and hit() that we needed so that we could connect a collider to it (stored in one more property – collider).

Screen Edges

We created a pair of these colliders positioned at the right and left-hand side of the screen. We made sure to add them to our list in check_colliders(). Nothing much happened. Why? Well, first, the Player didn’t have a collider, so we added one, liberally copying code from Enemy, which a minor change to the description argument.

Now we could see the contact occurring between the edge and the player, though nothing was stopping it moving yet.

Unintended Consequences

As  often happens with code, this change had unexpected consequences; bullets were not firing properly any more. Why? Because the player now had a collider and the bullets were becoming inactive immediately because they were hitting that. The fix was to modify the Bullet’s hit() function to ignore hitting a collider with the description “player”.

Stopping the Player Moving

We now knew our player was hitting an edge, but another problem became apparent: we didn’t know which edge!

To properly stop the player and stop it moving too far, we really needed to know which side of the player the collider we’d hit was, but that information wasn’t available to us with the current design.

A couple of quick changes were necessary:

  1. In Collider.touching(), instead of just passing the descriptors to the objects hit() functions, we changed it to pass the Collider itself.
  2. In all the hit() functions, we had to made a small adjustment to account for the fact that we were now getting a Collider and not just a string.

With the extra information from the collider, were were able to determine how far the player should be allowed to move to and prevent it going further by setting the x value appropriately.

Download

The files for this week can be found on our GitHub repository.

Hackers – How to Steer an Autonomous Wheeled Robot to Drive Towards a Detected Object

During the same session at which we figured out how to translate joystick movements into motor signals for a robot with two drive wheels (https://coderdojoathenry.org/2019/02/24/hackers-how-to-control-a-robots-wheel-motors-based-on-joystick-movements/), we moved on to figuring out how to control the motor so as to steer an autonomous robot towards an object of interest.

Again, we did a bunch of calculations on a whiteboard (see end of post), and I have re-drawn them for this post.

This builds on the previous work, led by Kevin, on object detection in Python: https://coderdojoathenry.org/2018/12/06/hackers-starting-with-object-recognition/

We assume the setup is as follows:

  • We have a robot with two wheels attached to motors, one on the left and one on the right
  • We have code to control the motors for the two wheels (which we call Motor M1 and Motor M2) with a value in the range -1 to 1, where 1 is full speed ahead, 0 is no movement, and -1 is full speed reverse
  • We have a camera mounted on the robot, facing directly ahead
  • We have code to get an image from the camera and can detect an object of interest, and find its centre (or centroid) within the image

The objective is:

  1. If the object is in the middle of the image, robot should go straight ahead (M1=1, M2=1)
  2. If the object is to the right, move forward-right (e.g. M1=1, M2=0.7)
  3. Likewise, if the object is to the left of centre, move forward-left (e.g. M1=0.7, M2=1)
  4. If the object is not found, we turn the robot around in a circle to search for it (M1=1, M2=-1)

The first three of these are illustrated below:

steering

Our solution is to treat this as a variant of what we previously worked out before, where we had a joystick input, where the X direction of the joystick controls forward movement and its Y direction controls whether to move left or right. In this case, we are steering left/right while always moving forward. Therefore, X has a fixed value (X=1) and Y is a value that depends on the direction of the object of interest.

The equations we came up with are:

X = 1 (a fixed value)

Y = (W – HWid) * YMax / HWid

Then use X and Y to calculate the motor control values as before:

M1 = X + Y, constrained to being between -1 and +1

M2 = X – Y, constrained to being between -1 and +1

steering3

Here:

  • X is a fixed positive value: X=1 for full speed, or make it smaller to move forward more slowly
  • Y is calculated from the equation above
  • W is the distance of object’s centre from left edge of the image, in pixels
  • HWid is half the width of the image, in pixels (for example, for a basic VGA image, 640×480, HWid is 640/2 = 320)
  • YMax is a value approximately 0.5, but needs to be calibrated – it depends on how sharply you want your robot to steer towards the object
  • M1 is the control signal for Motor M1, in the range -1 (full reverse) to +1 (full forward)
  • M2 is the same for Motor M2
  • Constrained means: if the calculated value is less than -1, set it to -1; if it is greater than +1, set it to +1.

Here are our calculations on the whiteboard:

whiteboard-calcs2

Hackers – How to Control a Robot’s Wheel Motors Based on Joystick Movements

robot+controller

At our most recent session in the Hackers group in CoderDojo Athenry, we spent out time on practical mathematics, figuring out how, if we want to make a robot that is controlled by a person with a joystick, how exactly do we translate movements of the joystick to signals sent to the motors.

joystick-robot-assumptions

Our assumptions are:

  • We are controlling robot with 2 drive wheels, one on the left and one on the right, like the one shown in the photo above (which we made last year)
  • We assume that we have code to control the motors for the two wheels (which we call Motor M1 and Motor M2) with a value in the range -1 to 1, where 1 is full speed ahead, 0 is no movement, and -1 is full speed reverse
  • To make the robot turn, drive the motors M1 and M2 at different speeds
  • We assume that we have code to receive signals from the joystick, and get X and Y values in the range -1 to 1, as shown in the diagram below

Our approach was to think about what joystick positions (X and Y) should result in what robot movements (M1 and M2), and then see if we could come up a way of expressing M1 and M2 in terms of X and Y. We filled a whiteboard with satisfying diagrams and calculations; see the bottom of this post. I have re-dawn them for clarity below.

The resulting equations are quite simple:

M1 = X + Y, constrained to being between -1 and +1

M2 = X – Y, constrained to being between -1 and +1

Here:

  • M1 is the control signal for Motor M1, in the range -1 (full reverse) to +1 (full forward)
  • M2 is the same for Motor M2
  • X is the forward position of the joystick from -1 (full back) to +1 (full forward)
  • Y is the left/right position of the joystick from -1 (full left) to +1 (full right)
  • Constrained means: if the calculated value is less than -1, set it to -1; if it is greater than +1, set it to +1.

Here is the full set of joystick positions and motor movements that we considered, showing how the equations work:

joystick-all-calcs

We previously figured out how to control motors with a H-bridge controller from an Arduino: https://coderdojoathenry.org/2019/02/07/hackers-controlling-robot-wheels-and-handling-interrupts-in-arduino/

Each motor needs two control signals in the range 0-255, one for forward and one for reverse, so we will need more code to convert our M1 and M2 to what is needed for the H-bridge, but that is a fairly easy job for a different day.

whiteboard-calcs1