diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/client.cpp | 213 | ||||
| -rw-r--r-- | src/client.h | 132 | ||||
| -rw-r--r-- | src/connection.cpp | 67 | ||||
| -rw-r--r-- | src/connection.h | 53 | ||||
| -rw-r--r-- | src/game.cpp | 456 | ||||
| -rw-r--r-- | src/game.h | 75 | ||||
| -rw-r--r-- | src/hand.cpp | 252 | ||||
| -rw-r--r-- | src/hand.h | 43 | ||||
| -rw-r--r-- | src/lobby.cpp | 81 | ||||
| -rw-r--r-- | src/lobby.h | 33 | ||||
| -rw-r--r-- | src/main.cpp | 13 | ||||
| -rw-r--r-- | src/player.cpp | 595 | ||||
| -rw-r--r-- | src/player.h | 118 | ||||
| -rw-r--r-- | src/score.cpp | 65 | ||||
| -rw-r--r-- | src/score.h | 40 | ||||
| -rw-r--r-- | src/tcpserver.cpp | 32 | ||||
| -rw-r--r-- | src/tcpserver.h | 27 | ||||
| -rw-r--r-- | src/wall.cpp | 42 | ||||
| -rw-r--r-- | src/wall.h | 30 | 
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 | 
