iPhone Port pt 4 - Touch Handling

We are in the home stretch now. After this session, the game will be playable. In the Cocoa Port, I used the keyboard to send the button events to the machine. Obviously the iPhone version is going to have to use screen touches to send events to the machine.

In the machine code, we'll have to take those events and set the appropriate bits in the input port reads. The game's code running in the 8080 emulator is already polling the ports for button presses, it just doesn't see any.

Accepting key events in the machine

I'll add 2 methods to SpaceInvadersMachine.m, ButtonDown and ButtonUp. When ButtonDown is called, set the corresponding bit in the input port. When ButtonUp is called, clear the corresponding bit. The code looks a lot like we presented a few sections back:

   - (void) ButtonDown: (uint8_t) key    
   {    
       switch (key)    
       {    
           case EVENT_COIN:    
               in_port1 |= 0x1;    
               break;    
           case EVENT_P1_LEFT:    
               in_port1 |= 0x20;    
               break;    
           case EVENT_P1_RIGHT:    
               in_port1 |= 0x40;    
               break;    
        /*.... etc.....*/    
       }    
   }

   - (void) ButtonUp: (uint8_t) key    
   {    
       switch (key)    
       {    
           case EVENT_COIN:    
               in_port1 &= ~0x1;    
               break;    
           case EVENT_P1_LEFT:    
               in_port1 &= ~0x20;    
               break;    
        /*.... etc.....*/    
       }    
   }    

I'll make a new class variable called in_port1 to track the state of port 1, then modify InSpaceInvaders to return that value instead of "1".

Getting the touch events

I'm going to make the controls be tied to "touch zones" or just fixed areas on the screen. I'll map the coin to the top left corner, the 1P start to the top right, and left, right, and fire underneath the screen.

I'll set this up one time in the setupGL routine in the GameViewController like this:


   //at the top of the file    
   struct TouchZones {    
       CGRect bounds;    
       uint8_t message;    
       uint8_t keystate_index;    
   };    

   //into @interface GameViewController ()    
    struct TouchZones tz[6];    

   //at the bottom of setupGL    
       //define touch zones    
       float  width = self.view.bounds.size.width;    
       float  bottom = self.view.bounds.size.height;    

       tz[0].bounds         = CGRectMake(0.0, 0.0, width/2, 150.0);    
       tz[0].message        = BUTTON_COIN;    
       tz[0].keystate_index = 0;    
       tz[1].bounds         = CGRectMake(width/2, 0.0, width/2, 150.0);    
       tz[1].message        = BUTTON_P1_START;    
       tz[1].keystate_index = 1;    
       tz[2].bounds         = CGRectMake(0.0, bottom-150.0, width/4, 150.0);    
       tz[2].message        = BUTTON_P1_LEFT;    
       tz[2].keystate_index = 2;    
       tz[3].bounds         = CGRectMake(width/4, bottom-150.0, width/2, 150.0);    
       tz[3].message        = BUTTON_P1_RIGHT;    
       tz[3].keystate_index = 3;    
       tz[4].bounds         = CGRectMake(width/2, bottom-150.0, width/2, 150.0);    
       tz[4].message        = BUTTON_P1_FIRE;    
       tz[4].keystate_index = 4;    
       tz[5].message        = 0;    
       tz[5].keystate_index = 0;    

The game template already has user interaction enabled set in the xib. So all we have to do is implement the touch routines.

   -(void) checkTouches:(NSSet *)touches ending:(BOOL) ending    
   {    
       uint8_t newstates[5] = {0,0,0,0,0};

       if ([touches count] > 0)    
       {    
           for (UITouch *touch in touches)    
           {    
               CGPoint  p = [touch locationInView:self.view];    
               struct TouchZones *z = tz;    

               while (z->message != 0)    
               {    
                   if ((CGRectContainsPoint(z->bounds, p)) && ! ending)    
                       newstates[z->keystate_index] = 1;    
                   if ((CGRectContainsPoint(z->bounds, p)) && ending)    
                       newstates[z->keystate_index] = 0;    

                   z++;    
               }    
           }    
       }    

       //Now we have an array of all the touches down.    
       //Compare them against the last touches:    
       // if the state has changed from not-touched to touched, send a key down    
       // if the state has changes from touched to not-touched, send a key up    
       struct TouchZones *z = tz;    

       while (z->message != 0)    
       {    
           int i = z->keystate_index;    
           if ((newstates[i] == 0) && (last_touchstate[i] == 1))    
               [invaders ButtonUp:z->message];    
           else if ((newstates[i] == 1) && (last_touchstate[i] == 0))    
               [invaders ButtonDown:z->message];    
           last_touchstate[i] = newstates[i];    
           z++;    
       }    

   }    

   - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event {    
    [self checkTouches:[event allTouches] ending:NO];    
   }    

   - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {    
    [self checkTouches:[event allTouches] ending:YES];    
   }    

   - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {    
    [self checkTouches:[event allTouches] ending:YES];    
   }    

   - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {    
    [self checkTouches:[event allTouches] ending:NO];    
   }    

I have to keep track of the last set of states to know if I need to send a new event to the machine or not. I keep track of that in the last_touchstate class variable.

When a touch event is received, I loop through the TouchZones list seeing if the location matches any of the zones. If so, and if its state has changed since the previous check, it sends an event to the machine.

El crashola

Once I hooked this up and tapped the top-left to put in a coin, I saw

   Error: Unimplemented instruction    
   0037 DAA    

Oops. I didn't think that DAA was necessary so I didn't implement it or the auxiliary carry (AC) flag. Let me look at the data book.... It says

The eight-bit number in the accumulator is adjusted to form two four-bit Binary-Coded-Decimal digits by the following Process:

  1. If the value of the least significant 4 bits of the accumulator is greater than 9 OR if the AC flag is set, 6 is added to the accumulator.

  2. If the value of the most significant 4 bits of the accumulator is now greater than 9, or if the CY flag is set, 6 is added to the most significant 4 bits of the accumulator.

I think all the game is going to do is convert the hex score to base-10 for display. I do not think it is going to do base-10 math, so I think I can get away with:

    case 0x27:                          //DAA    
        if ((state->a &0xf) > 9)    
            state->a += 6;    
        if ((state->a&0xf0) > 0x90)    
        {    
            uint16_t res = (uint16_t) state->a + 0x60;    
            state->a = res & 0xff;    
            ArithFlagsA(state, res);    
        }    
        break;    

And trying not to worry about AC not being set by any previous math operations. I tried this and I haven't seen any problems with number display yet.

It Works!

What do you know - the game is playable! How great is that? The only thing left to do is the sound.

My code with the touch handling is in the github project under iphonePart4-TouchHandling

← Prev: xcode-debug-tools   Next: iphone-port-pt-5---sound →