commit b71d81d8d61925dd3254deed5936a6b8b46a2c6f Author: eay Date: Mon May 29 14:16:49 2023 -0700 initial working version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..36f971e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin/* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4230caa --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +azool: + g++ src/*.cc -I./include -o bin/azool -Werror -Weffc++ -std=c++11 diff --git a/doc/design.txt b/doc/design.txt new file mode 100644 index 0000000..b91c7e1 --- /dev/null +++ b/doc/design.txt @@ -0,0 +1,140 @@ +Classes: + +class Factory { +public: + Factory() : tileCounts(), isEmpty(false) {} +private: + int tileCounts[NUMCOLORS]; // initialize to 0s + bool isEmpty; // initialize to false +} + +class Player { +public: + Player(); + void checkValidMove(TileColor color, int rowIdx) { + // check if valid move (color exists on selected factory, grid doesn't already have this color on that row, + // row is either empty or already has the same color) + } + void takeTilesFromFactory(int factoryIdx, TileColor color, int rowIdx) { + // call game board -> takeTiles(factoryIdx, color) + placeTiles(color, rowIdx); + } + void takeTilesFromPool(TileColor color, int rowIdx) { + // call game board -> takeTilesFromPool + int numTiles = 0; + bool poolPenalty = myGameBoardPtr->takeTilesFromPool(color, numTiles); + placeTiles(rowIdx, numTiles); + } // takeTilesFromPool + void placeTiles(int rowIdx, int numTiles) { + // increment row with # of new tiles + rows[rowIdx] += numTiles; + int maxNumInRow = rowIdx + 1; + // if tiles overflow the row, take penalty(ies) + if (rows[rowIdx] > maxNumInRow) { + myNumPenaltiesForTurn += (rows[rowIdx] - maxNumInRow); + rows[rowIdx] = maxNumInRow; + } + } // placeTiles + void endRound() { + // determine which rows are full of tiles + // update the grid + // send extra tiles back to the game board + // find the score for each tile placed + for (int ii = 0; ii < NUMCOLORS; ++ii) { + if (myRows[ii].first == (ii+1)) { + // filled a row. now place a tile on the grid + // determine which column it belongs to + int col = (ii + 5 - myRows[ii].second) % 5; + grid[ii][col] = true; + // search horizontally and vertically for points + myScore += scoreTile(ii, col); + // return extra tiles + myBoardPtr->returnTilesToBag(ii, myRows[ii].second); + // reset rows for next turn + myRows[ii].first = 0; + myRows[ii].second = NONE; + } + } + // Account for penalties + } // endRound + + int scoreTile(int row, int col) { + int tileScore = 1; + // Get column score + for (int ii = row - 1; ii > -1; --ii) { + if (!grid[ii][col]) { + break; + } + tileScore++; + } // iterate above current row + for (int ii = row; ii < NUMCOLORS; ++ii) { + if (!grid[ii][col]) { + break; + } + tileScore++; + } // iterate from current row down + // Get row score + for (int ii = col - 1; ii > -1; --ii) { + if (!grid[row][ii]) { + break; + } + tileScore++; + } // iterate left of current column + for (int ii = col; ii < NUMCOLORS; ++ii) { + if (!grid[row][ii]) { + break; + } + tileScore++; + } // iterate from current column to right + } // scoreTile + + void finalScore() { + // compute bonuses for rows, columns, 5 of a kind + } + +private: + // color = r + c % 5 + bool myGrid[5][5]; + std::pair myRows[NUMCOLORS]; // first - # of tiles on that row, second - color of tiles on row + GameBoard* myBoardPtr; + int myScore; + int myNumPenaltiesForTurn; +}; + +// who manages turns and rounds? probably the main function + +class GameBoard { +public: + enum TileColor { + NONE = -1, + RED = 0, + BLUE, + GREEN, + YELLOW, + BLACK, + NUMCOLORS + }; + GameBoard(); + bool takeTilesFromFactory(int factoryIdx, TileColor color, uint6_t& numTiles) { + // clear factory + // add other tiles to pool + // return # of tiles given to player + // if failed, return false + } + bool takeTilesFromPool(TileColor color, uint6_t& numTiles) { + // zero color count in pool + // return # of tiles given to player (-1 if failed) + // if failed, return false + } + bool returnTilesToBag(uint6_t numTiles, TileColor color); + void endGame(); // called by a player if they get a row +private: + void dealTiles(); + void resetTiles(); +members: + - vector of factories? + int pool[NUMCOLORS]; // stores the count of each color currently in the pool; initialize to 0s + bool whiteTileInPool; // initialize to true + std::vector tileBag; // initialize to 20 of each color + bool lastRound; // initialize to false +}; diff --git a/include/GameBoard.h b/include/GameBoard.h new file mode 100644 index 0000000..473f30b --- /dev/null +++ b/include/GameBoard.h @@ -0,0 +1,43 @@ +#ifndef GAMEBOARD_H_ +#define GAMEBOARD_H_ +#include +#include +#include +#include "tile_utils.h" + +class GameBoard { +public: + struct Factory { + Factory() : tileCounts() { + memset(tileCounts, 0, azool::NUMCOLORS*sizeof(int)); + } + int tileCounts[azool::NUMCOLORS]; + }; // struct Factory + + GameBoard(); + GameBoard(int factories); + friend std::ostream& operator<<(std::ostream& out, const GameBoard& board); + bool validFactoryRequest(int factoryIdx, azool::TileColor color); + 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(); + int numFactories() { return tileFactories.size(); } + bool endOfRound() { + // round ends when the pool and tile factories are empty + for (int ii = 0; ii < azool::NUMCOLORS; ++ii) { + if (pool[ii] > 0) return false; + } + return tileFactories.empty(); + } +private: + void resetBoard(); + // - vector of factories? + std::vector tileFactories; + int maxNumFactories; + int pool[azool::NUMCOLORS]; // stores the count of each color currently in the pool; initialize to 0s + bool whiteTileInPool; // initialize to true + std::vector tileBag; // initialize to 20 of each color + bool lastRound; // initialize to false +}; +#endif // GAMEBOARD_H_ diff --git a/include/Player.h b/include/Player.h new file mode 100644 index 0000000..484a4b7 --- /dev/null +++ b/include/Player.h @@ -0,0 +1,41 @@ +#ifndef PLAYER_H_ +#define PLAYER_H_ +#include "GameBoard.h" +#include "tile_utils.h" +#include + +class Player { +public: + Player(GameBoard* const board, std::string name = "1"); + void takeTurn(); + bool takeTilesFromFactory(int factoryIdx, azool::TileColor color, int rowIdx); + bool takeTilesFromPool(azool::TileColor color, int rowIdx); + bool discardFromFactory(int factoryIdx, azool::TileColor color); + bool discardFromPool(azool::TileColor color); + void placeTiles(int rowIdx, azool::TileColor color, int numTiles); + void endRound(bool& fullRow); + void finalizeScore(); + int getScore() const { return myScore; } + void printMyBoard() const; + bool tookPenalty() const { return myTookPoolPenaltyThisTurn; } + const std::string getPlayerName() const { return myName; } + +private: + Player(const Player&) = delete; + Player operator=(const Player&) = delete; + + bool checkValidMove(azool::TileColor color, int rowIdx) const; + int scoreTile(int row, int col); + + // first - # of tiles on that row, second - color of tiles on row + bool myGrid[azool::NUMCOLORS][azool::NUMCOLORS]; + typedef std::pair TileRow; + TileRow myRows[azool::NUMCOLORS]; + GameBoard* const myBoardPtr; + int myScore; + int myNumPenaltiesForTurn; + bool myTookPoolPenaltyThisTurn; + const std::vector PenaltyPoints = {0, 1, 2, 3, 5, 7, 10, 13, 15}; + std::string myName; +}; // class Player +#endif // PLAYER_H_ diff --git a/include/tile_utils.h b/include/tile_utils.h new file mode 100644 index 0000000..75808b5 --- /dev/null +++ b/include/tile_utils.h @@ -0,0 +1,23 @@ +#ifndef TILE_UTILS_H_ +#define TILE_UTILS_H_ +namespace azool { + enum TileColor { + NONE = -1, + RED = 0, + BLUE, + GREEN, + YELLOW, + BLACK, + NUMCOLORS + }; + const std::string TileColorStrings[NUMCOLORS] = { + "red", + "blue", + "green", + "yellow", + "black" + }; + + const char TileColorSyms[NUMCOLORS] = { 'r', 'b', 'g', 'y', 'k' }; +} +#endif // TILE_UTILS_H_ diff --git a/src/GameBoard.cc b/src/GameBoard.cc new file mode 100644 index 0000000..6d0de99 --- /dev/null +++ b/src/GameBoard.cc @@ -0,0 +1,132 @@ +#include "GameBoard.h" +#include +#include + +GameBoard::GameBoard() : + tileFactories(), + maxNumFactories(5), // TODO(feature) base on # of players? 2n+1 + pool(), + whiteTileInPool(true), + tileBag(), + lastRound(false) { + resetBoard(); + } // GameBoard::GameBoard + +GameBoard::GameBoard(int factories) : + tileFactories(), + maxNumFactories(factories), + pool(), + whiteTileInPool(true), + tileBag(), + lastRound(false) { + tileBag.reserve(azool::NUMCOLORS * 20); + for (int ii = 0; ii < azool::NUMCOLORS; ++ii) { + pool[ii] = 0; // initialize pool to 0s + // initialize tile bag to 20 of each color + for (int jj = 0; jj < 20; jj++) { + tileBag[ii * 20 + jj] = static_cast(ii); + } + } + } // GameBoard::GameBoard + +std::ostream& operator<<(std::ostream& out, const GameBoard& board) { + // user will input 1-indexed value, even though we 0-index internally + int factCt = 1; + out << "Factories:\n"; + for (auto factory : board.tileFactories) { + out << factCt++ << " "; + for (int ii = 0; ii < azool::NUMCOLORS; ++ii) { + for (int jj = 0; jj < factory.tileCounts[ii]; ++jj) { + out << azool::TileColorStrings[ii] << ","; + } + } + out << "\n"; + } + out << "\nPOOL:\n"; + if (board.whiteTileInPool) { + out << "[-1]\n"; + } + for (int ii = 0; ii < azool::NUMCOLORS; ++ii) { + out << azool::TileColorStrings[ii] << " x " << board.pool[ii] << "\n"; + } + return out; +} + +bool GameBoard::validFactoryRequest(int factoryIdx, azool::TileColor color) { + // check if color exists on specified factory + bool retVal = factoryIdx < tileFactories.size() and + tileFactories[factoryIdx].tileCounts[color] > 0; + return retVal; +} + +bool GameBoard::takeTilesFromFactory(int factoryIdx, azool::TileColor color, int& numTiles) { + // clear factory + // add other tiles to pool + // return # of tiles given to player + // if invalid return false + if (!validFactoryRequest(factoryIdx, color)) { + return false; + } + numTiles = tileFactories[factoryIdx].tileCounts[color]; + // zero out the tiles of this color before adding the rest to the pool + tileFactories[factoryIdx].tileCounts[color] = 0; + for (int ii = 0; ii < azool::NUMCOLORS; ++ii) { + pool[ii] += tileFactories[factoryIdx].tileCounts[ii]; + } + tileFactories.erase(tileFactories.begin() + factoryIdx); + return true; +} +bool GameBoard::takeTilesFromPool(azool::TileColor color, int& numTiles, bool& poolPenalty) { + numTiles = pool[color]; // # of tiles given to player + if (numTiles == 0) { + // invalid - no tiles of the given color are in the pool + return false; + } + // zero color count in pool + pool[color] = 0; + poolPenalty = whiteTileInPool; + whiteTileInPool = false; + return true; +} +void GameBoard::returnTilesToBag(int numTiles, azool::TileColor color) { + for (int ii = 0; ii < numTiles; ++ii) { + tileBag.emplace_back(color); + } +} + +// random shuffle then read from the beginning of the vector? +void GameBoard::dealTiles() { + tileFactories.clear(); + whiteTileInPool = true; + int numFactories = std::min(static_cast(tileBag.size()) / 4, maxNumFactories); + if (tileBag.size() < 4*numFactories) { + numFactories++; + } + // TODO(implementation) - set random seed (probably somewhere else) + std::shuffle(tileBag.begin(), tileBag.end(), std::default_random_engine(590)); + auto itr = tileBag.begin(); + for (int ii = 0; ii < numFactories and itr != tileBag.end(); ++ii) { + Factory fact; + for (int jj = 0; jj < 4; jj++) { + fact.tileCounts[*itr]++; + itr++; + if (itr == tileBag.end()) { + break; + } // last factory may have less than 4 tiles + } + tileFactories.push_back(fact); + } +} // GameBoard::dealTiles + +void GameBoard::resetBoard() { + memset(pool, 0, azool::NUMCOLORS*sizeof(int)); + tileBag.clear(); + tileBag.reserve(azool::NUMCOLORS * 20); + for (int ii = 0; ii < azool::NUMCOLORS; ++ii) { + // initialize tile bag to 20 of each color + for (int jj = 0; jj < 20; jj++) { + tileBag.emplace_back(static_cast(ii)); + } + } + whiteTileInPool = true; +} // GameBoard::resetBoard diff --git a/src/Player.cc b/src/Player.cc new file mode 100644 index 0000000..caac017 --- /dev/null +++ b/src/Player.cc @@ -0,0 +1,449 @@ +#include "Player.h" +#include +#include + +Player::Player(GameBoard* const board, std::string name) : + myGrid(), + myRows(), + myBoardPtr(board), + myName(name), + myScore(0), + myNumPenaltiesForTurn(0), + myTookPoolPenaltyThisTurn(false) { + int gridSize = azool::NUMCOLORS * azool::NUMCOLORS; + memset(myGrid, 0, gridSize*sizeof(bool)); + for (int ii = 0; ii < azool::NUMCOLORS; ++ii) { + myRows[ii].first = 0; + myRows[ii].second = azool::NONE; + } // initialize rows + } // Player::Player + +bool Player::checkValidMove(azool::TileColor color, int rowIdx) const { + // check if valid move + // grid doesn't already have this color on that row, + // row is either empty or already has the same color + if (color == azool::NONE) { + std::cerr << "HEY THIS IS WEIRD -- " + << "ASKING FOR COLOR = NONE?" << std::endl; + return false; // invalid and also probably shouldn't happen + } + int colIdx = (5 + static_cast(color) - rowIdx) % 5; + if (myGrid[rowIdx][colIdx]) { + return false; // already have that color on this row + } + if (!(myRows[rowIdx].second == color or + myRows[rowIdx].second == azool::NONE)) { + // TODO(implementation) to check for empty, should we check the color or # of tiles? + return false; + } + return true; +} // 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)) { + placeTiles(rowIdx, color, numTiles); + return true; + } + return false; +} // Player::takeTilesFromFactory + +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)) { + return false; // couldn't get that tile from the pool + } + if (poolPenalty) { + myTookPoolPenaltyThisTurn = poolPenalty; + myNumPenaltiesForTurn++; + } + placeTiles(rowIdx, color, numTiles); + return true; +} // Player::takeTilesFromPool +void Player::placeTiles(int rowIdx, azool::TileColor color, int numTiles) { + // increment row with # of new tiles + myRows[rowIdx].first += numTiles; + // TODO(debug) I can imagine a bug here where the row changes colors...make sure that's not possible + myRows[rowIdx].second = color; + int maxNumInRow = rowIdx + 1; + // if tiles overflow the row, take penalty(ies) + if (myRows[rowIdx].first > maxNumInRow) { + myNumPenaltiesForTurn += (myRows[rowIdx].first - maxNumInRow); + myRows[rowIdx].first = maxNumInRow; + } +} // Player::placeTiles + +void Player::endRound(bool& fullRow) { + // determine which rows are full of tiles + // update the grid + // send extra tiles back to the game board + // find the score for each tile placed + // color = row + column % 5 + // column = (row + 5 - color) % 5 + for (int rowIdx = 0; rowIdx < azool::NUMCOLORS; ++rowIdx) { + if (myRows[rowIdx].first == (rowIdx+1)) { + // filled a row. now place a tile on the grid + // determine which column it belongs to + // TODO(debug) -- possible bug -- what if color == -1? + int col = (5 + myRows[rowIdx].second - rowIdx) % 5; + myGrid[rowIdx][col] = true; + myScore += scoreTile(rowIdx, col); + // return extra tiles -- rowIdx = the number of leftover tiles + myBoardPtr->returnTilesToBag(rowIdx, myRows[rowIdx].second); + // reset rows for next turn + myRows[rowIdx].first = 0; + myRows[rowIdx].second = azool::NONE; + } + } +#ifdef DEBUG + std::cout << "Had " << myNumPenaltiesForTurn << " penalties this turn" << std::endl; +#endif + if (myNumPenaltiesForTurn >= PenaltyPoints.size()) { + myScore -= PenaltyPoints[PenaltyPoints.size() - 1]; + } + else { + myScore -= PenaltyPoints[myNumPenaltiesForTurn]; + } + // reset for next turn + myTookPoolPenaltyThisTurn = false; + myNumPenaltiesForTurn = 0; + // Check if there's a full row on the grid + for (int rowIdx = 0; rowIdx < azool::NUMCOLORS; ++rowIdx) { + fullRow = true; + for (int colIdx = 0; colIdx < azool::NUMCOLORS; ++colIdx) { + if (!myGrid[rowIdx][colIdx]) { + fullRow = false; + break; + } + } // iter over elements in row + if (fullRow) { + break; + // found a full row; will signal end of game + // break out of loop + } + } // iter over rows in grid +} // Player::endRound + +int Player::scoreTile(int tileRow, int tileCol) { + // search horizontally and vertically for points + int tileScore = 1; + // Get column score +#ifdef DEBUG + std::cout << "Placing tile at " << tileRow << "," << tileCol << std::endl; +#endif + for (int rowIdx = tileRow - 1; rowIdx > -1; --rowIdx) { +#ifdef DEBUG + std::cout << " Checking grid at " << rowIdx << "," << tileCol << ": " << myGrid[rowIdx][tileCol] << std::endl; +#endif + if (!myGrid[rowIdx][tileCol]) { + break; + } + tileScore++; + } // iterate above current tileRow + for (int rowIdx = tileRow + 1; rowIdx < azool::NUMCOLORS; ++rowIdx) { +#ifdef DEBBUG + std::cout << " Checking grid at " << rowIdx << "," << tileCol << ": " << myGrid[rowIdx][tileCol] << std::endl; +#endif + if (!myGrid[rowIdx][tileCol]) { + break; + } + tileScore++; + } // iterate from current tileRow down + // Get tileRow score + for (int colIdx = tileCol - 1; colIdx > -1; --colIdx) { +#ifdef DEBUG + std::cout << " Checking grid at " << tileRow << "," << colIdx << ": " << myGrid[tileRow][colIdx] << std::endl; +#endif + if (!myGrid[tileRow][colIdx]) { + break; + } + tileScore++; + } // iterate left of current column + for (int colIdx = tileCol + 1; colIdx < azool::NUMCOLORS; ++colIdx) { +#ifdef DEBUG + std::cout << " Checking grid at " << tileRow << "," << colIdx << ": " << myGrid[tileRow][colIdx] << std::endl; +#endif + if (!myGrid[tileRow][colIdx]) { + break; + } + tileScore++; + } // iterate from current column to right + std::cout << " Final Tile Score: " << tileScore << std::endl; + return tileScore; +} // Player::scoreTile + +void Player::finalizeScore() { + int numRows = 0; + int numCols = 0; + // compute bonus for rows + for (int rowIdx = 0; rowIdx < azool::NUMCOLORS; ++rowIdx) { + bool fullRow = true; + for (int colIdx = 0; colIdx < azool::NUMCOLORS; ++colIdx) { + if (!myGrid[rowIdx][colIdx]) { + fullRow = false; + break; + } + } // iterate over elements in row + if (fullRow) { + numRows++; + } + } // iterate over rows + myScore += (numRows*2); + // compute bonuses for columns + for (int colIdx = 0; colIdx < azool::NUMCOLORS; ++colIdx) { + bool fullCol = true; + for (int rowIdx = 0; rowIdx < azool::NUMCOLORS; ++rowIdx) { + if (!myGrid[rowIdx][colIdx]) { + fullCol = false; + } + } // iterate over elements in column + if (fullCol) { + numCols++; + } + } // iterate over columns + myScore += (numCols*7); + // compute bonuses for 5 of a kind + int numFives = 0; + for (int ii = 0; ii < azool::NUMCOLORS; ++ii) { + bool allFive = true; + // column = (row + 5 - color) % 5 + for (int rowIdx = 0; rowIdx < azool::NUMCOLORS; ++rowIdx) { + int colIdx = (ii + 5 - rowIdx) % 5; + if (!myGrid[rowIdx][colIdx]) { + allFive = false; + break; + } + } // check each row for color ii + if (allFive) { + numFives++; + } + } // iterate over all colors + myScore += (numFives*10); +} // Player::finalizeScore + +void Player::printMyBoard() const { + // TODO(implementation) - replace cout with ostream + std::cout << "*******************************\n"; + std::cout << "PLAYER: " << myName << "\n"; + std::cout << *myBoardPtr << "\n\n"; + for (int ii = 0; ii < azool::NUMCOLORS; ++ii) { + std::cout << ii+1 << ") "; + for (int jj = azool::NUMCOLORS; jj > (ii+1); --jj) { + std::cout << " "; + } + for (int jj = ii; jj > -1; --jj) { + if (myRows[ii].second == azool::NONE or + jj >= myRows[ii].first) { + std::cout << "_"; + } + else if (jj < myRows[ii].first) { + std::cout << azool::TileColorSyms[myRows[ii].second]; + } + } + // print grid row + std::cout << " |"; + for (int jj = 0; jj < azool::NUMCOLORS; ++jj) { + // color = row + column % 5 + char color = (ii + jj) % 5; + if (myGrid[ii][jj]) { + std::cout << static_cast(azool::TileColorSyms[color] - 32) << "|"; + } + else { + std::cout << azool::TileColorSyms[color] << "|"; + } + } + std::cout << "\n"; + } // iterate over rows + std::cout << "Penalties: " << myNumPenaltiesForTurn << std::endl; + std::cout << "Score: " << myScore << std::endl; + // TODO(feature) - print penalty tiles (?) +} // Player::printMyBoard + +bool Player::discardFromFactory(int factoryIdx, azool::TileColor color) { + int numTiles = -1; + if (myBoardPtr->takeTilesFromFactory(factoryIdx, color, numTiles)) { + myNumPenaltiesForTurn += numTiles; + return true; + } + return false; +} // Player::discardFromFactory + +bool Player::discardFromPool(azool::TileColor color) { + bool poolPenalty = false; + int numTiles = -1; + if (myBoardPtr->takeTilesFromPool(color, numTiles, poolPenalty)) { + if (poolPenalty) { + myNumPenaltiesForTurn++; + } + myNumPenaltiesForTurn += numTiles; + return true; + } + return false; +} // Player::discardFromPool + +namespace { + int promptForFactoryIdx(int maxNumFactories) { + static const char* promptFactoryIdxDraw = "Which factory? enter index\n"; + char factInput[256]; // wayy more than we need + std::cout << promptFactoryIdxDraw << std::flush; + std::cin >> factInput; + int factIdx = std::atoi(factInput); + if (factIdx < 1 or factIdx > maxNumFactories) { + return -1; + } + return factIdx; + } + azool::TileColor promptForColor() { + static const char* promptColorDraw = "Which color? [r|b|g|y|k]\n"; + char colorInput = '\0'; + std::cout << promptColorDraw << std::flush; + std::cin >> colorInput; + switch(colorInput) { + case 'r': + return azool::RED; + break; + case 'b': + return azool::BLUE; + break; + case 'g': + return azool::GREEN; + break; + case 'y': + return azool::YELLOW; + break; + case 'k': + return azool::BLACK; + break; + default: + return azool::NONE; + } // end switch + return azool::NONE; + } + int promptForRow() { + static const char* promptRowPlacement = "Place on which row? enter number [1-5]\n"; + char rowInput[256] = {0}; + std::cout << promptRowPlacement; + std::cin >> rowInput; + int rowIdx = std::atoi(rowInput); + if (rowIdx < 1 or rowIdx > azool::NUMCOLORS) { + return -1; + } + return rowIdx; + } +} // anonymous namespace + +void Player::takeTurn() { + // print game board, handle user input + if (myBoardPtr->endOfRound()) return; + printMyBoard(); + static const char* promptDrawInput = "What would you like to do?\n" + "[f] take from factory [p] take from pool " + "[d] discard tile(s) " + "[P] print game board again\n"; + static const char* promptDiscardInput = "From factory or pool? [f|p]\n"; + // TODO(feature) -- remove options when they're not valid? + // (ie don't print [f] factory when there are no factories left) + static const char* invalidEntryMessage = "Invalid entry, try again.\n"; + static const char* invalidMoveMessage = "That move was invalid, try again.\n"; + bool fullInput = false; + while (!fullInput) { + std::cout << promptDrawInput << std::flush; + char drawType; + std::cin >> drawType; + if (drawType == 'f') { + int factIdx = promptForFactoryIdx(myBoardPtr->numFactories()); + // draw from factory + if (factIdx == -1) { + std::cout << invalidEntryMessage << std::flush; + continue; + } + azool::TileColor colorSelection = promptForColor(); + if (colorSelection == azool::NONE) { + std::cout << invalidEntryMessage << std::flush; + continue; + } + int rowSelection = promptForRow(); + if (rowSelection == -1) { + std::cout << invalidEntryMessage << std::flush; + continue; + } + // user enters 1-5; we use 0 indexing internally + if (!takeTilesFromFactory(factIdx - 1, colorSelection, rowSelection - 1)) { + std::cout << invalidMoveMessage << std::flush; + continue; + } + fullInput = true; + } + else if (drawType == 'p') { + // draw from pool + azool::TileColor colorSelection = promptForColor(); + if (colorSelection == azool::NONE) { + std::cout << invalidEntryMessage << std::flush; + continue; + } + int rowSelection = promptForRow(); + if (rowSelection == -1) { + std::cout << invalidEntryMessage << std::flush; + continue; + } + // user enters 1-5; we use 0 indexing internally + if (!takeTilesFromPool(colorSelection, rowSelection - 1)) { + std::cout << invalidMoveMessage << std::flush; + continue; + } + fullInput = true; + } + else if (drawType == 'd') { + // TODO(implementation) (make these strings more betterer) + std::cout << promptDiscardInput << std::flush; + char discardFrom = '\0'; + std::cin >> discardFrom; + if (discardFrom == 'f') { + int factIdx = promptForFactoryIdx(myBoardPtr->numFactories()); + // draw from factory + if (factIdx == -1) { + std::cout << invalidEntryMessage << std::flush; + continue; + } + azool::TileColor colorSelection = promptForColor(); + if (colorSelection == azool::NONE) { + std::cout << invalidEntryMessage << std::flush; + continue; + } + int numTiles = -1; + if (!discardFromFactory(factIdx - 1, colorSelection)) { + std::cout << invalidMoveMessage << std::flush; + continue; + } + fullInput = true; + } // from factory + else if (discardFrom == 'p') { + azool::TileColor colorSelection = promptForColor(); + if (colorSelection == azool::NONE) { + std::cout << invalidEntryMessage << std::flush; + continue; + } + if (!discardFromPool(colorSelection)) { + std::cout << invalidMoveMessage << std::flush; + continue; + } + fullInput = true; + } // discard from pool + } + else if (drawType == 'P') { + printMyBoard(); + } + else { + std::cout << invalidEntryMessage << std::flush; + } + } // while !fullinput + // options: take tile from pool or take tile from factory + // TODO(feature) - temporary...reprint board and hold briefly? +} // Player::takeTurn diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..517e1ed --- /dev/null +++ b/src/main.cc @@ -0,0 +1,64 @@ +#include "GameBoard.h" +#include "Player.h" +#include + +// who manages turns and rounds? probably the main function + +void testPrint(GameBoard* game) { + game->dealTiles(); + Player p1(game); + p1.printMyBoard(); + return; +} + +void playGame(GameBoard* game) { + game->dealTiles(); + std::vector players = {new Player(game, "P1"), new Player(game, "P2")}; + bool endOfGame = false; + Player* firstPlayer = players[0]; // pointers to keep track of first and second player + Player* secondPlayer = players[1]; + while (!endOfGame) { + while (!game->endOfRound()) { + // TODO figure out how order will work for > 2 players + firstPlayer->takeTurn(); + secondPlayer->takeTurn(); + } + std::cout << "End of round!" << std::endl; + bool p0EndsGame = false; + bool p1EndsGame = false; + players[0]->endRound(p0EndsGame); + players[1]->endRound(p1EndsGame); + if (players[0]->tookPenalty()) { + firstPlayer = players[0]; + secondPlayer = players[1]; + } + else if (players[1]->tookPenalty()) { + firstPlayer = players[1]; + secondPlayer = players[0]; + } + else { + std::cerr << "SOMETHING WEIRD - SoMeone has to go first...\n" << std::flush; + firstPlayer = players[0]; + secondPlayer = players[1]; + } + if (!(p0EndsGame or p1EndsGame)) { + game->dealTiles(); + } + } + players[0]->finalizeScore(); + players[1]->finalizeScore(); + std::cout << " Final scores:\n" + << players[0]->getScore() << "\n" << std::flush; + players[0]->printMyBoard(); + std::cout << players[0]->getScore() << "\n" << std::flush; + players[1]->printMyBoard(); + if (players[0]) delete players[0]; + if (players[1]) delete players[1]; +} + +int main() { + GameBoard* game = new GameBoard(); + playGame(game); + if (game) delete game; + return 0; +}