Page 1 of 1

wxNim - rather simple game

Posted: Sat Oct 29, 2005 8:22 am
by Ryan Norton
many eons ago I took a java class. In it one of the assignments was to make a Nim game. Well, I managed to come across the java code today and decided to convert it (the player, computer and piles were all in seperate classes in the java version - I lost the computer one along with the AI, so the AI is in need of medical attention here).

Most of the original comments from the java assignment are here.

Code: Select all

/**
*	The game of nim.  Nim (the main class) contains keyboard input routines, <br>
*	information partaining to the upper-right window (or Frame, it's superclass), <br>
*	and 2 internal classes that create and manage windows and data of thier own. <br> <br>
*
*	Note:  I use Hungarian (Microsoft) notation unless directed otherwise	<br>
*	EXAMPLE:  	i = Initializing 	<br>
*				n=Int 				<br>
*				sz=Zero-terminated String (Normal String) 	<br>
*				j=Class (is capitol C in C)	<br>
*														
*	@author Ryan Norton									
*/
		
#include "wx/wxprec.h"

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include <time.h>
#include <math.h>

// --------------------------------------------------------------------------
//  wxNimApp
// --------------------------------------------------------------------------

class wxNimApp : public wxApp
{
public:
    virtual bool OnInit();
};

IMPLEMENT_APP(wxNimApp)


// --------------------------------------------------------------------------
//  wxNimWindow
// --------------------------------------------------------------------------

class wxNimWindow : public wxWindow
{
public:
    wxNimWindow(wxWindow* parent)
       : wxWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
       , m_nPile(0), m_bPile(true), 
         m_szComputerName(wxT("COMPUTER")), m_bComp(false),
         m_szPlayerName(wxT("PLAYER")), m_bName(false),
         m_bValid(true), m_jFont(14, wxFONTFAMILY_DEFAULT, 0, 
    		                     wxFONTWEIGHT_BOLD, false, wxT("Serif")),
         m_bOver(false), m_bWon(false)
    {	
        //clear piles & other arrays
        memset(m_nPiles, 0, sizeof(int) * 3);
        memset(m_nPLM, 0, sizeof(int) * 2);
        memset(m_nCLM, 0, sizeof(int) * 2);
        memset(m_nStats, 0, sizeof(int) * 2);
        
        //events
        Connect(wxID_ANY, wxEVT_PAINT,
                      wxPaintEventHandler(wxNimWindow::OnPaint));
        Connect(wxID_ANY, wxEVT_LEFT_DOWN,
                      wxKeyEventHandler(wxNimWindow::OnLeftDown));
        Connect(wxID_ANY, wxEVT_KEY_DOWN,
                      wxKeyEventHandler(wxNimWindow::OnKeyDown));
        Connect(wxID_ANY, wxEVT_CHAR,
                      wxKeyEventHandler(wxNimWindow::OnChar));
                      
        startNewGame();
    }

    /**
    *	Paints text and graphics: <br>
    *	The rules <br>
    *	Actions the player should take <br>
    *	and Rectanges representing the "sticks"
    *
    *	@param g Nim uses drawString() to draw commands to the player <br>
    *	in the upper-right window
    */
    void OnPaint(wxPaintEvent& WXUNUSED(event))		
    {
        wxPaintDC g(this);
    	//if the game is over
    	if (m_bOver)
    	{
    		wxString szWinner = m_bWon ? m_szPlayerName : m_szComputerName;
    		
    		g.SetFont( wxFont(36, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_ITALIC, 
    		                     wxFONTWEIGHT_BOLD, false, wxT("Serif")) );
    		
    		g.DrawText(wxT("The Winner Is:  ") + szWinner + wxT("!"),
    						550 / 12, 450 / 2);
    		g.SetFont(m_jFont);
    		
    		g.DrawText(wxT("(Click the mouse button to continue)"),
    						550 / 2 - 50, 450 / 2 + 100);
    	}
    	else
    	{	
        	//for for loops
        	int i = 0;
        	
        	//set the current font to a Serif-type font
        	g.SetFont(m_jFont);
        		
        	//get the metrics of the font
        	int nFX, nFY;
            g.GetTextExtent(wxT("X"), &nFX, &nFY);
        	
        	//Show the rules in the lower-right-hand corner
        	g.DrawText("Welcome to Nim!", 211, 251);
        	g.DrawText("How to Play:", 211, 251 + nFY);
        	g.DrawText(	"There are 3 piles, with 1-12 sticks in each pile.", 
        					211, 251 + nFY * 2);	
        	g.DrawText(	"On a turn you can pick up 1-3 sticks", 211, 
        					251 + nFY * 3);			
        	g.DrawText(	"The person who picks up the last stick wins!!!", 211, 
        					251 + nFY * 4);
        	
        	//set the font for the next piece of output
        	g.SetFont(wxFont(18, wxFONTFAMILY_DEFAULT, 0, 
    		                     wxFONTWEIGHT_BOLD, false, wxT("TimesRoman")));
        	
        	//general game info (enter pile/stick #, name change)
        	if (m_bName) 
        	{
        		g.DrawText ("Please Enter Your Name", 250, 50);
        	 	g.DrawText("(press escape when done)", 300, 100);
        	 	g.DrawText(m_szPlayerName, 275, 150);
        	}
        	else
        	{	
        		//if choosing pile
        		if (m_bPile) {g.DrawText ("Enter a pile to choose from (1-3):", 250, 50);}
        		//choosing stick
        		else g.DrawText ("# of sticks to remove (1-3):", 250, 50);
        	}
        	//user was silly
        	if (!m_bValid) g.DrawText("Invalid Key!", 250, 100);
        	
        	//set the font again
        	g.SetFont(m_jFont);
        	
        	//draw the pile #s
        	g.DrawText("Pile 1", 10, 50);
        	g.DrawText("Pile 2", 70, 50);
        	g.DrawText("Pile 3", 130, 50);
        	
        	//# left in piles
        	g.DrawText(wxString::Format("%i%s", m_nPiles[0], " left"), 10, 320);
        	g.DrawText(wxString::Format("%i%s", m_nPiles[1], " left"), 70, 320);
        	g.DrawText(wxString::Format("%i%s", m_nPiles[2], " left"), 130, 320);
        		
        	//Statistics
        	g.DrawText(	wxString::Format("%s%s%i%s%s%s%i",
        	                m_szPlayerName, ":", m_nStats[0], "     " ,
        					m_szComputerName, ":", m_nStats[1]),
        					10, 320 + nFY);
        						
        	//Keys & more statistics			
        	g.DrawText("Press 'n' to change your name",
        					10, 320 + nFY * 2);
        	g.DrawText("Press escape to quit",
        					10, 320 + nFY * 3);
        	g.DrawText("1-3 = pile # or # of sticks",
        					10, 320 + nFY * 4);
        	g.DrawText("Last Moves: (Pile, #Sticks)", 10, 320 + nFY * 5);
        	g.DrawText(wxString::Format("%s%i%s%i%s%s%i%s%i",
        	    "Player -> ", m_nPLM[0], ",", m_nPLM[1], "  "
        	    , "Computer -> ", m_nCLM[0], ",", m_nCLM[1]), 10,
        					320 + nFY * 6);
        		
        	//Draw rectangles representing sticks
        	//set the color of the rectangle
        	g.SetBrush(wxBrush(wxColour(20, 20, 100)));
        		
        	for (; i < m_nPiles[0]; i++)
        	{
        		//fill3DRect (upper-left corner (x,y), lower-right corner (x,y), whether
        		//to fill the rectangle or not)
        		g.DrawRectangle(10, 70 + i * 20, 35, 15);
        	}
        		
        	g.SetBrush(wxBrush(wxColour(20, 100, 20)));
        		
        	for (i = 0; i < m_nPiles[1]; i++)
        	{
        		g.DrawRectangle(70, 70 + i * 20, 35, 15);
        	}

        	g.SetBrush(wxBrush(wxColour(100, 20, 20)));

        	for (i = 0; i < m_nPiles[2]; i++)
        	{
        		g.DrawRectangle(130, 70 + i * 20, 35, 15);
        	}
    	}//end else
    }//end paint(Graphics)


	/**
	*	Sort of a multipart function.  <br>
	*	First, it determines if the game is over, and if not, returns false.  <br>
	*	However, if the game is over, it starts a new game before returning true.
	*
	*	@see Nim#startNewGame()
	*/
	bool isGameOver ()
	{
		for (int i = 0; i < 3; i++)
		{
			if (m_nPiles[i] != 0) return false;//nope
		}
		startNewGame();
		return true;//yep
	}
	
	/**
	*	Starts a new game by resetting the size of the 3 piles <br>
	*	with a random number from 1-12.  <br><br>
	*
	*	Note:  This is called only by isGameOver()
	*
	*	@see Nim#isGameOver()
	*/
	void startNewGame()
	{
	    srand(time(NULL));
	    
		//loop 3 times for the 3 piles
		for (int i = 0; i < 3; i++)
		{
			//instill each pile with a random # of sticks from 1-12
			m_nPiles[i] = (int) (ceil(  (rand() / ((double)RAND_MAX)) * 12));
		}
	}
	
	/**
	*	For determining whether or not the player is done <br>
	*	entering his/her name.  This happens when the user <br>
	*	presses escape (Esc).
	*
	*	@param e for obtaining the escape keycode (27)
	*/
	void OnKeyDown (wxKeyEvent& e)
	{
		//if the user is done entering his/her name
		if (m_bName && e.GetKeyCode() == WXK_ESCAPE) 
		{
			//show the changes
			Refresh();
			
			//We arn't renaming anymore
			m_bName = false;
		}
		else if(e.GetKeyCode() == WXK_ESCAPE) {Close(true);}
		
		e.Skip();
	}
	
	void OnLeftDown (wxMouseEvent& e)
	{
		//back to game
		if (m_bOver) 
		{
			m_bOver = false;
			Refresh();
		}
	}
	
	/**
	*	If the user types in a character.  Has various effects: <br>
	*	r = shows the rules window if the user closed it <br>
	*	n = halts the game and lets the user enter in a (new) name <br>
	*	1-3 = pile # to choose -or- the # of the sticks to pick out of a chosen pile
	*
	*	@param e Nim uses this for obtaining the character code the user typed in
	*/
	void OnChar (wxKeyEvent& e)
	{
		//if the game's not over
		if (!m_bOver)
		{
			//if the player is still deciding on a name
			if (m_bName)	
			{	
				//set the name
				m_szPlayerName += (wxChar) e.GetKeyCode();
				Refresh();
				return;
			}
			//change the player's name		
			if (e.GetKeyCode() == 'n') 
			{
				changeName();
				m_bValid = true;
				Refresh();
				return;
			}
			
			//player took a turn
			if (e.GetKeyCode() == '1' ||
				e.GetKeyCode() == '2' ||
				e.GetKeyCode() == '3')
			{
				//choosing a pile?
				if (m_bPile)
				{
					//in e.GetKeyCode() 49 = 1, 50 = 2, 51 = 3 (ASCII)
					m_nPile = e.GetKeyCode() - 48;
					
					//if pile is empty
					if (m_nPiles[m_nPile - 1] == 0)
					{
						//user was silly
						m_bValid = false;
					}
					else
					{
						//valid value
						m_bValid = true;
				
						//time to choose sticks
						m_bPile = false;
					}
				}
				//if choosing sticks
				else
				{
					//the user is being silly again
					if (m_nPiles[m_nPile - 1] < e.GetKeyCode() - 48)
					{
						m_bValid = false;
					}
							
					else
					{
						//bug fix v3.0
						m_nPiles[m_nPile - 1] -= e.GetKeyCode() - 48;		

						//valid key
						m_bValid = true;
					
						if (isGameOver()) 
						{
							m_bOver = true;
							//fix v3.0
							m_bPile = true;
							//player won
							m_bWon = true;	
							m_nStats[0]++;
							
							//clear last move statistics
							memset(m_nPLM, 0, sizeof(int) * 2);
							memset(m_nCLM, 0, sizeof(int) * 2);
						}
						else
						{
							//statistics
							m_nPLM[0] = m_nPile;
							m_nPLM[1] = e.GetKeyCode() - 48;
													
							//computer's turn
							m_bComp = true;
						}
					}
				}
				
				//computer time
				if (m_bComp)
				{
					//computer's turn (take from m_nPiles into m_CLM)
					takeComputerTurn();
					
					//If we do not stop the thread the player may be able to go
					//while the computer is still deciding!!
					wxThread::Sleep(100); //sleep for 1/10 a second
					
					//not the computer's turn anymore
					m_bComp = false;
					
					//time for the player to choose a pile again
					m_bPile = true;
					
					//if the game is over...
					if (isGameOver())
					{
						m_bOver = true;
						m_bWon = false;
						//the computer won
						m_nStats[1]++;							
					}
				}
			}//end if 1,2,3
			else if (e.GetKeyCode() != WXK_ESCAPE )m_bValid = false;
		
			Refresh();
			return;				
		}//end if(m_bOver)
	}//end keyTyped
	
	/**
	*	Changes the player's name.  <br>
	*	Is called in the user types in n.
	*/
	void changeName ()
	{
		//clear name
		m_szPlayerName = wxT("");
		
		//time to rename
		m_bName = true;
	}
	
	
	void takeComputerTurn()
	{
	    int nPile;
	    int nNum;
	    
	    int nTotal = m_nPiles[0] + m_nPiles[1] + m_nPiles[2];
	    
	    if(m_nPiles[0] > m_nPiles[1])
	        nPile = 0;
	    else
	        nPile = 1;
	    
	    if(m_nPiles[2] > m_nPiles[nPile])
	        nPile = 2;
	    
	    int nRemain = nTotal % 3;
	    int nDiv = nTotal / 3;
	    
	    if (m_nPiles[nPile] == nRemain)
	    {
	        nNum = nRemain;
	    }
	    else
	    {
	        if (m_nPiles[nPile] > 3)
	            nNum = 3;
	        else
	            nNum = m_nPiles[nPile];
	    }
	    
	    //Do the dirty work
	    m_nPiles[nPile] -= nNum;
	    m_nCLM[0] = nPile;
	    m_nCLM[1] = nNum;
	}
private:
	/**
	*	A 3-index array containing the piles of sticks.
	*/
	int m_nPiles[3];

	/**
	*	An array of integers representing the number of wins each player has <br>
	*	accumulated.  Index 0 is the player's # of wins while index 1 is the computer's.
	*/
	int m_nStats[2];

	/**
	*	Stores the pile number the player chose
	*/
	int m_nPile /*= 0*/;

	/**
	*	Determines whether the player is currently choosing a pile.  <br>
	*	true = yes		false = no
	*/
	bool m_bPile /*= true*/;

	/**
	*	A class containing the name of the computer and AI for the computer.
	*/
	wxString m_szComputerName /*= new Computer ("COMPUTER")*/;

	/**
	*	Specifies whether it is the computer's turn or not.  <br>
	*	true = yes		false = no
	*/
	bool m_bComp /*= false*/;

	/**
	*	The Name of the Player
	*/
	wxString m_szPlayerName /*= ("PLAYER") */;

	/**
	*	Determines whether the player is halting the game and renaming <br>
	*	his/her self.  <br>
	*	true = yes	false = no
	*/
	bool m_bName /*= false*/;
	
	/**
	*	Determines whether the last character the user typed in was a valid <br>
	*	one in the context in which the player typed.  If false, draws text <br>
	*	on the main game window informing the player the last key inputted  <br>
	*	was invalid.  <br><br>
	*	For Example:  If the user is choosing his/her name, then letters would <br>
	*	be okay to type in.  However, if the game itself is running then most <br>
	*	characters entered are not valid except 1-3, n, and r. <br>
	*/
	bool m_bValid/* = true*/;
	
	/**
	*	The font to display text in
	*/
	wxFont m_jFont /*= new Font ("Serif", Font.BOLD, 14)*/;
	
	/**
	*	Last move made by player (m_nPLM),
	*	last move made by computer (m_nCLM) 
	*/
	int m_nPLM[2];
	int m_nCLM[2];
	
	/**
	*	If the game is over (for displaying the winner)
	*/
	bool m_bOver /*= false*/;
	
	/**
	*	If the player won
	*/
	bool m_bWon /*= false*/;
};

// --------------------------------------------------------------------------
//  wxNimFrame
// --------------------------------------------------------------------------

class wxNimFrame : public wxFrame
{
public:
    wxNimFrame(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(550,450)),
         m_pWindow(new wxNimWindow(this))
    {	
        //usual gui stuff
        wxMenu *fileMenu = new wxMenu;
        wxMenu *helpMenu = new wxMenu;
        helpMenu->Append(wxID_ABOUT, _T("&About...\tF1"), _T("Show about dialog"));
        fileMenu->Append(wxID_EXIT, _T("E&xit\tAlt-X"), _T("Quit this program"));
        wxMenuBar *menuBar = new wxMenuBar();
        menuBar->Append(fileMenu, _T("&File"));
        menuBar->Append(helpMenu, _T("&Help"));
        SetMenuBar(menuBar);
        
        //events
        this->Connect(wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED,
                      wxCommandEventHandler(wxNimFrame::OnQuit));
        this->Connect(wxID_ABOUT, wxEVT_COMMAND_MENU_SELECTED,
                      wxCommandEventHandler(wxNimFrame::OnAbout));
    }

    void OnQuit(wxCommandEvent& WXUNUSED(event))
    {   Close(true);    }

    void OnAbout(wxCommandEvent& WXUNUSED(event))
    {   wxMessageBox(wxT("wxNim by Ryan Norton"));  }

private:
    wxNimWindow* m_pWindow;
};

// --------------------------------------------------------------------------
//  wxNimApp implementation
// --------------------------------------------------------------------------

bool wxNimApp::OnInit()
{
    wxNimFrame *frame = new wxNimFrame(_T("wxNim"));
    frame->Show(true);
    return true;
}


Posted: Sat Oct 29, 2005 8:29 am
by Ryan Norton
here's a screenshot for those who like them:
Image[/img]

Posted: Wed Nov 09, 2005 10:20 am
by prophet
Action packed and stunning graphics ;-)

Posted: Wed Nov 09, 2005 6:14 pm
by Ryan Norton
prophet wrote:Action packed and stunning graphics ;-)
You will be left breathless and at the edge of your seat!!!

Posted: Wed Nov 09, 2005 9:34 pm
by Jorg
Maybe you should submit this as a sample to the wxWidgets distrib. It is always fun to see how wxWidgets is used besides the rather dull samples in the contrib ;-)

- Jorgen