Friday, November 14, 2008

Step 3 - Designing a graphical user interface

Okay, you might think: "Who cares about a GUI and graphics? This has nothing to do with the game itself."

But I think, the whole process is important, and also fun and I don't want to learn just the Console Programming side of C++ Development, I want to do the whole stuff.

And because, I'm someone, who really likes fancy graphics, I decided to create a set of assets in Photoshop for my little game.
I need:
- An empty field
- A marked field
- Fields with numbers from 1 to 8
- A field with a mine (exploded)

I decided to bring in my company logo and brand it on the fuX theme. (Fuchs is german for "fox" and its spelled exactly as "fux" [fooks])
So, I decided to put my fox logo on the marked field and, instead of a mine, I decided to use a bear trap.

The main rule is: an unrevealed field is black, whereas a revealed field is white.

So here we go:

Minesweeper asset file.

Minesweeper asset file.

Thursday, November 13, 2008

Step 2 - The Minesweeper Class

Okay, after this poor theoretical stuff, I want to have some more fun and start coding. I wanted to have this algorithm first, to make the programing easier later on, so that I can focus absolutely on coding and learning the syntax, instead of thinking about program logics while learning C++.

So the first thing I realized was the main game class. My first steps reminded me of the pictures of new born cows: They are able to walk from the first minute of their life, but their legs are shaking heavily and they hit the dirt a lot. =)

Some programming details, that I learned and find very useful to know:

I'm using the this. keyword a lot in Actionscript 3. It gives me a lot of control over the Class variables, separation them from variables with the same name, that are just available in a class method. ( -> There can be a difference between this.var and var)
What was new for me is the syntax: this->var. Let me see if I got this right: Everytime, we address something via a pointer, we use the arrow ->. "this", in this case is a pointer to the class itself. If we had created a class directly as a variable, we'd use the point syntax.

Another very cool thing, that I learned is the initialization list:
When create a constructor, you often have the case, that you pass an argument, that you have to pass to a member variable of the class. So the first lines of your constructor will probably look like this:



MyClass::MyClass(int var1, int var2, int var3){

this->var1 = var1;
this->var2 = var2;
this->var3 = var3;
(...)



And so on. But C++ comes with a very handy feature, that makes this procedure a lot slimmer.



MyClass::MyClass(int var1, int var2, int var3):var1(var1), var2(var2), var3(var3){
(...)



Just put the variables into the constructor line directly next to a colon after the parameters. I think the syntax is kind of self-explaining.
Note that the member variables have to be initiated in the header file for reasons of memory calculation.


Another note: I intentionally avoided the usage of the "namespace" command. It makes coding a lot more comfortable, because I don't have to write things like "std::cout" all the time. But I wanted to code it as detailed as possible to get a feeling for where the most important methods are to find.

So, here's the Class:

Minesweeper.h


/*
* Minesweeper.h
* MineSweeper++
*
* Created by Felix Ullrich on 10/18/08.
* Copyright 2008 fuX. All rights reserved.
*
* This class represents a Minesweeper API
*
*/
#ifndef MINESWEEPER_H
#define MINESWEEPER_H

#include
#include

class MineSweeper{

public:

//public member variables
int mineCount;
bool initiated;

//public member methods
MineSweeper(int width, int height, int mineCount);
~MineSweeper();
int revolveField(int x, int y);
bool markItem(int x, int y);
bool revolveAdjacentFields(int x, int y);
std::vector< std::vector > getFieldVector();

private:

//private member variables
std::vector< std::vector > queue;
std::vector< std::vector > minefield;
std::vector< std::vector > revolvedfield;
std::vector< std::vector > flagfield;

//private member methods
int getAdjacentMineCount(int x, int y);
int getAdjacentFlagCount(int x, int y);
int getAdjacentCount(std::vector< std::vector > field, int x, int y);
void revolveNextQueueItem();
void addNecessaryItemsToQueue(std::vector< std::vector > adjacentArray);
bool MineSweeper::isItemMarked(std::vector item);
bool MineSweeper::isItemInQueue(std::vector item);
void fillMineField(int clickedX, int clickedY);
std::vector< std::vector > getAdjacentFields(int x, int y);

};

#endif /* MINESWEEPER_H */



Minesweeper.cpp



/*
* Minesweeper.cpp
* MineSweeper++
*
* Created by Felix Ullrich on 10/18/08.
* Copyright 2008 fuX. All rights reserved.
*
*/

#include "Minesweeper.h"

#include
#include
#include

MineSweeper::MineSweeper(int width, int height, int mineCount):
//Initializations
mineCount(mineCount){

//Constructor
this->minefield.assign(width, std::vector(height));
this->revolvedfield.assign(width, std::vector(height));
this->flagfield.assign(width, std::vector(height));

/** TODO: Event Empty mine field created **/

}

MineSweeper::~MineSweeper(){

//Destructor
std::cout << "Program End" << std::endl;

}

int MineSweeper::revolveField(int x, int y){

if (!this->flagfield[x][y]){
if (!this->revolvedfield[x][y]){
this->revolvedfield[x][y] = true;
if (!this->initiated){
std::cout << "First mine" << std::endl;
this->fillMineField(x, y);
this->initiated = true;
/** TODO: Event -> Mine field filled **/
}

//If the field is a mine
if (this->minefield[x][y]){
/** TODO: Event -> Mine exploded (x, y, 9) **/
std::cout << "Mine Exploded." << std::endl;
return 9;
} else {
std::cout << "No Mine at this field." << std::endl;
int adjacentMines = this->getAdjacentMineCount(x, y);
//If the mine count is larger than 0
if (adjacentMines > 0){
/** TODO: Event -> Field has adjacentMines count (x, y, adjacentMines) **/

return adjacentMines;
} else {
/** TODO: Event -> Field has 0 adjacent mines (x, y, 0) **/

std::vector< std::vector > adjacentFields = this->getAdjacentFields(x, y);
this->addNecessaryItemsToQueue(adjacentFields);
this->revolveNextQueueItem();
return 0;
}
}
}
}

}

std::vector< std::vector > MineSweeper::getFieldVector(){

std::vector< std::vector > field;
field.assign(this->minefield.size(), std::vector(this->minefield[0].size()));

for (int i = 0; i < this->minefield.size(); i++){

for (int j = 0; j < this->minefield[0].size(); j++){

if (!this->revolvedfield[i][j]){
if (this->flagfield[i][j]){
field[i][j] = 11;
} else {
field[i][j] = 10;
}
} else {
if (this->minefield[i][j]){
field[i][j] = 9;
} else {
field[i][j] = this->getAdjacentMineCount(i, j);
}
}

}

}

return field;

}

int MineSweeper::getAdjacentMineCount(int x, int y){

std::cout << "Get Adjacent Mine Count." << std::endl;
return this->getAdjacentCount(this->minefield, x, y);

}

int MineSweeper::getAdjacentFlagCount(int x, int y){

std::cout << "Get Adjacent Flag Count." << std::endl;
return this->getAdjacentCount(this->flagfield, x, y);

}

int MineSweeper::getAdjacentCount(std::vector< std::vector > field, int x, int y){

int adjacentItems = 0;
//Iterate each field around the selected field
for (int i = x - 1; i <= x + 1; i++){

for (int j = y - 1; j <= y + 1; j++){
//If the checked field is not out of bounds
if (i != -1 && j != -1 && i != field.size() && j != field[0].size()){
//If the field is filled
if(i != 0 || j != 0){
if (field[i][j]){
adjacentItems++;
}
}

}

}

}

std::cout << "Adjacent Items: " << adjacentItems << std::endl;
return adjacentItems;

}

void MineSweeper::revolveNextQueueItem(){

if (this->queue.size() > 0){
std::vector item = this->queue[this->queue.size() - 1];
std::cout << "Queue: " << this->queue.size() << " - ";
this->queue.pop_back();
std::cout << this->queue.size() << std::endl;
this->revolveField(item[0], item[1]);
}

if (this->queue.size() > 0){
this->revolveNextQueueItem();
}

}

void MineSweeper::addNecessaryItemsToQueue(std::vector< std::vector > adjacentArray){

for (int i = 0; i < adjacentArray.size(); i++){
if (!this->isItemInQueue(adjacentArray[i]) && !isItemMarked(adjacentArray[i])){
this->queue.push_back(adjacentArray[i]);
}
}

}

bool MineSweeper::isItemMarked(std::vector item){

if (this->flagfield[item[0]][item[1]]){
return true;
}

return false;

}

bool MineSweeper::isItemInQueue(std::vector item){

for (int i = 0; i < this->queue.size(); i++){
if (item[0] == this->queue[i][0] && item[1] == this->queue[i][1]){
return true;
}
}

return false;

}

bool MineSweeper::markItem(int x, int y){

if (!this->revolvedfield[x][y]){
this->flagfield[x][y] = this->flagfield[x][y] == false;
}

return this->flagfield[x][y];

}

void MineSweeper::fillMineField(int clickedX, int clickedY){
std::srand( (unsigned)std::time( NULL ) );

std::cout << "Mine Field filled; " << clickedX << " and " << clickedY << " ignored." << std::endl;
int addedMines = 0;
while (addedMines < this->mineCount){
int rndX = std::rand() % this->minefield.size();
int rndY = std::rand() % this->minefield[0].size();
if (!this->minefield[rndX][rndY]){
if (clickedX != rndX || clickedY != rndY){
this->minefield[rndX][rndY] = true;
std::cout << "Field: " << rndX << " - " << rndY << std::endl;
addedMines++;
}
}
}

}

std::vector< std::vector > MineSweeper::getAdjacentFields(int x, int y){

std::vector< std::vector > adjacentFields;

for (int i = x - 1; i <= x + 1; i++){

for (int j = y - 1; j <= y + 1; j++){
//If the checked field is not out of bounds
if (i != -1 && j != -1 && i != this->minefield.size() && j != this->minefield[0].size()){

if (i != x || j != y){
std::vector field;
field.push_back(i);
field.push_back(j);
adjacentFields.push_back(field);
}

}

}

}

return adjacentFields;

}

bool MineSweeper::revolveAdjacentFields(int x, int y){

if (this->getAdjacentFlagCount(x, y) == this->getAdjacentMineCount(x, y)){
this->addNecessaryItemsToQueue(this->getAdjacentFields(x, y));
this->revolveNextQueueItem();
return true;
}

return false;

}

Changes in the algorithm

Before I step over to the next chapter, I'll correct some minor things that didn't work out correctly in the Algorithm.

I will only mention the things that have changes, like ...

member variables:
isRevolved array (2d)

M:revolve field, x and y given:
right at the beginning:
put the field at the revolved array to true

beginning, length, height and mine count given:
-create 2 other arrays for the flags and the revolvedInformation (if its revolved or not)

M:revolve next queue item
after the condition
<-condition: if the queue is not empty{
-call this function again (recursive)
}


You see, the main thing is that I forgot, that there has to be something, that remembers, if a revolved field IS revolved. I solved that by an additional 2d array, that has the same size as the mine array and the flag array.

The other thing was, that the revolve next queue item method didn't work properly, because it was not recursive. so when the stack has still empty fields to revolve, it should call the function again.