You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
432 lines
10 KiB
432 lines
10 KiB
/** @file space9.c
|
|
@author M. P. Hayes, UCECE
|
|
@date 20 April 2007
|
|
@brief A simple space invaders game with different difficulty levels.
|
|
|
|
@defgroup space9 A simple space invaders game.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include "system.h"
|
|
#include "tinygl.h"
|
|
#include "pacer.h"
|
|
#include "navswitch.h"
|
|
#include "led.h"
|
|
#include "flasher.h"
|
|
#include "spacey.h"
|
|
#include "eeprom.h"
|
|
#include "uint8toa.h"
|
|
#include "../fonts/font3x5_1.h"
|
|
|
|
|
|
#define VERSION "1.6"
|
|
|
|
/** Define polling rates in Hz. */
|
|
enum {LOOP_RATE = 500};
|
|
enum {FLASHER_UPDATE_RATE = LOOP_RATE};
|
|
enum {BUTTON_POLL_RATE = 100};
|
|
enum {GAME_UPDATE_RATE = 100};
|
|
enum {GAME_OVER_PERIOD = 2};
|
|
enum {BUTTON_HOLD_PERIOD = 1};
|
|
|
|
|
|
/** Define flasher modes. */
|
|
static flasher_pattern_t flasher_patterns[] =
|
|
{
|
|
/** POLL_RATE, MOD_FREQ, MOD_DUTY, FLASHER_PERIOD,
|
|
FLASHER_DUTY, FLASHES, PERIOD. */
|
|
{FLASHER_PATTERN (FLASHER_UPDATE_RATE, 100, 100, 0.4, 100, 1, 0.4)},
|
|
{FLASHER_PATTERN (FLASHER_UPDATE_RATE, 100, 100, 0.4, 100, 1, 0.4)},
|
|
{FLASHER_PATTERN (FLASHER_UPDATE_RATE, 200, 100, 0.1, 50, 1, 0.1)},
|
|
};
|
|
|
|
typedef enum {FLASH_MODE_GUN, FLASH_MODE_SHELL,
|
|
FLASH_MODE_ALIEN, FLASH_MODE_NUM} flash_mode_t;
|
|
|
|
|
|
typedef enum {STATE_DUMMY, STATE_INIT, STATE_START,
|
|
STATE_PLAYING, STATE_OVER,
|
|
STATE_READY, STATE_MENU_LEVEL} state_t;
|
|
|
|
enum {GAME_LEVEL_MAX = 5};
|
|
|
|
|
|
typedef struct
|
|
{
|
|
uint8_t level;
|
|
uint8_t games;
|
|
} game_data_t;
|
|
|
|
|
|
|
|
/** Draw pixel on display. */
|
|
static void
|
|
display_handler (void *data, uint8_t col, uint8_t row, spacey_pix_t type)
|
|
{
|
|
uint8_t *display = data;
|
|
uint8_t pixel;
|
|
|
|
pixel = row * TINYGL_WIDTH + col;
|
|
display[pixel] = type;
|
|
}
|
|
|
|
|
|
/** Display the game over message. */
|
|
static void
|
|
game_over_display (char *buffer)
|
|
{
|
|
char *str = buffer;
|
|
|
|
strcpy (str, "GAME OVER ");
|
|
while (*str)
|
|
str++;
|
|
uint8toa (spacey_aliens_killed_get (), str, 0);
|
|
while (*str)
|
|
str++;
|
|
*str++ = '/';
|
|
uint8toa (spacey_shells_fired_get (), str, 0);
|
|
tinygl_clear ();
|
|
tinygl_text (buffer);
|
|
}
|
|
|
|
|
|
static void
|
|
game_text_display (uint8_t num, char *buffer, char *msg)
|
|
{
|
|
char *str = buffer;
|
|
|
|
while (*msg)
|
|
*str++ = *msg++;
|
|
uint8toa (num, str, 0);
|
|
tinygl_clear ();
|
|
tinygl_text (buffer);
|
|
}
|
|
|
|
|
|
/** Display the game level of difficulty. */
|
|
static void
|
|
game_level_display (uint8_t level, char *buffer)
|
|
{
|
|
game_text_display (level, buffer, "L");
|
|
}
|
|
|
|
|
|
/** Set the game level of difficulty. */
|
|
static void
|
|
game_level_set (uint8_t level)
|
|
{
|
|
spacey_options_t options;
|
|
uint8_t alien_num = 3;
|
|
uint8_t alien_steps = 4;
|
|
|
|
switch (level)
|
|
{
|
|
default:
|
|
case 0:
|
|
options.aliens_wrap = 0;
|
|
options.aliens_evasive = 0;
|
|
break;
|
|
|
|
case 1:
|
|
options.aliens_wrap = 0;
|
|
options.aliens_evasive = 1;
|
|
alien_steps = 3;
|
|
break;
|
|
|
|
case 2:
|
|
options.aliens_wrap = 1;
|
|
options.aliens_evasive = 0;
|
|
alien_steps = 3;
|
|
break;
|
|
|
|
case 3:
|
|
options.aliens_wrap = 1;
|
|
options.aliens_evasive = 1;
|
|
alien_steps = 3;
|
|
break;
|
|
|
|
case 4:
|
|
options.aliens_wrap = 1;
|
|
options.aliens_evasive = 1;
|
|
alien_steps = 3;
|
|
alien_num = 4;
|
|
break;
|
|
|
|
case 5:
|
|
options.aliens_wrap = 1;
|
|
options.aliens_evasive = 1;
|
|
alien_steps = 2;
|
|
alien_num = 4;
|
|
break;
|
|
}
|
|
|
|
spacey_alien_num_set (alien_num);
|
|
spacey_alien_create_steps_set (alien_steps);
|
|
spacey_options_set (options);
|
|
}
|
|
|
|
|
|
static void
|
|
game_start (game_data_t *data)
|
|
{
|
|
tinygl_clear ();
|
|
data->games++;
|
|
game_level_set (data->level);
|
|
spacey_start ();
|
|
eeprom_write (0, data, sizeof (*data));
|
|
}
|
|
|
|
|
|
void game_event (__unused__ void *data, spacey_event_t event)
|
|
{
|
|
switch (event)
|
|
{
|
|
case SPACEY_EVENT_ALIEN_HIT:
|
|
break;
|
|
|
|
case SPACEY_EVENT_ALIEN_LANDED:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
main (void)
|
|
{
|
|
uint8_t navswitch_ticks;
|
|
uint8_t game_ticks;
|
|
uint8_t game_over_ticks;
|
|
uint8_t navswitch_down_count;
|
|
state_t state = STATE_INIT;
|
|
flasher_t flashers[SPACEY_PIX_TYPE_NUM];
|
|
uint8_t flasher_state[SPACEY_PIX_TYPE_NUM];
|
|
flasher_obj_t flashers_info[SPACEY_PIX_TYPE_NUM];
|
|
uint8_t display[TINYGL_WIDTH * TINYGL_HEIGHT];
|
|
uint8_t i;
|
|
uint8_t j;
|
|
game_data_t data;
|
|
char message[44];
|
|
|
|
system_init ();
|
|
led_init ();
|
|
|
|
led_set (LED1, 1);
|
|
|
|
/* When the EEPROM is erased all the bytes are 0xFF so set to
|
|
sensible defaults. */
|
|
eeprom_read (0, &data, sizeof (data));
|
|
if (data.level == 0xff)
|
|
{
|
|
data.level = 0;
|
|
data.games = 0;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE (flashers); i++)
|
|
{
|
|
flashers[i] = flasher_init (&flashers_info[i]);
|
|
flasher_state[i] = 0;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE (display); i++)
|
|
display[i] = 0;
|
|
|
|
/* Set up flash patterns for different pixel types. */
|
|
flasher_pattern_set (flashers[SPACEY_PIX_GUN],
|
|
&flasher_patterns[FLASH_MODE_GUN]);
|
|
flasher_pattern_set (flashers[SPACEY_PIX_SHELL],
|
|
&flasher_patterns[FLASH_MODE_SHELL]);
|
|
flasher_pattern_set (flashers[SPACEY_PIX_ALIEN],
|
|
&flasher_patterns[FLASH_MODE_ALIEN]);
|
|
|
|
tinygl_init (LOOP_RATE);
|
|
tinygl_font_set (&font3x5_1);
|
|
tinygl_text_mode_set (TINYGL_TEXT_MODE_SCROLL);
|
|
tinygl_text_dir_set (TINYGL_TEXT_DIR_ROTATE);
|
|
tinygl_text_speed_set (10);
|
|
|
|
navswitch_init ();
|
|
|
|
spacey_init (GAME_UPDATE_RATE, TINYGL_WIDTH, TINYGL_HEIGHT,
|
|
display_handler, display);
|
|
|
|
spacey_event_handler_set (game_event, 0);
|
|
|
|
navswitch_ticks = 0;
|
|
game_ticks = 0;
|
|
game_over_ticks = 0;
|
|
navswitch_down_count = 0;
|
|
|
|
pacer_init (LOOP_RATE);
|
|
|
|
while (1)
|
|
{
|
|
pacer_wait ();
|
|
|
|
if (state == STATE_PLAYING)
|
|
{
|
|
uint8_t *src;
|
|
|
|
/* Update flasher states. NB, the first flasher is always off. */
|
|
for (i = 1; i < ARRAY_SIZE (flashers); i++)
|
|
flasher_state[i] = flasher_update (flashers[i]);
|
|
|
|
/* Update display. */
|
|
src = display;
|
|
for (j = 0; j < TINYGL_HEIGHT; j++)
|
|
for (i = 0; i < TINYGL_WIDTH; i++)
|
|
{
|
|
tinygl_point_t point = {i, j};
|
|
|
|
tinygl_draw_point (point, flasher_state[*src++]);
|
|
}
|
|
}
|
|
|
|
/* Advance messages and refresh display. */
|
|
tinygl_update ();
|
|
|
|
game_ticks++;
|
|
if (game_ticks >= LOOP_RATE / GAME_UPDATE_RATE)
|
|
{
|
|
game_ticks = 0;
|
|
|
|
switch (state)
|
|
{
|
|
case STATE_PLAYING:
|
|
if (! spacey_update ())
|
|
{
|
|
game_over_display (message);
|
|
game_over_ticks = 0;
|
|
led_set (LED1, 1);
|
|
state = STATE_OVER;
|
|
}
|
|
break;
|
|
|
|
case STATE_INIT:
|
|
tinygl_text ("SPACEY READY V" VERSION " BY MPH ");
|
|
state = STATE_READY;
|
|
break;
|
|
|
|
case STATE_OVER:
|
|
game_over_ticks ++;
|
|
if (game_over_ticks >= GAME_UPDATE_RATE * GAME_OVER_PERIOD)
|
|
state = STATE_READY;
|
|
/* Fall through. */
|
|
|
|
case STATE_READY:
|
|
case STATE_MENU_LEVEL:
|
|
default:
|
|
break;
|
|
|
|
case STATE_START:
|
|
/* Turn that bloody blimey space invader off... */
|
|
game_start (&data);
|
|
led_set (LED1, 0);
|
|
state = STATE_PLAYING;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Poll navswitch. */
|
|
navswitch_ticks++;
|
|
if (navswitch_ticks >= LOOP_RATE / BUTTON_POLL_RATE)
|
|
{
|
|
navswitch_ticks = 0;
|
|
|
|
navswitch_update ();
|
|
|
|
if (navswitch_down_p (NAVSWITCH_EAST))
|
|
navswitch_down_count++;
|
|
else
|
|
navswitch_down_count = 0;
|
|
|
|
if (navswitch_down_count >= BUTTON_POLL_RATE * BUTTON_HOLD_PERIOD)
|
|
state = STATE_INIT;
|
|
|
|
if (navswitch_push_event_p (NAVSWITCH_EAST))
|
|
{
|
|
switch (state)
|
|
{
|
|
case STATE_READY:
|
|
break;
|
|
|
|
case STATE_MENU_LEVEL:
|
|
if (data.level < GAME_LEVEL_MAX - 1)
|
|
data.level++;
|
|
game_level_display (data.level, message);
|
|
break;
|
|
|
|
case STATE_PLAYING:
|
|
spacey_gun_move_right ();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (navswitch_push_event_p (NAVSWITCH_WEST))
|
|
{
|
|
switch (state)
|
|
{
|
|
case STATE_READY:
|
|
break;
|
|
|
|
case STATE_MENU_LEVEL:
|
|
if (data.level > 1)
|
|
data.level--;
|
|
game_level_display (data.level, message);
|
|
break;
|
|
|
|
case STATE_PLAYING:
|
|
spacey_gun_move_left ();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (navswitch_push_event_p (NAVSWITCH_WEST))
|
|
{
|
|
switch (state)
|
|
{
|
|
case STATE_READY:
|
|
state = STATE_MENU_LEVEL;
|
|
game_level_display (data.level, message);
|
|
break;
|
|
|
|
case STATE_MENU_LEVEL:
|
|
break;
|
|
|
|
case STATE_PLAYING:
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (navswitch_push_event_p (NAVSWITCH_PUSH))
|
|
{
|
|
switch (state)
|
|
{
|
|
case STATE_READY:
|
|
state = STATE_START;
|
|
break;
|
|
|
|
case STATE_PLAYING:
|
|
spacey_gun_fire ();
|
|
break;
|
|
|
|
case STATE_MENU_LEVEL:
|
|
state = STATE_READY;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|