diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f92bec --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Ignore NXT executable files, RIC edit data and Gedit backup files. +*.rxe +*.RPA +*~ diff --git a/LICENSE b/LICENSE index e69de29..4869415 100644 --- a/LICENSE +++ b/LICENSE @@ -0,0 +1,25 @@ +A Space Invaders clone for the Mindstorms NXT. + +Copyright (c) 2013, Miguel Angel Astor Romero +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + *) Redistributions of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + *) Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITSOR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f7007b9 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +all: NXTInvaders.nxc + nbc NXTInvaders.nxc -O=NXTInvaders.rxe -sm+ -d -S=usb + +clean: + rm -f NXTInvaders.rxe diff --git a/NXTInvaders.nxc b/NXTInvaders.nxc new file mode 100644 index 0000000..b038c41 --- /dev/null +++ b/NXTInvaders.nxc @@ -0,0 +1,449 @@ +/*------------------------------------------------------------------------------ +; File : NXTInvaders.nxc +; Description : A Space Invaders clone for the Mindstorms NXT. +; Programmed by : Miguel Angel Astor, sonofgrendel@gmail.com +; Version: 1.0 +; Date: 8/13/2013 +;-----------------------------------------------------------------------------*/ + +#download "ship.ric" +#download "splash.ric" +#download "invader.ric" +#download "victory.ric" +#download "defeat.ric" +#define X_MAX 99 +#define Y_MAX 63 +#define X_MID (X_MAX + 1) / 2 +#define Y_MID (Y_MAX + 1) / 2 +#define DRAW_PARAMS DRAW_OPT_NORMAL | DRAW_OPT_LOGICAL_XOR | DRAW_OPT_FILL_SHAPE +#define ALIEN_WIDTH 11 +#define BASE_ALIEN_X 2 +#define BASE_ALIEN_Y 32 +#define ALIENS 6 + +/******************* + * Data structures * + *******************/ +struct PLAYER_T{ + int x; + int lives; + int score; +}; + +struct BULLET_T{ + int x, y; + bool alive; +}; + +struct ALIEN_T{ + int x, y; + bool alive; +}; + +/**************** + * Global data. * + ****************/ +const int alien_update_speed[] = {50, 72, 95, + 118, 140, 163, + 186, 209, 231, + 254, 277, 300 }; +int dead_aliens; +bool victory; +// keys[0] -> Left button; keys[1] -> Right button; keys[2] -> Center button. +bool keys[3]; +// State: 0 -> Splash screen; 1 -> Game; 2 -> Hall of fame screen. +int state, alien_frame, alien_x_offset; +bool alien_x_forward, done; +long then, then2; +PLAYER_T player; +BULLET_T p_bullets[3]; +ALIEN_T aliens1[ALIENS]; +ALIEN_T aliens2[ALIENS]; +BULLET_T a_bullets1[ALIENS]; +BULLET_T a_bullets2[ALIENS]; + +/******************* + * Game functions. * + *******************/ + /** + * Catch the 3 possible player inputs. + */ +void input(){ + if(ButtonPressed(BTNLEFT, false)){ + keys[0] = true; + }else{ + keys[0] = false; + } + + if(ButtonPressed(BTNRIGHT, false)){ + keys[1] = true; + }else{ + keys[1] = false; + } + + if(ButtonPressed(BTNCENTER, false)){ + keys[2] = true; + }else{ + keys[2] = false; + } +} + +/** + * Processes player input, moves game objects and updates the game's state. + */ +void update(){ + long now, delta_t, fire; + + if(state == 0){ + if(keys[2]){ + state = 1; + } + }else if(state == 1){ + // Move the player. + if(keys[0]){ + if(player.x - 1 >= 3){ + player.x -= 1; + } + } + if(keys[1]){ + if(player.x + 1 <= X_MAX - 4){ + player.x += 1; + } + } + + // If the center button was pressed, fire a bullet. + if(keys[2]){ + now = CurrentTick(); + delta_t = now - then; + if(delta_t >= 500){ + // One bullet every 500 miliseconds. + for(int i = 0; i < 3; i++){ + if(!p_bullets[i].alive){ + p_bullets[i].x = player.x; + p_bullets[i].y = 0; + p_bullets[i].alive = true; + PlayTone(262, 100); + break; + } + } + then = now; + } + } + + // Update the player's bullets. + for(int i = 0; i < 3; i++){ + if(p_bullets[i].alive){ + p_bullets[i].y += 1; + if(p_bullets[i].y >= Y_MAX + 2){ + p_bullets[i].alive = false; + } + } + } + + // Update the aliens. + now = CurrentTick(); + delta_t = now - then2; + if(delta_t >= alien_update_speed[11 - dead_aliens]){ + // Change the frame. + if(alien_frame == 0){ + alien_frame = 11; + }else{ + alien_frame = 0; + } + + // Move in the correct direction. + if(alien_x_forward){ + alien_x_offset += 1; + if(alien_x_offset >= 15){ + // Change direction 2 pixels before the edge of the screen. + alien_x_offset -= 1; + alien_x_forward = false; + } + }else{ + alien_x_offset -= 1; + if(alien_x_offset <= 0){ + // Change direction 2 pixels before the edge of the screen. + alien_x_offset += 1; + alien_x_forward = true; + } + } + + // Fire on the player. + for(int i = 0; i < ALIENS; i++){ + // First row. + if(aliens1[i].alive && !a_bullets1[i].alive){ + fire = rand() % 100; + if(fire < 25){ + // 0.25 probability of firing. + a_bullets1[i].alive = true; + a_bullets1[i].x = aliens1[i].x + alien_x_offset + 5; + a_bullets1[i].y = aliens1[i].y; + PlayTone(400, 100); + } + } + // Second row. + if(aliens2[i].alive && !a_bullets2[i].alive){ + fire = rand() % 100; + if(fire > 75){ + // 0.25 probability of firing. + a_bullets2[i].alive = true; + a_bullets2[i].x = aliens2[i].x + alien_x_offset + 5; + a_bullets2[i].y = aliens2[i].y; + PlayTone(400, 100); + } + } + } + then2 = now; + } + + // Update the alien's bullets. + for(int i = 0; i < ALIENS; i++){ + // First row. + if(a_bullets1[i].alive){ + a_bullets1[i].y -= 1; + if(a_bullets1[i].y < -2){ + a_bullets1[i].alive = false; + } + } + // Second row. + if(a_bullets2[i].alive){ + a_bullets2[i].y -= 1; + if(a_bullets2[i].y < -2){ + a_bullets2[i].alive = false; + } + } + } + + // Check if any bullet shot by the player hit an alien. + for(int i = 0; i < 3; i++){ + if(p_bullets[i].alive){ + if(p_bullets[i].y >= 22 && p_bullets[i].y <= 30){ + // Check the second row first. + for(int j = 0; j < ALIENS; j++){ + if(aliens2[j].alive){ + if(p_bullets[i].x >= aliens2[j].x + alien_x_offset && + p_bullets[i].x <= aliens2[j].x + alien_x_offset + 11){ + aliens2[j].alive = false; + p_bullets[i].alive = false; + player.score += 100; + dead_aliens += 1; + PlayTone(100, 250); + break; + } + } + } + }else if(p_bullets[i].y >= 32 && p_bullets[i].y <= 40){ + // Check the first row last. + for(int j = 0; j < ALIENS; j++){ + if(aliens1[j].alive){ + if(p_bullets[i].x >= aliens1[j].x + alien_x_offset && + p_bullets[i].x <= aliens1[j].x + alien_x_offset + 11){ + aliens1[j].alive = false; + p_bullets[i].alive = false; + player.score += 150; + dead_aliens += 1; + PlayTone(100, 250); + break; + } + } + } + } + } + } + + // Check alien bullet collisions with player. + // First row. + for(int i = 0; i < ALIENS; i++){ + if(a_bullets1[i].alive && a_bullets1[i].y <= 2){ + if(a_bullets1[i].x >= player.x - 3 && a_bullets1[i].x <= player.x + 4){ + // If the bullet hit the player, reset the game. + player.x = X_MID; + player.lives -= 1; + alien_x_offset = 0; + alien_x_forward = true; + for(int j = 0; j < ALIENS; j++){ + a_bullets1[i].alive = false; + a_bullets2[i].alive = false; + } + PlayTone(100, 250); + break; + } + } + } + + // Second row + for(int i = 0; i < ALIENS; i++){ + if(a_bullets2[i].alive && a_bullets2[i].y <= 2){ + if(a_bullets2[i].x >= player.x - 3 && a_bullets2[i].x <= player.x + 4){ + // If the bullet hit the player, reset the game. + player.x = X_MID; + player.lives -= 1; + alien_x_offset = 0; + alien_x_forward = true; + for(int j = 0; j < ALIENS; j++){ + a_bullets1[i].alive = false; + a_bullets2[i].alive = false; + } + PlayTone(100, 250); + break; + } + } + } + + // Check victory/defeat conditions. + if(dead_aliens == 12){ + state = 2; + victory = true; + PlayTone(500, 1000); + ClearScreen(); + }else if(player.lives < 0){ + state = 2; + victory = false; + PlayTone(100, 1000); + ClearScreen(); + } + }else{ + if(keys[2]){ + done = true; + } + } +} + +/** + * Draws all game objects to the LCD screen of the robot. + */ +void render(){ + int params[3] = {0, 0, 0}; + if(state == 0){ + // Render the splash screen. + GraphicOut(0, 0, "splash.ric"); + }else if(state == 1){ + ClearScreen(); + // Render informative text. + TextOut(0, LCD_LINE1, "LIVES:"); + //NumOut(35, LCD_LINE1, player.lives); + params[0] = 35; + GraphicOutEx(0, LCD_LINE1 + 1, "ship.ric", params); + if(player.lives > 1){ + params[0] = 44; + GraphicOutEx(0, LCD_LINE1 + 1, "ship.ric", params); + if(player.lives > 2){ + params[0] = 53; + GraphicOutEx(0, LCD_LINE1 + 1, "ship.ric", params); + } + } + TextOut(0, LCD_LINE2, "SCORE:"); + NumOut(35, LCD_LINE2, player.score); + + // Render the player's sprite. + params[0] = player.x - 3; + GraphicOutEx(0, 0, "ship.ric", params); + + // Render the player's bullets. + for(int i = 0; i < 3; i++){ + if(p_bullets[i].alive){ + RectOut(p_bullets[i].x, p_bullets[i].y, 0, 2, DRAW_PARAMS); + } + } + + // Render the aliens. + for(int i = 0; i < ALIENS; i++){ + if(aliens1[i].alive){ + // First row. + params[0] = alien_frame; + params[1] = aliens1[i].x + alien_x_offset; + params[2] = aliens1[i].y; + GraphicOutEx(0, 0, "invader.ric", params); + } + if(aliens2[i].alive){ + // Second row. + params[0] = alien_frame; + params[1] = aliens2[i].x + alien_x_offset; + params[2] = aliens2[i].y; + GraphicOutEx(0, 0, "invader.ric", params); + } + } + + // Render the aliens's bullets. + for(int i = 0; i < ALIENS; i++){ + if(a_bullets1[i].alive){ + RectOut(a_bullets1[i].x, a_bullets1[i].y, 0, 2, DRAW_PARAMS); + } + if(a_bullets2[i].alive){ + RectOut(a_bullets2[i].x, a_bullets2[i].y, 0, 2, DRAW_PARAMS); + } + } + }else{ + // Render the hall of fame. + if(victory){ + params[0] = player.score; + GraphicOutEx(0, 0, "victory.ric", params); + }else{ + GraphicOut(0, 0, "defeat.ric"); + } + } +} + +/** + * The heart of the game. + */ +void game_loop(){ + while(!done){ + input(); + update(); + render(); + Wait(50); + } +} + +/** + * Initializes all game data and call the game loop. + */ +task main(){ + // Initialize game data. + state = 0; + done = false; + alien_frame = 0; + alien_x_offset = 0; + alien_x_forward = true; + + // Initialize the player and bullets. + player.x = X_MID; + player.lives = 3; + player.score = 0; + + for(int i = 0; i < 3; i++){ + p_bullets[i].x = 0; + p_bullets[i].y = 0; + p_bullets[i].alive = false; + } + + // Initialize the aliens. + dead_aliens = 0; + for(int i = 0; i < ALIENS; i++){ + // First row. + aliens1[i].x = BASE_ALIEN_X + (i * 14); + aliens1[i].y = BASE_ALIEN_Y; + aliens1[i].alive = true; + // Second row. + aliens2[i].x = BASE_ALIEN_X + (i * 14); + aliens2[i].y = BASE_ALIEN_Y - 10; + aliens2[i].alive = true; + // Alien bullets, first row. + a_bullets1[i].x = 0; + a_bullets1[i].y = 0; + a_bullets1[i].alive = false; + // Second row. + a_bullets2[i].x = 0; + a_bullets2[i].y = 0; + a_bullets2[i].alive = false; + } + + // Initialize time variables. + then = CurrentTick(); + then2 = CurrentTick(); + + // Start the game! + PlayTone(500, 1000); + game_loop(); +} diff --git a/README.md b/README.md index 116ed91..5e69df0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ NXTInvaders =========== -An Space Invaders clone for the Lego Mindstorms NXT +A Space Invaders clone for the Lego Mindstorms NXT. + +This game is written in the NXC language. To compile on Windows use the BricxCC +IDE avaliable at http://bricxcc.sourceforge.net/nbc/. On Linux systems install +the NBC compiler available on the same page and run make. diff --git a/defeat.ric b/defeat.ric new file mode 100644 index 0000000..d4db90c Binary files /dev/null and b/defeat.ric differ diff --git a/invader.ric b/invader.ric new file mode 100644 index 0000000..fbf9e5f Binary files /dev/null and b/invader.ric differ diff --git a/ship.ric b/ship.ric new file mode 100644 index 0000000..44b42c8 Binary files /dev/null and b/ship.ric differ diff --git a/splash.ric b/splash.ric new file mode 100644 index 0000000..38c4ae3 Binary files /dev/null and b/splash.ric differ diff --git a/victory.ric b/victory.ric new file mode 100644 index 0000000..c4bb2f9 Binary files /dev/null and b/victory.ric differ