Super Crate Buino

I will bite, will have a look at this with Filmote. And if memory permits will maybe have a go at enhancements…

1 Like

Cool! Well here’s a branch with the new .ino and modified Arduboy2.cpp / Arduboy2.h I used:

EDIT: the changes to Arduboy2.* are the Gamebuino functions pasted in at the end, and making cursor_x / cursor_y public, and substituting in this drawPixel:

void Arduboy2Base::drawPixel(int16_t x, int16_t y, uint8_t color)
{
  #ifdef PIXEL_SAFE_MODE
  if (x < 0 || x > (WIDTH-1) || y < 0 || y > (HEIGHT-1))
  {
    return;
  }
  #endif

  switch (color)
  {
    case WHITE:  sBuffer[x + (y/8)*WIDTH] |=  (1 << (y&7)); break;
    case BLACK:  sBuffer[x + (y/8)*WIDTH] &= ~(1 << (y&7)); break;
    case INVERT: sBuffer[x + (y/8)*WIDTH] ^=  (1 << (y&7)); break;
  }
}

Had a small look at it and begin to think that the ‘SCALE’ define combined with the LCD_WIDTH/HEIGHT define might be something to look at…

I wasn’t having much luck with compiling it because of conflicts with the already installed Arduboy2 library, so I decided to write a new header file that declares an Arduboy2Gamebuino class that inherits Arduboy2 and extends it with the new functions.

I got it compiling, but unfortunately the map isn’t drawing properly, so I’ll have to investigate.

If you just overwrite the Arduboy2.cpp and Arduboy2.h files in your libraries/Arduboy2 folder with the ones I posted it will compile. And then work backwards from there, writing a new class until those files can be restored to the unmodified ‘official’ versions?

It took a bit of trial and error, but I finally found the problem.
The x and y coordinates for the drawBitmap functions needed to be int16_t.

The real question is “why did it work when putting the functions directly into Arduboy2?”.

I never like when I can fix a problem but can’t explain how I fixed it,
because it implies that I probably haven’t actually fixed it.


Unfortunately GitHub is still playing up for me, so you’ll have to make do with some in-line code:

#pragma once

#include <Arduboy2.h>

// Gamebuino LCD size
#define LCDWIDTH 84
#define LCDHEIGHT 48

// for Gamebuino extended bitmap function
#define NOROT 0
#define ROTCCW 1
#define ROT180 2
#define ROTCW 3
#define NOFLIP 0
#define FLIPH 1
#define FLIPV 2
#define FLIPVH 3

class Arduboy2Gamebuino : public Arduboy2
{
public:
  // Bring the cursor variables into public view
  using Arduboy2::cursor_x;
  using Arduboy2::cursor_y;

  // Prevent the inherited 'drawBitmap' functions from being hidden
  using Arduboy2::drawBitmap;

  static bool getBitmapPixel(const uint8_t* bitmap, uint8_t x, uint8_t y)
  {
    return pgm_read_byte(bitmap + 2 + y * ((pgm_read_byte(bitmap) + 7) / 8) + (x >> 3)) & (B10000000 >> (x % 8));
  }

  static bool collideRectRect(uint16_t x1, uint16_t y1, uint16_t w1, uint16_t h1 ,uint16_t x2 ,uint16_t y2, uint16_t w2, uint16_t h2){
      return !( x2     >=  x1+w1  ||
                x2+w2  <=  x1     ||
                y2     >=  y1+h1  ||
                y2+h2  <=  y1     );
  }

  void drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap)
  {
    int8_t w = pgm_read_byte(bitmap);
    int8_t h = pgm_read_byte(bitmap + 1);

    bitmap = bitmap + 2;


    uint8_t * buffer  = getBuffer();
    const uint8_t col = textColor;
    const uint8_t bw  = (w + 7) / 8;

    // clip
    if (x >= WIDTH)  return;
    if (y >= HEIGHT) return;
    if (x + w <= 0)     return;
    if (y + h <= 0)     return;

    if (y < 0)
    {
      h += y;
      bitmap -= bw * y;
      y = 0;
    }

    if (y + h > HEIGHT)
    {
      h = HEIGHT - y;
    }

    uint8_t x1 = max(0, x);
    uint8_t x2 = min(WIDTH, x + w);

    // draw
    uint8_t first_bitmap_mask = 0x80 >> ((x1 - x) & 7);
    const uint8_t * bitmap_line = bitmap + (x1 - x) / 8;
    uint8_t screen_mask = 0x01 << (y % 8);
    uint8_t * screen_row = buffer + (y / 8) * WIDTH + x1;

    for (uint8_t dy = 0; dy < h; dy++, bitmap_line += bw)
    {
      const uint8_t * bitmap_ptr = bitmap_line;
      uint8_t bitmap_mask = first_bitmap_mask;
      uint8_t pixels = pgm_read_byte(bitmap_ptr);
      uint8_t * dst = screen_row;

      if (col == BLACK)
      {
        for (uint8_t sx = x1; sx < x2; sx++, dst++)
        {
          if (pixels & bitmap_mask)
          {
            *dst |= screen_mask;
          }
          bitmap_mask >>= 1;

          if (!bitmap_mask)
          {
            bitmap_mask = 0x80;
            pixels = pgm_read_byte(++bitmap_ptr);
          }
        }
      }
      else if (col == WHITE)
      {
        uint8_t inv_screen_mask = ~screen_mask;

        for (uint8_t sx = x1; sx < x2; sx++, dst++)
        {
          if (pixels & bitmap_mask)
          {
            *dst &= inv_screen_mask;
          }
          bitmap_mask >>= 1;

          if (!bitmap_mask)
          {
            bitmap_mask = 0x80;
            pixels = pgm_read_byte(++bitmap_ptr);
          }
        }
      }
      else // invert
      {
        for (uint8_t sx = x1; sx < x2; sx++, dst++)
        {
          if (pixels & bitmap_mask)
          {
            *dst ^= screen_mask;
          }
          bitmap_mask >>= 1;

          if (!bitmap_mask)
          {
            bitmap_mask = 0x80;
            pixels = pgm_read_byte(++bitmap_ptr);
          }
        }
      }

      screen_mask <<= 1;

      if (!screen_mask)
      {
        screen_mask = 1;
        screen_row += WIDTH;
      }
    }
  }

  void drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t rotation, uint8_t flip)
  {
    if ((rotation == NOROT) && (flip == NOFLIP))
    {
      // use the faster algorithm
      drawBitmap(x, y, bitmap);
      return;
    }

    uint8_t w = pgm_read_byte(bitmap);
    uint8_t h = pgm_read_byte(bitmap + 1);

    bitmap = bitmap + 2;


    int8_t i, j; //coordinates in the raw bitmap
    int8_t k, l; //coordinates in the rotated/flipped bitmap
    int8_t byteNum, bitNum, byteWidth = (w + 7) >> 3;

    rotation %= 4;

    for (i = 0; i < w; i++)
    {
      byteNum = i / 8;
      bitNum  = i % 8;

      for (j = 0; j < h; j++)
      {
        if (pgm_read_byte(bitmap + j * byteWidth + byteNum) & (B10000000 >> bitNum))
        {
          switch (rotation)
          {
            // no rotation
            case NOROT:
              k = i;
              l = j;
              break;

            // 90 counter-clockwise
            case ROTCCW:
              k = j;
              l = w - i - 1;
              break;

            // 180
            case ROT180:
              k = w - i - 1;
              l = h - j - 1;
              break;

            // 90 clockwise
            case ROTCW:
              k = h - j - 1;
              l = i;
              break;
          }

          if (flip)
          {
            flip %= 4;

            // horizontal flip
            if (flip & B00000001)
            {
              k = w - k - 1;
            }

            // vertical flip
            if (flip & B00000010)
            {
              l = h - l;
            }
          }

          // place the bitmap on the screen
          k += x;
          l += y;
          if (textColor == BLACK){
            drawPixel(k, l);
          }
          else if (textColor == WHITE){
            drawPixel(k, l, BLACK);
          }
        }
      }
    }
  }
};

Name it whatever, preferably Arduboy2Gamebuino.h, then change:

#include <Gamebuino.h>
Gamebuino gb;

In the original Super-Crate-Buino.ino to:

#include "Arduboy2Gamebuino.h"
Arduboy2Gamebuino gb;

And it should all work automagically.

I intend to spruce it up a bit at some point.
There’s lots of room for improvement in those functions.

Nice. You’ll still need a drawPixel that can do INVERT, and can probably re-write the sketch to use Arduboy2’s collide instead of that collideRectRect?

Yes, but I’ll wait until GitHub’s done disagreeing with me before I go messing around with those.

I figured out why GitHub was misbehaving.

Sure enough, it was my own fault, but it was something I did months back, if not over a year ago, so I’m not surprised it took me the best part of a day to realise what it was.

Before I make a proper repo:

  • Did you write those functions yourself?
  • If not, where are the originals, and what licences did they have?

Also I’d like to point out that you’re probably violating the LGPL because you didn’t ‘make the work’:

carry prominent notices stating that you modified it, and giving a relevant date.

But how the licence behaves in this case is a bit hard to tell because the LGPL was intended to be used with libraries, not standalone programs.
(It literally uses the word ‘library’ about half a dozen times in its licence text.
I’m not sure Mr Rodot did his research properly.)

…as mentioned above, got them from:

Which got them from:

and the drawPixel is from:

EDIT: isn’t a GitHub fork by its nature a clear notice of the original source, the modifications made, and the relevant dates?

1 Like

The exact wording of the GPL is:

The work must carry prominent notices stating that you modified it, and giving a relevant date.

What ‘the work’ actually means isn’t actually specified by the licence, but from the usage it can be inferred to mean ‘the program, in source or object form’,
and presumably does not include a GitHub repo because the GitHub repo is merely a mechanism for storing and modifying the program.

I’ve been involved in so many things today that I completely lost track.

LGPL again… bloody thing.

Couldn’t be nice and straightforward like MIT or Apache 2.0 or BSD 3-clause,
has to be some great big goliath full of weasel words and other oddities.

(If the Gamebuino’s documentation is decent I might just read the explanation of the function and write a new one from scratch.)

I haven’t actually modified drawPixel in my version,
so that won’t matter for me.
When I do though, I’ll either duplicate the assembly one from Arduboy2 itself,
or use the one I wrote for the (still unfinished) Pokitto port of Arduboy2.

(At least Adafruit used BSD-3 though. A sensible choice.)

Pharap, you can use tis link: http://legacy.gamebuino.com/wiki/index.php?title=Reference
else you have the github repository: https://github.com/Gamebuino/Gamebuino-Classic

1 Like

Here’s an approximation (without noise channel, and without volume modulation) of the sound patterns in ArduboyTones format:

const uint16_t player_damage_sound[] PROGMEM = {NOTE_B4, 250, TONES_END};
const uint16_t revolver_sound[] PROGMEM = {NOTE_F5, 50, NOTE_A3, 50, NOTE_GS3, 50, NOTE_G3, 50, NOTE_FS3, 50, TONES_END};
const uint16_t grenade_sound[] PROGMEM = {NOTE_A3, 50, TONES_END};
const uint16_t machinegun_sound[] PROGMEM = {NOTE_D4, 50, NOTE_A3, 100, NOTE_GS3, 100, NOTE_G3, 50, TONES_END};
const uint16_t rocket_sound[] PROGMEM = {NOTE_A4, 3000, TONES_END};
const uint16_t blast_sound[] PROGMEM = {NOTE_GS3, 50, NOTE_G3, 50, NOTE_FS3, 50, NOTE_F3, 50, NOTE_E3, 50, NOTE_DS3, 50, NOTE_D3, 50, NOTE_CS3, 50, NOTE_C3, 50, NOTE_B2, 50, TONES_END};
const uint16_t power_up_sound[] PROGMEM = {NOTE_D4, 50, NOTE_FS4, 50, NOTE_A4, 50, NOTE_D5, 50, NOTE_FS5, 50, NOTE_CS5, 50, NOTE_G4, 50, NOTE_AS4, 50, NOTE_DS5, 50, NOTE_G5, 50, NOTE_F4, 50, NOTE_A4, 50, NOTE_C5, 50, NOTE_F5, 50, NOTE_A5, 50, TONES_END};
const uint16_t enemy_death_sound[] PROGMEM = {NOTE_G5, 50, TONES_END};
const uint16_t jump_sound[] PROGMEM = {NOTE_G4, 50, NOTE_GS4, 50, NOTE_A4, 50, TONES_END};
const uint16_t enemy_felt_sound[] PROGMEM = {NOTE_FS3, 750, TONES_END};
const uint16_t shotgun_sound[] PROGMEM = {NOTE_B3, 150, TONES_END};
const uint16_t laser_sound[] PROGMEM = {NOTE_D5, 50, NOTE_CS5, 50, NOTE_C5, 50, NOTE_B4, 50, NOTE_AS4, 50, NOTE_A4, 50, TONES_END};
const uint16_t club_sound[] PROGMEM = {NOTE_E3, 50, NOTE_DS3, 50, NOTE_D3, 50, TONES_END};

const uint16_t playOK[] PROGMEM = {NOTE_C4, 50, NOTE_C5, 50, TONES_END};
const uint16_t playTick[] PROGMEM = {NOTE_C5, 50, TONES_END};
1 Like

If you just want the whole screen inverted the hardware itself can do that, you don’t need any changes to the software draw functions. Just tell the screen to invert.

also BLACK and WHITE are sometimes opposite in the code because the Gamebuino screen is an LCD not an OLED

This makes sense when you consider B/W is part of the Arduboy brand/experience. Any faithful reproduction on an actual screen should not need inversion… on VGA hardware white should still be white and black should still be black. LCD is hard though. But I think inverting at the DRAW layer is still the wrong bet.

If you can’t invert the whole screen, then invert the paint function.

Yes.

The reason for a drawPixel that can do INVERT, is for the rectangle that gets drawn for an explosion:

I’d argue that’s more of a filter than a draw. I’d have a separate optimized function to handle that.

Interesting idea for an explosion.

1 Like

OK, have added in the sound now - two channels using Beep1 / Beep2:

Super-Crate-Buino.ino.hex (71.0 KB)

3 Likes

This game seems to have a bug related to the height of the character? I’ll have to take it out of the FX Goldcart for now unless we find a fix.

Do you have a more specific description at all?

The game is completely unplayable. Movement of the character does not relate to where they are actually are within the game code, IE you die from enemies that appear nowhere near you. Bullets come out from a totally different location from where you seem. Platforms are not able to be jumped on.

Try it out for a second and you will see immediately. On a side note, the game runs dramatically faster on my laptop than on my desktop for some reason, both running chrome.