initial working version
This commit is contained in:
commit
b71d81d8d6
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
bin/*
|
2
Makefile
Normal file
2
Makefile
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
azool:
|
||||||
|
g++ src/*.cc -I./include -o bin/azool -Werror -Weffc++ -std=c++11
|
140
doc/design.txt
Normal file
140
doc/design.txt
Normal file
@ -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<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
|
||||||
|
};
|
43
include/GameBoard.h
Normal file
43
include/GameBoard.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#ifndef GAMEBOARD_H_
|
||||||
|
#define GAMEBOARD_H_
|
||||||
|
#include <vector>
|
||||||
|
#include <ostream>
|
||||||
|
#include <cstring>
|
||||||
|
#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<Factory> 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<azool::TileColor> tileBag; // initialize to 20 of each color
|
||||||
|
bool lastRound; // initialize to false
|
||||||
|
};
|
||||||
|
#endif // GAMEBOARD_H_
|
41
include/Player.h
Normal file
41
include/Player.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#ifndef PLAYER_H_
|
||||||
|
#define PLAYER_H_
|
||||||
|
#include "GameBoard.h"
|
||||||
|
#include "tile_utils.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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<int, azool::TileColor> TileRow;
|
||||||
|
TileRow myRows[azool::NUMCOLORS];
|
||||||
|
GameBoard* const myBoardPtr;
|
||||||
|
int myScore;
|
||||||
|
int myNumPenaltiesForTurn;
|
||||||
|
bool myTookPoolPenaltyThisTurn;
|
||||||
|
const std::vector<int> PenaltyPoints = {0, 1, 2, 3, 5, 7, 10, 13, 15};
|
||||||
|
std::string myName;
|
||||||
|
}; // class Player
|
||||||
|
#endif // PLAYER_H_
|
23
include/tile_utils.h
Normal file
23
include/tile_utils.h
Normal file
@ -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_
|
132
src/GameBoard.cc
Normal file
132
src/GameBoard.cc
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
#include "GameBoard.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
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<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;
|
||||||
|
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<int>(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<azool::TileColor>(ii));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
whiteTileInPool = true;
|
||||||
|
} // GameBoard::resetBoard
|
449
src/Player.cc
Normal file
449
src/Player.cc
Normal file
@ -0,0 +1,449 @@
|
|||||||
|
#include "Player.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
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<int>(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<char>(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
|
64
src/main.cc
Normal file
64
src/main.cc
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#include "GameBoard.h"
|
||||||
|
#include "Player.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// 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<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) {
|
||||||
|
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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user