Compare commits
10 Commits
efd0c3b6f2
...
5158b2aa52
Author | SHA1 | Date | |
---|---|---|---|
5158b2aa52 | |||
86025a8836 | |||
6ed9035713 | |||
8f9f4d461d | |||
4c11df3160 | |||
9e4790a0a0 | |||
faeead0cf0 | |||
226bc2b340 | |||
cc2ce40642 | |||
6306327d74 |
254
src/life.cpp
254
src/life.cpp
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user