From 2427aed57134e83d32c7f664639727a87598abdb Mon Sep 17 00:00:00 2001 From: aeonphyxius Date: Mon, 20 Dec 2010 00:52:59 +0000 Subject: [PATCH] Tutorial development incremental commit git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/trunk@7095 178a84e3-b1eb-0310-8ba1-8eac791a3b58 --- data/grandprix/basic_drive.tutorial | 14 + src/ide/vc9/supertuxkart.vcproj | 16 ++ src/modes/tutorial_race.cpp | 194 ++++++++++++++ src/modes/tutorial_race.hpp | 55 ++++ src/states_screens/tutorial_screen.hpp | 2 +- src/tutorial/tutorial.hpp | 7 +- src/tutorial/tutorial_data.cpp | 339 +++++++++++++++++++++++++ src/tutorial/tutorial_data.hpp | 76 ++++++ 8 files changed, 699 insertions(+), 4 deletions(-) create mode 100644 data/grandprix/basic_drive.tutorial create mode 100644 src/modes/tutorial_race.cpp create mode 100644 src/modes/tutorial_race.hpp create mode 100644 src/tutorial/tutorial_data.cpp create mode 100644 src/tutorial/tutorial_data.hpp diff --git a/data/grandprix/basic_drive.tutorial b/data/grandprix/basic_drive.tutorial new file mode 100644 index 000000000..f982a30f7 --- /dev/null +++ b/data/grandprix/basic_drive.tutorial @@ -0,0 +1,14 @@ + + diff --git a/src/ide/vc9/supertuxkart.vcproj b/src/ide/vc9/supertuxkart.vcproj index d4e8da656..b2d969bea 100644 --- a/src/ide/vc9/supertuxkart.vcproj +++ b/src/ide/vc9/supertuxkart.vcproj @@ -502,6 +502,10 @@ RelativePath="..\..\modes\three_strikes_battle.cpp" > + + @@ -1118,6 +1122,10 @@ RelativePath="..\..\tutorial\tutorial.cpp" > + + @@ -1420,6 +1428,10 @@ RelativePath="..\..\modes\three_strikes_battle.hpp" > + + @@ -2024,6 +2036,10 @@ RelativePath="..\..\tutorial\tutorial.hpp" > + + diff --git a/src/modes/tutorial_race.cpp b/src/modes/tutorial_race.cpp new file mode 100644 index 000000000..5fb166ba9 --- /dev/null +++ b/src/modes/tutorial_race.cpp @@ -0,0 +1,194 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2010 Alejandro Santiago +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "modes/tutorial_race.hpp" + +#include "audio/music_manager.hpp" +#include "tutorial/tutorial_manager.hpp" +#include "config/user_config.hpp" +#include "items/powerup_manager.hpp" +#include "states_screens/race_gui_base.hpp" +#include "tracks/track.hpp" +#include "utils/translation.hpp" + + +//----------------------------------------------------------------------------- +TutorialRace::TutorialRace() : LinearWorld() +{ + // We have to make sure that no kart finished the set number of laps + // in a FTL race (since otherwise its distance will not be computed + // correctly, and as a result e.g. a leader might suddenly fall back + // after crossing the start line + race_manager->setNumLaps(99999); + + m_leader_intervals = stk_config->m_leader_intervals; + for(unsigned int i=0; im_leader_time_per_kart*race_manager->getNumberOfKarts(); + m_use_highscores = false; // disable high scores + setClockMode(WorldStatus::CLOCK_COUNTDOWN, m_leader_intervals[0]); +} + +//----------------------------------------------------------------------------- +TutorialRace::~TutorialRace() +{ +} + +#if 0 +#pragma mark - +#pragma mark clock events +#endif +//----------------------------------------------------------------------------- +/** Returns the original time at which the countdown timer started. This is + * used by the race_gui to display the music credits in FTL mode correctly. + */ +float TutorialRace::getClockStartTime() +{ + return m_leader_intervals[0]; +} // getClockStartTime + +//----------------------------------------------------------------------------- +/** Called when a kart must be eliminated. + */ +void TutorialRace::countdownReachedZero() +{ + if(m_leader_intervals.size()>1) + m_leader_intervals.erase(m_leader_intervals.begin()); + WorldStatus::setTime(m_leader_intervals[0]); + + // If the leader kart is not the first kart, remove the first + // kart, otherwise remove the last kart. + int position_to_remove = m_karts[0]->getPosition()==1 + ? getCurrentNumKarts() : 1; + Kart *kart = getKartAtPosition(position_to_remove); + if(!kart || kart->isEliminated()) + { + fprintf(stderr,"Problem with removing leader: position %d not found\n", + position_to_remove); + for(unsigned int i=0; iisEliminated(), m_karts[i]->getPosition()); + } // for i + } // + else + { + removeKart(kart->getWorldKartId()); + + // In case that the kart on position 1 was removed, we have + // to set the correct position (which equals the remaining + // number of karts) for the removed kart, as well as recompute + // the position for all other karts + if(position_to_remove==1) + { + // We have to add 1 to the number of karts to get the correct + // position, since the eliminated kart was already removed + // from the value returned by getCurrentNumKarts (and we have + // to remove the kart before we can call updateRacePosition). + // Note that we can not call WorldWithRank::setKartPosition + // here, since it would not properly support debugging kart + // ranks (since this kart would get its position set again + // in updateRacePosition). We only set the rank of the eliminated + // kart, and updateRacePosition will then call setKartPosition + // for the now eliminated kart. + kart->setPosition(getCurrentNumKarts()+1); + updateRacePosition(); + } + } + + // almost over, use fast music + if(getCurrentNumKarts()==3) + { + music_manager->switchToFastMusic(); + } + + if (isRaceOver()) + { + // Handle special FTL situation: the leader is kart number 3 when + // the last kart gets eliminated. In this case kart on position 1 + // is eliminated, and the kart formerly on position 2 is on + // position 1, the leader now position 2. In this case the kart + // on position 1 would get more points for this victory. So if + // this is the case, change the position + if(m_karts[0]->getPosition()!=1) + { + // Adjust the position of all still driving karts except + // the leader by +1, and move the leader to position 1. + for (unsigned int i=1; ihasFinishedRace() && + !m_karts[i]->isEliminated() ) + m_karts[i]->setPosition(m_karts[i]->getPosition()+1); + } + m_karts[0]->setPosition(1); + } + + // Mark all still racing karts to be finished. + for (unsigned int n=0; nisEliminated() && + !m_karts[n]->hasFinishedRace()) + { + m_karts[n]->finishedRace(getTime()); + } + } + } + // End of race is detected from World::updateWorld() + +} // countdownReachedZero + +//----------------------------------------------------------------------------- +/** The follow the leader race is over if there is only one kart left (plus + * the leader), or if all (human) players have been eliminated. + */ +bool TutorialRace::isRaceOver() +{ + // FIXME : add the logic to detect if the user have ended the tutorial. + return true; +} // isRaceOver + +//----------------------------------------------------------------------------- +void TutorialRace::restartRace() +{ + LinearWorld::restartRace(); + m_leader_intervals.clear(); + m_leader_intervals = stk_config->m_leader_intervals; + for(unsigned int i=0; im_leader_time_per_kart*race_manager->getNumberOfKarts(); + WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN, + m_leader_intervals[0]); +} // restartRace + +//----------------------------------------------------------------------------- +/** Returns the internal identifier for this kind of race. + */ +std::string TutorialRace::getIdent() const +{ + return FTL_IDENT; +} // getIdent + +//----------------------------------------------------------------------------- +/** Sets the title for all karts that is displayed in the icon list. In + * this mode the title for the first kart is set to 'leader'. + */ +RaceGUIBase::KartIconDisplayInfo* TutorialRace::getKartsDisplayInfo() +{ + LinearWorld::getKartsDisplayInfo(); + m_kart_display_info[0].special_title = _("Leader"); + return m_kart_display_info; +} // getKartsDisplayInfo diff --git a/src/modes/tutorial_race.hpp b/src/modes/tutorial_race.hpp new file mode 100644 index 000000000..49137e02e --- /dev/null +++ b/src/modes/tutorial_race.hpp @@ -0,0 +1,55 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2010 Alejandro Santiago +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef HEADER_TUTORIAL_MODE_HPP +#define HEADER_TUTORIAL_MODE_HPP + +#include "modes/linear_world.hpp" +#include "states_screens/race_gui_base.hpp" + +/** + * \brief An implementation of World, based on LinearWorld, to provide the Follow-the-leader game mode + * \ingroup modes + */ +class TutorialRace : public LinearWorld +{ +private: + + std::vector m_leader_intervals; // time till elimination in follow leader + +public: + + TutorialRace(); + virtual ~TutorialRace(); + + // clock events + virtual void countdownReachedZero(); + + // overriding World methods + virtual void restartRace(); + virtual std::string getIdent() const; + float getClockStartTime(); + virtual bool useFastMusicNearEnd() const { return false; } + virtual RaceGUIBase::KartIconDisplayInfo* getKartsDisplayInfo(); + + virtual bool isRaceOver(); + virtual bool raceHasLaps(){ return false; } + +}; + + +#endif //HEADER_TUTORIAL_MODE_HPP diff --git a/src/states_screens/tutorial_screen.hpp b/src/states_screens/tutorial_screen.hpp index 47950370b..8c5213192 100644 --- a/src/states_screens/tutorial_screen.hpp +++ b/src/states_screens/tutorial_screen.hpp @@ -25,7 +25,7 @@ using namespace irr::core; -const std::string BASIC_DRIVING = "bd"; +const std::string BASIC_DRIVING = "basicdriving"; const std::string SHARP_TURN = "st"; const std::string NITRO = "nt"; const std::string COLLECTIBLE_WEAPONS = "cw"; diff --git a/src/tutorial/tutorial.hpp b/src/tutorial/tutorial.hpp index e0f04f367..f5be41880 100644 --- a/src/tutorial/tutorial.hpp +++ b/src/tutorial/tutorial.hpp @@ -66,7 +66,7 @@ private: std::string m_Id; // short, internal name for this tutorial irr::core::stringw m_Name; // name used in menu for this tutorial - irr::core::stringw m_challenge_description; // Message the user gets when the feature is not yet unlocked + irr::core::stringw m_tutorial_description; // Message the user gets when the feature is not yet unlocked // vector m_feature; // Features to unlock // vector m_prerequisites; // what needs to be done before accessing this tutorial @@ -83,6 +83,8 @@ public: void addUnlockTrackReward(const std::string &track_name); void addUnlockModeReward(const std::string &internal_mode_name, const irr::core::stringw &user_mode_name); + void setTutorialDescription(const irr::core::stringw& d) + {m_tutorial_description=d; } /* void addUnlockGPReward(const std::string &gp_name); void addUnlockDifficultyReward(const std::string &internal_name, const irr::core::stringw &user_name); @@ -91,8 +93,7 @@ public: /*const vector& getFeatures() const { return m_feature; } - void setChallengeDescription(const irr::core::stringw& d) - {m_challenge_description=d; } + const irr::core::stringw& getChallengeDescription() const {return m_challenge_description; } void addDependency(const std::string id) {m_prerequisites.push_back(id); } diff --git a/src/tutorial/tutorial_data.cpp b/src/tutorial/tutorial_data.cpp new file mode 100644 index 000000000..b347aaf19 --- /dev/null +++ b/src/tutorial/tutorial_data.cpp @@ -0,0 +1,339 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2010 Alejandro Santiago +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "tutorial/tutorial_data.hpp" + +#include +#include + +#include "karts/kart.hpp" +#include "karts/kart_properties_manager.hpp" +#include "modes/linear_world.hpp" +#include "race/grand_prix_data.hpp" +#include "race/grand_prix_manager.hpp" +#include "race/race_manager.hpp" +#include "tracks/track.hpp" +#include "tracks/track_manager.hpp" +#include "utils/translation.hpp" + +TutorialData::TutorialData(const std::string& filename) +#ifndef WIN32 + throw(std::runtime_error) +#endif +{ + m_filename = filename; + m_major = RaceManager::MAJOR_MODE_SINGLE; + m_minor = RaceManager::MINOR_MODE_NORMAL_RACE; + m_difficulty = RaceManager::RD_EASY; + m_num_laps = -1; + m_num_karts = -1; + m_time = -1.0f; + m_track_name = ""; + m_gp_id = ""; + m_energy = -1; + + XMLNode *root = new XMLNode( filename ); + + // Check if the file have been correctly load + if(!root || root->getName()!="tutorial") + { + delete root; + std::ostringstream msg; + msg << "Couldn't load tutorial '" << filename << "': no tutorial node."; + throw std::runtime_error(msg.str()); + } + + std::string s_property; + int i_property; + float f_property; + + // Mode + + root->get("major", &s_property); + setMajor(s_property); + + // Minor + root->get("minor", &s_property); + setMinor(s_property); + + // Name + if(!root->get("name", &s_property) ) + error("name"); + setName( _(s_property.c_str()) ); + + // ID + if(!root->get("id", &s_property) ) + error("id"); + setId(s_property); + + // Description + if(!root->get("s_property", &s_property) ) + error("description"); + setTutorialDescription( _(s_property.c_str()) ); + + // Karts + if(!root->get("karts", &i_property) ) + error("karts"); + setNumKarts(i_property); + + // Difficulty + root->get("difficulty", &s_property); + setDifficulty(s_property); + + // Time + root->get("time", &f_property); // one of time/position + setTime(f_property); + + // Position + root->get("position", &m_position ); // must be set + if(m_time<0 && m_position<0) + error("position/time"); + + // Energy + root->get("energy", & i_property ); // This is optional + setEnergy(i_property); + + // FIXME do we need position for the tutorials??????? + // Position is optional except in GP and FTL + /*if(!root->get("position", &s_property)) + error("position"); + set + && + //RaceManager::getWorld()->areKartsOrdered() ) // FIXME - order and optional are not the same thing + (m_minor==RaceManager::MINOR_MODE_FOLLOW_LEADER || + m_major==RaceManager::MAJOR_MODE_GRAND_PRIX)) + error("position");*/ + + if(m_major==RaceManager::MAJOR_MODE_SINGLE) + { + // Track + if (!root->get("track", &s_property )) + error("track"); + setTrack(s_property); + + if (!root->get("laps", &i_property)) + error("laps"); + setLaps(i_property); + } + else // GP + { + if (!root->get("gp", &m_gp_id )) error("gp"); + if (grand_prix_manager->getGrandPrix(m_gp_id) == NULL) error("gp"); + } + + + delete root; + +} // TutorialData + +// ---------------------------------------------------------------------------- +void TutorialData::error(const char *id) const +{ + std::ostringstream msg; + msg << "Undefined or incorrect value for '" << id + << "' in tutorial file '" << m_filename << "'."; + + std::cerr << "TutorialData : " << msg.str() << std::endl; + + throw std::runtime_error(msg.str()); +} // error + +// ---------------------------------------------------------------------------- +/** Checks if this challenge is valid, i.e. contains a valid track or a valid + * GP. If incorrect data are found, STK is aborted with an error message. + * (otherwise STK aborts when trying to do this challenge, which is worse). + */ +void TutorialData::check() const +{ + if(m_major==RaceManager::MAJOR_MODE_SINGLE) + { + try + { + track_manager->getTrack(m_track_name); + } + catch(std::exception&) + { + error("track"); + } + } + else if(m_major==RaceManager::MAJOR_MODE_GRAND_PRIX) + { + const GrandPrixData* gp = grand_prix_manager->getGrandPrix(m_gp_id); + + if (gp == NULL) + { + error("gp"); + } + const bool gp_ok = gp->checkConsistency(false); + if (!gp_ok) + { + error("gp"); + } + } +} // check + + +void TutorialData::setRace() const +{ + race_manager->setMajorMode(m_major); + if(m_major==RaceManager::MAJOR_MODE_SINGLE) + { + race_manager->setMinorMode(m_minor); + race_manager->setTrack(m_track_name); + race_manager->setDifficulty(m_difficulty); + race_manager->setNumLaps(m_num_laps); + race_manager->setNumKarts(m_num_karts); + race_manager->setNumPlayers(1); + race_manager->setNumLocalPlayers(1); + race_manager->setCoinTarget(m_energy); + } + else // GP + { + race_manager->setMinorMode(m_minor); + const GrandPrixData *gp = grand_prix_manager->getGrandPrix(m_gp_id); + race_manager->setGrandPrix(*gp); + race_manager->setDifficulty(m_difficulty); + race_manager->setNumKarts(m_num_karts); + race_manager->setNumPlayers(1); + race_manager->setNumLocalPlayers(1); + } +} // setRace + +// ---------------------------------------------------------------------------- +bool TutorialData::raceFinished() +{ + // GP's use the grandPrixFinished() function, so they can't be fulfilled here. + if(m_major==RaceManager::MAJOR_MODE_GRAND_PRIX) return false; + + // Single races + // ------------ + World *world = World::getWorld(); + std::string track_name = world->getTrack()->getIdent(); + if(track_name!=m_track_name ) return false; // wrong track + if((int)world->getNumKarts()getPlayerKart(0); + if(m_energy>0 && kart->getEnergy() 0 && kart->getPosition()>m_position) return false; // too far behind + + // Follow the leader + // ----------------- + if(m_minor==RaceManager::MINOR_MODE_FOLLOW_LEADER) + { + // All possible conditions were already checked, so: must have been successful + return true; + } + // Quickrace / Timetrial + // --------------------- + // FIXME - encapsulate this better, each race mode needs to be able to specify + // its own challenges and deal with them + LinearWorld* lworld = dynamic_cast(world); + if(lworld != NULL) + { + if(lworld->getLapForKart( kart->getWorldKartId() ) != m_num_laps) return false; // wrong number of laps + } + if(m_time>0.0f && kart->getFinishTime()>m_time) return false; // too slow + return true; +} // raceFinished + +// ---------------------------------------------------------------------------- +bool TutorialData::grandPrixFinished() +{ + // Note that we have to call race_manager->getNumKarts, since there + // is no world objects to query at this stage. + if (race_manager->getMajorMode() != RaceManager::MAJOR_MODE_GRAND_PRIX || + race_manager->getMinorMode() != m_minor || + race_manager->getGrandPrix()->getId() != m_gp_id || + race_manager->getDifficulty()!= m_difficulty || + race_manager->getNumberOfKarts() < (unsigned int)m_num_karts || + race_manager->getNumPlayers() > 1) return false; + + // check if the player came first. + //assert(World::getWorld() != NULL); + // Kart* kart = World::getWorld()->getPlayerKart(0); + //const int rank = race_manager->getKartGPRank(kart->getWorldKartId()); + const int rank = race_manager->getLocalPlayerGPRank(0); + + //printf("getting rank for %s : %i \n", kart->getName().c_str(), rank ); + if (rank != 0) return false; + + return true; +} // grandPrixFinished + +void TutorialData::setNumKarts(int num_karts) +{ + this->m_num_karts= num_karts; +} +void TutorialData::setLaps(int num_laps) +{ + this->m_num_laps = num_laps; +} +void TutorialData::setTrack(std::string track_name) +{ + this->m_track_name == track_name; + if (track_manager->getTrack(m_track_name) == NULL) + error("track"); +} + +void TutorialData::setDifficulty(std::string difficulty) +{ + if(difficulty == "easy") + this->m_difficulty = RaceManager::RD_EASY; + else if(difficulty =="medium") + this->m_difficulty = RaceManager::RD_MEDIUM; + else if(difficulty =="hard") + this->m_difficulty = RaceManager::RD_HARD; + else + error("difficulty"); +} + +void TutorialData::setMinor(std::string minor) +{ + if(minor=="timetrial") + this->m_minor = RaceManager::MINOR_MODE_TIME_TRIAL; + else if(minor=="quickrace") + this->m_minor = RaceManager::MINOR_MODE_NORMAL_RACE; + else if(minor=="followtheleader") + this->m_minor = RaceManager::MINOR_MODE_FOLLOW_LEADER; + else + error("minor"); +} + +void TutorialData::setMajor(std::string major) +{ + if(major=="grandprix") + this->m_major = RaceManager::MAJOR_MODE_GRAND_PRIX; + else if(major=="single") + this->m_major = RaceManager::MAJOR_MODE_SINGLE; + else + error("major"); +} + +void TutorialData::setPosition(int position) +{ + this->m_position = position; +} + +void TutorialData::setTime (float time) +{ + this->m_time = time; +} + +void TutorialData::setEnergy(int energy) +{ + m_energy = energy; +} diff --git a/src/tutorial/tutorial_data.hpp b/src/tutorial/tutorial_data.hpp new file mode 100644 index 000000000..c93d25106 --- /dev/null +++ b/src/tutorial/tutorial_data.hpp @@ -0,0 +1,76 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2010 Alejandro Santiago +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef HEADER_TUTORIAL_DATA_HPP +#define HEADER_TUTORIAL_DATA_HPP + +#include +#include +#include +#include + +#include "tutorial/tutorial.hpp" +#include "race/race_manager.hpp" + +/** + * \ingroup tutorial + */ +class TutorialData : public Tutorial +{ +private: + RaceManager::MajorRaceModeType m_major; + RaceManager::MinorRaceModeType m_minor; + RaceManager::Difficulty m_difficulty; + int m_num_laps; + int m_position; + int m_num_karts; + float m_time; + std::string m_gp_id; + std::string m_track_name; + int m_energy; + std::string m_filename; + + void getUnlocks(const XMLNode *root, const std:: string type, REWARD_TYPE reward); + void error(const char *id) const; + +public: +#ifdef WIN32 + TutorialData(const std::string& filename); +#else + TutorialData(const std::string& filename) throw(std::runtime_error); +#endif + + /** sets the right parameters in RaceManager to try this tutorial */ + void setRace() const; + + void setEnergy(int m_energy); + void setTime(float m_time); + void setPosition(int position); + void setDifficulty(std::string difficulty); + void setTrack(std::string track_name); + void setLaps(int num_laps); + void setMajor(std::string major); + void setMinor(std::string minor); + void setNumKarts(int num_karts); + + virtual void check() const; + virtual bool raceFinished(); + virtual bool grandPrixFinished(); + +}; // TutorialData + +#endif // HEADER_TUTORIAL_DATA_HPP