Network Renju Game





3.00/5 (2 votes)
Jul 6, 2002
2 min read

74921

3952
This is a Renju game with network support.
Introduction
Renju is a famous game all over the world which is believed to originate from the East. In this network-supported game, people can connect to his/her opponent directly without any fuss of server. So it's sort of like a P2P network connection.
The connection requester plays the white side and goes second; the opponent (requestee) plays black side and goes first. Who's the first to align his/her 5 continuous pieces in a row in horizontal or vertical or diagonal direction is the winner. If black side wins first, the white side can go one more step because he/she goes second at the beginning.
Details of Code
To write the network part, I used CSocket
class from MFC and inherit my own socket classes from it. I write CListenSocket
class, which is used to monitor the hard-coded port to respond to any connection requests.
// ListenSocket.cpp : implementation file
//
#include "stdafx.h"
#include "Renju.h"
#include "RenjuDoc.h"
#include "RenjuView.h"
#include "ListenSocket.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CListenSocket
CListenSocket::CListenSocket(CRenjuView* pView)
{
m_pView = pView;
}
CListenSocket::~CListenSocket()
{
}
// Do not edit the following lines, which are needed by ClassWizard.
#if 0
BEGIN_MESSAGE_MAP(CListenSocket, CSocket)
//{{AFX_MSG_MAP(CListenSocket)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif // 0
/////////////////////////////////////////////////////////////////////////////
// CListenSocket member functions
void CListenSocket::OnAccept(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class
// not linked now
if(!m_pView->m_bLinked)
{
int result = AfxMessageBox(_T("Want to accept a new game?"), MB_YESNO);
if(result == IDYES)
{
m_pView->RequestAccepted(IDYES);
}
else
{
m_pView->RequestAccepted(IDNO);
}
}
// already linked to others
else
{
m_pView->RequestAccepted(IDNO);
}
CSocket::OnAccept(nErrorCode);
}
I also write CClientSocket
class, which is used to communicate with the opponent after the connection. // ClientSocket.cpp : implementation file
//
#include "stdafx.h"
#include "Renju.h"
#include "RenjuDoc.h"
#include "RenjuView.h"
#include "ClientSocket.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CClientSocket
CClientSocket::CClientSocket(CRenjuView* pView)
{
m_pView = pView;
}
CClientSocket::~CClientSocket()
{
}
// Do not edit the following lines, which are needed by ClassWizard.
#if 0
BEGIN_MESSAGE_MAP(CClientSocket, CSocket)
//{{AFX_MSG_MAP(CClientSocket)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif // 0
/////////////////////////////////////////////////////////////////////////////
// CClientSocket member functions
void CClientSocket::OnReceive(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class
int buf[3];
Receive(buf, 3 * sizeof(int));
// get a newgame request
if(buf[0] == NEWGAME)
{
m_pView->SendMessage(WM_USER_NEWGAME);
}
// partner refused the newgame request
else if(buf[0] == NONEWGAME)
{
m_pView->GetParent()->SetWindowText(_T("Renju - Game over"));
AfxMessageBox(_T("Your partner doesn't want to play a new game now"));
}
// partner disconnect the link to you
else if(buf[0] == DISCONNECT)
{
m_pView->m_bLinked = FALSE;
m_pView->m_bInGame = FALSE;
m_pView->DestroyClientSocket();
m_pView->ResetCoords();
m_pView->GetParent()->SetWindowText(_T("Renju - Ready"));
AfxMessageBox(_T("Your partner disconnected!"));
}
// partner accept the connection request or the newgame request
else if(buf[0] == ACCEPT)
{
m_pView->m_bMyTurn = FALSE;
m_pView->m_bLinked = TRUE;
m_pView->m_bOpponentWin = FALSE;
m_pView->m_bInGame = TRUE;
m_pView->ResetCoords();
m_pView->m_nColor = -1;
m_pView->GetParent()->SetWindowText(_T("Renju - Opponent's turn"));
}
// partner refused the connection request
else if(buf[0] == DECLINE)
{
AfxMessageBox(_T("Your partner cannot play with you right now!"));
m_pView->GetParent()->SetWindowText(_T("Renju - Ready"));
m_pView->DestroyClientSocket();
}
// partner surrender to you
else if(buf[0] == SURRENDER)
{
m_pView->m_bMyTurn = FALSE;
m_pView->m_bInGame = FALSE;
m_pView->GetParent()->SetWindowText(_T("Renju - Game over"));
AfxMessageBox(_T("Your partner surrendered to you.\n"
"You won this game!"));
}
// other messages
else
{
m_pView->OneStep(buf[1], buf[2]);
// even game and game over
if(buf[0] == EVEN)
{
m_pView->m_bMyTurn = FALSE;
m_pView->m_bInGame = FALSE;
m_pView->GetParent()->SetWindowText(_T("Renju - Game over"));
AfxMessageBox(_T("Even game!"));
}
// I lost this game
else if(buf[0] == ULOSE)
{
m_pView->m_bMyTurn = FALSE;
m_pView->m_bInGame = FALSE;
m_pView->GetParent()->SetWindowText(_T("Renju - Game over"));
AfxMessageBox(_T("Sorry!\n"
"You lost this game."));
}
// I won this game
else if(buf[0] == UWIN)
{
m_pView->m_bMyTurn = FALSE;
m_pView->m_bInGame = FALSE;
m_pView->GetParent()->SetWindowText(_T("Renju - Game over"));
AfxMessageBox(_T("Congratulations!\n"
"You won this game."));
}
// opponent won temporarily, waiting for my last step
else if(buf[0] == IWIN)
{
m_pView->m_bMyTurn = TRUE;
m_pView->m_bOpponentWin = TRUE;
m_pView->GetParent()->SetWindowText(_T("Renju - Your turn"));
}
// just an ordinary step
else if(buf[0] == COORDS)
{
m_pView->m_bMyTurn = TRUE;
m_pView->GetParent()->SetWindowText(_T("Renju - Your turn"));
}
}
CSocket::OnReceive(nErrorCode);
}
I fulfilled most of the functions in the CRenjuView
class. In it, I have two member variables of CClientSocket
which are m_pClientSocket
and m_pClientSocket2
. m_pClientSocket
is the main socket used in the game. m_pClientSocket2
is like a backup one. Whenever a connection request comes to the listening socket and m_pClientSocket
is used in a game, the program is responsible to create m_pClientSocket2
and use it to tell the other side that the request cannot be accepted. Of course, if m_pClientSocket
is not in a game, it will be used to do this work or accept the connection request. The part of code is in the function void RequestAccepted(int accept)
. void CRenjuView::RequestAccepted(int accept)
{
if(!m_pClientSocket)
{
m_pClientSocket = new CClientSocket(this);
if(!m_pListenSocket->Accept(*m_pClientSocket))
{
DestroyClientSocket();
GetParent()->SetWindowText(_T("Renju - Ready"));
AfxMessageBox(_T("You cannot accept this request!"));
}
if(accept == IDYES)
{
int buf[3] = {ACCEPT, 0, 0};
if(m_pClientSocket->Send(buf, 3 * sizeof(int)) == SOCKET_ERROR)
{
AfxMessageBox(_T("Connection interrupted!\n"
"Cannot open the new game."));
m_bLinked = FALSE;
m_bInGame = FALSE;
DestroyClientSocket();
GetParent()->SetWindowText(_T("Renju - Ready"));
return;
}
m_bMyTurn = TRUE;
m_bLinked = TRUE;
m_bOpponentWin = FALSE;
m_bInGame = TRUE;
ResetCoords();
m_nColor = 1;
GetParent()->SetWindowText(_T("Renju - Your turn"));
}
else
{
int buf[3] = {DECLINE, 0, 0};
m_pClientSocket->Send(buf, 3 * sizeof(int));
GetParent()->SetWindowText(_T("Renju - Ready"));
DestroyClientSocket();
}
return;
}
if(m_pClientSocket2)
DestroyClientSocket2();
m_pClientSocket2 = new CClientSocket(this);
if(!m_pListenSocket->Accept(*m_pClientSocket2))
{
DestroyClientSocket2();
GetParent()->SetWindowText(_T("Renju - Ready"));
AfxMessageBox(_T("You cannot accept this request!"));
}
int buf[3] = {DECLINE, 0, 0};
m_pClientSocket2->Send(buf, 3 * sizeof(int));
GetParent()->SetWindowText(_T("Renju - Ready"));
DestroyClientSocket2();
}
The current game condition is stored in a 2-D array int m_nCoords[DIVISIONS][DIVISIONS]
. Each element can have one of three values 1(Black), -1(White) and 0(Unoccupied). The message communication during the game is quite straightforward. I won't explain too much about it. I put the message information in an integer array
buf
which has 3 elements. buf[0]
is used to store the pre_defined message type(I comment them in the code so that they are easy to be followed.). buf[1]
and buf[2]
are used to store the position information(x, y) of last step if applicable, so that the corresponding m_Coords[x][y]
can be set.As for the painting, I used the bitmaps and GDI to draw the board and pieces which is just a piece of cake to pros. To draw the round BMP of the piece, I used a mask bitmap(mask.bmp in ./res) to erase the edge of the original rectangular BMP. I learned it from Charles Petzold
void CRenjuView::DrawPieces()
{
CClientDC dc(this);
CBitmap bmWhite, bmBlack, bmMask;
bmWhite.LoadBitmap(IDB_WHITEPIECE);
bmBlack.LoadBitmap(IDB_BLACKPIECE);
bmMask.LoadBitmap(IDB_MASK);
CDC dcMemWhite;
dcMemWhite.CreateCompatibleDC(&dc);
CBitmap* pOld1 = dcMemWhite.SelectObject(&bmWhite);
CDC dcMemBlack;
dcMemBlack.CreateCompatibleDC(&dc);
CBitmap* pOld2 = dcMemBlack.SelectObject(&bmBlack);
CDC dcMemMask;
dcMemMask.CreateCompatibleDC(&dc);
CBitmap* pOld3 = dcMemMask.SelectObject(&bmMask);
dcMemWhite.BitBlt(0, 0, 36, 36, &dcMemMask, 0, 0, SRCAND);
dcMemBlack.BitBlt(0, 0, 36, 36, &dcMemMask, 0, 0, SRCAND);
for(int i = 0; i < DIVISIONS; i++)
for(int j = 0; j < DIVISIONS; j++)
{
if(m_nCoords[i][j] == -1)
{
dc.BitBlt((i + 1) * CELLSIZE - 18,
(j + 1) * CELLSIZE - 18,
36, 36, &dcMemMask,
0, 0, 0x220326);
dc.BitBlt((i + 1) * CELLSIZE - 18,
(j + 1) * CELLSIZE - 18,
36, 36, &dcMemWhite,
0, 0, SRCPAINT);
}
if(m_nCoords[i][j] == 1)
{
dc.BitBlt((i + 1) * CELLSIZE - 18,
(j + 1) * CELLSIZE - 18,
36, 36, &dcMemMask,
0, 0, 0x220326);
dc.BitBlt((i + 1) * CELLSIZE - 18,
(j + 1) * CELLSIZE - 18,
36, 36, &dcMemBlack,
0, 0, SRCPAINT);
}
if(i == m_ptLastStep.x &&
j == m_ptLastStep.y &&
m_nCoords[i][j] != 0)
{
CPen pen(PS_SOLID, 1, RGB(192, 0, 0));
CPen* pOldPen = dc.SelectObject(&pen);
dc.SelectStockObject(NULL_BRUSH);
dc.Rectangle((i + 1) * CELLSIZE - 18,
(j + 1) * CELLSIZE - 18,
(i + 1) * CELLSIZE + 18,
(j + 1) * CELLSIZE + 18);
dc.SelectObject(pOldPen);
}
}
dcMemWhite.SelectObject(pOld1);
dcMemBlack.SelectObject(pOld2);
dcMemMask.SelectObject(pOld3);
}
Finally, to play the background MIDI music, I write my own CMidi
class using MCI API and used it in the CRenjuView
. A Timer is set to play the music continuously.There're some details I left unexplained. If you find it hard to follow, please feel free to tell me. Thanks!