The "dungeon" program is composed from several files: main.cp main program etc geom.h, geom.cp files with class Pt etc D.h, D.cp Dynamic array (needed by Window class) Dungeon.h, Dungeon.cp (the Dungeon stuff) Ditem.h, Ditem.cp the dungeon items class hierarchy WindowRep.h, WindowRep.cp - window display using cursor addressable terminal (on Borland this needs conio.h library for a DOS project, on Symantec its the console.h library; this copy has only the Symantec stuff. See the Hangman example from chapter 12 for the minor changes needed for Borland) there is also an example data file ===== main.cp ====== #include #include #include #include "WindowRep.h" #include "Dungeon.h" void Terminate(int status) { WindowRep::Instance()->CloseDown(); if(status == 0) cout << "\fYou Lost, better luck next time" << endl; else cout << "\fYou Won, congratulations. " "Now, what about those ideas for improving the program." << endl; } int main() { Dungeon *d; d = new Dungeon; cout << "Enter name of the file containing the spec. of the dungeon : "; char aName[100]; cin >> aName; d->Load(aName); int status = d->Run(); Terminate(status); return 0; } ====== geom.h ====== #ifndef __MYGEOM__ #define __MYGEOM__ class Pt { public: Pt(int x = 0, int y = 0); int X() const; int Y() const; void SetPt(int newx, int newy); void SetPt(const Pt& other); int Equals(const Pt& other) const; int Adjacent(const Pt& other) const; int Distance(const Pt& other) const; private: int fx; int fy; }; inline Pt::Pt(int x, int y) { fx = x; fy = y; } inline int Pt::X() const { return fx; } inline int Pt::Y() const { return fy; } inline int Pt::Equals(const Pt& other) const { return ((fx == other.fx) && (fy == other.fy)); } inline void Pt::SetPt(int newx, int newy) { fx = newx; fy = newy; } inline void Pt::SetPt(const Pt& other) { fx = other.fx; fy = other.fy; } #endif ====== geom.cp ====== #include #include #include "Geom.h" int Pt::Adjacent(const Pt& other) const { int dx = fx - other.fx; int dy = fy - other.fy; dx = abs(dx); dy = abs(dy); return ((dx <= 1) && (dy <= 1)); } int Pt::Distance(const Pt& other) const { int dx = fx - other.fx; int dy = fy - other.fy; long dsq = dx*dx + dy*dy; return ceil(sqrt(dsq)); } ======= D.h ==== #ifndef __MYDYNAM__ #define __MYDYNAM__ class DynamicArray { public: DynamicArray(int size = 10, int inc = 5); int Length(void) const; int Position(void *item) const; void *Nth(int n) const; void Append(void *item); void *Remove(void *item); void *Remove(int itempos); private: void Grow(int amount); int fNum; int fSize; int fCSize; int fInc; void **fItems; }; inline int DynamicArray::Length(void) const { return fNum; } #endif ==== D.cp ==== #include #include #include "D.h" const int kLARGE = 10000; const int kINCL = 5000; DynamicArray::DynamicArray(int size, int inc) { assert((size > 1) && (size < kLARGE)); assert((inc > 1) && (inc < kINCL)); fNum = 0; fCSize = fSize = size; fInc = inc; fItems = new void* [size]; } int DynamicArray::Position(void *item) const { for(int i = 0; i< fNum; i++) if(fItems[i] == item) return i+1; return 0; } void *DynamicArray::Nth(int n) const { if((n < 1) || (n > fNum)) return NULL; n--; return fItems[n]; } void DynamicArray::Append(void *item) { if(fNum == fCSize) Grow(fInc); fItems[fNum] = item; fNum++; } void DynamicArray::Grow(int delta) { int newsize = fCSize + delta; void **temp = new void* [newsize]; fCSize = newsize; for(int i=0;i < fNum; i++) temp[i] = fItems[i]; delete [] fItems; fItems = temp; } void *DynamicArray::Remove(void *item) { int where = Position(item); return Remove(where); } void *DynamicArray::Remove(int itempos) { if((itempos < 1) || (itempos > fNum)) return NULL; itempos--; void *tmp = fItems[itempos]; for(int i = itempos + 1; i < fNum; i++) fItems[i-1] = fItems[i]; fNum--; if((fNum > fSize) && (fNum < (fCSize / 2))) Grow(-fInc); return tmp; } ===== Dungeon.h ===== #ifndef __MYDUNGEON__ #define __MYDUNGEON__ #include #include "D.h" #include "Geom.h" #include "WindowRep.h" /* Dungeon size should be set smaller than the window size because we don't support scrolling. */ const int MAXHEIGHT = 20; const int MAXWIDTH = 75; class Player; class Monster; class Collectable; class Dungeon { public: Dungeon(); ~Dungeon(); void Load(const char filename[]); int Run(); int Accessible(Pt p) const; Window *Display(); Player *Human(); int ValidPoint(Pt p) const; Monster *M_at_Pt(Pt p); Collectable *PI_at_Pt(Pt p); void RemoveProp(Collectable *pi); void RemoveM(Monster *m); int ClearLineOfSight(Pt p1, Pt p2, int max, Pt path[]); char Ch(Pt p) { return fDRep[p.Y()-1][p.X()-1]; } private: int ClearRow(Pt p1, Pt p2, int max, Pt path[]); int ClearColumn(Pt p1, Pt p2, int max, Pt path[]); int ClearSemiVertical(Pt p1, Pt p2, int max, Pt path[]); int ClearSemiHorizontal(Pt p1, Pt p2, int max, Pt path[]); void LoadMap(ifstream& in); void PopulateDungeon(ifstream& in); void CreateWindow(); DynamicArray fProps; DynamicArray fInhabitants; Player *fPlayer; char fDRep[MAXHEIGHT][MAXWIDTH]; Window *fDWindow; int fHeight; int fWidth; }; #endif ==== Dungeon.cp ===== #include #include #include #include "Dungeon.h" #include "DItem.h" /* End of line character: supposed to be '\n', but the IDE may use '\r'. (Needed when reading data files). */ const int END_OF_LINE_CHAR = '\r'; Dungeon::Dungeon() { fDWindow = NULL; fPlayer = NULL; } Dungeon::~Dungeon() { if(fDWindow != NULL) delete fDWindow; } void Dungeon::Load(const char filename[]) { ifstream in(filename, ios::in | ios::nocreate); if(!in.good()) { cout << "File does not exist. Quitting." << endl; exit(1); } LoadMap(in); PopulateDungeon(in); in.close(); } void Dungeon::CreateWindow() { fDWindow = new Window(1, 1, fWidth, fHeight); for(int row = 1; row <= fHeight; row++) for(int col = 1; col <= fWidth; col++) fDWindow->SetBkgd(col, row, fDRep[row-1][col-1]); fDWindow->PrepareContent(); fDWindow->ShowAll(); } void Dungeon::LoadMap(ifstream& in) { /* Read in dungeon layout. */ in >> fWidth >> fHeight; in.ignore(100, END_OF_LINE_CHAR); for(int row = 1; row <= fHeight; row++ ) { char ch; for(int col = 1; col <= fWidth; col++) { in.get(ch); if((row<=MAXHEIGHT) && (col <= MAXWIDTH)) fDRep[row-1][col-1] = ch; } in.ignore(100, END_OF_LINE_CHAR); } if(!in.good()) { cout << "Sorry, problems reading that file. Quitting." << endl; exit(1); } cout << "Dungeon map read OK" << endl; if((fWidth > MAXWIDTH) || (fHeight > MAXHEIGHT)) { cout << "Map too large for window, only using part of map." << endl; fWidth = (fWidth < MAXWIDTH) ? fWidth : MAXWIDTH; fHeight = (fHeight < MAXHEIGHT) ? fHeight : MAXHEIGHT; } } void Dungeon::PopulateDungeon(ifstream& in) { // input of population // character (h = human, w = wanderer etc, q= no more input ) // then two integers giving location char ch; Monster *m; in >> ch; while(ch != 'q') { switch(ch) { case 'h': if(fPlayer != NULL) { cout << "Limit of one player violated." << endl; exit(1); } else { fPlayer = new Player(this); fPlayer->Read(in); } break; case 'w': m = new Wanderer(this); m->Read(in); fInhabitants.Append(m); break; case 'g': m = new Ghost(this); m->Read(in); fInhabitants.Append(m); break; case 'p': m = new Patrol(this); m->Read(in); fInhabitants.Append(m); break; case '*': case '=': case '$': Collectable *prop = new Collectable(this, ch); prop->Read(in); fProps.Append(prop); break; default: cout << "Unrecognizable data in input file." << endl; cout << "Symbol " << ch << endl; exit(1); } in >> ch; } if(fPlayer == NULL) { cout << "No player! No Game!" << endl; exit(1); } if(fProps.Length() == 0) { cout << "No items to collect! No Game!" << endl; exit(1); } cout << "Dungeon population read" << endl; } int Dungeon::Accessible(Pt p) const { return (' ' == fDRep[p.Y()-1][p.X()-1]); } Window *Dungeon::Display() { return fDWindow; } Player *Dungeon::Human() { return fPlayer; } int Dungeon::ValidPoint(Pt p) const { int x = p.X(); int y = p.Y(); // check x range if((x <= 1) || (x >= fWidth)) return 0; // check y range if((y <= 1) || (y >= fHeight)) return 0; // and accessiblity return Accessible(p); } Monster *Dungeon::M_at_Pt(Pt p) { int n = fInhabitants.Length(); for(int i=1; i<= n; i++) { Monster *m = (Monster*) fInhabitants.Nth(i); Pt w = m->Where(); if(w.Equals(p)) return m; } return NULL; } void Dungeon::RemoveM(Monster *m) { fInhabitants.Remove(m); m->Erase(); delete m; } Collectable *Dungeon::PI_at_Pt(Pt p) { int n = fProps.Length(); for(int i=1; i<= n; i++) { Collectable *pi = (Collectable*) fProps.Nth(i); Pt w = pi->Where(); if(w.Equals(p)) return pi; } return NULL; } void Dungeon::RemoveProp(Collectable *pi) { fProps.Remove(pi); pi->Erase(); delete pi; } int Dungeon::Run() { CreateWindow(); int n = fInhabitants.Length(); for(int i=1; i <= n; i++) { Monster *m = (Monster*) fInhabitants.Nth(i); m->Draw(); } fPlayer->Draw(); fPlayer->ShowStatus(); WindowRep::Instance()->Delay(1); while(fPlayer->Alive()) { for(int j=1; j <= fProps.Length(); j++) { Collectable *pi = (Collectable*) fProps.Nth(j); pi->Draw(); } fPlayer->Run(); if(fProps.Length() == 0) break; int n = fInhabitants.Length(); for(i=1; i<= n; i++) { Monster *m = (Monster*) fInhabitants.Nth(i); m->Run(); } } return fPlayer->Alive(); } int Dungeon::ClearRow(Pt p1, Pt p2, int max, Pt path[]) { int delta = (p1.X() < p2.X()) ? 1 : -1; int x = p1.X(); int y = p1.Y(); for(int i = 0; i < max; i++) { x += delta; Pt p(x,y); if(!Accessible(p)) return 0; path[i] = p; if(p.Equals(p2)) return 1; } return 0; } int Dungeon::ClearColumn(Pt p1, Pt p2, int max, Pt path[]) { int delta = (p1.Y() < p2.Y()) ? 1 : -1; int x = p1.X(); int y = p1.Y(); for(int i = 0; i < max; i++) { y += delta; Pt p(x,y); if(!Accessible(p)) return 0; path[i] = p; if(p.Equals(p2)) return 1; } return 0; } int Dungeon::ClearSemiVertical(Pt p1, Pt p2, int max, Pt path[]) { int ychange = p2.Y() - p1.Y(); if(abs(ychange) > max) return 0; int xchange = p2.X() - p1.X(); int deltax = (xchange > 0) ? 1 : -1; int deltay = (ychange > 0) ? 1 : -1; float slope = ((float)xchange)/((float)ychange); float error = slope*deltay; int x = p1.X(); int y = p1.Y(); for(int i=0;i0.5) { x += deltax; error -= deltax; } error += slope*deltay; y += deltay; Pt p(x, y); if(!Accessible(p)) return 0; path[i] = p; if(p.Equals(p2)) return 1; } return 0; } int Dungeon::ClearSemiHorizontal(Pt p1, Pt p2, int max, Pt path[]) { int ychange = p2.Y() - p1.Y(); int xchange = p2.X() - p1.X(); if(abs(xchange) > max) return 0; int deltax = (xchange > 0) ? 1 : -1; int deltay = (ychange > 0) ? 1 : -1; float slope = ((float)ychange)/((float)xchange); float error = slope*deltax; int x = p1.X(); int y = p1.Y(); for(int i=0;i0.5) { y += deltay; error -= deltay; } error += slope*deltax; x += deltax; Pt p(x, y); if(!Accessible(p)) return 0; path[i] = p; if(p.Equals(p2)) return 1; } return 0; } int Dungeon::ClearLineOfSight(Pt p1, Pt p2, int max, Pt path[]) { if(p1.Equals(p2)) return 0; if(!ValidPoint(p1)) return 0; if(!ValidPoint(p2)) return 0; if(p1.Y() == p2.Y()) return ClearRow(p1, p2, max, path); else if(p1.X() == p2.X()) return ClearColumn(p1, p2, max, path); int dx = p1.X() - p2.X(); int dy = p1.Y() - p2.Y(); if(abs(dx) >= abs(dy)) return ClearSemiHorizontal(p1, p2, max, path); else return ClearSemiVertical(p1, p2, max, path); } ======== Ditem.h ======== #ifndef __MYMONSTER__ #define __MYMONSTER__ #ifndef __MYGEOM__ #include "Geom.h" #endif class Dungeon; class NumberItem; class EditText; class ifstream; class DungeonItem { public: DungeonItem(Dungeon *d, char sym); virtual ~DungeonItem(); Pt Where() const; virtual void Draw(); virtual void Read(ifstream& in); virtual void Erase(); protected: Dungeon *fD; Pt fPos; char fSym; }; class Collectable : public DungeonItem { public: Collectable(Dungeon* d, char sym); int Hlth(); int Wlth(); int Manna(); virtual void Read(ifstream& in); private: int fHval; int fWval; int fMval; }; class ActiveItem : public DungeonItem { public: ActiveItem(Dungeon *d, char sym); virtual void Read(ifstream& in); virtual void Run() = 0; virtual void GetHit(int damage); virtual int Alive() const; protected: virtual void Move(const Pt& newpoint); Pt Step(int dir); int fHealth; int fStrength; }; class Monster : public ActiveItem { public: Monster(Dungeon* d, char sym); virtual ~Monster(); virtual void Run(); protected: virtual int CanAttack(); virtual void Attack(); virtual int CanDetect(); virtual void Advance(); virtual void NormalMove() { } }; class Player : public ActiveItem { public: Player(Dungeon* d); virtual void Run(); virtual void Read(ifstream& in); void ShowStatus(); private: void TryMove(int newx, int newy); void Attack(Monster *m); void Take(Collectable *pi); void UpdateState(); char GetUserCommand(); void PerformMovementCommand(char ch); void PerformMagicCommand(char ch); int fMoveCount; int fWealth; int fManna; NumberItem *fWinH; NumberItem *fWinW; NumberItem *fWinM; EditText *fWinE; }; class Wanderer : public Monster { public: Wanderer(Dungeon *d); protected: virtual void NormalMove(); virtual int CanDetect(); virtual void Advance(); int fLastX; int fLastY; Pt fPath[20]; }; class Ghost : public Monster { public: Ghost(Dungeon *d); protected: virtual int CanDetect(); virtual void Advance(); }; class Patrol : public Monster { public: Patrol(Dungeon *d); virtual void Read(ifstream& in); protected: virtual void NormalMove(); virtual int CanDetect(); virtual void Advance(); Pt fPath[20]; Pt fRoute[100]; int fRouteLen; int fNdx; int fDelta; }; #endif ====== #include #include #include #include #include "DItem.h" #include "WindowRep.h" #include "Dungeon.h" DungeonItem::DungeonItem(Dungeon *d, char sym) { fSym = sym; fD = d; } DungeonItem::~DungeonItem() { } void DungeonItem::Erase() { fD->Display()->Clear(fPos.X(), fPos.Y()); } void DungeonItem::Draw() { fD->Display()->Set( fPos.X(), fPos.Y(), fSym); } Pt DungeonItem::Where() const { return fPos; } void DungeonItem::Read(ifstream& in) { int x, y; in >> x >> y; if(!in.good()) { cout << "Problems reading coordinate data" << endl; exit(1); } if(!fD->ValidPoint(Pt(x,y))) { cout << "Invalid coords, out of range or already occupied" << endl; cout << "(" << x << ", " << y << ")" << endl; exit(1); } fPos.SetPt(x,y); } Collectable::Collectable(Dungeon* d, char sym) : DungeonItem(d, sym) { fHval = fWval = fMval = 0; } int Collectable::Hlth() { return fHval; } int Collectable::Wlth() { return fWval; } int Collectable::Manna() { return fMval; } void Collectable::Read(ifstream& in) { DungeonItem::Read(in); in >> fHval >> fWval >> fMval; if(!in.good()) { cout << "Problem reading a property" << endl; exit(1); } } ActiveItem::ActiveItem(Dungeon *d, char sym) : DungeonItem(d, sym) { fHealth = fStrength = 0; } void ActiveItem::Read(ifstream& in) { DungeonItem::Read(in); in >> fHealth >> fStrength; } void ActiveItem::GetHit(int damage) { fHealth -= damage; } int ActiveItem::Alive() const { return fHealth > 0; } void ActiveItem::Move(const Pt& newpoint) { Erase(); fPos.SetPt(newpoint); Draw(); } Pt ActiveItem::Step(int dir) { Pt p; switch(dir) { case 1: p.SetPt(-1,1); break; case 2: p.SetPt(0,1); break; case 3: p.SetPt(1,1); break; case 4: p.SetPt(-1,0); break; case 6: p.SetPt(1,0); break; case 7: p.SetPt(-1,-1); break; case 8: p.SetPt(0,-1); break; case 9: p.SetPt(1,-1); break; } return p; } Monster::Monster(Dungeon *d, char sym) : ActiveItem(d, sym) { } Monster::~Monster() { } void Monster::Run() { if(CanAttack()) Attack(); else if(CanDetect()) Advance(); else NormalMove(); } int Monster::CanAttack() { Player *p = fD->Human(); Pt target = p->Where(); return fPos.Adjacent(target); } void Monster::Attack() { Player *p = fD->Human(); p->GetHit(fStrength); } int Monster::CanDetect() { return 0; } void Monster::Advance() { } Player::Player(Dungeon *d) : ActiveItem(d, 'h') { fWealth = 0; fHealth = 0; fManna = 0; fMoveCount = 0; fWinH = NULL; fWinM = NULL; fWinW = NULL; } void Player::Read(ifstream& in) { ActiveItem::Read(in); in >> fManna; } void Player::ShowStatus() { if(fWinH == NULL) { fWinH = new NumberItem(2, 20, 20, "Health", fHealth); fWinM = new NumberItem(30,20, 20, "Manna ", fManna); fWinW = new NumberItem(58,20, 20, "Wealth", fWealth); fWinE = new EditText(2, 22, 20, "Direction", 1); fWinH->ShowAll(); fWinM->ShowAll(); fWinW->ShowAll(); fWinE->ShowAll(); } else { if(fHealth != fWinH->GetVal()) fWinH->SetVal(fHealth); if(fManna != fWinM->GetVal()) fWinM->SetVal(fManna); if(fWealth != fWinW->GetVal()) fWinW->SetVal(fWealth); } } void Player::Run() { char ch = GetUserCommand(); if(isdigit(ch)) PerformMovementCommand(ch); else PerformMagicCommand(ch); UpdateState(); ShowStatus(); } void Player::UpdateState() { fMoveCount++; if(0 == (fMoveCount % 3)) fHealth++; if(0 == (fMoveCount % 7)) fManna++; } void Player::PerformMovementCommand(char ch) { int x = fPos.X(); int y = fPos.Y(); Pt p = Step(ch - '0'); int newx = x + p.X(); int newy = y + p.Y(); Collectable *pi = fD->PI_at_Pt(Pt(newx, newy)); if(pi != NULL) Take(pi); Monster *m = fD->M_at_Pt(Pt(newx, newy)); if(m != NULL) { Attack(m); return; } TryMove(x + p.X(), y + p.Y()); } void Player::PerformMagicCommand(char ch) { int dx, dy; switch (ch) { case 'q': dx = -1; dy = -1; break; case 'w': dx = 0; dy = -1; break; case 'e': dx = 1; dy = -1; break; case 'a': dx = -1; dy = 0; break; case 'd': dx = 1; dy = 0; break; case 'z': dx = -1; dy = 1; break; case 'x': dx = 0; dy = 1; break; case 'c': dx = 1; dy = 1; break; default: return; } int x = fPos.X(); int y = fPos.Y(); int power = 8; fManna -= power; if(fManna < 0) { fHealth += 2*fManna; fManna = 0; } while(power > 0) { x += dx; y += dy; if(!fD->ValidPoint(Pt(x,y))) return; Monster* m = fD->M_at_Pt(Pt(x,y)); if(m != NULL) { m->GetHit(power); if(!m->Alive()) fD->RemoveM(m); } power /= 2; } } void Player::Take(Collectable* pi) { fHealth += pi->Hlth(); fWealth += pi->Wlth(); fManna += pi->Manna(); fD->RemoveProp(pi); } void Player::Attack(Monster *m) { m->GetHit(fStrength); if(!m->Alive()) fD->RemoveM(m); } char Player::GetUserCommand() { fWinE->GetInput(); char *str = fWinE->GetVal(); return *str; } void Player::TryMove(int newx, int newy) { if(!fD->Accessible(Pt(newx,newy))) return; Move(Pt(newx,newy)); } Wanderer::Wanderer(Dungeon *d) : Monster(d, 'w') { fLastX = fLastY = 0; } void Wanderer::NormalMove() { int x = fPos.X(); int y = fPos.Y(); // Try to keep going in much the same direction as last time if((fLastX != 0) || (fLastY != 0)) { int newx = x + fLastX; int newy = y + fLastY; if(fD->Accessible(Pt(newx,newy))) { Move(Pt(newx,newy)); return; } else if(fD->Accessible(Pt(newx,y))) { Move(Pt(newx,y)); fLastY = 0; return; } else if(fD->Accessible(Pt(x,newy))) { Move(Pt(x,newy)); fLastX= 0; return; } } int dir = rand(); dir = dir % 9; dir++; Pt p = Step(dir); x += p.X(); y += p.Y(); if(fD->Accessible(Pt(x,y))) { fLastX = p.X(); fLastY = p.Y(); Move(Pt(x,y)); } } int Wanderer::CanDetect() { Player *p = fD->Human(); return fD->ClearLineOfSight(fPos, p->Where(), 10, fPath); } void Wanderer::Advance() { Move(fPath[0]); } Ghost::Ghost(Dungeon *d) : Monster(d, 'g') { } int Ghost::CanDetect() { Player *p = fD->Human(); int range = fPos.Distance(p->Where()); return (range < 7); } void Ghost::Advance() { Player *p = fD->Human(); Pt p1 = p->Where(); int dx, dy; dx = dy = 0; if(p1.X() > fPos.X()) dx = 1; else if(p1.X() < fPos.X()) dx = -1; if(p1.Y() > fPos.Y()) dy = 1; else if(p1.Y() < fPos.Y()) dy = -1; Move(Pt(fPos.X() + dx, fPos.Y() + dy)); } Patrol::Patrol(Dungeon *d) : Monster(d, 'p') { } void Patrol::NormalMove() { if((fNdx == 0) && (fDelta == -1)) { fDelta = 1; return; } if((fNdx == fRouteLen) && (fDelta == 1)) { fDelta = -1; return; } fNdx += fDelta; Move(fRoute[fNdx]); } void Patrol::Read(ifstream& in) { Monster::Read(in); fRoute[0] = fPos; fNdx = 0; fDelta = 1; in >> fRouteLen; for(int i=1; i<= fRouteLen; i++) { int x, y; in >> x >> y; Pt p(x, y); if(!fD->ValidPoint(p)) { cout << "Bad data in patrol route" << endl; cout << "(" << x << ", " << y << ")" << endl; exit(1); } if(!p.Adjacent(fRoute[i-1])) { cout << "Non adjacent points in patrol route" << endl; cout << "(" << x << ", " << y << ")" << endl; exit(1); } fRoute[i] = p; } if(!in.good()) { cout << "Problems reading patrol route" << endl; exit(1); } } int Patrol::CanDetect() { Player *p = fD->Human(); return fD->ClearLineOfSight(fPos, p->Where(), 10, fPath); } void Patrol::Advance() { Player *p = fD->Human(); Pt target = p->Where(); Pt arrow = fPath[0]; int i = 1; while(!arrow.Equals(target)) { fD->Display()->Set( arrow.X(), arrow.Y(), ':'); WindowRep::Instance()->Delay(1); fD->Display()->Clear( arrow.X(), arrow.Y()); arrow = fPath[i]; i++; } p->GetHit(2); } ========== WindowRep.h ======== #ifndef __MYWINDOWREP__ #define __MYWINDOWREP__ const int CG_WIDTH = 78; const int CG_HEIGHT = 24; class WindowRep { public: static WindowRep *Instance(); void CloseDown(); void PutCharacter(char ch, int x, int y); void Clear(); void Delay(int seconds) const; char GetChar(); void MoveCursor(int x, int y); private: WindowRep(); void Initialize(); void PutCharacter(char ch); static WindowRep *sWindowRep; char fImage[CG_HEIGHT][CG_WIDTH]; }; class Window { public: Window(int x, int y, int width, int height, char bkgd = ' ', int framed = 1); virtual ~Window(); void Clear(int x, int y); void Set(int x, int y, char ch); void SetBkgd(int x, int y, char ch); char Ch(int x, int y) const; int X() const; int Y() const; int Width() const; int Height() const; void ShowAll() const; void ShowContent() const; void PrepareContent(); protected: void Change(int x, int y, char ch, char **img); int Valid(int x, int y) const; char Get(int x, int y, char **img) const; void SetFrame(); char **fBkgd; char **fCurrentImg; int fX; int fY; int fWidth; int fHeight; int fFramed; }; inline int Window::Valid(int x, int y) const { return ((x>0) && (x<= fWidth) && (y>0) &&(y<=fHeight)); } class NumberItem : public Window { public: NumberItem(int x, int y, int width, char *label, long initval = 0); void SetVal(long newVal); long GetVal() { return fVal; } private: void SetLabel(int s, char*); void ShowValue(); long fVal; int fLabelWidth; }; class EditText: public Window { public: EditText(int x, int y, int width, char *label, short size); void SetVal(char*); char* GetVal() { return fBuf; } char GetInput(); private: void SetLabel(int s, char*); void ShowValue(); int fLabelWidth; char fBuf[256]; int fSize; int fEntry; }; #endif ===== WindowRep.cp ====== #include #include #include #define SYMANTEC #if defined(SYMANTEC) /* stdio is needed for fputc etc; console is Symantec's set of functions like gotoxy unix for sleep function */ #include #include #include #endif #if defined(DOS) /* conio has Borland's cursor graphics primitives dos needed for sleep function */ #include #include #endif #if defined(EASYWIN) /* Still need conio, but can't use sleep, achieve delay by alternative mechanism */ #include #endif #ifndef __MYWINDOWREP__ #include "WindowRep.h" #endif WindowRep *WindowRep::sWindowRep = NULL; WindowRep *WindowRep::Instance() { if(sWindowRep == NULL) sWindowRep = new WindowRep; return sWindowRep; } WindowRep::WindowRep() { Initialize(); for(int row = 0; row < CG_HEIGHT; row++) for(int col = 0; col< CG_WIDTH; col++) fImage[row][col] = ' '; Clear(); } void WindowRep::Initialize() { #if defined(SYMANTEC) /* Have to change the "mode" for the 'console' screen. Putting it in C_CBREAK allows characters to be read one by one as they are typed */ csetmode(C_CBREAK, stdin); #else /* No special initializations are needed for Borland environments */ #endif } void WindowRep::CloseDown() { Clear(); #if defined(SYMANTEC) csetmode(C_ECHO, stdin); #endif sWindowRep = NULL; Delay(2); delete this; } void WindowRep::MoveCursor(int x, int y) { if((x<1) || (x>CG_WIDTH)) return; if((y<1) || (y>CG_HEIGHT)) return; #if defined(SYMANTEC) cgotoxy(x,y,stdout); #else gotoxy(x,y); #endif } void WindowRep::PutCharacter(char ch) { #if defined(SYMANTEC) fputc(ch, stdout); fflush(stdout); #elif putch(ch); #endif } void WindowRep::PutCharacter(char ch, int x, int y) { if((x<1) || (x>CG_WIDTH)) return; if((y<1) || (y>CG_HEIGHT)) return; if(ch != fImage[y-1][x-1]) { MoveCursor(x,y); PutCharacter(ch); fImage[y-1][x-1] = ch; } } void WindowRep::Clear() { for(int y=1;y<=CG_HEIGHT;y++) for(int x=1; x<=CG_WIDTH;x++) { MoveCursor(x, y); PutCharacter(' '); } } void WindowRep::Delay(int seconds) const { #if defined(EASYWIN) /* The EasyWin environment does not allow use of dos's sleep() function. So here do a "computational delay" The value 5000 will have to be adjusted to suit machine */ const long fudgefactor = 5000; double x; long lim = seconds*fudgefactor; while(lim>0) { x = 1.0/lim; lim--; } #else sleep(seconds); #endif } char WindowRep::GetChar() { #if defined(SYMANTEC) return fgetc(stdin); #elif return getche(); #endif } Window::Window(int x, int y, int width, int height, char bkgd, int framed ) { fX = x-1; fY = y-1; fWidth = width; fHeight = height; fFramed = framed; fBkgd = new char* [height]; fCurrentImg = new char* [height]; for(int row = 0; row < height; row++) { fBkgd[row] = new char[width]; fCurrentImg[row] = new char[width]; for(int col = 0; col < width; col++) fBkgd[row][col] = bkgd; } } Window::~Window() { for(int row = 0; row < fHeight; row++) { delete [] fCurrentImg[row]; delete [] fBkgd[row]; } delete [] fCurrentImg; delete [] fBkgd; } void Window::Clear(int x, int y) { if(Valid(x,y)) Change(x,y,Get(x,y,fBkgd), fCurrentImg); } void Window::Set(int x, int y, char ch) { if(Valid(x,y)) Change(x, y, ch, fCurrentImg); } void Window::SetBkgd(int x, int y, char ch) { if(Valid(x,y)) Change(x, y, ch, fBkgd); } char Window::Ch(int x, int y) const { if(Valid(x,y)) return Get(x, y, fCurrentImg); else return '\0'; } void Window::PrepareContent() { for(int row = 0; row < fHeight; row++) for(int col = 0; col < fWidth; col++) fCurrentImg[row][col] = fBkgd[row][col]; if(fFramed) SetFrame(); } char Window::Get(int x, int y, char **img) const { x--; y--; return img[y][x]; } void Window::SetFrame() { for(int x=1; xPutCharacter(fCurrentImg[row-1][col-1], fX+col, fY+row); } void Window::ShowContent() const { for(int row=2;rowPutCharacter(fCurrentImg[row-1][col-1], fX+col, fY+row); } void Window::Change(int x, int y, char ch, char** img) { if(fFramed) { if((x == 1) || (x == fWidth)) return; if((y == 1) || (y == fHeight)) return; } WindowRep::Instance()->PutCharacter(ch, x + fX, y + fY); x--; y--; img[y][x] = ch; } NumberItem::NumberItem(int x, int y, int width, char *label, long initval) : Window(x, y, width, 3) { fVal = initval; fLabelWidth = 0; int s = strlen(label); if((s > 0) && (s < (width-5))) SetLabel(s, label); PrepareContent(); ShowValue(); } void NumberItem::SetVal(long newVal) { fVal = newVal; ShowValue(); } void NumberItem::SetLabel(int s, char * l) { fLabelWidth = s; for(int i=0; i< s; i++) fBkgd[1][i+1] = l[i]; } void NumberItem::ShowValue() { int left = 2 + fLabelWidth; int pos = fWidth - 1; long val = fVal; for(int i = left; i<= pos; i++) fCurrentImg[1][i-1] = ' '; if(val<0) val = -val; if(val == 0) fCurrentImg[1][pos-1] = '0'; while(val > 0) { int d = val % 10; val = val / 10; char ch = d + '0'; fCurrentImg[1][pos-1] = ch; pos--; if(pos <= left) break; } if(pos<=left) for(i=left; i 0) && (s < (width-8))) SetLabel(s, label); PrepareContent(); fBuf[0] = '\0'; ShowValue(); } void EditText::SetVal(char* val) { int n = strlen(val); if(n>254) n = 254; strncpy(fBuf,val,n); fBuf[n] = '\0'; ShowValue(); } void EditText::SetLabel(int s, char * l) { fLabelWidth = s; for(int i=0; i< s; i++) fBkgd[1][i+1] = l[i]; } void EditText::ShowValue() { int left = 4 + fLabelWidth; int i,j; for(i=left; iMoveCursor(fX+left, fY+2); char ch = WindowRep::Instance()->GetChar(); while(isalnum(ch)) { fBuf[fEntry] = ch; fEntry++; if(fEntry == fSize) { ch = '\0'; break; } char ch = WindowRep::Instance()->GetChar(); } fBuf[fEntry] = '\0'; return ch; } ========== 71 20 +----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | # # # # # # | | ######## # # ####### # ######## # ####### ####### | | # # ######## # # # #### # # # # # # # # | | # ###### # # # # # # # # # # # # # # # # | + # # # # # # # # # # # # ########## ### # # + | # # #### # # ###### ####### # # # # #### # # # # | | # # # # # # # # ## # # # # ##### ##| | # # # # # # # ########### # # # ## ############ ##### # # | | # # # # # # # # # # # # # # # # ##### | + # # #### # # ### ####### # # ######### # # # # # + | # # # # # # # ##### # ####### # # # # # ### | | # ########## # # # # # # # # # # # # # | |#### # # # # # # # # # # # # # # | | ###### # ##### # # ####### # # # # # # | + # # # ################# # # # # # # + | # # # # # # # # # # | | # # # # # # ### | | # #### # # ######## # | +---------------------------------------------------------------------+ h 30 18 100 5 1 p 43 14 30 10 17 44 15 45 14 46 14 47 14 48 14 49 14 50 14 51 14 52 14 53 13 53 12 53 11 53 10 54 9 54 8 55 8 56 8 w 21 19 10 5 w 2 2 10 5 w 16 3 10 5 w 18 6 10 5 w 60 5 10 5 g 40 14 40 1 g 50 17 40 1 * 60 2 0 0 5 = 56 13 10 0 0 $ 26 6 0 30 0 q