#include "game.h" #include #include #include #include #ifdef DEBUG #include #endif Game::p Game::create(Client::p player_1, Client::p player_2, Client::p player_3, Client::p player_4) { Game::p p(new Game(player_1, player_2, player_3, player_4)); p->start(); return p; } Game::~Game() { std::cout << "Game destroyed." << std::endl; } Game::Game(Client::p player_1, Client::p player_2, Client::p player_3, Client::p player_4) { players[0].client = player_1; players[1].client = player_2; players[2].client = player_3; players[3].client = player_4; } void Game::handle_ready() { if(--awaiting_players) { return; } std::cout << "All ready!" << std::endl; round_start(); } void Game::start() { std::cout << "Started a game with " << players[0].client->nick() << ", " << players[1].client->nick() << ", " << players[2].client->nick() << " and " << players[3].client->nick() << "." << std::endl; awaiting_players = 4; std::vector pl; pl.push_back(players[0].client->nick()); pl.push_back(players[1].client->nick()); pl.push_back(players[2].client->nick()); pl.push_back(players[3].client->nick()); players[0].client->game_start(boost::bind(&Game::handle_ready, shared_from_this()), pl); pl.erase(pl.begin()); pl.push_back(players[0].client->nick()); players[1].client->game_start(boost::bind(&Game::handle_ready, shared_from_this()), pl); pl.erase(pl.begin()); pl.push_back(players[1].client->nick()); players[2].client->game_start(boost::bind(&Game::handle_ready, shared_from_this()), pl); pl.erase(pl.begin()); pl.push_back(players[2].client->nick()); players[3].client->game_start(boost::bind(&Game::handle_ready, shared_from_this()), pl); } void Game::round_start() { // Build a new wall. wall.build(); // Clear previous dora and draw a new. dora.clear(); dora.push_back(wall.take_one()); kan_dora_pending = false; // Notify players of round start. // TODO: Tell them where wall is broken. players[0].round_start(); players[1].round_start(); players[2].round_start(); players[3].round_start(); // Draw hands. for(int i = 0; i < 13; i++) { players[0].draw(wall.take_one()); players[1].draw(wall.take_one()); players[2].draw(wall.take_one()); players[3].draw(wall.take_one()); } // Sort hands. players[0].hand.sort(); players[1].hand.sort(); players[2].hand.sort(); players[3].hand.sort(); // Give east an extra tile. players[0].draw(wall.take_one()); current_player = 0; round_update_draw(); } void Game::round_update_draw() { // Get possible actions for current player. Actions possible_actions = players[current_player].get_actions_draw(); // Construct and send state to each client. PlayerNum player = 0; do { Player::State state[4]; state[0] = players[player].get_state(); state[1] = players[player + 1].get_state_filtered(); state[2] = players[player + 2].get_state_filtered(); state[3] = players[player + 3].get_state_filtered(); Actions a; if(player == current_player) { a = possible_actions; } players[player].client->round_state(state[0], state[1], state[2], state[3], dora, a); } while(++player); // Await action from client. players[current_player].client->get_action(boost::bind(&Game::handle_action_draw, shared_from_this(), _1), possible_actions); } void Game::round_update_discard() { Tile discarded_tile = players[current_player].last_discard(); std::map possible_actions; // Construct and send state to each client. PlayerNum player = 0; do { Player::State state[4]; state[0] = players[player].get_state(); state[1] = players[player + 1].get_state_filtered(); state[2] = players[player + 2].get_state_filtered(); state[3] = players[player + 3].get_state_filtered(); Actions a; if(player != current_player) { if(a = players[player].get_actions_discard(discarded_tile, current_player - player)) { possible_actions[player] = a; } } players[player].client->round_state(state[0], state[1], state[2], state[3], dora, a); } while(++player); preceding_action = Action::Pass; if(possible_actions.empty()) { awaiting_players = 1; handle_action_discard(Action::Pass, 0); } else { awaiting_players = possible_actions.size(); for(std::map::iterator it = possible_actions.begin(); it != possible_actions.end(); it++) { players[it->first].client->get_action(boost::bind(&Game::handle_action_discard, shared_from_this(), _1, it->first), it->second); } } } void Game::handle_action_draw(Action action) { switch(action.type) { case Action::Discard: { players[current_player].discard(action.target_offset); if(kan_dora_pending) { kan_dora_pending = false; dora.push_back(wall.take_one()); } round_update_discard(); } break; case Action::Riichi: case Action::Kan: case Action::Tsumo: case Action::Draw: // Not implemented yet. // Will never occur on draw: case Action::Pass: case Action::Chi: case Action::Pon: case Action::Ron: break; } } void Game::handle_action_discard(Action action, int player) { switch(preceding_action.type) { case Action::Pass: case Action::Chi: if(action.type == Action::Chi) { preceding_action = action; preceding_action_owner = player; break; } case Action::Kan: if(action.type == Action::Kan) { preceding_action = action; preceding_action_owner = player; break; } case Action::Pon: if(action.type == Action::Pon) { preceding_action = action; preceding_action_owner = player; break; } case Action::Ron: if(action.type == Action::Ron) { // TODO: Handle simultanous ron. preceding_action = action; preceding_action_owner = player; break; } break; // Will never occur on discard: case Action::Discard: case Action::Riichi: case Action::Tsumo: case Action::Draw: break; } if(--awaiting_players) { return; } switch(preceding_action.type) { case Action::Pass: { // Tile not claimed, next player draws. players[++current_player].draw(wall.take_one()); round_update_draw(); } break; case Action::Chi: { players[preceding_action_owner].make_chi(players[current_player].claim(), preceding_action.target_offset); current_player = preceding_action_owner; round_update_draw(); } break; case Action::Pon: { players[preceding_action_owner].make_pon(players[current_player].claim(), preceding_action.target_offset - 1, current_player - preceding_action_owner); current_player = preceding_action_owner; round_update_draw(); } break; case Action::Kan: { players[preceding_action_owner].make_kan(players[current_player].claim(), preceding_action.target_offset - 2, current_player - preceding_action_owner); current_player = preceding_action_owner; kan_dora_pending = true; round_update_draw(); } break; case Action::Ron: // Not implemented yet. std::cout << "Hei, morn, god dag!" << std::endl; break; // Will never occur on discard: case Action::Discard: case Action::Riichi: case Action::Tsumo: case Action::Draw: break; } } void Game::round_end() { // Flere runder? round_start() // Ferdig? game_end() players[0].client->round_end(); players[1].client->round_end(); players[2].client->round_end(); players[3].client->round_end(); #ifdef DEBUG time_t current_time = std::time(0); std::cout << std::ctime(¤t_time) << " Round is over..." << std::endl; #endif } void Game::Player::round_start() { // Reset contents. hand.clear(); open.clear(); pond.clear(); riichi = false; // Notify client of round start. client->round_start(); } Game::Player::State Game::Player::get_state() { State state = {hand, open, pond}; return state; } Game::Player::State Game::Player::get_state_filtered() { State state = {hand, open, pond}; return state; } Actions Game::Player::get_actions_draw() { Actions possible_actions; // Check if player can force a draw. // Check if player can tsumo. if(can_tsumo()) { possible_actions.push_back(Action(Action::Tsumo)); } // Check if player can declare a concealed kan or extend an open pon. List all possibilities. if(!riichi) { // Check if player can riichi. // List all tiles that can be discarded. for(std::size_t i = 0; i < hand.size(); i++) { possible_actions.push_back(Action(Action::Discard, Action::Hand, i)); } } else { // Only tile that can be discarded is the last drawn one. possible_actions.push_back(Action(Action::Discard, Action::Hand, hand.size() - 1)); } return possible_actions; } Actions Game::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::Hand, *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::Hand, target + 1)); // Check if tile can be called for a kan. if(can_kan(tile, target)) { possible_actions.push_back(Action(Action::Kan, Action::Hand, 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; } Game::Player::Targets Game::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 Game::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; } //Game::Player::Targets Game::Player::can_kan() { // return Targets(); // TODO //} bool Game::Player::can_kan(Tile tile, int target) { if(std::size_t(target + 2) < hand.size() && hand[target + 2] == tile) { return true; } return false; } //! Recursive function that eats away sets from a hand to determine if it's valid or not. bool complete_hand(Tiles tiles, bool pair_eaten = false) { if(!tiles) { return true; // All tiles eaten. } if(tiles[0] == tiles[1]) { if(!pair_eaten) { // We can eat a pair of tiles. Tiles t = tiles; t.erase(t.begin(), t.begin() + 2); if(complete_hand(t, true)) { return true; } } if(tiles[1] == tiles[2]) { // We can eat a pon of tiles. Tiles t = tiles; t.erase(t.begin(), t.begin() + 3); if(complete_hand(t, pair_eaten)) { return true; } } } Tile::Set set = tiles[0].get_set(); int num = tiles[0].get_num(); if(set == Tile::Honor || num > 7) { // Can't be chi-ed. return false; } Tiles::iterator it1 = std::find(tiles.begin(), tiles.end(), Tile(set, num + 1)); if(it1 != tiles.end()) { Tiles::iterator it2 = std::find(tiles.begin(), tiles.end(), Tile(set, num + 2)); if(it2 != tiles.end()) { // We can eat a chi of tiles. tiles.erase(it2); tiles.erase(it1); tiles.erase(tiles.begin()); return complete_hand(tiles, pair_eaten); } } return false; } bool Game::Player::can_tsumo() { return complete_hand(hand); } bool Game::Player::can_ron(Tile tile) { Tiles tiles = hand; tiles.push_back(tile); tiles.sort(); return complete_hand(tiles); } void Game::Player::draw(Tile tile) { hand.push_back(tile); } void Game::Player::discard(int target) { Tile tile = hand[target]; hand.erase(hand.begin() + target); hand.sort(); pond.push_back(tile); } Tile Game::Player::last_discard() { return pond.back(); } Tile Game::Player::claim() { Tile& t = pond.back(); t.invisible = true; return t; } void Game::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(chi); } void Game::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(pon); } void Game::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(kan); }