2006-03-31

Poggle: Working with the Registry

The program will access the registry in two places in Poggle's Application class. One is the ExitInstance() where the menu options the user has picked are stored until the next session. The other is the InitInstance() (of course). It goes without saying that the standard warnings about working in the registry are in full force even when doing entry change programmatically.
Not only that, for more involved programs, the programmer has to keep the registry structure for the program in mind. Will the structure need updating when the program changes? (I am doing this for my Concentration program, and it is an iron-casted pain.) How will changes in the registry (from the user or otherwise) be handled?

Step 15: I put the following after the program's initialization executes LoadStdProfileSettings(0), which loads the .INI file stuff.

Using the RegCreateKeyEx() function is a two-fer operation. If the key does not exit, the operation creates it. Otherwise, the key is opened for further work. The key returned from this function can be used in the following functions to point to the proper registry entry.
For the root registry key of the program the following options were used;
The Registry Key to be worked on was the hive, HKEY_CURRENT_USER (wait for it) to keep the user's choices personnal.
The SubKey used is "Software\\Your Company\\Poggle". (Thought I was going to work on the hive root, didn't you.) Note the escaped slashes.
The registry option used was REG_OPTION_NON_VOLATILE to keep the entries between sessions.
The Security option used was KEY_ALL_ACCESS. Used appropriately, this parameter allows the programmer to tailor the program's functionality to the best (lowest) security settings needed. Done wrong, only the Administrator will be able to run the program.

The returned value from this function is asserted to be equal to ERROR_SUCCESS.
The results value (what went on) is asserted NOT to be NULL.

If the results value is REG_CREATED_NEW_KEY then the program's session values for size and colour cycles need to be created, via RegSetValueEx().
Otherwise the values for this sessions can be retrieved with RegQueryValueEx(). Mind the results value of NULL (error from the registry key creation/open) should be handled as well.

Step 16: In the ExitInstance() function all that needs to be done is to use the RegSetValueEx() function to enter the session values before the program exits.

2006-03-29

Quick aside: A comment on 'Magic Numbers'.

First off, avoid 'Magic numbers' at all times in your program! It is extremely rare for a single use of a fudge factor in any program. Honestly, these adjustment numbers will, not might, WILL pop-up elsewhere as the program grows.
The only instances where such a number is used, that I can think of, would be in a mathematical equation (which really should be in its own routine anyways) or as a throw-away value only needed for debugging, e.g. the actual value does not matter just as long it is the same for each debugging session.

If a number is needed, consider making it a user defined value instead of a named constant. Many times the user will have a better idea as to the preffered value than the developer. (And many times the user will be a gormless git that will make a complete hash of things. But that is another rant.)
Case in point is the colour of the playing pieces. Now Black and White are considered traditional. But earlier, before mono-chrome displays, red was often used as the second colour in a number of games, i.e. Black and Red for backgammon and less commonly White and Red for chess. And what about adding an additional player? That third colour should be used?

Poggle: Is this thing on?

Step 14: The routine, bool finishedTest(void) the Board will use to find that the user has completed the challenge is fairly straight forward.

Pre-conditions:
- Is the Board instance valid? Actually this would be unneccessary as the routine is private and will not be call via the Singleton interface.
- Is the Board's states array valid?

Loop through the states array
- - (Note: I made an assumption here that I could have Assert'ed, that all states values are greater than or equal to 0.)
- - Is this element less than Board's cycle variable -1?
- - - If so then return false.

Return true.

2006-03-28

Drawing, Re-visited

Step 13: Now that the Board's states array is being used I can describe the void Draw(CDC* pDC) function in better detail.

In the constructor create and specify 5 solid CBrush objects (Yes, they are variables of the Board class). Using black for all off and white for all one, the three other CBrush object's colours are red, green and blue.

Preconditions:
- Is the Board instance still valid?
- Is the supplied pDC valid?
- Is the states array still valid?
- Is the places array still valid?

For each cell of the Board,
- - if the state of that cell is the Board's cycle -1 (all on), select the white CBrush
- - else if the state of that cell is 3, select a colour CBrush.
- - else if the state of that cell is 2, select a colour CBrush.
- - else if the state of that cell is 1, select a colour CBrush.
- - else the state of that cell is 0 (all off), select the black CBrush.

- - Draw the cell. Note, not specifying the CPen of the current drawing area (the pDC) will cause an 1 pixel black outline to be drawn. It would have been better to assume that the user would have chosen the window background and a contrasting text colour and gone with that text colour to outline the cells.

Post conditions:
- The Board instance is still valid.
- The same can be assumed about the places and states arrays.

The big part of Poggle

At least the part that I consider the MAIN part, everything else is secondary.

Step 13: Firat of, the mod portion of the program, int changeState(const int& state), the only thing that this routine does is to return the next integet in the current cycle.

The pre-condition is that the current Board instance is valid.

If the state parameter of the function is equal to the Board class's cycle variable -1, then return 0 (the starting point).

Otherwise, return the state +1.

Step 14: For void toggleCells(const int& index);
The pre-conditions are tha the Board instance is valid (again).
That the the place index is within bounds. (Release, otherwise just return.)
And that the states array is not NULL

First off, the clicked CRect element. Use the changeState() function on the state for the clicked CRect.

If the index is greater than the Board's size-1 (top row), then apply changeState() to the CRect above the current cell. (index - Board's size)
If the index is less than or equal to Board's size * (Board's size) (bottom row), than apply changeState() to the CRect below the current cell. (index +Board's size).
If the index mod() with the Board's size is NOT equal to 0 ( left side), apply changeState() to index -1.
If the index mod() the Board's size is NOT equal to the Board's size -1 (right side), then apply changeState() to index +1.

2006-03-26

Clicking on Poggle

In the View-Document model, only the View class processes mouse clicks. Which means that the program needs to jump through a couple of routines to detect and react to the user's mouse.

After adding the OnLButtonDown(UINT flags, CPoint point) handler to the View class, similar functions need to be added to the Document and the Board. The View calls the Document and the Document calls the Board.

Step 11: In the Document's mouse handler, void onClick(const CPoint& point), there are only three statements that need to be taken.

Assert that there is an instance of the Board class. (CBoard::getInstance() != NULL)

Call the Board's mouse handler.

Update view. Here the programmer will use UpdateAllViews(NULL). As only one View is associated with the Document, it does not matter if one goes through the effort to find the pointer this single View or just uses NULL for all (meaning one) Views. The other bit is that with only one parameter used, no hint value is passed and the OnUpdate routine will just Invalidate() the drawing area.

Note: I used a const reference for the click point as I did not see any need to manipulate the mouse.

Step 12: There are more things to process in the Board class's mouse handler, void onClick(const CPoint& point).

Check pre-conditions. (Is there a valid instance? Is places valid?)

Is the game over? (I tested a boolean variable to avoid calling a routine.) If finished, congratulate the user again and return.

Find the CRect object in places that contains the CPoint object point. It is highly recommended not to use the loop variable as the record holder for this. Currently C++ standards mean that this variable is likely to be out-of-scope outside of the loop. Most compilers will enforce this, although there are reportedly some that will keep the loop variable visible outside of the loop.

Ignoring out-of-bounds index values (less than 0 and greater than or equal to the Board's size variable squared), process the relevant state value and test for completion. If complete, congratulate the user and set the boolean finished variable for future reference.

Process post-conditions. (Is the Board instance still valid?)

Concentration: Reinventing the wheel

Specifically Object Oriented Programming.

Originally, I was accessing the specific advanced/medium/beginner variables directly. Actually there was every reason to leave the decision tree at the Application (with the Application) and just ask for the output variables.

Now that I have changed the options, not only do I have the normal beginner/medium/ advanced playing fields, I have the same variables for the 'Blink' (show cards before play), the 'Challenge' (change timer as player progresses), and 'Master' (hide matched cards) variants. I have to add specific registry keys for each combination of variant and still deliver them to the User with the same function call.

2006-03-23

View Updates II

Most of the Desktop checks would really not be an issue with regards to Poggle, but it is best to get into the habit of checking anyways.

The maximum width bounded by the desktop is found by using GetSystemMetrics(SM_CXMAXTRACK).

On the other hand, the height of the desktop is found using a different systems function. This is found using the function SystemParametersInfo() with the action SPI_GETWORKAREA. This will get the Rect denoting all the desktop that is NOT covered by the taskbar or by any application desktop toolbar. The function will need a temporary CRect object to store the retrieved value.

Finally, the function SetWindowPos() needs to be called to set the program's dimensions. This function can be used via the Windows pointer from AfxGetMainWnd(). There are some differences using SetWindowPos() between the initial update and the user demanded updates that will be explained below.

Step 9: The Initial Update.

Call the View class's CView::OnInitialUpdate() function.

Get and adjust the size of the Board object as mentioned earlier.

Set the Window size with AfxGetMainWnd()->SetWindowPos(NULL, 0, 0, adjustedSize.cx, adjustedSize.cy, SWP_NOMOVE | SWP_NOZORDER).

Step 10: The On-Demand Updates.

void CPoggleView::OnUpdate(CView* , LPARAM lHint, CObject*)
{
The only function parameter we are interested in the lParam lHint for communication between the Document and View.

Test lHint for the value specifying that the View needs resizing. (I used an enum to create a named constant in Poggle's Document class for this.) If so, then
{
Get and adjust the size of the Board object as mentioned earlier.

Set the Window size with AfxGetMainWnd()->SetWindowPos(NULL, 0, 0, adjustedSize.cx, adjustedSize.cy, SWP_NOMOVE | SWP_NOZORDER | SWP_NOCOPYBITS).
}

In any case Invalidate() and redraw everything.
}

Note: The flags in SetWindowPos (last parameter) specify the following:
SWP_NOZORDER - Leaves the program in the z-order it currently is placed, If top-most it stays top-most. This flag causes the function to ignore the first parameter (pointer to reference Window pointer, here NULL.
SWP_NOMOVE - Keep position. (Actually leave top-left corner of window in same position.) This flag causes function to ignore the second and third parameters (here 0 and 0).
SWP_NOCOPYBITS - Discards everything within the GUI. The program MUST redraw itself to show any changes.

2006-03-22

View Updates

Under Microsoft's View-Document model there are a few places that the programmer can change the size of the program's interface.

The common one given as an example is to use the Mainframe class's PreCreateWindow function and alter the CREATESTRUCT variable's cx and cy parameters. The problem is that this is only good for program where the height and width are already known.

While I am talking about the CMainFrame::PreCreateWindow function;
Step 8: Remove the style FWS_ADDTOTITLE from the cs.style parameter. This style adds the filename of the saved file the program is displaying or 'Untitled' when starting afresh.

The other place to re-size a program is in the View class's OnInitialUpdate and OnUpdate functions using Window's SetWindowPos(). OnInitialUpdate() is called between the time the Document first connects with the View but before the View is first displayed. OnUpdate() is called by the Document's UpdateAllView() function, which allows the programmer an opportunity to refresh the View on demand.

While there are some differences between the initial and on-demand updates, the bare bones is as follows;

Get the size of the Board. A pass-through function in the Document class should do this as there could be additional information in the Document to be added (not in this case). Reminder, this will be the right-most and bottom-most values, plus the BORDER value in each direction.


Example: The getSize() function from CBoard

CSize CBoard::getSize(void) const
{
Assert that this instance is not NULL.

Assert that Board's places is not NULL.
If Board's places is NULL, then return an empty CSize value, i.e. CSize(0,0).

Return Size with the right-most value of places[BoardSize-1]+BORDER and the bottom-most value of places[BoardSize * (BoardSize-1)]+BORDER.
}

The returned size is then adjusted for the GUI.
(For Microsoft) In the X direction this is given by GetSystemMetrics(SM_CXFIXEDFRAME) which is the width of the GUI frame for the thin frame style. (I did say to create the program with the thin frame didn't I?)
The Y direction adjustment is given by the GetSystemMetrics(SM_CYFIXEDFRAME) PLUS GetSystemMetrics(SM_CYCAPTION) (for the caption) PLUS GetSystemMetrics(SM_CYMENU) (for the menu bar). Please Note that this last metric is for single line menus only. (I am still working on multiple line menus.)

Next up, Desktop checks.

2006-03-21

Marks, and Revisions, and Versions, oh my!

I have not seen any sort of 'rules of thumb' for coming up with version numbers. So here is mine.

The Version (or Major Version) Number; This increments only when the User experience with the program will change in a new and major way or when the program design changes in a manner that the entire program has to be re-engineered from soup to nuts.
Simply extending the program's functionality, e.g. additional file types, would not make the cut. The extention would have to involve new capabilities for the user, not just additional capabilities much like ones that user had before.
For instance, adding a move hint capability would justify a change in version as this would involve multiple changes in multiple areas in the program to implement.
N.B. Zero in this position, by convention, is reserved for works in progress, alpha and beta programs.

The Mark (or Minor Version) Number; This increments when the user's experience is modified or when a major portion of the program needs changing, like when a class withing the program needs refactoring.
For instance, instead of just showing the best move, the program could indicate which moves are better than others. The only major changes are done in a very small set of classes, usually one, and minimal changes (if any) are needed elsewhere.

The Revision Number; Capabilities and user experience remains the same but how they are implemented changes. Usually the changes involve one, maybe two, functions.

The Build Number; Increments per build cycle. Setting this to zero would basically depend on the developer.

Sometimes Build number and Revision numbers are swapped.

On Menu actions

Step 7: All of the menu items concerned with changing the game (its size or colour cycle) can be written the same way.

Example;
on selecting size X by X
{
Set Document's changed size flag to true.

Set Application's size variable to X.

Call OnNewDocument() to recreate Board.

Update View.
}

on updating size X by X menu item
{
Set check if Application's size variable == X.
}


Note 1: Accessing the Application class's members is not hard but there is one trick to remember. AfxGetApp() returns a pointer to the CWinApp base class, not a pointer to the Application class. You will need to cast the pointer in order to get to the Application's class members, i.e. (CPoggle*)AfxGetApp().
And yes it would be better to use dynamic_cast to recast the pointer. Just remember use a try and catch structure for possible mis-casting.
[UPDATE] Standard OO mistake here. Directly accessing the Application's variables is not good form. It would be much better to use Set / Get functions.

Note 2: Changing the Board's size and the Board's colour cycle really do not need to be handled in the same manner as only changing the size means re-allocating memory. So this is another item that can be improved later on.

2006-03-19

State-ful variables

Step 6: The Document-View model that Mircosoft uses has a central application class that manages the WinMain portion of the program. Later on the registry founctions will ba added, but for now just the size of the playing field and the number of colours vaeiables need be created. Note: It would be a good idea that the limits of the Board class be enforced at this point but this can be compensated for at the game set up. (It is still a possible problem but only if the limits are not enforced constantly.)

2006-03-16

The Set-up functions

Step 3: The first set-up function is used to allocate the Rectangle array places and the int array states. It is only called when the size of the playing area or of the color cycle needs changing. of course the program start up calls this function as well.

boolean setUp(void)

// preconditions
Assert that the Board instance pointer is not NULL. ( redundant, but just in case )
Assert that the Board size value is within specified limits

If the places array exists, delete it.
If the states array exists, delete it.

Create the new Rectangle array using the Board's size value (squared) and asign it to places
Create the new Rectangle array using the Board's size value (squared) and asign it to states

Assert that places is not NULL. (Debug)
If places is NULL, return failed. (Release)

Using the constant values for the border, cell spacing and cell size, create in places an x-by-x square of Rectangles.

// post conditions
Assert that the Board instance is still not NULL.

Return success.

Step 4: The other set-up function is a reset or initialization function for new games. As everything else is set up all this function needs to do is to set the values in the states array to 0 (Black) and initialize any other game values in the Board instance.
The pre-conditions are that the Board instance exists, and that there is a valide states pointer.
The post-condition is that the Board instance still exists.

Step 5: Of course a function is needed to set the Board instance's field size and number of colours variables.

Step 6: As I am using the Microsoft View-Document model, I put the set-up calls in the Document class's OnNewDocument function.

// the variables denoting a change in the Board instance were set to true in the Document's constructor
If there was a change in the size or number of colours do{

Set the Board instance's size and number of colours.

CBoard::getInstance()->setUp();

Set the Documents variables for a changed Board to false.
}

Reset the Board instance to game starting values.

Of course, if the Board instance's setUp() failed, this will have to be passed on to the Document's base class for handling as an error.

2006-03-15

Changing the game: I

Step 1: First I enumerated the various constants (enum), the upper and lower limits to the size of the board and some delimiters for drawing the board (border, rectangle size, and space between). The lowest limit I chose for the board size was 3 (for a 3-by-3 cell board) and the upper was 7. At this time the constants for the upper and lower limits for the colours the game uses can also be added.
I would recommend using the lower limit to initialize the board size's static variable.

Step 2: Add the appropriate menu items. I went for 'Preferences'->'Size' ->[ 3x3,4x4,5x5,6x6,7x7]. Then the event handlers and update handlers can be added to the Document class for these menu items. The update handlers will allow the program to checkmark the right menu item. (I will go into this later.) The menu items for the number of colours can also be dealt with at this point.

2006-03-14

Constant for large values of X

No doubt you have started out creating constant values using the statement '#define VALUE=5'. The problem with this is that most debuggers will not be able to report this constant value. The fact that the preprocessor has processed this statement before the debug symbols are gathered is one reason for this.

A better way to represent constant values is to use the const keyword. (Final in Java.) One advantage is that any type of variable can be called constant, and this includes user defined structures.

If you would rather have the constant value have class scope, using the enum keyword is the way to go. You cannot use a variable type other than integer, but you can reference the value through the containing class, e.g. myClass::constVal.

2006-03-13

A Variation on Concentration

After reading about memory tests, I thought up a variant for my Concentration game. The user first sees the card layout. When ready, the user clicks on the 'Blink' menu item or button. The program then displays the card pairs for X seconds and then only shows the card backs. In order to rely on memory only, the program only indicates match success or failure by changing the colors of the card backs.

A couple of options to make the game even more sadistic. One, if the user can match cards in under an arbitrary number of attempts, the program automatically shortens the time that the cards are displayed when the user clicks 'Blink'.
The second, and worse, is to have the computer revert the display of the successful matches to 'un-played' after a short time. Not only would the user have to memorize the card positions, but the successful matches as well. Of course matching a pair of cards the second or more time would not go to the score.

This observation / memory training game is not a new idea. I have come accross it elsewhere, but the only place that I definitly remember it is in Heinlein's "Citizen of the Galaxy". Here a bunch of odds-and-ends are thrown on the ground / a tray / the table and then covered after a short time. Not only must the player remember what objects were thrown, the position and orientation (if possible) has to be remembered as well.

What type data members for a static instance?

In Poggle, I am using static for all data members in the Singleton opbject. In other program, the only static data member is the instance variable.

About the only advantage I can think of for all data variable to be declared static is to allow the calling classes to access them, directly or throught accessor functions, without going through the Singleton instance. The disadvantage is this will break the expected behavior of the Singleton as well as having potential syncronization issues.

2006-03-10

After adding the Board class

As the starting point one should start with four functions; one to draw, one to get the size of the field, and two to initialize the data. The first initialization function is for new games. The second is for those times the playing field changes. (Actually for non-solitare games, the Board class only needs to initialized once in its lifetime.)

The data variable that the Board class will start with is a pointer to a Rectangle class, *places. (CRect in Microsoft IDE.) Since the Board will be re-sized when different game playing options are selected, pointers will allow the board information to be destroyed and recreated on the fly. Clean-up is nothing out of the ordinary, so the Board class's destructor can be in charge of that.
The other starting variable is a integer to hold the size information. This is to avoid all the sizeof()/sizeof(Rectangle) hoops to jump through to get pointer array information. I recommend just storing the length of the side.

For right now, the drawing function for the class just can draw the rectangles that places point to. This will suffice as a place-holder until the rest of the game's variables are added. A single loop will be all that is required. Just assert() that there is a valid pointer before drawing random pointers.

2006-03-08

The Singleton: One More Time

What was the bit from the Highlander series? "There can be only one!"

The Singleton pattern enforces this idea. Hiding the details of creating the object from the calling class, a Singleton object forces the caller to access its functionality through a specialized interface. One advantage of this approach is that the caller will not need to track multiple objects over different calling objects.

While, supposedly, a Singleton object in C++ can be constructed in the manner as those in Java, I will be using a point to implement Singleton objects. The only disadvantage is that the user must manage the destruction of the object, and not depend on the scope of the Singleton.

Header File:

class Singleton
{
private:
     Singleton(void);
     ~Singleton(void);

     static Singleton *instance;

public:
     static Singleton* getInstance(void);
     static void Release(void);
};

Source:

#include "Singleton.h"

Singleton* Singleton::instance=NULL;

Singleton::Singleton(void)
{
     //do stuff here
}

~Singleton::Singleton(void)
{
     // clean up
}

Singleton* Singleton::getInstance(void)
{
     if(instance == NULL){
          instance = new Singleton();
     }

     return instance;
}

void Singleton::Release(void)
{
     if(instance != NULL){
          delete instance;
          instance = NULL;
     }
}

2006-03-07

Apologies

Given the lame fashion blogger renders HTML I will have to use psuedo-code to describe my code and not write about specific dependencies.

PS. Boolean tests for less than X and greater than Y are going to really be a pain.

2006-03-02

Excuse the delay

I need to find out post angle brackets.

2006-03-01

ASSERT II : Conditions, conditions, conditions,,,,,

One bit of program design that should be habitual is the use of pre-conditions and post-conditions in functions.

Pre-conditions are the guarantee that your function is getting it's objects in an expected manner. This will cover both any inputs to the function and any class objects or global objects that the function is expected to use.

Post-conditions are the guarantee that objects will be as expected when leaving the function. Again both the function output and objects external to the function are covered here.

One minor detail on this. Remember that Debug and Release versions of a program are usually different. If you are keeping an eye on pre- and post-conditions for Debug versions of a program, it is highly recommended that the same conditions are handled for the Release version as well.

ASSERT I : Assume makes a Burro out of U and Me

Assuming an object's condition or the condition of a part of an object is always risky. Especially hidden assumptions, things that you expect to be a certain way but find out that one time in a hundred actually contain complete nonsense.
The flip side on this is that your assertion is triggered you did or did not do earlier in the program. Don't get rid of the problem by removing the assert, fix the problem object before you get to the assert instead.

Too many assert()'s are not a problem. If you need to optimize them out, you can always wrap them in a macro that evaluates to a noop under release conditions.
The only issue is in the way they are implemented. DebugAssert(IsFileError()) is fine. DebugAssert(FileOpen() != NULL) is not.