Whats wrong with my code? (Minmax algorithm)

Advanced game related topics
Hydrate
Enthusiast
Enthusiast
Posts: 436
Joined: Mon May 16, 2005 9:37 pm
Contact:

Whats wrong with my code? (Minmax algorithm)

Post by Hydrate »

I am trying to use the minmax algorithm to code a tic tac toe game, the following code is what im using:

Code: Select all

Procedure Solver(Turn)
Protected x
Protected y
Protected Decider
For x=1 To 3
For y=1 To 3
If BoardPos(x,y)=2
BoardPos(x,y)=Turn
Decider=Judge()
If Decider=2 : Score+1 : BoardPos(x,y)=0
ElseIf Decider=1 : Score-1 : BoardPos(x,y)=0
Else
If Turn=1 : Turn=2 : Else : Turn=1 : EndIf
Score+Solver(Turn)
EndIf

EndIf
Next y
Next x
Turn=2
ProcedureReturn Score
EndProcedure

Macro BotCode()
Score(1,1)=0 : Score(1,2)=0 : Score(1,3)=0 : Score(2,1)=0 : Score(2,2)=0 : Score(2,3)=0 : Score(3,1)=0 : Score(3,2)=0 : Score(3,3)=0

If Turn=2
For  x=1 To 3 : For y=1 To 3
If BoardPos(x,y)=0
BoardPos(x,y)=2
WinOrNot=Judge()
If WinOrNot=2 : Score+1 : BoardPos(x,y)=0
ElseIf WinOrNot=1 : Score-1 : BoardPos(x,y)=0
Else : BoardPos(x,y)=0
Score(x,y)=Solver(1)
EndIf
EndIf
Next y : Next x
EndIf

Highestscore=0
For  x=1 To 3 : For y=1 To 3 : If Score(x,y)>Highestscore : Highestscore=Score(x,y) : EndIf : Next y : Next x
If Highestscore<>0
For  x=1 To 3 : For y=1 To 3 : If Highestscore=Score(x,y) : BoardPos(x,y)=2 : x=3 : y=3 : EndIf : Next y : Next x
EndIf
Turn=1
EndMacro
The only issue is that it doesnt work at all, in theory is should, its not, can anyone explain why? (PB4)
.::Image::.
User avatar
Fluid Byte
Addict
Addict
Posts: 2336
Joined: Fri Jul 21, 2006 4:41 am
Location: Berlin, Germany

Post by Fluid Byte »

I am trying to use the minmax algorithm to code a tic tac toe game, the following code is what im using:
What exactly you mean by a "minmax algorithm" ?
The only issue is that it doesnt work at all, in theory is should, its not, can anyone explain why?
Theory? So you didn't even tested this code once in PB yet? You have written that all from scratch? So it's not like a code excerpt from a project you actually working on?

Well, I just tried to run this code but it's missing either some arrays, functions or linked lists. If you want us to fully check the code we need the complete source, or at least the part that lets you run it without compiler errors.

I could temporary replace the missing elements on a random basis, just by guessing but this would lead nowhere.
Dare
Addict
Addict
Posts: 1965
Joined: Mon May 29, 2006 1:01 am
Location: Outback

Post by Dare »

Fluid Byte wrote:Theory? So you didn't even tested this code once in PB yet?
Hi Fluid Byte,

First, welcome to the forums.

Fluid Byte, based on your other posts, you sound like a professional or at least a competent programmer and, no doubt, when you start making some code contributions and providing coded help, we will see the reality of that.

Not everyone using the forums is a professional or full time coder (I am not speaking for Hydrate here, just in general) and these forums are the home of many people. Some people are [teachers / surgeons / mechanics / business owners / other] and are experts in [Educating your kids / saving your life / keeping your car roadworthy / employing you / other].

(I am sure that if asked on a relevant forum for that discipline, these people would be happy to clarify questions on things like overhead cams in an old car, a lesser trochanter in anatomy or a particular form for the taxman - even if the question was couched in tyro-talk).

So the forum caters for everyone from the uber-geek to the dabbler. That is one of the strengths of the forum (and indeed of PureBasic). Nobody needs to prove anything, everyone here is good in some discipline in life and nearly everyone here uses PureBasic.

Your replies are formulated in such a way as to suggest impatience and come across as put downs. This is probably not intentional, however it is the way it appears. Perhaps something a little softer, a little more verbose (but perhaps not as verbose as this :)) and a little less terse would, if coupled with genuine technical expertise, make you a hero here rather than an irritant. Maybe think a bit about your delivery when you post, it will make a huge difference to your (and our) experiences when using the forums.

Thanks.
Dare2 cut down to size
User avatar
Michael Vogel
Addict
Addict
Posts: 2811
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Post by Michael Vogel »

Back to the code question...

...missing also some additional informations, especially...

>>> what values will be returned from the Judge() routine...
>>> it would also help to understand, how to interprete cerain values for te BoardPos (see Enumeration)

Code: Select all

#Boardsize=3
Enumeration 0
#Empty
#Player
#Computer
EndEnumeration

Global Dim BoardPos(#BoardSize,#BoardSize)

Procedure Solver(Turn) 
Protected x 
Protected y 
Protected Decider 
For x=1 To #BoardSize 
For y=1 To #BoardSize 
If BoardPos(x,y)=2 ; Player or Computer ? 
BoardPos(x,y)=Turn ; what means Turn ?
Decider=Judge() ; oops - what will be returned here ?
Select Decider
Case 1
  Score-1 : BoardPos(x,y)=#Empty 
Case 2
  Score+1 : BoardPos(x,y)=#Empty; Right ?
Default
  Turn=3-Turn
EndSelect
Score+Solver(Turn) 
EndIf 

Next y 
Next x 
Turn=2 
ProcedureReturn Score 
EndProcedure 
 
User avatar
Fluid Byte
Addict
Addict
Posts: 2336
Joined: Fri Jul 21, 2006 4:41 am
Location: Berlin, Germany

Post by Fluid Byte »

@ Dare: I could go deeply into that issue and prepare a long answer but honestly, it's not worth it and also the wrong place for this. Just lemme tell you this. It's completely irrelevant for me if someones pro or noob as well if you put myself into one of those categories. I just expect if someones posting here that he's exactly aware of his problem and is able to articulate a precise question or description of his situtation.

Just look at Michael's post above, that's what I'm talking about. It's not about coding skills but more the lack of common sense that maybe makes me sound harsh sometimes.

@ Hydrate: If your code is just a excerpt from a project you working on it would be really helpful if could post the complete piece here. Otherwise it will be hard to help you. You could also explain what kind algorithm/routine you need so we can come up with some examples.
User avatar
Demivec
Addict
Addict
Posts: 4270
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Post by Demivec »

Hydrate wrote:The only issue is that it doesnt work at all, in theory is should, its not, can anyone explain why? (PB4)
What determines whether it "works?" It's hard to explain "why" when no example has been given that it does not work, nor what it's expected to do, nor the results of execution.

Here's a stab, assuming you've posted all code.

- You don't reference the BotCode Macro.
- You don't define Judge().
- Assuming the BotCode Macro is the only code which executes with Solver(), "turn" is not initialized (it would start as being equal to zero) and after executing BotCode Macro "turn" would equal one. If Execution of BotCode is then repeated the results would be the same because "turn" would never equal two and thus the Solver() would never be executed, thus no changes in BoardPos(x,y) or Score(x,y).


Demivec
"Good luck with your homework."
Hydrate
Enthusiast
Enthusiast
Posts: 436
Joined: Mon May 16, 2005 9:37 pm
Contact:

Post by Hydrate »

Thank you for the help so far guys, i have taken alot of it in and im still trying, but still no sucess. This is my current code:

Code: Select all

; -----------------------
; Tic Tac Toe
; -----------------------
; Structure Dec
Structure MainWindow;
  lx.l;
  ly.l;
  lWidth.l;
  lHeight.l ;
  sTitle.s;
  lParams.l;
  lWID.l;
  lHwnd.l;
  lGdgtLst.l;
  lTurn.l;
  lMoves.l;
EndStructure;
Define Windows.MainWindow;

Declare CheckWin(*WindStruct.MainWindow);
Declare MaxMove(*WindStruct.MainWindow,lTurn.l,lScore.l);
Declare MakeMove(*WindStruct.MainWindow);
Declare MainWindowGadgets(*WindStruct.MainWindow);
Declare MWGadgetEvents(*WindStruct.MainWindow);
Declare MainWindowLoop(*WindStruct.MainWindow);

Global Dim BoardPos(8);
Global Dim BoardScore(8)

For v=0 To 8;
  BoardPos(v)=0;
  BoardScore(v)=0;
Next v;

Procedure CheckWin(*WindStruct.MainWindow);
  Protected lScore.l=0;
  Protected lWin.l=0;
  
  For v=0 To 2;
    lScore.l+BoardPos(v);
  Next v;
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  For v=3 To 5;
    lScore.l+BoardPos(v);
  Next v;
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  For v=6 To 8;
    lScore.l+BoardPos(v);
  Next v;
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  ; ----------------------------------------------------------
  lScore.l+BoardPos(0)+BoardPos(3)+BoardPos(6);
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  lScore.l+BoardPos(1)+BoardPos(4)+BoardPos(7);
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  lScore.l+BoardPos(2)+BoardPos(5)+BoardPos(8);
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  ; ----------------------------------------------------------
  lScore.l+BoardPos(0)+BoardPos(4)+BoardPos(8);
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  lScore.l+BoardPos(2)+BoardPos(4)+BoardPos(6);
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  If lWin<>0;
    
  Else;  
   If *WindStruct\lMoves=9;
     lWin=4;
   EndIf;
  EndIf;
  ProcedureReturn lWin;
EndProcedure;

Procedure MaxMove(*WindStruct.MainWindow,lTurn.l,lScore.l);
  Protected lBestScore.l=0;;
  Protected Dim BoardScore2(8);
  For v=0 To 8;
    If BoardPos(v)=0;;
      BoardPos(v)=lTurn;
      If 6+CheckWin(*WindStruct.MainWindow)=3
        BoardScore(v)=lScore*2;
      EndIf;
      If BoardScore2(v)=0;
        BoardPos(v)=-lTurn;
        If CheckWin(*WindStruct.MainWindow)=3
          BoardScore2(v)=lScore*2;
        EndIf;
      BoardPos(v)=lTurn;
      EndIf;
      If BoardScore2(v)=0;
        BoardScore2(v)=MaxMove(*WindStruct.MainWindow,-lTurn,lScore/2);
      EndIf;
      BoardPos(v)=0;
    EndIf;
  Next v;
  For p=0 To 8;
    If BoardScore2(p)<lBestScore;
      lBestScore=BoardScore2(p);
    EndIf;
  Next p;
  ProcedureReturn lBestScore;
EndProcedure;

Procedure MakeMove(*WindStruct.MainWindow);
  Protected lBest.l=-1;

  For v=0 To 8;
  BoardScore(v)=0;
    If BoardPos(v)=0;
      BoardPos(v)=-1;
      If 0-CheckWin(*WindStruct.MainWindow)=3
        BoardScore(v)=600;
      EndIf;
      If BoardScore(v)=0;
        BoardPos(v)=1;
        If CheckWin(*WindStruct.MainWindow)=3
          BoardScore(v)=600;
        EndIf;
      EndIf;
      BoardPos(v)=-1;
      If BoardScore(v)=0;
        BoardScore(v)+MaxMove(*WindStruct.MainWindow,1,300);
      EndIf;
      BoardPos(v)=0;
    EndIf;
  Next v;
  p=0;
  lBestScore=0;
  
  While p<8
    If BoardScore(p)>lBestScore
      lBestScore=BoardScore(p)
      lBest=p
    EndIf
    p+1
  Wend
  If lBest<>-1;
    BoardPos(lBest)=*WindStruct\lTurn;
    SetGadgetText(lBest,Str(*WindStruct\lTurn));
    DisableGadget(lBest,1);
  EndIf;
  
EndProcedure;

; Gadget Loading Proc
Procedure MainWindowGadgets(*WindStruct.MainWindow);
  *WindStruct\lGdgtLst=CreateGadgetList(*WindStruct\lHWnd); Declare gadget list
  ButtonGadget(0,5,5,30,30,"");
  ButtonGadget(1,40,5,30,30,"");
  ButtonGadget(2,75,5,30,30,"");
  ButtonGadget(3,5,40,30,30,"");
  ButtonGadget(4,40,40,30,30,"");
  ButtonGadget(5,75,40,30,30,"");
  ButtonGadget(6,5,75,30,30,"");
  ButtonGadget(7,40,75,30,30,"");
  ButtonGadget(8,75,75,30,30,"")
  ProcedureReturn *WindStruct\lGdgtLst; Return gadget list handle
EndProcedure;

; Gadget Handling Proc
Procedure MWGadgetEvents(*WindStruct.MainWindow);
  lEvGad=EventGadget();
  For g=0 To 8;
    If lEvGad=g;
      If GetGadgetText(g)="";
        BoardPos(g)=*WindStruct\lTurn;
        SetGadgetText(g,Str(*WindStruct\lTurn));
        DisableGadget(g,1);
      EndIf;
    EndIf;
  Next g;
EndProcedure;

; Procedures
Procedure MainWindowLoop(*WindStruct.MainWindow);
  Repeat;
  Select WindowEvent();
    Case #PB_Event_Gadget;
    If *WindStruct\lTurn<>-1;
      MWGadgetEvents(*WindStruct.MainWindow);
      *WindStruct\lTurn=-*WindStruct\lTurn;
      *WindStruct\lMoves+1;
      MakeMove(*WindStruct.MainWindow);
      *WindStruct\lTurn=-*WindStruct\lTurn;
      *WindStruct\lMoves+1;
    EndIf;
    lWinner=CheckWin(*WindStruct.MainWindow);
    If lWinner=3;
      Debug "Player one wins the game!";
    ElseIf lWinner=-3;
      Debug "Player two wins the game!";
    ElseIf lWinner=4;
      Debug "No Player wins, game draw!";
    Else;
      Debug "No Player wins.";
    EndIf;

    Case #PB_Event_CloseWindow;
    lQuit=1;
    
  EndSelect;
  Until lQuit=1;
EndProcedure;

; Main Program
With Windows;
  \lx = 50;
  \ly = 50;
  \lWidth = 85;
  \lHeight = 110;
  \sTitle = "Tic Tac Toe";
  \lParams = #PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_ScreenCentered;
  \lWID = OpenWindow(#PB_Any,\lx,\ly,\lWidth,\lHeight,\sTitle,\lParams);
  \lHWnd = WindowID(\lWID);
  \lGdgtLst = MainWindowGadgets(Windows);
  \lTurn=1;
  \lMoves=0;
  MainWindowLoop(Windows);
EndWith;
End;
As you can see it has come along a bit since before. I would like to clear things up:

Fluid Byte - The minmax algorithm is a game search tree algorithm(for the computer player), mostly used in games like connect 4, tic tac toe, and chess. I have tested this code in purebasic, i have been for a while.

The code above is the complete code so i hope its more helpful for you helping me.

Thank you for anyone that can help.
.::Image::.
GBeebe
Enthusiast
Enthusiast
Posts: 263
Joined: Sat Oct 09, 2004 6:52 pm
Location: Franklin, PA - USA
Contact:

Post by GBeebe »

Tested... Seems to work... But it's not difficult to beat the computer. what is the actual problem?
Derek
Addict
Addict
Posts: 2354
Joined: Wed Apr 07, 2004 12:51 am
Location: England

Post by Derek »

I go topleft, computer bottomleft, I go middle, computer doesn't seem to have a go so I go bottomright and win, computer goes topmiddle.

Seems to be a few problems in there.
Derek
Addict
Addict
Posts: 2354
Joined: Wed Apr 07, 2004 12:51 am
Location: England

Post by Derek »

Change the code as below

Code: Select all

  While p<8 <--------------------------------- <9
    If BoardScore(p)>lBestScore 
      lBestScore=BoardScore(p) 
      lBest=p 
    EndIf 
    p+1 
  Wend 
  If lBest<>-1; 
    BoardPos(lBest)=*WindStruct\lTurn; 
    SetGadgetText(lBest,Str(*WindStruct\lTurn)); 
    DisableGadget(lBest,1); 
  EndIf; 
Still not working right.
Derek
Addict
Addict
Posts: 2354
Joined: Wed Apr 07, 2004 12:51 am
Location: England

Post by Derek »

Code: Select all

Procedure MaxMove(*WindStruct.MainWindow,lTurn.l,lScore.l); 
  Protected lBestScore.l=0;; 
  Protected Dim BoardScore2(8); 
  For v=0 To 8; 
    If BoardPos(v)=0;; 
      BoardPos(v)=lTurn; 
      If 6+CheckWin(*WindStruct.MainWindow)=3 
        BoardScore(v)=lScore*2;  <---------------------------- here
      EndIf; 
      If BoardScore2(v)=0; 
        BoardPos(v)=-lTurn; 
        If CheckWin(*WindStruct.MainWindow)=3 
          BoardScore2(v)=lScore*2;   <------------------------ here 
        EndIf; 
      BoardPos(v)=lTurn; 
      EndIf; 
      If BoardScore2(v)=0; 
        BoardScore2(v)=MaxMove(*WindStruct.MainWindow,-lTurn,lScore/2); 
      EndIf; 
      BoardPos(v)=0; 
    EndIf; 
  Next v; 
  For p=0 To 8; 
    If BoardScore2(p)<lBestScore; 
      lBestScore=BoardScore2(p); 
    EndIf; 
  Next p; 
  ProcedureReturn lBestScore; 
EndProcedure; 
I'm getting better results if I change the second variable to boardscore() but its still not working 100%
User avatar
Demivec
Addict
Addict
Posts: 4270
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Post by Demivec »

Here is your code with modifications to accomplish your goal
All differences between this and your code are marked with "*" at the beginning of the comment portion and either the words "added", "changed", or "modified".

Code: Select all

; -----------------------
; Tic Tac Toe
; -----------------------
; Structure Dec
Structure MainWindow;
  lx.l;
  ly.l;
  lWidth.l;
  lHeight.l ;
  sTitle.s;
  lParams.l;
  lWID.l;
  lHwnd.l;
  lGdgtLst.l;
  lTurn.l;
  lMoves.l;
EndStructure;
Define Windows.MainWindow;

Declare CheckWin(*WindStruct.MainWindow);
Declare MaxMove(*WindStruct.MainWindow,lTurn.l,lScore.l);
Declare MakeMove(*WindStruct.MainWindow);
Declare MainWindowGadgets(*WindStruct.MainWindow);
Declare MWGadgetEvents(*WindStruct.MainWindow);
Declare MainWindowLoop(*WindStruct.MainWindow);

Global Dim BoardPos(8);
Global Dim BoardScore(8)

For v=0 To 8;
  BoardPos(v)=0;
  BoardScore(v)=0;
Next v;

Procedure CheckWin(*WindStruct.MainWindow);
  Protected lScore.l=0;
  Protected lWin.l=0;
  
  For v=0 To 2;
    lScore.l+BoardPos(v);
  Next v;
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  For v=3 To 5;
    lScore.l+BoardPos(v);
  Next v;
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  For v=6 To 8;
    lScore.l+BoardPos(v);
  Next v;
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  ; ----------------------------------------------------------
  lScore.l+BoardPos(0)+BoardPos(3)+BoardPos(6);
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  lScore.l+BoardPos(1)+BoardPos(4)+BoardPos(7);
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  lScore.l+BoardPos(2)+BoardPos(5)+BoardPos(8);
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  ; ----------------------------------------------------------
  lScore.l+BoardPos(0)+BoardPos(4)+BoardPos(8);
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  lScore.l+BoardPos(2)+BoardPos(4)+BoardPos(6);
  If lScore=-3 Or lScore=3;
    lWin=lScore;
  EndIf;
  lScore=0;
  If lWin<>0;
    
  Else; 
    If *WindStruct\lMoves=8 ;*changed, last move is 8, not 9
      lWin=4;
    EndIf;
  EndIf;
  ProcedureReturn lWin;
EndProcedure;

Procedure MaxMove(*WindStruct.MainWindow,lTurn.l,lScore.l);

  Protected lBestScore.l=10000;*changed, initialized to a HIGH value so that LOWEST value can be found
  Protected Dim BoardScore2(8);
  Protected v,P ;*added for clarity, now includes all variables
  
  *WindStruct\lMoves+1 ;*added, advance move count
  
  For v=0 To 8;
    If BoardPos(v)=0;;
      BoardPos(v)=lTurn;
      If CheckWin(*WindStruct.MainWindow)=3*lTurn ;*modified calculation to reflect which player's move is being evaluated,=current player would have a WIN for this move
        BoardScore2(v)=lScore*lTurn ;*changed apparent typo to "BoardScore2(v)" and modified calculation to reflect which player's move is being evaluated
      EndIf;
      If BoardScore2(v)=0 ;=initial value
        BoardPos(v)=-lTurn;
        If CheckWin(*WindStruct.MainWindow)=3*-lTurn;;*modified calculation to reflect which player's move is being evaluated,=other player would WIN if they took this move
          BoardScore2(v)=lScore*lTurn ;*modified calculation to reflect which player's move is being evaluated
        EndIf;
        BoardPos(v)=lTurn;
      EndIf;
      If (BoardScore2(v)=0) And (*WindStruct\lMoves<8) ;*changed, =no score yet and at least one more move left
        BoardScore2(v)=MaxMove(*WindStruct.MainWindow,-lTurn,lScore/2);
      EndIf;
      BoardPos(v)=0;
    EndIf;
  Next v;
  For P=0 To 8;
    If BoardScore2(P)<lBestScore;
      lBestScore=BoardScore2(P);
    EndIf;
  Next P;
  
  *WindStruct\lMoves-1 ;*added, restore move count
  
  ProcedureReturn lBestScore;
EndProcedure;

Procedure MakeMove(*WindStruct.MainWindow);
  Protected lBest.l=-1;
  Protected v,P,lBestScore ;*added for clarity, now includes all variables
  Protected NewList moveOptions.l() ;*added, this is used to track best move options
  
  For v=0 To 8;
    BoardScore(v)=2000 ;*changed, initialize to a HIGH value so that LOWEST value can be found
    If BoardPos(v)=0;
      BoardPos(v)=-1;
      If CheckWin(*WindStruct.MainWindow)=-3 ;*modified to simplify calculation
        BoardScore(v)=600;
      EndIf;
      If BoardScore(v)=2000 ;*changed, =initial value
        BoardPos(v)=1;
        If CheckWin(*WindStruct.MainWindow)=3
          BoardScore(v)=600;
        EndIf;
      EndIf;
      BoardPos(v)=-1;
      If (BoardScore(v)=2000) And (*WindStruct\lMoves<8) ;*changed, =no score yet and at least one more move left
          BoardScore(v)+MaxMove(*WindStruct.MainWindow,1,600) ;*changed value of lScore parameter to reflect and simplify calculations
      EndIf;
      BoardPos(v)=0;
    EndIf;
  Next v;

  P=0;
  lBestScore=10000 ;*changed, initialize to a HIGH value so that LOWEST value can be found
  
  While P<9 ;*changed to correct loop count to go from element 0 through 8
    If BoardScore(P)<lBestScore ;*changed from "is-greater" to "is-less-than"
      lBestScore=BoardScore(P)
      lBest=P 
    EndIf
    P+1
  Wend
  If lBest<>-1 ;
    For P=0 To 8 ;*added, create a list of equal value moves, all of best value
      If BoardScore(P)=lBestScore ;*added
        AddElement(moveOptions()) ;*added
        moveOptions()=P  ;*added
      EndIf  ;*added
    Next P  ;*added
    SelectElement(moveOptions(),Random(CountList(moveOptions()))) ;*added, select a random move from the list
    lBest=moveOptions() ;* ;*added, use random move as next move
    BoardPos(lBest)=*WindStruct\lTurn;
    SetGadgetText(lBest,Str(*WindStruct\lTurn));
    DisableGadget(lBest,1);
  EndIf;
  
EndProcedure;

; Gadget Loading Proc
Procedure MainWindowGadgets(*WindStruct.MainWindow);
  *WindStruct\lGdgtLst=CreateGadgetList(*WindStruct\lHwnd); Declare gadget list
  ButtonGadget(0,5,5,30,30,"");
  ButtonGadget(1,40,5,30,30,"");
  ButtonGadget(2,75,5,30,30,"");
  ButtonGadget(3,5,40,30,30,"");
  ButtonGadget(4,40,40,30,30,"");
  ButtonGadget(5,75,40,30,30,"");
  ButtonGadget(6,5,75,30,30,"");
  ButtonGadget(7,40,75,30,30,"");
  ButtonGadget(8,75,75,30,30,"")
  ProcedureReturn *WindStruct\lGdgtLst; Return gadget list handle
EndProcedure;

; Gadget Handling Proc
Procedure MWGadgetEvents(*WindStruct.MainWindow);
  lEvGad=EventGadget();
  For g=0 To 8;
    If lEvGad=g;
      If GetGadgetText(g)="";
        BoardPos(g)=*WindStruct\lTurn;
        SetGadgetText(g,Str(*WindStruct\lTurn));
        DisableGadget(g,1);
      EndIf;
    EndIf;
  Next g;
EndProcedure;

; Procedures
Procedure MainWindowLoop(*WindStruct.MainWindow);
  Protected lWinner,lQuit ;*added for clarity, now includes all variables
  
  lWinner=0 ;*added, initialized to show no winner
  Repeat;
    Select WindowEvent();
      Case #PB_Event_Gadget;
        If lWinner=0 ;*changed from "if *WindStruct\lTurn<>-1" and re-ordered conditionals
          MWGadgetEvents(*WindStruct.MainWindow) ;assumes a valid gadget is found
          lWinner=CheckWin(*WindStruct.MainWindow) ;*added, a check is needed after EACH player's move
          If lWinner=3
            Debug "Player one wins the game!"
          Else
            If lWinner=4
             Debug "No Player wins, game draw!"
            Else
              Debug "No Player wins."
              *WindStruct\lTurn=-*WindStruct\lTurn ;toggle turn indicator
              *WindStruct\lMoves+1 ;advance move count
              MakeMove(*WindStruct.MainWindow) ;make a move for player 2
              
              lWinner=CheckWin(*WindStruct.MainWindow)  
              If lWinner=-3
                Debug "Player two wins the game!"
              Else
                If lWinner=4
                  Debug "No Player wins, game draw!"
                Else
                  Debug "No Player wins."
                EndIf
              EndIf
              *WindStruct\lTurn=-*WindStruct\lTurn ;toggle turn indicator
              *WindStruct\lMoves+1 ;advance move count
            EndIf 
          EndIf
        EndIf
        
         
      Case #PB_Event_CloseWindow;
        lQuit=1;
        
    EndSelect;
  Until lQuit=1;
EndProcedure;

; Main Program
With Windows;
  \lx = 50;
  \ly = 50;
  \lWidth = 85;
  \lHeight = 110;
  \sTitle = "Tic Tac Toe";
  \lParams = #PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_ScreenCentered;
  \lWID = OpenWindow(#PB_Any,\lx,\ly,\lWidth,\lHeight,\sTitle,\lParams);
  \lHwnd = WindowID(\lWID);
  \lGdgtLst = MainWindowGadgets(Windows);
  \lTurn=1;
  \lMoves=0;
  MainWindowLoop(Windows);
EndWith;
End; 
Summary:
CheckWin() had 1 change
MaxMove() had 6 changes & 3 additions
MakeMove() had 8 changes & 1 additions (+8 additions if random move is implemented)
MainWindowLoop() had 1 change & 3 additions and some reorganizing.

Comments:
MaxMove() needed: to account for which future turn was being evaluated, to initialize the lBestScore variable to a high value instead of 0 (because if only positive values were found, we needed to find the LEAST positive), and eliminated evaluating more moves if we already made the last one.
MakeMove() needed: to initialize lBestScore to a high value (same as above), to initialize BoardScore() to a positive value between max (lBestScore) and highest positive score (600), to correct loop values for bestscore review and find the LOWEST score NOT HIGHEST score.
MainWindowLoop() needed: to check for a win after EACH move, reorganizing of conditionals.

In my opinion, for clarity all variables should be Protected in a procedure if any are. (these additions can be removed without any effect as the program is currently written though)
The random move option was added for the sake of interest (from a human's point of view). If the best scored move was equal to 300 then why not randomly select among all scores of 300 instead of only the first one? The 9 lines that add this option can be removed without any effect on the program's functions. It will simply make the game even more predictable.

I changed the program as little as possible so that you could see what was needed to correct the errors in the programs execution. You seem to complicate some things (such as windows) unnecessarily while leaving other things (such as Checkwin) overly simplified. I also noticed you used two different loops to examine scores (and made an error because of it) when the same loop would have worked for both (either both WHILE-WENDS or both FOR-NEXT) .

Merry Christmas! Now see if you can get the computer to learn that it can't expect to win at tic-tac-toe.

Here's a sample of an equivalent CheckWin() that's shorter but functionally the same (it has the added plus of discontinuing the checks when a tic-tac-toe has been found):

Code: Select all

Procedure CheckWin(*WindStruct.MainWindow) ;check if a winning position exists, return -3(player 2 win),3(player 1 win),0(no win), or 4(draw)
  Protected lScore.l
  Protected lWin.l=0;
  
  lScore=0+BoardPos(0)+BoardPos(1)+BoardPos(2) ;check top row
  If Abs(lScore)<>3
    lScore=0+BoardPos(3)+BoardPos(4)+BoardPos(5) ;check middle row
    If Abs(lScore)<>3
      lScore=0+BoardPos(6)+BoardPos(7)+BoardPos(8);check bottom row
      If Abs(lScore)<>3
        ; ---------------------------------------------------
        lScore=0+BoardPos(0)+BoardPos(3)+BoardPos(6) ;check left column
        If Abs(lScore)<>3
          lScore=0+BoardPos(1)+BoardPos(4)+BoardPos(7) ;check middle column
          If Abs(lScore)<>3
            lScore=0+BoardPos(2)+BoardPos(5)+BoardPos(8) ;check right column
            If Abs(lScore)<>3
              ; -----------------------------------------------
              lScore=0+BoardPos(0)+BoardPos(4)+BoardPos(8) ;check diagonal top-left to bottom-right
              If Abs(lScore)<>3
                lScore=0+BoardPos(2)+BoardPos(4)+BoardPos(6) ;check diagonal top-right to bottom-left
              EndIf
            EndIf
          EndIf
        EndIf
      EndIf
    EndIf
  EndIf
  
  If Abs(lScore)=3 ;=a win for player 1 or 2
    lWin=lScore ;=-3 for player 2, or 3 for a player 1
  EndIf
  If lWin<>0 ;=a win
  ElseIf *WindStruct\lMoves=9 ;=no moves left to be made
    lWin=4 ;if no then indicate a draw
  EndIf
  ProcedureReturn lWin
EndProcedure;
User avatar
Demivec
Addict
Addict
Posts: 4270
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

minimax method (spelled correctly)

Post by Demivec »

Here's a commented version of the previous code and some bug corrections that I neglected to post. I had it sitting around my hard drive and thought it might be of some benefit.




Code: Select all

;Originally coded by hydrate
; modified 12/1/2006 by Demivec to make mini-max method functional
; 

 -----------------------
; Tic Tac Toe
; -----------------------
EnableExplicit

#computerWin=-3 ;value for computer win (player #2)
#humanWin=3 ;value for human win (player #1)
#Win=3 ;count of squares in a row for a win
#gameDraw=4 ;value for a Draw (no win possible)
#maxValue=600 ;highest score value for a single move

;-Structures and globals
Structure MainWindow;
  lx.l ;window x,y position
  ly.l 
  lWidth.l ;window width,height
  lHeight.l 
  sTitle.s ;window title
  lParams.l ;parameters for window
  lWID.l ;window's ID#
  lHwnd.l ;handle for window
  lGdgtLst.l ;handle for window's gadget list
  lTurn.l ;indicates which player's turn is active (-1 for computer, 1 for human)
  lMoves.l ;# of moves taken in game (0-9)
EndStructure;

Define Windows.MainWindow;

Global Dim BoardPos(8) ;board locations
Global Dim BoardScore(8);evaluations

;-declarations
Declare CheckWin(*WindStruct.MainWindow);check if a winning position exists, return -3(computer win),3(human win),0(no win), or 4(draw)
Declare MaxMove(*WindStruct.MainWindow,lTurn.l,lScore.l,FutureMoveFlg.l=0) ;recursive, returns the score for the current and all future moves from current point,set FutureMoveFlg=-1 for current move
Declare MakeMove(*WindStruct.MainWindow) ;
Declare MainWindowGadgets(*WindStruct.MainWindow);Sets up gadgets for window WindStruct\lHwnd and returns handle of gadget list
Declare MWGadgetEvents(*WindStruct.MainWindow);Handle Gadgets (mark boardpos() and gadget selected) used
Declare MainWindowLoop(*WindStruct.MainWindow);

;----Procedures----------------
Procedure CheckWin(*WindStruct.MainWindow) ;check if a winning position exists, return -3(computer win),3(human win),0(no win), or 4(draw)
  Protected lScore.l
  Protected lWin.l=0;
  
  lScore=0+BoardPos(0)+BoardPos(1)+BoardPos(2) ;check top row
  If Abs(lScore)<>3
    lScore=0+BoardPos(3)+BoardPos(4)+BoardPos(5) ;check middle row
    If Abs(lScore)<>3
      lScore=0+BoardPos(6)+BoardPos(7)+BoardPos(8);check bottom row
      If Abs(lScore)<>3
        ; ---------------------------------------------------
        lScore=0+BoardPos(0)+BoardPos(3)+BoardPos(6) ;check left column
        If Abs(lScore)<>3
          lScore=0+BoardPos(1)+BoardPos(4)+BoardPos(7) ;check middle column
          If Abs(lScore)<>3
            lScore=0+BoardPos(2)+BoardPos(5)+BoardPos(8) ;check right column
            If Abs(lScore)<>3
              ; -----------------------------------------------
              lScore=0+BoardPos(0)+BoardPos(4)+BoardPos(8) ;check diagonal top-left to bottom-right
              If Abs(lScore)<>3
                lScore=0+BoardPos(2)+BoardPos(4)+BoardPos(6) ;check diagonal top-right to bottom-left
              EndIf
            EndIf
          EndIf
        EndIf
      EndIf
    EndIf
  EndIf

  If Abs(lScore)=3 ;=a computer or human win
    lWin=lScore ;=-3 for computer or 3 for human win
  EndIf
  If lWin<>0 ;=a win
  ElseIf *WindStruct\lMoves=9 ;=no moves left to be made
    lWin=#gameDraw ;if no then indicate a draw
  EndIf
  ProcedureReturn lWin
EndProcedure;

Procedure MaxMove(*WindStruct.MainWindow,lTurn.l,lScore.l,FutureMoveFlg.l=0) ;recursive, returns the score for the current and all future moves from calling point,set FutureMoveFlg=-1 for current move
  Protected lBestScore.l=10000 ;initialized to a high value
  Protected Dim BoardScore2(8)
  Protected v
  
  If FutureMoveFlg = 0 ;=looking at future turns, not current one
     *WindStruct\lMoves+1 ;advance move count
  EndIf
  
  For v=0 To 8 ;check each board location as a possible move amd score it
    BoardScore2(v)=10000
    If BoardPos(v)=0 ;=board position is not yet used
      BoardPos(v)=lTurn ;evaluate move if current player made it
      If CheckWin(*WindStruct.MainWindow)=#Win*lTurn ;=current player would have a win for this move
        BoardScore2(v)=lScore*lTurn ;score the move at this board location
      Else ;=no score yet
        BoardPos(v)=-lTurn  ;evaluate move if other player were allowed to make it
        If CheckWin(*WindStruct.MainWindow)=#Win*-lTurn ;=other player would WIN if they took this move
          BoardScore2(v)=lScore*lTurn ;score the move at this board location
        ElseIf *WindStruct\lMoves<9;=still no score yet and =not last move
          BoardPos(v)=lTurn ;mark location for current player
          BoardScore2(v)=MaxMove(*WindStruct.MainWindow,-lTurn,lScore/2) ;examine next player's move, but reduce it's weight-value
        EndIf
      EndIf
      BoardPos(v)=0 ;restore board location to unused state
    EndIf
    If BoardScore2(v)<lBestScore ;keep track of best-score as we go
      lBestScore=BoardScore2(v)
    EndIf
  Next v
  
  If FutureMoveFlg = 0 ;=looking at a future move
    *WindStruct\lMoves-1 ;restore move count
    ProcedureReturn lBestScore 
  Else ;return completed scores for all possible moves
    For v = 0 To 8
      BoardScore(v) = BoardScore2(v)
    Next 
    ProcedureReturn lBestScore
  EndIf
EndProcedure

Procedure MakeMove(*WindStruct.MainWindow) ;
  Protected lBest.l=-1
  Protected lBestScore.l=10000
  Protected p 
  
  lBestScore = MaxMove(*WindStruct.MainWindow,-1,#maxValue,-1) ;evaluate all possible current and future moves
  
  NewList bestPossibleMoves.l()
  For p = 0 To 8 ;build a list of the best moves
    If BoardScore(p) = lBestScore
      AddElement(bestPossibleMoves())
      bestPossibleMoves()=p
    EndIf
  Next p
  SelectElement(bestPossibleMoves(),Random(CountList(bestPossibleMoves())-1))
  lBest = bestPossibleMoves() ;select a random choice
  
  Debug lBest
  BoardPos(lBest)=*WindStruct\lTurn ;take move and record changes to board
  SetGadgetText(lBest,Str(*WindStruct\lTurn))
   
EndProcedure

Procedure MainWindowGadgets(*WindStruct.MainWindow) ;Sets up gadgets for window WindStruct\lHwnd and returns handle of gadget list
  *WindStruct\lGdgtLst=CreateGadgetList(*WindStruct\lHwnd); Declare gadget list
  ButtonGadget(0,5,5,30,30,"")   ;(0,0) [board position top-left]
  ButtonGadget(1,40,5,30,30,"")  ;(1,0)
  ButtonGadget(2,75,5,30,30,"")  ;(2,0)
  ButtonGadget(3,5,40,30,30,"")  ;(0,1)
  ButtonGadget(4,40,40,30,30,"") ;(1,1) [board position middle]
  ButtonGadget(5,75,40,30,30,"") ;(2,1)
  ButtonGadget(6,5,75,30,30,"")  ;(0,2)
  ButtonGadget(7,40,75,30,30,"") ;(1,2)
  ButtonGadget(8,75,75,30,30,"") ;(2,2) [board position bottom-right]
  ProcedureReturn *WindStruct\lGdgtLst; Return gadget list handle
EndProcedure;

Procedure MWGadgetEvents(*WindStruct.MainWindow);Handle Gadgets (mark boardpos() and gadget selected) used
  Protected lEvGad 
  
  lEvGad=EventGadget()
  If lEvGad>=0 And lEvGad<=8
    If GetGadgetText(lEvGad)=""
      BoardPos(lEvGad)=*WindStruct\lTurn ;record player's action on Board
      SetGadgetText(lEvGad,Str(*WindStruct\lTurn)) ;change gadget to show player's move
    EndIf 
  EndIf
EndProcedure

Procedure MainWindowLoop(*WindStruct.MainWindow);
  Protected lWinner,lQuit 
  
  lWinner=0 ;show no winner
  Repeat;
    Select WindowEvent();
      Case #PB_Event_Gadget ;=board interaction event
        If lWinner=0
          MWGadgetEvents(*WindStruct.MainWindow) ;check which gadget selected for players turn
          *WindStruct\lTurn=-*WindStruct\lTurn ;toggle turn indicator
          *WindStruct\lMoves+1 ;advance move count
          lWinner=CheckWin(*WindStruct.MainWindow)
          If lWinner=#humanWin
            Debug "Human wins the game!"
          ElseIf lWinner=#gameDraw
            Debug "No Player wins, game draw!"
          Else
            Debug "No Player wins."
            MakeMove(*WindStruct.MainWindow) ;make a move for computer
            *WindStruct\lTurn=-*WindStruct\lTurn ;toggle turn indicator
            *WindStruct\lMoves+1 ;advance move count
            lWinner=CheckWin(*WindStruct.MainWindow)  
            If lWinner=#computerWin
              Debug "Computer wins the game!"
            ElseIf lWinner=#gameDraw
              Debug "No Player wins, game draw!"
            Else
              Debug "No Player wins."
            EndIf
          EndIf
        EndIf
      Case #PB_Event_CloseWindow;
        lQuit=1;
        
    EndSelect;
  Until lQuit=1;
EndProcedure;

;-main code initializations
Define v 

For v=0 To 8; clear board info
  BoardPos(v)=0;
  BoardScore(v)=0;
Next v;

; Main Program
With Windows
  \lx = 50
  \ly = 50
  \lWidth = 85
  \lHeight = 110
  \sTitle = "Tic Tac Toe"
  \lParams = #PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_ScreenCentered
  \lWID = OpenWindow(#PB_Any,\lx,\ly,\lWidth,\lHeight,\sTitle,\lParams)
  \lHwnd = WindowID(\lWID)
  \lGdgtLst = MainWindowGadgets(Windows)
  \lTurn=1;
  \lMoves=0;
  MainWindowLoop(Windows)
EndWith
End
Post Reply