Tutorials‎ > ‎

[C#] Socket Simple Multiplayer Game [Tic Tac Toe] (Part 2 - Game Interface, Game Management and Sending Information)

posted Feb 26, 2013, 4:00 AM by Davin Timothy Ivander   [ updated Aug 15, 2016, 11:23 PM by Surya Wang ]

Introduction

This part is the continuation of the first part. The second part will mainly focused on the game interface and management, plus sending and receiving information between users.

GameForm Interface
  • First Started
  • When user start playing
  • When Someone Win
  • The Result Screen

Components:
  • 9 buttons with flatstyle.
Descriptions:
  • User pick a single block within available block [empty one] and automatically switch turn to other player.
  • Server/Host will always X and Client will always O.
  • You cannot do anything until other player make their move.

GameForm Codes

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace DvTicTacToe
{
    public partial class GameForm : Form
    {
        private Main Owner;
        private bool isServer;
        private bool isMyTurn = false;
        private int[][] board = { new int[] { 0, 0, 0 }, new int[] { 0, 0, 0 }, new int[] { 0, 0, 0 } };// 0=netral, 1=server, 2=clint
        private SocketManagement con;
        private string[] mapping = {"","X","O"};// 0=netral, 1=server, 2=clint
        private bool isFinished = false;
        private bool isWinner = false;

        public GameForm(Main owner, bool isServer, SocketManagement con)
        {
            this.isMyTurn = isServer;
            this.Owner = owner;
            this.isServer = isServer;
            new ResizeForBorderlessForm(this) { AllowResizeAll = false, AllowMove = true };
            InitializeComponent();
            this.con = con;
        }

        private void ReSetBoard() 
        {
            p00.Text = mapping[board[0][0]];
            p01.Text = mapping[board[0][1]];
            p02.Text = mapping[board[0][2]];
            p10.Text = mapping[board[1][0]];
            p11.Text = mapping[board[1][1]];
            p12.Text = mapping[board[1][2]];
            p20.Text = mapping[board[2][0]];
            p21.Text = mapping[board[2][1]];
            p22.Text = mapping[board[2][2]];
        }

        private void CheckBoard() {
            if (!this.InvokeRequired)
            {
                // V Check
                if (board[0][0] != 0 && board[0][1] != 0 && board[0][1] != 0 && board[0][0] == board[0][1] && board[0][1] == board[0][2] && board[0][0] == board[0][2])
                {
                    // V0
                    isFinished = true;
                    if ((isServer && board[0][0] == 1) || (!isServer && board[0][0] == 2)) isWinner = true;
                }
                else if (board[1][0] != 0 && board[1][1] != 0 && board[1][1] != 0 && board[1][0] == board[1][1] && board[1][1] == board[1][2] && board[1][0] == board[1][2])
                {
                    // V1
                    isFinished = true;
                    if ((isServer && board[1][0] == 1) || (!isServer && board[1][0] == 2)) isWinner = true;
                }
                else if (board[2][0] != 0 && board[2][1] != 0 && board[2][1] != 0 && board[2][0] == board[2][1] && board[2][1] == board[2][2] && board[2][0] == board[2][2])
                {
                    // V2
                    isFinished = true;
                    if ((isServer && board[2][0] == 1) || (!isServer && board[2][0] == 2)) isWinner = true;
                }
                // H Check
                else if (board[0][0] != 0 && board[1][0] != 0 && board[2][0] != 0 && board[0][0] == board[1][0] && board[1][0] == board[2][0] && board[0][0] == board[2][0])
                {
                    // H0
                    isFinished = true;
                    if ((isServer && board[0][0] == 1) || (!isServer && board[0][0] == 2)) isWinner = true;
                }
                else if (board[0][1] != 0 && board[1][1] != 0 && board[2][1] != 0 && board[0][1] == board[1][1] && board[1][1] == board[2][1] && board[0][1] == board[2][1])
                {
                    // H1
                    isFinished = true;
                    if ((isServer && board[0][1] == 1) || (!isServer && board[0][1] == 2)) isWinner = true;
                }
                else if (board[0][2] != 0 && board[1][2] != 0 && board[2][2] != 0 && board[0][2] == board[1][2] && board[1][2] == board[2][2] && board[0][2] == board[2][2])
                {
                    // H2
                    isFinished = true;
                    if ((isServer && board[0][2] == 1) || (!isServer && board[0][2] == 2)) isWinner = true;
                }
                // D Check
                else if (board[0][0] != 0 && board[1][1] != 0 && board[2][2] != 0 && board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] == board[2][2])
                {
                    // D->
                    isFinished = true;
                    if ((isServer && board[0][0] == 1) || (!isServer && board[0][0] == 2)) isWinner = true;
                }
                else if (board[0][2] != 0 && board[1][1] != 0 && board[2][0] != 0 && board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[2][0] == board[0][2])
                {
                    // D<-
                    isFinished = true;
                    if ((isServer && board[1][1] == 1) || (!isServer && board[1][1] == 2)) isWinner = true;
                }
                if (isFinished)
                {
                    SetEnabled(true);
                    isMyTurn = false;
                    ReSetBoard();
                    WaitingPanel.Hide();
                    if (isWinner) MessageBox.Show(null, "You Win!!", "Result Screen", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    else MessageBox.Show(null, "You Lose!!", "Result Screen", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    Environment.Exit(0);
                }
            }
            else this.Invoke((MethodInvoker)delegate { CheckBoard();});
        }

        private void CheckTurn() {
            if (!this.InvokeRequired)
            {
                if (isMyTurn && isFinished==false)
                {
                    WaitingPanel.Hide();
                    SetEnabled(true);
                }
                else
                {
                    WaitingPanel.Show();
                    SetEnabled(false);
                    GetDataFromOthers();
                }
                ReSetBoard();
            }
            else this.Invoke((MethodInvoker)delegate { CheckTurn(); });
        }

        private void SetEnabled(bool value) 
        {
            p00.Enabled = value;
            p01.Enabled = value;
            p02.Enabled = value;
            p10.Enabled = value;
            p11.Enabled = value;
            p12.Enabled = value;
            p20.Enabled = value;
            p21.Enabled = value;
            p22.Enabled = value;
        }

        private void GameForm_Load(object sender, EventArgs e)
        {
            CheckTurn();
        }

        private void GetDataFromOthers() 
        {
            Task.Factory.StartNew(() => {
                board = con.getBoard();
                isMyTurn = true;
                CheckBoard();
                CheckTurn();
            });
        }

        private void SetBoardBasedOnButtonName(string code) {
            // 0=netral, 1=server, 2=clint
            char[] realCodeInChar = code.Substring(1).ToCharArray();
            board[Int32.Parse(""+realCodeInChar[0])][Int32.Parse(""+realCodeInChar[1])] = isServer?1:2;
        }

        private void p_click(object sender, EventArgs e)
        {
            if (isMyTurn && isFinished==false)
            {
                if (((Button)sender).Text == "")
                {
                    SetBoardBasedOnButtonName(((Button)sender).Name);
                    con.sendBoard(board);
                    isMyTurn = false;
                    CheckBoard();
                    CheckTurn();
                }
                else MessageBox.Show("Please select another box");
            }
        }

        private void GameForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            Environment.Exit(0);
        }
    }
}

Descriptions:
  • When ReSetBoard was called, the program will automatically reset the board according to the newest data available, normally the changed will occurred after receiving information from others.
  • When CheckBoard was called, the program will automatically check if anyone has win the game, and the program will show the result screen if someone has win the game.
  • When CheckTurn was called, the program will automatically check if now is your turn or not, if this was not turn the program will automatically disabled all input and show message "Waiting other player to move", but if this was your turn the program will show the game board and allow you to pick 1 slot only.
  • Task here is a function to make the working of reading the information asynchronously so the program won't freeze when waiting other user move. 
  • When getBoard  was called, the program will asking the other for the newest information or the move that they make, normally while this moment the program will freeze, but because we user asynchronous the program can keep working while asking for information.
  • When sendBoard was called, the program will send to the other information about their move, so both player can have the latest board. 
  • Normally when getBoard or sendBoard is called, the program will call CheckBoard and CheckTurn.


SocketManagement Code

public bool sendBoard(int[][] obj) {
    try
    {
        string temp = "";
        for (int y = 0; y < 3; y++)
            for (int x = 0; x < 3; x++)
                temp += obj[y][x];

        byte[] bytes = new byte[255];
        bytes = new ASCIIEncoding().GetBytes(temp);
        _STREAM.Write(bytes, 0, bytes.Length);
    }
    catch (Exception ex) { System.Windows.Forms.MessageBox.Show(ex.Message); return false; }
    return true;
}

public int[][] getBoard() {

    byte[] bytes = new byte[255];
    _STREAM.Read(bytes,0,bytes.Length);
    string temp = new ASCIIEncoding().GetString(bytes);
    char[] charOfTemp = temp.ToCharArray();
    int[][] obj = { new int[] { 0, 0, 0 }, new int[] { 0, 0, 0 }, new int[] { 0, 0, 0 } };
    for (int y = 0; y < 3; y++)
        for (int x = 0; x < 3; x++)
            obj[y][x] = Int32.Parse(""+charOfTemp[(y*3)+x]);
    return obj;
}

Description:
  • sendBoard:
    • Arrays of integer will be parsed to string.
    • The string converted into byte using new ASCIIEncoding().GetBytes(string)
    • The Stream write the bytes.
  • getBoard:
    • The Stream read the bytes.
    • Convert from string into char of array.
    • we parse back from array of char into arrays of int.


Explanation

This part of tutorial only explain about how to send and receiving using socket, and how the program manages the game.
The program at first will show all empty block, and will be filled by both of the player. The order of the program works is like:
  • Server will play first and pick 1 slot
  • Server send information and client get it
  • Server now waiting for information.
  • Client now must pick 1 slot.
  • Client will send information to server after 1 slot was picked.
  • Both Server and Client will do this until one of them was win.
  • Result screen is showed and after confirmation the program will automatically close.
Note: both of Server and Client will always check turn, re-set the board, and check the board every time they send or receiving any information from others.

Final Part

This concludes the end of my tutorial, This tutorial is created so you can understand the basic of socket game, and allows you to explore more about socket. Thank you for reading.