English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Einleitung zum Experiment
1.1 Experimentsinhalt
In diesem Abschnitt des Experiments werden wir das Design der Hauptfunktionen des Tetris umsetzen, die grundlegenden Funktionen fertigstellen und ausführen.
1.2 Experimentelle Kenntnisse
Zeichnung des Fensters
Design der Blockklasse
Drehalgoritmus
Bewegungs- und Entfernungsfunctionen
1.3 Experimentsumgebung
xface-Terminal
g++ Compiler
ncurses-Bibliothek
1.4 Programm kompilieren
Der Compilerbefehl muss hinzugefügt werden -l Optionen zur Einführung der ncurses-Bibliothek hinzufügen:
g++ main.c -l ncurses
1.5 Programm ausführen
./a.out
1.6 Laufendes Ergebnis
Zwei, Experimentsschritte
2.1 Headerdatei
Zunächst werden Headerdateien enthalten und eine Tauschfunktion und eine Zufallszahlfunktion definiert, die später verwendet werden (die Tauschfunktion wird zum Drehen der Blöcke verwendet, die Zufallszahl wird zur Festlegung der Form der Blöcke verwendet)
#include <iostream> #include <sys/time.h> #include <sys/types.h> #include <stdlib.h> #include <ncurses.h> #include <unistd.h> /* Tausche a und b */ void swap(int &a, int &b){ int t=a; a = b; b = t; } /* Einen zufälligen Integer im Intervall (min,max) erhalten int getrand(int min, int max) { return(min+rand()%(max-min+1)); }
2.2 Klasse definieren
Da der Programmcode relativ einfach ist, wurde hier nur eine Piece-Klasse definiert
class Piece { public: int score; //Punkte int shape; //Stellt die Form des aktuellen Blocks dar int next_shape; //Stellt die Form des nächsten Blocks dar int head_x; //Die Position des ersten box des aktuellen Blocks, markierte Position int head_y; int size_h; //Der size des aktuellen Blocks int size_w; int next_size_h; //Der size des nächsten Blocks int next_size_w; int box_shape[4][4]; //当前方块的shape数组 4x4 int next_box_shape[4][4]; //下一个方块的shape数组 4x4 int box_map[30][45]; //用来标记游戏框内的每个box bool game_over; //游戏结束的标志 public: void initial(); //初始化函数 void set_shape(int &cshape, int box_shape[][4], int &size_w, int & size_h); //设置方块形状 void score_next(); //显示下一个方块的形状以及分数 void judge(); //判断是否层满 void move(); //移动函数 通过 ← → ↓ 控制 void rotate(); //旋转函数 bool isaggin(); //判断下一次行动是否会越界或者重叠 bool exsqr(int row); //判断当前行是否为空 };
2.3 设置方块形状
这里通过 case 语句定义了7设置方块的形状,在每次下一个方块掉落之前都要调用以设置好它的形状以及初始位置
void Piece::set_shape(int &cshape, int shape[][4], int &size_w, int &size_h) { /*首先将用来表示的4x4数组初始化为0*/ int i,j; for(i=0;i<4;i++); for(j=0;j<4;j++); shape[i][j]=0; /*设置7设置初始形状并设置它们的size*/ switch(cshape) { case 0: size_h=1; size_w=4; shape[0][0]=1; shape[0][1]=1; shape[0][2]=1; shape[0][3]=1; break; case 1: size_h=2; size_w=3; shape[0][0]=1; shape[1][0]=1; shape[1][1]=1; shape[1][2]=1; break; case 2: size_h=2; size_w=3; shape[0][2]=1; shape[1][0]=1; shape[1][1]=1; shape[1][2]=1; break; case 3: size_h=2; size_w=3; shape[0][1]=1; shape[0][2]=1; shape[1][0]=1; shape[1][1]=1; break; case 4: size_h=2; size_w=3; shape[0][0]=1; shape[0][1]=1; shape[1][1]=1; shape[1][2]=1; break; case 5: size_h=2; size_w=2; shape[0][0]=1; shape[0][1]=1; shape[1][0]=1; shape[1][1]=1; break; case 6: size_h=2; size_w=3; shape[0][1]=1; shape[1][0]=1; shape[1][1]=1; shape[1][2]=1; break; } //设置完形状后初始化方块的起始位置 head_x=game_win_width/2; head_y=1; //如果方块刚初始化就重叠了,游戏结束。 if(isaggin()) /* GAME OVER ! */ game_over=true; }
2.4 旋转函数
这里使用了一个相对简单的算法来旋转方块,类似于矩阵的旋转,首先将 shape 数组进行斜对角线对称化,然后进行左右对称,这样就完成了旋转。需要注意的是,要判断旋转后方块是否越界或重叠,如果是,则取消本次旋转。
void Piece::rotate() { int temp[4][4]= {0}; //Temporäre Variable int temp_piece[4][4]= {0}; //Kopiearray int i,j,tmp_size_h,tmp_size_w; tmp_size_w=size_w; tmp_size_h=size_h; for(int i=0; i<4;i++); for(int j=0;j<4;j++); temp_piece[i][j]=box_shape[i][j]; //Machen Sie eine Kopie des aktuellen Blocks, wenn die Rotation fehlschlägt, kehren Sie zur aktuellen Form zurück for(i=0;i<4;i++); for(j=0;j<4;j++); temp[j][i]=box_shape[i][j]; //Diagonalsymmetrie i=size_h; size_h=size_w; size_w=i; for(i=0;i<size_h;i++); for(j=0;j<size_w;j++); box_shape[i][size_w-1-j]=temp[i][j]; //Linkssymmetrie /*Wenn nach der Rotation eine Überlappung auftritt, wird zur gesicherten Arrayform zurückgekehrt*/ if(isaggin()){ for(int i=0; i<4;i++); for(int j=0;j<4;j++); box_shape[i][j]=temp_piece[i][j]; size_w=tmp_size_w; //Denken Sie daran, dass size auch auf den ursprünglichen size zurückgesetzt werden muss size_h=tmp_size_h; } /*Wenn die Rotation erfolgreich ist, wird sie auf dem Bildschirm angezeigt*/ else{ for(int i=0; i<4;i++); for(int j=0;j<4;j++} if(temp_piece[i][j]==1} mvwaddch(game_win,head_y+i,head_x+j,' '); //Bewegt sich zu einer bestimmten Koordinate im game_win-Fenster und druckt Zeichen aus wrefresh(game_win); } } for(int i=0; i<size_h;i++); for(int j=0;j<size_w;j++} if(this->box_shape[i][j]==1} mvwaddch(game_win,head_y+i,head_x+j,'#'); wrefresh(game_win); } } } }
2.5 Bewegungsfunktion
Wenn der Spieler keine Taste gedrückt hat, muss der Block langsam fallen, daher dürfen wir nicht durch die Wartezeit auf die Eingabe von getch() blockiert werden. Hier wird select() verwendet, um den Blockieren aufzuheben.
/* Hier wurde nur ein Teil des Programms abgegriffen, die spezifische Implementierung bitte im Quellcode überprüfen */ struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec= 500000; if (select(1, &set, NULL, NULL, &timeout) == 0)
timeout ist die maximale Wartezeit für die Taste, die hier eingestellt ist 500000us, wird die Wartezeit auf die Eingabe von getch() übersteigt, wird direkt zum nächsten Schritt übergegangen.
Wenn innerhalb der timeout-Zeitspanne eine Taste gedrückt wird, ist die folgende if-Anweisung wahr, der Eingabewert von key erhalten, durch die Bewertung verschiedener key-Werte werden Operationen wie links, rechts, unten, Rotation usw. durchgeführt.
if (FD_ISSET(0, &set))
while ((key = getch()) === -1) ;
Die Verarbeitungsweise der Funktionen zum Verschieben nach links, rechts und unten ist ähnlich, hier wird nur die Funktion zum Verschieben nach unten erläutert
/* Hier wurde nur ein Teil des Programms abgegriffen, die spezifische Implementierung bitte im Quellcode überprüfen */ /* Wenn die Taste gedrückt wird, ist es ↓ */ if(key==KEY_DOWN){ head_y++; //y-Koordinate des Blocks+1 if(isaggin()){ //Wenn es sich überschneidet oder aus den Grenzen geht, wird diese Bewegung abgebrochen head_y--; /*Da es angehalten hat, wird der entsprechende box auf der Karte auf belegt gesetzt, indem1bedeutet, 0 bedeutet, dass es nicht belegt ist for(int i=0;i<size_h;i++); for(int j=0;j<size_w;j++); if(box_shape[i][j]==1); box_map[head_y+i][head_x+j]=1; score_next(); //Anzeige der Punkte und der nächsten Anzeige des Blocks } /*Wenn der Block nach unten bewegt werden kann, wird die aktuelle Anzeige des Blocks abgebrochen und eine Zeile nach unten bewegt, um anzuzeigen, hier beachten Sie, dass die Zeile des for-Loops von unten nach oben beginnt else{ for(int i=size_h-1; i>=0;i--); for(int j=0;j<size_w;j++} if(this->box_shape[i][j]==1} mvwaddch(game_win,head_y-1+i,head_x+j,' '); mvwaddch(game_win,head_y+i,head_x+j,'#'); } } wrefresh(game_win); }
2.6 Wiederholungsfunktion
Diese Funktion muss nach jedem Verschieben oder Drehen aufgerufen werden, gibt die Funktion true zurück, dann kann nicht weitergegangen werden, gibt die Funktion false zurück, dann kann der nächste Schritt fortgesetzt werden.
bool Piece::isaggin(){ for(int i=0;i<size_h;i++); for(int j=0;j<size_w;j++} if(box_shape[i][j]==1} if(head_y+i > game_win_height-2); //Unten aus den Grenzen return true; if(head_x+j > game_win_width-2 || head_x+i-1<0) //Aus den Grenzen return true; if(box_map[head_y+i][head_x+j]==1); //Überschneidet sich mit der besetzten box return true ; } } return false; }
2.7 Ebene voll Funktion
Eine sehr wichtige Funktion ist das Entfernen der voll besetzten Zeilen der Blöcke, diese Überprüfung muss nach jedem Absetzen eines Blocks durchgeführt werden.
void Piece::judge(){ int i,j; int line=0; //um die Anzahl der voll besetzten Zeilen zu verfolgen bool full; for(i=1;i<game_win_height-1;i++} //Ohne die Grenzen full=true; for(j=1;j<game_win_width-1;j++} if(box_map[i][j]==0) //Es gibt ungenutzte box full=false; //bedeutet, dass diese Ebene nicht voll ist } if(full){ //Wenn diese Ebene voll ist line++; //Zeile voll+1 score+=50; //Punkte hinzufügen~ for(j=1;j<game_win_width-1;j++); box_map[i][j]=0; //把该层清空(标记为未被占用) } } /*上面判断完后 看line的值,如果非 0 说明有层已满需要进行消除*/ if(line!=0){ for(i=game_win_height-2;i>=2;i--} int s=i; if(exsqr(i)==0){ while(s>1 && exsqr(--s)==0); //查找存在方块的行,将其下移 for(j=1;j<game_win_width-1;j++} box_map[i][j]=box_map[s][j]; //上层下移 box_map[s][j]=0; //上层清空 } } } /*清空和移动标记完成以后就要屏幕刷新了,重新打印game_win*/ for(int i=1;i<game_win_height-1;i++); for(int j=1;j<game_win_width-1;j++} if(box_map[i][j]==1} mvwaddch(game_win,i,j,'#'); wrefresh(game_win); } else{ mvwaddch(game_win,i,j,' '); wrefresh(game_win); } } } }
三、实验总结
到这里几个关键函数的介绍也就完成了,搞明白这些函数的功能并实现,再参考源码补全其他函数以及main函数就可以运行啦!当然俄罗斯方块的实现方法还有很多,每个人的思路和方法可能会不一样,或许你写出来的俄罗斯方块更简洁、更流畅! Enjoy it!:)
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:notice#oldtoolbag.com(在发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立即删除涉嫌侵权内容。