Click here to Skip to main content
15,885,244 members
Articles / Desktop Programming / MFC
Article

Network Renju Game

Rate me:
Please Sign up or sign in to vote.
3.00/5 (2 votes)
1 Jul 20032 min read 73.9K   3.9K   41   5
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.

C++
// 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.
C++
// 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).
C++
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<programming windows="">. It's like
C++
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!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Hey you,
Out there in the cold,
Getting lonely, getting old,
Can you feel me?
Hey you,
Standing in the aisle,
With itchy feet and fading smile,
Can you feel me?
Hey you,
Don't help them to bury the light.
Don't give in without a fight.

Comments and Discussions

 
Generalcan't send from XP to Me Pin
kenshiro710-Feb-04 3:40
kenshiro710-Feb-04 3:40 
I tried your App on my winXP and winMe machine.
When connected from Me first, everything looks fine.
But once connected from XP, overridden function OnReceive of CClientSocket on Me machine never gets called, although messages from Me to XP looks fine.
Do you have any suggestion?
Generalthe CView is destroyed !!! Pin
albatar30-Jul-03 8:03
albatar30-Jul-03 8:03 
GeneralRe: the CView is destroyed !!! Pin
Mingming Lu30-Jul-03 8:44
Mingming Lu30-Jul-03 8:44 
GeneralRe: the CView is destroyed !!! Pin
albatar30-Jul-03 10:23
albatar30-Jul-03 10:23 
GeneralThank You !! Pin
albatar23-Jun-03 0:55
albatar23-Jun-03 0:55 

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

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