312 lines
15 KiB
C++
312 lines
15 KiB
C++
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||
|
* Champlain College SDEV-240-81A
|
||
|
*
|
||
|
* C++ Final Project (first semester) - Coding Component (2020/05/02)
|
||
|
*
|
||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||
|
*
|
||
|
* This is a demonstration in C++ of a game called
|
||
|
* Game of life created by John Horton Conway in 1970
|
||
|
* https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
|
||
|
*
|
||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||
|
*
|
||
|
* Written by Llewellyn van der Merwe <llewellyn.vandermerw@mymail.champlain.edu>, April 2020
|
||
|
* Copyright (C) 2020. All Rights Reserved
|
||
|
* License GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html
|
||
|
*
|
||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
|
||
|
// internal classes
|
||
|
#include "Util.h"
|
||
|
#include "GameOfLife.h"
|
||
|
#include "Patterns.h"
|
||
|
#include "Message.h"
|
||
|
#include "GetInput.h"
|
||
|
// libraries
|
||
|
#include <iostream>
|
||
|
|
||
|
using namespace std;
|
||
|
|
||
|
namespace GOL {
|
||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||
|
* I am trying out namespace just to see how it works :) any input would be great!
|
||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
|
||
|
// the introduction message
|
||
|
void Util::introMessage(GameOfLife *game) {
|
||
|
game->message->add("LINE");
|
||
|
game->message->add("This is a demonstration in C++ of a game called");
|
||
|
game->message->add("Game of life created by John Horton Conway in 1970");
|
||
|
game->message->add("https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life");
|
||
|
game->message->add("LINE");
|
||
|
game->message->add("Written by Llewellyn van der Merwe");
|
||
|
game->message->add("LINE");
|
||
|
game->message->add("The Rules");
|
||
|
game->message->add("At each step in time, the following transitions occur:");
|
||
|
game->message->add("1. Any live cell with fewer than two live neighbours dies, as if by underpopulation.");
|
||
|
game->message->add("2. Any live cell with two or three live neighbours lives on to the next generation.");
|
||
|
game->message->add("3. Any live cell with more than three live neighbours dies, as if by overpopulation.");
|
||
|
game->message->add("4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.");
|
||
|
game->message->add("LINE")->print(0);
|
||
|
}
|
||
|
|
||
|
// getting the coordinates for the board
|
||
|
void Util::getCoordinates(GameOfLife *game) {
|
||
|
// lets make sure our board is clean
|
||
|
game->reset();
|
||
|
// instantiate x and y to be integers
|
||
|
int x, y;
|
||
|
// setup the question
|
||
|
game->message->add("This game has the following input options");
|
||
|
game->message->add("LINE");
|
||
|
game->message->add("1. Select a pattern from our presets.");
|
||
|
game->message->add("2. Enter a path to a file with the coordinates.");
|
||
|
game->message->add("3. Manually add coordinates to the board.");
|
||
|
game->message->add("or 'q' to quit")->print(0);
|
||
|
// get the user path to action
|
||
|
GetInput action(game->message->ask("Enter your selection: "));
|
||
|
// clean the screen
|
||
|
Util::clearScreen();
|
||
|
game->message->add("LINE");
|
||
|
// file or preset was selected
|
||
|
if (action.get() == "2") {
|
||
|
// clean the screen
|
||
|
Util::clearScreen();
|
||
|
while (true) {
|
||
|
// give info of the coordinates convention
|
||
|
game->message->add("Coordinates must be given in the following way");
|
||
|
game->message->add("LINE");
|
||
|
game->message->add("Each line must have two numbers separated by a space.");
|
||
|
game->message->add("The first number is the x axis value, and the second number is the y axis value.");
|
||
|
game->message->add("So each line represents a coordinate/cell on the board that is active/alive.");
|
||
|
game->message->add("The view area of the board has " +
|
||
|
to_string(game->getViewSize()) + " x " +
|
||
|
to_string(game->getViewSize()) + " = " +
|
||
|
to_string(game->getViewArea()) + " cells. But the board is far larger.");
|
||
|
game->message->add("Meaning you are able to add negative and positive values to a depth of -" +
|
||
|
to_string(game->getSettableArea()) + " and " +
|
||
|
to_string(game->getSettableArea()) + ".")->print();
|
||
|
// get the file path
|
||
|
GetInput filepath(game->message->ask("Enter file path to read coordinates from, or 'b' to go back: "));
|
||
|
// check if the player want to go back
|
||
|
if (filepath.get() == "b") {
|
||
|
// go back
|
||
|
getCoordinates(game);
|
||
|
break;
|
||
|
} else {
|
||
|
// read in the file path
|
||
|
ifstream readfile(filepath.get());
|
||
|
// if we can open the file continue
|
||
|
if (readfile.is_open()) {
|
||
|
// instantiate bucket values
|
||
|
string fileline, x_coordinate, y_coordinate;
|
||
|
// get the lines from the file
|
||
|
while (getline(readfile, fileline)) {
|
||
|
// get the lines from the file
|
||
|
stringstream line_coordinate(fileline);
|
||
|
// get coordinates
|
||
|
getline(line_coordinate, x_coordinate, ' ');
|
||
|
getline(line_coordinate, y_coordinate, ' ');
|
||
|
// convert to number (integer)
|
||
|
x = stoi(x_coordinate);
|
||
|
y = stoi(y_coordinate);
|
||
|
// set the coordinates
|
||
|
game->set(x, y);
|
||
|
}
|
||
|
break;
|
||
|
} else {
|
||
|
game->message->add("No such file, try again.")->print();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// ask to set infinity
|
||
|
game->setInfinity(false);
|
||
|
} else if (action.get() == "1") {
|
||
|
// clean the screen
|
||
|
Util::clearScreen();
|
||
|
game->message->add("These are the preset patterns");
|
||
|
game->message->add("LINE");
|
||
|
// instantiate Patterns class
|
||
|
Patterns patterns;
|
||
|
// loop over the presets
|
||
|
map<int, string>::iterator preset;
|
||
|
map<int, string> presets = patterns.getPresets();
|
||
|
for (preset = presets.begin(); preset != presets.end(); preset++) {
|
||
|
game->message->add(to_string(preset->first) + ". " + preset->second);
|
||
|
}
|
||
|
// add message to allow player to go back
|
||
|
game->message->add("or 'b' to go back")->print(0);
|
||
|
// get selected preset
|
||
|
GetInput activePreset(game->message->ask("Enter your selection: "));
|
||
|
// load the preset if the correct number was selected
|
||
|
if ((!activePreset.get().empty() && activePreset.get().find_first_not_of("0123456789") != string::npos) ||
|
||
|
!patterns.setPreset(stoi(activePreset.get()), game)) {
|
||
|
game->message->add("No such preset was found, try again.")->print();
|
||
|
// we try again
|
||
|
getCoordinates(game);
|
||
|
}
|
||
|
} else if (action.get() == "3") {
|
||
|
// clean the screen
|
||
|
Util::clearScreen();
|
||
|
// give info of the coordinates convention
|
||
|
game->message->add("Coordinates must be given in the following way");
|
||
|
game->message->add("LINE");
|
||
|
game->message->add("Each entry must have two numbers separated by a space.");
|
||
|
game->message->add("The first number is the x axis value, and the second number is the y axis value.");
|
||
|
game->message->add("So each entry represents a coordinate/cell on the board that is active/alive.");
|
||
|
game->message->add("The view area of the board has " +
|
||
|
to_string(game->getViewSize()) + " x " +
|
||
|
to_string(game->getViewSize()) + " = " +
|
||
|
to_string(game->getViewArea()) + " cells.")->print();
|
||
|
// get the coordinates from user manually
|
||
|
while (true) {
|
||
|
cout << game->message->ask(
|
||
|
"Enter the coordinates of an active/alive cell: ");
|
||
|
cin >> x >> y;
|
||
|
// make sure we get numbers
|
||
|
if (cin.fail()) {
|
||
|
// clear to ask the question again
|
||
|
cin.clear();
|
||
|
cin.ignore();
|
||
|
// give the warning
|
||
|
game->message->add("Must only be numbers!")->print();
|
||
|
} else {
|
||
|
// set the coordinates
|
||
|
game->set(x, y);
|
||
|
// check if coordinates are correct
|
||
|
game->message->add("Check your entry")->print();
|
||
|
// print the set coordinates
|
||
|
game->print(3);
|
||
|
// final check if the above coordinates are correct and if we must continue adding more
|
||
|
game->message->add("To continue you have the following options:");
|
||
|
game->message->add("1. Add another cell.");
|
||
|
game->message->add("2. Undo that last entry.");
|
||
|
// game->message->add("3. Save coordinates to a file and start game->"); TODO
|
||
|
game->message->add("3. Stop adding cells.")->print();
|
||
|
GetInput optionInput(game->message->ask("Enter the number: "));
|
||
|
// ready to start game?
|
||
|
if (optionInput.get() == "2") {
|
||
|
// unset the last coordinates
|
||
|
game->unset(x, y);
|
||
|
} else if (optionInput.get() == "3") {
|
||
|
// stop adding cells
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
game->setInfinity(false);
|
||
|
} else {
|
||
|
// quit playing the game
|
||
|
game->stopPlaying();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Util::run(GameOfLife *game) {
|
||
|
// make sure the game is active
|
||
|
if (game->isStillPlaying()) {
|
||
|
// get the coordinates
|
||
|
Util::getCoordinates(game);
|
||
|
// make sure the game is active
|
||
|
if (game->isStillPlaying()) {
|
||
|
// show the board and the selected coordinates
|
||
|
game->message->add("LINE");
|
||
|
game->message->add("These are the coordinates.")->print(0);
|
||
|
// print the set coordinates
|
||
|
game->print(0);
|
||
|
// give notice of infinity treatment
|
||
|
Util::infinityNote(game);
|
||
|
// final check if the above coordinates are correct
|
||
|
game->message->add("LINE");
|
||
|
GetInput startGame(game->message->ask("Board setup complete. Start the game ? (y/n) "));
|
||
|
// ready to start game?
|
||
|
if (startGame.get() == "y" || startGame.get() == "Y") {
|
||
|
while (true) {
|
||
|
// print the board
|
||
|
game->print();
|
||
|
// update the board state
|
||
|
game->setState();
|
||
|
// slow down... ;)
|
||
|
usleep(130000);
|
||
|
// clean the screen
|
||
|
if (game->isActive()) {
|
||
|
Util::clearScreen();
|
||
|
} else {
|
||
|
// show the board and the selected coordinates
|
||
|
Util::getCoordinates(game);
|
||
|
// if the player quit
|
||
|
if (game->isStillPlaying()) {
|
||
|
// show the board and the selected coordinates
|
||
|
game->message->add("LINE");
|
||
|
game->message->add("These are the new coordinates.")->print();
|
||
|
// print the set coordinates
|
||
|
game->print(0);
|
||
|
// give notice of infinity treatment
|
||
|
Util::infinityNote(game);
|
||
|
// final check if the above coordinates are correct
|
||
|
game->message->add("LINE");
|
||
|
GetInput startGameAgain(
|
||
|
game->message->ask("Board setup complete. Start the game again ? (y/n) "));
|
||
|
if (startGameAgain.get() != "y" && startGameAgain.get() != "Y") {
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// if we are still active
|
||
|
if (game->isStillPlaying()) {
|
||
|
// restart
|
||
|
Util::run(game);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// give notice of infinity treatment
|
||
|
void Util::infinityNote(GameOfLife *game) {
|
||
|
if (game->isInfinitySet()) {
|
||
|
game->message->add("All infinite loops will automatically exit, sit back and enjoy!");
|
||
|
game->message->add("LINE")->print();
|
||
|
} else {
|
||
|
game->message->add("Just a little heads-up, you will need to manually exit the game");
|
||
|
game->message->add("should it enter an infinite loop (Oscillator).");
|
||
|
game->message->add("The game will only automatically exit if all visible cells are dead or still.");
|
||
|
game->message->add("LINE")->print();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// method to clear the screen
|
||
|
void Util::clearScreen() {
|
||
|
// Below Code Taken From This Project https://github.com/MarioTalevski/game-of-life
|
||
|
// Tested and working on Ubuntu and Cygwin
|
||
|
#if defined(OS_WIN)
|
||
|
HANDLE hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
|
||
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
||
|
DWORD count;
|
||
|
DWORD cellCount;
|
||
|
COORD homeCoords = { 0, 0 };
|
||
|
|
||
|
if (hStdOut == INVALID_HANDLE_VALUE) return;
|
||
|
|
||
|
/* Get the number of cells in the current buffer */
|
||
|
GetConsoleScreenBufferInfo( hStdOut, &csbi );
|
||
|
cellCount = csbi.dwSize.X *csbi.dwSize.Y;
|
||
|
|
||
|
/* Fill the entire buffer with spaces */
|
||
|
FillConsoleOutputCharacter(hStdOut, (TCHAR) ' ', cellCount, homeCoords, &count);
|
||
|
|
||
|
/* Fill the entire buffer with the current colors and attributes */
|
||
|
FillConsoleOutputAttribute(hStdOut, csbi.wAttributes, cellCount, homeCoords, &count);
|
||
|
|
||
|
SetConsoleCursorPosition( hStdOut, homeCoords );
|
||
|
|
||
|
#elif defined(OS_LINUX) || defined(OS_MAC)
|
||
|
cout << "\033[2J" << "\033[1;1H"; // Clears screen and moves cursor to home pos on POSIX systems
|
||
|
#endif
|
||
|
// Above Code Taken From This Project https://github.com/MarioTalevski/game-of-life
|
||
|
}
|
||
|
}
|