diff --git a/asteroids/AsteroidsEnums.h b/asteroids/AsteroidsEnums.h index 251adf9..e8107c8 100644 --- a/asteroids/AsteroidsEnums.h +++ b/asteroids/AsteroidsEnums.h @@ -26,6 +26,7 @@ BEGIN_ENUM(GameState) DECL_ENUM_ELEMENT(GameStatePaused), DECL_ENUM_ELEMENT(GameStatePlayerDied), DECL_ENUM_ELEMENT(GameStateLevelComplete), + DECL_ENUM_ELEMENT(GameStateShowHighscore), DECL_ENUM_ELEMENT(GameStateGameOver) } END_ENUM(GameState) diff --git a/asteroids/AsteroidsEvents.h b/asteroids/AsteroidsEvents.h index a25531c..3c8e177 100644 --- a/asteroids/AsteroidsEvents.h +++ b/asteroids/AsteroidsEvents.h @@ -1,3 +1,6 @@ +#ifndef ASTEROIDSEVENTS_H +#define ASTEROIDSEVENTS_H + #include "EnumToString.h" namespace asteroids { @@ -7,9 +10,12 @@ BEGIN_ENUM(Event) DECL_ENUM_ELEMENT(EventAccelerateStart), DECL_ENUM_ELEMENT(EventAccelerateStop), DECL_ENUM_ELEMENT(EventLevelComplete), + DECL_ENUM_ELEMENT(EventGameOver), DECL_ENUM_ELEMENT(EventShipExplode) } END_ENUM(Event) } +#endif /* ASTEROIDSEVENTS_H */ + diff --git a/asteroids/MenuOverlay.h b/asteroids/MenuOverlay.h deleted file mode 100644 index ce8e4f0..0000000 --- a/asteroids/MenuOverlay.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef MENUOVERLAY -#define MENUOVERLAY - -namespace Engine { -class OverlayBase; -} - -#include "OverlayBase.h" -#include "Sprite.h" - -namespace asteroids { - -class Model; -class View; - -class MenuOverlay : public Engine::OverlayBase { - public: - MenuOverlay () { - }; - virtual void Init (); - virtual ~MenuOverlay() {}; - - virtual bool OnKeyDown (const SDL_keysym &keysym); - virtual void Draw (); - - void DrawGameOverScreen (); - void DrawGameMenu(); - void DrawGameLevelComplete (); - void DrawGamePaused (); - void DrawGameRunning (); - void DrawPlayerDied (); - - void SetModel (Model *model) { mModel = model; }; - void SetView (View *view) { mView = view; }; - - private: - Model *mModel; - View *mView; - Engine::Sprite mShipSprite; -}; - -} - -#endif /* MENUOVERLAY */ diff --git a/asteroids/Model.cc b/asteroids/Model.cc index 95fa5df..a6500a5 100644 --- a/asteroids/Model.cc +++ b/asteroids/Model.cc @@ -23,6 +23,8 @@ static Model* ModelInstance = NULL; int Model::OnInit (int argc, char* argv[]) { int result = Engine::ModelBase::OnInit (argc, argv); + Engine::LogMessage ("Model Initialization!"); + ModelInstance = this; mGameState = GameStateMainMenu; @@ -30,14 +32,38 @@ int Model::OnInit (int argc, char* argv[]) { /// \TODO use or similar for initialization of mCurrentLevelIndex mCurrentLevelIndex = 99999; + mNewestHighscoreEntryIndex = 99999; if (InitLevelList() == 0) Engine::LogError ("No levels found!"); + // First we reset the highscore list + mHighscoreList.clear(); + // then we try to load values from the file + LoadHighscoreList(); + + // if we have less than the usual number of entries we add default values + if (mHighscoreList.size() < 10) { + AddHighscoreEntry ("Imperator", 1000000); + AddHighscoreEntry ("Darth Vader", 800000); + AddHighscoreEntry ("Luke Skywalker", 600000); + AddHighscoreEntry ("Han Solo", 400000); + AddHighscoreEntry ("Princess Leia", 200000); + AddHighscoreEntry ("C3PO", 100000); + AddHighscoreEntry ("R2-D2", 50000); + AddHighscoreEntry ("Chewy", 10000); + AddHighscoreEntry ("Mr. Ewok", 5000); + AddHighscoreEntry ("Jabba the Hutt", 1000); + } + + // initialize event handlers and register them mLevelCompleteEventHandler = new LevelCompleteEventHandler (this); Engine::RegisterListener (mLevelCompleteEventHandler, EventLevelComplete); - Engine::LogMessage ("Model Initialization!"); + mGameOverEventHandler = new GameOverEventHandler (this); + Engine::RegisterListener (mGameOverEventHandler, EventGameOver); + + mPlayerName = "Player"; return result; } @@ -55,18 +81,19 @@ void Model::Process () { Engine::LogDebug ("Switching from %s->%s", GetStringGameState(mLastGameState), GetStringGameState(mGameState)); if (mLastGameState == GameStateMainMenu && mGameState == GameStateRunning) { - mPlayerLives = 3; - mCurrentLevelIndex = 0; - mPoints = 0; - DoLoadLevel (mLevelList[mCurrentLevelIndex].c_str()); + OnNewGame(); } else if (mLastGameState == GameStateRunning && mGameState == GameStatePlayerDied) { mPlayerLives --; ClearEntities(); - if (mPlayerLives == 0) - mGameState = GameStateGameOver; + if (mPlayerLives == 0) { + Engine::EventBasePtr gameover_event (new Engine::EventBase()); + gameover_event->mEventType = EventGameOver; + QueueEvent (gameover_event); + //mGameState = GameStateGameOver; + } } else if (mLastGameState == GameStateLevelComplete && mGameState == GameStateRunning) { mCurrentLevelIndex++; @@ -82,14 +109,11 @@ void Model::Process () { ClearEntities(); // ... and we have to set the last game state to the current gamestate - // otherwise we end up in an infinit loop of performing the switching + // otherwise we end up in an infinite loop of performing the switching // action. mLastGameState = mGameState; } -void DoLoadLevel (unsigned int level_index) { -} - unsigned int Model::InitLevelList () { const char* level_dir_name = "./data/levels/"; Engine::LogDebug ("Searching for levels in %s", level_dir_name); @@ -123,6 +147,97 @@ unsigned int Model::InitLevelList () { return mLevelList.size(); } +void Model::LoadHighscoreList () { + Engine::LogDebug ("Loading highscore file"); + boost::filesystem::path highscore_file("./highscore.dat"); + + // if the file does not exist, we create it and write standard values into + // it. + if (!boost::filesystem::exists(highscore_file)) + return; + + if (!boost::filesystem::is_regular_file(highscore_file)) { + Engine::LogError ("Could not load highscore file: %s is not a regular file!", highscore_file.filename().c_str()); + } + + std::ifstream score_stream (highscore_file.filename().c_str()); + while (!score_stream.eof()) { + std::string name; + unsigned int points; + + std::string line; + getline (score_stream, line); + + std::string::size_type delimiter = line.find ('\t'); + if (delimiter == std::string::npos) + break; + + name = line.substr(0, delimiter); + + std::istringstream points_stream(line.substr(delimiter + 1, line.size())); + points_stream >> points; + + Engine::LogDebug ("Read Highscore Entry Name: %s Points: %d", name.c_str(), points); + AddHighscoreEntry (name, points); + } +} + +void Model::SaveHighscoreList () { + std::ofstream highscore_file ("./highscore.dat"); + + std::list::iterator iter = mHighscoreList.begin(); + + while (iter != mHighscoreList.end()) { + highscore_file << iter->name << "\t" << iter->points << std::endl; + iter++; + } + + highscore_file.close(); +} + +bool highscore_cmp (Model::HighscoreEntry a, Model::HighscoreEntry b) { + return a.points > b.points; +} + +/** \brief Addes an entry to the highscore list and takes care that the list stays valid + * + * \TODO Re-think usage of mNewestHighscoreEntryIndex variable in this function + */ +unsigned int Model::AddHighscoreEntry(const std::string &name, const unsigned int points) { + HighscoreEntry entry; + entry.name = name; + entry.points = points; + + unsigned int counter = 0; + std::list::iterator iter = mHighscoreList.begin(); + while (iter != mHighscoreList.end()) { + if (points >= iter->points) { + mHighscoreList.insert (iter, entry); + mNewestHighscoreEntryIndex = counter; + break; + } + iter++; + counter ++; + } + + if (mHighscoreList.size() < 10) { + mHighscoreList.push_back(entry); + mNewestHighscoreEntryIndex = mHighscoreList.size(); + return mHighscoreList.size(); + } + + while (mHighscoreList.size() > 10) { + mHighscoreList.pop_back(); + } + + if (counter < 10) + return counter; + + mNewestHighscoreEntryIndex = 99999; + + return 99999; +} + int Model::DoLoadLevel (const char* filename) { Engine::LogMessage ("Loading level from %s", filename); std::fstream level_file (filename, std::ios::in); @@ -235,6 +350,27 @@ bool Model::OnLevelComplete() { } else { SetGameState (GameStateLevelComplete); } + + return true; +} + +bool Model::OnGameOver() { + Engine::LogMessage ("Points = %d lowest = %d", mPoints,mHighscoreList.back().points ); + if (mPoints > mHighscoreList.back().points) { + Engine::LogMessage ("New Highscore!"); + AddHighscoreEntry (mPlayerName, mPoints); + } + SetGameState(GameStateGameOver); + + return false; +}; + +void Model::OnNewGame() { + mNewestHighscoreEntryIndex = 99999; + mPlayerLives = 1; + mCurrentLevelIndex = 0; + mPoints = 0; + DoLoadLevel (mLevelList[mCurrentLevelIndex].c_str()); } void Model::OnCreateEntity (const int type, const unsigned int id) { @@ -265,7 +401,6 @@ void Model::OnKillEntity (const Engine::EntityBase *entity) { Engine::EventBasePtr level_complete_event (new Engine::EventBase()); level_complete_event->mEventType = EventLevelComplete; TriggerEvent (level_complete_event); -// SetGameState (GameStateLevelComplete); } } } diff --git a/asteroids/Model.h b/asteroids/Model.h index 827f9a6..85c7ed8 100644 --- a/asteroids/Model.h +++ b/asteroids/Model.h @@ -13,13 +13,35 @@ class Model : public Engine::ModelBase { int DoLoadLevel (const char* filename); int DoSaveLevel (const char* filename); bool OnLevelComplete(); + bool OnGameOver(); + /** \brief Resets values from a previous game */ + void OnNewGame (); int GetPlayerLives () { return mPlayerLives; }; unsigned int GetPoints () { return mPoints; }; + std::string GetPlayerName() { return mPlayerName; }; + void SetPlayerName(const std::string &name) { mPlayerName = name; }; float GetWorldWidth (); float GetWorldHeight (); + struct HighscoreEntry { + HighscoreEntry(): name ("unknown"), points (0) { }; + HighscoreEntry (const char *e_name, const unsigned int e_points): + name (e_name), points (e_points) { }; + + std::string name; + unsigned int points; + }; + + /* Highscore */ + void LoadHighscoreList (); + void SaveHighscoreList (); + unsigned int AddHighscoreEntry(const std::string &name, const unsigned int points); + bool HighscoreCmp (HighscoreEntry a, HighscoreEntry b); + std::list mHighscoreList; + unsigned int mNewestHighscoreEntryIndex; + protected: /** \brief Initializes the system */ virtual int OnInit (int argc, char* argv[]); @@ -27,7 +49,9 @@ class Model : public Engine::ModelBase { Engine::ModelBase::OnDestroy(); mAsteroids.clear(); mLevelList.clear(); + SaveHighscoreList(); delete mLevelCompleteEventHandler; + delete mGameOverEventHandler; }; virtual void OnRegisterCommands (); @@ -42,8 +66,9 @@ class Model : public Engine::ModelBase { int mPlayerLives; unsigned int mPoints; - std::vector mLevelList; unsigned int mCurrentLevelIndex; + std::string mPlayerName; + std::vector mLevelList; /* event handler class definitions */ class LevelCompleteEventHandler : public Engine::EventListenerBase { @@ -57,8 +82,20 @@ class Model : public Engine::ModelBase { Model *mModel; }; + class GameOverEventHandler : public Engine::EventListenerBase { + public: + explicit GameOverEventHandler (Model *view) : mModel (view) {}; + virtual bool HandleEvent (const Engine::EventBasePtr &event) const { + return mModel->OnGameOver(); + } + private: + GameOverEventHandler() {}; + Model *mModel; + }; + /* event handler member variables */ LevelCompleteEventHandler *mLevelCompleteEventHandler; + GameOverEventHandler *mGameOverEventHandler; friend class View; }; diff --git a/asteroids/UserInterface.cc b/asteroids/UserInterface.cc index c303118..191a067 100644 --- a/asteroids/UserInterface.cc +++ b/asteroids/UserInterface.cc @@ -37,6 +37,9 @@ bool MainMenuOverlay::OnKeyDown (const SDL_keysym &keysym) { case SDLK_RETURN: GetModel()->SetGameState(GameStateRunning); return true; + case SDLK_h: + GetModel()->SetGameState(GameStateShowHighscore); + return true; default: return true; } @@ -408,4 +411,124 @@ void PlayerDiedOverlay::Draw () { }; +/********************** + * + * Highscore + * + **********************/ + +void HighscoreOverlay::Init () { +} + +bool HighscoreOverlay::OnKeyDown (const SDL_keysym &keysym) { + switch (keysym.sym) { + case SDLK_ESCAPE: + case SDLK_RETURN: + // If we just entered a new entry we simply show the highscore table, + // otherwise we switch back to the main menu + if (GetModel()->mNewestHighscoreEntryIndex < GetModel()->mHighscoreList.size()) { + GetModel()->mNewestHighscoreEntryIndex = GetModel()->mHighscoreList.size(); + SDL_EnableUNICODE(-1); + SDL_EnableKeyRepeat(0,100); + + return true; + } + GetModel()->SetGameState(GameStateMainMenu); + + return true; + default: + break; + } + return true; +} + +void HighscoreOverlay::Draw () { + glClearColor (0., 0., 0., 1.); + + right = static_cast (Engine::GetWindowWidth()); + bottom = static_cast (Engine::GetWindowHeight()); + + // we switch to orthographic projection and draw the contents of the 2d + // overlay on top of the previous drawings + glMatrixMode (GL_PROJECTION); + glPushMatrix (); + glLoadIdentity (); + + // first we have to get the size of the current viewport to set up the + // orthographic projection correctly + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + gluOrtho2D (viewport[0], viewport[2], viewport[3], viewport[1]); + + glMatrixMode (GL_MODELVIEW); + glPushMatrix (); + glLoadIdentity (); + + GetView()->SelectFont("console.ttf"); + float x = right * 0.5 - 100; + float y = bottom * 0.5 - 8 - 128; + + // then we do the drawings + Engine::DrawGLString ( x, y, "A s t e r o i d s"); + y += 30; + Engine::DrawGLString ( x, y, "Highscore"); + + y += 30; + std::list::iterator highscore_iter = GetModel()->mHighscoreList.begin(); + + GetView()->SetFontJustification(Engine::FontJustificationLeft); + + unsigned int entry_length = 32; + + unsigned int i = 0; + while (highscore_iter != GetModel()->mHighscoreList.end()) { + // compute the number of digits + unsigned int digits = 1; + unsigned int temp_val = highscore_iter->points; + while (temp_val > 0) { + temp_val /= 10; + digits ++; + } + + // output of the name + std::ostringstream out_stream; + out_stream << highscore_iter->name; + + // add dots to fill the line + temp_val = entry_length - out_stream.str().size() - digits; + for (temp_val; temp_val > 0; temp_val--) { + out_stream << "."; + } + out_stream << highscore_iter->points; + + // Check whether we have to highlight an entry (such as when entering + // the name) + if (GetModel()->mNewestHighscoreEntryIndex < GetModel()->mHighscoreList.size() + && GetModel()->mNewestHighscoreEntryIndex == i) { + GetView()->SetFontColor (224./255., 200/255., 0.); + Engine::DrawGLString ( x, y, out_stream.str().c_str()); + GetView()->SetFontColor (1., 1., 1.); + } else { + Engine::DrawGLString ( x, y, out_stream.str().c_str()); + } + + // go down one line + y += 16; + + i++; + highscore_iter++; + } + + Engine::DrawGLString ( x + 16, y + 16, "Press [Return] to continue."); + + + glPopMatrix (); + + glMatrixMode (GL_PROJECTION); + glPopMatrix (); + + glMatrixMode (GL_MODELVIEW); + +}; + } diff --git a/asteroids/UserInterface.h b/asteroids/UserInterface.h index 45fe79f..98df575 100644 --- a/asteroids/UserInterface.h +++ b/asteroids/UserInterface.h @@ -91,6 +91,18 @@ class PlayerDiedOverlay : public Engine::OverlayBase { virtual void Draw (); }; +class HighscoreOverlay : public Engine::OverlayBase { + public: + HighscoreOverlay () { + }; + virtual ~HighscoreOverlay() {}; + + virtual void Init (); + + virtual bool OnKeyDown (const SDL_keysym &keysym); + virtual void Draw (); +}; + } #endif /* USERINTERFACE */ diff --git a/asteroids/View.cc b/asteroids/View.cc index 5b2d44f..d7f62ba 100644 --- a/asteroids/View.cc +++ b/asteroids/View.cc @@ -36,6 +36,7 @@ int View::OnInit (int argc, char* argv[]) { Engine::OverlayBasePtr level_complete_overlay (new LevelCompleteOverlay); Engine::OverlayBasePtr game_paused_overlay (new GamePausedOverlay); Engine::OverlayBasePtr player_died_overlay (new PlayerDiedOverlay); + Engine::OverlayBasePtr highscore_overlay (new HighscoreOverlay); mOverlayManager.Register (menu_overlay, GameStateMainMenu); mOverlayManager.Register (game_running_overlay, GameStateRunning); @@ -43,6 +44,7 @@ int View::OnInit (int argc, char* argv[]) { mOverlayManager.Register (game_over_overlay, GameStateGameOver); mOverlayManager.Register (player_died_overlay, GameStatePlayerDied); mOverlayManager.Register (game_paused_overlay, GameStatePaused); + mOverlayManager.Register (highscore_overlay, GameStateShowHighscore); mOverlayManager.InitOverlays(); diff --git a/engine/Engine.h b/engine/Engine.h index acb0c67..6399d0e 100644 --- a/engine/Engine.h +++ b/engine/Engine.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include