|
download Nim programma
Nim spel

Je speelt tegen de computer.
Per zet mag een "X" een willekeurig aantal kolommen naar links of naar rechts worden verschoven.
De stukken mogen elkaar niet kruisen.
Winnaar is degene die de ander klem zet.
Selecteer het aantal rijen door op een row knop te klikken.
Selecteer wie de eerste zet doet: jij of de computer.
Selecteer random (willekeurige) start positie of anders "O" links en "X" rechts uitgelijnd.
Muisklik op "new game" om de stukken "O" , "X" te plaatsen.
Muisklik op "start" om te beginnen.
Na de klik op "new game" staat het programma in de SETUP modus.
Stukken mogen dan worden verplaatst om een nieuwe beginstand te creeëren.
Een linker muisklik verplaatst de "X" naar het aangeklikte veld.
Een rechter muisklik verplaatst de "O".
Na de muisklik op "start" verplaats je een blauwe "X" door een muisklik op het veld van bestemming.
Nim games in het algemeen
Nim games zijn bordspelen die er uiterlijk heel anders kunnen uitzien
maar die dezelfde strategie, eigenschappen en onderliggende wiskunde bezitten.
Twee spelers verschuiven of verwijderen om beurten stukken (stenen) tot een speler niet meer kan zetten.
Hieronder staan drie typen Nim spelen afgebeeld:
Fig1a
Spelers verplaatsen om beurten de steen horizontaal naar links of vertikaal omlaag.
Winnaar is wie de steen in het groene veld linksonder plaatst.
De positie van de steen is (3,2).
(0,0) is de winnende positie.
Fig1b
Spelers pakken om beurten een willekeurig aantal stenen van een kolom.
Winnaar is wie de laatste stenen pakt.
Spel positie is hier (3,1,2), het aantal stenen per kolom geteld van links naar rechts.
Fig1c
Spelers (rood, blauw) verschuiven om beurten een steen van hun kleur een aantal kolommen links of rechts.
De stukken mogen elkaar niet kruisen.
Winnaar is wie de ander klem zet.
De spelpositie is (2,3,1), het aantal lege kolommen tussen de stenen van elke rij.
Opmerking: Spel a is twee dimensionaal. Spelen twee en drie zijn beide drie dimensionaal.
Elk aantal dimensies (vanaf twee) is mogelijk.
Nim game eigenschappen
Er zijn twee spelers. In dit project is één speler de computer.
Een bordpositie is altijd goed of slecht.
Een bordpositie is goed als er alleen zetten zijn die een slechte positie opleveren.
Een bordpositie is slecht als er een zet mogelijk is naar een goede bordpositie.
Een goede bordpositie is natuurlijk (0,0,0) in het geval van een driedimensionaal spel.
Dus posities (5,0,0) of (0,3,0) zijn slecht want met één zet is positie (0,0,0) te bereiken.
Goed posities zijn verder (1,1,0) , (1,0,1) en (0,1,1) omdat ze twee zetten van (0,0,0) verwijderd zijn.
Dit Nim game programma gebruikt type c met 12 kolommen en selecteerbare rijen van 2 tot 5.
Om alle goede bordposities te vinden heb ik een programma geschreven (NimResearch).
Het Nimresearch project
Het volgend plaatje toont output van het nimresearch programma:

64 goede posities zijn gevonden.
De cijfers geven per rij het aantal lege velden aan tussen de stukken.
Het Nimresearch programmma berekent goede posities op een numerieke manier.
Beginnend bij (0,0,0) is de volgende goede positie twee zetten hiervan verwijderd.
Dat levert posities (0,1,1) (1,0,1) (1,1,0)
(posities 1 zet verwijderd van een goede worden simpelweg genegeerd)
Array counter[1..5] {maximaal 5 rijen } telt de kolommen in elke rij.
De kolommen teller wordt met één verhoogd door:
function CounterInc : boolean;
var carry,i : byte;
begin
carry := 1;
for i := 1 to rowcount do
begin
counter[i] := counter[i] + carry;
if counter[i] < columns then
carry := 0 else counter[i] := 0;
end ;
result := carry = 0;
end;
De volgende procedure test elke stand van de kolommen teller op goed:
procedure TestPoint;
var i : word;
d,j : byte;
begin
i := 0;
repeat
inc(i);
j := 0;
d := 0;
repeat
inc(j);
if counter[j] <> GoodPoints[i,j] then inc(d);
until j=rowcount;
until (d=1) or (i=GPX);
if d <> 1 then savePoint;
end;
Opmerking: GPX is het aantal goede posities opgeslagen in array GoodPoints[..,..].
Conclusie
Na bestudering van de Nimresearch output zien we de simpele truuk:
om te zien of een bordpositie goed is:
- schrijf je de positie (X,Y,Z) binair uit
- een positie is goed als geldt: X xor Y xor Z = 0.
Een analytisch bewijs
Schrijf de bord posities als groepen (G0,G1,G2...)
G0 : 0,1 positie in (2*2) groep
G1 : 0,1 positie in (4*4) groep...
G2 : 0,1 positie in (8*8) groep etc.
Voor een twee dimensionale G0 groep zijn (0,0) en (1,1) goede posities.
Maar voor G1, G2.. groepen geldt hetzelfde, ook hier zijn (0,0,) en (1,1) goede posities.
Voor een goede positie moeten alle groepen goed zijn.

Decimaal geteld is positie (6,6) binair (110,110) goed.
G0=(0,0) G1=(1,1) G2=(1,1) allemaal goede groepen.
Positie (2,6) binair (010,110) is fout omdat G0=(0,0) G1=(1,1) G2(0,1)
G2 is fout (links boven 4*4 groep)
In drie dimensies is de winnende positie (0,0,0).
In de 2*2*2 groep G0 zijn goede posities (1,1,0) (1,0,1) en (0,1,1).
Maar opnieuw, dit geldt ook voor de G1, G2, G3 ..groepen.
Is (4,5,3) a goede positie?
(4,5,3) is binair (100,101,011)
G0 = (0,1,1) OK
G1 = (0,0,1) fout
G2 = (1,1,0) OK conclusie: foute positie.

Volgende goede postis zijn te maken door optellen van goede groepen:
Als G0 = (1,1,0) en G1 = (0,1,1) dan
G0 + G1 = (.1, .1,.0) + (0.,1.,1.) = (01,11, 01) decimaal (1,3,2)
Opmerling: shift voor het optellen G1 1 plaats naar links, G2 2 plaatsen etc.
Het Nim project
Data formats
var GPos : array[1..2,1..5] of byte; //1,2:computer,player; 1..5: row; value is column
Game : array[1..5,1..12] of byte; //0:empty; 1:computer; 2:player
rowcount : byte = 2;
Gmap : Tbitmap;
CHmap : array[0..2] of Tbitmap;
movebusy : boolean; //prevents interruption of moving
//stone
Het tekenen
Velden tellen 40*40 pixels.
De randen van het spel worden getekend op het canvas van form1.
Alle tekenwerk gebeurt op het canvas van bitmap Gmap.
Gedeelten van Gmap worden vervolgns gekopieerd naar paintbox1 op form1.
Dat om hinderlijk geknipper te vermijden wat zou ontstaan bij het wissen van paintbox1.
Bij de zetten van de "O" en "X" stukken glijden ze pixel voor pixel naar hun bestemming.
Een stuk schuift per 3 milliseconde een pixel op. De CPU klok controleert dit.
De Windows milleseconde klok kan dit niet omdat deze maar per 15 milliseconden ophoogt.
Zie de clock_unit voor details.
De "O" en "X" stukken zijn bij de start van het programma geteknd in bitmaps CHmap[1] en CHmap[2].
Gmap.canvas.draw(x,y,CHmap[2] ) plaatst een "X" op plek (x,y) van Gmap.
Ook is er een lege bitmap CHmap[0] gecreëerd om stukken te wissen.
De schuivende zetten zijn het werk van
procedure moveto(p,row,column : byte);
//move piece p of row to column
var x,destx,y : word;
dx : shortInt;
r : Trect;
begin
movebusy := true;
if Gpos[p,row]-column > 0 then dx := -1 else dx := 1;
destX := (column-1)*40;
x := (Gpos[p,row]-1)*40;
y := (row-1)*40;
while destX <> x do
begin
clearField(x,y);
x := x + dx;
Gmap.Canvas.Draw(x,y,CHmap[p]);
r := rect(x-1,y,x+41,y+40);
form1.PaintBox1.Canvas.CopyRect(r,Gmap.Canvas,r);
delay(3);
end;
Gpos[p,row] := 0;
Game[row,column] := 0;
Gpos[p,row] := column;
Game[row,column] := p;
end;
p = 1 bij een computerzet, p = 2 voor een zet van de speler.
Rijen en kolommen tellen 1..5 en 1..12
dx = 1 voor een zet naar rechts, dx = -1 voor een zet naar links.
destX is x coordinaat van de (kolom) bestemming.
Y is de y coordinaat van de rij..
Procedure clearField[x,y] wist een 40*40 veld en herstelt overschreven lijnen.
Berekening van de Computerzet
procedure computerMove;
var d : array[1..5] of byte;
dd : array[1..5] of byte;
col,row,i,newColumn,sum : byte;
OK : boolean;
begin
row := 0;
newColumn := 0;
for i := 1 to rowcount do d[i] := Gpos[2,i] - Gpos[1,i] - 1;
sum := 0;
for i := 1 to rowcount do sum := sum xor d[i];
if sum = 0 then
begin
col := 0;
row := 0;
for i := 1 to rowcount do
if Gpos[2,i] > col then
begin
row := i;
col := Gpos[2,i];
end;
NewColumn := 1 + random(col-2);
if NewColumn = Gpos[1,row] then inc(newColumn);
end
else begin
for i := 1 to rowcount do dd[i] := d[i] xor sum;
i := 0;
repeat
inc(i);
OK := dd[i] < d[i];
until OK or (i = rowcount);
if OK then
begin
newColumn := Gpos[2,i] - dd[i] - 1;
row := i;
end;
end;
moveBusy := true;
delay(500);//msecs
moveto(1,row,NewColumn);
gamecontrol(gmCompMoved);
end;
Het aantal lege velden tussen "O" en "X" stukken worden opgeslagen in array d[ ].
Variabele sum is de xor van d[1..rowcount] .
Bij sum = 0 is geen goede zet mogelijk en de computer doet dan een willekeurige zet.
Anders wordt een goede zet berekend waardoor sum=0 wordt.
Dat gaat zo: dd[1..rowcount] := d[1..rowcount] xor sum.
Na keuze van een geschikte rij is newColumn := column – dd[..] – 1;
Spel controle
type TgameState = (gsIdle,gsSetUp,gsPlayer,gsComp);
TgameMessage = (gmInit,gmNew,gmStart,gmPlayerMoved,gmCompMoved);
var gamestate : TGameState = gsIdle;
Gamestate
- idle : geen spel of geïndigs spel
- setUp : spel geselecteerd, wijziging beginstand mogelijk
- Player : wacht op zet van de speler
- Comp : computerzet is bezig
Messages naar procedure GameControl(gm : TGameMessage); controleren het spel.
Message
- Init : als het spel wordt gestart.
- new : knop ingedrukt
- start : the knop ingedrukt
- playermoved : de speler heeft een zet gedaan
- compmoved : de computer heeft een zet gedaan
De clock unit
unit clock_unit;
interface
uses windows;
procedure startClock;
function getCPUtime : single;
implementation
var t1,t2 : int64;
frequency : single; //millisecs
function CPUCLOCK : Int64;
asm
RDTSC;
end;
procedure initclock;
var a : longword;
begin
a := gettickcount;
repeat
until gettickcount > a;
t1 := CPUCLOCK;
a := a + 500;
repeat
until gettickcount >= a;
t2 := CPUCLOCK;
frequency := (t2 - t1)*0.002;
end;
procedure startClock;
begin
t1 := CPUclock;
end;
function GetCPUtime : single;
begin
t2 := CPUCLOCK;
result := (t2-t1)/frequency;
end;
initialization
initclock;
end.
De RDTSC assembler code plaatst de 64 bit CPU clock counter in 32bit registers edx en eax.
Wacht daarna voor een update van de windows (milliseconde) klok.
Tel dan het aantal CPU cycles gedurende 500 milliseconden.
De klokfrequentie is CPU cycles * 0.002.
Verstreken tijd wordt geleverd door function GetCPUtime : single;
Hierbij eindigt de beschrijving vn hit Nim game project.
Voor details verwijs ik naar de source code.
Interesse in dit (Delphi7) project?
download nim game source code
download nimresearch program
download nimresearch source code
|
|