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.

Sunday, October 19, 2008

Step 1 - The Minesweeper Algorithm

This algorithm is written in a quite high language and is meant to be a basis for the program itself.

The principle of minesweeper is as follows:
You are in front of a table of invisible content. Each field contains either a mine or no mine. If the user clicks on a field and it contains a mine, the game is over. if he clicks on an empty field, it reveals a number that states how many mines all the adjacent fields contain. If there are no mines around, all adjacent fields are automatically revolved. Hidden fields, which are supposed to contain a mine can be marked with a flag.



member variables:
mine count (how many mines are in the field)
queue (a stack of fields, which are to revolve one after the other)
minefield array (2d)
flag array (2d)

beginning, length, height and mine count given:
-make mine count accessible from anywhere inside the minesweeper class
-create a 2 dimensional array with the given length and height
-throw an event indicating the empty minefield is complete by now




M:revolve field, x and y given:
<-condition: If it is the first revolving{
->method: fill array randomly with (mine count) amount of mines, ignoring field x,y
-throw an event indication the minefield is filled by now
}
<-condition: if the field x,y is a mine{
-throw an event indicating field x, y has the value 9
-return the value 9
} else {
->method: get adjacent mine count
<-condition: if the adjacent mine count is larger than 0{
-throw an event indicating field x, y has the value (adjacent mine count)
-return the value (adjacent mine count)
} else {
-throw an event indicating field x, y has the value 0
->method: get the array of adjacent fields for field (x, y)
->method: add necessary items to the queue for (adjacent array)
->method: revolve next queue item
-return the value 0
}
}

M:get adjacent mine count, x and y given:
-remember a number
()loop: from x - 1 to x + 1{
()loop: from y- 1 to y + 1{
<-condition: if the x-loop is NOT at -1
AND if the y-loop is not at -1
AND if the x-loop is not at the length of the x-array
AND if the y-loop is not at the length of the y-array{
<-condition: if the x-loop is NOT 0 OR the y-loop is NOT 0{
<-condition: if the field is a mine{
-add 1 to the remembered number
}
}
}
}
}
-return the remembered number
M:get adjacent flag count
the same as above, just with the flag array

M:revolve next queue item
<-condition: if the queue is not empty{
-remember the item
-remove the item from the queue
->method: revolve field (x of item, y of item)
}

M:add necessary items to the queue, adjacent array given
()loop: for each item in the adjacent array{
<-condition: if the item is not yet in the queue
AND if the item is not revolved yet
AND if the item is not marked{
-add the item to the queue
}
}





M:mark item, x and y given:
<-condition: if the item is not revolved{
-swap the boolean value of the flag array at x, y
}
-return the boolean value of the field

M:fill array randomly, mine count, x and y given
-remember the amount of added mines.
()loop: as long as the added mines are less than the mine count{
-create a random number of the x position, maximum is the length of the x array of the minefield array
-create a random number of the y position, maximum is the length of the y array of the minefield array
<- condition: if the field at the random numbers contains NO mine{
-turn the random field into a mine
-add 1 to the amount of added mines in mind
}
}
-return the value 0

M:get the array of adjacent fields, x and y given
-remember a stack of fields
()loop: from x - 1 to x + 1{
()loop: from y- 1 to y + 1{
<-condition: if the x-loop is NOT at -1
AND if the y-loop is not at -1
AND if the x-loop is not at the length of the x-array
AND if the y-loop is not at the length of the y-array{
<-condition: if the x-loop is NOT 0 OR the y-loop is NOT 0{
-add the field to the stack of remembered fields
}
}
}
}
-return the stack of remembered fields

M:revolve adjacent fields, x and y given
->method: get adjacent flag count (x, y)
->method: get adjacent mine count(x, y)
<-condition: if the adjacent flag amount = the adjacent mine count{
->method: get the array of adjacent fields for field (x, y)
->method: add necessary items to the queue for (adjacent array)
->method: revolve next queue item
return the value 0
}
return the value 1

The first real project . . .

After all the little learning tasks from the tutorials, that can't really be called a project, I have now decided to begin my first real program in C++. It's gonna be a little game and I don't want to create something from scratch. This is rather meant to be an extended practice.

The requirements for this project are:
- It has to be a nice little game, with a relatively simple algorithm.
- The game engine itself has to be an encapsulated API, that serves the outer world with all the necessary information and methods do display and control the game. In this way, the game can easily be ported from one engine to another.
- It should serve a C++ graphics engine to provide a graphical user interface!

After some consideration, I decided to develop

Minesweeper++

Minesweeper is a quite easy game and I already had lots of fun with playing it. I'm gonna keep in in the fuX-Style (fuX is my one-man-company), but the graphical style is something for later on.

Much more important is, how I'm gonna do it step by step. I planned it in the following way:

1. Write the algorithm in a very high language. This is important to me, because I want the whole program logic to be clear, when I start to code, as I'm not really comfortable with C++ at the moment. So I don't want to add logical problems to the language problems, of which I surely will have plenty.

2. Write the game class in C++.

3. Prepare the images for the GUI.

4. Choose a graphics engine.

5. Build the GUI in the graphics engine and link it to the game engine. (alpha)

6. Polish.(beta)

7. Port the game for OSX and Windows and load it up here. (final)


So, let's go for it! =)

Thursday, October 16, 2008

Learning the basics of C++

My first step was learning the theoretical basics of C++. What do I have to do to get my first application running? How does the syntax look like? What do I already know and what is new?

Since I'm using a Mac and I am used to work in Eclipse (Java Development, Flex, Aptana), I searched for a possibility to use the Eclipse IDE for C++ as well. Luckily, it is already offered at the Eclipse start page, the Eclipse for C++ Developers Plugin. I quickly installed it and found out, that I do not even have to install a compiler, because my MacBook Pro brings everything I need.

So, how to get closer to the basic rules of C++? There are a lot of tutorials on the internet, and I don't want to say, that they aren't good, but I found a book at amazon.de called "C++ für Spieleprogrammierer" from Heiko Kalista. (Means "C++ for game developers"). So, I decided to do it in the old fashioned way and to learn from this book, as it seems to be made exactly for my purposes and the user reviews were all very cheerful.

I worked through the most parts of it in about 2 weeks, whereas this is not 2 weeks 24/7, but an hour or two after work, every second day. Something like that. I can agree to the user ratings, it makes the introduction very easy, especially, when you already bring some programming experience. Okay, if you don't speak german, it will be quite useless to you, but there will surely be other solutions ;). I worked through the syntax basics in about 3 days (It's all quite familiar from Java and Actionscript 3) and am now quite familiar with classes, pointers and addresses. I'm not totally comfortable with everything, but that's simply because the experience is missing. I think now is the time to think of the realization of a really small game, with a nice, logical API that serves a graphic engine to generate the front end.

Without this basic knowledge, I think it is senseless to think about starting to develop a game and/or think about an engine to develop it with. My next thought were some game concepts and the intensive search for the right game engine. To be continued...

Monday, October 13, 2008

Hello World

The first words, programmer speaks in a new language are the same words, that will introduce my blog: Hello World!

Since I was very young (I think I had a C64 at that time), I dreamt of creating my own computer games. Over the years, I've been able to gather a quite strong background in the design and development of computer software. I studies Character Modeling and Animation, alongside with Creative Media and Technologies and finished my studies with a Bsc. title.

My main focus was the development of web software so far, so my fortes are Actionscript (Version 3 preferred), Java, Javscript and Prototype, which I'm doing a lot at work at the moment.

But since almost every job in the game development business demands for proficiency in C++, I finally decided to learn everything I have to know about C++ and stack up my portfolio with some serious games.

I'm not completely new to the development of games. Besides some very old, forgotten Basic games and some small flash games, I've created a neat little 3D pet simulation game called fuXcats during my studies. For this game, I was forced by the University to use 3D Gamestudio A6. Basically, it was quite okay, because a lot of functionality came with the game engine, but I couldn't get the necessary C++ experience. If I succeed to export it to binary some day, I will post it here as well.



So much about my background. In the next posts, I will talk about what I already learned about C++ and what I'm planning to do next.

Cheers,
Felix