// Chaotic.m
// Part of Risk by Mike Ferris

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

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

@implementation Chaotic

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

- initPlayerNum:(int)pnum mover:mover gameSetup:gamesetup mapView:mapview
				cardManager:cardmanager
// initialize my instance vars
{
	int i;
	
	// always call the super
	[super initPlayerNum:pnum mover:mover gameSetup:gamesetup mapView:mapview
				cardManager:cardmanager];
	
	for (i=0;i<6;i++)  {
		countryInContinent[i] = NO;
	}
	
	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 country=nil;
	int continent=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 ((continent<6) && (country==nil))  {
		if (countryInContinent[continent] == NO)  {
			id contList = [self countriesInContinent:continent];
			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[continent]=YES;
			}
			// remember to always free any list you ask for
			[contList free];
		}
		continent++;
	}
	
	// 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];
	}
	
	// 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];

	return self;
}

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

	// for diagnostic
//	[self setNotes:"-yourInitialPlaceArmies: ran.  "
//					"yourInitialPlaceArmies placed all armies into random "
//					"non land locked countries."];
	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;
	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)
	[self placeArmies:numArmies];
		
	// now start attacking.  Go through the list of non land locked countries,
	// try attacking from each one in turn.
	attackers = [self myNonLandLockedCountriesCapableOfAttack:YES];
	if (attackers == nil)  {
		return nil;
	}
	acount = [attackers count];
	for (i=0;i<acount;i++)  {
		win = [self doAttackFrom:[attackers objectAt:i]];
		// if we won the game, clean up and get out.
		if (win)  {
			break;
		}
	}
	
	// only fortify if we haven't won the game
	if (!win)  {
		[self fortifyPosition];
	}
	
	// always free lists you asked for.
	[attackers free];
	return self;
}

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

- 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 fro, 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;
	}
}

- myNonLandLockedCountriesCapableOfAttack:(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)placeArmies:(int)numArmies
// this routine places numAmries armies in a random countries, but it 
// only chooses from those countries which it owns, but which have at
// least one enemy neighbor.
{
	id pcList;
	id country;
	int i;
	
	// get all non-isolated countries
	pcList = [self myNonLandLockedCountriesCapableOfAttack:NO];
	if (pcList == nil)  {
		return NO;
	}
	
	// for diagnostic
//	[self setNotes:"sent by -placeAnArmy.  "
//					"placeAnArmy placed one army in a random "
//					"non land locked country"];
	
	// choose random countries and place the armies
	for (i=0;i<numArmies;i++)  {
		country = [pcList objectAt:[rng randMax:[pcList count]-1]];
		[self placeArmies:1 inCountry:country];
	}
	// free what we asked for.
	[pcList free];
	return YES;
}

- (BOOL)doAttackFrom:fromc
// 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.
{
	id toc, en = [self enemyNeighborsTo:fromc];
	int i, minArmies, fa, ta, enc = [en count];
	BOOL vict, vanq, win;
	
	// figure out which neighbor to attack
	if (en == nil)  {
		return NO;
	}
	// attack the weakest neighbor (bully tactics)
	toc = [en objectAt:0];
	for (i=1;i<enc;i++)  {
		if ([[en objectAt:i] armies] < [toc armies])  {
			toc = [en objectAt:i];
		}
	}
	// now we're done with en so free it.
	[en free];
	
	// figure out how long to attack.  pick a number between one and
	// the number of armies in fromc.  attack until there are that
	// many armies left in fromc
	minArmies = [rng randMin:1 max:[fromc armies]];
	
	if (minArmies == [fromc armies])  {
		return NO;
	}
	
	// for diagnostic
//	[self setNotes:"sent by -(BOOL)doAttackFrom:.  doAttackFrom "
//					"attacked its weakest neighbor."];
	
	// now attack
	if ([self attackUntilLeft:minArmies from:fromc to:toc victory:&vict 
					fromArmies:&fa toArmies:&ta vanquished:&vanq weWin:&win]) {
		// if we didn't conquer the country, there is nothing more to do
		if (!vict)  {
			return NO;
		}
		// if we've won, there is no need to continue
		if (win)  {
			return YES;
		}
		// otherwise we must deal with the victory
		if (fa > 1)  {
			// if there are armies enough to move remaining in the fromc
			// move half of them into toc.
			
			// for diagnostic
//			[self setNotes:"sent by -(BOOL)doAttackFrom:.  doAttackFrom "
//						"advanced attacking forces."];
			
			// move half
			[self moveArmies:fa/2 from:fromc to:toc];
		}
		if (vanq)  {
			// if we vanquished a player, we got any cards he had, so check
			// to see if we must turn in cards.
			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 if we got here
		return NO;
	}  else  {
		// the attack failed... we didn't win
		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], temp;
		
		// before we move anything, figure out how many armies can be moved
		// from each country so we don't move anything twice
		for (i=0;i<clc;i++)  {
			numArmies[i]=[[cl objectAt:i] armies]-1;
		}
		// 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:
				temp=[rng randMax:clc-1];
				[self fortifyArmies:numArmies[temp] from:[cl objectAt:temp]];
				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 neighbors for the country
	id n=[self neighborsTo:country];
	int nc=[n count];
	id toc=nil;
	int i;
	
	// try to find a friendly neighbor who has unfriendly neighbors
	for (i=nc-1;((i>=0) && (toc==nil));i--)  {
		if ([[n objectAt:i] player] == myPlayerNum)  {
			// see if this will do
			id en=[self enemyNeighborsTo:[n objectAt:i]];
			if (en!=nil)  {
				toc=[n objectAt:i];
				[en free];
			}
		}  else  {
			// we aren't interested in this country
			[n removeObjectAt:i];
		}
	}
	// if we get here and toc is nil then there isn't a friendly neighbor
	// with unfriendly neighbors, so pick a random country
	nc=[n count];
	if ((nc!=0) && (toc==nil))  {
		toc = [n objectAt:[rng randMax:nc-1]];
	}
	[n free];
	
	if (toc!=nil)  {
		[self moveArmies:numArmies from:country to:toc];
	}
	return self;
}

@end
