Cet article présente principalement en détail la deuxième partie de l'écriture du code pour le jeu de puzzle C#. Il a une certaine valeur de référence. Les amis intéressés peuvent se référer à la préface
: Dans l'implémentation C#. de "Jigsaw Game" (Partie 1) en C# Jigsaw Game Writing Code Programming, téléchargé les codes de chaque module Dans cet article, les principes seront analysés en détail pour faciliter la compréhension et la compréhension des lecteurs. apprendre. Le programme comporte de nombreuses questions à souligner et nous pouvons apprendre et grandir ensemble !
Texte :
Puzzle est un jeu très classique En gros, tout le monde sait comment y jouer et comment il a commencé. fin. Alors, par où commencer quand on veut réaliser un puzzle ? La réponse est : partir de la réalité et décrire les exigences (essayez de les décrire sous forme de documents). Lorsque nous avons des exigences complètes, nous pouvons fournir des stratégies fiables, qui peuvent être implémentées dans le code et éventuellement devenir une œuvre !
(1) Exigences : (Cette exigence est écrite de manière plutôt bâclée et est personnalisée pour les débutants. Elle est basée sur la pensée des gens les plus courants et le processus de participation au jeu )
1. Image : Quand on joue à des puzzles, au moins on a une image
2. Découper : Le puzzle n'est pas une image, il faut découper le image entière en N*N petits morceaux Image
3. Perturber : Mélangez l'ordre de ces N*N petites images, mais assurez-vous qu'il peut être restauré en marchant selon les règles du jeu
4. Jugement : Juger le puzzle comme réussi
5. Interaction : Quelle méthode d'interaction utilisons-nous ? Ici, je choisis le clic de souris
6. Afficher la vignette complète du image originale
Ce qui précède correspond aux fonctions de base, les suivantes sont des fonctions étendues
7. Enregistrez le nombre d'étapes : enregistrez le nombre d'étapes nécessaires pour terminer
8. Changer les images : après avoir joué longtemps avec une image, peut-on la changer haha
9. Sélectionner la difficulté : trop facile ? je ne veux pas ! Après 3*3, il y a 5*5, et après 5*5, il y a 9*9. Mon colocataire a défié le niveau de difficulté le plus élevé 300 avec plus de 0 pas. Je suis désolé pour mon TAT de souris.
(2) Analyse :
Avec la demande, nous pouvons analyser comment la mettre en œuvre (cartographier la demande réelle dans l'ordinateur), notamment :
1. Plateforme de développement : Choisissez le langage C# ici
1). Stockage : Que voulons-nous stocker ? Quelle structure utilisons-nous pour le stocker ? Lorsque nous examinerons les exigences, nous constaterons que certaines ressources doivent être stockées
Image : utilisez l'image objet pour stocker
Unité (une collection de sous-images après découpe de l'image originale) : puisque Définir la structure struct Node, qui comprend l'objet Image utilisé pour stocker les petites images de l'unité, et le nombre stocké dans un entier (après découpe, chaque la petite unité reçoit un numéro pour faciliter la vérification si le jeu est terminé).
Chaque unité (une collection de sous-images après avoir coupé l'image originale) : utilisez tableau bidimensionnel (tel que puzzle, backgammon, musique d'élimination, Lianliankan, Tetris et autres points d'avion jeux matriciels) Vous pouvez l'utiliser pour stocker, pourquoi ? Parce que ça ressemble !) pour stocker
Difficulté : Utiliser des types d'énumération personnalisés (facile et normal et difficile) pour stocker
Nombre d'étapes : mise en forme Variableint Num stockage
Avec le stockage, on peut penser à la division des modules (La division logique correcte a été élargie, et la communication peut aussi devenir plus claire) et le construire, et implémenter les algorithmes spécifiques impliqués dans chaque module
Tout d'abord, les modules du programme sont divisés en quatre :
Type logique :
1. Classe Puzzle : utilisée pour décrire des puzzles
2. Classe de configuration : variables de configuration de stockage
Interactive :
3. Fenêtre du menu du jeu : Créer des options de menu
4. Fenêtre d'exécution du jeu : L'interface principale du jeu
1 . Grâce aux menus du jeu, vous pouvez manipuler des configurations telles que la difficulté ou les graphiques.
2. La fenêtre en cours d'exécution peut accéder et obtenir la configuration du jeu, et utiliser ses objets de puzzle de construction correspondants.
3. L'utilisateur interagit via la fenêtre en cours d'exécution, amenant indirectement l'objet puzzle à appeler la méthode de déplacement et à obtenir la méthode de motif
Étudiants qui lisent le code, je pense que la partie la plus problématique et déraisonnable est que le type d'énumération de difficulté est écrit dans la classe de puzzle. Il doit être écrit dans la classe de configuration, ou il doit être écrit dans un. classe séparée. Lecteurs Nous pouvons la modifier nous-mêmes
public enum Diff //游戏难度 { simple,//简单 ordinary,//普通 difficulty//困难 }
Nous pouvons considérer la classe de configuration comme un stockage de données, tandis que la classe puzzle est utilisée pour le traitement logique. , et le menu et la fenêtre d'exécution sont utilisés pour la présentation. Concernant l'interaction, j'avoue que cette conception n'est pas très raisonnable, mais lorsque l'ampleur du problème n'est pas assez grande, une considération excessive de la conception rendra-t-elle le programme gonflé ? Je pense qu'il doit y avoir un certain degré. Je ne sais pas exactement ce que c'est, mais je pense que pour ce programme, il suffit de le mettre en œuvre, et être obsédé par le design (type routine) l'emporte parfois sur le gain et la perte. (Opinion personnelle immature)
(3) Implémentation du code :
Description : Ce bloc se concentre sur la description de la classe Puzzle et sur l'implémentation spécifique et communication de l'entité de la classe d'exécution du jeu :
Méthode de construction du puzzle :
Devoir :
public Puzzle(Image Img,int Width, Diff GameDif)
// Image du puzzle, largeur (explication : la longueur du côté du carré, l'unité est en pixels, le le nom est Ambiguïté, désolé), la difficulté du jeu
La difficulté du jeu détermine le degré de votre division. Le degré de division détermine la taille du tableau que vous stockez. Par exemple, simple correspond à 3. lignes et 3 colonnes, normal correspond à 5 lignes et 5 colonnes La difficulté correspond à 9 lignes et 9 colonnes
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. l'image
//分割图片形成各单元保存在数组中 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++; } }
En fait, le processus d'attribution de valeurs au tableau d'unités utilise une double couche boucle for pour parcourir le tableau bidimensionnel, puis attribue le numéro node[x, y].Num en séquence
Attribuez ensuite une valeur à node[x, y].Img, qui est le petite image de l'unité. La méthode d'affectation consiste à utiliser la bibliothèque de classes d'images C# pour écrire une méthode de capture d'écran et utiliser cette méthode, intercepter la petite image de la taille correspondante correspondant à la position correspondante dans la grande image et enregistrez-la dans node[x,y].Img
Qu'est-ce que width/N ? C'est la longueur du côté divisée par le nombre de rangées, qui est l'intervalle, et l'intervalle est la longueur du côté de chaque unité ! Ensuite la coordonnée de départ (X, Y) signifie qu'après quelques unités, ma position est
, soit : (x, y) = (longueur du côté unitaire *Le nombre d'unités à partir du point de départ sur l'axe X, longueur du côté de l'unité *Le nombre d'unités à partir du point de départ sur l'axe Y)
Concernant ce type de problème, j'espère que les lecteurs pourront dessiner plus d'images, et alors ils comprendront naturellement ;
public Image CaptureImage(Image fromImage, int width, int height, int spaceX, int spaceY)
Logique principale : Utiliser la méthode 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());
Après la segmentation, nous devons effectuer un processus spécial, car nous savons qu'il y a est toujours une position blanche, non ? Nous passons par défaut à la dernière position, qui est node[N-1, N-1] ;
est écrit et transformé en une image blanche, puis les bords environnants sont peints en rouge, ce qui a été découvert par d'autres, pour le rendre plus visible, j'ai également dessiné des bordures sur d'autres unités précédentes, mais elles étaient blanches, également pour différencier les puzzles en termes de valeur ornementale. Ce code ne sera pas introduit.
3. Brouiller l'image :
En fait, il s'agit de brouiller le tableau bidimensionnel. Nous pouvons utiliser un peu de tri et de brouillage. méthodes, mais veuillez noter ! Toutes les perturbations ne peuvent pas être récupérées !
Alors, comment faire ? La méthode est très simple à comprendre : il s'agit de laisser notre ordinateur parcourir les unités complètes et ordonnées selon la méthode de marche prévue dans les règles sans règles et un grand nombre de fois avant le début de la partie ! En d’autres termes, vous pouvez certainement revenir par là !
Comprenez-le d'abord, la méthode spécifique de perturbation sera expliquée plus tard.
Méthode de déplacement (Move) :
Le mouvement des carrés dans le jeu de puzzle est en fait l'échange de deux unités adjacentes, et parmi ces deux unités, il doit y avoir Il y a une unité blanche (c'est-à-dire l'unité node[N-1,N-1] mentionnée ci-dessus, son numéro est N*N-1, il est recommandé de faire le calcul par vous-même)
Donc notre jugement conditions Oui, si vous déplacez un bloc, s'il y a une unité blanche adjacente dans ses quatre directions, haut, bas, gauche et droite, c'est-à-dire l'unité n° N*N-1, elle sera échangée. C'est la logique de base, mais elle n'inclut pas la condition contrainte Lorsque notre tableau atteint la limite, nous ne pouvons pas accéder aux données hors limites. Par exemple, lorsque l'unité est node[0,0. ], vous ne pouvez pas accéder aux données ci-dessus et à droite, car Node[-1,0] Node[0,-1] sortira des limites, une exception se produit
Le déplacement est réussi et VRAI est renvoyé
Le mouvement a échoué, Retour 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(); }
好,那么大体的逻辑,程序中最需要思考的算法已经讲完了,还有不太懂的地方,欢迎交流~么么哒~
加了点小功能 音乐历史成绩
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!