File moon-lander-autopilot.patch of Package moon-lander
--- moon_lander.c
+++ moon_lander.c
@@ -14,6 +14,12 @@
* 08/17/2001 - cleaned up some of the messy code, fixed last of the memory leaks,
* added ryan daniels' ship and thrusters
*
+* 03/17/2002 - Dr. Robert Meier improved the autopilot to use minimum fuel
+* trajectory from current position and velocity.
+*
+* 05/06/2005 - Dr. Robert Meier improved the autopilot to act as a
+* flight director.
+*
*/
@@ -37,7 +43,6 @@
#define XSIZE 640
#define YSIZE 480
#define TERRAIN_YSIZE (YSIZE / 2)
-#define FPS (1000 / 35)
#define DATAPATH ""
#define FRESHRUN 0
@@ -96,6 +101,7 @@
int small_font;
int demo_mode;
int autopilot;
+ int flightdirector;
int state;
int ActualTime, LastTime;
int landing_pad;
@@ -108,6 +114,10 @@
Sprite thrustb;
Sprite thrust_left;
Sprite thrust_right;
+ Sprite crosshairs;
+ Sprite crosshairsb;
+ Sprite beacon;
+ Sprite beaconb;
Sprite miniship;
Sprite logo;
Sprite gameover_screen;
@@ -118,6 +128,7 @@
int opt_lp_warn;
int opt_num_lives;
int opt_fancy_terrain;
+ int opt_frame_period;
AI ai;
#ifndef NOSOUND
Mix_Chunk *engine;
@@ -164,8 +175,8 @@
/* timer - so that things run at an even speed regardless of cpu speed */
game->ActualTime = SDL_GetTicks();
- if (game->ActualTime < game->LastTime + FPS){
- SDL_Delay(game->LastTime + FPS - game->ActualTime);
+ if (game->ActualTime < game->LastTime + 10*game->opt_frame_period){
+ SDL_Delay(game->LastTime + 10*game->opt_frame_period - game->ActualTime);
}
game->LastTime = game->ActualTime;
}
@@ -195,7 +206,7 @@
}
while (!done){
- if ( files[count] = readdir(dir) ){
+ if ( (files[count] = readdir(dir)) ){
//printf("I see - %d %s\n", count, files[count]->d_name);
count++;
@@ -279,8 +290,9 @@
if ( (file = fopen(filename, "w")) != NULL) {
- fprintf(file,"%d %d %d %d %d", game->opt_num_lives, game->opt_lp_bonus,game->opt_lp_warn,
- game->opt_prog_grav,game->opt_fancy_terrain);
+ fprintf(file,"%d %d %d %d %d %d", game->opt_num_lives,
+ game->opt_lp_bonus,game->opt_lp_warn,game->opt_prog_grav,
+ game->opt_fancy_terrain,game->opt_frame_period);
}
else{
@@ -318,8 +330,9 @@
if ( (file = fopen(filename, "r")) != NULL) {
- fscanf(file,"%d %d %d %d %d", &game->opt_num_lives, &game->opt_lp_bonus, &game->opt_lp_warn,
- &game->opt_prog_grav, &game->opt_fancy_terrain);
+ fscanf(file,"%d %d %d %d %d %d", &game->opt_num_lives,
+ &game->opt_lp_bonus, &game->opt_lp_warn, &game->opt_prog_grav,
+ &game->opt_fancy_terrain, &game->opt_frame_period);
}
else{
@@ -345,11 +358,11 @@
int done = 0;
int *selected;
int position = 0;
- char options[5][100];
+ char options[6][100];
char display_string[150];
char selected_text[3];
int count;
- int value[5];
+ int value[6];
Uint8 *key_table;
SDL_Event event;
@@ -358,6 +371,7 @@
sprintf(options[2],"%s", "Landing Pad Speed Warning");
sprintf(options[3],"%s", "Variable Speed Landing Pads");
sprintf(options[4],"%s", "Number Of Ships");
+ sprintf(options[5],"%s", "Frame Period (x10ms)");
/* clear event buffer */
@@ -384,18 +398,38 @@
if (key_table[SDLK_UP]){
position--;
if (position < 0){
- position = 4;
+ position = 5;
}
}
if (key_table[SDLK_DOWN]){
position++;
- if (position >4){
+ if (position >5){
position = 0;
}
}
- if (key_table[SDLK_RETURN]){
+ if (key_table[SDLK_LEFT]){
+ (*selected)--;
+
+ if (position == 4){
+ if (*selected < 1 ){
+ *selected = 5;
+ }
+ }
+ else if (position == 5) {
+ if (*selected < 1){
+ *selected = 1;
+ }
+ }
+ else{
+ if (*selected < 0){
+ *selected = 1;
+ }
+ }
+ }
+
+ if (key_table[SDLK_RETURN] || key_table[SDLK_RIGHT]){
(*selected)++;
if (position == 4){
@@ -403,6 +437,8 @@
*selected = 1;
}
}
+ else if (position == 5) {
+ }
else{
if (*selected > 1){
*selected = 0;
@@ -428,12 +464,16 @@
else if (position == 4){
selected = &(game->opt_num_lives);
}
+ else if (position == 5){
+ selected = &(game->opt_frame_period);
+ }
value[0] = game->opt_fancy_terrain;
value[1] = game->opt_prog_grav;
value[2] = game->opt_lp_warn;
value[3] = game->opt_lp_bonus;
value[4] = game->opt_num_lives;
+ value[5] = game->opt_frame_period;
/* draw the options */
@@ -442,7 +482,7 @@
DT_DrawText("Arrow Keys Select", game->screen, game->small_font, 260, 75);
DT_DrawText("ENTER changes value", game->screen, game->small_font, 260, 90);
- for (count = 0; count < 5; count ++) {
+ for (count = 0; count < 6; count ++) {
if (position == count) {
sprintf(selected_text,"%s","**");
@@ -883,6 +923,7 @@
draw_score(game, 0);
DT_DrawText("Arrow keys control the ship", game->screen, game->big_font, 100, 100 );
DT_DrawText("Q quit P pause ESC options", game->screen, game->big_font, 75, 125 );
+ DT_DrawText("A autopilot F flight director", game->screen, game->big_font, 75, 150 );
DT_DrawText("Press ENTER to play", game->screen, game->big_font, 175, 170 );
DT_DrawText("Score for each round = landing pad score + remaining fuel.", game->screen, game->small_font, 150, 280 );
@@ -1256,6 +1297,44 @@
/************************************************/
+void gamefd( Game *game)
+{
+ /* ----------------------------------------------------------------
+ Flight Directory - written by Dr. Robert Meier 06/05/2006
+
+ During powered descent from low-phi (nearly parallel to the
+ horizon) orbit, the lunar module analog computer presented the
+ pilot with two oscilloscope displays.
+ One presented the predicted rate of climb and crosstrack speed
+ at the time that downrange speed was zero.
+ The second predicted altitude and crosstrack error at the time
+ that downrange speed was zero.
+ The pilot's goal was to keep the blip on each oscilloscope
+ centered as the predictions were updated according to radar.
+
+ As a similar presentation, use a point to indicate the lowest
+ altitude and further side motion if thrust is applied continously.
+
+ ---------------------------------------------------------------- */
+ const float G = game->gravity;
+ const float A = 0.10;
+ const float B = 0.07;
+ Sprite *ship = &game->ship;
+ float xi = ship->x + 0.5 * ship->w;
+ float yi = ship->y + ship->h;
+ float hi = ship->x_vel;
+ float vi = ship->y_vel;
+ float sgn = hi > 0 ? 1 : -1;
+ int cx = xi - 4 + sgn * hi * hi / (2 * B);
+ int cy = yi - 4 + vi * vi / (2 * (A-G));
+ game->crosshairs.x = cx;
+ game->crosshairs.y = cy;
+ game->crosshairsb.x = game->crosshairs.x;
+ game->crosshairsb.y = game->crosshairs.y;
+}
+
+/************************************************/
+
void gameai( int *left,
int *right,
int *down,
@@ -1275,7 +1354,203 @@
velocity.
4 => Freefall with minor course changes
+ Game AI -- improved by Dr. Robert Meier 03/17/2002
+
+ The lunar lander powered descent from low-phi orbit was a
+ nonlinear problem well beyond autopilots until the 1970s.
+ Landing with perfect attitude control on a flat surface is
+ easy.
+
+ The minimum fuel course is called bang-bang control.
+
+ No vertical thrust until continuous vertical thrust will zero
+ descent rate at the pad.
+ Accelerate horizontally the minimum necessary to reach the pad.
+ Coast horizontally until continuous deceleration is required.
+ Decelerate continuously to reach zero groundspeed over the pad.
+
+ To avoid burning landing gear off with backblast, you should actually
+ shutdown shutdown and freefall when altitude and vertical speed are
+ low enough.
+
------------------------------------------------------------- */
+#if 1
+ // Find the nearest pad.
+ game->ai.pad = 0;
+ game->ai.distance = 9999;
+ float x = game->ship.x + game->ship.w/2;
+ for( count = 0; count < game->current_level.num_landings; count++ ) {
+ if( abs( x-game->current_level.landing_x[count] ) < game->ai.distance ) {
+ game->ai.distance = abs( x - game->current_level.landing_x[count] );
+ game->ai.pad = count;
+ }
+ }
+
+ // Find the target hovering point. (Allow .05 margin for error.)
+ int max_x = game->current_level.landing_x[game->ai.pad]
+ + game->current_level.landing_w[game->ai.pad]/2;
+ float max_v = game->current_level.landing_speed[game->ai.pad]-.05;
+ int max_y = game->current_level.landing_y[game->ai.pad];
+
+ // Let the user know which pad is in use
+ game->beacon.x = max_x;
+ game->beacon.y = max_y;
+ game->beaconb.x = max_x;
+ game->beaconb.y = max_y;
+
+ // Determine the time to target.
+ // y(-i) - freefall altitude that would result in current descent rate
+ // (assumed freefall)
+ // y(-t) - altitude now
+ // v(-t) - descent rate now
+ // (actual freefall)
+ // y(0) - altitude at burn
+ // (continuous burn)
+ // y(f) - pad altitude
+ //
+ // Thanks to Galileo, we know
+ // g - gravity
+ // a - acceleration (thrust - gravity)
+ // y(-i) = y(0) - g i i / 2
+ // y(-t) = y(-i) + g (i-t) (i-t) / 2
+ // v(-t) = g (i-t)
+ // y(f) = y(0) + a f f / 2
+ // a [y(f) - y(0)] = g [y(0) - y(-i)]
+ // a f = g i
+ // We can now rewrite
+ // y(-i) = y(-t) - v(-t) v(-t) / 2 g
+ // y(0) = [a y(f) + g y(-i)] / (a + g)
+ // i = sqrt(2 [y(0) - y(-i)] / g)
+ // f = sqrt(2 [y(f) - y(0)] / a)
+ // t = i - v(-t) / g
+
+ float g = game->gravity;
+ float a = .10 - g;
+ float yt = game->ship.y + game->ship.h;
+ float vt = game->ship.y_vel;
+ float yf = max_y;
+ float yi = yt - vt * vt / (2 * g);
+ float y0 = (a * yf + g * yi) / (a + g);
+ float i = sqrt(2 * (y0 - yi) / g);
+ float f = sqrt(2 * (yf - y0) / a);
+ float t = i - vt / g;
+
+ // Thrust upward if continuous thrust will be just enough.
+ // (Allow one frame margin for error.)
+ // (Dont thrust if we are climbing.)
+ // (Dont cancel pilot command.)
+ if( !*down && 0 < vt ) {
+ if( 2 > t ) {
+ *down = 1;
+ t = 0;
+ }
+ }
+
+// printf( "ai-y: pad: %3d ship: %3f %5.2f g: %4.2f d: %d\n",
+// max_y, yt, vt, g, *down);
+
+ // Bang-bang control has an eigenfunction and three states.
+ //
+ // The eigenfunction is the phase space (position vs speed) trajectory
+ // during continuous burn.
+ // w - acceleration
+ // x(-b) - position now
+ // u(-b) - groundspeed now
+ // C - position of zero groundspeed relative to speed direction
+ // T - time to coast and decelerate
+ // The eigenfunction, the position axis, and the time to target,
+ // divide the space into three pairs of regions.
+ // C = x(-b) sgn(u(-b)) + u(-b) u(-b) / 2 abs(w)
+ // T = abs(x(-b)) / abs(u(-b)) + abs(u(-b)) / 2 abs(w)
+ //
+ // x
+ // . accelerating : decelerating
+ // . :
+ // . <<<<:
+ // . <<< :
+ // . . .<< :
+ // . . . :
+ // .coast . :
+ // . . :
+ // . v . :
+ // . v :
+ // .v. :
+ // . . + . . --- v
+ // : . .
+ // : coast .
+ // : . .
+ // > : . .
+ // >> : . .
+ // >>> : . . .
+ // >>>>: . . .
+ // : .
+ // : .
+ // decelerating : accelerating .
+ // : .
+ //
+ // State I: decelerating
+ // C > 0 - condition
+ // w = -abs(w) sgn(u(-b))
+ // State I transitions to state II if actual acceleration is greater than w.
+ //
+ // State II: coasting
+ // C < 0 & t+f > T - condition
+ // w = 0
+ // State II transitions to state I as C increases.
+ //
+ // State III: accelerating
+ // C < 0 & t+f < T - condition
+ // w = abs(w) sgn(u(-b))
+ // State III transition to state II if actual acceleration is greater than w.
+ //
+
+ float xb = x - max_x;
+ float w = .065;
+ float ub = game->ship.x_vel;
+ float C = xb * (0 < ub ? 1 : 0 > ub ? -1 : 0) + ub * ub / (2 * fabs(w));
+ float T = fabs(xb) / (fabs(ub) + .001) + fabs(ub) / (2 * fabs(w));
+
+ // Thrust if continuous thrust will be just enough.
+ // (Dont cancel pilot command.)
+ // (Dont thrust if close enough)
+ // (Shutdown if altitude is low enough)
+ if( !*left && !*right ) {
+ *left = 0;
+ *right = 0;
+ if( .1 < fabs(ub) || 1 < fabs(xb) ) {
+ if( 0 < C ) { /* State I: decelerating */
+ if( 0 < ub ) {
+ *left = 1; /* confirmed */
+ }
+ else{
+ *right = 1; /* confirmed */
+ }
+ }
+ else{
+ if( t + f > T ) { /* State II: coasting */
+ }
+ else{ /* State III: accelerating */
+ if( 0 < ub ) {
+ *right = 1;
+ }
+ else{
+ *left = 1;
+ }
+ }
+ }
+ }
+ else{
+ float alt = game->current_level.landing_y[game->ai.pad] - yt;
+ if( 2 * g * alt + vt * vt < max_v * max_v ) {
+ *down = 0; /* shutdown */
+ }
+ }
+ }
+
+// printf( "ai-x: pad: %3d ship: %3f %5.2f C: %5.2f t+f: %5.2f T: %5.2f l: %d r: %d\n",
+// max_x, xb+max_x, ub, C, t+f, T, *left, *right);
+
+#else /* 1 */
*left = 0;
*right = 0;
@@ -1397,6 +1672,7 @@
//printf( "4: game->ship.x=%f state=%d target=%f x_vel=%f y_vel=%f vdiff=%f\n", game->ship.x, game->ai.state, game->ai.target, game->ship.x_vel, game->ship.y_vel, game->ai.vdiff );
}
+#endif /* 1 */
return;
}
@@ -1423,6 +1699,7 @@
int pause = 0;
int pressed = 0;
int appressed = 0;
+ int fdpressed = 0;
char display_string[100];
game->ai.state = 0;
@@ -1474,7 +1751,7 @@
// toggle
if( !appressed ) {
game->autopilot = (game->autopilot)?0:1;
- appressed++;
+ appressed = 1;
if( game->autopilot == 1 ) {
game->ai.state = 0;
}
@@ -1485,6 +1762,19 @@
appressed = 0;
}
}
+
+ if (key_table[SDLK_f]){
+ // toggle
+ if( !fdpressed ) {
+ game->flightdirector = (game->flightdirector)?0:1;
+ fdpressed = 1;
+ }
+ }
+ else{
+ if( fdpressed ) {
+ fdpressed = 0;
+ }
+ }
if (key_table[SDLK_q]){
exit(0);
@@ -1522,10 +1812,19 @@
if( !pause ) {
- if( game->demo_mode || game->autopilot ) {
+ if( game->demo_mode ) {
game->gravity = 0.05;
gameai( &left, &right, &down, game );
}
+ else{
+ if( game->autopilot ) {
+ gameai( &left, &right, &down, game );
+ }
+ }
+
+ if( game->flightdirector ) {
+ gamefd( game );
+ }
if( game->fuel > 0 ) {
if( right == 1 ) {
@@ -1632,6 +1931,40 @@
draw_sprite(game->screen, game->thrust_left);
}
+ /* display flight director */
+
+ if (game->flightdirector) {
+ if ( (odd_even%2) == 0) {
+ draw_sprite(game->screen, game->crosshairs);
+ }
+ else{
+ draw_sprite(game->screen, game->crosshairsb);
+ }
+ float hi = game->ship.x_vel;
+ float vi = game->ship.y_vel;
+ float scale = fabs(hi);
+ scale = fabs(vi) > scale ? fabs(vi) : scale;
+ scale = 2 > scale ? 2 : scale;
+ int xy, x, y;
+ int cx = game->crosshairs.x+4;
+ int cy = game->crosshairs.y+4;
+ for( xy = 1; xy < 15; xy++ ){
+ x = xy * (hi/scale) * 2, y = xy * (vi/scale) * 2;
+ DrawPixel(game->screen, 0, 255, 0, cx-x, cy-y);
+ }
+ }
+
+ /* display autopilot */
+
+ if (game->autopilot || game->demo_mode) {
+ if ( (odd_even%2) == 0) {
+ draw_sprite(game->screen, game->beacon);
+ }
+ else{
+ draw_sprite(game->screen, game->beaconb);
+ }
+ }
+
/* display fuel */
draw_score(game, 1);
@@ -1801,6 +2134,18 @@
sprintf(filename, "%simages/thrust2.png", DATAPATH);
new_sprite(&(game.thrustb), filename, 0, 0, 1, 0);
+ sprintf(filename, "%simages/crosshairs1.bmp", DATAPATH);
+ new_sprite(&(game.crosshairs), filename, 0, 0, 1, 0);
+
+ sprintf(filename, "%simages/crosshairs2.bmp", DATAPATH);
+ new_sprite(&(game.crosshairsb), filename, 0, 0, 1, 0);
+
+ sprintf(filename, "%simages/beacon1.bmp", DATAPATH);
+ new_sprite(&(game.beacon), filename, 0, 0, 1, 0);
+
+ sprintf(filename, "%simages/beacon2.bmp", DATAPATH);
+ new_sprite(&(game.beaconb), filename, 0, 0, 1, 0);
+
sprintf(filename, "%simages/thrust_left.bmp", DATAPATH);
new_sprite(&(game.thrust_left), filename, 0, 0, 1, 0);