Реализации игровой консоли 2048


Поэтому меня попросили сделать быстрый 2048 игры на C++ , но с использованием только такие вещи, как массивы, структуры, функции, указатели , не используя векторы, лямбды и так далее. Ограничения здесь, потому что это просто упражнение на исследование (или домашнее задание, я должен сказать).

Я могу закомментировать следующий код, если это необходимо. Вот что я придумал до сих пор:

#include <iostream>
#include <ctime>
#include <iomanip>
#include <windows.h>
#include <conio.h>

using namespace std;

enum colors 
{
    BLACK, BLUE, GREEN, CYAN, RED, PURPLE, YELLOW, GREY,
    LIGHTGREY, LIGHTBLUE, LIGHTGREEN, LIGHTCYAN, LIGHTRED,
    LIGHTPURPLE, LIGHTYELLOW, WHITE
};

void setConsoleColor(int textColor, int bgColor) 
{
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (textColor + (bgColor * 16)));
}

int randN(int start, int end)
{
    return rand() % (end - start) + start;
}

int randN(int n1, int n2, int percent)
{
    if ((rand() % 100) < percent)
        return n1;
    else
        return n2;
}

enum Direction
{
    Left = 75,
    Right = 77,
    Up = 72,
    Down = 80
};

struct Cell
{
    int x;
    int y;
    int value;

    Cell() 
    { 
        x = -1;
        y = -1;
        value = -1;
    }

    Cell(int x, int y, int value)
    {
        this->x = x;
        this->y = y;
        this->value = value;
    }

    void setValue(int value)
    {
        this->value = value;
    }

    void display()
    {
        cout << setw(4) << value;
    }

    bool isEmpty()
    {
        return value == 0;
    }

    bool isEqualTo(Cell cell)
    {
        return value == cell.value;
    }
};

struct Game
{
    Cell *cells;
    Cell *prevCells;
    int size;
    int score = 0;
    int highscore;
    int moves = 0;

    bool isMoved()
    {
        for (int i = 0; i < size * size; i++)
        {
            if (!cells[i].isEqualTo(prevCells[i]))
            {
                moves += 1;
                return true;
            }
        }
        return false;
    }

    void Doubled(int points)
    {
        score += points;
    }

    bool isCellsFull()
    {
        for (int i = 0; i < size * size; i++)
        {
            if (cells[i].isEmpty())
                return false;
        }
        return true;
    }

    Cell &findCell(int x, int y)
    {
        for (int i = 0; i < size * size; i++)
        {
            if (cells[i].x == x && cells[i].y == y)
                return cells[i];
        }
        return Cell();
    }

    Cell &randEmptyCell()
    {
        if (isCellsFull())
            return Cell();
        int i;
        do
        {
            i = randN(0, size * size);
        } while (!cells[i].isEmpty());
        return cells[i];
    }

    void create(int size)
    {
        this->size = size;
        cells = new Cell[size * size];
        prevCells = new Cell[size * size];

        for (int x = 0, c = 0; x < size; x++)
        {
            for (int y = 0; y < size; y++, c++)
            {
                cells[c] = Cell(x, y, 0);
            }
        }
    }

    void display()
    {
        for (int i = 0, c = 0; i < size + 1; i++)
        {
            for (int j = 0; j < size; j++)
                cout << " ----";
            cout << endl;
            if (i == size)
                break;
            for (int j = 0; j < size + 1; j++, c++)
            {
                cout << "|";
                if (j == size)
                    break;
                if (cells[c].isEmpty())
                    cout << setw(4) << " ";
                else
                {
                    setConsoleColor(LIGHTGREEN, BLUE);
                    cells[c].display();
                    setConsoleColor(GREY, BLACK);
                }
            }
            cout << endl;
        }
        cout << "Score: " << score << "  Highscore: " << highscore << endl;
        cout << "Moves: " << moves << endl;
    }

    void handleKey()
    {
        copy(cells, cells + size * size, prevCells);
        _getch();
        int ch = _getch();
        switch (ch)
        {
        case Left:
            moveCells(Left);
            break;
        case Right:
            moveCells(Right);
            break;
        case Up:
            moveCells(Up);
            break;
        case Down:
            moveCells(Down);
            break;
        }
    }

    void moveCells(Direction dir)
    {
        int start = dir == Left || dir == Up ? 0 : size - 1;
        int end = start == 0 ? size : -1;

        for (int x = 0; x < size; x++)
        {
            for (int y = start; y != end; (start < end ? y++ : y--))
            {
                Cell &c1 = dir == Left || dir == Right ? findCell(x, y) : findCell(y, x);
                if (c1.isEmpty())
                    continue;
                for (int k = y + (start == 0 ? -1 : 1); k != (end == -1 ? size : -1); (start < end ? k-- : k++))
                {
                    Cell &c2 = dir == Left || dir == Right ? findCell(x, k) : findCell(k, x);
                    Cell &c3 = dir == Left ?  findCell(x, k + 1) :
                               dir == Right ? findCell(x, k - 1) :
                               dir == Up ?    findCell(k + 1, x) :
                                              findCell(k - 1, x);
                    if (c1.isEqualTo(c2))
                    {
                        c2.setValue(c2.value * 2);
                        c1.setValue(0);
                        Doubled(c2.value);
                    }
                    else if (!c2.isEmpty() && c3.isEmpty())
                    {
                        c3.setValue(c1.value);
                        c1.setValue(0);
                    }
                    else if (k == start && c2.isEmpty())
                    {
                        c2.setValue(c1.value);
                        c1.setValue(0);
                    }
                }
            }
        }
    }

    void spawnCells(int amount)
    {
        for (int i = 0; i < amount; i++)
        {
            randEmptyCell().setValue(randN(2, 4, 85));
        }
    }

    bool isOver()
    {
        if (!isCellsFull())
            return false;
        for (int i = 0; i < size; i++)
        {
            for (int j = 0; j < size; j++)
            {
                Cell &center = findCell(i, j);
                Cell &right = findCell(i, j + 1);
                Cell &bottom = findCell(i + 1, j);

                if (center.isEqualTo(right) || center.isEqualTo(bottom))
                    return false;
            }
            return true;
        }
    }

    bool isWin()
    {
        for (int i = 0; i < size * size; i++)
        {
            if (cells[i].value == 2048)
                return true;
        }
        return false;
    }
};

void main()
{
    setlocale(LC_ALL, "rus");

    srand(time(0));

    bool playAgain = true;
    int highscore = 0;

    do
    {
        Game game;
        game.create(4);
        game.spawnCells(2);
        game.highscore = highscore;
        game.display();

        do
        {
            game.handleKey();
            if (game.isMoved())
                game.spawnCells(1);
            if (game.score > highscore)
                game.highscore = game.score;
            system("cls");
            game.display();
        } while (!game.isWin() && !game.isOver());

        if (game.isOver())
        {
            setConsoleColor(LIGHTRED, BLACK);
            cout << "You won!" << endl;
            setConsoleColor(GREY, BLACK);
        }
        else if (game.isWin())
        {
            setConsoleColor(LIGHTGREEN, BLACK);
            cout << "You lost!" << endl;
            setConsoleColor(GREY, BLACK);
        }

        cout << "Play again? y/n\n";
        char ch;
        do
        {
            cin >> ch;
            if (ch != 'y' && ch != 'n')
                cout << "Incorrect!\n";
        } while (ch != 'y' && ch != 'n');

        switch (ch)
        {
        case 'y':
            highscore = game.score;
            system("cls");
            break;
        case 'n':
            playAgain = false;
            break;
        }
    } while (playAgain);
}

Как вы думаете нужно менять/упрощенный? Есть ли способ, чтобы увеличить читаемость? Также я был бы признателен некоторые общие советы для написания консольных игр, как это.



1311
6
задан 15 февраля 2018 в 09:02 Источник Поделиться
Комментарии
1 ответ

Сломанной Код

Этот код:

Cell &findCell(int x, int y)
{
for (int i = 0; i < size * size; i++)
{
if (cells[i].x == x && cells[i].y == y)
return cells[i];
}
return Cell();
}

...имеет серьезные проблемы ... return Cell(); пытается возвращать ссылку на временный объект, что не будет больше действительным на момент вызывающий код получает его. Думаю, вы должны использовать старый компилятор--текущие версии компиляторами VC++ и MinGW оба отвергают эту ошибку.

К сожалению, исправить это не может быть тривиальным, а вы в значительной степени нужно переделать код в какой-то степени. Доступные опции включают в себя:


  1. Создавая "нет" объекта (например, со статическим жизни, наверное, как глобальный), и возвращает ссылку на это, когда искомый объект не может быть найден.

  2. бросать исключение, если объект не может быть найден.

  3. возвращает указатель вместо ссылки, что позволяет возвращать нулевой указатель, если не нашли.

  4. Возможно вернуть Optional<T> (но вы не можете сделать дополнительный ссылок, так что, если вы действительно хотите идти по этому пути, вы, вероятно, нужно что-то вроде optional<reference_wrapper<Cell>>).

По крайней мере, одного другого примера, randEmptyCell та же проблема.

Два Шага Инициализации

Ваш Game класс имеет create функция-член, которая выглядит как дизайн запах для меня. Для создания Game вы должны просто...создать Game. Создание Game объект, затем вызвать create на этот объект действительно создать реальную игру двухэтапной инициализации, который вы обычно хотите, чтобы избежать. Вообще говоря, если нужно что-то делать для создания объекта, что должно быть сделано в конструкторе.

Ненужный Переключатель

Мне кажется, этот оператор switch:

    switch (ch)
{
case Left:
moveCells(Left);
break;
case Right:
moveCells(Right);
break;
case Up:
moveCells(Up);
break;
case Down:
moveCells(Down);
break;
}
}

...неоправданно долго и бессмысленно. По большей части, это просто эквивалент: moveCells(ch);хотя, вам может потребоваться, чтобы гарантировать, что ch является одним из Up, Down, Left или Right во-первых. Одна из возможностей (что еще использует switch заявление) должна быть:

switch (ch) {
case Up:
case Down:
case Left:
case Right:
moveCells(ch);
}

Это выполняет свою работу довольно легко.

srand/RAND в должны идти

Хотя, используя их немного больше работы, я бы предпочел использовать генераторы случайных чисел в random вместо srand/rand. Они, как правило, значительно более высокого качества, и они уже дают код для обработки создания случайных чисел в диапазоне. Я провел некоторое время, работая над созданием обертку, чтобы сделать его немного легче использовать, и придумал следующее:

#pragma once
#include <array>
#include <random>
#include <algorithm>

class generator {
template <class Rand>
class Seed {
class seeder {
std::array < std::random_device::result_type, Rand::state_size > rand_data;
public:
seeder() {
std::random_device rd;
std::generate(rand_data.begin(), rand_data.end(), std::ref(rd));
}

typename std::array < std::random_device::result_type, Rand::state_size >::iterator begin() { return rand_data.begin(); }
typename std::array < std::random_device::result_type, Rand::state_size >::iterator end() { return rand_data.end(); }
} seed;

std::seed_seq s;

public:
Seed() : s(seed.begin(), seed.end()) { }

template <class I>
auto generate(I a, I b) { return s.generate(std::forward<I>(a), std::forward<I>(b)); }
};

using Rand = std::mt19937_64;
Seed<Rand> seed;
Rand rng;
std::uniform_int_distribution<int> uni;
public:
generator(int high) : rng(seed), uni(0, high) {}
generator(int low, int high) : rng(seed), uni(low, high) { }
int operator()() { return uni(rng); }
};

Я понимаю, что это куча кода, который вы, вероятно, не хотят иметь дело с прямо сейчас. Чтобы держать его простым, чтобы использовать, это написано в заголовке, так что вы просто сделать что-то вроде:

#include "rand.h"

int main() {
generator g(100); // create a generator for numbers from 0 to 100

// generate and print out some numbers:
for (int i = 0; i < 100; i++) {
std::cout << g() << "\t";
if (i % 10 == 0)
std::cout << "\n";
}
}

Поэтому, хотя это больше кода, чем хотелось бы, используя это довольно легко (даже легче, чем srand()/rand()), и вы получите намного лучшие генерации случайных чисел. О, и еще одна деталь: как она стоит сейчас, это зависит от того, на C++ 17 характеристика, что вывести параметр шаблона от значения, переданного в конструктор. Для компиляторов, которые не реализовать, что еще изменить generator g(100); для generator<int> g(100);.

5
ответ дан 16 февраля 2018 в 05:02 Источник Поделиться