2 * "forfax", a sample Stratego AI for the UCC Programming Competition 2012
3 * Implementations of classes Piece, Board and Forfax
4 * @author Sam Moore (matches) [SZM]
5 * @website http://matches.ucc.asn.au/stratego
7 * @git git.ucc.asn.au/progcomp2012.git
28 * The characters used to represent various pieces
29 * NOTHING, BOULDER, FLAG, SPY, SCOUT, MINER, SERGEANT, LIETENANT, CAPTAIN, MAJOR, COLONEL, GENERAL, MARSHAL, BOMB, ERROR
32 char Piece::tokens[] = {'.','*','F','s','9','8','7','6','5','4','3','2','1','B','?'};
36 * The number of units remaining for each colour
37 * Accessed by [COLOUR][TYPE]
39 * TYPE: NOTHING, BOULDER, FLAG, SPY, SCOUT, MINER, SERGEANT, LIETENANT, CAPTAIN, MAJOR, COLONEL, GENERAL, MARSHAL, BOMB, ERROR
41 int Forfax::remainingUnits[][15] = {{0,0,1,1,8,5,4,4,4,3,2,1,1,6,0},{0,0,1,1,8,5,4,4,4,3,2,1,1,6,0}};
47 * Constructor for a piece of unknown rank
48 * @param newX - x coord
49 * @param newY - y coord
50 * @param newColour - colour
52 Piece::Piece(int newX, int newY,const Colour & newColour)
53 : x(newX), y(newY), colour(newColour), minRank(Piece::FLAG), maxRank(Piece::BOMB), lastMove(0), lastx(newX), lasty(newY)
59 * Constructor for a piece of known rank
60 * @param newX - x coord
61 * @param newY - y coord
62 * @param newColour - colour
63 * @param fixedRank - rank of the new piece
65 Piece::Piece(int newX, int newY,const Colour & newColour, const Type & fixedRank)
66 : x(newX), y(newY), colour(newColour), minRank(fixedRank), maxRank(fixedRank), lastMove(0), lastx(newX), lasty(newY)
78 * HELPER - Returns the Piece::Type matching a given character
79 * @param token - The character to match
80 * @returns A Piece::Type corresponding to the character, or Piece::ERROR if none was found
82 Piece::Type Piece::GetType(char token)
84 for (int ii=0; ii < Piece::ERROR; ++ii)
86 if (Piece::tokens[ii] == token)
93 * Constructor for the board
94 * @param newWidth - width of the board
95 * @param newHeight - height of the board
98 Board::Board(int newWidth, int newHeight) : width(newWidth), height(newHeight), board(NULL), red(), blue()
100 //Construct 2D array of Piece*'s
101 board = new Piece**[width];
102 for (int x=0; x < width; ++x)
104 board[x] = new Piece*[height];
105 for (int y=0; y < height; ++y)
115 //Destroy the 2D array of Piece*'s
116 for (int x=0; x < width; ++x)
118 for (int y=0; y < height; ++y)
125 * Retrieve a piece from the board at specified coordinates
126 * @param x - x coord of the piece
127 * @param y - y coord of the piece
128 * @returns Piece* to the piece found at (x,y), or NULL if there was no piece, or the coords were invalid
130 Piece * Board::Get(int x, int y) const
132 if (board == NULL || x < 0 || y < 0 || x >= width || y >= height)
138 * Add a piece to the board
139 * Also updates the red or blue arrays if necessary
140 * @param x - x coord of the piece
141 * @param y - y coord of the piece
142 * @param newPiece - pointer to the piece to add
143 * @returns newPiece if the piece was successfully added, NULL if it was not (ie invalid coordinates specified)
146 Piece * Board::Set(int x, int y, Piece * newPiece)
148 if (board == NULL || x < 0 || y < 0 || x >= width || y >= height)
150 board[x][y] = newPiece;
152 //if (newPiece->GetColour() == Piece::RED)
153 // red.push_back(newPiece);
154 //else if (newPiece->GetColour() == Piece::BLUE)
155 // blue.push_back(newPiece);
162 * HELPER - Convert a string to a direction
163 * @param str - The string to convert to a direction
164 * @returns The equivalent Direction
166 Board::Direction Board::StrToDir(const string & str)
170 else if (str == "DOWN")
172 else if (str == "LEFT")
174 else if (str == "RIGHT")
181 * HELPER - Convert a Direction to a string
182 * @param dir - the Direction to convert
183 * @param str - A buffer string, which will contain the string representation of the Direction once this function returns.
185 void Board::DirToStr(const Direction & dir, string & str)
209 * HELPER - Translates the given coordinates in a specified direction
212 * @param dir - Direction to move in
213 * @param multiplier - Number of times to move
216 void Board::MoveInDirection(int & x, int & y, const Direction & dir, int multiplier)
238 * HELPER - Returns the best direction to move in to get from one point to another
239 * @param x1 - x coord of point 1
240 * @param y1 - y coord of point 1
241 * @param x2 - x coord of point 2
242 * @param y2 - y coord of point 2
243 * @returns The best direction to move in
245 Board::Direction Board::DirectionBetween(int x1, int y1, int x2, int y2)
249 double xDist = (x2 - x1);
250 double yDist = (y2 - y1);
251 if (abs(xDist) >= abs(yDist))
273 * HELPER - Returns the number of moves between two points
274 * @param x1 x coordinate of the first point
275 * @param y1 y coordinate of the first point
276 * @param x2 x coordinate of the second point
277 * @param y2 y coordinate of the second point
278 * @returns The number of moves taken to progress from (x1, y1) to (x2, y2), assuming no obstacles
280 int Board::NumberOfMoves(int x1, int y1, int x2, int y2)
282 return (abs(x2 - x1) + abs(y2 - y1)); //Pieces can't move diagonally, so this is pretty straight forward
286 * Searches the board's red and blue arrays for the piece, and removes it
287 * DOES NOT delete the piece. Calling function should delete piece after calling this function.
288 * @param forget - The Piece to forget about
289 * @returns true if the piece was actually found
291 bool Board::ForgetPiece(Piece * forget)
296 vector<Piece*> & in = GetPieces(forget->colour); bool result = false;
297 vector<Piece*>::iterator i=in.begin();
298 while (i != in.end())
317 * Gets the closest Piece of a specified colour to a point
318 * @param x The x coordinate of the point
319 * @param y The y coordinate of the point
320 * @param colour The colour that the piece must match (may be Piece::BOTH to match either colour)
321 * @returns Piece* pointing to the closest piece of a matching colour, NULL if no piece found
323 Piece * Board::GetClosest(int x, int y, const Piece::Colour & colour) const
325 if (x < 0 || y < 0 || x >= width || y >= height)
328 for (int dist = 0; dist < max<int>(width, height); ++dist)
331 for (int yy = y-dist; yy <= y+dist; ++yy)
333 Piece * get = Get(x+dist, y);
334 if ((get != NULL) && (get->colour == colour || colour == Piece::BOTH))
337 for (int yy = y-dist; yy <= y+dist; ++yy)
339 Piece * get = Get(x-dist, y);
340 if ((get != NULL) && (get->colour == colour || colour == Piece::BOTH))
343 for (int xx = x-dist; xx <= x+dist; ++xx)
345 Piece * get = Get(xx, y+dist);
346 if ((get != NULL) && (get->colour == colour || colour == Piece::BOTH))
349 for (int xx = x-dist; xx <= y+dist; ++xx)
351 Piece * get = Get(xx, y-dist);
352 if ((get != NULL) && (get->colour == colour || colour == Piece::BOTH))
366 * Construct the Forfax AI
368 Forfax::Forfax() : board(NULL), colour(Piece::NONE), strColour("NONE"), turnNumber(0)
370 //By default, Forfax knows nothing; the main function in main.cpp calls Forfax's initialisation functions
379 //fprintf(stderr,"Curse you mortal for casting me into the fires of hell!\n");
386 * Calculate the probability that attacker beats defender in combat
387 * @param attacker The attacking piece
388 * @param defender The defending piece
389 * @returns A double between 0 and 1 indicating the probability of success
392 double Forfax::CombatSuccessChance(Piece * attacker, Piece * defender) const
394 double probability=1;
395 for (Piece::Type aRank = attacker->minRank; aRank <= attacker->maxRank; aRank = (Piece::Type)((int)(aRank) + 1))
397 double lesserRanks=0; double greaterRanks=0;
398 for (Piece::Type dRank = defender->minRank; dRank <= defender->maxRank; dRank = (Piece::Type)((int)(dRank) + 1))
401 lesserRanks += remainingUnits[defender->colour][(int)(dRank)];
402 else if (dRank > aRank)
403 greaterRanks += remainingUnits[defender->colour][(int)(dRank)];
406 lesserRanks++; greaterRanks++;
409 probability *= lesserRanks/(lesserRanks + greaterRanks);
415 * Calculate the score of a move
416 * TODO: Alter this to make it better
417 * @param piece - The Piece to move
418 * @param dir - The direction in which to move
419 * @returns a number between 0 and 1, indicating how worthwhile the move is, or a negative number (-1) if the move is illegal
421 double Forfax::MovementScore(Piece * piece, const Board::Direction & dir) const
423 assert(piece != NULL);
426 int x2 = piece->x; int y2 = piece->y;
427 Board::MoveInDirection(x2, y2, dir);
432 if (!board->ValidPosition(x2, y2) || !piece->Mobile())
435 return -1; //No point attempting to move immobile units, or moving off the edges of the board
437 else if (board->Get(x2, y2) == NULL)
439 //If the position is empty...
440 Piece * closestEnemy = board->GetClosest(x2, y2, Piece::Opposite(piece->colour));
441 if (closestEnemy == NULL)
443 basevalue = 0.05*IntrinsicWorth(x2, y2); //Should never get this unless there are no enemies left
447 //Allocate score based on score of Combat with nearest enemy to the target square, decrease with distance between target square and the enemy
448 double multiplier = (double)(max<int>(board->Width(), board->Height()) - Board::NumberOfMoves(closestEnemy->x, closestEnemy->y, x2, y2)) / (double)(max<int>(board->Width(), board->Height()));
450 basevalue = CombatScore(closestEnemy->x, closestEnemy->y, piece)*multiplier*multiplier;
456 else if (board->Get(x2, y2)->colour != Piece::Opposite(piece->colour))
458 return -1; //The position is occupied by an ally, and so its pointless to try and move there
462 basevalue = CombatScore(x2, y2, piece); //The position is occupied by an enemy; compute combat score
468 //Hack which decreases score for units that moved recently
469 //Does not decrease below a threshold (so that at the start of the game units will actually move!)
470 double oldValue = basevalue;
471 basevalue -= (double)(1.0/((double)(1.0 + (turnNumber - piece->lastMove))));
472 if (basevalue < oldValue/1000.0)
473 basevalue = oldValue/1000.0;
477 if (x2 == piece->lastx && y2 == piece->lasty) //Hack which decreases score for backtracking moves
478 basevalue = basevalue/100;
480 if (rand() % 10 == 0) //Hack which introduces some randomness by boosting one in every 10 scores
489 * Initialisation for Forfax
490 * Reads information from stdin about the board, and Forfax's colour. Initialises board, and prints appropriate setup to stdout.
491 * @returns true if Forfax was successfully initialised, false otherwise.
493 Forfax::Status Forfax::Setup()
495 //The first line is used to identify Forfax's colour, and the size of the board
496 //Currently the name of the opponent is ignored.
498 //Forfax then responds with a setup.
499 //Forfax only uses one of two setups, depending on what colour he was assigned.
502 //Variables to store information read from stdin
504 string strOpponent; int boardWidth; int boardHeight;
506 cin >> strColour; cin >> strOpponent; cin >> boardWidth; cin >> boardHeight;
507 if (cin.get() != '\n')
510 //Determine Forfax's colour and respond with an appropriate setup
511 if (strColour == "RED")
514 cout << "FB8sB479B8\n";
515 cout << "BB31555583\n";
516 cout << "6724898974\n";
517 cout << "967B669999\n";
519 else if (strColour == "BLUE")
521 colour = Piece::BLUE;
522 cout << "967B669999\n";
523 cout << "6724898974\n";
524 cout << "BB31555583\n";
525 cout << "FB8sB479B8\n";
528 return INVALID_QUERY;
532 //NOTE: At this stage, the board is still empty. The board is filled on Forfax's first turn
533 // The reason for this is because the opponent AI has not placed pieces yet, so there is no point adding only half the pieces to the board
535 board = new Board(boardWidth, boardHeight);
543 * 1. Read result of previous move from stdin (or "START" if Forfax is RED and it is the very first move)
544 * 2. Read in board state from stdin (NOTE: Unused - all information needed to maintain board state is in 1. and 4.)
545 * TODO: Considering removing this step from the protocol? (It makes debugging annoying because I have to type a lot more!)
546 * 3. Print desired move to stdout
547 * 4. Read in result of chosen move from stdin
548 * @returns true if everything worked, false if there was an error or unexpected query
550 Forfax::Status Forfax::MakeMove()
556 Status firstMove = MakeFirstMove();
562 //Read and interpret the result of the previous move
563 Status interpret = InterpretMove();
564 if (interpret != OK) {return interpret;}
566 //Forfax ignores the board state; he only uses the move result lines
568 for (int y=0; y < board->Height(); ++y)
570 for (int x = 0; x < board->Width(); ++x)
572 if (cin.get() != '\n')
579 //Now compute the best move
580 // 1. Construct list of all possible moves
581 // As each move is added to the list, a score is calculated for that move.
582 // WARNING: This is the "tricky" part!
583 // 2. Sort the moves based on their score
584 // 3. Simply use the highest scoring move!
586 list<MovementChoice> choices;
587 vector<Piece*> & allies = board->GetPieces(colour);
588 for (vector<Piece*>::iterator i = allies.begin(); i != allies.end(); ++i)
590 choices.push_back(MovementChoice((*i), Board::UP, *this));
591 choices.push_back(MovementChoice((*i), Board::DOWN, *this));
592 choices.push_back(MovementChoice((*i), Board::LEFT, *this));
593 choices.push_back(MovementChoice((*i), Board::RIGHT, *this));
597 choices.sort(); //Actually sort the choices!!!
598 MovementChoice & choice = choices.back(); //The best one is at the back, because sort() sorts the list in ascending order
602 //Convert information about the move into a printable form
603 string direction; Board::DirToStr(choice.dir, direction);
605 //Print chosen move to stdout
606 cout << choice.piece->x << " " << choice.piece->y << " " << direction << "\n";
608 //cerr << "\nForfax move " << choice.piece->x << " " << choice.piece->y << " " << direction << " [score = " << choice.score << "]\n";
613 //Interpret the result of the chosen move
614 return InterpretMove();
621 * Reads and interprets the result of a move
622 * Reads information from stdin
623 * @returns true if the result was successfully interpreted, false if there was a contradiction or error
625 Forfax::Status Forfax::InterpretMove()
627 //Variables to store move information
628 int x; int y; string direction; string result = ""; int multiplier = 1;
629 Piece::Type attackerRank = Piece::NOTHING;
630 Piece::Type defenderRank = Piece::NOTHING;
632 //Read in information from stdin
633 cin >> x; cin >> y; cin >> direction; cin >> result;
635 //If necessary, read in the ranks of involved pieces (this happens if the outcome was DIES or KILLS or BOTHDIE)
636 if (cin.peek() != '\n')
641 s.clear(); s.str(buf);
644 attackerRank = Piece::GetType(temp);
648 s.clear(); s.str(buf);
650 defenderRank = Piece::GetType(temp);
655 //TODO: Deal with move multipliers somehow (when a scout moves more than one space)
657 //Check that the line ends where expected...
658 if (cin.get() != '\n')
669 //Work out the square moved into
670 int x2 = x; int y2 = y;
671 Board::Direction dir = Board::StrToDir(direction);
673 Board::MoveInDirection(x2, y2, dir, multiplier);
676 //Determine the attacker and defender (if it exists)
677 Piece * attacker = board->Get(x, y);
678 Piece * defender = board->Get(x2, y2);
681 //If ranks were supplied, update the known ranks of the involved pieces
682 if (attackerRank != Piece::NOTHING && attacker != NULL)
684 //assert(attacker->minRank <= attackerRank && attacker->maxRank >= attackerRank);
685 attacker->minRank = attackerRank;
686 attacker->maxRank = attackerRank;
688 if (defenderRank != Piece::NOTHING && defender != NULL)
690 //assert(defender->minRank <= defenderRank && defender->maxRank >= defenderRank);
691 defender->minRank = defenderRank;
692 defender->maxRank = defenderRank;
696 //There should always be an attacking piece (but not necessarily a defender)
697 if (attacker == NULL)
698 return EXPECTED_ATTACKER;
701 attacker->lastMove = turnNumber; //Update stats of attacking piece (last move)
703 //Eliminate certain ranks from the possibilities for the piece based on its movement
704 //(This is useful if the piece was an enemy piece)
705 if (attacker->minRank == Piece::FLAG)
706 attacker->minRank = Piece::SPY;
707 if (attacker->maxRank == Piece::BOMB)
708 attacker->maxRank = Piece::MARSHAL;
711 attacker->maxRank = Piece::SCOUT;
712 attacker->minRank = Piece::SCOUT;
718 //Now look at the result of the move (I wish you could switch strings in C++)
721 //The move was uneventful (attacker moved into empty square)
724 if (defender != NULL)
725 return UNEXPECTED_DEFENDER;
727 //Update board and piece
728 board->Set(x2, y2, attacker);
729 board->Set(x, y, NULL);
730 attacker->lastx = attacker->x;
731 attacker->lasty = attacker->y;
735 else if (result == "KILLS") //The attacking piece killed the defending piece
737 if (defender == NULL || defender->colour == attacker->colour)
738 return COLOUR_MISMATCH;
743 board->Set(x2, y2, attacker);
744 board->Set(x, y, NULL);
745 attacker->lastx = attacker->x;
746 attacker->lasty = attacker->y;
750 remainingUnits[(int)(defender->colour)][(int)(defenderRank)]--;
753 if (!board->ForgetPiece(defender))
758 else if (result == "DIES") //The attacking piece was killed by the defending piece
761 if (defender == NULL || defender->colour == attacker->colour)
762 return COLOUR_MISMATCH;
764 remainingUnits[(int)(attacker->colour)][(int)(attackerRank)]--;
766 if (!board->ForgetPiece(attacker))
770 board->Set(x, y, NULL);
772 else if (result == "BOTHDIE") //Both attacking and defending pieces died
774 if (defender == NULL || defender->colour == attacker->colour)
775 return COLOUR_MISMATCH;
777 remainingUnits[(int)(defender->colour)][(int)(defenderRank)]--;
778 remainingUnits[(int)(attacker->colour)][(int)(attackerRank)]--;
780 if (board->ForgetPiece(attacker) == false)
782 if (board->ForgetPiece(defender) == false)
786 board->Set(x2, y2, NULL);
787 board->Set(x, y, NULL);
789 else if (result == "VICTORY") //The attacking piece captured a flag
798 * Forfax's first move
799 * Sets the state of the board
800 * @returns true if the board was successfully read, false if an error occurred.
803 Forfax::Status Forfax::MakeFirstMove()
805 if (colour == Piece::RED)
810 return INVALID_QUERY;
811 if (cin.get() != '\n')
816 //TODO: Fix hack where BLUE ignores RED's first move
817 while (cin.get() != '\n');
820 for (int y=0; y < board->Height(); ++y)
822 for (int x = 0; x < board->Width(); ++x)
827 case '.': //Empty square
829 case '+': //Boulder/Obstacle
830 board->Set(x, y, new Piece(x, y, Piece::NONE, Piece::BOULDER));
832 case '#': //Enemy piece occupies square
835 Piece * toAdd = new Piece(x, y, Piece::Opposite(colour));
836 board->Set(x, y, toAdd);
837 board->GetPieces(toAdd->colour).push_back(toAdd);
840 default: //Allied piece occupies square
842 Piece::Type type = Piece::GetType(c);
843 Piece * toAdd = new Piece(x, y, colour, type);
844 board->Set(x, y, toAdd);
845 board->GetPieces(toAdd->colour).push_back(toAdd);
850 if (cin.get() != '\n')
858 * Calculates the intrinsic strategic worth of a point on the board
859 * @param x the x coordinate of the point
860 * @param y the y coordinate of the point
861 * @returns a value between 0 and 1, with 0 indicating worthless and 1 indicating highly desirable
862 * (NOTE: No points will actually be worth 0)
864 double Forfax::IntrinsicWorth(int x, int y) const
866 static double intrinsicWorth[][10][10] =
870 {0.1,0.5,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1},
871 {0.5,0.5,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1},
872 {0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2},
873 {0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3},
874 {0.6,0.6,0.1,0.1,0.65,0.65,0.1,0.1,0.6,0.6},
875 {0.6,0.6,0.1,0.1,0.65,0.65,0.1,0.1,0.6,0.6},
876 {0.6,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.6},
877 {0.6,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.6},
878 {0.6,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.6},
879 {0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7}
885 {0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7},
886 {0.6,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.6},
887 {0.6,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.6},
888 {0.6,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.6},
889 {0.6,0.6,0.1,0.1,0.65,0.65,0.1,0.1,0.6,0.6},
890 {0.6,0.6,0.1,0.1,0.65,0.65,0.1,0.1,0.6,0.6},
891 {0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3},
892 {0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2},
893 {0.5,0.5,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1},
894 {0.1,0.5,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1}
898 return intrinsicWorth[(int)(colour)][x][y];
902 * Calculates a score assuming that attacker will beat defender, indicating how much killing that piece is worth
903 * @param attacker the Attacking piece
904 * @param defender the Defending piece
905 * @returns a value between 0 and 1, with 0 indicating worthless and 1 indicating highly desirable
907 double Forfax::VictoryScore(Piece * attacker, Piece * defender) const
910 //If defender's rank is known, flags or bombs are worth more than usual
911 if (defender->minRank == defender->maxRank)
913 if (defender->minRank == Piece::FLAG)
915 else if (defender->minRank == Piece::BOMB)
918 //Return average of normalised defender ranks
919 return max<double>(((defender->maxRank / Piece::BOMB) + (defender->minRank / Piece::BOMB))/2, 1);
923 * Calculates a score assuming that attacker will lose to defender, indicating how much learning the rank of that piece is worth
924 * @param attacker the Attacking piece
925 * @param defender the Defending piece
926 * @returns a value between 0 and 1, with 0 indicating worthless and 1 indicating highly desirable
928 double Forfax::DefeatScore(Piece * attacker, Piece * defender) const
932 if (defender->minRank == defender->maxRank) //If the defender's rank is known for certain...
934 if (defender->minRank == Piece::BOMB) //Committing suicide to destroy bombs has a value that decreases with the attacker's rank
935 result = 1 - 0.5*(double)((double)(attacker->minRank) / (double)(Piece::BOMB));
936 else if (defender->minRank == Piece::FLAG)
937 result = 1; //Its impossible to lose to the flag anyway...
940 //This is committing suicide on a higher ranked non-bomb enemy piece.
941 //Basically pointless, but the greater the attacker's rank the more pointless!
942 double temp = (double)((double)(attacker->minRank) / (double)(Piece::BOMB));
943 result = 0.01*(1 - temp)*(1 - temp);
948 else //The defender's rank is not known
951 //Score is allocated based on how much knowledge is gained by attacking defender
952 //The more possible ranks for the defender, the greater the score
953 //The score decreases as the rank of the attacker is increased.
955 double possibleRanks = 0; double totalRanks = 0; double bonus = 0;
956 for (Piece::Type rank = Piece::NOTHING; rank <= Piece::BOMB; rank = Piece::Type((int)(rank) + 1))
958 totalRanks += remainingUnits[(int)(defender->colour)][(int)(rank)];
960 if (rank >= defender->minRank && rank <= defender->maxRank)
962 possibleRanks += remainingUnits[(int)(defender->colour)][(int)(rank)];
963 if (rank == Piece::BOMB)
964 bonus += remainingUnits[(int)(defender->colour)][(int)(rank)];
965 if (rank == Piece::FLAG)
966 bonus += 2*remainingUnits[(int)(defender->colour)][(int)(rank)];
974 double multiplier = ((double)(Piece::BOMB) - (double)(attacker->minRank)) / (double)(Piece::BOMB);
975 result = (possibleRanks/totalRanks) * multiplier * multiplier;
978 result += bonus / totalRanks;
984 if (attacker->minRank == Piece::SPY) //Spies are slightly more valuable than usual since they kill the Marshal
985 result = result / 1.5;
991 * Calculates a score indicating the worth of invoking combat in a square
992 * @param x The x coordinate
993 * @param y The y coordinate
994 * @param attacker The piece invoking the combat
995 * @returns A value between 0 in 1, with 0 indicating worthless (or no combat) and 1 indicating highest value
997 double Forfax::CombatScore(int x, int y, Piece * attacker) const
999 Piece * defender = board->Get(x, y);
1000 if (defender == NULL)
1002 double combatSuccess = CombatSuccessChance(attacker, defender);
1003 return IntrinsicWorth(x, y)*combatSuccess*VictoryScore(attacker, defender) + (1.0 - combatSuccess)*DefeatScore(attacker, defender);
1007 * DEBUG - Print the board seen by Forfax to a stream
1008 * @param out The stream to print to
1010 void Forfax::PrintBoard(ostream & out)
1012 for (int y = 0; y < board->Height(); ++y)
1014 for (int x = 0; x < board->Width(); ++x)
1016 Piece * at = board->Get(x, y);
1021 if (at->colour == colour)
1023 out << Piece::tokens[(int)(at->minRank)];
1025 else if (at->colour == Piece::Opposite(colour))