Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: mbed mbed-rtos 4DGL-uLCD-SE
main.cpp
- Committer:
- dmartin99
- Date:
- 2021-05-02
- Revision:
- 12:f49bae848977
- Parent:
- 11:0309bef74ba8
File content as of revision 12:f49bae848977:
// Copyright (c) 2015 Devon Cooper and Sidak Dhillon
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "mbed.h"
#include "uLCD_4DGL.h"
// LCD
uLCD_4DGL uLCD(p28, p27, p30); // serial tx, serial rx, reset pin;
// Input Devices
DigitalIn fire_button(p22, PullUp);
// Note: p15 is attached to VERT and p16 is attached to HORZ, however horizontal movement
// triggers VERT and vertical movement triggers HORZ, so they are switched here.
AnalogIn horizontal_in(p15);
AnalogIn vertical_in(p16);
#define SCREEN_MAX_X 128
#define SCREEN_MAX_Y 128
#define BLACK 0x000000
#define SHIP_COLOR 0xFFFFFF
#define SHIP_COLOR2 0xFFF00F
#define SHIP_DAMAGED_COLOR 0xFF0000
#define BARRICADE_COLOR 0xFF00FF
#define BULLET_COLOR 0x00FF00
#define ALIEN_COLOR 0x00FFFF
#define SHIP_WIDTH 16
#define SHIP_HEIGHT 8
#define SHIP_GUN_WIDTH 3
#define SHIP_GUN_HEIGHT 3
#define BULLET_WIDTH 1
#define BULLET_HEIGHT 3
#define BARRICADES_START_Y (SCREEN_MAX_Y - 32)
#define BARRICADES_END_Y (SCREEN_MAX_Y - 16)
#define BARRICADE_SIZE 16
#define ALIEN_HEIGHT 8
#define ALIEN_WIDTH 11
#define ALIEN_SCORE_AMOUNT 10
/***** Sprite Data ***********************************************************/
#define _ BLACK
#define X BARRICADE_COLOR
const int barricade_sprite[BARRICADE_SIZE * BARRICADE_SIZE] = {
_,_,_,X,X,X,X,X,X,X,X,X,X,_,_,_,
_,_,X,X,X,X,X,X,X,X,X,X,X,X,_,_,
_,X,X,X,X,X,X,X,X,X,X,X,X,X,X,_,
X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
X,X,X,X,X,X,X,_,_,X,X,X,X,X,X,X,
X,X,X,X,X,_,_,_,_,_,_,X,X,X,X,X,
X,X,X,X,_,_,_,_,_,_,_,_,X,X,X,X,
};
#undef _
#undef X
#define _ 0x000000
#define X 0x00FFFF
#define Q 0xFF0F0F
const int alien_sprite_1[ALIEN_HEIGHT * ALIEN_WIDTH] = {
_,_,X,_,_,_,_,_,X,_,_,
_,_,_,X,_,_,_,X,_,_,_,
_,_,X,X,X,X,X,X,X,_,_,
_,X,X,_,X,X,X,_,X,X,_,
X,X,X,X,X,X,X,X,X,X,X,
X,_,X,X,X,X,X,X,X,_,X,
X,_,X,_,_,_,_,_,X,_,X,
_,_,_,X,X,_,X,X,_,_,_,
};
const int alien_sprite_flip[ALIEN_HEIGHT * ALIEN_WIDTH] = {
_,_,_,Q,Q,_,Q,Q,_,_,_,
Q,_,Q,_,_,_,_,_,Q,_,Q,
Q,_,Q,Q,Q,Q,Q,Q,Q,_,Q,
Q,Q,Q,Q,Q,Q,Q,Q,Q,Q,Q,
_,Q,Q,_,Q,Q,Q,_,Q,Q,_,
_,_,Q,Q,Q,Q,Q,Q,Q,_,_,
_,_,_,Q,_,_,_,Q,_,_,_,
_,_,Q,_,_,_,_,_,Q,_,_,
};
const int alien_sprite_2[ALIEN_HEIGHT * ALIEN_WIDTH] = {
_,_,_,_,X,X,X,_,_,_,_,
_,X,X,X,X,X,X,X,X,X,_,
X,X,X,X,X,X,X,X,X,X,X,
X,X,X,_,_,X,_,_,X,X,X,
X,X,X,X,X,X,X,X,X,X,X,
_,_,_,X,X,_,X,X,_,_,_,
_,_,X,X,_,_,_,X,X,_,_,
X,X,_,_,_,X,_,_,_,X,X,
};
const int alien_sprite_3[ALIEN_HEIGHT * ALIEN_WIDTH] = {
_,_,_,_,_,X,X,_,_,_,_,
_,_,_,_,X,X,X,X,_,_,_,
_,_,_,X,X,X,X,X,X,_,_,
_,_,X,X,_,X,X,_,X,X,_,
_,_,X,X,X,X,X,X,X,X,_,
_,_,_,_,X,_,_,X,_,_,_,
_,_,_,X,_,X,X,_,X,_,_,
_,_,X,_,X,_,_,X,_,X,_,
};
const int blank[ALIEN_HEIGHT * ALIEN_WIDTH] = {
_,_,_,_,_,_,_,_,_,_,_,
_,_,_,_,_,_,_,_,_,_,_,
_,_,_,_,_,_,_,_,_,_,_,
_,_,_,_,_,_,_,_,_,_,_,
_,_,_,_,_,_,_,_,_,_,_,
_,_,_,_,_,_,_,_,_,_,_,
_,_,_,_,_,_,_,_,_,_,_,
_,_,_,_,_,_,_,_,_,_,_,
};
#undef _
#undef X
enum AlienSpecies {
ALIEN_SPECIES_1, ALIEN_SPECIES_2, ALIEN_SPECIES_3,
};
#define ALIEN_ROW_TO_SPECIES(_row) \
((_row) == 0 ? ALIEN_SPECIES_3 : (_row) < 3 ? ALIEN_SPECIES_2 : ALIEN_SPECIES_1)
// Input Device State
volatile bool fire_button_pressed;
volatile bool fire_button_pressed2;
volatile float horizontal_movement;
volatile float vertical_movement;
volatile float horizontal_movement2;
Ticker input_ticker;
// Barricade state
bool barricades[SCREEN_MAX_X][BARRICADE_SIZE];
// Player state
bool player_alive;
bool player_alive2;
int player_x;
int player_x2;
bool player_bullet_exists;
int player_bullet_x;
int player_bullet_y;
bool player_bullet_exists2;
int player_bullet_x2;
int player_bullet_y2;
int player_score;
int player_lives;
// Alien state
bool aliens[5][5];
int aliens_x; // left-most
int aliens_y; // bottom-most
struct { bool exists; int x; int y; } alien_bullets[3];
// Random seed
unsigned int random_seed;
bool random_seeded;
int pvp_state = -1;
RawSerial dev(p13,p14);
BusOut myled(LED1,LED2,LED3,LED4);
DigitalOut them_led(p24);
DigitalOut us_led(p20);
void left_blue() {
horizontal_movement2 -= 1;
}
void right_blue() {
horizontal_movement2 += 1;
}
Ticker flipper;
void dev_recv()
{
char bnum=0;
while(dev.readable()) {
if (dev.getc()=='!') {
if (dev.getc()=='B') { //button data
bnum = dev.getc(); //button number
if ((bnum>='5')&&(bnum<='8')) { //is a number button 1..4
player_alive2 = true;
bool on = dev.getc()-'0';
myled[bnum-'5']= on; //turn on/off that num LED
if (on) {
if(bnum=='5')
fire_button_pressed2 = 1;
else if(bnum=='7')
flipper.attach(&left_blue, 0.02);
else if(bnum=='8')
flipper.attach(&right_blue, 0.02);
} else {
flipper.detach();
}
}
}
}
}
}
// Audio
#include "SongPlayer.h"
#include "rtos.h"
float game_over_note[18]= {884.0, 798.45, 722.25, 722.25, 798.45, 884.0, 884.0, 884.0, 798.45, 722.25, 798.45, 884.0, 798.45, 722.25, 687.35, 722.25, 722.25, 100.0 };
float game_over_duration[18]= {0.6384, 0.3192, 0.9576, 0.6384, 0.3192, 0.6384, 0.3192, 0.3192, 0.3192, 0.3192, 0.3192, 0.3192, 0.3192, 0.6384, 0.3192, 0.6384, 0.6384, 0.0 };
float shootNote[1] = {950.0};
float durationNote[1] = {0.15};
float hitNote[2] = {1400.0, 1400.0};
float hit_durationNote[2] = {0.15, 0.15};
SongPlayer mySpeaker(p21);
volatile int isPlaying = -1;
Mutex isPlaying_mutex;
Thread audio_thread;
void audio_thread_run() {
while (true) {
isPlaying_mutex.lock();
if (isPlaying == -1) {
isPlaying_mutex.unlock();
} else {
int songID = isPlaying;
isPlaying = 0;
isPlaying_mutex.unlock();
if (songID == 1 ) {
mySpeaker.PlaySong(shootNote, durationNote);
} else if (songID == 2 ) {
us_led = 1;
mySpeaker.PlaySong(hitNote, hit_durationNote);
Thread::wait(500);
us_led = 0;
} else if (songID == 3 ) {
us_led = 1;
them_led = 1;
mySpeaker.PlaySong(game_over_note, game_over_duration);
Thread::wait(500);
us_led = 0;
them_led = 0;
}else if (songID == 5 ) {
them_led = 1;
mySpeaker.PlaySong(hitNote, hit_durationNote);
Thread::wait(500);
them_led = 0;
}
//mySpeaker.PlaySong(shootNote, durationNote);
isPlaying_mutex.lock();
isPlaying = -1;
isPlaying_mutex.unlock();
}
Thread::wait(100);
}
}
void add_audio(int song_id) {
isPlaying_mutex.lock();
int isSongPlaying = isPlaying;
if (isSongPlaying == 0) {
isPlaying_mutex.unlock();
return;
}
isPlaying = song_id;
isPlaying_mutex.unlock();
}
/***** Rendering Routines ****************************************************/
/* The player is drawn as two rectangles, one for the main body, and one for
* the cannon.
*/
void draw_player(int center_x, int color) {
uLCD.filled_rectangle(
center_x - (SHIP_WIDTH / 2),
SCREEN_MAX_Y - SHIP_HEIGHT,
center_x + (SHIP_WIDTH / 2),
SCREEN_MAX_Y - 1,
color);
uLCD.filled_rectangle(
center_x - (SHIP_GUN_WIDTH / 2),
SCREEN_MAX_Y - (SHIP_HEIGHT + SHIP_GUN_HEIGHT),
center_x + (SHIP_GUN_WIDTH / 2),
SCREEN_MAX_Y - SHIP_HEIGHT,
color);
}
/* Aliens are drawn using the sprite data and the uLCD's BLIT mode. */
void draw_alien(AlienSpecies species, int left_x, int bottom_y) {
const int *sprite =
species == ALIEN_SPECIES_1 ? alien_sprite_1 :
species == ALIEN_SPECIES_2 ? alien_sprite_2 :
alien_sprite_3;
uLCD.BLIT(left_x, bottom_y, ALIEN_WIDTH, ALIEN_HEIGHT, (int*)sprite);
}
/* Erase an alien. This is a special routine because aliens have sprites
* and so take more time to draw than other objects.
*/
void erase_alien(int left_x, int bottom_y) {
// We erase an extra 2 around the sides and bottom to account for movement.
// This should be safe because the aliens never get closer than 8 pixels to
// the edge of the LCD.
uLCD.filled_rectangle(left_x - 2, bottom_y, left_x + 11 + 2, bottom_y + 8 + 2, BLACK);
}
/* Draws all 25 aliens. (5 rows, 5 columns) */
void draw_aliens() {
uLCD.filled_rectangle(aliens_x, aliens_y, aliens_x + 16 * 5, aliens_y + 12 * 5, BLACK);
wait(0.05);
for (int x = 0; x < 5; x++) {
for (int y = 0; y < 5; y++) {
if (aliens[x][y]) {
draw_alien(ALIEN_ROW_TO_SPECIES(y), aliens_x + 16 * x, aliens_y + 12 * y);
}
}
}
}
/* Draw a bullet. A bullet is drawn as a 1x3 vertical line. */
void draw_bullet(int center_x, int bottom_y, int color) {
uLCD.line(
center_x,
bottom_y,
center_x,
bottom_y + BULLET_HEIGHT,
color);
}
/***** Initialization ********************************************************/
void initialize_screen() {
// Attempting to use a higher baud rate causes errors (the LCD freezes).
uLCD.baudrate(57600);
uLCD.background_color(BLACK);
uLCD.cls();
}
volatile float init_movement = 0;
volatile float init_movement2 = 0;
bool first_call = true;
void __input_ticker__() {
// while(true) {
if (first_call) {
init_movement = horizontal_in;
init_movement2 = vertical_in;
first_call = false;
}
fire_button_pressed |= !fire_button;
horizontal_movement += horizontal_in - init_movement;
vertical_movement += vertical_in - init_movement2;
if (fire_button_pressed)
player_alive = true;
// Thread::wait(100);
// }
}
Thread movement;
// We use interrupts for measuring input so that input remains responsive even
// when the graphics are lagging.
void initialize_input() {
input_ticker.detach();
input_ticker.attach(__input_ticker__, 0.005);
// movement.start(__input_ticker__);
// START;
}
void initialize_player() {
player_x = 2 * SCREEN_MAX_X / 3;
player_bullet_exists = false;
draw_player(player_x, SHIP_COLOR);
player_score = 0;
player_lives = 2;
uLCD.locate(0, 0);
uLCD.printf("Score:%i", player_score);
uLCD.locate(10, 0);
uLCD.printf("Lives:%i", player_lives);
}
void initialize_player2() {
player_x2 = SCREEN_MAX_X / 3;
draw_player(player_x2, SHIP_COLOR2);
}
void initialize_player3() {
player_x = SCREEN_MAX_X/2;
const int *sprite = alien_sprite_1;
uLCD.BLIT(player_x, SCREEN_MAX_Y-ALIEN_HEIGHT, ALIEN_WIDTH, ALIEN_HEIGHT, (int*)sprite);
}
void initialize_player4() {
player_x2 = SCREEN_MAX_X/2;
const int *sprite = alien_sprite_flip;
uLCD.BLIT(player_x2, ALIEN_HEIGHT, ALIEN_WIDTH, ALIEN_HEIGHT, (int*)sprite);
}
void initialize_aliens() {
// Start the aliens in the upper-left corner of the screen
aliens_x = 8;
aliens_y = 8;
for (int x = 0; x < 5; x++) {
for (int y = 0; y < 5; y++) {
aliens[x][y] = true;
}
}
for (int i = 0; i < 3; i++) {
alien_bullets[i].exists = false;
}
draw_aliens();
}
void initialize_barricades() {
memset(barricades, 0, sizeof(bool) * SCREEN_MAX_X * BARRICADE_SIZE);
for (int i = 1; i < 5; i++) {
int start_x = (SCREEN_MAX_X * i / 5) - (BARRICADE_SIZE / 2);
uLCD.BLIT(start_x, BARRICADES_START_Y, BARRICADE_SIZE, BARRICADE_SIZE, (int*)barricade_sprite);
for (int x = 0; x < BARRICADE_SIZE; x++) {
for (int y = 0; y < BARRICADE_SIZE; y++) {
barricades[start_x + x][y] = !!barricade_sprite[y * BARRICADE_SIZE + x];
}
}
}
}
// Devices only need to be initialized once during the program, at start up
void initialize_devices() {
initialize_screen();
initialize_input();
}
// Game state needs to be re-initialized whenever a new game is started after
// a game over or win.
void initialize_game_state() {
initialize_player();
initialize_player2();
initialize_barricades();
initialize_aliens();
}
void initialize_game_state2() {
initialize_player3();
initialize_player4();
}
/***** Logic *****************************************************************/
/* Causes a chunk of the barricade around the given x and y coordinates to
* be destroyed.
*/
void barricade_impact(int x, int y) {
// This is supposed to create a diamond pattern around the point of
// impact, but for some reason it creates a square.
// Eg, it should be * but instead is *****
// *** *****
// ***** *****
// *** *****
// * *****
int x_dist = 2;
for (int delta_x = -x_dist; delta_x <= x_dist; delta_x++) {
int y_dist = abs(x) == 2 ? 0 : abs(x) == 1 ? 1 : 2;
for (int delta_y = -y_dist; delta_y <= y_dist; delta_y++) {
int abs_x = x + delta_x;
int abs_y = y + delta_y;
if (abs_x >= 0 && abs_x < SCREEN_MAX_X
&& abs_y >= BARRICADES_START_Y && abs_y < BARRICADES_END_Y) {
barricades[abs_x][abs_y - BARRICADES_START_Y] = false;
uLCD.pixel(abs_x, abs_y, BLACK);
}
}
}
}
/* Returns true if a bullet at the given coordinates hits an alien.
* As a side effect, that alien is destroyed and the player's score is increased.
*/
bool alien_impact(int x, int y) {
int horizontal_region = (x - aliens_x) / 16;
int vertical_region = (y - aliens_y) / 12;
int horizontal_offset = (x - aliens_x) % 16;
int vertical_offset = (y - aliens_y) % 12;
if (horizontal_region >= 0 && horizontal_region < 5
&& vertical_region >= 0 && vertical_region < 5
&& aliens[horizontal_region][vertical_region]
&& horizontal_offset <= 11
&& vertical_offset <= 8) {
aliens[horizontal_region][vertical_region] = false;
erase_alien(aliens_x + 16 * horizontal_region, aliens_y + 12 * vertical_region);
player_score += ALIEN_SCORE_AMOUNT;
add_audio(5);
return true;
} else {
return false;
}
}
/* Returns true if a bullet at the given coordinates hits the player.
* As a side effect, the player loses a life.
*/
bool player_impact(int x, int y) {
int player_y = SCREEN_MAX_Y;
int player_y2 = pvp_state == -1 ? SCREEN_MAX_Y : 0;
// if (y < SCREEN_MAX_Y && y >= SCREEN_MAX_Y - (SHIP_HEIGHT + SHIP_GUN_HEIGHT)) {
if (x >= player_x - SHIP_WIDTH / 2 && x <= player_x + SHIP_WIDTH / 2 && y >= player_y - SHIP_HEIGHT / 2 && y <= player_y + SHIP_HEIGHT / 2) {
if (pvp_state == -1)
draw_player(player_x, SHIP_DAMAGED_COLOR);
player_lives -= 1;
add_audio(2);
return true;
} else if (x >= player_x2 - SHIP_WIDTH / 2 && x <= player_x2 + SHIP_WIDTH / 2 && y >= player_y2 - SHIP_HEIGHT / 2 && y <= player_y2 + SHIP_HEIGHT / 2){
if (pvp_state == -1)
draw_player(player_x2, SHIP_DAMAGED_COLOR);
player_lives -= 1;
add_audio(2);
return true;
}
// }
return false;
}
/* Checks if a barricade exists at the given x and y coordinates. */
bool barricade_exist_at(int x, int y) {
return y >= BARRICADES_START_Y && y < BARRICADES_END_Y && barricades[x][y - BARRICADES_START_Y];
}
/***** Updates ***************************************************************/
// Uses C++ functors for the updates. The original idea was that each update
// would return a continuation to allow more responsive updates to the player
// object, but I realized it would be easier to just update the state in-place.
// These should be turned into simple functions, but it works as it is now.
class PlayerUpdate {
public:
void operator()() {
// Sample input devices
float delta_x = horizontal_movement;
horizontal_movement = 0;
// Update player
int old_player_x = player_x;
player_x += int(delta_x * 2);
if (player_x < SHIP_WIDTH / 2) player_x = SHIP_WIDTH / 2;
if (player_x > SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1) player_x = SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1;
if (old_player_x != player_x) {
draw_player(old_player_x, BLACK);
draw_player(player_x, SHIP_COLOR);
draw_player(player_x2, SHIP_COLOR2);
}
}
void alien() {
// Sample input devices
float delta_x = horizontal_movement;
horizontal_movement = 0;
// Update player
int old_player_x = player_x;
player_x += int(delta_x * 2);
if (player_x < SHIP_WIDTH / 2) player_x = SHIP_WIDTH / 2;
if (player_x > SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1) player_x = SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1;
if (old_player_x != player_x) {
uLCD.BLIT(old_player_x, SCREEN_MAX_X - ALIEN_HEIGHT, ALIEN_WIDTH, ALIEN_HEIGHT, (int*)blank);
uLCD.BLIT(player_x, SCREEN_MAX_X - ALIEN_HEIGHT, ALIEN_WIDTH, ALIEN_HEIGHT, (int*)alien_sprite_1);
}
}
};
class PlayerUpdate2 {
public:
void operator()() {
// Sample input devices
float delta_x2 = horizontal_movement2;
horizontal_movement2 = 0;
// Update player
int old_player_x2 = player_x2;
player_x2 += int(delta_x2 * 2);
if (player_x2 < SHIP_WIDTH / 2) player_x2 = SHIP_WIDTH / 2;
if (player_x2 > SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1) player_x2 = SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1;
if (old_player_x2 != player_x2) {
draw_player(old_player_x2, BLACK);
draw_player(player_x, SHIP_COLOR);
draw_player(player_x2, SHIP_COLOR2);
}
}
void alien() {
// Sample input devices
float delta_x2 = horizontal_movement2;
horizontal_movement2 = 0;
// Update player
int old_player_x2 = player_x2;
player_x2 += int(delta_x2 * 2);
if (player_x2 < SHIP_WIDTH / 2) player_x2 = SHIP_WIDTH / 2;
if (player_x2 > SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1) player_x2 = SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1;
if (old_player_x2 != player_x2) {
uLCD.BLIT(old_player_x2, ALIEN_HEIGHT, ALIEN_WIDTH, ALIEN_HEIGHT, (int*)blank);
uLCD.BLIT(player_x2, ALIEN_HEIGHT, ALIEN_WIDTH, ALIEN_HEIGHT, (int*)alien_sprite_flip);
}
}
};
class AlienUpdate {
// This one is a bit complicated, essentially it is a hard-coded state
// machine that shifts one alien at a time so that the player can be
// updated in between rows of aliens shifting.
enum State {
SHIFTING_LEFT,
SHIFTING_RIGHT,
SHIFTING_DOWN_NEXT_IS_RIGHT,
SHIFTING_DOWN_NEXT_IS_LEFT,
} state;
int row_shifting, column_shifting;
public:
AlienUpdate() : state(SHIFTING_RIGHT), row_shifting(0), column_shifting(0) {}
void operator()() {
switch (this->state) {
case SHIFTING_LEFT:
if (aliens[column_shifting][row_shifting]) {
erase_alien(aliens_x + 16 * column_shifting, aliens_y + 12 * row_shifting);
draw_alien(ALIEN_ROW_TO_SPECIES(row_shifting), aliens_x + 16 * column_shifting - 2, aliens_y + 12 * row_shifting);
}
column_shifting -= 1;
if (column_shifting < 0) {
if (row_shifting >= 4) {
aliens_x -= 2;
if (aliens_x < 8) {
state = SHIFTING_DOWN_NEXT_IS_RIGHT;
row_shifting = 0;
column_shifting = 0;
} else {
row_shifting = 0;
column_shifting = 4;
}
} else {
row_shifting += 1;
column_shifting = 4;
}
}
break;
case SHIFTING_RIGHT:
if (aliens[column_shifting][row_shifting]) {
erase_alien(aliens_x + 16 * column_shifting, aliens_y + 12 * row_shifting);
draw_alien(ALIEN_ROW_TO_SPECIES(row_shifting), aliens_x + 16 * column_shifting + 2, aliens_y + 12 * row_shifting);
}
column_shifting += 1;
if (column_shifting > 4) {
if (row_shifting >= 4) {
aliens_x += 2;
if (aliens_x > SCREEN_MAX_X - (16 * 5) - 8) {
state = SHIFTING_DOWN_NEXT_IS_LEFT;
row_shifting = 0;
column_shifting = 0;
} else {
row_shifting = 0;
column_shifting = 0;
}
} else {
row_shifting += 1;
column_shifting = 0;
}
}
break;
case SHIFTING_DOWN_NEXT_IS_LEFT:
case SHIFTING_DOWN_NEXT_IS_RIGHT:
if (aliens[column_shifting][row_shifting]) {
erase_alien(aliens_x + 16 * column_shifting, aliens_y + 12 * row_shifting);
draw_alien(ALIEN_ROW_TO_SPECIES(row_shifting), aliens_x + 16 * column_shifting, aliens_y + 12 * row_shifting + 2);
}
column_shifting += 1;
if (column_shifting > 4) {
if (row_shifting >= 4) {
if (this->state == SHIFTING_DOWN_NEXT_IS_LEFT) {
state = SHIFTING_LEFT;
column_shifting = 4;
} else {
state = SHIFTING_RIGHT;
column_shifting = 0;
}
row_shifting = 0;
aliens_y += 2;
} else {
row_shifting += 1;
column_shifting = 0;
}
}
break;
}
}
};
class BulletUpdate {
public:
void operator()() {
// Sample input devices
bool fire_button_pressed_ = fire_button_pressed;
fire_button_pressed = false;
bool fire_button_pressed_2 = fire_button_pressed2;
fire_button_pressed2 = false;
// Update player bullet
if (player_bullet_exists) {
draw_bullet(player_bullet_x, player_bullet_y, BLACK);
player_bullet_y -= 2;
if (player_bullet_y < 0) {
player_bullet_exists = false;
}
} else if (fire_button_pressed_) {
add_audio(1);
player_bullet_exists = true;
player_bullet_x = player_x;
player_bullet_y = SCREEN_MAX_Y - (SHIP_HEIGHT + SHIP_GUN_HEIGHT) - BULLET_HEIGHT - 1;
}
if (player_bullet_exists) {
if (barricade_exist_at(player_bullet_x, player_bullet_y)) {
barricade_impact(player_bullet_x, player_bullet_y);
player_bullet_exists = false;
} else if (alien_impact(player_bullet_x, player_bullet_y)) {
player_bullet_exists = false;
} else if (pvp_state != -1 && player_impact(player_bullet_x, player_bullet_y + BULLET_HEIGHT)) {
pvp_state = 1;
}else {
draw_bullet(player_bullet_x, player_bullet_y, BULLET_COLOR);
}
}
// Update player2 bullet
if (player_bullet_exists2) {
draw_bullet(player_bullet_x2, player_bullet_y2, BLACK);
player_bullet_y2 -= pvp_state == -1 ? 2 : -2;
if (player_bullet_y2 < 0 || player_bullet_y2 >= SCREEN_MAX_Y) {
player_bullet_exists2 = false;
}
} else if (fire_button_pressed_2) {
add_audio(1);
player_bullet_exists2 = true;
player_bullet_x2 = player_x2;
if (pvp_state != -1)
player_bullet_y2 = 0 + (SHIP_HEIGHT + SHIP_GUN_HEIGHT) + BULLET_HEIGHT + 1;
else
player_bullet_y2 = SCREEN_MAX_Y - (SHIP_HEIGHT + SHIP_GUN_HEIGHT) - BULLET_HEIGHT - 1;
}
if (player_bullet_exists2) {
if (barricade_exist_at(player_bullet_x2, player_bullet_y2)) {
barricade_impact(player_bullet_x2, player_bullet_y2);
player_bullet_exists2 = false;
} else if (alien_impact(player_bullet_x2, player_bullet_y2)) {
player_bullet_exists2 = false;
} else if (pvp_state != -1 && player_impact(player_bullet_x2, player_bullet_y2 + BULLET_HEIGHT)) {
pvp_state = 2;
}else {
draw_bullet(player_bullet_x2, player_bullet_y2, BULLET_COLOR);
}
}
if (pvp_state != -1)
return;
// Update alien bullets
for (int i = 0; i < 3; i++) {
bool alien_fire_button_pressed = rand() % 64 == 0;
if (alien_bullets[i].exists) {
draw_bullet(alien_bullets[i].x, alien_bullets[i].y, BLACK);
alien_bullets[i].y += 2;
if (alien_bullets[i].y >= SCREEN_MAX_Y) {
alien_bullets[i].exists = false;
}
} else if (alien_fire_button_pressed) {
int column_firing = rand() % 5;
int row_firing = -1;
for (int y = 0; y < 5; y++) {
if (aliens[column_firing][y]) {
row_firing = y;
}
}
if (row_firing != -1) {
alien_bullets[i].exists = true;
alien_bullets[i].x = aliens_x + column_firing * 16 + (ALIEN_WIDTH / 2);
alien_bullets[i].y = aliens_y + row_firing * 12 + ALIEN_HEIGHT + 1;
}
}
if (alien_bullets[i].exists) {
if (barricade_exist_at(alien_bullets[i].x, alien_bullets[i].y + BULLET_HEIGHT)) {
barricade_impact(alien_bullets[i].x, alien_bullets[i].y + BULLET_HEIGHT);
alien_bullets[i].exists = false;
} else if (player_impact(alien_bullets[i].x, alien_bullets[i].y + BULLET_HEIGHT)) {
alien_bullets[i].exists = false;
} else {
draw_bullet(alien_bullets[i].x, alien_bullets[i].y, BULLET_COLOR);
}
}
}
}
};
PlayerUpdate player_updater;
PlayerUpdate2 player_updater2;
AlienUpdate alien_updater;
BulletUpdate bullet_updater;
void game_loop() {
player_updater();
player_updater2();
alien_updater();
bullet_updater();
// Seed random number generator if unseeded
// Basically, we count the number of cycles until a fire button is pressed.
// This should be sufficiently random, for game purposes at least.
if (!random_seeded) {
if (fire_button_pressed) {
srand(random_seed);
random_seeded = true;
} else {
random_seed += 1;
}
}
uLCD.text_height(1);
uLCD.text_width(1);
uLCD.locate(0, 0);
uLCD.printf("Score:%i", player_score);
uLCD.locate(10, 0);
uLCD.printf("Lives:%i", player_lives);
// Check for lose condition
if (player_lives < 0) {
uLCD.filled_rectangle(0, 0, SCREEN_MAX_X, BARRICADES_START_Y, BLACK);
uLCD.locate(0, 5);
uLCD.printf(" Game Over\n");
uLCD.printf(" Score: %5d\n", player_score);
uLCD.printf("\n");
uLCD.printf(" Press fire to\n");
uLCD.printf(" play again.\n");
wait(0.6);
add_audio(3);
wait(0.25);
while (!(fire_button_pressed || fire_button_pressed2));
wait(0.25);
uLCD.cls();
fire_button_pressed = false;
fire_button_pressed2 = false;
player_updater = PlayerUpdate();
player_updater2 = PlayerUpdate2();
alien_updater = AlienUpdate();
bullet_updater = BulletUpdate();
initialize_game_state();
return;
}
// Check for win condition
bool all_aliens_destroyed = true;
for (int x = 0; x < 5; x++) {
for (int y = 0; y < 5; y++) {
if (aliens[x][y]) {
all_aliens_destroyed = false;
break;
}
}
}
if (all_aliens_destroyed) {
uLCD.filled_rectangle(0, 0, SCREEN_MAX_X, BARRICADES_START_Y, BLACK);
uLCD.locate(0, 5);
uLCD.printf(" You Win!\n");
uLCD.printf(" Score: %5d\n", player_score);
uLCD.printf("\n");
uLCD.printf(" Press fire to\n");
uLCD.printf(" play again.\n");
wait(0.25);
while (!(fire_button_pressed || fire_button_pressed2) );
wait(0.25);
uLCD.cls();
fire_button_pressed = false;
fire_button_pressed2 = false;
player_updater = PlayerUpdate();
player_updater2 = PlayerUpdate2();
alien_updater = AlienUpdate();
bullet_updater = BulletUpdate();
initialize_game_state();
return;
}
}
void game_loop2() {
player_updater.alien();
player_updater2.alien();
bullet_updater();
// Check for lose condition
if (pvp_state != 0) {
uLCD.filled_rectangle(0, 0, SCREEN_MAX_X, BARRICADES_START_Y, BLACK);
uLCD.locate(0, 5);
uLCD.printf(" Game Over\n");
uLCD.printf(" Player %i won", pvp_state);
uLCD.printf("\n");
uLCD.printf(" Press fire to\n");
uLCD.printf(" play again.\n");
wait(0.6);
add_audio(3);
wait(0.25);
while (!(fire_button_pressed || fire_button_pressed2));
wait(0.25);
uLCD.cls();
fire_button_pressed = false;
fire_button_pressed2 = false;
player_bullet_exists = false;
player_bullet_exists2 = false;
initialize_game_state2();
pvp_state = 0;
}
}
int main() {
initialize_devices();
dev.attach(&dev_recv, Serial::RxIrq);
uLCD.locate(0, 5);
uLCD.printf(" Please Connect\n");
uLCD.printf(" Both Devices!\n");
uLCD.locate(0, 8);
uLCD.printf("Player 1:Not Ready");
uLCD.locate(0, 9);
uLCD.printf("Player 2:Not Ready");
while (!(player_alive && player_alive2)) {
if (player_alive) {
uLCD.locate(0, 8);
uLCD.printf("Player 1:Ready ");
}
if (player_alive2) {
uLCD.locate(0, 9);
uLCD.printf("Player 2:Ready ");
}
wait(0.01);
}
fire_button_pressed = false;
fire_button_pressed2 = false;
uLCD.cls();
uLCD.locate(0, 5);
uLCD.printf(" Select Mode!\n");
uLCD.locate(0, 8);
uLCD.printf("Normal -Seleted");
uLCD.locate(0, 9);
uLCD.printf("PVP ");
while (!(fire_button_pressed)) {
if (vertical_movement > 0.1) {
uLCD.locate(0, 8);
uLCD.printf("Normal ");
uLCD.locate(0, 9);
uLCD.printf("PVP -Seleted");
pvp_state = 0;
}else if (vertical_movement < 0.1) {
uLCD.locate(0, 8);
uLCD.printf("Normal -Seleted");
uLCD.locate(0, 9);
uLCD.printf("PVP ");
pvp_state = -1;
}
vertical_movement = 0;
wait(0.01);
}
fire_button_pressed = false;
fire_button_pressed2 = false;
uLCD.cls();
if (pvp_state != -1)
initialize_game_state2();
else
initialize_game_state();
audio_thread.start(audio_thread_run);
while (true) {
if (pvp_state != -1)
game_loop2();
else
game_loop();
wait(0.005);
}
}