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.