Click here to Skip to main content
6,305,776 members and growing! (17,380 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » Game Development » Games     Intermediate

Clickmania Game

By aaccoobb

A simple logical game.
C#.NET 1.1, WinXPVS.NET2003, Dev
Posted:31 Mar 2005
Views:28,994
Bookmarked:15 times
Announcements
Loading...
 
Search    
Advanced Search
printPrint   Broken Article?Report       add Share
  Discuss Discuss   Recommend Article Email
4 votes for this article.
Popularity: 1.88 Rating: 3.13 out of 5

1
1 vote, 25.0%
2

3
2 votes, 50.0%
4
1 vote, 25.0%
5

Introduction

I once stepped on Clickomania next generation from Matthias Sch�ssler and liked the game so much that I decided to implement that game in C# from scratch. My version is simpler but uses a lot of the same design. I tested the game on an Athlon 2000+ and it seemed to work smoothly.

Features

  • Animations and sounds.
  • Save the high scores (Top 10).
  • Take back move.

The object model

I use two big classes, MainForm and Engine. The MainForm deals with events, drawing and animation, the Engine contains the logic. I also use three structs, one for the Scores (Score, Player name, Date etc...):

public struct Scores
{
    public int _iScores;    // score

    public string _sNames;  // player name

    public int _SbestMove;  // his best move

    public int _SballsLeft; // balls left after game over

    public int _Stime;      // time for that game

    public string _Date;    // the date he played (mm/dd/yyyy mm:ss)

    public int _sTakeBacks; // how many times did he take back 

                            // during the game

}

One for the Ball:

public struct Ball
{
    public int _icolor;          // its color

    public bool _exists;         // does it still exist

    public bool _isDisappearing; // is it in animation status

    public int _vel;             // velocity of animation

    public int _yvel; 
}

and one for the Turn, which contains information about the disappeared balls, their position and the moved columns, so the moves can be taken back:

public struct Turn
{
    public int _itColor;   // Color of disappeared balls

    public int [] _itposX; // Position

    public int [] _itposY;
    public int [] _column; // Disappeared columns

}

A bit about the logic

On the PictureBox, the balls seem to move, but actually they don't. The balls array stores 8x12 balls. They stay at their place on the board. They only change color or get the status _exist = false when they are supposed to be gone.

When the player clicks on the ball, it checks if it belongs to a group of balls with the same color. This is done with a recursive function:

private int CheckNextBall(int x, int y, int color)
{
    int px, nx, py, ny;
    px = (x == 0 ? 0 : x - 1);  //prior X

    nx = (x == 7 ? 7 : x + 1);  //next X

    py = (y == 0 ? 0 : y - 1);  //prior Y

    ny = (y == 11 ? 11 : y + 1);//next Y

    int ret = 1;
    // the 4 "if" statements do basically the 

    // same, they check if the 

    // neighbour of the actual ball has the same color

    if((_ball[px, y]._icolor == color) && (_ball[px, y]._exists) && (px != x))
    {
        // make it inexistant

        _ball[px, y]._exists = false;
        // to animate it in the main form 

        _ball[px, y]._isDisappearing = true;
        // save color of ball( so we can take the turn back) 

        _turn[_turn.GetLength(0) - 1]._itColor = color;
        _X[_index] = px; // and save its position (for the same reason)

        _Y[_index] = y;
        _index++;
        ret += CheckNextBall(px, y, color);
    }
    if((_ball[nx, y]._icolor == color) && (_ball[nx, y]._exists) && (nx != x))
    {
        _ball[nx, y]._exists = false;
        _ball[nx, y]._isDisappearing = true;
        _turn[_turn.GetLength(0) - 1]._itColor = color;
        _X[_index] = nx;
        _Y[_index] = y;
        _index++;
        ret += CheckNextBall(nx, y, color);
    }
    if((_ball[x, py]._icolor == color) && (_ball[x, py]._exists) && (py != y))
    {
        _ball[x, py]._exists = false;
        _ball[x, py]._isDisappearing = true;
        _turn[_turn.GetLength(0) - 1]._itColor = color;
        _X[_index] = x;
        _Y[_index] = py;
        _index++;
        ret += CheckNextBall(x, py, color);
    }
    if((_ball[x, ny]._icolor == color) && (_ball[x, ny]._exists) && (ny != y))
    {
        _ball[x, ny]._exists = false;
        _ball[x, ny]._isDisappearing = true;
        _turn[_turn.GetLength(0) - 1]._itColor = color;
        _X[_index] = x;
        _Y[_index] = ny;
        _index++;
        ret += CheckNextBall(x, ny, color);
    }
    return ret;
}

This function checks the color of the four neighboring balls, and if they have the same color, sets its state to disappearing and checks the neighbors for their colors. It also saves the information about color and position in the Turn struct, so that the player can take back the move if he wants to.

Animation

This is done in the Mainform class using global variables of position and size of animated balls, and the pictureBox.Refresh() method. For example to make balls appear or disappear, do the following:

/// <summary>

/// Balls Disappear or appear

/// </summary>

/// <param name="appear"> if they are appearing or disappearing</param>

public void MakeBallsAppear(bool appear)
{
    if(!appear)
    {
        PlayWav(1); // play a sound

        // redraw all balls, but animate only thosse with appear state

        // this occours when balls that are clicked disappear

        for(int i = 0; i < 12; i++)
        {
           _var = i;
           Thread.Sleep(10); // sleep 10 ms between 2 frames

           this.pictureBox1.Refresh();
        }
        _var = 0;
    }
    else
    {
        PlayWav(8);
        // same thng here too, but balls reappaer when 

        // player takes a move back

        for(int i = 11; i >= 0; i--)
        {
            _var = i;
            Thread.Sleep(10);
            this.pictureBox1.Refresh();
        }
    }
}

The Paint event draws one frame when pictureBox.Refresh() is called in the loop above. We also wait for 10 ms before painting the next frame, so that the animations wouldn't appear faster on faster machines:

/// <summary>

/// here all the drawing takes place. for each 

/// frame, this event is called once, 

/// whith a different _var, _posx or _posy 

/// if an animation takes place

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{ 
    Graphics g = e.Graphics;
    for(int i = 0; i < 8; i++)
    {
        for(int j = 0; j < 12; j++)
        {
            if(engine._ball[i, j]._exists)
            {
                g.DrawImage(_bmp[engine._ball[i, j]._icolor], 
                  (i * 24) - _posx * engine._ball[i, j]._yvel, 
                   (j * 24) + _posy * engine._ball[i, j]._vel, 
                                                      24, 24);
            }
            if(engine._ball[i, j]._isDisappearing)
            {
                g.DrawImage(_bmp[engine._ball[i, j]._icolor], 
                            (i * 24) + _var, (j * 24) + _var, 
                               24 - (_var*2), 24 - (_var*2));
            }
        }
    }
    if((_gameover) && (!_gamewon))
    {
        // shadow of the text

        g.DrawString("GAME OVER", new Font("Arial", 20), 
                           System.Drawing.Brushes.Black,    
                                    new Point(10, 122)); 
        g.DrawString("GAME OVER", new Font("Arial", 20), 
                       System.Drawing.Brushes.LightBlue,
                                     new Point(8, 120));
    }
    if(_gamewon)
    {
        // shadow of the text

        g.DrawString("GAME WON", new Font("Arial", 20, 
                        System.Drawing.FontStyle.Bold),
                          System.Drawing.Brushes.Black, 
                                    new Point(12, 122)); 
        g.DrawString("GAME WON", new Font("Arial", 20, 
                       System.Drawing.FontStyle.Bold), 
                        System.Drawing.Brushes.Yellow, 
                                  new Point(10, 120));
    }
}

Sounds

The PlaySound(...) method from winmm.dll is used to play with WAV files:

[DllImport("winmm.dll")]
private static extern bool PlaySound( string lpszName, 
                                int hModule, int dwFlags );
private void PlayWav(int play)
{
    if(checkBox1.Checked)
    {
        string myFile = ".\\Sounds\\default.wav";
        switch(play)
        {
            case 1:
                myFile = ".\\Sounds\\BallDisappear.wav";
                break;
            case 2:
                myFile = ".\\Sounds\\BallDown.wav";
                break;
            case 3:
                myFile = ".\\Sounds\\lost.wav";
                break;
            case 4:
                myFile = ".\\Sounds\\newgame.wav";
                break;
            case 5:
                myFile = ".\\Sounds\\ColumnDis.wav";
                break;
            case 6:
                myFile = ".\\Sounds\\ColumnAppear.wav";
                break;
            case 7:
                myFile = ".\\Sounds\\BallUp.wav";
                break;
            case 8:
                myFile = ".\\Sounds\\BallAppear.wav";
                break;
            case 9:
                myFile = ".\\Sounds\\Won.wav";
                break;
            case 10:
                myFile = ".\\Sounds\\illegal.wav";
                break;
            default:
                break;
        }
        PlaySound(myFile, 0, 0x0003); // Play the sound

    }
}

The WAV files have to be in the subfolder Sounds of the folder containing Clickmania.exe. I have used some sounds of the game Half Life 2. If you want to use your own sounds, you can upload them to the sounds folder and rename them.

Scores

All scores are stored in a binary file called Scores.sco in the same folder as the exe. If this file does not exist, it will be generated when the game is run. This file also contains the name of the last user who reached the top 10, so he shouldn't reenter his name if he reaches the top10 again. To view the high scores, I have used the ListView control:

private void PopulateListbox()
{
    string name, score;
    listView1.Items.Clear(); // remove items in listview

    ListViewItem [] items = new ListViewItem[10];
    //DateTime date;

    Color textColor = new Color();
    Font font;
    for(int i = 0; i < 10; i++)
    {
        name = (i + 1).ToString() + ":" + 
                  " " + _MF._stScores[i]._sNames;
        score = _MF._stScores[i]._iScores.ToString();
        if (i == 9)name = (i + 1).ToString() + ":" + 
                        " " + _MF._stScores[i]._sNames;
        textColor = _MF._stScores[i]._SballsLeft == 0 ?
           System.Drawing.Color.Blue : System.Drawing.Color.Black;
        textColor = (_MF._stScores[i]._sTakeBacks == 0 &&
                     _MF._stScores[i]._SballsLeft == 0) ? 
                               Color.DarkRed : textColor;
        font = (textColor == Color.Black) ? 
             new Font("Arial", 8) : new Font("Fixedsys", 8,
                       (_MF._stScores[i]._sTakeBacks == 0) ? 
                       FontStyle.Italic : FontStyle.Regular);
        items[i] = new ListViewItem(new string[] {name, score,
                                          _MF._stScores[i]._Date,
                              _MF._stScores[i]._Stime.ToString(),
                          _MF._stScores[i]._SbestMove.ToString(),
                         _MF._stScores[i]._SballsLeft.ToString(),
                        _MF._stScores[i]._sTakeBacks.ToString()},
                               -1, textColor, Color.White, font);
        listView1.Items.Add(items[i]);
    }
}

Information is shown in a different color according to how the game was played, for example if the player left no balls or if he took no moves back.

About the program

This is one of my first C# implementations, so be indulgent if my code seems junk at some places.

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

About the Author

aaccoobb


Member
aasdfa
Location: United Kingdom United Kingdom

Other popular Game Development articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 4 of 4 (Total in Forum: 4) (Refresh)FirstPrevNext
GeneralDrawing the circle shape Pinmemberwalshi2k711:42 14 Nov '08  
Generalcoding style Pinmembersufwan4:35 25 May '06  
GeneralShame on you! PinsussLebesgue2:11 11 Apr '05  
GeneralRe: Shame on you! PinmemberMichael J. Collins12:10 14 Jun '05  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 31 Mar 2005
Editor: Sean Ewington
Copyright 2005 by aaccoobb
Everything else Copyright © CodeProject, 1999-2009
Web17 | Advertise on the Code Project