Retro Invaders a space invaders clone by Chris Favreau. Written for the RetroMbuino development board from outrageouscircuits.com for the game programming contest.

Dependencies:   mbed

This is a space invaders clone written for the Retro Mbuino from outrageous circuits.

Development board: http://www.outrageouscircuits.com/shop/product/15 ).

The game itself is basic space invaders. Shoot them before they get to the bottom of the screen. It has a UFO saucer which you can shoot for extra points. You get 4 shields and each shield can be hit up to 4 times before it is gone. Hmm... as each level increases the speed of the invaders shots goes up. The invaders only speed up when there is less of them. You complete the level when you shoot all the invaders. The game ends when a) you run out of lives (you start with 3) or the invaders get to the bottom.

The LEDs turned out to be a pretty cool addition to the game. I wrote a class that blinks them and turns them on for a specified amount of time. They add a nice extra to the game. I use them on the intro screen and when the UFO is present.

The sound turned out to be really difficult for a few reasons. The biggest was that I had never written a sound engine before. The interrupt service routine working off the timer was the easier part. I also had a lot of trouble because there is no filter to filter out the PWM frequency to the speaker... so I had to run the PWM frequency way up there 30 kHz.

The graphics turned out to be a bit of a bear too. Thanks to Chris Taylor for his really great LCD API. I picked up a couple of frames per second from that. I had modified the DisplayN18 class for blitting a single line buffer to the LCD panel however his is a little faster for some reason? I used a different approach to doing the graphics (as I have very little experience with anything other than double buffered displays). I have a tile map and a list of sprites. Each tile/sprite is 1 bit 8x8. They could be bigger. I ran out of time. That much is not special. What is different from what I can tell is that I use a 1 line buffer that is 160 shorts long. The render function first adds the tile map data into the line buffer first. Then the sprites are added over the existing data. You can have a great deal of different sprites and maps going to the screen and just have to rewrite the LCD memory once per frame. After each line is composited, the line is then drawn to the LCD. Kind of like an Atari 2600. Each sprite/tile has a foreground and background color and can be different from the other tiles/sprites. There is one color reserved for Transparency.

There are 16 colors to choose from. I chose a palette based on the Macintosh OS 4.1 palette I found on WikiPedia. It is a very nice mix of colors.

I found a sprite editor called SpriteX ( https://code.google.com/p/spritesx-ed/ )... it works nicely except that the 16x16 sprites are in a weird format. Time limited me to 8x8 sprites. Oh well.

I used nokring to make the music. It makes RTTTL formatted ring tones which my sound api can play. Here is a useful site that has lots of arcade/video game ring tones with a link to nokring in the utilities page. http://arcadetones.emuunlim.com/files.htm

Other than all that stuff I used state machines to do most of the game logic. Please excuse the horrible coding as I tried to comment a lot of it however it is not very nice to look at. Lots of long functions...

Display/display.cpp

Committer:
cfavreau
Date:
2015-03-03
Revision:
0:c79e1f29f029

File content as of revision 0:c79e1f29f029:

#include <stdarg.h>     // va_list
#include "display.h"
#include "font8x8.h"    // default 8x8 bitmap font
#include "colors.h"     // predefined colors

#define COLOR(fore, back) ((fore << 4) | (back & 0x0F))

display::display() : LCD_ST7735(P0_19, P0_20, P0_7, P0_21, P0_22, P1_15, P0_2, LCD_ST7735::RGB)
{
    // Fill out the default color palette - use the MSX/Coleco default 16 color palette
    // Found them here:  http://theadamresource.com/articles/misc/wk970202.html
    memset(m_color_pal, 0, COLOR_PAL_LEN * sizeof(uint16_t));
    /*
    m_color_pal[0] =    RGB2SHORT(0,    0,      0);         // BLACK - Transparent (support in display code)
    m_color_pal[1] =    RGB2SHORT(0,    0,      0);         // BLACK - Opaque
    m_color_pal[2] =    RGB2SHORT(71,   183,    59);        // Medium Green
    m_color_pal[3] =    RGB2SHORT(124,  207,    111);       // Light Green
    m_color_pal[4] =    RGB2SHORT(93,   18,     255);       // Dark Blue
    m_color_pal[5] =    RGB2SHORT(128,  114,    255);       // Light Blue
    m_color_pal[6] =    RGB2SHORT(182,  91,     71);        // Dark Red (maybe brown)
    m_color_pal[7] =    RGB2SHORT(93,   200,    237);       // Cyan
    m_color_pal[8] =    RGB2SHORT(215,  107,    72);        // Medium Red
    m_color_pal[9] =    RGB2SHORT(251,  143,    108);       // Light Red
    m_color_pal[10] =   RGB2SHORT(195,  205,    65);        // Dark Yellow
    m_color_pal[11] =   RGB2SHORT(211,  218,    118);       // Light Yellow
    m_color_pal[12] =   RGB2SHORT(62,   159,    47);        // Dark Green
    m_color_pal[13] =   RGB2SHORT(182,  100,    199);       // Magenta
    m_color_pal[14] =   RGB2SHORT(204,  204,    204);       // Grey
    m_color_pal[15] =   RGB2SHORT(255,  255,    255);       // White
    */
    /*
    m_color_pal[0] =    RGB2SHORT(0,    0,      0);         // BLACK - Transparent (support in display code)
    m_color_pal[1] =    RGB2SHORT(0,    0,      0);         // BLACK - Opaque
    m_color_pal[2] =    RGB2SHORT(32,   192,    32);        // Medium Green
    m_color_pal[3] =    RGB2SHORT(96,   224,    96);        // Light Green
    m_color_pal[4] =    RGB2SHORT(32,   32,     224);       // Dark Blue
    m_color_pal[5] =    RGB2SHORT(64,   96,     224);       // Light Blue
    m_color_pal[6] =    RGB2SHORT(160,  32,     32);        // Dark Red (maybe brown)
    m_color_pal[7] =    RGB2SHORT(64,   192,    224);       // Cyan
    m_color_pal[8] =    RGB2SHORT(224,  32,     32);        // Medium Red
    m_color_pal[9] =    RGB2SHORT(224,  96,     96);        // Light Red
    m_color_pal[10] =   RGB2SHORT(192,  192,    32);        // Dark Yellow
    m_color_pal[11] =   RGB2SHORT(192,  192,    128);       // Light Yellow
    m_color_pal[12] =   RGB2SHORT(32,   128,    32);        // Dark Green
    m_color_pal[13] =   RGB2SHORT(192,  64,     160);       // Magenta
    m_color_pal[14] =   RGB2SHORT(160,  160,    160);       // Grey
    m_color_pal[15] =   RGB2SHORT(255,  255,    255);       // White
    */
    
    // Mac OS4.1 16 Color Palette (modified for transparent)
    // Found here: http://upload.wikimedia.org/wikipedia/commons/5/57/Mac_16colors_palette.png
    m_color_pal[0] =    RGB2SHORT(255,  255,    255);       // White
    m_color_pal[1] =    RGB2SHORT(255,  255,    0);         // Yellow
    m_color_pal[2] =    RGB2SHORT(255,  102,    0);         // Orange
    m_color_pal[3] =    RGB2SHORT(255,  0,      0);         // Red
    m_color_pal[4] =    RGB2SHORT(255,  0,      103);       // Magenta
    m_color_pal[5] =    RGB2SHORT(51,   0,      153);       // Dark Blue
    m_color_pal[6] =    RGB2SHORT(0,    0,      204);       // Blue
    m_color_pal[7] =    RGB2SHORT(0,    153,    255);       // Light Blue
    m_color_pal[8] =    RGB2SHORT(0,    255,    0);         // Green
    m_color_pal[9] =    RGB2SHORT(0,    102,    0);         // Dark Green
    m_color_pal[10] =   RGB2SHORT(102,  51,     0);         // Brown
    m_color_pal[11] =   RGB2SHORT(153,  102,    51);        // Light Brown
    m_color_pal[12] =   RGB2SHORT(187,  187,    187);       // Light Grey
    m_color_pal[13] =   RGB2SHORT(136,  136,    136);       // Grey
    m_color_pal[14] =   RGB2SHORT(0,    0,      0);       // White
    m_color_pal[15] =   RGB2SHORT(0,    0,      0);         // Transparent (black)
    
    // Allocate our line buffer
    m_line_buffer = new uint8_t[LINE_BUFFER_LEN];
    
    // Set our default colors
    m_fore_color = PAL_WHITE;
    m_back_color = PAL_TRANSPARENT;
    
    clear();
    
    // Initialize our sprite list
    for (int i = 0; i < MAX_SPRITES; i++)
        memset(&sprite_list[i], 0, sizeof(SPRITE));
        
    // Clear our user character buffer
    memset(user_char, 0, MAX_USER_CHAR_BUF_LEN);
}

display::~display()
{
}

void display::clear()
{
    // Clear our character map buffer
    memset(m_char_map, 0, CHAR_MAP_LEN);
    // Clear our color map buffer
    memset(m_color_map, COLOR(m_fore_color, m_back_color), CHAR_MAP_LEN);
    
    // Set the cursor position back to 0
    m_cursor_x = 0;
    m_cursor_y = 0;
}

void display::bounds_check_and_scroll()
{
    // Check to see if our Y cursor location is beyond our map height
    if (m_cursor_y >= CHAR_MAP_HEIGHT)
    {
        // Check to see if we are 
        // Scroll everything up 1 line
        memcpy(m_char_map, &m_char_map[CHAR_MAP_WIDTH], CHAR_MAP_LAST_LINE);
        memcpy(m_color_map, &m_color_map[CHAR_MAP_WIDTH], CHAR_MAP_LAST_LINE);
        // Clear the bottom line
        memset(&m_char_map[CHAR_MAP_LAST_LINE], 0, CHAR_MAP_WIDTH);
        memset(&m_color_map[CHAR_MAP_LAST_LINE], COLOR(m_fore_color, m_back_color), CHAR_MAP_WIDTH);
        // Put the position of the cursor on the last line
        m_cursor_y = CHAR_MAP_HEIGHT - 1;
        m_cursor_x = 0;
    }
}

void display::crlf()
{
    // Advance our cursor to the next line
    m_cursor_x = 0;
    m_cursor_y++;
}

void display::print(char *pString)
{   
    // Bounds check the cursor position (and do scrolling if we need to)
    // before we put any characters in the map
    bounds_check_and_scroll();
    
    // Calculate the current linear cursor position
    int pos = get_linear_cursor_pos();
    
    // Loop through the string and put it into the character map
    while (*pString)
    {
        char character = *pString;
        
        /*
        // Handle a carriage return
        if (character == 10)
        {
            m_cursor_x = 0;
            pos = get_linear_cursor_pos();
        }
        else
        // Handle a line feed
        if (character == 13)
        {
            m_cursor_y++;
            bounds_check_and_scroll();
            pos = get_linear_cursor_pos();
        }
        else
        */
        {
            if ((character < 32) || (character > 126))
            {
                // Some other special character
                character = 95; // substitue a solid block if there is a special character
            }
            else
            {
                // Just subtract out the special character offset
                character -= 32;
            }
            
            // Place the character into the map
            m_char_map[pos] = character;
            // Copy the forecolor into the map
            m_color_map[pos] = COLOR(m_fore_color, m_back_color); 
            // Next linear position
            pos++;
            // Advance the cursor position
            m_cursor_x++;
            if (m_cursor_x >= CHAR_MAP_WIDTH)
            {
                if (m_word_wrap)
                {
                    // Next line
                    crlf();
                    pos = get_linear_cursor_pos();
                }
                else
                {
                    // Stop putting the characters in the map
                    // we have hit the end of the line
                    break;
                }
                    
            }
        }
        // Next character
        pString++;
    }
}

void display::print(int iNumber)
{
    char sTemp[CHAR_MAP_WIDTH];
    snprintf(sTemp, CHAR_MAP_WIDTH, "%d", iNumber);
    print(sTemp);
}

void display::print(float fNumber)
{
    char sTemp[CHAR_MAP_WIDTH];
    snprintf(sTemp, CHAR_MAP_WIDTH, "%0.2f", fNumber);
    print(sTemp);
}

void display::print(const char *format, ...)
{
    char sTemp[CHAR_MAP_WIDTH];
    
    va_list list;
    va_start(list, format);

    // Print something in the file
    vsnprintf(sTemp, CHAR_MAP_WIDTH, format, list);
    
    print(sTemp);
}

void display::println(char *pString)
{
    print(pString);
    crlf();
    
}

void display::println(int iNumber)
{
    print(iNumber);
    crlf();
}

void display::println(float fNumber)
{
    print(fNumber);
    crlf();
}

void display::println(const char *format, ...)
{
    char sTemp[CHAR_MAP_WIDTH];
    
    va_list list;
    va_start(list, format);

    // Print something in the file
    vsnprintf(sTemp, CHAR_MAP_WIDTH, format, list);
    
    println(sTemp);
}

void display::printat(uint8_t x, uint8_t y, char *pString)
{
    // Bounds check the coordinates
    if (x >= CHAR_MAP_WIDTH) return;
    if (y >= CHAR_MAP_HEIGHT) return;
    
    // Get the starting linear position
    int pos = get_linear_pos(x, y);
    
    // Loop through the string and put it into the character map
    while (*pString)
    {
        char character = *pString;
        
        // Ignore CR and LF
        if ((character == 10) || (character == 13))
        {
            pString++;
            continue;
        }
        
        if ((character < 32) || (character > 126))
        {
            // Some other special character
            character = 95; // substitue a solid block if there is a special character
        }
        else
        {
            // Just subtract out the special character offset
            character -= 32;
        }
        
        // Place the character into the map
        m_char_map[pos] = character;
        // Copy the forecolor into the map
        m_color_map[pos] = COLOR(m_fore_color, m_back_color);
        // Next linear position
        pos++;
        // Advance the cursor position
        x++;
        // Check for out of bounds in which case we just exit
        if (x >= CHAR_MAP_WIDTH) return;
        if (pos >= CHAR_MAP_LEN) return;
        // Next character
        pString++;
    }
}

void display::printat(uint8_t x, uint8_t y, int iNumber)
{
    char sTemp[CHAR_MAP_WIDTH];
    snprintf(sTemp, CHAR_MAP_WIDTH, "%d", iNumber);
    printat(x, y, sTemp);
}

void display::printat(uint8_t x, uint8_t y, float fNumber)
{
    char sTemp[CHAR_MAP_WIDTH];
    snprintf(sTemp, CHAR_MAP_WIDTH, "%0.2f", fNumber);
    printat(x, y, sTemp);
}

void display::printat(uint8_t x, uint8_t y, const char *format, ...)
{
    char sTemp[CHAR_MAP_WIDTH];
    
    va_list list;
    va_start(list, format);

    // Print something in the file
    vsnprintf(sTemp, CHAR_MAP_WIDTH, format, list);
    
    printat(x, y, sTemp);
}

void display::setcharat(uint8_t x, uint8_t y, uint8_t character, uint8_t fore_color, uint8_t back_color)
{
    // Bounds check the coordinates
    if (x >= CHAR_MAP_WIDTH) return;
    if (y >= CHAR_MAP_HEIGHT) return;
    
    // Get the starting linear position
    int pos = get_linear_pos(x, y);
    
    // Place the character into the map
    m_char_map[pos] = character;
    
    // Check the colors
    if (fore_color >= 16) fore_color = m_fore_color;
    if (back_color >= 16) back_color = m_back_color;
    
    // Copy the forecolor into the map
    m_color_map[pos] = COLOR(fore_color, back_color);
    
}

void display::blit_line(uint8_t line)
{
    if (line >= LCD_HEIGHT) return;
    
    // LCD_ST7735
    clip(0, line, 159, 0);
    write(0x2C, m_line_buffer, LINE_BUFFER_LEN);
    
    // DisplayN18
    //setClippingArea(0, line, 159, 0);
    //writeCommand(0x2C);
    //writeData(m_line_buffer, LINE_BUFFER_LEN);
}

void display::render()
{
    uint16_t *line_buf = (uint16_t *)m_line_buffer;
    uint8_t *char_buf = font8x8_basic;
    register uint8_t data = 0;
    
    int cindex = 0;
    int char_line = 0;
    int line_x = 0;
    
    for (int line = 0; line < LCD_HEIGHT; line++)
    {
        line_x = 0;
        cindex = (line >> 3) * CHAR_MAP_WIDTH;
        for (int map_x = 0; map_x < CHAR_MAP_WIDTH; map_x++)
        {
            register uint8_t data_index = m_char_map[cindex];
            if (data_index < 128)
                data = char_buf[(data_index << 3) + char_line];
            else
                data = user_char[((data_index - 128) << 3) + char_line];
            register uint16_t fore_color = m_color_pal[m_color_map[cindex] >> 4];
            register uint16_t back_color = m_color_pal[(m_color_map[cindex] & 0x0F)];
            line_buf[line_x++] = (data & 0x01) ? fore_color : back_color;
            line_buf[line_x++] = (data & 0x02) ? fore_color : back_color;
            line_buf[line_x++] = (data & 0x04) ? fore_color : back_color;
            line_buf[line_x++] = (data & 0x08) ? fore_color : back_color;
            line_buf[line_x++] = (data & 0x10) ? fore_color : back_color;
            line_buf[line_x++] = (data & 0x20) ? fore_color : back_color;
            line_buf[line_x++] = (data & 0x40) ? fore_color : back_color;
            line_buf[line_x++] = (data & 0x80) ? fore_color : back_color;
            // Next character in our map
            cindex++;
        }
        
        char_line++;
        if (char_line >= CHAR_HEIGHT) char_line = 0;
        
        // Add in the sprites ... they are the TOP layer
        for (int iSprite = 0; iSprite < MAX_SPRITES; iSprite++)
        {
            // Check to see if the sprite is enabled
            if (!sprite_list[iSprite].enabled) continue;
            
            // Check to see if the sprite is within our current line
            if (line < sprite_list[iSprite].y) continue;
            if (line >= (sprite_list[iSprite].y + 8)) continue;
            
            // Get the row of the sprite we are blitting
            register uint8_t row = line - sprite_list[iSprite].y;
            // Get the x position
            register uint8_t x = sprite_list[iSprite].x;
            // Get the foreground color
            register uint16_t fc = m_color_pal[sprite_list[iSprite].fore_color];
            register uint16_t bc = m_color_pal[sprite_list[iSprite].back_color];
            // Get the starting index for the sprite into the user character buffer
            register uint8_t data = user_char[(sprite_list[iSprite].char_index << 3) + row];
            
            if (sprite_list[iSprite].back_color != PAL_TRANSPARENT)
            {
                line_buf[x] = (data & 0x01) ? fc : bc; x++;
                line_buf[x] = (data & 0x02) ? fc : bc; x++;
                line_buf[x] = (data & 0x04) ? fc : bc; x++;
                line_buf[x] = (data & 0x08) ? fc : bc; x++;
                line_buf[x] = (data & 0x10) ? fc : bc; x++;
                line_buf[x] = (data & 0x20) ? fc : bc; x++;
                line_buf[x] = (data & 0x40) ? fc : bc; x++;
                line_buf[x] = (data & 0x80) ? fc : bc; x++;
            }
            else
            {
                if (data & 0x01) line_buf[x] = fc; x++;
                if (data & 0x02) line_buf[x] = fc; x++;
                if (data & 0x04) line_buf[x] = fc; x++;
                if (data & 0x08) line_buf[x] = fc; x++;
                if (data & 0x10) line_buf[x] = fc; x++;
                if (data & 0x20) line_buf[x] = fc; x++;
                if (data & 0x40) line_buf[x] = fc; x++;
                if (data & 0x80) line_buf[x] = fc; x++;
            }
        }
                
        // Draw the line
        blit_line(line);
    }
}

void display::AddSprite(int iSpriteNum, int iChar, int x, int y, int fore_color, int back_color, bool midhandle)
{
    if (iSpriteNum < 0) return;
    if (iSpriteNum >= MAX_SPRITES) return;
    if (iChar >= MAX_USER_CHAR) return;
    
    sprite_list[iSpriteNum].char_index = iChar;
    sprite_list[iSpriteNum].x = x;
    sprite_list[iSpriteNum].y = y;
    sprite_list[iSpriteNum].fore_color = fore_color;
    sprite_list[iSpriteNum].back_color = back_color;
    sprite_list[iSpriteNum].midhandle = midhandle;
    
    // CHeck to see if we treat the X and Y coordinates as the middle of the sprite...
    if (midhandle)
    {
        // Redo the XY coordinates
        SetSpritePos(iSpriteNum, x, y);
    }
    
    // Set the enabled flag last just in case we get called from an interrupt or we are interrupted
    sprite_list[iSpriteNum].enabled = true;
}

void display::EnableSprite(int iSpriteNum, bool bEnable)
{
    if (iSpriteNum < 0) return;
    if (iSpriteNum >= MAX_SPRITES) return;
    
    sprite_list[iSpriteNum].enabled = bEnable;
}

void display::RemoveSprite(int iSpriteNum)
{
    EnableSprite(iSpriteNum, false);
}

void display::SetSpriteChar(int iSpriteNum, int iChar)
{
    if (iSpriteNum < 0) return;
    if (iSpriteNum >= MAX_SPRITES) return;
    if (iChar >= MAX_USER_CHAR) return;
    
    sprite_list[iSpriteNum].char_index = iChar;
}

void display::SetSpritePos(int iSpriteNum, int x, int y)
{
    if (iSpriteNum < 0) return;
    if (iSpriteNum >= MAX_SPRITES) return;
    
    if (sprite_list[iSpriteNum].midhandle)
    {
        x -= 4;
        y -= 4;
    }
    
    sprite_list[iSpriteNum].x = x;
    sprite_list[iSpriteNum].y = y;
}

void display::SetSpriteColor(int iSpriteNum, int fore_color, int back_color)
{
    if (iSpriteNum < 0) return;
    if (iSpriteNum >= MAX_SPRITES) return;
    
    sprite_list[iSpriteNum].fore_color = fore_color;
    sprite_list[iSpriteNum].back_color = back_color;
}

void display::SetMidHandle(int iSpriteNum, bool bEnable)
{
    if (iSpriteNum < 0) return;
    if (iSpriteNum >= MAX_SPRITES) return;
    
    sprite_list[iSpriteNum].midhandle = bEnable;
}

void display::SetCustomChar(int iCharNum, uint8_t *pBuf, int iNum, bool isMSX)
{
    if (iCharNum >= MAX_USER_CHAR) return;
    if ((iNum + iCharNum) > MAX_USER_CHAR) return;
    
    for (int i = 0; i < iNum; i++)
    {
        if (isMSX)
            SetCustomChar8x8MSX(iCharNum + i, pBuf);
        else
            SetCustomChar8x8Norm(iCharNum + i, pBuf);
        pBuf += 8;
    }
}

void display::SetCustomChar8x8Norm(int iCharNum, uint8_t *pBuf)
{
    if (iCharNum >= MAX_USER_CHAR) return;
    
    memcpy(&user_char[iCharNum * 8], pBuf, 8);
}

void display::SetCustomChar8x8MSX(int iCharNum, uint8_t *pBuf)
{
    if (iCharNum >= MAX_USER_CHAR) return;
    
    // 8x8 MSX tiles have the bit order reversed
    for (int i = 0; i < 8; i++)
    {
        uint8_t temp2 = 0;
        uint8_t temp1 = pBuf[i];
        for (int bit = 0; bit < 8; bit++)
        {
            temp2 = temp2 << 1;
            temp2 |= (temp1 & 0x01);
            temp1 = temp1 >> 1;
        }
        user_char[iCharNum * 8 + i] = temp2;
    }
}

void display::set_map(uint8_t char_index, uint8_t x, uint8_t y, uint8_t fore_color, uint8_t back_color)
{
    if (x >= CHAR_MAP_WIDTH) return;
    if (y >= CHAR_MAP_HEIGHT) return;
    
    int pos = get_linear_pos(x, y);
    
    m_char_map[pos] = char_index;
    m_color_map[pos] = COLOR(fore_color, back_color);
    
}

void display::set_map_info(uint16_t tile_info, uint8_t x, uint8_t y)
{
    if (x >= CHAR_MAP_WIDTH) return;
    if (y >= CHAR_MAP_HEIGHT) return;
    
    int pos = get_linear_pos(x, y);
    
    m_char_map[pos] = (tile_info >> 8);
    m_color_map[pos] = (tile_info & 0x00FF);
}

uint8_t display::get_map(uint8_t x, uint8_t y)
{
    return (get_map_info(x, y) >> 8);
}

uint8_t display::get_map_color(uint8_t x, uint8_t y)
{
    return (get_map_info(x, y) & 0x00FF);
}

uint16_t display::get_map_info(uint8_t x, uint8_t y)
{
    if (x >= CHAR_MAP_WIDTH) return 0;
    if (y >= CHAR_MAP_HEIGHT) return 0;
    
    int pos = get_linear_pos(x, y);
    
    return ((m_char_map[pos] << 8) | m_color_map[pos]);
}

void display::shift_map(SHIFT_DIRECTION direction, int inc, bool clear_after_shift)
{
    switch (direction)
    {
    case (eShiftUp):
        for (int i = 1; i < CHAR_MAP_HEIGHT; i++)
        {
            memcpy(&m_char_map[(i - 1) * CHAR_MAP_WIDTH], &m_char_map[i * CHAR_MAP_WIDTH], CHAR_MAP_WIDTH);
            memcpy(&m_color_map[(i - 1) * CHAR_MAP_WIDTH], &m_color_map[i * CHAR_MAP_WIDTH], CHAR_MAP_WIDTH);
        }
        if (clear_after_shift)
        {
            // Clear the bottom row
            memset(&m_char_map[CHAR_MAP_HEIGHT - 1], 0, CHAR_MAP_WIDTH); 
             memset(&m_color_map[CHAR_MAP_HEIGHT - 1], COLOR(m_fore_color, m_back_color), CHAR_MAP_WIDTH);
        }
        break;
    case (eShiftDown):
        for (int i = (CHAR_MAP_HEIGHT - 1); i > 0; i--)
        {
            memcpy(&m_char_map[i * CHAR_MAP_WIDTH], &m_char_map[(i - 1) * CHAR_MAP_WIDTH], CHAR_MAP_WIDTH);
            memcpy(&m_color_map[i * CHAR_MAP_WIDTH], &m_color_map[(i - 1) * CHAR_MAP_WIDTH], CHAR_MAP_WIDTH);
        }
        if (clear_after_shift)
        {
            // Clear the top row
            memset(&m_char_map[0], 0, CHAR_MAP_WIDTH); 
            memset(&m_color_map[0], COLOR(m_fore_color, m_back_color), CHAR_MAP_WIDTH);
        }
        break;
    case (eShiftLeft):
        for (int y = 0; y < CHAR_MAP_HEIGHT; y++)
        {
            for (int x = 1; x < CHAR_MAP_WIDTH; x++)
            {
                int pos = get_linear_pos(x, y);
                m_char_map[pos - 1] = m_char_map[pos];
                m_color_map[pos -1] = m_color_map[pos];
            }
        }
        if (clear_after_shift)
        {
            // Clear the right column
            for (int y = 0; y < CHAR_MAP_HEIGHT; y++)
            {
                m_char_map[get_linear_pos((CHAR_MAP_WIDTH - 1), y)] = 0;
                m_color_map[get_linear_pos((CHAR_MAP_WIDTH - 1), y)] = COLOR(m_fore_color, m_back_color);
            }
        }
        break;
    case (eShiftRight):
        for (int y = 0; y < CHAR_MAP_HEIGHT; y++)
        {
            for (int x = (CHAR_MAP_WIDTH - 1); x > 0; x--)
            {
                int pos = get_linear_pos(x, y);
                m_char_map[pos] = m_char_map[pos - 1];
                m_color_map[pos] = m_color_map[pos - 1];
            }
        }
        if (clear_after_shift)
        {
            // Clear the left column
            for (int y = 0; y < CHAR_MAP_HEIGHT; y++)
            {
                m_char_map[get_linear_pos(0, y)] = 0;
                m_color_map[get_linear_pos(0, y)] = COLOR(m_fore_color, m_back_color);
            }
        }
        break;
    }
}

bool display::SpriteCollision(int iSpriteNum1, int iSpriteNum2)
{
    if (iSpriteNum1 < 0) return false;
    if (iSpriteNum1 >= MAX_SPRITES) return false;
    if (iSpriteNum2 < 0) return false;
    if (iSpriteNum2 >= MAX_SPRITES) return false;
    
    SPRITE *pSprite1 = &sprite_list[iSpriteNum1];
    SPRITE *pSprite2 = &sprite_list[iSpriteNum2];
    
    if (!pSprite1->enabled) return false;
    if (!pSprite2->enabled) return false;
    
    // Bounding box collision - might be broken
    /*
    int dx = pSprite1->x - pSprite2->x;
    if (dx < 0) dx *= -1;
    int dy = pSprite1->y - pSprite2->y;
    if (dy < 0) dy *= -1;

    return ((dx < 16) && (dy < 16));
    */
    
    // Circle based collision detection
    int dx = pSprite1->x - pSprite2->x;
    int dy = pSprite1->y - pSprite2->y;
    int dist = (dx * dx) + (dy * dy);
    int r = 3 + 3;  // make the radius 4 - 1
    return (dist < (r * r));
    
    // TODO - perhaps after the circle detection maybe we can do pixel perfect detection...
    // OR - do detection when we are rendering???
}