Compare commits

...

10 Commits

View File

@ -1,17 +1,259 @@
// This is the CPP file you will edit and turn in.
// Also remove these comments here and add your own.
// TODO: remove this comment header
// A TUI implementation of Conway's Game of Life
// Author: Love Billenius <lovbi127@student.liu.se>
#include <iostream>
#include "grid.h"
#include "lifeutil.h"
#include "unistd.h"
#include <fstream>
#include <iostream>
#include <string>
const char *const FORMAT_UNDERSCORE = "\033[4m";
const char *const END_FORMATTING = "\033[0m";
const int ANIMATION_SLEEP_MS = 100, ANIMATION_N_GENERATIONS = 20;
void printWelcome() {
std::cout << "Welcome to the TDDD86 Game of Life,\n"
<< "a simulation of the lifecycle of a bacteria colony.\n"
<< "Cells (X) live and die by the following rules:\n"
<< " - A cell with 1 or fewer neighbours dies.\n"
<< " - Locations with 2 neighbours remain stable.\n"
<< " - Locations with 3 neighbours will create life.\n"
<< " - A cell with 4 or more neighbours dies." << std::endl;
}
std::string input(const char *message) {
if (message != nullptr)
std::cout << message;
std::cout << FORMAT_UNDERSCORE;
std::flush(std::cout);
std::string ret;
std::getline(std::cin, ret);
std::cout << END_FORMATTING;
return ret;
}
bool fileExists(const char *fileName) { return access(fileName, 0) == 0; }
std::string fileRead(const std::string &filePath) {
std::ifstream file(filePath, std::ios::binary);
if (!file.is_open())
throw std::runtime_error("File couldn't be found");
std::string content;
const int BUF_LENGTH = 1024;
char buf[BUF_LENGTH];
while (file.read(buf, BUF_LENGTH))
content.append(buf, file.gcount());
content.append(buf, file.gcount());
if (!file.eof() && file.fail())
throw std::runtime_error("Error: Failed to read the file '" + filePath +
"'");
return content;
}
enum MenuAction { ANIMATE, TICK, QUIT };
MenuAction askMenu() {
while (true) {
std::string userInput = input("a)nimate, t)ick, q)uit? ");
if (userInput == "a")
return MenuAction::ANIMATE;
else if (userInput == "t")
return MenuAction::TICK;
else if (userInput == "q")
return MenuAction::QUIT;
std::cout << "The input '" << userInput << "' is an invalid option!\n";
}
}
std::vector<std::string> split(const std::string &s, const char at) {
std::vector<std::string> ret;
size_t last = 0, idx;
while ((idx = s.find(at, last)) != std::string::npos) {
std::string sub = s.substr(last, idx - last);
ret.push_back(sub);
last = idx + 1;
}
ret.push_back(s.substr(last));
return ret;
}
enum Cell { ALIVE, DEAD };
void gridPrint(Grid<Cell> &grid) {
std::stringstream ss;
for (int row = 0; row < grid.numRows(); row++) {
for (int col = 0; col < grid.numCols(); col++) {
char sign = grid.get(row, col) == Cell::ALIVE ? 'X' : '-';
ss << sign;
}
if (row == grid.numRows() - 1)
ss << std::endl;
else
ss << '\n';
}
std::cout << ss.str();
}
int gridAliveNeighbors(Grid<Cell> &grid, int row, int col) {
int alive = 0;
for (int rowDelta = -1; rowDelta <= 1; rowDelta++) {
int checkRow = row + rowDelta;
// Skip out of bounds
if (checkRow < 0 || checkRow >= grid.numRows())
continue;
for (int colDelta = -1; colDelta <= 1; colDelta++) {
// Don't check the original piece
if (rowDelta == 0 && colDelta == 0)
continue;
int checkCol = col + colDelta;
// Skip out of bounds
if (checkCol < 0 || checkCol >= grid.numCols())
continue;
if (grid.get(checkCol, checkRow) == Cell::ALIVE)
alive++;
}
}
return alive;
}
/**
* Run one Game of Life tick.
* Cells (X) live and die by the following rules:
* - A cell with 1 or fewer neighbours dies.
* - Locations with 2 neighbours remain stable.
* - Locations with 3 neighbours will create life.
* - A cell with 4 or more neighbours dies.
*/
void gridTick(Grid<Cell> &grid) {
Grid<Cell> nextGrid = grid;
for (int row = 0; row < grid.numRows(); row++) {
for (int col = 0; col < grid.numCols(); col++) {
int alive = gridAliveNeighbors(grid, row, col);
// A cell with 1 or fewer neighbours dies.
if (alive == 1)
nextGrid.set(row, col, Cell::DEAD);
// Locations with 2 neighbours remain stable.
else if (alive == 2)
nextGrid.set(row, col, grid.get(row, col));
// Locations with 3 neighbours will create life.
else if (alive == 3)
nextGrid.set(row, col, Cell::ALIVE);
// A cell with 4 or more neighbours dies.
else if (alive == 4)
nextGrid.set(row, col, Cell::DEAD);
}
}
grid = nextGrid;
}
int main() {
printWelcome();
// TODO: Finish the program!
std::string inputFile;
while (true) {
inputFile = input("Grid input file name? ");
if (fileExists(inputFile.c_str()))
break;
std::cout << "File '" << inputFile << "' doesn't exist!" << std::endl;
}
std::string fileContents;
try {
fileContents = fileRead(inputFile);
} catch (std::exception &e) {
std::cerr << "Failed to read file: " << e.what() << std::endl;
return 1;
}
std::vector<std::string> lines = split(fileContents, '\n');
if (lines.size() <= 2) {
std::cerr << "No column, row header, in the file!" << std::endl;
return 1;
}
int headerRows, headerColumns;
try {
headerRows = std::stoi(lines[0]);
headerColumns = std::stoi(lines[1]);
} catch (const std::invalid_argument &e) {
std::cerr << "Column or row wasn't a number" << std::endl;
return 1;
} catch (const std::out_of_range &e) {
std::cerr << "Column or row was too large" << std::endl;
return 1;
}
std::cout << "Have a nice Life! " << std::endl;
int possibleRows = lines.size() - 2;
if (possibleRows < headerRows) {
std::cerr << "There's less rows (" << possibleRows
<< ") than described in the header! (" << headerRows << ')'
<< std::endl;
return 1;
}
Grid<Cell> grid(headerColumns, headerRows);
for (int x = 0; x < headerRows; x++) {
std::string &row = lines[x + 2];
if (headerColumns > row.size()) {
std::cerr << "There's less columns (" << row.size()
<< "), than described in the header! (" << headerRows << ')'
<< std::endl;
return 1;
}
for (int y = 0; y < headerColumns; y++) {
char sign = row[y];
switch (sign) {
case '-':
grid.set(x, y, Cell::DEAD);
break;
case 'X':
grid.set(x, y, Cell::ALIVE);
break;
default:
std::cerr << "Invalid option '" << sign << "' in grid description!"
<< std::endl;
return 1;
}
}
}
gridPrint(grid);
MenuAction action;
while ((action = askMenu()) != MenuAction::QUIT) {
// We don't have to handle QUIT, since we do in the while check
switch (action) {
case MenuAction::TICK:
gridTick(grid);
gridPrint(grid);
break;
case MenuAction::ANIMATE:
for (int i = 0; i < ANIMATION_N_GENERATIONS; i++) {
gridTick(grid);
clearConsole();
gridPrint(grid);
pause(ANIMATION_SLEEP_MS);
}
break;
}
}
std::cout << "Have a nice Life!" << std::endl;
return 0;
}