commit 68197111c4268f263dd1cad4c86ba0d66d2f8c56 Author: Riyyi Date: Thu Feb 18 15:43:17 2021 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b42bb6d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +space-walk diff --git a/board.cpp b/board.cpp new file mode 100644 index 0000000..febfa4d --- /dev/null +++ b/board.cpp @@ -0,0 +1,74 @@ +#include + +#include "board.h" +#include "spaceship.h" + +Board::Board() { + // Reserve 0 SpaceShips of memory for each planet + for (unsigned char i = 0; i < BOARD_SIZE; i++) { + this->planets[i] = new std::vector(0); + } +} + +Board::~Board() { + for (auto planet : this->planets) { + delete planet; + } +} + +SpaceShip *Board::getShip(unsigned char planet, unsigned char pos) { + if (planet >= BOARD_SIZE || pos >= this->planets[planet]->size()) { + return new SpaceShip; + } + + return this->planets[planet]->at(pos); +} + +std::vector *Board::getShips(unsigned char planet) { + if (planet >= BOARD_SIZE) { + return nullptr; + } + + return this->planets[planet]; +} + +bool Board::setShip(unsigned char planet, SpaceShip *ship) { + if (planet >= BOARD_SIZE) { + return false; + } + + // Insert if the ship doesn't already exist on a planet + if (ship->getPlanet() == PLANET_UNSET) { + ship->setPlanet(planet); + this->planets[planet]->push_back(ship); + this->sortPlanet(planet); + } + + return true; +} + +bool Board::moveShip(unsigned char planet, SpaceShip *ship) { + char shipPlanet = ship->getPlanet(); + if (planet >= BOARD_SIZE || shipPlanet == -1 || shipPlanet == planet) { + return false; + } + + auto tmp = this->planets[(unsigned char)shipPlanet]; + // Move ship to the end of the vector and then erase the last element + // (Erase-remove idiom) + tmp->erase(std::remove(tmp->begin(), tmp->end(), ship), tmp->end()); + + ship->setPlanet(planet); + this->planets[planet]->push_back(ship); + this->sortPlanet(planet); + + return true; +} + +void Board::sortPlanet(unsigned char planet) { + // Sort all ships on the planet, using function/functor/lambda + std::sort(this->planets[planet]->begin(), this->planets[planet]->end(), + [](SpaceShip *s1, SpaceShip *s2) -> bool { + return s1->getSize() > s2->getSize(); + }); +} diff --git a/board.h b/board.h new file mode 100644 index 0000000..94f0309 --- /dev/null +++ b/board.h @@ -0,0 +1,28 @@ +#ifndef BOARD_H +#define BOARD_H + +#define BOARD_SIZE 14 + +#include + +class SpaceShip; + +class Board +{ +public: + Board(); + ~Board(); + + SpaceShip *getShip(unsigned char planet, unsigned char pos); + std::vector *getShips(unsigned char planet); + bool setShip(unsigned char planet, SpaceShip *ship); + bool moveShip(unsigned char planet, SpaceShip *ship); + void sortPlanet(unsigned char planet); + +private: + // Data layout: + // Black hole, 6 planets, 6 planets, black hole + std::vector *planets[BOARD_SIZE]; +}; + +#endif // BOARD_H diff --git a/boardwindow.cpp b/boardwindow.cpp new file mode 100644 index 0000000..1c7a8bd --- /dev/null +++ b/boardwindow.cpp @@ -0,0 +1,84 @@ +#include "board.h" +#include "boardwindow.h" +#include "maingame.h" +#include "spaceship.h" + +BoardWindow::BoardWindow(int height, int width, Board *board) : + height(height), + width(width), + board(board) { +} + +BoardWindow::~BoardWindow() { +} + +void BoardWindow::update() { + Window::update(); + this->resize(); + + int twoThirds = (this->getRows() / 3) * 2; + int halfCols = this->getCols() / 2; + int y = (twoThirds / 2) - 12; + int x = halfCols - 4 - 1; + // Print black hole + this->printBlackHole(y, x, 0); + + y += 6; + x = (this->getCols() / 2) - (46 / 2) - 1; + // Print all planets + for (int i = 1; i < BOARD_SIZE - 1; i++) { + this->printPlanet(y, x, i); + x += 8; + + if (i == (BOARD_SIZE - 2) / 2) { + x = (this->getCols() / 2) - (46 / 2) - 1; + y += 6; + } + } + + y += 6; + x = halfCols - 4 - 1; + // Print black hole + this->printBlackHole(y, x, BOARD_SIZE - 1); +} + +void BoardWindow::resize() { + int rows = this->getMaxRows() - this->height; + int cols = this->getMaxCols() - this->width; + this->setWindow(rows, cols, 0, 0); + Window::resize(); +} + +void BoardWindow::printBlackHole(int y, int x, int planet) { + this->print("+------+", y, x); + this->print("| |", y + 1, x); + this->print("| |", y + 2, x); + this->print("| |", y + 3, x); + this->print("+------+", y + 4, x); + + this->printSpaceShip(y + 1, x + 1, planet, 6); +} + +void BoardWindow::printPlanet(int y, int x, int planet) { + this->print("+----+", y, x); + this->print("| |", y + 1, x); + this->print("| |", y + 2, x); + this->print("| |", y + 3, x); + this->print("+----+", y + 4, x); + + this->printSpaceShip(y + 1, x + 1, planet, 4); +} + +void BoardWindow::printSpaceShip(int y, int x, int planet, int width) { + auto tmp = this->board->getShips(planet); + int size = (int)tmp->size() + 1; + for (int i = 1; i < size; i++) { + tmp->at(i - 1)->render(this, y, x, false); + x++; + + if (i != 1 && i % width == 0) { + y++; + x -= width; + } + } +} diff --git a/boardwindow.h b/boardwindow.h new file mode 100644 index 0000000..21e8c47 --- /dev/null +++ b/boardwindow.h @@ -0,0 +1,26 @@ +#ifndef BOARDWINDOW_H +#define BOARDWINDOW_H + +#include "window.h" + +class Board; + +class BoardWindow : public Window +{ +public: + BoardWindow(int height, int width, Board *board); + ~BoardWindow(); + + void update(); + void resize(); + void printBlackHole(int y, int x, int planet); + void printPlanet(int y, int x, int planet); + void printSpaceShip(int y, int x, int planet, int width); + +private: + int height; + int width; + Board *board; +}; + +#endif // BOARDWINDOW_H diff --git a/gamestate.h b/gamestate.h new file mode 100644 index 0000000..c8fb105 --- /dev/null +++ b/gamestate.h @@ -0,0 +1,75 @@ +#ifndef GAMESTATE +#define GAMESTATE + +#include // time +#include // sleep_for + +#include "inputoutput.h" +#include "window.h" + +class GameState +{ +public: + // Makes sure that the inherited class's destructor is called + virtual ~GameState() {} + + virtual void initialize() {} + virtual void update() {} + virtual void render() {} + virtual void destroy() {} + + static void sleep(int amount) { + // Sleep for amount ms + std::this_thread::sleep_for(std::chrono::milliseconds(amount)); + } +}; + +class GameStateManager +{ +public: + GameStateManager() : state(nullptr) { + Window::initialize(); + } + + ~GameStateManager() { + if(this->state != nullptr) { + this->state->destroy(); + delete this->state; + } + + Window::destroy(); + } + + void setState(GameState* state) { + if(this->state != nullptr) { + this->state->destroy(); + delete this->state; + } + + this->state = state; + if(this->state != nullptr) { + this->state->initialize(); + } + } + + void update() { + GameState::sleep(15); + + if(this->state != nullptr) { + this->state->update(); + } + } + + void render() { + if(this->state != nullptr) { + this->state->render(); + } + } + +private: + GameState *state; +}; + +extern GameStateManager gameStateManager; + +#endif // GAMESTATE diff --git a/infowindow.cpp b/infowindow.cpp new file mode 100644 index 0000000..e5d84d7 --- /dev/null +++ b/infowindow.cpp @@ -0,0 +1,47 @@ +#include "infowindow.h" +#include "maingame.h" +#include "player.h" +#include "spaceship.h" + +InfoWindow::InfoWindow(int height, int width, Player **player, SpaceShip **ship) : + height(height), + width(width), + player(player), + ship(ship) { +} + +InfoWindow::~InfoWindow() { +} + +void InfoWindow::update() { + Window::update(); + this->resize(); + + int y = 0; + int x = 1; + for (unsigned char i = 0; i < PLAYER_SIZE; i++) { + this->player[i]->render(this, y, x, i + 1); + y += 6; + + int shipIdx = i * SHIP_SIZE / 2; + for (unsigned char j = shipIdx; j < shipIdx + SHIP_SIZE / 2; j++) { + this->ship[j]->render(this, y, x); + x++; + + if (x > 3) { + y++; + x = 1; + } + } + + y += 2; + } +} + +void InfoWindow::resize() { + int rows = this->getMaxRows() - this->height; + int x = this->getMaxCols() - this->width; + int cols = this->getMaxCols() - x; + this->setWindow(rows, cols, 0, x); + Window::resize(); +} diff --git a/infowindow.h b/infowindow.h new file mode 100644 index 0000000..5d21f2a --- /dev/null +++ b/infowindow.h @@ -0,0 +1,25 @@ +#ifndef INFOWINDOW_H +#define INFOWINDOW_H + +#include "window.h" + +class Player; +class SpaceShip; + +class InfoWindow : public Window +{ +public: + InfoWindow(int height, int width, Player **player, SpaceShip **ship); + ~InfoWindow(); + + void update(); + void resize(); + +private: + int height; + int width; + Player **player; + SpaceShip **ship; +}; + +#endif // INFOWINDOW_H diff --git a/inputoutput.cpp b/inputoutput.cpp new file mode 100644 index 0000000..4ff1977 --- /dev/null +++ b/inputoutput.cpp @@ -0,0 +1,69 @@ +#include // size_t +#include + +#include "inputoutput.h" + +void IO::initialize() { +} + +void IO::update() { + IO::keyCode = getch(); + + if (keyCode == KEY_ESC) { + IO::setQuit(); + return; + } +} + +std::string IO::trim(std::string str) { + size_t first = str.find_first_not_of(" "); + if (first == std::string::npos) { + return ""; + } + size_t last = str.find_last_not_of(" "); + return str.substr(first, (last - first + 1)); +} + +std::string IO::untrim(std::string str, unsigned int length) { + unsigned int strLength = str.length(); + if (length > strLength) { + int halfDiff = (length - strLength) / 2; + + std::string tmpStr; + for (int i = 0; i < halfDiff; i++) { + tmpStr += " "; + } + + str.insert(0, tmpStr); + str.append(tmpStr); + } + + return str; +} + +int IO::getKeyCode() { + return IO::keyCode; +} + +std::string IO::getKeyName() { + return std::string(keyname(IO::keyCode)); +} + +std::string IO::getStrInput() { + echo(); + char *tmp = new char(); + getstr(tmp); + noecho(); + return std::string(tmp); +} + +bool IO::getQuit() { + return IO::quit; +} + +void IO::setQuit() { + IO::quit = true; +} + +int IO::keyCode = ERR; +bool IO::quit = false; diff --git a/inputoutput.h b/inputoutput.h new file mode 100644 index 0000000..51bbf29 --- /dev/null +++ b/inputoutput.h @@ -0,0 +1,29 @@ +#ifndef INPUTOUTPUT_H +#define INPUTOUTPUT_H + +#define KEY_ESC 27 +#define KEY_SPACE 32 + +#include + +class IO +{ +public: + static void initialize(); + static void update(); + + static std::string trim(std::string str); + static std::string untrim(std::string str, unsigned int length); + + static int getKeyCode(); + static std::string getKeyName(); + static std::string getStrInput(); + static bool getQuit(); + static void setQuit(); + +private: + static int keyCode; + static bool quit; +}; + +#endif // INPUTOUTPUT_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..367d8b1 --- /dev/null +++ b/main.cpp @@ -0,0 +1,56 @@ +/* + * Rick van Vonderen + * 0945444 + * TI2B + * + * Avoid repeated writing by using: + * - functions with parameters (no globals) instead of almost identical pieces of code + * - repetitive loops and arrays instead of identical activities + * - constants instead of identical values + * + * Divide your program into modules and/or classes that: + * - have an explicit, well-defined interface + * - be as independent as possible + * - to be developed and tested separately + * + * Make your code understandable for others by using: + * - Clear names in camel case, only start class names with a capital letter, consistently indent + * - Clear, concise commentary, only where this adds something + * - Standard terminology from the customer's field or application + * + * ClassExample + * variableExample + * DEFINE_EXAMPLE + */ + +#include // strcmp() +#include // refresh() + +#include "gamestate.h" +#include "inputoutput.h" +#include "mainmenu.h" +#include "test.h" + +GameStateManager gameStateManager; + +int main(int argc, char *argv[]) { + +#ifdef DEBUG + // IO::print("DEBUG MODE ENABLED"); + refresh(); + GameState::sleep(1000); + + if (argc >= 2 && strcmp(argv[1], "-t") == 0) { + Test(); + } +#endif + + gameStateManager.setState(new MainMenu()); + + while (!IO::getQuit()) { + gameStateManager.render(); + gameStateManager.update(); + } + + return 0; +} diff --git a/maingame.cpp b/maingame.cpp new file mode 100644 index 0000000..d09920d --- /dev/null +++ b/maingame.cpp @@ -0,0 +1,338 @@ +#include + +#include "board.h" +#include "boardwindow.h" +#include "inputoutput.h" +#include "maingame.h" +#include "mainmenu.h" +#include "messagebox.h" +#include "player.h" +#include "selectionbox.h" +#include "spaceship.h" +#include "window.h" +#include "infowindow.h" + +MainGame::MainGame(Player **player) : + help("Phase 1!\n\nThe players take turns, placing a spaceship on a planet\nof their choice."), + phase(false), + playerTurn(0), + queuePlanet(-1), + player(player), + board(nullptr), + ship(nullptr), + boardWindow(nullptr), + infoWindow(nullptr) { + // Reserve 0 spaceships of memory for the queue + this->queue = new std::vector(0); +} + +void MainGame::initialize() { + this->board = new Board(); + + this->ship = new SpaceShip*[SHIP_SIZE] { nullptr }; + unsigned char cCounter = 0; + unsigned char colors[PLAYER_SIZE] = { PAIR_RED_BLACK, PAIR_BLUE_BLACK }; + unsigned char sCounter = 0; + unsigned char sizes[3] = { SpaceShip::SMALL, SpaceShip::MEDIUM, SpaceShip::BIG }; + // Create 9 ships for each player, 3 of each size + for (unsigned char i = 0; i < SHIP_SIZE; i++) { + if (i != 0 && i % (SHIP_SIZE / PLAYER_SIZE) == 0) { + cCounter++; + } + + if (i != 0 && i % 3 == 0) { + sCounter++; + } + + if (sCounter >= 3) { + sCounter = 0; + } + + this->ship[i] = new SpaceShip(colors[cCounter], sizes[sCounter]); + } + + this->boardWindow = new BoardWindow(0, 16, this->board); + this->infoWindow = new InfoWindow(0, 16, this->player, this->ship); + + // Display help message + MainGame::render(); + MessageBox(this->help); +} + +void MainGame::update() { + MainGame::sleep(250); + + // Display new help message on phase change + if (this->changePhase) { + this->phase = true; + this->changePhase = false; + + this->help = "Phase 2!\n\nThe players take turns, evacuating a planet of their choice."; + MessageBox(this->help); + return; + } + + if (!this->phase) { + this->phase1(); + } + else { + this->phase2(); + } +} + +void MainGame::render() { + this->boardWindow->clear(); + this->infoWindow->clear(); + + this->boardWindow->update(); + this->infoWindow->update(); + + this->boardWindow->render(); + this->infoWindow->render(); +} + +void MainGame::destroy() { + delete this->board; + delete this->boardWindow; + delete this->infoWindow; + + for (int i = 0; i < SHIP_SIZE; i++) { + delete this->ship[i]; + } + delete []this->ship; + + for (int i = 0; i < PLAYER_SIZE; i++) { + delete this->player[i]; + } + delete []this->player; +} + +void MainGame::phase1() { + std::string playerTurnStr = std::to_string(this->playerTurn + 1); + SelectionBox s = SelectionBox(0, 16); + + // Ask the current player to pick a Spaceship size + std::string sizeStr = "Player " + playerTurnStr + ", pick a spaceship size."; + std::string sizeOptions[] = {"S", "M", "B", "\0"}; + unsigned char sizeSelection = s.select(sizeStr, &sizeOptions[0]); + + // Ask the current player to pick a planet + std::string planetStr = "Player " + playerTurnStr + ", pick a planet to put this spaceship on."; + std::string planetOptions[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "\0"}; + unsigned char planetSelection = s.select(planetStr, &planetOptions[0]); + + // Calculate array index + int startLoop = SHIP_SIZE / PLAYER_SIZE * this->playerTurn + (sizeSelection * 3); + // Get ships of player selected planet + auto ships = this->board->getShips(std::stoi(planetOptions[planetSelection])); + + bool placedShip = false; + bool shouldExit; + // Check all 3 ships of the size the player selected + for (int i = startLoop; i < startLoop + 3; i++) { + shouldExit = false; + + // Check if spaceship is already on a planet + if (this->ship[i]->getPlanet() != PLANET_UNSET) { + continue; + } + + // If planet already holds spaceship of equal size + for(auto ship : *ships) { + if (this->ship[i]->getSize() == ship->getSize()) { + shouldExit = true; + break; + } + } + + if (shouldExit) { + MessageBox("You cant place spaceships of the same size on a planet during this phase!", "Error"); + break; + } + + // Add spaceship to planet + this->board->setShip(std::stoi(planetOptions[planetSelection]), this->ship[i]); + this->nextPlayerTurn(); + placedShip = true; + break; + } + if (!placedShip && !shouldExit) { + MessageBox("Please select a size of which you still have unplaced spaceships!", "Error"); + } + + // Check if all ships have been placed + bool allPlaced = true; + for (int i = 0; i < SHIP_SIZE; i++) { + if (this->ship[i]->getPlanet() == PLANET_UNSET) { + allPlaced = false; + break; + } + } + if (allPlaced) { + this->changePhase = true; + } +} + +void MainGame::phase2() { + std::string playerTurnStr = std::to_string(this->playerTurn + 1); + SelectionBox s = SelectionBox(0, 16); + + // Move next spaceship in the queue + if (this->queue->size() > 0) { + // Determine the middle of the board + unsigned char halfBoard = (BOARD_SIZE - 2) / 2; // 6 + + // Planet rotation order, where |x| is a black hole + // 1 ... 6 -> |13| -> 12 ... 7 -> |0| -> 1 + + // 0 .. 5 + if (this->queuePlanet < halfBoard) { + this->queuePlanet++; + } + // 8 .. 13 + else if (this->queuePlanet > halfBoard + 1) { + this->queuePlanet--; + } + // 6 + else if (this->queuePlanet == halfBoard) { + this->queuePlanet = BOARD_SIZE - 1; + } + // 7 + else if (this->queuePlanet == halfBoard + 1) { + this->queuePlanet = 0; + } + + unsigned char shipSelection = 0; + // If the the current and next ship are of the same size, ask which goes first + if (this->queue->size() > 1) { + SpaceShip *tmpShip = this->queue->at(0); + SpaceShip *tmpShip2 = this->queue->at(1); + if (tmpShip->getSize() == tmpShip2->getSize() && + tmpShip->getColor() != tmpShip2->getColor()) { + std::string shipStr = "Player " + playerTurnStr + ", pick the next ship that will evacuate."; + std::string shipOptions[] = {"Yours", "Theirs", "\0"}; + shipSelection = s.select(shipStr, &shipOptions[0]); + } + } + + board->moveShip(this->queuePlanet, this->queue->at(shipSelection)); + return; + } + else if (this->queuePlanet != -1) { + this->queuePlanet = -1; + this->nextPlayerTurn(); + this->calculateWinner(); + return; + } + + // Ask the current player if they want to skip their turn + unsigned char playerChip = this->player[this->playerTurn]->getChip(); + if (playerChip > 0) { + std::string skipStr = "Player " + playerTurnStr + ", do you want to skip your turn for 1 chip?"; + std::string skipOptions[] = {"No", "Yes", "\0"}; + unsigned char skipSelection = s.select(skipStr, &skipOptions[0]); + + if (skipOptions[skipSelection] == "Yes") { + this->player[this->playerTurn]->setChip(playerChip - 1); + this->nextPlayerTurn(); + return; + } + } + + // Ask the current player to pick a planet + std::string planetStr = "Player " + playerTurnStr + ", pick a planet to evacuate."; + std::string planetOptions[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "\0"}; + unsigned char planetSelection = s.select(planetStr, &planetOptions[0]); + + // Get ships of player selected planet + auto ships = this->board->getShips(std::stoi(planetOptions[planetSelection])); + + // Check if the selected planet holds a spaceship of the current player, + // using function/functor/lambda + unsigned char playerColor = this->player[(int)this->playerTurn]->getColor(); + auto findShip = std::find_if(ships->begin(), ships->end(), + [playerColor](SpaceShip *ship) {return ship->getColor() == playerColor; }); + + // If so, add this planet to the queue + if (findShip != ships->end()) { + this->queue = ships; + this->queuePlanet = this->queue->at(0)->getPlanet(); + } + else { + MessageBox("Please select a planet that holds a ship of your own!", "Error"); + } +} + +void MainGame::nextPlayerTurn() { + this->playerTurn++; + if (this->playerTurn > PLAYER_SIZE - 1) { + this->playerTurn = 0; + } +} + +void MainGame::calculateWinner() { + // Get all ships from the black holes + auto blackHole1 = this->board->getShips(0); + auto blackHole2 = this->board->getShips(BOARD_SIZE - 1); + + int totalScore = CHIP_AMOUNT * CHIP_SCORE + + 3 * SpaceShip::BIG + + 3 * SpaceShip::MEDIUM + + 3 * SpaceShip::SMALL; + + // Fill players possible total score + int score[PLAYER_SIZE]; + std::fill_n(score, PLAYER_SIZE, totalScore); + + // Calculate players score + int crashedShips; + bool gameOver = false; + for (int i = 0; i < PLAYER_SIZE; i++) { + + // Subtract all used chips from the total score + score[i] -= (CHIP_AMOUNT - this->player[i]->getChip()) * CHIP_SCORE; + + crashedShips = 0; + for (auto ship : *blackHole1) { + if (this->player[i]->getColor() == ship->getColor()) { + crashedShips++; + + // Subtract crashed ship from the total score + score[i] -= ship->getSize(); + } + } + for (auto ship : *blackHole2) { + if (this->player[i]->getColor() == ship->getColor()) { + crashedShips++; + + // Subtract crashed ship from the total score + score[i] -= ship->getSize(); + } + } + + // If a player has run out of ships.. + if (crashedShips == SHIP_SIZE / PLAYER_SIZE) { + gameOver = true; + } + } + + // Print victory screen + if (gameOver) { + std::string printStr; + + int winnerIdx = 0; + for (int i = 0; i < PLAYER_SIZE; i++) { + printStr += "Player " + std::to_string(i + 1) + ": scored " + + std::to_string(score[i]) + " points.\n"; + + if (i < PLAYER_SIZE - 1 && score[i] < score[i + 1]) { + winnerIdx = i + 1; + } + } + + printStr.insert(0, this->player[winnerIdx]->getName() + " has won!\n\n"); + MessageBox(printStr, "Victory"); + + gameStateManager.setState(new MainMenu()); + } +} diff --git a/maingame.h b/maingame.h new file mode 100644 index 0000000..005f31d --- /dev/null +++ b/maingame.h @@ -0,0 +1,49 @@ +#ifndef MAINGAME_H +#define MAINGAME_H + +// Each player holds 9 ships, 3 of each size +#define SHIP_SIZE 18 +#define PLAYER_SIZE 2 + +#include + +#include "gamestate.h" + +class Board; +class BoardWindow; +class InfoWindow; +class Player; +class SpaceShip; + +class MainGame : public GameState +{ +public: + MainGame(Player **player); + + void initialize(); + void update(); + void render(); + void destroy(); + +private: + void phase1(); + void phase2(); + void nextPlayerTurn(); + void calculateWinner(); + + std::string help; + bool phase; + bool changePhase; + unsigned char playerTurn; + + char queuePlanet; + std::vector *queue; + + Player **player; + Board *board; + SpaceShip **ship; + BoardWindow *boardWindow; + InfoWindow *infoWindow; +}; + +#endif // MAINGAME_H diff --git a/mainmenu.cpp b/mainmenu.cpp new file mode 100644 index 0000000..0105745 --- /dev/null +++ b/mainmenu.cpp @@ -0,0 +1,98 @@ +#include "inputoutput.h" +#include "maingame.h" +#include "mainmenu.h" +#include "player.h" +#include "window.h" + +void MainMenu::initialize() { + int rows; + int cols; + Window::getMaXYZ(stdscr, rows, cols); + + this->window = new Window(rows, cols, 0, 0); + this->titleScreen = true; + this->player = new Player*[PLAYER_SIZE] { nullptr }; +} + +void MainMenu::update() { + if (this->titleScreen) { + IO::update(); + + if (IO::getKeyName() == " ") { + this->titleScreen = false; + } + } + else { + int y; + int x; + std::string tmpStr; + unsigned char colors[PLAYER_SIZE] = { PAIR_RED_BLACK, PAIR_BLUE_BLACK }; + for (int i = 0; i < PLAYER_SIZE; i++) { + if (this->player[i] == nullptr) { + this->window->getYX(y, x); + + tmpStr = ""; + while (tmpStr == "") { + tmpStr = IO::trim(this->window->getStr()); + this->window->setYX(y, x); + } + + this->player[i] = new Player(); + this->player[i]->setName(tmpStr); + this->player[i]->setColor(colors[i]); + break; + } + + if (i == PLAYER_SIZE - 1) { + gameStateManager.setState(new MainGame(this->player)); + } + } + } +} + +void MainMenu::render() { + this->window->clear(); + + this->window->update(); + this->window->setWindow(this->window->getMaxRows(), this->window->getMaxCols(), 0, 0); + this->window->resize(); + + if (this->titleScreen) { + this->window->printSide("", "Rick van Vonderen"); + this->window->setYX(1, 0); + this->window->printSide("", "0945444"); + this->window->setYX(2, 0); + this->window->printSide("", "TI2B"); + + this->window->setYX(this->window->getRows() / 2 - 1, 0); + this->window->printCenter("--- Space Walk ---"); + + this->window->setYX(this->window->getRows() - 4, 0); + this->window->printCenter("Press to walk.."); + } + else { + int x = this->window->getCols() / 2 - 11 ; + int y = this->window->getRows() / 2; + std::string tmp; + for (int i = 0; i < PLAYER_SIZE; i++) { + tmp = std::string("Enter Player ") + std::to_string(i + 1) + std::string(" name: "); + + if (this->player[i] != nullptr) { + tmp += this->player[i]->getName(); + } + + this->window->print(tmp, y, x); + y++; + + if (this->player[i] == nullptr) { + break; + } + } + } + + this->window->render(); +} + +void MainMenu::destroy() { + delete this->window; +} diff --git a/mainmenu.h b/mainmenu.h new file mode 100644 index 0000000..83ad5d2 --- /dev/null +++ b/mainmenu.h @@ -0,0 +1,25 @@ +#ifndef MAINMENU_H +#define MAINMENU_H + +#include + +#include "gamestate.h" + +class Player; +class Window; + +class MainMenu : public GameState +{ +public: + void initialize(); + void update(); + void render(); + void destroy(); + +private: + bool titleScreen; + Window *window; + Player **player; +}; + +#endif // MAINMENU_H diff --git a/makefile b/makefile new file mode 100644 index 0000000..b2b7ae4 --- /dev/null +++ b/makefile @@ -0,0 +1,54 @@ +#------------------------------------------------------------------------------# + +PROGRAM := "space-walk" +HEADERS := \ + board.h \ + boardwindow.h \ + gamestate.h \ + infowindow.h \ + inputoutput.h \ + maingame.h \ + mainmenu.h \ + messagebox.h \ + player.h \ + selectionbox.h \ + spaceship.h \ + test.h \ + window.h +SOURCES := \ + board.cpp \ + boardwindow.cpp \ + infowindow.cpp \ + inputoutput.cpp \ + main.cpp \ + maingame.cpp \ + mainmenu.cpp \ + messagebox.cpp \ + player.cpp \ + selectionbox.cpp \ + spaceship.cpp \ + test.cpp \ + window.cpp + +#------------------------------------------------------------------------------# + +CXX := g++ +CXXFLAGS := -lncurses -std=c++11 -Wall -Wextra + +.PHONY: all debug run clean + +all: run + +debug: CXXFLAGS += -g -DDEBUG +debug: run + +run: compile + @./$(PROGRAM) ; \ + stty sane + +compile: ${HEADERS} ${SOURCES} + $(CXX) $(CXXFLAGS) $? -o $(PROGRAM) + +clean: + @-echo "Cleaning project.." ; \ + rm -f $(PROGRAM) diff --git a/messagebox.cpp b/messagebox.cpp new file mode 100644 index 0000000..f38c9cd --- /dev/null +++ b/messagebox.cpp @@ -0,0 +1,57 @@ +#include // A_REVERSE +#include // istringstream +#include + +#include "inputoutput.h" +#include "messagebox.h" + +MessageBox::MessageBox(std::string message) : + MessageBox(message, "") { +} + +MessageBox::MessageBox(std::string message, std::string title) { + // Split message on newline + std::vector lines; + std::istringstream strStream(message); + std::string tmpStr; + while (std::getline(strStream, tmpStr, '\n')) { + lines.push_back(tmpStr); + } + + int rows; + int cols; + Window::getMaXYZ(stdscr, rows, cols); + + // Calculate window dimension and size + int winRows = lines.size() + 8; + int winCols = cols * 0.9; + int y = (rows - winRows) / 2; + int x = (cols - winCols) / 2; + this->setWindow(winRows, winCols, y, x); + + // Print title + title = IO::untrim(!title.empty() ? title : "Message", winCols - 2); + this->print(title, 0, 0, A_REVERSE); + + // Print the message line by line + y = (winRows - lines.size()) / 2 - 1; + for (auto l : lines) { + this->setYX(y, 0); + this->printCenter(l); + y++; + } + + this->setYX(winRows - 3, 0); + this->printCenter("Press to continue ", + COLOR_PAIR(PAIR_GRAY_BLACK_BRIGHT)); + + this->render(); + + // Press space to continue + while(!IO::getQuit()) { + IO::update(); + if (IO::getKeyName() == " ") { + break; + } + } +} diff --git a/messagebox.h b/messagebox.h new file mode 100644 index 0000000..db27c27 --- /dev/null +++ b/messagebox.h @@ -0,0 +1,17 @@ +#ifndef MESSAGEBOX_H +#define MESSAGEBOX_H + +#include + +#include "window.h" + +class MessageBox : public Window +{ +public: + MessageBox(std::string message); + MessageBox(std::string message, std::string title); + +private: +}; + +#endif // MESSAGEBOX_H diff --git a/player.cpp b/player.cpp new file mode 100644 index 0000000..7261135 --- /dev/null +++ b/player.cpp @@ -0,0 +1,53 @@ +#include // A_REVERSE +#include + +#include "player.h" +#include "infowindow.h" + +Player::Player() : + chip(CHIP_AMOUNT), + name("") { +} + +void Player::render(InfoWindow *window, int y, int x, unsigned char number) { + std::string print; + print += " Player "; + print += std::to_string(number); + print += " "; + window->print(print, y, x - 1, A_REVERSE); + y += 2; + + window->print(this->name, y, x, COLOR_PAIR(this->color)); + y += 2; + + for (unsigned char i = 0; i < this->chip; i++) { + window->print("C", y, x, COLOR_PAIR(PAIR_WHITE_BLACK)); + x++; + window->print(" ", y, x); + x++; + } +} + +unsigned char Player::getChip() { + return this->chip; +} + +void Player::setChip(unsigned char chip) { + this->chip = chip; +} + +unsigned char Player::getColor() { + return this->color; +} + +void Player::setColor(unsigned char color) { + this->color = color; +} + +std::string Player::getName() { + return this->name; +} + +void Player::setName(std::string name) { + this->name = name; +} diff --git a/player.h b/player.h new file mode 100644 index 0000000..1901790 --- /dev/null +++ b/player.h @@ -0,0 +1,31 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#define CHIP_AMOUNT 4 +#define CHIP_SCORE 2 + +#include + +class InfoWindow; + +class Player +{ +public: + Player(); + + void render(InfoWindow *window, int y, int x, unsigned char number); + + unsigned char getChip(); + void setChip(unsigned char chip); + unsigned char getColor(); + void setColor(unsigned char color); + std::string getName(); + void setName(std::string name); + +private: + unsigned char chip; + unsigned char color; + std::string name; +}; + +#endif // PLAYER_H diff --git a/selectionbox.cpp b/selectionbox.cpp new file mode 100644 index 0000000..9c9f0c6 --- /dev/null +++ b/selectionbox.cpp @@ -0,0 +1,88 @@ +#include // strlen +#include // istringstream + +#include "inputoutput.h" +#include "selectionbox.h" + +SelectionBox::SelectionBox(int height, int width) : + height(height), + width(width) { + this->update(); +} + +void SelectionBox::update() { + Window::update(); + this->resize(); +} + +void SelectionBox::resize() { + int rows = (this->getMaxRows() - this->height) / 4; + int cols = this->getMaxCols() - this->width; + this->setWindow(rows, cols, this->getMaxRows() - rows, 0); + Window::resize(); +} + +unsigned char SelectionBox::select(std::string str, std::string *selection) { + int amount = 0; + while (!selection[amount].empty()) { + amount++; + } + + std::string tmpStr; + int tmpInt; + // Display options and get user input + while(!IO::getQuit()) { + this->clear(); + this->update(); + this->printSelection(str, selection); + this->render(); + + tmpStr = IO::trim(this->getStr()); + if (tmpStr == "") { + continue; + } + + if (this->isNumber(tmpStr)) { + tmpInt = std::stoi(tmpStr); + if (tmpInt > 0 && tmpInt < amount + 1) { + break; + } + } + } + + return (unsigned char)tmpInt - 1; +} + +void SelectionBox::printSelection(std::string str, std::string *selection) { + int y = 0; + + // Split and print message on newline + std::istringstream strStream(str); + std::string tmpStr; + while (std::getline(strStream, tmpStr, '\n')) { + this->print(tmpStr, y, 1); + y++; + } + y++; + + int amount = 0; + while (!selection[amount].empty()) { + if (amount < 6) { + this->print("- " + selection[amount], y, 1); + } + else { + this->print("- " + selection[amount], y - 6, 10); + } + + y++; + amount++; + } + + this->print("Select option number: ", this->getRows() - 3, 1); +} + +bool SelectionBox::isNumber(const std::string &s) { + std::string::const_iterator it = s.begin(); + while (it != s.end() && std::isdigit(*it)) it++; + return !s.empty() && it == s.end(); +} diff --git a/selectionbox.h b/selectionbox.h new file mode 100644 index 0000000..307a8e7 --- /dev/null +++ b/selectionbox.h @@ -0,0 +1,26 @@ +#ifndef SELECTIONBOX_H +#define SELECTIONBOX_H + +#include + +#include "window.h" + +class SelectionBox : public Window +{ +public: + SelectionBox(int height, int width); + + void update(); + void resize(); + + unsigned char select(std::string str, std::string *selection); + void printSelection(std::string str, std::string *selection); + +private: + bool isNumber(const std::string &s); + + int height; + int width; +}; + +#endif // SELECTIONBOX_H diff --git a/spaceship.cpp b/spaceship.cpp new file mode 100644 index 0000000..250f14d --- /dev/null +++ b/spaceship.cpp @@ -0,0 +1,57 @@ +#include "spaceship.h" +#include "window.h" + +SpaceShip::SpaceShip() : + color(COLOR_UNSET), + size(SpaceShip::SIZE_UNSET), + planet(PLANET_UNSET) { +} + +SpaceShip::SpaceShip(unsigned char color, unsigned char size) : + color(color), + size(size), + planet(PLANET_UNSET) { +} + +void SpaceShip::render(Window *window, int y, int x, bool checkPlaced) { + if (checkPlaced && this->planet != PLANET_UNSET) { + return; + } + + std::string print = ""; + if (this->size == SpaceShip::SMALL) { + print = "S"; + } + else if (this->size == SpaceShip::MEDIUM) { + print = "M"; + } + else if (this->size == SpaceShip::BIG) { + print = "B"; + } + + window->print(print, y, x, COLOR_PAIR(this->color)); +} + +unsigned char SpaceShip::getColor() { + return this->color; +} + +void SpaceShip::setColor(unsigned char color) { + this->color = color; +} + +unsigned char SpaceShip::getSize() { + return this->size; +} + +void SpaceShip::setSize(unsigned char size) { + this->size = size; +} + +char SpaceShip::getPlanet() { + return this->planet; +} + +void SpaceShip::setPlanet(char planet) { + this->planet = planet; +} diff --git a/spaceship.h b/spaceship.h new file mode 100644 index 0000000..bcdb1f1 --- /dev/null +++ b/spaceship.h @@ -0,0 +1,36 @@ +#ifndef SPACESHIP_H +#define SPACESHIP_H + +#define PLANET_UNSET -1 + +class Window; + +class SpaceShip +{ +public: + SpaceShip(); + SpaceShip(unsigned char color, unsigned char size); + + void render(Window *window, int y, int x, bool checkPlaced = true); + + unsigned char getColor(); + void setColor(unsigned char color); + unsigned char getSize(); + void setSize(unsigned char size); + char getPlanet(); + void setPlanet(char planet); + + enum Size { + SIZE_UNSET = 0, + SMALL = 1, + MEDIUM = 3, + BIG = 4, + }; + +private: + unsigned char color; + unsigned char size; + char planet; +}; + +#endif // SPACESHIP_H diff --git a/test.cpp b/test.cpp new file mode 100644 index 0000000..f9af136 --- /dev/null +++ b/test.cpp @@ -0,0 +1,68 @@ +#include +#include +#include + +#include "board.h" +#include "inputoutput.h" +#include "maingame.h" +#include "player.h" +#include "spaceship.h" +#include "test.h" + +Test::Test() { + + // IO::print("Unit test\n"); + refresh(); + + // Board tests + Board *board = new Board; + + char arrayShip[3] = { SpaceShip::SMALL, SpaceShip::MEDIUM, SpaceShip::BIG }; + SpaceShip *ship = new SpaceShip(1, arrayShip[0]); + SpaceShip *ship2 = new SpaceShip(1, arrayShip[1]); + SpaceShip *ship3 = new SpaceShip(1, arrayShip[2]); + board->setShip(0, ship); + board->setShip(0, ship); + board->setShip(1, ship); + board->setShip(1, ship2); + board->setShip(2, ship3); + + for (int i = 0; i < 3; i++) { + auto ships = board->getShips(i); + // printf("%lu\n", ships->size()); + for (int j = 0; j < (int)ships->size(); j++) { + // printf("%d\n", ships->at(j)->getSize()); + assert(ships->at(j)->getSize() == arrayShip[i]); + } + assert(ships->size() == 1); + } + board->moveShip(3, ship3); + assert(board->getShip(3, 0)->getSize() == arrayShip[2]); + + // char array[SHIP_SIZE] = { + // SpaceShip::SMALL, SpaceShip::SMALL, SpaceShip::SMALL, + // SpaceShip::MEDIUM, SpaceShip::MEDIUM, SpaceShip::MEDIUM, + // SpaceShip::BIG, SpaceShip::BIG, SpaceShip::BIG, + // SpaceShip::SMALL, SpaceShip::SMALL, SpaceShip::SMALL, + // SpaceShip::MEDIUM, SpaceShip::MEDIUM, SpaceShip::MEDIUM, + // SpaceShip::BIG, SpaceShip::BIG, SpaceShip::BIG, + // }; + // for (unsigned char i = 0; i < SHIP_SIZE; i++) { + // // printf("%d\n", board.ships[i]->getSize()); + // // printf("%d\n", array[i]); + // assert(board.ships[i]->getSize() == array[i]); + // } + + // IO::print("Passed board checks.."); + refresh(); + + getch(); + + // Cleanup resources + delete board; + delete ship; + delete ship2; + delete ship3; + + IO::setQuit(); +} diff --git a/test.h b/test.h new file mode 100644 index 0000000..44eeee8 --- /dev/null +++ b/test.h @@ -0,0 +1,12 @@ +#ifndef TEST_H +#define TEST_H + +class Test +{ +public: + Test(); + +private: +}; + +#endif // TEST_H diff --git a/window.cpp b/window.cpp new file mode 100644 index 0000000..8976872 --- /dev/null +++ b/window.cpp @@ -0,0 +1,222 @@ +#include + +#include "gamestate.h" +#include "window.h" + +Window::Window(int rows, int cols, int y, int x) : + rows(rows), + cols(cols), + y(0), + x(0), + window(newwin(rows, cols, y, x)) { +} + +Window::~Window() { + delwin(this->window); +} + +void Window::initialize() { + // Start curses mode + initscr(); + // Disable line buffering + cbreak(); + // Enable reading of function keys + keypad(stdscr, TRUE); + // Dont echo while reading input + noecho(); + // Disable cursor visibility + curs_set(0); + + // If terminal supports colors + if(!has_colors()) { + printw("Your terminal does not support color\n"); + } + else { + start_color(); + use_default_colors(); + + init_pair(PAIR_WHITE_BLACK, COLOR_WHITE, COLOR_BLACK); + init_pair(PAIR_WHITE_BRIGHT_BLACK, COLOR_WHITE_BRIGHT, COLOR_BLACK); + init_pair(PAIR_RED_BLACK, COLOR_RED, COLOR_BLACK); + init_pair(PAIR_BLUE_BLACK, COLOR_BLUE, COLOR_BLACK); + init_pair(PAIR_GRAY_BLACK_BRIGHT, COLOR_GRAY, COLOR_BLACK_BRIGHT); + } + + // Add bright colors, intensity range: 0-1000 + if(!can_change_color()) { + printw("Your terminal does not support changing colors\n"); + } + else { + init_color(COLOR_BLACK, 0, 0, 0); + init_color(COLOR_WHITE, 680, 680, 680); + init_color(COLOR_GRAY, 380, 380, 380); + + init_color(COLOR_BLACK_BRIGHT, 110, 110, 110); // 0, 0, 0 + init_color(COLOR_RED_BRIGHT, 900, 0, 0); // 680, 0, 0 + init_color(COLOR_GREEN_BRIGHT, 0, 900, 0); // 0, 680, 0 + init_color(COLOR_YELLOW_BRIGHT, 900, 900, 0); // 680, 680, 0 + init_color(COLOR_BLUE_BRIGHT, 0, 0, 900); // 0, 0, 680 + init_color(COLOR_MAGENTA_BRIGHT, 900, 0, 900); // 680, 0, 680 + init_color(COLOR_CYAN_BRIGHT, 0, 900, 900); // 0, 680, 680 + init_color(COLOR_WHITE_BRIGHT, 900, 900, 900); // 680, 680, 680 + } +} + +void Window::update() { + Window::getMaXYZ(stdscr, this->maxRows, this->maxCols); +} + +void Window::render() { + box(this->window, 0, 0); + wrefresh(this->window); +} + +void Window::destroy() { + // Restore line buffering + nocbreak(); + // Restore cursor + curs_set(1); + // End curses mode + endwin(); +} + +void Window::clear() { + this->setYX(0, 0); + + // Set background color + // wbkgdset(this->window, COLOR_PAIR(3)); + + // Copy blanks to every position in the window, clearing the screen + werase(this->window); +} + +void Window::resize() { + wresize(this->window, this->rows, this->cols); + mvwin(this->window, this->y, this->x); +} + +void Window::print(std::string str, char newLine) { + this->print(str, this->y, this->x); + this->x += str.length(); + if (newLine == 1) { + this->x = 0; + this->y++; + } +} + +void Window::print(std::string str, int y, int x) { + this->y = y; + this->x = x; + mvwprintw(this->window, y + 1, x + 1, str.c_str()); +} + +void Window::print(std::string str, int y, int x, int attribute) { + wattron(this->window, attribute); + this->print(str, y, x); + wattroff(this->window, attribute); +} + +void Window::printCenter(std::string str) { + int halfBoard = this->cols / 2; + int halfStr = str.length() / 2; + this->print(str, this->y, halfBoard - halfStr - 1); +} + +void Window::printCenter(std::string str, int attribute) { + wattron(this->window, attribute); + this->printCenter(str); + wattroff(this->window, attribute); +} + +void Window::printSide(std::string left, std::string right) { + this->print(left, this->y, 1); + this->print(right, this->y, this->cols - right.length() - 3); +} + +void Window::getYX(int &y, int &x) { + getyx(this->window, y, x); +} + +void Window::setYX(int y, int x) { + this->y = y; + this->x = x; + wmove(this->window, y, x); +} + +void Window::getMaXYZ(int &rows, int &cols) { + getmaxyx(this->window, rows, cols); +} + +void Window::getMaXYZ(WINDOW *window, int &rows, int &cols) { + getmaxyx(window, rows, cols); +} + +std::string Window::getStr() { + curs_set(1); + echo(); + char *tmp = new char(); + wgetstr(this->window, tmp); + noecho(); + curs_set(0); + return std::string(tmp); +} + +int Window::getMaxRows() { + return this->maxRows; +} + +int Window::getMaxCols() { + return this->maxCols; +} + +int Window::getRows() { + return this->rows; +} + +void Window::setRows(int rows) { + this->rows = rows; +} + +int Window::getCols() { + return this->cols; +} + +void Window::setCols(int cols) { + this->cols = cols; +} + +int Window::getY() { + return this->y; +} + +void Window::setY(int y) { + this->y = y; +} + +int Window::getX() { + return this->x; +} + +void Window::setX(int x) { + this->x = x; +} + +void Window::setWindow(int rows, int cols, int y, int x) { + this->setRows(rows); + this->setCols(cols); + this->setY(y); + this->setX(x); + if (this->window == nullptr) { + this->window = newwin(rows, cols, y, x); + } +} + +//----------------------------------------------------------------------------// + +Window::Window() : + rows(0), + cols(0), + y(0), + x(0), + window(nullptr) { +} diff --git a/window.h b/window.h new file mode 100644 index 0000000..d0b5065 --- /dev/null +++ b/window.h @@ -0,0 +1,75 @@ +#ifndef WINDOW_H +#define WINDOW_H + +#define COLOR_UNSET 0 +#define COLOR_BLACK_BRIGHT 8 +#define COLOR_RED_BRIGHT 9 +#define COLOR_GREEN_BRIGHT 10 +#define COLOR_YELLOW_BRIGHT 11 +#define COLOR_BLUE_BRIGHT 12 +#define COLOR_MAGENTA_BRIGHT 13 +#define COLOR_CYAN_BRIGHT 14 +#define COLOR_WHITE_BRIGHT 15 +#define COLOR_GRAY 16 + +#define PAIR_WHITE_BLACK 1 +#define PAIR_WHITE_BRIGHT_BLACK 2 +#define PAIR_RED_BLACK 3 +#define PAIR_BLUE_BLACK 4 +#define PAIR_GRAY_BLACK_BRIGHT 5 + +#include +#include + +class Window +{ +public: + Window(int rows, int cols, int y, int x); + ~Window(); + + static void initialize(); + void update(); + void render(); + static void destroy(); + void clear(); + void resize(); + + void print(std::string str, char newLine = 1); + void print(std::string str, int y, int x); + void print(std::string str, int y, int x, int attribute); + void printCenter(std::string str); + void printCenter(std::string str, int attribute); + void printSide(std::string left, std::string right); + + void getYX(int &y, int &x); + void setYX(int y, int x); + void getMaXYZ(int &rows, int &cols); + static void getMaXYZ(WINDOW *window, int &rows, int &cols); + std::string getStr(); + + int getMaxRows(); + int getMaxCols(); + int getRows(); + void setRows(int rows); + int getCols(); + void setCols(int cols); + int getY(); + void setY(int y); + int getX(); + void setX(int x); + void setWindow(int rows, int cols, int y, int x); + +protected: + Window(); + +private: + int maxRows; + int maxCols; + int rows; + int cols; + int y; + int x; + WINDOW *window; +}; + +#endif // WINDOW_H