使用C++一步步實現(xiàn)俄羅斯方塊后續(xù)
一、實驗簡介
1.1 實驗內容
本節(jié)實驗我們將實現(xiàn)俄羅斯方塊主要函數(shù)的設計,完成基本功能并運行。
1.2 實驗知識點
窗口的繪制
方塊類的設計
旋轉算法
移動、消除函數(shù)
1.3 實驗環(huán)境
xface 終端
g++ 編譯器
ncurses 庫
1.4 編譯程序
編譯命令要加上 -l 選項引入 ncurses 庫:
g++ main.c -l ncurses
1.5 運行程序
./a.out
1.6 運行結果

二、實驗步驟
2.1 頭文件
首先包含頭文件以及定義一個交換函數(shù)和隨機數(shù)函數(shù),后面用到(交換函數(shù)用來做方塊的旋轉,隨機數(shù)用來設置方塊的形狀)
#include <iostream>
#include <sys/time.h>
#include <sys/types.h>
#include <stdlib.h>
#include <ncurses.h>
#include <unistd.h>
/* 交換a和b */
void swap(int &a, int &b){
int t=a;
a = b;
b = t;
}
/* 得到一個(min,max)區(qū)間的隨機整數(shù)
int getrand(int min, int max)
{
return(min+rand()%(max-min+1));
}
2.2 定義類
由于程序內容相對簡單,這里只定義了一個 Piece 類
class Piece
{
public:
int score; //得分
int shape; //表示當前方塊的形狀
int next_shape; //表示下一個方塊的形狀
int head_x; //當前方塊首個box的位置,標記位置
int head_y;
int size_h; //當前方塊的size
int size_w;
int next_size_h; //下一個方塊的size
int next_size_w;
int box_shape[4][4]; //當前方塊的shpe數(shù)組 4x4
int next_box_shape[4][4]; //下一個方塊的shpe數(shù)組 4x4
int box_map[30][45]; //用來標記游戲框內的每個box
bool game_over; //游戲結束的標志
public:
void initial(); //初始化函數(shù)
void set_shape(int &cshape, int box_shape[][4],int &size_w, int & size_h); //設置方塊形狀
void score_next(); //顯示下一個方塊的形狀以及分數(shù)
void judge(); //判斷是否層滿
void move(); //移動函數(shù) 通過 ← → ↓ 控制
void rotate(); //旋轉函數(shù)
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數(shù)組初始化為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 旋轉函數(shù)
這里用了一個比較簡單的算法對方塊進行旋轉,類似于矩陣的旋轉,先將 shape 數(shù)組進行斜對角線對稱化,再進行左右對稱,便完成了旋轉,需要注意的是要判斷旋轉后方塊是否出界或重合,如果是,則取消本次旋轉。
void Piece::rotate()
{
int temp[4][4]={0}; //臨時變量
int temp_piece[4][4]={0}; //備份用的數(shù)組
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]; //備份一下當前的方塊,如果旋轉失敗則返回到當前的形狀
for(i=0;i<4;i++)
for(j=0;j<4;j++)
temp[j][i]=box_shape[i][j]; //斜對角線對稱
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]; //左右對稱
/*如果旋轉以后重合,則返回到備份的數(shù)組形狀*/
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; //記得size也要變回原來的size
size_h=tmp_size_h;
}
/*如果旋轉成功,那么在屏幕上進行顯示*/
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,' '); //移動到game_win窗口的某個坐標處打印字符
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 移動函數(shù)
如果玩家沒有按下任何按鍵,方塊需要慢速下落,所以我們不能夠因為等待按鍵輸入而阻塞在 getch() ,這里用到了 select() 來取消阻塞。
/* 這里只是截取了程序的一部分,具體實現(xiàn)請參考源碼 */ struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec= 500000; if (select(1, &set, NULL, NULL, &timeout) == 0)
timeout 就是我們最多等待按鍵的時間,這里設置了 500000us,超過這個時間就不再等待 getch() 的輸入,直接進行下一步。
如果在 timeout 時間內檢測到按鍵,則下面的 if 語句為真,得到輸入的 key 值,通過判斷不同的 key 值進行向左、右、下、旋轉等操作。
if (FD_ISSET(0, &set))
while ((key = getch()) == -1) ;
向左、右、下移動的函數(shù)處理方式基本相同,這里只拿向下移動的函數(shù)進行說明
/* 這里只是截取了程序的一部分,具體實現(xiàn)請參考源碼 */
/* 如果輸入的按鍵是 ↓ */
if(key==KEY_DOWN){
head_y++; //方塊的y坐標+1
if(isaggin()){ //如果重合或出界,則取消這次移動
head_y--;
/*既然停下來了,那么把地圖上對應的box設置為已被占用,用1表示,0表示未被占用
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(); //顯示分數(shù)以及提示下一個方塊
}
/*如果能夠向下移動,那么取消當前方塊的顯示,向下移動一行進行顯示,這里注意for循環(huán)的行要從下往上
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 重復函數(shù)
每次移動或旋轉之后要進行判斷的函數(shù),函數(shù)返回真則不能行動,返回假則可以進行下一步。
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) //下面出界
return true;
if(head_x+j > game_win_width-2 || head_x+i-1<0) //左右出界
return true;
if(box_map[head_y+i][head_x+j]==1) //與已占用的box重合
return true ;
}
}
return false;
}
2.7 層滿函數(shù)
最后一個很重要的功能是對方塊已滿的行進行消除,每當一個方塊向下移動停止后都需要進行判斷。
void Piece::judge(){
int i,j;
int line=0; //用來記錄層滿的行數(shù)
bool full;
for(i=1;i<game_win_height-1;i++){ //除去邊界
full=true;
for(j=1;j<game_win_width-1;j++){
if(box_map[i][j]==0) //存在未被占用的box
full=false; //說明本層未滿
}
if(full){ //如果該層滿
line++; //行滿+1
score+=50; //加分~
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);
}
}
}
}
三、實驗總結
到這里幾個關鍵函數(shù)的介紹也就完成了,搞明白這些函數(shù)的功能并實現(xiàn),再參考源碼補全其他函數(shù)以及main函數(shù)就可以運行啦!當然俄羅斯方塊的實現(xiàn)方法還有很多,每個人的思路和方法可能會不一樣,或許你寫出來的俄羅斯方塊更簡潔、更流暢! Enjoy it !:)
相關文章
C++?容器中map和unordered?map區(qū)別詳解
這篇文章主要為大家介紹了C++?容器中map和unordered?map區(qū)別示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11
使用C++開發(fā)一個串口讀寫軟件的實現(xiàn)步驟
這篇文章主要介紹了使用xmake(一個項目管理工具兼包管理工具)和asio2(一個asio的框架,可以實現(xiàn)輕松各種網絡應用,一般支持tcp,udp,http,websocket,rpc,ssl,icmp,serial_port.)來快速的開發(fā)個串口讀寫軟件(整合例程),需要的朋友可以參考下2025-04-04

