From ec379c009eb79312a6b75962237bef0cb61ad1e4 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Tue, 15 Oct 2019 20:06:19 +0200 Subject: [PATCH] Effect snake is now a real snake game, with apples, dying and even a more or less smart AI. --- include/effect_snake.h | 41 +++++--- src/effect_snake.cpp | 217 ++++++++++++++++++++++++++++++++--------- 2 files changed, 199 insertions(+), 59 deletions(-) diff --git a/include/effect_snake.h b/include/effect_snake.h index 3529ea2..eee214c 100644 --- a/include/effect_snake.h +++ b/include/effect_snake.h @@ -3,23 +3,42 @@ #include "Effect.h" #include "prototypes.h" +#define SNAKE_DIR_NORTH 0 +#define SNAKE_DIR_EAST 1 +#define SNAKE_DIR_SOUTH 2 +#define SNAKE_DIR_WEST 3 + class SnakeEffect : public Effect { private: - Coords coords; - uint8_t direction = 1; - uint8_t hue = 0; - uint8_t run = 0; - bool is_turn_needed(); - void turn_random(); - bool turn_right(); - bool turn_left(); - bool is_direction_okay(uint8_t direction); + Coords _pos; + Coords _apple; + int8_t _dir = SNAKE_DIR_NORTH; + uint8_t* _map; + uint16_t _pixels; + uint8_t _length; + unsigned long _last_apple_at; + unsigned long _last_move_at; + // The following code is a handwritten "ai". Useful for testing and stuff. + //int8_t _decisions[64] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,-1,1,-1,-1,-1,-1,0,0,0,0,-1,-1,0,0,0,1,0,0,-1,-1,0,0}; + int8_t _decisions[64] = {0, 1, 1, -1, 0, 1, 1, -1, 0, -1, 1, -1, -1, 0, 1, -1, -1, 0, 0, 1, -1, -1, 0, 0, -1, 0, 0, -1, 0, -1, 0, -1, -1, 0, 0, -1, -1, 0, 1, -1, -1, 1, 1, -1, -1, 1, -1, 0, 0, 1, 0, 1, -1, -1, 0, 0, 0, -1, 0, 1, 0, -1, 0, -1}; + //int8_t _decisions[64] = {1, 1, 0, 0, 1, 1, -1, -1, 0, -1, 0, -1, -1, 0, -1, 1, 1, -1, 0, 0, 1, -1, 0, 1, 0, -1, 0, -1, 0, 1, 1, 1, -1, 0, 0, 0, -1, -1, -1, 1, -1, -1, 1, 1, -1, 0, 1, 1, 1, 0, 0, -1, -1, 0, -1, 1, 0, 0, 0, -1, 0, -1, 1, 1}; + uint16_t _xy2i(uint8_t x, uint8_t y); + uint16_t _xy2i(Coords c); + Coords _i2xy(uint16_t i); + Coords _new_pos(uint8_t dir); + uint8_t _dying = 0; + bool _is_free(uint8_t dir); + bool _to_apple(uint8_t dir); + void _place_apple(); + void _init(); + void _decide(); + int8_t _manual_decision(); + void _move(); + void _draw(); public: SnakeEffect(); ~SnakeEffect(); void loop(uint16_t ms); - boolean valid_position(Coords c); - Coords update_position(Coords c, uint8_t direction); boolean can_be_shown_with_clock(); String get_name() override { return "snake"; } }; diff --git a/src/effect_snake.cpp b/src/effect_snake.cpp index cf03fac..e422418 100644 --- a/src/effect_snake.cpp +++ b/src/effect_snake.cpp @@ -2,63 +2,184 @@ #include "functions.h" SnakeEffect::SnakeEffect() { - this->coords = {0, 0}; - this->window = new Window(0, 0, LED_WIDTH, LED_HEIGHT-6); + window = new Window(0, 0, LED_WIDTH, LED_HEIGHT-6); + _pixels = window->width * window->height; + _map = new uint8_t[_pixels]; + _init(); +} + +void SnakeEffect::_init() { + _dying = 0; + _last_apple_at = millis(); + _last_move_at = millis(); + _dir = SNAKE_DIR_NORTH; + _length = 4; + _pos = {(uint8_t)(window->width/2), (uint8_t)(window->height/2)}; + for (int i=0; i<_pixels; i++) _map[i]=0; + _map[_xy2i(_pos)]=1; + _place_apple(); } SnakeEffect::~SnakeEffect() { delete window; + delete _map; +} + +void SnakeEffect::_place_apple() { + if (_length < _pixels) { + uint8_t start = random8(_pixels); + for (int i=0; i<_pixels; i++) { + if (_map[start + i]==0) { + _apple = _i2xy(start + i); + return; + } + } + } +} + +void SnakeEffect::_decide() { + uint8_t input = 0; + if (_is_free(_dir - 1)) input |= 1<<5; + if (_is_free(_dir)) input |= 1<<4; + if (_is_free(_dir + 1)) input |= 1<<3; + if (_to_apple(_dir - 1)) input |= 1<<2; + if (_to_apple(_dir)) input |= 1<<1; + if (_to_apple(_dir + 1)) input |= 1; + + _dir += _decisions[input]; + if (_dir < 0) _dir += 4; +} + +/** + * This is old (and hence disabled) code, showing + * a simple, hand-crafted "AI" for plaing snake. + ** +int8_t SnakeEffect::_manual_decision() { + bool free_l = _is_free(_dir - 1); + bool free_s = _is_free(_dir); + bool free_r = _is_free(_dir + 1); + bool apple_l = _to_apple(_dir - 1); + bool apple_s = _to_apple(_dir); + bool apple_r = _to_apple(_dir + 1); + + if (!free_s) { + if (apple_l && free_l) return -1; + if (apple_r && free_r) return 1; + if (free_l) return -1; + return 1; + } + + if (apple_s) return 0; + if (apple_l && free_l) return -1; + if (apple_r && free_r) return 1; + return 0; +}*/ + +bool SnakeEffect::_is_free(uint8_t dir) { + Coords np = _new_pos(dir); + return np.x>=0 && np.xwidth && np.y>=0 && np.yheight && _map[_xy2i(np)]==0; +} + +bool SnakeEffect::_to_apple(uint8_t dir) { + uint8_t d = dir % 4; + switch(d) { + case SNAKE_DIR_NORTH: return _apple.y<_pos.y; + case SNAKE_DIR_EAST: return _apple.x>_pos.x; + case SNAKE_DIR_SOUTH: return _apple.y>_pos.y; + case SNAKE_DIR_WEST: return _apple.x<_pos.x; + } + return true; +} + +Coords SnakeEffect::_new_pos(uint8_t dir) { + uint8_t d = dir % 4; + Coords p(_pos); + switch(d) { + case SNAKE_DIR_NORTH: p.y--; break; + case SNAKE_DIR_EAST: p.x++; break; + case SNAKE_DIR_SOUTH: p.y++; break; + case SNAKE_DIR_WEST: p.x--; break; + } + return p; +} + +uint16_t SnakeEffect::_xy2i(Coords c) { + return _xy2i(c.x, c.y); +} + +uint16_t SnakeEffect::_xy2i(uint8_t x, uint8_t y) { + return y*window->width + x; +} + +Coords SnakeEffect::_i2xy(uint16_t i) { + return {(uint16_t)(i%window->width), (uint16_t)(i/window->width)}; +} + +void SnakeEffect::_move() { + if (_dying==0 && !_is_free(_dir)) { + _dying = 150; + return; + } + + if (_dying > 0) { + _dying--; + + if (_dying==0) { + _init(); + } + + return; + } + + unsigned long now = millis(); + if (_last_move_at < now && now - _last_move_at < 100) { + return; + } + _last_move_at = now; + _pos = _new_pos(_dir); + if (_pos.x==_apple.x && _pos.y==_apple.y) { + _last_apple_at = millis(); + _length++; + } + for (int i=0; i<_pixels; i++) { + if (_map[i]>0 && _map[i]<_length-1) _map[i]++; + else _map[i]=0; + } + _map[_xy2i(_pos)] = 1; + if (_pos.x==_apple.x && _pos.y==_apple.y) { + _place_apple(); + } +} + +void SnakeEffect::_draw() { + if (_dying) { + window->fadeToBlackBy(4); + return; + } + window->clear(); + CRGB red(0xBB0000); + for (int i=0; i<_pixels; i++) { + if (_map[i]>0) window->setPixelByIndex(i, &red); + } + CRGB white(0xFFFFFF); + window->setPixel(_pos.x, _pos.y, &white); + CRGB green(0xFFFF00); + window->setPixel(_apple.x, _apple.y, &green); } void SnakeEffect::loop(uint16_t ms) { - if (run++ % settings.effects.snake.slowdown == 0) { // Change the coordinates only on every n-th run. - if (random8(settings.effects.snake.direction_change)==0 || is_turn_needed()) turn_random(); - - this->coords = update_position(this->coords, this->direction); + //window->fadeToBlackBy(2); + //CRGB color(CHSV(hue, 200, 255)); + //window->setPixel(this->coords.x, this->coords.y, &color); + //hue++; + if (millis() < _last_apple_at || millis() - _last_apple_at > 30000) { + _dying = 150; } - - window->fadeToBlackBy(2); - CRGB color(CHSV(hue, 200, 255)); - window->setPixel(this->coords.x, this->coords.y, &color); - hue++; -} - -void SnakeEffect::turn_random() { - if ((random8() & 1) == 0) { - turn_right() || turn_left(); - } else { - turn_left() || turn_right(); + if (_dying==0) { + _decide(); } -} - -bool SnakeEffect::turn_left() { - if (!is_direction_okay(this->direction - 1)) return false; - this->direction--; - return true; -} - -bool SnakeEffect::turn_right() { - if (!is_direction_okay(this->direction + 1)) return false; - this->direction++; - return true; -} - -bool SnakeEffect::is_turn_needed() { - return !is_direction_okay(this->direction); -} - -bool SnakeEffect::is_direction_okay(uint8_t dir) { - Coords c = update_position(this->coords, dir); - return c.xwidth && c.yheight; -} - -Coords SnakeEffect::update_position(Coords original, uint8_t direction) { - direction = direction % 4; - if (direction == 0) original.y--; - else if (direction == 1) original.x++; - else if (direction == 2) original.y++; - else if (direction == 3) original.x--; - return original; + _move(); + _draw(); } boolean SnakeEffect::can_be_shown_with_clock() { return true; }