Compare commits

...

3 Commits

Author SHA1 Message Date
eay
8d6935a0d3 add terminal colors 2023-08-28 08:16:30 -07:00
eay
cfcd99b0db use json serialization everywhere (staged for adding socket functionality) 2023-08-28 08:05:30 -07:00
eay
4b80878d33 json serialization for requests and responses 2023-08-25 16:51:35 -07:00
8 changed files with 245 additions and 44 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
bin/*
*.swp

View File

@ -1,2 +1,6 @@
azool:
g++ src/*.cc -I./include -o bin/azool -Werror -Weffc++ -std=c++11
mkdir -p bin
g++ src/*.cc -I./include -o bin/azool -Werror -Weffc++ -std=c++11 -DBOOST_ALLOW_DEPRECATED_HEADERS -DBOOST_BIND_GLOBAL_PLACEHOLDERS
clean:
rm bin/*

View File

@ -4,6 +4,7 @@
#include <ostream>
#include <cstring>
#include <random>
#include <boost/property_tree/ptree.hpp>
#include "tile_utils.h"
class GameBoard {
@ -17,15 +18,17 @@ public:
GameBoard(int nPlayers=2);
friend std::ostream& operator<<(std::ostream& out, const GameBoard& board);
bool validFactoryRequest(int factoryIdx, azool::TileColor color);
bool validFactoryRequest(int factoryIdx, azool::TileColor color) const;
bool takeTilesFromFactory(int factoryIdx, azool::TileColor color, int& numTiles);
bool takeTilesFromPool(azool::TileColor color, int& numTiles, bool& poolPenalty);
void returnTilesToBag(int numTiles, azool::TileColor color);
void dealTiles();
std::string handleRequest(const std::string&);
boost::property_tree::ptree serializeBoard() const;
int numFactories() {
return tileFactories.size();
}
bool endOfRound() {
bool endOfRound() const {
// round ends when the pool and tile factories are empty
for (int ii = 0; ii < azool::NUMCOLORS; ++ii) {
if (pool[ii] > 0) return false;
@ -39,6 +42,7 @@ private:
// - vector of factories?
std::vector<Factory> tileFactories;
int maxNumFactories;
int myNumPlayers;
int pool[azool::NUMCOLORS]; // stores the count of each color currently in the pool; initialize to 0s
bool whiteTileInPool; // initialize to true
std::vector<azool::TileColor> tileBag; // initialize to 20 of each color

View File

@ -15,6 +15,7 @@ public:
void placeTiles(int rowIdx, azool::TileColor color, int numTiles);
void endRound(bool& fullRow);
void finalizeScore();
std::string sendRequest(const std::string&);
int getScore() const {
return myScore;
}
@ -22,9 +23,11 @@ public:
bool tookPenalty() const {
return myTookPoolPenaltyThisRound;
}
int getScorePenalty() const;
const std::string getPlayerName() const {
return myName;
}
void sendRequest(/*string or stringstream?*/);
private:
Player(const Player&) = delete;

View File

@ -7,17 +7,23 @@ enum TileColor {
BLUE,
GREEN,
YELLOW,
BLACK,
WHITE,
NUMCOLORS
};
const std::string TileColorStrings[NUMCOLORS] = {
"red",
"blue",
"green",
"yellow",
"black"
"\x1B[1;41mR\x1B[0m",//"red",
"\x1B[1;44mB\x1B[0m",//"blue",
"\x1B[1;42mG\x1B[0m",//"green",
"\x1B[1;30;103mY\x1B[0m",//"yellow",
"\x1B[1;30;107mW\x1B[0m",//"white"
};
const char TileColorSyms[NUMCOLORS] = { 'r', 'b', 'g', 'y', 'k' };
const char TileColorSyms[NUMCOLORS] = { 'r', 'b', 'g', 'y', 'w' };
const int MAXPLAYERS = 4;
const std::string REQ_TYPE_DRAW_FROM_FACTORY = "DRAW_FROM_FACTORY";
const std::string REQ_TYPE_DRAW_FROM_POOL = "DRAW_FROM_POOL";
const std::string REQ_TYPE_RETURN_TO_BAG = "RETURN_TO_BAG";
const std::string REQ_TYPE_GET_BOARD = "GET_BOARD";
}
#endif // TILE_UTILS_H_

View File

@ -1,9 +1,14 @@
#include "GameBoard.h"
#include <algorithm>
#include <chrono>
#include <boost/property_tree/json_parser.hpp>
#include <iostream>
namespace pt = boost::property_tree;
GameBoard::GameBoard(int numPlayers) :
tileFactories(),
myNumPlayers(numPlayers),
maxNumFactories(numPlayers*2+1),
pool(),
whiteTileInPool(true),
@ -37,9 +42,10 @@ std::ostream& operator<<(std::ostream& out, const GameBoard& board) {
return out;
}
bool GameBoard::validFactoryRequest(int factoryIdx, azool::TileColor color) {
bool GameBoard::validFactoryRequest(int factoryIdx, azool::TileColor color) const {
// check if color exists on specified factory
bool retVal = factoryIdx < tileFactories.size() and
factoryIdx > -1 and
tileFactories[factoryIdx].tileCounts[color] > 0;
return retVal;
}
@ -72,12 +78,12 @@ bool GameBoard::takeTilesFromPool(azool::TileColor color, int& numTiles, bool& p
poolPenalty = whiteTileInPool;
whiteTileInPool = false;
return true;
}
} // GameBoard::takeTilesFromPool
void GameBoard::returnTilesToBag(int numTiles, azool::TileColor color) {
for (int ii = 0; ii < numTiles; ++ii) {
tileBag.emplace_back(color);
}
}
} // GameBoard::returnTilesToBag
// random shuffle then read from the beginning of the vector?
void GameBoard::dealTiles() {
@ -102,6 +108,94 @@ void GameBoard::dealTiles() {
}
} // GameBoard::dealTiles
pt::ptree GameBoard::serializeBoard() const {
pt::ptree outTree;
// factories
pt::ptree factTree;
int factCt = 1;
for (auto factory : tileFactories) {
std::stringstream factSS;
for (int ii = 0; ii < azool::NUMCOLORS; ++ii) {
for (int jj = 0; jj < factory.tileCounts[ii]; ++jj) {
factSS << azool::TileColorStrings[ii] << ",";
}
}
factTree.put(std::to_string(factCt), factSS.str());
factCt++;
}
outTree.add_child("factories", factTree);
outTree.put("num_factories", tileFactories.size());
// pool
pt::ptree poolTree;
for (int ii = 0; ii < azool::NUMCOLORS; ++ii) {
poolTree.put(azool::TileColorStrings[ii], pool[ii]);
}
if (whiteTileInPool) {
poolTree.put("penalty", -1);
}
else {
poolTree.put("penalty", 0);
}
outTree.add_child("pool", poolTree);
outTree.put("end_of_round", endOfRound());
return outTree;
} // GameBoard::serializeBoard
std::string GameBoard::handleRequest(const std::string& instring) {
std::stringstream iss(instring);
pt::ptree inTree;
pt::read_json(iss, inTree);
pt::ptree outTree;
std::string req_type = inTree.get<std::string>("req_type");
// check type of request; check that required parameters are present; etc
// return: json with results, status, error msg if any
if (req_type.compare(azool::REQ_TYPE_DRAW_FROM_FACTORY) == 0) {
int idx = inTree.get<int>("factory_idx", -1);
int numTiles = 0;
azool::TileColor color = static_cast<azool::TileColor>(inTree.get<int>("tile_color"));
bool success = takeTilesFromFactory(idx, color, numTiles);
outTree.put("status", success ? "success" : "fail");
outTree.put("success", success);
outTree.put("num_tiles_returned", numTiles);
if (!success) {
outTree.put("error_msg", "INVALID FACTORY REQUEST");
}
}
else if (req_type.compare(azool::REQ_TYPE_DRAW_FROM_POOL) == 0) {
azool::TileColor color = static_cast<azool::TileColor>(inTree.get<int>("tile_color"));
int numTiles = 0;
bool penalty = false;
bool success = takeTilesFromPool(color, numTiles, penalty);
outTree.put("success", success);
outTree.put("status", success ? "success" : "fail");
outTree.put("num_tiles_returned", numTiles);
outTree.put("pool_penalty", penalty);
if (!success) {
outTree.put("error_msg", "INVALID POOL REQUEST");
}
}
else if (req_type.compare(azool::REQ_TYPE_RETURN_TO_BAG) == 0) {
int numTiles = inTree.get<int>("num_tiles_returned", 0);
azool::TileColor color = static_cast<azool::TileColor>(inTree.get<int>("tile_color"));
returnTilesToBag(numTiles, color);
outTree.put("success", true); // no reason for this to fail
outTree.put("status", "success");
}
else if (req_type.compare(azool::REQ_TYPE_GET_BOARD) == 0) {
outTree = serializeBoard();
}
else {
// ERROR, BAD THINGS
outTree.put("error_msg", "INVALID REQUEST TYPE");
}
outTree.put("req_type", req_type); // include request type in returned data
std::stringstream oss;
pt::write_json(oss, outTree);
std::string output = oss.str();
return output;
// send output string over socket
} // GameBoard::handleRequest
void GameBoard::resetBoard() {
memset(pool, 0, azool::NUMCOLORS*sizeof(int));
tileBag.clear();

View File

@ -2,6 +2,8 @@
#include <iostream>
#include <cstring>
#include <sstream>
#include <boost/property_tree/json_parser.hpp>
namespace pt = boost::property_tree;
Player::Player(GameBoard* const board, std::string name) :
myGrid(),
@ -41,10 +43,24 @@ bool Player::checkValidMove(azool::TileColor color, int rowIdx) const {
} // Player::checkValidMove
bool Player::takeTilesFromFactory(int factoryIdx, azool::TileColor color, int rowIdx) {
// call game board -> takeTiles(factoryIdx, color)
int numTiles = 0;
if (checkValidMove(color, rowIdx) and
myBoardPtr->takeTilesFromFactory(factoryIdx, color, numTiles)) {
// checkValidMove first - can't undo request to boardptr
if (!checkValidMove(color, rowIdx)) {
return false;
}
pt::ptree request;
request.put("req_type", azool::REQ_TYPE_DRAW_FROM_FACTORY);
request.put("tile_color", color);
request.put("factory_idx", factoryIdx);
std::stringstream oss;
pt::write_json(oss, request);
// send request to board, recieve response
std::stringstream iss(sendRequest(oss.str()));
pt::ptree response;
pt::read_json(iss, response);
// std::cout << iss.str() << std::endl;
bool success = response.get<bool>("success", false);
int numTiles = response.get<int>("num_tiles_returned", 0);
if (success) {
placeTiles(rowIdx, color, numTiles);
return true;
}
@ -53,12 +69,22 @@ bool Player::takeTilesFromFactory(int factoryIdx, azool::TileColor color, int ro
bool Player::takeTilesFromPool(azool::TileColor color, int rowIdx) {
// call game board -> takeTilesFromPool
int numTiles = 0;
bool poolPenalty = false;
if (!checkValidMove(color, rowIdx)) {
return false;
}
if (!myBoardPtr->takeTilesFromPool(color, numTiles, poolPenalty)) {
pt::ptree request;
request.put("req_type", azool::REQ_TYPE_DRAW_FROM_POOL);
request.put("tile_color", color);
std::stringstream oss;
pt::write_json(oss, request);
// send request to board, recieve response
std::stringstream iss(sendRequest(oss.str()));
pt::ptree response;
pt::read_json(iss, response);
bool success = response.get<bool>("success", false);
int numTiles = response.get<int>("num_tiles_returned", 0);
bool poolPenalty = response.get<bool>("pool_penalty", false);
if (!success) {
return false; // couldn't get that tile from the pool
}
if (poolPenalty) {
@ -98,18 +124,22 @@ void Player::endRound(bool& fullRow) {
myGrid[rowIdx][col] = true;
myScore += scoreTile(rowIdx, col);
// return extra tiles -- rowIdx = the number of leftover tiles
myBoardPtr->returnTilesToBag(rowIdx, myRows[rowIdx].second);
pt::ptree request;
request.put("req_type", azool::REQ_TYPE_RETURN_TO_BAG);
request.put("num_tiles_returned", rowIdx);
request.put("tile_color", myRows[rowIdx].second);
std::stringstream oss;
pt::write_json(oss, request);
// send request to gameboard...don't care abt response here (?)
sendRequest(oss.str());
// myBoardPtr->returnTilesToBag(rowIdx, myRows[rowIdx].second);
// reset rows for next turn
myRows[rowIdx].first = 0;
myRows[rowIdx].second = azool::NONE;
}
}
if (myNumPenaltiesForRound >= PenaltyPoints.size()) {
myScore -= PenaltyPoints[PenaltyPoints.size() - 1];
}
else {
myScore -= PenaltyPoints[myNumPenaltiesForRound];
}
myScore -= getScorePenalty();
myScore = std::max(myScore, 0); // let's not allow negatives
// reset for next turn
// FOR THIS REASON
// main loop needs to check who took penalty BEFORE calling this function
@ -134,6 +164,12 @@ void Player::endRound(bool& fullRow) {
} // iter over rows in grid
} // Player::endRound
int Player::getScorePenalty() const {
if (myNumPenaltiesForRound >= PenaltyPoints.size()) {
return PenaltyPoints[PenaltyPoints.size() - 1];
}
return PenaltyPoints[myNumPenaltiesForRound];
}
int Player::scoreTile(int tileRow, int tileCol) {
// search horizontally and vertically for points
int tileScore = 1;
@ -235,10 +271,11 @@ void Player::finalizeScore() {
} // Player::finalizeScore
std::string Player::printMyBoard() const {
// pt::ptree request;
// request.put("req_type", azool::REQ_TYPE_GET_BOARD);
std::ostringstream oss;
oss << "*******************************\n";
oss << "PLAYER: " << myName << "\n";
oss << *myBoardPtr << "\n\n";
for (int ii = 0; ii < azool::NUMCOLORS; ++ii) {
oss << ii+1 << ") ";
for (int jj = azool::NUMCOLORS; jj > (ii+1); --jj) {
@ -259,7 +296,7 @@ std::string Player::printMyBoard() const {
// color = row + column % 5
char color = (ii + jj) % 5;
if (myGrid[ii][jj]) {
oss << static_cast<char>(azool::TileColorSyms[color] - 32) << "|";
oss << azool::TileColorStrings[color] << "|";
}
else {
oss << azool::TileColorSyms[color] << "|";
@ -267,15 +304,34 @@ std::string Player::printMyBoard() const {
}
oss << "\n";
} // iterate over rows
oss << "Penalties: " << myNumPenaltiesForRound << "\n";
oss << "Penalties: " << myNumPenaltiesForRound;
if (myTookPoolPenaltyThisRound) {
oss << "*";
}
if (myNumPenaltiesForRound > 0) {
oss << " (-" << getScorePenalty() << ")";
}
oss << "\n";
oss << "Score: " << myScore << "\n";
oss << "-------------------------------\n";
return oss.str();
// TODO(feature) - print penalty tiles (?)
} // Player::printMyBoard
bool Player::discardFromFactory(int factoryIdx, azool::TileColor color) {
int numTiles = -1;
if (myBoardPtr->takeTilesFromFactory(factoryIdx, color, numTiles)) {
pt::ptree request;
request.put("req_type", azool::REQ_TYPE_DRAW_FROM_FACTORY);
request.put("factory_idx", factoryIdx);
request.put("tile_color", color);
std::stringstream oss;
pt::write_json(oss, request);
// send request to board, recieve response
std::stringstream iss(sendRequest(oss.str()));
pt::ptree response;
pt::read_json(iss, response);
bool success = response.get<bool>("success", false);
int numTiles = response.get<int>("num_tiles_returned", 0);
if (success) {
myNumPenaltiesForRound += numTiles;
return true;
}
@ -283,9 +339,19 @@ bool Player::discardFromFactory(int factoryIdx, azool::TileColor color) {
} // Player::discardFromFactory
bool Player::discardFromPool(azool::TileColor color) {
bool poolPenalty = false;
int numTiles = -1;
if (myBoardPtr->takeTilesFromPool(color, numTiles, poolPenalty)) {
pt::ptree request;
request.put("req_type", azool::REQ_TYPE_DRAW_FROM_POOL);
request.put("tile_color", color);
std::stringstream oss;
pt::write_json(oss, request);
// send request to board, recieve response
std::stringstream iss(sendRequest(oss.str()));
pt::ptree response;
pt::read_json(iss, response);
bool success = response.get<bool>("success", false);
int numTiles = response.get<int>("num_tiles_returned", 0);
bool poolPenalty = response.get<bool>("pool_penalty", false);
if (success) {
if (poolPenalty) {
myNumPenaltiesForRound++;
}
@ -295,20 +361,26 @@ bool Player::discardFromPool(azool::TileColor color) {
return false;
} // Player::discardFromPool
std::string Player::sendRequest(const std::string& inStr) {
// do the socket stuff here
std::string rxStr = myBoardPtr->handleRequest(inStr);
return rxStr;
} // Player::sendRequest
namespace {
int promptForFactoryIdx(int maxNumFactories) {
int promptForFactoryIdx(int maxFactIdx) {
static const char* promptFactoryIdxDraw = "Which factory? enter index\n";
char factInput; // TODO can we safely say there will never be more than 9 possible?
std::cout << promptFactoryIdxDraw << std::flush;
std::cin >> factInput;
int factIdx = std::atoi(&factInput);
if (factIdx < 1 or factIdx > maxNumFactories) {
if (factIdx < 1 or factIdx > maxFactIdx) {
return -1;
}
return factIdx;
}
azool::TileColor promptForColor() {
static const char* promptColorDraw = "Which color? [r|b|g|y|k]\n";
static const char* promptColorDraw = "Which color? [r|b|g|y|w]\n";
char colorInput = '\0';
std::cout << promptColorDraw << std::flush;
std::cin >> colorInput;
@ -325,8 +397,8 @@ azool::TileColor promptForColor() {
case 'y':
return azool::YELLOW;
break;
case 'k':
return azool::BLACK;
case 'w':
return azool::WHITE;
break;
default:
return azool::NONE;
@ -348,7 +420,19 @@ int promptForRow() {
void Player::takeTurn() {
// print game board, handle user input
if (myBoardPtr->endOfRound()) return;
pt::ptree request;
request.put("req_type", azool::REQ_TYPE_GET_BOARD);
std::stringstream reqss;
pt::write_json(reqss, request);
// send request to board, recieve response
std::stringstream iss(sendRequest(reqss.str()));
pt::ptree response;
pt::read_json(iss, response);
bool endOfRound = response.get<bool>("end_of_round");
// max idx for input - users use 1-indexing
int maxFactIdx = response.get<int>("num_factories");
if (endOfRound) return;
std::cout << *myBoardPtr << "\n\n";
std::cout << printMyBoard();
static const char* promptDrawInput = "What would you like to do?\n"
"[f] take from factory "
@ -366,7 +450,7 @@ void Player::takeTurn() {
char drawType;
std::cin >> drawType;
if (drawType == 'f') {
int factIdx = promptForFactoryIdx(myBoardPtr->numFactories());
int factIdx = promptForFactoryIdx(maxFactIdx);
// draw from factory
if (factIdx == -1) {
std::cout << invalidEntryMessage << std::flush;
@ -413,7 +497,7 @@ void Player::takeTurn() {
char discardFrom = '\0';
std::cin >> discardFrom;
if (discardFrom == 'f') {
int factIdx = promptForFactoryIdx(myBoardPtr->numFactories());
int factIdx = promptForFactoryIdx(maxFactIdx);
// draw from factory
if (factIdx == -1) {
std::cout << invalidEntryMessage << std::flush;
@ -445,6 +529,7 @@ void Player::takeTurn() {
} // discard from pool
}
else if (drawType == 'P') {
std::cout << *myBoardPtr << "\n\n";
std::cout << printMyBoard();
}
else {
@ -452,6 +537,7 @@ void Player::takeTurn() {
}
} // while !fullinput
// options: take tile from pool or take tile from factory
std::cout << "\033c" << std::flush;
std::cout << printMyBoard() << std::flush;
// flush out any inputs still in the buffer
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

View File

@ -23,10 +23,12 @@ void playGame(GameBoard* game) {
while (!game->endOfRound()) {
// TODO figure out how order will work for > 2 players
firstPlayer->takeTurn();
sleep(1);
secondPlayer->takeTurn();
sleep(1);
}
// check who took penalty
// nees to be done before claling player->endRound()
// needs to be done before calling player->endRound()
if (players[0]->tookPenalty()) {
firstPlayer = players[0];
secondPlayer = players[1];
@ -41,6 +43,7 @@ void playGame(GameBoard* game) {
secondPlayer = players[1];
}
std::cout << "End of round!" << std::endl;
std::cout << "\033c";
players[0]->endRound(p0EndsGame);
players[1]->endRound(p1EndsGame);
}