/* * PixsailAIController.cpp * Pirates2011 * * Created by Alan Dorin on 19/04/11. * Copyright 2011 __MyCompanyName__. All rights reserved. * */ #include "PixsailAIController.h" #include #include using namespace std; PixsailAIController::PixsailAIController(void) { } PixsailAIController::~PixsailAIController(void) { } // This function is called once per time step to update your ship by the external framework // You will need to write it to return what you want your ship to do after each time step. // Available actions for each ship to choose from once per time step are ONLY: // Globals::actionMoveAhead - move the ship one square ahead in the direction it is facing // Globals::actionTurnLeft - turn the ship to the left by 90 degrees // Globals::actionTurnRight - turn the ship to the right by 90 degrees // Globals::actionCollectGold - attempt to collect gold from the current location // Globals::actionFireCannonAhead - fire the cannon straight ahead // Globals::actionPass - do nothing (wait out this time step) Globals::ShipAction PixsailAIController::computeNextAction(void) { // This PixSail assumes it has three cannons. It is not // a general controller for other numbers of cannon assert (getShipNumCannons() == 3); Globals::ShipAction myAction; myAction = avoidRocksShootAllOthers(); return myAction; } // Here is a ship controller routine that looks in front of it for rocks or other ships. // If the space in front is clear it moves ahead or sometimes turns randomly. // If there is a rock ahead it turns in a random direction. // If there is a non-friendly (any!) ship ahead it fires its cannon at it. // This ship immitates the signal flags of enemy it sees. // *NB This ship does not collect gold, it just loiters around, // waiting to shoot any ships it finds. Globals::ShipAction PixsailAIController::avoidRocksShootAllOthers(void) { Globals::ShipAction action = Globals::actionPass; Globals::NeighbourDirection aheadDirection = getShipAheadDirection(); long aheadX = calcNextXInc(aheadDirection); long aheadY = calcNextYInc(aheadDirection); // first check for threats (and mimic enemy signals if found) action = evadeImmediateThreats(); // if a threat was handled, return the recommended action now if (action != Globals::actionPass) { return action; } // otherwise, continue to choose an action... // Since no ship was threatening, if one is in range, fire (and mimic target's signal flags) if (isTargetShipInRange() == true) // if there is a ship anywhere in the full range of any cannons { action = Globals::actionFireCannonAhead; // fire the cannons at it } // else if there is a rock ahead or just for random fun else if ((isRockAt(aheadX, aheadY) == true) || (!(random() % 5))) { action = getRandomTurnAction(); // choose a random direction to turn } else { action = Globals::actionMoveAhead; } // otherwise move ahead if (rand()%10==0) // occasionally randomly set the signalling flags *NB this won't happen if { randomiseSignals(); } // evadeImmediateThreats() occurred since the method returns the action directly above return action; } // set the signal flags randomly void PixsailAIController::randomiseSignals(void) { setSignalFlag(Globals::flagCyan, getRandomBool()); setSignalFlag(Globals::flagMagenta, getRandomBool()); setSignalFlag(Globals::flagYellow, getRandomBool()); } // take the location of a ship and mimic its signals void PixsailAIController::mimicSignals(long aheadX, long aheadY) { bool enemyFlagState; if (isShipAt(aheadX, aheadY) == false) { cerr << "\nERROR: PixsailAIController::mimicSignals() called without a ship present at param. location! Continuing..."; } // set my own signal flags to immitate its flags so as to trick it into thinking I am one of its kind enemyFlagState = getShipSignalFlag(aheadX, aheadY, Globals::flagCyan); setSignalFlag(Globals::flagCyan, enemyFlagState); enemyFlagState = getShipSignalFlag(aheadX, aheadY, Globals::flagMagenta); setSignalFlag(Globals::flagMagenta, enemyFlagState); enemyFlagState = getShipSignalFlag(aheadX, aheadY, Globals::flagYellow); setSignalFlag(Globals::flagYellow, enemyFlagState); } // This targetting routine checks to see if a ship (any ship, friend or foe) // is in range ahead of any cannons. bool PixsailAIController::isTargetShipInRange(void) { extern Globals::NeighbourDirection getOppositeNeighbourDirection(Globals::NeighbourDirection currentDirection); extern Globals::NeighbourDirection getClockwiseNeighbourDirection(Globals::NeighbourDirection currentDirection); extern Globals::NeighbourDirection getAnticlockwiseNeighbourDirection(Globals::NeighbourDirection currentDirection); bool targetInRangeFlag=false; // Algorithm: // for each cannon direction where I have a cannon // check its range to see if there is a ship there that is not behind a rock // if (there is a ship in any one of the cannon ranges) // then return true // else return false // Work out which direction my ship is facing Globals::NeighbourDirection aheadDirection = getShipAheadDirection(); Globals::NeighbourDirection cannonDirection; // always test a bow cannon for >0 cannons cannonDirection = aheadDirection; if (isTargetShipInRangeDirection(cannonDirection) == true) { targetInRangeFlag = true; } // test port and starboard cannons (*assume* ship has 3 cannons) cannonDirection = getClockwiseNeighbourDirection(aheadDirection); if (isTargetShipInRangeDirection(cannonDirection) == true) { targetInRangeFlag = true; } cannonDirection = getAnticlockwiseNeighbourDirection(aheadDirection); if (isTargetShipInRangeDirection(cannonDirection) == true) { targetInRangeFlag = true; } return targetInRangeFlag; } bool PixsailAIController::isTargetShipInRangeDirection(Globals::NeighbourDirection cannonDirection) { long cellAhead=0; long aheadX=0, aheadY=0; // Work out which increment will allow us to progressively get the // index of the grid cells in the location ahead of the cannon based on its facing direction. long aheadXinc = calcNextXInc(cannonDirection); long aheadYinc = calcNextYInc(cannonDirection); aheadX = aheadXinc; aheadY = aheadYinc; for (cellAhead=0; ((cellAhead getShipMaxDamage()/2.0) { damageHigh = true; } if (justDamagedShip()) { damageRecent = true; } // Conditions under which to flee... // Ship is threatened from behind (whether damageRecent or not doesn't matter) if I have no cannon there // Ship is damageRecent from any direction and damageHigh==true // Ship is threatened from more than one direction (whether damageRecent or not doesn't matter) // Check ahead for an unblocked enemy in range and facing me... enemyRangeAstern = isEnemyThreateningMeFromDirection(getOppositeNeighbourDirection(aheadDirection)); enemyRangeAhead = isEnemyThreateningMeFromDirection(aheadDirection); enemyRangeStarboard = isEnemyThreateningMeFromDirection(getClockwiseNeighbourDirection(aheadDirection)); enemyRangePort = isEnemyThreateningMeFromDirection(getAnticlockwiseNeighbourDirection(aheadDirection)); /* std::cerr << "\n..."; if (enemyRangeAhead) std::cerr << "\nenemy ahead!"; if (enemyRangeAstern) std::cerr << "\nenemy astern!"; if (enemyRangeStarboard) std::cerr << "\nenemy starboard!"; if (enemyRangePort) std::cerr << "\nenemy port!"; */ // ...very dangerous situation. It is time to move! if (enemyRangeAstern > 0) { // if I am right at the limit of its cannon range, sail out of range if ((enemyRangeAstern == getShipCannonRange()-1) && (isCellEmpty(calcNextXInc(aheadDirection), calcNextYInc(aheadDirection)) == true)) { recommendedAction = Globals::actionMoveAhead; } else // I must turn, try to avoid (i) end up with stern to another enemy (ii) facing a rock after the turn { if (enemyRangeStarboard>0) { recommendedAction = Globals::actionTurnRight; } else if (enemyRangePort>0) { recommendedAction = Globals::actionTurnLeft; } else { recommendedAction = turnToFlee(); } } } // No ship is threatening from behind so... else if (enemyRangeAhead>0) { // it will take at least two turns to avoid this enemy // here I can turn and flee if things are bad, or I can return fire if ((enemyRangeStarboard>0) || (enemyRangePort>0)) // I am also being attacked from the sides in addition to the front { if (isCellEmpty(calcNextXInc(aheadDirection), calcNextYInc(aheadDirection)) == true) // its free ahead { recommendedAction = Globals::actionMoveAhead; } // so move out of range of one of the enemy } // but if I am only being attacked from in front... else if (damageHigh) // and damage is high { if (damageRecent) // and things are recently serious { recommendedAction = turnToFlee(); } // turning will cost but we are under fire, flee. else // damage is high but not recent so things are only moderately serious { recommendedAction = Globals::actionFireCannonAhead; } // the enemy has missed a chance to shoot. Fire in the hope they think we are friendly or are just otherwise busy } else { recommendedAction = Globals::actionFireCannonAhead; } // damage is low, shoot ahead } // but I am being attacked from both sides (but not the front or astern) else if ((enemyRangeStarboard>0) && (enemyRangePort>0)) { if (isCellEmpty(calcNextXInc(aheadDirection), calcNextYInc(aheadDirection)) == true) // its free ahead { recommendedAction = Globals::actionMoveAhead; } // so move out of range of one of the enemy else // its not free ahead and an attack by two enemy is still serious { recommendedAction = turnToFlee(); } // turning may cost but we should flee. } // but I am being attacked only from starboard else if (enemyRangeStarboard>0) { if ((damageHigh) && (damageRecent)) // and things are still serious { if (isCellEmpty(calcNextXInc(aheadDirection), calcNextYInc(aheadDirection)) == true) // its free ahead { recommendedAction = Globals::actionMoveAhead; } // so move out of range of one of the enemy else { recommendedAction = Globals::actionTurnRight; } } else { recommendedAction = Globals::actionFireCannonAhead; } } else if (enemyRangePort>0) { if ((damageHigh) && (damageRecent)) // and things are still serious { if (isCellEmpty(calcNextXInc(aheadDirection), calcNextYInc(aheadDirection)) == true) // its free ahead { recommendedAction = Globals::actionMoveAhead; } // so move out of range of one of the enemy else { recommendedAction = Globals::actionTurnLeft; } } else { recommendedAction = Globals::actionFireCannonAhead; } } else { recommendedAction = Globals::actionPass; } return recommendedAction; } bool PixsailAIController::isCellEmpty(long relX, long relY) { if ( isShipAt(relX, relY) || isRockAt(relX, relY) ) { return false; } return true; } // calculate the X increment that will take us one square in the specified direction long PixsailAIController::calcNextXInc(Globals::NeighbourDirection direction) { long aheadXinc=0; switch (direction) { case Globals::neighbourUp: aheadXinc = 0; // if facing up, ahead does not involve moving along the x-axis break; case Globals::neighbourRight: aheadXinc = 1; // if facing right, ahead increases the X coordinate break; case Globals::neighbourDown: aheadXinc = 0; // if facing down, ahead does not involve moving along the x-axis break; case Globals::neighbourLeft: aheadXinc = -1; // if facing left, ahead decreases the X coordinate break; default: std::cerr << "ERROR: PixsailAIController::calcNextXInc() selected invalid direction!"; } return aheadXinc; } long PixsailAIController::calcNextYInc(Globals::NeighbourDirection direction) { long aheadYinc=0; switch (direction) { case Globals::neighbourUp: aheadYinc = 1; // moving ahead increases the Y coordinate break; case Globals::neighbourRight: aheadYinc = 0; // if facing right, moving ahead does not involve moving along the y-axis break; case Globals::neighbourDown: aheadYinc = -1; // moving ahead decreases the Y coordinate break; case Globals::neighbourLeft: aheadYinc = 0; // if facing left, moving ahead does not involve moving along the y-axis break; default: std::cerr << "ERROR: PixsailAIController::calcNextYInc() selected invalid direction!"; } return aheadYinc; } Globals::ShipAction PixsailAIController::getRandomTurnAction(void) { // choose a random direction to turn long randNum = (random() % 2); switch (randNum) { case 0: return Globals::actionTurnRight; break; default: return Globals::actionTurnLeft; break; } return Globals::actionTurnLeft; // silence compiler warning } bool PixsailAIController::getRandomBool(void) { return ((random() % 2)==0) ? true : false; } // turn towards a free space if one is available Globals::ShipAction PixsailAIController::turnToFlee(void) { extern Globals::NeighbourDirection getOppositeNeighbourDirection(Globals::NeighbourDirection currentDirection); extern Globals::NeighbourDirection getClockwiseNeighbourDirection(Globals::NeighbourDirection currentDirection); extern Globals::NeighbourDirection getAnticlockwiseNeighbourDirection(Globals::NeighbourDirection currentDirection); Globals::ShipAction recommendedAction = Globals::actionPass; Globals::NeighbourDirection aheadDirection = getShipAheadDirection(); Globals::NeighbourDirection antiClockwiseDirection = getAnticlockwiseNeighbourDirection(aheadDirection); Globals::NeighbourDirection clockwiseDirection = getClockwiseNeighbourDirection(aheadDirection); // if both directions port and starboard are empty of rocks, choose randomly... if ((isRockAt(calcNextXInc(antiClockwiseDirection), calcNextYInc(antiClockwiseDirection)) == true) && (isRockAt(calcNextXInc(clockwiseDirection), calcNextYInc(clockwiseDirection)) == true)) { recommendedAction = getRandomTurnAction(); } // choose a random direction to turn // else if only port is free, turn to port (clockwise == starboard) else if (isRockAt(calcNextXInc(clockwiseDirection), calcNextYInc(clockwiseDirection)) == true) { recommendedAction = Globals::actionTurnLeft; } // else if only starboard is free, turn to starboard (anticlockwise == port) else if (isRockAt(calcNextXInc(antiClockwiseDirection), calcNextYInc(antiClockwiseDirection)) == true) { recommendedAction = Globals::actionTurnRight; } // else, no rocks so turn randomly else { recommendedAction = getRandomTurnAction(); } return recommendedAction; }