From fae209a9e93400c3a2072befda9c820634cf9278 Mon Sep 17 00:00:00 2001 From: Vegard Storheil Eriksen Date: Sat, 25 Dec 2010 12:54:59 +0100 Subject: Restructured repository. --- src/client.cpp | 213 +++++++++++++++++++ src/client.h | 132 ++++++++++++ src/connection.cpp | 67 ++++++ src/connection.h | 53 +++++ src/game.cpp | 456 ++++++++++++++++++++++++++++++++++++++++ src/game.h | 75 +++++++ src/hand.cpp | 252 +++++++++++++++++++++++ src/hand.h | 43 ++++ src/lobby.cpp | 81 ++++++++ src/lobby.h | 33 +++ src/main.cpp | 13 ++ src/player.cpp | 595 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/player.h | 118 +++++++++++ src/score.cpp | 65 ++++++ src/score.h | 40 ++++ src/tcpserver.cpp | 32 +++ src/tcpserver.h | 27 +++ src/wall.cpp | 42 ++++ src/wall.h | 30 +++ 19 files changed, 2367 insertions(+) create mode 100644 src/client.cpp create mode 100644 src/client.h create mode 100644 src/connection.cpp create mode 100644 src/connection.h create mode 100644 src/game.cpp create mode 100644 src/game.h create mode 100644 src/hand.cpp create mode 100644 src/hand.h create mode 100644 src/lobby.cpp create mode 100644 src/lobby.h create mode 100644 src/main.cpp create mode 100644 src/player.cpp create mode 100644 src/player.h create mode 100644 src/score.cpp create mode 100644 src/score.h create mode 100644 src/tcpserver.cpp create mode 100644 src/tcpserver.h create mode 100644 src/wall.cpp create mode 100644 src/wall.h (limited to 'src') diff --git a/src/client.cpp b/src/client.cpp new file mode 100644 index 0000000..afec605 --- /dev/null +++ b/src/client.cpp @@ -0,0 +1,213 @@ +#include "client.h" + +#include + +std::map > Client::client_list; + +Client::p Client::create(Connection::p c, std::string n) { + Client::p p(new Client(c, n)); + p->start(); + return p; +} + +Client::p Client::exists(Message::Login::p login_msg) { + if(client_list.count(login_msg->cookie)) { + // Obtain shared pointer to existing client. + Client::p client = client_list[login_msg->cookie].lock(); + + // TODO: Check if game still is ongoing? + // A client should only be reconnected to if still participating in an active game. + // It may however be proven that a client is always destructed if the game has ended, so this test might not be neccessary. + + return client; + + } else { + // No or invalid existing client found. + return Client::p(); + } +} + +Client::Client(Connection::p c, std::string n) : connection(c), timer(c->socket.get_io_service()), nick_(n) { + +} + +Client::~Client() { + client_list.erase(cookie); +} + +void Client::reconnect(Connection::p c) { + connection = c; + // TODO: Resync client. +} + +void Client::start() { + // Create a unique cookie for this client. + cookie = uint64_t(this); // TODO: Use a better cookie source. + + // Store a weak pointer to this client. + client_list[cookie] = shared_from_this(); + + // Logged in to lobby. + connection->send(make_shared(Message::LoginResponse::Lobby)); +} + +void Client::handle_login(Message::p msg, boost::function login_callback) { + if(msg->type != Message::Types::Login) { + return; + } + + Message::Login::p login_msg = dynamic_pointer_cast(msg); + + // Check if nick is invalid. + if(login_msg->nick.size() == 0) { + connection->send(make_shared(Message::LoginResponse::Invalid)); + connection->recv(boost::bind(&Client::handle_login, shared_from_this(), _1, login_callback)); + return; + } + + connection->send(make_shared(Message::LoginResponse::Lobby)); + + nick_ = login_msg->nick; + + login_callback(shared_from_this()); +} + +void Client::handle_lobby(Message::p msg, boost::function lobby_callback) { + if(msg->type != Message::Types::LobbyAction) { + return; + } + + Message::LobbyAction::p lobby_msg = dynamic_pointer_cast(msg); + + lobby_callback(lobby_msg->index); +} + +void Client::handle_ready(Message::p msg, boost::function ready_callback) { + if(msg->type != Message::Types::Ready) { + // Wait for another message. + connection->recv(boost::bind(&Client::handle_ready, shared_from_this(), _1, ready_callback)); + return; + } + + ready_callback(); +} + +void Client::handle_action(Message::p msg, boost::function action_callback, Actions possible_actions) { + if(msg->type != Message::Types::RoundAction) { + return; + } + + Message::RoundAction::p action_msg = dynamic_pointer_cast(msg); + + // Check if the action is valid. + if(possible_actions.contains(action_msg->action)) { + action_callback(action_msg->action); + } else { + action_callback(possible_actions.back()); + } +} + +void Client::lobby_status(const std::vector& game_modes, boost::function callback) { + Message::LobbyStatus::p msg = make_shared(); + + msg->game_modes = game_modes; + + connection->send(msg); + + connection->recv(boost::bind(&Client::handle_lobby, shared_from_this(), _1, callback)); +} + +std::string Client::nick() { + return nick_; +} + +void Client::game_start(boost::function callback, std::vector players) { + if(connection) { + Message::GameStart::p msg = make_shared(); + + msg->players = players; + + connection->send(msg); + + connection->recv(boost::bind(&Client::handle_ready, shared_from_this(), _1, callback)); + } else { + callback(); + } +} + +void Client::round_start() { + if(connection) { + connection->send(make_shared()); + } +} + +void Client::round_state(const PlayerState& pl_d, const PlayerState& pl_r, const PlayerState& pl_u, const PlayerState& pl_l, const GameState& g, const Actions& a) { + if(connection) { + connection->send(make_shared(pl_d, pl_r, pl_u, pl_l, g, a)); + } +} + +void Client::round_end(Message::RoundEnd::p msg, boost::function callback) { + if(connection) { + connection->send(msg); + + // Check if we're waiting for ready. + if(callback) { + connection->recv(boost::bind(&Client::handle_ready, shared_from_this(), _1, callback)); + } + } else { + if(callback){callback();} + } +} + +void Client::get_action(boost::function callback, Actions expected_actions) { + if(connection) { + connection->recv(boost::bind(&Client::handle_action, shared_from_this(), _1, callback, expected_actions)); + } else { + callback(expected_actions.back()); + } +} + +void Client::game_end(Message::GameEnd::p msg, boost::function callback) { + if(connection) { + connection->send(msg); + + if(callback) { + connection->recv(boost::bind(&Client::handle_ready, shared_from_this(), _1, callback)); + } + } else { + callback(); + } +} + +unsigned int ClientDumb::id() { + return 0; +} + +std::string ClientDumb::nick() { + return "CPU"; +} + +void ClientDumb::game_start(boost::function callback, std::vector players) { + callback(); +} + +void ClientDumb::round_start() { + +} + +void ClientDumb::round_state(const PlayerState& pl_d, const PlayerState& pl_r, const PlayerState& pl_u, const PlayerState& pl_l, const GameState& g, const Actions& a) { + +} + +void ClientDumb::round_end(Message::RoundEnd::p msg, boost::function callback) { + if(callback) callback(); +} + +void ClientDumb::get_action(boost::function callback, Actions expected_actions) { + callback(expected_actions.back()); +} + +void ClientDumb::game_end(Message::GameEnd::p msg, boost::function callback) { + callback(); +} diff --git a/src/client.h b/src/client.h new file mode 100644 index 0000000..e6cfcc7 --- /dev/null +++ b/src/client.h @@ -0,0 +1,132 @@ +#ifndef CLIENT_H +#define CLIENT_H + +#include +#include +#include +#include +#include +#include + +#include "connection.h" + +//! Abstract client base class. +class ClientBase { + public: + typedef boost::shared_ptr p; + + virtual ~ClientBase() {} + + //! Return client's nick. + virtual std::string nick() = 0; + + //! Notify client of a game start. + virtual void game_start(boost::function callback, std::vector players) = 0; + + //! Notify client of a round start. + virtual void round_start() = 0; + + //! Send round state. + virtual void round_state(const PlayerState& pl_d, const PlayerState& pl_r, const PlayerState& pl_u, const PlayerState& pl_l, const GameState& g, const Actions& a) = 0; + + //! Send round end. + virtual void round_end(Message::RoundEnd::p msg, boost::function callback) = 0; + + //! Get action. Upon connection error, last element of expected_actions will be provided. + virtual void get_action(boost::function callback, Actions expected_actions) = 0; + + virtual void game_end(Message::GameEnd::p msg, boost::function callback) = 0; + +}; + +//! Class implementing ClientBase for real clients. +class Client : public ClientBase, public boost::enable_shared_from_this { + public: + typedef boost::shared_ptr p; + + //! Construct and return a new Client instance. + static p create(Connection::p c, std::string n); + + //! Check if a Client instance of the user already exists and return it. + static p exists(Message::Login::p login_msg); + + ~Client(); + + private: + uint64_t cookie; + + static std::map > client_list; + + Connection::p connection; + + boost::asio::deadline_timer timer; + + std::string nick_; + + Client(Connection::p c, std::string n); + + //! Start communicating. + void start(); + + //! Handle Login-message. + void handle_login(Message::p msg, boost::function login_callback); + + //! Handle LobbyAction-message. + void handle_lobby(Message::p msg, boost::function lobby_callback); + + //! Handle Ready-message. + void handle_ready(Message::p msg, boost::function ready_callback); + + //! Handle Action-message. + void handle_action(Message::p msg, boost::function action_callback, Actions expected_actions); + + public: + //! Inform client of lobby status (available game modes). + void lobby_status(const std::vector& game_modes, boost::function callback); + + //! Return client's nick. + virtual std::string nick(); + + //! Notify client of a game start. + virtual void game_start(boost::function callback, std::vector players); + + //! Notify client of a round start. + virtual void round_start(); + + //! Send round state. + virtual void round_state(const PlayerState& pl_d, const PlayerState& pl_r, const PlayerState& pl_u, const PlayerState& pl_l, const GameState& g, const Actions& a); + + //! Send round end. + virtual void round_end(Message::RoundEnd::p msg, boost::function callback); + + //! Get action. Upon connection error, last element of expected_actions will be provided. + virtual void get_action(boost::function callback, Actions expected_actions); + + //! Reconnect a player. + virtual void reconnect(Connection::p c); + + virtual void game_end(Message::GameEnd::p msg, boost::function callback); +}; + +typedef std::vector Clients; + +class ClientDumb : public ClientBase { + public: + virtual unsigned int id(); + + virtual std::string nick(); + + virtual void game_start(boost::function callback, std::vector players); + + virtual void round_start(); + + virtual void round_state(const PlayerState& pl_d, const PlayerState& pl_r, const PlayerState& pl_u, const PlayerState& pl_l, const GameState& g, const Actions& a); + + virtual void round_end(Message::RoundEnd::p msg, boost::function callback); + + virtual void get_action(boost::function callback, Actions expected_actions); + + virtual void game_end(Message::GameEnd::p msg, boost::function callback); +}; + +#endif diff --git a/src/connection.cpp b/src/connection.cpp new file mode 100644 index 0000000..1e38f7b --- /dev/null +++ b/src/connection.cpp @@ -0,0 +1,67 @@ +#include "connection.h" + +#include + +Connection::Connection(boost::asio::io_service& io_service) : socket(io_service) { + +} + +void Connection::handle_read(uint8_t* data, std::size_t bytes, const boost::system::error_code& error_code) { + if(error_code) { + error("Read error."); + return; + } + + got_data(data, bytes); + + delete[] data; +} + +void Connection::handle_write() { + +} + +void Connection::request_data(std::size_t bytes) { + uint8_t* buf = new uint8_t[bytes]; + + boost::asio::async_read(socket, boost::asio::buffer(buf, bytes), + boost::bind(&Connection::handle_read, shared_from_this(), buf, bytes, boost::asio::placeholders::error)); +} + +void Connection::got_message(const Message::p& msg) { + // TODO: Implement message queueing. For now, unexpected messages will cause an error. + + boost::function f = recv_callback; + recv_callback.clear(); + error_callback.clear(); + + f(msg); +} + +void Connection::error(const std::string& msg) { + if(error_callback) { + boost::function f = error_callback; + recv_callback.clear(); + error_callback.clear(); + + f(msg); + } else { + recv_callback.clear(); + } +} + +void Connection::write_data(uint8_t* data, std::size_t bytes) { + boost::asio::async_write(socket, boost::asio::buffer(data, bytes), + boost::bind(&Connection::handle_write, shared_from_this())); +} + +Connection::p Connection::create(boost::asio::io_service& io_service) { + return Connection::p(new Connection(io_service)); +} + +void Connection::recv(boost::function callback, boost::function error) { + recv_callback = callback; + error_callback = error; + + start_recv(); +} \ No newline at end of file diff --git a/src/connection.h b/src/connection.h new file mode 100644 index 0000000..b51a67f --- /dev/null +++ b/src/connection.h @@ -0,0 +1,53 @@ +#ifndef CONNECTION_H +#define CONNECTION_H + +#include +#include +#include + +#include "../common/connectionbase.h" + +class Connection : public ConnectionBase, public boost::enable_shared_from_this { + private: + friend class TCPServer; + friend class Client; + + boost::asio::ip::tcp::socket socket; + + boost::function recv_callback; + boost::function error_callback; + + Connection(boost::asio::io_service& io_service); + + //! Callback for when data is read. + void handle_read(uint8_t* data, std::size_t bytes, const boost::system::error_code& error_code); + + //! Callback for when data is written. + void handle_write(); + + protected: + //! Implements request_data(). + virtual void request_data(std::size_t bytes); + + //! Implements write_data(). + virtual void write_data(uint8_t* data, std::size_t bytes); + + //! Implements got_message(). + virtual void got_message(const Message::p& msg); + + //! Implements error(). + virtual void error(const std::string& msg); + + public: + typedef boost::shared_ptr p; + + //! Constructs a new instance and returns a shared pointer. + static p create(boost::asio::io_service& io_service); + + //! Initiates an asynchronous message receive. + //! \param callback Callback for received message. + //! \param error Callback for error. + void recv(boost::function callback, boost::function error = 0); +}; + +#endif diff --git a/src/game.cpp b/src/game.cpp new file mode 100644 index 0000000..e791379 --- /dev/null +++ b/src/game.cpp @@ -0,0 +1,456 @@ +#include "game.h" + +#include + +#include +#include +#include + +#ifdef DEBUG +#include +#endif + +Game::p Game::create(ClientBase::p player_1, ClientBase::p player_2, ClientBase::p player_3, ClientBase::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(ClientBase::p player_1, ClientBase::p player_2, ClientBase::p player_3, ClientBase::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; + + round_wind = 0; + round_num = 0; + + 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() { + std::cout << "Starting a new round." << std::endl; + + // 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(-round_num); + players[1].round_start(-round_num + 1); + players[2].round_start(-round_num + 2); + players[3].round_start(-round_num + 3); + + // 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 { + PlayerState 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; + } + + GameState gstate = {dora, current_player - player, round_wind, round_num, 0, 0}; + + players[player].client->round_state(state[0], state[1], state[2], state[3], gstate, 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 { + PlayerState 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; + } + } + + GameState gstate = {dora, current_player - player, round_wind, round_num, 0, 0}; + + players[player].client->round_state(state[0], state[1], state[2], state[3], gstate, 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::Tsumo: { + players[current_player].declare_tsumo(); + round_end(Tsumo); + } break; + + case Action::Riichi: { + players[current_player].declare_riichi(); + round_update_draw(); + } break; + + case Action::Kan: { + if(action.target_type == Action::Index) { + players[current_player].make_kan(action.target_offset - 2); + dora.push_back(wall.take_one()); + } else { + players[current_player].make_kan_extend(action.target_offset); + kan_dora_pending = true; + } + players[current_player].draw(wall.take_one()); + round_update_draw(); + } break; + + 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; + } + + // First ron. + if(action.type == Action::Ron) { + preceding_action = action; + preceding_action_owner = player; + break; + } + + // Multiple ron. + case Action::Ron: + if(action.type == Action::Ron && current_player - player > current_player - preceding_action_owner) { + 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. + + // Check if the wall has run out. + if(wall.remaining() <= 14) { + round_end(Draw); + break; + } + + 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; + players[current_player].draw(wall.take_one()); + round_update_draw(); + } break; + + case Action::Ron: { + players[preceding_action_owner].declare_ron(players[current_player].claim()); + round_end(Ron); + } break; + + // Will never occur on discard: + case Action::Discard: + case Action::Riichi: + case Action::Tsumo: + case Action::Draw: + break; + } +} + +Tiles merge_hand_open(const Tiles& hand, const Sets& open) { + Tiles tiles = hand; + for(Sets::const_iterator it = open.begin(); it != open.end(); it++) { + tiles.insert(tiles.end(), it->tiles.begin(), it->tiles.end()); + } + return tiles; +} + +void Game::round_end(Endcondition end) { + + Message::RoundEnd::p msg[4] = make_shared(); + + for(CyclicInt<4> count_player = 0; count_player < 4; count_player++) { + msg[count_player]->total_han = msg[count_player]->total_fu = msg[count_player]->won = msg[count_player]->score[count_player].won = 0; + for(int i = 0; i < 4; i++) { + msg[count_player]->score[count_player + i].score = players[count_player + i].get_state().score; + msg[count_player]->score[count_player + i].won = 0; + } + + switch(end) { + case Draw: + //msg->total_han = msg->total_fu = msg->won = 0; + break; + + case Tsumo: { + Player& player = players[current_player]; + + msg[count_player]->hand = merge_hand_open(player.hand, player.open); + + msg[count_player]->yakus = player.won_han; + + msg[count_player]->total_han = player.won_value.han(); + msg[count_player]->total_fu = player.won_value.fu_rounded(); + + if(current_player == round_num) { + msg[count_player]->won = 3 * player.won_value.tsumo_east(); + + for(int y = 0; y < 4; y++) { + msg[count_player]->score[current_player + y].won = -(msg[count_player]->won / 3); + } + msg[count_player]->score[count_player + current_player ].won = msg[count_player]->won; + + } else { + msg[count_player]->won = player.won_value.tsumo_east() + 2 * player.won_value.tsumo(); + + for(int y = 0; y < 4; y++) { + if(count_player + y == current_player) { + msg[count_player]->score[y].won = msg[count_player]->won; + } else if (count_player + y == round_num){ + msg[count_player]->score[y].won = -(player.won_value.tsumo_east()); + } else { + msg[count_player]->score[y].won = -(player.won_value.tsumo()); + } + } + } + + } break; + + case Ron: + for(int i = 0; i < 4; i++) { + if(players[current_player + i].won) { + Player& player = players[current_player + i]; + + msg[count_player]->hand = merge_hand_open(player.hand, player.open); + + msg[count_player]->yakus = player.won_han; + + msg[count_player]->total_han = player.won_value.han(); + msg[count_player]->total_fu = player.won_value.fu_rounded(); + + if(current_player + i == round_num) { + msg[count_player]->won = player.won_value.ron_east(); + msg[count_player]->score[current_player + i].won = player.won_value.ron_east(); + msg[count_player]->score[current_player].won = -(player.won_value.ron_east()); + + } else { + msg[count_player]->won = player.won_value.ron(); + msg[count_player]->score[current_player + i].won = player.won_value.ron(); + msg[count_player]->score[current_player].won = -(player.won_value.ron()); + } + + // TODO: Support multiple wins. + break; + } + } + break; + } + + //Send med score + msg[count_player]->game_end = false; + if(count_player == 3) break; + } + + round_num++; + + if(!round_num) { + round_wind++; + //Do a 4-round game for now + for(int i = 0; i < 4; ++i) { + msg[i]->game_end = true; + players[i].client->round_end(msg[i], 0); + } + game_end(); + } else { + awaiting_players = 4; + players[0].client->round_end(msg[0], boost::bind(&Game::handle_ready, shared_from_this())); + players[1].client->round_end(msg[1], boost::bind(&Game::handle_ready, shared_from_this())); + players[2].client->round_end(msg[2], boost::bind(&Game::handle_ready, shared_from_this())); + players[3].client->round_end(msg[3], boost::bind(&Game::handle_ready, shared_from_this())); + } + + // Ferdig? game_end() +} + +void Game::game_end() { + Message::GameEnd::p msg = make_shared(); + for(int i = 0; i < 4; i++) { + msg->scores[i].score = players[i].score; + msg->scores[i].won = players[i].score - 25000; + } + + awaiting_players = 4; + players[0].client->game_end(msg, boost::bind(&Game::handle_ready, shared_from_this())); + players[1].client->game_end(msg, boost::bind(&Game::handle_ready, shared_from_this())); + players[2].client->game_end(msg, boost::bind(&Game::handle_ready, shared_from_this())); + players[3].client->game_end(msg, boost::bind(&Game::handle_ready, shared_from_this())); + + +} diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..874a525 --- /dev/null +++ b/src/game.h @@ -0,0 +1,75 @@ +#ifndef GAME_H +#define GAME_H + +#include +#include + +#include + +#include "wall.h" +#include "player.h" + +class Game : public boost::enable_shared_from_this { + public: + typedef boost::shared_ptr p; + + static p create(ClientBase::p player_1, ClientBase::p player_2, ClientBase::p player_3, ClientBase::p player_4); + + ~Game(); + + private: + Player players[4]; + + Wall wall; + + Tiles dora; + + bool kan_dora_pending; + + PlayerNum current_player; + + PlayerNum round_wind; + PlayerNum round_num; + + int awaiting_players; + + Action preceding_action; + int preceding_action_owner; + + Game(ClientBase::p player_1, ClientBase::p player_2, ClientBase::p player_3, ClientBase::p player_4); + + //! Handle Ready message from player. + void handle_ready(); + + //! Start the game. + void start(); + + //! Start a new round. + void round_start(); + + //! Send update after a tile is drawn. + void round_update_draw(); + + //! Send update after a tile is discarded. + void round_update_discard(); + + //! Handle action after draw. + void handle_action_draw(Action action); + + //! Handle actions after discard. + void handle_action_discard(Action action, int player); + + enum Endcondition { + Draw, + Tsumo, + Ron + }; + + //! End the round. + void round_end(Endcondition end); + + //! End the game + void game_end(); +}; + +#endif diff --git a/src/hand.cpp b/src/hand.cpp new file mode 100644 index 0000000..bc4fb7a --- /dev/null +++ b/src/hand.cpp @@ -0,0 +1,252 @@ +#include "hand.h" + +bool Hand::agari(const Tiles& tiles) { + return basic_format(tiles); +} + +bool Hand::tenpai(const Tiles& tiles) { + return basic_format_tenpai(tiles); +} + +List Hand::get_breakdowns(const Tiles& tiles) { + List hands; + + basic_format(tiles, hands); + + return hands; +} + +bool Hand::basic_format(const Tiles& tiles, bool pair_eaten) { + if(!tiles) { + return true; // All tiles eaten. + } + + Tiles rest; + + if(!pair_eaten) { + rest = tiles; + if(eat_pair(rest) && basic_format(rest, true)) { + return true; + } + } + + if(tiles.size() < 3) { + return false; + } + + rest = tiles; + if(eat_pon(rest) && basic_format(rest, pair_eaten)) { + return true; + } + + rest = tiles; + if(eat_chi(rest) && basic_format(rest, pair_eaten)) { + return true; + } + + return false; +} + +void Hand::basic_format(const Tiles& tiles, List& hands, const Sets& hand, bool pair_eaten) { + if(!tiles) { + hands.push_back(hand); + return; // All tiles eaten. + } + + Tiles rest; + Sets new_hand; + + if(!pair_eaten) { + rest = tiles; + new_hand = hand; + if(eat_pair(rest, new_hand)) { + basic_format(rest, hands, new_hand, true); + } + } + + if(tiles.size() < 3) { + return; + } + + rest = tiles; + new_hand = hand; + if(eat_pon(rest, new_hand)) { + basic_format(rest, hands, new_hand, pair_eaten); + } + + rest = tiles; + new_hand = hand; + if(eat_chi(rest, new_hand)) { + basic_format(rest, hands, new_hand, pair_eaten); + } +} + +bool Hand::basic_format_tenpai(const Tiles& tiles, bool pair_eaten, bool wait_eaten) { + if(!tiles) { + return true; + } + + Tiles rest; + + if(!pair_eaten && !wait_eaten) { + rest = tiles; + if(eat_tanki(rest) && basic_format_tenpai(rest, true, true)) { + return true; + } + } + + if(tiles.size() < 2) { + return false; + } + + if(!pair_eaten) { + rest = tiles; + if(eat_pair(rest) && basic_format_tenpai(rest, true, wait_eaten)) { + return true; + } + } + + if(!wait_eaten) { + rest = tiles; + if(eat_chi_wait(rest) && basic_format_tenpai(rest, pair_eaten, true)) { + return true; + } + } + + if(tiles.size() < 3) { + return false; + } + + rest = tiles; + if(eat_pon(rest) && basic_format_tenpai(rest, pair_eaten, wait_eaten)) { + return true; + } + + rest = tiles; + if(eat_chi(rest) && basic_format_tenpai(rest, pair_eaten, wait_eaten)) { + return true; + } + + return false; +} + +bool Hand::eat_tanki(Tiles& tiles) { + tiles.erase(tiles.begin()); + return true; +} + +bool 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 false; + } + + // 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 true; + } + + 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 true; + } + + return false; +} + +bool Hand::eat_pair(Tiles& tiles) { + if(tiles[0] == tiles[1]) { + tiles.erase(tiles.begin(), tiles.begin() + 2); + return true; + } + return false; +} + +bool Hand::eat_pair(Tiles& tiles, Sets& hand) { + if(tiles[0] == tiles[1]) { + hand.push_back(Set(Set::Pair, Tiles(tiles.begin(), tiles.begin() + 2), false)); + tiles.erase(tiles.begin(), tiles.begin() + 2); + return true; + } + return false; +} + +bool Hand::eat_pon(Tiles& tiles) { + if(tiles[0] == tiles[2]) { + tiles.erase(tiles.begin(), tiles.begin() + 3); + return true; + } + return false; +} + +bool Hand::eat_pon(Tiles& tiles, Sets& hand) { + if(tiles[0] == tiles[2]) { + hand.push_back(Set(Set::Pon, Tiles(tiles.begin(), tiles.begin() + 3), false)); + tiles.erase(tiles.begin(), tiles.begin() + 3); + return true; + } + return false; +} + +bool Hand::eat_chi(Tiles& tiles) { + Tile::Set set = tiles.front().get_set(); + int num = tiles.front().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 true; + } + } + return false; +} + +bool Hand::eat_chi(Tiles& tiles, Sets& hand) { + Tile::Set set = tiles.front().get_set(); + int num = tiles.front().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 chi; + chi.push_back(tiles.front()); + chi.push_back(*it1); + chi.push_back(*it2); + hand.push_back(Set(Set::Chi, chi, false)); + tiles.erase(it2); + tiles.erase(it1); + tiles.erase(tiles.begin()); + return true; + } + } + return false; +} diff --git a/src/hand.h b/src/hand.h new file mode 100644 index 0000000..b7ae8d2 --- /dev/null +++ b/src/hand.h @@ -0,0 +1,43 @@ +#ifndef HAND_H +#define HAND_H + +#include "../common/tile.h" +#include "../common/set.h" + +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); + + //! Get list of possible breakdowns of a complete hand. + List get_breakdowns(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); + void basic_format(const Tiles& tiles, List& hands, const Sets& hand = Sets(), 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 success flag. + bool eat_tanki(Tiles& tiles); + + // Eat two tiles waiting for a third to complete a chi (i.e. ryanmen, penchan or kanchan machi) if possible and return success flag. + bool eat_chi_wait(Tiles& tiles); + + // Eat a pair from beginning of list if possible and return success flag. + bool eat_pair(Tiles& tiles); + bool eat_pair(Tiles& tiles, Sets& hand); + + // Eat a pon from beginning of list if possible and return success flag. + bool eat_pon(Tiles& tiles); + bool eat_pon(Tiles& tiles, Sets& hand); + + // Eat a chi from beginning of list if possible and return success flag. + bool eat_chi(Tiles& tiles); + bool eat_chi(Tiles& tiles, Sets& hand); +} + +#endif diff --git a/src/lobby.cpp b/src/lobby.cpp new file mode 100644 index 0000000..9fb769e --- /dev/null +++ b/src/lobby.cpp @@ -0,0 +1,81 @@ +#include "lobby.h" + +#include + +#include + +#include "game.h" + +void Lobby::handle_connect(Connection::p connection) { + // Send Hello. + connection->send(make_shared("aotenjoud git")); + + // Wait for Login. + connection->recv(boost::bind(&Lobby::handle_login, this, connection, _1)); + + // Get another connection. + server.get_connection(boost::bind(&Lobby::handle_connect, this, _1)); +} + +void Lobby::handle_login(Connection::p connection, Message::p msg) { + if(msg->type != Message::Types::Login) { + return; + } + + Message::Login::p login_msg = dynamic_pointer_cast(msg); + + Client::p client; + + // Check if this is a reconnection attempt and whether a reconnection is possible. + if(login_msg->cookie && (client = Client::exists(login_msg))) { + client->reconnect(connection); + return; + } + + // Validate nick. + if(login_msg->nick.size() == 0) { + connection->send(make_shared(Message::LoginResponse::Invalid)); + connection->recv(boost::bind(&Lobby::handle_login, this, connection, _1)); + return; + } + + // Create a new client. + client = Client::create(connection, login_msg->nick); + + std::vector game_modes; + + game_modes.push_back("1p test mode"); + game_modes.push_back("4p test mode"); + + client->lobby_status(game_modes, boost::bind(&Lobby::handle_action, this, client, _1)); +} + +void Lobby::handle_action(Client::p client, int game_mode) { + std::cout << "Client " << client->nick() << " joined a game." << std::endl; + + switch(game_mode) { + case 0: { + Game::create(client, make_shared(), make_shared(), make_shared()); + } break; + + case 1: { + if(waiting.size() >= 3) { + Game::create(waiting[0], waiting[1], waiting[2], client); + waiting.clear(); + } else { + waiting.push_back(client); + } + } break; + } +} + +Lobby::Lobby() : server(io_service) { + +} + +void Lobby::run() { + // Get a connection. + server.get_connection(boost::bind(&Lobby::handle_connect, this, _1)); + + io_service.run(); +} diff --git a/src/lobby.h b/src/lobby.h new file mode 100644 index 0000000..c445506 --- /dev/null +++ b/src/lobby.h @@ -0,0 +1,33 @@ +#ifndef LOBBY_H +#define LOBBY_H + +#include + +#include + +#include "tcpserver.h" +#include "client.h" + +class Lobby { + private: + boost::asio::io_service io_service; + TCPServer server; + + std::vector waiting; + + //! Handle new connection. + void handle_connect(Connection::p connection); + + //! Handle login. + void handle_login(Connection::p connection, Message::p msg); + + //! Handle action. + void handle_action(Client::p client, int game_mode); + + public: + Lobby(); + + void run(); +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..207d6d6 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,13 @@ +#include "lobby.h" + +#include + +int main() { + try { + Lobby lobby; + lobby.run(); + + } catch(std::exception& e) { + std::cerr << "Exception caught: " << e.what() << std::endl; + } +} 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 +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::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 hands = Hand::get_breakdowns(hand); + + Sets hand = hands.front(); + hand.insert(hand.end(), open.begin(), open.end()); + + List > 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 hands = Hand::get_breakdowns(hand); + + Sets hand = hands.front(); + hand.insert(hand.end(), open.begin(), open.end()); + + List > han; + + Score score = calculate_score(hand, han, true); + + won = true; + won_han = han; + won_value = score; +} + +Score Player::calculate_score(const Sets& hand, List >& 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; +} diff --git a/src/player.h b/src/player.h new file mode 100644 index 0000000..a4b3416 --- /dev/null +++ b/src/player.h @@ -0,0 +1,118 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#include "client.h" +#include "score.h" + +#include "../common/set.h" +#include "../common/cyclicint.h" +#include "../common/state.h" +#include "../common/action.h" + +typedef CyclicInt<4> PlayerNum; + +class Player { + public: + ClientBase::p client; + + Tiles hand; + Sets open; + Tiles pond; + bool riichi; + int score; + int wind; + + //! Indexes of tiles that will give tenpai (used after riichi declaration). + List tenpai_indexes; + + //! Player has won. + bool won; + + //! List of han in winning hand. + List > won_han; + + //! Value of winning hand. + Score won_value; + + //! Prepare for a new round. + void round_start(int w); + + //! Get a state snapshot. + PlayerState get_state(); + + //! Get a state snapshot, with concealed tiles filtered. + PlayerState get_state_filtered(); + + //! Get possible actions after a draw. + Actions get_actions_draw(); + + //! Get possible actions on discarded tile. + Actions get_actions_discard(Tile tile, PlayerNum discarder); + + typedef std::vector Targets; + + //! Check if player can declare riichi. + bool can_riichi(); + + //! Check if tile can be called for a chi. + Targets can_chi(Tile tile); + + //! Check if tile can be called for a pon. + int can_pon(Tile tile); + + //! Check if it's possible to make a concealed kan. + Targets can_kan(); + + //! Check if it's possible to extend a pon to a kan. + Targets can_kan_extend(); + + //! Check if tile can be called to kan target. + bool can_kan(Tile tile, int target); + + //! Check if hand is complete. + bool can_tsumo(); + + //! Check if tile can be called to complete hand. + bool can_ron(Tile tile); + + //! Draw tile. + void draw(Tile tile); + + //! Discard tile. + void discard(int target); + + //! Look at last discard in pond. + Tile last_discard(); + + //! Claim last discard from pond. + Tile claim(); + + //! Declare riichi. + void declare_riichi(); + + //! Make chi from tile. + void make_chi(Tile tile, int target); + + //! Make pon from tile. + void make_pon(Tile tile, int target, PlayerNum discarder); + + //! Make open kan from tile. + void make_kan(Tile tile, int target, PlayerNum discarder); + + //! Make concealed kan. + void make_kan(int target); + + //! Make extended kan. + void make_kan_extend(int target); + + //! Declare win on discarded tile. + void declare_ron(Tile tile); + + //! Declare win on self-drawn tile. + void declare_tsumo(); + + //! Calculate score for winning hand combination. + Score calculate_score(const Sets& hand, List >& han, bool tsumo); +}; + +#endif diff --git a/src/score.cpp b/src/score.cpp new file mode 100644 index 0000000..7d0dc41 --- /dev/null +++ b/src/score.cpp @@ -0,0 +1,65 @@ +#include "score.h" + +inline int roundup(int value, int roundto) { + return value % roundto ? value + roundto - value % roundto : value; +} + +inline int max(int x, int y) { + return x < y ? x : y; +} + +Score::Score(int yaku_, int fu_, int dora_) : yaku(yaku_), fu(fu_), dora(dora_) { + +} + +int Score::han() { + return yaku + dora; +} + +int Score::fu_rounded() { + return roundup(fu, 10); +} + +int Score::base_points() { + if(han() < 5) { + // Normal hand. + return max(fu_rounded() << (2 + han()), 2000); + + } else if(han() <= 5) { + // Mangan. + return 2000; + + } else if(han() <= 7) { + // Haneman. + return 3000; + + } else if(han() <= 10) { + // Baiman. + return 4000; + + } else if(han() <= 12) { + // Sanbaiman. + return 6000; + + } else { + // Kazoe yakuman. + return 8000; + + } +} + +int Score::ron() { + return roundup(base_points() * 4, 100); +} + +int Score::ron_east() { + return roundup(base_points() * 6, 100); +} + +int Score::tsumo() { + return roundup(base_points(), 100); +} + +int Score::tsumo_east() { + return roundup(base_points() * 2, 100); +} diff --git a/src/score.h b/src/score.h new file mode 100644 index 0000000..6dee09a --- /dev/null +++ b/src/score.h @@ -0,0 +1,40 @@ +#ifndef SCORE_H +#define SCORE_H + +class Score { + public: + //! Points (doubles). + int yaku; + + //! Minipoints. + int fu; + + //! Bonus points. + int dora; + + //! Constructor. + Score(int yaku_ = 0, int fu_ = 0, int dora_ = 0); + + //! Calculate han. (yaku + dora) + int han(); + + //! Calculate fu rounded up to closest 10. + int fu_rounded(); + + //! Calculate base points. + int base_points(); + + //! Score paid upon ron. (4 BP) + int ron(); + + //! Score paid upon ron by east. (6 BP) + int ron_east(); + + //! Score paid by others upon tsumo by others. (BP) + int tsumo(); + + //! Score paid by east upon tsumo by others or others upon tsumo by east. (2 BP) + int tsumo_east(); +}; + +#endif diff --git a/src/tcpserver.cpp b/src/tcpserver.cpp new file mode 100644 index 0000000..3f34997 --- /dev/null +++ b/src/tcpserver.cpp @@ -0,0 +1,32 @@ +#include "tcpserver.h" + +#include + +TCPServer::TCPServer(boost::asio::io_service& io_service) + : acceptor_(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 12345)) { + + // Start listening for first connection attempt. + //listen(); +} + +void TCPServer::listen() { + Connection::p new_connection = Connection::create(acceptor_.io_service()); + + acceptor_.async_accept(new_connection->socket, + boost::bind(&TCPServer::handle_connection, this, new_connection, boost::asio::placeholders::error)); +} + +void TCPServer::handle_connection(Connection::p connection, const boost::system::error_code& error) { + if(error) { + return; + } + + connect_callback(connection); +} + +void TCPServer::get_connection(boost::function f) { + connect_callback = f; + + // Start listening for a connection attempt. + listen(); +} diff --git a/src/tcpserver.h b/src/tcpserver.h new file mode 100644 index 0000000..47c5056 --- /dev/null +++ b/src/tcpserver.h @@ -0,0 +1,27 @@ +#ifndef TCPSERVER_H +#define TCPSERVER_H + +#include +#include + +#include "connection.h" + +class TCPServer { + private: + boost::asio::ip::tcp::acceptor acceptor_; + + boost::function connect_callback; + + //! Listen for incoming connection. + void listen(); + + //! Handle new connection. + void handle_connection(Connection::p connection, const boost::system::error_code& error); + + public: + TCPServer(boost::asio::io_service& io_service); + + void get_connection(boost::function f); +}; + +#endif diff --git a/src/wall.cpp b/src/wall.cpp new file mode 100644 index 0000000..42df094 --- /dev/null +++ b/src/wall.cpp @@ -0,0 +1,42 @@ +#include "wall.h" + +#include +#include +#include + +#include + +Wall::Wall() : rand_gen(std::time(0)) { + +} + +void Wall::build() { + // Clear any previous wall. + wall.clear(); + + for(Tile tile = Tile::Man_1; tile.type <= Tile::Hatsu; tile++) { + wall.push_back(Tile(tile.type, tile.get_num() == 5)); // Insert a copy of the current tile, and make it red if it is 5. + wall.push_back(tile); + wall.push_back(tile); + wall.push_back(tile); + } +} + +int Wall::remaining() { + return wall.size(); +} + +Tile Wall::take_one() { + //return wall.front(); + + boost::uniform_int<> range(0, wall.size() - 1); + + boost::variate_generator > die(rand_gen, range); + + int num = die(); + + Tile to_return = wall[num]; + wall.erase(wall.begin() + num); + + return to_return; +} diff --git a/src/wall.h b/src/wall.h new file mode 100644 index 0000000..aeef031 --- /dev/null +++ b/src/wall.h @@ -0,0 +1,30 @@ +#ifndef WALL_H +#define WALL_H + +#include "../common/tile.h" + +#include +#include + +class Wall { + private: + //! Our random number generator. Initialized with a seed of the current time that the object is constructed. + boost::mt19937 rand_gen; + + //! Contains the remaining tiles in the wall. + std::vector wall; + + public: + Wall(); + + //! Build a new wall. + void build(); + + //! Number of remaining tiles in wall. + int remaining(); + + //! Returns a random tile from the wall. + Tile take_one(); +}; + +#endif // WALL_H -- cgit v1.2.3