/* =============================================================================
********************************* a3_utils.c ***********************************
============================================================================= */

#include <dos.h>
#include <stdio.h>
#include <stdlib.h>

#define Boolean int
#define True 1
#define False 0

#define KEYBOARD 0x9
#define VIDEO 0x10
#define COM1 0xc

#define LASTROW 23
#define LASTCOL 79
#define STATUS_ROW 24

#define NORM_ATTR 0x07          /* Normal or blank attribute */
#define INV_ATTR 0x70           /* Inverse video attribute */
#define NORMBLNK_ATTR 0x87      /* Normal blinking attribute */
#define INVBLNK_ATTR 0xF0       /* Inverse video blinking */
#define NORMHLIT_ATTR 0x0F      /* Normal with highlighting */
#define INVHLIT_ATTR 0x78       /* Inverse video with highlighting */
#define NORMBLNKHLIT_ATTR 0x8F  /* Normal, blinking, highlighting */
#define INVBLNKHLIT_ATTR 0xF8   /* Inverse, blinking, highlighting */
#define UNDERLINE_ATTR 0x01     /* Underline attribute */

#define TANK_ATTR 0x1e     
#define ALIEN_ATTR 0x1c

/* Of course, you can increase these numbers if you compile under the
   large memory model. Make sure the three NUM_XXX defines sum up
   to the MAX_TASKS value. */

#define MAX_TASKS 20 
#define NUM_TANKS 2
#define NUM_ALIENS 8
#define NUM_BULLETS 10

#define BUF_SIZE 500
typedef struct 
   {
   unsigned char buffer[BUF_SIZE];
   unsigned int front, rear;
   unsigned int char_count;
   } BUF_STRUCT;

/* Increase this if you like under the large memory model. */

#define STACK_SIZE 1200

/* ADD YOUR CODE HERE. Add other members to these structures as necessary. */

typedef struct
   {
   int row, col;
   } AlienParams;


typedef struct
   {

   int row, col;
   } TankParams;

typedef struct
   {
   int row, col;
   } BulletParams;

typedef union 
   {
   AlienParams a_p;
   TankParams t_p;
   BulletParams b_p;
   } TaskParams;

typedef enum {Alien, Tank, Bullet, Bogus} TaskType;

struct PCB 
   {
   unsigned int proc_ss;
   unsigned int proc_sp;
   unsigned int *stack_base;
   Boolean in_use;
   TaskType task_type;
   TaskParams *params_ptr;
   struct PCB *next;
   };

typedef struct
   {
   struct PCB *front;
   struct PCB *rear;
   } Queue;

typedef struct
   {
   int value;
   Queue *queue;
   } Sema;


BUF_STRUCT KEY_BUF;
Boolean DONE;

void interrupt (*oldtimer)(void);
void interrupt (*oldkeyhand)(void);

struct PCB *CURRENT_PROC;
struct PCB *PCB_LIST;

Sema SCREEN_SEMA;
Queue READY_QUEUE;

unsigned int system_stack_ss;
unsigned int system_stack_sp; 

void AlienTask(AlienParams *a_params);
void TankTask(TankParams *t_params);
void BulletTask(BulletParams *b_params);

extern unsigned _stklen = 5000;

/* =============================================================================
********************************* ClearScreen **********************************
============================================================================= */
/* This routine simply clears the screen by scrolling it upward by the number
   of lines defined between cx and dx registers. */

void ClearScreen(void)
   {
   union REGS regs;

/* Upper left corner of the screen in cx. */

   regs.x.cx = 0;   

/* Lower right corner of the screen in dx. */

   regs.h.dh = (char)LASTROW + 1;
   regs.h.dl = (char)LASTCOL;

/* Attribute bit for the scrolled lines. 0x7 is normal. */

   regs.x.bx = (NORM_ATTR << 8) & 0xff00;

/* Bios function 0x6 for scroll up (use 0x7 for scroll down); service 0x10. */

   regs.x.ax = 0x600;
   int86( VIDEO, &regs, &regs );
   }

/* =============================================================================
**************************** WriteStrAtPosWithAttr *****************************
============================================================================= */
/* Use this routine for the Space Invaders program since it is much faster. */

void WriteStrAtPosWithAttr( char *str, unsigned int attr, unsigned int row, 
   unsigned int col )
   {
   static char far *screen = 0;
   int i;

/* Set the screen pointer. Specific to color screens only. */

   screen = MK_FP(0xB800, row*160+col*2);

/* Write the string out to the screen. */

   for ( i = 0; str[i]; i++ )
      {
      *screen++ = str[i];
      *screen++ = attr;
      }
   } 

/* =============================================================================
*********************************** InitBuf ************************************
============================================================================= */
/* Initialize the buffer data members. */

void InitBuf(BUF_STRUCT *ring)
   {
   ring->front = ring->rear = 0;
   ring->char_count = 0;
   }

/* =============================================================================
******************************** GetCharFromBuf ********************************
============================================================================= */
/* This routine gets a character from the ring buffer given by the parameter. 
   We must disable interrupts otherwise this routine may be preempted mid way 
   through the update to the ring buffer variables. */

unsigned char GetCharFromBuf(BUF_STRUCT *ring)
   {
   unsigned char current;

   disable();

/* If characters exist in the buffer, then get one and return it to the calling
   routine. */

   if ( ring->char_count > 0 )
      {

/* Get a character from the front of the buffer. */

      current = ring->buffer[ring->front];

/* Decrement the number in the buffer. */

      ring->char_count--;

/* Move the pointer (index) forward wrapping to the first position when 
   appropriate. */

      ring->front = (ring->front + 1)%BUF_SIZE;
      }

/* No characters - return NULL character to loop. */

   else
      current = '\0';

   enable();
   return(current);
   }

/* =============================================================================
********************************* PutCharInBuf *********************************
============================================================================= */
/* This routine puts a character into the ring buffer if there is room. It is 
   called from the Keyboard ISR defined below. */

void PutCharInBuf(unsigned char c, BUF_STRUCT *ring)
   {

/* Check for ring buffer overflow - character lost if this is the case. */

   if ( ring->char_count >= BUF_SIZE )
      return;

/* Put the character in at the rear of the ring buffer. */

   ring->buffer[ring->rear] = c;

/* Increment the rear pointer - wrapping around to the beginning when 
   necessary. */

   ring->rear = (ring->rear + 1)%BUF_SIZE;

/* Update the number of characters currently in the ring buffer. */

   ring->char_count++;

   return;
   }

/* =============================================================================
********************************** IsBufEmpty **********************************
============================================================================= */
/* This routine simply checks the ring buffer and returns True (1) if it is
   empty. */

Boolean IsBufEmpty(BUF_STRUCT *ring)
   {
   if ( ring->char_count > 0 )
      return(False); 
   else
      return(True); 
   }

/* =============================================================================
************************************* Beep *************************************
============================================================================= */
/* Make a nice disturbing beep sound. DO NOT USE 'delay' HERE. */

void Beep()
   {
   long i = 0;
   sound(554);
   while (i++ < 500000);
   nosound();
   }

/* =============================================================================
********************************* KEYBOARD ISR *********************************
============================================================================= */
/* Scan code to ASCII translations. I've defined the first 70 (the rest are 0). 
   1B - ESC, 8 - backspace, 9 - tab, D - carriage return,
   F - shift in, 1 - F1, 2 - F2, 3 - F3, 4 - F4, 5 - F5,
   6 - F6, 7 - F7, A - F8, B - F9, C - F10  - E is invalid */

char trans_table[128] = 
   { 0xE, 0x1B, '1', '2', '3', '4', '5', '6', '7', '8', 
     '9', '0', '.', '=', 0x8, 0x9, 'q', 'w', 'e', 'r',
     't', 'y', 'u', 'i', 'o', 'p', '[', ']', 0xD, 0xE,
     'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',
     '\'', '`', 0xF, '\\', 'z', 'x', 'c', 'v', 'b', 'n',
     'm', ',', '.', '/', 0xF, 0xE, 0xE, ' ', 0xF, 0x1,
     0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xA, 0xB, 0xC, 0xE };

/* =============================================================================
********************************* NewKeyHandler ********************************
============================================================================= */
/* This routine will be called whenever the user presses a key. This address 
   of this routine replaces the address at interrupt number 0x9 in the 
   vector table. Note that this routine is called TWICE for each key press - 
   once when pressed and once when released. The release call is ignored. */

void interrupt NewKeyHandler(void)
   {
   int scan_code;   
   unsigned char control_port;  
   static Boolean ctrl_flag = False;

/* The input port and control port for the keyboard. */

   scan_code = inportb(0x60);
   control_port = inportb(0x61);

/* TOGGLE (set to 1 and then to 0) bit 7 of the control port to enable the 
   keyboard to put the next char in the data register. */

   outportb( 0x61, control_port | 0x80 );
   outportb( 0x61, control_port );
   
/* Filter out calls caused by the release of the key. These scan_codes are 
   0x80 larger (128 decimal ) than scan_code returned when that key is 
   pressed. */

   if ( scan_code < 128 )
      {
      unsigned char c; 

      if ( scan_code == 29 )
         ctrl_flag = True;
      else if ( trans_table[scan_code] == 'c' && ctrl_flag == True )
         ExitProgram();
      else if ( trans_table[scan_code] == 'f' )
         PutCharInBuf((unsigned char)trans_table[scan_code], &KEY_BUF);
      else if ( scan_code == 75 )
         PutCharInBuf('l', &KEY_BUF);
      else if ( scan_code == 77 )
         PutCharInBuf('r', &KEY_BUF);
      else
         Beep();
      }
   else
      if ( scan_code == 29 + 128 )
         ctrl_flag = False;

/* Send EOI (end of interrupt signal to 8259). */

   outportb( 0x20, 0x20 );

   }

/* =============================================================================
********************************* C_Function ***********************************
============================================================================= */
/* This routine simply allows c function calls (in this case, sprintf) to 
   execute on the system stack since they commonly have a large number of
   nested procedure calls within them. This allows me to reduce the size of each
   of the process stacks. */ 

unsigned int temp_ss;
unsigned int temp_sp;

void C_Function(char *buf, unsigned int val)
   {
   asm cli;
   temp_ss = _SS;
   temp_sp = _SP;

/* Switch to the system stack before calling sprintf. */

   _SS = system_stack_ss;
   _SP = system_stack_sp;
   
   sprintf(buf, "%u", val );

   _SS = temp_ss;
   _SP = temp_sp;
   asm sti;
   }

/* =============================================================================
************************************ EnQueue ***********************************
============================================================================= */
/* Add the process (PCB) given by the second parameter to the Queue given by 
   the first parameter. */

void EnQueue(Queue *queue, struct PCB *proc )
   {

/* ADD YOUR CODE HERE - Update 'next' field of the PCBs as well as 'front' and
   'rear' pointers of the queue. Enqueue puts the PCB on the END of the list.
   Single linked list is sufficient. */

   }

/* =============================================================================
************************************ DeQueue ***********************************
============================================================================= */
/* Remove the process (PCB) from the Queue given by the parameter. */

struct PCB *DeQueue(Queue *queue)
   {

/* ADD YOUR CODE HERE. Update 'next' field of the PCBs as well 'front' and 
   'rear' pointers of the queue. Dequeue puts the PCB on the FRONT of the 
   list. Single linked list is sufficient. */

   }

/* =============================================================================
************************************ Remove ************************************
============================================================================= */
/* This routine is called from the Scheduler when a task is put on the blocked
   queue (because it called Wait and the screen resouce was in use), is being
   removed from the blocked queue (by a process that has finished using the
   screen resouce and has called Signal), when a task is Terminated or when a 
   process (Tank) voluntarily gives up the CPU. It saves the context of the
   currently running process on the stack of that process by pushing the values
   of the registers. Since the NewTimerHandler routine is likely to restore the 
   process and the Timer handler is an ISR, we have to make the stack look like
   an ISR stack by pushing the flags, CS and IP. In order to return to the
   Scheduler, I also have to push the IP again before this routine can return
   successfully. In other words, we will return TWICE to the Scheduler, once
   when Scheduler calls this routine and once when the Timer restores this
   routine. In order to tell the difference in Scheduler, ax is loaded with
   a 0 or a 1 - see the comments below. */

unsigned int save_BP;
unsigned int save_IP;
unsigned int save_CS;

Boolean Remove(void)
   {

/* The Scheduler calls Remove. Right now, we have di, si, bp and ip on the
   stack (If you are curious, use 'bcc -S space.c' to determine that this is
   true.) There is NO cs since this is a near procedure. To make it look like
   an iret whenever we Restore, we need to replace these values with ip, cs
   and flags. */

   asm {

/* Pop off the stack the di and si values pushed by the assembly code generated
   by the compiler. */

      pop ax
      pop ax

/* Save the value of BP since we will need it below. */

      pop ax
      mov save_BP, ax

/* Save the value of the IP (which points to the assembly language statement
   in Scheduler corresponding to the 'if ( Remove() == False )' code in that
   routine) and save it since we will need it below. */

      pop ax
      mov save_IP, ax
      }

#if defined(__MEDIUM__) || defined(__LARGE__) || defined(__HUGE__) 
      asm pop ax;
      asm mov save_CS, ax;
#endif 

/* Push flags and CS (since interrupts are far) followed by IP. */

   asm {
      pushf
      push cs
      mov ax, save_IP
      push ax

/* Save the return value. This will be the value put in AX when Restore is
   called. When we return to Scheduler with this value, the
   'if ( Remove() == False )' comparison will fail - see the Scheduler code. */

      mov ax, 1

/* Push the registers followed by the value of BP. */

      push ax
      push bx
      push cx
      push dx
      push es
      push ds
      push si
      push di
      push save_BP
      }

/* These will be set into the stack registers by the NewTimerHandler in order to
   restore this process to the running state. */

   CURRENT_PROC->proc_ss = _SS;
   CURRENT_PROC->proc_sp = _SP;

/* Now we need to build a second return frame so that we can successfully return
   to the Scheduler. Since this is a normal sub-routine return, all we need to
   do is push the IP and restore the old value of BP. Generate assembly code
   using 'bcc -S space.c' to see that this is done automatically by the
   compiler. Note, we would also NEED to push CS if we compiled this program
   using a LARGE memory model !!!!! */

#if defined(__MEDIUM__) || defined(__LARGE__) || defined(__HUGE__)
      asm mov ax, save_CS;
      asm push ax;
#endif
   asm {
      mov ax, save_IP
      push ax
      mov ax, save_BP
      push ax
      pop bp
      mov ax, 0
      ret
      }

/* This does nothing - we never make it this far but it makes the compiler
   happy. */

   return 0;
   }

/* =============================================================================
*********************************** Restore ************************************
============================================================================= */
/* This routine is called from the Schedular whenever a new process is scheduled
   to run. The NewTimeHandler does NOT call this routine - it just simply 
   changes the stack pointers. This will cause a return to the Scheduler 
   statement 'if ( Remove() == False )' with a value of 1 in ax or it will
   return to directly to the process that was interrupted by the 
   NewTimerHandler if the timer fired and saved the state of this process. Think
   about it a while. */

void Restore()
   {

/* Set the stack pointers to point to the state saved for the process given
   by the CURRENT_PROC PCB and do an iret to restore the registers. */

   _SS = CURRENT_PROC->proc_ss; 
   _SP = CURRENT_PROC->proc_sp;

   asm {
      pop bp
      pop di
      pop si
      pop ds
      pop es
      pop dx
      pop cx
      pop bx
      pop ax
      iret	
      }
   }


/* =============================================================================
********************************** Scheduler ***********************************
============================================================================= */
/* Called from routines Wait, Signal, TerminateTask and any process that 
   voluntarily gives up the CPU. When Scheduler is called, it will call Remove 
   and the 'if ( Remove() == False )' statement will succeed when Remove 
   returns. Remove saves the current state TWICE on the stack of the current 
   process. The first state that is pushed will work if an iret is used to 
   restore it. This will happen from either Restore or when the NewTimerHandler 
   routine re-schedules the process. When either of these routines restore the 
   state of this process, ax will be set with a 1 and and 'if ( Remove() == 
   False )' statement will fail the second time Remove() returns. See Remove 
   for an extended explanation. When Scheduler is called explicitly by one of 
   the routines mentioned above, the parameters will determine what is done 
   with the current process (CURRENT_PROC) and what process will be scheduled 
   to run next. I have found that these two parameters handle all 
   possibilities. You may want to do this differently. */

void Scheduler(struct PCB *proc, Boolean enqueue_current)
   {
   asm cli;
   if ( Remove() == False )
      {

/* ADD YOUR CODE HERE. Enqueue and Dequeue as dictated by the parameters. */

      Restore();
      }

/* Re-enable interrupts. */
 
   asm sti;
   }


/* =============================================================================
************************************* Wait *************************************
============================================================================= */
/* The Wait part of the semaphore defined on page 178 in the text book. */

void Wait(Sema *sema)
   {

/* ADD YOUR CODE HERE. Call Scheduler if Screen is in use. */

   }


/* =============================================================================
************************************* Signal ***********************************
============================================================================= */
/* The Signal part of the semaphore defined on page 178 in the text book. */

void Signal(Sema *sema)
   {

/* ADD YOUR CODE HERE. Call Scheduler if others are waiting on the screen. */

   }


/* =============================================================================
******************************** NewTimerHandler *******************************
============================================================================= */
/* The timer handler that is run 18 times every second. IT MUST BE INSTALLED
   AS THE HANDLER FOR VECTOR 0x8 - DO NOT USE 0x1C since the stack pointer is 
   modified before the 0x1C handler is called !!!!!!!!! This will cause this
   many problems in your debug efforts. */


   {

/* ADD YOUR CODE HERE. Save _SS and _SP in PCB of CURRENT_PROC, call Enqueue
   and Dequeue, set _SS and _SP with new PCB values. */

/* Send a EOI signal so the timer will fire again and restore the new process
   to the running state using an implicit iret (use 'bcc -S space' to convince
   yourself that this occurs). */

   outportb( 0x20, 0x20 );
   }


/* =============================================================================
****************************** AllocTaskStructs ********************************
============================================================================= */
/* This routine allocates the PCBs, stack spaces and parameters for all 
   processes. It should be called in the main program before the first process 
   is 'restored' and made running. */

void AllocTaskStructs(void)
   {
   int i;

/* Allocate the PCB array structure. */

   if (( PCB_LIST = 
      (struct PCB *)malloc(MAX_TASKS * sizeof(struct PCB))) == NULL )
      {
      WriteStrAtPosWithAttr("ERROR: AllocTaskStructs(): Not enough memory for \
PCBs !", NORM_ATTR, STATUS_ROW, 0);
      exit(0);
      }

/* For each PCB structure (process), allocate a stack and parameters 
   structure. */

   for ( i = 0; i < MAX_TASKS; i++ )
      {

/* Mark all PCBs as available. */

      PCB_LIST[i].in_use = False;

/* Mark each PCB with the appropiate type. This is necessary so that the 
   parameters are referenced correctly (see below) and to make the game
   fun to play. */

      if ( i < NUM_TANKS )
         PCB_LIST[i].task_type = Tank;
      else if ( i < NUM_TANKS + NUM_ALIENS )
         PCB_LIST[i].task_type = Alien;
      else 
         PCB_LIST[i].task_type = Bullet;

/* Create stack space for each of the processes. Save the base in the PCB. */

      if (( PCB_LIST[i].stack_base = 
         (unsigned int *)malloc(sizeof(unsigned int)*STACK_SIZE) ) == NULL )
         {
         WriteStrAtPosWithAttr("ERROR: AllocTaskStructs(): No space for \
stack !", NORM_ATTR, STATUS_ROW, 0 );
         exit(0);
         }

/* Allocate a parameter structure for each PCB dependent on the task_type 
   defined above. */

      if ( PCB_LIST[i].task_type == Tank )
         {
         if (( PCB_LIST[i].params_ptr = 
            (TaskParams *)malloc(sizeof(TankParams))) == NULL )
            {
            WriteStrAtPosWithAttr("ERROR: AllocTaskStructs(): No space for \
params !", NORM_ATTR, STATUS_ROW, 0 );
            exit(0);
            }
         }

      if ( PCB_LIST[i].task_type == Alien )
         {
         if (( PCB_LIST[i].params_ptr = 
            (TaskParams *)malloc(sizeof(AlienParams))) == NULL )
            {
            WriteStrAtPosWithAttr("ERROR: AllocTaskStructs(): No space for \
params !", NORM_ATTR, STATUS_ROW, 0 );
            exit(0);
            }
         }

      if ( PCB_LIST[i].task_type == Bullet )
         {
         if (( PCB_LIST[i].params_ptr = 
            (TaskParams *)malloc(sizeof(BulletParams))) == NULL )
            {
            WriteStrAtPosWithAttr("ERROR: AllocTaskStructs(): No space for \
params !", NORM_ATTR, STATUS_ROW, 0 );
            exit(0);
            }
         }
      }
   }

/* =============================================================================
********************************** CreateTask **********************************
============================================================================= */
/* This routine will look for a unused PCB and initialize the PCB structure
   variables with the values necessary to get the process up and running. The
   parameters determine what type of task is to be created and the initial
   values to give to the parameter structure associated with the task. This
   allows one process to control the attributes of another process. */

unsigned int code_seg, instr_ptr;

struct PCB *CreateTask( TaskType tt, unsigned int row, unsigned int col, 
   int direction )
   {
   static unsigned int temp_ss;
   static unsigned int temp_sp;
   static TaskParams *params_ptr;
   static int i, pcb_index;
   static unsigned int num_bytes;

   num_bytes = sizeof(unsigned int) * STACK_SIZE;

/* Not reentrant code - interrupts MUST be off when this 
   code executes - You have to make sure of this in
   the calling code or uncomment this and do an
   'asm sti;' before the return statements in this routine. */

/*   asm cli; */

/* Find an unused PCB. */

   pcb_index = -1;
   for ( i = 0; i < MAX_TASKS; i++ )
      if ( PCB_LIST[i].in_use == False && PCB_LIST[i].task_type == tt )
         {
         pcb_index = i; 
         break;
         }

/* If none are available of the type requested, return NULL. */

   if ( pcb_index == -1 )
      return NULL;

/* Make sure its not pointing to any other PCBs and mark the PCB in use. */

   PCB_LIST[pcb_index].next = NULL;
   PCB_LIST[pcb_index].in_use = True;

/* Get a local pointer to the parameters structure. */

   params_ptr = PCB_LIST[pcb_index].params_ptr; 
      
/* Set the parameter structure values with the parameters passed into this
   routine. ADD YOUR CODE HERE. You may need to add other stuff here 
   yourself. */

   if ( tt == Alien )
      {
      code_seg = FP_SEG(AlienTask);
      instr_ptr = FP_OFF(AlienTask);
      params_ptr->a_p.row = row;
      params_ptr->a_p.col = col;
      }
   else if ( tt == Tank )
      {
      code_seg = FP_SEG(TankTask);
      instr_ptr = FP_OFF(TankTask);
      params_ptr->t_p.row = row;
      params_ptr->t_p.col = col;
      }
   else 
      {
      code_seg = FP_SEG(BulletTask);
      instr_ptr = FP_OFF(BulletTask);
      params_ptr->b_p.row = row;
      params_ptr->b_p.col = col;
      }


/* Save the segment value and the offset of the stack pointer. Note that stacks
   grow toward lower memory. Leave room for the address of the parameter to the
   procedure. Note that FP_OFF(stackptr[i]) is an int - do NOT use STACK_SIZE 
   here since this is in words - not bytes. */

   PCB_LIST[pcb_index].proc_ss = FP_SEG(PCB_LIST[pcb_index].stack_base);
   PCB_LIST[pcb_index].proc_sp = 
      (unsigned int)( FP_OFF(PCB_LIST[pcb_index].stack_base) + num_bytes );

/* Save the current stack values so we can restore them before we return. */

   temp_ss = _SS;
   temp_sp = _SP;

/* Set the Stack registers to point to the process stack of the process being
   created. */

   _SS = PCB_LIST[pcb_index].proc_ss;
   _SP = PCB_LIST[pcb_index].proc_sp;

/* First save the pointer to the parameters so that the processes (functions)
   can reference them correctly. */

#if defined(__MEDIUM__) || defined(__LARGE__) || defined(__HUGE__)
   _AX = FP_SEG(params_ptr);
   asm push ax;
#endif
   _AX = FP_OFF(params_ptr);
   asm push ax;

/* Since the scheduler will be returning into the first line of the task,
   we have to create a dummy stack frame (pretend the task was called via a
   normal call).  Why?  The tasks are assembled using the "enter" and "leave"
   opcodes which automatically generate a stack frame; in doing so they are
   even smart enough to know whether the call to the procedure is near or far
   and offset all parameter variables from bp to "miss" the return address.
   Hence, the below code that pushes either one word (for "near" function
   models) or two words (for "far" function models). */

#if defined(__MEDIUM__) || defined(__LARGE__) || defined(__HUGE__)
      asm mov ax, 0;
      asm push ax;
#endif
   asm {
      mov ax, 0
      push ax

/* Make the stack look like an ISR was called. This will allow us to use an
   iret to restore the state of the process and run it for the first time. */

      pushf
      mov ax, code_seg
      push ax
      mov ax, instr_ptr
      push ax
      push ax         /* AX=BX=CX=DX=0 */
      push ax
      push ax
      push ax
      push es         /* save es, ds */
      push ds
      push ax         /* SI=DI=0 */
      push ax
      push ax         /* BP */
      }

/* Save the new offset of stack - SS would not have changed by the pushes. */

   PCB_LIST[pcb_index].proc_sp = _SP;

/* Restore the stack pointers and return a pointer to the PCB. */

   _SS = temp_ss;
   _SP = temp_sp;

   return &PCB_LIST[pcb_index];
   }

/* =============================================================================
********************************** BulletTask **********************************
============================================================================= */
/* This routine represents the bullet process. It has a parameter, which is a
   pointer to a structure of values used to initialize its position and other
   attributes. This can be done with local (stack) variables also but using
   parameters allows one process to create another process with specific values
   that could not be done reliably through global variables. Since both Tanks
   and Aliens create bullets, we must use parameters to give the bullet a 
   direction which in turn can be used to determine whether an alien or a tank
   created the bullet. BulletTask processes live until their bullets move off 
   the top or bottom of the screen. */

void BulletTask(BulletParams *b_p)
   {

/* Loop until user presses Ctrl-C or some game exit condition is satisfied. */

   while ( DONE == False )
      {

/* ADD YOUR CODE HERE. */

/* Erase the Bullet - NOTE - screen updates MUST call Wait before the update and 
   then Signal after the update is complete. */

/* Update its position. */

/* Check if it is off the screen and TERMINATE task if true - call TerminateTask
   which calls Scheduler to schedule a new task on the Ready Queue. */

/* Redisplay bullet at the new position. */

/* Check for a hit on a Tank or an Alien by scanning PCBs and examining the 
   pointer to the parameters saved in each PCB. */

      }

/* Clean up and exit. */

   exit(0);
   }

/* =============================================================================
*********************************** AlienTask **********************************
============================================================================= */
/* This routine represents the alien process. AlienTask generates its new 
   position using a random number generator -> 'rand()' - this function returns
   a value between 0 and 2^15 - 1. */

void AlienTask(AlienParams *a_p)
   {

/* Loop until user presses Ctrl-C or some game exit condition is satisfied. */

   while ( DONE == False )
      {

/* ADD YOUR CODE HERE. */

/* Check for a hit by the Tank - if true, update game variables, erase Alien 
   and Terminate the Alien task. */ 

/* Erase the Alien task. Update its column position using 'rand()' and possibly 
   its row position. Redraw the Alien. */

/* Fire a bullet occasionally by calling CreateTask/MakeReady. */

      }

/* Clean up and exit. */

   exit(0);
   }


/* =============================================================================
********************************** TankTask ************************************
============================================================================= */
/* This routine represents the tank process. TankTask processes keyboard input 
   and puts itself to sleep if nothing is on the keyboard buffer. */

void TankTask(TankParams *t_p)
   {

/* ADD YOUR CODE HERE. */

/* Loop until user presses Ctrl-C or some game exit condition is satisfied. 
   You may want to draw the Tank originally before the while loop because of the 
   condition below. */

   while( DONE == False )
      {

/* ADD YOUR CODE HERE. */

/* Check if the Tank has been hit. Update game variables. DO NOT TERMINATE
   TANKS since you must have at least one process running at all times. */

/* May want to create new Aliens if none exist. */

/* Check the Keyboard buffer - if a character exists get it otherwise, you
   MUST call the Scheduler (voluntarily give up CPU). */

/* Erase the Tank. Update its column position by examining the keyboard 
   input - 'l' means move left and 'r' means move right. Redraw the Tank. */

/* Check if a bullet was fired - the keyboard ISR will put an 'f' character
   on the ring buffer when the user presses 'f' on the keyboard. If so, 
   create a BulletTask. */

      }

/* Clean up and exit. */

   exit(0);
   }


/* =============================================================================
************************************* main *************************************
============================================================================= */

void main(void)
   {
   Boolean first_fnd = False;
   char c;
   int i;

/* ADD CODE HERE. Initialize the global data structures. */
   ClearScreen();

   InitBuf(&KEY_BUF);
   InitSema(&SCREEN_SEMA);
   InitQueue(&READY_QUEUE);

   PCB_LIST = NULL;

/* Allocate the data structures (PCBs, Stacks, etc) for all processes. */

   AllocTaskStructs();

/* ADD CODE HERE. Create an initial set of tasks, e.g., */

   CreateTask(Alien, 0, 10, 0);
   CreateTask(Alien, 3, 20, 0);
   CreateTask(Alien, 6, 30, 0);
   CreateTask(Tank, LASTROW, 40, 0);

/* Set CURRENT_PROC to one of the processes and queue up the others on the 
   READY_QUEUE. */

   for ( i = 0; i < MAX_TASKS; i++ )
      if ( PCB_LIST[i].in_use == True )
         {
         if ( first_fnd == False )
            {
            first_fnd = True;
            CURRENT_PROC = &PCB_LIST[i];         
            }
         else
            MakeReady(&PCB_LIST[i]);
         }

   asm cli;

/* Install the Keyboard and Timer ISR. YOU MUST USE 0x8 and NOT 0x1C for
   the Timer. See the note given in the header comment for NewTimerHandler. */

   oldkeyhand = getvect(0x9);
   setvect(0x9, NewKeyHandler);

/* DO NOT MOVE THIS ABOVE CreateTask and MakeReady SINCE THOSE ROUTINES ARE
   NOT PROTECTED WITH cli and sti !!!!!!!!!! Install the Timer ISR. YOU 
   MUST USE 0x8 and NOT 0x1C. See the note given in the header comment for
   NewTimerHandler. */

   oldtimer = getvect(0x8); 
   setvect( 0x8, NewTimerHandler );

/* Save the system stack register values for C_Function. See the comment in
   the header for that routine. */

   system_stack_ss = _SS;
   system_stack_sp = _SP;

/* Load the context (state) of the current process using the PCB pointed to
   by CURRENT_PROC by resetting the stack registers as defined in CreateTask. 
   Note that iret will pop the ip, cs and flags registers in that order. */

   _SS = CURRENT_PROC->proc_ss;
   _SP = CURRENT_PROC->proc_sp;

   asm {
      pop bp; 
      pop di; 
      pop si; 
      pop ds;
      pop es;
      pop dx; 
      pop cx; 
      pop bx; 
      pop ax;
      iret;
      }

   asm iret;

/* Program should never come back here */
   }

