Home  >  Article  >  Backend Development  >  Sharing of graphic and text code for writing jigsaw puzzles in C# (Part 2)

Sharing of graphic and text code for writing jigsaw puzzles in C# (Part 2)

黄舟
黄舟Original
2017-04-18 09:11:331552browse

This article mainly introduces the second part of writing code for C# puzzle game in detail. It has certain reference value. Interested friends can refer to the

preface: In the C# implementation of "Jigsaw Game" (Part 1) in C# Jigsaw Game Writing Code Programming, uploaded the code of each module, and in this article the principles will be analyzed in detail to make it easier for readers to understand and learn. The program has Many questions are welcome to point out and we can learn and grow together!

Text:

Jigsaw puzzle is a very classic game. Basically everyone knows how to play it and how it started. Run, end. So, how do we start when we want to make a puzzle? The answer is: start from reality and describe the requirements (try to describe them as documents). When we have comprehensive requirements, we can provide reliable strategies, which can be implemented in the code and eventually become a work!

(1) Requirements: (This requirement is written in a rather sloppy way and is customized for the majority of novices. It is based on the most common people’s thinking and the process of participating in the game)

 1.Picture: When we play puzzles, at least we have a picture

2. Cutting: The puzzle is not a picture, we need to cut the whole picture into N*N small pieces Picture

3. Disorganize: Shuffle the order of these N*N small pictures, but make sure it can be restored by walking according to the game rules

4. Judgment: Judge the puzzle success

5. Interaction: Which interaction method do we use? Here I choose mouse click

6. Display the complete thumbnail of the original image

The above is the basic Functions, the following are extended functions

7. Record number of steps: record how many steps are required to complete

8. Change pictures: Can we change a picture after playing with it for a long time? Hahahaha

9. Select difficulty: too easy? don't want! After 3*3, there are 5*5, and after 5*5, there are 9*9. My roommates challenged the highest difficulty level 300 with 0 steps. I feel sorry for my mouse TAT

(2) Analysis:

Once we have the demand, we can analyze how to implement it (map the actual demand on the computer), including:

1. Development platform: Choose C# language here

 1). Storage: What do we want to store? What structure do we use to store it? When we look back at the requirements, we will find that there are some resources that need to be stored

Pictures: Use Image objects to store

Units (a collection of sub-images after cutting the original picture): since DefinitionStructure struct Node, which includes the Image object used to store small pictures of the unit, and the number stored in an integer (after cutting, each small unit is given a number to facilitate checking whether the game is completed).

Each unit (a collection of sub-images after cutting the original picture): use two-dimensional array (such as puzzle, backgammon, Xiaoxiaole, Lianliankan, Tetris and other plane dot matrix games) You can use it to store, why? Because it looks similar! ) to store

Difficulty: Use custom enumeration types (easy and normal and difficult) to store

Number of steps: shaping Variable int Num storage

With storage, we can think about the division of modules (The correct logical division has been expanded, and it can also make the communication clearer) and build it, and implement the specific algorithms involved in each module

First of all, the program modules are divided into four:

Logical type:

1. Puzzle class: used to describe puzzles

2. Configuration class: storage configuration variables

Interactive:

3. Game menu window: Make menu options

4. Game running window: The main interface of the game

1. Through the game Menus can manipulate configurations such as difficulty or graphics.

 2. The running window can access and obtain the game configuration, and use its corresponding construction puzzle objects.

 3. The user interacts through the running window, indirectly causing the puzzle object to call the move method and obtain the pattern method

 Students who read the code, I think the most problematic and unreasonable part is that the difficulty enumeration type is written in the puzzle class. It should be written in the configuration class, or a separate class. Readers We can change it ourselves


 public enum Diff //游戏难度
 {
  simple,//简单
  ordinary,//普通
  difficulty//困难
 }

We can think of the configuration class as data storage, while the puzzle class serves as logical processing, and the menu and run window serve as presentation for interaction. , I admit that this design is not very reasonable, but when the scale of the problem is not large enough, will excessive consideration of the design make the program bloated? I think there must be a certain degree. I don’t know exactly what it is, but I feel that for this program, just implement it, and be obsessed with design (routine type), and sometimes the gain outweighs the loss. (Personal immature opinion)

(3) Code implementation:

Description: This block focuses on describing the Puzzle class and The specific implementation and entity communication of the game running class:

Puzzle’s construction method:

1. Assignment:

public Puzzle(Image Img,int Width, Diff GameDif)

//Puzzle picture, width (explanation: the side length of the square, the unit is pixels, the name is Ambiguity, sorry), the difficulty of the game

The difficulty of the game determines the degree of segmentation. The degree of segmentation determines the size of the array you store. For example, simple corresponds to 3 rows and 3 columns, and normal corresponds to 5 rows and 5 columns. The difficulty corresponds to 9 rows and 9 columns


 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. Split the picture


 //分割图片形成各单元保存在数组中
  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++;
  }
  }

In fact, the process of assigning values ​​to the unit array uses a double-layer for loop to traverse the two-dimensional array, and then assigns the number node[x,y].Num in order;

Then assign a value to node[x,y].Img, which is the small picture of the unit. The assignment method is to write a screenshot method in the class library of C# image. Using this method, the large The small picture of the corresponding size corresponding to the corresponding position in the picture is intercepted and saved in node[x,y].Img;

What is width/N? It is the side length divided by the number of rows, which is the interval, and the interval is the side length of each unit! Then the starting coordinates (X, Y) are saying that after a few units, my position,

is: (x, y)

= (unit side length *The number of units from the starting point on the X-axis, unit side length *The number of units from the starting point on the Y-axis);

Regarding this type of problem, I hope readers can draw more pictures, and then they will understand naturally;

public Image CaptureImage(Image fromImage, int width, int height, int spaceX, int spaceY)

Main logic: Use the DrawImage method:


//创建新图位图 
 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());

After segmentation, we need to do a special process, because we know that there is always a position that is white, right? We default to the last position, which is node[N-1, N-1];

is written and changed into a white picture, and then the surrounding edges are painted in red. It has been discovered by others. , to make it more conspicuous, I also drew borders on other previous units, but they were white, also to differentiate the puzzles in terms of their ornamental value. This code will not be introduced.

3. Scramble the picture:

In fact, it means to scramble the two-dimensional array. We can use some sorting and scrambling methods, but please Notice! Not every disruption can be recovered!

So how to make it feasible? The method is very simple to understand. It is to let our computer walk the complete and ordered units in an irregular and large number of times according to the walking method provided in the rules before the game starts! In other words, you can definitely go back this way!

Understand it first, the specific method of disruption will be explained later.

Move method (Move):

The movement of squares in the puzzle game is actually the exchange of two adjacent units, and among these two units, there must be There is a white unit (that is, the node[N-1,N-1] unit mentioned above, its number is N*N-1, it is recommended to do the math by yourself)

So our judgment conditions Yes, if you move a block, if there is an adjacent white unit in its four directions, up, down, left, and right, that is, unit No. N*N-1, it will be exchanged. This is basic logic, but it does not include

constraints conditions. When our array reaches the boundary, we cannot access out-of-bounds data. For example, when the unit is node[0,0], you cannot Access the data above and to the right, because Node[-1,0] Node[0,-1] will go out of bounds, and an exception will occur.

Move successfully, return TRUE

Move failed, Return 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私有权限


 //交换两个单元格
 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();
 }

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

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

The above is the detailed content of Sharing of graphic and text code for writing jigsaw puzzles in C# (Part 2). For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn