The following files have been attached to this tutorial:
This tutorial hasn't been translated.
This tutorial is licensed under CC BY 4.0. Please refer to the license text if you wish to reuse, share or remix the content contained within this tutorial.
UPDATE UPDATE: there's actually a major bug in the plugin. See here for details and a substitution: Part 2.1
UPDATE: since part-2 was released, there have been two bugs found. I've had to rewrite this part of the tutorial to fix these issues. I'm now taking advantage of the built-in Function plugin which, if I had this in the part-1 tutorial, would have made the complete tutorial simpler to follow, as I now have to move things around.
Start with the previous tutorial's CAPX file: Part 1
In the layout, insert new item BHT Tic-Tac-Toe AI.
In the layout, add two instance variables to the Tile sprite. Name them BoardX and BoardY. Also, change the font size on LogText to 6, as we will be added more logging.
Insert the General:Function object.
In the Event sheet, at event (2), set the new instance variables as follows:
At this point we need to add two functions as we need to call the code several times, and a function is the perfect mechanism for this.
At the end of the event sheet, Add Event: Function ->On function. Name it "LogArray". Now copy the logging code at events 10 & 11 to this function. You can't copy as a sub-event, so with the copied events still selected drag them right onto the function, and they will move under the function. Change event 10 from Set to Append: "--" & Function.Param(0) & "--" & newline
Now Add Event: Function->On function. Name it "CheckForWinner". Now, this is a bit cumbersome, but we want to move our check-for-winner logic at Event 12 to our function, as we need to call it once after the player moves, and then again after the computer moves. We need to juggle a bit here, so on "CheckForWinner", Add->Add blank sub-event. This gives us somewhere to add our local varibles. (C2 won't let us copy the current locals, so we need to redo this step). Remake the three "Current" local variables on the blank event: CurrentA, CurrentB, CurrentC.
Now we can actually copy the Event 12 code (select on the very left of the event to get the sub-events also), and paste on the blank event. Delete the blank event.
At event (7), Mark an X – Board: Is CurrentTurnIsX, we add the extra action: BHTTicTacToeAI:Store a move:Move Player X:index=Board.UID2LY(Tile.UID)*3 + Board.UID2LX(Tile.UID). Since the AI uses the 0-8 indexing, we convert our XY coordinates from the Board object in two-dimensions to one dimension, by multiplying Y by 3 to get the row offset and adding X to get the column offset.
At event 10, we want to use our new functions to call our code that we need to repeat for the player's move, and the computer's move.
Delete the action: LogText:Set text to "". Add action: Function:Call function:"LogArray", and add parameter "player". Add action: Function:Call function:"CheckForWinner". (We don't need parameters).
Select event 11, and Toggle disable. It isn't needed any more, but deleting it would move the event numbers around, and I'm trying to avoid this for the sake of the tutorial.
To automate Player O, we need a new event between events 11 and 12. Select event 11, right-click that event and do an Insert new event below. Select Board:Is boolean instance variable set. Choose CurrentTurnIsX, but immediately alter it with an Invert on the condition. We are checking that it is NOT X’s turn. Add a second condition: GameDone=0. This stop play if the game is over (X wins before).
Create a local variable on our new event: PlayerOMove. Add action: System:Set value:PlayerOMove=BHTTicTacToeAI.GetMoveO.
Create a new sub-event under (12) Board… System:Compare variable:PlayerOMove >= 0
Now we store the move. Add a new action, BHTTicTacToeAI:Store a move:Move Player O=PlayerOMove.
Create two local variable on this new event: tileX, tileY. Add action tileX=PlayerOMove%3, and tileY=int(PlayerOMove/3). This just converts our 0 to 8 index to two dimensions again. "%" is the modulus operator which gives the remainer of a division, that is, 7 % 3 is 1, since 7/3 is the same as (6 + 1)/3. We throw away the part that is a multiple of 3 and just care about the remainder, 1. This gives us our X index, and the integer value of the division by 3 is our Y, so from single to double to single we get:
(1,2) is row 2, column 1, converted to one dimension is 2*3 + 1 = 7. 7%3 is 1, and int(7/3) = 2, or (1,2) again. (Again, to get back to our human readable grid, add one to the PlayerOMove to get our 1-9 scheme, from the grid in Part 1).
Now that we have the correct coordinates for our Board object, we create a new chess piece. To keep our local CurrentPlay array up to date, we set its value to 2, the O player’s value. And we toggle our current player again.
Now select event 13, and Insert new event below:System:Pick All:Tile. Add a second condition: System:For each:Tile. On event 14, add a sub-event: Tile:Compare instance variable:BoardX=tileX. Add a second condition: Tile:Compare instance variable:BoardY=tileY. Add an action: Tile:Set visible:Invisible. This marks that tile as used, and can't be played.
We now add one more event to check if Os move wins the game. Select event 12, and do an Insert new event below. C2 doesn't let us add a blank peer event, so we just create any event for now:System:Every X seconds:1.0. Select the condition (not the event) and delete. Add action: Function:Call function:"LogArray", Add parameter: "computer". Add action: Function:Call function:"CheckForWinner".
Now select the check-for-winner event below and Toggle disable, as we don't need this anymore.
You can now play against the computer, using an AI algorithm. I'll discuss the algorithm next.