This is RiskGameManager.m in view mode; [Download] [Up]
// // This file is a part of Risk by Mike Ferris. // #import "Risk.h" RCSID ("$Id: RiskGameManager.m,v 1.7 1997/12/18 21:03:46 nygard Exp $"); #import "RiskGameManager.h" #import "ArmyPlacementValidator.h" #import "ArmyView.h" #import "BoardSetup.h" #import "CardPanelController.h" #import "CardSet.h" #import "Country.h" #import "DiceInspector.h" #import "GameConfiguration.h" #import "RiskCard.h" #import "RiskMapView.h" #import "RiskPlayer.h" #import "RiskWorld.h" #import "SNRandom.h" #import "StatusView.h" #import "WorldInfoController.h" //====================================================================== // The RiskGameManager controls most of the game play. It notifies // the players of the various phases of game play, and does some // checking of messages to try to limit invalid actions by players (or // some cheating.) //====================================================================== DEFINE_NSSTRING (RGMGameOverNotification); #define AGSReason(state1) [NSString stringWithFormat:@"Current game state is (Player %d, %@). Expected game state to be %@.", currentPlayerNumber, NSStringFromGameState (gameState), NSStringFromGameState (state1)] #define AGSReason2(state1, state2) [NSString stringWithFormat:@"Current game state is (Player %d, %@). Expected game state to be %@ or %@.", currentPlayerNumber, NSStringFromGameState (gameState), NSStringFromGameState (state1), NSStringFromGameState (state2)] #define AGSReason3(state1, state2, state3) [NSString stringWithFormat:@"Current game state is (Player %d, %@). Expected game state to be %@, %@ or %@.", currentPlayerNumber, NSStringFromGameState (gameState), NSStringFromGameState (state1), NSStringFromGameState (state2), NSStringFromGameState (state3)] #define AGSReason4(state1, state2, state3, state4) [NSString stringWithFormat:@"Current game state is (Player %d, %@). Expected game state to be %@, %@, %@ or %@.", currentPlayerNumber, NSStringFromGameState (gameState), NSStringFromGameState (state1), NSStringFromGameState (state2), NSStringFromGameState (state3), NSStringFromGameState (state4)] #define AssertGameState(state1) NSAssert1 (gameState == state1, @"%@", AGSReason (state1)) #define AssertGameState2(state1, state2) NSAssert1 (gameState == state1 || gameState == state2, @"%@", AGSReason2 (state1, state2)) #define AssertGameState3(state1, state2, state3) NSAssert1 (gameState == state1 || gameState == state2 || gameState == state3, @"%@", AGSReason3 (state1, state2, state3)) #define AssertGameState4(state1, state2, state3, state4) NSAssert1 (gameState == state1 || gameState == state2 || gameState == state3 || gameState == state4, @"%@", AGSReason4 (state1, state2, state3, state4)) #define RiskGameManager_VERSION 1 @implementation RiskGameManager + (void) initialize { if (self == [RiskGameManager class]) { [self setVersion:RiskGameManager_VERSION]; } } //---------------------------------------------------------------------- - init { NSString *nibFile; BOOL loaded; int l; if ([super init] == nil) return nil; world = nil; mapView = nil; phaseComputerMove = nil; phasePlaceArmies = nil; phaseAttack = nil; phaseFortify = nil; phaseChooseCountries = nil; currentPhaseView = nil; rng = [[SNRandom instance] retain]; nibFile = @"GameBoard.nib"; loaded = [NSBundle loadNibNamed:nibFile owner:self]; if (loaded == NO) { NSLog (@"Could not load %@.", nibFile); [super dealloc]; return nil; } configuration = [[GameConfiguration alloc] init];; activePlayerCount = 0; for (l = 0; l < MAX_PLAYERS; l++) { players[l] = nil; playersActive[l] = NO; } initialArmyCount = 0; gameState = gs_no_game; currentPlayerNumber = 0; cardPanelController = nil; cardDeck = nil; discardDeck = [[NSMutableArray array] retain]; playerHasConqueredCountry = NO; armyPlacementValidator = nil; nextCardSetValue = 4; diceInspector = nil; worldInfoController = nil; toolMenu = [[[NSApp mainMenu] itemWithTitle:@"Tools"] target]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (defaultsChanged:) name:RiskBoardSetupPlayerColorsChangedNotification object:nil]; return self; } //---------------------------------------------------------------------- // Never really gets dealloc'd - (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [self stopGame]; SNRelease (world); SNRelease (phaseComputerMove); SNRelease (phasePlaceArmies); SNRelease (phaseAttack); SNRelease (phaseFortify); SNRelease (phaseChooseCountries); SNRelease (cardDeck); SNRelease (discardDeck); SNRelease (cardPanelController); SNRelease (armyPlacementValidator); SNRelease (diceInspector); SNRelease (worldInfoController); SNRelease (rng); [super dealloc]; } //---------------------------------------------------------------------- - (void) awakeFromNib { NSView *tmp1, *tmp2; // The windows should not be visible at launch time, so we can // move them to the default locations. [mapWindow setFrameAutosaveName:[mapWindow title]]; [mapWindow orderFront:self]; [controlPanel setFrameAutosaveName:[controlPanel title]]; [controlPanel orderFront:self]; [[phaseComputerMove retain] removeFromSuperview]; [[phasePlaceArmies retain] removeFromSuperview]; [[phaseAttack retain] removeFromSuperview]; [[phaseFortify retain] removeFromSuperview]; [[phaseChooseCountries retain] removeFromSuperview]; // Try to make sure the map view doesn't obscure any peer // views when it redraws. It must also set it's superview // to need display whenever it needs display. tmp1 = [mapView superview]; tmp2 = [mapView retain]; [tmp2 removeFromSuperview]; [tmp1 addSubview:tmp2 positioned:NSWindowBelow relativeTo:nil]; [tmp2 release]; #ifdef __APPLE_CPP__ // We don't want to have to validate the items like we do menu items. [attackMethodPopup setAutoenablesItems:NO]; #endif } //---------------------------------------------------------------------- - (void) _logGameState { NSString *str; switch (gameState) { case gs_no_game: str = @"No game"; break; case gs_establishing_game: str = @"Establishing game."; break; case gs_choose_countries: str = [NSString stringWithFormat:@"Choose countries -- player %d.", currentPlayerNumber]; break; case gs_place_initial_armies: str = [NSString stringWithFormat:@"Place initial armies -- player %d.", currentPlayerNumber]; break; case gs_place_armies: str = [NSString stringWithFormat:@"Place armies -- player %d.", currentPlayerNumber]; break; case gs_attack: str = [NSString stringWithFormat:@"Attack -- player %d.", currentPlayerNumber]; break; case gs_move_attacking_armies: str = [NSString stringWithFormat:@"Move attacking armies -- player %d.", currentPlayerNumber]; break; case gs_fortify: str = [NSString stringWithFormat:@"Fortify -- player %d.", currentPlayerNumber]; break; case gs_place_fortifying_armies: str = [NSString stringWithFormat:@"Place fortifying armies -- player %d.", currentPlayerNumber]; break; default: str = @"<Unknown>."; break; } NSLog (@"-- Game state: %@.", str); } //---------------------------------------------------------------------- - (void) showControlPanel:sender { [controlPanel orderFront:nil]; } //---------------------------------------------------------------------- - (void) showDiceInspector:sender { if (diceInspector == nil) { diceInspector = [[DiceInspector alloc] init]; } [diceInspector showPanel]; } //---------------------------------------------------------------------- - (void) showWorldInfoPanel:sender { if (worldInfoController == nil) { worldInfoController = [[WorldInfoController alloc] init]; [worldInfoController setWorld:world]; } [worldInfoController showPanel]; } //---------------------------------------------------------------------- - (BOOL) validateMenuItem:(NSMenuItem *)menuCell { SEL action; BOOL valid; int tag; valid = NO; action = [menuCell action]; tag = [menuCell tag]; if (action == @selector (showPlayerConsole:)) { NSAssert (tag > 0 && tag < MAX_PLAYERS, @"Tag for player number out of range."); if (players[tag] != nil) valid = YES; } else if (action == @selector (showControlPanel:) || action == @selector (showDiceInspector:) || action == @selector (showWorldInfoPanel:)) { valid = YES; } return valid; } //---------------------------------------------------------------------- // Set the country name in the map view. Forward event to current player. // This is the delegate method of RiskMapView. - (void) mouseDown:(NSEvent *)theEvent inCountry:(Country *)aCountry { [countryNameTextField setStringValue:[aCountry countryName]]; if (players[currentPlayerNumber] != nil) [players[currentPlayerNumber] mouseDown:theEvent inCountry:aCountry]; } //====================================================================== // General access to world data //====================================================================== - (RiskWorld *) world { return world; } //---------------------------------------------------------------------- // Set the world to be used for the game. - (void) setWorld:(RiskWorld *)newWorld { // Can't change world while game is in progress. AssertGameState (gs_no_game); SNRelease (world); world = [newWorld retain]; [mapView setCountryArray:[[world allCountries] allObjects]]; SNRelease (armyPlacementValidator); armyPlacementValidator = [[ArmyPlacementValidator alloc] initWithRiskWorld:world]; } //---------------------------------------------------------------------- - (GameConfiguration *) gameConfiguration { return configuration; } //---------------------------------------------------------------------- - (void) setGameConfiguration:(GameConfiguration *)newGameConfiguration { // Can't change the rules of a game in progress. AssertGameState (gs_no_game); SNRelease (configuration); configuration = [newGameConfiguration retain]; } //---------------------------------------------------------------------- - (GameState) gameState { return gameState; } //====================================================================== // For status view. //====================================================================== - (BOOL) isPlayerActive:(Player)number { NSAssert (number > 0 && number < MAX_PLAYERS, @"Player number out of range."); return playersActive[number]; } //---------------------------------------------------------------------- - (Player) currentPlayerNumber { return currentPlayerNumber; } //---------------------------------------------------------------------- - (int) activePlayerCount { return activePlayerCount; } //---------------------------------------------------------------------- - (RiskPlayer *) playerNumber:(Player)number { NSAssert (number > 0 && number < MAX_PLAYERS, @"Player number out of range."); return players[number]; } //====================================================================== // Player menu support //====================================================================== - (void) showPlayerConsole:sender { int tag; tag = [sender tag]; NSAssert (tag > 0 && tag < MAX_PLAYERS, @"Tag for player number out of range."); if (players[tag] != nil) [players[tag] showConsolePanel:self]; } //====================================================================== // Establish Game //====================================================================== - (void) startNewGame { NSEnumerator *countryEnumerator; Country *country; NSAssert ([self gameInProgress] == NO, @"Game already in progress."); countryEnumerator = [[world allCountries] objectEnumerator]; [[mapView window] disableFlushWindow]; while (country = [countryEnumerator nextObject]) { [country setPlayerNumber:0]; } //[mapView display]; // This is because drawCountry: doesn't draw the background... and it probably should. [[mapView window] enableFlushWindow]; gameState = gs_establishing_game; // Set up card and discard decks. cardDeck = [[world cards] mutableCopy]; nextCardSetValue = 4; } //---------------------------------------------------------------------- - (BOOL) addPlayer:(RiskPlayer *)aPlayer number:(Player)number { // Can only add players while establishing a new game. AssertGameState (gs_establishing_game); NSAssert (players[number] == nil, @"Already have a player in that slot."); //NSAssert ([type isKindOfClass:[RiskPlayer class]] == YES, @"Player class must be a subclass of RiskPlayer."); players[number] = [aPlayer retain]; playersActive[number] = YES; activePlayerCount++; [players[number] setPlayerToolMenu:[[toolMenu itemWithTitle:[NSString stringWithFormat:@"Player %d", number]] target]]; return YES; } //---------------------------------------------------------------------- - (void) beginGame { // Can't begin a game that hasn't been established AssertGameState (gs_establishing_game); // Calculate initial army count initialArmyCount = RiskInitialArmyCountForPlayers (activePlayerCount); [self tryToStart]; } //---------------------------------------------------------------------- - (void) tryToStart { // Turn done... [self endTurn]; } //---------------------------------------------------------------------- - (void) stopGame { int l; gameState = gs_no_game; for (l = 1; l < MAX_PLAYERS; l++) { [self deactivatePlayerNumber:l]; } activePlayerCount = 0; currentPlayerNumber = 0; // cardDeck will be a mutable copy of the world cards. SNRelease (cardDeck); [discardDeck removeAllObjects]; if (currentPhaseView != nil) { [currentPhaseView removeFromSuperview]; currentPhaseView = nil; } [[NSNotificationCenter defaultCenter] postNotificationName:RGMGameOverNotification object:self]; } //====================================================================== // Game State //====================================================================== - (BOOL) gameInProgress { return gameState != gs_no_game; } //---------------------------------------------------------------------- - (void) enteringChooseCountriesPhase { currentPlayerNumber = 0; while ([self nextActivePlayer] == NO) { [players[currentPlayerNumber] willBeginChoosingCountries]; } } //---------------------------------------------------------------------- - (void) leavingChooseCountriesPhase { currentPlayerNumber = 0; while ([self nextActivePlayer] == NO) { [players[currentPlayerNumber] willEndChoosingCountries]; } } //---------------------------------------------------------------------- - (void) enteringInitialArmyPlacementPhase { currentPlayerNumber = 0; while ([self nextActivePlayer] == NO) { [players[currentPlayerNumber] willBeginPlacingInitialArmies]; } } //---------------------------------------------------------------------- - (void) leavingInitialArmyPlacementPhase { currentPlayerNumber = 0; while ([self nextActivePlayer] == NO) { [players[currentPlayerNumber] willEndPlacingInitialArmies]; } } //---------------------------------------------------------------------- // This method changes the game state to the next state, and sets up the // user interface elements for that state. The normal // state progression is: // // No Game -> Establishing Game -> Choose Countries -> Place Initial Armies // -> Place Armies -> Attack -> Fortify // -> Place Armies -> Attack -> Fortify // -> Place Armies -> Attack -> Fortify ... // // // In addition, there are special methods to enter other game states. //---------------------------------------------------------------------- // Option to skip fortify phase? - (void) endTurn { BOOL isInteractivePlayer; NSView *newPhaseView; FortifyRule fortifyRule; int count, tmp; NSAssert (gameState != gs_no_game /*&& gameState != gs_establishing_game*/, @"No game in progess."); //[self _logGameState]; // 1. Determine next phase. // 2. Setup UI elements for that phase (switched view). // 3. Initiate the phase. // Determine next phase. switch (gameState) { case gs_establishing_game: gameState = gs_choose_countries; if ([configuration initialCountryDistribution] == RandomlyChosen) { [self randomlyChooseCountriesForActivePlayers]; gameState = gs_place_initial_armies; [self enteringInitialArmyPlacementPhase]; } else { [self enteringChooseCountriesPhase]; } currentPlayerNumber = 0; [self nextActivePlayer]; break; case gs_choose_countries: if ([[self unoccupiedCountries] count] > 0) { [mapView selectCountry:nil]; [self nextActivePlayer]; } else { [self leavingChooseCountriesPhase]; gameState = gs_place_initial_armies; [self enteringInitialArmyPlacementPhase]; currentPlayerNumber = 0; [self nextActivePlayer]; } break; case gs_place_initial_armies: [mapView selectCountry:nil]; if ([self nextActivePlayer] == YES) { initialArmyCount -= [configuration armyPlacementCount]; } //NSLog (@"initial army count: %d", initialArmyCount); if (initialArmyCount < 1) { [self leavingInitialArmyPlacementPhase]; gameState = gs_place_armies; currentPlayerNumber = 0; [self nextActivePlayer]; [players[currentPlayerNumber] willBeginTurn]; [self setArmiesLeftToPlace:[self earnedArmyCountForPlayer:currentPlayerNumber]]; } break; case gs_place_armies: if (armiesLeftToPlace > 0) { NSLog (@"Player %d has %d unplaced armies.", currentPlayerNumber, armiesLeftToPlace); } gameState = gs_attack; break; case gs_attack: if (playerHasConqueredCountry == YES) { // Deal a card to the player. [self dealCardToPlayerNumber:currentPlayerNumber]; } gameState = gs_fortify; armiesBefore = [self totalTroopsForPlayerNumber:currentPlayerNumber]; //NSLog (@"Attack->Fortify, player %d, armies: %d", currentPlayerNumber, armiesBefore); break; case gs_move_attacking_armies: // Go back to the attack phase: if ([[players[currentPlayerNumber] playerCards] count] > 4) { // Force the player to turn in cards. gameState = gs_place_armies; [self setArmiesLeftToPlace:0]; } else { gameState = gs_attack; } break; case gs_fortify: tmp = [self totalTroopsForPlayerNumber:currentPlayerNumber]; //NSLog (@"Fortify->next, Player %d: armies before = %d, armies now = %d", currentPlayerNumber, armiesBefore, tmp); if (armiesBefore != tmp) { NSLog (@"!!Player %d: armies before = %d, armies now = %d", currentPlayerNumber, armiesBefore, tmp); } [players[currentPlayerNumber] willEndTurn]; gameState = gs_place_armies; [self nextActivePlayer]; [players[currentPlayerNumber] willBeginTurn]; [self setArmiesLeftToPlace:[self earnedArmyCountForPlayer:currentPlayerNumber]]; break; case gs_place_fortifying_armies: fortifyRule = [configuration fortifyRule]; if (fortifyRule == OneToOneNeighbor || fortifyRule == OneToManyNeighbors) { tmp = [self totalTroopsForPlayerNumber:currentPlayerNumber]; //NSLog (@"PlaceFortify->next, Player %d: armies before = %d, armies now = %d",currentPlayerNumber, armiesBefore, tmp); if (armiesBefore != tmp) { NSLog (@"!!Player %d: armies before = %d, armies now = %d", currentPlayerNumber, armiesBefore, tmp); } [players[currentPlayerNumber] willEndTurn]; gameState = gs_place_armies; [self nextActivePlayer]; [players[currentPlayerNumber] willBeginTurn]; [self setArmiesLeftToPlace:[self earnedArmyCountForPlayer:currentPlayerNumber]]; } else { gameState = gs_fortify; } break; default: NSLog (@"Invalid game state."); } //[self _logGameState]; //NSLog (@"active player count: %d", activePlayerCount); //------------------------------------------------------------ if (currentPhaseView != nil) { [currentPhaseView removeFromSuperview]; currentPhaseView = nil; } if (activePlayerCount < 2) { //NSLog (@"Will not continue with game..."); return; } isInteractivePlayer = [players[currentPlayerNumber] isInteractive]; newPhaseView = nil; //[controlPanel makeMainWindow]; if ([players[currentPlayerNumber] isInteractive] == YES) { [[mapView window] makeKeyWindow]; } // Update phase controls for this new phase: switch (gameState) { case gs_choose_countries: newPhaseView = (isInteractivePlayer == YES) ? phaseChooseCountries : phaseComputerMove; break; case gs_place_initial_armies: newPhaseView = (isInteractivePlayer == YES) ? phasePlaceArmies : phaseComputerMove; // Do this here to avoid a little bit of flicker. [initialArmiesLeftTextField setIntValue:initialArmyCount]; count = [configuration armyPlacementCount]; if (initialArmyCount < count) count = initialArmyCount; [self setArmiesLeftToPlace:count]; [armyPlacementValidator placeInAnyCountryForPlayerNumber:currentPlayerNumber]; // You can always review your cards, even if you have none. if ([players[currentPlayerNumber] canTurnInCardSet] == YES) [turnInCardsButton setEnabled:YES]; else [turnInCardsButton setEnabled:NO]; break; case gs_place_armies: [mapView selectCountry:nil]; newPhaseView = (isInteractivePlayer == YES) ? phasePlaceArmies : phaseComputerMove; [initialArmiesLeftTextField setStringValue:@"--"]; [armyPlacementValidator placeInAnyCountryForPlayerNumber:currentPlayerNumber]; // You can always review your cards, even if you have none. if ([players[currentPlayerNumber] canTurnInCardSet] == YES) [turnInCardsButton setEnabled:YES]; else [turnInCardsButton setEnabled:NO]; break; case gs_attack: newPhaseView = (isInteractivePlayer == YES) ? phaseAttack : phaseComputerMove; [self takeAttackMethodFromPlayerNumber:currentPlayerNumber]; [attackingFromTextField setStringValue:@""]; break; case gs_fortify: newPhaseView = (isInteractivePlayer == YES) ? phaseFortify : phaseComputerMove; break; case gs_move_attacking_armies: case gs_place_fortifying_armies: // These states are entered in a different manner. default: NSLog (@"Invalid game state."); } // Now set up player stuff in "Turn Phase" box: [nameTextField setStringValue:[players[currentPlayerNumber] playerName]]; [playerColorWell setColor:[[BoardSetup instance] colorForPlayer:currentPlayerNumber]]; [self updatePhaseBox]; if (newPhaseView != nil) { [[controlPanel contentView] addSubview:newPhaseView]; [newPhaseView setFrameOrigin:NSMakePoint (281, 8)]; currentPhaseView = newPhaseView; // Show updated panel immediately. //[newPhaseView display]; [newPhaseView setNeedsDisplay:YES]; [statusView setNeedsDisplay:YES]; //[controlPanel display]; // And this in turn will redisplay the status view. } else { [self _logGameState]; } if (activePlayerCount > 1) { // Prevent deep recursion: [self performSelector:@selector (executeCurrentPhase:) withObject:self afterDelay:0]; } else { //NSLog (@"The game should be over..."); } } //---------------------------------------------------------------------- - (void) executeCurrentPhase:sender { //NSLog (@"active player count: %d", activePlayerCount); //[self _logGameState]; // Initiate next phase switch (gameState) { case gs_choose_countries: [players[currentPlayerNumber] chooseCountry]; break; case gs_place_initial_armies: [players[currentPlayerNumber] placeInitialArmies:armiesLeftToPlace]; break; case gs_place_armies: [self resetMovableArmiesForPlayerNumber:currentPlayerNumber]; //NSLog (@"current player: %d, count: %d", currentPlayerNumber, [[players[currentPlayerNumber] playerCards] count]); if ([[players[currentPlayerNumber] playerCards] count] > 4) { [players[currentPlayerNumber] mustTurnInCards]; [self automaticallyTurnInCardsForPlayerNumber:currentPlayerNumber]; } else { [players[currentPlayerNumber] mayTurnInCards]; } [players[currentPlayerNumber] placeArmies:armiesLeftToPlace]; break; case gs_attack: [players[currentPlayerNumber] attackPhase]; break; case gs_move_attacking_armies: //NSLog (@"player #%d", currentPlayerNumber); [players[currentPlayerNumber] moveAttackingArmies:armiesLeftToPlace between:[armyPlacementValidator sourceCountry]:[armyPlacementValidator destinationCountry]]; break; case gs_fortify: [players[currentPlayerNumber] fortifyPhase:[configuration fortifyRule]]; break; case gs_place_fortifying_armies: //NSLog (@"Fortifying %d armies from: %@", armiesLeftToPlace, sourceCountry); [players[currentPlayerNumber] placeFortifyingArmies:armiesLeftToPlace fromCountry:[armyPlacementValidator sourceCountry]]; break; default: NSLog (@"Invalid game state."); } } //---------------------------------------------------------------------- // Advance to next active player (regardless of current phase) // Returns whether it wrapped. //---------------------------------------------------------------------- - (BOOL) nextActivePlayer { BOOL wrappedFlag; int limit; //int l; wrappedFlag = NO; limit = MAX_PLAYERS; // 0 1 2 3 4 5 6 #if 0 for (l = 0; l < MAX_PLAYERS; l++) { NSLog (@"[%d]: %@ %@", l, (playersActive[l] == YES) ? @"Y" : @"N", players[l]); } NSLog (@"currentPlayerNumber: %d", currentPlayerNumber); #endif do { currentPlayerNumber = (currentPlayerNumber + 1) % MAX_PLAYERS; if (currentPlayerNumber == 0) wrappedFlag = YES; } while (--limit > 0 && playersActive[currentPlayerNumber] == NO); //NSLog (@"limit: %d", limit); NSAssert (limit != 0, @"No active players."); playerHasConqueredCountry = NO; return wrappedFlag; } //---------------------------------------------------------------------- - (void) fortify:sender { // Fortify action should only be executed during the attack phase. AssertGameState (gs_attack); [self endTurn]; } //---------------------------------------------------------------------- // End turn for interactive player. May skip over fortify phase. - (void) endTurn:sender { // End turn action should only be executed in either the attack or fortify phase. AssertGameState2 (gs_attack, gs_fortify); [self endTurn]; if (gameState == gs_fortify) [self endTurn]; } //---------------------------------------------------------------------- - (void) moveAttackingArmies:(int)minimum between:(Country *)source:(Country *)destination { BOOL isInteractivePlayer; NSView *newPhaseView; int count; AssertGameState (gs_attack); // Allow the movement of the remaining armies into either source or destination. [destination setTroopCount:minimum]; count = [source troopCount] - minimum; //NSLog (@"minimum: %d, armiesLeftToPlace: %d", minimum, count); gameState = gs_move_attacking_armies; // What if armiesLeftToPlace == 0? [armyPlacementValidator placeInEitherCountry:source orCountry:destination forPlayerNumber:currentPlayerNumber]; [source setTroopCount:0]; [mapView drawCountry:source]; if (currentPhaseView != nil) { [currentPhaseView removeFromSuperview]; currentPhaseView = nil; } isInteractivePlayer = [players[currentPlayerNumber] isInteractive]; newPhaseView = (isInteractivePlayer == YES) ? phasePlaceArmies : phaseComputerMove; [self updatePhaseBox]; // From above: [initialArmiesLeftTextField setStringValue:@"--"]; [self setArmiesLeftToPlace:count]; // You can always review your cards, even if you have none. if ([players[currentPlayerNumber] canTurnInCardSet] == YES) [turnInCardsButton setEnabled:YES]; else [turnInCardsButton setEnabled:NO]; [[controlPanel contentView] addSubview:newPhaseView]; [newPhaseView setFrameOrigin:NSMakePoint (281, 8)]; currentPhaseView = newPhaseView; [newPhaseView setNeedsDisplay:YES]; // Break the recursion: [self performSelector:@selector (executeCurrentPhase:) withObject:self afterDelay:0]; } //---------------------------------------------------------------------- - (void) fortifyArmiesFrom:(Country *)source { BOOL isInteractivePlayer; NSView *newPhaseView; FortifyRule fortifyRule; int count; //NSLog (@"source: %@", source); AssertGameState (gs_fortify); count = [source movableTroopCount]; if (count < 1) return; gameState = gs_place_fortifying_armies; // Need to base this on current fortify rule fortifyRule = [configuration fortifyRule]; switch (fortifyRule) { case OneToOneNeighbor: //NSLog (@"1:1"); [armyPlacementValidator placeInOneNeighborOfCountry:source forPlayerNumber:currentPlayerNumber]; break; case OneToManyNeighbors: //NSLog (@"1:N"); [armyPlacementValidator placeInAnyNeighborOfCountry:source forPlayerNumber:currentPlayerNumber]; break; case ManyToManyNeighbors: //NSLog (@"N:M"); [armyPlacementValidator placeInAnyNeighborOfCountry:source forPlayerNumber:currentPlayerNumber]; break; case ManyToManyConnected: //NSLog (@"N:M*"); [armyPlacementValidator placeInConnectedCountries:source forPlayerNumber:currentPlayerNumber]; break; default: [armyPlacementValidator placeInOneNeighborOfCountry:source forPlayerNumber:currentPlayerNumber]; NSLog (@"Unknown fortify rule: %d", fortifyRule); } [source setTroopCount:[source unmovableTroopCount]]; [mapView drawCountry:source]; if (currentPhaseView != nil) { [currentPhaseView removeFromSuperview]; currentPhaseView = nil; } isInteractivePlayer = [players[currentPlayerNumber] isInteractive]; newPhaseView = (isInteractivePlayer == YES) ? phasePlaceArmies : phaseComputerMove; [self updatePhaseBox]; // From above: [initialArmiesLeftTextField setStringValue:@"--"]; [self setArmiesLeftToPlace:count]; [turnInCardsButton setEnabled:NO]; [[controlPanel contentView] addSubview:newPhaseView]; [newPhaseView setFrameOrigin:NSMakePoint (281, 8)]; currentPhaseView = newPhaseView; [newPhaseView setNeedsDisplay:YES]; // Prevent deep recursion: [self performSelector:@selector (executeCurrentPhase:) withObject:self afterDelay:0]; } //---------------------------------------------------------------------- - (void) forceCurrentPlayerToTurnInCards { BOOL isInteractivePlayer; NSView *newPhaseView; AssertGameState (gs_attack); isInteractivePlayer = [players[currentPlayerNumber] isInteractive]; gameState = gs_place_armies; [self setArmiesLeftToPlace:0]; [mapView selectCountry:nil]; newPhaseView = (isInteractivePlayer == YES) ? phasePlaceArmies : phaseComputerMove; [initialArmiesLeftTextField setStringValue:@"--"]; [armyPlacementValidator placeInAnyCountryForPlayerNumber:currentPlayerNumber]; [turnInCardsButton setEnabled:YES]; [[controlPanel contentView] addSubview:newPhaseView]; [newPhaseView setFrameOrigin:NSMakePoint (281, 8)]; currentPhaseView = newPhaseView; // Show updated panel immediately. [newPhaseView setNeedsDisplay:YES]; [statusView setNeedsDisplay:YES]; // Prevent deep recursion: [self performSelector:@selector (executeCurrentPhase:) withObject:self afterDelay:0]; } //---------------------------------------------------------------------- - (void) resetMovableArmiesForPlayerNumber:(Player)number { NSEnumerator *countryEnumerator; Country *country; countryEnumerator = [[world countriesForPlayer:number] objectEnumerator]; while (country = [countryEnumerator nextObject]) { [country resetUnmovableTroops]; } } //====================================================================== // Choose countries //====================================================================== - (BOOL) player:(RiskPlayer *)aPlayer choseCountry:(Country *)country { Player number; BOOL valid; AssertGameState (gs_choose_countries); number = [aPlayer playerNumber]; NSAssert (currentPlayerNumber == number, @"Not your turn."); valid = NO; if ([country playerNumber] == 0) { [country setPlayerNumber:number]; valid = YES; } return valid; } //---------------------------------------------------------------------- // unoccupied means playerNumber == 0 (not troopCounty == 0) - (NSArray *) unoccupiedCountries { NSMutableArray *array; NSEnumerator *countryEnumerator; Country *country; array = [NSMutableArray array]; countryEnumerator = [[world allCountries] objectEnumerator]; while (country = [countryEnumerator nextObject]) { if ([country playerNumber] == 0) [array addObject:country]; } return array; } //---------------------------------------------------------------------- - (void) randomlyChooseCountriesForActivePlayers { NSMutableArray *array; Country *country; int count, index; AssertGameState (gs_choose_countries); currentPlayerNumber = 0; array = [NSMutableArray arrayWithArray:[[world allCountries] allObjects]]; count = [array count]; while (count > 0) { [self nextActivePlayer]; index = [rng randomNumberModulo:count]; country = [array objectAtIndex:index]; [country setPlayerNumber:currentPlayerNumber]; [array removeObjectAtIndex:index]; count--; } } //====================================================================== // Place Armies //====================================================================== - (BOOL) player:(RiskPlayer *)aPlayer placesArmies:(int)count inCountry:(Country *)country { BOOL okay; AssertGameState4 (gs_place_armies, gs_place_initial_armies, gs_move_attacking_armies, gs_place_fortifying_armies); NSAssert2 (count <= armiesLeftToPlace, @"Tried to place too many(%d) armies. max: %d ", count, armiesLeftToPlace); okay = [armyPlacementValidator validatePlacement:country]; if (okay == YES) { [armyPlacementValidator placeArmies:count inCountry:country]; if (gameState == gs_place_fortifying_armies) { [country addUnmovableTroopCount:count]; } armiesLeftToPlace -= count; if (gameState == gs_place_initial_armies) [initialArmiesLeftTextField setIntValue:[initialArmiesLeftTextField intValue] - count]; [self setArmiesLeftToPlace:armiesLeftToPlace]; } [country update]; return okay; } //====================================================================== // Attacking //====================================================================== - (AttackResult) attackUntilUnableToContinueFromCountry:(Country *)attacker toCountry:(Country *)defender moveAllArmiesUponVictory:(BOOL)moveFlag { AttackResult attackResult; attackResult.conqueredCountry = NO; while (attackResult.conqueredCountry == NO && [attacker troopCount] > 0) { attackResult = [self attackOnceFromCountry:attacker toCountry:defender moveAllArmiesUponVictory:moveFlag]; } return attackResult; } //---------------------------------------------------------------------- - (AttackResult) attackMultipleTimes:(int)count fromCountry:(Country *)attacker toCountry:(Country *)defender moveAllArmiesUponVictory:(BOOL)moveFlag { AttackResult attackResult; attackResult.conqueredCountry = NO; while (count-- > 0 && attackResult.conqueredCountry == NO && [attacker troopCount] > 0) { attackResult = [self attackOnceFromCountry:attacker toCountry:defender moveAllArmiesUponVictory:moveFlag]; } return attackResult; } //---------------------------------------------------------------------- - (AttackResult) attackFromCountry:(Country *)attacker toCountry:(Country *)defender untilArmiesRemain:(int)count moveAllArmiesUponVictory:(BOOL)moveFlag { AttackResult attackResult; attackResult.conqueredCountry = NO; count = MAX (count, 0); while (attackResult.conqueredCountry == NO && [attacker troopCount] > count) { attackResult = [self attackOnceFromCountry:attacker toCountry:defender moveAllArmiesUponVictory:moveFlag]; } return attackResult; } //---------------------------------------------------------------------- // Specify move flag to avoid distracting switch of phase view for // interactive players. // // When moveFlag == YES, we don't re-enter the attack phase, and // therefore are not forced to turn in card sets when we have > 4 // cards. //---------------------------------------------------------------------- //---------------------------------------------------------------------- // 1. Figure out how many dice to roll. // 2. Get numbers for the dice. The arrays come back sorted. // 3. Show the dice if needed. // 4. Figure out how many countries each side loses. // - Notify the computer player that it has been attacked so that // it can retaliate later. // 5. Now see if the defender lost. // - If the country is conquered, notify the computer player that // it has lost a country so that it can retaliate later. //---------------------------------------------------------------------- - (AttackResult) attackOnceFromCountry:(Country *)attacker toCountry:(Country *)defender moveAllArmiesUponVictory:(BOOL)moveFlag { AttackResult attackResult; DiceRoll diceRoll; int compareCount; int l; int attackerLosses, defenderLosses; Player attackingPlayerNumber, defendingPlayerNumber; GameState initialGameState; BOOL isGameOver; NSAssert ([defender isAdjacentToCountry:attacker] == YES, @"The countries are not neighbors."); initialGameState = gameState; attackResult.conqueredCountry = NO; isGameOver = NO; attackingPlayerNumber = [attacker playerNumber]; defendingPlayerNumber = [defender playerNumber]; diceRoll = [self rollDiceWithAttackerArmies:[attacker troopCount] defenderArmies:[defender troopCount]]; // return YES if we won -- enter gs_move_attacking_armies state if (diceInspector != nil && [diceInspector isPanelOnScreen] == YES) { [diceInspector showAttackFromCountry:attacker toCountry:defender withDice:diceRoll]; } compareCount = MIN (diceRoll.attackerDieCount, diceRoll.defenderDieCount); attackerLosses = 0; defenderLosses = 0; for (l = 0; l < compareCount; l++) { if (diceRoll.attackerDice[l] > diceRoll.defenderDice[l]) defenderLosses++; else attackerLosses++; } if (attackerLosses > 0) { [attacker addTroops:-attackerLosses]; [mapView drawCountry:attacker]; } [players[defendingPlayerNumber] playerNumber:attackingPlayerNumber attackedCountry:defender]; if (defenderLosses > 0) { [defender addTroops:-defenderLosses]; [mapView drawCountry:defender]; // Still have to work out precise details... Old version always has 1 army in each country. // How do we adjust for same effect, since we allow countries with troopCount == 0? // Defender doesn't lose until troopCount < 0... if ([defender troopCount] < 0) { [players[defendingPlayerNumber] playerNumber:attackingPlayerNumber capturedCountry:defender]; [defender setPlayerNumber:attackingPlayerNumber]; [defender setTroopCount:0]; [mapView drawCountry:defender]; attackResult.conqueredCountry = YES; // Now, check to see if that was the last country of the defender. if ([[world countriesForPlayer:defendingPlayerNumber] count] == 0) { [self transferCardsFromPlayer:players[defendingPlayerNumber] toPlayer:players[attackingPlayerNumber]]; isGameOver = [self checkForEndOfPlayerNumber:defendingPlayerNumber]; // And, check to see if activePlayerCount == 1 // Perhaps delay until after armies moved... } if (isGameOver == NO) { if (moveFlag == YES) { [defender setTroopCount:[attacker troopCount]]; [attacker setTroopCount:0]; [mapView drawCountry:attacker]; // May no longer need with removal of ARMY class... [mapView drawCountry:defender]; if ([[players[attackingPlayerNumber] playerCards] count] > 4) { // And make sure we're back in the place armies phase... [self forceCurrentPlayerToTurnInCards]; } } else { [self moveAttackingArmies:diceRoll.attackerDieCount between:attacker:defender]; } } playerHasConqueredCountry = YES; } } // If the phase has changed, the player should return immediately. If it has not changed, // the player may end the turn to enter the fortify phase. attackResult.phaseChanged = initialGameState != gameState; return attackResult; } //====================================================================== // Game Manager calculations //====================================================================== - (int) earnedArmyCountForPlayer:(Player)number { int count; // Calculated the number of armies earned at the beginning of a turn, // based on the number of countries/continents controller by that // player. count = ([[world countriesForPlayer:number] count] / 3) + [world continentBonusArmiesForPlayer:number]; if (count < 3) count = 3; return count; } //---------------------------------------------------------------------- // Roll dice based on number of attacking/defending armies. Figures out // proper number of dice to use, and returns sorted results. //---------------------------------------------------------------------- - (DiceRoll) rollDiceWithAttackerArmies:(int)attackerArmies defenderArmies:(int)defenderArmies { DiceRoll diceRoll; int l; int temp1, temp2, temp3; NSAssert (attackerArmies >= 0, @"Attacker army count must be positive."); NSAssert (defenderArmies >= 0, @"Defender army count must be positive."); temp1 = temp2 = temp3 = 0; // Calculate number of dice to use for attacker and defender diceRoll.attackerDieCount = (attackerArmies > 3) ? 3 : attackerArmies; diceRoll.defenderDieCount = (defenderArmies == 0) ? 1 : 2; // Roll dice and fill unused dice with 0 so that we can sort them. for (l = 0; l < diceRoll.attackerDieCount; l++) diceRoll.attackerDice[l] = [rng rollDieWithSides:6]; for (l = diceRoll.attackerDieCount; l < 3; l++) diceRoll.attackerDice[l] = 0; for (l = 0; l < diceRoll.defenderDieCount; l++) diceRoll.defenderDice[l] = [rng rollDieWithSides:6]; for (l = diceRoll.defenderDieCount; l < 2; l++) diceRoll.defenderDice[l] = 0; // sort the arrays if ((diceRoll.attackerDice[0] >= diceRoll.attackerDice[1]) && (diceRoll.attackerDice[0] >= diceRoll.attackerDice[2])) { temp1 = diceRoll.attackerDice[0]; if (diceRoll.attackerDice[1] >= diceRoll.attackerDice[2]) { temp2 = diceRoll.attackerDice[1]; temp3 = diceRoll.attackerDice[2]; } else { temp2 = diceRoll.attackerDice[2]; temp3 = diceRoll.attackerDice[1]; } } else if ((diceRoll.attackerDice[1] >= diceRoll.attackerDice[0]) && (diceRoll.attackerDice[1] >= diceRoll.attackerDice[2])) { temp1 = diceRoll.attackerDice[1]; if (diceRoll.attackerDice[0] >= diceRoll.attackerDice[2]) { temp2 = diceRoll.attackerDice[0]; temp3 = diceRoll.attackerDice[2]; } else { temp2 = diceRoll.attackerDice[2]; temp3 = diceRoll.attackerDice[0]; } } else if ((diceRoll.attackerDice[2] >= diceRoll.attackerDice[0]) && (diceRoll.attackerDice[2] >= diceRoll.attackerDice[1])) { temp1 = diceRoll.attackerDice[2]; if (diceRoll.attackerDice[0] >= diceRoll.attackerDice[1]) { temp2 = diceRoll.attackerDice[0]; temp3 = diceRoll.attackerDice[1]; } else { temp2 = diceRoll.attackerDice[1]; temp3 = diceRoll.attackerDice[0]; } } diceRoll.attackerDice[0] = temp1; diceRoll.attackerDice[1] = temp2; diceRoll.attackerDice[2] = temp3; if (diceRoll.defenderDice[1] > diceRoll.defenderDice[0]) { temp1 = diceRoll.defenderDice[1]; diceRoll.defenderDice[1] = diceRoll.defenderDice[0]; diceRoll.defenderDice[0] = temp1; } return diceRoll; } //====================================================================== // General player interaction //====================================================================== - (void) selectCountry:(Country *)aCountry { [mapView selectCountry:aCountry]; } //---------------------------------------------------------------------- - (void) takeAttackMethodFromPlayerNumber:(Player)number { AttackMethod attackMethod; int attackMethodValue; int index; attackMethod = [players[number] attackMethod]; attackMethodValue = [players[number] attackMethodValue]; switch (attackMethod) { case AttackMultipleTimes: index = 1; break; case AttackUntilArmiesRemain: index = 2; break; case AttackUntilUnableToContinue: index = 3; break; case AttackOnce: default: index = 0; break; } [attackMethodPopup selectItemAtIndex:index]; [methodCountSlider setIntValue:attackMethodValue]; } //---------------------------------------------------------------------- - (void) setAttackMethodForPlayerNumber:(Player)number { int index; AttackMethod attackMethods[] = { AttackOnce, AttackMultipleTimes, AttackUntilArmiesRemain, AttackUntilUnableToContinue }; index = [attackMethodPopup indexOfSelectedItem]; if (index < 0 || index > 3) index = 0; [players[number] setAttackMethod:attackMethods[index]]; [players[number] setAttackMethodValue:[methodCountSlider intValue]]; } //---------------------------------------------------------------------- // Can't add ...Field, because, of course, then it will be used to set // the connection. - (void) setAttackingFromCountryName:(NSString *)string { [attackingFromTextField setStringValue:string]; } //---------------------------------------------------------------------- - (void) attackMethodAction:sender { if (sender == methodCountSlider) { [methodCountTextField takeIntValueFrom:sender]; } else if (sender == methodCountTextField) { [methodCountSlider takeIntValueFrom:sender]; } [self setAttackMethodForPlayerNumber:currentPlayerNumber]; } //---------------------------------------------------------------------- - (void) setArmiesLeftToPlace:(int)count { armiesLeftToPlace = count; [armyView setArmyCount:armiesLeftToPlace]; [armiesLeftToPlaceTextField setIntValue:armiesLeftToPlace]; } //====================================================================== // Card management //====================================================================== - (void) _recycleDiscardedCards { [cardDeck addObjectsFromArray:discardDeck]; [discardDeck removeAllObjects]; } //---------------------------------------------------------------------- // We don't shuffle the deck -- instead, we just choose a random card. // This is fine as long as we don't want a deck inspector for debugging. // It may also be an issue for saved games -- loading the same saved // game multiple times will result in different ordering of the cards. //---------------------------------------------------------------------- - (void) dealCardToPlayerNumber:(Player)number { RiskCard *card; int index, count; count = [cardDeck count]; if (count == 0) { [self _recycleDiscardedCards]; count = [cardDeck count]; } if (count == 0) return; //NSAssert (count != 0, @"Ran out of cards!"); index = [rng randomNumberModulo:count]; card = [cardDeck objectAtIndex:index]; [players[number] addCardToHand:card]; [cardDeck removeObjectAtIndex:index]; [statusView setNeedsDisplay:YES]; } //---------------------------------------------------------------------- - (int) _valueOfNextCardSet:(int)currentValue { CardSetRedemption cardSetRedemption; int nextValue; cardSetRedemption = [configuration cardSetRedemption]; switch (cardSetRedemption) { case IncreaseByOne: nextValue = currentValue + 1; break; case IncreaseByFive: if (currentValue < 12) nextValue = currentValue + 2; else if (currentValue == 12) nextValue = 15; else nextValue = currentValue + 5; break; case RemainConstant: default: nextValue = 5; } return nextValue; } //---------------------------------------------------------------------- - (int) armiesForNextCardSet { return nextCardSetValue; } //---------------------------------------------------------------------- // The player is *NOT* responsible for removing cards from hand. // How are computer players affected by the forced turning in of cards? // i.e. There is placeArmies: followed by optionally turning in card sets // and then there is the forced turning in of card sets, followed by placeArmies: //---------------------------------------------------------------------- - (void) turnInCardSet:(CardSet *)cardSet forPlayerNumber:(Player)number { RiskCard *card; Country *country; AssertGameState (gs_place_armies); // Add number of armies to currently available armies for placement. // Add bonus armies to those card countries that we control. if (cardSet != nil) { //NSLog (@"turning in this card set: %@", cardSet); card = [cardSet card1]; country = [card country]; if ([country playerNumber] == number) { [country addTroops:2]; [mapView drawCountry:country]; } [players[number] removeCardFromHand:card]; [discardDeck addObject:card]; card = [cardSet card2]; country = [card country]; if ([country playerNumber] == number) { [country addTroops:2]; [mapView drawCountry:country]; } [players[number] removeCardFromHand:card]; [discardDeck addObject:card]; card = [cardSet card3]; country = [card country]; if ([country playerNumber] == number) { [country addTroops:2]; [mapView drawCountry:country]; } [players[number] removeCardFromHand:card]; [discardDeck addObject:card]; [self setArmiesLeftToPlace:armiesLeftToPlace + nextCardSetValue]; [players[number] didTurnInCards:nextCardSetValue]; //armiesLeftToPlace += nextCardSetValue; nextCardSetValue = [self _valueOfNextCardSet:nextCardSetValue]; [statusView setNeedsDisplay:YES]; } } //---------------------------------------------------------------------- // For the currently active (interactive) player //---------------------------------------------------------------------- - (void) reviewCards:sender { [self _loadCardPanel]; [cardPanelController runCardPanel:NO forPlayer:players[currentPlayerNumber]]; } //---------------------------------------------------------------------- - (void) turnInCards:sender { AssertGameState (gs_place_armies); [self _loadCardPanel]; [cardPanelController runCardPanel:YES forPlayer:players[currentPlayerNumber]]; if ([players[currentPlayerNumber] canTurnInCardSet] == YES) { [turnInCardsButton setEnabled:YES]; } else { [turnInCardsButton setEnabled:NO]; } } //---------------------------------------------------------------------- - (void) automaticallyTurnInCardsForPlayerNumber:(Player)number { CardSet *cardSet; while ([[players[number] playerCards] count] > 4) { cardSet = [players[number] bestSet]; [self turnInCardSet:cardSet forPlayerNumber:number]; } } //---------------------------------------------------------------------- - (void) transferCardsFromPlayer:(RiskPlayer *)source toPlayer:(RiskPlayer *)destination { NSArray *cardArray; NSEnumerator *cardEnumerator; RiskCard *card; cardArray = [NSArray arrayWithArray:[source playerCards]]; cardEnumerator = [cardArray objectEnumerator]; //NSLog (@"transfering %d cards.", [cardArray count]); while (card = [cardEnumerator nextObject]) { [source removeCardFromHand:card]; [destination addCardToHand:card]; } [statusView setNeedsDisplay:YES]; } //---------------------------------------------------------------------- - (void) _loadCardPanel { if (cardPanelController == nil) { cardPanelController = [[CardPanelController alloc] init]; [cardPanelController setGameManager:self]; } } //---------------------------------------------------------------------- - (void) updatePhaseBox { if ([players[currentPlayerNumber] isInteractive] == YES) { [infoTextField setStringValue:gameStateInfo (gameState)]; [phaseTextField setStringValue:NSStringFromGameState (gameState)]; } else { [phaseTextField setStringValue:@"Computer Move"]; [infoTextField setStringValue:@"The computer player named above is moving, please wait."]; } } //---------------------------------------------------------------------- - (int) totalTroopsForPlayerNumber:(Player)number { NSEnumerator *countryEnumerator; Country *country; int total; total = 0; countryEnumerator = [[world countriesForPlayer:number] objectEnumerator]; while (country = [countryEnumerator nextObject]) { total += [country troopCount]; } return total; } //---------------------------------------------------------------------- - (void) defaultsChanged:(NSNotification *)aNotification { if (currentPlayerNumber > 0) [playerColorWell setColor:[[BoardSetup instance] colorForPlayer:currentPlayerNumber]]; } //====================================================================== // End of game stuff: //====================================================================== - (BOOL) checkForEndOfPlayerNumber:(Player)number { BOOL isGameOver; Player winner; isGameOver = NO; if ([[world countriesForPlayer:number] count] == 0) { [self playerHasLost:number]; if (activePlayerCount < 2) { isGameOver = YES; } } if (isGameOver == YES) { for (winner = 1; winner < MAX_PLAYERS; winner++) { if (playersActive[winner] == YES) { // Use notification instead, and the brain can do the alert panel. // And make gameState == gs_game_over... NSRunAlertPanel (@"Victory", @"Winner was %@.", @"OK", nil, nil, [players[winner] playerName]); [self playerHasWon:winner]; break; } } [self stopGame]; } return isGameOver; } //---------------------------------------------------------------------- - (void) playerHasLost:(Player)number { [players[number] youLostGame]; [self deactivatePlayerNumber:number]; } //---------------------------------------------------------------------- - (void) playerHasWon:(Player)number { [players[number] youWonGame]; [self deactivatePlayerNumber:number]; } //---------------------------------------------------------------------- // 1. Release player // 2. Free items of the player's submenu (if any), and disable the player menu. //---------------------------------------------------------------------- - (void) deactivatePlayerNumber:(Player)number { NSMenu *playerMenu; NSArray *itemArray; NSMenuItem *menuItem; if (playersActive[number] == YES) { playersActive[number] = NO; if (players[number] != nil) { playerMenu = [players[number] playerToolMenu]; itemArray = [playerMenu itemArray]; while ([itemArray count] > 1) { menuItem = [itemArray lastObject]; //NSLog (@"removing item: %@", [menuItem title]); [playerMenu removeItem:menuItem]; } // Best to autorelease, especially for the Human player. [players[number] autorelease]; players[number] = nil; } activePlayerCount--; } } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.