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.