summaryrefslogtreecommitdiff
path: root/src/player.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/player.cpp')
-rw-r--r--src/player.cpp595
1 files changed, 595 insertions, 0 deletions
diff --git a/src/player.cpp b/src/player.cpp
new file mode 100644
index 0000000..a267b1d
--- /dev/null
+++ b/src/player.cpp
@@ -0,0 +1,595 @@
+#include "game.h"
+
+#include "hand.h"
+
+#include <boost/assign/list_of.hpp>
+using boost::assign::list_of;
+
+void Player::round_start(int w) {
+ // Reset contents.
+ hand.clear();
+ open.clear();
+ pond.clear();
+ tenpai_indexes.clear();
+ riichi = false;
+ score = 25000;
+ wind = w;
+ won = 0;
+
+ // Notify client of round start.
+ client->round_start();
+}
+
+PlayerState Player::get_state() {
+ Tilegroups h;
+ h.push_back(hand);
+
+ for(Sets::iterator it = open.begin(); it != open.end(); it++) {
+ h.push_back(it->tiles);
+ }
+
+ PlayerState state = {h, pond, riichi, score, wind};
+ return state;
+}
+
+PlayerState Player::get_state_filtered() {
+ Tiles hand_filtered;
+ for(int i = 0; i < hand.size(); i++) {
+ hand_filtered.push_back(Tile::Back);
+ }
+
+ Tilegroups h;
+ h.push_back(hand_filtered);
+
+ for(Sets::iterator it = open.begin(); it != open.end(); it++) {
+ h.push_back(it->tiles);
+ }
+
+ PlayerState state = {h, pond, riichi, score, wind};
+ return state;
+}
+
+Actions Player::get_actions_draw() {
+ Actions possible_actions;
+
+ // Check if player can force a draw.
+ // TODO: Implementation. (Low priority)
+
+ // Check if player can tsumo.
+ if(can_tsumo()) {
+ possible_actions.push_back(Action(Action::Tsumo));
+ }
+
+ // Check if player can declare a concealed kan.
+ Targets targets = can_kan();
+ for(Targets::iterator it = targets.begin(); it != targets.end(); it++) {
+ possible_actions.push_back(Action(Action::Kan, Action::Index, *it + 2));
+ }
+
+ // Check if player can extend an open pon.
+ targets = can_kan_extend();
+ for(Targets::iterator it = targets.begin(); it != targets.end(); it++) {
+ possible_actions.push_back(Action(Action::Kan, Action::Group, *it));
+ }
+
+ 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++) {
+ // Skip tiles already used to call kan.
+ if(possible_actions.contains(Action(Action::Kan, Action::Index, i))) {
+ continue;
+ }
+
+ possible_actions.push_back(Action(Action::Discard, Action::Index, i));
+ }
+
+ } else {
+ if(tenpai_indexes) {
+ for(List<int>::iterator it = tenpai_indexes.begin(); it != tenpai_indexes.end(); it++) {
+ possible_actions.push_back(Action(Action::Discard, Action::Index, *it));
+ }
+ tenpai_indexes.clear();
+ } else {
+ // Only tile that can be discarded is the last drawn one.
+ possible_actions.push_back(Action(Action::Discard, Action::Index, hand.size() - 1));
+ }
+ }
+
+ return possible_actions;
+}
+
+Actions Player::get_actions_discard(Tile tile, PlayerNum discarder) {
+ Actions possible_actions;
+
+ if(!riichi) {
+ // Check if tile can be called for a chi. Enumerate all combinations.
+ if(discarder == 3) {
+ Targets targets = can_chi(tile);
+ if(!targets.empty()) {
+ for(Targets::iterator it = targets.begin(); it != targets.end(); it++) {
+ possible_actions.push_back(Action(Action::Chi, Action::Index, *it));
+ }
+ }
+ }
+
+ // Check if tile can be called for a pon.
+ int target = can_pon(tile);
+ if(target >= 0) {
+ possible_actions.push_back(Action(Action::Pon, Action::Index, target + 1));
+
+ // Check if tile can be called for a kan.
+ if(can_kan(tile, target)) {
+ possible_actions.push_back(Action(Action::Kan, Action::Index, target + 2));
+ }
+ }
+ }
+
+ // Check if tile can be ron-ed.
+ if(can_ron(tile)) {
+ possible_actions.push_back(Action(Action::Ron));
+ }
+
+ // If any action is possible, add option to pass.
+ if(possible_actions) {
+ possible_actions.push_back(Action::Pass);
+ }
+
+ return possible_actions;
+}
+
+bool Player::can_riichi() {
+ if(open) {
+ return false;
+ }
+
+ // Iterate over the hand, testing to remove each tile and see if it gives tenpai.
+ for(Tiles::iterator it = hand.begin(); it != hand.end(); it++) {
+ Tiles tiles = hand;
+ tiles.del(it - hand.begin());
+ tiles.sort();
+
+ if(Hand::tenpai(tiles)) {
+ tenpai_indexes.push_back(it - hand.begin());
+ }
+ }
+
+ return bool(tenpai_indexes);
+}
+
+Player::Targets Player::can_chi(Tile tile) {
+ Targets targets;
+
+ Tile::Set set = tile.get_set();
+ int num = tile.get_num();
+
+ // Check if tile actually can be chi-ed.
+ if(set == Tile::Honor) {
+ return targets;
+ }
+
+ bool have_above = false;
+
+ // Check if we have tile below.
+ Tiles::iterator it = std::find(hand.begin(), hand.end(), Tile(set, num - 1));
+ if(num > 1 && it != hand.end()) {
+
+ // Check if we also have tile below tile below.
+ Tiles::iterator it2 = std::find(hand.begin(), hand.end(), Tile(set, num - 2));
+ if(num > 2 && it2 != hand.end()) {
+ targets.push_back(it2 - hand.begin()); // (T-2 T-1 T)
+ }
+
+ // Check if we have tile above.
+ it2 = std::find(hand.begin(), hand.end(), Tile(set, num + 1));
+ if(num < 9 && it2 != hand.end()) {
+ targets.push_back(it - hand.begin()); // (T-1 T T+1)
+ have_above = true;
+ it = it2;
+ }
+ }
+
+ // Check if we have tile above.
+ if(have_above || (it = std::find(hand.begin(), hand.end(), Tile(set, num + 1))) != hand.end()) {
+ // Check if we have tile above tile above.
+ Tiles::iterator it2 = std::find(hand.begin(), hand.end(), Tile(set, num + 2));
+ if(num < 8 && it2 != hand.end()) {
+ targets.push_back(it - hand.begin()); // (T T+1 T+2)
+ }
+ }
+
+ return targets;
+}
+
+int Player::can_pon(Tile tile) {
+ Tiles::iterator it = std::find(hand.begin(), hand.end(), tile);
+ if(it + 1 < hand.end() && it[1] == tile) {
+ return it - hand.begin();
+ }
+
+ return -1;
+}
+
+Player::Targets Player::can_kan() {
+ Targets targets;
+
+ for(Tiles::iterator it = hand.begin(); it != hand.end(); it++) {
+ Tiles::iterator it_s = it;
+ int i = 1;
+ do {
+ it_s = std::find(it_s + 1, hand.end(), *it);
+ } while(it_s != hand.end() && i++);
+ if(i >= 4) {
+ targets.push_back(it - hand.begin());
+ }
+ }
+
+ return targets;
+}
+
+Player::Targets Player::can_kan_extend() {
+ Targets targets;
+
+ for(Sets::iterator it = open.begin(); it != open.end(); it++) {
+ if(it->type == Set::Pon && hand.contains(it->tiles.front())) {
+ targets.push_back(it - open.begin() + 1);
+ }
+ }
+
+ return targets;
+}
+
+bool Player::can_kan(Tile tile, int target) {
+ if(std::size_t(target + 2) < hand.size() && hand[target + 2] == tile) {
+ return true;
+ }
+ return false;
+}
+
+bool Player::can_tsumo() {
+ Tiles tiles = hand;
+ tiles.sort();
+ return Hand::agari(tiles);
+}
+
+bool Player::can_ron(Tile tile) {
+ // Check furiten.
+ if(pond.contains(tile)) {
+ return false;
+ }
+ // TODO: Furiten due to unclaimed discard in last go-around.
+
+ Tiles tiles = hand;
+ tiles.push_back(tile);
+ tiles.sort();
+ return Hand::agari(tiles);
+}
+
+void Player::draw(Tile tile) {
+ hand.push_back(tile);
+}
+
+void Player::discard(int target) {
+ Tile tile = hand[target];
+ hand.erase(hand.begin() + target);
+ hand.sort();
+ pond.push_back(tile);
+}
+
+Tile Player::last_discard() {
+ return pond.back();
+}
+
+Tile Player::claim() {
+ Tile& t = pond.back();
+ t.invisible = true;
+ return t;
+}
+
+void Player::declare_riichi() {
+ riichi = true;
+}
+
+void Player::make_chi(Tile tile, int target) {
+ Tiles chi;
+
+ tile.rotated = true;
+ chi.push_back(tile);
+
+ Tile::Set set = tile.get_set();
+ int num = tile.get_num();
+
+ Tiles::iterator it = hand.begin();
+
+ switch(hand[target].get_num() - num) {
+ case -2:
+ it = std::find(it, hand.end(), Tile(set, num - 2));
+ chi.push_back(*it);
+ it = hand.erase(it);
+
+ case -1:
+ it = std::find(it, hand.end(), Tile(set, num - 1));
+ chi.push_back(*it);
+ it = hand.erase(it);
+
+ case 1:
+ if(chi.size() == 3) {
+ break;
+ }
+
+ it = std::find(it, hand.end(), Tile(set, num + 1));
+ chi.push_back(*it);
+ it = hand.erase(it);
+
+ if(chi.size() == 3) {
+ break;
+ }
+
+ it = std::find(it, hand.end(), Tile(set, num + 2));
+ chi.push_back(*it);
+ hand.erase(it);
+ }
+
+ open.push_back(Set(Set::Chi, chi, true));
+}
+
+void Player::make_pon(Tile tile, int target, PlayerNum discarder) {
+ Tiles pon;
+
+ tile.rotated = true;
+
+ if(discarder == 3) {
+ pon.push_back(tile);
+ }
+
+ pon.push_back(hand[target]);
+ hand.del(target);
+
+ if(discarder == 2) {
+ pon.push_back(tile);
+ }
+
+ pon.push_back(hand[target]);
+ hand.del(target);
+
+ if(discarder == 1) {
+ pon.push_back(tile);
+ }
+
+ open.push_back(Set(Set::Pon, pon, true));
+}
+
+void Player::make_kan(Tile tile, int target, PlayerNum discarder) {
+ Tiles kan;
+
+ tile.rotated = true;
+
+ if(discarder == 3) {
+ kan.push_back(tile);
+ }
+
+ kan.push_back(hand[target]);
+ hand.del(target);
+
+ if(discarder == 2) {
+ kan.push_back(tile);
+ }
+
+ kan.push_back(hand[target]);
+ hand.del(target);
+
+ kan.push_back(hand[target]);
+ hand.del(target);
+
+ if(discarder == 1) {
+ kan.push_back(tile);
+ }
+
+ open.push_back(Set(Set::Kan, kan, true));
+}
+
+void Player::make_kan(int target) {
+ Tiles kan;
+
+ Tiles::iterator it = hand.begin() + target;
+ Tile t = *it;
+ kan.push_back(Tile::Back);
+ it = hand.erase(it);
+
+ it = std::find(it, hand.end(), t);
+ kan.push_back(*it);
+ it = hand.erase(it);
+
+ it = std::find(it, hand.end(), t);
+ kan.push_back(*it);
+ it = hand.erase(it);
+
+ it = std::find(it, hand.end(), t);
+ kan.push_back(Tile::Back);
+ hand.erase(it);
+
+ open.push_back(Set(Set::Kan, kan, false));
+}
+
+void Player::make_kan_extend(int target) {
+ Set& set = open[target - 1];
+
+ Tiles::iterator it = std::find(hand.begin(), hand.end(), set.tiles.front());
+ Tile t = *it;
+ hand.erase(it);
+
+ t.rotated = true;
+
+ for(it = set.tiles.begin(); it != set.tiles.end(); it++) {
+ if(it->rotated) {
+ set.tiles.insert(it + 1, t);
+ break;
+ }
+ }
+
+ set.type = Set::Kan;
+}
+
+void Player::declare_ron(Tile tile) {
+ hand.push_back(tile);
+ // TODO: Mark winning tile.
+ hand.sort();
+
+ List<Sets> hands = Hand::get_breakdowns(hand);
+
+ Sets hand = hands.front();
+ hand.insert(hand.end(), open.begin(), open.end());
+
+ List<std::pair<std::string, int> > han;
+
+ Score score = calculate_score(hand, han, false);
+
+ won = true;
+ won_han = han;
+ won_value = score;
+}
+
+void Player::declare_tsumo() {
+ // TODO: Mark winning tile.
+ hand.sort();
+
+ List<Sets> hands = Hand::get_breakdowns(hand);
+
+ Sets hand = hands.front();
+ hand.insert(hand.end(), open.begin(), open.end());
+
+ List<std::pair<std::string, int> > han;
+
+ Score score = calculate_score(hand, han, true);
+
+ won = true;
+ won_han = han;
+ won_value = score;
+}
+
+Score Player::calculate_score(const Sets& hand, List<std::pair<std::string, int> >& han, bool tsumo) {
+ Tiles yakuhai = list_of(Tile::Chun)(Tile::Hatsu)(Tile::Haku); // TODO: Add seat and prevalent wind.
+ Tiles dora; // TODO: Fill this.
+ Tiles uradora; // TODO: Fill this.
+
+ Score score(0, 20, 0); // Always 20 fu for winning.
+
+ // Check riichi.
+ if(riichi) {
+ score.yaku += 1;
+ han.push_back(std::make_pair("Riichi", 1));
+ // TODO: Check ippatsu.
+ }
+
+ // Check menzen tsumo.
+ if(tsumo && !open) {
+ score.yaku += 1;
+ han.push_back(std::make_pair("Menzen tsumo", 1));
+ }
+
+ bool possible_tanyao = true;
+ bool possible_toitoi = true;
+ int dora_n = 0;
+ int uradora_n = 0;
+ int akadora_n = 0;
+
+ for(Sets::const_iterator set = hand.begin(); set != hand.end(); set++) {
+ switch(set->type) {
+ case Set::Pair:
+ // Check for pair-related fu.
+ score.fu += 2 * yakuhai.count(set->tiles.front());
+
+ break;
+
+ case Set::Chi:
+ // Toitoi can't contain chi.
+ possible_toitoi = false;
+ break;
+
+ case Set::Pon:
+ case Set::Kan:
+ // Calculate fu.
+ score.fu += 2 << (!set->open + !set->tiles.front().is_simple() + 2 * (set->type == Set::Kan));
+
+ // Check yakuhai.
+ if(int yakuhai_n = yakuhai.count(set->tiles.front())) {
+ score.yaku += yakuhai_n;
+ han.push_back(std::make_pair("Yakuhai", yakuhai_n)); // TODO: Specify which tile.
+ }
+
+ break;
+ }
+
+ for(Tiles::const_iterator tile = set->tiles.begin(); tile != set->tiles.end(); tile++) {
+ // Check tanyao.
+ if(!tile->is_simple()) {
+ possible_tanyao = false;
+ }
+
+ // Check dora.
+ dora_n += dora.count(*tile);
+
+ // Check uradora.
+ uradora_n += uradora.count(*tile);
+
+ // Check akadora.
+ if(tile->red) {
+ akadora_n += 1;
+ }
+ }
+ }
+
+ // Check tanyao.
+ if(possible_tanyao) {
+ score.yaku += 1;
+ han.push_back(std::make_pair("Tanyao", 1));
+ }
+
+ // Check toitoi.
+ if(possible_toitoi) {
+ score.yaku += 2;
+ han.push_back(std::make_pair("Toitoi", 2));
+ }
+
+ // Check pinfu.
+ if(score.fu == 20) {
+ if(open) {
+ score.fu += 2;
+ } else {
+ score.yaku += 1;
+ han.push_back(std::make_pair("Pinfu", 1));
+ }
+ } else if(tsumo) {
+ score.fu += 2;
+ }
+
+ // Check fu for ron.
+ if(!tsumo && !open) {
+ // 10 fu for ron.
+ score.fu += 10;
+ }
+
+ // Check dora.
+ if(dora_n) {
+ score.dora += dora_n;
+ han.push_back(std::make_pair("Dora", dora_n));
+ }
+
+ // Check uradora.
+ if(uradora_n) {
+ score.dora += uradora_n;
+ han.push_back(std::make_pair("Uradora", uradora_n));
+ }
+
+ // Check akadora.
+ if(akadora_n) {
+ score.dora += akadora_n;
+ han.push_back(std::make_pair("Akadora", akadora_n));
+ }
+
+ return score;
+}