// // 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // #if defined (HAVE_CONFIG_H) #include #endif // HAVE_CONFIG_ #include #include #include "Amoeba.h" #include "Grid.h" #include "GridStatus.h" using namespace Amoebax; /// /// \brief Default constructor. /// /// Creates an empty state. /// GridStatus::GridStatus (void): m_State (Grid::k_GridWidth * Grid::k_GridHeight, Amoeba::ColourNone) { } /// /// \brief Constructor. /// /// \param state The current state of the grid. /// GridStatus::GridStatus (const std::vector &state): m_State (state) { assert ( m_State.size () == (Grid::k_GridWidth * Grid::k_GridHeight) && "The state size is not the same as the grid size." ); } /// /// \brief Checks what would happen in a given position. /// /// Checks what would happen if the falling pair falls in the given /// positions. This is mainly used for AI players. /// /// \note This code assumes that the main amoeba will fall first and then /// will fall the satellite amoeba. This means that the satellite amoeba /// can't be below the main amoeba for this function to work properly. /// /// \param main The position of the main falling amoeba. /// \param satellite The position of the satellite falling amoeba. /// \param result The result of the position of the pair. /// void GridStatus::checkPositions (const Grid::FallingAmoeba &main, const Grid::FallingAmoeba &satellite, PositionResult &result) { // Store the main colour. Amoeba::Colour mainColour = main.amoeba->getColour (); // Find the Y position of the main amoeba. int mainY = Grid::k_GridHeight - 1; while ( Amoeba::ColourNone != getAmoebaColourAt (main.x, mainY) ) { mainY--; } // Place the main amoeba as if it already fell. setAmoebaColourAt (main.x, mainY, mainColour); // Store the satellite colour. Amoeba::Colour satelliteColour = satellite.amoeba->getColour (); // Find the Y position of the satellite amoeba. int satelliteY = Grid::k_GridHeight - 1; while ( Amoeba::ColourNone != getAmoebaColourAt (satellite.x, satelliteY) ) { satelliteY--; } // Place the main amoeba as if it already fell. setAmoebaColourAt (satellite.x, satelliteY, satelliteColour); result.ghostAmoebasRemoved = 0; // Now check the groups that the main and satellite amoebas can make. { uint16_t stepChainGroup = 0; std::set visitedPositions; Group group; Group ghosts; // Main amoeba. makeGroup (group, ghosts, main.x, mainY, visitedPositions); if ( 4 <= group.size () ) { stepChainGroup += group.size (); removeAmoebaGroup (group); result.ghostAmoebasRemoved += ghosts.size (); removeAmoebaGroup (ghosts); } // Satellite amoeba. makeGroup (group, ghosts, satellite.x, satelliteY, visitedPositions); if ( 4 <= group.size () ) { stepChainGroup += group.size (); removeAmoebaGroup (group); result.ghostAmoebasRemoved += ghosts.size (); removeAmoebaGroup (ghosts); } result.stepChains.clear (); result.stepChains.push_back (stepChainGroup); } // Find the amoebas that are left floating and make them fall, check // for groups and remove the groups, until there are no more floating // amoebas. Group floatingAmoebas (findFloatingAmoebas ()); while ( !floatingAmoebas.empty () ) { std::vector groups (makeFloatingAmoebasFall (floatingAmoebas)); std::vector::const_iterator currentGroup = groups.begin (); uint16_t stepChainGroup = 0; for ( bool isGhostGroup = false ; currentGroup != groups.end () ; ++currentGroup, isGhostGroup = !isGhostGroup ) { // For each floating there's two consecutive groups: one for // the "regular" amoebas and the other for the ghosts groups. if ( isGhostGroup ) { result.ghostAmoebasRemoved += currentGroup->size (); } else { stepChainGroup += currentGroup->size (); } removeAmoebaGroup (*currentGroup); } result.stepChains.push_back (stepChainGroup); floatingAmoebas = findFloatingAmoebas (); } // Now get the height of each column, accumulate the height to then // compute the mean, store the maximum and also store separatelly the // third column's height. result.heightMax = std::numeric_limits::min (); result.heightMean = 0; result.heightThirdColumn = 0; for ( uint8_t currentColumn = 0 ; currentColumn < Grid::k_GridWidth ; ++currentColumn ) { // Get the column's height. uint16_t y = Grid::k_GridHeight - 1; uint16_t height = 0; while ( Amoeba::ColourNone != getAmoebaColourAt (currentColumn, y) ) { --y; ++height; } // Accumulate the height to compute the mean and store it if it's max. result.heightMax = std::max (result.heightMax, height); result.heightMean += height; // If we are on the third column (i.e, column 2) store it. if ( 2 == currentColumn ) { result.heightThirdColumn = height; } } // Now compute the actual mean. result.heightMean /= Grid::k_GridWidth; // Get the number of groups of less than 4 amoebas (i.e., at this state // just the groups since there's no group of 4 or more amoebas.) { std::set visitedPositions; result.groups.clear (); result.groupsAmoebasNumber = 0; for ( int16_t currentY = 0 ; currentY < Grid::k_GridHeight ; ++currentY ) { for ( int16_t currentX = 0 ; currentX < Grid::k_GridWidth ; ++currentX ) { Group group; Group ghosts; makeGroup (group, ghosts, currentX, currentY, visitedPositions); if ( 1 < group.size () ) { result.groupsAmoebasNumber += group.size (); result.groups.push_back (group.size ()); } } } } } /// /// \brief Finds all floating amoebas. /// /// A floating amoeba is just an amoeba that has lost its supporting /// amoeba and so it's "floating" on the grid. /// /// \return The list of all floating amoebas starting from the lower /// grid's line. /// std::vector GridStatus::findFloatingAmoebas (void) const { // Check all but the last line. Group floatingAmoebas; for ( int16_t row = Grid::k_GridHeight - 2 ; row >= 0 ; --row ) { for ( int16_t column = 0 ; column < Grid::k_GridWidth ; ++column ) { if ( Amoeba::ColourNone != getAmoebaColourAt (column, row) && Amoeba::ColourNone == getAmoebaColourAt (column, row + 1) ) { floatingAmoebas.push_back (std::make_pair (column, row)); } } } return floatingAmoebas; } /// /// \brief Gets the amoeba's colour at a given grid's position. /// /// \param x The X position of the grid to get the amoeba's colour from. /// \param y The Y position of the grid to get the amoeba's colour from. /// \return The amoeba's colour at position \p x and \p y or Amoeba::ColourNone /// if there is no amoeba at this position or the position is /// incorrect (i.e., out of grid's limits.) /// inline Amoeba::Colour GridStatus::getAmoebaColourAt (int16_t x, int16_t y) const { if ( 0 <= x && x < Grid::k_GridWidth && 0 <= y && y < Grid::k_GridHeight ) { return m_State[y * Grid::k_GridWidth + x]; } return Amoeba::ColourNone; } /// /// \brief Makes the floating amoebas fall into they right position. /// /// Once a floating amoeba is identified, this function just takes it and /// makes it fall until another amoeba is found or the bottom-most line /// is reached. Then it checks if it can make more groups and stores it. /// /// \param floatingAmoebas The floating amoebas to make fall. /// \return A list with all groups made when the amoebas felt. /// std::vector GridStatus::makeFloatingAmoebasFall (const Group &floatingAmoebas) { Group positionsToCheck; for ( Group::const_iterator currentAmoeba = floatingAmoebas.begin () ; currentAmoeba != floatingAmoebas.end () ; ++currentAmoeba ) { int x = currentAmoeba->first; int y = currentAmoeba->second; Amoeba::Colour colour = getAmoebaColourAt (x, y); setAmoebaColourAt (x, y, Amoeba::ColourNone); y = Grid::k_GridHeight - 1; while ( Amoeba::ColourNone != getAmoebaColourAt (x, y) ) { --y; } setAmoebaColourAt (x, y, colour); positionsToCheck.push_back (std::make_pair (x, y)); } // Now check if we can make groups from the floating amoebas once fallen. std::vector floatingGroups; std::set visitedPositions; for ( Group::const_iterator currentPosition = positionsToCheck.begin () ; currentPosition != positionsToCheck.end () ; ++currentPosition ) { Group group; Group ghosts; makeGroup (group, ghosts, currentPosition->first, currentPosition->second, visitedPositions); if ( 4 <= group.size () ) { floatingGroups.push_back (group); floatingGroups.push_back (ghosts); } } return floatingGroups; } /// /// \brief Makes a group of equal coloured amoebas recursively. /// /// \param group The group vector to save the group member found to. /// \param ghosts The ghosts amoebas that are in contact with the group. /// \param x The X position to look for a possible group candidate. /// \param y The Y position to look for a possible group candidate. /// \param visitedPositions The set of the already visited position by the /// algorithm. Initially, this should be empty. /// \param groupColour The colour of the amoeba to be part of the group. /// \param initialAmoeba Tells if the amoeba at \p x and \p y should be the /// first amoeba of the group and use its colour to check /// for more amoebas. /// void GridStatus::makeGroup (Group &group, Group &ghosts, int16_t x, int16_t y, std::set &visitedPositions, Amoeba::Colour groupColour, bool initialAmoeba) { Position currentPosition (std::make_pair (x, y)); // Only check this position if we didn't visited it yet. if ( visitedPositions.end () == visitedPositions.find (currentPosition) ) { // If there's any amoeba at the given position, check if is of the // same colour as the others in the group. Amoeba::Colour colour = getAmoebaColourAt (x, y); if ( Amoeba::ColourNone != colour ) { // Add the current position to the already visited positions. visitedPositions.insert (currentPosition); // If this amoeba is the first one, then we need to use its // colour as the group's colour. if ( initialAmoeba ) { groupColour = colour; } // Can't make groups of ghost amoebas. If this amoeba is // a ghosts, then set it to the ghosts groups and don't look // any further. if ( Amoeba::ColourGhost == colour ) { ghosts.push_back (currentPosition); } // Otherwise, if the amoeba's colour is the same as the // group's colour, add it to the group vector and check for its // neighbours. else if ( colour == groupColour ) { group.push_back (currentPosition); makeGroup (group, ghosts, x, y - 1, visitedPositions, groupColour, false); makeGroup (group, ghosts, x + 1, y, visitedPositions, groupColour, false); makeGroup (group, ghosts, x, y + 1, visitedPositions, groupColour, false); makeGroup (group, ghosts, x - 1, y, visitedPositions, groupColour, false); } } } } /// /// \brief Removes a group of amoebas from the grid. /// /// \param group The group of amoebas to remove from the grid. /// void GridStatus::removeAmoebaGroup (const Group &group) { for ( Group::const_iterator currentPosition = group.begin () ; currentPosition != group.end () ; ++currentPosition ) { setAmoebaColourAt (currentPosition->first, currentPosition->second, Amoeba::ColourNone); } } /// /// \brief Sets the amoeba's colour at a given grid's position. /// /// \param x The X position to set the amoeba's colour to. /// \param y The Y position to set the amoeba's colour to. /// \param colour The amoeba's colour to set at grid's position \p x and \p y. /// inline void GridStatus::setAmoebaColourAt (int16_t x, int16_t y, Amoeba::Colour colour) { if ( 0 <= x && x < Grid::k_GridWidth && 0 <= y && y < Grid::k_GridHeight ) { m_State[y * Grid::k_GridWidth + x] = colour; } }