ホームページ バックエンド開発 C#.Net チュートリアル C#で「ジグソーゲーム」を実装してみる(その2)

C#で「ジグソーゲーム」を実装してみる(その2)

Apr 16, 2017 pm 02:31 PM


テキスト:

パズルは基本的に、そのゲームプレイ、開始、実行、終了を誰もが知っています。では、パズルを作りたいときはどうやって始めればよいのでしょうか?答えは、現実から始めて要件を記述します (要件をドキュメントとして記述するように努めてください)。包括的な要件があれば、信頼できる戦略を提供でき、それをコードに実装して最終的に作品にすることができます。

(1) 要件: (この要件はかなり読みにくく書かれており、大多数の初心者向けにカスタマイズされています。最も一般的な人々の思考とゲームに参加するプロセスに基づいています)

1. 写真 : 私たちはパズルをします 少なくとも絵があります

2. 切り抜き: パズルは絵ではありません。全体の絵を N*N 個の小さな絵に切り分ける必要があります

3. 整理する: これらの N*N の順序をシャッフルします。ただし、ゲームのルールに従って歩くことで復元できることを確認してください

4. 判定: パズルは成功と判断されます

5. インタラクション: ここではどのインタラクション方法を選択しますか?

6. 元画像の完全な

サムネイルを表示します

上記が基本機能、以下が拡張機能です

7. 歩数の記録: 完了するまでに何歩か記録します

8.写真を変更する: 長い間遊んだ後、写真を変更できますか? (笑) 9. 難易度を選択します: 簡単すぎますか?欲しくない! 3*3の次は5*5があり、5*5の次は9*9があり、ルームメイトは最高難易度

300

0ステップに挑戦しました、そして私のマウスTATは残念です

(2)。分析:

要件、次のような実装方法 (実際の要件をコンピューターにマッピングする) を分析できます:

1. 開発プラットフォーム: ここで C# 言語を選択します

1. ストレージ: 何をしたいのか店?それを保管するためにどのような構造を使用しますか?要件を振り返ると、保存する必要のあるリソースがいくつかあることがわかります

画像: 画像

オブジェクト

を使用して

を保存します ユニット (元の画像を切り取った後のサブ画像のコレクション): カスタム 構造体

struct Node (Image オブジェクトを含む) 整形を使用して保存されたユニットの小さな画像と番号を保存するために使用されます (切り取った後、ゲームが完了したかどうかを確認しやすくするために、各小さなユニットに番号が付けられます)。

各ユニット(元の画像を切り取った後のサブ画像の集合):二次元配列

を使用(ジグソー、バックギャモン、マッチ、連聯館、テトリスなどの平面格子ゲームを使用して保存できます)理由は? 似ているからです! ) を保存する

モジュールの分割 (正しい論理分割が拡張されており、コミュニケーションも明確になります

) を構築して、各モジュールに含まれる特定のアルゴリズムを実装します

まず、プログラムモジュールは 4 つに分割されます:

論理タイプ:

1. パズルクラス: パズルを記述するために使用されます

2. 構成クラス: 構成変数を保存します

インタラクティブタイプ:

3. ゲームメニューウィンドウ: メニューオプションを実行します

4. ゲーム実行ウィンドウ: ゲームのメインインターフェイス

1. ゲームメニューを通じて、難易度や写真などの設定を制御できます。

2. 実行中のウィンドウは、ゲーム構成にアクセスして取得し、対応する構築パズル オブジェクトを使用できます。

3. ユーザーは実行ウィンドウを通じて対話し、間接的にパズルオブジェクトに move メソッドを呼び出してパターンメソッドを取得させます

コードを見た学生の皆さん、最も問題があり、不合理な部分は列挙を書くことだと思いますパズルクラスでは、難易度の種類を構成クラスに記述するか、別のクラスに記述する必要があり、読者が自分で変更できます

 public enum Diff         //游戏难度        {
            simple,//简单
            ordinary,//普通
            difficulty//困难
        }
ログイン後にコピー

我们可以认为,配置类就像数据存储,而拼图类呢作为逻辑处理,菜单和运行窗口作为表现用于交互,我承认这种设计不是很合理,但是在问题规模不够大的时候,过分的考虑设计,会不会使程序变得臃肿?我想一定是有一个度,具体是多少,我不得而知,但我感觉,针对这个程序,实现就好,沉迷设计(套路型),有时得不偿失。(个人不成熟的小观点)

(三)代码实现:

  说明:本块重点描述 Puzzle(拼图)类与游戏运行类的具体实现及实体通讯:

拼图的构造方法

    1.赋值 :


public Puzzle(Image Img,int Width, Diff GameDif)// 拼图的图片,宽度(解释:正方形的边长,单位是像素,名字有歧义,抱歉),游戏的难度
ログイン後にコピー


 

 游戏的难度决定你分割的程度,分割的程度,决定你存储的数组的大小,如简单对应3行3列,普通对应5行5列,困难对应9行9列

 


 switch(this._gameDif)
            {                case Diff.simple:    //简单则单元格数组保存为3*3的二维数组
                    this.N = 3;
                    node=new Node[3,3];                    break;                case Diff.ordinary:    //一般则为5*5
                    this.N = 5;
                    node = new Node[5, 5];                    break;                case Diff.difficulty:  //困难则为9*9
                    this.N = 9;
                    node = new Node[9, 9];                    break;
            }
ログイン後にコピー


2.分割图片


            //分割图片形成各单元保存在数组中
            int Count = 0;            for (int x = 0; x < this.N; x++)
            {                for (int y = 0; y < this.N; y++)
                {

                    node[x, y].Img = CaptureImage(this._img, this.Width / this.N, this.Width / this.N, x * (this.Width / this.N), y * (this.Width / this.N));
                    node[x, y].Num = Count;
                    Count++;
                }
            }
ログイン後にコピー


其实对单元数组进行赋值的过程,使用双层for循环对二维数组进行遍历操作,然后按序赋值编号node[x,y].Num;

然后对node[x,y].Img,也就是单元的小图片赋值,赋值的方法是,C#的图像的类库,写一个截图方法,使用这个方法,将大图中对应对位置的对应大小的小图截取下来,并保存在node[x,y].Img中;

width/N是什么?是边长除以行数,也就是间隔嘛,间隔也就是每个单元的边长嘛!然后起始坐标(X,Y)起始就是在说,隔了几个单元后,我的位置,

即 :(x,y)=(单元边长*距离起始X轴相距单元数,单元边长*距离起始点Y轴相距单元数);

关于此类问题,希望读者能够多画画图,然后自然就明白了;


 public  Image CaptureImage(Image fromImage, int width, int height, int spaceX, int spaceY)
ログイン後にコピー


主要逻辑:利用DrawImage方法:


            //创建新图位图   
            Bitmap bitmap = new Bitmap(width, height);            //创建作图区域   
            Graphics graphic = Graphics.FromImage(bitmap);            //截取原图相应区域写入作图区   
            graphic.DrawImage(fromImage, 0, 0, new Rectangle(x, y, width, height), GraphicsUnit.Pixel);            //从作图区生成新图   
            Image saveImage = Image.FromHbitmap(bitmap.GetHbitmap());
ログイン後にコピー


分割了以后,我们要做一个特殊处理,因为我们知道,总有那么一个位置是白的吧?我们默认为最后一个位置,即node[N-1,N-1];

就是写改成了个白色的图片,然后四周的边线都给画成红色,已于被人发现,显著一些,之前的其他单元我也画了边线,但是是白色,也是为了在拼图的观赏性上得到区分。该代码不做介绍。

3.打乱图片:

其实就是将二维数组打乱,我们可以采取一些排序打乱方法但是请注意!不是每一种打乱都能够复原的!

那么如何做到可行呢?方法理解起来很简单,就是让我们的电脑在开局之前,将完整的有序的单元按照规则中提供的行走方式进行无规则,大次数的行走!也就是说这种方法一定能走回去!

先理解,具体打乱方法,在后面讲解。

移动方法(Move):

拼图游戏中方格的移动,其实就是两个相邻单元的交换,而这两个单元中,必定存在一个白色单元(即上面提到的node[N-1,N-1]单元,他的编号为N*N-1,建议自己动笔算一算)

所以我们的判断条件是,如果移动一个方块,他的上下左右四个方向中,一旦有一个相邻的是白色单元,即N*N-1号单元,则与其交换。这是基本逻辑,但不包括约束条件,当我们的数组达到边界的时候,我们就不能对越界数据进行访问,如当单元为node[0,0]时,你就不能对他上面和右面的数据进行访问,因为Node[-1,0] Node[0,-1]都会越界,发生异常

移动成功,返回TRUE

移动失败,返回FALSE


/// <summary>
        /// 移动坐标(x,y)拼图单元        /// </summary>
        /// <param name="x">拼图单元x坐标</param>
        /// <param name="y">拼图单元y坐标</param>
        public bool Move(int x,int y)
        {            //MessageBox.Show(" " + node[2, 2].Num);
            if (x + 1 != N && node[x + 1, y].Num ==  N * N - 1)
            {
                Swap(new Point(x + 1, y), new Point(x, y));                return true;
            }            if (y + 1 != N && node[x, y + 1].Num ==  N * N - 1)
            {
                Swap(new Point(x, y + 1), new Point(x, y));                return true;
            }                
            if (x - 1 != -1 && node[x - 1, y].Num == N * N - 1)
            {
                Swap(new Point(x - 1, y), new Point(x, y));                return true;
            }   
            if (y - 1 != -1 && node[x, y - 1].Num == N * N - 1)
            {
                Swap(new Point(x, y - 1), new Point(x, y));                return true;
            }            return false;
                
        }
ログイン後にコピー


  交换方法(Swap):交换数组中两个元素的位置,该方法不应该被类外访问,顾设置为private私有权限


        
          = = .node[a.X, a.Y] = .node[b.X, b.Y] =
ログイン後にコピー


 

打乱方法:

 前面提到,其实就是让电脑帮着乱走一通,说白了就是大量的调用Move(int X,int y)方法,也就是对空白位置的上下左右四个相邻的方块中随机抽取一个,并把它的坐标传递给Move使其进行移动,同样要进行越界考虑,这样的操作大量重复!代码自己看吧 ,利用随机数。


   /// <summary>
        /// 打乱拼图        /// </summary>
        public void Upset()
        {            int sum = 100000;            if (this._gameDif == Diff.simple) sum = 10000;            //if (this._gameDif == Diff.ordinary) sum = 100000;
            Random ran = new Random();            for (int i = 0, x = N - 1, y = N - 1; i < sum; i++)
            {                long tick = DateTime.Now.Ticks;
                ran = new Random((int)(tick & 0xffffffffL) | (int)(tick >> 32)|ran.Next());                switch (ran.Next(0, 4))
                {                    case 0:                        if (x + 1 != N)
                        {
                            Move(x + 1, y);
                            x = x + 1;
                        }                            
                        break;                    case 1:                        if (y + 1 != N)
                        {
                            Move(x, y + 1);
                            y = y + 1;
                        } 
                        break;                    case 2:                        if (x - 1 != -1)
                        {
                            Move(x - 1, y);
                            x = x - 1;
                        }      
                        break;                    case 3:                        if (y - 1 != -1)
                        {
                            Move(x, y - 1);
                            y = y - 1;
                        }                        break;
                }

            }
        }
ログイン後にコピー


返回图片的方法:

当时怎么起了个这样的鬼名字。。。DisPlay。。。

这个方法与分割方法刚好相背,这个方法其实就是遍历数组,并将其进行组合,组合的方法很简单,就是将他们一个一个的按位置画在一张与原图相等大小的空白图纸上!最后提交图纸,也就是return一个Image;


        public Image Display()
        {
            Bitmap bitmap = new Bitmap(this.Width, this.Width);            //创建作图区域   
            Graphics newGra = Graphics.FromImage(bitmap);            for (int x = 0; x < this.N; x++)                for (int y = 0; y < this.N; y++)
                    newGra.DrawImage(node[x, y].Img, new Point(x * this.Width / this.N, y * this.Width / this.N));            return bitmap;
        }
ログイン後にコピー


同样利用的是DrawImage方法,知道如何分割,这个应该很容易理解,自己算一算,在纸上比划比划就明白了;

 

判断方法:

该方法很容易理解,就是按序按序!遍历所有单元,如果他们的结果中有一个单元的编号

node[x, y].Num 不等于遍历的序号,那么说明,该单元不在原有位置上,即整个图片还没有完成,我们就可以直接返回假值false
如果所有遍历结果都正确,我们可认为,图片已复原,此时返回真值true
ログイン後にコピー


  count= ( x = ; x < .N; x++ ( y = ; y < .N; y++ (.node[x, y].Num != ++
ログイン後にコピー


 

 

游戏运行窗口:即游戏玩耍时用于交互的窗口

这里只讲一个方法:即当接受用户鼠标点击事件时我们应该怎么处理并作出什么样反应

其实说白了就这句难懂:

puzzle.Move(e.X / (puzzle.Width / puzzle.N), e.Y / (puzzle.Width / puzzle.N))

调用了移动方法,移动方块

横坐标为:e.X / (puzzle.Width / puzzle.N)
ログイン後にコピー
<em>纵坐标为:e.Y / (puzzle.Width / puzzle.N)<br></em><br><span style="font-size: 15px">我们<a href="http://www.php.cn/wiki/44.html" target="_blank">编程</a>中的<a href="http://www.php.cn/code/12117.html" target="_blank">整数</a>除法和数学里的除法是不一样的!比如10/4数学上等于2余2或者2.5,计算机里直接就是等于2了,只取整数部分<br><img src="/static/imghw/default1.png" data-src="https://img.php.cn/upload/article/000/000/004/48684ccad126d0d6436ec85a0a36293a-3.jpg" class="lazy" alt=""></span>
ログイン後にコピー


行数=行坐标 / 方块边长

列数=列坐标 / 方块边长

我们看P1,P2这两点

P1:40/30*30=1
P2:50/30*30=1

我们会发现同在一个单元格中,无论点击哪个位置,通过这个算法都能转化为
同一个坐标。

(e.x,e.y)为鼠标点击事件点击坐标


 private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
        {            if (puzzle.Move(e.X / (puzzle.Width / puzzle.N), e.Y / (puzzle.Width / puzzle.N)))
            {
                Num++;
                pictureBox1.Image = puzzle.Display();                if (puzzle.Judge())
                { 
                    if (MessageBox.Show("恭喜过关", "是否重新玩一把", MessageBoxButtons.OKCancel) == DialogResult.OK)
                    {
                        Num = 0;
                        puzzle.Upset();
                        pictureBox1.Image = puzzle.Display();
                        
                    }                    else
                    {
                        Num = 0;
                        closefather();                        this.Close();
                    }

                } 

            }
            NumLabel.Text = Num.ToString();
        }
ログイン後にコピー


 

 好,那么大体的逻辑,程序中最需要思考的算法已经讲完了,还有不太懂的地方,欢迎交流~么么哒~

加了点小功能 音乐历史成绩 

 

以上がC#で「ジグソーゲーム」を実装してみる(その2)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

C言語でさまざまなシンボルを使用する方法 C言語でさまざまなシンボルを使用する方法 Apr 03, 2025 pm 04:48 PM

c言語のシンボルの使用方法は、算術、割り当て、条件、ロジック、ビット演算子などをカバーします。算術演算子は基本的な数学的操作に使用されます。割り当てと追加、下位、乗算、除算の割り当てには、条件操作に使用されます。ポインター、ファイル終了マーカー、および非数値値。

C文字列におけるcharの役割は何ですか C文字列におけるcharの役割は何ですか Apr 03, 2025 pm 03:15 PM

Cでは、文字列でCharタイプが使用されます。1。単一の文字を保存します。 2。配列を使用して文字列を表し、ヌルターミネーターで終了します。 3。文字列操作関数を介して動作します。 4.キーボードから文字列を読み取りまたは出力します。

C言語で特殊文字を処理する方法 C言語で特殊文字を処理する方法 Apr 03, 2025 pm 03:18 PM

C言語では、以下などのエスケープシーケンスを通じて特殊文字が処理されます。\ nはラインブレークを表します。 \ tはタブ文字を意味します。 ESACEシーケンスまたは文字定数を使用して、Char C = '\ n'などの特殊文字を表します。バックスラッシュは2回逃げる必要があることに注意してください。さまざまなプラットフォームとコンパイラが異なるエスケープシーケンスを持っている場合があります。ドキュメントを参照してください。

C言語のcharとwchar_tの違い C言語のcharとwchar_tの違い Apr 03, 2025 pm 03:09 PM

C言語では、charとwchar_tの主な違いは文字エンコードです。CharはASCIIを使用するか、ASCIIを拡張し、WCHAR_TはUnicodeを使用します。 Charは1〜2バイトを占め、WCHAR_Tは2〜4バイトを占有します。 charは英語のテキストに適しており、wchar_tは多言語テキストに適しています。 CHARは広くサポートされており、WCHAR_TはコンパイラとオペレーティングシステムがUnicodeをサポートするかどうかに依存します。 CHARの文字範囲は限られており、WCHAR_Tの文字範囲が大きく、特別な機能が算術演算に使用されます。

マルチスレッドと非同期C#の違い マルチスレッドと非同期C#の違い Apr 03, 2025 pm 02:57 PM

マルチスレッドと非同期の違いは、マルチスレッドが複数のスレッドを同時に実行し、現在のスレッドをブロックせずに非同期に操作を実行することです。マルチスレッドは計算集約型タスクに使用されますが、非同期はユーザーインタラクションに使用されます。マルチスレッドの利点は、コンピューティングのパフォーマンスを改善することですが、非同期の利点はUIスレッドをブロックしないことです。マルチスレッドまたは非同期を選択することは、タスクの性質に依存します。計算集約型タスクマルチスレッド、外部リソースと相互作用し、UIの応答性を非同期に使用する必要があるタスクを使用します。

C言語でCharを変換する方法 C言語でCharを変換する方法 Apr 03, 2025 pm 03:21 PM

C言語では、charタイプの変換は、キャスト:キャスト文字を使用することにより、別のタイプに直接変換できます。自動タイプ変換:あるタイプのデータが別のタイプの値に対応できる場合、コンパイラは自動的に変換します。

C言語合計の機能は何ですか? C言語合計の機能は何ですか? Apr 03, 2025 pm 02:21 PM

C言語に組み込みの合計機能はないため、自分で書く必要があります。合計は、配列を通過して要素を蓄積することで達成できます。ループバージョン:合計は、ループとアレイの長さを使用して計算されます。ポインターバージョン:ポインターを使用してアレイ要素を指し示し、効率的な合計が自己概要ポインターを通じて達成されます。アレイバージョンを動的に割り当てます:[アレイ]を動的に割り当ててメモリを自分で管理し、メモリの漏れを防ぐために割り当てられたメモリが解放されます。

C言語でchar配列の使用方法 C言語でchar配列の使用方法 Apr 03, 2025 pm 03:24 PM

Char Arrayは文字シーケンスをC言語で保存し、char array_name [size]として宣言されます。アクセス要素はサブスクリプト演算子に渡され、要素は文字列のエンドポイントを表すnullターミネーター「\ 0」で終了します。 C言語は、strlen()、strcpy()、strcat()、strcmp()など、さまざまな文字列操作関数を提供します。

See all articles