2006-06-28

Kono: the Board class (again)

Actually a bit more than a variable move made it necessary to refoactor every thing.

O.K., the CBoard class is purely about the playing field. The reason why earlier versions placed movement functionality here was confusion about the similarity between checking for possible movement paths and checking for neighbouring regions.

So, the minimum required for the CBoard class is (ignoring Singleton issues) drawing the board and retrieving board and region information. The functions about neighbourhoods can be skipped as there is no special or missing neighbouring cells to be concerned with.

The named constants:
Private;
drawing constants for the playing field, and
number of cells
Public:
OFF_BOARD.

Variables:
Private;
array of regions for selecting pieces, and
array of regiond for selecting playing regions.

Static Functions (no need to create or release instances to call these):
Public;
retrieve dimensions of layout (number of cells for each direction), and
check that location (x,y) is in bounds.

Functions:
Public;
retrieve the region for location (x,y),
match location (x,y) to point,
drawing,
retrieving the size of the playing field (in pixels), and
Singleton functionality.

2006-06-17

The Scoring System, Nimbers III

Once Nimber arithmetic is explained with 3 stacks, there is no need to go any further. The value of four or more stacks can be determined as combinations of 1, 2 and/or 3 stacks.

The starting point here is to use 3 stacks containing 1, 2 and 3 counters. These 3 stacks (1* + 2* + 3*) have the combined value of 0. If the first person removes one of the stacks, the second person can then equalize the remaining stacks and leave the Game with a value of 0 again. Alternatively if the first person equalizes two of the three stacks, then the second person can remove the un-equal third stack and leave the game with 2 equal stacks again.
So the first set of equivalences in Nimber Addition are;

1* + 2* + 3* = 0
1* + 2* = 3*
1* + 3* = 2*
2* + 3* = 1*

The next triplet of Nimbers in Winning Ways, Vol. 1 that equal 0 can be played out in much the same manner. In addition to the moves mentioned before (first player: removes a stack, second player: equalizes the remaining stacks and first player: equalizes 2 stacks, second player: removes the un-equal stack), the second player also has the following strategy. If the first player reduces one of the bigger stacks to either 2* or 3*, the second player can remove counters from the other large stack to give it a value of 3* or 2* respectively. The game now has the value of 1* + 2* + 3* or, as previously determined, 0.

Evaluating any random set of 3 stacks is not terribly difficult. One just has to find the Nimber value of two smaller stacks. If this value equals the third stack, the total value of the triplet is 0 and the first person to play loses. If the combined Nimber value of the smaller stack does NOT equal the third stack, all the first player needs to do to win is to reduce the third stack to the value calculated and leave the Game with the value of 0.

Further examples of Nimber addition can be found in Berkamp, Conway and Guy's "Winning Ways, Vol. 1", pages 42 and 59 - 59.

The Scoring System, Nimbers II

Beginning with two stacks, the properties of Nimbers start appearing.

Two stacks of equal height (with equal Nimber values) turns out to have a total value of 0 (the first person to move loses). If the first person takes an entire stack, the second person take the second stack and the first person is left without a move. On the other hand if the first person takes only part of a stack, the second person takes the same amount from the other stack and leaves the total value 0 as before.
Thus the first property of Nimbers is that two Nimbers of equal value total to 0 (n* + n* = 0). In other words, a Nimber is its own negative.

Two stacks of unequal value mean that the first person wins, of course. The best strategy is for the first person to equalize the stacks leaving a Game with the value of 0 for the second player.

2006-06-13

The Scoring System II, Nimbers

Now consider Lucy and Robert with a single stack of cookies. They take turns eating one or more cookies from the stack. The loser in this game is the one that cannot get any more cookies to eat from the stack.

The best strategy for either player is to grab the entire stack.

Here the first person to play wins.

The Scoring System I

Skipping over fractional game values for the time being, let us talk whole numbers.

We will start off considering games blocking games where the first person without a move loses. By convention, the Left player (which I'll name Lucy) will win if the game's value is positive, >0, or, in other words, has move moves than the opposing side. And the right player (Robert) will win if the game's value is less than 0, more moves than Lucy.
If the game has a value of 0 (think of a game board without pieces), then the first person to move is the loser (no piece to move).


Follow along with Berlekamp, Conway, and Guy in "Winning Ways".

scheduling

One of the problems I am having is that when I built up my collection of programs I tended to skip around, going from one program to another, to avoid burning out. This tends to make for long development times.

I will try to blog on a regular basis, once I get my scheduling together. Until then, all I can do is random posts.

2006-06-09

Kono: regarding Rules class

Applying rules of the game without specifiing who is in that location just dose not make sense. About the only way that one can apply a movement rule and not care about ownership is with non-partisan (pieces can be moved by anyone) games or games that have both partisan and non-partisan pieces.

2006-06-04

Kono, re-going over the go-over

It is rather embassasing to re-do teh rules class in order to get it to check that the piece moved is actually owned, much less owned by the proper side. I.e., Pre-Condition from point is playerX.

2006-05-28

An Edward Gorey Death







What horrible Edward Gorey Death will you die?




You will swallow some tacks. You are a little weird, maybe not so much in a good way. Buy a yellow tie and wear it on your head.
Take this quiz!








Quizilla |
Join

| Make A Quiz | More Quizzes | Grab Code

2006-05-26

Kono: the beginning

The two classes that are essentially remain the same (expect for the Microsoft supplied classes) are the CStone and the CBoard classes. And about the ONLY thing that changes for the CBoard class is 1) the movement functions are moved the CRule class, 2) the named constant for the size of the playing area becomes private, and 3) that a new public function is added to indicate whether or not a given point is indeed on the playing area.

PS. I will start commenting on the COwner class when I finished refactoring it. The only other bit than needs to be mentioned is that the stored piece arrays are combined within a CPoint array in order to keep the location values together. (Saves on mental sweat.)

2006-05-24

Kono: Today's post is brought to you by the letter R

R for ARRRRRGH!

And here is my book recommendation;

2006-05-23

Kono: Refactoring

If I ever compile these posts, I will include my old code so programmers can have something to point to and snicker.

1) The first part that gets changed is to place every function that reflects / models / or is based on a rule of the game into the Rules class.

2) Hide as much as possible. While the signels enumerated constants need to remain exposed, there are others that do not. The size of the board (CBoard::DX) is one.

3) Even if copying data in two classes is faster, don't. Better to 'waste' computer cycles in function calls that to miss synchronizing the data and causing odd results. (The exapmle here is the COwner::Player ownership array in the CGame class.)

4) Right now the only other bit that I can see is to minimize the amount of data that goes into the Undo and the MoveGenerator classes. (Note: Optimize was not mentioned.)

2006-05-22

Poggle: more solutions

5-by-5
Black and White
10110
01110
11100
11011
00011
3 Colours
01102
10020
10201
02001
20110
4 Colours
32110
21312
13100
11011
02011
5 Colours
33401
34243
42001
04040
13104


6-by-6
Black and White
101101
011110
111111
111111
011110
101101
3 Colours
010010
111111
012210
012210
111111
010010
4 Colours
321123
213312
133331
133331
213312
321123
5 Colours
214412
120021
403304
403304
120021
214412


7-by-7
Black and White
1101011
1110111
0110110
1001001
0110110
1110111
1101011
3 Colours
0120210
1221221
2210122
0102010
2210122
1221221
0120210
4 Colours
1123211
1310131
2112112
3023203
2112112
1310131
1123211
5 Colours
1443441
4033304
4342434
3321233
4342434
4033304
1443441

2006-05-20

Poggle, the solutions

One interesting point is that there is only ONE solution for each of the 3-by-3 Poggle variants. For the 4-by-4 Poggle versions, quite a few more.
The 4-by-4 Poggle game's linear algebra solutions required that two or more buttons be completely specified before the entire solution could be show. I did re-discover a solution for this version that I had first found back when I was first putting this program together. (A "d'oh!" type solution you will see when it is described.)

For the 3-by-3 Poggle puzzles;
2 Colours
101
010
101

3 Colours
010
111
010

4 Colours
323
232
323

5 Colours
141
434
141


For the 4-by-4 Poggle version, consider the following. There are location on any Poggle board that toggle EXACTLY 4 buttons. Given that the 4-by-4 Poggle board has 16 button, only 4 buttons should be pressed to cover the entire Poggle board. And here they are.
00X0
X000
000X
0X00
and its mirror image
0X00
000X
X000
00X0
. See what was meant by this solution being a 'kick yourself' moment for not seeing it immediately?

2006-05-18

Poggle, Revisited II

You know you're in trouble when one find statements similar to "... which proves that some initial states are insolvable." (My emphasis.) The question becomes, how does one find out this difference?

In "Adventures in Groups Theory", David Joyner points to a post by D. Rusin. In it, one Carsten Haese lays out the solution to a similar game that can be applied to Poggle. I will attempt to reproduce it in this post.

Short form: convert the game to linear algebraic form and solve. Not-so short form: stock up on asprin.

Think of Poggle as a state machine with; a internal state (here, the buttons that are light), an input alphabet (the buttons), an output alphabet (the lights), an internal trasition table (the new internal state, given a specified internal state and the input), and an output table (given an internal state and an input, the output). As the output, input and internal state can be folded into one set and the tables can be considered as one, there are only two items that need to be looked at.

The state can be represented by a one-dimensional vector that is n-squared elements long. Think of this vector representing the Poggle board's display layed out row by row (from top to bottom).

Now to build the transition table. The size of the table will be n-squared by n-squared elements. The building blocks cosist of;
the Identity Matrix (size n-by-n),
the Zero Matrix (size n-by-n), and
B=
110 .. 00
111 .. 00
011 .. 00
..
000 .. 11
(size n-by-n).
The construction of the B matrix is placing 1's in the main diagonal of the n-by-n matrix and in the diagonals above and below it. 0's are placed every where else.

The transition matrix is formed
A=
B10 .. 00
1B1 .. 00
01B .. 00
..
000 .. 1B


For instance the 3-by-3 Poggle board's transition matrix is;
110100000
111010000
011001000
100110100
010111010
001011001
000100110
000010111
000001011


For Poggle the solution can be foudn with the linear equation;
(A x = s0) mod y.
where s0 is the starting vector (a 1 dimensional vector containing n-squared 1's for Poggle), and x is the vector containing the buttons to input. The modulus y is simply the total number of colours that the program cycles through.

Using {a,b,c,d,e,f,g,h,i} to denote the solution vector, the 3-by-3 Pobble board can be solved with the solution of;
a + b + d = 1 mod y,
a + b + c + e = 1 mod y,
b + c + f = 1 mod y,
a + d + e + g = 1 mod y,
b + d + e + f + h = 1 mod y,
c + e + f + i = 1 mod y,
d + g + h = 1 mod y,
e + g + h + i = 1 mod y,
f + h + i = 1 mod y.

For y=2 it is easy to see that {1,0,1,0,1,0,1,0,1} is the solution.

2006-05-17

Programming Query

When programming (and this is top-down, caller-to-callee programming) does one; program a function call before writting the function? program a function stub with a bogus and/or error return so that the program compiles? or, write a function stub and include an assert or otherwise alert the person running the program of the missing function?

2006-05-16

Kono, some changes

OK, one added class is a Singleton class to check moves against rules of the game. This will (not in this game but in others) reduce the CBoard class to checking if cell X and cell Y are, in fact, neighbours.
The other class that will be added is a display class that will show either al possible moves or possible moves that were graded into best to worst.

One big change in this program is that the game can be saved. A short-cut toward this is to derive the CGame and the COwner classes from CObject to take advantage of the Serialize function.

2006-05-15

Kono, the rule of the game

The playing field is a 4 by 4 square. Each player has 8 pieces starting on opposing halves of the playing field.

Pieces can move one place either vertically or horizontally.

Pieces capture by jumping over one friendly piece onto an opposing piece, again either vertically or horizontally.

One side wins when the other side cannot move, either by blocking the other side's pieces or by capturing all of the opposing pieces.

2006-05-14

The next game

One can almost flip a coin between Alquerque and 'Four Field' Kono, remove all the opposing pieces or keep the pieces from moving. Well, considering that the popular game to program is Alquerqeu, I will go with Kono. The mechanism is pretty much the same if one elides past the huff rule and the capture method.
I could have gone with Alquerque's cousin, Fanorona. But the capture rules are not exactly clear.

I will present 'Fox and Geese' and Four Handed Shaturanga (an early form of chess) later.

2006-05-13

Mu Torere, Improvements

Step 21.

Well one thing that can be added is accessibility, again. In other words, adjusting the colours and enabling keybourd-only input.

Another improvement is to change the art-work. As was said earlier another background that can be used is an eight spoked wheel, with or without the rim. A bitmap can be used for the background too. Use your imagination, is the playing field scratched on stone? scribbled into sand? or in-laid in wood? 3-D sprites can also be used for the playing pieces.

Other improvements range from optimizing the program to adding more moves to the 'AI's' look-ahead.

Good Luck.

2006-05-11

Mu Torere, the Final test

Step 19.
Like last time, the program needs to be checked for syntax errors at each of the previous steps.

For Q & A (re-test all the steps each time corrections are needed);

A - Run the 'Mu Torere' program. Examine the menu, but do not try anything. Then exit the program.
Did it start cleanly? Was the menu correct? Was the program displaying correctly? Did the program exit cleanly?

B - Run the program. Select and then re-select the Colour Legend menu item. Select the 'Change User Colour' menu item and change the colour. Do the same with the 'Change Computer Colour' menu item. Select each of the pre-set AI level of effort menu items. Select the Custom AI Level menu item and change the value to the neighbourhood ( + / - the EPSILON that you picked ) of each pre-set level, selecting 'OK' each time and re-selecting the Custom AI Level dialog to select the next value. Exit the program and then re-start it. Examine the menu items again.
Did the Colour Legend display toggle (appear / disappear) along with the selection? Was the Colour Legend menu item checked at the same time as the Legend was displayed and unchecked when the Legend was NOT displayed? Did the colours of the proper pieces change as new colours were selected and were those colours the ones selected? Did the Colour Legend change as well and show the right colours?
Did the AI Level menu items check and uncheck as different levels were selected? Did the check mark follow the selection? Did the changing the AI Level with the Custom AI Level dialog do anything? Did the checkmark show when the custom value was in the neighbourhood?
When re-starting the program, did the colours match those selected during the last run? Does the Custom AI value match that from the last run?
And does the program start and exit cleanly?

C - Run the program. Set the AI level of effort to 'Off' or set the Custom AI Level to 0. Select points on; the Desktop, the program caption, the program's background, the program's empty location, all pieces owned by the Computer, each of the User owned pieces that CANNOT be moved legally (one of the middle ones), a User piece that CAN be moved legally. After the Computer moves, select points on; the program's background, the program's new empty location, all of the Computer's pieces, all of the User pieces that CANNOT legally move, and then the User piece that CAN legally move. Select the 'New' menu item and then exit the program.

Did the program move the expected legal pieces as expected and ONLY when a legal USER piece was selected? Was this behaviour repeated with the new positions? Did selecting 'New' reset the program to the initial positions?
Again, did the program start and exit cleanly?

D - Start the program again and play a few rounds. If the choice was made to NOT let the AI level of effort change after starting play, test that this is so. Continue until one side or the other wins. Try selected various points on the program to see if the program continues to move pieces. Select 'New' and continue playing with the AI 'Off' until the side that did not win, wins.

Did selecting a new AI level work? Did the winning side messages display correctly? Did the program stop playing at this point?
Again, did the program start and exit cleanly?

Step 20.
Re compile the program as 'Release'. Make sure that all options that appear under 'Debug' compiling are no longer availible.

2006-05-10

Mu Torere, the rest of the story

Step 17.
Not much else to do at this point.

Just add the event handlers for the AI menu items, and code the relevant values into them. The only vit that I would recommend is to use some sort of variance value when coding the OnUpdateXXX() handlers. This way when the user pick a custom AI level-of-effort that is in the neighbourhood of the program's idea of the level, the right menu item is checked. For example;

pCmdUI->SetCheck(abs(((CMuTorereApp*)AfxGetApp())->getAI() - CAILevel::LOW) < CAILevel::EPSILON ? 1 : 0);

I used CAILevel::EPSILON with the value of 3 to determine how close user chosen values had to be.

Step 18.
The registry stuff was much the same as that for the Poggle project. So go to this page to review.

2006-05-09

Mu Torere, setting the AI

Step 16.
The dialog for the custom AI 'Level-of-Effort' is fairly simple. All the special functionality works on a dialog box that contains a horizontal scrollbar, a couple of static texts to mark the high and low end of the scrollbar, an edit box and the 'OK' and 'Cancel' buttons. The only functions that need to be explained are the InitDialog(), the OnHScroll() and the OnEnChangeEdit1() (or what ever the edit box is id'ed in your program) functions.
Using MS's IDE one should associate a variable with the edit box.

Oh yes, there are a few named constants to specify the AI levels for the program. As long as 0 is used for the Off end of the scale and around 95 for High, it really does not matter what value is used for the Low and Middle of the scale (example, 25 and 66 respectively). Remember that the program uses the randomMove() function whenever the random value (mod 100) is GREATER than the AI level choosen.

On dialog initialization;
BOOL CAILevel::OnInitDialog()
Get a CEdit pointer the the dialog's Edit box.
Get a CScrollbar pointer to the dialog's horizontal scrollbar.

Get the text associated with the edit box. (Part of the Data Exchange process.)
Re-format the text as integer. (Use _tstoi() for the UNICODE enabled programs.)

Set the lower and upper limits of the scrollbar to 0 and 100 resp.
Set the scrollbar position to the integer value retrieved from the edit box. (As this is an initialization, the scrollbar will not need redrawing.)


On changes of the edit box;
void CAILevel::OnEnChangeEdit1()
Get a CEdit pointer the the dialog's Edit box.
Get a CScrollbar pointer to the dialog's horizontal scrollbar.

Get the text associated with the edit box. (Part of the Data Exchange process.)
Re-format the text as integer. (Use _tstoi() for the UNICODE enabled programs.)

Set the scrollbar position to the integer value retrieved from the edit box.


to reflect the changes of the scrollbar in the edit bxo (and the variable that is retrievable);
void CAILevel::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
Get the lowest limit, the highest limit and the current position of the scrollbar.

Given how the scrollbar position is changing (given by nSBCode),change the scrollbar position as appropriate.

Get the CEdit pointer the edit box.
Set the edit box's text to the value of the scrollbar's current position.

2006-05-07

Mu Torere AI II

Step 15.

Two new steps and a modification for the Move Generator class and a new function and a modification for the Game class.

The modification is for the CMoveGenerator's getMove function;
int CMoveGenerator::getMove(const int& aiL)
Pre-Condition:
That aiL is between 0% and 100%, inclusive.
If a random value (mod 100) is greater than aiL, use the randomMove(COwner::playC) function.
Otherwise use the aiMove(COwner::playC) function.

(The move returned should NOT equal CBoard::OFF_BOARD.)

Return the move.
Instead of constructing the class instance with the value to use for the AI effort, we are just going to submit the value when getting the Computer's move. No need to store anything that way.

The 'AI';
int CMoveGenerator::aiMove(const COwner::Player& tag)
Pre-Condition:
That tag is either COwner::playC or COwner::playU.
Create two COwner::NUM_PIECES-sized integer arrays.

Initialize both arrays with the MoveGenerator's thisGame's buildMove() for player tag.
(The returned caount should be within limits.)

Use evaluateMoves() to assign values.
(The returned array should NOT equal NULL.)

Return a move given that;
If any move was assigned a 1 (the player tag won) has the highest priority.
Otherwise, pick a random move with the value of 0 (no-one won).
Any moves assigned -1 are to be ignored.
And, yes, only one array to store the moves could be used here. The thought in my so-called mind was that the issue was where the next move needs to be considered as well, needing a spare array.

Evaluating the move;
void CMoveGenerator::evaluateMoves(const COwner::Player& forTag, const CGame& startG, int* moves)
Pre-Condition:
That tag is either COwner::playC or COwner::playU.
That moves is NOT NULL.
Create an integer array with COwner::NUM_PIECES with elements.
Assign a COwner::Player variable to the opposing player.

For each move in moves;
{
If move equals CBoard::OFF_BOARD,
Re-assign element to and go to next element.

Create a temporary CGame variable with thisGame's values.
Apply this move to the temporary CGame.

Did forTag win?
{
Assign +1 to the element.
}
Else,
{
Use the created move array to store the opponent's moves given the applied move. (The program has already tested the applied move to see if forTag won, now check the opposing side.)

If any of the opposing move is a winner,
{
Assign -1 to this moves element.
}
Else,
{
Assign 0 to this moves element.
}
}
}
Creating the move array before the loop gets the possible error condition out of the way.

The new function in CGame is;
bool CGame::applyMove(const COwner::Player& tag, const int& moveFrom)
Pre-Condition:
That tag is either COwner::playC or COwner::playU.
That moveFrom is with CBoard bounds.
That moveFrom is NOT equals emptyLoc.

Pre-Condition:
Apply Move(tag,moveTag)


The modification in CGame is in the computerMove() function, one just needs to give the CMoveGenerator's getMove() function the specified AI level.

2006-05-05

Mu Torere, Undo II

Step 14.
All of the functions in CUndoQueue are pass-through. One way looking at this is to consider the Interface - Implementation Pattern. One class to give the using program something to handle, and the other class actually does the work but is 'hidden' from the program. One distinct advantage is that once the Interface part is stable, any functional changes in the Implementation will not cause the rest of the program to be re-compiled.

Step 15.
There are only a few places that need changing in the Document class to add the undo interface. Of course the menu item 'Undo' needs to have event handlers for the OnUndo and the OnUpdateUndo events. OnUndo just calls the undo() function of the queue, apply it to the game and update the program's view. The OnUpdateUndo calls the queue's canUndo() function and uses it to enable or disable the 'Undo' menu item.

The undo queue, a pointer, is created in the Document's constructor using the value stored in the App class to determine the number of element in the queue.
The queue is initialized then in the Document's OnNewDocument. At the risk of being obvious, this means that each new game starts with an empty undo queue.

As for the mechanism of storing the game in the queue, it will proceed along the following lines;

The current game is stored in a temporary CGame variable.
The move function of CGame, selectMove() is called.
ONLY IF a valid move was done is the CGame value in the temporary variable stored in the queue. Note that valid move will include cases were the Computer or the User won.

2006-05-03

Mu Torere, Undo

Disclaimer: Before going on to describing the Undo functionality, here is the reason for the program using a base and derived class and for storing the old information as a CGame object.
It looked like a good idea at the time, OK?
Seriously though, the reasoning for the Queue class from the undo calls was to allow updating the Queue class for further functionality in the future and to avoid the temptation of putting queue functionality in the undo calls.
As for using an entire CGame object, well consider that a placeholder. To minimize the run-time footprint one can extract the bare minimum data needed to represent the game and then apply this data later. But that can be left for future updates.

Step 13.
The Base Queue.
The CCircleQueue class uses several named constants to improve program readability.
First off are the upper and lower limits to size of the queue. Now it seems to make very little sense to worry about limits while talking about a static queue. Except, suppose the decision on the queue size was re-considered and a change was requested. In this program the change can be implemented in the registry and avoids the re-compiling that a hard-coded number would need.
While the upper limit is pretty much arbitrary, the lower limit of 2 was chosen with the view to keep the special cases within reason.

The data structure is relatively simple; one pointer going forward (into the future), one pointer going back (back to the past), and one pointer to the data. The class uses two pointers to this data structure for its purposes.

The constructor simply sets the structure pointers, topPoint and stopPoint, to NULL and sets the bookkeeping integer, qSize, to 0. The queue will be created seperately. The destructor uses a seperate function to actually de-allocate the queue.
void CCircleQueue::Release(void)
Pre-Conditions:
That topPoint is NOT NULL.
That topPoint->forward is NOT NULL.
While a temp pointer is NOT equal to topPoint AND is NOT NULL,
{
Copy the temp pointer to another pointer for deletion.
Move the temp pointer over to temp->forward.

Process the copied pointer by deleting its data pointer if NOT NULL and by deleting this pointer.
}

Delete topPoint->data, if NOT NULL.
Delete topPoint.

Finally, set stopPoint to NULL


The Queue creation begins with;
bool CCircleQueue::newQueue(const int& levels)
Pre-Conditions:
That levels is between CCircleQueue::Q_LLIMIT and CCircleQueue::Q_ULIMIT inclusively.
Assign an integer variable, counter, to levels (or the value of levels that is within bounds).

Create 2 elements for the initial Queue using topPoint and a temporary pointer. If this allocation fails, return false.
Assign the forward and back pointers.

Assign CCircleQueue::Q_LLIMIT to qSize.
While qSize is less than counter AND less than or equal to CCircleQueue::Q_ULIMIT,
Add a queue element using growOne(). If this fails at any point( NOT equal to CCircleQueue::QGROW_GOOD), break out of loop. (qSize is incremented within growOne().)

In any case, return true.


The function used for growing the Queue builds on an already initialized queue. At this time the only point at which the queue will grow is from topPoint (which is known) and will only use the forward structure pointer. This means that (if the queue is already in use and has data) the historical data structure can still be reached.
CCircleQueue::qReport CCircleQueue::growOne(const CCircleQueue::qPoint& addTo)
Pre-Conditions:
That topPoint is NOT NULL.
That topPoint->forward is NOT NULL.
That addTo equals CCircleQueue::QPOINT_FIRST.
Attempt to allocate a queue element. Set its data pointer to NULL.

Insert this element into the queue forward of topPoint.

Return CCircle::QGROW_GOOD.
There are two distinct error returns from this function. CCircleQueue::Q_ERROR is used on the event of a mal-formed queue, while CCircleQueue::QGROW_FAIL reports a failure (possible memory shortage) in adding additional queue elements.

The queue resetting function is as follows;
void CCircleQueue::resetQueue(void)
Pre-Conditions:
That topPoint is NOT NULL.
That qSize is between CCircleQueue::Q_LLIMIT and CCircleQueue::Q_ULIMIT inclusive.
A while loop is used to visit each element in the queue, delete any allocated CGame pointer and set those pointers to NULL.
NOTE:If the loop eve finds that the next element in the queue is missing (NULL), the program SHOULD exit.

Finally, stopPoint is set to NULL.
The program does use a 'called-out-of-order' condition statment before the preconditions, if qSize is less than CCircleQueue::Q_LLIMIT then the create Queue function is called with the specification of CCircleQueue::Q_LLIMIT.

Both the push() and the pop() functions are overloaded. But the basic push() function is;
bool CCircleQueue::push(const CGame& data, const CCircleQueue::qPoint& addTo)
Pre-Conditions:
That topPoint is NOT NULL.
That qSize is between CCircleQueue::Q_LLIMIT and CCircleQueue::Q_ULIMIT inclusive.
That addTo equals CCircleQueue::QPOINT_FIRST.

Again a 'called-out-of-order' condition statment before the preconditions, if qSize is less than CCircleQueue::Q_LLIMIT then the create Queue function is called with the specification of CCircleQueue::Q_LLIMIT.
Attempt to allocate a new CGame pointer for the queue. Set this allocation to data.

If this is the very first data pointer for the queue (topPoint->data == NULL),
{
(stopPoint should be NULL at this time.)

Set topPoint->data to the new CGame allocated pointer.
Set stopPoint to topPoint.
}
Else if the queue is full up (the pointer forward of topPoint is stopPoint),
{
(There is a valid pointer for stopPoint->forward.)

Set topPoint to stopPoint.
Move stopPoint to stopPoint->forward.

Delete the CGame allocation (if it exists) in the current topPoint->data.
Set topPoint->data to the new CGame allocated pointer.
}
Otherwise,
{
(stopPoint should be a valid pointer.)
(topPoint->forward should exist.)

Move topPoint to topPoint->forward.
Delete the CGame allocation (if it exists) in the current topPoint->data.
Set topPoint->data to the new CGame allocated pointer.
}
After all that return true.
And the pop() function is;
void CCircleQueue::pop(CGame& data, const CCircleQueue::qPoint& getFrom)
Pre-Conditions:
That topPoint is NOT NULL.
That qSize is between CCircleQueue::Q_LLIMIT and CCircleQueue::Q_ULIMIT inclusive.
That getFrom equals CCircleQueue::QPOINT_FIRST.
If stopPoint is NULL, there is nothing left to pop.

If this is the last one in the queue (topPoint == stopPoint),
{
(topPoint->data should exist.)

Set returned data to topPoint->data.
Delete topPoint->data and set it to NULL.

Set stopPoint to NULL.
}
Else,
{
(topPoint->data should exist.)
(topPoint->back should exist.)

Set returned data to topPoint->data.
Delete topPoint->data and set it to NULL.

Move topPoint to topPoint->back.
}
The test for popping data is check if topPoint is NOT NULL AND that stopPoint is NOT NULL.

2006-04-30

Mu Torere, a digression

In this entry the number of possible combinations of playing pieces will be determined. What will be done is to find the Generating Function given by Burnside's Lemma, which is a special case of Polya's enumeration theorem. Before anyone asks, the only thing that I could determine about Generating Functions is that they are a way to encode sequences (and not a decent how-to in the description either.) Examples are better.

First off, Permutation Groups are collection of permutaion operations (element swapping) on the elements of a set. They can be denoted by cycle decompositions of the swaps being made. For instance;
A 2-element swap on a 6 element Set
123456
153426
=
(1)(3)(4)(6)(25)
has 4 1-element cycles and 1 2-element cycle
While
Rotation on the same 6 element Set
123456
234561
=
(123456)
ha only 1 cycle of 6 elements.

Polya's enumeration theorem uses equivalent decompositions, i.e. (1)(2)(3)(4)(56) and (1)(3)(4)(5)(26) having 4 1 elements cycles and 1 2 element cycles are equivalent, to
create the generating function. Each permutation operation is weighted according to the number of equivalents and then the average is taken.
As an example the number of distinct ways to add edges to a 3 vertice graph can be found as follows. The permutations on a 3 element set is (second example);
[1 2 3] = (1)(2)(3)
[1 3 2] = (1)(2 3)
[2 1 3] = (1 2)(3)
[2 3 1] = (1 2 3)
[3 1 2] = (1 3 2)
[3 2 1] = (1 3)(2)
Which creates the function; 1/6 ( x1^3 + 3 a1 a2 + 2 a3 ). The translation is that there is one equivalent of 1-cycle * 1-cycle * 1-cycle plus three equivalents of 1-cycle * 2-cycle plus two of 3-cycle operations.
The generating function is then created using (z+1), where z denotes an edge and 1 denotes no edge. This produces ((z+1)^3 +3 (z+1)(z^2+1)+(z^3+1))/6.
Or rather, z^3 + z^2 + z^1 + 1. Which can be read as there is only one way to add three edges to the graph, only one way to add 2 edges, only one way to add 1 edge and only one way to add no edges. Only considering rotations will procude the same results.

For Mu Torere, only the rotation permutations will be used. This allows us to equate, for instance, all swaps that are 2 places apart. Ignoring teh central location, means that only 8 locations need be considered. From this, the permutaion groups are;
[12345678] = (1)(2)(3)(4)(5)(6)(7)(8)
[23456781] = (12345678)
[34567812] = (1357)(2468)
[45678123] = (14725836)
[56781234] = (15)(26)(37)(48)
[67812345] = (16385274)
[78123456] = (1753)(1864)
[81234567] = (18765432)
Being 4 permutations 8-element cycles, 1 permutation group of 1-element cycles, 1 permutation group of 2-element cycles, and 2 groups of 4-element cycles. This gives the preliminary function of 1/8 (g1^8 + g2^4 + 2 g4^2 + 4 g8).
For the generating function, we will use (c+u+1) where c will denote a piece owned by the Computer, u will denote one owned by the User, and 1 will be a empty location. This creates the generating function ((c+u+1)^8 + (c^2+u^2+1)^4 + 2 (c^4+u^4+1)^2 + 4(c^8+u^8+1))/8.

HomeWork: What terms will we need to examine? And what will be final number?

Update:
The terms to examine are C^4 U^4, C^4 U^3, and C^3 U^4.
The numbers are 10, 35, and 35, which totals 80.

2006-04-29

Mu Torere AI

While I recover the Undo functionality, I will go over the background of the program's AI in the meanwhile.

Essentially, the program can use one of 3 kinds of decision making routines to simulate a proper AI. (This is in addition to the random move routine.)
The first is the basic tree graph to layout what move lead to which moves and an evaluation of that move. For this version I will only evaluate the NEXT Computer's move and the following User's move (one ply each) for the winning move.
Another is to use pattern recognition. The program can check the current positions of the playing pieces and find out if a winning move is possible.
The related routine (the third one) is to use a state machine. The program will not need to decipher the game to find the proper state, but the current state of the game will need to be stored to determine the next game state. The trade-offs are pretty much obvious, program complexity (deciphering the game to a state value) versus footprint (storing the current state and the next-state table.)

The pattern matching and the state machine routines are possible because of the limited distinct positions of the playing pieces. (At least the computer can calculate them).

Poggle update

OK, I found the references I wanted to prove that Poggle can be solved. It seems that this game was marketed under the name Merlin's Machine, Lights Out and Orbix. The obly difference it seems is that the neighbourhood of cells along the edge and corners of the game wrapp around to the other side. (Which should suggest ideas.)
The only issue is that this kind of problem has only been looked at using wrap-around neighbourhoods.

SoI need to apply a little mental knuckle-work.

Now I just need to read the chapter.

2006-04-28

Mu Torere, the AI II

Step 12.
Given all the de-allocation problems with CMoveGenerator as a Singleton, I decided to toss it over and just use a pointer within the Computer's move function (see update in last step.)

So there are only three functions that need to be constructed at this time, in addition to the constructor (which preps the RNG.) The public functions are the getMove(void) returning the CBoard location the Computer moves to and setGame(const CGame* data). The private function, randomMove(const COwner::Player& tag), just grabs an availible move at random. The decision tree part for the AI will be added later. This is enough to start play testing the program.

The only thing really unusual about the comstructor is that it requires an integer. This will be for the AI part.
The Constructor
CMoveGenerator::CMoveGenerator(const int& aiL)
Enforce limits on the chosen AI level.

If debugging, seed Random Number Generator with specific value.
Otherwise, use the value returned from time() as the seed.
In case this is new, the standard headers for doing this are time.h, math.h, and stdlib.h.

Updating the CMoveGenerator instance with the current game is straight forward.
void CMoveGenerator::setGame(const CGame* aGame)
Pre-Condition:
That aGame is valid.
Set private CGame variable to COPY aGame.
This point is one that is debatable. Just how much data is needed to determine the next move? Here the costs on system resources from multiple copies of the CGame object were balanced against the straight-forwardness of only one operation needed to list the possible moves.

The interface of getMove() allows the program to hide all the messy details of actually determing the next move.
int CMoveGenerator::getMove(void)
Call the private move generator for the Computer's move.
Post-Condition:
That the returned move is valid.


The only move generator right now is purely random (as much as a ccomputer can get anyway.)
int CMoveGenerator::randomMove(const COwner::Player& tag)
Allocate memory to store the possible moves.

Get the possible moves based on the store data.
Perform reality checks on data. (Not much to this one. The only biggie is the possiblity that this function is called when there are no moves left.)

Get a random possible move. I used a while loop (with an emergency limiter) to extract a non-CBoard::OFF_BOARD value.

Clean-up and return the move.

2006-04-24

Complaint, MS de-allocation.

Every once in a while the Microsoft compiler will complain when de-allocating memory. It seems that the guard space allocated along with that allocated by new has been altered. From comments within the provided code, the only other explaination for this is that the de-allocation is being done on the 'wrong' heap.

Solution? If your program breaks when de-allocating the same bit of memory over and over again (And I am NOT talking memory leaks.), the best work-around that I have found is to allocate an extra amount of memory for that 'problem' allocation to keep everyone happy.

2006-04-23

Mu Torere, moving the pieces II

Step 11.
Now the User and the Computer needs to move. An enumerated type lSelection needs to be created with Invalid, Valid, ComputerWon, and UserWon as values to allow for CGame to signal the Document.
AND, might as well do this now, a boolean flag needs to be added to the CGame class to denote that there are no more moves to do. This flag will need to be added to the various copying functions.

For the User the CGame adds the function
CGame::lSelection CGame::selectMove(const CPoint& point)
If the finished flag is true, then return a CGame::Invalid signal.

Get a pointer to the CBoard instance.
If this pointer is NULL, return CGame::Invalid.

If there is no valid selection from CBoard's whatIndex(), return CGame Invalid.
Otherwise,
Test the move. If not possible to move, return CGame::Invalid.
If moving is not possible, return CGame::Invalid.

If the User won, set the finished flag to true and return CGame::UserWon.

Get the move from the computer Move function.
ASSERT that this move is not CBoard::OFF_BOARD.

Move the Computer's piece.
ASSERT that this Move was possible.

If the Computer won, set the finished flag to true and return CGame::ComputerWon.

If nothing else, CGame::Valid.
I should point out the issue with the move. (As if you had not noticed.) If the User's move work but the Computer's did not, at this poin the only reasonable action is to exit the program, preffereably with some message to the user. Even then there must be a better way to handle this situation.
The only thing that I can think of is to use the Undo functionality to restore the game, and try again. (Either ask the user to re-do the move, or to loop a few times and THEN ask the user.)

The function for testing who won;
COwner::Player CGame::whoWon(void) const
If the emptyLoc is the center location (8), return COwner::playN.

Get the COwner::Player value of the side that owns the center.

Switch on the value in emptyLoc,
{
If either neighbouring array element (element 7 is next to element 0) is owned by the same side of the center, return that COwner::Player value.
}

Otherwise, just return COnwer::playN.


The function for testing the move;
bool CGame::canMove(const COwner::Player& tag, const int& fromLoc)const
Get the pointer to the CBoard instance.
Pre-Conditions:
That tag is either COwner::playC or COwner::playU.
That fromLoc is greater than or equal to 0 and less than CBoard::NUM_PLACES.
And that fromLoc is NOT equal to emptyLoc.
Get a pointer to the CBoard instance.

If the move from fromLoc to emptyLoc is NOT possible, return false.

Get the COwner::Player value of the opposing side.

If emptyLoc is the center location, test to see if one and / or the other neighbour of fromLoc is owned by the opposing side.
If this is the case, return true.
Otherwise, return false.

And if emptyLoc is NOT the center, return true.


And the function for the computer's move;
int CGame::computerMove(void) const
Get a pointer to the CMoveGenerator instance.
Allocate a CMoveGenerator instance.
Test pointer to see if it is valid.

Setup the MoveGenerator instance with the current CGame.

Get the move.
ASSERT that the move will not be CBoard::OFF_BOARD. (Actually done AFTER releasing the CMoveGenerator instance.)

Release Delete the CMoveGenerator instance.

Return the move


Update: Changed CMoveGenerator instance from Singleton instance to straight pointer allocation.

2006-04-22

Mu Torere, moving the pieces

(I neglected to post this before going on about the AI.)

There are two classes that have information that changes because pieces move. I will cover the user interaction part in the next post.

For the COwner class;
bool COwner::Move(const int& fromL, const int& toL)
Pre-Conditions:
That fromL is greater than 0 and less than CBoard::NUM_PLACES.
That toL is greater than 0 and less than CBoard::NUM_PLACES.
And that fromL is NOT equal to toL.
Get the element in Board's loc array that has the same value as fromL. Return false if this fails.

Then assign toL to this element.
Return true.


And for the CGame class;
bool CGame::Move(const COwner::Player& tag, const int& fromLoc)
Pre-Conditions:
That tag is either COwner::playC or COwner::playU.
That fromLoc is greater than 0 and less than CBoard::NUM_PLACES.
And that fromLoc is NOT equal to emptyLoc.
If tag was COwner::playC
{
If the Computer's Move routine succeeds
{
Change the owned array as appropriate.
Assign fromLoc to emptyLoc.

return true.
}
}
else
{
If the User's Move routine succeeds
{
Change the owned array as appropriate.
Assign fromLoc to emptyLoc.

return true.
}
}

If the program gets this far return false.

2006-04-20

Mu Torere, the AI!

Now we are cooking with gas! The only thing is that this will not be that elaborate, and for that matter the AI will not much more that an Random Number Generator in order to check the rest of hte system. But here goes.

First a few functions are needed to establish the ground work, specifically copy contructors and the like.
While 'friend' classes and functions could have been used, adding a CGame object or pointer directly to the CMoveGenerator allows for the contruction of building hypothetical moves, e.g. 'What Computer moves are availible if the User moved this way?' (And yeah, I do hear the eye-balls rolling at this point.)

So, for the COwner and CGame class the copy constructor looks like;
The Copy Constructor
CXxxxx::CXxxxx(const CXxxxx& obj)
: Copy initialization list
For each element in array, copy obj.array[element]

The '=' operator function works as follows;
The Equal Operator
const CXxxxx& CXxxxx::operator =(const CXxxxx &obj)
If this is NOT equal the address of obj (&obj)then
{
Copy obj's data members.

For each element in array, copy obj.array[element]
}

In anycase, return the object reffered to by this. (*this)
I really recommend reading a good book on C++ to find out WHY operator=() is declared in this manner.

And finally, a kluge to copy from pointers to objects for the CGame class. I went through a bit of hoop-jumping and pointer manipulation and massaging before settling on this to extract pointer data members to copy to temporary objects. (But then I do not know the complete ins-and-outs of C++.) Much like the operator=() function the only difference is that there is no need to return the object to which this reffers;
void setGame(CGame* aGame);
void setGame(const CGame* aGame); // overloaded to copy-from this
If this is NOT equal the address of aGame (&aGame)then
{
Copy aGame's data members.

For each element in array, copy aGame->array[element]
}


Note: that there is NO data validation. While this can be added, it might be better to do that in a separate step.

2006-04-19

Mu Torere, the Stone Singleton

Step 8.
A basic Singleton instance, all that is required of this class is to draw the playing pieces.
Why isn't this folded in with one of the other classes? The basic reason is that this class can be considered a working stub class. If need be, a different drawing routine can be substituted to draw 3-D playing pieces. Or a Sprite class can be used instead. Or the user can decide.

For drawing;
void CStone::Draw(CDC* pDC, const COLORREF& res, const CRect& rect)
Create a 2-pixel wide pen with the colour of the menu text, usu. black.
Create a solid bruch with the provided colour, res.

Select the pen and the brush into the drawing context.
Draw an Ellipse contained by rect.
Select the old pen and old brush back into the drawing context.
Note that all the needed information for drawing the piece is provided. Nothing is stored in the CStone instance.

Mu Torere, the Owner class

Step 9.
The COwner class has only 3 variable to track, and a couple of named constants. One variable res holds the colour for the pieces it manages, an integer array loc (with named constant NUM_PIECES) to store the location on the board, and a variable tagP indicating which side the object is on (using COwner's enumerated Player type).
A couple of in-line functions to get the CBoard and CStone instance are useful. The only in-line function that could be added is one setting the COwner object's colour. Just remember to check that the object has actually been set-up by looking for a proper value in tagP. (No need to colour an un-assigned COwner object.)

Aside from the drawing function, the only thing that needs adding is the set-up function for initalization and the reset function for new games.

The COwner constructor just initializes the class's variables to what are essentially don't-care / error values that will be corrected when the COwner objects are properly initialized. In this case, res and tagP are set to RGB(255,0,0) and COwner::playN, while all elements in loc are set to CBoard::OFF_BOARD.
All the destructor needs to do is to Release any CBoard and CStone instances.

The bare-bones void COwner::setUp(const COwner::Player& tag) just sets the COwner object's tagP variable (only if tag is COwner::playC or COwner::playU) and then calls the reSet() function.

The void COwner::reSet(void) initialization function does have a Pre-condition that the only tagP values are COwner::playC or COwner::playU. Aside from that, the loc array is now initalized for each side. As long as all pieces from the same side are adjoining, placement does not matter. In my program I initialized the User and Computer arrays so that the User had the two places on the left and the two places on the top of the playing field. (From all the examples that I have seen.) The center playing location remains empty.

For drawing;
void COwner::Draw(CDC* pDC)
PreConditions:
That pDC is not-NULL.
That the COwner object's tagP equals COwner::playC or COwner::playU.
That there is a valid (not-NULL) CBoard instance.
That there is a valid (not-NULL) CStone instance.
For each location in loc,
{
Get the board location's CRect.
DEBUG: Is the CRect not-empty (0,0,0,0)?
If the returned CRect is empty, skip this location.

Draw the CStone instance with the returned CRect and the COwner's res colour.
}


Step 10.
All that needs to be done to display the playing pieces is to;

A - Create private COwner objects in CGame for the Computer and the User.
B - Use the setUp() function for each object with the proper COwner::Player value for each.
C - Create an in-line function for CGame to set the colours for the COwner objects. My program set the colours for both COwner objects at the same time. You can try a different approach if you wish.
D - The CGame setColours() is called in the Document's constructor for the initial colour. The function is also called in each of the 'Change Colour' menu item actions.
E - Each COwner object is drawn after the CBoard instance in the CGame's drawing function.
F - (Optional.) A reset funtion can also be added to the CGame at this time, calling each of the COwner objects. This reset function is used in the Document's OnNewDocument(0 function.

Try running the program. The playing pieces should be of the colour that were specified, e.g. they should match the colours displayed in the CInfoDisplay legend. Also the pieces should be in the playing location that were programming in to the COwner reSet() function.

2006-04-18

Mu Torere, changing colours

Step 6.
Given that the program can display colours, might as well go ahead and show how to change those colours. But first, the menu
FileSettingsHelp
New gameUndo About Mu Torere...


  
ExitShow Colour Legend  
 
  
 Change Computer Colour...  
 Change User Colour...  
 
  
 AI LevelOff 
  
 
  Low 
  Medium 
  High 
  
 
  Custom AI Level... 


Step 7.
On changing the colour in the Document;
void CMuTorereDoc::OnSettingsChangeXxxx()
Create CColorDialog object.

Set dialog flags.
dlg.m_cc.Flags |= CC_FULLOPEN | CC_RGBINIT;
dlg.m_cc.rgbResult = ((CMuTorereApp*)AfxGetApp())->getColourXxxx();

If the colour dialog returns IDOK.
{
Set the game's colours.
Set the application's Xxxx colour.

Update View with colour hint.
}


In the View, the Update View function is as follows;
void CMuTorereView::OnUpdate(CView* /*pSender*/, LPARAM lHint, CObject* /*pHint*/)
If lHint equals the Document's colour hint.
{
Set the colours in the CInfoDisplay instance.
}

In all cases, Invalidate the window to re-draw.
In my program I set both the User and the Computer colours at the same time. For efficiency, a different hint from the Document should be used for each colour change.

The addition to the View's Draw function is minor (done after the game is drawn);
If the application's m_Display is true, draw the CInfoDisplay instance.

The CinfoDisplay is set up in the View constructor;
Go to the first element in the CInfoDisplay instance.

Add _T("Computer") and the application's computer colour to the first element.
Add _T("User") and the application's user colour the next element.

2006-04-17

Mu Torere, the Legend

The sole reason for the InfoDisplay class is to show to the user which colour belongs to which player. In this program, the class is draw directly on the window, over-lapping the playing field. If I ever figure out a way to allow a program to close a child modeless window, I'll post it but for now this is it.

A Singleton class, CInfoDisplay contains the following as variables;
variableinital valuecomment
static CString *labelsNULL 
static COLORREF *coloursNULL 
static CPoint topLeftCPoint(0,0)for positioning
static int counter0for the iterator
static int numLabel0 

There are some named constants used for spacing included as well as a limit to the number of array elements.

The functions that will be used are static CInfoDisplay* getInstance(const int& count) and static void Release(void) for the pattern (Note that getInstance requires a parameter), void Draw(CDC* pDC), CSize getLabelSize(CDC* pDC) const, static void First(void) (to reset counter to 0), bool setInfo(const CString& sLabel, const COLORREF& rgbColour), and bool setRGB(const int& index, const COLORREF& rgb). Also note that the setRGB() function requires a index. I would have been better not to depend on such things for synchronization.

Since the labels and the colours are in dynamic arrays, the destructor can be used for the clean-up.
destructor
CInfoDisplay::~CInfoDisplay(void)
If labels is not-NULL, delete it.
If colours is not-NULL, delete it.


The additional code in this class's getInstance involves creating the labels and colours arrays;
CInfoDisplay* CInfoDisplay::getInstance(const int& count)
Pre-condition;
That count is greater than 0.
If instance is NULL and count greater than 0
{
Create new CInfoDisplay and assign to instance.
(Strictly speaking if this fails, the function should return NULL.)

If count is greater than CInfoDisplay::MAX_LABELS, use CInfoDisplay::MAX_LABELS instead.

Try allocating CString[count] memory and assigning it to labels.

Try allocating COLORREF[count] memory and assigning it to colours.

If labels is not-NULL, assign _T(" ") to each element.
If colours is not_NULL, assign RGB(255,0,0), i.e. red, to each element.

Assign (0,0) to topLeft.
}

Return instance.

Note the use of _T() for the CString elements. This Microsoft macro handles the difference between UNICODE and ANSCII at compile-time.

For the iterator function;
bool CInfoDisplay::setInfo(const CString& sLabel, const COLORREF& rgbColour)
Pre-condition;
That instance is not-NULL.
That counter is greater or equal to 0 and less than numLabel.
That labels is not_NULL.
That colours is not-NULL.
If counter equals numLabel, then return false. // no more can be entered

Assign sLabel to labels[counter].
Assign rgbColour to colours[counter].

Increment counter.

Return true.


The index-based colour-setting function works as follows;
bool CInfoDisplay::setRGB(const int& index, const COLORREF& rgb)
Pre-condition;
That instance is not-NULL.
That colours is not-NULL.
That index is greater than or equal to 0 and less than numLabel.
Assign rgb to colours[index].

Return true.


The following function helps the program determine the size of all the text that will be drawn. For now mostly redundant, but if positioning the CInfoDisplay instance is being done it will allow the programmer to compensate for weirdness.
CSize CInfoDisplay::getLabelSize(CDC* pDC) const
Pre-condition;
That instance is not-NULL.
That numLabel is greater or equal to 0 and less than numLabel.
That labels is not_NULL.
That pDC is not-NULL.
Initialize the x and y values to return.

For each label,
{
Get the size of the text of the label. (via GetTextExtent())

If the label's x-extent is greater, assign the value to x.

If the label's y-extent is smaller than the rectangle used to display the colour (CInfoDisplay::CELL_SIZE)
{
Increment y by CInfoDisplay::CELL_SIZE.
}
else
{
Increment y by the label's y-extent.
}

If not at the last label
{
Increment y by CInfoDisplay::SPACING.
}

Return CSize(x,y).


Finally, for drawing the instance;
void CInfoDisplay::Draw(CDC* pDC)
Pre-condition;
That instance is not-NULL.
That labels is not_NULL.
That pDC is not-NULL.
Get the size of the text from getLabelSize().

Create a solid pen with the colour of the menu text.

Initialize dy with the BORDER value + PADDING value + topLeft.y.
For each label,
{
Draw the label.

Get the size of the label.
Adjust like the gatLabelSize() function.

Set up a square CELL_SIZE-by-CELL_SIZE separated from the label by SPACING.
Create a solid brush with the appropriate colour.
Draw the square.

Increment dv by the label height and by the SPACING.
}

Now draw the enclosing box. Remember that the box is reduced by the BORDER value, that topLeft positions the box, and that the box's width is expanded by SPACING and the colour display.

2006-04-16

Mu Torere, the Playing Board II

Step 4.
Once the Board class has been set-up, all that needs to be done is to pass the right values to the right functions in the View class. For the moment, create a Game class and two pass-through function for Draw(CDC* pDC) and getSize(void).
Note; if interested here are the in-line functions to specifically catch allocation errors wrt. Board class.
CBoard* getBoard(void)
Get pointer to CBoard instance.

ASSERT that pointer is not-NULL.

Return pointer.

All the second version of the getBoard() function is defined using const to allow the CGame's getSize() function to use it.

Step 5.
It is preferred to use similar pass-through functionality in the program's Document class. (With a private CGame variable.)

Step 6.
Since the View class's onDraw(CDC *pDC) only has one statement to be added, pDoc->Draw(pDC), we will go onto the OnInitialUpdate() function.
void CMuTorereView::OnInitialUpdate(void)
// from the development's' function wizard
Call CView::OnInitialUpdate().

Get the Board class's CSize from the Document.

Increase the retrieved size in the X direction by twice the window's frame.
Increase the retrieved size in the Y direction by the Window's frame + the height of the menu and the height of the titlebar.

Get the width of the desktop.
If the retrieved size is wider than the desktop, then limit the retrieved size's width.

Get the height of the desktop.
If the retrieved size is taller than the desktop, then limit the retrieved size's height.

AfxGetMainWnd()->SetWindowPos(NULL, 0, 0, tSz.cx, tSz.cy, SWP_NOMOVE |
SWP_NOZORDER);

Mu Torere, the Playing Board

Step 3,
As a starting point, the Board class is a good one. Once this class is set-up, the View can be set to right size and to draw. There are two of the class's functions right there, getSize(void) const const and Draw(CDC *pDC).

The Board class will use the Singleton pattern, so getInstance(void) and Release(void) will need to be added.
Other functions needed for the game are getPlace(const int&) const to return a rectangle for drawing playing pieces, whatIndex(const CPoint&) const for determining valid mouse clicks, and canMove(const int& from, const int& to) const for checking for valid neighbours.

In addition to the Singleton's instance, the Board class also needs an array for the program's playable CRect's, places[CBoard::NUM_PLACES].
The named constants are OFF_BOARD = -1 (a public constant, not really needed in the Board class but a habit), NUM_PLACES = 9 (private, used for readability), and BORDER = 20 (private).

Now all of the numbers used in this class were determined by hand because of the general awkwardness of calculating the positions of the 8 points of the star plus the indents. (Actually I use a positioning kluge to come up with the right numbers, but I will not subject the readers to that mess.)


Constructor
CBoard::CBoard(void)
places[0].SetRect(80,20,130,70);
places[1].SetRect(160,20,210,70);
places[2].SetRect(220,95,270,145);
places[3].SetRect(220,180,270,230);
places[4].SetRect(160,250,210,300);
places[5].SetRect(80,250,130,300);
places[6].SetRect(20,180,70,230);
places[7].SetRect(20,95,70,145);
places[8].SetRect(120,130,175,185);


For the Singleton pattern;

CBoard* CBoard::getInstance(void)
Is instance NULL?
{
Try allocating new CBoard instance.

If allocation fails, set instance to NULL.
}
Return instance.


void CBoard::Release(void)
Is instance not-NULL?
{
Delete instance.

Set instance to NULL.
}


For the View;
CSize CBoard::getSize(void) const
Pre-Condition:
instance is not-NULL.
Is Return CSize(right-most point+CBoard::BORDER,bottom-most point+CBoard::BORDER)


void CBoard::Draw(CDC* pDC)
Pre-Condition:
instance is not-NULL.
Create a solid brush with the colour of the window's background.
Create a pen with the colour of the menu's text.
(This should provide enough contrast.)

Select into drawing context the pen and brush.

Draw a circle in the rectangle given by place[8].

Move to center of place[0].
Draw line to (145,95).
Draw line to center of places[1].
Draw line to (185,120).
Draw line to center of places[2].
Draw line to (205,160).
Draw line to center of places[3].
Draw line to (185,205).
Draw line to center of places[4]
Draw line to (145,230).
Draw line to center of places[5].
Draw line to (105,205).
Draw line to center of places[6].
Draw line to (90,160).
Draw line to center of places[7].
Draw line to (105,120).
Draw line to center of places[0].

Select into drawing context the old pen and brush.

Note: As an alternative playing field, a 8-spoked wheel can also be draw. Just draw the center last to keep it from being over-drawn.