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.