diff options
Diffstat (limited to 'src/snake.cpp')
-rw-r--r-- | src/snake.cpp | 442 |
1 files changed, 335 insertions, 107 deletions
diff --git a/src/snake.cpp b/src/snake.cpp index 79ced0d..9c3c4d0 100644 --- a/src/snake.cpp +++ b/src/snake.cpp @@ -19,6 +19,13 @@ #define CELL_WALL 1 #define CELL_SNAKE 2 #define CELL_FOOD 3 +#define CELL_BRICK 4 +#define CELL_SNAKEBRICK 5 + +#define AXIS_X 0 +#define AXIS_Y 1 + +#define FIELD(x,y) _field[(x) + ((y) * _width)] struct Cell { @@ -26,13 +33,46 @@ struct Cell 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 willKill() const { return type == CELL_WALL || type == CELL_SNAKE || type == CELL_SNAKEBRICK; } bool isFree() const { return type == CELL_FREE; } - bool isWall() const { return type == CELL_WALL; } + bool isBrick() const { return type == CELL_BRICK || type == CELL_SNAKEBRICK; } + bool canSpawnFood() const { return type == CELL_SNAKE || type == CELL_FREE || type == CELL_BRICK; } + bool ballWillDestroy() const { return isFood() || isBrick(); } +}; + +struct Ball +{ + int x, y; + int dx, dy; + const Cell *cell; + QPoint gridPos; + Ball(int startX, int startY) : x(startX), y(startY), dx(qrand() % 4 + 1), dy(qrand() % 4 + 1), + cell(new Cell(CELL_WALL, QColor::fromHsv(qrand() % 360, 32 + qrand() % 64, 32 + qrand() % 64))) {} + virtual ~Ball() { delete cell; } +}; + +struct Paddle +{ + int x, y; + int axis; + int size; + const Cell *cell; + Paddle(int startX, int startY, GameCore *field, int ax) : x(startX), y(startY), axis(ax), size(5), + cell(new Cell(CELL_WALL, QColor::fromHsv(qrand() % 360, 192 + qrand() % 64, 192 + qrand() % 64))) { + for (int i = 0; i < size; ++i) { + if (axis == AXIS_X) { + field->setField(x + i, y, cell); + } else { + field->setField(x, y + i, cell); + } + } + } }; const Cell empty(CELL_FREE, QColor()); const Cell wall(CELL_WALL, QColor::fromRgb(80, 80, 90)); +const Cell snakebrick(CELL_SNAKEBRICK, QColor::fromRgb(200, 200, 200)); +const Cell brick(CELL_BRICK, QColor::fromRgb(255, 255, 255)); const Cell food[] = { Cell(CELL_FOOD, QColor::fromRgb(0, 255, 0)), Cell(CELL_FOOD, QColor::fromRgb(0, 200, 0)), @@ -40,141 +80,324 @@ const Cell food[] = { Cell(CELL_FOOD, QColor::fromRgb(0, 120, 0)), }; -#define FIELD(x,y) _field[(x) + ((y) * _width)] - -class RealSnake +class Snake { public: - int _x, _y; - int _direction; - int _snakeLen; - QList<QPoint> _snake; + int x, y; + int direction; + int len; + QList<QPoint> parts; Cell *cell; - RealSnake(int sx, int sy) : _x(sx), _y(sy), _direction(DIR_UP), _snakeLen(5), _snake(), + Snake(int sx, int sy) : x(sx), y(sy), direction(DIR_UP), len(5), parts(), cell(new Cell(CELL_SNAKE, QColor::fromHsv(qrand() % 360, 128 + qrand() % 128, 128 + qrand() % 128))) {} - virtual ~RealSnake() { delete cell; } + virtual ~Snake() { delete cell; } }; -Snake::Snake(QWidget *widget) +void GameCore::setField(int x, int y, const Cell *val) { + _field[x + _width * y] = val; + _widget->update(x * SCALING, y * SCALING, SCALING, SCALING); +} + +GameCore::GameCore(QWidget *widget) : _widget(widget), - _field(nullptr), _deaths(0), - _lastMeal(QDateTime::currentMSecsSinceEpoch()) + _lastMeal(QDateTime::currentMSecsSinceEpoch()), + _field(nullptr) { + qsrand((uint)_lastMeal); _width = widget->width() / SCALING; _height = widget->height() / SCALING; if (_width <= 0 || _height <= 0) return; initField(); + // Should be either 4 player pong or two player + tetris + // Bricks in the center also only make sense without tetris + _paddles.append(new Paddle(1, _height - 2, this, AXIS_X)); + _paddles.append(new Paddle(1, 1, this, AXIS_X)); + _paddles.append(new Paddle(_width - 2, 1, this, AXIS_Y)); + _paddles.append(new Paddle(1, 1, this, AXIS_Y)); + for (int y = 15; y < _height - 16; y += 3) { + for (int x = 15; x < _width - 16; x += 3) { + FIELD(x, y) = &brick; + FIELD(x + 1, y) = &brick; + FIELD(x, y + 1) = &brick; + FIELD(x + 1, y + 1) = &brick; + } + } qDebug() << "Field:" << _width << _height; addFood(); QTimer *t = new QTimer(widget); - t->start(100); - // Game logic + t->start(15); + // GAME 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]) { + // + static int tick = 0; + ++tick; + // SNAKE + if (tick % 6 == 0) { + for (Snake *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; } - } 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(); + if (what[best]->isFood()) { + snake->direction = best; + } else if (dist[snake->direction] <= 1 || qrand() % 20 == 0) { + snake->direction = best; } - 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); + // 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; } - 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 (snake->x < 0 || snake->x >= _width || snake->y < 0 || snake->y >= _height || + FIELD(snake->x, snake->y)->willKill()) { + // Snek ded + _snakes.removeAll(snake); + for (const QPoint &p : snake->parts) { 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(); + setField(p.x(), p.y(), &snakebrick); + } + delete snake; + if (_snakes.isEmpty()) { + addSnake(); + } + break; // 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->parts.size() >= snake->len) { + const QPoint &p = snake->parts.front(); + if (FIELD(p.x(), p.y()) == snake->cell) { + setField(p.x(), p.y(), &empty); + } + snake->parts.pop_front(); + } + // Eat + if (FIELD(snake->x, snake->y)->isFood()) { + _lastMeal = QDateTime::currentMSecsSinceEpoch(); + if (qrand() % snake->len > 40) { + // Snake break! + snake->len = 10; + while (snake->parts.size() > snake->len) { + const QPoint &p = snake->parts.front(); + if (FIELD(p.x(), p.y())->isFood()) + continue; + setField(p.x(), p.y(), &snakebrick); + snake->parts.pop_front(); + } + addFood(); + } else { + snake->len += 5; } addFood(); - } else { - snake->_snakeLen += 5; } + setField(snake->x, snake->y, snake->cell); + snake->parts.append(QPoint(snake->x, snake->y)); + } + // If no food was picked up within a minute, spawn more + if (QDateTime::currentMSecsSinceEpoch() - _lastMeal > 60000) { + _lastMeal = QDateTime::currentMSecsSinceEpoch(); 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(); + // + // Balls + Ball *bbottom = nullptr, *btop = nullptr, *bleft = nullptr, *bright = nullptr; + for ( Ball* ball : _balls) { + const QPoint old(ball->x / SCALING, ball->y / SCALING); + int nx = ball->x + ball->dx; + int ny = ball->y + ball->dy; + // Safety + if (nx < 0) { + nx = 0; + ball->dx = qAbs(ball->dx); + } else if (nx >= _width * SCALING) { + nx = _width * SCALING - 1; + ball->dx = -qAbs(ball->dx); + } + if (ny < 0) { + ny = 0; + ball->dy = qAbs(ball->dy); + } else if (ny >= _height * SCALING) { + ny = _height * SCALING - 1; + ball->dy = -qAbs(ball->dy); + } + // Collision + const QPoint noo(nx / SCALING, ny / SCALING); + if (old != noo && !FIELD(noo.x(), noo.y())->isFree()) { + bool one = false; + if (FIELD(old.x(), noo.y()) == ball->cell || FIELD(old.x(), noo.y())->isFree()) { + one = true; + if (old.x() > noo.x()) { + ball->dx = qAbs(ball->dx); + } else if (old.x() < noo.x()) { + ball->dx = -qAbs(ball->dx); + } + } + if (FIELD(noo.x(), old.y()) == ball->cell || FIELD(noo.x(), old.y())->isFree()) { + one = true; + if (old.y() > noo.y()) { + ball->dy = qAbs(ball->dy); + } else if (old.y() < noo.y()) { + ball->dy = -qAbs(ball->dy); + } + } + if (!one) { + ball->dx = -ball->dx; + ball->dy = -ball->dy; + } + // Destroy cell? + if (FIELD(noo.x(), noo.y())->ballWillDestroy()) { + if (FIELD(noo.x(), noo.y())->isFood()) { + // TODO: Make paddle that last touched the ball bigger + addFood(); + } + if (FIELD(noo.x(), noo.y())->isBrick()) { + for (int y = -1; y <= 1; ++y) { + for (int x = -1; x <= 1; ++x) { + if (FIELD(noo.x() + x, noo.y() + y)->isBrick()) { + setField(noo.x() + x, noo.y() + y, &empty); + } + } + } + } else { + setField(noo.x(), noo.y(), &empty); + } + } + } else { + // Free movevemnt + ball->gridPos = noo; + ball->x = nx; + ball->y = ny; + if (old != noo) { + if (FIELD(old.x(), old.y()) == ball->cell) { + setField(old.x(), old.y(), &empty); + } + setField(noo.x(), noo.y(), ball->cell); + } + } + if (bbottom == nullptr || (ball->dy > 0 && bbottom->y < ball->y)) { + bbottom = ball; + } + if (btop == nullptr || (ball->dy < 0 && btop->y > ball->y)) { + btop = ball; + } + if (bright == nullptr || (ball->dx > 0 && bright->x < ball->x)) { + bright = ball; + } + if (bleft == nullptr || (ball->dx < 0 && bleft->x > ball->x)) { + bleft = ball; + } + } + // Paddles + if (bbottom != nullptr) { + for (Paddle *paddle : _paddles) { + if (paddle->axis == AXIS_X) { + bool left = false, right = false; + Ball *check; + // Paddle moves along the X axis + if (paddle->y < 10) { + // Paddle is at top + check = btop; + } else { + // Paddle at bottom + check = bbottom; + } + if (paddle->x > 0 && check->gridPos.x() < paddle->x + paddle->size / 2) { + // Move left + left = true; + } else if (paddle->x + paddle->size < _width && check->gridPos.x() > paddle->x + paddle->size / 2) { + // Move right + right = true; + } + if (left) { + if (FIELD(paddle->x - 1, paddle->y)->isFree()) { + paddle->x--; + setField(paddle->x, paddle->y, paddle->cell); + if (FIELD(paddle->x + paddle->size, paddle->y) == paddle->cell) { + setField(paddle->x + paddle->size, paddle->y, &empty); + } + } + } else if (right) { + if (FIELD(paddle->x + paddle->size, paddle->y)->isFree()) { + setField(paddle->x + paddle->size, paddle->y, paddle->cell); + if (FIELD(paddle->x, paddle->y) == paddle->cell) { + setField(paddle->x, paddle->y, &empty); + } + paddle->x++; + } + } + } else { + // Paddle moves along the Y axis + bool top = false, bottom = false; + Ball *check; + // Paddle moves along the y ayis + if (paddle->x < 10) { + // Paddle is at top + check = bleft; + } else { + // Paddle at bottom + check = bright; + } + if (paddle->y > 0 && check->gridPos.y() < paddle->y + paddle->size / 2) { + // Move top + top = true; + } else if (paddle->y + paddle->size < _height && check->gridPos.y() > paddle->y + paddle->size / 2) { + // Move bottom + bottom = true; + } + if (top) { + if (FIELD(paddle->x, paddle->y - 1)->isFree()) { + paddle->y--; + setField(paddle->x, paddle->y, paddle->cell); + if (FIELD(paddle->x, paddle->y + paddle->size) == paddle->cell) { + setField(paddle->x, paddle->y + paddle->size, &empty); + } + } + } else if (bottom) { + if (FIELD(paddle->x, paddle->y + paddle->size)->isFree()) { + setField(paddle->x, paddle->y + paddle->size, paddle->cell); + if (FIELD(paddle->x, paddle->y) == paddle->cell) { + setField(paddle->x, paddle->y, &empty); + } + paddle->y++; + } + } + } // Done with movement logic + } // End loop over paddles } }); } -Snake::~Snake() +GameCore::~GameCore() { free(_field); } -void Snake::scanDir(RealSnake *snake, int dx, int dy, const Cell* &what, int &dist) +void GameCore::scanDir(Snake *snake, int dx, int dy, const Cell* &what, int &dist) { - int x = snake->_x; - int y = snake->_y; + int x = snake->x; + int y = snake->y; dist = 0; for (;;) { x += dx; @@ -184,26 +407,32 @@ void Snake::scanDir(RealSnake *snake, int dx, int dy, const Cell* &what, int &di what = &wall; return; } - if (!FIELD(x, y)->isFree()) { + if (FIELD(x, y)->willKill() || FIELD(x, y)->isFood()) { what = FIELD(x, y); return; } } } -void Snake::addSnake() +void GameCore::addSnake() { - for (int i = 0; i < 10; ++i) { + for (int i = 0; i < 100; ++i) { int x = qrand() % _width; int y = qrand() % _height; if (FIELD(x, y)->isFree()) { - _snakes.append(new RealSnake(x, y)); + if (qrand() % 2 == 0) { + qDebug() << "Adding Snake at" << x << y; + _snakes.append(new Snake(x, y)); + } else { + qDebug() << "Adding Ball at" << x << y; + _balls.append(new Ball(x * SCALING, y * SCALING)); + } break; } } } -void Snake::initField() +void GameCore::initField() { if (_field == nullptr) { _field = (const Cell**)calloc(_width * _height, sizeof(*_field)); @@ -224,20 +453,19 @@ void Snake::initField() _widget->update(); } -void Snake::addFood() +void GameCore::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); + if (FIELD(x, y)->canSpawnFood()) { + setField(x, y, &food[qrand() % (sizeof(food) / sizeof(*food))]); break; } } } -void Snake::paint(QPaintEvent *event) +void GameCore::paint(QPaintEvent *event) { QPainter p(_widget); const int width = qMin(_width, (event->rect().x() + event->rect().width() + SCALING - 1) / SCALING); |