GameOfLife implemented but not tested

This commit is contained in:
Patrick Haßel 2021-12-24 00:20:50 +01:00
commit cc8482fccf
13 changed files with 582 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.pio
CMakeListsPrivate.txt
cmake-build-*/
/.idea

33
CMakeLists.txt Normal file
View File

@ -0,0 +1,33 @@
# !!! WARNING !!! AUTO-GENERATED FILE, PLEASE DO NOT MODIFY IT AND USE
# https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags
#
# If you need to override existing CMake configuration or add extra,
# please create `CMakeListsUser.txt` in the root of project.
# The `CMakeListsUser.txt` will not be overwritten by PlatformIO.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER_WORKS 1)
set(CMAKE_CXX_COMPILER_WORKS 1)
project("MediaTable" C CXX)
include(CMakeListsPrivate.txt)
if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/CMakeListsUser.txt)
include(CMakeListsUser.txt)
endif ()
add_custom_target(
Production ALL
COMMAND platformio -c clion run "$<$<NOT:$<CONFIG:All>>:-e${CMAKE_BUILD_TYPE}>"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
add_custom_target(
Debug ALL
COMMAND platformio -c clion debug "$<$<NOT:$<CONFIG:All>>:-e${CMAKE_BUILD_TYPE}>"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
add_executable(Z_DUMMY_TARGET ${SRC_LIST} src/display/Display.h src/display/Pixel.h src/mode/GameOfLife/Cell.h)

39
include/README Normal file
View File

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

46
lib/README Normal file
View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

14
platformio.ini Normal file
View File

@ -0,0 +1,14 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino

8
src/BASICS.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef BASICS_H
#define BASICS_H
#include <Arduino.h>
typedef unsigned long millis_t;
#endif

86
src/display/Display.h Normal file
View File

@ -0,0 +1,86 @@
#ifndef DISPLAY_H
#define DISPLAY_H
#include "Pixel.h"
class Display {
private:
uint8_t width;
uint8_t height;
Pixel *pixels;
uint16_t pixelCount;
bool dirty = true;
public:
Display(uint8_t width, uint8_t height) :
width(width), height(height) {
pixelCount = width * height;
pixels = (Pixel *) malloc(pixelCount * sizeof(Pixel));
clear();
}
~Display() {
if (pixels != nullptr) {
free(pixels);
pixels = nullptr;
}
}
uint8_t getWidth() const {
return width;
}
uint8_t getHeight() const {
return height;
}
uint16_t getPixelCount() const {
return pixelCount;
}
void set(uint8_t x, uint8_t y, Pixel pixel) {
if (x > width || y > height) {
Serial.printf("ERROR: Cannot set pixel (%d/%d) in matrix (%d/%d).\n", x, y, width, height);
return;
}
*(pixels + y * width + x) = pixel;
dirty = true;
}
void clear() {
for (Pixel *p = pixels; p < end(); p++) {
*p = {0, 0, 0};
}
dirty = true;
}
void setup() {
// TODO init IO
}
void loop() {
if (!dirty) {
return;
}
dirty = false;
// TODO
}
Pixel *begin() {
return pixels;
}
Pixel *end() {
return pixels + pixelCount;
}
};
#endif

28
src/display/Pixel.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef PIXEL_H
#define PIXEL_H
#include "BASICS.h"
class Pixel {
public:
uint8_t r;
uint8_t g;
uint8_t b;
bool isOn() const {
return r != 0 || g != 0 || b != 0;
}
void setGray(uint8_t brightness) {
r = brightness;
g = brightness;
b = brightness;
}
};
#endif

77
src/main.cpp Normal file
View File

@ -0,0 +1,77 @@
#include "mode/Mode.h"
#include "mode/GameOfLife/GameOfLife.h"
#include "display/Display.h"
enum ModeId {
NONE, GAME_OF_LIFE
};
Display display(8, 16);
ModeId newModeId = GAME_OF_LIFE;
ModeId currentModeId = NONE;
millis_t lastMillis = 0;
Mode *mode = nullptr;
void checkMode();
void stepMode();
void unloadOldMode();
void loadNewMode();
void setup() {
Serial.begin(115200);
Serial.println("\n\n\nStartup!\n");
display.setup();
}
void loop() {
checkMode();
display.loop();
stepMode();
}
void checkMode() {
if (currentModeId != newModeId) {
unloadOldMode();
loadNewMode();
}
}
void unloadOldMode() {
if (mode != nullptr) {
Serial.printf("Unloading state \"%s\".\n", mode->getName());
delete mode;
mode = nullptr;
}
display.clear();
}
void loadNewMode() {
currentModeId = newModeId;
lastMillis = 0;
Serial.printf("Loading state #%d.\n", currentModeId);
switch (currentModeId) {
case NONE:
break;
case GAME_OF_LIFE:
mode = new GameOfLife(&display);
break;
}
Serial.printf("Loaded state \"%s\".\n", mode == nullptr ? "None" : mode->getName());
}
void stepMode() {
if (mode == nullptr) {
return;
}
millis_t currentMillis = millis();
millis_t dt = currentMillis - lastMillis;
lastMillis = currentMillis;
mode->step(dt);
}

View File

@ -0,0 +1,74 @@
#ifndef CELL_H
#define CELL_H
#include "BASICS.h"
#define DEAD 0
#define GROWING 1
#define ALIVE 2
#define DYING 3
class Cell {
public:
uint8_t state = DEAD;
uint8_t value = 0;
bool isAlive() const {
return state == GROWING || state == ALIVE;
}
void spawn() {
state = GROWING;
}
void kill() {
state = DYING;
}
void animate(uint8_t dt) {
switch (state) {
case GROWING:
if (dt < 255 - value) {
value += dt;
} else {
value = 255;
state = ALIVE;
}
break;
case DYING:
if (dt < value) {
value -= dt;
} else {
value = 0;
state = DEAD;
}
break;
}
}
uint8_t getR() const {
if (state == DYING) {
return value;
}
return 0;
}
uint8_t getG() const {
if (state == GROWING) {
return value;
}
return 0;
}
uint8_t getB() const {
if (state == ALIVE) {
return 255;
}
return 0;
}
};
#endif

View File

@ -0,0 +1,130 @@
#ifndef GAMEOFLIFE_H
#define GAMEOFLIFE_H
#include "mode/Mode.h"
#include "display/Display.h"
#include "Cell.h"
class GameOfLife : public Mode {
private:
const uint8_t width;
const uint8_t height;
const uint16_t count;
uint16_t aliveCount = 0;
millis_t runtime = 0;
uint8_t steps = 0;
Cell *cells;
Cell *next;
public:
explicit GameOfLife(Display *display) :
Mode(display, "Game of Life"),
width(display->getWidth()),
height(display->getHeight()),
count(display->getPixelCount()) {
cells = (Cell *) malloc(count * sizeof(Cell));
next = (Cell *) malloc(count * sizeof(Cell));
}
~GameOfLife()
override {
if (cells != nullptr) {
free(cells);
cells = nullptr;
}
if (next != nullptr) {
free(next);
next = nullptr;
}
}
void step(millis_t dt)
override {
runtime += dt;
while (runtime % 100 == 0) {
if (steps++ == 0 || aliveCount == 0) {
steps = 0;
randomFill();
} else {
nextGeneration();
}
}
animate(dt);
}
private:
void randomFill() {
for (Cell *cell = cells; cell < cells + count; cell++) {
if (random(4) == 0) {
if (!cell->isAlive()) {
cell->spawn();
aliveCount++;
}
} else {
if (cell->isAlive()) {
cell->kill();
aliveCount--;
}
}
}
}
void nextGeneration() {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Cell *cell = get(x, y);
uint8_t around = countAround(x, y);
if (cell->isAlive()) {
if (around <= 2 || 4 <= around) {
cell->kill();
aliveCount--;
}
} else if (around == 3) {
cell->spawn();
aliveCount++;
}
}
}
memcpy(cells, next, count);
}
void animate(millis_t dt) {
Cell *cell = cells;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
cell->animate(dt);
display->set(x, y, {cell->getR(), cell->getG(), cell->getB()});
cell++;
}
}
}
uint8_t countAround(int x, int y) {
return countIfAlive(x - 1, y - 1) + countIfAlive(x + 0, y - 1) + countIfAlive(x + 1, y - 1) +
countIfAlive(x - 1, y + 0) + countIfAlive(x + 0, y + 0) + countIfAlive(x + 1, y + 0) +
countIfAlive(x - 1, y + 1) + countIfAlive(x + 0, y + 1) + countIfAlive(x + 1, y + 1);
}
uint8_t countIfAlive(int x, int y) {
if (x < 0 || y < 0 || x >= width || y >= height) {
return 0;
}
return get(x, y)->isAlive() ? 1 : 0;
}
Cell *get(int x, int y) {
return cells + width * y + x;
}
};
#endif

32
src/mode/Mode.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef MODE_H
#define MODE_H
#include "BASICS.h"
#include "display/Display.h"
class Mode {
protected:
Display *display;
const char *name;
public:
explicit Mode(Display *display, const char *name) :
display(display),
name(name) {
// nothing
}
virtual ~Mode() = default;
const char *getName() {
return name;
}
virtual void step(millis_t dt) = 0;
};
#endif

11
test/README Normal file
View File

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html