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. --- .gitignore | 22 +- SConstruct | 32 +++ common | 2 +- server/SConstruct | 32 --- server/client.cpp | 213 -------------- server/client.h | 132 --------- server/connection.cpp | 67 ----- server/connection.h | 53 ---- server/game.cpp | 456 ------------------------------ server/game.h | 75 ----- server/hand.cpp | 252 ----------------- server/hand.h | 43 --- server/lobby.cpp | 81 ------ server/lobby.h | 33 --- server/main.cpp | 13 - server/player.cpp | 595 --------------------------------------- server/player.h | 118 -------- server/score.cpp | 65 ----- server/score.h | 40 --- server/tcpserver.cpp | 32 --- server/tcpserver.h | 27 -- server/tests/SConscript | 11 - server/tests/calculate_score.cpp | 47 ---- server/tests/test_framework.cpp | 1 - server/wall.cpp | 42 --- server/wall.h | 30 -- 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 ++ tests/SConscript | 11 + tests/calculate_score.cpp | 47 ++++ tests/test_framework.cpp | 1 + 48 files changed, 2462 insertions(+), 2478 deletions(-) create mode 100644 SConstruct delete mode 100644 server/SConstruct delete mode 100644 server/client.cpp delete mode 100644 server/client.h delete mode 100644 server/connection.cpp delete mode 100644 server/connection.h delete mode 100644 server/game.cpp delete mode 100644 server/game.h delete mode 100644 server/hand.cpp delete mode 100644 server/hand.h delete mode 100644 server/lobby.cpp delete mode 100644 server/lobby.h delete mode 100644 server/main.cpp delete mode 100644 server/player.cpp delete mode 100644 server/player.h delete mode 100644 server/score.cpp delete mode 100644 server/score.h delete mode 100644 server/tcpserver.cpp delete mode 100644 server/tcpserver.h delete mode 100644 server/tests/SConscript delete mode 100644 server/tests/calculate_score.cpp delete mode 100644 server/tests/test_framework.cpp delete mode 100644 server/wall.cpp delete mode 100644 server/wall.h 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 create mode 100644 tests/SConscript create mode 100644 tests/calculate_score.cpp create mode 100644 tests/test_framework.cpp diff --git a/.gitignore b/.gitignore index a807d2a..32d8726 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,6 @@ *.o .sconsign.dblite -client/aotenjou -server/aotenjoud -*.test -*.passed - -*.suo -*.depend -*.layout -*.cbp -*.save -*.cbTemp -*.workspace - -doc/client/ +aotenjoud +tests/*.test +tests/*.passed doc/server/ - -client/bin/* -client/obj/* -server/bin/* -server/obj/* diff --git a/SConstruct b/SConstruct new file mode 100644 index 0000000..7b883e0 --- /dev/null +++ b/SConstruct @@ -0,0 +1,32 @@ +import os + +env = Environment( + ENV = os.environ, +) + +AddOption('--release', action = 'store_true') +AddOption('--profiling', action = 'store_true') + +if env['PLATFORM'] == 'win32': + env.Append(CPPFLAGS = ['-D _WIN32_WINNT']) + env.Append(LINKFLAGS = ['-Wl,--enable-auto-import']) + env.Append(LIBS = ['libboost_system-mgw44-mt-1_44.a', 'wsock32', 'ws2_32']) +else: + env.Append(LIBS = ['boost_system', 'boost_serialization', 'pthread']) + +if not GetOption('release'): + env.Append(CPPFLAGS = ['-Wall', '-g', '-D DEBUG']) + +if GetOption('profiling'): + env.Append(CPPFLAGS = ['-pg']) + env.Append(LINKFLAGS = ['-pg']) + +Export('env') + +env.Program('aotenjoud', Glob('src/*.cpp') + Glob('common/*.cpp')) + +env.SConscript('tests/SConscript') + +Default('aotenjoud') + +# vim: syn=python diff --git a/common b/common index dd64a35..2b14831 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit dd64a35c949738c2c321989d065e0754556823d5 +Subproject commit 2b14831ac8bdb4b8b8845cf78673105f0fa157ae diff --git a/server/SConstruct b/server/SConstruct deleted file mode 100644 index 09f34c7..0000000 --- a/server/SConstruct +++ /dev/null @@ -1,32 +0,0 @@ -import os - -env = Environment( - ENV = os.environ, -) - -AddOption('--release', action = 'store_true') -AddOption('--profiling', action = 'store_true') - -if env['PLATFORM'] == 'win32': - env.Append(CPPFLAGS = ['-D _WIN32_WINNT']) - env.Append(LINKFLAGS = ['-Wl,--enable-auto-import']) - env.Append(LIBS = ['libboost_system-mgw44-mt-1_44.a', 'wsock32', 'ws2_32']) -else: - env.Append(LIBS = ['boost_system', 'boost_serialization', 'pthread']) - -if not GetOption('release'): - env.Append(CPPFLAGS = ['-Wall', '-g', '-D DEBUG']) - -if GetOption('profiling'): - env.Append(CPPFLAGS = ['-pg']) - env.Append(LINKFLAGS = ['-pg']) - -Export('env') - -env.Program('aotenjoud', Glob('*.cpp') + Glob('../common/*.cpp')) - -env.SConscript('tests/SConscript') - -Default('aotenjoud') - -# vim: syn=python diff --git a/server/client.cpp b/server/client.cpp deleted file mode 100644 index afec605..0000000 --- a/server/client.cpp +++ /dev/null @@ -1,213 +0,0 @@ -#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/server/client.h b/server/client.h deleted file mode 100644 index e6cfcc7..0000000 --- a/server/client.h +++ /dev/null @@ -1,132 +0,0 @@ -#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/server/connection.cpp b/server/connection.cpp deleted file mode 100644 index 1e38f7b..0000000 --- a/server/connection.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#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/server/connection.h b/server/connection.h deleted file mode 100644 index b51a67f..0000000 --- a/server/connection.h +++ /dev/null @@ -1,53 +0,0 @@ -#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/server/game.cpp b/server/game.cpp deleted file mode 100644 index e791379..0000000 --- a/server/game.cpp +++ /dev/null @@ -1,456 +0,0 @@ -#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/server/game.h b/server/game.h deleted file mode 100644 index 874a525..0000000 --- a/server/game.h +++ /dev/null @@ -1,75 +0,0 @@ -#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/server/hand.cpp b/server/hand.cpp deleted file mode 100644 index bc4fb7a..0000000 --- a/server/hand.cpp +++ /dev/null @@ -1,252 +0,0 @@ -#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/server/hand.h b/server/hand.h deleted file mode 100644 index b7ae8d2..0000000 --- a/server/hand.h +++ /dev/null @@ -1,43 +0,0 @@ -#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/server/lobby.cpp b/server/lobby.cpp deleted file mode 100644 index 9fb769e..0000000 --- a/server/lobby.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#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/server/lobby.h b/server/lobby.h deleted file mode 100644 index c445506..0000000 --- a/server/lobby.h +++ /dev/null @@ -1,33 +0,0 @@ -#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/server/main.cpp b/server/main.cpp deleted file mode 100644 index 207d6d6..0000000 --- a/server/main.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#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/server/player.cpp b/server/player.cpp deleted file mode 100644 index a267b1d..0000000 --- a/server/player.cpp +++ /dev/null @@ -1,595 +0,0 @@ -#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/server/player.h b/server/player.h deleted file mode 100644 index a4b3416..0000000 --- a/server/player.h +++ /dev/null @@ -1,118 +0,0 @@ -#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/server/score.cpp b/server/score.cpp deleted file mode 100644 index 7d0dc41..0000000 --- a/server/score.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#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/server/score.h b/server/score.h deleted file mode 100644 index 6dee09a..0000000 --- a/server/score.h +++ /dev/null @@ -1,40 +0,0 @@ -#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(); -