Compare commits
No commits in common. "38aa654c54125924388fcb5f1616afda196c49bb" and "a3caaa1fefd25cee692f80ad8eb70ae782038e13" have entirely different histories.
38aa654c54
...
a3caaa1fef
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,4 +10,3 @@ include/config.h
|
||||
.pioenvs
|
||||
.DS_Store
|
||||
.vscode
|
||||
src/tools/snakenet/data_set.dat
|
||||
|
@ -29,7 +29,7 @@ public:
|
||||
void setSubPixel(accum88 x, accum88 y, CRGB* color, SubpixelRenderingMode m = SUBPIXEL_RENDERING_ADD);
|
||||
void setPixelByIndex(uint16_t index, CRGB* color);
|
||||
void raisePixel(uint8_t x, uint8_t y, CRGB* color);
|
||||
void line(accum88 x1, accum88 y1, accum88 x2, accum88 y2, CRGB* color);
|
||||
void line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, CRGB* color);
|
||||
void lineWithAngle(uint8_t x, uint8_t y, uint8_t angle, uint8_t length, CRGB* color);
|
||||
void lineWithAngle(uint8_t x, uint8_t y, uint8_t angle, uint8_t startdist, uint8_t length, CRGB* color);
|
||||
void circle(uint8_t x, uint8_t y, uint8_t r, CRGB* color);
|
||||
|
@ -8,8 +8,6 @@
|
||||
#define SNAKE_DIR_SOUTH 2
|
||||
#define SNAKE_DIR_WEST 3
|
||||
|
||||
#define SNAKE_DEBUG false
|
||||
|
||||
class SnakeEffect : public Effect {
|
||||
private:
|
||||
Coords _pos;
|
||||
@ -20,32 +18,17 @@ private:
|
||||
uint8_t _length;
|
||||
unsigned long _last_apple_at;
|
||||
unsigned long _last_move_at;
|
||||
uint16_t _round;
|
||||
|
||||
// Neural net config
|
||||
// These are actually float values. But in order to prevent rounding errors and stuff, they are provided
|
||||
// in form of the raw binary data of the IEE754 floating point numbers.
|
||||
// In _decide() there's code to memcpy()-convert them to a float.
|
||||
// Round 340, 223.4 points, length 39, 36% stopped, 64% died
|
||||
// const uint32_t _weights[36] = {0xbd8e626e, 0xbee2cd2c, 0x3e4d5cab, 0x3eceb8c3, 0xbed0a514, 0x3ec62438, 0x3e947ed4, 0xbe4b8bf2, 0xbf301113, 0xbf3f0a75, 0x3f1868f7, 0xbf0253ca, 0xbedca2f2, 0xbd547c6d, 0x3edd6a8a, 0xbd4b97b6, 0x3f64ec26, 0xbe5323c1, 0x3eccf87d, 0xbf2d4796, 0xbf62b6e8, 0xbf71daf6, 0xbf03f08e, 0xbf222609, 0x3e26c03c, 0xbf497837, 0xbee4d175, 0x3ec601de, 0x3e4e0695, 0x3eef2619, 0xbe849370, 0xbf18fb2b, 0x3f25bbd1, 0xbf3dcd78, 0x3f37a58d, 0x3ef4a25b};
|
||||
// Round 630, 221.0 points, length 38, 36% stopped, 64% died
|
||||
const uint32_t _weights[36] = {0xbd25943f, 0xbf279d81, 0x3e25d128, 0x3ec62438, 0x3f0e719c, 0x3eefbea9, 0x3e947ed4, 0xbe5323c1, 0xbf2d4796, 0xbf3f0a75, 0x3f0e45d9, 0xbf0253ca, 0xbedca2f2, 0xbd79073c, 0x3ede80ec, 0xbd4b97b6, 0x3f69a6be, 0xbe4b8bf2, 0x3eccf87d, 0xbf301113, 0xbf62b6e8, 0xbf71daf6, 0xbf204130, 0xbf222609, 0x3e26c03c, 0xbf497837, 0xbee4d175, 0x3ec601de, 0x3e4954eb, 0x3eef2619, 0xbe849370, 0xbf18fb2b, 0x3f25bbd1, 0xbf3b4e44, 0x3f484d59, 0x3edd6a8a};
|
||||
// Round 193, 164.8 points, length 36, 6% stopped, 94% died
|
||||
//const uint32_t _weights[36] = {0x3e872ffb, 0xbea57262, 0xbee269bf, 0x3ed790a3, 0xbf54014f, 0x3ecde0a6, 0xbf240a93, 0xbe9e4782, 0x3f205106, 0xbf4465c2, 0xbf79579a, 0xbf07f122, 0x3ed0e1bc, 0xbf7a5a09, 0xbf0fc70b, 0xbf6d1971, 0xbe0f5585, 0xbec94b12, 0x3f51f7a9, 0x3eaac42b, 0xbe6aafa6, 0x3d3e3ce3, 0xbf7c4232, 0xbe634103, 0x3f800000, 0x3eff886c, 0x3deae1e8, 0x3eea6988, 0xbf800000, 0xbf426a20, 0x3e3a0a45, 0xbe848803, 0x3e84e8c9, 0x3ef9fabc, 0xbe7733e6, 0xbecda633};
|
||||
|
||||
const uint8_t _net_layout[3] = {6, 4, 3};
|
||||
const uint8_t _net_layers = 3;
|
||||
const uint8_t _net_total_size = 36;
|
||||
|
||||
|
||||
// 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);
|
||||
uint8_t _free_spaces(uint8_t dir);
|
||||
uint8_t _to_apple(uint8_t dir);
|
||||
bool _to_apple(uint8_t dir);
|
||||
void _place_apple();
|
||||
void _init();
|
||||
void _decide();
|
||||
|
@ -81,7 +81,7 @@ struct Settings {
|
||||
} snake;
|
||||
|
||||
struct /* tv_static */ {
|
||||
uint16_t black_bar_speed = 12;
|
||||
uint16_t black_bar_speed = 3500;
|
||||
} tv_static;
|
||||
} effects;
|
||||
};
|
||||
|
@ -150,27 +150,26 @@ void Window::fadeToBlackBy(fract8 speed) {
|
||||
}
|
||||
}
|
||||
|
||||
void Window::line(accum88 x1, accum88 y1, accum88 x2, accum88 y2, CRGB* color) {
|
||||
void Window::line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, CRGB* color) {
|
||||
// Bresenham algorithm
|
||||
const uint8_t stepsize = 32;
|
||||
saccum78 dx = x2-x1;
|
||||
saccum78 dy = y2-y1;
|
||||
int dx = x2-x1;
|
||||
int dy = y2-y1;
|
||||
|
||||
accum88 x = x1;
|
||||
accum88 y = y1;
|
||||
int x = x1;
|
||||
int y = y1;
|
||||
|
||||
saccum78 p = 2*dy - dx;
|
||||
int p = 2*dy - dx;
|
||||
|
||||
while (x < x2) {
|
||||
if (p >= 0) {
|
||||
setSubPixel(x, y, color, SUBPIXEL_RENDERING_RAISE);
|
||||
y+=stepsize;
|
||||
setPixel(x, y, color);
|
||||
y++;
|
||||
p = p + 2*dy - 2*dx;
|
||||
} else {
|
||||
setSubPixel(x, y, color, SUBPIXEL_RENDERING_RAISE);
|
||||
setPixel(x, y, color);
|
||||
p = p + 2*dy;
|
||||
}
|
||||
x+=stepsize;
|
||||
x++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,16 +206,16 @@ void Window::lineWithAngle(uint8_t x, uint8_t y, uint8_t angle, uint8_t length,
|
||||
}
|
||||
|
||||
void Window::lineWithAngle(uint8_t x, uint8_t y, uint8_t angle, uint8_t startdist, uint8_t length, CRGB* color) {
|
||||
accum88 x1 = x<<8;
|
||||
accum88 y1 = y<<8;
|
||||
int16_t x1 = x;
|
||||
int16_t y1 = y;
|
||||
|
||||
if (startdist > 0) {
|
||||
x1 = (x<<8) + (startdist<<8) * cos8(angle) / 256;
|
||||
y1 = (y<<8) + (startdist<<8) * sin8(angle) / 256;
|
||||
x1 = x + scale8(startdist, cos8(angle));
|
||||
y1 = y + scale8(startdist, sin8(angle));
|
||||
}
|
||||
|
||||
accum88 x2 = (x<<8) + (startdist + length) * cos16(angle);
|
||||
accum88 y2 = (y<<8) + (startdist + length) * sin16(angle);
|
||||
int16_t x2 = x + scale8(startdist + length, cos8(angle));
|
||||
int16_t y2 = y + scale8(startdist + length, sin8(angle));
|
||||
|
||||
line(x1, y1, x2, y2, color);
|
||||
}
|
||||
|
@ -9,9 +9,8 @@ void AnalogClockEffect::loop(uint16_t ms) {
|
||||
window->circle(8, 8, 7, &white);
|
||||
|
||||
uint8_t seconds = ntpClient.getSeconds();
|
||||
uint8_t angle = seconds * 256 / 60;
|
||||
window->lineWithAngle(8, 8, angle, 10, &red);
|
||||
window->line(1<<8, 1<<8, 12<<8, 4<<8, &white);
|
||||
uint8_t angle = 6 * seconds;
|
||||
window->lineWithAngle(8, 8, angle, 0, 10, &red);
|
||||
/*for (uint8_t i=0; i<=12; i++) {
|
||||
window->lineWithAngle(8, 8, 255/12*i, 5, 2, &white);
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ SnakeEffect::SnakeEffect() {
|
||||
|
||||
void SnakeEffect::_init() {
|
||||
_dying = 0;
|
||||
_round = 0;
|
||||
_last_apple_at = millis();
|
||||
_last_move_at = millis();
|
||||
_dir = SNAKE_DIR_NORTH;
|
||||
@ -18,9 +17,6 @@ void SnakeEffect::_init() {
|
||||
_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;
|
||||
_map[_xy2i(_pos)+window->width*1]=2;
|
||||
_map[_xy2i(_pos)+window->width*2]=3;
|
||||
_map[_xy2i(_pos)+window->width*3]=4;
|
||||
_place_apple();
|
||||
}
|
||||
|
||||
@ -30,10 +26,6 @@ SnakeEffect::~SnakeEffect() {
|
||||
}
|
||||
|
||||
void SnakeEffect::_place_apple() {
|
||||
if (SNAKE_DEBUG) {
|
||||
_apple = {3, 3};
|
||||
return;
|
||||
}
|
||||
if (_length < _pixels) {
|
||||
uint8_t start = random8(_pixels);
|
||||
for (int i=0; i<_pixels; i++) {
|
||||
@ -46,57 +38,16 @@ void SnakeEffect::_place_apple() {
|
||||
}
|
||||
|
||||
void SnakeEffect::_decide() {
|
||||
uint8_t f_l = _free_spaces(_dir - 1);
|
||||
uint8_t f_s = _free_spaces(_dir);
|
||||
uint8_t f_r = _free_spaces(_dir + 1);
|
||||
uint8_t a_l = _to_apple(_dir - 1);
|
||||
uint8_t a_s = _to_apple(_dir);
|
||||
uint8_t a_r = _to_apple(_dir + 1);
|
||||
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;
|
||||
|
||||
float* inputs = new float[6];
|
||||
inputs[0] = f_l;
|
||||
inputs[1] = f_s;
|
||||
inputs[2] = f_r;
|
||||
inputs[3] = a_l;
|
||||
inputs[4] = a_s;
|
||||
inputs[5] = a_r;
|
||||
if (SNAKE_DEBUG) LOGln("SnakeEffect * Position: %d, %d - Inputs: %3.1f %3.1f %3.1f %3.1f %3.1f %3.1f", _pos.x, _pos.y, inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5]);
|
||||
float* outputs = NULL;
|
||||
uint8_t i=0;
|
||||
for (uint8_t layer=1; layer<_net_layers; layer++) {
|
||||
outputs = new float[_net_layout[layer]];
|
||||
for (uint8_t j=0; j<_net_layout[layer]; j++) {
|
||||
outputs[j] = 0.0;
|
||||
}
|
||||
for (uint8_t idx_out=0; idx_out<_net_layout[layer]; idx_out++) {
|
||||
for (uint8_t idx_in=0; idx_in<_net_layout[layer-1]; idx_in++) {
|
||||
float weight;
|
||||
memcpy(&weight, &(_weights[i]), sizeof(weight));
|
||||
outputs[idx_out] += weight * inputs[idx_in];
|
||||
//outputs[idx_out] += (*(float*)&(_weights[i])) * inputs[idx_in];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
delete inputs;
|
||||
inputs = outputs;
|
||||
}
|
||||
|
||||
int8_t decision = 0;
|
||||
float last;
|
||||
for (uint8_t i=0; i<_net_layout[_net_layers - 1]; i++) {
|
||||
if (i==0 || outputs[i]>last) {
|
||||
last = outputs[i];
|
||||
decision = i;
|
||||
}
|
||||
}
|
||||
decision = decision - 1;
|
||||
delete outputs;
|
||||
|
||||
if (SNAKE_DEBUG) LOGln("SnakeEffect * Decision: %d", decision);
|
||||
|
||||
_dir += decision;
|
||||
_dir += _decisions[input];
|
||||
if (_dir < 0) _dir += 4;
|
||||
if (_dir > 3) _dir -= 4;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -125,43 +76,19 @@ int8_t SnakeEffect::_manual_decision() {
|
||||
}*/
|
||||
|
||||
bool SnakeEffect::_is_free(uint8_t dir) {
|
||||
return _free_spaces(dir)!=0;
|
||||
Coords np = _new_pos(dir);
|
||||
return np.x>=0 && np.x<window->width && np.y>=0 && np.y<window->height && _map[_xy2i(np)]==0;
|
||||
}
|
||||
|
||||
uint8_t SnakeEffect::_free_spaces(uint8_t dir) {
|
||||
int8_t x=0;
|
||||
int8_t y=0;
|
||||
bool SnakeEffect::_to_apple(uint8_t dir) {
|
||||
uint8_t d = dir % 4;
|
||||
switch(d) {
|
||||
case SNAKE_DIR_NORTH: y=-1; break;
|
||||
case SNAKE_DIR_EAST: x=1; break;
|
||||
case SNAKE_DIR_SOUTH: y=1; break;
|
||||
case SNAKE_DIR_WEST: x=-1; break;
|
||||
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;
|
||||
}
|
||||
Coords p(_pos);
|
||||
uint8_t i=0;
|
||||
while (true) {
|
||||
p.x += x;
|
||||
p.y += y;
|
||||
if (p.x<0 || p.x>=window->width || p.y<0 || p.y>=window->height || _map[_xy2i(p)]!=0) {
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t SnakeEffect::_to_apple(uint8_t dir) {
|
||||
uint8_t d = dir % 4;
|
||||
int8_t d_x = _apple.x - _pos.x;
|
||||
int8_t d_y = _apple.y - _pos.y;
|
||||
|
||||
switch(d) {
|
||||
case SNAKE_DIR_NORTH: return d_y < 0 ? -d_y : 0;
|
||||
case SNAKE_DIR_EAST: return d_x > 0 ? d_x : 0;
|
||||
case SNAKE_DIR_SOUTH: return d_y > 0 ? d_y : 0;
|
||||
case SNAKE_DIR_WEST: return d_x < 0 ? -d_x : 0;
|
||||
}
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
Coords SnakeEffect::_new_pos(uint8_t dir) {
|
||||
@ -189,7 +116,10 @@ Coords SnakeEffect::_i2xy(uint16_t i) {
|
||||
}
|
||||
|
||||
void SnakeEffect::_move() {
|
||||
|
||||
if (_dying==0 && !_is_free(_dir)) {
|
||||
_dying = 150;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_dying > 0) {
|
||||
_dying--;
|
||||
@ -202,21 +132,11 @@ void SnakeEffect::_move() {
|
||||
}
|
||||
|
||||
unsigned long now = millis();
|
||||
if (_last_move_at < now && now - _last_move_at < 0) {
|
||||
if (_last_move_at < now && now - _last_move_at < 100) {
|
||||
return;
|
||||
}
|
||||
_round++;
|
||||
_last_move_at = now;
|
||||
_decide();
|
||||
|
||||
if (_dying==0 && !_is_free(_dir)) {
|
||||
_dying = 150;
|
||||
return;
|
||||
}
|
||||
|
||||
_pos = _new_pos(_dir);
|
||||
if (SNAKE_DEBUG) LOGln("SnakeEffect * new_pos: %d, %d", _pos.x, _pos.y);
|
||||
if (SNAKE_DEBUG) LOGln("SnakeEffect * apple: %d, %d", _apple.x, _apple.y);
|
||||
if (_pos.x==_apple.x && _pos.y==_apple.y) {
|
||||
_last_apple_at = millis();
|
||||
_length++;
|
||||
@ -252,9 +172,12 @@ void SnakeEffect::loop(uint16_t ms) {
|
||||
//CRGB color(CHSV(hue, 200, 255));
|
||||
//window->setPixel(this->coords.x, this->coords.y, &color);
|
||||
//hue++;
|
||||
if (_dying==0 && (millis() < _last_apple_at || millis() - _last_apple_at > 30000)) {
|
||||
if (millis() < _last_apple_at || millis() - _last_apple_at > 30000) {
|
||||
_dying = 150;
|
||||
}
|
||||
if (_dying==0) {
|
||||
_decide();
|
||||
}
|
||||
_move();
|
||||
_draw();
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
#include "effect_tv_static.h"
|
||||
|
||||
void TvStaticEffect::loop(uint16_t ms) {
|
||||
//uint8_t dark_position = (millis() % settings.effects.tv_static.black_bar_speed) * _window->width / settings.effects.tv_static.black_bar_speed;
|
||||
accum88 dark_position = (beat16(settings.effects.tv_static.black_bar_speed) * _window->width) >> 8;
|
||||
uint8_t dark_position = (millis() % settings.effects.tv_static.black_bar_speed) * _window->width / settings.effects.tv_static.black_bar_speed;
|
||||
for (uint8_t y=0; y<_window->height; y++) {
|
||||
accum88 row_dark_position = (dark_position + (y<<8)/3) % (_window->width<<8);
|
||||
uint8_t row_dark_position = (dark_position + y/3) % _window->width;
|
||||
for (uint8_t x=0; x<_window->width; x++) {
|
||||
uint8_t brightness = random8();
|
||||
uint8_t darkening = 0;
|
||||
accum88 distance = (x<<8) - row_dark_position;
|
||||
if (distance < 256) darkening = random8(distance, 255);
|
||||
else if (distance < (4<<8)) darkening = random8(distance >> 2, 255);
|
||||
|
||||
uint8_t distance = x - row_dark_position;
|
||||
if (distance == 0) darkening = random8(192, 255);
|
||||
else if (distance == 1) darkening = random8(128, 255);
|
||||
else if (distance == 2) darkening = random8(92, 192);
|
||||
else if (distance == 3) darkening = random8(32, 128);
|
||||
if (darkening > brightness) brightness = 0;
|
||||
else brightness -= darkening;
|
||||
CRGB color(brightness, brightness, brightness);
|
||||
|
@ -100,17 +100,8 @@ void loop() {
|
||||
last_loop_ago = 0;
|
||||
}
|
||||
|
||||
#ifdef MQTT_REPORT_METRICS
|
||||
unsigned long effect_loop_started = millis();
|
||||
#endif
|
||||
|
||||
current_effect->loop(last_loop_ago);
|
||||
|
||||
#ifdef MQTT_REPORT_METRICS
|
||||
metrics_frame_count++;
|
||||
metrics_frame_time += (millis() - effect_loop_started);
|
||||
#endif
|
||||
|
||||
// Save the time for the next run.
|
||||
_last_effect_loop_finished_at = now;
|
||||
|
||||
@ -118,7 +109,10 @@ void loop() {
|
||||
effect_clock.loop_with_invert(current_effect->clock_as_mask());
|
||||
}
|
||||
FastLED.show();
|
||||
|
||||
#ifdef MQTT_REPORT_METRICS
|
||||
metrics_frame_count++;
|
||||
metrics_frame_time += (millis() - loop_started_at);
|
||||
#endif
|
||||
|
||||
#ifdef RECORDER_ENABLE
|
||||
recorder->loop();
|
||||
|
@ -1,6 +0,0 @@
|
||||
#!/usr/bin/gnuplot -c
|
||||
set term dumb 79 49
|
||||
plot 'data_set.dat' using 1:2 title 'Points', \
|
||||
# 'data_set.dat' using 1:3 title 'Length' axes x1y2, \
|
||||
# 'data_set.dat' using 1:4 title 'Stopped' axes x1y2, \
|
||||
# 'data_set.dat' using 1:5 title 'Dead' axes x1y2
|
@ -1,415 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
require 'rubygems'
|
||||
require 'pp'
|
||||
require 'thread/pool'
|
||||
|
||||
GAMES_PER_ROUND = 50
|
||||
|
||||
class Game
|
||||
WIDTH = 16
|
||||
HEIGHT = 10
|
||||
|
||||
POINTS_APPLE = 10
|
||||
POINTS_MOVING_CLOSER = 1
|
||||
POINTS_MOVING_FAR = -1.5
|
||||
|
||||
attr_reader :points, :dead, :ai, :length
|
||||
attr_accessor :apple
|
||||
|
||||
def initialize(a, debug=false)
|
||||
@debug = debug
|
||||
@ai = a
|
||||
@data = [0]*(WIDTH*HEIGHT)
|
||||
@dir = 0
|
||||
@pos = [WIDTH/2, HEIGHT/2]
|
||||
@data[(@pos[1] )*WIDTH + @pos[0]]=1
|
||||
@data[(@pos[1]+1)*WIDTH + @pos[0]]=2
|
||||
@data[(@pos[1]+2)*WIDTH + @pos[0]]=3
|
||||
@data[(@pos[1]+3)*WIDTH + @pos[0]]=4
|
||||
@length = 4
|
||||
@points = 0.0
|
||||
@dead = false
|
||||
@round = 0
|
||||
@last_apple_at = 0
|
||||
@count_left = @count_right = 0
|
||||
place_apple()
|
||||
end
|
||||
|
||||
def place_apple
|
||||
x=-1
|
||||
while @data[x]!=0 || x==-1
|
||||
x = rand(WIDTH*HEIGHT)
|
||||
end
|
||||
@apple = [x%WIDTH, x/WIDTH]
|
||||
@old_distance = apple_distance()
|
||||
end
|
||||
|
||||
def apple_distance
|
||||
return (@pos[0] - @apple[0]).abs + (@pos[1] - @apple[1]).abs
|
||||
end
|
||||
|
||||
def to_s
|
||||
str = @data.join("").gsub("0", " ")
|
||||
str[@apple[1]*WIDTH+@apple[0]] = "*"
|
||||
s = "+" + "-"*(WIDTH-@points.to_s.length-1)+" "+@points.to_s+"+\n"
|
||||
(0...HEIGHT).each do |y|
|
||||
s += "|" + str[y*WIDTH, WIDTH] + "|\n"
|
||||
end
|
||||
s += "+" + "-"*WIDTH+"+\n"
|
||||
return s
|
||||
end
|
||||
|
||||
def draw; puts to_s; puts; end
|
||||
|
||||
def loop
|
||||
#puts "Loop. Position: #{@pos}"
|
||||
return if @dead
|
||||
decision = @ai.decide(free?(@dir-1), free?(@dir), free?(@dir+1), apple?(@dir-1), apple?(@dir), apple?(@dir+1))
|
||||
#puts "Decision: #{decision}"
|
||||
@count_left += 1 if decision==-1
|
||||
@count_right += 1 if decision==1
|
||||
@dir = (@dir + decision) % 4
|
||||
if (free?(@dir)==0)
|
||||
#puts "Dead."
|
||||
die()
|
||||
return
|
||||
end
|
||||
|
||||
move
|
||||
end
|
||||
|
||||
def ranking; @length*10 - (@dead ? 200 : 0) - (stopped? ? 100 : 0) - (@count_right - @count_left).abs * 0.05; end
|
||||
|
||||
def move
|
||||
newpos = calc_new_pos(@pos, @dir)
|
||||
puts "Newpos: #{newpos}" if @debug
|
||||
if newpos==@apple
|
||||
@length+=1
|
||||
@points += POINTS_APPLE
|
||||
@last_apple_at = @round
|
||||
place_apple
|
||||
end
|
||||
@data.each_with_index do |value, key|
|
||||
@data[key]=value+1 if value>0
|
||||
@data[key]=0 if value>=@length
|
||||
end
|
||||
@data[newpos[1]*WIDTH + newpos[0]] = 1
|
||||
@pos = newpos
|
||||
ad_d = apple_distance - @old_distance
|
||||
@old_distance = apple_distance()
|
||||
if (ad_d < 0)
|
||||
@points += POINTS_MOVING_CLOSER
|
||||
elsif (ad_d > 0)
|
||||
@points += POINTS_MOVING_FAR
|
||||
end
|
||||
@round+=1
|
||||
end
|
||||
|
||||
def since_last_apple; @round - @last_apple_at; end
|
||||
|
||||
def calc_new_pos(p, d)
|
||||
d = d%4
|
||||
np = p.dup
|
||||
case d
|
||||
when 0 then np[1]-=1
|
||||
when 1 then np[0]+=1
|
||||
when 2 then np[1]+=1
|
||||
when 3 then np[0]-=1
|
||||
end
|
||||
return np
|
||||
end
|
||||
|
||||
def free?(dir)
|
||||
# count the free fields from @pos in dir until a wall or something
|
||||
dir = dir % 4
|
||||
x=y=0
|
||||
case dir
|
||||
when 0 then y=-1
|
||||
when 1 then x=+1
|
||||
when 2 then y=+1
|
||||
when 3 then x=-1
|
||||
end
|
||||
i = 0
|
||||
pos = @pos.dup
|
||||
|
||||
[WIDTH, HEIGHT].max.times do
|
||||
pos[0]+=x
|
||||
pos[1]+=y
|
||||
break if pos[0]<0 || pos[0]>=WIDTH || pos[1]<0 || pos[1]>=HEIGHT || @data[pos[1]*WIDTH + pos[0]]!=0
|
||||
i+=1
|
||||
end
|
||||
return i
|
||||
end
|
||||
|
||||
def apple?(dir)
|
||||
dir = dir%4
|
||||
d_x = @apple[0] - @pos[0]
|
||||
d_y = @apple[1] - @pos[1]
|
||||
case dir
|
||||
when 0 then return @apple[1]<@pos[1] ? -d_y : 0
|
||||
when 1 then return @apple[0]>@pos[0] ? d_x : 0
|
||||
when 2 then return @apple[1]>@pos[1] ? d_y : 0
|
||||
when 3 then return @apple[0]<@pos[0] ? -d_x : 0
|
||||
#when 0 then return d_y<0 && d_x.abs<d_y.abs
|
||||
#when 1 then return d_x>0 && d_x.abs>d_y.abs
|
||||
#when 2 then return d_y>0 && d_x.abs<d_y.abs
|
||||
#when 3 then return d_x<0 && d_x.abs>d_y.abs
|
||||
end
|
||||
end
|
||||
|
||||
def die
|
||||
@dead = true
|
||||
end
|
||||
|
||||
def stopped?; since_last_apple >= WIDTH*HEIGHT*2; end
|
||||
|
||||
def ai_ranking; ai.ranking; end
|
||||
end
|
||||
|
||||
class AI
|
||||
NETWORK_LAYOUT = [6, 4, 3]
|
||||
attr_reader :weights, :id
|
||||
attr_accessor :ranking, :rounds, :count_dead, :count_stopped, :sum_length
|
||||
|
||||
def initialize(w=nil, debug=false)
|
||||
@debug = debug
|
||||
reset()
|
||||
@rounds = 1
|
||||
@id = rand(0xFFFFFF)
|
||||
if w==nil
|
||||
@weights = Array.new(network_size()) { rand() * 2.0 - 1.0 }
|
||||
puts "Initialized with random values: #{@weights}" if @debug
|
||||
else
|
||||
if w[0].is_a? Integer
|
||||
@weights = w.map{|s| s.to_s(16).rjust(8, "0").split("").each_slice(2).to_a.map(&:join).map{|s| s.to_i(16).chr}.join.unpack("g")}.flatten
|
||||
else
|
||||
@weights = w
|
||||
end
|
||||
puts "Initialized with given values: #{@weights}" if @debug
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def network_size
|
||||
s = 0
|
||||
(0...(NETWORK_LAYOUT.count-1)).each do |i|
|
||||
s += NETWORK_LAYOUT[i] * NETWORK_LAYOUT[i+1]
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
def reset
|
||||
@ranking = 0.0
|
||||
@count_dead = 0
|
||||
@count_stopped = 0
|
||||
@sum_length = 0
|
||||
end
|
||||
|
||||
def add_ranking(g)
|
||||
@ranking += g.ranking
|
||||
@count_dead += 1 if g.dead
|
||||
@count_stopped += 1 if g.stopped?
|
||||
@sum_length += g.length
|
||||
end
|
||||
|
||||
def decide(left_free, straight_free, right_free, apple_left, apple_straight, apple_right)
|
||||
inputs = [left_free, straight_free, right_free, apple_left, apple_straight, apple_right]
|
||||
puts "Inputs: #{inputs}" if @debug
|
||||
outputs = nil
|
||||
x = 0
|
||||
(1...(NETWORK_LAYOUT.count)).each do |i|
|
||||
c_in = NETWORK_LAYOUT[i-1]
|
||||
c_out = NETWORK_LAYOUT[i]
|
||||
outputs = Array.new(c_out){0.0}
|
||||
(0...c_out).each do |o|
|
||||
(0...c_in).each do |i|
|
||||
outputs[o] += inputs[i] * @weights[x]
|
||||
x+=1
|
||||
end
|
||||
end
|
||||
inputs = outputs
|
||||
end
|
||||
|
||||
max = 0
|
||||
take = 0
|
||||
(0...(NETWORK_LAYOUT.last)).each do |x|
|
||||
if outputs[x]>max
|
||||
max = outputs[x]
|
||||
take = x
|
||||
end
|
||||
end
|
||||
puts "Decision: #{take-1}" if @debug
|
||||
return take-1
|
||||
end
|
||||
|
||||
def evolve
|
||||
w = @weights.dup
|
||||
action = rand(4)
|
||||
#if action==0 #swap
|
||||
# i1 = rand(network_size())
|
||||
# i2 = rand(network_size())
|
||||
# temp = w[i1]
|
||||
# w[i1] = w[i2]
|
||||
# w[i2] = temp
|
||||
if action==0 #change single value
|
||||
i = rand(network_size())
|
||||
diff = rand() * 0.2 - 0.1
|
||||
w2 = w.dup
|
||||
w[i] += diff
|
||||
w[i] = 1.0 if w[i]>1.0
|
||||
w[i] = -1.0 if w[i]<-1.0
|
||||
w2[i] -= diff
|
||||
w2[i] = 1.0 if w2[i]>1.0
|
||||
w2[i] = -1.0 if w2[i]<-1.0
|
||||
return [AI.new(w), AI.new(w2)]
|
||||
elsif action==1 #invert single value
|
||||
i = rand(network_size())
|
||||
w[i] *= -1.0
|
||||
elsif action==2
|
||||
(0...network_size()).each do |i|
|
||||
w[i] = rand() * 2 - 1.0 if rand(5)==0
|
||||
end
|
||||
else #change multiple values
|
||||
w2 = w.dup
|
||||
(0...network_size()).each do |i|
|
||||
if (rand(5)==0)
|
||||
diff = rand() * 0.2 - 0.1
|
||||
w[i] += diff
|
||||
w[i] = 1.0 if w[i]>1.0
|
||||
w[i] = -1.0 if w[i]<-1.0
|
||||
w2[i] -= diff
|
||||
w2[i] = 1.0 if w2[i]>1.0
|
||||
w2[i] = -1.0 if w2[i]<-1.0
|
||||
end
|
||||
end
|
||||
return [AI.new(w), AI.new(w2)]
|
||||
end
|
||||
|
||||
return AI.new(w)
|
||||
end
|
||||
|
||||
def merge(ai)
|
||||
w = @weights.dup
|
||||
w2 = ai.weights.dup
|
||||
(0...network_size()).each do |i|
|
||||
if rand(2)==0
|
||||
w[i] = w2[i]
|
||||
end
|
||||
end
|
||||
return AI.new(w)
|
||||
end
|
||||
|
||||
def average(ai)
|
||||
w = @weights.dup
|
||||
w2 = ai.weights
|
||||
(0...network_size()).each do |i|
|
||||
w[i] = (w[i] + w2[i]) / 2.0
|
||||
end
|
||||
return AI.new(w)
|
||||
end
|
||||
|
||||
def dump
|
||||
puts "const uint32_t _weights[#{network_size()}] = {#{@weights.map{|x| "0x" + [x].pack('g').split("").map(&:ord).map{|i| i.to_s(16).rjust(2, '0')}.join}.join(", ")}};"
|
||||
#puts "Simplified: #{simplified}"
|
||||
end
|
||||
end
|
||||
|
||||
## Simulate
|
||||
=begin
|
||||
ai = AI.new([0xbd547c6d, 0xbedc84a5, 0x3e750239, 0x3ec5ae8a, 0xbcc9a683, 0x3f18715a, 0x3e947ed4, 0xbe4b8bf2, 0xbf2ee4ec, 0xbf3f0a75,
|
||||
0x3f5392dc, 0xbf06687b, 0xbedca2f2, 0xbcde3698, 0x3edd6a8a, 0xbd7284ca, 0x3ea7bac9, 0xbe5323c1, 0x3eccf87d,
|
||||
0xbf2d4796, 0xbf62b6e8, 0xbf71daf6, 0xbeff40aa, 0xbf207014, 0x3e26c03c, 0xbf497837, 0xbee4d175, 0x3ec601de, 0x3e4e0695, 0x3eef2619,
|
||||
0xbe849370, 0xbf18fb2b, 0x3f128e17, 0xbf3dcd78, 0x3f517299, 0x3eef3270], true)
|
||||
g = Game.new(ai, true)
|
||||
g.apple = [3, 3]
|
||||
10.times do
|
||||
g.loop
|
||||
end
|
||||
exit
|
||||
=end
|
||||
|
||||
|
||||
graph = File.open(File.dirname(__FILE__) + "/data_set.dat", "w")
|
||||
graph.puts("# Round - Points - Length - Stopped - Dead")
|
||||
|
||||
ais = []
|
||||
round = 1
|
||||
games = []
|
||||
(0...50).each do |x|
|
||||
ais[x] = AI.new#(SEEDS.sample)
|
||||
end
|
||||
|
||||
best_old_game = nil
|
||||
best_old_ai = nil
|
||||
begin
|
||||
loop do
|
||||
GAMES_PER_ROUND.times do
|
||||
(0...50).each do |x|
|
||||
games[x] = Game.new(ais[x])
|
||||
end
|
||||
|
||||
pool = Thread.pool(16)
|
||||
games.each do |g|
|
||||
|
||||
pool.process do
|
||||
15_000.times do
|
||||
g.loop
|
||||
break if g.dead || g.stopped?
|
||||
end
|
||||
g.ai.add_ranking(g)
|
||||
|
||||
end
|
||||
end
|
||||
pool.shutdown
|
||||
end
|
||||
|
||||
games_sorted = games.sort_by(&:ai_ranking).reverse.take(5)
|
||||
g = games_sorted[0]
|
||||
|
||||
if (round-1)%50==0
|
||||
puts "----------------------------------------------------"
|
||||
puts "Round | Points | Length | Stopped | Dead | ID "
|
||||
puts "----------------------------------------------------"
|
||||
end
|
||||
|
||||
puts "%5d | %7.1f | %6.0f | %6.0f%% | %3.0f%% | 0x%06x" % [round, g.ai_ranking / GAMES_PER_ROUND, g.ai.sum_length.to_f / GAMES_PER_ROUND, g.ai.count_stopped.to_f / GAMES_PER_ROUND * 100, g.ai.count_dead.to_f / GAMES_PER_ROUND * 100, g.ai.id]
|
||||
graph.puts("%d %f %f %f %f" % [round, g.ai_ranking / GAMES_PER_ROUND, g.ai.sum_length.to_f / GAMES_PER_ROUND, g.ai.count_stopped.to_f / GAMES_PER_ROUND * 100, g.ai.count_dead.to_f / GAMES_PER_ROUND * 100])
|
||||
graph.flush
|
||||
|
||||
if round%10==0
|
||||
g.ai.dump
|
||||
end
|
||||
|
||||
best_old_game = g
|
||||
best_old_ai = g.ai.dup
|
||||
|
||||
ais = []
|
||||
games_sorted.each do |g|
|
||||
g.ai.reset
|
||||
g.ai.rounds += 1
|
||||
ais << g.ai
|
||||
9.times do
|
||||
ais << g.ai.evolve
|
||||
end
|
||||
end
|
||||
|
||||
5.times do
|
||||
ais << games_sorted[0].ai.merge(games_sorted[1].ai)
|
||||
end
|
||||
5.times do
|
||||
ais << games_sorted[1].ai.merge(games_sorted[0].ai)
|
||||
end
|
||||
ais << games_sorted[0].ai.average(games_sorted[1].ai)
|
||||
10.times do
|
||||
ais << AI.new
|
||||
end
|
||||
ais = ais.flatten
|
||||
round+=1
|
||||
end
|
||||
rescue SystemExit, Interrupt
|
||||
puts
|
||||
puts
|
||||
puts "// Round %d, %5.1f points, length %3d, %2.0f%% stopped, %2.0f%% died" % [round-1, best_old_game.ai_ranking / GAMES_PER_ROUND, best_old_ai.sum_length.to_f / GAMES_PER_ROUND, best_old_ai.count_stopped.to_f / GAMES_PER_ROUND * 100, best_old_ai.count_dead.to_f / GAMES_PER_ROUND * 100]
|
||||
best_old_ai.dump
|
||||
graph.close
|
||||
end
|
Loading…
Reference in New Issue
Block a user