ftp.nice.ch/pub/next/games/board/RiskStrat.s.tar.gz#/Strat.cp/PlayerCode.m

This is PlayerCode.m in view mode; [Download] [Up]

// PlayerCode.m
// Part of Risk by Mike Ferris
// Based on Chaotic.m by Mike Ferris, modified by Royce Howland

// From Mike's original Chaotic.m:
// Chaotic players don't have much strategy, but they do attack, turn in
// cards, and generally try to screw other players up.

#import "PlayerCode.h"
#import "SortedList.h"
#import <appkit/Application.h>
#import <objc/List.h>
#import <appkit/Panel.h>
#import <appkit/publicWraps.h>

#define rearguard()	(2 * ((gameTurn / 4) + 1))

@implementation Strat

+ initialize
// set the version number
{
	if (self = [Strat class])  {
		[self setVersion:1];
	}
	return self;
}

- initPlayerNum:(int)pnum mover:mover gameSetup:gamesetup mapView:mapview
				cardManager:cardmanager
// initialize my instance vars
{
	id countries;
	int i, j, countryNum;
	id theCountry, otherCountries;
	int k, exits, forneighbors, contneighbors;
	
	// always call the super
	[super initPlayerNum:pnum mover:mover gameSetup:gamesetup mapView:mapview
				cardManager:cardmanager];
	
	// Loop through each continent, setting up various instance vars.
	for (i = 0; i < 6; i++)  {
		// No country possessed in this continent yet.
		countryInContinent[i] = NO;

		// Set up the country/continent array for this continent.
		// Also init country conquered array.
		countries = [self countriesInContinent:i];
		for (j = 0; j < [countries count]; j++) {
			countryNum = [[countries objectAt:j] idNum];
			if (countryNum >= MINCOUNTRY && countryNum <= MAXCOUNTRY) {
				countryContinents[countryNum] = i;
				turnCountryConquered[countryNum] = 0;
			}
		}
		[countries free];
	}

	// For each continent, find the number of countries on it that
	// border another continent.  Also find the number of countries
	// on other continents that border each country on it, and that
	// border it in total (non-unique; a country on continent B that
	// borders two countries on continent A counts twice as a neighbor
	// of continent A).
	continentFudge[NORTH_AMERICA] = initContinentFudge[NORTH_AMERICA] = 50;
	continentFudge[SOUTH_AMERICA] = initContinentFudge[SOUTH_AMERICA] = 50;
	continentFudge[EUROPE] = initContinentFudge[EUROPE] = 25;
	continentFudge[AFRICA] = initContinentFudge[AFRICA] = 25;
	continentFudge[ASIA] = initContinentFudge[ASIA] = -50;
	continentFudge[AUSTRALIA] = initContinentFudge[AUSTRALIA] = 50;
	for (i = 0; i < 6; i++)  {
		initContinentFudge[i] += [rng randMax:30] - 15;
		continentExits[i] = continentNeighbors[i] = contneighbors = 0;
		countries = [self countriesInContinent:i];
		for (j = 0; j < [countries count]; j++) {
			forneighbors = exits = 0;
			theCountry = [countries objectAt:j];
			countryNum = [theCountry idNum];
			countryNeighbors[countryNum] = 0;
			otherCountries = [self neighborsTo:theCountry];
			for (k = 0; k < [otherCountries count]; k++) {
				countryNeighbors[countryNum]++;
				if (countryContinents[countryNum] !=
						countryContinents[[[otherCountries objectAt:k] idNum]]) {
					exits = 1;
					forneighbors++;
					contneighbors++;
				}
			}
			countryForeignNeighbors[countryNum] = forneighbors;
			if (exits)
				continentExits[i]++;
			[otherCountries free];
		}
		continentNeighbors[i] = contneighbors;
		[countries free];
	}
#if 0
	continentExits[NORTH_AMERICA] = 3;
	continentNeighbors[NORTH_AMERICA] = 3;
	continentExits[SOUTH_AMERICA] = 2;
	continentNeighbors[SOUTH_AMERICA] = 2;
	continentExits[EUROPE] = 4;
	continentNeighbors[EUROPE] = 8;
	continentExits[AFRICA] = 3;
	continentNeighbors[AFRICA] = 6;
	continentExits[ASIA] = 5;
	continentNeighbors[ASIA] = 8;
	continentExits[AUSTRALIA] = 1;
	continentNeighbors[AUSTRALIA] = 1;
#endif

	gameTurn = 0;
	numCountriesLastTurn = 0;
	turnsSinceVict = 0;
	front1 = front2 = front3 = -1;

	// Attack all out all the time?
	banzaiMode = NO;
	if ([rng randMax:99] >= 85)
		banzaiMode = YES;

	// Limit our attack to a lesser number of fronts depending on
	// various conditions?
	limitMode = NO;
	if ([rng randMax:99] >= 75)
		limitMode = YES;

	return self;
}

// *****************subclass responsibilities*********************

- yourChooseCountry
// A chaotic player seeks to wreak havoc (in a very limited way) on the
// rest of the players.  Toward this end he tries to choose at least one
// country in each continent.  After that he chooses random countries.
{
	id temp, country=nil;
	int cont=0;

#if 0
	// first try to get a country in each of the continents
	// find the first continent we haven't got a country in yet
	while ((cont<6) && (country==nil))  {
		if (countryInContinent[cont] == NO)  {
			id contList = [self countriesInContinent:cont];
			int i, c = [contList count];
			// we found a continent we don't have a country in yet.
			// see if there are unoccupied countries in it.
			
			// loop backwards cause we're gonna delete from the list
			for (i=c-1;i>=0;i--)  {
				// if this country is occupied remove it
				if ([[contList objectAt:i] player] != -1)  {
					[contList removeObjectAt:i];
				}
			}
			// now contList contains a list of all unoccupied countries
			// in the continent, but are there any?
			if ([contList count] > 0)  {
				// Yes, so choose a random country
				country=[contList objectAt:[rng randMax:[contList count]-1]];
				// record the fact that we've got a country in this continent
				countryInContinent[cont]=YES;
			}
			// remember to always free any list you ask for
			[contList free];
		}
		cont++;
	}
	
	// choose a random country if we still haven't got one
	if (country == nil)  {
		id unoccList = [self unoccupiedCountries];
		if ([unoccList count] > 0)  {
			country = [unoccList objectAt:[rng randMax:[unoccList count]-1]];
		}
		// always free lists you ask for
		[unoccList free];
	}
#else
	// Now choose countries according to how desirable their continents
	// look.
	if (country == nil) {
		id countries;
		int i, cc, numfr, bestCont = 0;
		float score, bestContScore = -1000, bestCountryScore = -1000;

		// For each continent, find its score and compare to the
		// best score seen so far.  Record the best continent & its
		// score.  A continent with no unoccupied countries is
		// ignored.
		for (cont = 0; cont < 6; cont++) {
			// Basic score based on the percentage of the
			// continent occupied by us already, if there are
			// unoccupied countries, plus a bit for each
			// unoccupied country.  If there aren't any, the
			// continent is ignored.
			countries = [self countriesInContinent:cont];
			cc = [countries count];
			numfr = 0;
			score = 0;
			for (i = cc - 1; i >= 0; i--) {
				temp = [countries objectAt:i];
				if ([temp player] == -1)
					score += 5;
				else {
					if ([temp player] == myPlayerNum)
						numfr++;
					[countries removeObjectAt:i];
				}
			}
			if ([countries count] == 0)	// None unoccupied.
				score = -1000;
			else
				score += (double)numfr / (double)cc * 500;
			[countries free];

			// Ignore the continent?
			if (score == -1000)
				continue;	// Yes.

			// Penalty for the number of countries in the
			// continent.  This makes us tend to go after smaller
			// continents (smaller penalty for smaller continents).
			// Other penalties for the number of entry/exit points
			// on the continent, and the number of neighboring
			// countries on other continents.
			score -= cc * 5;
			score -= [self exitsOfContinent:cont] * 5;
			score -= [self neighborsOfContinent:cont] * 5;

			// Add the game start fudge factor bonus.
			score += initContinentFudge[cont];

			// Is this the best score so far?  Record it if so.
			// If it equals a previous score, maybe pick it.
			if (score > bestContScore ||
					(score == bestContScore &&
					[rng randMax:99] < 50)) {
				bestCont = cont;
				bestContScore = score;
			}
		}

		// Now that we have the best continent picked, grab an
		// unoccupied country on it.  Pick the country with the
		// fewest neighbors.  If several have an equal number of
		// neighbors, semi-randomly choose one of them.
		countries = [self countriesInContinent:bestCont];
		for (i = 0; i < [countries count]; i++) {
			temp = [countries objectAt:i];
			if ([temp player] == -1 &&
					(countryNeighbors[[temp idNum]] > bestCountryScore ||
					(countryNeighbors[[temp idNum]] == bestCountryScore &&
					[rng randMax:99] < 50))) {
				country = [countries objectAt:i];
				bestCountryScore = countryNeighbors[[country idNum]];
			}
		}
	}
#endif
	
	// for diagnostic
	// the following code is used while developing when you are making the
	// player a subclass of Diagnostic.  When the class is a subclass of
	// ComputerPlayer, it must be removed.
//	[self setNotes:"sent by yourChooseCountry.  "
//					"yourChooseCountry chose a country at random."];
	
	// now occupy the country we've chosen
	[self occupyCountry:country];
	numCountriesLastTurn++;		// We have another country.

	return self;
}

- yourInitialPlaceArmies:(int)numArmies
// Place all armies in countries with placeArmies:.
{
	[self placeArmies:numArmies];

	// for diagnostic
//	[self setNotes:"yourInitialPlaceArmies: ran.  "
//					"yourInitialPlaceArmies placed all armies into "
//					"non land locked countries."];
	return self;
}

// Take a country list and return a list sorted by armies.  Does not
// free the input list.
- sortCountriesByArmies:countries
{
	int i;
	id scountries;

	scountries = [[StratSortedList alloc] initCount:2 forPlayer:self];
	[scountries setSortOrder:DESCENDING];
	[scountries setKeySortType:COUNTRYBYARMIES];

	for (i=0; i<[countries count]; i++)
		[scountries addObject:[countries objectAt:i]];

//	[scountries printKeyValues];

	return scountries;
}

// Take a country list and return a list sorted by armies.  Does not
// free the input list.
- sortCountriesByTAS:countries
{
	int i;
	id scountries;

	scountries = [[StratSortedList alloc] initCount:2 forPlayer:self];
	[scountries setSortOrder:DESCENDING];
	[scountries setKeySortType:COUNTRYBYTACTICALADVANTAGESTRONG];

	for (i=0; i<[countries count]; i++)
		[scountries addObject:[countries objectAt:i]];

//	[scountries printKeyValues];

	return scountries;
}

// Take a country list and return a list sorted by need for armies, with most
// needy countries first, least needy countries last.  Does not free the
// input list.
- sortCountriesByReinforcePrio:countries
{
	int i;
	id scountries;

	scountries = [[StratSortedList alloc] initCount:2 forPlayer:self];
	[scountries setSortOrder:DESCENDING];
	[scountries setKeySortType:COUNTRYBYREINFORCEPRIO];

	for (i=0; i<[countries count]; i++)
		[scountries addObject:[countries objectAt:i]];

//	[scountries printKeyValues];

	return scountries;
}

// Take an enemy list and return a list sorted by attackability, with most
// choice targets first, least choice target last.  Does not free the
// input list.
- sortEnemiesByAttackability:countries
{
	int i;
	id scountries;

	scountries = [[StratSortedList alloc] initCount:2 forPlayer:self];
	[scountries setSortOrder:DESCENDING];
	[scountries setKeySortType:ENEMYBYATTACKABILITY];

	for (i=0; i<[countries count]; i++)
		[scountries addObject:[countries objectAt:i]];

//	[scountries printKeyValues];

	return scountries;
}

// Wipe out the fronts we were using.
- resetFronts
{
	front1 = front2 = front3 = -1;
	return self;
}

- (int)continentOfCountry:country
// Return the integer continent code (0 - 5) for the given country.
{
	int countryNum = [country idNum];

	if (countryNum >= MINCOUNTRY && countryNum <= MAXCOUNTRY)
		return countryContinents[countryNum];
	else
		return -1;	// Should never happen, but...
}

// Return which front a country is located along, 0 if none.
- (int)frontOfCountry:country
{
	int cont = [self continentOfCountry:country];

	if (cont == front1)
		return 1;
	else if (cont == front2)
		return 2;
	else if (cont == front3)
		return 3;
	return 0;
}

// Return the number of foreign neighbors (on another continent) a country has.
- (int)foreignNeighborsOfCountry:country
{
	int countryNum = [country idNum];

	if (countryNum >= MINCOUNTRY && countryNum <= MAXCOUNTRY)
		return countryForeignNeighbors[countryNum];
	else
		return 0;	// Should never happen, but...
}

// Find out how many countries a player has left.
- (int)numPlayersCountries:(int)player
{
	id countries;
	int num = 0;

	countries = [self playersCountries:player];
	if (countries != nil) {
		num = [countries count];
		[countries free];
	}
	return num;
}

// Find out how many players are left.
- (int)numPlayers
{
	int i, num = 0;

	for (i = 0; i < 6; i++) {
		if ([self numPlayersCountries:i] > 0)
			num++;
	}
	return num;
}

// Given a list of countries from which we can attack, find the top 3
// continents represented by them, and remove any countries not in the
// top 3 continents.  This concentrates our efforts on fewer fronts.
- limitCountriesToFronts:countries
{
	int i, cc, cont, numpl, numfronts;

	// Sanity check; shouldn't ever happen.
	if (countries == nil)
		return self;

	// Find out how many fronts we should be concentrating on.
	numpl = [self numPlayers];
	if (numpl > 4 && gameTurn > 1 && gameTurn < 5)
		numfronts = 1;
	else if (numpl > 4)
		numfronts = 2;
	else if (numpl > 1)
		numfronts = 3;
	else
		return self;	// No fronts, if only 1 enemy player.

	// If we're not doing well recently, reduce the fronts a bit.
	if (turnsSinceVict > 0 ||
			numCountriesLastTurn > [self numPlayersCountries:myPlayerNum])
		numfronts = (numfronts + 1) / 2;

	// Make one pass through the countries to find the top-ranked fronts,
	// which are really just continents we possess that border some
	// desirable enemy target(s).
	cc = [countries count];
	for (i = 0; i < cc; i++) {
		cont = [self continentOfCountry:[countries objectAt:i]];
		if (front1 == -1)
			front1 = cont;
		else if (numfronts >= 2 && front2 == -1 && cont != front1)
			front2 = cont;
		else if (numfronts >= 3 && front3 == -1 && cont != front1 && cont != front2) {
			front3 = cont;
			break;
		}
	}

	// Make another pass to remove the countries that aren't on one of
	// the fronts.
	for (i = cc - 1; i >= 0; i--) {
		cont = [self continentOfCountry:[countries objectAt:i]];
		if (cont != front1 && cont != front2 && cont != front3) {
			[countries removeObjectAt:i];
		}
	}

	return self;
}

- yourTurnWithArmies:(int)numArmies andCards:(int)numCards
// go through the phases of a well-formed Risk turn.  turn in cards,
// place armies, attack, fortify.
{
	int i, acount;
	id attackers, sattackers;
	BOOL vict=NO, avict=YES;
	BOOL win=NO;
	
	// turn in cards if possible, keeping track of the extra armies
	// we receive from the card sets.
	numArmies += [self turnInCards];
	
	// Place armies (only as many as we are supposed to).  Pick some
	// new fronts to attack along, maybe.
	[self resetFronts];
	[self placeArmies:numArmies];
		
	// We haven't taken a country yet.
	takenc = NO;

	// Now start attacking.  Go through the list of my countries with
	// 2 or more armies and at least one enemy neighbor, and try
	// attacking from each one in turn.  Sort my countries by attack
	// advantage.
	attackers = [self myCountriesCapableOfAttack:YES];
//	sattackers = [self sortCountriesByArmies:attackers];
	sattackers = [self sortCountriesByTAS:attackers];
	[attackers free];

	while (sattackers != nil && avict && !win) {
		acount = [sattackers count];
		avict = NO;		// Start with no victories
		for (i=0;i<acount && !win;i++)  {
			vict = NO;
			win = [self doAttackFrom:[sattackers objectAt:i] victory:&vict];

			// As soon as we conquer a country, break out of the
			// loop so we can rebuild the attacker list, which
			// might have moved some countries around due to
			// altered advantages.
			if (vict) {
				avict = YES;	// Got a victory
				break;
			}
		}

		if (avict && !win) {
			[sattackers free];
			attackers = [self myCountriesCapableOfAttack:YES];
//			sattackers = [self sortCountriesByArmies:attackers];
			sattackers = [self sortCountriesByTAS:attackers];
			[attackers free];
		}
	}

	// always free lists you asked for.
	[sattackers free];
	
	// only fortify if we haven't won the game
	if (!win)  {
		[self fortifyPosition];
	}
	
	gameTurn++;			// Another turn done.
	if (!takenc)			// Count winless turns.
		turnsSinceVict++;

	// Record how many countries we've got so we can tell next turn if
	// we're slipping.
	numCountriesLastTurn = [self numPlayersCountries:myPlayerNum];

	return self;
}

// ************************** my utilities ***************************

- (BOOL)takenACountry
// Return YES if we've taken a country this round, NO otherwise.
{
	return takenc;
}

- (float)ourPercentageOfContinent:(int)cont
// Return the percentage of a continent's countries possessed by us.
{
	int i, numfr = 0;
	float ratio;
	id countries;

	countries = [self countriesInContinent:cont];
	for (i = 0; i < [countries count]; i++) {
		if ([[countries objectAt:i] player] == myPlayerNum)
			numfr++;
	}
	ratio = (double)numfr / (double)[countries count];
	[countries free];

	return ratio;
}

- (int)exitsOfContinent:(int)cont
// Return the number of countries on this continent that border some other
// continent.
{
	return continentExits[cont];
}

- (int)neighborsOfContinent:(int)cont
// Return the number of countries on other continents that border this
// continent.
{
	return continentNeighbors[cont];
}

- (int)fudgeOfContinent:(int)cont
// Return the fudge factor for a continent.
{
	return continentFudge[cont];
}

- countryOfAttack
// Return the country from which we're planning an attack on somebody.
{
	return theCountryOfAttack;
}

- setCountryOfAttack:country
// Set the country from which we're planning an attack on somebody.
{
	theCountryOfAttack = country;
	return self;
}

- enemyNeighborsTo:country
// returns a list of all the neighbors to country which are not
// occupied by you.  We use this to find out if the country borders
// at least one enemy.
{
	id en = [self neighborsTo:country];
	int i;
	
	// weed out the neighbors which are mine
	// loop backwards since we want to delete from the list as we go
	for (i=[en count]-1;i>=0;i--)  {
		// if the country is ours, get rid of it.
		if ([[en objectAt:i] player] == myPlayerNum)  {
			[en removeObjectAt:i];
		}
	}
	
	// if the list is empty, free it and return nil, otherwise return it.
	if ([en count]==0)  {
		// free the list and return nil
		[en free];
		return nil;
	}  else  {
		// don't free en since we want to return it.
		return en;
	}
}

- (int)enemyArmiesAround:country
// returns the total number of armies in all the neighbors to country which
// are not occupied by you.
{
	id enemies = [self neighborsTo:country];
	int i, armies=0;
	
	// add up the armies in the neighbors which are not mine
	for (i=0;i<[enemies count];i++)  {
		if ([[enemies objectAt:i] player] != myPlayerNum)  {
			armies += [[enemies objectAt:i] armies];
		}
	}
	[enemies free];
	
	return armies;
}

- friendlyNeighborsTo:country
// returns a list of all the neighbors to country which are
// occupied by you.
{
	id fr = [self neighborsTo:country];
	int i;
	
	// weed out the neighbors which are not mine
	// loop backwards since we want to delete from the list as we go
	for (i=[fr count]-1;i>=0;i--)  {
		// if the country isn't ours, get rid of it.
		if ([[fr objectAt:i] player] != myPlayerNum)  {
			[fr removeObjectAt:i];
		}
	}
	
	// if the list is empty, free it and return nil, otherwise return it.
	if ([fr count]==0)  {
		// free the list and return nil
		[fr free];
		return nil;
	}  else  {
		// don't free fr since we want to return it.
		return fr;
	}
}

- (int)friendlyArmiesAround:country
// returns the total number of armies in all the neighbors to country which
// are occupied by you.
{
	id friends = [self neighborsTo:country];
	int i, armies=0;
	
	// add up the armies in the neighbors which are mine
	for (i=0;i<[friends count];i++)  {
		if ([[friends objectAt:i] player] == myPlayerNum)  {
			armies += [[friends objectAt:i] armies];
		}
	}
	[friends free];
	
	return armies;
}

- myCountriesCapableOfAttack:(BOOL)attack
// returns a list of all my countries which have at least one enemy neighbor.
// if attack is YES then only countries with 2 or more armies are returned.
{
	id l;
	int i, c;
	
	// get a list of countries depending on attack
	if (attack)  {
		l = [self myCountriesWithAvailableArmies];
	}  else  {
		l = [self myCountries];
	}
	c = [l count];
	
	// weed out those countries which are completely surrounded by friendly
	// neighbors.  loop backwards cause we'll be deleting from the list.
	for (i=c-1;i>=0;i--)  {
		id en = [self enemyNeighborsTo:[l objectAt:i]];
		if (en != nil)  {
			// it has enemy neighbors, so keep it in the list, but free en.
			[en free];
		}  else  {
			[l removeObjectAt:i];
		}
	}
	
	// if there aren't any such countries, just return nil
	// This should not happen, but better safe than sorry.
	if ([l count] == 0)  {
		[l free];
		return nil;
	}
	
	// don't free l because we're gonna return it.
	return l;
}

- (int)turnInCards
// if we can turn in cards, do it until we can't
{
	id cardSet;
	int temp, numArmies = 0;
	
	// get our best set
	cardSet = [self bestSet];
	// if there is a best set, play it, and see if there's another set.
	while (cardSet != nil)  {
		// for diagnostic
//		[self setNotes:"sent by -(int)turnInCards.  turnInCards turned in "
//						"the best possible set of cards."];
		
		// play the set.
		temp = [self playCards:cardSet];
		// free the list we asked for
		[cardSet free];
		// if playCards returned -1 there was a problem
		if (temp == -1)  {
			// should never happen
			NXRunAlertPanel("Debug", "bestSet returned an invalid cardset", 
							"OK", NULL, NULL);
			// stop trying to turn in cards, but this is an error.
			cardSet=nil;
		}  else  {
			// all is well, accumulate the armies received
			numArmies += temp;
			// see if there is another set.
			cardSet = [self bestSet];
		}
	}
	// return the number of armies received in total, will be 0 if we didn't
	// turn any in.
	return numArmies;
}

- (BOOL)weGotOverkill:country
// For a country we're thinking of putting some armies into, see if we
// should avoid this because of strong friendly neighbors, even though the
// country itself might be outnumbered by neighboring enemies.  I.e. don't
// put more armies here if weGotOverkill!
{
	id en = [self enemyNeighborsTo:country];
	id enemy;
	int enc;
	BOOL overkill;

	enc = [en count];
	// Sanity check; shouldn't ever execute this method if
	// there are no enemies; if it does, claim to have overkill
	// so we put no armies here.
	if ([en count] == 0)
		overkill = YES;
	else if (enc == 1) {
		// only one enemy neighbor; try to figure out if we
		// need more armies or not by looking at the combined
		// armies of all friendlies surrounding the enemy,
		// compared to all enemies surrounding the enemy (incl.
		// itself, too).
		enemy = [en objectAt:0];
		if (([self enemyArmiesAround:enemy] + [enemy armies]) * 1.5 >=
				[self friendlyArmiesAround:enemy])
			overkill = NO;
		else
			overkill = YES;
	}
	else {
		// several enemy neighbors; give up trying to figure
		// out subtleties, just use massive overkill!
		overkill = NO;
	}
	[en free];
	return overkill;
}

// This routine places numAmries armies in suitable countries. It only
// chooses from those countries which we own, and which have at least
// one enemy neighbor.  Such countries are prioritized according to need;
// high priority countries get all the armies first, up until the point
// at which the armies in them outnumber their surrounding enemies, or
// exceed a reasonable rearguard threshhold, whichever is higher.
- (BOOL)placeArmies:(int)numArmies
{
	id countries, prioCountries, someCountries, otherCountries;
	id country = nil, weakenemy = nil;
	int i, j, num, numToPlace, placeAmt;
	float contRatio, outgunRatio;
	BOOL attackWeak = NO, foundIt;
	
	// Get all countries with enemy neighbors.
	countries = [self myCountriesCapableOfAttack:NO];

	// Sanity check.  Should never happen.
	if (countries == nil)  {
		return NO;
	}

	// Prioritize the countries with enemy neighbors in order of
	// greatest to least need for reinforcements.
	prioCountries = [self sortCountriesByReinforcePrio:countries];
	[countries free];

	// Now perhaps limit the playing field to key fronts.
	if (limitMode)
		[self limitCountriesToFronts:prioCountries];

	// for diagnostic
//	[self setNotes:"sent by placeArmies.  "
//					"placeArmies placed some armies in a "
//					"suitable country"];
	
	// Place armies in larger chunks as the game progesses, and if we are
	// placing above a certain threshhold amount.
	if (gameTurn >= 14 || numArmies >= 25)
		placeAmt = 5;
	else if (gameTurn >= 7 || numArmies >= 9)
		placeAmt = 3;
	else
		placeAmt = 1;

	// Stagger the ratio by which we must outgun enemy neighbors,
	// decreasing as we go along.  Victory is more crucial early on when
	// the numbers of armies we're dealing with are smaller, and thus
	// odds may go against us more easily even though we have more
	// armies; so we want more of an edge when attacking.
	if (gameTurn >= 14) {
		outgunRatio = 1.25;
		banzaiMode = NO;		// Stop going all out at this point.
	}
	else if (gameTurn >= 7)
		outgunRatio = 1.65;
	else
		outgunRatio = 2;

	// If we're down to few countries or have lost ground, double the
	// outgun ratio so our armies will be concentrated in fewer places.
	if ([self numPlayersCountries:myPlayerNum] < 5 ||
			[self numPlayersCountries:myPlayerNum] <
			numCountriesLastTurn * 0.8)
		outgunRatio *= 2;

	// If we haven't taken a country recently, pick our weakest enemy
	// neighbor overall as a target for destruction.
	if (turnsSinceVict > 0) {
		attackWeak = YES;
		num = 1000;
		for (i = 0; i < [prioCountries count]; i++) {
			someCountries = [self enemyNeighborsTo:[prioCountries objectAt:i]];
			for (j = 0; j < [someCountries count]; j++) {
				if ([[someCountries objectAt:j] armies] < num) {
					weakenemy = [someCountries objectAt:j];
					num = [weakenemy armies];
				}
			}
			[someCountries free];
		}
		// Now that we have our weakest enemy, find our strongest
		// country neighboring it, so we can reinforce our strongest
		// position.
		someCountries = [self friendlyNeighborsTo:weakenemy];
		otherCountries = [self sortCountriesByArmies:someCountries];
		country = [otherCountries objectAt:0];
		[someCountries free];
		[otherCountries free];
	}

	// Reinforce countries around our weakest enemy if this is the action
	// we need to take.  Otherwise reinforce our countries in order of
	// priority needs for armies.
	numToPlace = numArmies;
	while (numToPlace)  {
		if (!attackWeak) {
			// Look first for a country that doesn't outgun its
			// enemy neighbors.
			foundIt = NO;
			for (i = 0; i < [prioCountries count]; i++) {
				country = [prioCountries objectAt:i];

        			// Now that we have a country to look at, find
				// the percentage of its continent possessed
				// by us, for use in modifying the outgunRatio.
				// The concept is to go after smaller
				// continents where we have a stronger
				// presence, so we want higher outnumber ratios
				// when placing armies in such countries.
				contRatio = [self ourPercentageOfContinent:[self continentOfCountry:country]];

				// Now that we have the occupation ratio,
				// adjust it to a value that can be multiplied
				// to the outgunRatio to achieve our aim.
				if (contRatio <= .25)
					contRatio = .75;
				else if (contRatio <= .5)
					contRatio = 1.25;
				else if (contRatio <= .75)
					contRatio = 1.5;
				else
					contRatio = 1.0;

				// Now see if this country needs more armies.
				if (([self enemyArmiesAround:country] + 1) *
						outgunRatio * contRatio >=
							[country armies] &&
						![self weGotOverkill:country]) {
					foundIt = YES;
					break;
				}
			}

			// If no luck, look next for a country with less than
			// a suitable rear guard.
			if (!foundIt) {
				for (i = 0; i < [prioCountries count]; i++) {
					country = [prioCountries objectAt:i];
					if ([country armies] < rearguard()) {
						foundIt = YES;
						break;
					}
				}
			}

			// If still no luck, pick a random country.
			if (!foundIt)
				country = [prioCountries objectAt:[rng randMax:[prioCountries count] - 1]];
		}

		if (numToPlace > placeAmt) {
			[self placeArmies:placeAmt inCountry:country];
			numToPlace -= placeAmt;
		}
		else {
			[self placeArmies:numToPlace inCountry:country];
			numToPlace = 0;
		}
		if (attackWeak) {
			if ([country armies] > ([weakenemy armies] + 1) * (outgunRatio + 0.25))
				attackWeak = NO;
		}

		// Reprioritize the countries; now that we've placed a few
		// armies, the ordering may have shifted a little.
		someCountries = [prioCountries copy];
		[prioCountries free];
		prioCountries = [self sortCountriesByReinforcePrio:someCountries];
		[someCountries free];
	}

	// Free what we asked for.
	[prioCountries free];
	return YES;
}

- (BOOL)doAttackFrom:fromc victory:(BOOL *)vict
// attacks from fromc.  This method chooses a number between 1 and the number
// of armies in fromc.  It attacks until fromc has that number of armies left.
// Sometimes it won't attack at all (if the random number is the same as the
// number of armies in fromc).  If it conquers, it moves forward half of the
// armies remaining in fromc.  If it vanquishes someone, it turns in cards
// if it needs to and places any armies resulting.  returns whether the
// game is over.  Sets the BOOL vict if the attack succeeded.
{
	id target, enemies, sortedenemies;
	int i, fa, ta, olda, newa, movea, opc, enc, minArmies, cont;
	float attrat, discretion1, discretion2;
	BOOL vanq, win, weakenemy = NO, outnumbered = NO;
	
	// If no enemy neighbors, no attack is possible, so return.
	enemies = [self enemyNeighborsTo:fromc];
	if (enemies == nil)  {
		return NO;
	}
	enc = [enemies count];

	// Set our continent of attack, locally and globally.
	cont = [self continentOfCountry:fromc];
	[self setCountryOfAttack:fromc];

	// Order enemy neighbors by attackability.  Pick the most desirable
	// target (first in the sorted list) and proceed.
	sortedenemies = [self sortEnemiesByAttackability:enemies];
	target = [sortedenemies objectAt:0];
	[sortedenemies free];
	[enemies free];

	// See if we've got either a really weak opponent that we should
	// go after with greater abandon, or if we've got an opponent
	// blocking us from nearly having a continent and whom we should
	// thus go after with greater abandon.
	opc = [self numPlayersCountries:[target player]];
	if (opc <= 3 || ([self ourPercentageOfContinent:cont] >= .7 &&
			cont == [self continentOfCountry:target]))
		weakenemy = YES;

	// Set up some values that influence discretion as the better part
	// of valor.  If we haven't had a victory recently, we want to
	// attack with a little less concern for odds being in our favor.
	// Likewise if our territory is shrinking.
	discretion1 = 1.25;
	if (turnsSinceVict > 0) {
		discretion1 = 0.95;
	}
	else if ([self numPlayersCountries:myPlayerNum] <
			numCountriesLastTurn * 0.8) {
		discretion1 = 1.05;
	}
	discretion2 = discretion1 * 2;

	// If our combined surrounding strength is weaker than the chosen
	// country's strength plus 25% (safety margin), don't attack, unless
	// we're in banzai mode.
	if (!banzaiMode && takenc && [self friendlyArmiesAround:target] <
			([target armies] + (takenc ? 1 : 0)) * discretion1) {
		return NO;
	}

	// If our combined surrounding strength is weaker than the chosen
	// country's strength plus 25% (safety margin), factoring in other
	// surrounding enemies, and we have taken a country this turn, and
	// the enemy is not near extinction, then don't attack, unless we're
	// in banzai mode.
	attrat = (float)([self friendlyArmiesAround:target] -
		[self enemyArmiesAround:target]) /
			(float)([target armies] + (takenc ? 1 : 0));
	if (!banzaiMode && takenc && !weakenemy && attrat < discretion1) {
		return NO;
	}

	// If we have taken a country this turn, and we're early in the
	// game or getting down to few countries, and the enemy we're looking
	// at attacking is not somebody we really want to stomp, and we don't
	// massively outnumber the enemy and its surroundings, then don't
	// attack, unless we're in banzai mode.
	if (!banzaiMode && takenc && !weakenemy && attrat < discretion2 &&
			(gameTurn < 4 || [self numPlayersCountries:myPlayerNum] < 5))
		return NO;

	// If there's only the one enemy country, and we've got some
	// surrounding friendlies, hold off attacking to give a chance to
	// fortify for a stronger attack next turn; unless the opponent is
	// near extinction or we massively outnumber him or we haven't
	// taken a country yet or we're in banzai mode, in which case go
	// for it.
	if (!banzaiMode && takenc && enc == 1 && !weakenemy) {
		id fr = [self friendlyNeighborsTo:fromc], country;
		int i, frc = 0;
		float nattrat;

		if ( fr != nil )
			for (i = 0; i < [fr count]; i++) {
				country = [fr objectAt:i];
				if ([self enemyArmiesAround:country] == 0)
					frc += [country armies];
			}
		[fr free];
		nattrat = (float)([self friendlyArmiesAround:target] + frc -
				[self enemyArmiesAround:target]) /
				(float)([target armies] + (takenc ? 1 : 0));
		if (frc && ((attrat < discretion2 && nattrat - attrat >= 2) ||
				(attrat < discretion2 * 2 && nattrat - attrat >= 4)))
			return NO;
	}

	// Figure out whether to attack until a certain number of armies
	// remain (if we have taken a country this turn), or until no
	// further attack is possible (if we haven't taken a country yet
	// this turn).  If the enemy is weak or we're in banzai mode, attack
	// all out.
	minArmies = 1;
	if (!banzaiMode && takenc && !weakenemy) {
		if ([fromc armies] >= [self friendlyArmiesAround:target] * .5)
			minArmies = rearguard() / 3 + 1;
	}

	// for diagnostic
//	[self setNotes:"sent by -(BOOL)doAttackFrom:.  doAttackFrom "
//					"attacked its weakest neighbor."];
	
	// Now attack.  If the attack takes place, deal with the results.
	if ([self attackUntilLeft:minArmies from:fromc to:target victory:vict 
		fromArmies:&fa toArmies:&ta vanquished:&vanq weWin:&win]) {
#if 0
	if ([self attackUntilCantFrom:fromc to:target victory:vict 
		fromArmies:&fa toArmies:&ta vanquished:&vanq weWin:&win]) {
#endif

		// If we didn't conquer the country, there is nothing to do.
		if (!*vict)  {
			return NO;
		}
		// If we've won the game, there is no need to continue.
		if (win)  {
			return YES;
		}

		// If we get here, we must deal with the victory.  Make note
		// that we took a country this turn, and note which turn the
		// country was taken.  Update the turn conquered value of the
		// attacking country as well.
		takenc = YES;
		turnsSinceVict = 0;
		turnCountryConquered[[target idNum]] = gameTurn;
		turnCountryConquered[[fromc idNum]] = gameTurn;

		// If there are any armies to move, figure out how many
		// and do it.
		if (fa > 1)  {
			
			// for diagnostic
//			[self setNotes:"sent by -(BOOL)doAttackFrom:.  doAttackFrom "
//						"advanced attacking forces."];
			
			// Figure out how many armies to move; count up enemy
			// armies around old & new countries.  Set a flag if
			// an enemy by the new country is near extinction, and
			// not already outnumbered by surrounding friendlies,
			// so we can shift more armies that direction.
			// Set a second flag if the new country has only one
			// enemy neighbor who is already outnumbered by
			// friendlies.
			weakenemy = NO;
			enemies = [self enemyNeighborsTo:target];
			if (enemies != nil) {
				enc = [enemies count];
				for (i = 0; i < enc; i++) {
					opc = [self numPlayersCountries:[[enemies objectAt:i] player]];
					if (opc <= 3 && [self friendlyArmiesAround:[enemies objectAt:i]] <
							([[enemies objectAt:i] armies] + 1) * 1.5) {
						weakenemy = YES;
						break;
					}
				}
				if (enc == 1) {
					id theEnemy = [enemies objectAt:0];

					if ([self friendlyArmiesAround:theEnemy] >=
							([theEnemy armies] + 1) * 1.5)
						outnumbered = YES;
				}
				[enemies free];
			}

			// Set a flag if there's only one enemy neighbor left
			// by the old country, and it also neighbors the
			// conquered country, and it is outnumbered and thus
			// can be taken out on another attack, if we move all
			// the armies into the conquered country.
			if (!weakenemy) {
				id oldEnemies = [self enemyNeighborsTo:fromc];

				if (oldEnemies != nil && [oldEnemies count] == 1) {
					id theEnemy = [oldEnemies objectAt:0];
					id friends = [self friendlyNeighborsTo:theEnemy];

					for (i = 0; i < [friends count]; i++) {
						if (target == [friends objectAt:i]) {
							attrat = (float)([self friendlyArmiesAround:theEnemy] -
								[self enemyArmiesAround:theEnemy]) /
									(float)([theEnemy armies] + (takenc ? 1 : 0));
							if (attrat >= discretion1 || banzaiMode)
								weakenemy = YES;
							break;
						}
					}
					[friends free];
				}
				if (oldEnemies != nil)
					[oldEnemies free];
			}
			// Move half if no enemies around either country.
			olda = [self enemyArmiesAround:fromc];
			newa = [self enemyArmiesAround:target];
			if (olda + newa == 0) {
				[self moveArmies:((fa+ta)/2-ta) from:fromc to:target];
			}
			// Else move all if no enemies around old country, or
			// if new country has an enemy neighbor near death;
			// must leave one army behind.
			else if (olda == 0 || weakenemy) {
				[self moveArmies:fa-1 from:fromc to:target];
			}
			// Else move none if no enemies around new country, or
			// if there is one enemy that is already outnumbered
			// by surrounding friendlies.
			else if (newa == 0 || outnumbered) {
//				[self moveArmies:0 from:fromc to:target];
			}
			// Else move proportionate amount to each country,
			// leaving behind a reasonable rearguard, at minimum.
			else {
				movea = (int)((float)(fa + ta) * (float)newa /
					((float)olda + (float)newa)) - ta;
		
				// Must leave at least 1 behind.
				if (movea == fa) {
					--movea;
				}

				// Leave a reasonable rear guard, if the
				// proportions would cause too little to be
				// left behind.
				if ((fa - movea) < rearguard()) {
					movea = fa - rearguard();
				}

				// Move 'em out.
				if (movea > 0) {
					[self moveArmies:movea from:fromc to:target];
				}
			}
	
		}

		// If we vanquished a player, we got any cards he had, so check
		// to see if we must turn in cards.
		if (vanq) {
			int numArmies = 0;
			id cards = [self myCards];
			
			// only turn in if we're allowed to (we have five or more)
			if ([cards count] > 4)  {
				numArmies += [self turnInCards];
			}
			// now we are done with cards
			[cards free];
			if (numArmies > 0)  {
				[self placeArmies:numArmies];
			}
		}
	}

	// We didn't win the game if we get here.
	return NO;
}

- fortifyPosition
// fortify our position at the end of the turn.
{
	id cl=[self myCountriesWithAvailableArmies];
	int i, clc=[cl count];
	
	// first get a list of isolated countries with armies
	for (i=clc-1;i>=0;i--)  {
		id en=[self enemyNeighborsTo:[cl objectAt:i]];
		if (en != nil)  {
			[en free];
			[cl removeObjectAt:i];
		}
	}
	clc = [cl count];
	if (clc != 0)  {
		int numArmies[clc], strongest;
		
		// before we move anything, figure out how many armies can be moved
		// from each country so we don't move anything twice; also find
		// the country with the most armies to be moved
		strongest = 0;
		for (i=0;i<clc;i++)  {
			numArmies[i]=[[cl objectAt:i] armies]-1;
			if (numArmies[i] > [[cl objectAt:strongest] armies] - 1)
				strongest = i;
		}
		// we fortify all our countries from one to one at a time
		// all we need to distinguish is whether we can fortify once
		// or as many as we want.
		switch ([self fortifyRule])  {
			case FR_ONE_ONE_N:
			case FR_ONE_MANY_N:
//				strongest=[rng randMax:clc-1];
				// fortify from the country with the most
				// armies to be moved
				[self fortifyArmies:numArmies[strongest] from:[cl objectAt:strongest]];
				break;
			case FR_MANY_MANY_N:
			case FR_MANY_MANY_C:
				// loop through the countries and try to fortify from each
				for (i=0;i<clc;i++)  {
					[self fortifyArmies:numArmies[i] from:[cl objectAt:i]];
				}
				break;
			default:
				break;
		}
	}
	// free the list
	[cl free];
	return self;
}

- fortifyArmies:(int)numArmies from:country
{
	// get a list of friendly neighbors for the country
	id fr=[self friendlyNeighborsTo:country];
	int frc=[fr count];
	id fr2;
	int fr2c, cont, cont2, cid, conqed, turnconqed;
	id toc=nil;
	int i, j, numWithEnemies = 0, firstOne = 0, anyOutGunned = NO, toa,
		totalEnemies = 0, firstId = 0;
	float outgunnedBy;
	int armiesAt[10], enemiesAround[10], ownershipRatio[10];
	
	// sanity check
	if (frc == 0) {
		[fr free];
		return self;
	}

	// count up enemy armies around each friendly neighbor
	for (i = 0; i < frc; i++) {
		armiesAt[i] = [[fr objectAt:i] armies];
		enemiesAround[i] = [self enemyArmiesAround:[fr objectAt:i]];
		if (enemiesAround[i] > 0) {
			if (!numWithEnemies)
				firstOne = i;
			numWithEnemies++;
		}
	}

	// If numWithEnemies = 0 then there isn't a friendly neighbor
	// with unfriendly neighbors, so look one country further out
	// beyond the current friendly neighbors for other friendly
	// neighbors with enemies, or that are on a continent that contains
	// enemies somewhere.
	if (numWithEnemies == 0)  {
		// First look for friendly neighbors that have friendly
		// neighbors that have enemy neighbors.
		for (i = 0; i < frc; i++)  {
			fr2 = [self friendlyNeighborsTo:[fr objectAt:i]];
			fr2c = [fr2 count];
			for (j = 0; j < fr2c; j++) {
				enemiesAround[i] += [self enemyArmiesAround:[fr2 objectAt:j]];
			}
			[fr2 free];
			if (enemiesAround[i] > 0) {
				numWithEnemies++;
				if (enemiesAround[i] > totalEnemies) {
					firstOne = i;
					totalEnemies = enemiesAround[i];
				}
			}
		}

		// If numWithEnemies = 0, there are no friendly neighbors with
		// friendly neighbors with enemy neighbors, so look for
		// friendly neighbors that border another continent
		// containing some enemies.
		if (numWithEnemies == 0) {
			for (i = 0; i < frc; i++)  {
				cont = [self continentOfCountry:[fr objectAt:i]];
				fr2 = [self friendlyNeighborsTo:[fr objectAt:i]];
				fr2c = [fr2 count];
				for (j = 0; j < fr2c; j++) {
					cont2 = [self continentOfCountry:[fr2 objectAt:j]];
					if (cont != cont2)
						ownershipRatio[i] += 100 - 100 *
							[self ourPercentageOfContinent:cont2];
				}
				[fr2 free];
				if (ownershipRatio[i] > 0) {
					numWithEnemies++;
					if (ownershipRatio[i] > totalEnemies) {
						firstOne = i;
						totalEnemies = ownershipRatio[i];
					}
				}
			}
		}

		// If numWithEnemies = 0 still, give up and follow the trail
		// of most recent conquest.
		if (numWithEnemies == 0)  {
			conqed = -1;
			for (i = 0; i < frc; i++) {
				cid = [[fr objectAt:i] idNum];
				turnconqed = turnCountryConquered[cid];
				if (conqed < turnconqed) {
					conqed = turnconqed;
					firstOne = i;
					firstId = cid;
				}
				else if (conqed == turnconqed) {
					if (countryForeignNeighbors[firstId] < countryForeignNeighbors[cid] ||
							countryNeighbors[firstId] < countryNeighbors[cid]) {
						conqed = turnconqed;
						firstOne = i;
						firstId = cid;
					}
				}
			}
		}

		// Now move the armies to the friendly neighbor that is in
		// the direction of greatest need.
#if 0
		toc = [fr objectAt:[rng randMax:frc-1]];
#endif
		toc = [fr objectAt:firstOne];
		[self moveArmies:numArmies from:country to:toc];
	}
	// else if numWithEnemies = 1 then there is one friendly neighbor
	// with unfriendly neighbors, so pick it
	else if (numWithEnemies == 1) {
		toc = [fr objectAt:firstOne];
		[self moveArmies:numArmies from:country to:toc];
	}
	// else if we can only move into one neighbor, pick the one that
	// needs it most
	else if ([self fortifyRule] == FR_ONE_ONE_N) {
		// find neighbor most in need
		firstOne = 0;
		outgunnedBy = (float)armiesAt[0] / (float)enemiesAround[0];
		for (i = 1; i < frc; i++) {
			if ((float)armiesAt[i] / (float)enemiesAround[i] < outgunnedBy) {
				firstOne = i;
				outgunnedBy = (float)armiesAt[i] / (float)enemiesAround[i];
			}
		}
		toc = [fr objectAt:firstOne];
		[self moveArmies:numArmies from:country to:toc];
	}
	// else there are several friendly neighbors with unfriendly
	// neighbors, so distribute the armies among them as needed
	else {
		// first see if any of our friendly neighbors are outgunned;
		// if so, remove any that are not outgunned from the list
		for (i = 0; i < frc; i++) {
			if ((float)armiesAt[i] / (float)enemiesAround[i] < 1.25) {
				anyOutGunned = YES;
				break;
			}
		}
		if (anyOutGunned) {
			for (i = frc - 1; i >= 0; i--) {
				if ((float)armiesAt[i] / (float)enemiesAround[i] >= 1.25)
					[fr removeObjectAt:i];
			}
			frc = [fr count];
			// redo arrays
			for (i = 0; i < frc; i++)  {
				armiesAt[i] = [[fr objectAt:i] armies];
				enemiesAround[i] = [self enemyArmiesAround:[fr objectAt:i]];
			}
		}
		// now we have a list of countries to move armies into;
		// move in proportion to how many enemies are around each
		for (i = 0; i < frc; i++)  {
			totalEnemies += enemiesAround[i];
		}
		for (i = 0; i < frc - 1; i++) {
			toa = numArmies * ((float)enemiesAround[i] / (float)totalEnemies);
			if (toa) {
				toc = [fr objectAt:i];
				[self moveArmies:toa from:country to:toc];
				numArmies -= toa;
			}
		}
		// move remainder armies to last country
		toc = [fr objectAt:(frc - 1)];
		[self moveArmies:numArmies from:country to:toc];
	}
	[fr free];
	return self;
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.