Missile Command

Advice on general approaches or feasibility and discussions about game design

Missile Command

Postby DFX2KX » Sat Sep 13, 2014 1:25 pm

So, I've been working on making a missile command game the last few weeks or so.

Got missiles moving (though not correctly) and figured out how to draw more then one on the screen. Cities draw based on health, and automatically space themselves evenly (though it'll be capped at five after i figure out what's wrong with the enemy missiles).

missiles are all saved in an array (to be shared between enemy and ally missiles, currently) As are cities, and (some century) explosions to stop the enemy missiles.

Here's the main body of the code.

Code: Select all
//imports the SPI library (needed to communicate with Gamebuino's screen)
#include <SPI.h>
//imports the Gamebuino library
#include <Gamebuino.h>
#include <stdlib.h>
//imports the header to make this blasted game work.
#include "MISCMD.h"
//creates a Gamebuino object named gb
Gamebuino gb;




// drawing functions
void drawTarget(int x,int y){
  gb.display.drawFastHLine(x-5, y,4);
  gb.display.drawFastHLine(x+1, y,4);
  gb.display.drawFastVLine(x,y-5,4);
  gb.display.drawFastVLine(x,y+1,4);
};

// missile controllers
boolean cpuFireRandomMissile(){
 
  int missileID; // the index of the chosen missile in the array, used to make sure there's one avaible
  while (missileID <= MISSILEARRAYSIZE - 1){
    if (missileArray[missileID].active == true){
      // missileid is already taken
      missileID += 1;
    }else{
      // missileID isn't taken, set new values, set missile active, select random target
      missileArray[missileID].active = true;
      missileArray[missileID].start_x = random(LCDWIDTH); // random starting point, sans the very edges
      missileArray[missileID].start_y = 0.0;
      missileArray[missileID].target_y = LCDHEIGHT - 8;
      missileArray[missileID].target_x = random(LCDWIDTH); // random target point, sans the very edges
      missileArray[missileID].rel_frame = 0;
      missileArray[missileID].num_frames = 10; /*(float) abs(sqrt(sq(missileArray[missileID].target_x - missileArray[missileID].start_x) + sq(missileArray[missileID].target_y - missileArray[missileID].start_y))) / 5; */ // number of frames of travel, based on speed
      missileArray[missileID].player = false; // this is a CPU missile, after all
      missileArray[missileID].velocity_x = (float)(missileArray[missileID].target_x - missileArray[missileID].start_x) / missileArray[missileID].num_frames;
      missileArray[missileID].velocity_y = (float)(missileArray[missileID].target_y - missileArray[missileID].start_y) / missileArray[missileID].num_frames;
      // plug into the init function, most of these values are simply reset, but this calculates the velocity of x and y
      // init_missile(missileArray[missileID], missileArray[missileID].start_x, missileArray[missileID].start_y, missileArray[missileID].target_x, missileArray[missileID].target_y, missileArray[missileID].num_frames);
      return true;
      break;
    };
  };
  return false;
};

void missileStep(){
  // calculates the motion of all currently active missiles, calculate collisions or detonations (for player-fired missiles)
  int missileID = 0;
  while (missileID <= (MISSILEARRAYSIZE - 1)){
    // scan and update all missile ID positions, if active.
    missileArray[missileID].current_x += missileArray[missileID].velocity_x;
    missileArray[missileID].current_y += missileArray[missileID].velocity_y;
    missileID += 1;
  };
};

// Drawing functinos.
void drawCity(int citycount){
  // Draws the cities. takes the number of cities currently being used
  // pick sprite based on percentage of city health
  int cityx;
  for (int curCity = 0; curCity <= citycount - 1; curCity++){
    // sprite location is based on number of cities
    cityx = (int) (LCDWIDTH/(citycount + 1)) * (curCity + 1); // determine spacing for the cities to draw
    switch (cityArray[curCity].hp){
      case 2:
      gb.display.drawBitmap(cityx - 4, LCDHEIGHT - 16, sprCityFull); // Two hits left, full health
      break;
      case 1:
      gb.display.drawBitmap(cityx - 4, LCDHEIGHT - 16, sprCityHalf); // One hit left, half health
      break;
      case 0:
      gb.display.drawBitmap(cityx - 4, LCDHEIGHT - 16, sprCityDestroyed); // And these residents will need to move.
      break;
      default:
      gb.display.drawBitmap(cityx - 4, LCDHEIGHT - 16, sprCityFull); // Two hits left, full health
      break;
    };
  };
  gb.display.drawFastHLine(0,LCDHEIGHT - 8, LCDWIDTH);
};

void drawMissiles(){
  // draws all active missiles
  int curMissile;
  while (curMissile <= (MISSILEARRAYSIZE - 1)){
    if (missileArray[curMissile].active == true){
      // missile is active, draw the missile!
      gb.display.drawLine(missileArray[curMissile].start_x, missileArray[curMissile].start_y, missileArray[curMissile].current_x, missileArray[curMissile].current_y);
      //gb.display.drawPixel((int) missileArray[curMissile].current_x, (int) missileArray[curMissile].current_y);
    };
    curMissile += 1;
  };
};
// the setup routine runs once when Gamebuino starts up
void setup(){
  // initialize the Gamebuino object
  gb.begin();
  //display the main menu:
  gb.titleScreen(F("Missle Command"));
  gb.setFrameRate(1);
  gb.pickRandomSeed();
  // initialize memory space
   
}

// the loop routine runs over and over again forever
void loop(){
  //updates the gamebuino (the display, the sound, the auto backlight... everything)
  //returns true when it's time to render a new frame (20 times/second)
  if(gb.update()){
      while (cpuFireRandomMissile() == false){};
    //if (gb.frameCount % 5 == 1){
    missileStep();
   // };
    drawMissiles();
    drawCity(3);
    gb.display.cursorX = 0;
    gb.display.cursorY = LCDHEIGHT - 7;
    gb.display.setFont(font3x5);
    gb.display.print(missileArray[1].start_x);
    gb.display.print(" ");
    gb.display.print(missileArray[1].start_y);
    gb.display.print(" ");
    gb.display.print(missileArray[1].target_x);
    gb.display.print(" ");
    gb.display.print(missileArray[1].target_y);
    gb.display.print(" ");
    gb.display.print(missileArray[1].current_x);
    gb.display.print(" ");
    gb.display.print(missileArray[1].current_y);
    gb.display.print(" ");
    gb.display.print(missileArray[1].active);
  };
};


It has test values where the number of missiles left in each city should go, but alas.

Here's the header file, where the bitmaps and Myndale's original functions are stored.

Code: Select all
// header file for the game, which saves all of the sprites, data structures, and other things needed for the game to work.

//City Sprites
static unsigned char PROGMEM sprCityFull[] =
{
8,8,
B00000000,
B00000000,
B00001000,
B00011000,
B01011100,
B01111110,
B11111111,
B11111111,
};

static unsigned char PROGMEM sprCityHalf[] =
{
8,8,
B00000000,
B00010100,
B00101010,
B00010100,
B00101100,
B01111110,
B11111111,
B11111111,
};

static unsigned char PROGMEM sprCityDestroyed[] =
{
8,8,
B00000000,
B00010100,
B00101010,
B00010100,
B00101001,
B01010110,
B01101010,
B11111111,
};

// struct for cities;
typedef struct {
  // hit points of the city.
  int hp;
  // how many missiles the city has left
  int mis;
} city;

// structure for missiles

typedef struct {
   int start_x, start_y; // The starting positions of the missile
   int target_x, target_y; // The Target of the missile
   float current_x, current_y; // current position of the missile
   float velocity_x, velocity_y; // horizonatal and vertical speed of the missile, used for calculating current x and y
   int num_frames; // The number of frames to move the missile in, calculated from linear distance and speed.
   int rel_frame; //relitive frame since missile fired
   boolean player; // is this a player missile?
   boolean active; // is this missile active?
} missile;

// structure for a 'level'

typedef struct{
  int citycount; // how many cities
  int mismax; // how many missiles in each city 0 is unlimited
  int emiscount; // how many missiles will the CPU try and fire?
  int emisvolleycount; // how many missiles AT ONCE will the CPU try and fire.
  int volleytime; // average time in frames between missile volleys
} mission;
// constants

const int MISSILEARRAYSIZE = 3;
const int CITYARRAYSIZE = 5;
// array for the cities
static city cityArray[CITYARRAYSIZE];

// array of all the missiles
static missile missileArray[MISSILEARRAYSIZE];

// motion functions

void init_missile(missile & mis, int start_x, int start_y, int target_x, int target_y, int num_frames)
{
   mis.start_x = mis.current_x = start_x;
   mis.start_y = mis.current_y = start_y;
   mis.target_x = target_x;
   mis.target_y = target_y;
   mis.velocity_x = (float)(target_x - start_x) / num_frames;
   mis.velocity_y = (float)(target_y - start_y) / num_frames;
   mis.num_frames = num_frames;
};

void update_missile(missile & mis)
{
   mis.current_x += mis.velocity_x;
   mis.current_y += mis.velocity_y;
};

void resetMissile(int id){
      missileArray[id].active = false;
      missileArray[id].start_x = 0;
      missileArray[id].start_y = 0;
      missileArray[id].target_y = 0;
      missileArray[id].target_x = 0;
      missileArray[id].rel_frame = 0;
      missileArray[id].num_frames = 0; // number of frames of travel, based on speed
      missileArray[id].player = true; // this is a CPU missile, after all
      missileArray[id].velocity_x = 0.0;
      missileArray[id].velocity_y = 0.0;
};

// Drawing functions

extern const byte font3x5[];


I'd stick up A HEX and ELF file, but it crashes after three (slowed down for testing) frames.

I'm completing this out of dogged determination at this point. XD I think I'll be happy to never play/touch it again once it works.
DFX2KX
 
Posts: 250
Joined: Mon Apr 14, 2014 3:48 am

Re: Missile Command

Postby DFX2KX » Sat Sep 13, 2014 2:46 pm

It seems like I'll have to rewrite all of this from scratch. Trying to use the 'active' variable as a test condition whether or not we want to update the position fails, either not triggering any other tests (the missile 's current x and y keep going forever), or it seems to erase the target and starting x,y values. I think I'm going to have to make these functions isolated completely from the array storing the values, and hope that fixes it.

The sample code Myndale gave me seems to work by itself, it's something wrong with the implementation I'm doing. and I have no idea why.
DFX2KX
 
Posts: 250
Joined: Mon Apr 14, 2014 3:48 am

Re: Missile Command

Postby Myndale » Sat Sep 13, 2014 11:15 pm

Your code is very close to working, just a few minor things that need fixing...

First of all, variables in C have to be initialized. Failing to initialize local variables means they'll wind up with whatever value happens to be on the stack, which could be anything. The first instance of an uninitialized variable is in the drawMissiles function:

Code: Select all
void drawMissiles(){
  // draws all active missiles
  int curMissile;                                                         <--------- needs to be "int curMissile = 0;"
  while (curMissile <= (MISSILEARRAYSIZE - 1)){


The second is in the cpuFireRandomMissile function:

Code: Select all
boolean cpuFireRandomMissile(){
 
  int missileID; // the index of the chosen missile in the array, used to make sure there's one avaible
  while (missileID <= MISSILEARRAYSIZE - 1){


The next problem is with your use of current_x and current_y, your update code is fine:

Code: Select all
    missileArray[missileID].current_x += missileArray[missileID].velocity_x;
    missileArray[missileID].current_y += missileArray[missileID].velocity_y;


Problem is these variables aren't being initialized anywhere. They need to be set to the start value in the cpuFireRandomMissile function:

Code: Select all
      missileArray[missileID].current_x = missileArray[missileID].start_x;
      missileArray[missileID].current_y = missileArray[missileID].start_y;


Finally there's a logic error in the main loop:

Code: Select all
while (cpuFireRandomMissile() == false){};


The cpuFireRandomMissile function returns false when the missile table is full, so once you've fired all 3 of your missiles this line causes to the CPU to sit in a never-ending loop waiting for a slot to become available. Call cpuFireRandomMissile() without the while loop and the code will stop locking up.
Myndale
 
Posts: 507
Joined: Sat Mar 01, 2014 1:25 am

Re: Missile Command

Postby DFX2KX » Sun Sep 14, 2014 9:04 am

Well, That seems to have worked!, I've gotten to the point where the missiles at least go where they're supposed to! :D
YAY!.jpg
YAY!.jpg (23.34 KiB) Viewed 4502 times

Whoo!, now to calculate the number of frames the missiles will take to get to their destinations (which I have the algorithm for already), and get collisions and explosions done. The hard part might very well be done.
DFX2KX
 
Posts: 250
Joined: Mon Apr 14, 2014 3:48 am

Re: Missile Command

Postby DFX2KX » Sun Sep 14, 2014 3:35 pm

So, after having tons of lock-ups involving the explosions, and a if statement that I believe firmly is still cursed, I got things to behave slightly for a bit. missiles fired by the CPU are 'destroyed' properly (most of the time). Your missiles change into explosions most of the time (sometimes it crashes the game XD), but the basics are finally doing things I'd expect Missile Command to do.
playing the game!.jpg
Heyyyy, it looks like it worked for a few moments! :D
playing the game!.jpg (17 KiB) Viewed 4490 times


And after tweaking things that had nothing to do with the code actually *working* (the multiplier that slows down the missiles), I found myself PLAYING it for a few minutes instead of cursing at it.
Missile Command has Arrived.jpg
The score is negative... Dunno why! I'll have to look at it when I get the rest of the functions working more reliably.
Missile Command has Arrived.jpg (16.84 KiB) Viewed 4490 times


I was was doing some coding practice, and suddenly a game happened. :O

(Edit, the souce code for anyone who wants to play with it)
MISCMD.INO
Code: Select all
//imports the SPI library (needed to communicate with Gamebuino's screen)
#include <SPI.h>
//imports the Gamebuino library
#include <Gamebuino.h>
#include <stdlib.h>
//imports the header to make this blasted game work.
#include "MISCMD.h"
//creates a Gamebuino object named gb
Gamebuino gb;




// drawing functions
void drawTarget(int x,int y){
  gb.display.drawFastHLine(x-5, y,4);
  gb.display.drawFastHLine(x+1, y,4);
  gb.display.drawFastVLine(x,y-5,4);
  gb.display.drawFastVLine(x,y+1,4);
};

// missile controllers
boolean cpuFireRandomMissile(){
 
  int missileID = 0; // the index of the chosen missile in the array, used to make sure there's one avaible
  while (missileID <= MISSILEARRAYSIZE - 1){
    if (missileArray[missileID].active == true){
      // missileid is already taken
      missileID += 1;
    }else{
      // missileID isn't taken, set new values, set missile active, select random target
      missileArray[missileID].active = true;
      missileArray[missileID].start_x = random(LCDWIDTH); // random starting point, sans the very edges
      missileArray[missileID].start_y = 0.0;
      missileArray[missileID].current_x = missileArray[missileID].start_x;
      missileArray[missileID].current_y = missileArray[missileID].start_y;
      missileArray[missileID].target_y = LCDHEIGHT - 8;
      missileArray[missileID].target_x = random(LCDWIDTH); // random target point, sans the very edges
      missileArray[missileID].rel_frame = 0;
      missileArray[missileID].num_frames = (float) 5 * abs(sqrt(sq(missileArray[missileID].target_x - missileArray[missileID].start_x) + sq(missileArray[missileID].target_y - missileArray[missileID].start_y))); // number of frames of travel, based on speed
      missileArray[missileID].player = false; // this is a CPU missile, after all
      missileArray[missileID].velocity_x = (float)(missileArray[missileID].target_x - missileArray[missileID].start_x) / missileArray[missileID].num_frames;
      missileArray[missileID].velocity_y = (float)(missileArray[missileID].target_y - missileArray[missileID].start_y) / missileArray[missileID].num_frames;
      // plug into the init function, most of these values are simply reset, but this calculates the velocity of x and y
      // init_missile(missileArray[missileID], missileArray[missileID].start_x, missileArray[missileID].start_y, missileArray[missileID].target_x, missileArray[missileID].target_y, missileArray[missileID].num_frames);
      return true;
      break;
    };
  };
  return false;
};
boolean playerFireMissile(int tX,int tY){
  // Fire a missile at the given target will eventually take closest city into accunt
  int sX = LCDWIDTH / 2;
  int sY = LCDHEIGHT - 8;
  int missileID = 0; // the index of the chosen missile in the array, used to make sure there's one avaible
  while (missileID <= MISSILEARRAYSIZE - 1){
    if (missileArray[missileID].active == true){
      // missileid is already taken
      missileID += 1;
    }else{
      // missileID isn't taken, set new values, set missile active, select random target
      missileArray[missileID].active = true;
      missileArray[missileID].start_x = sX;
      missileArray[missileID].start_y = sY;
      missileArray[missileID].current_x = missileArray[missileID].start_x;
      missileArray[missileID].current_y = missileArray[missileID].start_y;
      missileArray[missileID].target_y = tY;
      missileArray[missileID].target_x = tX;
      missileArray[missileID].rel_frame = 0;
      missileArray[missileID].num_frames = (float) 1.5 * abs(sqrt(sq(missileArray[missileID].target_x - missileArray[missileID].start_x) + sq(missileArray[missileID].target_y - missileArray[missileID].start_y))); // number of frames of travel, based on speed
      missileArray[missileID].player = true; // this is a player missile, after all
      missileArray[missileID].velocity_x = (float)(missileArray[missileID].target_x - missileArray[missileID].start_x) / missileArray[missileID].num_frames;
      missileArray[missileID].velocity_y = (float)(missileArray[missileID].target_y - missileArray[missileID].start_y) / missileArray[missileID].num_frames;
      // plug into the init function, most of these values are simply reset, but this calculates the velocity of x and y
      // init_missile(missileArray[missileID], missileArray[missileID].start_x, missileArray[missileID].start_y, missileArray[missileID].target_x, missileArray[missileID].target_y, missileArray[missileID].num_frames);
      return true;
      break;
    };
  };
};

void createExploionAtPos(int x, int y, int ID){
  // finds a subtible ID to save an explosion in, and sets it there.
  int curExpl = 0;
  while (curExpl <= EXPLOSIONARRAYSIZE - 1){
    // seach all explosion IDs
    if (missileArray[ID].active != true || missileArray[ID].player != true){
      // bail if something odd happend, hence the checking of the missile that set this one off.
      break;
      curExpl = 100;
    };
      if (explosionArray[curExpl].active == false){
      // this ID is open, so use this.
      explosionArray[curExpl].active = true;
      explosionArray[curExpl].x = x;
      explosionArray[curExpl].y = y;
      explosionArray[curExpl].radius = 1;
      explosionArray[curExpl].framecount = 0;
      explosionArray[curExpl].downcount = 0;
      curExpl +=1;
    };
  };
};

void missileStep(){
  // calculates the motion of all currently active missiles, calculate collisions or detonations (for player-fired missiles)
  int missileID = 0;
  while (missileID <= (MISSILEARRAYSIZE - 1)){
    // scan and update all missile ID positions, if active.
    if (missileArray[missileID].active == true){
      // move active missiles only
      missileArray[missileID].current_x += missileArray[missileID].velocity_x;
      missileArray[missileID].current_y += missileArray[missileID].velocity_y;
    };
    if (missileArray[missileID].current_x >= LCDWIDTH || missileArray[missileID].current_x < 0){
      // off the left/right of the screen
      resetMissile(missileID);
    };
    if (missileArray[missileID].player == false){
      //CPU missiles
      if (missileArray[missileID].target_y <= missileArray[missileID].current_y){
        // missile hit the ground, city collsion goes here
        resetMissile(missileID);
      };
    }else{
      // player missiles
      if (missileArray[missileID].current_y < 0){
        // above screen
        resetMissile(missileID);
      }else{
        // it didn't fly off into space, let's see if it reached close to it's target
        int xdif = missileArray[missileID].current_x - missileArray[missileID].target_x;
        int ydif = missileArray[missileID].current_y - missileArray[missileID].target_y;
        xdif = abs(xdif);
        ydif = abs(ydif);
        if (xdif <= 2 && ydif <=2){
          // missile has reached target, boom time!
          if (missileArray[missileID].player == true){
            createExploionAtPos(missileArray[missileID].current_x, missileArray[missileID].current_y, missileID);
            resetMissile(missileID);
          };
        };
      };
    };
    missileID += 1;
  };
};

void explosionStep(){
  // update all of the explosions, clearing IDS of expired blasts, and removing any AI missiles they destroy
  int curExpl = 0;
  int curMis = 0;
  while (curExpl <= EXPLOSIONARRAYSIZE - 1){
    if (explosionArray[curExpl].active == true){
      // only check active explosions.
      curMis = 0;
      while (curMis <= MISSILEARRAYSIZE - 1){
        if (missileArray[curMis].player == false && missileArray[curMis].active == true){
          // missille is AI/CPU and active. If it's within range, destroy it, and count up one on the explosion's 'missiles shot down counter'
          int xrad = missileArray[curMis].current_x - explosionArray[curExpl].x;
          xrad = abs(xrad);
          int yrad = missileArray[curMis].current_x - explosionArray[curExpl].x;
          yrad = abs(xrad);
          if (xrad <= explosionArray[curExpl].radius && yrad <= explosionArray[curExpl].radius){
            // in blast range, destroy the missile.
            resetMissile(curMis);
            explosionArray[curExpl].downcount += 1;
          };
        };
        curMis +=1; // scan the next missile
      };
      if (explosionArray[curExpl].framecount % 45 == 1){
        explosionArray[curExpl].radius +=1; // expand the radius by one every 15 frames
      };
      if (explosionArray[curExpl].radius >= 3){
        // explosion has reached max radius, destroy the explosion tally the score gained in here.
        playerScore += (10 * explosionArray[curExpl].downcount) + ( 5 * (explosionArray[curExpl].downcount - 1));
        explosionArray[curExpl].active = false;
        explosionArray[curExpl].x = 0;
        explosionArray[curExpl].y = 0;
        explosionArray[curExpl].downcount = 0;
        explosionArray[curExpl].radius = 0;
        explosionArray[curExpl].framecount = 0;
      };
      explosionArray[curExpl].framecount +=1; //toggle this explosion's framecount
     
    };
    curExpl += 1;
  };
};
// Drawing functinos.
void drawCity(int citycount){
  // Draws the cities. takes the number of cities currently being used
  // pick sprite based on percentage of city health
  int cityx = 0;
  for (int curCity = 0; curCity <= citycount - 1; curCity++){
    // sprite location is based on number of cities
    cityx = (int) (LCDWIDTH/(citycount + 1)) * (curCity + 1); // determine spacing for the cities to draw
    switch (cityArray[curCity].hp){
      case 2:
      gb.display.drawBitmap(cityx - 4, LCDHEIGHT - 16, sprCityFull); // Two hits left, full health
      break;
      case 1:
      gb.display.drawBitmap(cityx - 4, LCDHEIGHT - 16, sprCityHalf); // One hit left, half health
      break;
      case 0:
      gb.display.drawBitmap(cityx - 4, LCDHEIGHT - 16, sprCityDestroyed); // And these residents will need to move.
      break;
      default:
      gb.display.drawBitmap(cityx - 4, LCDHEIGHT - 16, sprCityFull); // Two hits left, full health
      break;
    };
  };
  gb.display.drawFastHLine(0,LCDHEIGHT - 8, LCDWIDTH);
};

void drawMissiles(){
  // draws all active missiles
  int curMissile = 0;
  while (curMissile <= (MISSILEARRAYSIZE - 1)){
    if (missileArray[curMissile].active == true){
      // missile is active, draw the missile!
      gb.display.drawLine(missileArray[curMissile].start_x, missileArray[curMissile].start_y, missileArray[curMissile].current_x, missileArray[curMissile].current_y);
      //gb.display.drawPixel((int) missileArray[curMissile].current_x, (int) missileArray[curMissile].current_y);
      //drawTarget(missileArray[curMissile].target_x,missileArray[curMissile].target_y);
    };
    curMissile += 1;
  };
};

void drawExplosions(){
  // draws all active missiles
  int curExpl = 0;
  while (curExpl <= (EXPLOSIONARRAYSIZE - 1)){
    if (explosionArray[curExpl].active == true){
      gb.display.fillCircle(explosionArray[curExpl].x, explosionArray[curExpl].y, explosionArray[curExpl].radius);     
    };
    curExpl += 1;
  };
};

void playerControlTarget(){
  // checks for key input, and moves the crosshair target appropriately.
  if (gb.buttons.repeat(BTN_LEFT,1)){
    //move target left to the edge of the screen
    playerTargetX -= 1;
  };
 
  if (gb.buttons.repeat(BTN_RIGHT,1)){
    //move target left to the edge of the screen
    playerTargetX += 1;
  };
 
  if (gb.buttons.repeat(BTN_UP,1)){
    //move target left to the edge of the screen
    playerTargetY -= 1;
  };
 
  if (gb.buttons.repeat(BTN_DOWN,1)){
    //move target left to the edge of the screen
    playerTargetY += 1;
  };
 
  // constrain the values, why didn't I know about this for the tank game!?
  playerTargetY = constrain(playerTargetY,0,LCDHEIGHT - 8);
  playerTargetX = constrain(playerTargetX,0,LCDWIDTH);
};

void missileInitState(){
  // calculates the motion of all currently active missiles, calculate collisions or detonations (for player-fired missiles)
  int missileID = 0;
  while (missileID <= (MISSILEARRAYSIZE - 1)){
    resetMissile(missileID);
    missileID += 1;
  };
};

void explosionInitState(){
  // calculates the motion of all currently active missiles, calculate collisions or detonations (for player-fired missiles)
  int explosionID = 0;
  while (explosionID <= (EXPLOSIONARRAYSIZE - 1)){
    explosionArray[explosionID].x = 0;
    explosionArray[explosionID].y = 0;
    explosionArray[explosionID].active = false;
    explosionArray[explosionID].downcount = 0;
    explosionArray[explosionID].framecount = 0;
    explosionArray[explosionID].radius = 0;
    explosionID += 1;
  };
};

// the setup routine runs once when Gamebuino starts up
void setup(){
  // initialize the Gamebuino object
  gb.begin();
  //display the main menu:
  gb.titleScreen(F("Missle Command"));
  gb.setFrameRate(20);
  gb.pickRandomSeed();
  explosionInitState();
  missileInitState();
  framestowait = random(40,120);
};

// the loop routine runs over and over again forever
void loop(){
  //updates the gamebuino (the display, the sound, the auto backlight... everything)
  //returns true when it's time to render a new frame (20 times/second)
  if(gb.update()){
    // test game loop
    playerControlTarget();
    if (gb.buttons.pressed(BTN_A)){
      // test firing of player missiles
      playerFireMissile(playerTargetX,playerTargetY);
    };
    // testing some basic AI logic, so it's not like someone's loaned every Kaytusha launcher on the planet to pester the player with.
    if (framestowait == 0){
      // done waiting, now send some missiles the player's way
      thisVolley = random(1,5);
      int i=0;
      while (i <= thisVolley){
        // fire off random missiles
        cpuFireRandomMissile();
        i +=1;
      };
      framestowait = random(40,120);
    };
    framestowait -=1; // countdown to next wave
    missileStep();
    explosionStep();
    drawMissiles();
    drawExplosions();
    drawCity(3);
    drawTarget(playerTargetX,playerTargetY);
   gb.display.cursorX = 0;
    gb.display.cursorY = LCDHEIGHT - 7;
    gb.display.setFont(font3x5);
    //gb.display.print(framestowait);
    gb.display.print("Score: ");
    gb.display.print(playerScore);
    /*gb.display.print(" ");
    gb.display.print(missileArray[0].target_x);
    gb.display.print(" ");
    gb.display.print(missileArray[0].target_y);
    gb.display.print(" ");
    gb.display.print(missileArray[0].current_x);
    gb.display.print(" ");
    gb.display.print(missileArray[0].current_y);
    gb.display.print(" ");
    gb.display.print(missileArray[0].active); */
  };
};

MISCMD.h
Code: Select all
// header file for the game, which saves all of the sprites, data structures, and other things needed for the game to work.

//City Sprites
static unsigned char PROGMEM sprCityFull[] =
{
8,8,
B00000000,
B00000000,
B00001000,
B00011000,
B01011100,
B01111110,
B11111111,
B11111111,
};

static unsigned char PROGMEM sprCityHalf[] =
{
8,8,
B00000000,
B00010100,
B00101010,
B00010100,
B00101100,
B01111110,
B11111111,
B11111111,
};

static unsigned char PROGMEM sprCityDestroyed[] =
{
8,8,
B00000000,
B00010100,
B00101010,
B00010100,
B00101001,
B01010110,
B01101010,
B11111111,
};

// struct for cities;
typedef struct {
  // hit points of the city.
  int hp;
  // how many missiles the city has left
  int mis;
} city;

// structure for missiles

typedef struct {
   int start_x, start_y; // The starting positions of the missile
   int target_x, target_y; // The Target of the missile
   float current_x, current_y; // current position of the missile
   float velocity_x, velocity_y; // horizonatal and vertical speed of the missile, used for calculating current x and y
   int num_frames; // The number of frames to move the missile in, calculated from linear distance and speed.
   int rel_frame; //relitive frame since missile fired
   boolean player; // is this a player missile?
   boolean active; // is this missile active?
} missile;

// structure for explosions
typedef struct {
  int active; // actively exploding or not
  int x, y; // center of blast
  int radius; // blast radius
  int framecount; // number of frames the explosion has been alive
  int downcount; // number of missiles downed, for soring
} explosion;

// structure for a 'level'

typedef struct{
  int citycount; // how many cities
  int mismax; // how many missiles in each city 0 is unlimited
  int emiscount; // how many missiles will the CPU try and fire?
  int emisvolleycount; // how many missiles AT ONCE will the CPU try and fire.
  int volleytime; // average time in frames between missile volleys
} mission;

// constants

const int MISSILEARRAYSIZE = 16;
const int CITYARRAYSIZE = 5;
const int EXPLOSIONARRAYSIZE = 16; // unlike a Micheal Bay Movie, the Gamebuino can only support so many of these.
// array for the cities
static city cityArray[CITYARRAYSIZE];

// array of all the missiles
static missile missileArray[MISSILEARRAYSIZE];

// array of explosions

static explosion explosionArray[EXPLOSIONARRAYSIZE];

//Static Score
static int playerScore = 0;
static int playerTargetX = LCDWIDTH/2;
static int playerTargetY = LCDHEIGHT/2;
static int thisVolley = 0;
static int framestowait = 0;
// motion functions

void init_missile(missile & mis, int start_x, int start_y, int target_x, int target_y, int num_frames)
{
   mis.start_x = mis.current_x = start_x;
   mis.start_y = mis.current_y = start_y;
   mis.target_x = target_x;
   mis.target_y = target_y;
   mis.velocity_x = (float)(target_x - start_x) / num_frames;
   mis.velocity_y = (float)(target_y - start_y) / num_frames;
   mis.num_frames = num_frames;
};

void update_missile(missile & mis)
{
   mis.current_x += mis.velocity_x;
   mis.current_y += mis.velocity_y;
};

void resetMissile(int id){
      missileArray[id].active = false;
      missileArray[id].start_x = 0;
      missileArray[id].start_y = 0;
      missileArray[id].target_y = 0;
      missileArray[id].target_x = 0;
      missileArray[id].rel_frame = 0;
      missileArray[id].num_frames = 0; // number of frames of travel, based on speed
      missileArray[id].player = true; // this is a CPU missile, after all
      missileArray[id].velocity_x = 0.0;
      missileArray[id].velocity_y = 0.0;
};

// Drawing functions

extern const byte font3x5[];


whoot! I'll be in a good mood all day, now.
DFX2KX
 
Posts: 250
Joined: Mon Apr 14, 2014 3:48 am

Re: Missile Command

Postby yodasvideoarcade » Sun Sep 14, 2014 10:14 pm

Looks nice!

In case you're interested to make it more like the original:

- Computer missiles ALWAYS aim directly at a city or weapon-storage of the player. They NEVER go in between. Most important thing: You have to destroy all computer missiles, because each one will hit a city.

- Every city can be hit only once and is destroyed then, no multiple hits of cities.

- In the original, there's six cities and three weapon-storages (left, middle, right). You can fire them by using three different buttons.
For the gamebuino-screen, I'd recommend two weapon-storages (left and right), that can be fired using the A and B buttons. Then you can put 4 cities between them.

- Original cities are much more flat, so they don't take up too much vertical space.

- Ammo is limited and shown in the storages. Storages can also be hit, then the ammo is gone.

Looking forward to play the finished game!
User avatar
yodasvideoarcade
 
Posts: 102
Joined: Sat Apr 19, 2014 10:48 am
Location: Frankfurt/Germany

Re: Missile Command

Postby DFX2KX » Sun Sep 14, 2014 10:47 pm

yodasvideoarcade wrote:Looks nice!

In case you're interested to make it more like the original:

- Computer missiles ALWAYS aim directly at a city or weapon-storage of the player. They NEVER go in between. Most important thing: You have to destroy all computer missiles, because each one will hit a city.

- Every city can be hit only once and is destroyed then, no multiple hits of cities.

- In the original, there's six cities and three weapon-storages (left, middle, right). You can fire them by using three different buttons.
For the gamebuino-screen, I'd recommend two weapon-storages (left and right), that can be fired using the A and B buttons. Then you can put 4 cities between them.

- Original cities are much more flat, so they don't take up too much vertical space.

- Ammo is limited and shown in the storages. Storages can also be hit, then the ammo is gone.

Looking forward to play the finished game!


I know I want to limit ammo, not sure how I'd lay it out cities-wise. the version I played had varying numbers of cities, which this version is set up to do for flavor's sake. the multiple hits for the cities are mainly for a difficulty drop. I can make a 'hard' mode that doesn't give you the extra hit.

I'm not aiming for a perfect clone, but since I did the city drawing routine for practice, and got Myndale's help with predicted motion, I might as well make a game out of it (also huh, I remember the version I played missing cities now and again. Think it was the 2600 remake on the sega genisis. Had a whole bunch of games on there)

Thanks for the info on the Original!
DFX2KX
 
Posts: 250
Joined: Mon Apr 14, 2014 3:48 am


Return to Project Guidance & Game development

Who is online

Users browsing this forum: No registered users and 6 guests