Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C#
Article

JawBreaker Game in C#

Rate me:
Please Sign up or sign in to vote.
4.83/5 (44 votes)
28 Dec 20034 min read 412.2K   14.7K   111   41
A simple implementation of Jawbreaker

Introduction

This is a C# implementation of JawBreaker, A free game shipped with Pocket PC 2003.

Background

After playing Jawbreaker on my pocket pc for couple of days, I was having some issues with the program. One was it allowed only one Undo and the board had a fixed number of cells. I also wanted to play this game on my PC. So I decided to write the program in C#. I never programmed a game before. So I used this as a learning experience. The game has no AI and very simple to implement (Caution: It is very addictive).

Using the code

The zip file contains source code that was compiled for Visual Studio.NET 2003. Just open the solution file and compile it. The executable created is called JawBreaker.exe. You could play the game very easily. All you have to do is get as many similar cells together and then remove them (The more the cells, the higher the score). The first click selects the block of similar neighbor cells recursively. If you click anywhere on the selected block the entire block is removed. The objective of the game is to score as many points as possible.

Program Design

The program is organized into 3 classes:

  • Cell
  • Board
  • MainForm

Cell

Cell is a representation of a cell on the Board . Every cell has a type. The type is what determines its color.

C#
int row;
int col;
int type;

public Cell(int row,int col,int type)
{
    this.row=row;
    this.col=col;
    this.type=type;
}

Board

The Board class is what stores the game state. It has a two dimensional array of Cell objects. It also has two Stack objects for storing the Undo and Redo information. The totalScore has the current score.

C#
// Members of the Board class
    private int rows;
    private int cols;
    private int types;
    private Cell[,] data;
    private ArrayList selected;
    private Stack undoStack;
    private Stack redoStack;
    private int totalScore;

When cells are removed, those locations are set to be null. The above member variables explain the state of the game. The variable totalScore is what maintains the total score of the board.

  • public void Remove(ArrayList arl)
  • public void Select(Cell c)
  • public bool Undo()
  • public bool Redo()
  • public void Initialize()
  • public bool GameFinished()

The function Select(Cell c) will select the neighbourhood cells recursively for the given Cell.

C#
//Selects the neighborhood Cells recursively

  public void Select(Cell c)
  {
      selected.Add(c);
      ArrayList arl=GetNeighborhoodCells(c);
      if(arl.Count==0)
      {
          return;
      }
      foreach(Cell cell in arl)
      {
          if(cell.Type==c.Type&&!selected.Contains(cell))
          {
              Select(cell);
          }
      }
  }

The function Remove(ArrayList arl) will remove cells that are sent in the ArrayList. It will make those locations as null .

C#
// Removes cells from the board

public void Remove(ArrayList arl)
{
    if(arl.Count>0)
    {
        //Fist push the current data to Undo Stack

        undoStack.Push(GetTypeArray());

        //Clear the redo Stack..

        redoStack.Clear();




        foreach(Cell c in arl)
        {
            data[c.Row,c.Col]=null;
        }
        //Sync the board
        Syncronize();

        //update the score

        totalScore+=GetScore(arl.Count);
    }
}

The function bool Undo() will set the current boards state with the popped element from the Undo Stack. If the undo succeeds it returns true.

C#
public bool Undo()
{
    if(undoStack.Count>0)
    {
        selected.Clear();
        //Push to redo
        int[,] current=GetTypeArray();
        int[,] popped=(int[,])undoStack.Pop();

        //update the scores
        totalScore-=GetScore(
             GetActiveCells(popped)-GetActiveCells(current));

        redoStack.Push(current);
        SetTypeArray(popped);
        return true;
    }

    return false;

}

The function bool Redo() will set the current boards state with the popped element from the Redo Stack. If the redo succeeds it returns true.

C#
public bool Redo()
{
    if(redoStack.Count>0)
    {
        selected.Clear();
        int[,] current=GetTypeArray();
        int[,] popped=(int[,])redoStack.Pop();

        //update score
        totalScore+=GetScore(GetActiveCells(
              current)-GetActiveCells(popped));

        //push current to undo
        undoStack.Push(current);

        //pop for redo stack
        SetTypeArray(popped);
        return true;
    }

    return false;


}

The function Initialize() initializes the board with Random Cells, sets the score to zero and clears the Undo and Redo Stacks.

C#
public void Initialize()
{
    Random r=new Random();


    for(int i=0;i<rows;i++)
    {
        for(int j=0;j<cols;j++)
        {
            data[i,j]=new Cell(i,j,r.Next(types));
        }
    }

    totalScore=0;
    undoStack.Clear();
    redoStack.Clear();
    selected.Clear();

}

The function GameFinished() checks whether there are any moves left in the board. If there are any it returns false, else it returns true.

C#
//Checks if all the moves are finished
public bool GameFinished()
{
    for(int i=0;i<rows;i++)
    {
        for(int j=0;j<cols;j++)
        {
            Cell c=data[i,j];
            if(c!=null)
            {
                ArrayList arl=GetNeighborhoodCells(c);
                foreach (Cell ce in arl)
                {
                    if(c.Type==ce.Type)
                        return false;
                }
            }

        }
    }

    return true;

}

MainForm

MainForm is where most of the drawing takes place. There are two drawing methods.

  • public void DrawBall(Graphics grfx,Rectangle rect,Color c,bool Selected)
  • public void DrawBoard(Graphics grfx)

The DrawBall(..) method draws a ball on the given graphics object. It is bounded by the rectangle and is of the color passed to it. If Selected is true, it draws a background before drawing the ball as you see in the following code

C#
public void DrawBall(Graphics grfx,Rectangle rect,Color c,bool Selected)
{
    if(Selected)
    {
        grfx.FillRectangle(Brushes.Goldenrod,rect);
    }
    GraphicsPath path=new GraphicsPath();

    path.AddEllipse(rect);

    PathGradientBrush pgbrush= new PathGradientBrush(path);
    pgbrush.CenterPoint=new Point((rect.Right- rect.Left)
       /3+rect.Left,(rect.Bottom - rect.Top) /3+rect.Top);
    pgbrush.CenterColor=Color.White;
    pgbrush.SurroundColors=new Color[] { c };

    grfx.FillRectangle(pgbrush,rect);
    grfx.DrawEllipse(new Pen(c),rect);

}

The method DrawBoard is called whenever a board refresh is needed. Right now it just redraws the entire screen with the current contents of the board. Also this is the method that determines which color is used for each type of the cell. Right now it can have upto 5 colors, but this could be easily increased by modifying this function.

C#
    public void DrawBoard(Graphics grfx)
    {
	//Do Double Buffering
	Bitmap offScreenBmp;
	Graphics offScreenDC;
	offScreenBmp = new Bitmap(boardPanel.Width, boardPanel.Height);
	offScreenDC = Graphics.FromImage(offScreenBmp);

	offScreenDC.Clear(boardPanel.BackColor);
	offScreenDC.SmoothingMode=SmoothingMode.AntiAlias;

	int height=boardPanel.Height-1;
	int width=boardPanel.Width-1;

	int rectHeight=(int)Math.Round((double)height/b.Cols);
	int rectWidth=(int)Math.Round((double)width/b.Rows);



	int x=0;
	int y=0;
	ArrayList arl=b.Selected;


	for(int i=0;i<b.Rows;i++)
	{
		x=0;
		for(int j=0;j<b.Cols;j++)
		{
			Rectangle r=new Rectangle(x,y,rectWidth-2,rectHeight-2);

			Cell c=b.Data[i,j];
			if(c!=null)
			{
				if(arl.Contains(c))
				{

					DrawBall(offScreenDC,r,colors[c.Type],true);
				}
				else
				{
					DrawBall(offScreenDC,r,colors[c.Type],false);
				}
			}
			else
			{
				//Do nothing
			}

			x+=rectWidth;
		}
		y+=rectHeight;
	}

	if(arl.Count>0)
	{
		//Select the first cell(The cell the user clicked on first)
		Cell c=(Cell)arl[0];

		//Calculate the x and y coordinates
		int x1=c.Col*rectWidth;
		int y1=c.Row*rectHeight;

		//Show the Score..s
		Rectangle scr=new Rectangle(x1,y1,30,30);
		//offScreenDC.F
		StringFormat sf=new StringFormat();
		sf.Alignment=StringAlignment.Center;
		sf.LineAlignment=StringAlignment.Center;
		offScreenDC.DrawString(b.CurrentSelection.ToString(),
                    new Font("Arial",8,FontStyle.Bold),Brushes.Black,scr,sf);

	}

	grfx.DrawImageUnscaled(offScreenBmp, 0, 0);

}

User moves

User moves are handled by the MouseDown event. It calculates the row and column and determines the cell based on the region where the mouse is clicked. Then it calls the select method of the Board. If the current cell is already in the selected items, it means the user wants to remove the cells that are currently selected.

C#
private void boardPanel_MouseDown(object sender, MouseEventArgs e)
{

    int x=e.X;
    int y=e.Y;

    int height=boardPanel.Height-1;
    int width=boardPanel.Width-1;

    int rectHeight=(int)Math.Round((double)height/b.Cols);
    int rectWidth=(int)Math.Round((double)width/b.Rows);


    int col=(int)x/rectWidth;
    int row=(int)y/rectHeight;

    ArrayList selected=b.Selected;

    Cell currentCell=b.Data[row,col];
    if(currentCell!=null)
    {

        if(selected.Contains(currentCell))
        {

            expectedScore.Text="Current Selection: 0";

            b.Remove(selected);
            UpdateScore();

        }
        else
        {
            b.Selected.Clear();
            b.Select(currentCell);
            expectedScore.Text="Current Selection: "+
                  GetScore(b.Selected.Count);
        }


    }
    else
    {
        //He clicked on a null Cell -> Clear the selection

        b.Selected.Clear();
        expectedScore.Text="Current Selection: 0";
    }

    DrawBoard(boardPanel.CreateGraphics());
    if(b.GameFinished())
    {


        if(MessageBox.Show(this,"Game Over!!. Your total Score was: "+
                  b.Score+"\nDo you want to Start a New Game?",
                  "JawBreaker",MessageBoxButtons.YesNo,
                  MessageBoxIcon.Exclamation)==DialogResult.Yes)
        {
            NewGame();
        }

    }


}

After every move it checks for GameFinished, if game is finished, it gives an option of starting a new game.

Points of Interest

I did a quick and dirty Syncronize() method that is called from the Remove(ArrayList) method of the Board class. This might affect performance of the game. Also instead of drawing the entire board, it might be more efficient to draw in the regions that are affected. This game is very interesting in AI point of view. There can be many solutions and finding the optimal solution might be a good challenge.

History

  • December 29, 2003
    • Some additional UI enhancements
  • December 22, 2003
    • Initial Version 1.0

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
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionSaving Highscore Pin
Member 1225658210-Jan-16 23:29
Member 1225658210-Jan-16 23:29 
GeneralLicense regd Pin
AlwaysACreator5-Jul-09 23:41
AlwaysACreator5-Jul-09 23:41 
GeneralHelp file needed Pin
Donsw11-Dec-08 11:05
Donsw11-Dec-08 11:05 
General[Message Deleted] Pin
Danny Rodriguez27-Jan-08 9:18
Danny Rodriguez27-Jan-08 9:18 
[Message Deleted]
GeneralExcellent Pin
User 37388876-Mar-07 6:08
User 37388876-Mar-07 6:08 
Generaljawbreaker Pin
Timothyjohnbaker16-Oct-05 13:19
Timothyjohnbaker16-Oct-05 13:19 
Generaljawbreaker Pin
Anonymous16-Oct-05 13:16
Anonymous16-Oct-05 13:16 
GeneralJawbreaker Pin
Anonymous18-Jun-05 15:22
Anonymous18-Jun-05 15:22 
GeneralRe: Jawbreaker Pin
Anonymous15-Jul-05 8:42
Anonymous15-Jul-05 8:42 
GeneralError executing jawbreaker.exe Pin
salest27-Aug-04 3:34
salest27-Aug-04 3:34 
GeneralRe: Error executing jawbreaker.exe Pin
Gavi Narra27-Aug-04 3:42
Gavi Narra27-Aug-04 3:42 
GeneralRe: Error executing jawbreaker.exe Pin
salest27-Aug-04 4:12
salest27-Aug-04 4:12 
GeneralAnother Jawbreaker for download Pin
ReneAmse13-May-04 2:35
sussReneAmse13-May-04 2:35 
QuestionRules? Pin
darrellp16-Jan-04 8:27
darrellp16-Jan-04 8:27 
AnswerRe: Rules? Pin
Gavi Narra16-Jan-04 10:31
Gavi Narra16-Jan-04 10:31 
GeneralBest results Pin
nahumtakum8-Jan-04 8:47
nahumtakum8-Jan-04 8:47 
GeneralRe: Best results Pin
Phill McGuire13-Jan-04 9:02
Phill McGuire13-Jan-04 9:02 
GeneralSmall enhancement Pin
Mackay_S6-Jan-04 23:51
Mackay_S6-Jan-04 23:51 
GeneralSamegame Pin
David Wulff5-Jan-04 0:16
David Wulff5-Jan-04 0:16 
GeneralRe: Samegame Pin
Gavi Narra5-Jan-04 4:08
Gavi Narra5-Jan-04 4:08 
Questionvb.net ? Pin
SFerrell31-Dec-03 11:49
SFerrell31-Dec-03 11:49 
AnswerRe: vb.net ? Pin
robiweb9016-Jan-04 5:47
robiweb9016-Jan-04 5:47 
GeneralExecutable format Pin
mansour_ahmadian30-Dec-03 3:57
mansour_ahmadian30-Dec-03 3:57 
GeneralRe: Executable format Pin
Gavi Narra30-Dec-03 4:10
Gavi Narra30-Dec-03 4:10 
GeneralRe: Executable format Pin
mansour_ahmadian30-Dec-03 4:31
mansour_ahmadian30-Dec-03 4:31 

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.