简体   繁体   中英

Design pattern for waiting for user interaction in iOS?

I'm developing a BlackJack game for iOS. Keeping track of the current state and what needs to be done is becoming difficult. For example, I have a C++ class which keeps track of the current Game:

class Game {
  queue<Player> playerQueue;
  void hit();
  void stand();
}

Currently I'm implementing it using events (Method A):

- (void)hitButtonPress:(id)sender {
  game->hit();
}

void Game::hit() {
  dealCard(playerQueue.top());
}

void Game::stand() {
  playerQueue.pop();
  goToNextPlayersTurn();
}

as more and more options are added to the game, creating events for each one is becoming tedious and hard to keep track of.

Another way I thought of implementing it is like so (Method B):

void Game::playersTurn(Player *player) {
  dealCards(player);
  while (true) {
    string choice = waitForUserChoice();
    if (choice == "stand") break;
    if (choice == "hit")
      dealCard(player);
    // etc.
  }
  playerQueue.pop();
  goToNextPlayersTurn();
}

Where waitForUserChoice is a special function that lets the user interact with the UIViewController and once the user presses a button, only then returns control back to the playersTurn function. In other words, it pauses the program until the user clicks on a button.

With method A, I need to split my functions up every time I need user interaction. Method B lets everything stay a bit more in control. Essentially the difference between method A and B is the following:

A:

function A() {
  initialize();
  // now wait for user interaction by waiting for a call to CompleteA
}

function CompleteA() {
  finalize();
}

B:

function B() {
  initialize();
  waitForUserInteraction();
  finalize();
}

Notice how B keeps the code more organized. Is there even a way to do this with Objective-C? Or is there a different method which I haven't mentioned recommended instead?

A third option I can think of is using a finite state machine. I have heard a little about them, but I'm sure if that will help me in this case or not.

What is the recommended design pattern for my problem?

I understand the dilemma you are running into. When I first started iOS I had a very hard time wrapping my head around relinquishing control to and from the operating system.

In general iOS would encourage you to go with method A. Usually you have variables in your ViewController which are set in method A(), and then they are checked in CompleteA() to verify that A() ran first etc.

Regarding your question about Finite State Machines, I think that it may help you solve your problem. The very first thing I wrote in iOS was a FSM (there for this is pretty bad code) however you can take a look here (near the bottom of FlipsideViewController.m:

https://github.com/esromneb/ios-finite-state-machine

The general idea is that you put this in your .h file inside an @interface block

static int state = 0;
static int running = 0;

And in your .m you have this:

- (void) tick {

    switch (state) {
        case 0:
            //this case only runs once for the fsm, so setup one time initializations

            // next state
            state = 1;

            break;
        case 1:
            navBarStatus.topItem.title = @"Connecting...";
            state = 2;
            break;
        case 2:
            // if something happend we move on, if not we wait in the connecting stage
            if( something )
                state = 3;
            else
                state = 1;
            break;
        case 3:
            // respond to something

            // next state
            state = 4;
            break;
        case 4:
            // wait for user interaction
            navBarStatus.topItem.title = @"Press a button!";
            state = 4;

            globalCommand = userInput;

            // if user did something
            if( globalCommand != 0 )
            {
                // go to state to consume user interaction
                state = 5;  
            }

            break;

        case 5:
            if( globalCommand == 6 )
            {
                // respond to command #6
            }
            if( globalCommand == 7 )
            {
                // respond to command #7
                }

                        // go back and wait for user input
                        state = 4;
            break;

        default:
            state = 0;
            break;
    }

    if( running )
    {
        [self performSelector:@selector(tick) withObject:nil afterDelay:0.1];
    }
}

In this example (modified from the one on github) globalCommand is an int representing the user's input. If globalCommand is 0, then the FSM just spins in state 4 until globalCommand is non zero.

To start the FSM, simply set running to 1 and call [self tick] from the viewController. The FSM will "tick" every 0.1 seconds until running is set to 0.

In my original FSM design I had to respond to user input AND network input from a windows computer running it's own software. In my design the windows PC was also running a similar but different FSM. For this design, I built two FIFO queue objects of commands using an NSMutuableArray. User interactions and network packet would enqueue commands into the queues, while the FSM would dequeue items and respond to them. I ended up using https://github.com/esromneb/ios-queue-object for the queues.

Please comment if you need any clarification.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM