Thomas Laubach

FabAcademy 2016, Kamp-Lintfort, Germany


ABOUT THOMAS OUR FABLAB HOME


Week 8: Embedded Programming


Homework for this week:

  1. Read a microcontroller data sheet.
  2. Program your Hello World board to do something, with as many languages and programming environments as possible.
  3. Extra credit: experiment with other architectures.

Programming of my "Hello World" board


Cabling
The prominent task for week 8 was to program my "Hello World" board that I had built in week 6. Contrary to my colleagues, I had attached the switch to processor pin 6 = PA7 = output 7, and the LED to processor pin 5 = PB2 = output 8 (see figs. 1a and 1b, taken from here).

The first chart is taken from this tutorial. Both charts contain basically the same information. The 'D0 .. D10' in the upper chart correspond to 'Pin0 .. Pin10' in the lower one.

Fig. 1a. ATtiny44 pin assignments

Fig. 1b. ATtiny44 pin assignments, more info

I used wires in different colors to link the "Hello World" board with the Arduino Uno. Both need to be attached to a USB hub, so to not destroy the computer in case of a stress peak or similar. The VCC pin of the 2x3 pin header need not be attached to power.


Fig. 2. Linking "Hello World" board and Arduino Uno: Arduino side

Fig. 3. Linking "Hello World" board and Arduino Uno: "Hello World" board side

Fig. 4. Both boards linked together and powered via USB hub


So far, I have managed to program my "Hello World" board only using an Arduino Uno and The Arduino IDE. Some nice guy has explained here how this can be accomplished. Why exactly this works and why I cannot program it via my in-circuit programmer that I have built in week 4 is not clear yet. AVRDude does not recognize the ATtiny44 (see my documentation for week 6).

Plain Arduino Sketch
Programming the board from within the Arduino IDE is fairly straightforward. The Arduino sketch from fig. 5 turns the attached LED on or off every 500 milliseconds.


Fig. 5. Blink the LED in Arduino Sketch (with Macros)


C-like program within Arduino IDE
I replaced the Arduino Sketch functions digitalWrite(), digitalRead() and pinMode() for the input/output settings with some C bit manipulation code. '_BV(...)' is a Macro and stands for 'Bit Vector()'. '<<' is a 'bit shift to the left'-operation. Both do the same. Please see the code in fig. 6.


Fig. 6. Blink the LED in Arduino IDE with some bitwise operations


Python
I also tried to program my "Hello World" board in Python. I copied the files rx.py and term.py into directory week6_files/HelloWorldBoard/Hello.FTDI, found out in the Arduino IDE to which port the ATtiny44 is attached and executed


	python term.py /dev/tty.usbmodemFD1331 115200
	

from the shell. The GUI for the terminal came up. When I typed something at the shell, nothing was mirrored to the terminal's GUI. I have not yet found out why this does not work.

Wednesday, April 8th.: The terminal application in Python works now with my "Hello World" board, as you can see in fig. 7. Please excuse the silly text. I have found out that it does not work when I use the Arduino Uno as programmer. Instead, I need to use my home-brew programmer. I have also verified the correct cabling (see figs. 8 and 9).


Fig. 7. Neil's terminal application in Python

Fig. 8. Correct cabling for my "Hello World" board

Fig. 9. Correct cabling for my in-circuit programmer


	thomas ~/Desktop/FabAcademy/repository/fablabkamplintfort/students/125/week6_files/HelloWorldBoard/Hello.FTDI $ sudo make -f hello.ftdi.44.echo.c.make program-usbtiny-fuses
avr-objcopy -O ihex hello.ftdi.44.echo.out hello.ftdi.44.echo.c.hex;\
	avr-size --mcu=attiny44 --format=avr hello.ftdi.44.echo.out
AVR Memory Usage
----------------
Device: attiny44

Program:     758 bytes (18.5% Full)
(.text + .data + .bootloader)

Data:         64 bytes (25.0% Full)
(.data + .bss + .noinit)


avrdude -p t44 -P usb -c usbtiny -U lfuse:w:0x5E:m

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.01s

avrdude: Device signature = 0x1e9207
avrdude: reading input file "0x5E"
avrdude: writing lfuse (1 bytes):

Writing | ################################################## | 100% 0.01s

avrdude: 1 bytes of lfuse written
avrdude: verifying lfuse memory against 0x5E:
avrdude: load data lfuse data from input file 0x5E:
avrdude: input file 0x5E contains 1 bytes
avrdude: reading on-chip lfuse data:

Reading | ################################################## | 100% 0.00s

avrdude: verifying ...
avrdude: 1 bytes of lfuse verified

avrdude: safemode: Fuses OK (H:FF, E:DF, L:5E)

avrdude done.  Thank you.

	

and


thomas ~/Desktop/FabAcademy/repository/fablabkamplintfort/students/125/week6_files/HelloWorldBoard/Hello.FTDI $ sudo make -f hello.ftdi.44.echo.c.make program-usbtiny
Password:
avr-objcopy -O ihex hello.ftdi.44.echo.out hello.ftdi.44.echo.c.hex;\
	avr-size --mcu=attiny44 --format=avr hello.ftdi.44.echo.out
AVR Memory Usage
----------------
Device: attiny44

Program:     758 bytes (18.5% Full)
(.text + .data + .bootloader)

Data:         64 bytes (25.0% Full)
(.data + .bss + .noinit)


avrdude -p t44 -P usb -c usbtiny -U flash:w:hello.ftdi.44.echo.c.hex

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.01s

avrdude: Device signature = 0x1e9207
avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "hello.ftdi.44.echo.c.hex"
avrdude: input file hello.ftdi.44.echo.c.hex auto detected as Intel Hex
avrdude: writing flash (758 bytes):

Writing | ################################################## | 100% 2.34s

avrdude: 758 bytes of flash written

Wallbreaker - Example Game for the Video Game System


My final project will be based on something I have already done, the LEDmePlay video game console that uses as display a 32x32 RGB LED matrix panel. While the LEDmePlay uses an Arduino Mega 2560, my final project will use an Atmel ATmega1284p. The reason is that I need much more memory, variable memory and program memory, than what the Arduino Uno offers. But I cannot solder the processor of the Arduino Mega 2560.

Some weeks ago, when I had decided to change my final project, I began to program a Breakout-style game by the name Wallbreaker. The game is controlled with a digital joystick. The Arduino sketch for Wallbreaker is to a large extent compatible with the LEDmePlay. I wanted it to feature five balls, four bats and different screens, like the classic game Arkanoid for instance.

Until now, I have programmed a single screen and the basic game mechanics. I can already move five balls simultaneously and can control four bats at once, one pair with vertical joystick movements, the other with horizontal movements. So far, I had no time to implement a functioning collision detection for them. It is hopefully visible in the picture that you can distinguish the various bricks. To achieve this, I had to slightly vary the color patterns they are made of.

Fig. 10. My demonstration game for my video game system, "Wallbreaker"


I will briefly explain the key elements of the sketch:

Excerpts from the Wallbreaker program (Note: this is not university-quality code :-) )

void initializePlayfield()
{
  // Initialize play field structure
  for (byte i = 0; i < 32; i++)
  {
    for (byte j = 0; j < 32; j++)
    {
      playfield[i][j] = EMPTY;
    }
  }
  // Draw border on play field
  for(i = 0; i < 32; i++)
  {
    playfield[i][0] = WALL;
    playfield[i][31] = WALL;
    playfield[0][i] = WALL;
    playfield[31][i] = WALL;
  }
}

// Reads the play field structure from Flash ROM (PROGMEM) and transfers it to data structure in RAM
// Draws the play field
void drawPlayfield()
{
  // Draw play field border
  matrix.drawLine(0, 0, 31, 0, matrix.Color888(17, 17, 68));
  matrix.drawLine(0, 0, 0, 31, matrix.Color888(17, 17, 68));
  matrix.drawLine(31, 0, 31, 31, matrix.Color888(17, 17, 68));
  matrix.drawLine(0, 31, 31, 31, matrix.Color888(17, 17, 68));

  matrix.fillRect(1, 1, 31, 31, matrix.Color333(0,0,0)); // Clear LED matrix
   
  initializePlayfield();
  
  // Initialize the levelData and play field data structures
  for (byte i = 0; i < 140; i++) { levelData[i] = EMPTY; }
  // Copy the maze from program memory into data structure levelData
  for (byte k = 0; k < 140; k++)
  {
    levelData[k] = pgm_read_byte_near(maze + (stage*140) + k);
  }
  
  // Draw bricks and walls, and store them in the play field
  for (i = 0; i < 10; i++)
  {
    for (j = 0; j < 14; j++)
    {
      if (levelData[(j * 10) + i] == EMPTY) // Passage
      {
        setPlayfieldElement(i, j, EMPTY); 
      }  
      else if(levelData[(j * 10) + i] == WALL) // Wall
      {
        //setPlayfieldElement(i, j, WALL); 
        //matrix.drawLine(i*3+1, j*2+1, i*3+3, j*2+1, wall_color);
      }
      else if(levelData[(j * 10) + i] == RED_BRICK)
      {
        setPlayfieldElement(i, j, RED_BRICK); 
        matrix.drawLine(i*3+1, j*2+1, i*3+1, j*2+2, matrix.Color333(1, 0, 0));
        matrix.drawLine(i*3+2, j*2+1, i*3+2, j*2+2, matrix.Color333(3, 0, 0));
        matrix.drawLine(i*3+3, j*2+1, i*3+3, j*2+2, matrix.Color333(5, 0, 0));
      }
      else if(levelData[(j * 10) + i] == GREEN_BRICK)
      {
        setPlayfieldElement(i, j, GREEN_BRICK); 
        matrix.drawLine(i*3+1, j*2+1, i*3+1, j*2+2, matrix.Color333(0, 1, 0));
        matrix.drawLine(i*3+2, j*2+1, i*3+2, j*2+2, matrix.Color333(0, 3, 0));
        matrix.drawLine(i*3+3, j*2+1, i*3+3, j*2+2, matrix.Color333(0, 5, 0));
      }
      else if(levelData[(j * 10) + i] == BLUE_BRICK)
      {
        setPlayfieldElement(i, j, BLUE_BRICK); 
        matrix.drawLine(i*3+1, j*2+1, i*3+1, j*2+2, matrix.Color333(0, 0, 1));
        matrix.drawLine(i*3+2, j*2+1, i*3+2, j*2+2, matrix.Color333(0, 0, 3));
        matrix.drawLine(i*3+3, j*2+1, i*3+3, j*2+2, matrix.Color333(0, 0, 5));
      }
      else if(levelData[(j * 10) + i] == VIOLET_BRICK)
      {
        setPlayfieldElement(i, j, VIOLET_BRICK); 
        matrix.drawLine(i*3+1, j*2+1, i*3+1, j*2+2, matrix.Color333(1, 0, 1));
        matrix.drawLine(i*3+2, j*2+1, i*3+2, j*2+2, matrix.Color333(3, 0, 3));
        matrix.drawLine(i*3+3, j*2+1, i*3+3, j*2+2, matrix.Color333(5, 0, 5));
      }
      else if(levelData[(j * 10) + i] == YELLOW_BRICK)
      {
        setPlayfieldElement(i, j, YELLOW_BRICK); 
        matrix.drawLine(i*3+1, j*2+1, i*3+1, j*2+2, matrix.Color333(1, 1, 0));
        matrix.drawLine(i*3+2, j*2+1, i*3+2, j*2+2, matrix.Color333(3, 3, 0));
        matrix.drawLine(i*3+3, j*2+1, i*3+3, j*2+2, matrix.Color333(5, 5, 0));
      }
      else if(levelData[(j * 10) + i] == SOLID_BRICK)
      {
        setPlayfieldElement(i, j, SOLID_BRICK); 
        matrix.drawLine(i*3+1, j*2+1, i*3+1, j*2+2, matrix.Color333(1, 1, 1));
        matrix.drawLine(i*3+2, j*2+1, i*3+2, j*2+2, matrix.Color333(3, 3, 3));
        matrix.drawLine(i*3+3, j*2+1, i*3+3, j*2+2, matrix.Color333(5, 5, 5));
      }
    } // FOR j
  } // FOR i
} // drawMaze

void setPlayfieldElement(byte i, byte j, byte element)
{
    for (byte ox = 0; ox < 4; ox++)
    {
      for (byte oy = 0; oy < 2; oy++)  
      {
        playfield[1 + (i * 3) + ox][(1 + j * 2) + oy] = element;
      }
    }
  }

...

double calcMoveAngle_x(double angle)
{
  return (double) cos(angle * PI_FRACTION);
}

double calcMoveAngle_y(double angle)
{
  return (double) (sin(angle * PI_FRACTION));
}

double getInkrement_x(double angle)
{
 if      ((angle > 0) && (angle < 180))     { return 1; }
 else if ((angle > 180) && (angle < 360))  { return -1; }
 else                                       { return 0; }
}

double getInkrement_y(double angle)
{
  if      ( ((angle > 270) && (angle < 360)) || ((angle > 0) && (angle < 90)) )   { return -1; }
  else if ( (angle > 90) && (angle < 270))                                        { return 1; }
  else                                                                            { return 0; }
}

...

void moveBat()
{
  boolean ignoreJoystick = false;

  if (!ignoreJoystick)
  {
    // Check joystick 1 for player 1
    // Player indicates a new direction
    if ((digitalRead(buttonL1) == LOW) && (digitalRead(buttonU1) != LOW) && (digitalRead(buttonD1) != LOW)) // LEFT
    {
      if (bat_active[LOWER_BAT]) { if (bat_pos[LOWER_BAT] > 2) { bat_pos_old[LOWER_BAT] = bat_pos[LOWER_BAT]; bat_pos[LOWER_BAT]--; } }
      if (bat_active[UPPER_BAT]) { if (bat_pos[UPPER_BAT] > 2) { bat_pos_old[UPPER_BAT] = bat_pos[UPPER_BAT]; bat_pos[UPPER_BAT]--; } }
      repaintBat();
    }
    else if ((digitalRead(buttonR1) == LOW)  && (digitalRead(buttonU1) != LOW) && (digitalRead(buttonD1) != LOW)) // RIGHT
    {
      if (bat_active[LOWER_BAT]) { if (bat_pos[LOWER_BAT] < 26) { bat_pos_old[LOWER_BAT] = bat_pos[LOWER_BAT]; bat_pos[LOWER_BAT]++; } }
      if (bat_active[UPPER_BAT]) { if (bat_pos[UPPER_BAT] < 26) { bat_pos_old[UPPER_BAT] = bat_pos[UPPER_BAT]; bat_pos[UPPER_BAT]++; } }
      repaintBat();
    }
    else if ((digitalRead(buttonU1) == LOW)  && (digitalRead(buttonL1) != LOW) && (digitalRead(buttonR1) != LOW)) // UP
    {
      if (bat_active[LEFT_BAT]) { if (bat_pos[LEFT_BAT] > 2) { bat_pos_old[LEFT_BAT] = bat_pos[LEFT_BAT]; bat_pos[LEFT_BAT]--; } }
      if (bat_active[RIGHT_BAT]) { if (bat_pos[RIGHT_BAT] > 2) { bat_pos_old[RIGHT_BAT] = bat_pos[RIGHT_BAT]; bat_pos[RIGHT_BAT]--; } }
      repaintBat();
    }
    else if ((digitalRead(buttonD1) == LOW)  && (digitalRead(buttonL1) != LOW) && (digitalRead(buttonR1) != LOW)) // DOWN
    {
      if (bat_active[LEFT_BAT]) { if (bat_pos[LEFT_BAT] < 26) { bat_pos_old[LEFT_BAT] = bat_pos[LEFT_BAT]; bat_pos[LEFT_BAT]++; } }
      if (bat_active[RIGHT_BAT]) { if (bat_pos[RIGHT_BAT] < 26) { bat_pos_old[RIGHT_BAT] = bat_pos[RIGHT_BAT]; bat_pos[RIGHT_BAT]++; } }
      repaintBat();
    }
    
    if (digitalRead(buttonFire1) == LOW)
    {
      //if (fireSkipsStage) { skipStage = true; }
    }
  } // !ignoreJoystick

} // moveBat  

...
  
boolean reflectAtUpperSurface(byte b, int randomAngleOffset)
{
  if  ((ball_moveAngle[b] > 270) && (ball_moveAngle[b] < 360)) 
  { 
    ball_moveAngle[b] = 180 + (360 - ball_moveAngle[b]);
    ball_vector_y[b] = getInkrement_y(ball_moveAngle[b]) * abs(calcMoveAngle_y(min(360, ball_moveAngle[b])));
    return true;
  }
  else if ((ball_moveAngle[b] >= 0) && (ball_moveAngle[b] < 90))   
  { 
    ball_moveAngle[b] = 180 - ball_moveAngle[b];
    ball_vector_y[b] = getInkrement_y(ball_moveAngle[b]) * abs(calcMoveAngle_y(min(360, ball_moveAngle[b])));
    return true;
  }
  return false;
}

...

void checkBallReflectedAtWall(byte i, double ball_x, double ball_y)
{
  // IS BALL I BEING REFLECTED AT ONE OF THE WALLS?
  // CHECK WHETHER THE NEXT POSITION WOULD BE ON A WALL

  // Calculate the next position of the ball
  if (floor(ball_y) <= 1) // Ball is reflected at the upper wall
  { 
    ballReflected[i] = reflectAtUpperSurface(i, 0);
  }
  else if (floor(ball_y) >= 30) // Ball is reflected at the lower wall
  {
    ballReflected[i] = reflectAtLowerSurface(i, 0);
  }
  
  if (floor(ball_x) <= 1) // Ball is reflected at the left wall
  { 
    ballReflected[i] = reflectAtLeftSurface(i, 0);
  }
  else if (floor(ball_x) >= 30) // Ball is reflected at the right wall
  { 
    ballReflected[i] = reflectAtRightSurface(i, 0);
  }    
} // checkBallReflectedAtWall


void checkBallReflectedAtBat(byte i, double ball_x, double ball_y) // DID THE BALL HIT ONE OF THE ACTIVE BATS?
{
  // CHECK LOWER BAT
  if (bat_active[LOWER_BAT])
  {
    if (29-ball_y <= 1.0) // Ball is right over the bat (can potentially be reflected) 
    {
      if  ((abs(ball_x-1 - (double) bat_pos[LOWER_BAT]) <= 1.0) ||  (abs(ball_x-2 - (double) bat_pos[LOWER_BAT]) <= 1.0)) // Ball hit pad central
      {
        ballReflected[i] = reflectAtLowerSurface(i, 10);
      }
      else if (abs(ball_x-0 - (double) bat_pos[LOWER_BAT]) <= 1.0) // Ball hit pad leftmost
      {
        ballReflected[i] = reflectAtRightSurface(i, -45);
      }
      else if (abs(ball_x-3 - (double) bat_pos[LOWER_BAT]) <= 1.0) // Ball hit pad rightmost
      {
        ballReflected[i] = reflectAtLeftSurface(i, 45);
      }
    } // ball right over the bat
    else if (abs(29-ball_y) <= 0.2) // Ball is right beside the bat
    {
      if (bat_pos[LOWER_BAT]-ball_x  <= 1.0) // Ball hits the left side of the pad
      {
        ballReflected[i] = reflectAtRightSurface(i, 10);
      }
      else if (bat_pos[LOWER_BAT]+3-ball_x  <= 1.0) // Ball hits the right side of the pad
      {
        ballReflected[i] = reflectAtLeftSurface(i, 10);
      }
    }
  
  if (collisionOccurred)
  {
      // Compute a new y-inkrement, as the angle got reflected at the bat 
      //avertHorizontalOrVerticalPath(i);
      ball_vector_y[i] = getInkrement_y(ball_moveAngle[i]) * abs(calcMoveAngle_y(min(360, ball_moveAngle[i] + random(5))));
  }
} // checkBallReflectedAtBat


// (ball_x, ball_y) is the next position of the ball. Check whether the ball gets reflected at a brick
void checkBallReflectedAtBrick(byte i, double ball_x, double ball_y)
{
  byte elementHit;
  byte nx = ball_x;
  byte ny = ball_y;
  byte ex;
  byte ey;
  
  // IS BALL i HITTING A BRICK OR A WALL?
  elementHit = playfield[nx][ny];
  if (elementHit > WALL)// PROBLEM: Wenn der Ball diagonal von außen in den unteren Bereich eines Steins kommt, wird das nicht erkannt
  {
    elementHit = playfield[nx][ny];
    ex = 1 + 3*((nx - 1) / 3);
    ey = 1 + 2*((ny - 1) / 2);

    if ((elementHit == RED_BRICK)   || (elementHit == GREEN_BRICK) || (elementHit == YELLOW_BRICK) ||
        (elementHit == BLUE_BRICK)  || (elementHit == VIOLET_BRICK) || (elementHit == SOLID_BRICK))
    {
      matrix.fillRect(ex, ey, 3, 2, matrix.Color333(0, 0, 0));
      // Delete element from play field
      playfield[ex][ey] = EMPTY; playfield[ex+1][ey] = EMPTY; playfield[ex+2][ey] = EMPTY;
      playfield[ex][ey+1] = EMPTY; playfield[ex+1][ey+1] = EMPTY; playfield[ex+2][ey+1] = EMPTY;
    }
    
    // LET THE OBJECT REFLECT THE BALL
    // CASE: BALL IS LEFT OR RIGHT FROM THE OBJECT
    //DAS STIMMT NOCH NICHT: DER BALL WIRD MANCHMAL NACH OBEN REFLEKTIERT, OBWOHL ER BEI KOLLISION VON UNTEN HER GEKOMMEN IST UND UMGEKEHRT
    if ((floor(ey) == floor(ball_y)) || (floor(ey+1) == floor(ball_y))) // Ball is at the same y-position as the object
    {
      if (  ((ball_moveAngle[i] > 0) && (ball_moveAngle[i] <= 90)) // Ball comes from the left
      ||    ((ball_moveAngle[i] > 90) && (ball_moveAngle[i] < 180)) )
      {
        ballReflected[i] = reflectAtRightSurface(i, 0);
      }
      else if (((ball_moveAngle[i] > 270) && (ball_moveAngle[i] < 360)) // Ball comes from the right
      ||    ((ball_moveAngle[i] > 180) && (ball_moveAngle[i] <= 270)) )
      {
        ballReflected[i] = reflectAtLeftSurface(i, 0);
      }
    } // Ball left or right from the object
    else if ((((ball_moveAngle[i] > 90) && (ball_moveAngle[i] < 180)) // CASE: BALL IS ABOVE THE OBJECT, 90° > a > 180° or or 270° > a > 180°  
    ||      ((ball_moveAngle[i] >= 180) && (ball_moveAngle[i] < 270))))
    {
      ballReflected[i] = reflectAtLowerSurface(i, 0);
    }
    else if ( (((ball_moveAngle[i] > 270) && (ball_moveAngle[i] < 360)) // CASE: Ball is beneath the object, 270° < a <= 0° or 0° < a < 90°
    ||        ((ball_moveAngle[i] > 0) && (ball_moveAngle[i] < 90))))
    {
      ballReflected[i] = reflectAtUpperSurface(i, 0);
    }      
  } // hit something that is not a wall
} // checkBallReflectedAtBrick


void moveBall()
{
  for (byte i = 0; i < 5; i++)
  {
    if (ball_active[i])
    {
      // Update ball position
      // Clear ball at old position
      matrix.drawPixel(byte(ball_x[i]), byte(ball_y[i]), matrix.Color333(0, 0, 0));
      //Serial.print("\nBall inkrements: "); Serial.print(ball_vector_x[i]); Serial.print(" ,"); Serial.print(ball_vector_y[i]);
      //Serial.print("\nBall: "); Serial.print(ball_x[i]); Serial.print(" ,"); Serial.print(ball_y[i]);
      //Serial.println();
      //Serial.print("\nBall velocity: "); Serial.print(ball_velocity_x[i]); Serial.print(" ,"); Serial.print(ball_velocity_y[i]);
      
      // For certain angles, the ball moves in a stairway fashion. 
      // We prevent this by looking at the next two positions of the ball
      // If both are within the 8-neighborhood of the current position, we skip the next position
      // and directly go to the next after the skipped one.
      if (bothInMooreNeighborhood(ball_x[i], ball_y[i], ball_vector_x[i], ball_vector_y[i]))
      {
        ball_x[i] = ball_x[i] + 2*ball_vector_x[i];
        ball_y[i] = ball_y[i] + 2*ball_vector_y[i];
      }
      else
      {
        ball_x[i] = ball_x[i] + ball_vector_x[i];
        ball_y[i] = ball_y[i] + ball_vector_y[i];
      }

      // Only at this point the coordinates of the ball are known
      // The collision detection is, therefore, at the right place here
      // (ball_x, ball_y) is the next position of the ball
      ballReflected[i] = false;
      checkBallReflectedAtWall(i, ball_x[i], ball_y[i]);
      checkBallReflectedAtBat(i, ball_x[i], ball_y[i]);
      checkBallReflectedAtBrick(i, ball_x[i], ball_y[i]);
      
      // Paint ball at new position
      matrix.drawPixel(ball_x[i], ball_y[i], ball_color);
      
    } // ball_active[i]
  } // for

   // Draw bats
   repaintBat();  // TODO: Do only, if necessary. This means after a collision between bat and ball/baddie
}


boolean bothInMooreNeighborhood(double x, double y, double vx, double vy)
{
  double x2 = x + vx;
  double y2 = y + vy;
  double x3 = x + 2*vx;
  double y3 = y + 2*vy;
  
  if ((abs(byte(x3)-byte(x)) == 1) && (abs(byte(y3)-byte(y)) == 1)) { return true; }
  return false; 
}

...

When the preliminary work for the final project is complete, I will make a video of the Video Game System and Wallbreaker in action. Over the years, my friend and I have programmed some games for our LEDmePlay, the spiritual father of the device I am building in my final project. Some videos of them are here.


Source files

Example game "Wallbreaker" for the LEDmePlay w/u (my final project): here

Lessons learned