diff options
-rw-r--r-- | src/mainwindow.cpp | 2 | ||||
-rw-r--r-- | src/snake.cpp | 254 | ||||
-rw-r--r-- | src/snake.h | 19 |
3 files changed, 185 insertions, 90 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a9ff11e..9c23c58 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -124,6 +124,8 @@ void MainWindow::mouseDoubleClickEvent(QMouseEvent *) { if (m_Snake == nullptr) { m_Snake = new Snake(this); + } else { + m_Snake->addSnake(); } } diff --git a/src/snake.cpp b/src/snake.cpp index 6f18670..79ced0d 100644 --- a/src/snake.cpp +++ b/src/snake.cpp @@ -6,6 +6,7 @@ #include <QRectF> #include <QTimer> #include <QDebug> +#include <QDateTime> #define SCALING (16) @@ -14,95 +15,153 @@ #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)] -#define FIELD_FREE 0 -#define FIELD_SNAKE 1 -#define FIELD_FOOD 2 + +class RealSnake +{ +public: + int _x, _y; + int _direction; + int _snakeLen; + QList<QPoint> _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), - _direction(DIR_UP) + _deaths(0), + _lastMeal(QDateTime::currentMSecsSinceEpoch()) { _width = widget->width() / SCALING; _height = widget->height() / SCALING; - _snakeLen = 5; if (_width <= 0 || _height <= 0) return; - _field = (int*)calloc(_width * _height, sizeof(*_field)); - _x = _y = -1; + initField(); qDebug() << "Field:" << _width << _height; + addFood(); QTimer *t = new QTimer(widget); t->start(100); // Game logic QTimer::connect(t, &QTimer::timeout, [this]() { - switch (_direction) { - case DIR_UP: - _y--; break; - case DIR_DOWN: - _y++; break; - case DIR_LEFT: - _x--; break; - case DIR_RIGHT: - _x++; break; - } - if (_x < 0 || _x >= _width || _y < 0 || _y >= _height || - FIELD(_x, _y) == FIELD_SNAKE) { - // Snek ded - qDebug() << "DEATH"; - memset(_field, 0, sizeof(*_field) * _width * _height); - _x = _width / 2; - _y = _height / 2; - _snake.clear(); - _widget->update(); - _snakeLen = 5; - addFood(); - return; - } - // Normal - if (_snake.size() >= _snakeLen) { - const QPoint &p = _snake.front(); - if (FIELD(p.x(), p.y()) == FIELD_SNAKE) { - FIELD(p.x(), p.y()) = FIELD_FREE; - _widget->update(p.x() * SCALING, p.y() * SCALING, SCALING, SCALING); + 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; + } } - _snake.pop_front(); - } - if (FIELD(_x, _y) == FIELD_FOOD) { - if (qrand() % _snakeLen > 40) { - _snakeLen = 10; - while (_snake.size() > _snakeLen) { - _snake.pop_front(); + 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); } - addFood(); - } else { - _snakeLen += 5; + 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 } - addFood(); - } - FIELD(_x, _y) = FIELD_SNAKE; - _snake.append(QPoint(_x, _y)); - _widget->update(_x * SCALING, _y * SCALING, SCALING, SCALING); - // AI - int what[4], dist[4]; - int best = 0; - scanDir(0, -1, what[DIR_UP], dist[DIR_UP]); - scanDir(0, 1, what[DIR_DOWN], dist[DIR_DOWN]); - scanDir(-1, 0, what[DIR_LEFT], dist[DIR_LEFT]); - scanDir(1, 0, what[DIR_RIGHT], dist[DIR_RIGHT]); - for (int i = 1; i < 4; ++i) { - if (what[i] == FIELD_FOOD) { - if (what[best] != FIELD_FOOD || dist[best] > dist[i]) { - best = i; + // 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; } - } else if (what[best] != FIELD_FOOD && dist[i] > dist[best]) { - best = i; + 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 (what[best] == FIELD_FOOD) { - _direction = best; - } else if (dist[_direction] <= 1) { - _direction = best; + // If no food was picked up within a minute, spawn more + if (QDateTime::currentMSecsSinceEpoch() - _lastMeal > 60000) { + _lastMeal = QDateTime::currentMSecsSinceEpoch(); + addFood(); } }); } @@ -112,33 +171,66 @@ Snake::~Snake() free(_field); } -void Snake::scanDir(int dx, int dy, int &what, int &dist) +void Snake::scanDir(RealSnake *snake, int dx, int dy, const Cell* &what, int &dist) { - int x = _x; - int y = _y; + 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 = FIELD_SNAKE; + what = &wall; return; } - if (FIELD(x, y) != FIELD_FREE) { + 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) != FIELD_FOOD) { - FIELD(x, y) = FIELD_FOOD; + if (!FIELD(x, y)->isWall()) { + FIELD(x, y) = &food[qrand() % (sizeof(food) / sizeof(*food))]; _widget->update(x * SCALING, y * SCALING, SCALING, SCALING); break; } @@ -156,16 +248,10 @@ void Snake::paint(QPaintEvent *event) for (int x = event->rect().x() / SCALING; x < width; ++x) { if (x < 0) continue; - switch (FIELD(x, y)) { - case FIELD_FOOD: - p.setBrush(Qt::green); - break; - case FIELD_SNAKE: - p.setBrush(Qt::white); - break; - default: + const Cell *c = FIELD(x, y); + if (c->isFree()) continue; - } + p.setBrush(c->color); p.drawRect(x * SCALING, y * SCALING, SCALING-1, SCALING-1); } } diff --git a/src/snake.h b/src/snake.h index 502c53f..9377575 100644 --- a/src/snake.h +++ b/src/snake.h @@ -7,16 +7,19 @@ class QWidget; class QPaintEvent; +class RealSnake; + +struct Cell; + class Snake { private: QWidget *_widget; - int *_field; + const Cell **_field; int _width, _height; - int _x, _y; - int _direction; - int _snakeLen; - QList<QPoint> _snake; + QList<RealSnake*> _snakes; + int _deaths; + qint64 _lastMeal; public: Snake(QWidget *widget); @@ -27,7 +30,11 @@ public: void addFood(); - void scanDir(int x, int y, int &what, int &dist); + void scanDir(RealSnake *snake, int x, int y, const Cell* &what, int &dist); + + void addSnake(); + + void initField(); }; #endif |