From cc8482fccfd27237f10c729027be91fe84756870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Fri, 24 Dec 2021 00:20:50 +0100 Subject: [PATCH] GameOfLife implemented but not tested --- .gitignore | 4 + CMakeLists.txt | 33 ++++++++ include/README | 39 ++++++++++ lib/README | 46 +++++++++++ platformio.ini | 14 ++++ src/BASICS.h | 8 ++ src/display/Display.h | 86 ++++++++++++++++++++ src/display/Pixel.h | 28 +++++++ src/main.cpp | 77 ++++++++++++++++++ src/mode/GameOfLife/Cell.h | 74 ++++++++++++++++++ src/mode/GameOfLife/GameOfLife.h | 130 +++++++++++++++++++++++++++++++ src/mode/Mode.h | 32 ++++++++ test/README | 11 +++ 13 files changed, 582 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 include/README create mode 100644 lib/README create mode 100644 platformio.ini create mode 100644 src/BASICS.h create mode 100644 src/display/Display.h create mode 100644 src/display/Pixel.h create mode 100644 src/main.cpp create mode 100644 src/mode/GameOfLife/Cell.h create mode 100644 src/mode/GameOfLife/GameOfLife.h create mode 100644 src/mode/Mode.h create mode 100644 test/README diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eda9072 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.pio +CMakeListsPrivate.txt +cmake-build-*/ +/.idea \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..284162b --- /dev/null +++ b/CMakeLists.txt @@ -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 "$<$>:-e${CMAKE_BUILD_TYPE}>" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) + +add_custom_target( + Debug ALL + COMMAND platformio -c clion debug "$<$>:-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) diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -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 diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -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 +#include + +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 diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..4b30716 --- /dev/null +++ b/platformio.ini @@ -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 diff --git a/src/BASICS.h b/src/BASICS.h new file mode 100644 index 0000000..2d3fb59 --- /dev/null +++ b/src/BASICS.h @@ -0,0 +1,8 @@ +#ifndef BASICS_H +#define BASICS_H + +#include + +typedef unsigned long millis_t; + +#endif diff --git a/src/display/Display.h b/src/display/Display.h new file mode 100644 index 0000000..9b377a2 --- /dev/null +++ b/src/display/Display.h @@ -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 diff --git a/src/display/Pixel.h b/src/display/Pixel.h new file mode 100644 index 0000000..74949c4 --- /dev/null +++ b/src/display/Pixel.h @@ -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 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..f6fb655 --- /dev/null +++ b/src/main.cpp @@ -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); +} diff --git a/src/mode/GameOfLife/Cell.h b/src/mode/GameOfLife/Cell.h new file mode 100644 index 0000000..81a2baf --- /dev/null +++ b/src/mode/GameOfLife/Cell.h @@ -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 diff --git a/src/mode/GameOfLife/GameOfLife.h b/src/mode/GameOfLife/GameOfLife.h new file mode 100644 index 0000000..2e4240c --- /dev/null +++ b/src/mode/GameOfLife/GameOfLife.h @@ -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 diff --git a/src/mode/Mode.h b/src/mode/Mode.h new file mode 100644 index 0000000..8206239 --- /dev/null +++ b/src/mode/Mode.h @@ -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 diff --git a/test/README b/test/README new file mode 100644 index 0000000..b94d089 --- /dev/null +++ b/test/README @@ -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