This is GameManager.m in view mode; [Download] [Up]
// // $Id: GameManager.m,v 1.21 1997/10/31 08:35:27 nygard Exp $ // // // This file is a part of Empire, a game of exploration and conquest. // Copyright (C) 1996 Steve Nygard // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // // You may contact the author by: // e-mail: nygard@telusplanet.net // #import "Empire.h" RCSID ("$Id: GameManager.m,v 1.21 1997/10/31 08:35:27 nygard Exp $"); #import "City.h" #import "EmPlayer.h" #import "EmpireProtocols.h" #import "GameManager.h" #import "Human.subproj/Human.h" #import "Map.h" #import "ShipReportController.h" #import "SNRandom.h" #import "StatusController.h" #import "Unit.h" #import "WarReportController.h" #import "World.h" #define AGSReason(state1) [NSString stringWithFormat:@"Current game state is (%@). Expected game state to be %@.", NSStringFromGameState (gameState), NSStringFromGameState (state1)] #define AssertGameState(state1) NSAssert1 (gameState == state1, @"%@", AGSReason (state1)) #define AssertNotGameState(state1) NSAssert1 (gameState != state1, @"%@", AGSReason (state1)) //---------------------------------------------------------------------- NSString *NSStringFromGameState (GameState state) { NSString *stateNames[] = { @"No Game", @"Establishing Game", @"Client Active", @"Player 1 Turn", @"Player 2 Turn", @"Player 3 Turn", @"Game Over" }; NSString *name; name = nil; if (state <= gs_game_over) name = stateNames[state]; return name; } //====================================================================== #define GameManager_VERSION 1 @implementation GameManager + (void) initialize { if (self == [GameManager class]) { [self setVersion:GameManager_VERSION]; } } //---------------------------------------------------------------------- - init { [super init]; berserk = NO; world = nil; activePlayerCount = 0; players[p_neutral] = nil; players[p_player1] = nil; players[p_player2] = nil; players[p_player3] = nil; playersActive[p_neutral] = NO; playersActive[p_player1] = NO; playersActive[p_player2] = NO; playersActive[p_player3] = NO; currentTurn = 0; gameState = gs_no_game; awaitingCookie = nil; attackingUnit = nil; defendingUnit = nil; defendingCity = nil; finalMaps[0] = nil; finalMaps[1] = nil; finalMaps[2] = nil; finalMaps[3] = nil; gameStatusController = [[StatusController alloc] initWithGameManager:self]; [gameStatusController showPanel]; return self; } //---------------------------------------------------------------------- - (void) dealloc { Player p; //NSLog (@"dealloc'ing GameManager."); //[self logStatus]; [self stopGame]; SNRelease (world); SNRelease (awaitingCookie); for (p = p_neutral; p <= p_player3; p++) { SNRelease (players[p]); SNRelease (finalMaps[p]); } SNRelease (gameStatusController); SNRelease (warReportController); SNRelease (shipReportController); [super dealloc]; } #if 0 - retain { int retainCount = [self retainCount]; NSLog (@"retaining GM from %d to %d", retainCount, retainCount + 1); return [super retain]; } - autorelease { DSTART; return [super autorelease]; } - (void) release { int retainCount = [self retainCount]; NSLog (@"releasing GM from %d to %d", retainCount, retainCount - 1); [super release]; } #endif //====================================================================== // Interface Managment //====================================================================== - (void) showWarReport:sender forPlayer:(EmPlayer *)player { if (warReportController == nil) warReportController = [[WarReportController alloc] init]; [warReportController warReportForPlayer:player]; } //---------------------------------------------------------------------- - (void) showShipReport:sender forPlayer:(EmPlayer *)player { if (shipReportController == nil) shipReportController = [[ShipReportController alloc] init]; [shipReportController shipReportForPlayer:player]; } //====================================================================== // Debugging //====================================================================== // The "Crystal Ball" was part of my early development. It kept an // accurate map of the real positions of all units. I haven't put it // back since the distributed games have complicated things. - (void) showCrystalBall:sender { } //---------------------------------------------------------------------- - (void) removeCrystalBall:sender { } //---------------------------------------------------------------------- - (void) setBerserk:(BOOL)flag { berserk = flag; } //---------------------------------------------------------------------- - (BOOL) isBerserk { return berserk; } //---------------------------------------------------------------------- - (void) toggleBerserk:sender { berserk = (berserk == YES) ? NO : YES; [sender setTitle:(berserk == YES) ? @"Turn Berserk OFF" : @"Turn Berserk ON"]; } //====================================================================== // Menu Actions //====================================================================== //====================================================================== // World Information //====================================================================== - (int) worldCityCount { NSAssert (world != nil, @"No active world."); return [world worldCityCount]; } //---------------------------------------------------------------------- - (City *) randomNeutralCity { NSAssert (world != nil, @"No active world."); return [world randomNeutralCity]; } //====================================================================== // Establish Game //====================================================================== - (void) startGameWithMapNamed:(NSString *)mapName { AssertGameState (gs_no_game); NSAssert (world == nil, @"There is already an active world."); world = [[World alloc] initWithMapNamed:mapName]; NSAssert (world != nil, @"Could not create world."); [self setGameState:gs_establishing_game]; } //---------------------------------------------------------------------- - (BOOL) addPlayer:(Player)number name:(NSString *)name type:(NSString *)playerType withEfficiencies:(int)pe:(int)ce { City *city; NSAssert (number >= p_player1 && number <= p_player3 && players[number] == nil && playersActive[number] == NO, @"Invalid player."); AssertGameState (gs_establishing_game); NSAssert (world != nil, @"No active world."); //NSLog (@"pe: %d, ce: %d", pe, ce); city = [self randomNeutralCity]; NSAssert (city != nil, @"No more neutral cities."); players[number] = [[NSClassFromString (playerType) alloc] initWithName:name number:number world:[world worldMap] capital:city withEfficiencies:pe:ce gameManager:self]; playersActive[number] = YES; activePlayerCount++; [[NSNotificationCenter defaultCenter] postNotificationName:EMPlayerStateChangedNotification object:self]; //NSLog (@"player:%d, map:%08lx", number, [players[number] map]); return YES; } //---------------------------------------------------------------------- - (void) beginGame { AssertGameState (gs_establishing_game); //gameState = gs_player1_turn; NSAssert (awaitingCookie == nil, @"Expected nil cookie."); awaitingCookie = [[NSNumber numberWithUnsignedLong:42] retain]; // Start next turn... [self tryToStart]; } //---------------------------------------------------------------------- - (void) tryToStart { AssertGameState (gs_establishing_game); [self turnDone:awaitingCookie]; } //---------------------------------------------------------------------- // Server - (void) stopGame { int activePlayers; Player p; // What about: // war_report_panel // ship_report_panel // world_map_panel // wizard_window // wizard_controller // Notify players that the game has been stopped. if (gameState == gs_client_active) return; activePlayers = [self activePlayers]; for (p = p_player1; p <= p_player3; p++) { if (playersActive[p] == YES) [self storeFinalMapForPlayer:p]; } for (p = p_player1; p <= p_player3; p++) { if (playersActive[p] == YES) { [self gameHasStopped:p activePlayers:activePlayers]; } } } //---------------------------------------------------------------------- - (void) gameHasStopped:(Player)number activePlayers:(int)activePlayers { NSAssert (players[number] != nil, @"We don't have that player."); [players[number] gameStopped:activePlayers]; [self deactivatePlayer:number]; } //---------------------------------------------------------------------- - (void) gameOver { } //====================================================================== // Game State //====================================================================== - (int) turnNumber { #if 0 NSAssert (gameState == gs_player1_turn || gameState == gs_player2_turn || gameState == gs_player3_turn, @"No game in progress."); #endif return currentTurn; } //---------------------------------------------------------------------- - (BOOL) gameInProgress { return gameState != gs_no_game; } //====================================================================== // Turn Handling //====================================================================== - (Player) nextPlayerTurn { Player nextPlayer; AssertNotGameState (gs_no_game); switch (gameState) { case gs_player3_turn: currentTurn++; [[NSNotificationCenter defaultCenter] postNotificationName:EMTurnChangedNotification object:self]; case gs_establishing_game: [self setGameState:gs_player1_turn]; nextPlayer = p_player1; break; case gs_player1_turn: [self setGameState:gs_player2_turn]; nextPlayer = p_player2; break; case gs_player2_turn: [self setGameState:gs_player3_turn]; nextPlayer = p_player3; break; default: nextPlayer = p_neutral; NSLog (@"Invalid game state to end turn."); } //NSLog (@"The next player is %d", nextPlayer); return nextPlayer; } //---------------------------------------------------------------------- // Note: This is not called by the DistributedGameManger subclass. //---------------------------------------------------------------------- - (void) turnDone:(NSNumber *)aCookie { Player nextPlayer; int limit = 3; if ([awaitingCookie isEqual:aCookie] == NO) { //NSLog (@"awaiting: %@, got: %@", awaitingCookie, aCookie); NSLog (@"[1] Invalid cookie!"); return; } NSAssert (activePlayerCount > 0, @"No active players."); [awaitingCookie release]; awaitingCookie = [[NSNumber numberWithUnsignedLong:[[SNRandom instance] randomNumber]] retain]; #if 0 NSLog (@"active player count: %d", activePlayerCount); NSLog (@"playersActive: %@ %@ %@ %@", playersActive[p_neutral] == YES ? @"Yes" : @"No", playersActive[p_player1] == YES ? @"Yes" : @"No", playersActive[p_player2] == YES ? @"Yes" : @"No", playersActive[p_player3] == YES ? @"Yes" : @"No"); #endif do { nextPlayer = [self nextPlayerTurn]; } while (limit-- > 0 && (players[nextPlayer] == nil || playersActive[nextPlayer] == NO)); if (playersActive[nextPlayer] == YES && players[nextPlayer] != nil) { [players[nextPlayer] yourTurn:currentTurn withCookie:awaitingCookie]; } else { NSLog (@"No active players."); } } //====================================================================== // Movement and combat //====================================================================== //---------------------------------------------------------------------- // This is a rather large method, but is probably the simplest way of // doing what needs to be done. // // We check for the most specific cases first. //---------------------------------------------------------------------- - (MoveResult) moveUnit:(Unit *)thisUnit inDirection:(Direction)dir { static const int row_deltas[9] = { -1, -1, -1, 0, 0, 0, 1, 1, 1 }; static const int column_deltas[9] = { -1, 0, 1, -1, 0, 1, -1, 0, 1 }; EMMapSize mapSize; EMMapLocation thisLocation = [thisUnit unitLocation]; EMMapLocation targetLocation; EmPlayer *thisOwner = [thisUnit owner]; Player thisPlayer = [thisOwner playerNumber]; UnitType thisUnitType = [thisUnit unitType]; MapToken targetToken; Terrain targetTerrain; Player targetPlayer; Icon targetIcon; UnitType targetUnitType; BOOL moveUnit = NO; BOOL tookTurn = NO; MoveResult moveResult; BOOL targetIsFoe; if ([thisUnit currentRange] <= 0) return mr_zero_range; NSAssert (world != nil, @"No active world."); mapSize = [[world worldMap] mapSize]; targetLocation.row = thisLocation.row + row_deltas[dir]; targetLocation.column = thisLocation.column + column_deltas[dir]; // // Make sure we're not trying to move off of the map. // if (targetLocation.row < 0 || targetLocation.column < 0 || targetLocation.row >= mapSize.height || targetLocation.column >= mapSize.width) { return mr_blocked; } // // Check terrain in target location // targetToken = [self canonTokenAtLocation:targetLocation]; EMMapTokenComponents (targetToken, &targetTerrain, &targetPlayer, &targetIcon, NULL); targetUnitType = EMConvertIconToUnitType (targetIcon); //printf ("target_icon: %d, target_player: %d\n", target_icon, target_player); // No! It could be a Unit in a remote process. We'll let the central // GameManager arbitrate combat, and let the interested parties know the // results... targetIsFoe = targetPlayer != thisPlayer; if (targetTerrain == t_land && [thisUnit canBombard] == YES && targetIsFoe == YES && targetUnitType == u_army) { //NSLog (@"Ship(%@) is bombarding army.", thisUnit); moveResult = [self unit:thisUnit attacksPlayer:targetPlayer unitAtLocation:targetLocation withBombardment:YES]; tookTurn = YES; moveUnit = NO; } else if (targetTerrain == t_water && thisUnitType == u_army && targetIsFoe == YES && targetIcon != i_none && targetUnitType != u_submarine) { //NSLog (@"Army(%@) is attacking enemy on water.", thisUnit); moveResult = [self unit:thisUnit attacksPlayer:targetPlayer unitAtLocation:targetLocation withBombardment:NO]; tookTurn = YES; moveUnit = NO; } else if (targetUnitType == u_transport && targetIsFoe == NO && thisUnitType == u_army) { Unit *transport = [thisOwner primaryUnitAtLocation:targetLocation]; //NSLog (@"Army(%@) is trying to board friendly transport.", thisUnit); //NSLog (@"transport(%@): %@", [transport unitName], transport); //if ([transport loadUnit:thisUnit] == YES) // The unit will be loaded on the transport when it is moved. Here, we just // check if it will fit. if ([transport isFull] == NO) { moveResult = mr_moved; tookTurn = YES; moveUnit = YES; } else { moveResult = mr_blocked; tookTurn = NO; moveUnit = NO; } } else if (targetUnitType == u_carrier && targetIsFoe == NO && thisUnitType == u_fighter) { Unit *carrier = [thisOwner primaryUnitAtLocation:targetLocation]; //NSLog (@"Fighter(%@) is trying to board friendly carrier.", thisUnit); //NSLog (@"Carrier(%@): %@", [carrier unitName], carrier); //if ([carrier loadUnit:thisUnit] == YES) if ([carrier isFull] == NO) { moveResult = mr_moved; tookTurn = YES; moveUnit = YES; // Clear remaing moves for this turn. [thisUnit skipMove]; } else { moveResult = mr_blocked; tookTurn = NO; moveUnit = NO; } } else if (targetIcon != i_none && targetIsFoe == NO) { //NSLog (@"Unit(%@) is blocked by a friendly unit.", thisUnit); moveResult = mr_blocked; tookTurn = NO; moveUnit = NO; } else if (targetTerrain == t_city && targetIsFoe == YES && [thisUnit unitType] == u_army) { //NSLog (@"Army(%@) is attacking enemy city.", thisUnit); moveResult = [self unit:thisUnit attacksPlayer:targetPlayer cityAtLocation:targetLocation]; //NSLog (@"Move result: %d", moveResult ); tookTurn = YES; //moveUnit = moveResult == mr_victory; moveUnit = NO; if (moveResult == mr_victory) { [thisOwner destroyUnit:thisUnit wasDisbanded:YES]; moveResult = mr_captured_city; moveUnit = YES; // Needed to remove old icon. } } else if (targetTerrain != t_city && [thisUnit canMoveOnTerrain:targetTerrain] == YES && targetIcon != i_none && targetIsFoe == YES) { // Attack enemy unit. //NSLog (@"Unit(%@) is attacking enemy unit.", thisUnit); moveResult = [self unit:thisUnit attacksPlayer:targetPlayer unitAtLocation:targetLocation withBombardment:NO]; tookTurn = YES; moveUnit = moveResult == mr_victory; } else if (targetTerrain == t_city && targetIsFoe == NO) { //NSLog (@"Unit(%@) is entering friendly city.", thisUnit); // Clear remaing moves for this turn. [thisUnit skipMove]; moveResult = mr_moved; tookTurn = YES; moveUnit = YES; } else if ([thisUnit canMoveOnTerrain:targetTerrain] == YES) { //NSLog (@"Unit(%@) is NOT blocked by terrain.", thisUnit); moveResult = mr_moved; tookTurn = YES; moveUnit = YES; } else { //NSLog (@"Unit(%@) is blocked by terrain.", thisUnit); moveResult = mr_blocked; tookTurn = NO; moveUnit = NO; } // Player still needs map updated... if (tookTurn == YES) { //NSLog (@"Took turn."); [thisUnit moved]; } //NSLog (@"thisUnit: %@, destroyed: %@\n", thisUnit, ([thisUnit hasBeenDestroyed] == YES) ? @"Yes" : @"No"); // We need to remove the enemy icon first. if (moveResult == mr_victory && targetTerrain != t_city) { // Remove enemy unit from enemy map. //NSLog (@"Remove enemy unit from enemy map."); [self remove:targetIcon atLocation:targetLocation forPlayer:targetPlayer]; #if 0 //NSLog (@"target player: %d, (%d,%d)", targetPlayer, targetLocation.row, targetLocation.column); [self explorePlayer:targetPlayer around3x3Location:targetLocation updateOtherPlayers:YES]; #endif } if (moveResult == mr_captured_city && targetTerrain == t_city) { //NSLog (@"Remove enemy city from enemy(%d) map.", targetPlayer); if (targetPlayer != p_neutral) { //[self setCityAtLocation:targetLocation toPlayer:p_neutral forPlayer:targetPlayer]; [self setCityAtLocation:targetLocation toPlayer:thisPlayer forPlayer:targetPlayer]; } //NSLog (@"Put our city in our map."); [self setCityAtLocation:targetLocation toPlayer:thisPlayer forPlayer:thisPlayer]; } if (moveResult == mr_destroyed) { EMMapLocation location = [thisUnit unitLocation]; //NSLog (@"Remove our unit from our map."); [self remove:[thisUnit icon] atLocation:location forPlayer:thisPlayer]; } if (moveUnit == YES) { //Map *thisMap = [thisOwner map]; //NSLog (@"Move unit."); #if 0 NSLog (@"Remove %d @ (%d,%d) [%@]", [thisUnit icon], thisLocation.row, thisLocation.column, EMFormatComponents ([thisMap tokenAtLocation:thisLocation])); NSLog (@"Put %d @ (%d,%d) [%@]", [thisUnit icon], targetLocation.row, targetLocation.column, EMFormatComponents ([thisMap tokenAtLocation:targetLocation])); NSLog (@"Now @ (%d,%d) [%@]", targetLocation.row, targetLocation.column, EMFormatComponents ([thisMap tokenAtLocation:targetLocation])); #endif [self remove:[thisUnit icon] atLocation:thisLocation forPlayer:thisPlayer]; [self put:[thisUnit icon] atLocation:targetLocation forPlayer:thisPlayer]; [thisUnit setLocation:targetLocation]; } if (moveResult == mr_victory || moveResult == mr_captured_city || moveResult == mr_destroyed || moveUnit == YES) { //NSLog (@"Update players."); [self explorePlayer:thisPlayer around3x3Location:thisLocation updateOtherPlayers:YES]; [self explorePlayer:thisPlayer around3x3Location:targetLocation updateOtherPlayers:YES]; } if (thisUnitType == u_fighter && [thisUnit remainingFuel] <= 0 && [thisUnit isInCity] == NO && [thisUnit isOnBoardShip] == NO) // Only for fighter { EMMapLocation location = [thisUnit unitLocation]; //NSLog (@"Fighter ran out of fuel."); //NSLog (@"Fighter @ (%d,%d), target: (%d,%d)", location.row, location.column, targetLocation.row, targetLocation.column); // destroy unit [[thisUnit owner] destroyUnit:thisUnit wasDisbanded:NO]; moveResult = mr_destroyed; [self showExplosions:5 forPlayer:thisPlayer atLocation:location]; // Remove icon from map & update everyone. // Client game manager should have destroyed the unit. [self remove:[thisUnit icon] atLocation:location forPlayer:thisPlayer]; [self explorePlayer:thisPlayer around3x3Location:location updateOtherPlayers:YES]; } // Check for end of player/game at end of turn. Nah. if (moveResult == mr_captured_city) { BOOL gameOver; //NSLog (@"Checking for the end of player %d", targetPlayer); // Have defender check whether that was the last city. gameOver = [self checkForEndOfPlayer:targetPlayer]; } return moveResult; } //====================================================================== // Etc. //====================================================================== //====================================================================== // Map access/management //====================================================================== //---------------------------------------------------------------------- // By having the Distributed Game Manager (DGM) implement these // methods, some of the methods in this class don't need to be // reimplemented in the DGM. This is especially important for the // methods that have complex logic. //---------------------------------------------------------------------- // Needs to be overridden in DGM - (Map *) mapForPlayer:(Player)number { Map *map = nil; if (players[number] != nil) map = [players[number] map]; return map; } //---------------------------------------------------------------------- - (Map *) fetchMapForPlayer:(Player)number { return [self mapForPlayer:number]; } //---------------------------------------------------------------------- // Needs to be overridden in DGM - (void) set3x3Tokens:(MapToken *)tokens aroundLocation:(EMMapLocation)target forPlayer:(Player)number { Map *map = [self mapForPlayer:number]; NSAssert (map != nil, @"Player map was nil"); [map set3x3Tokens:tokens aroundLocation:target]; // In DGM, also set original copy... } //---------------------------------------------------------------------- - (void) remove:(Icon)icon atLocation:(EMMapLocation)target forPlayer:(Player)number { Map *map = [self mapForPlayer:number]; NSAssert (map != nil, @"Map was nil."); //NSLog (@"remove icon %d at %d,%d for player %d", icon, target.row, target.column, number); [map remove:icon atLocation:target]; } //---------------------------------------------------------------------- - (void) put:(Icon)icon atLocation:(EMMapLocation)target forPlayer:(Player)number { Map *map = [self mapForPlayer:number]; NSAssert (map != nil, @"Map was nil."); //NSLog (@"put icon %d at %d,%d for player %d", icon, target.row, target.column, number); [map put:number:icon atLocation:target]; } //---------------------------------------------------------------------- - (void) setCityAtLocation:(EMMapLocation)target toPlayer:(Player)newCityPlayer forPlayer:(Player)number { Map *map = [self mapForPlayer:number]; NSAssert (map != nil, @"Map was nil."); [map setCityAtLocation:target toPlayer:newCityPlayer]; } //====================================================================== // Etc. //====================================================================== //---------------------------------------------------------------------- // No need to override in DGM, thanks to mapForPlayer: //---------------------------------------------------------------------- - (MapToken) canonTokenAtLocation:(EMMapLocation)target { MapToken mapToken; Player p; Map *map; for (p = p_player1; p <= p_player3; p++) { map = [self mapForPlayer:p]; if (map != nil) { mapToken = [map tokenAtLocation:target]; if (EMPlayerComponent (mapToken) == p) return mapToken; } } NSAssert (world != nil, @"No active world."); return [[world worldMap] tokenAtLocation:target]; } //---------------------------------------------------------------------- // No need to override in DGM, thanks to mapForPlayer: //---------------------------------------------------------------------- - (int) playersAdjacentToLocation:(EMMapLocation)target { MapToken tokens[9]; BOOL playerFlags[4] = {NO, NO, NO, NO}; Player p; int l; int adjacentPlayers = 0; Map *map; for (p = p_player1; p <= p_player3; p++) { map = [self mapForPlayer:p]; if (map != nil) { [map get3x3Tokens:tokens aroundLocation:target]; //NSLog (@"player: %d, tokens: %@", p, EMFormatNineComponents (tokens)); for (l = 0; l < 9; l++) { if (EMPlayerComponent (tokens[l]) == p) { playerFlags[p] = YES; break; } } } } if (playerFlags[p_player1] == YES) adjacentPlayers |= ADJ_PLAYER1; if (playerFlags[p_player2] == YES) adjacentPlayers |= ADJ_PLAYER2; if (playerFlags[p_player3] == YES) adjacentPlayers |= ADJ_PLAYER3; //NSLog (@"players adjacent to (%d,%d): %x", target.row, target.column, adjacentPlayers); return adjacentPlayers; } //---------------------------------------------------------------------- // Simple version -- everything can see everything adjacent. //---------------------------------------------------------------------- //---------------------------------------------------------------------- // The hasChanged shortcut isn't valid, since we can put/remove/set without // notifying remote versions. // // Perhaps this "compression" of updates should be in the map. //---------------------------------------------------------------------- // No need to override in DGM... - (void) X_explorePlayer:(Player)number around3x3Location:(EMMapLocation)target updateOtherPlayers:(BOOL)flag { MapToken player_tokens[4][9]; MapToken original_tokens[9]; int enemyIndex[4]; Player p; int l; BOOL hasChanged; Map *map; // Get tokens for all players map = [self mapForPlayer:number]; NSAssert (map != nil, @"Player map was nil"); NSAssert (world != nil, @"No active world."); [[world worldMap] get3x3Tokens:player_tokens[p_neutral] aroundLocation:target]; [map get3x3Tokens:original_tokens aroundLocation:target]; for (p = p_player1; p <= p_player3; p++) { enemyIndex[p] = -1; map = [self mapForPlayer:p]; if (map != nil) { [map get3x3Tokens:player_tokens[p] aroundLocation:target]; //tmp = p; } } // Calculate real positions of all units for (p = p_player1; p <= p_player3; p++) { // Would be better to keep track of what maps were nil, above. map = [self mapForPlayer:p]; if (map != nil) { for (l = 0; l < 9; l++) { if (EMPlayerComponent (player_tokens[p][l]) == p) { player_tokens[p_neutral][l] = player_tokens[p][l]; enemyIndex[p] = l; } } } } hasChanged = NO; #if 0 for (l = 0; l < 9; l++) { player_tokens[p_neutral][l] = EMChangeExploredComponent (player_tokens[p_neutral][l], YES); if (player_tokens[p_neutral][l] != original_tokens[l]) { //NSLog (@"l: %d", l); hasChanged = YES; } } if (hasChanged == YES) ; #endif #if 0 if (debug_flag == YES) { //NSLog (@"Player: %d @ (%d,%d)", number, target.row, target.column); //NSLog (@"or: %@", EMFormatNineComponents (original_tokens)); //NSLog (@"p0: %@", EMFormatNineComponents (player_tokens[p_neutral])); //NSLog (@"p1: %@", EMFormatNineComponents (player_tokens[p_player1])); //NSLog (@"p2: %@", EMFormatNineComponents (player_tokens[p_player2])); //NSLog (@"p3: %@", EMFormatNineComponents (player_tokens[p_player3])); #if 0 NSLog (@"%08x %08x %08x %08x %08x %08x %08x %08x %08x", player_tokens[p_neutral][0], player_tokens[p_neutral][1], player_tokens[p_neutral][2], player_tokens[p_neutral][3], player_tokens[p_neutral][4], player_tokens[p_neutral][5], player_tokens[p_neutral][6], player_tokens[p_neutral][7], player_tokens[p_neutral][8] ); NSLog (@"%08x %08x %08x %08x %08x %08x %08x %08x %08x", player_tokens[p_player1][0], player_tokens[p_player1][1], player_tokens[p_player1][2], player_tokens[p_player1][3], player_tokens[p_player1][4], player_tokens[p_player1][5], player_tokens[p_player1][6], player_tokens[p_player1][7], player_tokens[p_player1][8] ); NSLog (@"%08x %08x %08x %08x %08x %08x %08x %08x %08x", player_tokens[p_player2][0], player_tokens[p_player2][1], player_tokens[p_player2][2], player_tokens[p_player2][3], player_tokens[p_player2][4], player_tokens[p_player2][5], player_tokens[p_player2][6], player_tokens[p_player2][7], player_tokens[p_player2][8] ); NSLog (@"%08x %08x %08x %08x %08x %08x %08x %08x %08x", player_tokens[p_player3][0], player_tokens[p_player3][1], player_tokens[p_player3][2], player_tokens[p_player3][3], player_tokens[p_player3][4], player_tokens[p_player3][5], player_tokens[p_player3][6], player_tokens[p_player3][7], player_tokens[p_player3][8] ); NSLog (@"%08x %08x %08x %08x %08x %08x %08x %08x %08x", original_tokens[0], original_tokens[1], original_tokens[2], original_tokens[3], original_tokens[4], original_tokens[5], original_tokens[6], original_tokens[7], original_tokens[8] ); #endif } #endif // // The hasChanged semantics aren't quite correct. We want to update our lost token, but // in this cas hasChanged will be NO. // // We will just have to filter out no-effect changes when recording updates... // // { //NSLog (@"Has changed."); [self set3x3Tokens:player_tokens[p_neutral] aroundLocation:target forPlayer:number]; } if (flag == YES) { int delta_row[9] = { -1, -1, -1, 0, 0, 0, 1, 1, 1 }; int delta_col[9] = { -1, 0, 1, -1, 0, 1, -1, 0, 1 }; for (p = p_player1; p <= p_player3; p++) { if (p != number && enemyIndex[p] >= 0) { EMMapLocation enemyLocation; int ei = enemyIndex[p]; enemyLocation.row = target.row + delta_row[ei]; enemyLocation.column = target.column + delta_col[ei]; [self explorePlayer:p around3x3Location:enemyLocation updateOtherPlayers:NO]; } } } } //---------------------------------------------------------------------- // The (NOT SO) simple version -- everything can see everything adjacent. //---------------------------------------------------------------------- // This is a somewhat nightmarish method, and perhaps one of the most // difficult to understand. // // The key is to realize that there is not a single source that has // the real location of all units. Instead, we can derive this // information by combining all the player maps. The icons of player N // on the map of player N represent the real location of those icons. // // We first get the canonical location of all units, and then decide // whether the player can see all the enemy units. // // The advantage of this is that we only have to distribute maps -- // we don't need to share lists of units, or provide access to units // over the connection of a distributed game. // // There is also some cleverness happening with the visibility flags. // I'd highly recommend drawing diagrams if you're trying to understand // what this code is doing. I've included two examples in the // technical documentation. //---------------------------------------------------------------------- // No need to override in DGM... - (void) explorePlayer:(Player)number around3x3Location:(EMMapLocation)target updateOtherPlayers:(BOOL)flag { MapToken player_tokens[4][9]; MapToken original_tokens[9]; int enemyIndex[4]; Player p; int l; Map *map; int canSeeFlags[9]; int lastUnitFlagList[9], realUnitFlagList[9]; int unitFlags[UNIT_TYPE_COUNT] = { 0, VIS_LAND, VIS_AIR, VIS_SEA, VIS_SUB, VIS_SEA, VIS_SEA, VIS_SEA, VIS_SEA, VIS_AIR, // VIS_SEA }; // Get tokens for all players map = [self mapForPlayer:number]; NSAssert (map != nil, @"Player map was nil"); NSAssert (world != nil, @"No active world."); [[world worldMap] get3x3Tokens:player_tokens[p_neutral] aroundLocation:target]; [map get3x3Tokens:original_tokens aroundLocation:target]; for (p = p_player1; p <= p_player3; p++) { enemyIndex[p] = -1; map = [self mapForPlayer:p]; if (map != nil) { [map get3x3Tokens:player_tokens[p] aroundLocation:target]; //tmp = p; } } // Calculate real positions of all units for (p = p_player1; p <= p_player3; p++) { // Would be better to keep track of what maps were nil, above. map = [self mapForPlayer:p]; if (map != nil) { for (l = 0; l < 9; l++) { if (EMPlayerComponent (player_tokens[p][l]) == p) { player_tokens[p_neutral][l] = player_tokens[p][l]; enemyIndex[p] = l; } } } } // Build up visiblity flags [self get3x3flags:canSeeFlags forPlayer:number aroundLocation:target]; //NSLog (@"real icons: %@", EMFormatNineComponents (player_tokens[p_neutral])); //NSLog (@"last icons: %@", EMFormatNineComponents (player_tokens[number])); // Build up attribute flags -- not really, both last known and real.. // Update player knowledge for (l = 0; l < 9; l++) { Player player; Icon icon; UnitType unitType; lastUnitFlagList[l] = 0; realUnitFlagList[l] = 0; EMMapTokenComponents (player_tokens[number][l], NULL, &player, &icon, NULL); unitType = EMConvertIconToUnitType (icon); if (player != number) lastUnitFlagList[l] = unitFlags[unitType]; EMMapTokenComponents (player_tokens[p_neutral][l], NULL, &player, &icon, NULL); unitType = EMConvertIconToUnitType (icon); if (player != number) realUnitFlagList[l] = unitFlags[unitType]; } //NSLog (@"real attrs: %@", EMFormatNineVisibilityFlags (realUnitFlagList)); //NSLog (@"last attrs: %@", EMFormatNineVisibilityFlags (lastUnitFlagList)); //NSLog (@"can see attrs: %@", EMFormatNineVisibilityFlags (canSeeFlags)); for (l = 0; l < 9; l++) { Terrain realTerrain, lastTerrain; Player realPlayer, lastPlayer; Icon realIcon, lastIcon; UnitType realUnitType, lastUnitType; EMMapTokenComponents (player_tokens[number][l], &lastTerrain, &lastPlayer, &lastIcon, NULL); EMMapTokenComponents (player_tokens[p_neutral][l], &realTerrain, &realPlayer, &realIcon, NULL); realUnitType = EMConvertIconToUnitType (realIcon); lastUnitType = EMConvertIconToUnitType (lastIcon); // Everything can see cities. if (realTerrain == t_city) { player_tokens[number][l] = player_tokens[p_neutral][l]; } // if (lastPlayer != number && lastIcon != realIcon && (unitFlags[lastUnitType] & canSeeFlags[l])) if (lastPlayer != number && lastIcon != realIcon && (lastUnitFlagList[l] & canSeeFlags[l])) { // remove the icon player_tokens[number][l] = EMCreateMapToken (realTerrain, p_neutral, i_none, YES); } // if (realPlayer != number && (unitFlags[realUnitType] & canSeeFlags[l])) if (realPlayer != number && (realUnitFlagList[l] & canSeeFlags[l])) { // put that icon player_tokens[number][l] = player_tokens[p_neutral][l]; } // remove known no longer there // add known there } for (l = 0; l < 9; l++) { player_tokens[p_neutral][l] = EMChangeExploredComponent (player_tokens[p_neutral][l], YES); player_tokens[number][l] = EMChangeExploredComponent (player_tokens[number][l], YES); } //NSLog (@"new icons: %@", EMFormatNineComponents (player_tokens[number])); //NSLog (@"...................."); #if 0 if (debug_flag == YES) { //NSLog (@"Player: %d @ (%d,%d)", number, target.row, target.column); //NSLog (@"or: %@", EMFormatNineComponents (original_tokens)); //NSLog (@"p0: %@", EMFormatNineComponents (player_tokens[p_neutral])); //NSLog (@"p1: %@", EMFormatNineComponents (player_tokens[p_player1])); //NSLog (@"p2: %@", EMFormatNineComponents (player_tokens[p_player2])); //NSLog (@"p3: %@", EMFormatNineComponents (player_tokens[p_player3])); #if 0 NSLog (@"%08x %08x %08x %08x %08x %08x %08x %08x %08x", player_tokens[p_neutral][0], player_tokens[p_neutral][1], player_tokens[p_neutral][2], player_tokens[p_neutral][3], player_tokens[p_neutral][4], player_tokens[p_neutral][5], player_tokens[p_neutral][6], player_tokens[p_neutral][7], player_tokens[p_neutral][8] ); NSLog (@"%08x %08x %08x %08x %08x %08x %08x %08x %08x", player_tokens[p_player1][0], player_tokens[p_player1][1], player_tokens[p_player1][2], player_tokens[p_player1][3], player_tokens[p_player1][4], player_tokens[p_player1][5], player_tokens[p_player1][6], player_tokens[p_player1][7], player_tokens[p_player1][8] ); NSLog (@"%08x %08x %08x %08x %08x %08x %08x %08x %08x", player_tokens[p_player2][0], player_tokens[p_player2][1], player_tokens[p_player2][2], player_tokens[p_player2][3], player_tokens[p_player2][4], player_tokens[p_player2][5], player_tokens[p_player2][6], player_tokens[p_player2][7], player_tokens[p_player2][8] ); NSLog (@"%08x %08x %08x %08x %08x %08x %08x %08x %08x", player_tokens[p_player3][0], player_tokens[p_player3][1], player_tokens[p_player3][2], player_tokens[p_player3][3], player_tokens[p_player3][4], player_tokens[p_player3][5], player_tokens[p_player3][6], player_tokens[p_player3][7], player_tokens[p_player3][8] ); NSLog (@"%08x %08x %08x %08x %08x %08x %08x %08x %08x", original_tokens[0], original_tokens[1], original_tokens[2], original_tokens[3], original_tokens[4], original_tokens[5], original_tokens[6], original_tokens[7], original_tokens[8] ); #endif } #endif // // The hasChanged semantics aren't quite correct. We want to update our lost token, but // in this cas hasChanged will be NO. // // We will just have to filter out no-effect changes when recording updates... // // { //NSLog (@"Has changed."); [self set3x3Tokens:player_tokens[number] aroundLocation:target forPlayer:number]; } if (flag == YES) { int delta_row[9] = { -1, -1, -1, 0, 0, 0, 1, 1, 1 }; int delta_col[9] = { -1, 0, 1, -1, 0, 1, -1, 0, 1 }; for (p = p_player1; p <= p_player3; p++) { if (p != number && enemyIndex[p] >= 0) { EMMapLocation enemyLocation; int ei = enemyIndex[p]; enemyLocation.row = target.row + delta_row[ei]; enemyLocation.column = target.column + delta_col[ei]; [self explorePlayer:p around3x3Location:enemyLocation updateOtherPlayers:NO]; } } } } //---------------------------------------------------------------------- - (void) get3x3flags:(int *)flags forPlayer:(Player)number aroundLocation:(EMMapLocation)target { MapToken ours[25]; int can_see_flags[25]; int merged_flags[9]; int blarg[9][9] = { { 0, 1, 2, 5, 6, 7, 10, 11, 12}, { 1, 2, 3, 6, 7, 8, 11, 12, 13}, { 2, 3, 4, 7, 8, 9, 12, 13, 14}, { 5, 6, 7, 10, 11, 12, 15, 16, 17}, { 6, 7, 8, 11, 12, 13, 16, 17, 18}, { 7, 8, 9, 12, 13, 14, 17, 18, 19}, { 10, 11, 12, 15, 16, 17, 20, 21, 22}, { 11, 12, 13, 16, 17, 18, 21, 22, 23}, { 12, 13, 14, 17, 18, 19, 22, 23, 24} }; int a, b; int unit_can_see_flags[UNIT_TYPE_COUNT] = { 0, VIS_LAND | VIS_SEA | VIS_AIR, VIS_LAND | VIS_SEA | VIS_AIR, VIS_LAND | VIS_SEA | VIS_AIR, VIS_SUB | VIS_SEA, VIS_LAND | VIS_SEA | VIS_AIR, VIS_SUB | VIS_LAND | VIS_SEA | VIS_AIR, VIS_LAND | VIS_SEA | VIS_AIR, VIS_LAND | VIS_SEA | VIS_AIR, VIS_LAND | VIS_SEA | VIS_AIR, // VIS_SUB | VIS_LAND | VIS_SEA | VIS_AIR }; Terrain terrain; Player player; Icon icon; [[self mapForPlayer:number] get5x5Tokens:ours aroundLocation:target]; //NSLog (@"player %d's 25 tokens: %@", number, EMFormat25Components (ours)); for (a = 0; a < 25; a++) { can_see_flags[a] = 0; EMMapTokenComponents (ours[a], &terrain, &player, &icon, NULL); if (player == number) { if (terrain == t_city) can_see_flags[a] = VIS_SUB | VIS_LAND | VIS_SEA | VIS_AIR; else can_see_flags[a] = unit_can_see_flags[EMConvertIconToUnitType (icon)]; } } //NSLog (@"player %d can see: %@", number, EMFormat25VisibilityFlags (can_see_flags)); // Merge flags for (a = 0; a < 9; a++) { merged_flags[a] = 0; for (b = 0; b < 9; b++) { merged_flags[a] |= can_see_flags[ blarg[a][b] ]; } flags[a] = merged_flags[a]; } //NSLog (@"merged flags: %@", EMFormatNineVisibilityFlags (merged_flags)); } //====================================================================== // Combating... //====================================================================== - (MoveResult) unit:(Unit *)attacker attacksPlayer:(Player)cityPlayer cityAtLocation:(EMMapLocation)target { CombatProfile attackerProfile; MoveResult moveResult; //NSLog (@"attacker: %@, cityPlayer: %d, cityLoc: %d,%d", attacker, cityPlayer, target.row, target.column); NSAssert (attackingUnit == nil, @"attackingUnit is not nil."); attackingUnit = attacker; // Build up attacking army's combat profile: attackerProfile = [attacker combatProfileAgainstBombardment:NO]; attackerProfile.adjacent_players = [self playersAdjacentToLocation:attackerProfile.location]; moveResult = [self unitWithCombatProfile:attackerProfile attacksPlayer:cityPlayer cityAtLocation:target playersAdjacentToDefender:[self playersAdjacentToLocation:target]]; attackingUnit = nil; return moveResult; } //---------------------------------------------------------------------- - (MoveResult) unitWithCombatProfile:(CombatProfile)attackerProfile attacksPlayer:(Player)cityPlayer cityAtLocation:(EMMapLocation)target playersAdjacentToDefender:(int)adjacentPlayers { CombatProfile defenderProfile; MoveResult moveResult; //NSLog (@"cityPlayer: %d, cityLocation: %d,%d patd: %d", cityPlayer, target.row, target.column, adjacentPlayers); // Get defender combat profile defenderProfile = [self readyDefendingCityAtLocation:target forPlayer:cityPlayer]; defenderProfile.adjacent_players = adjacentPlayers; moveResult = [self attackerWithCombatProfile:attackerProfile attacksDefenderWithCombatProfile:defenderProfile]; return moveResult; } //---------------------------------------------------------------------- - (MoveResult) unit:(Unit *)attacker attacksPlayer:(Player)defender unitAtLocation:(EMMapLocation)target withBombardment:(BOOL)bombarding { CombatProfile attackerProfile; MoveResult moveResult; //NSLog (@"attacker: %@, defender: %d, cityLoc: %d,%d", attacker, defender, target.row, target.column); NSAssert (attackingUnit == nil, @"attackingUnit is not nil."); attackingUnit = attacker; // Build up attacking army's combat profile: attackerProfile = [attacker combatProfileAgainstBombardment:NO]; attackerProfile.adjacent_players = [self playersAdjacentToLocation:attackerProfile.location]; moveResult = [self unitWithCombatProfile:attackerProfile attacksPlayer:defender unitAtLocation:target withBombardment:bombarding playersAdjacentToDefender:[self playersAdjacentToLocation:target]]; attackingUnit = nil; return moveResult; } //---------------------------------------------------------------------- - (MoveResult) unitWithCombatProfile:(CombatProfile)attackerProfile attacksPlayer:(Player)defender unitAtLocation:(EMMapLocation)target withBombardment:(BOOL)bombarding playersAdjacentToDefender:(int)adjacentPlayers { CombatProfile defenderProfile; MoveResult moveResult; //NSLog (@"defender: %d, cityLocation: %d,%d patd: %d", defender, target.row, target.column, adjacentPlayers); // Get defender combat profile defenderProfile = [self readyDefendingUnitAtLocation:target forPlayer:defender againstBombardment:bombarding]; defenderProfile.adjacent_players = adjacentPlayers; moveResult = [self attackerWithCombatProfile:attackerProfile attacksDefenderWithCombatProfile:defenderProfile]; return moveResult; } //---------------------------------------------------------------------- // Duke it out: - (MoveResult) attackerWithCombatProfile:(CombatProfile)attackerProfile attacksDefenderWithCombatProfile:(CombatProfile)defenderProfile { int attackerAttacks; int attackerDefends; int defenderAttacks; int defenderDefends; MoveResult result = mr_blocked; BOOL done; SNRandom *rng; int defenderBits = 1 << (defenderProfile.player - p_neutral); int attackerBits = 1 << (attackerProfile.player - p_neutral); // Create combatant mask // [self showExplosions:l+1 playerMask:attackerProfile.adjacent&~mask atLocation:attackerProfile.location]; // Defender hit: // - show explosions for masked observers // - [defender hitFor:n]; // - [attacker showExplosions...]; // Attacker hit: // - show explosions for masked observers // - [attacker hitFor:n]; // - [defender showExplosions...]; // Originating game manager will update map(s) by removing appropriate icon after it is destroyed... NSAssert (defenderProfile.attacking_efficiency != 0, @"Defenders efficiency is 0."); NSAssert (attackerProfile.attacking_efficiency != 0, @"Attackers efficiency is 0."); NSAssert (attackerProfile.damage_per_hit > 0, @"Attackers damage per hit is 0."); NSAssert (defenderProfile.damage_per_hit > 0, @"Defenders damage per hit is 0."); rng = [SNRandom instance]; // First, we need to make sure that both units can see each other. // For example, normally a sub and a fighter can't see each other, but if // one tries to move into the square occupied by the other, they will fight. done = NO; while (done == NO) { attackerAttacks = [rng randomNumberModulo:100]; attackerDefends = [rng randomNumberModulo:100]; defenderAttacks = [rng randomNumberModulo:100]; defenderDefends = [rng randomNumberModulo:100]; #if 0 NSLog (@"a[d]/e, Attacker: %d[%d]/%d[%d], Defender: %d[%d]/%d[%d]", attackerAttacks, attackerProfile.attacking_efficiency, attackerDefends, attackerProfile.defending_efficiency, defenderAttacks, defenderProfile.attacking_efficiency, defenderDefends, defenderProfile.defending_efficiency); #endif if (attackerAttacks < attackerProfile.attacking_efficiency && defenderDefends >= defenderProfile.defending_efficiency) { //NSLog (@"Defender hit."); // Hit defender for attackerProfile.damage_per_hit point(s). // Show explosions to both combatants. (as well as third parties...) [self showExplosions:attackerProfile.damage_per_hit atLocation:defenderProfile.location toPlayers:defenderProfile.adjacent_players & ~defenderBits]; if (defenderProfile.is_a_city == YES) { //[self hitDefendingCity:defenderProfile.player withDamage:attackerProfile.damage_per_hit]; City *capturedCity = [self lostDefendingCityOfPlayer:defenderProfile.player]; //NSLog (@"captured city: %@", capturedCity); //NSLog (@"it's owner: %@", [capturedCity owner]); [self player:attackerProfile.player hasCapturedCity:capturedCity]; //NSLog (@"about to explore"); //[self explorePlayer:attackerProfile.player around3x3Location:defenderProfile.location updateOtherPlayers:YES]; //NSLog (@"explored"); } else { [self hitDefendingUnit:defenderProfile.player withDamage:attackerProfile.damage_per_hit]; } defenderProfile.current_hit_points -= attackerProfile.damage_per_hit; if (defenderProfile.current_hit_points <= 0) { done = YES; result = mr_victory; } } // Hmm. We don't want a city captured, but the army destroyed in the attempt. // The army is supposed to be transformed into a garrison... if (done == NO && defenderAttacks < defenderProfile.attacking_efficiency && attackerDefends >= attackerProfile.defending_efficiency) { //NSLog (@"Attacker hit."); // Hit attacker for defenderProfile.damage_per_hit point(s). // Show both combatants. [self showExplosions:defenderProfile.damage_per_hit atLocation:attackerProfile.location toPlayers:attackerProfile.adjacent_players &~ attackerBits]; // The attacker better not be a city! [self hitAttacker:attackerProfile.player withDamage:defenderProfile.damage_per_hit]; attackerProfile.current_hit_points -= defenderProfile.damage_per_hit; if (attackerProfile.current_hit_points <= 0) { [self finishedCombatForPlayer:defenderProfile.player]; done = YES; result = mr_destroyed; } } } return result; } //---------------------------------------------------------------------- - (CombatProfile) readyDefendingCityAtLocation:(EMMapLocation)target forPlayer:(Player)number { City *city; CombatProfile cityProfile; //NSLog (@"target: %d,%d player: %d", target.row, target.column, number); NSAssert (defendingCity == nil, @"Defending city is not nil."); NSAssert (defendingUnit == nil, @"Defending unit is not nil."); if (number == p_neutral) { // Fill in standard city combat profile cityProfile.location = target; cityProfile.player = p_neutral; cityProfile.attacking_efficiency = 50; cityProfile.defending_efficiency = 50; cityProfile.damage_per_hit = 1; cityProfile.current_hit_points = 1; cityProfile.is_a_city = YES; defendingCity = [world neutralCityAtLocation:target]; } else { NSAssert (players[number] != nil, @"We don't have that player."); // Find city city = [players[number] cityAtLocation:target]; NSAssert (city != nil, @"That player doesn't have that city."); defendingCity = city; cityProfile.location = target; cityProfile.player = number; cityProfile.attacking_efficiency = [players[number] combatEfficiency]; cityProfile.defending_efficiency = [players[number] combatEfficiency]; cityProfile.damage_per_hit = 1; cityProfile.current_hit_points = 1; cityProfile.is_a_city = YES; } cityProfile.adjacent_players = [self playersAdjacentToLocation:target]; return cityProfile; } //---------------------------------------------------------------------- - (CombatProfile) readyDefendingUnitAtLocation:(EMMapLocation)target forPlayer:(Player)number againstBombardment:(BOOL)bombarding { Unit *unit; CombatProfile unitProfile; //NSLog (@"target: %d,%d player: %d", target.row, target.column, number); NSAssert (defendingCity == nil, @"Defending city is not nil."); NSAssert (defendingUnit == nil, @"Defending unit is not nil."); NSAssert (players[number] != nil, @"We don't have that player."); // Find city unit = [players[number] primaryUnitAtLocation:target]; NSAssert (unit != nil, @"That player doesn't a unit at that location."); defendingUnit = unit; unitProfile = [unit combatProfileAgainstBombardment:bombarding]; unitProfile.adjacent_players = [self playersAdjacentToLocation:target]; return unitProfile; } //---------------------------------------------------------------------- - (void) showExplosions:(int)count atLocation:(EMMapLocation)target toPlayers:(int)playerMask { if (playerMask & 0x01) [self showExplosions:count forPlayer:p_player1 atLocation:target]; if (playerMask & 0x02) [self showExplosions:count forPlayer:p_player2 atLocation:target]; if (playerMask & 0x04) [self showExplosions:count forPlayer:p_player3 atLocation:target]; } //---------------------------------------------------------------------- - (void) showExplosions:(int)count forPlayer:(Player)number atLocation:(EMMapLocation)target { NSAssert (players[number] != nil, @"We don't have that player."); [players[number] showExplosions:count atLocation:target]; } //---------------------------------------------------------------------- - (void) hitDefendingUnit:(Player)number withDamage:(int)damage { BOOL wasDestroyed; NSAssert (players[number] != nil, @"We don't have that player."); NSAssert (defendingUnit != nil, @"No defending unit!"); wasDestroyed = [defendingUnit hitForDamage:damage]; if (wasDestroyed == YES) { //NSLog (@"Unit(defender) was destroyed."); defendingUnit = nil; } } //---------------------------------------------------------------------- - (City *) lostDefendingCityOfPlayer:(Player)number { City *city; NSAssert (number == p_neutral || players[number] != nil, @"We don't have that player."); NSAssert (defendingCity != nil, @"No defending city!"); NSAssert (world != nil, @"No active world."); city = [[defendingCity retain] autorelease]; defendingCity = nil; if (number == p_neutral) { [world lostCity:city]; } else { [players[number] showExplosions:1 atLocation:[city cityLocation]]; [players[number] lostCity:city]; } return city; } //---------------------------------------------------------------------- - (void) hitAttacker:(Player)number withDamage:(int)damage { BOOL wasDestroyed; NSAssert (players[number] != nil, @"We don't have that player."); NSAssert (attackingUnit != nil, @"No attacking unit!"); wasDestroyed = [attackingUnit hitForDamage:damage]; if (wasDestroyed == YES) { //NSLog (@"Unit(attacker) was destroyed."); attackingUnit = nil; } } //---------------------------------------------------------------------- - (void) player:(Player)number hasCapturedCity:(City *)capturedCity { NSAssert (players[number] != nil, @"We don't have that player."); [players[number] capturedCity:capturedCity]; // Will also need notification of lost unit... attackingUnit = nil; } //---------------------------------------------------------------------- // Notify defender that the combat is over. (He won). //---------------------------------------------------------------------- - (void) finishedCombatForPlayer:(Player)number { // Should not need to reset attacker, done elsewhere. defendingUnit = nil; defendingCity = nil; } //---------------------------------------------------------------------- // Returns YES if the attacker has won. // Client -> Server - (BOOL) checkForEndOfPlayer:(Player)number { Player winningPlayer, p; BOOL done = NO; BOOL gameOver = NO; //NSAssert (number == p_neutral || players[number] != nil, @"We don't have that player."); //[self logStatus]; if (number != p_neutral) { done = [self hasPlayerLost:number]; // Server -> Client if (done == YES) { [self storeFinalMapForPlayer:number]; [self playerHasLost:number activePlayers:[self activePlayers]]; // Server -> Client //nyi: Strip icons for other players? Probably best to avoid fighting phantom units...? //[self deactivatePlayer:number]; // Notify remaining active players? //NSLog (@"active player count: %d", activePlayerCount); if (activePlayerCount < 2) { gameOver = YES; } } } else { // Hmm. Captured last neutral city, need to detect win. // Captured last neutral city, but still remaining enemy cities.. } if (done == YES && gameOver == NO) { // Notify other players so they can strip that players icons from their maps. // Do this after notifying player, so that player picks up pre-stripped maps. // Don't want to fight phantoms. for (p = p_player1; p <= p_player3; p++) { if (playersActive[p] == YES && p != number) [self notifyPlayer:p aPlayerHasResigned:number]; } } // Perhaps this should be moved into the server? if (gameOver == YES) { BOOL keepPlaying; winningPlayer = p_neutral; for (p = p_player1; p <= p_player3; p++) { if (playersActive[p] == YES) winningPlayer = p; } NSAssert (playersActive[winningPlayer] == YES, @"Expected an active player to win."); keepPlaying = [self playerHasWon:winningPlayer activePlayers:[self activePlayers]]; // Server -> Client // Also terminates both client and server... } //[self logStatus]; return gameOver; } //---------------------------------------------------------------------- - (BOOL) hasPlayerLost:(Player)number { NSAssert (players[number] != nil, @"We don't have that player."); return [players[number] hasPlayerLost]; } //---------------------------------------------------------------------- - (void) playerHasLost:(Player)number activePlayers:(int)activePlayers { NSAssert (players[number] != nil, @"We don't have that player."); // cache current (final) map for player number [players[number] playerHasLost:activePlayers]; //nyi: ?? Hmm... Not in clients -- but it doesn't hurt. [self deactivatePlayer:number]; } //---------------------------------------------------------------------- - (BOOL) playerHasWon:(Player)number activePlayers:(int)activePlayers { BOOL keepPlaying; NSAssert (players[number] != nil, @"We don't have that player."); keepPlaying = [players[number] playerHasWon:activePlayers]; if (keepPlaying == NO) { [self deactivatePlayer:number]; } return keepPlaying; } //---------------------------------------------------------------------- // Server: // currentMap:(Map *)currentMap // or perhaps we still need a separate method... - (void) resignPlayerFromGame:(Player)number { NSArray *remainingCities; Player p; //[self logStatus]; remainingCities = [self remainingCitiesForPlayer:number]; //NSLog (@"remaining cities: %@", remainingCities); NSAssert (world != nil, @"No active world."); [world addNeutralCities:remainingCities]; [self storeFinalMapForPlayer:number]; [self playerHasResigned:number activePlayers:[self activePlayers]]; // Notify other players so they can strip that players icons from their maps. // Do this after notifying player, so that player picks up pre-stripped maps. for (p = p_player1; p <= p_player3; p++) { if (playersActive[p] == YES && p != number) [self notifyPlayer:p aPlayerHasResigned:number]; } // I need to reduce the activePlayerCount here so that I know if // a player has won... [self deactivatePlayer:number]; if (activePlayerCount <= 1) { Player p, winningPlayer = p_neutral; for (p = p_player1; p <= p_player3; p++) if (playersActive[p] == YES) winningPlayer = p; //NSLog (@"winning player: %d", winningPlayer); // What about a remote player? if (winningPlayer != p_neutral) { BOOL keepPlaying; NSAssert (playersActive[winningPlayer] == YES, @"Expected an active player to win."); keepPlaying = [self playerHasWon:winningPlayer activePlayers:[self activePlayers]]; //NSLog (@"keep playing? %@", keepPlaying ? @"Yes" : @"No"); if (keepPlaying == YES && number == [self activePlayer]) { // force the next turn... [self turnDone:awaitingCookie]; } } } else if (number == [self activePlayer]) { // force the next turn... [self turnDone:awaitingCookie]; } //[self logStatus]; } //---------------------------------------------------------------------- // Should only be called in server. - (int) activePlayers { int active = 0; if (playersActive[p_player1] == YES) active |= ADJ_PLAYER1; if (playersActive[p_player2] == YES) active |= ADJ_PLAYER2; if (playersActive[p_player3] == YES) active |= ADJ_PLAYER3; return active; } //---------------------------------------------------------------------- // Sent only to active player. // NO - need to send to all other players. //... - (void) notifyPlayer:(Player)number aPlayerHasResigned:(Player)resignedPlayer { NSAssert (players[number] != nil, @"We don't have that player."); [players[number] aPlayerHasResigned:resignedPlayer]; } //---------------------------------------------------------------------- // Returns player whose turn it is. - (Player) activePlayer { Player player = p_neutral; switch (gameState) { case gs_player1_turn: player = p_player1; break; case gs_player2_turn: player = p_player2; break; case gs_player3_turn: player = p_player3; break; default: player = p_neutral; } return player; } //---------------------------------------------------------------------- - (NSArray *) remainingCitiesForPlayer:(Player)number { NSArray *remainingCities; NSAssert (players[number] != nil, @"We don't have that player."); remainingCities = [players[number] remainingCities]; return remainingCities; } //---------------------------------------------------------------------- - (void) deactivatePlayer:(Player)number { if (playersActive[number] == YES) { playersActive[number] = NO; SNRelease (players[number]); activePlayerCount--; //NSLog (@"active player count: %d", activePlayerCount); [[NSNotificationCenter defaultCenter] postNotificationName:EMPlayerStateChangedNotification object:self]; // DGM does further cleanup after this. Just make it do it before? if (activePlayerCount == 0) { [self theGameIsOver]; } } } //---------------------------------------------------------------------- - (void) playerHasResigned:(Player)number activePlayers:(int)activePlayers { NSAssert (players[number] != nil, @"We don't have that player."); [players[number] playerHasResigned:activePlayers]; } //---------------------------------------------------------------------- // Only done in server. - (void) storeFinalMapForPlayer:(Player)number { NSAssert (finalMaps[number] == nil, @"Final map already set."); finalMaps[number] = [[self fetchMapForPlayer:number] retain]; } //---------------------------------------------------------------------- // returns final map (if it exists i.e. player already done), or present map otherwise. //---------------------------------------------------------------------- - (Map *) finalMapForPlayer:(Player)number { Map *map = finalMaps[number]; //NSLog (@"GM-player: %d", number); //NSLog (@"final map[%d]: %@", number, finalMaps[number]); if (map == nil) map = [self fetchMapForPlayer:number]; //NSLog (@"map: %@", map); return map; } //---------------------------------------------------------------------- - (void) logStatus { NSLog (@"foo."); } //---------------------------------------------------------------------- - (void) theGameIsOver { //[self logStatus]; } //---------------------------------------------------------------------- // Game Status support //---------------------------------------------------------------------- - (NSString *) gameStatus { NSString *gameStateNames[] = { @"No Game", @"Establishing Game", @"Client Active", @"Player 1 Turn", @"Player 2 Turn", @"Player 3 Turn", @"Game Over", }; //NSLog (@"game state: %d", gameState); //NSLog (@"gs_no_game: %d", gs_no_game); return gameStateNames[gameState]; } //---------------------------------------------------------------------- - (NSDictionary *) playerStatus:(Player)number { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; [dict setObject:playersActive[number] == YES ? @"Active" : @"Inactive" forKey:@"Status"]; if (players[number] != nil) [dict setObject:[players[number] playerName] forKey:@"Name"]; return dict; } //---------------------------------------------------------------------- - (void) setGameState:(GameState)newGameState { gameState = newGameState; [[NSNotificationCenter defaultCenter] postNotificationName:EMGameStateChangedNotification object:self]; } //---------------------------------------------------------------------- - (BOOL) validateMenuItem:(NSMenuItem *)menuCell { SEL action = [menuCell action]; BOOL valid = NO; if (action == @selector (showCrystalBall:)) { valid = [self gameInProgress]; } else if (action == @selector (toggleBerserk:)) { valid = YES; } else if (action == @selector (stopGame:)) { valid = [self gameInProgress]; } return valid; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.