2010-04-21 2 views
2

EDIT # 2 - Я сделал предыдущую государственную часть. Теперь мне нужен алгоритм состояния, так что сетка действует как тороидальный массив. То есть верхние/нижние и правые/левые края обертываются вокруг.C++ - Game of Life Life & Stepping Backback

Не знаю, как это сделать. Как мне изменить вложенный-if's в thisTimer(), чтобы сделать это?


EDIT - это serialising действительно необходимо? Я вижу, как это может сработать, но мне просто нужно простое, хотя и грубое решение для отступления назад.


Я был в состоянии создать версию игры Конвея жизни, либо ступенчатую вперед каждый клик, или просто выбежала вперед с помощью таймера. (Я делаю это с помощью Qt.)

Теперь мне нужно сохранить все предыдущие игровые сетки, чтобы я мог шагнуть назад, нажав кнопку. Я пытаюсь использовать стек, и кажется, что я нажимаю старые ячейки сетки на стек правильно. Но когда я запускаю его в QT, сетки не меняются, когда я нажимаю BACK.

Я пробовал разные вещи в течение последних трех часов, но безрезультатно. Есть идеи?

gridwindow.cpp - Моя проблема должна быть где-то здесь. Вероятно, функция handleBack().

#include <iostream> 
#include "gridwindow.h" 

using namespace std; 

// Constructor for window. It constructs the three portions of the GUI and lays them out vertically. 
GridWindow::GridWindow(QWidget *parent,int rows,int cols) 
: QWidget(parent) 
{ 
    QHBoxLayout *header = setupHeader(); // Setup the title at the top. 
    QGridLayout *grid = setupGrid(rows,cols); // Setup the grid of colored cells in the middle. 
    QHBoxLayout *buttonRow = setupButtonRow(); // Setup the row of buttons across the bottom. 
    QVBoxLayout *layout = new QVBoxLayout(); // Puts everything together. 
    layout->addLayout(header); 
    layout->addLayout(grid); 
    layout->addLayout(buttonRow); 
setLayout(layout); 
} 

// Destructor. 
GridWindow::~GridWindow() 
{ 
delete title; 
} 

// Builds header section of the GUI. 
QHBoxLayout* GridWindow::setupHeader() 
{ 
QHBoxLayout *header = new QHBoxLayout(); // Creates horizontal box. 
header->setAlignment(Qt::AlignHCenter); 

this->title = new QLabel("CONWAY'S GAME OF LIFE",this); // Creates big, bold, centered label (title): "Conway's Game of Life." 
    this->title->setAlignment(Qt::AlignHCenter); 
    this->title->setFont(QFont("Arial", 32, QFont::Bold)); 

    header->addWidget(this->title); // Adds widget to layout. 

    return header;  // Returns header to grid window. 
} 

// Builds the grid of cells. This method populates the grid's 2D array of GridCells with MxN cells. 
QGridLayout* GridWindow::setupGrid(int rows,int cols) 
{ 
isRunning = false; 
QGridLayout *grid = new QGridLayout(); // Creates grid layout. 

    grid->setHorizontalSpacing(0); // No empty spaces. Cells should be contiguous. 
    grid->setVerticalSpacing(0); 
    grid->setSpacing(0); 
    grid->setAlignment(Qt::AlignHCenter); 

    for(int i=0; i < rows; i++)  //Each row is a vector of grid cells. 
    { 
    std::vector<GridCell*> row;  // Creates new vector for current row. 
    cells.push_back(row); 
    for(int j=0; j < cols; j++) 
    { 
    GridCell *cell = new GridCell(); // Creates and adds new cell to row. 
    cells.at(i).push_back(cell); 

    grid->addWidget(cell,i,j); // Adds to cell to grid layout. Column expands vertically. 
    grid->setColumnStretch(j,1); 
    } 
    grid->setRowStretch(i,1);  // Sets row expansion horizontally. 
    } 
    return grid;   // Returns grid. 
} 

// Builds footer section of the GUI. 
QHBoxLayout* GridWindow::setupButtonRow() 
{ 
QHBoxLayout *buttonRow = new QHBoxLayout(); // Creates horizontal box for buttons. 
buttonRow->setAlignment(Qt::AlignHCenter); 

// Clear Button - Clears cell; sets them all to DEAD/white. 
    QPushButton *clearButton = new QPushButton("CLEAR"); 
    clearButton->setFixedSize(100,25); 
    connect(clearButton, SIGNAL(clicked()), this, SLOT(handlePause())); // Pauses timer before clearing. 
connect(clearButton, SIGNAL(clicked()), this, SLOT(handleClear())); // Connects to clear function to make all cells DEAD/white. 
    buttonRow->addWidget(clearButton); 

// Forward Button - Steps one step forward. 
    QPushButton *forwardButton = new QPushButton("FORWARD"); 
    forwardButton->setFixedSize(100,25); 
    connect(forwardButton, SIGNAL(clicked()), this, SLOT(handleForward())); // Signals to handleForward function.. 
    buttonRow->addWidget(forwardButton); 

// Back Button - Steps one step backward. 
    QPushButton *backButton = new QPushButton("BACK"); 
    backButton->setFixedSize(100,25); 
    connect(backButton, SIGNAL(clicked()), this, SLOT(handleBack())); // Signals to handleBack funciton. 
    buttonRow->addWidget(backButton); 

// Start Button - Starts game when user clicks. Or, resumes game after being paused. 
QPushButton *startButton = new QPushButton("START/RESUME"); 
    startButton->setFixedSize(100,25); 
connect(startButton, SIGNAL(clicked()), this, SLOT(handlePause())); // Deletes current timer if there is one. Then restarts everything. 
    connect(startButton, SIGNAL(clicked()), this, SLOT(handleStart())); // Signals to handleStart function. 
    buttonRow->addWidget(startButton); 

// Pause Button - Pauses simulation of game. 
    QPushButton *pauseButton = new QPushButton("PAUSE"); 
    pauseButton->setFixedSize(100,25);  
    connect(pauseButton, SIGNAL(clicked()), this, SLOT(handlePause())); // Signals to pause function which pauses timer. 
    buttonRow->addWidget(pauseButton); 

    // Quit Button - Exits program. 
    QPushButton *quitButton = new QPushButton("EXIT"); 
quitButton->setFixedSize(100,25); 
connect(quitButton, SIGNAL(clicked()), qApp, SLOT(quit()));  // Signals the quit slot which ends the program. 
    buttonRow->addWidget(quitButton); 

    return buttonRow; // Returns bottom of layout. 
} 

/* 
SLOT method for handling clicks on the "clear" button. 
Receives "clicked" signals on the "Clear" button and sets all cells to DEAD. 
*/ 
void GridWindow::handleClear() 
{ 

    for(unsigned int row=0; row < cells.size(); row++) // Loops through current rows' cells. 
{ 
for(unsigned int col=0; col < cells[row].size(); col++) // Loops through the rows'columns' cells. 
    { 
    GridCell *cell = cells[row][col];  // Grab the current cell & set its value to dead. 
    cell->setType(DEAD);   
    } 
} 
} 

/* 
SLOT method for handling clicks on the "start" button. 
Receives "clicked" signals on the "start" button and begins game simulation. 
*/ 
void GridWindow::handleStart() 
{ 
isRunning = true;    // It is running. Sets isRunning to true. 
this->timer = new QTimer(this);   // Creates new timer. 
connect(this->timer, SIGNAL(timeout()), this, SLOT(timerFired())); // Connect "timerFired" method class to the "timeout" signal fired by the timer. 
this->timer->start(500);   // Timer to fire every 500 milliseconds. 
} 

/* 
SLOT method for handling clicks on the "pause" button. 
Receives "clicked" signals on the "pause" button and stops the game simulation. 
*/ 
void GridWindow::handlePause() 
{ 
if(isRunning)  // If it is running... 
    this->timer->stop(); // Stops the timer. 
isRunning = false; // Set to false. 

} 

void GridWindow::handleForward() 
{ 
if(isRunning);  // If it's running, do nothing. 
else 
    timerFired(); // It not running, step forward one step. 
} 

void GridWindow::handleBack() 
{ 
std::vector<std::vector<GridCell*> > cells2; 
if(isRunning);  // If it's running, do nothing. 
else if(backStack.empty()) 
    cout << "EMPTYYY" << endl; 
else 
{ 
cells2 = backStack.peek(); 
    for (unsigned int f = 0; f < cells.size(); f++)  // Loop through cells' rows. 
    { 
    for (unsigned int g = 0; g < cells.at(f).size(); g++) // Loop through cells columns. 
    { 
    cells[f][g]->setType(cells2[f][g]->getType()); // Set cells[f][g]'s type to cells2[f][g]'s type. 
    } 
    } 
cout << "PRE=POP" << endl; 
backStack.pop(); 
cout << "OYYYY" << endl; 
} 
} 

// Accessor method - Gets the 2D vector of grid cells. 
std::vector<std::vector<GridCell*> >& GridWindow::getCells() 
{ 
return this->cells; 
} 

/* 
TimerFired function: 
    1) 2D-Vector cells2 is declared. 
    2) cells2 is initliazed with loops/push_backs so that all its cells are DEAD. 
    3) We loop through cells, and count the number of LIVE neighbors next to a given cell. 
    --> Depending on how many cells are living, we choose if the cell should be LIVE or DEAD in the next simulation, according to the rules. 
    -----> We save the cell type in cell2 at the same indice (the same row and column cell in cells2). 
    4) After check all the cells (and save the next round values in cells 2), we set cells's gridcells equal to cells2 gridcells. 
    --> This causes the cells to be redrawn with cells2 types (white or black). 
*/ 
void GridWindow::timerFired() 
{ 
backStack.push(cells); 
std::vector<std::vector<GridCell*> > cells2; // Holds new values for 2D vector. These are the next simulation round of cell types. 
for(unsigned int i = 0; i < cells.size(); i++) // Loop through the rows of cells2. (Same size as cells' rows.) 
{ 
    vector<GridCell*> row;   // Creates Gridcell* vector to push_back into cells2. 
    cells2.push_back(row);   // Pushes back row vectors into cells2. 
    for(unsigned int j = 0; j < cells[i].size(); j++) // Loop through the columns (the cells in each row). 
    { 
    GridCell *cell = new GridCell();  // Creates new GridCell. 
    cell->setType(DEAD);  // Sets cell type to DEAD/white. 
    cells2.at(i).push_back(cell);  // Pushes back the DEAD cell into cells2. 
    }    // This makes a gridwindow the same size as cells with all DEAD cells. 
    } 

    for (unsigned int m = 0; m < cells.size(); m++) // Loop through cells' rows. 
    { 
     for (unsigned int n = 0; n < cells.at(m).size(); n++) // Loop through cells' columns. 
     { 
      unsigned int neighbors = 0;  // Counter for number of LIVE neighbors for a given cell. 

    // We know check all different variations of cells[i][j] to count the number of living neighbors for each cell. 
    // We check m > 0 and/or n > 0 to make sure we don't access negative indexes (ex: cells[-1][0].) 
    // We check m < size to make sure we don't try to access rows out of the vector (ex: row 5, if only 4 rows). 
    // We check n < row size to make sure we don't access column item out of the vector (ex: 10th item in a column of only 9 items). 
    // If we find that the Type = 1 (it is LIVE), then we add 1 to the neighbor. 
    // Else - we add nothing to the neighbor counter. 
    // Neighbor is the number of LIVE cells next to the current cell. 
    if(m > 0 && n > 0) 
    { 
    if (cells[m-1][n-1]->getType() == 1) 
    neighbors += 1; 
    } 
    if(m > 0) 
    { 
    if (cells[m-1][n]->getType() == 1) 
    neighbors += 1; 
    if(n < (cells.at(m).size() - 1)) 
    { 
    if (cells[m-1][n+1]->getType() == 1) 
     neighbors += 1; 
    } 
    } 
    if(n > 0) 
    { 
    if (cells[m][n-1]->getType() == 1) 
    neighbors += 1; 
    if(m < (cells.size() - 1)) 
    { 
     if (cells[m+1][n-1]->getType() == 1) 
     neighbors += 1; 
    } 
    } 
    if(n < (cells.at(m).size() - 1)) 
    { 
    if (cells[m][n+1]->getType() == 1) 
    neighbors += 1; 
      } 
    if(m < (cells.size() - 1)) 
    { 
    if (cells[m+1][n]->getType() == 1) 
    neighbors += 1; 
      } 
    if(m < (cells.size() - 1) && n < (cells.at(m).size() - 1)) 
    { 
    if (cells[m+1][n+1]->getType() == 1) 
    neighbors += 1; 
    } 

    // Done checking number of neighbors for cells[m][n] 
    // Now we change cells2 if it should switch in the next simulation step. 
    // cells2 holds the values of what cells should be on the next iteration of the game. 
    // We can't change cells right now, or it would through off our other cell values. 
    // Apply game rules to cells: Create new, updated grid with the roundtwo vector. 
    // Note - LIVE is 1; DEAD is 0. 
    if (cells[m][n]->getType() == 1 && neighbors < 2) // If cell is LIVE and has less than 2 LIVE neighbors -> Set to DEAD. 
       cells2[m][n]->setType(DEAD); 
      else if (cells[m][n]->getType() == 1 && neighbors > 3) // If cell is LIVE and has more than 3 LIVE neighbors -> Set to DEAD. 
       cells2[m][n]->setType(DEAD); 
      else if (cells[m][n]->getType() == 1 && (neighbors == 2 || neighbors == 3)) // If cell is LIVE and has 2 or 3 LIVE neighbors -> Set to LIVE. 
       cells2[m][n]->setType(LIVE); 
      else if (cells[m][n]->getType() == 0 && neighbors == 3) // If cell is DEAD and has 3 LIVE neighbors -> Set to LIVE. 
       cells2[m][n]->setType(LIVE); 
    } 
    } 

    // Now we've gone through all of cells, and saved the new values in cells2. 
    // Now we loop through cells and set all the cells' types to those of cells2. 
    for (unsigned int f = 0; f < cells.size(); f++)  // Loop through cells' rows. 
    { 
    for (unsigned int g = 0; g < cells.at(f).size(); g++) // Loop through cells columns. 
    { 
     cells[f][g]->setType(cells2[f][g]->getType()); // Set cells[f][g]'s type to cells2[f][g]'s type. 
    } 
    } 
} 

stack.h - Вот мой стек.

#ifndef STACK_H_ 
#define STACK_H_ 
#include <iostream> 
#include "node.h" 

template <typename T> 
class Stack 
{ 
private: 
    Node<T>* top; 
    int listSize; 

public: 
    Stack(); 
    int size() const; 
    bool empty() const; 
    void push(const T& value); 
    void pop(); 
    T& peek() const; 
}; 

template <typename T> 
Stack<T>::Stack() : top(NULL) 
{ 
    listSize = 0; 
} 

template <typename T> 
int Stack<T>::size() const 
{ 
    return listSize; 
} 

template <typename T> 
bool Stack<T>::empty() const 
{ 
    if(listSize == 0) 
    return true; 
    else 
    return false; 
} 

template <typename T> 
void Stack<T>::push(const T& value) 
{ 
Node<T>* newOne = new Node<T>(value); 
newOne->next = top; 
top = newOne; 
listSize++; 
} 

template <typename T> 
void Stack<T>::pop() 
{ 
    Node<T>* oldT = top; 
    top = top->next; 
    delete oldT; 
    listSize--; 
} 

template <typename T> 
T& Stack<T>::peek() const 
{ 
    return top->data; 
    // Returns data in top item. 
} 

#endif 

gridcell.cpp - реализация GridCell

#include <iostream> 

#include "gridcell.h" 

using namespace std; 

// Constructor: Creates a grid cell. 
GridCell::GridCell(QWidget *parent) 
: QFrame(parent) 
{ 
this->type = DEAD; // Default: Cell is DEAD (white). 
setFrameStyle(QFrame::Box); // Set the frame style. This is what gives each box its black border. 

this->button = new QPushButton(this); //Creates button that fills entirety of each grid cell. 
this->button->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); // Expands button to fill space. 
this->button->setMinimumSize(19,19); //width,height   // Min height and width of button. 

QHBoxLayout *layout = new QHBoxLayout(); //Creates a simple layout to hold our button and add the button to it. 
layout->addWidget(this->button); 
setLayout(layout); 

layout->setStretchFactor(this->button,1); // Lets the buttons expand all the way to the edges of the current frame with no space leftover 
layout->setContentsMargins(0,0,0,0); 
layout->setSpacing(0); 

connect(this->button,SIGNAL(clicked()),this,SLOT(handleClick())); // Connects clicked signal with handleClick slot. 
redrawCell(); // Calls function to redraw (set new type for) the cell. 
} 

// Basic destructor. 
GridCell::~GridCell() 
{ 
delete this->button; 
} 


// Accessor for the cell type. 
CellType GridCell::getType() const 
{ 
return(this->type); 
} 

// Mutator for the cell type. Also has the side effect of causing the cell to be redrawn on the GUI. 
void GridCell::setType(CellType type) 
{ 
this->type = type; 
redrawCell(); // Sets type and redraws cell. 
} 

// Handler slot for button clicks. This method is called whenever the user clicks on this cell in the grid. 
void GridCell::handleClick() 
{   // When clicked on... 
    if(this->type == DEAD) // If type is DEAD (white), change to LIVE (black). 
    type = LIVE; 
    else 
type = DEAD;  // If type is LIVE (black), change to DEAD (white). 

    setType(type);   // Sets new type (color). setType Calls redrawCell() to recolor. 
} 

// Method to check cell type and return the color of that type. 
Qt::GlobalColor GridCell::getColorForCellType() 
{ 
switch(this->type) 
{ 
    default: 
    case DEAD: 
    return Qt::white; 
    case LIVE: 
    return Qt::black; 
} 
} 


// Helper method. Forces current cell to be redrawn on the GUI. Called whenever the setType method is invoked. 
void GridCell::redrawCell() 
{ 
Qt::GlobalColor gc = getColorForCellType(); //Find out what color this cell should be. 
this->button->setPalette(QPalette(gc,gc)); //Force the button in the cell to be the proper color. 
this->button->setAutoFillBackground(true); 
this->button->setFlat(true);  //Force QT to NOT draw the borders on the button 
} 

Спасибо большое. Позвольте мне знать, если вам нужно что-нибудь еще.

ответ

2

Возможно, вы хотите, чтобы serialise (довольно полная статья) состояние игры на каждом шаге (для каждого игрока, или глобально, или и то и другое - глобальное отмена?), А затем нажмите это сериализованное состояние. Затем, когда вам нужно сделать шаг назад, вы просто восстанавливаете правильное состояние и десериализуете его.

Дополнительный бонус к правильной сериализации заключается в том, что для сохранения игры вы просто берете стек сериализованных состояний и записываете его в файл, а для загрузки игры вы восстанавливаете стек сериализованных состояний.

+0

Может работать ... до тех пор, пока код сериализации достаточно умен, чтобы исключить кнопки, которые являются частью его основного класса состояний «GridCell». –

+0

Право. Или, если код переработан, чтобы больше не использовать кнопки в GridCell. –

+0

Или, возможно, полностью переработан, чтобы содержать состояние игры в соответствующем классе состояний. Важное значение имеет разделение GUI и игровой логики/состояния. –

2

Вы храните векторы указателей. Когда вы их восстанавливаете, вы сами восстанавливаете указатели, но не указываете на данные.

Вы выделяете много новых объектов GridCell в timerFired, добавляя их в структуру данных cells2, а затем вы их утечка. Все они. После перезаписи содержимого, на которое указывают сохраненные вами указатели.

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

EDIT: Wait a sec, эти экземпляры GridCell просочились ... являются виджетами графического интерфейса GUI? Я удивлен, что симуляция работает правильно, не говоря уже об отмене функциональности. Возможно, Qt не выделяет ресурсы GUI до тех пор, пока экземпляр не будет зарегистрирован. Вы используете GridCell двумя разными способами, что демонстрируется двумя конструкторами, и это очень плохой запах кода.

+0

+1 - хороший улов на кнопках в структуре данных - определенно делает сохранение и восстановление состояния сложнее. –

0

Чтобы решить тороидальное сопоставление, вы можете добавить else к каждому из соответствующих операторов if, обрабатывая граничные условия.

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

Смежные вопросы