Space Invaders - Embedded Systems Project 15/16 - Avinash Patel 200860407
Dependencies: Joystick N5110 SDFileSystem mbed
main.cpp
- Committer:
- avi23
- Date:
- 2016-05-05
- Revision:
- 11:eab3fb334bde
- Parent:
- 9:cbb982b7e353
File content as of revision 11:eab3fb334bde:
/** @file main.cpp @brief Implementation File */ /* Space Invaders - Avinash Patel 200860407 Week 19 - Set up joystick class Week 20 - Changed to space invaders as constrained too much by screen resolution - Core cannon is drawn and can move, enemies are visible and they switch between states every second Week 21 - Begun to set up barriers Easter - Barriers work, invader and player can shoot. Begun setting up menus Week 22 - Most menus work Week 23 - Menus work */ #include "mbed.h" #include "main.h" int main() { ///Wait for 2 seconds to allow power to settle wait(1); ///Initalises the board and perhiperals init_K64F(); init_serial(); init_shoot(); init_rng(); joystick.init(); lcd.init(); lcd.clear(); LoadHighScores(); ///Configures the function pointer for the invaders normal missile move_invader_normal_missile_isr[0] = &move_invader_normal_missile_0_isr; move_invader_normal_missile_isr[1] = &move_invader_normal_missile_1_isr; ///Samples joystick every 0.05 second move_joystick.attach(&move_joystick_isr, 0.05); ///Splash Screen SplashScreen(); while (true) { if (game_state == menu) { ///Menu screen lcd.clear(); fsm_state = 0; ///Sets the fsm state to 0 MenuScreen(); } else if (game_state == paused) { ///Paused screen ///Clears the screen lcd.clear(); fsm_state = 0; ///Ssets the fsm state to 0 PauseScreen(); } else if (game_state == game) { ///Game screen AttachTickers(); Game(); } else if (game_state == save) { SaveGameData(); } else if (game_state == load) { LoadGameData(); } else if (game_state == over) { lcd.clear(); GameOverScreen(); } else if (game_state == scores) { lcd.clear(); ScoreScreen(); } else if (game_state == settings) { lcd.clear(); fsm_state = 0; SettingsScreen(); } sleep(); } } void init_K64F() { /// on-board LEDs are active-low, so set pin high to turn them off. r_led = 1; g_led = 1; b_led = 1; /// since the on-board switches have external pull-ups, we should disable the internal pull-down /// resistors that are enabled by default using InterruptIn sw2.mode(PullNone); sw3.mode(PullNone); } void error() { while(1) { /// if error, hang while flashing error message r_led = 0; wait(0.2); r_led = 1; wait(0.2); } } void init_serial() { /// set to highest baud - ensure terminal software matches pc.baud(115200); } ///Seeds the random number generator with noise from an analog in pin void init_rng() { AnalogIn rng_seed(PTC10); ///Creates a AnalogIn on a unused pin srand(floor(10000*rng_seed.read())); ///Sets the seed as 10000x the input of the analog in } void init_shoot() { shoot_button.mode(PullUp); shoot_button.fall(&shoot_pressed_isr); shoot_button.rise(&shoot_pressed_rise_isr); } void update_screen_isr() { g_update_screen_flag = true; } void move_enemies_isr() { g_move_enemies_flag = true; } void shoot_pressed_isr() { ///Only sets the shoot pressed flag 0.1s after the last press if (!g_shoot_button_debounce_flag) { g_shoot_pressed_flag = true; g_shoot_button_debounce_flag = true; } } void move_cannon_missile_isr() { g_move_cannon_missile_flag = true; } void move_joystick_isr() { ///Always set the move flag in a game if (game_state == game) { g_move_joystick_flag = true; } else if (!g_joystick_cursor_regulator_flag) { ///Only sets the flag if the regulator is not set g_move_joystick_flag = true; g_joystick_cursor_regulator_flag = true; ///Attachs a timeout to clear the regulator in 0.1s to prevent the cursor from behaving erratically joystick_cursor_regulator.attach(&joystick_cursor_regulator_isr, 0.1); } } void joystick_cursor_regulator_isr() { g_joystick_cursor_regulator_flag = false; } void shoot_button_debounce_isr() { g_shoot_button_debounce_flag = false; } void move_invader_normal_missile_0_isr() { g_move_invader_normal_missile_flag[0] = true; } void move_invader_normal_missile_1_isr() { g_move_invader_normal_missile_flag[1] = true; } void cannon_hit_isr() { g_cannon_hit_flag = false; } void move_ufo_isr() { g_move_ufo_flag = true; } void fire_buzzer_isr() { g_fire_buzzer_flag = true; } void lp_wait_isr() { g_lp_wait_flag = false; } void shoot_pressed_rise_isr() { ///Attaches a timeout to clear the debounce flag in 0.125 seconds shoot_button_debounce.attach(&shoot_button_debounce_isr, 0.125); } void Game() { ///Stays within the loop while the selected state is game while (game_state == game) { pc.printf("Mute: %d\n", is_muted); ///If the game is over detach all the tickers and set game state to over if (number_of_lives == 0) { DetachTickers(); game_state = over; } else if (no_of_alive_invaders == 0) { ///If the player wins a round ///Resets the no of alive invaders no_of_alive_invaders = 15; down_count = 0; ///Detaches the enemy ticker while reinitalising invaders move_enemies.detach(); ///Reinitalises objects InitSmallInvaders(); InitMediumInvaders(); InitLargeInvaders(); ///Reattaches enemy ticker move_enemies.attach(&move_enemies_isr, 1); } else { ///Updates pixels on the screen if (g_update_screen_flag) { g_update_screen_flag = false; UpdateScreen(); } ///Controls cannon movement if (g_move_joystick_flag) { g_move_joystick_flag = false; MoveCannon(); DrawBarriers(); } ///Controls enemy movement if (g_move_enemies_flag) { g_move_enemies_flag = false; ///Increses the speed the invaders move move_enemies.detach(); ticker_period = 0.1+(no_of_alive_invaders*0.06); move_enemies.attach(&move_enemies_isr, ticker_period); //Attach the buzzer timeout if mute is unselected if (!is_muted) { fire_buzzer.attach(&fire_buzzer_isr, ticker_period/2.0f); } ///Clears the old bitmaps ClearSmallInvaders(); ClearMediumInvaders(); ClearLargeInvaders(); MoveInvaderXPositions(); ///Switches the bitmap state invaders_in_state2 = !invaders_in_state2; ///Draws the invaders DrawSmallInvaders(); DrawMediumInvaders(); DrawLargeInvaders(); ///Attempts to fire the invaders missiles AttemptToFireInvaderNormalMissiles(); ///Attempts to spawn UFO AttemptToSpawnUFO(); } if (g_fire_buzzer_flag && !is_muted) { g_fire_buzzer_flag = false; PlayBuzzer(); } ///Spawns a player bullet if the shoot button is pressed and there isn't a bullet on the screen if (g_shoot_pressed_flag) { g_shoot_pressed_flag = false; if (!cannon_missile_on_screen) { FireCannonMissile(); ///Counts fired missiles to see if the ufo bonus can be applied if (ufo_bonus && shot_count > 14) { shot_count = 0; } else if (shot_count > 22) { shot_count = 0; } else { ++shot_count; } } } ///Move the cannon shot if (g_move_cannon_missile_flag) { g_move_cannon_missile_flag = false; MoveCannonMissile(); } ///Moves the invaders 1st normal missile if (g_move_invader_normal_missile_flag[0]) { g_move_invader_normal_missile_flag[0] = false; MoveInvaderNormalMissile(0); } ///Moves the invaders 2nd normal missile if (g_move_invader_normal_missile_flag[1]) { g_move_invader_normal_missile_flag[1] = false; MoveInvaderNormalMissile(1); } ///Moves the UFO if (g_move_ufo_flag) { g_move_ufo_flag = false; ///Clears the UFO ClearUFO(); ///Moves the UFO in the correct direction if (ufo_direction == RIGHT) { ufo_x_pos += 2; } else { ufo_x_pos -= 2; } ///Draws the UFO DrawUFO(); } if (joystick.get_button_flag()) { joystick.set_button_flag(0); ///Detach all game tickers and turn off PWM DetachTickers(); buzzer.write(0.0); game_state = paused; } } sleep(); } } void InitaliseGame() { ///Clears the screen buffer and runs init functions memset(screen_buffer, 0, sizeof(screen_buffer)); no_of_alive_invaders = 15; score = 0; number_of_lives = 3; down_count = 0; cannon_xpos = 13; ufo_on_screen = false; ufo_bonus = false; shot_count = 0; InitSmallInvaders(); InitMediumInvaders(); InitLargeInvaders(); InitBarriers(); ///If the sound is set activate the buzzer flag if (!is_muted) { g_fire_buzzer_flag = true; } ///Sets the flags so enemies pop up straight away g_update_screen_flag = true; g_move_joystick_flag = true; g_move_enemies_flag = true; g_fire_buzzer_flag = true; ///Forces the missiles to have the fired flags to flase cannon_missile_on_screen = false; invader_normal_missile[0].fired = false; invader_normal_missile[1].fired = false; } void UpdateScreen() { ///Loops through the screen buffer and sets pixels on the LCD for (int col = 0; col < 84; ++col) { for (int row = 0; row < 48; ++row) { if (screen_buffer[col][row]) { lcd.setPixel(col, row); } else { lcd.clearPixel(col, row); } } } lcd.refresh(); } void MoveCannon() { ///Clears the cannon for (int col = 0; col < 9; ++col) { for (int row = 0; row < 5; ++row) { if(cannon_bitmap[row][col]) { screen_buffer[cannon_xpos+col][cannon_ypos+row] = empty_pixel; } } } ///Changes the position of the cannon when the joystick is moved, capping at 0 and 75 so it always fits on the screen if (joystick.GetXValue() < 0.25f) { cannon_xpos--; if (cannon_xpos < 0) { cannon_xpos = 0; } } else if (joystick.GetXValue() > 0.75f) { cannon_xpos++; if (cannon_xpos > 75) { cannon_xpos = 75; } } ///Redraws the cannon for (int col = 0; col < 9; ++col) { for (int row = 0; row < 5; ++row) { if(cannon_bitmap[row][col]) { screen_buffer[cannon_xpos+col][cannon_ypos+row] = cannon_pixel; } } } } ///Sets the position and status of the small invaders void InitSmallInvaders() { for (int i = 0; i < 5; ++i) { small_invader[i].x_pos = 2 + (i*13); /// Large invaders are 12 across so add 13 for a gap space small_invader[i].y_pos = 1; small_invader[i].status = alive; } } ///Cycles through all the small invaders. If they're not already dead clear them void ClearSmallInvaders() { for (int i = 0; i < 5; ++i) { if (small_invader[i].status) { ClearSingleSmallInvader(i); } } } ///Cycles through the the screen invader bitmap and sets the pixels in the buffer to 0 void ClearSingleSmallInvader(int invader_no) { for (int col = 0; col < 8; ++col) { for (int row = 0; row < 6; ++row) { if (invaders_in_state2) { if (small_invader_bitmap_1[row][col]) { screen_buffer[small_invader[invader_no].x_pos + col][small_invader[invader_no].y_pos + row] = empty_pixel; } } else { if (small_invader_bitmap_2[row][col]) { screen_buffer[small_invader[invader_no].x_pos + col][small_invader[invader_no].y_pos + row] = empty_pixel; } } } } small_invader[invader_no].status = (small_invader[invader_no].status == dying) ? dead : alive; } ///Cycles through all the small invaders. If they're alive set them in the screen buffer void DrawSmallInvaders() { for (int i = 0; i < 5; ++i) { if (small_invader[i].status == alive) { DrawSingleSmallInvader(i); } } } ///Cycles through the the screen invader bitmap and sets the pixels in the buffer void DrawSingleSmallInvader(int invader_no) { for (int col = 0; col < 8; ++col) { for (int row = 0; row < 6; ++row) { if (invaders_in_state2) { if (small_invader_bitmap_1[row][col]) { screen_buffer[small_invader[invader_no].x_pos + col][small_invader[invader_no].y_pos + row] = first_small_invader_pixel + invader_no; } } else { if (small_invader_bitmap_2[row][col]) { screen_buffer[small_invader[invader_no].x_pos + col][small_invader[invader_no].y_pos + row] = first_small_invader_pixel + invader_no; } } } } } ///Sets the position and aliveness of the medium invaders void InitMediumInvaders() { for (int i = 0; i < 5; ++i) { medium_invader[i].x_pos = 1 + (i*13); /// Large invaders are 12 across so add 13 for a gap space medium_invader[i].y_pos = 8; medium_invader[i].status = alive; } } ///Cycles through all the medium invaders. If they're not already dead clear them void ClearMediumInvaders() { for (int i = 0; i < 5; ++i) { if (medium_invader[i].status) { ClearSingleMediumInvader(i); } } } ///Cycles through the the screen invader bitmap and sets the pixels in the buffer to 0 void ClearSingleMediumInvader(int invader_no) { for (int col = 0; col < 10; ++col) { for (int row = 0; row < 6; ++row) { if (invaders_in_state2) { if (medium_invader_bitmap_1[row][col]) { screen_buffer[medium_invader[invader_no].x_pos + col][medium_invader[invader_no].y_pos + row] = empty_pixel; } } else { if (medium_invader_bitmap_2[row][col]) { screen_buffer[medium_invader[invader_no].x_pos + col][medium_invader[invader_no].y_pos + row] = empty_pixel; } } } } medium_invader[invader_no].status = (medium_invader[invader_no].status == dying) ? dead : alive; } ///Cycles through all the medium invaders. If they're alive set them in the screen buffer void DrawMediumInvaders() { for (int i = 0; i < 5; ++i) { if (medium_invader[i].status == alive) { DrawSingleMediumInvader(i); } } } ///Cycles through the the screen invader bitmap and sets the pixels in the buffer void DrawSingleMediumInvader(int invader_no) { for (int col = 0; col < 10; ++col) { for (int row = 0; row < 6; ++row) { if (invaders_in_state2) { if (medium_invader_bitmap_1[row][col]) { screen_buffer[medium_invader[invader_no].x_pos + col][medium_invader[invader_no].y_pos + row] = first_medium_invader_pixel + invader_no; } } else { if (medium_invader_bitmap_2[row][col]) { screen_buffer[medium_invader[invader_no].x_pos + col][medium_invader[invader_no].y_pos + row] = first_medium_invader_pixel + invader_no; } } } } } ///Sets the position and status of the large invaders void InitLargeInvaders() { for (int i = 0; i < 5; ++i) { large_invader[i].x_pos = 0 + (i*13); large_invader[i].y_pos = 15; large_invader[i].status = alive; } } ///Cycles through all the large invaders. If they're not already dead clear them void ClearLargeInvaders() { for (int i = 0; i < 5; ++i) { if (large_invader[i].status) { ClearSingleLargeInvader(i); } } } ///Loops through the large invader bitmap, if the pixel in the bitmap is set to 1, set the pixel in the buffer to 0 void ClearSingleLargeInvader(int invader_no) { for (int col = 0; col < 12; ++col) { for (int row = 0; row < 6; ++row) { if (invaders_in_state2) { if (large_invader_bitmap_1[row][col]) { screen_buffer[large_invader[invader_no].x_pos + col][large_invader[invader_no].y_pos + row] = empty_pixel; } } else { if (large_invader_bitmap_2[row][col]) { screen_buffer[large_invader[invader_no].x_pos + col][large_invader[invader_no].y_pos + row] = empty_pixel; } } } } large_invader[invader_no].status = (large_invader[invader_no].status == dying) ? dead : alive; } ///Cycles through all the large invaders. If they're alive set them in the screen buffer void DrawLargeInvaders() { for (int i = 0; i < 5; ++i) { if (large_invader[i].status == alive) { DrawSingleLargeInvader(i); } } } ///Cycles through the the screen invader bitmap and sets the pixels in the buffer void DrawSingleLargeInvader(int invader_no) { for (int col = 0; col < 12; ++col) { for (int row = 0; row < 6; ++row) { if (invaders_in_state2) { if (large_invader_bitmap_1[row][col]) { screen_buffer[large_invader[invader_no].x_pos + col][large_invader[invader_no].y_pos + row] = first_large_invader_pixel + invader_no; } } else { if (large_invader_bitmap_2[row][col]) { screen_buffer[large_invader[invader_no].x_pos + col][large_invader[invader_no].y_pos + row] = first_large_invader_pixel + invader_no; } } } } } ///Sets the position and loads the bitmap into the barrier objects void InitBarriers() { for (int i = 0; i < 3; ++i) { barrier[i].x_pos = 10 + (i*25); barrier[i].y_pos = 33; ///Copies the bitmap into the structs memcpy(barrier[i].before_bitmap, barrier_bitmap, sizeof(barrier_bitmap)); memcpy(barrier[i].after_bitmap, barrier_bitmap, sizeof(barrier_bitmap)); } } void DrawBarriers() { ///Clears the barrier and redraws it with damage applied for (int i = 0; i < 3; ++i) { for (int col = 0; col < 14; ++col) { for (int row = 0; row < 8; ++row) { if (barrier[i].before_bitmap[row][col]) { screen_buffer[barrier[i].x_pos + col][barrier[i].y_pos + row] = empty_pixel; } if (barrier[i].after_bitmap[row][col]) { screen_buffer[barrier[i].x_pos + col][barrier[i].y_pos + row] = first_barrier_pixel + i; } } } ///Copies the after array to the before array memcpy(barrier[i].before_bitmap, barrier[i].after_bitmap, sizeof(barrier[i].after_bitmap)); } } void MoveInvaderXPositions() { ///Moves the invader in the current direction if (invader_direction == RIGHT) { ///Checking the right limit int right_invader_limit = CalculateInvaderRightLimit(); MoveInvadersRight(right_invader_limit); } else { ///Checks the left limit int left_invader_limit = CalculateInvaderLeftLimit(); MoveInvadersLeft(left_invader_limit); } } ///Checks the status off the invaders per column, starting from the left ///If they're alive return the row number int CalculateInvaderLeftLimit() { for (int i = 0; i < 5; ++i) { if (small_invader[i].status == alive || medium_invader[i].status == alive || large_invader[i].status == alive) { return i; } } ///Sort gameover out stuff after return 4; } ///Checks the status off the invaders per column, starting from the right ///If they're alive return the row number int CalculateInvaderRightLimit() { for (int i = 4; i >= 0; --i) { if (small_invader[i].status == alive || medium_invader[i].status == alive || large_invader[i].status == alive) { return i; } } ///Sort gameover stuff return 0; } void MoveInvadersLeft(int limit) { ///Checks the first large invader to see if it can travel anymore if (large_invader[limit].x_pos > 1) { for (int i = 0; i < 5; ++i) { ///Moves the invaders 2 to the left small_invader[i].x_pos -= 2; medium_invader[i].x_pos -= 2; large_invader[i].x_pos -= 2; } } else { ///Shifts the Invaders down and passes in the new direction MoveInvaderYPositions(RIGHT); } } void MoveInvadersRight(int limit) { ///Checks the first large invader to see if it can travel anymore if (large_invader[limit].x_pos < 71) { ///Moves the invaders 2 to the right for (int i = 0; i < 5; ++i) { small_invader[i].x_pos += 2; medium_invader[i].x_pos += 2; large_invader[i].x_pos += 2; } } else { ///Shifts the Invaders down and passes in the new direction MoveInvaderYPositions(LEFT); } } void MoveInvaderYPositions(bool new_direction) { ///Finds the invaders lower limit Invader lowest_invader = CalculateInvaderYLimit(); ///When moving down lowest_invader should not equal none if (lowest_invader == none) { error(); } ///If an invader touches the bottom the game ends, otherwise the invaders descend if (small_invader[0].y_pos < 33 - (7*lowest_invader)) { for (int i = 0; i < 5; ++i) { small_invader[i].y_pos += 3; medium_invader[i].y_pos += 3; large_invader[i].y_pos += 3; } ++down_count; invader_direction = new_direction; } else { number_of_lives = 0; } } Invader CalculateInvaderYLimit() { ///Checks to see which row of invaders are still alive to work out maximum y positions if (large_invader[0].status == alive || large_invader[1].status == alive || large_invader[2].status == alive || large_invader[3].status == alive || large_invader[4].status == alive) { return large; } else if (medium_invader[0].status == alive || medium_invader[1].status == alive || medium_invader[2].status == alive || medium_invader[3].status == alive || medium_invader[4].status == alive) { return medium; } else { return small; } } ///Queries the invaders on their status and returns the type of invader which is alive Invader LowestInvaderInColumn(int column) { if (large_invader[column].status == alive) { return large; } else if (medium_invader[column].status == alive) { return medium; } else if (small_invader[column].status == alive) { return small; } else { return none; } } void FireCannonMissile() { ///Sets the cannon fired flag to true cannon_missile_on_screen = true; ///Offset cannon missile x position by 4 of the cannons x pos cannon_missile_x_pos = cannon_xpos + 4; ///Will always have a starting y of 40 cannon_missile_y_pos = 40; ///Attach the move cannon missile move_cannon_missile.attach(&move_cannon_missile_isr, 0.05); } void MoveCannonMissile() { ///Checks bullet will not go beyond the bounds of the screen buffer if (cannon_missile_y_pos > -1) { ///Loops throught the shot bitmap and clears the pixels in the screen buffer for (int row = 0; row < 4; ++row) { ///Clears the position where the bullet was screen_buffer[cannon_missile_x_pos][cannon_missile_y_pos + row] = empty_pixel; } ///Increments the shot going up the screen --cannon_missile_y_pos; ///Checks to see if the shot will hit anything CollisionDetectionCannonMissile(); } else { ///Loops throught the shot bitmap and clears the pixels in the screen buffer for (int row = 1; row < 4; ++row) { ///Clears the position where the bullet was screen_buffer[cannon_missile_x_pos][cannon_missile_y_pos + row] = empty_pixel; } cannon_missile_on_screen = false; move_cannon_missile.detach(); } } ///Checks to see what the shot hits. If it hits nothing the shot gets pushed to the screen buffer void CollisionDetectionCannonMissile() { for (int row = 0; row < 4; ++row) { int object_no; int pixel_check = screen_buffer[cannon_missile_x_pos][cannon_missile_y_pos + row]; if (pixel_check >= first_small_invader_pixel && pixel_check < ufo_pixel) { ///Collides with a small invader ///Find the object no of the small invader it hit, clears it and increments the score object_no = CannonMissileHitInvader(first_small_invader_pixel, row, small_invader); ClearSingleSmallInvader(object_no); score += 40; break; } else if (pixel_check >= first_medium_invader_pixel && pixel_check < first_small_invader_pixel) { ///Collides with a medium invader ///Find the object no of the medium invader it hit, clears it and increments the score object_no = CannonMissileHitInvader(first_medium_invader_pixel, row, medium_invader); ClearSingleMediumInvader(object_no); score += 20; break; } else if (pixel_check >= first_large_invader_pixel && pixel_check < first_medium_invader_pixel) { ///Collides with a large invader ///Find the object no of the large invader it hit, clears it and increments the score object_no = CannonMissileHitInvader(first_large_invader_pixel, row, large_invader); ClearSingleLargeInvader(object_no); score += 10; break; } else if (pixel_check >= first_barrier_pixel && pixel_check < (first_barrier_pixel + 3)) { ///Collides with a barrier ///Adds the destruction done to the barrier to the bitmap and redraws the barriers CannonMissileHitBarrier(row); DrawBarriers(); break; } else if (pixel_check == ufo_pixel) { ///Collides with a UFO ///Clears the UFO, calculates score depending on the ufo bonus ClearUFO(); ufo_on_screen = false; move_ufo.detach(); if (ufo_bonus && shot_count == 15) { ///If the shot bonus is active and on the 15th shot, give 300 points score += 300; } else if (shot_count == 23) { ///If the shot bonus isn't active and on the 23rd shot, give 300 points and activate the bonus ufo_bonus = true; score += 300; } else { ///Randomly assign score between 50, 100, 150 points score += 50 * ((rand() % 3) + 1); } cannon_missile_on_screen = false; move_cannon_missile.detach(); break; } else { screen_buffer[cannon_missile_x_pos][cannon_missile_y_pos + row] = cannon_missile_pixel; } } } ///Finds the invader number the missile hits, sets the hit invader to dying, decrements the no_of_alive_invaders and stops the shot from travalling up the screen int CannonMissileHitInvader(int first_pixel, int row, struct Invaders (&invader)[5]) { int invader_no = screen_buffer[cannon_missile_x_pos][cannon_missile_y_pos + row] - first_pixel; invader[invader_no].status = dying; cannon_missile_on_screen = false; --no_of_alive_invaders; move_cannon_missile.detach(); return invader_no; } ///Calculates where to start drawing the damage bitmap over the barrier bitmap and performs the operation void CannonMissileHitBarrier(int row) { int barrier_no = screen_buffer[cannon_missile_x_pos][cannon_missile_y_pos + row] - first_barrier_pixel; ///Essentially inverse of barrier init int relative_x_pos = cannon_missile_x_pos - 10 - (barrier_no*25) - 1; int relative_y_pos = (cannon_missile_y_pos + row) - 33 - 1; ///Don't know why it's -1 and not -2 ///Loops through the damage bitmap and modifies the barrier's after bitmap for (int col = 0; col < 3; ++col) { for (int row_bit = 0; row_bit < 3; ++row_bit) { ///Makes sure bitmap index does not go out of bounds. If it does go to the next iteration ///Element by element multiplication of the 2 bitmaps to clear the required pixals if (relative_x_pos + col >= 0 && relative_x_pos + col < 14 && relative_y_pos + row_bit >= 0 && relative_y_pos + row_bit < 8) { barrier[barrier_no].after_bitmap[relative_y_pos + row_bit][relative_x_pos + col] *= barrier_cannon_missile_damage_bitmap[row_bit][col]; } } } cannon_missile_on_screen = false; move_cannon_missile.detach(); } void AttemptToFireInvaderNormalMissiles() { ///Fires the normal missiles ///Loops through the 2 allowed missiles and if they're not on the screen, randomly fire for (int i = 0; i < 2; ++i) { if (!invader_normal_missile[i].fired) { ///If the random mumber is 1, fire a missile ///Higher chance when there are less invaders on the screen, up to a 1/3 chance if ((rand() % (no_of_alive_invaders + 2)) == 1) { int fired_column; int loop_limit = 0; ///Stops loop from never ending Invader missile_source; do { fired_column = rand() % 5; missile_source = LowestInvaderInColumn(fired_column); ++loop_limit; } while (missile_source == none && loop_limit < 10); ///Finds the centre point of the chosen invader and fires the missile ///If the loop limit is reached, the missile source will be none and the for loop will jump to the next iteration if (missile_source == none) { continue; } else if (missile_source == large) { FireNormalInvaderMissile(i, missile_source, large_invader[fired_column]); } else if (missile_source == medium) { FireNormalInvaderMissile(i, missile_source, medium_invader[fired_column]); } else { FireNormalInvaderMissile(i, missile_source, small_invader[fired_column]); } } } } } void FireNormalInvaderMissile(int missile_no, Invader source, const struct Invaders (&invader)) { ///Finds the centre point of the chosen invader and fires the missile ///Enums are implicity converted to ints --> the middle x position of the invader found by offsetting the invader type by 3 invader_normal_missile[missile_no].x_pos = invader.x_pos + (3 + source); invader_normal_missile[missile_no].y_pos = invader.y_pos + 6; invader_normal_missile[missile_no].fired = true; ///Uses a function pointer to attach the ticker move_invader_normal_missile[missile_no].attach(move_invader_normal_missile_isr[missile_no], 0.05); } void MoveInvaderNormalMissile(int missile_no) { ///Loops through the bitmap and clears the missile from the screen buffer for (int col = 0; col < 3; ++col) { for (int row = 0; row < 4; ++row) { if (invader_normal_missile_bitmap[row][col]) { screen_buffer[invader_normal_missile[missile_no].x_pos + col][invader_normal_missile[missile_no].y_pos + row] = empty_pixel; } } } ///Checks missile will not exceed screen buffer if (invader_normal_missile[missile_no].y_pos < 44) { ///Increments the position of the missile ++invader_normal_missile[missile_no].y_pos; ///Collision detection CollisionDetectionInvaderNormalMissile(missile_no); } else { ///Sets the missiles fired flag as false and detaches the tickers invader_normal_missile[missile_no].fired = false; move_invader_normal_missile[missile_no].detach(); } } ///Checks the bottom centre point for a collision. If it doesn't push the bitmap to the screen buffer void CollisionDetectionInvaderNormalMissile(int missile_no) { ///Invader missile coordinates shifted to match centre bottom of bitmap int relative_x_pos = invader_normal_missile[missile_no].x_pos + 1; int relative_y_pos = invader_normal_missile[missile_no].y_pos + 3; if (screen_buffer[relative_x_pos][relative_y_pos] == cannon_pixel) { ///Cannon has invunerability for 1s after getting hit if (!g_cannon_hit_flag) { g_cannon_hit_flag = true; InvaderNormalMissileHitCannon(); ///Attaches a timeout to allow cannon to be hit again cannon_hit.attach(&cannon_hit_isr, 1); } invader_normal_missile[missile_no].fired = false; move_invader_normal_missile[missile_no].detach(); } else if (screen_buffer[relative_x_pos][relative_y_pos] >= first_barrier_pixel && screen_buffer[relative_x_pos][relative_y_pos] < (first_barrier_pixel + 3)) { ///Finds barrier number InvaderNormalMissileHitBarrier(invader_normal_missile[missile_no]); invader_normal_missile[missile_no].fired = false; move_invader_normal_missile[missile_no].detach(); } else { for (int col = 0; col < 3; ++col) { for (int row = 0; row < 4; ++row) { if (invader_normal_missile_bitmap[row][col]) { screen_buffer[invader_normal_missile[missile_no].x_pos + col][invader_normal_missile[missile_no].y_pos + row] = invader_normal_missile_pixel; } } } } } void InvaderNormalMissileHitCannon() { ///Decrements the number of lives, pauses the game for 2 seconds ///Detaches all tickers DetachTickers(); LPWait(2); --number_of_lives; AttachTickers(); } ///Calculates where to start drawing the damage bitmap over the barrier bitmap and performs the operation void InvaderNormalMissileHitBarrier(const struct InvaderNormalMissiles (&missile)) { int barrier_no = screen_buffer[missile.x_pos + 1][missile.y_pos + 3] - first_barrier_pixel; ///Essentially inverse of barrier init int relative_x_pos = (missile.x_pos + 1) - 10 - (barrier_no*25) - 1; int relative_y_pos = (missile.y_pos + 3) - 33; ///Loops through the damage bitmap and modifies the barrier's after bitmap for (int col = 0; col < 3; ++col) { for (int row_bit = 0; row_bit < 2; ++row_bit) { ///Makes sure bitmap index does not go out of bounds. If it does go to the next iteration if (relative_x_pos + col >= 0 && relative_x_pos + col < 14 && relative_y_pos + row_bit >= 0 && relative_y_pos + row_bit < 8) { barrier[barrier_no].after_bitmap[relative_y_pos + row_bit][relative_x_pos + col] *= barrier_invader_normal_missile_damage_bitmap[row_bit][col]; } } } } ///Detaches game related tickers void DetachTickers() { update_screen.detach(); move_enemies.detach(); ///Only detaches if are on the screen if (cannon_missile_on_screen) { move_cannon_missile.detach(); } if (ufo_on_screen) { move_ufo.detach(); } for (int i = 0; i < 2; ++i) { if (invader_normal_missile[i].fired) { move_invader_normal_missile[i].detach(); } } } ///Attaches game related tickers void AttachTickers() { update_screen.attach(&update_screen_isr, 0.05); move_enemies.attach(&move_enemies_isr, ticker_period); ///Only attaches if missiles were on the screen if (cannon_missile_on_screen) { move_cannon_missile.attach(&move_cannon_missile_isr, 0.05); } if (ufo_on_screen) { move_ufo.attach(&move_ufo_isr, 0.1); } for (int i = 0; i < 2; ++i) { if (invader_normal_missile[i].fired) { move_invader_normal_missile[i].attach(move_invader_normal_missile_isr[i], 0.05); } } } void PauseScreen() { ///Prints the pause screen, score etc PrintPauseScreen(); while (game_state == paused) { ///Draws the cursor cursor_y_pos = ((fsm_state+3)*8)+3; ///Adds 3 to the fsm state to get the bank, multiplies it by 8 to get the pixel no and offsets it by 3 lcd.drawRect(74, cursor_y_pos, 2, 2, 1); ///Draws the cursor if (g_move_joystick_flag) { g_move_joystick_flag = false; ///Moves the cursor to match the selected option, only if the joystick isn't centred MoveCursor(fsm_paused); } ///If the button is pressed the selected option will appear on the screen if (g_shoot_pressed_flag) { g_shoot_pressed_flag = false; game_state = fsm_paused[fsm_state].output; } ///Returns to the game state when the joystick is pressed if (joystick.get_button_flag()) { joystick.set_button_flag(0); game_state = game; } lcd.refresh(); sleep(); } } void PrintPauseScreen() { ///Prints paused and a line underneath lcd.printString("Paused", 24, 0); ///Displays the current score char buffer[14]; sprintf(buffer, "Score: %d", score); lcd.printString(buffer, 0, 1); ///Displays the no of lives sprintf(buffer, "Lives: %d", number_of_lives); lcd.printString(buffer, 0, 2); lcd.drawLine(0, 23, 83, 23, 2); ///Prints options on pause menu lcd.printString("Resume", 5, 3); lcd.printString("Save", 5, 4); lcd.printString("Quit", 5, 5); } void MoveCursor(const struct FSMMenus *fsm) { ///If the joystick is is pushed down half way clear the cursor and set the next state if (joystick.GetYValue() < 0.25f) { ///Clears the cursor lcd.drawRect(74, cursor_y_pos, 2, 2, 2); ///Sets the new state fsm_state = fsm[fsm_state].next_state[0]; } else if (joystick.GetYValue() > 0.75f) { ///Clears the cursor lcd.drawRect(74, cursor_y_pos, 2, 2, 2); ///Sets the new state fsm_state = fsm[fsm_state].next_state[1]; } } void MenuScreen() { ///Print the menu text PrintMenuScreen(); while (game_state == menu) { ///Draws the cursor cursor_y_pos = ((fsm_state+2)*8)+3; ///Adds 2 to the fsm state to get the bank, multiplies it by 8 to get the pixel no and offsets it by 3 lcd.drawRect(74, cursor_y_pos, 2, 2, 1); if (g_move_joystick_flag) { g_move_joystick_flag = false; ///Moves the cursor to match the selected option, only if the joystick isn't centred MoveCursor(fsm_main_menu); } ///If shoot is pressed go to the selected screen if (g_shoot_pressed_flag) { g_shoot_pressed_flag = false; game_state = fsm_main_menu[fsm_state].output; ///If the game state is equal to the game initalise it if (game_state == game) { InitaliseGame(); } } ///Do nothing when the joystick is pressed if(joystick.get_button_flag()) { joystick.set_button_flag(0); } lcd.refresh(); sleep(); } } void PrintMenuScreen() { ///Prints Space invaders, then menu below, then a line underneath lcd.printString("Space Invaders", 0, 0); lcd.printString("Menu", 28, 1); lcd.drawLine(0, 15, 83, 15, 2); ///Prints menu options lcd.printString("New Game", 5, 2); lcd.printString("Load Game", 5, 3); lcd.printString("High Scores", 5, 4); lcd.printString("Settings", 5, 5); } void AttemptToSpawnUFO() { ///1/8 chance of UFO spawning if it hasn't already if (!ufo_on_screen && (rand() % 8 == 1) && down_count > 1 && no_of_alive_invaders > 3) { ///Marks the UFO as on the screen and sets its position and direction ufo_on_screen = true; ufo_direction = (rand() % 2); if (ufo_direction == RIGHT) { ufo_x_pos = -14; } else { ufo_x_pos = 83; } ///Attachs the move UFO ticker move_ufo.attach(&move_ufo_isr, 0.1); } } void ClearUFO() { ///Clears the UFO for (int col = 0; col < 14; ++col) { for (int row = 0; row < 5; ++row) { ///Only clear the pixel in the buffer if it is within bounds if (ufo_x_pos + col >= 0 && ufo_x_pos + col < 84) { if (ufo_bitmap[row][col]) { screen_buffer[ufo_x_pos + col][ufo_y_pos + row] = empty_pixel; } } } } } void DrawUFO() { ///Checks if the ufo can be within the screen bounds ///If it is, push the ufo to the screen buffer if (ufo_x_pos > -14 && ufo_x_pos < 84) { for (int col = 0; col < 14; ++col) { for (int row = 0; row < 5; ++row) { ///Only clear the pixel in the buffer if it is within bounds if (ufo_x_pos + col >= 0 && ufo_x_pos + col < 84) { if (ufo_bitmap[row][col]) { screen_buffer[ufo_x_pos + col][ufo_y_pos + row] = ufo_pixel; } } } } } else { ///Otherwise mark it as not on the screen and detach the ticker ufo_on_screen = false; move_ufo.detach(); } } bool high_scores_sorter(const struct HighScores (&lhs), const struct HighScores (&rhs)) { return lhs.score > rhs.score; } void ScoreScreen() { lcd.printString("High Scores", 8, 0); lcd.drawLine(0, 7, 83, 7, 2); PrintHighScores(); lcd.printString("Back", 5, 5); const int cursor_y_pos = 43; lcd.drawRect(74, cursor_y_pos, 2, 2, 1); lcd.refresh(); while (game_state == scores) { if (joystick.get_button_flag()) { joystick.set_button_flag(0); } if (g_shoot_pressed_flag) { g_shoot_pressed_flag = false; game_state = menu; // If the button is pressed go to the menu state } sleep(); } } void PrintHighScores() { ///Loops throught the high scores and prints them to the screen for (int i = 0; i < 3; ++i) { ///Counts how many digits are in score int temp_score = high_scores[i].score; int no_of_digits = 0; do { temp_score /= 10; ++no_of_digits; } while (temp_score != 0); ///Prints name to c-string char buffer[14]; sprintf(buffer, "%s", high_scores[i].name); ///Prints correct number of spaces to c-string int no_of_spaces = 14 - no_of_digits; for (int space = 3; space < no_of_spaces; ++space) { sprintf(buffer, "%s%s", buffer, " "); } ///Prints score to c-string sprintf(buffer, "%s%d", buffer, high_scores[i].score); lcd.printString(buffer, 0, 2 + i); } } void GameOverScreen() { lcd.printString("Game Over", 18, 0); char buffer[14]; sprintf(buffer, "Score: %d", score); lcd.printString(buffer, 0, 1); ///Save score input if (score > high_scores[2].score) { lcd.printString("High Score!", 0, 2); lcd.printChar('A', 27, 3); lcd.printChar('A', 42, 3); lcd.printChar('A', 57, 3); int name[3] = {0, 0, 0}; GetNameForScore(name); SaveHighScore(name); } ///After high score is inputted or no high score has been reached lcd.printString("Quit", 5, 5); const int cursor_y_pos = 43; lcd.drawRect(74, cursor_y_pos, 2, 2, 1); while (game_state == over) { if (joystick.get_button_flag()) { joystick.set_button_flag(0); } if (g_shoot_pressed_flag) { g_shoot_pressed_flag = false; game_state = menu; } lcd.refresh(); sleep(); } } void GetNameForScore(int (&name)[3]) { int name_cursor_pos = 0; ///Cursor is at first letter while (name_cursor_pos < 3) { ///Prints the name cursor lcd.drawRect(27+(15*name_cursor_pos), 32, 4, 2, 1); if (g_shoot_pressed_flag) { g_shoot_pressed_flag = false; ///Clears the name cursor and moves it lcd.drawRect(27+(15*name_cursor_pos), 32, 4, 2, 2); ++name_cursor_pos; } if (g_move_joystick_flag) { g_move_joystick_flag = false; if (joystick.GetYValue() < 0.25f) { ///Increments the name position, if it is over 25 set it to 0 if (++name[name_cursor_pos] > 25) { name[name_cursor_pos] = 0; } } else if (joystick.GetYValue() > 0.75f) { ///Decrements the name position, if it is under 0 set it to 25 if (--name[name_cursor_pos] < 0) { name[name_cursor_pos] = 25; } } lcd.printChar(name[name_cursor_pos] + 65, 27+(15*name_cursor_pos), 3); ///ASCII is offset by 65 } lcd.refresh(); sleep(); } } void SaveHighScore(const int (&name)[3]) { ///Saves name and score to struct array, overwritting lowest score for (int letter = 0; letter < 3; ++letter) { high_scores[2].name[letter] = name[letter] + 65; } high_scores[2].score = score; ///Sorts struct array std::sort(high_scores, high_scores + 3, &high_scores_sorter); ///Saves the score std::ofstream high_score_data("/sd/high_scores.dat", std::ios::binary); if (high_score_data.good()) { ///Checks to see if file opening was good high_score_data.write((char *)(&high_scores), sizeof(high_scores)); ///Flushes the buffer and saves the file high_score_data.close(); lcd.printString("Saved", 27, 4); } else { lcd.clear(); lcd.printString("Error", 1, 1); lcd.printString("Opening", 1, 2); lcd.printString("File.", 1, 3); LPWait(2); } } void SaveGameData() { ///Creates the output file as a binary file std::ofstream save_data("/sd/game_save.dat", std::ios::binary); if (save_data.good()) { ///Checks to see if file opening was good ///Prints booleans to file save_data.write((char *)(&invaders_in_state2), sizeof(invaders_in_state2)); save_data.write((char *)(&invader_direction), sizeof(invader_direction)); save_data.write((char *)(&cannon_missile_on_screen), sizeof(cannon_missile_on_screen)); save_data.write((char *)(&ufo_on_screen), sizeof(ufo_on_screen)); save_data.write((char *)(&ufo_direction), sizeof(ufo_direction)); save_data.write((char *)(&ufo_bonus), sizeof(ufo_bonus)); ///Prints integers to file save_data.write((char *)(&score), sizeof(score)); save_data.write((char *)(&shot_count), sizeof(shot_count)); save_data.write((char *)(&number_of_lives), sizeof(number_of_lives)); save_data.write((char *)(&cannon_xpos), sizeof(cannon_xpos)); save_data.write((char *)(&cannon_missile_x_pos), sizeof(cannon_missile_x_pos)); save_data.write((char *)(&cannon_missile_y_pos), sizeof(cannon_missile_y_pos)); save_data.write((char *)(&no_of_alive_invaders), sizeof(no_of_alive_invaders)); save_data.write((char *)(&down_count), sizeof(down_count)); save_data.write((char *)(&ufo_x_pos), sizeof(ufo_x_pos)); ///Prints invaders to file save_data.write((char *)(&small_invader), sizeof(small_invader)); save_data.write((char *)(&medium_invader), sizeof(medium_invader)); save_data.write((char *)(&large_invader), sizeof(large_invader)); ///Prints invader missiles and barriers to file save_data.write((char *)(&invader_normal_missile), sizeof(invader_normal_missile)); save_data.write((char *)(&barrier), sizeof(barrier)); ///Flushes the buffer and saves the file save_data.close(); } else { lcd.clear(); lcd.printString("Error", 1, 1); lcd.printString("Opening", 1, 2); lcd.printString("File.", 1, 3); LPWait(2); } game_state = paused; } void LoadGameData() { ///Creates the input binary file object std::ifstream save_data("/sd/game_save.dat", std::ios::binary); if (save_data.good()) { ///Checks to see if file opening was good ///Gets booleans from file save_data.read((char *)(&invaders_in_state2), sizeof(invaders_in_state2)); save_data.read((char *)(&invader_direction), sizeof(invader_direction)); save_data.read((char *)(&cannon_missile_on_screen), sizeof(cannon_missile_on_screen)); save_data.read((char *)(&ufo_on_screen), sizeof(ufo_on_screen)); save_data.read((char *)(&ufo_direction), sizeof(ufo_direction)); save_data.read((char *)(&ufo_bonus), sizeof(ufo_bonus)); ///Gets integers from file save_data.read((char *)(&score), sizeof(score)); save_data.read((char *)(&shot_count), sizeof(shot_count)); save_data.read((char *)(&number_of_lives), sizeof(number_of_lives)); save_data.read((char *)(&cannon_xpos), sizeof(cannon_xpos)); save_data.read((char *)(&cannon_missile_x_pos), sizeof(cannon_missile_x_pos)); save_data.read((char *)(&cannon_missile_y_pos), sizeof(cannon_missile_y_pos)); save_data.read((char *)(&no_of_alive_invaders), sizeof(no_of_alive_invaders)); save_data.read((char *)(&down_count), sizeof(down_count)); save_data.read((char *)(&ufo_x_pos), sizeof(ufo_x_pos)); ///Gets invaders from file save_data.read((char *)(&small_invader), sizeof(small_invader)); save_data.read((char *)(&medium_invader), sizeof(medium_invader)); save_data.read((char *)(&large_invader), sizeof(large_invader)); ///Gets invader missiles and barriers from file save_data.read((char *)(&invader_normal_missile), sizeof(invader_normal_missile)); save_data.read((char *)(&barrier), sizeof(barrier)); ///Closes the file save_data.close(); ///Clears the screen buffer memset(screen_buffer, 0, sizeof(screen_buffer)); ///Sets the game state flags game_state = game; g_update_screen_flag = true; g_move_joystick_flag = true; g_move_enemies_flag = true; g_fire_buzzer_flag = true; } else { lcd.clear(); lcd.printString("Error", 1, 1); lcd.printString("Opening", 1, 2); lcd.printString("File.", 1, 3); LPWait(2); game_state = menu; } } void LoadHighScores() { ///Clears high score data before loading file memset(high_scores, 0, sizeof(high_scores)); ///Extracts high scores from file std::ifstream high_score_data("/sd/high_scores.dat", std::ios::binary); if (high_score_data.good()) { high_score_data.read((char *)(&high_scores), sizeof(high_scores)); } high_score_data.close(); ///If there are any empty names, fill them with AAA to prevent crashes using printString for (int i = 0; i < 3; ++i) { if (high_scores[i].name[0] < 'A') { for (int letter = 0; letter < 3; ++letter) { high_scores[i].name[letter] = 'A'; } } } } void SettingsScreen() { PrintSettingsText(); while (game_state == settings) { ///Prints options and cursor PrintSettingsOptions(); ///Returns to menu if joystick button is pressed if (joystick.get_button_flag()) { joystick.set_button_flag(0); game_state = menu; } ///Moves the cursor when the joystick moves if (g_move_joystick_flag) { g_move_joystick_flag = false; MoveCursor(fsm_settings); } if (g_shoot_pressed_flag) { g_shoot_pressed_flag = false; EditOption(); } lcd.refresh(); sleep(); } } void PrintSettingsText() { ///Prints the options lcd.printString("Settings", 17, 0); lcd.drawLine(0, 7, 83, 7, 2); lcd.printString("Light:", 0, 2); lcd.printString("Mute: ", 0, 3); lcd.printString("Delete Data", 0, 4); lcd.printString("Back", 10, 5); } void PrintSettingsOptions() { ///Prints the active options char buffer[14] = {0}; sprintf(buffer, "%d%%", (int)(100*fsm_brightness[brightness_state].output)); pc.printf("%s\n", buffer); lcd.printString(buffer, 42, 2); if (is_muted) { lcd.printString("On", 42, 3); } else { lcd.printString("Off", 42, 3); } ///Draws the cursor cursor_y_pos = ((fsm_state+2)*8)+3; ///Adds 2 to the fsm state to get the bank, multiplies it by 8 to get the pixel no and offsets it by 3 lcd.drawRect(74, cursor_y_pos, 2, 2, 1); } void EditOption() { if (fsm_state == 0) { ///Change brightness brightness_state = fsm_brightness[brightness_state].next_state; lcd.setBrightness(fsm_brightness[brightness_state].output); ///Clears displayed brightness lcd.drawRect(42, 16, 24, 7, 2); } else if (fsm_state == 1) { ///Mute game is_muted = !is_muted; lcd.drawRect(42, 24, 24, 7, 2); } else if (fsm_state == 2) { ///Delete save data DeleteGameData(); ///Sets FSM list back to 0 and resets the cursor lcd.drawRect(74, cursor_y_pos, 2, 2, 2); fsm_state = 0; } else if (fsm_state == 3) { game_state = menu; } } void DeleteGameData() { ///Delete game state data std::ifstream save_data("/sd/game_save.dat", std::ios::binary); if (save_data.good()) { ///Close the file and remove it save_data.close(); remove("/sd/game_save.dat"); } ///Deletes high score data std::ifstream high_scores_data("/sd/high_scores.dat", std::ios::binary); if (high_scores_data.good()) { ///Closes the file and remove it high_scores_data.close(); remove("/sd/high_scores.dat"); } ///Reloads high score list LoadHighScores(); } void LPWait(float t) { ///Sets the wait flag g_lp_wait_flag = true; ///Attachs the timeout to fire at the users request then sleep lp_wait.attach(&lp_wait_isr, t); while (g_lp_wait_flag) { sleep(); } } void SplashScreen() { ///Prints text and waits for 3 seconds lcd.printString("Space Invaders", 0, 1); lcd.printString("Avinash Patel", 3, 3); lcd.printString("200860407", 15, 4); LPWait(3); } void PlayBuzzer() { if (play_sound) { ///Sets the frequency and activates the buzzer buzzer.period(1/fsm_buzzer[buzzer_state].frequency); buzzer.write(0.5); play_sound = false; } else { ///Deactivates the buzzer and sets the next frequency buzzer.write(0); buzzer_state = fsm_buzzer[buzzer_state].next_state; play_sound = true; } }