// File: lunar.cxx // Written by Michael Main, Sep 15, 2005 // This is a demonstration game. The user tries to land a lunar lander. #include // Provides bgi graphics #include // Provides M_PI #include // Provides cout using namespace std; /////////////////////////////////////////////////////////////////////////////// // Physical constants const double FORCE_MAIN_THRUSTER = 30000; // kg m/s^2 const double FORCE_SIDE_THRUSTER = 100; // kg rad/s^2 const double GRAVITY = -9.8/6; // m/s^2 const double LANDER_MASS = 1000; // kg const double STARTING_FUEL = 250; // kg const double MAIN_FUEL_BURN_RATE = 1; // kg/sec const double SIDE_FUEL_BURN_RATE = 0.01; // kg/sec const int DELAY = 100; // ms const double DELTA_T = DELAY/1000.0; // s // Graphical constants const double IMAGE_SIZE = 0.65; const int WINDOW_WIDTH = static_cast(851*IMAGE_SIZE); const int WINDOW_HEIGHT = static_cast(608*IMAGE_SIZE); const int GROUND_ZERO_Y = static_cast(566*IMAGE_SIZE); const int ROCKET_SIZE = static_cast(100*IMAGE_SIZE); const double PIXELS_PER_METER = 10; const int BACKGROUND_BYTES = 2069656; const int ROCKET_BYTES = 68896; const int IMAGES = 100; /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // Set or modify the variables that keep track of the state of the lander void update_accelerations( double& ax, double& ay, double& ar, bool main, bool left, bool right, double mass, double r ); void update_fuel(double& fuel, bool& main, bool& left, bool& right); void update_state(double& position, double& velocity, double acceleration); // Allow the user to change the controls void change_one_control(bool& thruster, int event, bool action); void change_controls(bool& main, bool& left, bool& right); // Create the rocket images at different angles void compute_original_pixel_coordinates (int newx, int newy, double r, int& x, int& y); void create_images(char buffer[][IMAGES][ROCKET_BYTES], int image_index); // Draw the background image void draw_background( ); // Draw the lander on the screen. void draw_lander( double x, double y, // Current x and y positions (m) double r, // Current rotational pos (radians) bool main, bool left, bool right // Which thrusters are on ); // Write some of the state data to the screen void draw_stats( double vx, double vy, // Current velocities (m/s) double vr, // Current rotational velocitie (radians/s) double fuel, // Remaining fuel (kg) double t // Current time (s) ); /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// //The main program int main( ) { bool main = false, left = false, right = false; // Are the thrusters on? double x = (static_cast(WINDOW_WIDTH)/PIXELS_PER_METER)/2.0; double y = (static_cast(GROUND_ZERO_Y)/PIXELS_PER_METER); double fuel = STARTING_FUEL; // Remaining fuel (kg) double vx = 0, vy = 0; // Velocity (m/s) double r = M_PI/2; // Rotational pos radians double vr = 0; // Rotational radians/s double t = 0; // Time since start double ax, ay; // Accelerations (m/s^2) double ar; // Rotational acc (rad/s^2) /* int a, b; compute_original_pixel_coordinates(0, 0, 0, a, b); cout << a << " " << b << endl; exit(0); */ initwindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Mario Lander", 0, 0, true); cout << "Countdown..." << endl; do { // Adjust the control variables: main, left, and right change_controls(main, left, right); // Adjust the physical variables: positions, velocities, fuel, t update_fuel(fuel, main, left, right); update_accelerations(ax, ay, ar, main, left, right, LANDER_MASS+fuel, r); update_state(x, vx, ax); update_state(y, vy, ay); update_state(r, vr, ar); t += DELTA_T; // Redraw the screen and delay draw_background( ); draw_stats(vx,vy,vr,fuel,t); draw_lander(x,y,r,main,left,right); swapbuffers( ); delay(DELAY); } while(true); return 0; } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void change_controls(bool& main, bool& left, bool& right) { change_one_control(left, WM_LBUTTONDOWN, true); change_one_control(left, WM_LBUTTONUP, false); change_one_control(right, WM_RBUTTONDOWN, true); change_one_control(right, WM_RBUTTONUP, false); if (kbhit( )) { switch (getch( )) { case ' ': main = !main; break; case 'q': case 'Q': exit(0); default: break; } } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void change_one_control(bool& thruster, int event, bool action) { if (ismouseclick(event)) { thruster = action; clearmouseclick(event); } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void compute_original_pixel_coordinates (int newx, int newy, double r, int& x, int& y) { double cart_newx = newx - ROCKET_SIZE/2.0; double cart_newy = ROCKET_SIZE/2.0 - newy; double theta = -(r - M_PI/2); double cos_theta = cos(theta); double sin_theta = sin(theta); double cart_x = cos_theta * cart_newx - sin_theta * cart_newy; double cart_y = sin_theta * cart_newx + cos_theta * cart_newy; x = static_cast(cart_x) + ROCKET_SIZE/2; if (x < 0) x = 0; y = ROCKET_SIZE/2 - static_cast(cart_y); if (y < 0) y = 0; } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void create_images(char buffer[][IMAGES][ROCKET_BYTES], int image_index) { // When this function is called, the original vertical rocket // is already drawn on the screen in pixels 0..99, 0..99 int angle_number; double r; string filename = "rocketX.bmp"; int x, y, newx, newy; int color; // Read the image file and put it at location 0,0 on the screen. filename[6] = '0' + image_index; readimagefile(filename.c_str(), 0, 0, ROCKET_SIZE-1, ROCKET_SIZE-1); for (angle_number = 0; angle_number < IMAGES; ++angle_number) { // Create the image for buffer[image_index][angle_number] r = angle_number * 2 * M_PI / IMAGES; for (newx = 0; newx < ROCKET_SIZE; newx++) { for (newy = 0; newy < ROCKET_SIZE; newy++) { compute_original_pixel_coordinates(newx, newy, r, x, y); color = getpixel(x,y); putpixel(newx + ROCKET_SIZE, newy + ROCKET_SIZE, color); } } getimage( ROCKET_SIZE, ROCKET_SIZE, 2*ROCKET_SIZE-1, 2*ROCKET_SIZE-1, buffer[image_index][angle_number] ); } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void draw_background( ) { static bool first = true; static char buffer[BACKGROUND_BYTES]; if (first) { readimagefile("bg.bmp", 0, 0, WINDOW_WIDTH-1, WINDOW_HEIGHT-1); getimage(0, 0, WINDOW_WIDTH-1, WINDOW_HEIGHT-1, buffer); first = false; } else { putimage(0, 0, buffer, COPY_PUT); } setbkcolor(getpixel(0,0)); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void draw_lander( double x, double y, // Current x and y positions (m) double r, // Current rotational pos (radians) bool main, bool left, bool right // Which thrusters are on ) { static bool first = true; static char buffer[8][IMAGES][ROCKET_BYTES]; int image_index; int angle_index; if (first) { // Make and store all the different rocket images: for (image_index = 0; image_index < 8; ++image_index) { create_images(buffer, image_index); cout << 7-image_index << endl; } first = false; } // Get the right rocket image and put it on the screen: image_index = static_cast(main)*4+static_cast(left)*2+static_cast(right); angle_index = static_cast(r*IMAGES/(2*M_PI)) % IMAGES; putimage( static_cast(x*PIXELS_PER_METER), static_cast(GROUND_ZERO_Y - y*PIXELS_PER_METER), buffer[image_index][angle_index], OR_PUT ); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void draw_stats( double vx, double vy, // Current velocities (m/s) double vr, // Current rotational velocitie (radians/s) double fuel, // Remaining fuel (kg) double t // Current time (s) ) { bgiout << "vr (rad/s): " << vr << endl; bgiout << "Fuel (kg): " << static_cast(fuel) << endl; bgiout << "vx (m/s): " << static_cast(vx) << endl; bgiout << "vy (m/s): " << static_cast(vy) << endl; bgiout << "Time: " << static_cast(t) << endl; outstreamxy(10, 10); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void update_accelerations( double& ax, double& ay, double& ar, bool main, bool left, bool right, double mass, double r ) { double a_from_thruster; // Accelerations when no thrusters are on: ax = ar = 0; ay = GRAVITY; if (main) { // The x and y accelerations are modified by the main thruster: a_from_thruster = FORCE_MAIN_THRUSTER/mass; ax += a_from_thruster * cos(r); ay += a_from_thruster * sin(r); } if (left) ar += FORCE_SIDE_THRUSTER/mass; if (right) ar -= FORCE_SIDE_THRUSTER/mass; } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void update_fuel(double& fuel, bool& main, bool& left, bool& right) // Adjust the current amount of fuel based on which thrusters are on. // If the fuel drops to zero, then the thrusters are turned off. { if (main) fuel -= MAIN_FUEL_BURN_RATE * DELTA_T; if (left) fuel -= SIDE_FUEL_BURN_RATE * DELTA_T; if (right) fuel -= SIDE_FUEL_BURN_RATE * DELTA_T; if (fuel <= 0) { fuel = 0; main = left = right = false; } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void update_state(double& position, double& velocity, double acceleration) { position = position + velocity*DELTA_T; velocity = velocity + acceleration*DELTA_T; } ///////////////////////////////////////////////////////////////////////////////