SPYSpheres
ZRGameInternal.cpp
Go to the documentation of this file.
00001 //ZRHS2014
00002 #include <string.h>
00003 #include <math.h>
00004 
00005 #include "ZRGame.h"
00006 #include "ZRGameInternal.h"
00007 #include "ZR_API_internal.h"
00008 #include "ZR_API.h"
00009 #include "Constants.h"
00010 
00011 //SPHERES C includes
00012 extern "C" {
00013 #include "commands.h"
00014 #include "system.h"
00015 #include "math_matrix.h"
00016 #include "gsp.h"
00017 #include "control.h"
00018 #include "find_state_error.h"
00019 #include "housekeeping_internal.h"
00020 }
00021 
00022 /**
00023  * This file is used to define the C++ implementation of both the public and the
00024  * private game API functions.  Public functions will be prefixed with ZeroRoboticsGame::
00025  * while private implementation functions will be prefixed with ZeroRoboticsGameImpl::
00026  */
00027 
00028  /**
00029   * Use these vectors to choose the satellites state when the user is given control.
00030   */
00031 #if SPHERE_ID==SPHERE1
00032 state_vector initStateZR = {0.4,-0.6,0, 0,0,0, 0.7071,-0.7071,0,0, 0,0,0};
00033 #else
00034 state_vector initStateZR = {-0.4,-0.6,0, 0,0,0, 0.7071,-0.7071,0,0, 0,0,0};
00035 #endif
00036 
00037 //======= Singleton Instance of Game and Implementation=====//
00038 ZeroRoboticsGameImpl &ZeroRoboticsGameImpl::instance() {
00039   ///Singleton instance of the game implementation
00040   static ZeroRoboticsGameImpl gameImplInstance(ZeroRoboticsAPIImpl::instance());
00041   return gameImplInstance;
00042 }
00043 ZeroRoboticsGame &ZeroRoboticsGame::instance() {
00044   static ZeroRoboticsGame gameInstance(ZeroRoboticsGameImpl::instance(), ZeroRoboticsAPIImpl::instance());
00045   return gameInstance;
00046 }
00047 
00048 //======== Constructors ========//
00049 
00050 //Constructor for ZeroRobotics game with initializers for the ZR API.  Do not modify.
00051 ZeroRoboticsGame::ZeroRoboticsGame(ZeroRoboticsGameImpl &impl, ZeroRoboticsAPIImpl &apiImpl): pimpl(impl), apiImpl(apiImpl)
00052 {
00053   impl.game = this;
00054 }
00055 
00056 ZeroRoboticsGameImpl::ZeroRoboticsGameImpl(ZeroRoboticsAPIImpl &apiImpl) : apiImpl(apiImpl) {}
00057 
00058 
00059 //Internal accessor for game implementation
00060 ZeroRoboticsGameImpl *getGameImpl()
00061 {
00062   return &ZeroRoboticsGameImpl::instance();
00063 }
00064 
00065 //======== GAME IMPLEMENTATION METHODS ========//
00066 
00067 
00068 //Add methods here for the public game
00069 
00070 void ZeroRoboticsGameImpl::init()
00071 {
00072   /*  Game Initialization setup.
00073   * Then set the locations of the memory packs.
00074   */
00075 
00076   apiImpl.gameTime = GAME_TIME;
00077   memset(&challInfo,0,sizeof(ChallengeInfo));   // general clear of challInfo to 0 = false = empty
00078 
00079   // update the state data on challInfo
00080   updateStates();
00081   challInfo.me.score = START_SCORE;
00082 
00083   // Lights, Camera, Action!
00084   initLight();
00085   initCamera();
00086   initEnergy();
00087 
00088   initItems();
00089 
00090 
00091 
00092   // send information to ground and sat2 at the end of init
00093   sendDebug();
00094 
00095 }
00096 
00097 void ZeroRoboticsGameImpl::updateStates() {
00098   apiImpl.api->getMyZRState(challInfo.me.zrState);
00099   apiImpl.api->getOtherZRState(challInfo.other.zrState);
00100 
00101   apiImpl.api->getMySphState(challInfo.me.sphState);
00102   apiImpl.api->getOtherSphState(challInfo.other.sphState);
00103 
00104   challInfo.currentTime = apiImpl.api->getTime();
00105 
00106   challInfo.camera.tookPicture = false;
00107   challInfo.camera.uploadedPictures = false;
00108 }
00109 
00110 bool ZeroRoboticsGameImpl::update(float forceTorqueOut[6]) {
00111   updateStates();
00112   updateLight();
00113   updateEnergy();
00114 
00115   // empty the userForces array
00116   memset(challInfo.me.userForces,0,sizeof(float)*6);
00117 
00118   // declare a static finalResult variable
00119   // updateGameOverManuever will change it
00120   static unsigned char finalResult = 0;
00121 
00122   unsigned char useForces = false;
00123 
00124   // maneuver 3 = game mode
00125   // maneuver 202 = end game (get final score)
00126   // maneuver 204 = terminate
00127 
00128   switch (ctrlManeuverNumGet())
00129   {
00130 
00131     case 3:
00132       useForces = updateGameModeManeuver(forceTorqueOut);
00133       break;
00134 
00135     //------End main game code------//
00136 
00137     case 202: // Endgame scenario
00138       useForces = updateGameOverManeuver(forceTorqueOut, finalResult);
00139       break;
00140 
00141     case 203: // terminate test after determining final score
00142       {
00143         ctrlTestTerminate(finalResult);
00144       }
00145       break;
00146   }
00147 
00148   //Store user forces for sending to ground
00149   memcpy(challInfo.me.userForces, forceTorqueOut, sizeof(float)*6);
00150 
00151 #ifdef ZR2D
00152   memset(&challInfo.me.userForces[2], 0, sizeof(float)*3);
00153 #endif
00154 
00155   //Send Telemetry
00156   sendDebug();
00157 
00158   return useForces;
00159 
00160 }
00161 
00162 #ifdef ZR2D
00163 static float pintegralX = 0.0f, pintegralY = 0.0f, pintegralZ = 0.0f;
00164 #endif
00165 
00166 void ZeroRoboticsGameImpl::modify2DForceTorque(float forceTorque[6])
00167 {
00168   state_vector myState, targetState;
00169   apiImpl.api->getMySphState(myState);
00170   memcpy(targetState, myState, sizeof(state_vector));   
00171   
00172   targetState[POS_Z] = 0.0f;
00173   targetState[VEL_Z] = 0.0f;
00174   targetState[10] = 0.0f;
00175   targetState[11] = 0.0f;
00176   targetState[12] = 0.0f;
00177 
00178   float q_sw[4], q_tw[4];
00179   float axis[3] = {0.0f, 0.0f, -1.0f};
00180   swingTwistDecomposition(&targetState[QUAT_1], q_sw, q_tw, axis);
00181   quatToPositive(q_tw);
00182   memcpy(&targetState[QUAT_1], q_tw, 4*sizeof(float));
00183   
00184   state_vector error;
00185   findStateError(error, myState, targetState);
00186 
00187   float newForces[6] = {0};
00188   
00189   ctrlPositionPD(KPpositionPD, KDpositionPD, error, newForces);
00190   ctrlAttitudeNLPIDwie(KPattitudePID, KIattitudePID, KDattitudePID, 
00191              KPattitudePID, KIattitudePID, KDattitudePID, 
00192              KPattitudePID, KIattitudePID, KDattitudePID, 
00193              1.0f, error, newForces);
00194   forceTorque[FORCE_Z] = newForces[FORCE_Z];
00195   forceTorque[TORQUE_X] = newForces[TORQUE_X];
00196   forceTorque[TORQUE_Y] = newForces[TORQUE_Y];
00197 }
00198 
00199 // Decomposes the quaternion into swing and twist rotation quaternions.
00200 // More info http://www.alinenormoyle.com/weblog/?p=726.
00201 void swingTwistDecomposition(float q[4], float q_sw[4],
00202   float q_tw[4], float tw_axis[3]) {
00203   
00204   // p = dot(q[0:2], tw_axis) * tw_axis
00205   // q_tw = [p[0], p[1], p[2], q[3]]
00206   float c = mathVecInner(q, tw_axis, 3);
00207   for (int i = 0; i < 3; i++) {
00208     q_tw[i]= tw_axis[i] * c;    
00209   }
00210   q_tw[3] = q[3];
00211   mathVecNormalize(q_tw, 4);
00212   
00213   // invert twist quaternion
00214   float q_tw_inv[4];
00215   memcpy(q_tw_inv, q_tw, 4*sizeof(float));  
00216   for (int i = 0; i < 3; i++) {
00217     q_tw_inv[i] *= -1.0f;
00218   }
00219     
00220   quatMult(q_sw, q_tw_inv, q);
00221 }
00222 
00223 void quatToPositive(float quat[4]) {
00224     if (quat[3] < 0) {
00225       for (int i = 0; i < 4; i++) {
00226         quat[4] *= -1;
00227       }
00228     }
00229 }
00230 
00231 bool ZeroRoboticsGameImpl::updateGameModeManeuver(float forceTorqueOut[6]) {
00232   float collisionInfo[2];
00233 
00234   // get state-of-health of other satellite (mainly for end of test)
00235   comm_payload_soh soh_partner;
00236   #if (SPHERE_ID == SPHERE1)
00237     commBackgroundSOHGet(SPHERE2, &soh_partner);
00238   #else
00239     commBackgroundSOHGet(SPHERE1, &soh_partner);
00240   #endif
00241 
00242   // determine current speed of satellite, use to determine collision avoidance motion
00243   float speed = mathVecMagnitude(&challInfo.me.sphState[3],3);
00244 
00245   unsigned char useForces = false;
00246 
00247   // if camera was used (so it can't be used again for 3 seconds), check when to enable it again
00248   if((!challInfo.camera.cameraOn) && ((challInfo.currentTime - challInfo.camera.cameraOffTime) > DISABLE_CAMERA_TIME))
00249   {
00250     activateCamera();
00251   }
00252 
00253   // Run user's "loop" function.  apiImpl is used because we are accessing
00254   // a private part of the API containing a pointer to the user's API
00255   apiImpl.zruser->loop();
00256 
00257   //Check if items are being picked up
00258   for (int i = 0; i < NUM_ITEMS; i++)
00259     itemPickUp(i);
00260 
00261   useForces = useForces || apiImpl.getForceTorque(forceTorqueOut);
00262   
00263   prop_time tent_times;
00264   #ifdef ZRFLATFLOOR
00265     ctrlMixWLoc(&tent_times, forceTorqueOut, challInfo.me.sphState, 10, 40.0f, FORCE_FRAME_INERTIAL);
00266   #else
00267     ctrlMixWLoc(&tent_times, forceTorqueOut, challInfo.me.sphState, 10, 20.0f, FORCE_FRAME_INERTIAL);
00268   #endif
00269 
00270   float tent_time_sum = 0;
00271   for (int i = 0; i < 12; i++) {
00272     tent_time_sum += tent_times.off_time[i]-tent_times.on_time[i];
00273   }
00274 
00275   //Check for fuel or energy out at this point
00276   if((game->getFuelRemaining() <= 0.0f || game->getEnergy() <= tent_time_sum * ENERGY_COST_THRUSTERS))
00277   {
00278     memset(forceTorqueOut,0,6*sizeof(float));   // anything the user has commanded is ignored, only firings would be from out-of-bounds or collission avoidance
00279     useForces = false;  // assume there will be no firings
00280   }
00281 
00282   //Enforce boundaries of the game
00283   if (enforceBoundaries(forceTorqueOut))
00284   {
00285     //challInfo.me.fuelUsed += OFFSIDES_PENALTY;
00286     GAME_TRACE(("[%d] out of bounds penalty | ", apiImpl.api->getTime()));
00287     useForces = true;
00288   }
00289 
00290   //Collision avoidance
00291   //check to see that its speed is not neglible.
00292   if (speed > 0.01f) {
00293     challInfo.me.collisionActive = ctrlAvoidWithOverride(challInfo.me.sphState,&challInfo.other.sphState,1,forceTorqueOut,collisionInfo,3);
00294   }
00295 
00296   //------Thruster control------//
00297     // Run standard mixer to get fuel consumption
00298   prop_time times;
00299   ctrlMixWLoc(&times, forceTorqueOut, challInfo.me.sphState, 10, 20.0f, FORCE_FRAME_INERTIAL);
00300 
00301   //Tally fuel and energy used for firing thrusters
00302   for (int i=0; i<12; i++)
00303   {
00304     float thrusterTime = times.off_time[i]-times.on_time[i];
00305 
00306     challInfo.me.fuelUsed += thrusterTime / 1000.0f;
00307 
00308     tryToUseEnergy(thrusterTime * ENERGY_COST_THRUSTERS);
00309 
00310 
00311     //Protect against NAN or INF hacking
00312     if (challInfo.me.fuelUsed < 0.0f)
00313     {
00314       DEBUG(("WARNING: invalid forces/torques applied to satellite at t=%d. Fuel will be set to 0. Check your code for errors.\n", challInfo.currentTime));
00315       challInfo.me.fuelUsed = PROP_ALLOWED_SECONDS;
00316     }
00317   }
00318   
00319   #ifdef ZR2D
00320     modify2DForceTorque(forceTorqueOut);
00321     useForces = true;
00322   #endif
00323 
00324   //------Game end conditions------//
00325   if (challInfo.currentTime >= (MAX_GAME_TIME))
00326     ctrlManeuverNumSet(202);
00327 
00328   return useForces;
00329 }
00330 
00331 bool ZeroRoboticsGameImpl::updateGameOverManeuver(float forceTorqueOut[6], unsigned char& finalResult) {
00332   unsigned char useForces = false;
00333   int result;
00334 
00335   float collisionInfo[2];
00336 
00337   // get state-of-health of other satellite (mainly for end of test)
00338   comm_payload_soh soh_partner;
00339   #if (SPHERE_ID == SPHERE1)
00340     commBackgroundSOHGet(SPHERE2, &soh_partner);
00341   #else
00342     commBackgroundSOHGet(SPHERE1, &soh_partner);
00343   #endif
00344 
00345   float myScore;
00346   float otherScore;
00347 
00348   myScore = game->getScore();
00349   otherScore = game->getOtherScore();
00350 
00351   useForces = ctrlAvoidWithOverride(challInfo.me.sphState, &challInfo.other.sphState, 1, forceTorqueOut, collisionInfo, 3) > 0;
00352   useForces = useForces || enforceBoundaries(forceTorqueOut);
00353 
00354   // break ties by making a tiny change based on the random state
00355   if (myScore == otherScore)
00356   {
00357     challInfo.me.score  -= (0.01f * sqrtf((challInfo.me.zrState[0]*challInfo.me.zrState[0])
00358       +(challInfo.me.zrState[1]*challInfo.me.zrState[1])+(challInfo.me.zrState[2]*challInfo.me.zrState[2])));
00359     GAME_TRACE (("Tie Breaker (%12.10f = %12.10f), new score = %12.10f | ", myScore, otherScore, challInfo.me.score));
00360   }
00361 
00362   // get final score
00363   if ((soh_partner.last_test_result || (soh_partner.maneuver_number > 200) || (NUM_SPHERES == 1)))
00364   {
00365     result = 10*floorf(myScore);
00366     if (result < 10) result = 10;
00367     if (result > 220) result = 220;
00368     if (myScore > otherScore)
00369       result += 10;
00370     finalResult = (result + apiImpl.getTeamId() + 1);
00371     useForces = false;
00372   }
00373 
00374   // terminate after we find the final result & timeout (4s)
00375   if (finalResult && (ctrlManeuverTimeGet() > 4000))
00376   {
00377     GAME_TRACE (("GAME ENDED! Final Score Float: %10.8f / Integer: %d | Test Result %d | ", myScore, result, finalResult));
00378     ctrlManeuverTerminate();
00379   }
00380   return useForces;
00381 }
00382 
00383 bool ZeroRoboticsGameImpl::enforceBoundaries(float forceTorqueOut[6])
00384 {
00385   state_vector sphState;
00386   apiImpl.api->getMySphState(sphState);
00387   bool retValue = false;
00388 
00389   //Run velocity controller to stop motion out of volume
00390   //Zero out any controls that are leading the user out of the volume
00391   for (int i=0; i<3; i++)
00392   {
00393     if (sphState[POS_X+i] > limits[i])
00394     {
00395       limitDirection(sphState, forceTorqueOut, i, 1.0f);
00396       retValue = true;
00397     }
00398     else if (sphState[POS_X+i] < -limits[i])
00399     {
00400       limitDirection(sphState, forceTorqueOut, i, -1.0f);
00401       retValue = true;
00402     }
00403   }
00404   return retValue;
00405 }
00406 
00407 void ZeroRoboticsGameImpl::limitDirection(state_vector ctrlState, float ctrlControl[6], unsigned int idx, float dir)
00408 {
00409   // if command is to move out further in same direction as limit
00410   if (ctrlControl[idx]*dir >= 0.0f)
00411   {
00412     ctrlControl[idx] = -1.0f * OOBgain * ctrlState[VEL_X+idx];  // slow down to get 0 velocity (ignore position)
00413   }
00414 }
00415 
00416 /******************Collision Avoidance Functions************************/
00417 void ZeroRoboticsGameImpl::resolveCollision(float zrState[12], float forceTorqueOut[6])
00418 {
00419   for (int i=0; i<3; i++)
00420   {
00421     if (zrState[i] > 0)
00422       limitDirection(zrState, forceTorqueOut, i, -1.0f);
00423     else if (zrState[i] < 0)
00424       limitDirection(zrState, forceTorqueOut, i, 1.0f);
00425   }
00426 }
00427 
00428 float ZeroRoboticsGameImpl::dist3d(float* pos1, float* pos2) const
00429 {
00430   float tmpVec[3];
00431   mathVecSubtract(tmpVec,static_cast<float*>(pos1),static_cast<float*>(pos2),3);
00432   return mathVecMagnitude(tmpVec,3);
00433 }
00434 
00435 
00436 
00437 /*************************Score functions***********************/
00438 float ZeroRoboticsGame::getScore()
00439 {
00440   return pimpl.challInfo.me.score;
00441 }
00442 
00443 float ZeroRoboticsGame::getOtherScore()
00444 {
00445   return pimpl.challInfo.other.score;
00446 }
00447 
00448 //======== PUBLIC API METHODS ========//
00449 //------General Functions------//
00450 
00451 float ZeroRoboticsGame::getFuelRemaining()
00452 {
00453   ZeroRoboticsGameImpl::ChallengeInfo& challInfo = pimpl.challInfo;
00454   float total = PROP_ALLOWED_SECONDS-challInfo.me.fuelUsed;
00455   total = MIN_FUEL(total, 0.0f); //~Prevents fuel from being less than 0.0f
00456   total = MAX_FUEL(total, PROP_ALLOWED_SECONDS); //~Prevents fuel from being greater than MAX fuel in tank
00457   return total;
00458 }
00459 
00460 int ZeroRoboticsGame::getCurrentTime()
00461 {
00462   return pimpl.challInfo.currentTime;
00463 }
00464 
00465 void ZeroRoboticsGame::sendMessage(unsigned char inputMsg)
00466 {
00467   pimpl.challInfo.me.message = (short)inputMsg;
00468 }
00469 
00470 unsigned char ZeroRoboticsGame::receiveMessage()
00471 {
00472   return (unsigned char)pimpl.challInfo.other.message;
00473 }