#include "snake.h" #include #include #include #include #include #include #include #define SCALING (16) #define DIR_UP 0 #define DIR_DOWN 1 #define DIR_LEFT 2 #define DIR_RIGHT 3 #define CELL_FREE 0 #define CELL_WALL 1 #define CELL_SNAKE 2 #define CELL_FOOD 3 struct Cell { int type; QBrush color; Cell(int t, QColor c) : type(t), color(QBrush(c)) {} bool isFood() const { return type == CELL_FOOD; } bool willKill() const { return type == CELL_WALL || type == CELL_SNAKE; } bool isFree() const { return type == CELL_FREE; } bool isWall() const { return type == CELL_WALL; } }; const Cell empty(CELL_FREE, QColor()); const Cell wall(CELL_WALL, QColor::fromRgb(80, 80, 90)); const Cell food[] = { Cell(CELL_FOOD, QColor::fromRgb(0, 255, 0)), Cell(CELL_FOOD, QColor::fromRgb(0, 200, 0)), Cell(CELL_FOOD, QColor::fromRgb(0, 160, 0)), Cell(CELL_FOOD, QColor::fromRgb(0, 120, 0)), }; #define FIELD(x,y) _field[(x) + ((y) * _width)] class RealSnake { public: int _x, _y; int _direction; int _snakeLen; QList _snake; Cell *cell; RealSnake(int sx, int sy) : _x(sx), _y(sy), _direction(DIR_UP), _snakeLen(5), _snake(), cell(new Cell(CELL_SNAKE, QColor::fromHsv(qrand() % 360, 128 + qrand() % 128, 128 + qrand() % 128))) {} virtual ~RealSnake() { delete cell; } }; Snake::Snake(QWidget *widget) : _widget(widget), _field(nullptr), _deaths(0), _lastMeal(QDateTime::currentMSecsSinceEpoch()) { _width = widget->width() / SCALING; _height = widget->height() / SCALING; if (_width <= 0 || _height <= 0) return; initField(); qDebug() << "Field:" << _width << _height; addFood(); QTimer *t = new QTimer(widget); t->start(100); // Game logic QTimer::connect(t, &QTimer::timeout, [this]() { for (RealSnake *snake : _snakes) { // AI const Cell* what[4]; int dist[4]; int best = 0; scanDir(snake, 0, -1, what[DIR_UP], dist[DIR_UP]); scanDir(snake, 0, 1, what[DIR_DOWN], dist[DIR_DOWN]); scanDir(snake, -1, 0, what[DIR_LEFT], dist[DIR_LEFT]); scanDir(snake, 1, 0, what[DIR_RIGHT], dist[DIR_RIGHT]); for (int i = 1; i < 4; ++i) { if (what[i]->isFood()) { if (!what[best]->isFood() || dist[best] > dist[i]) { best = i; } } else if (!what[best]->isFood() && (dist[i] > dist[best] || (dist[i] > 10 && qrand() % (4 * (4 - i)) == 0))) { best = i; } } if (what[best]->isFood()) { snake->_direction = best; } else if (dist[snake->_direction] <= 1 || qrand() % 20 == 0) { snake->_direction = best; } // Move switch (snake->_direction) { case DIR_UP: snake->_y--; break; case DIR_DOWN: snake->_y++; break; case DIR_LEFT: snake->_x--; break; case DIR_RIGHT: snake->_x++; break; } if (snake->_x < 0 || snake->_x >= _width || snake->_y < 0 || snake->_y >= _height || FIELD(snake->_x, snake->_y)->willKill()) { // Snek ded qDebug() << "DEATH"; _snakes.removeAll(snake); for (const QPoint &p : snake->_snake) { if (FIELD(p.x(), p.y())->isFood()) continue; FIELD(p.x(), p.y()) = &wall; _widget->update(p.x() * SCALING, p.y() * SCALING, SCALING, SCALING); } delete snake; if (_snakes.isEmpty()) { initField(); addSnake(); addFood(); } return; // Skip rest of list for this tick since we removed a snake from the list while iterating } // Remove tail from field if snake reached desired length if (snake->_snake.size() >= snake->_snakeLen) { const QPoint &p = snake->_snake.front(); if (FIELD(p.x(), p.y()) == snake->cell) { FIELD(p.x(), p.y()) = ∅ _widget->update(p.x() * SCALING, p.y() * SCALING, SCALING, SCALING); } snake->_snake.pop_front(); } // Eat if (FIELD(snake->_x, snake->_y)->isFood()) { _lastMeal = QDateTime::currentMSecsSinceEpoch(); if (qrand() % snake->_snakeLen > 40) { // Snake break! snake->_snakeLen = 10; while (snake->_snake.size() > snake->_snakeLen) { const QPoint &p = snake->_snake.front(); if (FIELD(p.x(), p.y())->isFood()) continue; FIELD(p.x(), p.y()) = &wall; _widget->update(p.x() * SCALING, p.y() * SCALING, SCALING, SCALING); snake->_snake.pop_front(); } addFood(); } else { snake->_snakeLen += 5; } addFood(); } FIELD(snake->_x, snake->_y) = snake->cell; snake->_snake.append(QPoint(snake->_x, snake->_y)); _widget->update(snake->_x * SCALING, snake->_y * SCALING, SCALING, SCALING); } // If no food was picked up within a minute, spawn more if (QDateTime::currentMSecsSinceEpoch() - _lastMeal > 60000) { _lastMeal = QDateTime::currentMSecsSinceEpoch(); addFood(); } }); } Snake::~Snake() { free(_field); } void Snake::scanDir(RealSnake *snake, int dx, int dy, const Cell* &what, int &dist) { int x = snake->_x; int y = snake->_y; dist = 0; for (;;) { x += dx; y += dy; dist++; if (x < 0 || x >= _width || y < 0 || y >= _height) { what = &wall; return; } if (!FIELD(x, y)->isFree()) { what = FIELD(x, y); return; } } } void Snake::addSnake() { for (int i = 0; i < 10; ++i) { int x = qrand() % _width; int y = qrand() % _height; if (FIELD(x, y)->isFree()) { _snakes.append(new RealSnake(x, y)); break; } } } void Snake::initField() { if (_field == nullptr) { _field = (const Cell**)calloc(_width * _height, sizeof(*_field)); } for (int i = 0; i < _width; ++i) { FIELD(i, 0) = &wall; FIELD(i, _height - 1) = &wall; } for (int i = 0; i < _height; ++i) { FIELD(0, i) = &wall; FIELD(_width - 1, i) = &wall; } for (int y = 1; y < _height - 1; ++y) { for (int x = 1; x < _width - 1; ++x) { FIELD(x, y) = ∅ } } _widget->update(); } void Snake::addFood() { for (int i = 0; i < 10; ++i) { int x = qrand() % _width; int y = qrand() % _height; if (!FIELD(x, y)->isWall()) { FIELD(x, y) = &food[qrand() % (sizeof(food) / sizeof(*food))]; _widget->update(x * SCALING, y * SCALING, SCALING, SCALING); break; } } } void Snake::paint(QPaintEvent *event) { QPainter p(_widget); const int width = qMin(_width, (event->rect().x() + event->rect().width() + SCALING - 1) / SCALING); const int height = qMin(_height, (event->rect().y() + event->rect().height() + SCALING - 1) / SCALING); for (int y = event->rect().y() / SCALING; y < height; ++y) { if (y < 0) continue; for (int x = event->rect().x() / SCALING; x < width; ++x) { if (x < 0) continue; const Cell *c = FIELD(x, y); if (c->isFree()) continue; p.setBrush(c->color); p.drawRect(x * SCALING, y * SCALING, SCALING-1, SCALING-1); } } }