GameOfLife implemented but not tested
This commit is contained in:
commit
cc8482fccf
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.pio
|
||||
CMakeListsPrivate.txt
|
||||
cmake-build-*/
|
||||
/.idea
|
||||
33
CMakeLists.txt
Normal file
33
CMakeLists.txt
Normal 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
39
include/README
Normal 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
46
lib/README
Normal 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
14
platformio.ini
Normal 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
8
src/BASICS.h
Normal 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
86
src/display/Display.h
Normal 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
28
src/display/Pixel.h
Normal 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
77
src/main.cpp
Normal 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);
|
||||
}
|
||||
74
src/mode/GameOfLife/Cell.h
Normal file
74
src/mode/GameOfLife/Cell.h
Normal 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
|
||||
130
src/mode/GameOfLife/GameOfLife.h
Normal file
130
src/mode/GameOfLife/GameOfLife.h
Normal 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
32
src/mode/Mode.h
Normal 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
11
test/README
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user