// // Cross-platform free Puyo-Puyo clone. // Copyright (C) 2006, 2007 Emma's Software // // 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 2 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #if defined (HAVE_CONFIG_H) #include #endif // HAVE_CONFIG_H #include #include #include "AIPlayerFactory.h" #include "CongratulationsState.h" #include "File.h" #include "HumanPlayer.h" #include "System.h" #include "TournamentState.h" #include "TwoComputerPlayersState.h" #include "TwoPlayersState.h" #include "VersusState.h" #if defined (IS_GP2X_HOST) #include "TwoGP2XPlayersState.h" #endif // IS_GP2X_HOST using namespace Amoebax; static const int32_t k_BlinkTime = 400; static const uint8_t k_InvalidCharacterIndex = 255; static const uint8_t k_FaceSize = 128; static const uint8_t k_FacesPerRow = 7; static const uint16_t k_InitialHorizontalPosition = 1280 / 2 - k_FaceSize / 2; static const int32_t k_MatchStartTime = k_BlinkTime * 8; /// /// \brief Constructor. /// /// \param players The list of players going to compete in the tournament. /// TournamentState::TournamentState (const std::vector &players): IState (), m_CurrentMatch (0), m_Background (0), m_BackgroundMusic (0), m_CurrentMatchBlinkTime (k_BlinkTime), m_CurrentMatchStartTime (k_MatchStartTime), m_Faces (0), m_HorizontalOffset (), m_Matches (), m_NumPlayers (players.size ()), m_ShowCurrentOpponents (true), m_SoundLose (Sound::fromFile (File::getSoundFilePath ("youlose.wav"))), m_SoundWin (Sound::fromFile (File::getSoundFilePath ("youwin.wav"))), m_VerticalPosition () { loadGraphicResources (); createMatchTree (players); } /// /// \brief Destructor. /// TournamentState::~TournamentState (void) { deleteMatch (m_Matches.children.first); deleteMatch (m_Matches.children.second); } void TournamentState::activate (void) { if ( Music::isPaused () ) { Music::resume (); } else if ( !Music::isPlaying () ) { if ( 0 == m_BackgroundMusic.get () ) { m_BackgroundMusic.reset (Music::fromFile (File::getMusicFilePath ("menu.ogg"))); } m_BackgroundMusic->play (); } } /// /// \brief Creates the tree of matches. /// /// Based on the number of players, this function creates a tree /// of matches and initializes it. It also initializes the vertical /// positions and horizontal offsets of each level of the tree. /// /// \param players The vector of players that will play at the tournament. /// void TournamentState::createMatchTree (const std::vector &players) { assert ( (2 == players.size () || 4 == players.size ()) && "Currently only tournaments of 2 or 4 players are supported." ); // This is a sentinel player used to represent unused entries. Player sentinelPlayer; sentinelPlayer.characterIndex = k_InvalidCharacterIndex; // The first "match" is the winner. m_Matches.parent = 0; m_Matches.opponents.first = sentinelPlayer; m_Matches.opponents.second = sentinelPlayer; m_Matches.children.second = 0; Match *semifinal = new Match; semifinal->parent = &m_Matches; if ( 2 == players.size () ) { m_HorizontalOffset.push_back (0); m_HorizontalOffset.push_back (192); m_VerticalPosition.push_back (288); m_VerticalPosition.push_back (544); semifinal->opponents.first = players[0]; semifinal->opponents.first.score = 0; semifinal->opponents.second = players[1]; semifinal->opponents.second.score = 0; semifinal->children.first = 0; semifinal->children.second = 0; m_Matches.children.first = semifinal; m_CurrentMatch = semifinal; } else { m_HorizontalOffset.push_back (0); m_HorizontalOffset.push_back (321); m_HorizontalOffset.push_back (192); m_VerticalPosition.push_back (160); m_VerticalPosition.push_back (416); m_VerticalPosition.push_back (672); semifinal->opponents.first = sentinelPlayer; semifinal->opponents.second = sentinelPlayer; m_Matches.children.first = semifinal; Match *leftMatch = new Match; leftMatch->children.first = 0; leftMatch->children.second = 0; leftMatch->parent = semifinal; leftMatch->opponents.first = players[0]; leftMatch->opponents.first.score = 0; leftMatch->opponents.second = players[1]; leftMatch->opponents.second.score = 0; Match *rightMatch = new Match; rightMatch->children.first = 0; rightMatch->children.second = 0; rightMatch->parent = semifinal; rightMatch->opponents.first = players[2]; rightMatch->opponents.first.score = 0; rightMatch->opponents.second = players[3]; rightMatch->opponents.second.score = 0; semifinal->children.first = leftMatch; semifinal->children.second = rightMatch; m_CurrentMatch = leftMatch; } } /// /// \brief Recursively deletes a match and all its children. /// /// \param match The match to delete, and all its children. /// void TournamentState::deleteMatch (Match *match) { if ( 0 != match ) { deleteMatch (match->children.first); deleteMatch (match->children.second); delete match; } } void TournamentState::endOfMatch (IPlayer::PlayerSide winner, uint32_t leftPlayerScore, uint32_t rightPlayerScore) { // Check if both players are computers, to play the correct sound later. bool bothComputer = getCurrentMatch ()->opponents.first.isComputerPlayer && getCurrentMatch ()->opponents.second.isComputerPlayer; // Accumulate the scores. getCurrentMatch ()->opponents.first.score += leftPlayerScore; getCurrentMatch ()->opponents.second.score += rightPlayerScore; Player winnerPlayer; if ( IPlayer::LeftSide == winner ) { winnerPlayer = getCurrentMatch ()->opponents.first; getCurrentMatch ()->opponents.first.characterIndex = k_InvalidCharacterIndex; } else { winnerPlayer = getCurrentMatch ()->opponents.second; getCurrentMatch ()->opponents.second.characterIndex = k_InvalidCharacterIndex; } if ( getCurrentMatch()->parent == &m_Matches ) { setCurrentMatch (&m_Matches); m_Matches.opponents.first = winnerPlayer; } else if ( getCurrentMatch () == getCurrentMatch ()->parent->children.first ) { getCurrentMatch ()->parent->opponents.first = winnerPlayer; if ( 0 != getCurrentMatch ()->parent->children.second ) { setCurrentMatch (getCurrentMatch ()->parent->children.second); } else { setCurrentMatch (getCurrentMatch ()->parent); } } else { getCurrentMatch ()->parent->opponents.second = winnerPlayer; setCurrentMatch (getCurrentMatch ()->parent); } // Play the "winner" sound effect if the winner is human, otherwise // play the "lose" sound effect. if ( winnerPlayer.isComputerPlayer ) { if ( bothComputer ) { m_SoundWin->play (); } else { m_SoundLose->play (); } } else { m_SoundWin->play (); } } /// /// \brief Gets the best two players states based on the characters. /// /// \param leftCharacter The information about the left player's character. /// \param rightCharacter The information about the right player's character. /// \return The best two players state based on \p leftCharacter and /// \p rightCharacter fully initialized and ready to be set as /// active. /// IState * TournamentState::getBestTwoPlayersState (const Player &leftCharacter, const Player &rightCharacter) { IState *twoPlayersState = 0; if ( leftCharacter.isComputerPlayer && rightCharacter.isComputerPlayer ) { twoPlayersState = new TwoComputerPlayersState (leftCharacter.computerPlayerLevel, rightCharacter.computerPlayerLevel, this); } #if defined (IS_GP2X_HOST) else if ( !leftCharacter.isComputerPlayer && !rightCharacter.isComputerPlayer ) { twoPlayersState = new TwoGP2XPlayersState ( AIPlayerFactory::getRandomBackgroundFileName (), leftCharacter.score, rightCharacter.score, this); } #endif // IS_GP2X_HOST else { IPlayer *leftPlayer = getMatchPlayer (leftCharacter, IPlayer::LeftSide); IPlayer *rightPlayer = getMatchPlayer (rightCharacter, IPlayer::RightSide); twoPlayersState = new TwoPlayersState (leftPlayer, rightPlayer, AIPlayerFactory::getRandomBackgroundFileName (), leftCharacter.score, rightCharacter.score, this); } return twoPlayersState; } /// /// \brief Gets the current match. /// /// \return The pointer to the current match. /// inline TournamentState::Match * TournamentState::getCurrentMatch (void) { return m_CurrentMatch; } /// /// \brief Gets the time remaining to change the current match blink state. /// /// \return The time remaining until the application should change the /// blinking state of the current match's opponents. /// inline int32_t TournamentState::getCurrentMatchBlinkTime (void) const { return m_CurrentMatchBlinkTime; } /// /// \brief Gets the time remaining to start the current match. /// /// \return The time remaining until the current match starts. /// inline int32_t TournamentState::getCurrentMatchStartTime (void) const { return m_CurrentMatchStartTime; } /// /// \brief Gets a player of a match. /// /// \param player The definition of the match's player to create the object /// from. /// \param side The side the player will play on. /// \return The IPlayer created from the definition of \p player. /// IPlayer * TournamentState::getMatchPlayer (const Player &player, IPlayer::PlayerSide side) const { IPlayer *matchPlayer = 0; if ( player.isComputerPlayer ) { matchPlayer = AIPlayerFactory::create (player.computerPlayerLevel, side); } else { matchPlayer = new HumanPlayer (side); } return matchPlayer; } void TournamentState::joyMotion (uint8_t joystick, uint8_t axis, int16_t value) { } void TournamentState::joyDown (uint8_t joystick, uint8_t button) { #if defined (IS_GP2X_HOST) switch (button) { case GP2X_BUTTON_A: case GP2X_BUTTON_B: case GP2X_BUTTON_CLICK: startCurrentMatch (); break; case GP2X_BUTTON_START: case GP2X_BUTTON_X: System::getInstance ().pause (); break; } #endif // IS_GP2X_HOST } void TournamentState::joyUp (uint8_t joystick, uint8_t button) { } #if !defined (IS_GP2X_HOST) void TournamentState::keyDown (uint32_t key) { switch ( key ) { case SDLK_ESCAPE: System::getInstance ().pause (); break; case SDLK_RETURN: startCurrentMatch (); break; } } void TournamentState::keyUp (uint32_t key) { } #endif // !IS_GP2X_HOST /// /// \brief Loads graphic resources. /// void TournamentState::loadGraphicResources (void) { const float screenScale = System::getInstance ().getScreenScaleFactor (); m_Background.reset ( Surface::fromFile (File::getGraphicsFilePath ("menuBackground.png"))); { std::ostringstream fileName; fileName << "Tournament"; fileName << m_NumPlayers; fileName << "p.png"; std::auto_ptr grid ( Surface::fromFile (File::getGraphicsFilePath (fileName.str ()))); grid->blit (m_Background->getWidth () / 2 - grid->getWidth () / 2, m_Background->getHeight () / 2 - grid->getHeight () / 2, m_Background->toSDLSurface ()); } m_Background->resize (screenScale); m_Faces.reset ( Surface::fromFile (File::getGraphicsFilePath ("faces.png"))); m_Faces->resize (screenScale); } void TournamentState::redrawBackground (SDL_Rect *region, SDL_Surface *screen) { m_Background->blit (region->x, region->y, region->w, region->h, region->x, region->y, screen); } void TournamentState::render (SDL_Surface *screen) { renderMatch (&m_Matches, screen, k_InitialHorizontalPosition); } /// /// \brief Recursively draw a match and all its children. /// /// Renders a match and all its children in their appropriate position. /// /// \param match The match to render along with all its children. /// \param screen The screen surface to render the matches to. /// \param initialX The central X where the matches should be rendered to. /// This function takes into account the horizontal offset /// between the position of the opponents. /// \param level The current recursive level. The first call it must be 0. /// void TournamentState::renderMatch (Match *match, SDL_Surface *screen, uint16_t initialX, uint8_t level) { const float screenScale = System::getInstance ().getScreenScaleFactor (); const uint16_t faceSize = static_cast (k_FaceSize * screenScale); if ( 0 != match ) { if ( getCurrentMatch () != match || showCurrentOpponents () ) { if ( match->opponents.first.characterIndex != k_InvalidCharacterIndex ) { uint8_t character = match->opponents.first.characterIndex; uint16_t x = static_cast ((initialX - m_HorizontalOffset[level]) * screenScale); uint16_t y = static_cast (m_VerticalPosition[level] * screenScale); m_Faces->blit (character % k_FacesPerRow * faceSize, character / k_FacesPerRow * faceSize, faceSize, faceSize, x, y, screen); } if ( match->opponents.second.characterIndex != k_InvalidCharacterIndex ) { uint8_t character = match->opponents.second.characterIndex; uint16_t x = static_cast ((initialX + m_HorizontalOffset[level]) * screenScale); uint16_t y = static_cast (m_VerticalPosition[level] * screenScale); m_Faces->blit (character % k_FacesPerRow * faceSize, character / k_FacesPerRow * faceSize, faceSize, faceSize, x, y, screen); } } renderMatch (match->children.first, screen, initialX - m_HorizontalOffset[level], level + 1); renderMatch (match->children.second, screen, initialX + m_HorizontalOffset[level], level + 1); } } /// /// \brief Tells if the current match's opponents should be shown. /// /// \return \a true if the current match's opponents should be rendered, /// \a false otherwise. /// inline bool TournamentState::showCurrentOpponents (void) const { return m_ShowCurrentOpponents; } /// /// \brief Changes the current match. /// /// \param match The pointer to the match to set as current. /// inline void TournamentState::setCurrentMatch (Match *match) { m_CurrentMatch = match; } /// /// \brief Sets the time until the blink state should change. /// /// \param blinkTime The time to wait until the blink state of the current /// match's opponents should change. /// inline void TournamentState::setCurrentMatchBlinkTime (int32_t blinkTime) { m_CurrentMatchBlinkTime = blinkTime; } /// /// \brief Sets the time until the current match starts. /// /// \param startTime The time to wait until the current match starts. /// inline void TournamentState::setCurrentMatchStartTime (int32_t startTime) { m_CurrentMatchStartTime = startTime; } /// /// \brief Sets if show the current match's opponents or not. /// /// \param showCurrentOpponents set to \a true if the match's opponents should /// be shown. Otherwise set to \a false. /// inline void TournamentState::setShowCurrentOpponents (bool showCurrentOpponents) { m_ShowCurrentOpponents = showCurrentOpponents; } /// /// \brief Starts the current match. /// void TournamentState::startCurrentMatch (void) { if ( getCurrentMatch () == &m_Matches ) { System::getInstance ().removeActiveState (false); Player winner = m_Matches.opponents.first; // We don't want a computer player to enter as high score. uint32_t score = winner.isComputerPlayer ? 0 : winner.score; System::getInstance ().setActiveState ( new CongratulationsState (winner.name, score)); } else { System::getInstance ().setActiveState ( getBestTwoPlayersState (getCurrentMatch ()->opponents.first, getCurrentMatch ()->opponents.second), System::FadeIn); System::getInstance ().setActiveState ( new VersusState (getCurrentMatch ()->opponents.second.name, getCurrentMatch ()->opponents.first.name)); setCurrentMatchStartTime (k_MatchStartTime); } } void TournamentState::update (uint32_t elapsedTime) { setCurrentMatchBlinkTime (getCurrentMatchBlinkTime () - elapsedTime); if ( 0 >= getCurrentMatchBlinkTime () ) { setCurrentMatchBlinkTime (getCurrentMatchBlinkTime () + k_BlinkTime); setShowCurrentOpponents (!showCurrentOpponents ()); } setCurrentMatchStartTime (getCurrentMatchStartTime () - elapsedTime); if ( 0 >= getCurrentMatchStartTime () ) { startCurrentMatch (); } } void TournamentState::videoModeChanged (void) { loadGraphicResources (); }