Jan 312010
 

Recently someone asked me to write a TicTacToe game. So I worked out a rough logic for it. Heres the code. The idea is that the computer first scans through the tic-tac-toe board for any winning positions available. If none are available, then it looks for the manual user’s winning positions. If its able to find them, it blocks it. If not the next available position is filled. The game can be played by clicking any of the squares below.

Here is the code for the game as a silverlight application. The complete project can be downloaded here.

The XAML is quite simple. Just a grid with three rows and three columns

    <grid x:Name="gridGame" Background="White" Width="300" Height="300" HorizontalAlignment="Center" VerticalAlignment="Center">
        </grid><grid .RowDefinitions>
            <rowdefinition Height="100" />
            <rowdefinition Height="100" />
            <rowdefinition Height="100" />
        </grid>
        <grid .ColumnDefinitions>
            <columndefinition Width="100" />
            <columndefinition Width="100" />
            <columndefinition Width="100" />
        </grid>

It is then filled with the buttons in the Code Behind. I have hardcoded the grid size of three but this will work with any size.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 public MainPage()
        {
            InitializeComponent();
            InitializeGameLogic(true);
        }
 
        private void InitializeGameLogic(bool _fromConstructor)
        {
            if (!_fromConstructor)
            {
                gridGame.Children.Clear();
            }
            _logic = new TicTacLogic();
            for (int i = 0; i < 3; i++)
                for (int j = 0; j < 3; j++)
                {
                    Button _btnGrid = new Button();
                    _btnGrid.Name = "btn" + i.ToString() + j.ToString();
                    gridGame.Children.Add(_btnGrid);
                    Grid.SetRow(_btnGrid, j);
                    Grid.SetColumn(_btnGrid, i);
                    _btnGrid.Height = 80;
                    _btnGrid.Width = 80;
                    _btnGrid.Click += new RoutedEventHandler(_btnGrid_Click);
                }
            _logic.StartGame();
        }

All the dynamically generated buttons have a single event handler. The idea is to identify the button which sent the event from the name. Once we have the position of the button, the content has to be changed with an X. After the user marks, the computer does its stuff and makes its move. After every marking, a check is done if someone has won or the board is full.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void _btnGrid_Click(object sender, RoutedEventArgs e)
        {
            Button _userClicked = sender as Button;
            int _xPos = Convert.ToInt32(_userClicked.Name.Substring(3,1));
            int _yPos = Convert.ToInt32(_userClicked.Name.Substring(4,1));
            _userClicked.Content = GetMarking("X");
            _logic.ClickUser(_xPos, _yPos);
            if (!(_logic.CheckIfSomeoneWon() || _logic.CheckIfBoardFull()))
            {
                int _xCom = 0;
                int _yCom = 0;
                _logic.MoveComputer(ref _xCom, ref _yCom);
                foreach (UIElement _tempElem in gridGame.Children)
                {
                    Button _comBtn = _tempElem as Button;
                    if (_comBtn != null && _comBtn.Name == "btn" + _xCom + _yCom)
                    {
                        _comBtn.Content = GetMarking("O");
                        break;
                    }
                }
                if (_logic.CheckIfSomeoneWon() || _logic.CheckIfBoardFull())
                    InitializeGameLogic(false);
            }
            else
                InitializeGameLogic(false);
 
        }
 
        private TextBlock GetMarking(string _mark)
        {
            TextBlock _t = new TextBlock();
            _t.Text = _mark;
            _t.FontSize = 25;
            _t.FontWeight = FontWeights.Bold;
            return _t;
        }

The Board class maintains two important things. The position of all the squares and which player has it marked. The Board doesnt depend on the Player objects. It has an object of the WinningPositions object which calculates the winning positions which can be compared with later. This object makes it possible to play with larger grids.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public class Board
{
    readonly int MAXSIZE;
    List<positionmarking> _boardState = new List</positionmarking><positionmarking>();
    List<position []> _allWinningPositions;
 
    public List<positionmarking> BoardState { get { return _boardState; } }
    public List<position []> AllWinningPositions { get { return _allWinningPositions; } }
    public int MaxSize { get { return MAXSIZE; } }
 
    public Board()
    {
        this.MAXSIZE = 3;
        Initialize();
    }
 
    public Board(int _maxSize)
    {
        MAXSIZE = _maxSize;
        Initialize();
    }
 
    private void Initialize()
    {
        _allWinningPositions = (new WinningPositions(MAXSIZE)).AllWinningPositions;
        for(int _xPos=0;_xPos<maxsize ;_xPos++)
            for (int _yPos = 0; _yPos < MAXSIZE; _yPos++)
            {
                _boardState.Add(new PositionMarking(_xPos,_yPos));
            }
    }
 
    internal void MarkPosition(int _xUser, int _yUser, bool _userMarked)
    {
        foreach (PositionMarking _tempPos in _boardState)
        {
            if (_tempPos.BoardPosition.X == _xUser && _tempPos.BoardPosition.Y == _yUser)
            {
                _tempPos.MarkPosition(_userMarked);
                break;
            }
        }
#if DEBUG
        PrintBoardState();
#endif
    }
 
    internal bool? MarkedByUser(Position _tempPos)
    {
        foreach (PositionMarking _tempMark in _boardState)
        {
            if (_tempPos.X == _tempMark.BoardPosition.X && _tempPos.Y == _tempMark.BoardPosition.Y)
                return _tempMark.IsUserMarked;
 
        }
        throw new Exception("Position Not found on Board");
    }
 
    #if DEBUG
    internal void PrintBoardState()
    {
        string _userMarked = "";
        string _compMarked = "";
        foreach (PositionMarking _tempMark in _boardState)
        {
 
            if (_tempMark.IsUserMarked == true)
                _userMarked += "("+ _tempMark.BoardPosition.X + "," + _tempMark.BoardPosition.Y + ") ";
            else if (_tempMark.IsUserMarked == false)
                _compMarked += "(" + _tempMark.BoardPosition.X + "," + _tempMark.BoardPosition.Y + ") ";
        }
        Debug.WriteLine("USER: " + _userMarked);
        Debug.WriteLine("COMP: " + _compMarked);
        Debug.WriteLine("________________________________________");
    }
#endif
    internal bool IsBoardFull()
    {
        foreach (PositionMarking _tempMark in _boardState)
        {
            if (_tempMark.IsUserMarked == null)
                return false;
 
        }
        MessageBox.Show("Board Full");
        return true;
    }
}

The Winning Position class is given below and all positions are calculated at initialization. First the horizontal ones are considered and then the vertical ones. The remaining diagonal positons are taken care later. Each winning position is stored in an array of the position object which just holds the value of the Squares. These arrays are stored in a generic List.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class WinningPositions
{
    List<position []> _allWinningPositions = new List</position><position []>();
    Position[] _winPos1;
    Position[] _winPos2;
 
    public List</position><position []> AllWinningPositions
    {
        get { return _allWinningPositions; }
    }
 
    public WinningPositions(int _maxSize)
    {
        _winPos1 = new Position[_maxSize];
        _winPos2 = new Position[_maxSize];
        PopulateWinningPositions();
    }
 
    private void PopulateWinningPositions()
    {
        int _iCounter=0;
        int _xPos = 0;
        int _yPos = 0;
        for (_xPos = 0; _xPos < _winPos2.Length; _xPos++)
        {
            for (_yPos = 0; _yPos < _winPos2.Length; _yPos++)
            {
                _winPos1[_iCounter] = new Position(_xPos, _yPos);
                _winPos2[_iCounter] = new Position(_yPos, _xPos);
                _iCounter++;
            }
            _allWinningPositions.Add(_winPos1);
            _allWinningPositions.Add(_winPos2);
            _winPos1 = new Position[_winPos1.Length];
            _winPos2 = new Position[_winPos2.Length];
            _iCounter = 0;
        }
        _iCounter = 0;
        for (_xPos = 0, _yPos = 0;_xPos < _winPos1.Length; _xPos++, _yPos++)
        {
            _winPos1[_iCounter] = new Position(_xPos, _yPos);
 
            _iCounter++;
        }
        _allWinningPositions.Add(_winPos1);
        _iCounter = 0;
        for (_xPos = 0, _yPos = _winPos1.Length - 1; _xPos < _winPos1.Length; _xPos++, _yPos--)
        {
            _winPos2[_iCounter] = new Position(_xPos, _yPos);
            _iCounter++;
        }
        _allWinningPositions.Add(_winPos2);
    }
}

Then there is the Player class of which we create two objects. The user takes care of his own moving logic, all we need to do is to change the state of the square for him. But the computer has its method called the SmartMove. Here we first move through the winning positions to identify any open squares that can be marked. If there are no squares, then we look to block the opponent’s winning. If even this opportunity is not found, we mark an available open square.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public class Player
{
    Board _currentBoardPosition;
    bool _isComputer;
    List<position> _allMarkedPositions;
 
    public Player(bool _isComputer,Board _currentBoardPos)
    {
        this._isComputer = _isComputer;
        _allMarkedPositions = new List</position><position>();
        _currentBoardPosition = _currentBoardPos;
    }
 
    internal void SmartMove(ref int _xCom, ref int _yCom)
    {
        bool _markingDone = false;
        bool _blockUser = false;
        Position _userBlock = new Position();
        foreach (Position[] _winPos in _currentBoardPosition.AllWinningPositions)
        {
            int _userMarked = 0;
            int _computerMarked = 0;
            int _nullMarked=0;
 
            Position _openPos= new Position();
            foreach (Position _tempPos in _winPos)
            {
                bool? isUserMarked = _currentBoardPosition.MarkedByUser(_tempPos);
                if (isUserMarked==true)
                    _userMarked++;
                else if (isUserMarked == false)
                    _computerMarked++;
                else
                {
                    _openPos = _tempPos;
                    _nullMarked++;
                }
 
            }
            if (_computerMarked == 2 && _nullMarked == 1)
            {
                Debug.WriteLine("Wining block: " + _openPos.X + "," + _openPos.Y);
                _currentBoardPosition.MarkPosition(_openPos.X, _openPos.Y, false);
                _xCom = _openPos.X;
                _yCom = _openPos.Y;
                _markingDone = true;
                break;
            }
            else if (_userMarked == 2 && _nullMarked == 1)
            {
                _userBlock = _openPos;
                _blockUser = true;
            }
 
        }
        if (_blockUser && !_markingDone)
        {
            _currentBoardPosition.MarkPosition(_userBlock.X, _userBlock.Y, false);
            _xCom = _userBlock.X;
            _yCom = _userBlock.Y;
            _markingDone = true;
        }
 
        if (!_markingDone)
        {
 
            //No Winning Position Found or not able to block;
            foreach (PositionMarking _posMark in _currentBoardPosition.BoardState)
            {
                if (_posMark.IsUserMarked == null)
                {
                    _markingDone = true;
                    _currentBoardPosition.MarkPosition(_posMark.BoardPosition.X, _posMark.BoardPosition.Y,false);
                    _xCom = _posMark.BoardPosition.X;
                    _yCom = _posMark.BoardPosition.Y;
                    break;
                }
            }
 
        }
    }
}
</position>

Since the Board needs to maintain the state of each and every position on itself, we have another object for it, which maintains a position object and a nullable boolean. If the boolean is true, then the square is marked by the user, if false – then by the computer. If its still null – its open and can be marked. The marking is done by the object’s method which makes a check to make sure no players marking is overwritten by another.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class PositionMarking
{
    Position _boardPos;
    bool? _isUserMarked;
 
    public Position BoardPosition { get { return _boardPos; } }
    public bool? IsUserMarked { get { return _isUserMarked; } }
 
    public PositionMarking(int _xPos,int _yPos)
    {
        _isUserMarked = null;
        _boardPos = new Position(_xPos, _yPos);
    }
 
    public void MarkPosition(bool _markedByUser)
    {
        if(_isUserMarked == null)
        _isUserMarked = _markedByUser;
        else
        throw new Exception("Position already taken");
    }
}

Then there is the rest of the code including our business logic layer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
public class TicTacLogic
{
    bool _userToMove;
    bool _resultHasCome;
    Player _user;
    Player _computer;
    Board _gameBoard;
 
    public TicTacLogic()
    {
        _userToMove = true;
        _resultHasCome = false;
    }
 
    internal void StartGame()
    {
        Initialize();
    }
 
    internal bool ClickUser(int _xUser, int _yUser)
    {
        return _gameBoard.MarkPosition(_xUser, _yUser, true);
    }
 
    internal void MoveComputer(ref int _xCom, ref int _yCom)
    {
        _computer.SmartMove(ref _xCom, ref _yCom);
    }
 
    internal void Initialize()
    {
        _gameBoard = new Board(3);
        _user = new Player(false,_gameBoard);
        _computer = new Player(true,_gameBoard);
 
    }
 
    public bool CheckIfSomeoneWon()
    {
        foreach (Position[] _tempWin in _gameBoard.AllWinningPositions)
        {
            int _userWon=0;
            int _compWon=0;
            foreach (Position _p in _tempWin)
            {
                bool? isUserMarked = _gameBoard.MarkedByUser(_p);
                if (isUserMarked == null)
                    break;
                else if (isUserMarked == true)
                    _userWon++;
                else
                    _compWon++;
 
            }
            if (_userWon == _gameBoard.MaxSize)
            {
                MessageBox.Show("User Won");
                return true;
            }
            else if (_compWon == _gameBoard.MaxSize)
            {
                MessageBox.Show("Computer Won");
                return true;
            }
 
        }
        return false;
    }
 
    internal bool CheckIfBoardFull()
    {
        return _gameBoard.IsBoardFull();
    }
}
 
public class Position
{
    int _xPos;
    int _yPos;
 
    public Position()
    {
    }
 
    public int X { get { return _xPos; } }
    public int Y { get { return _yPos; } }
 
    public Position(int xPos, int yPos)
    {
        this._xPos = xPos;
        this._yPos = yPos;
    }
}