summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/mainwindow.cpp2
-rw-r--r--src/snake.cpp254
-rw-r--r--src/snake.h19
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()) = &empty;
+ _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) = &empty;
+ }
+ }
+ _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