-=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- (c) WidthPadding Industries 1987 0|35|0 -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=-
SoCoder -> Article Home -> Coding Basics


 
Jayenkai
Created : 13 May 2009
 
Language : D

MidletPascal : Cookie : Tic-Tac-Toe part three

AI!

Note : These tutorials have been created for an "LG Cookie" centric forum, and as such are targetted at that phone.
They're also aimed at folk who may/may not be all that good at coding.
Only time will tell if folk are able to follow!!




Tic-Tac-tuTOErial-Three : The Hard Part

So far we've got a basic 2 player Tic-Tac-Toe game.
It wasn't really that hard.
A simple 2 dimensional array for the grid, a couple of pictured, and some simple logic to pass through the plays.

Today, we'll add the juicy bits!

Lets start with our good old friend, Mr Vibrate!
This is the easier part!




Wiggle it! (Just a little bit)

You should already have uses vibra; at the top of your project.
The Vibra library is there to give us a wiggle when we need it.
To vibrate, we simply use the Vibrate(n) function, where n is the number of seconds you want to vibrate for.

Here, we'll use vibrate when the player makes their move.
Simple stuff.

Inside That Big If thing, somewhere inbetween "Playground[x,y]:=1;" and "mudown:=1;", place a vibrate.
Vibrate(1);

That's it..
Simple!

If you compile and run, your emulator will go "meeep", and your phone will give a wiggle!




Midi GETS!!

OK, slightly harder.
We need a midi sequencer.
Having hunted high and low for the past few hours, I can't find a decent free one.
Personally, I've had a copy of Cakewalk Express since around 1999! Seems to still work, albeit getting a bit rough around the edges, nowadays! (8.3 filenames!? wtf!)
I went to the Cakewalk site, and.. there doesn't appear to be any actual Cakewalk anymore!!
I must be getting old!

Either way, you need to grab yourself something that can create midi files.
Cakewalk, Evolution Audio*, plenty of things.
(FFS, even Evolution Audio doesn't exist anymore!! I don't do much with midis, can you tell!?)

In fact, even Fruityloops has an "Export Midi" option.. well, as long as you've used midi instruments, anyway!!

So, whatever you've got, make yourself a nice "Plink" sound for Ecksy, and a "Plonk" for Ohsie, a couple of little jingles for the 2 winning things, and a Draw, too.
Can't manage?
Grab mine, here


Next, shove them into your project.. (Big green, Import, Change the file viewer to .mid format, get each one... You can select all, btw, to save you doing them one at a time...)

To play a midi file, there's a 3 or 4 line set of tasks to perform.
First, we need to stop anything else that's playing.
That's easy.. StopPlayer;
Next up, we open the midi file we need, and let it know that it's a midi.
OpenPlayer('MyTune.mid', 'audio/midi')
Then we set the count to 1. This is how many times the tune will play. Since we don't want it looping, set it to 1.
SetPlayerCount(1)
And, last, we start it. StartPlayer

Now, in a perfect world, all that would work.
But we're not in a perfect world, and as great as mobile phones are, they still can't quite hack it! (Come on, phone makers, it's nearly 2010! Surely you can manage this by now!!)
So, instead of just Doing things, when we deal with audio, we really need to double check EVERYTHING..

stopplayer;
if OpenPlayer('MyTune.mid', 'audio/midi') then begin
if SetPlayerCount(1) then begin
if StartPlayer then res:='';
end;
end;

^ don't put it in yet!!

Same set of commands, but every line is a check.
If OpenPlayer doesn't quite work, the rest won't happen.
Similarly, if it can't set it to 1 play, it'll stop.
And, well, the last bit's just a cheat, really, 'cos it's either that, or n:=StartPlayer, which looks weird!


That's quite neat, but we could do with something a bit neater.
See, if we keep shoving those 5 lines everywhere, our game will start to look a bit messy.
Instead, we create a Function. The function has a name, and inside the function is all of the play stuff. Then, rather than type all that play stuff, we just call the function, and it does it all for us.
Handy!

procedure Play(res:string);
begin
stopplayer;
if OpenPlayer(res, 'audio/midi') then begin
if SetPlayerCount(1) then begin
if StartPlayer then res:='';
end;
end;
end;

^ shove that, near the top, under where we put all the variables, but above the //Main Program.


Inside the function, we define res as a string. The string is used as the filename to load the midi, and then the midi gets going.

To call the player, all we need do is use
Play('/MyTune.mid');
And MyTune.mid will play. (assuming you got the case sensitivity right!)
Note, we also add "/" to the start, because the file's in the root of our structure.
*phew*
crazy!

Ok, head into the Big If again.
In between changing the grid, and switching the player number, we need to play the right tune.

if Player=1 then Play('/ecksy.mid');
if Player=2 then Play('/ohsie.mid');

^ in the big-if

Everything working?
Give it a whirl..
Compile, you should be able to tap, get a wiggle, play a tune, and then do the same for the other player!

You'll probably also notice the slight delay after every tap. That's the midi opener..
Kinda sucks..
Nothing we can do!



More Sounds

So, we got the tap sounds playing, should be easy enough to get the winner sounds to play, too.

The thing is, if we play when winner=1 or winner=2, then the tune will keep trying, and trying, and trying to play, since we're inside a bit loop.
What we need to do, is setup a trigger of sorts, so that the tune only starts to play once.
So, start off with a new variable

var PlayedTune:integer;
^ up top, Variables section.

We'll set it to 0 whilst playing, then once we play a "Win/Draw" tune, we'll set it to 1. That way, we won't keep playing it over and over!
Once the game starts again, it gets put back to 0, then 1 when we play, then 0 and 1 and so on..
But importantly, it only lets us play it once, for ever win/draw.
That's what it's there for.

So, we need a bunch of little ifs, to do all the work.

1st, if game's still going, make it 0.
(You can shove these lines within the main drawing chunk of your game, next to where you drew the ecksywin and ohsiewin and things like that..)

if (winner=0) and (GridLeft>0) then PlayedTune:=0;

That'll set us to 0 whilst game's in play.
Next, if player 1 wins, play his tune, and set to 1.
if (winner=1) and (PlayedTune=0) then begin PlayedTune:=1; Play('/ecksywin.mid'); end;
Same again for player 2.
if (winner=2) and (PlayedTune=0) then begin PlayedTune:=1; Play('/ohsiewin.mid'); end;

And if it's a draw, do that, too.
if (winner=0) and (GridLeft=0) and (PlayedTune=0) then begin PlayedTune:=1; Play('/draw.mid'); end;

Yeay!
Give it a whirl..

Compile, Run, play some tunes!

Sourcecode, so far!




We've now got a nice game.
We've included touchscreen controls, sound, and vibrate functions.
We've learned about arrays, for loops, functions and other such things.

I hope you've managed to follow this tutorial well.
If not, now's the time to go back, re-read it all, maybe google "Arrays", and be 100% sure that you can understand everything.
If you need some practice, open a new project, and try to remake this from scratch without using the tutorial.
Try a few other versions.
Perhaps a 4x4 grid, instead of 3x3.
Try adding a third player.
Try lots of things. That's what coding's all about.

And only, and I mean ONLY, when you're ready..

You can deal with the next section...




Artificial Intelligence This is where it all goes horrible horribly wrong!

Lets start with the easy part.
We currently have the buttons set up together. Hit either of our two available buttons, and the game will reset.
Let's change that.

Start with a Variable that we'll use to hold the AI Option.
var AI_On:integer;
^ Variables

and start with it being off.
AI_On:=0;
^ Loading bit

Next, we'll change our button pushing stuff.

Previously, we had the line if GetKeyClicked <> KE_NONE then reset:=1;
We'll change it, so that a push on the Camera Button resets the game.
First, we need a chart.
Keep this in mind, it's handy!

Green Connect : -10
Middle, App : -12
Camera : -15

Those are the real values of each of the buttons on the cookie.
Apologies that these are numbers, and not virtual keys. I can't fathom that bit!
We'll just have to stick with numbers

Note, though, that Middle App brings up that messy App menu... so .. best not to use that.

Right, back to the game.
Change the "If GetKeyClicked" line to..
if GetKeyClicked = -15 then reset:=1;
^switcheroo..

ie, if we hit Camera, reset the game.

That's NEARLY right, but we also have to do another little tweak.
See, GetKeyClicked only works once every frame.
As soon as we ask, it'll reset.
So, if we need to ask again (which we do), it'll always be 0 by the time we get to the second question.

Instead, we need to hold the value in a variable, and use that version when we need to ask..

var KeyTapped:integer;
^ top.

Now replace the GetKeyClicked line, with the two lines..

KeyTapped:=GetKeyClicked;
if KeyTapped = -15 then reset:=1;


Good, now shove another line under that, the switches the AI_On value from 0 to 1 and back again, when the player hits Green..
if KeyTapped=-10 then AI_On:=1-AI_On;

(if AI_On=1 then 1-1 = 0.. if ai_on=0 then 1-0 = 1.. and so on in a never ending loop)
*phew*

OK, that's turned the AI on.

Whilst the AI's on, we need to turn off the ability to play as player 2..
Head into the Big If.
So far, it has three layers...


We need to add an extra layer..
This layer needs to check that, either, we're player 1, or we're player 2 and AI is off.

if (player=1) or ( (player=2) and (AI_On=0) ) then begin

end;

Wrap that around the outside of the "stuff in here", but the inside of the previous layers.

Crazy!

And that's just to turn off the player.
Next up.. the actual AI stuff!

Make a new little section, between "//Win Checking" and "//Reset Section"




// AI Section

Start with a nice if to put everything in.
if (player=2) and (AI_On=1) and (GridLeft>0) and (mudown=0) then begin // If Gridleft=0, we don't want the AI freaking out!! We also don't want it taking a go in a split second, so Mudown will let it wait until you let go of the screen!


end;


Here's what we'll do.
In the same way that we did the Win Check, we'll work a single line at a time, but get the loop to multiply that over the entire grid.
Then we'll work on two extra bits for diagonals.

Each grid reference will be given a score, and at the end we use the score to decide which slot to take.

Easy


If Player 1 has 2 points on a line, we need to capture the third.
If player 2 has 2 points on a line, we win if we capture the third.
But if player 1 has 2 points on one line, and player 2 has 2 points on another, then the player 2 line should be given priority..
So, We also need to do different checks for Player 1 and Player 2, or at least score them differently.

First, make a Score array, which we'll use to place our scores into.
var score:array[1..3,1..3] of integer;

inside our new AI If-wrap, place a simple Score Reset loop.

for x:=1 to 3 do begin
for y:=1 to 3 do begin
score[x,y]:=0;
end;
end;

Because each play will be different, we need everything nice and clear.

Next, if the middle square is clear, we want it!!
if Playground[2,2]=0 then score[2,2]:=50000;
so it gets a nice big value..


Make a for loop, to skip through all the possibilities.

for y:=1 to 3 do begin
end;


And, in there we'll start to build our basic AI.

First line..
if (Playground[1,y]=Playground[2,y]) and (Playground[3,y]=0) and (Playground[1,y]>0) then score[3,y]:=score[3,y]+(Playground[1,y]*100);
eg [x][x][ ] will get 100 points, and [o][o][ ] will get 200!

next, same for [x][ ][x], and again for [ ][x][x]

if (Playground[1,y]=Playground[3,y]) and (Playground[2,y]=0) and (Playground[1,y]>0) then score[2,y]:=score[2,y]+(Playground[1,y]*100);
if (Playground[2,y]=Playground[3,y]) and (Playground[1,y]=0) and (Playground[2,y]>0) then score[1,y]:=score[1,y]+(Playground[2,y]*100);

Note, we're adding these to the total score for each grid point, so if we get multiple hits, it gets a nice big score.

That's the horizontals, now do the verticals by switching the x,y like we did for the checks.

if (Playground[y,1]=Playground[y,2]) and (Playground[y,3]=0) and (Playground[y,1]>0) then score[y,3]:=score[y,3]+(Playground[y,1]*100);
if (Playground[y,1]=Playground[y,3]) and (Playground[y,2]=0) and (Playground[y,1]>0) then score[y,2]:=score[y,2]+(Playground[y,1]*100);
if (Playground[y,2]=Playground[y,3]) and (Playground[y,1]=0) and (Playground[y,2]>0) then score[y,1]:=score[y,1]+(Playground[y,2]*100);


And then, also like the checks, we need to do seperate ones for diagonals.


if (Playground[1,1]=Playground[2,2]) and (Playground[3,3]=0) and (Playground[1,1]>0) then score[3,3]:=score[3,3]+(Playground[1,1]*100);
if (Playground[1,1]=Playground[3,3]) and (Playground[2,2]=0) and (Playground[1,1]>0) then score[2,2]:=score[2,2]+(Playground[1,1]*100);
if (Playground[2,2]=Playground[3,3]) and (Playground[1,1]=0) and (Playground[2,2]>0) then score[1,1]:=score[1,1]+(Playground[2,2]*100);

if (Playground[3,1]=Playground[2,2]) and (Playground[1,3]=0) and (Playground[3,1]>0) then score[1,3]:=score[1,3]+(Playground[3,1]*100);
if (Playground[3,1]=Playground[1,3]) and (Playground[2,2]=0) and (Playground[3,1]>0) then score[2,2]:=score[2,2]+(Playground[3,1]*100);
if (Playground[2,2]=Playground[1,3]) and (Playground[3,1]=0) and (Playground[2,2]>0) then score[3,1]:=score[3,1]+(Playground[2,2]*100);

^outside the loop, but obviously still inside the if!

All of that should've given us a decent set of values for the AI player, so now we'll let him make his move.

Three new variables go up top.
var AI_X,AI_Y,AI_Best:integer;
Best holds his best value so far, and X and Y are where that best score is.

Reset the values, and make a loop.

AI_X:=0;
AI_Y:=0;
AI_Best:=0;

for x:=1 to 3 do begin
for y:=1 to 3 do begin

end;
end;

^ Inside the AI-if, under all the other stuff.

Inside our new for's, we'll check the value of score. If it's bigger than Best, we'll make best that new value, and keep track of the X and Y.
For a little randomness, we'll also add a random value to each grid point.
That way, if the scores are all 0's, we still get at least something..


score[x,y]:=score[x,y]+random(10)+10; // Add a random number to score, between 0 and 10.. plus 10, for good measure!
if (score[x,y]>AI_Best) and (Playground[x,y]=0) then
begin
AI_Best:=score[x,y];
AI_X:=x;
AI_Y:=y;
end;

^ middle of that previous for loop.

And, then, under all of that, just before the end of our AI-If, make the move..


// Taken and tweaked from the Big-If..
Playground[AI_x,AI_y]:=Player;
Play('/ohsie.mid');
Player:=3-Player;
Vibrate(1);
mudown:=1;


So.. That's...


..
Blinkin' eck, that's it!!

Compile, run.. Make a move for X, then hit the green phone button.. AI should turn on, and make his move.

Note : Green button = Home in the emulator.
..
Haven't figured out what the Camera button is, yet!!

Get it all going, have fun, and let me know how you get on with it all.
Sourcecode!

Then write me a game

 

Comments