Introduction
The .NET Framework provides developers with a rich set of class libraries that enable the creation of visually compelling applications that can effectively communicate with each other. The two technologies that afford us this luxury are WPF and WCF. WPF provides the eye-candy while WCF enables communication.
Some Windows [7 and Vista] features also enable discovery and ad-hoc collaboration, making it easier to find peers and send invitations for collaboration. People Near Me (PNM), Windows Contacts, and Invitations are the Windows features that enable discovery and collaboration.
It is with all this in mind that I decided to develop an application that would make use of the previously mentioned technologies, and what better application than a Peer-to-Peer (P2P) chess game. P2P Chess specifically makes use of WPF, WCF, and PNM.
Requirements
I will expect that you are conversant with WPF and WCF. If you're unfamiliar with Peer-to-Peer programming, check out the References section of this article.
To use the application, you require the following:
- A PC running Win 7 or Vista
- Connection to a local network
- Suitable screen real-estate (15" + will do good)
Hopefully you have admin privileges on the machine you intend to run the application and there are other users on the same local network who also have the same app on their machines.
P2P Chess
So you want to challenge some worthy, or unworthy, fellow to a game of chess and you have launched P2P Chess. The first thing you should do is to check whether there are any worthy, or unworthy, fellows on the same local network. To do this, click on the expander button (the down facing arrow on the bottom left of the app) and click on the NewPeerButton
(the button with an image of two fellows).
You'll be presented with a request to enter a name to use when playing. After entering a name and clicking on the Enter button, or pressing the Enter key, the application will check if there are other people who have signed-into PNM on their machines and are on the same local subnet. If there are people who have satisfied those conditions, you'll see them listed and you can select a name and click on the Invite button.
If the application didn't find anyone on the local subnet, you'll be presented with a message box informing you of this unfortunate state of affairs and that you should try again later. But let's consider that people were found and you selected a name and clicked on the Invite button. An invitation will be sent to the invitee and he/she will see the following message box:
In the best case scenario, the invitee is brave enough to accept the invitation and clicks on the Accept button. If the invitee, for reasons best known to him/her, declines the invitation, you'll be presented with a message box informing you of this unfortunate state of affairs. You can then proceed to try challenging someone else. But we shall consider that the invitation was accepted. Since that is the case, the application will assign playing colors and will inform you and your opponent which colors to use.
After clicking the OK button, you can proceed with the game. To select a piece, just click on it, and to move it, click on the square where you intend to move the piece. Selected pieces are slightly opaque.
To castle, select your King and click on the square where you should move. The appropriate Rook will shift to the correct square.
You and your opponent can also engage in some interplay chat using the MessageTxtBox
and the Send button.
If you and your opponent want to start a new game, one of you can click on the NewGameButton
. The application will be reset for a new game when you click Yes in the resulting dialog box.
If you find it better to find another opponent, click the NewPeerButton
(the one with an image of two fellows).
Issues to Note
P2P Chess will enforce chess rules, in regards to how you move a piece; for instance, only Knights can jump over other pieces. There are some rules though that aren't currently enforced so self-discipline and logic will have to prevail. The unenforced rules are:
- Which piece moves first (obviously it is white)
- How many times you can move even if the other player hasn't moved
- Not moving a King to a position of check
En Passant is also currently not supported and you can't undo a move. Despite the fact that you can't undo a move, this doesn't prevent you from moving a piece back to its initial position if it isn't a pawn and you didn't take your opponent's piece.
A Pawn is automatically promoted to a Queen when it reaches the last rank.
Design and Layout
I designed some of the elements of P2P Chess in Expression Design and some in Expression Blend. The chess pieces though are made up of images I found on the net. The content elements/controls that are of importance are PiecesCanvas
and LocationCanvas
. Both are of equal dimensions and are stacked one after the other.
Chess pieces will be added to the PiecesCanvas
. The LocationCanvas
contains 64 Rectangle
objects that assist in piece selection and movement.
NB: The colored squares are not the LocationCanvas
rectangles.
Each of the chess pieces is a UserControl
.
The Code
First off is the service contract:
<ServiceContract()> _
Public Interface IPlayChess
<OperationContract(IsOneWay:=True)> _
Sub SelectOrMovePiece(ByVal rctX As Double, ByVal rctY As Double, _
ByVal pcColor As String)
<OperationContract(IsOneWay:=True)> _
Sub AssignOpponentColor(ByVal myColor As String)
<OperationContract(IsOneWay:=True)> _
Sub AssignOpponentName(ByVal name As String)
<OperationContract(IsOneWay:=True)> _
Sub NewGame()
<OperationContract(IsOneWay:=True)> _
Sub Chat(ByVal message As String, ByVal player As String)
End Interface
The service and client configuration are defined in the application's configuration file.
="1.0" ="utf-8"
<configuration>
<system.serviceModel>
<services>
<service name="p2pChess.MainWindow">
<host>
<baseAddresses>
<add baseAddress="net.p2p://MeshackLabsCollab/P2PChess"/>
</baseAddresses>
</host>
<endpoint name="p2pChessServiceEndpoint"
address=""
binding="netPeerTcpBinding"
bindingConfiguration="BindingUnsecure"
contract="p2pChess.IPlayChess"/>
</service>
</services>
<client>
<endpoint name="p2pChessClientEndpoint"
address="net.p2p://MeshackLabsCollab/P2PChess"
binding="netPeerTcpBinding"
bindingConfiguration="BindingUnsecure"
contract="p2pChess.IPlayChess" />
</client>
<bindings>
<netPeerTcpBinding>
<binding name="BindingUnsecure">
<security mode="None"/>
<resolver mode="Pnrp"/>
</binding>
</netPeerTcpBinding>
</bindings>
</system.serviceModel>
...
</configuration>
The endpoint address is net.p2p://MeshackLabsCollab/P2PChess and the endpoint's binding is set to netPeerTcpBinding
since this is a P2P application. From the address, you can gather that the mesh name is MeshackLabsCollab.
The MainWindow
class is our service and it implements IPlayChess
.
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single)> _
Class MainWindow
Implements IPlayChess
...
The class is decorated with the ServiceBehavior
attribute with InstanceContextMode
set to Single
so that it acts as a singleton.
When the application is loaded, several methods are called:
Private Sub MainWindow_Loaded(ByVal sender As Object, _
ByVal e As System.Windows.RoutedEventArgs) _
Handles Me.Loaded
StartService()
RegisterApp()
LoadPieces()
End Sub
The StartService
method instantiates a ServiceHost
object and call its Open
method. It also creates a channel.
Private Sub StartService()
host = New ServiceHost(Me)
host.Open()
channelFactory = New ChannelFactory(Of IPlayChess)("p2pChessClientEndpoint")
channel = channelFactory.CreateChannel()
End Sub
The RegisterApp
method registers P2P Chess with the peer infrastructure.
Private Sub RegisterApp()
Try
PeerCollaboration.SignIn(PeerScope.NearMe)
Catch ex As PeerToPeerException
MessageBox.Show("Make sure that you're connected to a local network", _
"P2P Chess", MessageBoxButton.OK, MessageBoxImage.Error)
Me.Close()
End Try
Dim apps As PeerApplicationCollection = _
PeerCollaboration.GetLocalRegisteredApplications()
Dim appsCount As Integer = apps.Count
p2pChessApp = New PeerApplication
With p2pChessApp
.Description = "P2P chess game"
.Id = New Guid("faad461e-4d07-4ddb-99f2-94da5caa1124")
.Path = Environment.CurrentDirectory & "\P2P Chess.exe"
.PeerScope = PeerScope.NearMe
End With
If (appsCount > 0) Then
For Each app As PeerApplication In apps
If (app.Id = New Guid("faad461e-4d07-4ddb-99f2-94da5caa1124")) Then
Exit Sub
End If
Next
PeerCollaboration.RegisterApplication(p2pChessApp, _
PeerApplicationRegistrationType.CurrentUser)
Else
PeerCollaboration.RegisterApplication(p2pChessApp, _
PeerApplicationRegistrationType.CurrentUser)
End If
End Sub
Registration of the application with the collaboration infrastructure is necessary if the user of the application intends to send invitations. Registration is typically done during installation of the app and the GUIDs of the application on the inviter's and invitee's machine should be similar. When you run P2P Chess for the first time, registration will be done once and ignored on the other occasions you run the application. Signing-in to PNM is also done in the RegisterApp
method and is necessary to allow application registration or checking of applications already registered on the user's machine.
The LoadPieces
method adds chess pieces to PiecesCanvas
. You can take a look at this method, and other methods that load the chess pieces, in the MainWindow
class.
Other methods to take note of in the MainWindow
class are FindPeers
and InvitePeer
. FindPeers
looks for peers who are currently on the same local network and are signed-into PNM on their machines.
Private Sub FindPeers()
Dim peers As PeerNearMeCollection = PeerCollaboration.GetPeersNearMe
If (peers.Count > 0) Then
For Each peer As PeerNearMe In peers
If (peer.IsOnline = True) Then
PeersListBox.Items.Add(peer.Nickname)
End If
Next
InviteButton.Visibility = Windows.Visibility.Visible
Else
MessageBox.Show("No peers are online." & vbCrLf & "Try again later.", _
"P2P Chess", MessageBoxButton.OK, MessageBoxImage.Information)
PeersGrid.Visibility = Windows.Visibility.Hidden
End If
End Sub
InvitePeer
sends an invitation for establishing a collaboration session.
Private Sub InvitePeer()
If (PeersListBox.SelectedIndex <> -1) Then
Dim peerToInvite As String = PeersListBox.SelectedValue.ToString
Dim peers As PeerNearMeCollection = PeerCollaboration.GetPeersNearMe
Dim nearPeer As PeerNearMe
For Each pr As PeerNearMe In peers
If (pr.Nickname = peerToInvite) Then
nearPeer = pr
End If
Next
otherPlayer = nearPeer.Nickname
Dim response As PeerInvitationResponse = nearPeer.Invite(p2pChessApp, _
"Fancy a game of chess?", _
Nothing)
WaitResponseGrid.Visibility = Windows.Visibility.Visible
PeersGrid.Opacity = 0.5
If (response.PeerInvitationResponseType = PeerInvitationResponseType.Accepted) Then
Dim colors() As String = {"White", "Black"}
Dim rand As New Random
Dim i As Integer = rand.Next(0, 2)
pieceColor = colors(i)
System.Threading.Thread.Sleep(4000)
WaitResponseGrid.Visibility = Windows.Visibility.Hidden
If (pieceColor = "White") Then
channel.AssignOpponentColor("Black")
Else
channel.AssignOpponentColor("White")
End If
channel.AssignOpponentName(otherPlayer)
LocationCanvas.IsEnabled = True
ElseIf (response.PeerInvitationResponseType = _
PeerInvitationResponseType.Declined) Then
WaitResponseGrid.Visibility = Windows.Visibility.Hidden
MessageBox.Show(otherPlayer & " declined your invitation.", "P2P Chess")
ElseIf (response.PeerInvitationResponseType = _
PeerInvitationResponseType.Expired) Then
WaitResponseGrid.Visibility = Windows.Visibility.Hidden
MessageBox.Show("Invitation expired.", "P2P Chess", _
MessageBoxButton.OK, MessageBoxImage.Exclamation)
End If
PeersGrid.Opacity = 1.0
End If
End Sub
In InvitePeer
, we also check on the response to the invitation from the invitee and respond accordingly. If the invitee accepts the invitation, playing colors will be assigned and a call to AssignOpponentColor()
and AssignOpponentName()
will be made on the service. AssignOpponentColor()
mainly functions to assign a color to the invitee, but it also informs players which pieces to play with, among other things.
Public Sub AssignOpponentColor(ByVal color As String) _
Implements IPlayChess.AssignOpponentColor
If (pieceColor Is Nothing) Then
pieceColor = color
End If
If (PeersGrid.Visibility = Windows.Visibility.Visible) Then
PeersGrid.Visibility = Windows.Visibility.Hidden
End If
If (InviteButton.Visibility = Windows.Visibility.Visible) Then
InviteButton.Visibility = Windows.Visibility.Hidden
End If
If (NameGrid.Visibility = Windows.Visibility.Visible) Then
NameGrid.Visibility = Windows.Visibility.Hidden
End If
SendButton.IsEnabled = True
NewGameButton.IsEnabled = True
AddHandler PeerNearMe.PeerNearMeChanged, AddressOf PeerNearMeChangedCallback
LocationCanvas.IsEnabled = True
MessageBox.Show("Play with " & pieceColor & " pieces", "P2P Chess")
End Sub
Piece Selection and Movement
When the user clicks on the board to select or move a piece, the LocationCanvas
MouseLeftButtonDown
event handler is called.
Private Sub LocationCanvas_MouseLeftButtonDown(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs) _
Handles LocationCanvas.MouseLeftButtonDown
Dim mouseX As Double = e.GetPosition(LocationCanvas).X
Dim mouseY As Double = e.GetPosition(LocationCanvas).Y
For Each rct As Rectangle In LocationCanvas.Children
locRctX = Canvas.GetLeft(rct)
locRctY = Canvas.GetTop(rct)
If ((locRctX < mouseX) And ((locRctX + 44) > mouseX)) Then
If ((locRctY < mouseY) And ((locRctY + 44) > mouseY)) Then
channel.SelectOrMovePiece(locRctX, locRctY, pieceColor)
End If
End If
Next
End Sub
SelectOrMovePiece()
checks whether the player intends to select or move a piece. It also checks whether the player is handling his type of pieces (black or white).
Public Sub SelectOrMovePiece(ByVal rctX As Double, ByVal rctY As Double, _
ByVal pceColor As String) _
Implements IPlayChess.SelectOrMovePiece
If (selectedPiece IsNot Nothing) Then
rules.MovePiece(selectedPiece, rctX, rctY, PiecesCanvas)
selectedPiece.Opacity = 1
selectedPiece = Nothing
Else
For Each piece As UIElement In PiecesCanvas.Children
Dim pieceX As Double = Canvas.GetLeft(piece)
Dim pieceY As Double = Canvas.GetTop(piece)
If (pieceX = rctX) And (pieceY = rctY) Then
selectedPiece = piece
If (selectedPiece.ToString.Contains(pceColor)) Then
selectedPiece.Opacity = 0.4
Else
selectedPiece = Nothing
End If
End If
Next
End If
End Sub
If the user intends to move a piece, a call is made to MovePiece()
that resides in the class ChessRules
. MovePiece()
helps to ensure that chess pieces are moved according to the rules of chess.
Friend Sub MovePiece(ByRef selectedPiece As UIElement, _
ByVal rctX As Double, ByVal rctY As Double, _
ByRef piecesCanvas As Canvas)
InitPieceX = Canvas.GetLeft(selectedPiece)
InitPieceY = Canvas.GetTop(selectedPiece)
If (TypeOf (selectedPiece) Is WhiteKing) Or (TypeOf (selectedPiece) Is BlackKing) Then
If ((rctY - InitPieceY) = 44 Or (rctY - InitPieceY) = -44) Then
Move(selectedPiece, rctX, rctY)
ElseIf ((rctX - InitPieceX) = 44 Or (rctX - InitPieceX) = -44) Then
Move(selectedPiece, rctX, rctY)
End If
End If
If (TypeOf (selectedPiece) Is WhiteQueen) Or (TypeOf (selectedPiece) Is BlackQueen) Then
If (((InitPieceY - rctY) - (rctX - InitPieceX) = 0) Or _
((InitPieceY - rctY) - (InitPieceX - rctX) = 0)) Then
Move(selectedPiece, rctX, rctY)
ElseIf (InitPieceX = rctX) Or (InitPieceY = rctY) Then
Move(selectedPiece, rctX, rctY)
End If
End If
If (TypeOf (selectedPiece) Is WhiteBishop) Or (TypeOf (selectedPiece) Is BlackBishop) Then
If (((InitPieceY - rctY) - (rctX - InitPieceX) = 0) Or _
((InitPieceY - rctY) - (InitPieceX - rctX) = 0)) Then
Move(selectedPiece, rctX, rctY)
End If
End If
If (TypeOf (selectedPiece) Is WhiteCastle) Or (TypeOf (selectedPiece) Is BlackCastle) Then
If ((InitPieceX = rctX) Or (InitPieceY = rctY)) Then
Move(selectedPiece, rctX, rctY)
End If
End If
If (TypeOf (selectedPiece) Is WhiteKnight) Or (TypeOf (selectedPiece) Is BlackKnight) Then
Dim xDiff As Integer = CInt(Math.Abs(rctX - InitPieceX))
Dim yDiff As Integer = CInt(Math.Abs(rctY - InitPieceY))
If ((xDiff = 44) And (yDiff = 88)) Or ((xDiff = 88) And (yDiff = 44)) Then
Move(selectedPiece, rctX, rctY)
End If
End If
MoveWhitePawn(selectedPiece, rctX, rctY, piecesCanvas)
MoveBlackPawn(selectedPiece, rctX, rctY, piecesCanvas)
PreventJumpingOfPieces(selectedPiece, rctX, rctY, piecesCanvas)
AvoidTeamPieces(selectedPiece, rctX, rctY, piecesCanvas)
Castle(selectedPiece, rctX, rctY, piecesCanvas)
CapturePiece(selectedPiece, rctX, rctY, piecesCanvas)
capture = False
PromotePawn(selectedPiece, piecesCanvas)
End Sub
You can take a look at the ChessRules
class to get an idea of how the rules are enforced. I've made a great effort to add comments where necessary and the logic is quite simple. Just to note, each chess piece has a size of 44 by 44 and each Rectangle
in LocationCanvas
has a similar size.
Conclusion
I hope that you have learnt something useful from this article and that P2P Chess has enhanced your social interaction with workmates or family. If there are any bugs, please inform me and I'll try my best to iron them out. If you aren't too familiar with Peer-to-Peer programming in .NET, then make sure to check out the links in the References section.
References
History
- 17th Jun, 2011: Initial post.
- 21st Jun, 2011: Updated code to enable castling.