Saturday, September 15, 2012

Creating Tic-Tac-Toe with C# - Part 3 (Final part)

So now we will finalize our project.
In part 2, we created the computer role in the Tic Tac Toe game, but as a dumb.
So now we are going to program the smart playing algorithm in C#.
We will consider two main points in the algorithm:

  1. Do we have a winning play? if yes lets play it and win! No? proceed to step 2.
  2. Check if the opponent has a winning move, block it if yes, continue to step 3 if no.
  3. Play like our old friend from part 2, yes it's the dumb.


HELPER METHODS

the NormalPCWrite method is not that easy, so we are going to create four helper methods.

Get Coordinates

        private void getCoordinates(int p, out int x, out int y)
        {//change the variables x and y to the correct coordinate
            switch (p)
            {
                case 1: x = 0; y = 0; break;
                case 2: x = 0; y = 1; break;
                case 3: x = 0; y = 2; break;
                case 4: x = 1; y = 0; break;
                case 5: x = 1; y = 1; break;
                case 6: x = 1; y = 2; break;
                case 7: x = 2; y = 0; break;
                case 8: x = 2; y = 1; break;
                default: x = 2; y = 2; break;
            }
        }

Clicked (+1 overload)

private bool clicked(int index)
        {//true if it was clicked, false if it's still clickable
            return (ButtonsIndexs.IndexOf(index.ToString()) == -1);
        }
private bool clicked(int index,string p)
        {//checks if "p" is written on the button with index "index"
            int a,b;
            switch(index){
                case 1:a=0;b=0;break;
                case 2:a=0;b=1;break;
                case 3:a=0;b=2;break;
                case 4:a=1;b=0;break;
                case 5:a=1;b=1;break;
                case 6:a=1;b=2;break;
                case 7:a=2;b=0;break;
                case 8:a=2;b=1;break;
                default:a=2;b=2;break;
                    }
            if (p == "X")
                return (X[a, b]);
            return (O[a, b]);
        }

Write_buttonIndex

private void Write_buttonIndex(int index, string toWrite)
        {//Writing 'toWrite' in a button by knowing its index p
            switch (index)
            {
                case 1: Write(toWrite, button1); break;
                case 2: Write(toWrite, button2); break;
                case 3: Write(toWrite, button3); break;
                case 4: Write(toWrite, button4); break;
                case 5: Write(toWrite, button5); break;
                case 6: Write(toWrite, button6); break;
                case 7: Write(toWrite, button7); break;
                case 8: Write(toWrite, button8); break;
                default: Write(toWrite, button9); break;
            } 
        }

check

        private bool check(int i, int k, string toCheck,string toWrite)
        {
            //k=1 for rows, k=3 for columns, k=5-i for diagonals
            string P = (toWrite == "X") ? "O" : "X";
            if ((clicked(i,toCheck) && clicked(i+k,toCheck) && !clicked(i + k + k)))
            {
                getCoordinates(i + k + k, out x, out y);
                Write_buttonIndex(i + k + k, toWrite);
                return true;
            }
            if ((clicked(i,toCheck) && clicked(i+k+k,toCheck) && !clicked(i + k)))
            {
                getCoordinates(i + k, out x, out y);
                Write_buttonIndex(i + k, toWrite);
                return true;
            }
            if ((clicked(i+k,toCheck) && clicked(i+k+k,toCheck) && !clicked(i)))
            {
                getCoordinates(i, out x, out y);
                Write_buttonIndex(i, toWrite);
                return true;
            }
            return false;
        }

Please read the helper methods carefully to understand well how they work. Now Finally lets write the NormalPCWrite method!

Normal level method

private void NormalPCWrite(string p)
        { 
            string q = (p == "X") ? "O" : "X";
            //Lets check if we can win
            if (!check(1, 1, p, p))
                if (!check(4, 1, p, p))
                    if (!check(7, 1, p, p))
                        if (!check(1, 3, p, p))
                            if (!check(2, 3, p, p))
                                if (!check(3, 3, p, p))
                                    if (!check(1, 4, p, p))
                                        if (!check(3, 2, p, p))
                                            if (!check(1, 1, q, p))
                                                //It seems that we can't, let's defend
                                                if (!check(4, 1, q, p))
                                                    if (!check(7, 1, q, p))
                                                        if (!check(1, 3, q, p))
                                                            if (!check(2, 3, q, p))
                                                                if (!check(3, 3, q, p))
                                                                    if (!check(1, 4, q, p))
                                                                        if(!check(3, 2, q, p))
                                                                            //play randomly
                                                                        EasyPCWrite(p);   
        }
Note: this is not the best playing, as we can create a more smarter algorithm, which will avoid and even try to create TRAPS. Maybe you can do it? try to add one more difficulty, the Unbeatable.

For the best performance, please edit some of our previously created methods.

EDIT: resetVars()

private void resetVars()
        {
            ButtonsIndexs = "123456789";
            finished = false;
            rnd = new Random();
            O = new bool[3, 3];
            X = new bool[3, 3];
        }

EDIT: Form3_Load()

        private void Form3_Load(object sender, EventArgs e)
        {
            difficulity = 0;
            resetVars();
        }

EDIT: back_Click()

private void back_Click(object sender, EventArgs e)
        {
            if (difficulity == 0)
                easyRB.Checked = true;
            else
                normalRB.Checked = true;
            playAgainButton.PerformClick();
            xLabel.Text = oLabel.Text = "0";
            tableLayoutPanel1.Visible = false;
            this.Size = new Size(229, 300);
        }
If you are working  with me, you probably noticed while debugging, that if you click the close button (X) of the form, the debugger doesn't stop.
That's because you are closing a single form, while previous ones are hidden.
To solve this and to end the application process when the user click the (X) button, go to each form, look for an event called Form closing. double click it and add this line of code:
Application.Exit();
Finally we are done! I wish you enjoyed this tutorial, go play some Tic Tac Toe now ;) see you later..