summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client.cpp213
-rw-r--r--src/client.h132
-rw-r--r--src/connection.cpp67
-rw-r--r--src/connection.h53
-rw-r--r--src/game.cpp456
-rw-r--r--src/game.h75
-rw-r--r--src/hand.cpp252
-rw-r--r--src/hand.h43
-rw-r--r--src/lobby.cpp81
-rw-r--r--src/lobby.h33
-rw-r--r--src/main.cpp13
-rw-r--r--src/player.cpp595
-rw-r--r--src/player.h118
-rw-r--r--src/score.cpp65
-rw-r--r--src/score.h40
-rw-r--r--src/tcpserver.cpp32
-rw-r--r--src/tcpserver.h27
-rw-r--r--src/wall.cpp42
-rw-r--r--src/wall.h30
19 files changed, 2367 insertions, 0 deletions
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 <boost/bind.hpp>
+
+std::map<uint64_t, boost::weak_ptr<Client> > 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>(Message::LoginResponse::Lobby));
+}
+
+void Client::handle_login(Message::p msg, boost::function<void (Client::p)> login_callback) {
+ if(msg->type != Message::Types::Login) {
+ return;
+ }
+
+ Message::Login::p login_msg = dynamic_pointer_cast<Message::Login>(msg);
+
+ // Check if nick is invalid.
+ if(login_msg->nick.size() == 0) {
+ connection->send(make_shared<Message::LoginResponse>(Message::LoginResponse::Invalid));
+ connection->recv(boost::bind(&Client::handle_login, shared_from_this(), _1, login_callback));
+ return;
+ }
+
+ connection->send(make_shared<Message::LoginResponse>(Message::LoginResponse::Lobby));
+
+ nick_ = login_msg->nick;
+
+ login_callback(shared_from_this());
+}
+
+void Client::handle_lobby(Message::p msg, boost::function<void (int)> lobby_callback) {
+ if(msg->type != Message::Types::LobbyAction) {
+ return;
+ }
+
+ Message::LobbyAction::p lobby_msg = dynamic_pointer_cast<Message::LobbyAction>(msg);
+
+ lobby_callback(lobby_msg->index);
+}
+
+void Client::handle_ready(Message::p msg, boost::function<void ()> 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<void (Action)> action_callback, Actions possible_actions) {
+ if(msg->type != Message::Types::RoundAction) {
+ return;
+ }
+
+ Message::RoundAction::p action_msg = dynamic_pointer_cast<Message::RoundAction>(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<std::string>& game_modes, boost::function<void (int)> callback) {
+ Message::LobbyStatus::p msg = make_shared<Message::LobbyStatus>();
+
+ 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<void ()> callback, std::vector<std::string> players) {
+ if(connection) {
+ Message::GameStart::p msg = make_shared<Message::GameStart>();
+
+ 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<Message::RoundStart>());
+ }
+}
+
+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<Message::RoundState>(pl_d, pl_r, pl_u, pl_l, g, a));
+ }
+}
+
+void Client::round_end(Message::RoundEnd::p msg, boost::function<void ()> 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<void (Action)> 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<void ()> 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<void ()> callback, std::vector<std::string> 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<void ()> callback) {
+ if(callback) callback();
+}
+
+void ClientDumb::get_action(boost::function<void (Action)> callback, Actions expected_actions) {
+ callback(expected_actions.back());
+}
+
+void ClientDumb::game_end(Message::GameEnd::p msg, boost::function<void ()> 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 <string>
+#include <map>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/function.hpp>
+#include <boost/asio.hpp>
+
+#include "connection.h"
+
+//! Abstract client base class.
+class ClientBase {
+ public:
+ typedef boost::shared_ptr<ClientBase> p;
+
+ virtual ~ClientBase() {}
+
+ //! Return client's nick.
+ virtual std::string nick() = 0;
+
+ //! Notify client of a game start.
+ virtual void game_start(boost::function<void ()> callback, std::vector<std::string> 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<void ()> callback) = 0;
+
+ //! Get action. Upon connection error, last element of expected_actions will be provided.
+ virtual void get_action(boost::function<void (Action)> callback, Actions expected_actions) = 0;
+
+ virtual void game_end(Message::GameEnd::p msg, boost::function<void ()> callback) = 0;
+
+};
+
+//! Class implementing ClientBase for real clients.
+class Client : public ClientBase, public boost::enable_shared_from_this<Client> {
+ public:
+ typedef boost::shared_ptr<Client> 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<uint64_t, boost::weak_ptr<Client> > 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<void (Client::p)> login_callback);
+
+ //! Handle LobbyAction-message.
+ void handle_lobby(Message::p msg, boost::function<void (int)> lobby_callback);
+
+ //! Handle Ready-message.
+ void handle_ready(Message::p msg, boost::function<void ()> ready_callback);
+
+ //! Handle Action-message.
+ void handle_action(Message::p msg, boost::function<void (Action)> action_callback, Actions expected_actions);
+
+ public:
+ //! Inform client of lobby status (available game modes).
+ void lobby_status(const std::vector<std::string>& game_modes, boost::function<void (int)> callback);
+
+ //! Return client's nick.
+ virtual std::string nick();
+
+ //! Notify client of a game start.
+ virtual void game_start(boost::function<void ()> callback, std::vector<std::string> 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<void ()> callback);
+
+ //! Get action. Upon connection error, last element of expected_actions will be provided.
+ virtual void get_action(boost::function<void (Action)> callback, Actions expected_actions);
+
+ //! Reconnect a player.
+ virtual void reconnect(Connection::p c);
+
+ virtual void game_end(Message::GameEnd::p msg, boost::function<void ()> callback);
+};
+
+typedef std::vector<Client> Clients;
+
+class ClientDumb : public ClientBase {
+ public:
+ virtual unsigned int id();
+
+ virtual std::string nick();
+
+ virtual void game_start(boost::function<void ()> callback, std::vector<std::string> 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<void ()> callback);
+
+ virtual void get_action(boost::function<void (Action)> callback, Actions expected_actions);
+
+ virtual void game_end(Message::GameEnd::p msg, boost::function<void ()> 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 <boost/bind.hpp>
+
+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<void (Message::p)> f = recv_callback;
+ recv_callback.clear();
+ error_callback.clear();
+
+ f(msg);
+}
+
+void Connection::error(const std::string& msg) {
+ if(error_callback) {
+ boost::function<void (const std::string&)> 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<void (Message::p)> callback, boost::function<void (const std::string&)> 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 <boost/asio.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/function.hpp>
+
+#include "../common/connectionbase.h"
+
+class Connection : public ConnectionBase, public boost::enable_shared_from_this<Connection> {
+ private:
+ friend class TCPServer;
+ friend class Client;
+
+ boost::asio::ip::tcp::socket socket;
+
+ boost::function<void (Message::p)> recv_callback;
+ boost::function<void (const std::string&)> 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<Connection> 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<void (Message::p)> callback, boost::function<void (const std::string&)> 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 <boost/bind.hpp>
+
+#include <iostream>
+#include <algorithm>
+#include <map>
+
+#ifdef DEBUG
+#include <ctime>
+#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<std::string> 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<int, Actions> 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<int, Actions>::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<Message::RoundEnd>();
+
+ 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<Message::GameEnd>();
+ 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 <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <vector>
+
+#include "wall.h"
+#include "player.h"
+
+class Game : public boost::enable_shared_from_this<Game> {
+ public:
+ typedef boost::shared_ptr<Game> 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<Sets> Hand::get_breakdowns(const Tiles& tiles) {
+ List<Sets> 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(re