65.9K
CodeProject is changing. Read more.
Home

Network Renju Game

starIconstarIconstarIconemptyStarIconemptyStarIcon

3.00/5 (2 votes)

Jul 6, 2002

2 min read

viewsIcon

74921

downloadIcon

3952

This is a Renju game with network support.

Sample Image - screenshot.jpg

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. It's like
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 CMidiclass 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!