wxNim - rather simple game

If you have a cool piece of software to share, but you are not hosting it officially yet, please dump it in here. If you have code snippets that are useful, please donate!
Post Reply
User avatar
Ryan Norton
Moderator
Moderator
Posts: 1319
Joined: Mon Aug 30, 2004 6:01 pm

wxNim - rather simple game

Post by Ryan Norton » Sat Oct 29, 2005 8:22 am

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;
}

Last edited by Ryan Norton on Sat Oct 29, 2005 8:29 am, edited 1 time in total.
[Mostly retired moderator, still check in to clean up some stuff]

User avatar
Ryan Norton
Moderator
Moderator
Posts: 1319
Joined: Mon Aug 30, 2004 6:01 pm

Post by Ryan Norton » Sat Oct 29, 2005 8:29 am

here's a screenshot for those who like them:
Image[/img]
[Mostly retired moderator, still check in to clean up some stuff]

prophet
Knows some wx things
Knows some wx things
Posts: 32
Joined: Wed Sep 01, 2004 6:19 am

Post by prophet » Wed Nov 09, 2005 10:20 am

Action packed and stunning graphics ;-)

User avatar
Ryan Norton
Moderator
Moderator
Posts: 1319
Joined: Mon Aug 30, 2004 6:01 pm

Post by Ryan Norton » Wed Nov 09, 2005 6:14 pm

prophet wrote:Action packed and stunning graphics ;-)
You will be left breathless and at the edge of your seat!!!
[Mostly retired moderator, still check in to clean up some stuff]

Jorg
Moderator
Moderator
Posts: 3971
Joined: Fri Aug 27, 2004 9:38 pm
Location: Delft, Netherlands
Contact:

Post by Jorg » Wed Nov 09, 2005 9:34 pm

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
Forensic Software Engineer
Netherlands Forensic Insitute
http://english.forensischinstituut.nl/
-------------------------------------
Jorg's WasteBucket
http://www.xs4all.nl/~jorgb/wb

Post Reply