Click here to Skip to main content
Click here to Skip to main content

Pacman Multiplayer

, 4 Jan 2013
Rate this:
Please Sign up or sign in to vote.
A basic LAN game in C#

Introduction 

Everyone knows the legendary Pacman game and how fun it is even if played for hours and hours. This game's mechanics have always grasped my attention. It's very simple. However, there has been a lot of thoughts put behind it.  

Much of my own understanding of the original game, came from a very nice website dedicated to explaining the original game. You can give that site a visit by clicking here.

In this article, you will find a similar implementation of the game, with a small twist on it.

DOWNLOAD: Here's a link on dropbox for the files of the project.  

Acknowledgement

This game has been created as a project for the course "SWE344 - Internet protocols and client-server programming" given at King Fahd University of Petroleum and Minerals; Suggested by my instructor, Nasir Darwish

Disclaimer

The sole purpose of the game is giving a working example of using .NET framework UPD classes. This may not be the best example since many good programmers may know better tricks to solve this game's algorithms.   

The graphics of the game only scratches the surface of what the original game actually looks like. I'm not a graphics designing expert. All of the images you will see in the game, I have drawn them my self using Microsoft Paint.   


Game Logic

The main logic of the game is based on the abstract logic of the original game: Pacman eats dot; Ghosts haunt Pacman. The twist on the original game is as follows: the game has two Pacman players, and four ghosts. Initially, each pair of ghosts will follow one pacman. Whenever one pacman eats a Super Dot (The bigger dot that originally will make ghosts fear pacman), all of the ghosts will now haunt the opponent pacman. Simple? Continue reading the next sections for a clearer understanding.  

The Maze

The following image shows the background of the game, inspired from the original game: 

The maze is full of tiles, where each tile represents a place where an Actor can exist. An Actor could be either a Pacman, or a Ghost. One tile is of the size 20x20 pixels. The following image shows the tiles highlighted in white:

Notes: 

Ignore the tunnels or the mid-left and mid-right sides of the background as I have not included them in the game - Check first background image -. This image is only for showing the tiles. Tiles are actually right next to each other. That is, there's no separators between them as shown in the image as black. This is just for making things clear. 

 

Ghosts have brains (I don't know if that is actually applicable in real life). However, they are magically limited to work only when a ghost exists in an intersection. This is a actually a shortcut version of the original Ghost's logic. You can read more about that here if you were interested. Nevertheless, back to this game. The following image shows where they can actually think: 

If a ghost had made a decision of where to go, its brain will shut-off and it will keep going on a straight line until it reaches another intersection where it can magically think again.

That's the most abstract representation for the logic of the game. Let's get more into details now. 


Classes of the Code  

I think it's worth to mention here that the game was designed as Host-Client architecture, where the host makes all of the logic and client just shows what it receives from the host. More details of this will be explained further ahead. 

"At this exact exact moment when I was writing this article, I was enjoying a great cup of coffee, Sumatran, home brewed, at it's finest." 

The following list shows all the classes that were used to build the game:   

  • Actor
  • Pacman
  • Ghost
  • Tile 
  • Maze
  • MainMenu
  • GameWindow
  • Logic
  • HostLogic
  • ClientLogic

Let's tackle each, one-by-one, and dig deep into it. Tip: assume all code in code blocks is continuous. Not separate as illustrated.  

Actor

This is an abstract class representing an actor which can move in the tiles of the maze. 

public class Actor
{

These are the X and Y positions of an actor: 

protected short X;
public short getX() { return this.X; }
public void setX(short newX) { this.X = newX; }

protected short Y;
public short getY() { return this.Y; }
public void setY(short newY) { this.Y = newY; }

This byte hold the direction of an actor. A direction, could only be in on of the four possibilities: 0,1,2, or 3 where each represents Up, Right, Down and Left, respectively to order. 

protected byte direction;
public byte getDirection() { return this.direction; }

The tiles where the actor is: 

protected Tile currentTile;
public Tile getCurrentTile() { return this.currentTile; }

Notice that setting the current tile of an actor, will set it's position to the new tile's position:  

public void setCurrentTile(Tile newTile)
{
    this.currentTile = newTile;
    this.X = newTile.getX();
    this.Y = newTile.getY();
}

This method moves an actor to the next adjacent tile, based on it's direction. If the tile doesn't have a neighboring tile to the direction of the actor, the actor will not move. I will explain the Tile class further ahead. 

public void move()
{
    Tile destinationTile = null;
    if (this.direction == 0)
    {
        destinationTile = this.getCurrentTile().getTileAbove();
    }
    else if (this.direction == 1)
    {
        destinationTile = this.getCurrentTile().getTileToRight();
    }
    else if (this.direction == 2)
    {
        destinationTile = this.getCurrentTile().getTileBelow();
    }
    else
    {
        destinationTile = this.getCurrentTile().getTileToLeft();
    }

Notice that is destinationTile is null, means that we're at an edge. We need to check the there is a tile that we can move into, and that that tile is not a wall. 

if (destinationTile != null && (!destinationTile.isWall()))
    {
        setCurrentTile(destinationTile);
    }
}

A constructor that will set the Actors current tile, to a tile given as a parameter, and another empty constructor. 

public Actor(Tile initialTile)
    {
        this.setCurrentTile(initialTile);
    }

    protected Actor()
    {

    }
}

Pacman 

"Do you like Yellow or green  more? I wish I had made a red one. This coffee is really great!"

This class represents Pacman. Our main player in the game. This class extends Actor, thus inheriting all of it's methods and properties. 

public class Pacman : Actor
{

This constructor take more details than its base constructor in Actor that takes only a tile. So, we set our currentTile into initialTile from Actor's base constructor, our image to the image in imagePath (Explain a little bit forward), our direction into initialDirection and we tell our Pacman weather he's Yellow or Green through isYellow. (No discrimination I assure you).

public Pacman(Tile initialTile, string imagePath, byte initialDirection, bool isYellow)
    : base(initialTile)
{
    this.setImage(imagePath);
    this.setDirection(initialDirection);
    this.yellow = isYellow;
}

A bool variable to see weather Pacman is yellow otherwise green: 

private bool yellow;
public bool isYellow() { return this.yellow; }

This is the image of pacman. Setting the image will load the image from a file given its path as a parameter. The copyImage will be used in the next method. Setting the image will clone the otherImage

protected Image image;
public Image getImage() { return this.image; }
public void setImage(string imagePath)
{
    this.image = Image.FromFile(imagePath);
    this.copyImage = (Image)this.image.Clone();
}
public void setImage(Image otherImage)
{
    this.image = (Image)otherImage.Clone();
}

protected Image copyImage; 

Since we want the game to look a little bit nicer, setting the direction of Pacman will rotate him (or her if you would like) to point at that direction. This assumes that the original image we feed when constructing a Pacman object, will have Pacman pointing to the right. The code is self-explanatory. 

public void setDirection(byte newDirection)
{
    this.direction = newDirection;

    this.image = (Image)this.copyImage.Clone();
    if (this.direction == 1)
    {
        this.image.RotateFlip(RotateFlipType.RotateNoneFlipNone);
    }
    else if (this.direction == 0)
    {
        this.image.RotateFlip(RotateFlipType.Rotate270FlipNone);
    }
    else if (this.direction == 2)
    {
        this.image.RotateFlip(RotateFlipType.Rotate90FlipNone);
    }
    else if (this.direction == 3)
    {
        this.image.RotateFlip(RotateFlipType.RotateNoneFlipX);
    }
}

Empty constructor. (I really don't know why I have these here ... but ok it won't make a difference)

"*Slurrrrrp* This coffee is from heaven!"  

protected Pacman() : base()
{

}  

Ghost  

" Spooky,  hard working,  restless and  very smart (according to their own IQ measures)" .

This class represents a Ghost. This class, alike Pacman, also extends Actor. This is one of the parts where I know I may not have written the best class even existed to human beings, neither if it is patent pending. But it serves it's purpose. 

public class Ghost : Actor
{

This constructor takes and initialTile and set it to our currentTile, four images each representing an image for each direction, and a Pacman target to haunt! 

If you were wondering "Four images? Why? Why Pacman had only one?" Wait a moment. It will be explained. 

"It's getting a little more interesting; I know. *Slurrrrp*"  

public Ghost(Tile initialTile,
    string image0Path,string image1Path, string image2Path, string image3Path,
    byte initialDirection, Pacman target)
    : base(initialTile)
{
    this.setImage0(image0Path);
    this.setImage1(image1Path);
    this.setImage2(image2Path);
    this.setImage3(image3Path);
    this.setDirection(initialDirection);
    this.target = target;
}

A Pacman variable holding the pacman to haunt. 

protected Pacman target;
public Pacman getTarget() { return this.target; }
public void setTarget(Pacman newTarget)
{
    this.target = newTarget;
}

Four images, numbered 0,1,2 and 3 representing images of the ghost with its eyes pointing Up, Right, Down and Left respectively to order. The reason why a ghost has four images but pacman has only one, is that we cannot flip a ghost's image around. A ghost's lower part must always be pointing down. Setting any image, alike Pacman class, will load the image from a file given it's path. 

protected Image image0;
public Image getImage0() { return this.image0; }
public void setImage0(string path){this.image0 = Image.FromFile(path);}

protected Image image1;
public Image getImage1() { return this.image1; }
public void setImage1(string path){this.image1 = Image.FromFile(path);}

protected Image image2;
public Image getImage2() { return this.image2; }
public void setImage2(string path){this.image2 = Image.FromFile(path);}

protected Image image3;
public Image getImage3() { return this.image3; }
public void setImage3(string path){this.image3 = Image.FromFile(path);}

Notice that this method is named getImage() without a number appended at the end like the ones before. This is mainly used for ease of used when we want to draw the ghost. It will return an image according to the ghost's direction.

public Image getImage()
{
    if (this.direction == 0) return image0;
    else if (this.direction == 1) return image1;
    else if (this.direction == 2) return image2;
    else if (this.direction == 3) return image3;
    return null;
}

This is just a shorthand instead of calling four methods. 

"Programmers. Sometimes we call ourselves smart. We're just lazy, really. *Sluuurp* Third cup of coffee." 

public void setImages(string i0, string i1, string i2, string i3)
{
    this.setImage0(i0);
    this.setImage1(i1);
    this.setImage2(i2);
    this.setImage3(i3);
}

No need to explain much on this one: 

public void setDirection(byte newDirection)
{
    this.direction = newDirection;
}

This methods tells its caller if the ghost had eaten it's target. Basically, if a ghost's X and Y are equal to its target's X and Y, then he dies. (Kinda cruel naming.)

This could have been made in another way. That is, directly checking if X and Y are equal; not calculating the difference. However, consider this to be a warm-up to what you're going to see in the next method. 

public bool targetDies()
{
    int xDifference = getX() - target.getX();
    int yDifference = getY() - target.getY();
    return (xDifference == 0 && yDifference == 0);
}

Seeking the target is equivalent to thinking where to go next. First of all, before initiating an thinking, we check if the current tile is a ghost tile. What is a ghost tile? This is the magical tile we talked earlier about where the ghost is able to think. Refer to Maze class explained ahead for more details. 

public void seekTarget()
{
    if (this.getCurrentTile().isGhostTile())
    {

So, we're in a ghost tile. Next thing to do is calculate the X and Y differences between the our ghost's and its target's. 

int xDifference = getX() - target.getX(); //-ve means pacman is to you're right
int yDifference = getY() - target.getY(); //-ve means pacman is below you

Hold you horses on these two variables. We're gonna need them. 

goCheckY means two things; either the difference between X's is less that the difference between Y's, thus go on Y's (shorter distance). Else, means that the ghost cannot move on on X-axis because there are no adjacent tiles. 

fail means that we couldn't move either on X neither Y. So, (Lazy approach), just invert the ghost direction. 

bool goCheckY = true;
bool fail = true;  

If the difference on X is greater than it is on Y: 

if (Math.Abs(xDifference) >= Math.Abs(yDifference))
{
    //move on xaxis
    if (xDifference < 0) //target is to my right
    {
        if (!currentTile.getTileToRight().isWall()) // if it's not a wall
        {
            //I can move to right!
            setDirection(1);
            goCheckY = false; //No need to go to Y
            fail = false;     //we did not fail
        }
    }
    else
    {
        if (!currentTile.getTileToLeft().isWall()) // if it's not a wall
        {
            //I can move to left!
            setDirection(3);
            goCheckY = false; //No need to go to Y
            fail = false;     //we did not fail
        }
    }
}

If goCheckY is true, then either the difference in Y's is greater, which means we did not enter the previous if statement, or we couldn't move more to any adjacent tile on the x-axis. 

if (goCheckY)
{
    //move on yaxis
    if (yDifference < 0) //target is to my right
    {
        if (!currentTile.getTileBelow().isWall()) // if it's not a wall
        {
            //I can move to below!
            setDirection(2);
            fail = false;
        }
    }
    else
    {
        if (!currentTile.getTileAbove().isWall()) // if it's not a wall
        {
            //I can move to top!
            setDirection(0);
            fail = false;
        }
    }
}

If fail is still true, then we couldn't move on any direction. Thus, just go back, ghost, where you came from. 

            if (fail)
            {
                if (direction == 0)
                {
                    direction = 2;
                }
                else if (direction == 1)
                {
                    direction = 3;
                }
                else if (direction == 2)
                {
                    direction = 0;
                }
                else if (direction == 3)
                {
                    direction = 1;
                }
            }
        }
    }
}

Tile 

This class represents a tile. A place where an actor can exists. There are different types of tiles. The following list shows them:

  • Wall Tile: Nothing can exist in this.
  • Dot Tile: Holds a dot, and an Actor can move in it.
  • Super Dot Tile: Also a dot tile, but holds a Super dot.
  • Ghost Dot Tile: Also a dot tile, but a ghost can think here.
  • Ghost Super Dot Tile: Also a super dot tile, but a ghost can think here. 

So, you can think that there's some inheritance relation between them. However, this is not represented as inheritance and the code; It's just some variables holding these information. 

public class Tile
{

The following four variables hold the adjacent tiles to a tile. 

protected Tile tileAbove;
public Tile getTileAbove() { return this.tileAbove; }
public void setTileAbove(Tile newTile) { this.tileAbove = newTile; }

protected Tile tileBelow;
public Tile getTileBelow() { return this.tileBelow; }
public void setTileBelow(Tile newTile) { this.tileBelow = newTile; }

protected Tile tileToRight;
public Tile getTileToRight() { return this.tileToRight; }
public void setTileToRight(Tile newTile) { this.tileToRight = newTile; }

protected Tile tileToLeft;
public Tile getTileToLeft() { return this.tileToLeft; }
public void setTileToLeft(Tile newTile) { this.tileToLeft = newTile; }

The X and Y position of a tile: 

protected short X;
public short getX() { return this.X; }
public void setX(short newX) { this.X = newX; }

protected short Y;
public short getY() { return this.Y; }
public void setY(short newY) { this.Y = newY; }

This bool tells us if a tile had a dot or not, regardless of the fact if it's a super dot or not. 

protected bool dot;
public bool hasDot() { return dot; }
public void setDot(bool newDot) { this.dot = newDot; }

This bool, however, tells us if the dot is a super dot or not. 

protected bool superDot;
public bool isSuperDot() { return superDot; }
public void setSuperDot(bool newSuperDot) { this.superDot = newSuperDot; }

A bool that tells us if it's a wall or not: 

protected bool wall;
public bool isWall() { return wall; }
public void setWall(bool newWall) { this.wall = newWall; }

An image for the tile. This should be an image of a dot, or a super dot, if any holds. 

protected Image image;
public Image getImage() { return this.image; }
public void setImage(string path)
{
    this.image = Image.FromFile(path);
}
public void setImage(Image otherImage)
{
    this.image = otherImage;
}

Lastly, this bool tells us it the tile is a magical tile for the ghosts (Where they can think) 

private bool ghostTile;
    public bool isGhostTile() { return this.ghostTile; }
    public void setGhostTile(bool value) { this.ghostTile = value; }

    public Tile(){} //Just a plain constructor...
} 

Maze

This class represents a maze, which holds all the tiles. This class also constructs the tiles once its self is created. Notice that the constructor saves us from creating tiles one-by-one by hand. I think it's kind of a smart way this is implemented. Basically, we have a text file as shown below, where the tiles are sorted according to their types, mentioned previously.

WWWWWWWWWWWWWWWWWWWWWWWWWWWW
WdDDDDdDDDDDsWWsDDDDDdDDDDdW
WDWWWWDWWWWWDWWDWWWWWDWWWWDW
WDWWWWDWWWWWDWWDWWWWWDWWWWDW
WDWWWWDWWWWWDWWDWWWWWDWWWWDW
WdDDDDdDDDDDdDDdDDDDDdDDDDdW
WDWWWWDWWDWWWWWWWWDWWDWWWWDW
WDWWWWDWWDWWWWWWWWDWWDWWWWDW
WsDDDDdWWdDDdWWdDDdWWdDDDDsW
WWWWWWDWWWWWDWWDWWWWWDWWWWWW
WWWWWWDWWWWWDWWDWWWWWDWWWWWW
WWWWWWDWWdDDdDDdDDdWWDWWWWWW
WWWWWWDWWDWWWWWWWWDWWDWWWWWW
WWWWWWDWWDWWWWWWWWDWWDWWWWWW
WWWWWWdDDsWWWWWWWWsDDdWWWWWW
WWWWWWDWWDWWWWWWWWDWWDWWWWWW
WWWWWWDWWDWWWWWWWWDWWDWWWWWW
WWWWWWdWWdDDDDDDDDdWWdWWWWWW
WWWWWWDWWDWWWWWWWWDWWDWWWWWW
WWWWWWDWWDWWWWWWWWDWWDWWWWWW
WdDDDDsDDdDDdWWdDDdDDsDDDDdW
WDWWWWDWWWWWDWWDWWWWWDWWWWDW
WDWWWWDWWWWWDWWDWWWWWDWWWWDW
WdDdWWdDDDDDdDDdDDDDDdWWdDdW
WWWDWWDWWDWWWWWWWWDWWDWWDWWW
WWWDWWDWWDWWWWWWWWDWWDWWDWWW
WsDdDDdWWdDDdWWdDDdWWdDDdDsW
WDWWWWWWWWWWDWWDWWWWWWWWWWDW
WDWWWWWWWWWWDWWDWWWWWWWWWWDW
WdDDDDDDDDDDsDDsDDDDDDDDDDdW
WWWWWWWWWWWWWWWWWWWWWWWWWWWW 

If you carefully examine the previous text, you will notice it represents the background shown in the second picture in the Game Logic section.

The different characters in the text represent types of the tiles as follows: 

  • Wall Tile: W 
  • Dot Tile: D 
  • Super Dot Tile: S 
  • Ghost Dot Tile: d 
  • Ghost Super Dot Tile: s 

Parsing the text, we can easily construct our tiles. 

public class Maze
{

textualMaze will hold lines of strings which holds the characters of the tiles. The Tile 2D array is 28 tiles wide and 31 tiles in height and will hold tiles that spans all over the background. 

protected string[] textualMaze;
public Tile[,] tiles = new Tile[28,31];   //The maze must be of 28tiles wide, 31tiles high

totalScore is dynamically calculated (10 for each dot and 100 for each super dot). This is a pure example of laziness because I didn't want to manually calculate it. The totalScore use will be explained futher ahead.  

public static int totalScore = 0;

First thing to do in the constructor, is to read all the lines in the text file, and save it in out textualMaze array. 

public Maze()
{
    textualMaze = System.IO.File.ReadAllLines("maze.txt");

The following for-loop will loop 28x31 times just to initialize all the tiles. It may have been merged with the next loop, but I didn't. Again, I don't recall why. 

for (short i = 0; i < 28; i++)        //this will be X
{
    for (short j = 0; j < 31; j++)    //this will be Y
    {
        tiles[i, j] = new Tile();          //Just initilize it
    }
}

This is where the logic for creating the maze lays. First of all, we a string at index i, and take a character from it at index j. 

for (short i = 0; i < 28; i++)        //this will be X
{
    for (short j = 0; j < 31; j++)    //this will be Y
    {
        char readChar = textualMaze[j][i];  //textualMaze[j] <-- up to here it gives a string
                                            //textualMaze[j][i] <-- this is a char in a string

Next step, we set the X and Y positions of the tile by simply multiplying i and j by 20. 

//Creating the tile ------------------------------------------------

tiles[i, j].setX((short)(i * 20)); //Set x
tiles[i, j].setY((short)(j * 20)); //Set y

If the char we read was NOT a 'W', we set it as a dot tile and not a wall. Else, we set the inverse. 

if (readChar != 'W')        //if it isn't a wall, it's a dot.
{
    tiles[i, j].setDot(true);
    tiles[i, j].setWall(false);
}
else
{
    tiles[i, j].setDot(false);
    tiles[i, j].setWall(true);
}

If the char read was 'D' or 'd', then it's a dot. We set it its superDot bool as false, and its image to that of a dot. We also increment the totalScore by 10. 

if (readChar == 'D' || readChar == 'd')        //this is not a super dot.
{
    totalScore += 10;
    tiles[i, j].setSuperDot(false);
    tiles[i, j].setImage("img/dotTile.png");
}

Else if the read char was 'S' or 's', then we set it as a super dot and load the super dot image. We also increment the totalScore by 100.  

else if (readChar == 'S' || readChar == 's')   // this is.
{
    totalScore += 100;
    tiles[i, j].setSuperDot(true);
    tiles[i, j].setImage("img/superDotTile.png");
}

Here, if the read char was either 'd' or 's', it mean itss a ghost tile. Else, it's not. 

if (readChar == 'd' || readChar == 's')
    tiles[i, j].setGhostTile(true);
else
    tiles[i, j].setGhostTile(false);

Here, we check (on x-axis) if i is greater than 0 and less than 27, because there are no tiles to the left of 0 and no right tiles to the right of 27. Also, there is a small flaw in this logic as we will not set the adjacent tiles to those in the edges of our maze. It actually won't matter since they all are walls and Actors will never exist there. The same logic goes for the y-axis. 

//--------------------------------------------------------------------
            //Setting its adjacent tiles------------------------------------------
            if (i > 0 && i < 27)         //@0 and @27 are null becuase there's nothing to the left or right there
            {
                tiles[i, j].setTileToLeft(tiles[i - 1, j]);
                tiles[i, j].setTileToRight(tiles[i + 1, j]);
            }
            if (j > 0 && j < 30)         //@0 and @30 are null becuase there's nothing above or below there
            {
                tiles[i, j].setTileAbove(tiles[i, j - 1]);
                tiles[i, j].setTileBelow(tiles[i, j + 1]);
            }
        }
    }
}

A getter that will get a tile by its number in the tiles array 

public Tile getTile(int i, int j)
{
    return tiles[i, j];
}

A getter that will get a tile by its X and Y position. We just divide by 20 and Voila, we get its position in the array. 

public Tile findTile(int i, int j)
    {
        return tiles[i / 20, j / 20];
    }

    public Tile[,] getTiles() //just a getter for all tiles.
    {
        return this.tiles;
    }
} 

MainMenu 

This is a class that extends Form. It will be the first thing the user sees when the application is launched. The following image shows how it looks like: 

 

Now let's get back to the code: 

public partial class MainMenu : Form
{

Four variables: isHost to tell if this game is a host or not. remoteIP to save the IP address of the other player, a thread to have the connection in it, and a UdpClient for sending/receiving requests.  The constructor is just a normal form constructor.  

private bool isHost;
private string remoteIP;

private Thread connectionThread;
private UdpClient client;

public MainMenu()
{
    InitializeComponent();
}

This is the method that will be called when a client clicks on the Host button. First of all, we disable all the controls, show the user a text that we're waiting for a client, and then we run a method that waits for a client on another thread.  

private void hostGameButton_Click(object sender, EventArgs e)
{
    hostGameButton.Enabled = false;
    joinGameButton.Enabled = false;
    hostIPTextBox.Enabled = false;
    statusLabel.Text = "Waiting for a player to join...";
    try
    {
        connectionThread = new Thread(waitForClientResponse);
        connectionThread.Start();
    }
    catch (Exception exception)
    {
        hostGameButton.Enabled = true;
        joinGameButton.Enabled = true;
        hostIPTextBox.Enabled = true;
        statusLabel.Text = "Error awaiting a player to join! Please try again.";
    }
}

Otherwise, if the client click on the Join button, first we also disable all the controls, but then we take the IP Address that he entered, and then run the sendConnectionRequest method, which does what it's named, on a different thread. 

private void joinGameButton_Click(object sender, EventArgs e)
{
    hostGameButton.Enabled = false;
    joinGameButton.Enabled = false;
    hostIPTextBox.Enabled = false;
    statusLabel.Text = "Joining game...";
    try
    {
        connectionThread = new Thread(sendConnectionRequest);
        connectionThread.Start();
    }
    catch (Exception exception)
    {
        hostGameButton.Enabled = true;
        joinGameButton.Enabled = true;
        hostIPTextBox.Enabled = true;
        statusLabel.Text = "Error sending join request! Please try again.";
    }
}

The third line in this method will block. Waiting for a client to send a specific string "JOIN_REQUEST" which then we will know that he wants to join. Therefore, we will send him a "JOIN_REQUEST_ACEEPTED" message and open the game window locally. Notice that we set isHost to true. 

private void waitForClientResponse()
{
    client = new UdpClient(new IPEndPoint(IPAddress.Any, 12345));
    IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 12345);
    byte[] data = client.Receive(ref remoteEndPoint);
    remoteIP = remoteEndPoint.Address.ToString();
    if (ASCIIEncoding.ASCII.GetString(data).Equals("JOIN_REQUEST"))
    {
        client.Send(ASCIIEncoding.ASCII.GetBytes("JOIN_REQUEST_ACCEPTED"), 21, remoteEndPoint);
        isHost = true;
        statusLabel.Invoke((MethodInvoker)delegate() { statusLabel.Text = "A Player has joined the game!"; });
        openGameWindow();
    }
    client.Close();
}

This method will directly send a message "JOIN_REQUEST" to the host at the IP Address supplied by user in the user interface. Next, we will wait for the client to send back "JOIN_REQUEST_ACCEPTED". If done correctly, we will set isHost to false, the open the game window. 

private void sendConnectionRequest()
{
    client = new UdpClient(hostIPTextBox.Text, 12345);
    client.Send(ASCIIEncoding.ASCII.GetBytes("JOIN_REQUEST"), 12);
    IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse(hostIPTextBox.Text), 12345);
    byte[] data = client.Receive(ref remoteEndPoint);
    remoteIP = remoteEndPoint.Address.ToString();
    if (ASCIIEncoding.ASCII.GetString(data).Equals("JOIN_REQUEST_ACCEPTED"))
    {
        isHost = false;
        statusLabel.Invoke((MethodInvoker)delegate() { statusLabel.Text = "You have joined the game!"; });
        openGameWindow();
    }
    client.Close();
}

Opening a game window, creates a a thread that creates a GameWindow, starts it and then hides this form. 

private void openGameWindow()
{
    Thread t = new Thread(ThreadProc);
    t.Start();
}

private void ThreadProc()
{
    this.Invoke((MethodInvoker)delegate() { this.Hide(); });
    Application.Run(new GameWindow(isHost, remoteIP, this));
}

When the form is closed, we make sure the connection is also closed and then close this form. 

private void MainMenu_FormClosing(object sender, FormClosingEventArgs e)
{
        if (connectionThread != null)
        {
            if (client != null)
            {
                client.Close();
            }
            connectionThread.Abort();
        }
    }
} 

GameWindow 

This is a class that extends Form. This is the class that the game will be drawn into.

public partial class GameWindow : Form
{

A Maze, Two pacman players (Yellow for the host and Green for the client), and four ghosts. The gameLogic holds the Logic object that controls the game. yellowScore and greenScores are self explanatory. We have here a reference to a MainMenu object just for un-hiding it after the game finishes. 

public Maze maze;
public Pacman yellowPacman;
public Pacman greenPacman;
public Ghost ghost0;
public Ghost ghost1;
public Ghost ghost2;
public Ghost ghost3;

private Logic gameLogic;

private int yellowScore = 0;
private int greenScore = 0;

private MainMenu mainMenu;

After constructing the game, we will initializeGame which will be explained in a moment, then create the Logic object depending on weather this game isAHostedGame or not. After that, we will subscribe this game window to the event in Logic class (Explained in a moment) with our refreshUI method. The unhide method will only unhide the main menu. 

public GameWindow(bool isAHostedGame, string remoteIP, MainMenu mainMenu)
{
    this.mainMenu = mainMenu;

    InitializeComponent();
    initializeGame();

    if (isAHostedGame)
        this.gameLogic = new HostLogic(remoteIP, this);
    else
        this.gameLogic = new ClientLogic(remoteIP, this);

    gameLogic.gameStateChange += refreshUI;
    gameLogic.startGame();
}

public void unhideMainMenu()
{
    mainMenu.Invoke((MethodInvoker)delegate() { this.mainMenu.Show(); }); 
}

This method was made separate from the constructor for clarity. All of the Actors are created in a fixed position in the maze, using their constructors that were mentioned in their sections up above. 

private void initializeGame()
{
    maze = new Maze();
    yellowPacman = new Pacman(maze.getTile(12, 17), "img/pacman_yellow_1.png", 3, true);
    greenPacman = new Pacman(maze.getTile(15, 17), "img/pacman_green_1.png", 1, false);
    ghost0 = new Ghost(maze.getTile(1, 1),
        "img/ghost_yellow_up.png",
        "img/ghost_yellow_right.png",
        "img/ghost_yellow_down.png",
        "img/ghost_yellow_left.png", 1, greenPacman);
    ghost1 = new Ghost(maze.getTile(1, 29),
        "img/ghost_yellow_up.png",
        "img/ghost_yellow_right.png",
        "img/ghost_yellow_down.png",
        "img/ghost_yellow_left.png", 0, greenPacman);
    ghost2 = new Ghost(maze.getTile(26, 1),
        "img/ghost_green_up.png",
        "img/ghost_green_right.png",
        "img/ghost_green_down.png",
        "img/ghost_green_left.png", 2, yellowPacman);
    ghost3 = new Ghost(maze.getTile(26, 29),
        "img/ghost_green_up.png",
        "img/ghost_green_right.png",
        "img/ghost_green_down.png",
        "img/ghost_green_left.png", 3, yellowPacman);
}

The following method will only invalidate the form. Which will trigger the paint event, thus repainting everything. 

private void refreshUI(object sender, EventArgs e)
{
    this.Invalidate();
}

This method will be run whenever the refreshUI is called. 

private void GameWindow_Paint(object sender, PaintEventArgs e)
{
    try
    {

First of all, we get the graphics object from the paint event args. 

Graphics g = e.Graphics;

Notice that we are cloning every image object we have so that we never have any UI-Cross-Thread exceptions. Now, for each tile in the maze, we will draw its image at its position. Then we will draw all the actors in their position -4 on x-axis and -4 on the y-axis because their images are 28x28 pixels and our background has the walls 4 pixels away from its edges. 

foreach (Tile t in maze.getTiles())
        {
            if (t.hasDot())
            {
                if (t.isSuperDot())
                    g.DrawImage((Image)t.getImage().Clone(), new Point(t.getX(), t.getY()));
                else
                    g.DrawImage((Image)t.getImage().Clone(), new Point(t.getX(), t.getY()));
            }
        }
        g.DrawImage((Image)ghost0.getImage().Clone(), new Point(ghost0.getX() - 4, ghost0.getY() - 4));
        g.DrawImage((Image)ghost1.getImage().Clone(), new Point(ghost1.getX() - 4, ghost1.getY() - 4));
        g.DrawImage((Image)ghost2.getImage().Clone(), new Point(ghost2.getX() - 4, ghost2.getY() - 4));
        g.DrawImage((Image)ghost3.getImage().Clone(), new Point(ghost3.getX() - 4, ghost3.getY() - 4));
        g.DrawImage((Image)yellowPacman.getImage().Clone(), new Point(yellowPacman.getX() - 4, yellowPacman.getY() - 4));
        g.DrawImage((Image)greenPacman.getImage().Clone(), new Point(greenPacman.getX() - 4, greenPacman.getY() - 4));
        yellowScoreLabel.Text = "" + gameLogic.getYellowScore();
        greenScoreLabel.Text = "" + gameLogic.getGreenScore();
    }
    catch (Exception excp)
    {

    }
}

For this method, it's a listener for whenever a player presses the keyboard arrows. We just set the direction of the gameLogic to 0,1,2 or 3 as discussed before. 

private void GameWindow_KeyDown(object sender, KeyEventArgs e)
{
        if (e.KeyCode == Keys.Up) { this.gameLogic.setDirection(0); }
        else if (e.KeyCode == Keys.Right) { this.gameLogic.setDirection(1); }
        else if (e.KeyCode == Keys.Down) { this.gameLogic.setDirection(2); }
        else if (e.KeyCode == Keys.Left) { this.gameLogic.setDirection(3); }
    }
} 

Logic  

This is an abstract (Just not used really) that will hold some type of logic for the game. 

public class Logic
{

First of all, we have a delegate that we create an event from. This event will notify its subscribers that the game state have been change.  

public delegate void ChangedEventHandler(object sender, EventArgs e);
 
public event ChangedEventHandler gameStateChange;
protected void OnGameStateChanged(EventArgs e)
{
    if (gameStateChange != null)
    {
        gameStateChange(this, e);
    }
}

The Scores for both Yellow Pacman and Green Pacman 

protected int yellowScore = 0;
public int getYellowScore() { return this.yellowScore; }
protected int greenScore = 0;
public int getGreenScore() { return this.greenScore; }

The remote IP Address of the other player 

private string remoteIP;
public string getRemoteIP() { return this.remoteIP; }
public void setRemoteIP(string newRemoteIP) { this.remoteIP = newRemoteIP; }

The direction that the user inputs into the game 

protected byte direction;
public void setDirection(byte newDirection) { this.direction = newDirection; }

The GameWindow instance that the logic is tied with 

protected GameWindow gameWindow;

Just a constructor  

public Logic(string newRemoteIP, GameWindow gameWindow)
{
    this.setRemoteIP(newRemoteIP);
    this.gameWindow = gameWindow;
}

Finally, two virtual methods that will be overridden in children of Logic 

public virtual void recieveDirection(object sender, EventArgs e) { }
   public virtual void startGame() { }
}

HostLogic 

This class represents the logic that runs at the Host side. It extends Logic.

public class HostLogic : Logic
{

This thread will run the logic along with connection to the client. 

private Thread connectionThread;  

The method startGame can be called to this class notifying it that it should start the game logic 

public override void startGame()
{
    base.startGame();
    connectionThread = new Thread(tick);
    connectionThread.Start();
}

This is where all the magic happens. tick() starts with a Sleep(2000) (Two Seconds) to make sure that the client has also set its self up. The byte directionRecieved will hold the direction that the client and will be used to change Green Pacman's direction. 

public void tick()
{
    byte directionRecieved = 0;
    Thread.Sleep(2000);

This while-loop will keep going forever until it has been from within.  It will sleep for a short while of 280 milli second. 

while (true)
{
    Thread.Sleep(280); //This is the "Tick"

Here, we just set the direction of Yellow Pacman (since this is the host) to whatever we have in our variable direction. It keeps getting modified from the gameWindow object discussed previously. 

//modify yellowPacman direction before doing any logic
this.gameWindow.yellowPacman.setDirection(this.direction);
this.gameWindow.greenPacman.setDirection(qwe);

Here, if pacman is standing on a tile that has a dot, it get's eaten. 

//Do some logic.
//if pacman's tile has a dot, eat it
if (this.gameWindow.yellowPacman.getCurrentTile().hasDot())
{
    //eat it
    this.gameWindow.yellowPacman.getCurrentTile().setDot(false);
    this.yellowScore += 10; 

Now if it's a super dot, we do something different. We set the target of all the ghosts to the green pacman and set their images too. We add 90 more points to the score since previous we added 10. 

    //if it's a super dot, then all ghosts should change their target
    if (this.gameWindow.yellowPacman.getCurrentTile().isSuperDot())
    {
        this.yellowScore += 90;

        this.gameWindow.ghost0.setTarget(this.gameWindow.greenPacman);
        this.gameWindow.ghost1.setTarget(this.gameWindow.greenPacman);
        this.gameWindow.ghost2.setTarget(this.gameWindow.greenPacman);
        this.gameWindow.ghost3.setTarget(this.gameWindow.greenPacman);
        string i0 = "img/ghost_yellow_up.png";
        string i1 = "img/ghost_yellow_right.png";
        string i2 = "img/ghost_yellow_down.png";
        string i3 = "img/ghost_yellow_left.png";
        this.gameWindow.ghost0.setImages(i0, i1, i2, i3);
        this.gameWindow.ghost1.setImages(i0, i1, i2, i3);
        this.gameWindow.ghost2.setImages(i0, i1, i2, i3);
        this.gameWindow.ghost3.setImages(i0, i1, i2, i3);
    }
}

Same logic as discussed before, for the green pacman now.  

//if pacman's tile has a dot, remove it
if (this.gameWindow.greenPacman.getCurrentTile().hasDot())
{
    //eat it
    this.gameWindow.greenPacman.getCurrentTile().setDot(false);
    this.greenScore += 10;

    //if it's a super dot, then all ghosts should change their target
    if (this.gameWindow.greenPacman.getCurrentTile().isSuperDot())
    {
        this.greenScore += 90;
        this.gameWindow.ghost0.setTarget(this.gameWindow.yellowPacman);
        this.gameWindow.ghost1.setTarget(this.gameWindow.yellowPacman);
        this.gameWindow.ghost2.setTarget(this.gameWindow.yellowPacman);
        this.gameWindow.ghost3.setTarget(this.gameWindow.yellowPacman);
        string i0 = "img/ghost_green_up.png";
        string i1 = "img/ghost_green_right.png";
        string i2 = "img/ghost_green_down.png";
        string i3 = "img/ghost_green_left.png";
        this.gameWindow.ghost0.setImages(i0, i1, i2, i3);
        this.gameWindow.ghost1.setImages(i0, i1, i2, i3);
        this.gameWindow.ghost2.setImages(i0, i1, i2, i3);
        this.gameWindow.ghost3.setImages(i0, i1, i2, i3);
    }
}

Here, we tell all the ghosts to set their directions to follow their targets. 

this.gameWindow.ghost0.seekTarget();
this.gameWindow.ghost1.seekTarget();
this.gameWindow.ghost2.seekTarget();
this.gameWindow.ghost3.seekTarget();

We move all the actors: 

this.gameWindow.yellowPacman.move();
this.gameWindow.greenPacman.move();
this.gameWindow.ghost0.move();
this.gameWindow.ghost1.move();
this.gameWindow.ghost2.move();
this.gameWindow.ghost3.move();

Now before into getting to a bit of a tricky part, I'd like to explain to you what data will be sent to the client. Refer to the following table: 

Notice that each index represents a byte. Thus, the following should be easy to figure out: 

byte[] data = new byte[40];
 
byte[] temp = BitConverter.GetBytes(this.gameWindow.yellowPacman.getX());
data[0] = temp[0];
data[1] = temp[1];

temp = BitConverter.GetBytes(this.gameWindow.yellowPacman.getY());
data[2] = temp[0];
data[3] = temp[1];

data[4] = (byte)this.gameWindow.yellowPacman.getDirection();

temp = BitConverter.GetBytes(this.gameWindow.greenPacman.getX());
data[5] = temp[0];
data[6] = temp[1];

temp = BitConverter.GetBytes(this.gameWindow.greenPacman.getY());
data[7] = temp[0];
data[8] = temp[1];

data[9] = (byte)this.gameWindow.greenPacman.getDirection();

temp = BitConverter.GetBytes(this.gameWindow.ghost0.getX());
data[10] = temp[0];
data[11] = temp[1];

temp = BitConverter.GetBytes(this.gameWindow.ghost0.getY());
data[12] = temp[0];
data[13] = temp[1];

data[14] = (byte)this.gameWindow.ghost0.getDirection();

temp = BitConverter.GetBytes(this.gameWindow.ghost1.getX());
data[15] = temp[0];
data[16] = temp[1];

temp = BitConverter.GetBytes(this.gameWindow.ghost1.getY());
data[17] = temp[0];
data[18] = temp[1];
 
data[19] = (byte)this.gameWindow.ghost1.getDirection();

temp = BitConverter.GetBytes(this.gameWindow.ghost2.getX());
data[20] = temp[0];
data[21] = temp[1];

temp = BitConverter.GetBytes(this.gameWindow.ghost2.getY());
data[22] = temp[0];
data[23] = temp[1];

data[24] = (byte)this.gameWindow.ghost2.getDirection();

temp = BitConverter.GetBytes(this.gameWindow.ghost3.getX());
data[25] = temp[0];
data[26] = temp[1];

temp = BitConverter.GetBytes(this.gameWindow.ghost3.getY());
data[27] = temp[0];
data[28] = temp[1];

data[29] = (byte)this.gameWindow.ghost3.getDirection();

temp = BitConverter.GetBytes(yellowScore);
data[30] = temp[0];
data[31] = temp[1];
data[32] = temp[2];
data[33] = temp[3];

temp = BitConverter.GetBytes(greenScore);
data[34] = temp[0];
data[35] = temp[1];
data[36] = temp[2];
data[37] = temp[3];

data[38] = 0; // 1 if game has fin
data[39] = 0; // 0=yellow, 1=green, 2=draw :(

if (this.gameWindow.ghost0.targetDies())
{
    data[38] = 1;
    if (this.gameWindow.ghost0.getTarget().isYellow())
    {
        data[39] = 1;
    }
}
else if (this.gameWindow.ghost1.targetDies())
{
    data[38] = 1;
    if (this.gameWindow.ghost0.getTarget().isYellow())
    {
        data[39] = 1;
    }
}
else if (this.gameWindow.ghost2.targetDies())
{
    data[38] = 1;
    if (this.gameWindow.ghost0.getTarget().isYellow())
    {
        data[39] = 1;
    }
}
else if (this.gameWindow.ghost3.targetDies())
{
    data[38] = 1;
    if (this.gameWindow.ghost0.getTarget().isYellow())
    {
        data[39] = 1;
    }
}
else if (this.yellowScore + this.greenScore == Maze.totalScore)
{
    data[38] = 1;
    if (this.yellowScore < this.greenScore) data[39] = 1;
    else if (this.yellowScore == this.greenScore) data[39] = 2;
}

//send data to client

if (data[38] == 1)
{
    string textToShow = "";
    if (data[39] == 0)
    {
        textToShow = "Yellow Has won the game! You have Won!";
    }
    else if(data[39] == 1)
    {
        textToShow = "Green Has won the game! You have lost, Yellow :-(";
    }
    else
    {
        textToShow = "OMG It's a draw!";
    }
    Thread t = new Thread(new ParameterizedThreadStart(exit)); //check last method
    t.Start(textToShow);
    this.gameWindow.Invoke((MethodInvoker)delegate() { this.gameWindow.unhideMainMenu(); this.gameWindow.Close(); });
}

This is where the sending takes place. Notice that we're putting it in a loop while the client hasn't sent anything back within the time out of the UdpClient. In the code here, however, I've removed that line for debugging purposes. You can easily add it. After sending, we wait for the client to send green pacman's direction. 

            bool recieved = false;
            while (!recieved)
            {
                try
                {
                    UdpClient UDPclient = new UdpClient(getRemoteIP(), 12345);
                    IPEndPoint dummyPoint = new IPEndPoint(IPAddress.Any, 0);
                    UDPclient.Send(data, data.Length);
                    UDPclient.Close();

                    //Recieve state from client
                    IPEndPoint localEP = new IPEndPoint(IPAddress.Any, 12345);
                    UDPclient = new UdpClient(localEP);
                    IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);

                    directionRecieved = UDPclient.Receive(ref remoteEP)[0];
                    UDPclient.Close();
                    recieved = true;
                }
                catch (Exception e)
                {
                    recieved = false;
                }
            }
            if (data[38] == 1) break; //if the game is finished.
            this.OnGameStateChanged(EventArgs.Empty);
        }
    }
    public void exit(object o)
    {
        MessageBox.Show((string)o);
    }
}

ClientLogic 

This is a class that extends Logic. It represents the logic running at the client. This class is similar to the one before. But the only difference is that it only sends Green pacman direction, and receives everything else. So, I will skip till the important parts. 

public class ClientLogic : Logic
{
    private Thread connectionThread;
    public ClientLogic(string newRemoteIP, 
           GameWindow gameWindow) : base(newRemoteIP, gameWindow)
    {
    }

    public override void startGame()
    {
        base.startGame();
        connectionThread = new Thread(tick);
        connectionThread.Start();
    }

    private void tick()
    {
        while (true)
        {
            //DONE: TEST I
            UdpClient UDPclient = new UdpClient(new IPEndPoint(IPAddress.Any, 12345));
            IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
            byte[] data = UDPclient.Receive(ref remoteEndPoint); //this will block
            UDPclient.Close();

            string remoteIPint = remoteEndPoint.Address.ToString();

Basically, exactly as we have sent the information in the HostLogic,  we just put the info back in. Notice that there's and if statement, checking if data[38] == 0 (game has not finished) 

Unfortunately, this is where I got extremely confused (*Ahem* lazy), and just had to redo the dot eating logic again. We cold have sent the tiles' states along, but then the size would be large and the UDP won't be able to send it. 

//dycrypting the protocol!
if (data[38] == 0)
{
    this.gameWindow.yellowPacman.setX(BitConverter.ToInt16(data, 0));
    this.gameWindow.yellowPacman.setY(BitConverter.ToInt16(data, 2));
    this.gameWindow.yellowPacman.setDirection(data[4]);
    Tile yellowTile = this.gameWindow.maze.findTile(BitConverter.ToInt16(data, 0), BitConverter.ToInt16(data, 2));
    if (yellowTile.hasDot())
    {
        //eat it
        yellowTile.setDot(false);

        //if it's a super dot, then all ghosts should change their target
        if (yellowTile.isSuperDot())
        {
            string i0 = "img/ghost_yellow_up.png";
            string i1 = "img/ghost_yellow_right.png";
            string i2 = "img/ghost_yellow_down.png";
            string i3 = "img/ghost_yellow_left.png";
            this.gameWindow.ghost0.setImages(i0, i1, i2, i3);
            this.gameWindow.ghost1.setImages(i0, i1, i2, i3);
            this.gameWindow.ghost2.setImages(i0, i1, i2, i3);
            this.gameWindow.ghost3.setImages(i0, i1, i2, i3);
        }
    }

    this.gameWindow.greenPacman.setX(BitConverter.ToInt16(data, 5));
    this.gameWindow.greenPacman.setY(BitConverter.ToInt16(data, 7));
    this.gameWindow.greenPacman.setDirection(data[9]);

    Tile greenTile = this.gameWindow.maze.findTile(
           BitConverter.ToInt16(data, 5), BitConverter.ToInt16(data, 7));
    if (greenTile.hasDot())
    {
        //eat it
        greenTile.setDot(false);

        //if it's a super dot, then all ghosts should change their target
        if (greenTile.isSuperDot())
        {
            string i0 = "img/ghost_green_up.png";
            string i1 = "img/ghost_green_right.png";
            string i2 = "img/ghost_green_down.png";
            string i3 = "img/ghost_green_left.png";
            this.gameWindow.ghost0.setImages(i0, i1, i2, i3);
            this.gameWindow.ghost1.setImages(i0, i1, i2, i3);
            this.gameWindow.ghost2.setImages(i0, i1, i2, i3);
            this.gameWindow.ghost3.setImages(i0, i1, i2, i3);
        }
    }

    this.gameWindow.ghost0.setX(BitConverter.ToInt16(data, 10));
    this.gameWindow.ghost0.setY(BitConverter.ToInt16(data, 12));
    this.gameWindow.ghost0.setDirection(data[14]);

    this.gameWindow.ghost1.setX(BitConverter.ToInt16(data, 15));
    this.gameWindow.ghost1.setY(BitConverter.ToInt16(data, 17));
    this.gameWindow.ghost1.setDirection(data[19]);

    this.gameWindow.ghost2.setX(BitConverter.ToInt16(data, 20));
    this.gameWindow.ghost2.setY(BitConverter.ToInt16(data, 22));
    this.gameWindow.ghost2.setDirection(data[24]);

    this.gameWindow.ghost3.setX(BitConverter.ToInt16(data, 25));
    this.gameWindow.ghost3.setY(BitConverter.ToInt16(data, 27));
    this.gameWindow.ghost3.setDirection(data[29]);

    this.yellowScore = BitConverter.ToInt32(data, 30);
    this.greenScore = BitConverter.ToInt32(data, 34);

This is the part where we send the green pacman position back to the host. 

UDPclient = new UdpClient(remoteIPint, 12345);
    IPEndPoint dummyPoint = new IPEndPoint(IPAddress.Any, 0);
    UDPclient.Send(new byte[] { this.direction }, 1);
    UDPclient.Close();

    this.OnGameStateChanged(EventArgs.Empty);
}

Else, if the game had finished: 

            else
            {
                string textToShow = "";
                if (data[39] == 0)
                {
                    textToShow = "Yellow Has won the game! You have lost, Green :-(!";
                }
                else if (data[39] == 1)
                {
                    textToShow = "Green Has won the game! You have Won!";
                }
                else
                {
                    textToShow = "OMG It's a draw!";
                }
                MessageBox.Show(textToShow);

                this.gameWindow.Invoke((MethodInvoker)delegate() { 
                       this.gameWindow.unhideMainMenu(); this.gameWindow.Close(); }); 
                
                break;
            }
        }
    }
} 

Closing things up

Again, I'd like to mention that this game might not have been implemented in the best way. However, I really hope you enjoy it.

Please, do comment and let me know what you feel. If you were to criticize something, remember that I do know that It could have been implemented differently. Nevertheless, put it there. Who knows one day I may develop it fully Smile | <img src= " src="http://www.codeproject.com/script/Forums/Images/smiley_smile.gif" />

Best Regards,

Mamoun Alghaslan 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Mamoun Alghaslan
Student King Fahd University of Petroleum & Minerals
Saudi Arabia Saudi Arabia
No Biography provided
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 PinmemberManiezzo7-Jan-13 21:44 
GeneralRe: My vote of 5 PinmemberMamoun Alghaslan7-Jan-13 22:03 
QuestionMy vote of 4 PinmemberSteve Conlan7-Jan-13 20:51 
AnswerRe: My vote of 4 PinmemberMamoun Alghaslan7-Jan-13 22:00 
GeneralMy Vote of 5 PinmemberClark Kent1234-Jan-13 11:12 
GeneralRe: My Vote of 5 PinmemberMamoun Alghaslan4-Jan-13 22:06 
QuestionUdp.Send and Udp.Close. PinmvpPaulo Zemek3-Jan-13 11:18 
AnswerRe: Udp.Send and Udp.Close. PinmemberMamoun Alghaslan3-Jan-13 12:22 
AnswerRe: Udp.Send and Udp.Close. PinmemberMamoun Alghaslan3-Jan-13 12:47 
GeneralMy vote of 4 PinmemberDanielSheets3-Jan-13 9:09 
GeneralRe: My vote of 4 PinmemberMamoun Alghaslan3-Jan-13 12:32 
GeneralRe: My vote of 4 PinmemberSteve Conlan7-Jan-13 20:48 
GeneralRe: My vote of 4 PinmemberDanielSheets8-Jan-13 1:06 
GeneralRe: My vote of 4 PinmemberSteve Conlan28-Jan-13 0:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 4 Jan 2013
Article Copyright 2013 by Mamoun Alghaslan
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid