この記事では主に、ジグソーパズルのC#コーディングの次の部分を詳しく紹介します。興味のある方は参考にしてください。
前書き: C# でのジグソーパズルのコードの作成 - C# でのプログラミングを実装します。 「ジグソー ゲーム」 (パート 1)、各モジュールのコードをアップロードしました。この記事では、読者が理解しやすく、学習しやすいように原理を詳細に分析します。このプログラムには多くの問題があります。それらを指摘し、一緒に学び、成長してください。
テキスト:
パズルは基本的に、そのゲームプレイ、開始、実行、終了を誰もが知っています。では、パズルを作りたいときはどうやって始めればよいのでしょうか?答えは、現実から始めて要件を記述します (要件をドキュメントとして記述するように努めてください)。包括的な要件があれば、信頼できる戦略を提供でき、それをコードに実装して最終的に作品にすることができます。(1) 要件: (この要件はかなり読みにくく書かれており、大多数の初心者向けにカスタマイズされています。最も一般的な人々の思考とゲームに参加するプロセスに基づいています)
1. 2. 切り抜き: パズルは絵ではありません。全体の絵を N*N 個の小さな絵に切り分ける必要があります 3. 整理する: これらの N*N の順序をシャッフルします。ただし、ゲームのルールに従って歩くことで復元できることを確認してください 4. 判定: パズルは成功と判断されます 5. インタラクション: ここではどのインタラクション方法を選択しますか?6. 元画像の完全な
サムネイルを表示します7. 歩数の記録: 完了するまでに何歩必要かを記録します
8.写真を変更する: 長い間遊んだ後、写真を変更できますか? (笑) 9. 難易度を選択します: 簡単すぎますか?欲しくない! 3*3の次は5*5があり、5*5の次は9*9があります。 ルームメイトは0ステップ以上の最高難易度に挑戦しました
。分析:はい、要件。以下を含む実装方法 (実際の要件をコンピューターにマッピング) を分析できます。
1. 開発プラットフォーム: ここで C# 言語を選択します 1) 何を保存するか。 ?それを保管するためにどのような構造を使用しますか?要件を振り返ると、保存する必要のあるリソースがいくつかあることがわかります
画像: 画像
オブジェクトを使用して
を保存します ユニット (元の画像を切り取った後のサブ画像のコレクション): カスタム
構造体struct Node (Image オブジェクトを含む) 整形を使用して保存されたユニットの小さな画像と番号を保存するために使用されます (切り取った後、ゲームが完了したかどうかを確認しやすくするために、各小さなユニットに番号が付けられます)。 各ユニット(元の画像を切り取った後のサブ画像の集合):
二次元配列を使用(ジグソー、バックギャモン、マッチ、連聯館、テトリスなどの平面格子ゲームを使用して保存できます)理由は? 似ているからです! ) を保存する モジュールの分割 (
正しい論理分割が拡張され、コミュニケーションがより明確になります) を構築して、各モジュールに含まれる特定のアルゴリズムを実装します まず、プログラム モジュールは 4 つに分割されます:
1. パズルクラス: パズルの記述に使用 2. 設定クラス: 設定変数の保存
対話型: 3. ゲームメニューウィンドウ: メニューオプションの実行
4 .ゲーム実行ウィンドウ: ゲームのメインインターフェイス
1. 難易度や画像などの設定は、ゲームメニューから操作できます。 2. 実行中のウィンドウは、ゲーム構成にアクセスして取得し、対応する構築パズル オブジェクトを使用できます。
3. ユーザーは実行中のウィンドウを通じて対話し、間接的にパズルオブジェクトに move メソッドを呼び出してパターンメソッドを取得させますコードを読んだ学生の皆さん、一番問題で無理があると思うのは、パズルクラスに難易度列挙型が書かれていることです。構成クラスか別のクラスに書くべきで、読者は自分で変更できます。
public enum Diff //游戏难度 { simple,//简单 ordinary,//普通 difficulty//困难 }設定クラスはデータストレージのようなものであり、パズルクラスは論理処理として使用され、メニューと実行ウィンドウは対話のためのプレゼンテーションとして使用されると考えることができますが、この設計はあまり良くないことは認めます。合理的だが、問題の規模が十分でない場合 設計する際、考えすぎてプログラムが肥大化してしまわないか?ある程度のことはあると思うんですが、よく分かりませんが、このプログラムに関しては、とにかく実装して、デザイン(ルーチン型)にこだわったほうが得が上回ることもあると思います。 (個人的な未熟な意見)
(3) コード実装:
説明: このブロックは、Puzzle クラスとゲーム実行クラス間の特定の実装とエンティティ通信の記述に焦点を当てています: パズルの構造メソッド :
1. 割り当て:
public Puzzle(Image Img, intWidth, Diff GameDif)
// パズルの画像、幅 (説明: 正方形の辺の長さ、単位はピクセル、名前があいまいです、ごめんなさい)、ゲームの難易度です ゲームの難易度は、分割の度合いを決定します。たとえば、simple は 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. 画像を分割します。実際、cell 配列に値を割り当てるプロセスは、2 層の for ループ を使用して 2 次元配列を走査し、次に Sequential assign Number 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 space 白ですよね?デフォルトの位置は、node[N-1, N-1] です。 は白い絵に書き込まれ、周囲のエッジは他の人によって発見され、より目立つようになります。以前の他のユニットにも境界線を描きましたが、パズルの装飾的な品質を区別するために、境界線は白でした。このコードは紹介されません。3. 画像をスクランブルする:
は実際には 2 次元配列をスクランブルするために、いくつかの並べ替えおよびスクランブル方法を使用できますが、注意してください。すべての混乱を回復できるわけではありません。 では、どうすればそれを実現できるのでしょうか?この方法は理解するのが非常に簡単です。つまり、ゲーム開始前に、ルールで指定された歩行方法に従って、完全かつ順序付けられたユニットを不規則かつ多数回コンピュータに歩行させます。つまり、この道なら絶対に戻れる!
具体的な破壊方法については後ほど説明しますので、まず理解してください。 移動方法(移動):パズルゲームにおける正方形の移動は、実際には隣接する2つのユニットの交換であり、これら2つのユニットの間には白いユニット(つまり、上記のノード[ N- 1, N-1] ユニット、その番号は N*N-1 です。自分で計算することをお勧めします)
制約
条件は含まれていません。たとえば、ユニットがnode[0,0]の場合、境界外のデータにはアクセスできません。上記にアクセスすると、Node[-1,0] Node[0,-1] が範囲外となり例外が発生するため、右側のデータがアクセスされます 移動が成功すると TRUE が返されます。 .移動が失敗した場合は、
が返されます。/// <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私有权限
//交换两个单元格 private void Swap(Point a, Point b) { Node temp = new Node(); temp = this.node[a.X, a.Y]; this.node[a.X, a.Y] = this.node[b.X, b.Y]; this.node[b.X, b.Y] = temp; }
打乱方法:
前面提到,其实就是让电脑帮着乱走一通,说白了就是大量的调用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
public bool Judge() { int count=0; for (int x = 0; x < this.N; x++) { for (int y = 0; y < this.N; y++) { if (this.node[x, y].Num != count) return false; count++; } } return true; }
游戏运行窗口:即游戏玩耍时用于交互的窗口
这里只讲一个方法:即当接受用户鼠标点击事件时我们应该怎么处理并作出什么样反应
其实说白了就这句难懂:
puzzle.Move(e.X / (puzzle.Width / puzzle.N), e.Y / (puzzle.Width / puzzle.N))
调用了移动方法,移动方块
横坐标为:e.X / (puzzle.Width / puzzle.N)
纵坐标为:e.Y / (puzzle.Width / puzzle.N)
我们编程中的整数除法和数学里的除法是不一样的!比如10/4数学上等于2余2或者2.5,计算机里直接就是等于2了,只取整数部分
行数=行坐标 / 方块边长
列数=列坐标 / 方块边长
我们看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 サイトの他の関連記事を参照してください。