diff options
-rw-r--r-- | server/game.h | 3 | ||||
-rw-r--r-- | server/hand.cpp | 92 | ||||
-rw-r--r-- | server/hand.h | 12 | ||||
-rw-r--r-- | server/player.cpp | 28 |
4 files changed, 133 insertions, 2 deletions
diff --git a/server/game.h b/server/game.h index 6c986f5..1de6c73 100644 --- a/server/game.h +++ b/server/game.h @@ -50,6 +50,9 @@ class Game : public boost::enable_shared_from_this<Game> { typedef std::vector<int> Targets; + //! Check if player can declare riichi. + bool can_riichi(); + //! Check if tile can be called for a chi. Targets can_chi(Tile tile); diff --git a/server/hand.cpp b/server/hand.cpp index 393a6c0..abdeb4b 100644 --- a/server/hand.cpp +++ b/server/hand.cpp @@ -4,6 +4,10 @@ bool Hand::agari(const Tiles& tiles) { return basic_format(tiles); } +bool Hand::tenpai(const Tiles& tiles) { + return basic_format_tenpai(tiles); +} + bool Hand::basic_format(const Tiles& tiles, bool pair_eaten) { if(!tiles) { return true; // All tiles eaten. @@ -34,6 +38,90 @@ bool Hand::basic_format(const Tiles& tiles, bool pair_eaten) { return false; } +bool Hand::basic_format_tenpai(const Tiles& tiles, bool pair_eaten, bool wait_eaten) { + if(!tiles) { + return true; + } + + if(!pair_eaten && !wait_eaten) { + if(basic_format_tenpai(eat_tanki(tiles), true, true)) { + return true; + } + } + + if(tiles.size() < 2) { + return false; + } + + if(!pair_eaten) { + if(Tiles rest = eat_pair(tiles)) { + if(basic_format_tenpai(rest, true, wait_eaten)) { + return true; + } + } + } + + if(!wait_eaten) { + if(Tiles rest = eat_chi_wait(tiles)) { + if(basic_format_tenpai(rest, pair_eaten, true)) { + return true; + } + } + } + + if(tiles.size() < 3) { + return false; + } + + if(Tiles rest = eat_pon(tiles)) { + if(basic_format_tenpai(rest, pair_eaten, wait_eaten)) { + return true; + } + } + + if(Tiles rest = eat_chi(tiles)) { + return basic_format_tenpai(rest, pair_eaten, wait_eaten); + } + + return false; +} + +Tiles Hand::eat_tanki(Tiles tiles) { + tiles.erase(tiles.begin()); + return tiles; +} + +Tiles Hand::eat_chi_wait(Tiles tiles) { + Tile::Set set = tiles.front().get_set(); + int num = tiles.front().get_num(); + + if(set == Tile::Honor || num > 8) { + return Tiles(); + } + + // Look for T+1. + Tiles::iterator it = std::find(tiles.begin(), tiles.end(), Tile(set, num + 1)); + if(it != tiles.end()) { + tiles.erase(it); + tiles.erase(tiles.begin()); + return tiles; + } + + if(num > 7) { + return Tiles(); + } + + // Look for T+2. + it = std::find(tiles.begin(), tiles.end(), Tile(set, num + 2)); + if(it != tiles.end()) { + tiles.erase(it); + tiles.erase(tiles.begin()); + return tiles; + } + + return Tiles(); +} + Tiles Hand::eat_pair(Tiles tiles) { if(tiles[0] == tiles[1]) { tiles.erase(tiles.begin(), tiles.begin() + 2); @@ -51,8 +139,8 @@ Tiles Hand::eat_pon(Tiles tiles) { } Tiles Hand::eat_chi(Tiles tiles) { - Tile::Set set = tiles[0].get_set(); - int num = tiles[0].get_num(); + Tile::Set set = tiles.front().get_set(); + int num = tiles.front().get_num(); if(set == Tile::Honor || num > 7) { // Can't be chi-ed. diff --git a/server/hand.h b/server/hand.h index 46ae40a..e38a387 100644 --- a/server/hand.h +++ b/server/hand.h @@ -7,9 +7,21 @@ namespace Hand { //! Check if the tiles constitute a complete hand. Also valid for the concealed part of an open hand. bool agari(const Tiles& tiles); + //! Check if the tiles miss only one from constituting a complete hand. Also valid for the concealed part of an open hand. + bool tenpai(const Tiles& tiles); + // Check if the tiles is matching the normal format of one pair and rest triplets. bool basic_format(const Tiles& tiles, bool pair_eaten = false); + // Check if the tiles is matching the normal format but missing one. + bool basic_format_tenpai(const Tiles& tiles, bool pair_eaten = false, bool wait_eaten = false); + + // Eat a single tile (i.e. the tanki machi) from beginning of list and return rest. + Tiles eat_tanki(Tiles tiles); + + // Eat two tiles waiting for a third to complete a chi (i.e. ryanmen, penchan or kanchan machi) if possible. + Tiles eat_chi_wait(Tiles tiles); + // Eat a pair from beginning of list if possible and return rest, else return empty list. Tiles eat_pair(Tiles tiles); diff --git a/server/player.cpp b/server/player.cpp index 4407095..37dc3bc 100644 --- a/server/player.cpp +++ b/server/player.cpp @@ -36,6 +36,9 @@ Actions Game::Player::get_actions_draw() { if(!riichi) { // Check if player can riichi. + if(can_riichi()) { + possible_actions.push_back(Action(Action::Riichi)); + } // List all tiles that can be discarded. for(std::size_t i = 0; i < hand.size(); i++) { @@ -89,6 +92,31 @@ Actions Game::Player::get_actions_discard(Tile tile, PlayerNum discarder) { return possible_actions; } +bool Game::Player::can_riichi() { + if(open) { + return false; + } + + Tiles tiles = hand; + + // Take first tile out of the set. + Tile out = tiles.front(); + tiles.erase(tiles.begin()); + + // Iterate over the rest of the set, exchanging the tile until a tenpai is found or all is tested. + for(Tiles::iterator it = tiles.begin(); it != tiles.end(); it++) { + if(Hand::tenpai(tiles)) { + return true; + } + + // TODO: Skip unnecessary tests if player got several equal tiles on hand. + + std::swap(*it, out); + } + + return false; +} + Game::Player::Targets Game::Player::can_chi(Tile tile) { Targets targets; |