The Java TM Tutorial

| Start of Tutorial > Start of Trail > Start of Lesson | Search

Trail: Putting It All Together
Lesson: BINGO!

## Coordinating Threads in the Game

> The `RingMaster` class provides two methods that the `GamesThread` uses to coordinate its activities with the other threads in the Game application: `waitForFirstPlayer` and `waitForGameToEnd`. As the names of these methods imply, these methods cause `GamesThread` to wait until for a condition set by another thread.

### Waiting for the First Player to Register

> The `run` method for `GamesThread` contains a loop that continues until told to stop by the `ControlPane`. Each iteration through the loop is a single BINGO game. At the top of the loop, and therefore at the beginning of each BINGO game, the `run` method for `GamesThread` calls `RingMaster`'s `waitForFirstPlayer` method which looks like this:

> >     synchronized void waitForFirstPlayer() {
> >         gameNumber++;
> >         state = WAITING;
> >         socketGate.sendGameStatusMessage(statusString());

> >         while (state == WAITING) {
> >             try {
> >                wait();
> >             } catch (InterruptedException e) {
> >             }
> >         }

> >         socketGate.sendGameStatusMessage("Beginning count down ... ");
> >     }

> In plain English, this method causes the `GamesThread` to wait until the first Player registers. Let's look at the code to find out how.

> First the method sets the `RingMaster`'s `state` to `WAITING`. Then, `waitForFirstPlayer` enters a loop that causes the current thread to `wait` as long as `state` is still `WAITING`. Which begs the question "How can this loop ever end?" The answer is that another thread, a thread in the Player application changes `state` and wakes up the `GamesThread`. Here's how.

> To register, a Player application makes a remote method call to `RegistrarImpl`'s `mayIPlay` method. Part of the registration process involves adding a `PlayerRecord` to the `Roster` which, if this is the first player to register, also involves calling the `RingMaster`'s `startCountDown` method. `startCountDown` looks like this:

> >     synchronized void startCountDown() {
> >         state = COUNTINGDOWN;
> >         notifyAll();
> >     }

> This method changes the `state` to `COUNTINGDOWN` and then calls `notifyAll`. `notifyAll` causes `GamesThread` to return from its `wait` method. Remember that the call to `wait` is in a loop that continues as long as `state` is `WAITING`. So after `GamesThread` returns from `wait`, it checks the `state`, which is no longer `WAITING` and so exits the loop. So this method causes `GamesThread` to return from `waitForFirstPlayer`.

> Thus when `GamesThread` calls `waitForFirstPlayer` the effect is that the threads stops and waits until the first player registers for the game. Then the count down begins.

> [PENDING: consider diagraming the flow of this using diagrams similar to Doug Lea's]

### Waiting for the Current Game to End

> `GamesThread` uses a very similar mechanism to wait until the current game is over. A game ends when a Player wins or the `BallAnnouncer` runs out of balls). The code is very similar to that used by `GamesThread` to wait for the first player to register so we'll briefly point out the classes and methods involved and let you figure out the rest.

> After `GamesThread` has created a `BallAnnouncer` and started it, `GamesThread` calls `RingMaster`'s `waitForGameToEnd` method which looks like this:

> >     synchronized void waitForGameToEnd() {
> >         while (gameInProgress()) {
> >             try {
> >                wait();
> >             } catch (InterruptedException e) {
> >             }
> >         }
> >     }

> Like the `waitForFirstPlayer` method, this method waits until the `state` changes and indicates that the game is no longer in progress. This causes the `GamesThread` to go into a wait state and do nothing.

> The `state` gets changed when `RingMaster`'s `setGameOver` method gets called.

> >     synchronized void setGameOver() {
> >         state = GAMEOVER;
> >         announceBall(new BingoBall(BingoBall.GAME_OVER));
> >         announcedBalls.removeAllElements();
> >         announcedBalls.push(new BingoBall(BingoBall.FREE_SPACE));
> >         roster.removeAllElements();
> >         notifyAll();
> >     }

> `setGameOver` can be called under two conditions:
> 1. A Player won the game. When a Player wins, it remotely calls `RegistrarImpl`'s `BINGO` method which verifies the claim. If the BINGO claim is valid, then `BINGO` calls `setGameOver`.
> 2. The `BallAnnouncer` announced all of the balls and there was no winning claim from any Player. In this case the `BallAnnouncer` calls `setGameOver`.
> `setGameOver` changes the `RingMaster`'s `state` and then calls `notifyAll` which wakes up `GamesThread`.

| Start of Tutorial > Start of Trail > Start of Lesson | Search