ftp.nice.ch/pub/next/unix/tools/jug.0.7.N.bs.tar.gz#/jug.c

This is jug.c in view mode; [Download] [Up]

/*  (c) 1991 S.Hawtin
   Permission is granted to make any use of this file provided
    1) It is not used for commercial gain without my permission
    2) This notice is included in all copies
    3) Altered copies are marked as such

  No liability is accepted for the contents of the file.

*/

/* JUG a juggler animator */

/*

 This program reads descriptions of juggling patterns in an "English like" 
language then animates them on the screen.  See "README" for a description 
of the language.

For my first attempt I have tried to make the program as portable as 
possible, this means that it does all its output using ASCII printing, 
via the code in "ascii.c".

The one change that is well worth doing is to add the escape sequence 
for your terminal to clear the screen and put the cursor at the top 
left in screen_flush().

If you want to add fancy output graphics for a particular machine then just 
replace "ascii.c" with, for example "amiga.c", by editing the "Makefile".  
The file you create must define display() in the same sort of way as in 
"ascii.c" but obviously should draw balls and hands better.  Once you have 
created such a file send it to me to allow others with your machine to 
benefit.

*/

/* This file controls the main loop and simulation */

#include <stdio.h>

#include "jug.h"

/* Parameters set by the display file */
extern int time_x;
extern int time_y;
extern int ball_offset;
extern double x_scale,y_scale;

/* Here are all the balls we can use */
Ball balls[MAX_BALLS] = 
   {   {"red",0,DEFAULT}    ,{"green",0,DEFAULT},
       {"blue",0,DEFAULT}   ,{"yellow",0,DEFAULT},
       {"violet",0,DEFAULT} ,{"cyan",0,DEFAULT},
       {"white",0,DEFAULT}  ,{"black",0,DEFAULT},
       {"brown",0,DEFAULT}  ,{"orange",0,DEFAULT},
    };

/* Each ball in use is either being held by a hand, or it is 
  travelling under its own steam.  The free_list contains pointers 
  to all the balls moving arround by themselves */
int num_free = 0;
Ball *free_list[MAX_BALLS];

/* Here are all the hands we can use, for the moment just two */
Hand hands[MAX_HANDS] =
   {   {"left", 4,0,0,-1,"***",DEFAULT},
       {"right",0,0,0, 1,"ooo",DEFAULT},
    };

/* The current move number, this indicates the next move that will 
  be made (when the time comes) */
int cur_move;
int num_moves = 0;		/* The total number of moves */
int size_moves = 0;
MoveInst *moves;		/* The list of moves to be made */

/* Variables that control the simulation, these may be changed by 
  the init_screen() function to make the default setup the best for 
  that system  */
int max_time = 100;		/* When to stop the simulation */
int loop_count = 0;		/* Number of times round the loop */
char src_name[NAME_LEN] = "cascade";		/* The default simulation file */
char pat_dir[NAME_LEN] = "patterns/";		/* Directory to put patterns in */
int step = 1;			/* If 1 waits for return after each tick */
int delay = 1;			/* The number of seconds for each tick */
int gravity = SCALE_FACTOR*DEF_GRAVITY;	/* The force of gravity */
int got_error = 0;
int sub_div = 1;
char pattern_name[128];		/* String giving the name of the pattern */

/* Offsets for up to 6 balls in each hand */
static int x_ball_offset[] = {0,1,-1,0,1,-1};
static int y_ball_offset[] = {0,0,0,4,4,4};

/* Variables used throughout the file */
int clock_time = 0;		/* The current time */
int time = 0;			/* The time within the pattern */
int sub_time = 0;		/* The time within a wall click */

int max_ball = 3;		/* Set when reading */
int max_hand = 2;

/* Translate a number into something */

/* Hands are used as virtual numbers, this allows "swap hand" to do 
   the same instructions with the hands swaped */
int cur_hand_table;
Hand ***hand_table;

/* Similarlarly with the balls */
int cur_ball_table;
Ball ***ball_table;

/**********************************************************************/

/* Support functions for the moves */

Hand *
get_hand(num)
    int num;
   {/* Translate a virtual hand number into a hand number */
    int count,c2;

    if(hand_table == NULL)
       {/* Need to create a new hand table.  This consists of an array 
           of arrays with the possible permutations in each array
           (well look at the code then!) */
        for(count=0;count<MAX_HANDS;++count)
            hands[count].hand_num = count+1;

        /* How many hands in use? */
        max_hand = 0;
        for(count=0;count<MAX_HANDS;++count)
            if(hands[count].x != DEFAULT) /* Any used hand will have x set */
                max_hand++;

        hand_table = (Hand ***)malloc(max_hand*sizeof(Hand **));
        if(hand_table == NULL)
           {fprintf(stderr,"Malloc failed\n");
            exit(20);
            }

        /* The first table consists of all the used hands in order */
        hand_table[0] = (Hand **)malloc(max_hand*sizeof(Hand *));
        if(hand_table[0] == NULL)
           {fprintf(stderr,"Malloc failed\n");
            exit(20);
            }
        c2 = 0;
        for(count=0;count<MAX_HANDS;++count)
            if(hands[count].x != DEFAULT)
               {hand_table[0][c2++] = &hands[count];
                }

        for(count=1;count<max_hand;++count)
           {hand_table[count] = (Hand **)malloc(max_hand*sizeof(Hand *));
            if(hand_table[count] == NULL)
               {fprintf(stderr,"Malloc failed\n");
                exit(20);
                }
            for(c2=0;c2<max_hand;++c2)
               {hand_table[count][c2] = &hands[(c2+count)%max_hand];
                }
            }
        }
    if(num<1 || num>(max_hand+1))
       {fprintf(stderr,"%d: get_hand range error\n",time);
        exit(20);
        }
    return(hand_table[cur_hand_table][num-1]);
    }

Ball *
get_ball(num)
    int num;
   {/* translate a virtual hand number into a hand number */
    int count,c2;

    if(ball_table == NULL)
       {/* need to create a new ball table */

        /* First do some initialising on the balls */
        for(count=0;count<MAX_BALLS;++count)
           {balls[count].ball_num = count+1;
            }
        /* How many balls are in use ? */
        max_ball = 0;
        for(count=0;count<MAX_BALLS;++count)
            if(balls[count].y != DEFAULT) /* Any used ball will have y set */
                max_ball++;

        ball_table = (Ball ***)malloc(max_ball*sizeof(Ball **));
        if(ball_table == NULL)
           {fprintf(stderr,"Malloc failed\n");
            exit(20);
            }

        /* The first table consists of all the used balls in order */
        ball_table[0] = (Ball **)malloc(max_ball*sizeof(Ball *));
        if(ball_table[0] == NULL)
           {fprintf(stderr,"Malloc failed\n");
            exit(20);
            }
        c2 = 0;
        for(count=0;count<MAX_BALLS;++count)
            if(balls[count].y != DEFAULT)
               {ball_table[0][c2++] = &balls[count];
                }

        /* Now the rest of the tables are just the first one rotated */
        for(count=1;count<max_ball;++count)
           {ball_table[count] = (Ball **)malloc(max_ball*sizeof(Ball *));
            if(ball_table[count] == NULL)
               {fprintf(stderr,"Malloc failed\n");
                exit(20);
                }
            for(c2=0;c2<max_ball;++c2)
               {ball_table[count][c2] = ball_table[0][(c2+count)%max_ball];
                }
            }
        }
    if(num<1 || num>(max_ball+1))
       {fprintf(stderr,"%d: get_ball range error\n",time);
        exit(20);
        }
    return(ball_table[cur_ball_table][num-1]);
    }

void
hand_to_real(hand,x,y,z,real_x,real_y,real_z)
    Hand *hand;
    int  x,y,z;
    int *real_x;
    int *real_y;
    int *real_z;
   {/* Convert from hand space to real space */
    *real_x = SCALE_FACTOR*(hand->backwards * x + hand->def_x);
    *real_y = SCALE_FACTOR*(y + hand->def_y);
    *real_z = SCALE_FACTOR*(z + hand->def_z);
    }

static 
int divide(numer,denom)
    int numer,denom;
   {/* Divide one number by antother rounding the result to the 
      nearest integer (rather than the one close to 0) */
    if(numer > 0)
        numer += denom/2;
      else
        numer -= denom/2;
    return(numer/denom);
    }

void
real_to_hand(hand,x,y,z,hand_x,hand_y,hand_z)
    Hand *hand;
    int  x,y,z;
    int *hand_x;
    int *hand_y;
    int *hand_z;
   {/* Convert from real space to hand space */
    *hand_x = (divide(x,SCALE_FACTOR) - hand->def_x)*hand->backwards;
    *hand_y = (divide(y,SCALE_FACTOR) - hand->def_y);
    *hand_z = (divide(z,SCALE_FACTOR) - hand->def_z);
    }

/**********************************************************************/

/* Functions to perform the moves */

void
move_nop(the_move)
    MoveInst *the_move;
   {/* Call this move kills the program */
    fprintf(stderr,"%d: Move NOP\n",time);
    exit(20);
    }

void
move_move(the_move)
    MoveInst *the_move;
   {/* Either a real move or moving just before a throw or catch */
    Hand *this_hand;

    /* First work out which hand we are talking about */
    if(the_move->param[OFF_HAND] == DEFAULT)
       {if(max_hand!=1)
           {fprintf(stderr,"%d: Hand not specified for move\n",time);
            got_error++;
            }
        the_move->param[OFF_HAND] = 1;
        }
    this_hand = get_hand(the_move->param[OFF_HAND]);

    /* Moves are simple, just set the position of the hand */
    if(the_move->param[OFF_X] != DEFAULT)
        this_hand->x = the_move->param[OFF_X];

    if(the_move->param[OFF_Y] != DEFAULT)
        this_hand->y = the_move->param[OFF_Y];

    if(the_move->param[OFF_Z] != DEFAULT)
        this_hand->z = the_move->param[OFF_Z];

    hand_to_real(this_hand,this_hand->x,this_hand->y,this_hand->z,
                           &this_hand->r_x,&this_hand->r_y,&this_hand->r_z);
    }

void
move_throw(the_move)
    MoveInst *the_move;
   {/* Throw a ball from a hand */
    Hand *this_hand;
    Ball *this_ball;
    int count,found;

    /* Does the throw include a move? */
    if(the_move->param[OFF_X] != DEFAULT)
        move_move(the_move);

    /* Now work out which hand and ball we are talking about */
    if(the_move->param[OFF_HAND] == DEFAULT)
       {if(max_hand!=1)
           {fprintf(stderr,"%d: Hand not specified for throw\n",time);
            got_error++;
            }
        the_move->param[OFF_HAND] = 1;
        }
    this_hand = get_hand(the_move->param[OFF_HAND]);

    if(the_move->param[OFF_BALL] == DEFAULT)
       {/* The move does not specify a ball, do we have a choice? */
        if(this_hand->num_balls!=1)
           {fprintf(stderr,"%d: Ball not specified for throw\n",time);
            got_error++;
            }
        this_ball = this_hand->balls[0];
        }
      else
       {this_ball = get_ball(the_move->param[OFF_BALL]);
        }

    /* Throw with the right force */
    if(the_move->param[OFF_DX] != DEFAULT && the_move->param[OFF_DY] != -1)
       {/* X speed set to land at the right place */
        this_ball->dx = (SCALE_FACTOR*this_hand->backwards*
                           the_move->param[OFF_DX])/(the_move->param[OFF_DY]+1);
        }
      else
        this_ball->dx = 0;

    if(the_move->param[OFF_DY] == DEFAULT)
       {fprintf(stderr,"%d: Must specify time of flight for throw\n",time);
        exit(20);
        }
      else if (gravity==0)
        this_ball->dy = SCALE_FACTOR*the_move->param[OFF_DY];
      else
        this_ball->dy = (gravity*(the_move->param[OFF_DY]))/2;
    
    if(the_move->param[OFF_DZ] != DEFAULT)
        this_ball->dz = (SCALE_FACTOR*the_move->param[OFF_DZ])/
                                          the_move->param[OFF_DY];
      else
        this_ball->dz = 0;

    this_ball->x = this_hand->r_x + this_ball->dx;
    this_ball->y = this_hand->r_y + this_ball->dy;
    this_ball->z = this_hand->r_z + this_ball->dz;

    /* Now detach the ball from the hand and add it to the free list */
    found = 0;
    for(count=0;count<this_hand->num_balls;++count)
       {if(found)
            this_hand->balls[count-1] = this_hand->balls[count];
          else if(this_hand->balls[count] == this_ball)
           {found = 1;
            free_list[num_free++] = this_ball;
            }
        }

    if(!found)
       {fprintf(stderr,"%d: %s Ball not in correct hand\n",
                time,this_ball->alias);
        got_error++;
        }
      else
        this_hand->num_balls--;
    }

void
move_catch(the_move)
    MoveInst *the_move;
   {Hand *this_hand;
    Ball *this_ball;
    int count,found;

    /* If we need to move to get then go there */
    if(the_move->param[OFF_X] != DEFAULT)
        move_move(the_move);

    /* Now work out which hand and ball we are talking about */
    if(the_move->param[OFF_HAND] == DEFAULT)
       {if(max_hand!=1)
           {fprintf(stderr,"%d: Hand not specified for catch\n",time);
            got_error++;
            }
        the_move->param[OFF_HAND] = 1;
        }
    this_hand = get_hand(the_move->param[OFF_HAND]);

    if(the_move->param[OFF_BALL] == DEFAULT)
       {/* Look through the free balls to find the closest one */
        int dist,best,best_dist;

        best_dist = INFINITY;

        for(count=0;count<num_free;++count)
           {this_ball = free_list[count];
            /* Distance calculation is complicated by the twin spaces */
            dist = abs(this_ball->x - this_hand->r_x) + abs(this_ball->y - this_hand->r_y) +
                   abs(this_ball->z - this_hand->r_z);
            /* If this ball is closer then note it */
            if(dist < best_dist)
               {best_dist = dist;
                best = count;
                }
            }
        if(best_dist == INFINITY)
           {fprintf(stderr,"%d: No balls to catch\n",time);
            exit(20);
            }
        this_ball = free_list[best];
        }
      else
       {/* We were told which ball to catch */
        this_ball = get_ball(the_move->param[OFF_BALL]);
        }

    /* Now move the hand to where the ball is */
    real_to_hand(this_hand,this_ball->x,this_ball->y,this_ball->z,
                 &this_hand->x,&this_hand->y,&this_hand->z);
    hand_to_real(this_hand,this_hand->x,this_hand->y,this_hand->z,
                           &this_hand->r_x,&this_hand->r_y,&this_hand->r_z);

    /* Set the offsets to make the animation work */
    if(this_hand->num_balls >= 0 && this_hand->num_balls < 6)
       {this_ball->o_x -= ball_offset*x_ball_offset[this_hand->num_balls]*this_hand->backwards;
/*        printf("Offset by %d [%d]\n",this_hand->num_balls,
               x_ball_offset[this_hand->num_balls]*this_hand->backwards); */
        this_ball->o_y -= ball_offset*y_ball_offset[this_hand->num_balls];
        }

    /* Now detach the ball from the free list and add it to the hand */
    found = 0;
    for(count=0;count<num_free;++count)
       {if(found)
            free_list[count-1] = free_list[count];
          else if(free_list[count] == this_ball)
           {found = 1;
            this_hand->balls[this_hand->num_balls++] = this_ball;
            }
        }
    if(!found)
       {fprintf(stderr,"%d: Ball not in free_list\n",time);
        got_error++;
        }
      else
        num_free--;
    }

void
move_swap(the_move)
    MoveInst *the_move;
   {/* Change the mapping of logical to physical for balls or hands */
    if(the_move->param[OFF_HAND]!=DEFAULT)
        cur_hand_table = (cur_hand_table+the_move->param[OFF_HAND])%max_hand;
    if(the_move->param[OFF_BALL]!=DEFAULT)
        cur_ball_table = (cur_ball_table+the_move->param[OFF_BALL])%max_ball;
    }

void
move_loop(the_move)
    MoveInst *the_move;
   {/* Reset the time value and change the cur_move */
    int count;

    if(loop_count != 0)
       {loop_count--;
        if(loop_count == 0)
            max_time = 0;
          else
            max_time = 100;
        }
    time = the_move->param[OFF_LABEL];
    for(cur_move = 0;cur_move<num_moves;++cur_move)
       {if(moves[cur_move].time >= time)
            break;
        }
    if(cur_move >= num_moves)
       {fprintf(stderr,"%d: No moves after loop label\n",time);
        exit(20);
        }
    cur_move--;
    }

/**********************************************************************/

/* General functions */

void
readopts(argc,argv)
    int argc;
    char **argv;
   {/* Read all the command line options */
    int arg_num,count;
    double val;

    arg_num = 0;
    for(count=1;count<argc;++count)
       {/* Is this a switch */
        if(argv[count][0] == '-')
           {switch(argv[count][1])
               {case '?':
		case 'q': case 'Q':
usage:
                    if(is_option(&count,argv,argc))
                        break;
                    printf("Usage jug sourcefile\n");
                    printf("          -t<max time>\n");
                    printf("          -? -q\n");
                    printf("          -s -v\n");
                    printf("          -d<delay time>\n");
                    printf("          -g<gravity>\n");
                    printf("          -p<clock divisions>\n");
                    printf("          -x<scale> -e<vertical scale>\n");
                    printf("          -b[<loop count>]\n");
                    printf("          -r<jug dir>\n");
                    exit(20);
                case 'b': case 'B':
                    if(argv[count][2] == '\0')
                        loop_count = 1;
                      else
                        sscanf(&argv[count][2],"%d",&loop_count);
                    break;
                case 'x': case 'X':
                    /* Change the scale */
                    /* sscanf is almost garenteed not to work the same on other 
                       systems, this code will fail because of buggy c libraries */
                    sscanf(&argv[count][2],"%lg",&val);
                    x_scale *= val;
                    y_scale *= val;
                    ball_offset *= val;
                    rescale();
                    break;
                case 'e': case 'E':
                    /* Change the vertical exageration, usefull for creating 
                      ASCII files showing the steps in a juggle */
                    /* See sscanf notes above */
                    sscanf(&argv[count][2],"%lg",&val);
                    y_scale *= val;
                    rescale();
                    break;
                case 'v': case 'V':
                    printf("Version %d.%d\n",MAJ_VERSION,MIN_VERSION);
                    exit(20);
		case 'g': case 'G':
                    sscanf(&argv[count][2],"%d",&gravity);
                    break;
		case 'p': case 'P':
                    sscanf(&argv[count][2],"%d",&sub_div);
                    break;
		case 't': case 'T':
                    sscanf(&argv[count][2],"%d",&max_time);
                    break;
                case 's': case 'S':
                    step = !step;
                    break;
                case 'd': case 'D':
                    sscanf(&argv[count][2],"%d",&delay);
                    break;
                case 'r': case 'R':
                    strcpy(pat_dir,&argv[count][2]);
                    break;
                case '\0':
                    goto real_arg;
                default:
                    goto usage;
                }
            }
          else
           {/* A normal argument */
real_arg:
            switch(arg_num)
               {case 0:
                    strcpy(src_name,argv[count]);
                    break;
                default:
                    goto usage;
                }
            arg_num++;
            }
        }
    }

static void
process_free()
   {/* Move all the balls under thier own steam */
    int count;

    for(count=0;count<num_free;++count)
       {/* Move each ball in turn, just gravity and Newton */
        free_list[count]->dy -= gravity;
        free_list[count]->x += free_list[count]->dx;
        free_list[count]->y += free_list[count]->dy;
        free_list[count]->z += free_list[count]->dz;
        }
    }

static void
display(sub_time)
    int sub_time;
   {/* Show the user what the simulation is doing at the moment */
    char str[128];
    int count,c2;
    double ratio,ratio_1;
    int x,y;
    Hand *this_hand;
    int hand_x,hand_y,hand_z,old_hand_x,old_hand_y,old_hand_z;
    Ball *this_ball;

    screen_clr();

    ratio = (double)(sub_time)/(double)(sub_div);
    ratio_1 = 1 - ratio;

    /* First draw the hands */
    for(c2=0;c2<max_hand;++c2)
       {/* Convert the hand positions to absolute coordinates */

        this_hand = get_hand(c2+1);
        if(sub_time == 0)
            hand_to_real(this_hand,this_hand->x,this_hand->y,this_hand->z,
                         &this_hand->r_x,&this_hand->r_y,&this_hand->r_z);

        draw_hand((int)(ratio*this_hand->r_x + ratio_1*this_hand->o_x),
                  (int)(ratio*this_hand->r_y + ratio_1*this_hand->o_y),
                  (int)(ratio*this_hand->r_z + ratio_1*this_hand->o_z),this_hand);

        for(count=0;count<this_hand->num_balls && count<6;count++)
           {/* Now draw the balls that the hand is holding */

            this_ball = this_hand->balls[count];
            if(sub_time == 0)
               {this_ball->x = this_hand->r_x;
                this_ball->y = this_hand->r_y;
                this_ball->z = this_hand->r_z;
                }
            draw_ball(x_ball_offset[count]*ball_offset*this_hand->backwards + 
                      (int)(ratio*this_ball->x + ratio_1*this_ball->o_x),
                      y_ball_offset[count]*ball_offset + 
                      (int)(ratio*this_ball->y + ratio_1*this_ball->o_y),
                      (int)(ratio*this_ball->z + ratio_1*this_ball->o_z),this_ball);
            }
        }

    /* Now draw all the free balls */
    for(count=0;count<num_free;++count)
       {/* Convert the balls position to coordinates */
        Ball *this_ball;

        this_ball = free_list[count];
        draw_ball((int)(ratio*this_ball->x + ratio_1*this_ball->o_x),
                  (int)(ratio*this_ball->y + ratio_1*this_ball->o_y),
                  (int)(ratio*this_ball->z + ratio_1*this_ball->o_z),
                  this_ball);
        }

    /* Tell the user what time it is */
    sprintf(str,"Time %4d | %-4d",time,clock_time);
    out_string(time_x,time_y,str);

    if(sub_time != (sub_div-1))
       {screen_flush();
        return;
        }
      else if (step)
       {/* We should pause after each step to let the user hit a key */
        wait_step();
        }
      else if (delay != 0)
       {/* We do not need to give the user a chance to press a key */

        /* Now display the screen we have constructed */
        screen_flush();

        /* But we do need to delay for a while before drawing the 
           next screen */
#ifdef AMIGA
	Delay(50*delay);
#else
        sleep(delay);
#endif
        }
    }

setup()
   {
    int count,c2;

    /* Clear out the previous tables */
    if(hand_table != NULL)
       {for(count = 0;count<max_hand;count++)
            free(hand_table[count]);
        free(hand_table);
        hand_table = NULL;
        }
    if(ball_table != NULL)
       {for(count = 0;count<max_ball;count++)
            free(ball_table[count]);
        free(ball_table);
        ball_table = NULL;
        }
    /* Reset the global variables */
    for(count=0;count<MAX_BALLS;count++)
       {balls[count].x = 0;
        balls[count].y = DEFAULT;
        }
    for(count=0;count<MAX_HANDS;count++)
       {hands[count].num_balls = 0;
        hands[count].x = DEFAULT;
        }
    num_free = num_moves = cur_hand_table = cur_ball_table = 0;

    /* Now read the description of the moves */
    read_file(src_name);

    /* Were we told the name of the pattern? */
    if(pattern_name[0] != '\0')
        puts(pattern_name);

    /* Make sure all the tables are set up correctly */
    get_hand(1); get_ball(1);

    /* Note where the hands are starting off */
    for(count=0;count<max_hand;count++)
       {/* Get the position in real space */
        hand_to_real(hand_table[0][count],hand_table[0][count]->x,hand_table[0][count]->y,
                     hand_table[0][count]->z,&hand_table[0][count]->r_x,
                     &hand_table[0][count]->r_y,&hand_table[0][count]->r_z);
        for(c2=0;c2<hand_table[0][count]->num_balls;++c2)
           {hand_table[0][count]->balls[c2]->x = hand_table[0][count]->r_x;
            hand_table[0][count]->balls[c2]->y = hand_table[0][count]->r_y;
            hand_table[0][count]->balls[c2]->z = hand_table[0][count]->r_z;
            }
        }

    /* Reset time */
    cur_move = clock_time = time = 0;
    }

int
process_key(ch)
    char ch;
   {/* The user gave some single key command */
    int is_output;
    double zoom_val;

    is_output = 0;
    switch(ch)
       {case 'q': case 'Q':
            tidy_screen();
            exit(0);
        case '\n': case '\r': case ' ':
            return(1);
        case '\0':
            return(0);
        case '?':
            /* Tell the user what the valid keys are */
            printf("Single key commands\n");
            printf("    Q    Quit\n");
            printf("    S    Invert step value\n");
            printf("    I    Zoom in\n");
            printf("    O    Zoom out\n");
            printf("    R    Reread source file\n");
            break;
        case 's': case 'S':
            step = !step;
            return(1);
        case 'i': case 'I': case 'o': case 'O':
            /* Zoom */
            switch(ch)
               {case 'i':
                    zoom_val = 1.1;
                    break;
                case 'I':
                    zoom_val = 1.4;
                    break;
                case 'o':
                    zoom_val = 1.0/1.1;
                    break;
                case 'O':
                    zoom_val = 1.0/1.4;
                    break;
                }
            x_scale *= zoom_val;
            y_scale *= zoom_val;
            ball_offset *= zoom_val;
            rescale();
            return(1);
        case 'r': case 'R':
            /* Reread the source file */
            setup();
            return(1);
        default:
            printf("Key <%c> is not a command, try <?>\n",ch);
        }
    /* If we get here then something weird is going on, wait for the user to respond */
    printf("Press <Return> to continue\n");
    getchar();
    return(0);
    }

void
main(argc,argv)
    int argc;
    char **argv;
   {void (*fun)();
    int count,c2;

    /* Read the command line options */
    init_screen(&argc,argv);
    readopts(argc,argv);

    /* Load the file and set time to 0 */
    setup();

    while(clock_time < max_time)
       {
        /* Note the old positions */
        for(count=0;count<max_ball;count++)
           {ball_table[0][count]->o_x = ball_table[0][count]->x;
            ball_table[0][count]->o_y = ball_table[0][count]->y;
            ball_table[0][count]->o_z = ball_table[0][count]->z;
            }
        for(count=0;count<max_hand;count++)
           {hand_table[0][count]->o_x = hand_table[0][count]->r_x;
            hand_table[0][count]->o_y = hand_table[0][count]->r_y;
            hand_table[0][count]->o_z = hand_table[0][count]->r_z;
            }
        /* Move the things under thier own steam */
        process_free();

        /* Find all the moves needed at this time */
        while(cur_move != -1 && moves[cur_move].time <= time)
           {/* Assign the function to get round a bug in NorthC on the Amiga,
               beware of free C compilers :-) */
            fun = moves[cur_move].cmnd->fun;
            (*fun)(&moves[cur_move]);
            cur_move++;
            if(cur_move >= num_moves)
               {fprintf(stderr,"%d: Run out of moves!!\n",time);
                cur_move = -1;
                got_error++;
                }
            }

        if(got_error != 0)
           {printf("Found errors Press <Return>\n");
            getchar();
            got_error = 0;
            }

        /* Draw a picture of the world at the moment */
        for(sub_time=0;sub_time<sub_div;++sub_time)
            display(sub_time);

        /* Move time */
        clock_time++;
        time++;
        }
    /* Now tidy up */
    tidy_screen();
    }

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.