Chris Pollett > Old Classes > PIC197 ( Print View ) Enrollment info Course Info:
Homework Assignments: Practice Exams: PIC: |
HW3 Solutions Page// // Filename: AStarNode.h // // Purpose: interface for AStarNode struct used by CIntelligentObject // for A* search. // #ifndef __ASTARNODE__ #define __ASTARNODE__ #pragma warning (disable:4786) #include <set> using namespace std; #include "Polygon.h" struct CAStarNode { CPoint point; // current point int distSource; // cost to get to this point int distTarget; // estimated cost to solution CAStarNode *prev; // points parent bool operator<(const CAStarNode *b) const; // used by open priority_queue // cks if distSource+distTarget > b's values }; struct lthan // used by STL set. ck if a's point lex less than b's point. { bool operator()(const CAStarNode* a,const CAStarNode* b) const; }; #endif // Filename: AStarNode.cpp: // // Purpose: implementation of the CAStarNode class. // #include "AStarNode.h" // // Member Function: operator<(const CAStarNode* b) const // // Purpose: used by STL priority_queue in A* algorithm // to return the node of minimal estimated cost. // cost is distance from start to node + estimated distance from // node to solution // bool CAStarNode::operator <(const CAStarNode* b) const { return distSource+distTarget>b->distSource+b->distTarget; } // // Member Function: operator()(const CAStarNode* a, const CAStarNode* b) const // // Purpose: used by STL set in find function. Returns whether // a's point is lexicographically less than b's. // bool lthan::operator()(const CAStarNode* a, const CAStarNode* b) const { return a->point.x< b->point.x || ((a->point.x ==b->point.x) && ( a->point.y <b->point.y)); } // // Filename: ai.h: // // Purpose: Header file for intelligent object class // This is the class used to control the monster's motion // // Modified by: Chris Pollett 2001 // // Copyright Ian Parberry, 1999 // Last updated October 25, 1999 #ifndef __AI__ #define __AI__ #include<vector> #include<stack> #include "objects.h" #include "polygon.h" #include "astarnode.h" class CIntelligentObject: public CObject{ private: int m_nLastAiTime; //last time AI was used int m_nAiDelayTime; //time until AI next used vector<CObject *> m_Obstacles; //an array of obstacles between // monster and player CPoint m_NextGoal; //last recorded position of player stack<CPoint> m_Path; // stores path to player. void ai(); //artificial intelligence void aStar(); // A* algorithm vector<CPoint> findChildren(CAStarNode *b); // used by A* to find // reachable vertices. public: CIntelligentObject(ObjectType object,int x,int y, int xspeed,int yspeed); // constructor virtual void move(); // move depending on time and speed stack<CPoint> getPath(); // returns most recent path to player void player(CPoint p); // used by ObjectManager to write player's // cooridinates. }; #endif // // Filename: ai.cpp // // Purpose: Has the definition of the CIntelligentObject class. // This class control the games monster and uses // the A* algorithm to intelligently hunt the player. // //Modified by Chris Pollett 2001 //Copyright Ian Parberry, 1999 //Last updated October 25, 1999 #pragma warning (disable:4786) // stop Visual complaining // about overlong debugger info #include <set> #include<queue> #include<vector> using namespace std; #include "ai.h" //class header #include "timer.h" //game timer #include "random.h" //for random number generator #include "Objman.h" //for ObjectManager #include "AStarNode.h" // for nodes for A* algorithm #include "Polygon.h" // for polygon class extern CTimer Timer; //game timer extern CRandom Random; //random number generator extern CObjectManager ObjectManager; //game ObjectManager // // Member Function: CIntelligentObject // // Purpose: sets up default values for monster object // CIntelligentObject::CIntelligentObject(ObjectType object, int x,int y,int xspeed,int yspeed): CObject(object,x,y,xspeed,yspeed){ //constructor m_bIntelligent=TRUE; m_nLastAiTime=0; m_nAiDelayTime=250; m_NextGoal = CObject::getLoc();// first goal is for nowhere } // // Member Function: move() // // Purpose: updates monsters coordinates // void CIntelligentObject::move(){ //move object CObject::move(); //move like a dumb object ai(); //act intelligently } // // Member Function: ai() // // Purpose: provides the ai for monsters. cks if we have reached // a goal pt. If so we check our A* path to get the next goal. // if the AiDelayTime has passed, check if we can directly go // to player. If so abandon A* star path and head to player. // If not and the current A* path is empty, calculate new path. // Otherwise, follow old path. // void CIntelligentObject::ai(){ //main AI function //do the following periodically vector<CPoint> tmpVec; CPolygon destPoly; CPoint p; // // ck if at goal pt. if so get next one. // if(CObject::m_GoalPt == CObject::getLoc() && m_Path.size()>0) { CObject::m_GoalPt=m_Path.top(); m_Path.pop(); } // // ck time since last ai move. // if(Timer.elapsed(m_nLastAiTime,m_nAiDelayTime)) { p.create(0,0); // // ck for obstacles b/w player and monster // tmpVec.push_back(CObject::getLoc()); tmpVec.push_back(m_NextGoal); destPoly.create(tmpVec); m_Obstacles=ObjectManager.polyIntersect(p,destPoly,OBSTACLE); if(m_Obstacles.size() > 0 ) // if yes see if need to do A* { if(m_Path.size() == 0) { m_GoalPt = CObject::getLoc(); aStar(); } } else //if no head to player { CObject::m_GoalPt = m_NextGoal; m_Path=stack<CPoint>(); } } } // // Member Function: player(CPoint p) // // Purpose: used by ObjectManager to write the current location of the player. // void CIntelligentObject::player(CPoint p){ m_NextGoal=p; } // // Member Function: aStar // // Purpose: calculates A* path b/w monster and player // void CIntelligentObject::aStar() { priority_queue<CAStarNode *> open; //nodes to searched set<CAStarNode*, lthan> closed, allElts; // nodes that have been // searched and a set // of all nodes. // The reason for ellElts // is that it is hard to find // an elt of a priority queue set<CAStarNode*, lthan>::iterator iter; vector<CPoint> children; CPoint childi; int i; CAStarNode *node, *child,tmp; // // we start the search from the current location // node= new CAStarNode(); node->point=CObject::getLoc(); node->distSource = 0; node->distTarget = (m_NextGoal - node->point).length(); node->prev = NULL; open.push(node); BOOL solved = FALSE; // // loop while there are still nodes we haven't searched. // while(open.size()>0) { node = open.top(); // get the current best candidate open.pop(); if(node->point == m_NextGoal) //ck if it is the player { solved=TRUE; break; } children=findChildren(node); // get a vector of its children closed.insert(node); // add the node to the list of // nodes we've already seen. // // now look at the children // for(i=0;i < children.size() ;i++) { childi = children[i]; child = new CAStarNode(); child->point = childi; tmp.point = childi; // // forget those we've already seen. // if(closed.find(&tmp) ==closed.end()) { child->distSource = node->distSource + (node->point - childi).length(); child->distTarget = (m_NextGoal-childi).length(); child->prev=node; // // ck if still on open list by looking at // allElts. If so ck if we've found a // a shorter path and update. // iter = allElts.find(&tmp); if(iter == allElts.end()) { open.push(child); allElts.insert(child); } else if (child->distSource < (*iter)->distSource ) // // otherwise we just push on open queue // { open.push(child); allElts.erase(iter); allElts.insert(child); } } } } // // if we found a solution record it in m_Path // otherwise sit and do nothing. // if(solved) { m_Path=stack<CPoint>(); do { m_Path.push(node->point); node = node->prev; }while(node->prev != NULL); if(m_Path.size() >0 ) { CObject::m_GoalPt=m_Path.top(); } } else CObject::m_GoalPt=CObject::getLoc(); } // // Member Function: findChildren(CAStarNode *b) // // Purpose: finds all nodes "reachable" from node b. // Reachable means: (a) it is a vertex before or after b // on an obstacle. (b) b is the monster and the node // is a vertex of an obstacle and there is a clear path b/w // the two. (c) b is a vertex of an obstacle and the node // is on another obstacle and there is a clear path b/w them. // // Caveat: This algorithm could probably be improved a lot. // vector<CPoint> CIntelligentObject::findChildren(CAStarNode *b) { vector<CPoint> children, tmpVec; CPolygon obstPoly, ckEdge; CPoint bPoint = b->point, obstLoc, obstVertex, origin, midPt; int i,j; origin.create(0,0); // // loop over all obstacles; // for(i=0; i< m_Obstacles.size(); i++) { obstPoly = m_Obstacles[i]->getPathPolygon(); obstLoc = m_Obstacles[i]->getLoc(); // // loop over all points on an obstacle // for(j=0; j<obstPoly.size(); j++) { obstVertex = obstLoc + obstPoly.getVertex(j); // // ck if this is a vertex of an obstacle // if(!(obstVertex == bPoint)) { tmpVec.clear(); tmpVec.push_back(bPoint); tmpVec.push_back(obstVertex); ckEdge.create(tmpVec); // // ck if edge (b,obstVertex) crosses anything // where we have deleted the vertices b // and obstVertex from the polygon (if they // exist). This notion of deleting points // from a polygon should work if we only // delete one point. // if(ObjectManager.polyIntersect(origin,ckEdge,OBSTACLE,&tmpVec).size()==0) { // // Make sure b was either the monster // or we had two vertices on different // polygons // if(bPoint == CObject::getLoc() || ObjectManager.polyIntersect(origin,ckEdge,OBSTACLE).size()==2) children.push_back(obstVertex); } } else // if vertex of obstacle add adjacent vertices { if(j>0) children.push_back(obstLoc+obstPoly.getVertex(j-1)); if(j<obstPoly.size()-1) children.push_back(obstLoc+obstPoly.getVertex(j+1)); // break; } } // // finally check if there is a direct path from here to player. // tmpVec.clear(); tmpVec.push_back(bPoint); tmpVec.push_back(m_NextGoal); ckEdge.create(tmpVec); if(ObjectManager.polyIntersect(origin,ckEdge,OBSTACLE,&tmpVec).size()==0) children.push_back(m_NextGoal); } return children; } // // Member Function: getPath() // // Purpose: returns the current A* path to the player. // stack<CPoint> CIntelligentObject::getPath() { return m_Path; } // // Filename: Main.cpp // // Purpose: contain WinMain, event handlers for HW3. Loads in images. // // Modified by: Chris Pollett // Copyright Ian Parberry, 1999 // Last updated May 22, 2000 //system includes #include <windows.h> #include <windowsx.h> #include <ddraw.h> #include <stdio.h> //system defines #define WIN32_LEAN_AND_MEAN //custom includes #include "defines.h" //global definitions #include "bmp.h" //bmp file reader #include "timer.h" //game timer #include "csprite.h" //for clipped sprite class #include "objects.h" //for object class #include "objman.h" //for object manager #include "random.h" //for random number generator //defines #define MAX_OBJECTS 5 //max number of objects in game //globals BOOL ActiveApp; //is this application active? LPDIRECTDRAW lpDirectDrawObject=NULL; //direct draw object LPDIRECTDRAWSURFACE lpPrimary=NULL; //primary surface LPDIRECTDRAWPALETTE lpPrimaryPalette; //its palette LPDIRECTDRAWSURFACE lpSecondary=NULL; //back buffer LPDIRECTDRAWPALETTE lpSecondaryPalette; //its palette LPDIRECTDRAWSURFACE lpBackground=NULL; //background image CTimer Timer; //game timer CBmpFileReader background; //background image CBmpSpriteFileReader g_cSpriteImages; //sprite images CObjectManager ObjectManager(MAX_OBJECTS); //object manager CClippedSprite *g_pSprite[NUM_SPRITES]; //sprites CRandom Random; //random number generator // // Helper functions Prototypes // LPDIRECTDRAWPALETTE CreatePalette(LPDIRECTDRAWSURFACE surface); BOOL InitDirectDraw(HWND hwnd); HWND CreateDefaultWindow(char* name,HINSTANCE hInstance); // // Function Category: Load Sprite Helper functions // // Purpose: Load in sprites... We have a different sprite for collided and // non-collided player monsters. // BOOL LoadNPlayerSprite(){ BOOL result=TRUE; result=result&&g_pSprite[NORMALPLAYER]-> load(&g_cSpriteImages,0,1,1); result=result&&g_pSprite[NORMALPLAYER]-> load(&g_cSpriteImages,1,58,1); return result; } //LoadNPlayerSprite BOOL LoadCPlayerSprite(){ BOOL result=TRUE; result=result&&g_pSprite[COLLIDEPLAYER]-> load(&g_cSpriteImages,0,115,1); result=result&&g_pSprite[COLLIDEPLAYER]-> load(&g_cSpriteImages,1,172,1); return result; } //LoadNPlayerSprite BOOL LoadNMonsterSprite(){ BOOL result=TRUE; result=result&&g_pSprite[NORMALMONSTER]-> load(&g_cSpriteImages,0,229,1); result=result&&g_pSprite[NORMALMONSTER]-> load(&g_cSpriteImages,1,287,1); return result; } //LoadNMonsterSprite BOOL LoadCMonsterSprite(){ BOOL result=TRUE; result=result&&g_pSprite[COLLIDEMONSTER]-> load(&g_cSpriteImages,0,345,1); result=result&&g_pSprite[COLLIDEMONSTER]-> load(&g_cSpriteImages,1,403,1); return result; } //LoadNMonsterSprite BOOL LoadObstacleSprite(){ return g_pSprite[OBSTACLE]-> load(&g_cSpriteImages,0,1,230); } //LoadObstacleSprite BOOL LoadImages(){ //load graphics from files to surfaces //get the background image if(!background.load("bckgnd.bmp"))return FALSE; //read from file background.draw(lpPrimary); background.draw(lpSecondary); background.draw(lpBackground); //draw to background surface //set palettes in all surfaces if(!background.setpalette(lpPrimaryPalette))return FALSE; if(!background.setpalette(lpSecondaryPalette))return FALSE; //load the sprites... if(!g_cSpriteImages.load("sprites.bmp"))return FALSE; //...the player g_pSprite[NORMALPLAYER]=new CClippedSprite(2,56,58); if(!LoadNPlayerSprite())return FALSE; //load normal player g_pSprite[COLLIDEPLAYER]=new CClippedSprite(2,56,58); if(!LoadCPlayerSprite())return FALSE; //load collide player //...the monster g_pSprite[NORMALMONSTER]=new CClippedSprite(2,53,58); if(!LoadNMonsterSprite())return FALSE; //load normal player g_pSprite[COLLIDEMONSTER]=new CClippedSprite(2,53,58); if(!LoadCMonsterSprite())return FALSE; //load collide player //...the obstacle g_pSprite[OBSTACLE]=new CClippedSprite(1,168,185); if(!LoadObstacleSprite())return FALSE; //load normal player return TRUE; } //LoadImages // // Function Name: CreateObjects // // Purpose: uses the ObjectManager to create the monster and // the player and sets the player as the current object. // void CreateObjects(){ ObjectManager.create(NORMALMONSTER,3*SCREEN_WIDTH/4, SCREEN_HEIGHT/2,0,0); ObjectManager.set_current( ObjectManager.create(NORMALPLAYER,SCREEN_WIDTH/4, SCREEN_HEIGHT/2,0,0)); } //CreateObjects // // Function Category: Frame processing functions // // Purpose: maintain and update DirectDraw surfaces // BOOL RestoreSurfaces(){ //restore all surfaces BOOL result=TRUE; if(SUCCEEDED(lpPrimary->Restore())) //if primary restored result=result&&background.draw(lpPrimary)&& //redraw image background.setpalette(lpPrimaryPalette); //set palette else return FALSE; if(SUCCEEDED(lpSecondary->Restore())) //if secondary restored result=result&&background.draw(lpSecondary)&& //redraw image background.setpalette(lpSecondaryPalette); //set palette else return FALSE; if(SUCCEEDED(lpBackground->Restore())) //if background restored result=result&&background.draw(lpBackground); //redraw image else return FALSE; if(g_pSprite[NORMALPLAYER]->Restore()) //if normal player restored result=result&&LoadNPlayerSprite(); //redraw image else return FALSE; if(g_pSprite[COLLIDEPLAYER]->Restore()) //if collide player restored result=result&&LoadCPlayerSprite(); //redraw image else return FALSE; if(g_pSprite[NORMALMONSTER]->Restore()) //if normal monster restored result=result&&LoadNMonsterSprite(); //redraw image else return FALSE; if(g_pSprite[COLLIDEMONSTER]->Restore()) //if collide monster restored result=result&&LoadCMonsterSprite(); //redraw image else return FALSE; if(g_pSprite[OBSTACLE]->Restore()) //if normal monster restored result=result&&LoadObstacleSprite(); //redraw image else return FALSE; return result; } //RestoreSurfaces BOOL PageFlip(){ //return TRUE if page flip succeeds if(lpPrimary->Flip(NULL,DDFLIP_WAIT)==DDERR_SURFACELOST) return RestoreSurfaces(); return TRUE; } //PageFlip BOOL ComposeFrame(){ //compose a frame of animation background.draw(lpSecondary); ObjectManager.animate(lpSecondary); //draw objects return TRUE; } //ComposeFrame BOOL ProcessFrame(){ //process a frame of animation ComposeFrame(); //compose a frame in secondary surface return PageFlip(); //flip video memory surfaces } //ProcessFrame // // Function Category: Event Handlers and WinMain // // Purpose: set up main input loops handle events in particular // player keystrokes // BOOL keyboard_handler(WPARAM keystroke){ //keyboard handler BOOL result=FALSE; //return TRUE if game is to end BOOL notGood=TRUE; int index, tries=0; switch(keystroke){ case VK_ESCAPE: result=TRUE; break; //exit game // //Player direction motions // case VK_UP: ObjectManager.accelerate(0,-1); break; case VK_DOWN: ObjectManager.accelerate(0,1); break; case VK_LEFT: ObjectManager.accelerate(-1,0); break; case VK_RIGHT: ObjectManager.accelerate(1,0); break; case VK_F2: ObjectManager.stopCurrent(); break; //stop player // // add a new obstacle // case VK_F1: // loop around trying to add an obstacle. // If collides with an existing object kill the new object. while(notGood && tries <100) { index=ObjectManager.create(OBSTACLE, Random.number(84,SCREEN_WIDTH-84), Random.number(185,SCREEN_HEIGHT),0,0); if(index >=0 ) { if(!ObjectManager.objectIntersect(index)) notGood=FALSE; else ObjectManager.kill(index); } else notGood=FALSE; tries++; } break; // //Print player coords as well as draw A* path // case VK_F3: ObjectManager.toggleCoords(); break; // //Make monster avoid or chase player // case VK_SPACE: ObjectManager.toggleAvoid(); break; default: break; } return result; } //keyboard_handler //message handler (window procedure) long CALLBACK WindowProc(HWND hwnd,UINT message, WPARAM wParam,LPARAM lParam){ switch(message){ case WM_ACTIVATEAPP: ActiveApp=wParam; break; case WM_CREATE: break; case WM_KEYDOWN: //keyboard hit if(keyboard_handler(wParam))DestroyWindow(hwnd); break; case WM_DESTROY: //end of game if(lpDirectDrawObject!=NULL){ //if DD object exists if(lpSecondary!=NULL) //if secondary surface exists lpSecondary->Release(); //release secondary surface if(lpPrimary!=NULL) //if primary surface exists lpPrimary->Release(); //release primary surface if(lpBackground!=NULL) //if background exists lpBackground->Release(); //release background for(int i=0; i<NUM_SPRITES; i++){ //for each sprite if(g_pSprite[i]) //if sprite exists g_pSprite[i]->Release(); //release sprite delete g_pSprite[i]; //delete sprite } lpDirectDrawObject->Release(); //release DD object } ShowCursor(TRUE); //show the mouse cursor PostQuitMessage(0); //and exit break; default: //default window procedure return DefWindowProc(hwnd,message,wParam,lParam); } //switch(message) return 0L; } //WindowProc int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow){ MSG msg; //current message HWND hwnd; //handle to fullscreen window hwnd=CreateDefaultWindow("directX demo 6",hInstance); if(!hwnd)return FALSE; //set up window ShowWindow(hwnd,nCmdShow); UpdateWindow(hwnd); SetFocus(hwnd); //allow input from keyboard ShowCursor(FALSE); //hide the cursor //init graphics for(int i=0; i<NUM_SPRITES; i++) //null out sprites g_pSprite[i]=NULL; BOOL OK=InitDirectDraw(hwnd);//initialize DirectDraw if(OK)OK=LoadImages(); //load images from disk if(!OK){ //bail out if initialization failed DestroyWindow(hwnd); return FALSE; } //start game timer Timer.start(); //create objects CreateObjects(); //message loop while(TRUE) if(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){ if(!GetMessage(&msg,NULL,0,0))return msg.wParam; TranslateMessage(&msg); DispatchMessage(&msg); } else if(ActiveApp)ProcessFrame(); else WaitMessage(); } //WinMain // // Filename: objects.h // // Purpose: header file for CObject class // // Modified by: Chris Pollett // // Copyright Ian Parberry, 1999 // Last updated October 27, 1999 #ifndef __OBJECTS__ #define __OBJECTS__ #include "bsprite.h" #include "bmp.h" #include "polygon.h" //object types enum ObjectType{NORMALMONSTER=0, COLLIDEMONSTER, NORMALPLAYER, COLLIDEPLAYER, OBSTACLE,NUM_SPRITES}; //note: NUM_SPRITES must be last enum LocomotionType{PLAYER_MOTION,MONSTER_MOTION,NO_MOTION}; // player motion is given by the object x and y speed // monster motion is based on a nonadjustable speed and // is always directed toward the point stored in m_Goal. class CObject{ //class for a moving object friend class CObjectManager; protected: CPolygon m_Polygon; // polygon used to ck for object collisions CPolygon m_PathPolygon; // polygon used for A* path finding CPoint m_GoalPt; // if a Monster then the next point to move towards // or away from BOOL m_bAvoid; //holds whether monster is in avoid or chase mode int m_nX,m_nY; //current location int m_nXspeed,m_nYspeed; //current speed int m_nLastXMoveTime; //last time moved horizontally int m_nLastYMoveTime; //last time moved vertically CBaseSprite *m_pSprite; //pointer to sprite int m_nWidth,m_nHeight; //width and height of sprite int m_nMinXSpeed,m_nMaxXSpeed; //min, max horizontal speeds int m_nMinYSpeed,m_nMaxYSpeed; //min, max vertical speeds int m_nCurrentFrame; //frame to be displayed int m_nFrameCount; //number of frames in animation int m_nLastFrameTime; //last time the frame was changed int m_nFrameInterval; //interval between frames LocomotionType m_eLocomotion; //mode of travel ObjectType m_eObject; //what kind of object is this? int m_nBirthTime; //time of creation BOOL m_bIntelligent; //TRUE if object is intelligent public: CObject(ObjectType object,int x,int y, int xspeed,int yspeed); //constructor void draw(LPDIRECTDRAWSURFACE surface); //draw void accelerate(int xdelta,int ydelta=0); //change speed virtual void move(); //move depending on time and speed CPolygon getPolygon(); // returns bounding polygon of object CPolygon getPathPolygon(); // returns polygon used for A* path finding CPoint getLoc(); // returns current location of object void toggleAvoid(); // changes monster for chase to avoid and back }; BOOL IntersectObject(CObject *o1, CObject *o2); // cks if two // objects bounding polygons // intersect #endif // // Filename: objects.cpp // // Purpose: contains implementation of CObject class // // Modified by: Chris Pollett 2001 // //Copyright Ian Parberry, 1999 //Last updated October 27, 1999 #include "objects.h" #include "polygon.h" // for polygons #include "timer.h" //game timer #include "csprite.h" //for clipped sprite class #include "random.h" //for random number generator extern CTimer Timer; //game timer extern CRandom Random; //random number generator extern CClippedSprite *g_pSprite[]; //sprite CObject::CObject(ObjectType object,int x,int y, int xspeed,int yspeed){ //constructor vector<CPoint> v; //used to set polygons around objects CPoint tmp; // //defaults m_nCurrentFrame=0; m_nLastFrameTime=Timer.time(); m_nFrameInterval=30; m_eLocomotion=NO_MOTION; m_bIntelligent=FALSE; //common values m_eObject=object; //type of object m_nLastXMoveTime=m_nLastYMoveTime=Timer.time(); //time m_nX=x; m_nY=y; //location m_nXspeed=xspeed; m_nYspeed=yspeed; //speed m_pSprite=g_pSprite[object]; m_nFrameCount=m_pSprite->frame_count(); m_nHeight=m_pSprite->height(); m_nWidth=m_pSprite->width(); m_nBirthTime=Timer.time(); //time of creation m_bAvoid=FALSE; m_GoalPt=getLoc(); //customized values switch(object){ case NORMALPLAYER: case COLLIDEPLAYER: m_nMinXSpeed=-3; m_nMaxXSpeed=3; m_nMinYSpeed=-3; m_nMaxYSpeed=3; m_nFrameInterval=500; m_eLocomotion=PLAYER_MOTION; tmp.create(-28,0); // set up bounding polygon for player v.push_back(tmp); // Let's do it in as brute force a way tmp.create(28,0); // as possible v.push_back(tmp); tmp.create(28,-56); v.push_back(tmp); tmp.create(-28,-56); v.push_back(tmp); tmp.create(-28,0); v.push_back(tmp); m_Polygon.create(v); break; case NORMALMONSTER: case COLLIDEMONSTER: m_nMinXSpeed=-3; m_nMaxXSpeed=-3; m_nMinYSpeed=-3; m_nMaxYSpeed=3; m_nFrameInterval=500; m_eLocomotion=MONSTER_MOTION; m_bIntelligent = TRUE; tmp.create(-28,0); // set up bounding polygon for monster v.push_back(tmp); tmp.create(28,0); v.push_back(tmp); tmp.create(28,-56); v.push_back(tmp); tmp.create(-28,-56); v.push_back(tmp); tmp.create(-28,0); v.push_back(tmp); m_Polygon.create(v); break; case OBSTACLE: tmp.create(-84,0); // set up bounding polygon for obstacle v.push_back(tmp); tmp.create(84,0); v.push_back(tmp); tmp.create(84,-185); v.push_back(tmp); tmp.create(-84,-185); v.push_back(tmp); tmp.create(-84,-130); v.push_back(tmp); tmp.create(30,-130); v.push_back(tmp); tmp.create(30,-50); v.push_back(tmp); tmp.create(-84,-50); v.push_back(tmp); tmp.create(-84,0); v.push_back(tmp); m_Polygon.create(v); v.clear(); // set up path polygon for obstacle tmp.create(-106,50); // used by A* in finding paths v.push_back(tmp); tmp.create(106,50); v.push_back(tmp); tmp.create(106,-179); v.push_back(tmp); tmp.create(-106,-179); v.push_back(tmp); tmp.create(-106,-80); //add an additional edge monster is allowed to v.push_back(tmp); //traverse not in bounding polygon. tmp.create(-106,-44); v.push_back(tmp); tmp.create(-106,-80); v.push_back(tmp); tmp.create(8,-80); v.push_back(tmp); tmp.create(8,-44); v.push_back(tmp); tmp.create(-106,-44); v.push_back(tmp); tmp.create(-106,50); v.push_back(tmp); m_PathPolygon.create(v); break; } } void CObject::draw(LPDIRECTDRAWSURFACE surface){ //draw //draw the current frame m_pSprite->draw(m_nCurrentFrame,m_nX, m_nY,surface); //repeating animation int t=m_nFrameInterval/(1+abs(m_nXspeed)); //frame interval if(m_nFrameCount>1&&Timer.elapsed(m_nLastFrameTime,t)) if(++m_nCurrentFrame>=m_nFrameCount){ //wrap m_nCurrentFrame=0; } } void CObject::accelerate(int xdelta,int ydelta){ //change speed //horizontal m_nXspeed+=xdelta; if(m_nXspeed<m_nMinXSpeed)m_nXspeed=m_nMinXSpeed; if(m_nXspeed>m_nMaxXSpeed)m_nXspeed=m_nMaxXSpeed; //vertical m_nYspeed+=ydelta; if(m_nYspeed<m_nMinYSpeed)m_nYspeed=m_nMinYSpeed; if(m_nYspeed>m_nMaxYSpeed)m_nYspeed=m_nMaxYSpeed; } void CObject::toggleAvoid(){ //toggle whether monster should avoid or chase if(m_bAvoid) m_bAvoid=FALSE; else m_bAvoid=TRUE; } void CObject::move(){ //move object const int XSCALE=16; //to scale back horizontal motion const int YSCALE=32; //to scale back vertical motion int xdelta,ydelta; //change in position int time=Timer.time(); //current time int tfactor; //time since last move CPoint tmp; switch(m_eLocomotion){ case PLAYER_MOTION:// players move according to their x and y speed //horizontal motion tfactor=time-m_nLastXMoveTime; //time since last move xdelta=(m_nXspeed*tfactor)/XSCALE; //x distance moved m_nX+=xdelta; //x motion if(xdelta||m_nXspeed==0) //record time of move m_nLastXMoveTime=time; if(m_nX<-m_nWidth/2)m_nX=SCREEN_WIDTH+m_nWidth/2; if(m_nX>SCREEN_WIDTH+m_nWidth/2)m_nX=-m_nWidth/2; //vertical motion tfactor=time-m_nLastYMoveTime; //time since last move ydelta=(m_nYspeed*tfactor)/YSCALE; //y distance moved m_nY+=ydelta; //y motion if(m_nY<0)m_nY=SCREEN_HEIGHT+m_nHeight; if(m_nY> SCREEN_HEIGHT+m_nHeight)m_nY=0; if(ydelta||m_nYspeed==0) //record time of move m_nLastYMoveTime=time; break; case MONSTER_MOTION: //monster move towards a goal pt m_Goal tmp = CObject::getLoc(); if( tmp == m_GoalPt) { m_nYspeed = 0; m_nXspeed = 0; } else { tmp = m_GoalPt-tmp; m_nYspeed = abs(tmp.y*m_nMaxYSpeed/tmp.length()); m_nXspeed = m_nMaxYSpeed-m_nYspeed; if(tmp.y <0) m_nYspeed = -m_nYspeed; if(tmp.x <0) m_nXspeed = -m_nXspeed; } if(m_bAvoid) //or in the opposite direction if they are in avoid mode { m_nYspeed = -m_nYspeed; m_nXspeed = -m_nXspeed; } tfactor=time-m_nLastXMoveTime; //time since last move xdelta=(m_nXspeed*tfactor)/YSCALE; //x distance moved m_nX+=xdelta; //x motion if(xdelta || m_nXspeed==0) //record time of move m_nLastXMoveTime=time; if(m_nX<-m_nWidth/2)m_nX=SCREEN_WIDTH+m_nWidth/2; if(m_nX> SCREEN_WIDTH+m_nWidth/2)m_nX=-m_nWidth/2; tfactor=time-m_nLastYMoveTime; //time since last move ydelta=(m_nYspeed*tfactor)/YSCALE; //x distance moved m_nY+=ydelta; //y motion if(ydelta || m_nYspeed==0) //record time of move m_nLastYMoveTime=time; if(m_nY<0)m_nY=SCREEN_HEIGHT+m_nHeight; if(m_nY>SCREEN_HEIGHT+m_nHeight)m_nY=0; break; default: break; } } CPolygon CObject::getPolygon() // return objects bounding polygon { return m_Polygon; } CPolygon CObject::getPathPolygon() // return object A* path polygon { return m_PathPolygon; } CPoint CObject::getLoc() // return current position of object { CPoint tmp; tmp.create(m_nX,m_nY); return tmp; } BOOL IntersectObject(CObject *ob1, CObject *ob2) { return IntersectPolygon(ob1->getLoc(),ob1->getPolygon(), ob2->getLoc(),ob2->getPolygon()); } // Filename: objman.h // // Purpose: Header file for the object manager. This class handles the // creation and drawing of objects on the screen // // Modified by: CHris Pollett 2001 // // Copyright Ian Parberry, 1999 // Last updated October 27, 1999 #ifndef __OBJMAN__ #define __OBJMAN__ #include <windows.h> #include <windowsx.h> #include <ddraw.h> #include <stack> #include "objects.h" #include "Polygon.h" class CObjectManager{ private: CObject **m_pObjectList; //list of objects in game int m_nCount; //how many objects in list int m_nMaxCount; //maximum number of objects int m_nCurrentObject; //index of the current object //distance functions int distance(int x1,int y1,int x2,int y2); //in universe int distance(int first,int second); //between objects BOOL m_bCoordsOn; int drawCoordinates(CPoint p, LPDIRECTDRAWSURFACE surface); int drawPath(CPoint p, CPoint p2, stack<CPoint> path, LPDIRECTDRAWSURFACE surface); //collision detection void collision_detection(); //all collisions //managing dead objects void replace(int index); //replace by next in series public: CObjectManager(int max); //constructor ~CObjectManager(); //destructor int create(ObjectType object,int x,int y, int xspeed,int yspeed); //create new object void kill(int index); //remove object from list //animate all objects void animate(LPDIRECTDRAWSURFACE surface); //the following functions operate on the current object void accelerate(int xdelta,int ydelta=0); //change speed BOOL objectIntersect(int j); //cks if any object // intersect with object j in the object list // probably should access this with an object // rather than an integer. (uses bounding // not A* polygon) vector<CObject *> polyIntersect(CPoint off, CPolygon poly, ObjectType type, vector<CPoint> *rem=NULL); // ck if given polygon + offset // intersects the A* path polygons of // objects of the given type after the points in rem // have been removed. void set_current(int index); //set current object void toggleAvoid(); // make monster swith b/w avoid and chase. void toggleCoords(); // say whether we draw each objects coordinates void stopCurrent(); // stop the current object motion }; #endif // // Filename: objman.cpp // // Purpose: object manager class implementation // // Modified by: Chris Pollett 2001 // // Copyright Ian Parberry, 1999 // Last updated October 28, 1999 #include <math.h> //for sqrt #include<vector> #include<stack> #include "objman.h" #include "polygon.h" //for polygons #include "timer.h" //game timer #include "csprite.h" //for clipped sprite class #include "ai.h" //for intelligent objects extern CTimer Timer; //game timer extern CClippedSprite *g_pSprite[]; //sprites CObjectManager::CObjectManager(int max){ //constructor m_nMaxCount=max; m_nCount=0; m_nCurrentObject=0; m_bCoordsOn = FALSE; m_pObjectList=new CObject*[max]; //object list for(int i=0; i<m_nMaxCount; i++) //create objects m_pObjectList[i]=NULL; } CObjectManager::~CObjectManager(){ //destructor for(int i=0; i<m_nMaxCount; i++) //for each object delete m_pObjectList[i]; //delete it delete[]m_pObjectList; //delete object list } int CObjectManager::create(ObjectType object,int x,int y, int xspeed,int yspeed){ if(m_nCount<m_nMaxCount){ //if room, create object int i=0; //index into object list while(m_pObjectList[i]!=NULL)i++; //find first free slot if(object==NORMALMONSTER || object==COLLIDEMONSTER) //intelligent object m_pObjectList[i]= new CIntelligentObject(object,x,y,xspeed,yspeed); else //dumb object m_pObjectList[i]=new CObject(object,x,y,xspeed,yspeed); m_nCount++; return i; } else return -1; } void CObjectManager::animate(LPDIRECTDRAWSURFACE surface){ //move objects int i,j; CPolygon poly; CObject *obj; CPoint p; for(i=0; i<m_nMaxCount; i++) //for each object slot if(m_pObjectList[i]!=NULL){ //if there's an object there m_pObjectList[i]->move(); //move it if(m_pObjectList[i]->m_bIntelligent) //if intelligent //tell object about plane current position ((CIntelligentObject*)m_pObjectList[i])->player( m_pObjectList[m_nCurrentObject]->getLoc()); } //collision detection collision_detection(); //draw objects for(i=0; i<m_nMaxCount; i++) //for each object slot { obj = m_pObjectList[i]; if(obj !=NULL) //if there's an object there { obj->draw(surface); //draw it if(m_bCoordsOn) //if we're in coords mode draw coords { // and if monster its A* path poly = obj->getPolygon(); p = obj->getLoc(); for(j=0; j<poly.size(); j++) drawCoordinates(p+poly.getVertex(j), surface); if(obj->m_bIntelligent) drawPath(p, obj->m_GoalPt, ((CIntelligentObject*)obj)->getPath(), surface); } } } } void CObjectManager::accelerate(int xdelta,int ydelta){ //change speed of current object m_pObjectList[m_nCurrentObject]-> accelerate(xdelta,ydelta); } void CObjectManager::toggleAvoid() //changes monsters b/w chase and avoid mode { int i; for(i=0; i<m_nMaxCount; i++) //for each object slot if(m_pObjectList[i]!=NULL) //if there's an object there m_pObjectList[i]->toggleAvoid(); //toggle it } void CObjectManager::toggleCoords() //toggles whethere we draw objects coords or // not { m_bCoordsOn = !m_bCoordsOn; } void CObjectManager::set_current(int index){ //set current object if(index>=0&&index<m_nCount)m_nCurrentObject=index; } int CObjectManager::distance(int x1,int y1,int x2,int y2){ //return distance in universe int x=abs(x1-x2),y=abs(y1-y2);//x and y distance //return result return (int)sqrt((double)x*x+(double)y*y); } int CObjectManager::distance(int first,int second){ //return distance between objects //bail if bad index if(first<0||first>=m_nMaxCount)return -1; if(second<0||second>=m_nMaxCount)return -1; //get coordinates of centers int x1,y1,x2,y2; //coordinates of objects x1=m_pObjectList[first]->m_nX; y1=m_pObjectList[first]->m_nY- m_pObjectList[first]->m_nHeight/2; x2=m_pObjectList[second]->m_nX; y2=m_pObjectList[second]->m_nY- m_pObjectList[second]->m_nHeight/2; //return distance between coordinates return distance(x1,y1,x2,y2); } void CObjectManager::kill(int index){ //remove object m_nCount--; delete m_pObjectList[index]; m_pObjectList[index]=NULL; } void CObjectManager::replace(int index){ //replace object by next in series CObject *object=m_pObjectList[index]; //current object ObjectType newtype; //decide on new object type switch(object->m_eObject){ case NORMALPLAYER: newtype=COLLIDEPLAYER; break; case COLLIDEPLAYER: newtype=NORMALPLAYER; break; case NORMALMONSTER: newtype=COLLIDEMONSTER; break; case COLLIDEMONSTER: newtype=NORMALMONSTER; break; } object->m_eObject=newtype; object->m_pSprite=g_pSprite[newtype]; } void CObjectManager::collision_detection(){ //check for all collisions CObject *object=m_pObjectList[m_nCurrentObject], *obj2; int i=0; //index of object collided with BOOL mCollide=FALSE; while(i<m_nMaxCount){ // loop over potential objects obj2=m_pObjectList[i]; if(obj2!=NULL && m_nCurrentObject != i) // make sure not player { //if i is a valid object index if(IntersectObject(object,obj2)) //ck if intersects player { switch(obj2->m_eObject){ case NORMALMONSTER: replace(i); //replace object that is hit // notice no break case COLLIDEMONSTER: mCollide = TRUE; if(object->m_eObject==NORMALPLAYER) replace(m_nCurrentObject); break; case OBSTACLE: stopCurrent(); //stop player break; } } else if(obj2->m_eObject==COLLIDEMONSTER) //handle monster no //colliding with player replace(i); } ++i; //next object } if(object->m_eObject==COLLIDEPLAYER && mCollide==FALSE) replace(m_nCurrentObject); //revert player if not colliding with //monster } BOOL CObjectManager::objectIntersect(int j){ //check if object j's //bounding polygon intersects // anything int i=0; //index of object collided with CObject *object = m_pObjectList[j], *obj2; while(i<m_nMaxCount) { obj2=m_pObjectList[i]; if(obj2!=NULL && i !=j) { //if i is a valid object index if(IntersectObject(object, obj2)) return TRUE; } i++; } return FALSE; } vector<CObject *> CObjectManager::polyIntersect(CPoint off, CPolygon poly, ObjectType type, vector<CPoint> *rem) { // return all the objects whose path polygon when the points in rem // have been deleted intersects poly+off CObject *object; vector<CObject* > collisions; int i; for(i=0;i<m_nMaxCount;i++) { object=m_pObjectList[i]; if(object!=NULL) { //if i is a valid object index if(object->m_eObject == type) { if(IntersectPolygon(off, poly, object->getLoc(), object->getPathPolygon(), rem)) collisions.push_back(object); } } } return collisions; } int CObjectManager::drawCoordinates(CPoint p, LPDIRECTDRAWSURFACE surface) { //draw the coordinates of the point p to the surface given HDC xdc; //GDI device context char out[11]; if(FAILED(surface->GetDC(&xdc))) //get the device context for surface return 0; SetTextColor(xdc,RGB(0,0,255)); //set the color to blue SetBkMode(xdc, TRANSPARENT); //don't draw backgroup of char's out[0] = 'x'; //make our char array out[1] = '='; out[2] = '0'+p.x/100; out[3] = '0'+(p.x/10)%10; out[4] = '0'+p.x%10; out[5] = ','; out[6] = 'y'; out[7] = '='; out[8] = '0'+p.y/100; out[9] = '0'+(p.y/10)%10; out[10] = '0'+p.y%10; TextOut(xdc, p.x,p.y, out, 11); //print out char array of size 11 // at p.x, p.y on screen surface->ReleaseDC(xdc); //release context when done return 1; } int CObjectManager::drawPath(CPoint p, CPoint p2, stack<CPoint> path, LPDIRECTDRAWSURFACE surface) { // draw the the path given in stack HDC xdc; // GDI device context HPEN blue_pen; // make a pen CPoint start, stop; if(path.size() == 0) return 0; // no path do nothing if(FAILED(surface->GetDC(&xdc))) //get context return 0; blue_pen = CreatePen(PS_SOLID,1, RGB(0,0,255)); // make a blue pen SelectObject(xdc,blue_pen); // set to use it stop = path.top(); MoveToEx(xdc,p.x,p.y, NULL); //start at monster LineTo(xdc,p2.x,p2.y); // go to first goal pt LineTo(xdc, stop.x, stop.y); // go to next path point while(path.size()>1) //draw the rest of the edges of path { start=path.top(); path.pop(); stop=path.top(); MoveToEx(xdc,start.x,start.y, NULL); //unneccessary LineTo(xdc, stop.x, stop.y); } DeleteObject(blue_pen); surface->ReleaseDC(xdc); return 1; } void CObjectManager::stopCurrent(){ //stop current object m_pObjectList[m_nCurrentObject]->m_nXspeed =0; m_pObjectList[m_nCurrentObject]->m_nYspeed =0; } // // Filename: Polygon.h // // Purpose: contains definitions of point, edge and polygon structs/classes // #ifndef __POLYGON__ #define __POLYGON__ #include<windows.h> #include<vector> using namespace std; struct CPoint //encapsulates notion of a pt on the screen { int x; // coords int y; CPoint(); void create(int xcoord, int ycoord); //sets the coords BOOL operator==(CPoint p2); //cks if two pts the same CPoint operator+(CPoint p2); // adds points componentwise CPoint operator-(CPoint p2); //subtracts points component wise int length(); // returns Euclidean length of pts. }; struct CEdge //encapsulates the notions of edge in a graph { CPoint left; // vertices CPoint right; CEdge(); void create(CPoint first, CPoint second); // sets vertices CEdge operator+(CPoint p2); // adds p2 to both left and right int length(); // returns length of edge CEdge boundRect(); // returns bounding rectangle of edge }; int CrossProduct( CPoint p1, CPoint p2); // computes cross product of two // points viewed as 2d vectors BOOL Straddle( CEdge e1, CEdge e2); //cks if edges straddle each other BOOL EdgeCross( CPoint off1, CEdge e1, CPoint off2, CEdge e2); //cks if two edges cross class CPolygon // encapsulates notions of a polygon { protected: vector<CPoint> m_Vertices; // vertices in polygon public: CPolygon(); ~CPolygon(); void create(vector<CPoint> vertices); //set vertices CEdge getEdge(int i); // returns the ith edge CPoint getVertex(int i); // returns the i vertex void erase(int i); // deletes the i vertex. implement so // if done only once will not add any new //edge to polygon int size(); // returns number of vertices }; BOOL IntersectPolygon( CPoint off1, CPolygon p1, CPoint off2, CPolygon p2, vector<CPoint> *rem=NULL); // cks if when we offset two polygons and delete pts rem from the second // if there are any intersections. #endif // // Filename: Polygon.cpp // // Purpose: contains implementation of point, edge and polygon structs/classes // #include <math.h> #include<iostream> #include "Polygon.h" CPoint::CPoint() //point constructor { x=0; y=0; } void CPoint::create(int xcoord, int ycoord) //set coords { x=xcoord; y=ycoord; } CPoint CPoint::operator+(CPoint p2) //add point component-wise { CPoint tmp; tmp.create(x+p2.x,y+p2.y); return tmp; } CPoint CPoint::operator-(CPoint p2) //subtract component-wise { CPoint tmp; tmp.create(x-p2.x,y-p2.y); return tmp; } BOOL CPoint::operator==(CPoint p2) //ck equality component-wise { if(x==p2.x && y==p2.y) return TRUE; return FALSE; } int CPoint::length() //return euclidean length of pt { return (int)sqrt((double)x*x+(double)y*y); } CEdge::CEdge() //constrcutor { left=CPoint(); right=CPoint(); } void CEdge::create(CPoint first, CPoint second) //set vertices { left=first; right=second; } CEdge CEdge::operator +(CPoint p) //add p to each vertex in edge { CEdge tmp; tmp.create(left + p, right+p); return tmp; } int CEdge::length() //return length of edge { return (left-right).length(); } CEdge CEdge::boundRect() //returns bounding rectangle of edge { CPoint tmpL, tmpR; CEdge e; tmpL.create(min(left.x, right.x),min(left.y, right.y)); tmpR.create(max(left.x, right.x),max(left.y, right.y)); e.create(tmpL, tmpR); return e; } int CrossProduct( CPoint p1, CPoint p2) //view pts as vectors and take cross //product { return p1.x*p2.y - p1.y*p2.x; } BOOL Straddle( CEdge e1, CEdge e2) //ck if edges straddle each other { CPoint deltE1 =e1.right-e1.left; int tmp = CrossProduct(e2.left-e1.left,deltE1); int tmp2 = CrossProduct(e2.right-e1.left,deltE1); tmp *= tmp2; if (tmp<=0) return TRUE; else return FALSE; } BOOL EdgeCross( CPoint off1, CEdge e1, CPoint off2, CEdge e2) //ck if edge+offset cross { CEdge tmpE1 = (e1+off1).boundRect(), tmpE2=(e2+off2).boundRect(); if(!((tmpE1.right.x >= tmpE2.left.x) && (tmpE2.right.x >= tmpE1.left.x) && (tmpE1.right.y >= tmpE2.left.y) && (tmpE2.right.y >= tmpE1.left.y))) return FALSE; if( Straddle(tmpE1, tmpE2) && Straddle(tmpE2, tmpE1)) return TRUE; else return FALSE; } CPolygon::CPolygon(){} //constructor void CPolygon::create(vector<CPoint> vertices) //set vertices of polygon { m_Vertices=vertices; } CPolygon::~CPolygon(){} CEdge CPolygon::getEdge(int i) //return ith edge { CEdge tmp; tmp.create(m_Vertices[i],m_Vertices[i+1]); return tmp; } CPoint CPolygon::getVertex(int i) //return ith vertex { return m_Vertices[i]; } void CPolygon::erase(int i) //erases ith edge so that if polygon's // first vertex connected last // then we won't add new edges { int j; CPoint p; for(j=0; j<i; j++) // move edge before i to end of vertices { p= m_Vertices[0]; m_Vertices.erase(m_Vertices.begin()); m_Vertices.push_back(p); } m_Vertices.erase(m_Vertices.begin()); //erase i } int CPolygon::size() //return size of polygon { return m_Vertices.size(); } BOOL IntersectPolygon( CPoint off1, CPolygon p1, CPoint off2, CPolygon p2, vector<CPoint> *rem) // ck if two poly's+offset intersect after remove pts rem from 2nd one. { int i,j; if(rem) { for(i=0; i<(*rem).size(); i++) { for(j=0; j<p2.size(); j++) { if((*rem)[i]==off2+p2.getVertex(j)) p2.erase(j); } } } for(i=0; i<p1.size()-1; i++) { for(j=0; j<p2.size()-1; j++) { if(EdgeCross(off1, p1.getEdge(i), off2, p2.getEdge(j))) return TRUE; } } return FALSE; } |