关闭

C#游戏实例:拼图游戏

标签: C#游戏拼图
1009人阅读 评论(0) 收藏 举报
分类:

本游戏素材和大部分代码都来自于《C#Windows游戏设计》一书中,在这里我只说明和记录一些关键方法。这些对大家来说比较熟悉的游戏,做起来也并不是很麻烦。

拼图游戏就是将一幅图片平均分成一些方块,然后随机将这些方块打乱。游戏运行效果如下图:





本游戏的难点在于随机打乱图片,这也是该游戏的核心算法,下面阐述一下如何实现:

首先举个例子,我们定义并初始化一个数组:

int[] a = new int[10];
            //初始化数组a[10]
            for (int i = 0; i < 10; i++)
                a[i] = i + 1;
现在要随机打乱这个数组,方法是产生一个0~9的随机数,假设这个随机数为6,则交换a[6]与a[9],现在a[0]到a[9]分别为1、2、3、4、5、6、10、8、9、7

然后产生一个0~8的随机数x,交换a[x]与a[8];

产生一个0~7的随机数y,交换a[y]与a[7];

......

直到产生随机数的范围为1时停止,就将这个数组的次序彻底打乱了,而且每一项保证不会重复,就像抽奖程序,从50人里随机抽出10个人,如果你只是简单的使用代码:

PersonNum = GetRandom(50);

就很容易出现一个人被抽取两次甚至多次的结果,而拼图游戏绝对不允许在打乱之后有两个一模一样的小方块

上述思想的实现代码如下:

//随机打乱一个数组
            int lenth = 10;
            Random rd = new Random();
            while (lenth >= 1)
            {
                //产生0~length-1之间的一个随机数index
                int index = rd.Next(lenth);
                //交换a[index]与a[lenth - 1]
                int t = a[index];
                a[index] = a[lenth - 1];
                a[lenth - 1] = t;
                lenth--;
            }

这样就实现了打乱数组


该游戏将图片文件夹(GamePictures)与声音文件夹(GameSounds)均放在bin/Debug文件夹中,在解决方案中新建文件夹,加入了GetRandom与LoadBitmap类

游戏实现的步骤:

5.1界面尺寸的设计

5.2全局变量及相关类

5.3显示图片及方格

5.4为菜单项添加响应函数

5.5随机打乱图片

5.6响应用户输入(鼠标事件)

5.7处理游戏状态与添加声音


源代码如下:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

//5.2添加
using PuzzleGame.BaseClass;
//5.7添加
using System.Runtime.InteropServices;

namespace PuzzleGame
{
    public partial class FrmPuzzle : Form
    {
        public FrmPuzzle()
        {
            InitializeComponent();
        }

        //5.2 全局变量
        readonly Color COLOR_SELECTED = Color.OrangeRed;                               //图片方块被选中的边框颜色
        readonly Color COLOR_MOUSEMOVEIN = Color.MediumBlue;                     //鼠标移入方块的边框颜色
           //以下几个全局变量在大部分游戏中都需要考虑到
        readonly int[,] Difficulty = new int[,] { { 2, 3 }, { 3, 3}, { 3, 4 }, { 4, 4 }, { 4, 6 } }; //不同难度对应的行数和列数
        int iGameLevel = 0;                                                                                    //游戏难度级别,开始时为0,即Difficulty的第一个数组2X3
        bool bPlaySound = true;                                                                          //是否开启声音,默认开启
        string[] GameState=new string[]{"等待","运行","胜利","失败"};                       //游戏状态所对应的字符串,在状态栏中显示
        int iGameState = 0;                                                                                   //游戏状态,和GameState的索引对应,初始为"等待"   
        //int iGameLeaveTime = 0;                                                                           //游戏剩余时间,在状态栏中显示

        bool bUseMyPicture = false;                                                                      //是否自选图片,默认为false 
        Bitmap[] bmpGrids = new Bitmap[24];                                                        //位图数组,这里将其最大索引设置为最高级别的行数乘以列数4X6=24,当级别较低时,例如2X3时,后18个为空
        readonly int bmpWidth = 840, bmpHeight = 600;                                         //位图的宽度和高度
                                                  
        
        //5.3 全局变量
        readonly int iWidth = 840, iHeight = 600;                                                    //图片的尺寸
        readonly Point ptLeftTop = new Point(2, 26);                                                 //图片左上角的坐标
        int iRowsCount=2,iColumnsCount=3;                                                         //图片在初始状态下被分成2X3个方块,当所选择的难度变化时,这两个变量也随之变化
        string strMyPicturePath = "";                                                                    //自选图片资源所在的路径
        Bitmap bmpRate;//保存将原始图片缩放为840X600的位图

        //5.5 全局变量
        int[] NewIndex=new int[24];  //保存每块图片的新的索引
        int GameLeaveSeconds = 5;//游戏由一个状态进入另一个状态所剩的时间(秒),这里将其赋值为5,即当打开游戏时默认的是难度的最低级别,玩家在图像打乱之前有5秒钟的观察时间

        //5.6 全局变量
        int oldMouseMoveInIndex = -1;//鼠标移入的上一个方块的索引值
        int newMouseMoveInIndex = -1;//鼠标移入的当前方块的索引值

        int firstSelectedIndex = -1; //鼠标选中的一个方块的索引值
        int secondSelectedIndex = -1;//鼠标选中的二个方块的索引值

        //5.7
        [DllImport("winmm")]
        public static extern bool PlaySound(string szSound, int hMod, int i);

        //5.6自定义函数GetIndex
        int GetIndex(MouseEventArgs e)
        {
            if (e.X >= ptLeftTop.X && e.X < ptLeftTop.X + bmpWidth && e.Y >= ptLeftTop.Y && e.Y < ptLeftTop.Y + bmpHeight)
                return (e.X - ptLeftTop.X) /(bmpWidth / iColumnsCount) + (e.Y - ptLeftTop.Y) / (bmpHeight / iRowsCount) * iColumnsCount;
            return -1;
        }

        //5.5 自定义函数 InitNewIndex,初始化数组NewIndex的值
        private void InitNewIndex()
        {
            for (int i = 0; i < 24; i++)
                NewIndex[i] = i;
        }

        //5.5 自定义函数RandomNewIndex,将数组打乱
        private void RandomNewIndex()
        {
            int nums = iRowsCount * iColumnsCount;
            while (nums >= 1)
            {
                int index = GetRandom.GetRandomInt(nums);
                int temp=NewIndex[index];
                NewIndex[index]=NewIndex[nums-1];
                NewIndex[nums - 1] = temp;
                nums--;
            }
        }
        //5.3 自定义函数LoadPicture
        private void LoadPicture()
        {
            Bitmap bmpOriginal;
            if (!bUseMyPicture)//系统随机自选图片
                bmpOriginal = LoadBitmap.LoadBmp(GetRandom.GetRandomInt(30).ToString());
            else
                bmpOriginal = new Bitmap(strMyPicturePath);
            bmpRate=new Bitmap(bmpOriginal,new Size(iWidth,iHeight));//将原始图片缩放为840X600
            
       }

        //5.3 初始化全局变量bmpGrids
        private void InitGrids()
        {
            for (int row = 0; row < iRowsCount; row++)
                for (int col = 0; col < iColumnsCount; col++)
                    bmpGrids[col + row * iColumnsCount] = bmpRate.Clone(new Rectangle(col * iWidth / iColumnsCount, row * iHeight / iRowsCount, iWidth / iColumnsCount, iHeight / iRowsCount), bmpRate.PixelFormat);
            //5.5
            this.InitNewIndex();
            GameLeaveSeconds = 5 + iGameLevel*2;
        }
        //5.3 窗体加载事件
        private void FrmPuzzle_Load(object sender, EventArgs e)
        {
            this.LoadPicture();
            this.InitGrids();
            //5.5 添加
            this.GameTimer.Start();//
        }

        //5.3 自定义函数DrawGame()
        private void DrawGame(Graphics graphic)
        { 
            //画图片,5.3临时代码
            /*
            if (iGameState == 0)//游戏状态为等待,即显示原始图片,玩家在等待阶段观察原始图片
                for (int row = 0; row < iRowsCount; row++)
                    for (int col = 0; col < iColumnsCount; col++)
                        graphic.DrawImage(bmpGrids[col+iColumnsCount*row],ptLeftTop.X+col*iWidth/iColumnsCount,ptLeftTop.Y+row*iHeight/iRowsCount);*/
            
            //画图片 5.5节的代码
            for (int i = 0; i < iRowsCount * iColumnsCount; i++)
            {
                graphic.DrawImage(bmpGrids[NewIndex[i]], ptLeftTop.X + (i%iColumnsCount) * iWidth / iColumnsCount, ptLeftTop.Y + (i/iColumnsCount)* iHeight / iRowsCount);
            }
            //画网格线
            Pen penWindow = new Pen(SystemColors.Window, 1);
            for (int row = 1; row <iRowsCount; row++)
            {

                graphic.DrawLine(penWindow, 2, 26 + row * iHeight / iRowsCount, 842, 26 + row * iHeight / iRowsCount);
            }
            for (int col = 1; col <iColumnsCount; col++)
            {
                graphic.DrawLine(penWindow, 2 + col * iWidth / iColumnsCount, 26, 2 + col * iWidth / iColumnsCount, 26 + iHeight);
            }
            //5.6 画鼠标移入的方块边框
            if (newMouseMoveInIndex >= 0)
            {
                Pen penMouseMoveIn = new Pen(COLOR_MOUSEMOVEIN, 2);
                Point ptSquareLT = new Point(ptLeftTop.X + newMouseMoveInIndex % iColumnsCount * bmpWidth / iColumnsCount, ptLeftTop.Y + newMouseMoveInIndex / iColumnsCount * bmpHeight / iRowsCount);//鼠标移入的方块的左上角的坐标
                Point ptSquareRT = new Point(ptSquareLT.X + bmpWidth / iColumnsCount, ptSquareLT.Y);
                Point ptSquareLB = new Point(ptSquareLT.X, ptSquareLT.Y + bmpHeight / iRowsCount);
                Point ptSquareRB = new Point(ptSquareRT.X, ptSquareLB.Y);
                graphic.DrawLine(penMouseMoveIn, ptSquareLT, ptSquareRT);//上边框
                graphic.DrawLine(penMouseMoveIn, ptSquareRT, ptSquareRB);//右边框
                graphic.DrawLine(penMouseMoveIn, ptSquareRB, ptSquareLB);//下边框
                graphic.DrawLine(penMouseMoveIn, ptSquareLB, ptSquareLT);//左边框
            }
            //5.6 画选中方块的边框
            if (firstSelectedIndex >= 0)
            {
                Pen penSelected = new Pen(COLOR_SELECTED, 2);
                Point ptSquareLT = new Point(ptLeftTop.X + firstSelectedIndex % iColumnsCount * bmpWidth / iColumnsCount, ptLeftTop.Y + firstSelectedIndex / iColumnsCount * bmpHeight / iRowsCount);//选中方块的左上角的坐标
                Point ptSquareRT = new Point(ptSquareLT.X + bmpWidth / iColumnsCount, ptSquareLT.Y);
                Point ptSquareLB = new Point(ptSquareLT.X, ptSquareLT.Y + bmpHeight / iRowsCount);
                Point ptSquareRB = new Point(ptSquareRT.X, ptSquareLB.Y);
                graphic.DrawLine(penSelected, ptSquareLT, ptSquareRT);//上边框
                graphic.DrawLine(penSelected, ptSquareRT, ptSquareRB);//右边框
                graphic.DrawLine(penSelected, ptSquareRB, ptSquareLB);//下边框
                graphic.DrawLine(penSelected, ptSquareLB, ptSquareLT);//左边框
            }
            //5.7 显示游戏失败或胜利
            if(iGameState==2)
                graphic.DrawString("游戏成功", new Font("黑体", 72), new SolidBrush(Color.OrangeRed), new Point(210, 270));
            if (iGameState == 3)
                graphic.DrawString("游戏失败", new Font("黑体", 72), new SolidBrush(Color.Black), new Point(210, 270));
        }

        //5.3 重载OnPaint函数
        protected override void OnPaint(PaintEventArgs e)
        {
            Bitmap bufferBmp = new Bitmap(this.ClientRectangle.Width - 1, this.ClientRectangle.Height - 1);
            Graphics g = Graphics.FromImage(bufferBmp);
            this.DrawGame(g);

            e.Graphics.DrawImage(bufferBmp, 0, 0);
            g.Dispose();
            base.OnPaint(e);
        }

        //5.4 自定义函数,改变游戏难度
        private void ChangeDifficulty()
        {
            iRowsCount = Difficulty[iGameLevel, 0];
            iColumnsCount=Difficulty[iGameLevel,1];

            //5.5添加
            iGameState = 0;
            GameLeaveSeconds= 5 + iGameLevel*2;//

            //5.6添加
            oldMouseMoveInIndex = -1;
            newMouseMoveInIndex = -1;//
            firstSelectedIndex = -1;
            secondSelectedIndex = -1;
            //5.7
            this.GameTimer.Start();
            this.InitGrids();
            this.Invalidate();
        }

        //5.4 难度菜单的响应函数
        private void 菜鸟ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            iGameLevel = 0;
            this.菜鸟ToolStripMenuItem.Checked = true;
            this.入门ToolStripMenuItem.Checked = false;
            this.业余ToolStripMenuItem.Checked = false;
            this.专业ToolStripMenuItem.Checked = false;
            this.骨灰ToolStripMenuItem.Checked = false;
            this.ChangeDifficulty();
        }

        private void 入门ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            iGameLevel = 1;
            this.菜鸟ToolStripMenuItem.Checked = false;
            this.入门ToolStripMenuItem.Checked = true;
            this.业余ToolStripMenuItem.Checked = false;
            this.专业ToolStripMenuItem.Checked = false;
            this.骨灰ToolStripMenuItem.Checked = false;
            this.ChangeDifficulty();
        }

        private void 业余ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            iGameLevel = 2;
            this.菜鸟ToolStripMenuItem.Checked = false;
            this.入门ToolStripMenuItem.Checked = false;
            this.业余ToolStripMenuItem.Checked = true;
            this.专业ToolStripMenuItem.Checked = false;
            this.骨灰ToolStripMenuItem.Checked = false;
            this.ChangeDifficulty();
        }

        private void 专业ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            iGameLevel = 3;
            this.菜鸟ToolStripMenuItem.Checked = false;
            this.入门ToolStripMenuItem.Checked = false;
            this.业余ToolStripMenuItem.Checked = false;
            this.专业ToolStripMenuItem.Checked = true;
            this.骨灰ToolStripMenuItem.Checked = false;
            this.ChangeDifficulty();
        }

        private void 骨灰ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            iGameLevel = 4;
            this.菜鸟ToolStripMenuItem.Checked = false;
            this.入门ToolStripMenuItem.Checked = false;
            this.业余ToolStripMenuItem.Checked = false;
            this.专业ToolStripMenuItem.Checked = false;
            this.骨灰ToolStripMenuItem.Checked = true;
            this.ChangeDifficulty();
        }

        //5.4 声音开启菜单项的响应函数
        private void 开启ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            bPlaySound = !bPlaySound;
            if (bPlaySound)
                this.开启ToolStripMenuItem.Checked = true;
            else
                this.开启ToolStripMenuItem.Checked = false;
        }

        //5.4 更改图像的响应函数
        private void 更改图像ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            bUseMyPicture = false;
            this.LoadPicture();
            //this.InitGrids();
            //this.Invalidate();
            //5.5修改
            this.ChangeDifficulty();
        }

        //5.4 自选图像的响应函数
        private void 自选图像ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            bUseMyPicture = true;
            openFileDialog1.Filter = "(*.bmp)|*.bmp|(*.jpg)|*.jpg";
            openFileDialog1.ShowDialog();
        }

        //5.4 当玩家在本地计算机上选择好图片文件后执行的代码,即openFileDialog1的FileOk事件
        private void openFileDialog1_FileOk(object sender, CancelEventArgs e)
        {
            strMyPicturePath = openFileDialog1.FileName;
            this.LoadPicture();
            //this.InitGrids();
            //this.Invalidate();

            //5.5修改
            this.ChangeDifficulty();
        }

        //5.5
        private void GameTimer_Tick(object sender, EventArgs e)
        {
            //5.5添加
            GameLeaveSeconds--;
            
            if (GameLeaveSeconds == 0)
            {
                //5.7
                if (iGameState == 1)
                {
                    iGameState = 3;
                    oldMouseMoveInIndex = -1;
                    newMouseMoveInIndex = -1;
                    firstSelectedIndex = -1;
                    secondSelectedIndex = -1;
                    if (bPlaySound)
                    {
                        Play("GameFail");
                    }
                    this.GameTimer.Stop();
                }
                if (iGameState == 0)
                {
                    iGameState = 1;//由等待变为运行
                    GameLeaveSeconds = (iGameLevel+1) * 60;
                    this.RandomNewIndex();//调用核心算法
                    //5.7
                    if (bPlaySound)
                    {
                        Play("GameBegin");
                    }
                }
                
                
            }//

            //5.7
            if (iGameState == 0)
            {
                if (bPlaySound)
                {
                    Play("Clock");
                }
            }
            if (iGameState == 1&&GameLeaveSeconds<10)
            {
                if (bPlaySound)
                {
                    Play("Clock");
                }
            }
            this.toolStripStatusLabelGameState.Text = GameState[iGameState];
            this.toolStripStatusLabelLeaveTime.Text = GameLeaveSeconds.ToString();
            this.Invalidate();
        }

        private void FrmPuzzle_MouseMove(object sender, MouseEventArgs e)
        {
            if (iGameState != 1)
                return;//如果不是运行状态,返回,不执行下面的代码
            newMouseMoveInIndex = this.GetIndex(e);
            
            if (newMouseMoveInIndex != oldMouseMoveInIndex)//如果鼠标在一个方块内移动,但还没有进入两一个方块,不更新画面,这样可以避免频繁的更新画面
                this.Invalidate();
            oldMouseMoveInIndex = newMouseMoveInIndex;
        }

        //5.6 鼠标的按下事件
        private void FrmPuzzle_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)//按下了鼠标左键
            {
                if (firstSelectedIndex >= 0 && secondSelectedIndex < 0)//首先判断是否已经选中了第一个方块
                {
                    secondSelectedIndex = newMouseMoveInIndex;
                }
                if (firstSelectedIndex <0&&secondSelectedIndex <0&&newMouseMoveInIndex>=0)//判断是否还没有选中任何方块
                {
                    firstSelectedIndex = newMouseMoveInIndex;
                    //5.7
                    if (bPlaySound)
                    {
                        Play("FirstClick");
                    }
                    this.Invalidate();
                }
                if (firstSelectedIndex != secondSelectedIndex&&firstSelectedIndex>=0&&secondSelectedIndex>=0)//如果选中了两个不同的方块,则交换着两个方块的位置
                {
                    int temp = NewIndex[firstSelectedIndex];
                    NewIndex[firstSelectedIndex] = NewIndex[secondSelectedIndex];
                    NewIndex[secondSelectedIndex] = temp;
                    //
                    if (bPlaySound)
                    {
                        Play("Exchange");
                    }
                    //5.7
                    if (IsVictory())
                    {
                        iGameState = 2;
                        oldMouseMoveInIndex = -1;
                        newMouseMoveInIndex = -1;
                        firstSelectedIndex = -1;
                        secondSelectedIndex = -1;
                        if (bPlaySound)
                        {
                            Play("GameVictory");
                        }
                        this.GameTimer.Stop();
                        this.toolStripStatusLabelGameState.Text = GameState[iGameState];
                    }
                    firstSelectedIndex = -1;
                    secondSelectedIndex = -1;
                    this.Invalidate();
                }
                if (firstSelectedIndex == secondSelectedIndex && firstSelectedIndex >= 0 && secondSelectedIndex >= 0)//如果第二次点击的方块和第一次点击的方块相同,就意味着取消第一次的选择
                {
                    firstSelectedIndex = -1;
                    secondSelectedIndex = -1;
                    //5.7
                    if (bPlaySound)
                    {
                        Play("Cancel");
                    }
                    this.Invalidate();
                    //MessageBox.Show("执行了第4段代码");
                }   
            }
        }

            //5.7 自定义函数IsVictory()
            private bool IsVictory()
            {
                for(int i=0;i<iRowsCount*iColumnsCount;i++)
                    if(NewIndex[i]!=i)
                        return false;
                return true;
            }

            //5.7 播放声音
            private void Play(string waveName)
            {
                PlaySound(Application.StartupPath + "\\GameSounds\\" + waveName + ".wav", 0, 1);
            }
        
        

        
    }
}


GetRandom类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PuzzleGame.BaseClass
{
    class GetRandom
    {
        public static Random globalRandomGenerator = GenerateNewRandomGenerator();
        public static Random GenerateNewRandomGenerator()
        {
            globalRandomGenerator = new Random((int)DateTime.Now.Ticks);
            return globalRandomGenerator;
        }
        public static int GetRandomInt()//随机产生0至9的一个整数
        {
            return globalRandomGenerator.Next(10);
        }

        public static int GetRandomInt(int max)
        {
            return globalRandomGenerator.Next(max);
        }
        public static int GetRandomInt(int min, int max)
        {
            return globalRandomGenerator.Next(max - min) + min;
        }

        public static float GetRandomFloat(float min, float max)
        {
            return (float)globalRandomGenerator.NextDouble() * (max - min) + min;
        }
    }
}



LoadBitmap类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//添加以下两个引用
using System.Drawing;//因为要使用Bitmap
using System.Windows.Forms;//因为要使用Application.StartupPath

namespace PuzzleGame.BaseClass
{
    class LoadBitmap
    {
        public static Bitmap LoadBmp(string bmpFileName)
        {
            
            return new Bitmap(Application.StartupPath + "\\GamePictures\\" + bmpFileName + ".bmp");
        }
    }
}



0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:75617次
    • 积分:1664
    • 等级:
    • 排名:千里之外
    • 原创:84篇
    • 转载:14篇
    • 译文:8篇
    • 评论:11条