Compare commits

...

3 Commits
1.0 ... master

Author SHA1 Message Date
eay
7ba82b569b add license and readme 2023-06-04 15:59:18 -07:00
eay
6d6716acbf bugfixes for starting new round / finishing game
also rename some variables for clarity
2023-06-04 15:42:43 -07:00
eay
0f5928266b Clean up some todos -
print player's board by creating string (not using cout)
dynamically set # of factories based on # of players
reuse random number generator (no hard code seed)
2023-06-01 17:14:02 -07:00
8 changed files with 103 additions and 227 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 emclrk
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1
README.md Normal file
View File

@ -0,0 +1 @@
TODO: write readme :)

View File

@ -1,140 +0,0 @@
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<int, TileColor> 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<TileColor> tileBag; // initialize to 20 of each color
bool lastRound; // initialize to false
};

View File

@ -3,6 +3,7 @@
#include <vector>
#include <ostream>
#include <cstring>
#include <random>
#include "tile_utils.h"
class GameBoard {
@ -14,8 +15,7 @@ public:
int tileCounts[azool::NUMCOLORS];
}; // struct Factory
GameBoard();
GameBoard(int factories);
GameBoard(int nPlayers=2);
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);
@ -31,6 +31,8 @@ public:
return tileFactories.empty();
}
private:
GameBoard(const GameBoard&) = delete;
GameBoard operator=(const GameBoard&) = delete;
void resetBoard();
// - vector of factories?
std::vector<Factory> tileFactories;
@ -39,5 +41,6 @@ private:
bool whiteTileInPool; // initialize to true
std::vector<azool::TileColor> tileBag; // initialize to 20 of each color
bool lastRound; // initialize to false
std::default_random_engine rng;
};
#endif // GAMEBOARD_H_

View File

@ -16,8 +16,8 @@ public:
void endRound(bool& fullRow);
void finalizeScore();
int getScore() const { return myScore; }
void printMyBoard() const;
bool tookPenalty() const { return myTookPoolPenaltyThisTurn; }
std::string printMyBoard() const;
bool tookPenalty() const { return myTookPoolPenaltyThisRound; }
const std::string getPlayerName() const { return myName; }
private:
@ -33,8 +33,8 @@ private:
TileRow myRows[azool::NUMCOLORS];
GameBoard* const myBoardPtr;
int myScore;
int myNumPenaltiesForTurn;
bool myTookPoolPenaltyThisTurn;
int myNumPenaltiesForRound;
bool myTookPoolPenaltyThisRound;
const std::vector<int> PenaltyPoints = {0, 1, 2, 3, 5, 7, 10, 13, 15};
std::string myName;
}; // class Player

View File

@ -1,34 +1,19 @@
#include "GameBoard.h"
#include <algorithm>
#include <random>
#include <chrono>
GameBoard::GameBoard() :
GameBoard::GameBoard(int numPlayers) :
tileFactories(),
maxNumFactories(5), // TODO(feature) base on # of players? 2n+1
maxNumFactories(numPlayers*2+1),
pool(),
whiteTileInPool(true),
tileBag(),
lastRound(false) {
lastRound(false),
rng(std::default_random_engine(
std::chrono::system_clock::now().time_since_epoch().count())) {
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<azool::TileColor>(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;
@ -102,8 +87,7 @@ void GameBoard::dealTiles() {
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));
std::shuffle(tileBag.begin(), tileBag.end(), rng);
auto itr = tileBag.begin();
for (int ii = 0; ii < numFactories and itr != tileBag.end(); ++ii) {
Factory fact;

View File

@ -1,6 +1,7 @@
#include "Player.h"
#include <iostream>
#include <cstring>
#include <sstream>
Player::Player(GameBoard* const board, std::string name) :
myGrid(),
@ -8,8 +9,8 @@ Player::Player(GameBoard* const board, std::string name) :
myBoardPtr(board),
myName(name),
myScore(0),
myNumPenaltiesForTurn(0),
myTookPoolPenaltyThisTurn(false) {
myNumPenaltiesForRound(0),
myTookPoolPenaltyThisRound(false) {
int gridSize = azool::NUMCOLORS * azool::NUMCOLORS;
memset(myGrid, 0, gridSize*sizeof(bool));
for (int ii = 0; ii < azool::NUMCOLORS; ++ii) {
@ -61,12 +62,13 @@ bool Player::takeTilesFromPool(azool::TileColor color, int rowIdx) {
return false; // couldn't get that tile from the pool
}
if (poolPenalty) {
myTookPoolPenaltyThisTurn = poolPenalty;
myNumPenaltiesForTurn++;
myTookPoolPenaltyThisRound = true;
myNumPenaltiesForRound++;
}
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;
@ -75,7 +77,7 @@ void Player::placeTiles(int rowIdx, azool::TileColor color, int numTiles) {
int maxNumInRow = rowIdx + 1;
// if tiles overflow the row, take penalty(ies)
if (myRows[rowIdx].first > maxNumInRow) {
myNumPenaltiesForTurn += (myRows[rowIdx].first - maxNumInRow);
myNumPenaltiesForRound += (myRows[rowIdx].first - maxNumInRow);
myRows[rowIdx].first = maxNumInRow;
}
} // Player::placeTiles
@ -102,18 +104,19 @@ void Player::endRound(bool& fullRow) {
myRows[rowIdx].second = azool::NONE;
}
}
#ifdef DEBUG
std::cout << "Had " << myNumPenaltiesForTurn << " penalties this turn" << std::endl;
#endif
if (myNumPenaltiesForTurn >= PenaltyPoints.size()) {
if (myNumPenaltiesForRound >= PenaltyPoints.size()) {
myScore -= PenaltyPoints[PenaltyPoints.size() - 1];
}
else {
myScore -= PenaltyPoints[myNumPenaltiesForTurn];
myScore -= PenaltyPoints[myNumPenaltiesForRound];
}
// reset for next turn
myTookPoolPenaltyThisTurn = false;
myNumPenaltiesForTurn = 0;
// FOR THIS REASON
// main loop needs to check who took penalty BEFORE calling this function
// I don't love that, but not sure what else to do
myTookPoolPenaltyThisRound = false;
myNumPenaltiesForRound = 0;
// Check if there's a full row on the grid
for (int rowIdx = 0; rowIdx < azool::NUMCOLORS; ++rowIdx) {
fullRow = true;
@ -175,7 +178,9 @@ int Player::scoreTile(int tileRow, int tileCol) {
}
tileScore++;
} // iterate from current column to right
#ifdef DEBUG
std::cout << " Final Tile Score: " << tileScore << std::endl;
#endif
return tileScore;
} // Player::scoreTile
@ -183,6 +188,7 @@ void Player::finalizeScore() {
int numRows = 0;
int numCols = 0;
// compute bonus for rows
// TODO: print bonus info
for (int rowIdx = 0; rowIdx < azool::NUMCOLORS; ++rowIdx) {
bool fullRow = true;
for (int colIdx = 0; colIdx < azool::NUMCOLORS; ++colIdx) {
@ -228,48 +234,49 @@ void Player::finalizeScore() {
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";
std::string Player::printMyBoard() const {
std::ostringstream oss;
oss << "*******************************\n";
oss << "PLAYER: " << myName << "\n";
oss << *myBoardPtr << "\n\n";
for (int ii = 0; ii < azool::NUMCOLORS; ++ii) {
std::cout << ii+1 << ") ";
oss << ii+1 << ") ";
for (int jj = azool::NUMCOLORS; jj > (ii+1); --jj) {
std::cout << " ";
oss << " ";
}
for (int jj = ii; jj > -1; --jj) {
if (myRows[ii].second == azool::NONE or
jj >= myRows[ii].first) {
std::cout << "_";
oss << "_";
}
else if (jj < myRows[ii].first) {
std::cout << azool::TileColorSyms[myRows[ii].second];
oss << azool::TileColorSyms[myRows[ii].second];
}
}
// print grid row
std::cout << " |";
oss << " |";
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<char>(azool::TileColorSyms[color] - 32) << "|";
oss << static_cast<char>(azool::TileColorSyms[color] - 32) << "|";
}
else {
std::cout << azool::TileColorSyms[color] << "|";
oss << azool::TileColorSyms[color] << "|";
}
}
std::cout << "\n";
oss << "\n";
} // iterate over rows
std::cout << "Penalties: " << myNumPenaltiesForTurn << std::endl;
std::cout << "Score: " << myScore << std::endl;
oss << "Penalties: " << myNumPenaltiesForRound << "\n";
oss << "Score: " << myScore << "\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)) {
myNumPenaltiesForTurn += numTiles;
myNumPenaltiesForRound += numTiles;
return true;
}
return false;
@ -280,9 +287,9 @@ bool Player::discardFromPool(azool::TileColor color) {
int numTiles = -1;
if (myBoardPtr->takeTilesFromPool(color, numTiles, poolPenalty)) {
if (poolPenalty) {
myNumPenaltiesForTurn++;
myNumPenaltiesForRound++;
}
myNumPenaltiesForTurn += numTiles;
myNumPenaltiesForRound += numTiles;
return true;
}
return false;
@ -291,10 +298,10 @@ bool Player::discardFromPool(azool::TileColor color) {
namespace {
int promptForFactoryIdx(int maxNumFactories) {
static const char* promptFactoryIdxDraw = "Which factory? enter index\n";
char factInput[256]; // wayy more than we need
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);
int factIdx = std::atoi(&factInput);
if (factIdx < 1 or factIdx > maxNumFactories) {
return -1;
}
@ -328,10 +335,10 @@ namespace {
}
int promptForRow() {
static const char* promptRowPlacement = "Place on which row? enter number [1-5]\n";
char rowInput[256] = {0};
std::cout << promptRowPlacement;
char rowInput;
std::cout << promptRowPlacement << std::flush;
std::cin >> rowInput;
int rowIdx = std::atoi(rowInput);
int rowIdx = std::atoi(&rowInput);
if (rowIdx < 1 or rowIdx > azool::NUMCOLORS) {
return -1;
}
@ -342,9 +349,10 @@ namespace {
void Player::takeTurn() {
// print game board, handle user input
if (myBoardPtr->endOfRound()) return;
printMyBoard();
std::cout << printMyBoard();
static const char* promptDrawInput = "What would you like to do?\n"
"[f] take from factory [p] take from pool "
"[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";
@ -401,7 +409,6 @@ void Player::takeTurn() {
fullInput = true;
}
else if (drawType == 'd') {
// TODO(implementation) (make these strings more betterer)
std::cout << promptDiscardInput << std::flush;
char discardFrom = '\0';
std::cin >> discardFrom;
@ -438,12 +445,14 @@ void Player::takeTurn() {
} // discard from pool
}
else if (drawType == 'P') {
printMyBoard();
std::cout << 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?
std::cout << printMyBoard() << std::flush;
// flush out any inputs still in the buffer
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
} // Player::takeTurn

View File

@ -7,27 +7,26 @@
void testPrint(GameBoard* game) {
game->dealTiles();
Player p1(game);
p1.printMyBoard();
std::cout << p1.printMyBoard();
return;
}
void playGame(GameBoard* game) {
game->dealTiles();
std::vector<Player*> 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) {
bool p0EndsGame = false;
bool p1EndsGame = false;
while (!(p0EndsGame or p1EndsGame)) {
game->dealTiles();
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);
// check who took penalty
// nees to be done before claling player->endRound()
if (players[0]->tookPenalty()) {
firstPlayer = players[0];
secondPlayer = players[1];
@ -41,19 +40,18 @@ void playGame(GameBoard* game) {
firstPlayer = players[0];
secondPlayer = players[1];
}
if (!(p0EndsGame or p1EndsGame)) {
game->dealTiles();
}
std::cout << "End of round!" << std::endl;
players[0]->endRound(p0EndsGame);
players[1]->endRound(p1EndsGame);
}
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];
std::cout << " Final scores:\n" << players[0]->getScore() << "\n" << players[1]->getScore() << "\n" << std::flush;
std::cout << players[0]->printMyBoard();
std::cout << players[1]->printMyBoard();
for (auto player : players) {
if (player) delete player;
}
}
int main() {