(游戏及源代码)C#拼图游戏

来源:互联网 发布:机械制图用什么软件 编辑:程序博客网 时间:2024/04/25 07:47

    大家好,这次我要为大家带来的是一个用C#写的WinForm游戏,名字叫做拼图游戏。拼图游戏想必大家肯定玩过,玩法我就不赘述了。

首先向大家展示一下运行效果图吧:

 

 

 

    1:创建WindowForm应用程序?

   打开vs2010,创建一个Window应用程序了。那么我们就创建一个应用程序,并命名为: LilacFlower。对于一个窗体应用程序,vs2010在图形界面对控件的布局以及属性事件的设置可以说是当今世界上最好的软件了。那么我们从工具箱里面拖动控件放到窗体界面上,效果图如下:

具体参照Form1.Designer.cs文件:

namespace LilacFlower{    partial class Form1    {        /// <summary>        ///         /// </summary>        private System.ComponentModel.IContainer components = null;        /// <summary>        ///         /// </summary>        /// <param name="disposing"></param>        protected override void Dispose(bool disposing)        {            if (disposing && (components != null))            {                components.Dispose();            }            base.Dispose(disposing);        }        #region Windows フォーム デザイナーで生成されたコード        /// <summary>        ///         ///         /// </summary>        private void InitializeComponent()        {            this.mainPanel = new System.Windows.Forms.Panel();            this.modePicture = new System.Windows.Forms.PictureBox();            this.difficultySelection = new System.Windows.Forms.ComboBox();            this.txtLabel = new System.Windows.Forms.Label();            this.label1 = new System.Windows.Forms.Label();            this.startbtn = new System.Windows.Forms.Button();            this.addPicturebtn = new System.Windows.Forms.Button();            this.pictureShowListBox = new System.Windows.Forms.ListBox();            ((System.ComponentModel.ISupportInitialize)(this.modePicture)).BeginInit();            this.SuspendLayout();            //             // mainPanel            //             this.mainPanel.Location = new System.Drawing.Point(151, 14);            this.mainPanel.Name = "mainPanel";            this.mainPanel.Size = new System.Drawing.Size(500, 500);            this.mainPanel.TabIndex = 0;            //             // modePicture            //             this.modePicture.Location = new System.Drawing.Point(12, 12);            this.modePicture.Name = "modePicture";            this.modePicture.Size = new System.Drawing.Size(120, 120);            this.modePicture.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;            this.modePicture.TabIndex = 0;            this.modePicture.TabStop = false;            this.modePicture.Click += new System.EventHandler(this.modePicture_Click);            //             // difficultySelection            //             this.difficultySelection.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;            this.difficultySelection.FormattingEnabled = true;            this.difficultySelection.Items.AddRange(new object[] {            "简单(3*3)",            "中等(5*5)",            "困难(7*7)"});            this.difficultySelection.Location = new System.Drawing.Point(45, 160);            this.difficultySelection.Name = "difficultySelection";            this.difficultySelection.Size = new System.Drawing.Size(88, 20);            this.difficultySelection.TabIndex = 0;            //             // txtLabel            //             this.txtLabel.AutoSize = true;            this.txtLabel.Location = new System.Drawing.Point(10, 163);            this.txtLabel.Name = "txtLabel";            this.txtLabel.Size = new System.Drawing.Size(29, 12);            this.txtLabel.TabIndex = 0;            this.txtLabel.Text = "难度";            //             // label1            //             this.label1.AutoSize = true;            this.label1.Location = new System.Drawing.Point(12, 197);            this.label1.Name = "label1";            this.label1.Size = new System.Drawing.Size(59, 12);            this.label1.TabIndex = 0;            this.label1.Text = "图片选择:";            //             // startbtn            //             this.startbtn.Location = new System.Drawing.Point(31, 474);            this.startbtn.Name = "startbtn";            this.startbtn.Size = new System.Drawing.Size(75, 23);            this.startbtn.TabIndex = 0;            this.startbtn.Text = "开始";            this.startbtn.UseVisualStyleBackColor = true;            this.startbtn.Click += new System.EventHandler(this.startbtn_Click);            //             // addPicturebtn            //             this.addPicturebtn.Location = new System.Drawing.Point(31, 429);            this.addPicturebtn.Name = "addPicturebtn";            this.addPicturebtn.Size = new System.Drawing.Size(75, 23);            this.addPicturebtn.TabIndex = 0;            this.addPicturebtn.Text = "添加图片";            this.addPicturebtn.UseVisualStyleBackColor = true;            this.addPicturebtn.Click += new System.EventHandler(this.addPicturebtn_Click);            //             // pictureShowListBox            //             this.pictureShowListBox.FormattingEnabled = true;            this.pictureShowListBox.ItemHeight = 12;            this.pictureShowListBox.Location = new System.Drawing.Point(14, 212);            this.pictureShowListBox.Name = "pictureShowListBox";            this.pictureShowListBox.Size = new System.Drawing.Size(120, 196);            this.pictureShowListBox.TabIndex = 0;            this.pictureShowListBox.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged);            //             // Form1            //             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;            this.ClientSize = new System.Drawing.Size(678, 525);            this.Controls.Add(this.pictureShowListBox);            this.Controls.Add(this.addPicturebtn);            this.Controls.Add(this.startbtn);            this.Controls.Add(this.label1);            this.Controls.Add(this.txtLabel);            this.Controls.Add(this.difficultySelection);            this.Controls.Add(this.modePicture);            this.Controls.Add(this.mainPanel);            this.Name = "Form1";            this.Text = "Form1";            ((System.ComponentModel.ISupportInitialize)(this.modePicture)).EndInit();            this.ResumeLayout(false);            this.PerformLayout();        }        #endregion        private System.Windows.Forms.Panel mainPanel;        private System.Windows.Forms.PictureBox modePicture;        private System.Windows.Forms.ComboBox difficultySelection;        private System.Windows.Forms.Label txtLabel;        private System.Windows.Forms.Label label1;        private System.Windows.Forms.Button startbtn;        private System.Windows.Forms.Button addPicturebtn;        private System.Windows.Forms.ListBox pictureShowListBox;    }}

现在窗体界面设计好了,下面该干嘛呢?对了,是设置Form的属性,为了把这些设置更好的讲解给大家,我就不在窗体设计文件中进行设置,在构造函数中设置,具体需要设置的窗体信息如下:1,窗体的名称 2,禁止拉伸窗口 3,图块所在容器背景色 4,去掉最大化按钮。具体代码如下:

            // 窗体相关设置            this.Text = "拼图游戏(LilacFlower)";            // 去掉最大化按钮            this.MaximizeBox = false;            // 禁止拉伸窗口            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;            // 显示背景            this.mainPanel.BackColor = Color.Wheat;            // 双缓冲绘图            this.DoubleBuffered = true;


2:动态加载图片

    讲到图片,就不得不提System.Drawing.Bitmap对象了。这是来着MSDN的解释:

    位图由图形图像及其特性的像素数据组成。可使用许多标准格式将位图保存到文件中。GDI+ 支持下列文件格式:BMP、GIF、EXIF、JPG、PNG 和 TIFF。有关支持的格式的更多信息,请参见位图类型

可以使用Bitmap 构造函数中的一种来从文件、流和其他源创建图像,然后使用Save 方法将这些图像保存到流或文件系统中。使用Graphics 对象的DrawImage 方法,将图像绘制到屏幕上或内存中。有关使用图像文件的主题的列表,请参见使用图像、位图、图标和图元文件

    。。。

    说白了,Bitmap对象就是一张存放在内存中的一张图片,所有格式的图片在GDI+中,加载到内存当中去,就应该是Bitma对象了,其常用的2个构造函数如下:

            Bitmap bmp1 = new Bitmap("path");            Bitmap bmp2 = new Bitmap(100, 200);
Bitmap对象可以用来作为图片显示,你也可以使用GDI+改变该图片。并且在控件中显示出来。

上面那个构造函数式图片路径,可以是绝对路径也可以说相对路径。在程序中有个叫Image的目录,是专门存放图片的,该目录由应用程序在第一次运行的时候创建,并且在以后添加文件时自动添加到该目录下,如:

      ①:应用程序自动创建文件夹:

      1:目录名称使用常量:

 

        /// <summary>        /// 存放图片的文件目录名称        /// </summary>        private const string PicturePath = "Image";


      2:应用程序检查是否存在Image目录,不存在就创建它

            // 第一次运行创建Image目录            if (!Directory.Exists(PicturePath))            {                Directory.CreateDirectory(PicturePath);            }

      

②:Image目录下的文件一览:

 

 

③:添加图片

      我们给按钮 ‘添加图片’ 增加一个相应函数(在窗体设计界面双击该按钮即可),这样你选择的图片就会自动拷贝到Image目录中被应用程序加载了。

        /// <summary>        /// 增加图片        /// </summary>        /// <param name="sender"></param>        /// <param name="e"></param>        private void addPicturebtn_Click(object sender, EventArgs e)        {            // 显示打开文件对话框            OpenFileDialog dlg = new OpenFileDialog();            dlg.Filter = "jpg文件|*.jpg|png图片|*.png|gif图片|*.gif";            // 按下确定            if (DialogResult.OK == dlg.ShowDialog())            {                try                {                    // 把文件拷贝到 Image目录下                    File.Copy(dlg.FileName, PicturePath + "\\" + System.IO.Path.GetFileName(dlg.FileName));                    NewPicture(PicturePath + "\\" + System.IO.Path.GetFileName(dlg.FileName));                }                catch (Exception ex)                {                    // 文件拷贝失败                    MessageBox.Show("文件复制失败!" + ex.Message);                }            }            dlg.Dispose();        }

对于dlg的Filter属性,其用'|'分割,并且前面奇数位置是显示在对话框中的文字,偶数位置是前一个的目录筛选后缀名。

dlg.ShowDialog()函数将显示一个对话框并返回你点击的按钮结果。

④:增加图片成功,拷贝成功

 

解决了加载图片问题,下面就是解决图块(就是上图那些标有数字的控件)问题了。

3:图块类及图块的特效

     考虑到图块需要显示图片,并且程序要对图块的位置进行判断是否已经拼完。那么我们可以创建一个类,并且让它继承PictureBox类,因为使用PictureBox来显示图片是再好不过的控件了。

 

    /// <summary>    /// 图块    /// </summary>    public class PictureItem : PictureBox


继承PictureBox后图块具有了PictureBox的一切特效,当能作为子类,我们需要为‘它’添加一些其它的特性,如:当鼠标移入的时候,图片框的颜色会发生变化,鼠标移出的时候图片框的颜色还原,并且当需要的时候还显示图片的位置(就是它上面的数字)。

 

    /// <summary>    /// 图块    /// </summary>    public class PictureItem : PictureBox    {        private Bitmap _mouseOutBitmap;// 默认图片        private Bitmap _mouseInBitmap; // 鼠标移入的图片        private Bitmap _mouseOutBitmapContainsNumber;// 包含数字的默认图片        private Bitmap _mouseInBitmapContainsNumber;// 包含数字的鼠标移入图片        /// <summary>        /// 是否显示图片数字        /// </summary>        private bool _showNumber;        public bool ShowNumber        {            get            {                return this._showNumber;            }            set            {                this._showNumber = value;                this.PictureItem_MouseLeave(this, new EventArgs());            }        }        /// <summary>        /// 图块所在的位置        /// </summary>        public Point At        {            get;            set;        }        /// <summary>        /// 图块的正确位置 用于判断图块是否在正确位置上        /// </summary>        public Point RightAt        {            protected set;            get;        }        public PictureItem()        {            // 鼠标移入图片方框颜色变化            this.MouseEnter += new EventHandler(PictureItem_MouseEnter);            // 鼠标移出使用默认图片            this.MouseLeave += new EventHandler(PictureItem_MouseLeave);        }        /// <summary>        /// 鼠标离开        /// </summary>        /// <param name="sender"></param>        /// <param name="e"></param>        void PictureItem_MouseLeave(object sender, EventArgs e)        {            if (!this._showNumber)            {                this.Image = this._mouseOutBitmap;            }            else this.Image = this._mouseOutBitmapContainsNumber;        }        /// <summary>        /// 鼠标移入        /// </summary>        /// <param name="sender"></param>        /// <param name="e"></param>        void PictureItem_MouseEnter(object sender, EventArgs e)        {            if (!this._showNumber)            {                this.Image = this._mouseInBitmap;            }            else this.Image = this._mouseInBitmapContainsNumber;        }        /// <summary>        /// 设置图块的图片        /// </summary>        /// <param name="msIn"></param>        /// <param name="msOut"></param>        public void SetBitmap(Bitmap msIn, Bitmap msOut, Bitmap numMsIn, Bitmap numMsOut)        {            this._mouseInBitmap = msIn;            this._mouseOutBitmap = msOut;            this._mouseOutBitmapContainsNumber = numMsOut;            this._mouseInBitmapContainsNumber = numMsIn;            this.Image = msOut;        }        /// <summary>        /// 设置图块的正确位置        /// </summary>        /// <param name="x"></param>        /// <param name="y"></param>        public void SetRightPoint(int x, int y)        {            this.RightAt = new Point(x, y);        }    }

这么一作,一个图块类以及完成了,接下来就是使用这个图块类了。

①:图块数组

    因为我们在程序中需要N*N的图块,而N是未知数,那么就创建一个名为_pictureArray的二维数组,用于动态创建图块群。

private PictureItem[,] _pictureArray;

②创建图块,指定图片
   根据用户的选择创建指定长度的图块后,下面就是计算和确定每一个图块的图片,把一张图片划分为N*N个‘格子’这儿就把它暂时叫做为格子吧,如何划分呢?现在就是解决图片的分割问题了,当能GDI+足够强大,这个功能可以不复杂的完成。这个我们就要借助Graphics对象了,具体方法是在内存中创建一张图片Bitmap对象b,大小和格子一样大,能后创建一个Graphics对象g,使用g将图片的指定位置(也就是格子上的图片)绘制到b中。根据程序需求,在给图片加上方框和数字即可。再调用PictureItem.Image = b;就可以将图块的图片设置为这个带方框的图片了。

 

        /// <summary>        /// 截取bmp图片中的一部分,并在图片四周画上方框        /// </summary>        /// <param name="bmp">被截取的图片</param>        /// <param name="width">截取的长度</param>        /// <param name="heigth">截取的宽度</param>        private Bitmap DivisionPicture(Bitmap bmp, int x, int y, int width, int height, Pen p, int number)        {            Bitmap b = new Bitmap(width, height);            // g上的绘图将直接绘制到位图b上            Graphics g = Graphics.FromImage(b);            g.DrawImage(bmp, new Rectangle(0, 0, width, height), new Rectangle(x, y, width, height), GraphicsUnit.Pixel);            g.DrawRectangle(p, new Rectangle(1, 1, width - 2, height - 2));            // 需要绘制数字            if (number >= 0)            {                g.DrawString(number.ToString(), new System.Drawing.Font("楷体", width / 5, FontStyle.Bold), Brushes.Red, new PointF(width / 2 - width / 10, height / 2 - width / 10));            }            g.Dispose();            return b;        }


使用完了别忘记销毁Graphics对象g。上面这个函数将直接返回绘制出来的位图对象。

③打乱图块,并扣掉左上角那个

            // 随机打乱这些图块来            // TODO            Random r = new Random(System.DateTime.Now.Millisecond);            for (int i = 0; i < 200; i++)            {                // 随机拿2个图快                var box1 = this.mainPanel.Controls[r.Next(0, num * num - 2)] as PictureItem;                var box2 = this.mainPanel.Controls[r.Next(0, num * num - 2)] as PictureItem;                if (box1 == box2)                    continue;                // 交换他们的位置                var tempAt = box1.At;                var tempLoca = box1.Location;                box1.At = box2.At;                box1.Location = box2.Location;                box2.At = tempAt;                box2.Location = tempLoca;            }

200来打乱完全足够了吧?在初始化这些图块的时候记得加上:

                    // 删掉左上角那个                    if (i == 0 && j == 0)                        continue;              

用于去掉左上角那个图块。对于设置图片的显示方式:

box.SizeMode = PictureBoxSizeMode.StretchImage;

 将图片显示方式设置为:适应控件大小,那么图片将按比例缩放至控件大小一致。

 

④图块移动

        1:为了使图块移动有动态效果,那么不得不考虑多线程了。对于多线程对象System.Threading.Thread.下面是来着MSDN中的解释 

一个进程可以创建一个或多个线程以执行与该进程关联的部分程序代码。使用 ThreadStart 委托或 ParameterizedThreadStart 委托指定由线程执行的程序代码。使用 ParameterizedThreadStart 委托可以将数据传递到线程过程。在线程存在期间,它总是处于由 ThreadState 定义的一个或多个状态中。可以为线程请求由 ThreadPriority 定义的调度优先级,但不能保证操作系统会接受该优先级。GetHashCode 提供托管线程的标识。在线程的生存期内,无论获取该值的应用程序域如何,它都不会和任何来自其他线程的值冲突。      它和我们在C++学的多线程是基本一致的,但是C#中的Thread对象是.Net 库中内置的,对于C++中的多线程我们需要直接借助于操作系统来实现。C#中的Thread多线程是安全的。

我们给每一个图块增加一个相同的相应函数:

// 图片的点击函数                    box.Click += new EventHandler(box_Click);


点击图块后,我们得首先拿到这个控件,拿到后,做个判断,是否是在空白位置的旁边:

        /// <summary>        /// 任意一个图块被点击        /// </summary>        /// <param name="sender"></param>        /// <param name="e"></param>        void box_Click(object sender, EventArgs e)        {            // 获取被点击的图块            PictureItem box = sender as PictureItem;            // 如果旁边是能点击的,移动该box位置            int xL = this._nullBox.X - box.At.X;            int yL = this._nullBox.Y - box.At.Y;            if ((xL == 0 && yL == -1) || (xL == 0 && yL == 1) || (xL == -1 && yL == 0) || (xL == 1 && yL == 0))            {                MoveTo(box, _nullBox);            }        }


如果图块可以移动,那么我们调用MoveTo函数对图块进行多线程移动。如果图块可以移动,那么我们调用MoveTo函数对图块进行多线程移动。

 

       2:在下面的代码中我们使用了匿名函数来实现,对于匿名函数的使用,有兴趣的朋友可以参照MSDN中的解释,具体我就不多解释了。在这儿值得一提的是对于跨线程更改控件的属性问题了。在应用程序中,如果不是创建那个控件的线程中控件的属性被更改,那么在调试(Debug)的过程中将出一个异常,那么需要借助Form类的一个函数BeginInvoke来实现.

 

       /// <summary>        /// 将box图块移动到to位置        /// </summary>        /// <param name="box"></param>        /// <param name="to"></param>        private void MoveTo(PictureItem box, Point to)        {            // 使用多线程来完成 匿名函数            Thread start = new Thread(() =>                {                    lock (this)                    {                        if (this.InvokeRequired)                        {                            // 虑到跨线程访问控件的值安全性 使用该方法                            this.BeginInvoke(new MoveMode(MoveBox), box, to);                        }                        else                        {                            MoveBox(box, to);                        }                    }                }            );            // 开始运行            start.Start();        }        /// <summary>        /// 移动图块        /// </summary>        /// <param name="box"></param>        /// <param name="to"></param>        private void MoveBox(PictureItem box, Point to)        {            // 计算 to位置的像素点屏幕坐标            int x = box.Size.Width * to.X;            int y = box.Size.Height * to.Y;            int xL = box.Location.X - x;            int yL = box.Location.Y - y;            int size = (int)Math.Sqrt(xL * xL + yL * yL);            // 缓缓的从本身位置移动到 to位置            while (size > 5)            {                box.Location = new Point(box.Location.X - MoveSize * xL / size, box.Location.Y - MoveSize * yL / size);                xL = box.Location.X - x;                yL = box.Location.Y - y;                size = (int)Math.Sqrt(xL * xL + yL * yL);                Thread.Sleep(1);            }            box.Location = new Point(x, y);            // 交换图块和空白位置的坐标            var temp = this._nullBox;            this._nullBox = box.At;            box.At = temp;            // 判断移动完毕后图块是否正确!            if (IsOver() && WinNow != null)            {                WinNow(this, new EventArgs());            }        }


考虑到多线程数据的同步性在这儿我们使用lock关键字,防止图块在移动的过程中点击其他图块导致2个图块同时移动。使用lock后,一个直接结果就是一个图块在移动的时候点击别的一个图块,别的一个图块移动需要排队等待上一个图块移动结束。

⑤胜利的判定

首先我们在每一个图块对象上使用2个属性:

 

        /// <summary>        /// 图块所在的位置        /// </summary>        public Point At        {            get;            set;        }        /// <summary>        /// 图块的正确位置 用于判断图块是否在正确位置上        /// </summary>        public Point RightAt        {            protected set;            get;        }


前面那个是完全公开的属性,用于记录该图块当前的位置,而后面那个属性的set函数被保护,对于被保护的原因大家都知晓了了吧,在创建图块的时候该属性被赋值,即图块的标准位置。之后你无法再任何地方更改它的值,因为它的值在这之后就是固定的。判断是否该图块在正确的位置,方法就是:

 

                // 图块摆放不正确                if (box.At != box.RightAt)                    return false;

 

==========================================================================================

讲了这么多最后附上代码下载网站吧:

http://download.csdn.net/detail/lilacflower/4765775

 

原创粉丝点击