用TC2.0寫俄羅斯方塊---cnasm

Tc2.0編寫俄羅斯方塊遊戲


  很多編程愛好者都編寫過俄羅斯方塊的遊戲程序。很久以前,我用Tc2.0也做過一個;最近有好些朋友看見我以前的俄羅斯方塊的程序後,
問我是怎麼做的。我一直想把這個程序的整個過程寫一份詳細的東西,與各位編程愛好者分享,一直沒空。正好現在放假了,而且離回家還有幾天。於是我就把這個程序重新寫了一遍,儘量使程序的結構比較清晰好懂一些。同時寫了下面的這份東西。

  俄羅斯方塊遊戲的程序中用到了一些方法。爲了比較容易理解這些方法,我在講述的同時寫了些專門針對這些方法的示例程序。這些示例程序力求短小,目的是用最小的代碼能夠清楚的示例所用的方法。這些示例程序都經過tc2.0測試。最後還附了完整的俄羅斯方塊遊戲的源代碼,和最終的可執行程序。如果你看了這份東東,有什麼意見和想法,請發電子郵件告訴我。我將會繼續更新這分東東,最新的版本可以在我的個人主頁上下載。

  下面的問題是有關俄羅斯方塊程序的,其中有些是朋友問我的,有些是我認爲可能會被問到的。我儘量按問題從易到難排列這些問題。 關於俄羅斯方塊程序的一些問題:
******************************************************
Tc2.0中怎麼樣設置圖形顯示?
Tc2.0中常用圖形函數的用法?
怎樣獲取鍵盤輸入?
怎樣控制方塊的移動?
怎樣控制時間間隔(用於遊戲中控制形狀的下落)?
遊戲中的各種形狀及整個遊戲空間怎麼用數據表示?
遊戲中怎麼判斷左右及向下移動的可能性?
遊戲中怎麼判斷某一形狀旋轉的可能性?
按向下方向鍵時加速某一形狀下落速度的處理?
怎麼判斷某一形狀已經到底?
怎麼判斷某一已經被填滿?
怎麼消去已經被填滿的一行?
怎麼消去某一形狀落到底後能夠消去的所有的行?(如長條最多可以消去四行)
怎樣修改遊戲板的狀態?
怎樣統計分數?
怎樣處理升級後的加速問題?
怎樣判斷遊戲結束?
關於計分板設計的問題。
關於“下一個”形狀取法的問題。
剩下的問題。

******************************************************
新的問題:
 我想有一個最高記錄的顯示,應該怎麼做呀?
 我想實現一個進度存儲功能,應該怎麼做呀?



Tc2.0中怎麼樣設置圖形顯示?

  Tc2.0中有兩種顯示模式,一種是我們所熟知的字符模式,另一種是圖形模式。在字符模式下只能顯式字符,如ASCII字符。一般是顯示25
行,每行80個字符。程序缺省的是字符模式。在字符模式下不能顯式圖形和進行繪圖操作。要想進行圖形顯示和繪圖操作,必須切換到圖形模
式下。

  Tc2.0中用initgraph()函數可以切換到圖形模式,用closegraph()可以從圖形模式切換回字符模式。initgraph()和closegraph()都是圖形
函數,使用圖形函數必須包括頭文件"graphics.h"。

  void far initgraph(int far *graphdriver,int far *graphmode,char far *pathtodriver);graphdriver是上漲指向圖形驅動序號變量的指針;graphmode是在graphdriver選定後,指向圖形顯示模式序號變量的指針。pathtodriver表示存放圖形驅動文件的路徑。

  Tc2.0中有多種圖形驅動,每種圖形驅動下又有幾種圖形顯示模式。在我的程序中圖形驅動序號爲VGA,圖形顯示模式序號爲VGAHI。這是一種分辨率爲640*480(從左到右座標依次爲0-639,從上到下座標依次爲0-479),能夠顯示16種顏色的圖形模式。別的圖形驅動序號和圖形顯示模式序號,可以從手冊或聯機幫助中找到。

  pathtodriver指示存放圖形驅動文件的路徑。圖形驅動序號不同,圖形驅動文件也不同。序號爲VGA圖形驅動對應"egavga.bgi"這個圖形驅動文件。"egavga.bgi"一般在Tc目錄下。

void far closegraph(void);
  沒有參數,從圖形模式直接返回字符模式。

initgraph()和closegraph()的常用用法如下:
int gdriver = VGA, gmode=VGAHI, errorcode;

/* initialize graphics mode */
initgraph(&gdriver, &gmode, "e://tc2");

/* read result of initialization */
errorcode = graphresult();

if (errorcode != grOk) /* an error occurred */
{
printf("Graphics error: %s/n", grapherrormsg(errorcode));
printf("Press any key to halt:");
getch();
exit(1); /* return with error code */
}

/* return to text mode */
closegraph();


Tc2.0中常用圖形函數的用法?

在這裏講幾個遊戲中用到的繪圖用的圖形函數:
setcolor();
line();
rectangle();
settextjustify();
outtextxy();
setfillstyle();
bar();

void far setcolor(int color);
  設置畫線、畫框和在圖形模式下顯示文字的當前顏色。這個函數將影響line()、rectangle()和outtextxy()函數繪圖的顏色。
color可以取常的顏色常量:
BLACK ? 0
BLUE ? 1
GREEN ? 2
CYAN ? 3
RED ? 4
MAGENTA ? 5
BROWN ? 6
LIGHTGRAY ? 7
DARKGRAY ? 8
LIGHTBLUE ? 9
LIGHTGREEN ?10
LIGHTCYAN ?11
LIGHTRED ?12
LIGHTMAGENTA ?13
YELLOW ?14
WHITE ?15

void far line(int x1,int y1,int x2,int y2);
用當前顏色從(x1,y1)畫一條到(x2,y2)的線段。

void far rectangle(int left,int top,int right,int bottom);
用當前顏色畫一個左上角爲(left,top)、右下角爲(right,bottom)的矩形框。

void far settextjustify(int horz,int vert);
設置圖形模式下文字輸出的對齊方式。主要影響outtextxy()函數。
horiz和vert可取如下枚舉常量:
horiz ?LEFT_TEXT ? 0 ?Left-justify text
?CENTER_TEXT ? 1 ?Center text
?RIGHT_TEXT ? 2 ?Right-justify text
vert ?BOTTOM_TEXT ? 0 ?Justify from bottom
?CENTER_TEXT ? 1 ?Center text
?TOP_TEXT ? 2 ?Justify from top

void far outtextxy(int x,int y,char * textstring);
在(x,y)處用當前字體(缺省的字體是DEFAULT_FONT)顯示字符串textstring,字符串的對齊方式由settextjustify()指定。

void far setfillstyle(int pattern,int color);
設置圖形的填充模式和填充顏色,主要影響bar()等函數。
pattern一般取枚舉常量值SOLID_FILL,color的取值與setcolor(int color)中color的取值範圍相同。

  介紹完了前面兩個問題,現在來寫一個程序。這個程序演示前了面所介紹的幾個圖形函數。
程序prog1.c


怎樣獲取鍵盤輸入?

  在Tc2.0中有一個處理鍵盤輸入的函數bioskey();
int bioskey(int cmd);
  當cmd爲1時,bioskey()檢測是否有鍵按下。沒有鍵按下時返回0;有鍵按下時返回按鍵碼(任何按鍵碼都不爲0),但此時並不將檢測到的按
鍵碼從鍵盤緩衝隊列中清除。
  當cmd爲0時,bioskey()返回鍵盤緩衝隊列中的按鍵碼,並將此按鍵碼從鍵盤緩衝隊列中清除。如果鍵盤緩衝隊列爲空,則一直等到有鍵按
下,纔將得到的按鍵碼返回。

  Escape鍵的按鍵碼爲0x11b,下面的小程序可以獲取按鍵的按鍵碼。

for (;;)
{
key=bioskey(0); /* wait for a keystroke */
printf("0x%x/n",key);
if (key==0x11b) break; /* Escape */
}

常用按鍵的按鍵碼如下:

#define VK_LEFT 0x4b00
#define VK_RIGHT 0x4d00
#define VK_DOWN 0x5000
#define VK_UP 0x4800
#define VK_HOME 0x4700
#define VK_END 0x4f00
#define VK_SPACE 0x3920
#define VK_ESC 0x011b
#define VK_ENTER 0x1c0d


  完整的程序請參見prog2.c、prog3.c。
prog2.c獲取按鍵的按鍵碼,按Escape鍵退出程序。
prog3.c根據不同的按鍵進行不同的操作,按Escape鍵退出程序。


怎樣控制方塊的移動?
  方塊移動的實現很簡單,將方塊原來的位置用背景色畫一個同樣大小的方塊,將原來的方塊塗去。然後在新的位置上重新繪製方塊就可以
了。這樣就實現了方塊的移動。完整的程序請參見prog4.c。這個用方向鍵控制一個黃色的小方塊在屏幕上上、下、左、右移動。這個程序用到了前面幾個問題講的內容,如果你有點忘了,還要回頭看看哦。:)


怎樣控制時間間隔(用於遊戲中控制形狀的下落)?
  解決這個問題要用到時鐘中斷。時鐘中斷大約每秒鐘發生18.2次。截獲正常的時鐘中斷後,在處理完正常的時鐘中斷後,將一個計時變量
加1。這樣,每秒鐘計時變量約增加18。需要控控制時間的時候,只需要看這個計時變量就行了。


  截獲時鐘中斷要用到函數getvect()和setvect()。
兩個函數的聲明如下:
?void interrupt (*getvect(int interruptno))();
?void setvect(int interruptno, void interrupt (*isr) ( ));

  保留字interrupt指示函數是一箇中斷處理函數。在調用中斷處理函數的時候,所有的寄存器將會被保存。中斷處理函數的返回時的指令是iret,而不是一般函數用到的ret指令。

getvect()根據中斷號interruptno獲取中斷號爲interruptno的中斷處理函數的入口地址。
setvect()將中斷號爲interruptno的中斷處理函數的入口地址改爲isr()函數的入口地址。即中斷髮生時,將調用isr()函數。


  在程序開始的時候截獲時鐘中斷,並設置新的中斷處理。在程序結束的時候,一定要記着恢復時鐘中斷哦,不然系統的計時功能會出問題
的。具體演示程序請參見prog5.c。由於中斷處理大家可能用的不多,所以我把prog5.c這個程序完整地貼在下面,並加上詳細的解釋。

/* prog5.c */
This is an interrupt service routine. You can NOT compile this
program with Test Stack Overflow turned on and get an executable
file which will operate correctly. */

/* 這個程序每隔1秒鐘輸出一個整數,10秒鐘後結束程序。
按escape鍵提前退出程序 。*/

#include <stdio.h>
#include <dos.h>
#include <conio.h>

/* Escape key */
#define VK_ESC 0x11b

#define TIMER 0x1c /* 時鐘中斷的中斷號 */

/* 中斷處理函數在C和C++中的表示略有不同。
如果定義了_cplusplus則表示在C++環境下,否則是在C環境下。 */

#ifdef __cplusplus
#define __CPPARGS ...
#else
#define __CPPARGS
#endif

int TimerCounter=0; /* 計時變量,每秒鐘增加18。 */

/* 指向原來時鐘中斷處理過程入口的中斷處理函數指針(句柄) */
void interrupt ( *oldhandler)(__CPPARGS);

/* 新的時鐘中斷處理函數 */
void interrupt newhandler(__CPPARGS)
{
/* increase the global counter */
TimerCounter++;

/* call the old routine */
oldhandler();
}

/* 設置新的時鐘中斷處理過程 */
void SetTimer(void interrupt (*IntProc)(__CPPARGS))
{
oldhandler=getvect(TIMER);
disable(); /* 設置新的時鐘中斷處理過程時,禁止所有中斷 */
setvect(TIMER,IntProc);
enable(); /* 開啓中斷 */
}

/* 恢復原有的時鐘中斷處理過程 */
void KillTimer()
{
disable();
setvect(TIMER,oldhandler);
enable();
}


void main(void)
{
int key,time=0;

SetTimer(newhandler); /* 修改時鐘中斷 */

for (;;)
{
if (bioskey(1))
{
key=bioskey(0);
if (key==VK_ESC) /* 按escape鍵提前退出程序 */
{
printf("User cancel!/n");
break;
}
}
if (TimerCounter>18) /* 1秒鐘處理一次 */
{
/* 恢復計時變量 */
TimerCounter=0;
time++;
printf("%d/n",time);
if (time==10) /* 10秒鐘後結束程序 */
{
printf("Program terminated normally!/n");
break;
}
}
}
KillTimer(); /* 恢復時鐘中斷 */

}


遊戲中的各種形狀及整個遊戲空間怎麼用數據表示?

以後我提到的形狀都是指下面七種形之一及它們旋轉後的變形體。

□□□□ □□□□ □□□□ □□□□
□■□□ □■■□ □□□□ □□□□
□■□□ □■□□ □■□□ □■■□
□■■□ □■□□ ■■■□ ■■□□

□□□□ □■□□ □□□□
□□□□ □■□□ □□□□
■■□□ □■□□ □■■□
□■■□ □■□□ □■■□

我定義了一個結構來表示形狀。
struct shape
{
int xy[8];
int color;
int next;
}
-1 0 1 2
-3□□□□
-2□□□□
-1□□□□
0□■□□

  所有的各種形狀都可以放在4x4的格子裏。假定第二列,第四行的格子座標爲(0,0)(如上圖中黑塊所示),則每個形狀的四個方塊都可以用4
個數對來表示。座標x從左向右依次增加,y從上到下依次增加。表示的時候,組成該形狀的四個方塊從左到右,從上到下(不一定非要按這個順
序)。如上面七種形狀的第一個用數對來表示就是(-2,0)、(-1,0)、(0,0)、(1,0)。結構shape中的xy就是用來表示這4個數對的。爲了簡化程序,用一維數組xy[8]來表示。xy[0]、xy[1]表示第一個數對,xy[2]、xy[3]表示第二個數對,依次類推。
  shape中的color表示形狀的顏色,不同的形狀有不同的顏色。七種形狀及它們旋轉後的變形體一共有19種形狀,用一個全局數組表示。假定旋轉的方向是逆時針方向(順時針方向道理一樣)。shape中的next就表示當前形狀逆時針旋轉後的下一個形狀的序號。例如:第一種形狀及其旋
轉變形的形狀用結構表示如下。

□□□□ □□□□ □□□□ □□□□
□■□□ □□□□ □■■□ □□□□
□■□□ □□■□ □□■□ ■■■□
□■■□ ■■■□ □□■□ ■□□□

struct shape shapes[19]=
{
/*{x1,y1,x2,y2,x3,y3,x4,y4, color, next}*/
{ 0,-2, 0,-1, 0, 0, 1, 0, CYAN, 1}, /* */
{-1, 0, 0, 0, 1,-1, 1, 0, CYAN, 2}, /* # */
{ 0,-2, 1,-2, 1,-1, 1, 0, CYAN, 3}, /* # */
{-1,-1,-1, 0, 0,-1, 1,-1, CYAN, 0}, /* ## */

……

}

  遊戲空間指的是整個遊戲主要的界面(呵呵,這個定義我實在想不出更準確的,還請哪位大蝦指點)。實際上是一個寬10格子、高20格子的
遊戲板。用一個全局數組board[12][22]表示。表示的時候:board[x][y]爲1時表示遊戲板上(x,y)這個位置上已經有方塊佔着了,board[x][y]
爲0表示遊戲板上這位置還空着。爲了便於判斷形狀的移動是否到邊、到底,初始的時候在遊戲板的兩邊各加一列,在遊戲板的下面加一行,全
部填上1,表示不能移出界。即board[0][y],board[11][y](其中y從0到21)初始都爲1,board[x][21](其中x從1到10)初始都爲1。
1 2 3 4 5 6 7 8 910
1□□□□□□□□□□
2□□□□□□□□□□
3□□□□□□□□□□
4□□□□□□□□□□
5□□□□□□□□□□
6□□□□□□□□□□
7□□□□□□□□□□
8□□□□□□□□□□
9□□□□□□□□□□
10□□□□□□□□□□
11□□□□□□□□□□
12□□□□□□□□□□
13□□□□□□□□□□
14□□□□□□□□□□
15□□□□□□□□□□
16□□□□□□□□□□
17□□□□□□□□□□
18□□□□□□□□□□
19□□□□□□□□□□
20□□□□□□□□□□

  prog6.c演示了用結構表示各種形狀的方法。雖然程序稍長一些,但並不是特別複雜。其中游戲板初始化部分並沒有真正用到,但是後面的程
序會用到的。其中SIZE定義爲16,這樣將整個屏幕的座標系由原來的640×480轉換成40×30(640/16=40,480/16=30)。遊戲中所有的座標都是基於40×30的座標系的,這樣有助於簡化程序。座標的轉換在程序中由DrawBlock(int x,int y)來體現。

  新的座標系如下圖所示:
-8-7-6-5-4-3-2-1 0 1 2 3 4 5 6 7 8 9101112131415161718192021222324252627282930-4□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
-3□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
-2□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
-1□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
0□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
1□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□
2□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□
3□□□□□□□□□■■■■■■■■■■□□□■■■■□□□□□□□□□□□□□
4□□□□□□□□□■■■■■■■■■■□□□■■■■□□□□□□□□□□□□□
5□□□□□□□□□■■■■■■■■■■□□□■■■■□□□□□□□□□□□□□
6□□□□□□□□□■■■■■■■■■■□□□■■■■□□□□□□□□□□□□□
7□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□
8□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□
9□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□
10□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□
11□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□
12□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□
13□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□
14□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□
15□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□
16□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□
17□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□
18□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□
19□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□
20□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□
21□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
22□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
23□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
24□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
25□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
26□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□

  新座標中最主要的是就是上面兩塊黑色的部分。左邊那塊大的就是遊戲板(橫座標從1到10,縱座標從1到20),右邊那塊小的就是顯示“下一個”形狀的部分(橫座標從14到17,縱座標從3到6)。這個新的座標系是整個遊戲的基礎,後面所有的移動、變形等的計算都是基於這個座標系的。


遊戲中怎麼判斷左右及向下移動的可能性?

  看懂了前面的各種形狀和遊戲板等的表示,接下來的東西就都好辦多了。先來看一下某個形狀如何顯示在遊戲板當中。假設要在遊戲板中
顯示第一個形狀。第一個形狀在結構中的表示如下:

struct shape shapes[19]=
{
/*{x1,y1,x2,y2,x3,y3,x4,y4, color, next}*/
{ 0,-2, 0,-1, 0, 0, 1, 0, CYAN, 1},

……

}

  那麼這個組成形狀四個方塊的座標表示爲(0,-2)、(0,-1)、(0,0)和(1,0)。這實際上是相對座標。假形狀的實際座標指的是4x4方塊中的第
二列、第三行的方塊的位置,設這個位置爲(x,y)。那麼組成這個形狀的四個小方塊的實際座標(以第一個形狀爲例)就是(x+0,y-2)、(x+0,y-1)、(x+0,y+0)和(x+1,y+0)。由於所有的形狀都可以在4x4的方塊陣列中表示,這樣就找到了一種統一的方法來表示所有的形狀了。

-1 0 1 2
-3□□□□ 相對座標
-2□■□□
-1□■□□ 組成第一種形狀的四個方塊的相對座標爲(0,-2)、(0,-1)、(0,0)和(1,0)。
0□■■□

讓我們看看形狀是如何顯示在遊戲板中的(以第一個形狀爲例)。

1 2 3 4 5 6 7 8 910
1□■□□□□□□□□ 形狀的座標爲(2,3)。組成形狀的四個方塊的座標由形狀的
2□■□□□□□□□□ 座標加上這四個小方塊各自的相對座標得出。它們分別是:
3□■■□□□□□□□ (2+0,3-2)、(2+0,3-1)、(2+0,3-0)和(2+1,3-0)。即:
4□□□□□□□□□□ (2,1)、(2,2)、(2,3)和(3,3)。如左圖所示。
5□□□□□□□□□□
6□□□□□□□□□□
7■□□□□□□□□□ 形狀的座標爲(1,9)。組成形狀的四個方塊的座標分別是:
8■□□□□□□□□□ (1+0,9-2)、(1+0,9-1)、(1+0,9-0)和(1+1,9-0)。即:
9■■□□□□□□□□ (1,7)、(1,8)、(1,9)和(2,9)。如左圖所示。
10□□□□□□□□□□
11□□□□□□□□□□
12□□□□□□□□□□
13□□□□□□□□□□
14□□□□□□□□□□
15□□□□□□□□□□
16□□□□□□□□□□
17□□□□□□□□□□
18□□□□□□□□■□ 形狀的座標爲(9,20)。組成形狀的四個方塊的座標分別是:
19□□□□□□□□■□ (9+0,20-2)、(9+0,20-1)、(9+0,20-0)和(9+1,20-0)。即:
20□□□□□□□□■■ (9,18)、(9,19)、(9,20)和(10,20)。如左圖所示。

  從現在起,我不再舉別的示例程序了。從現在開始所有的示例代碼均來自於我寫的"Russia.c"。爲了記錄遊戲板的狀態,用了一個全局數組board[12][22]。board[x][y](其中x從0到11,y從1到21)等於1表示(x,y)這個位置已經被填充了,組成形狀的四個方塊的座標都不能爲(x,y),否則將發生衝突。board[x][y](其中x從1到10,y從1到20)等於表示(x,y)這個位置還沒有被填充。

  遊戲板初始化時,給board[0][y],board[11][y](其中y從1到21)都賦爲1,給board[x][21](其中x從1到10)都賦爲1。這相當於一開始就給遊戲板左右和下方加了個“邊”。所有的形狀都不能夠移入這個“邊”,否則將發生衝突。

  現在我們可以開始討論如何判斷一個形狀向左、向右和向下移動的可能性了。先說個概念,“當前形狀”是指那個正在下落還沒有落到底的那個形狀。如果當前形狀向左移動,不與遊戲板現有狀態發生衝突,則可以向左移動。具體做法是:先假設當前形狀已經向左移動了,判斷此時是否與遊戲板現有狀態發生衝突。如果不發生衝突,則可以向左移動。否則,不可以向左移動。

  判斷索引號爲ShapeIndex的形狀在座標(x,y)是否與遊戲板當前狀態發生衝突的代碼如下。我把詳細的說明加在這段代碼中。

enum bool Confilict(int ShapeIndex,int x,int y)
{
int i;

/* 對組成索引號爲ShapeIndex的形狀的四個方塊依次判斷 */
for (i=0;i<=7;i++,i++) /* i分別取0,2,4,6 */
{
/* 如果四個方塊中有任何一個方塊的x座標小於1或大於10,表示超出左邊界或右邊界。
此時,發生衝突。 */
if (shapes[ShapeIndex].xy+x<1 ||
shapes[ShapeIndex].xy+x>10) return True;

/* 如果四個方塊中某個方塊的y座標小於1,表示整個形狀還沒有完全落入遊戲板中。
此時,沒有必要對這個方塊進行判斷。*/
if (shapes[ShapeIndex].xy[i+1]+y<1) continue;

/* 如果四個方塊中有任何一個方塊與遊戲板當前狀態發生衝突,則整個形狀在(x,y)處
與遊戲板當前狀態衝突 */
if (board[shapes[ShapeIndex].xy+x][shapes[ShapeIndex].xy[i+1]+y])
return True;
}

/* 四個方塊中沒有任何一個方塊與遊戲板當前狀態發生衝突,則整個形狀在(x,y)處
沒有與遊戲板當前狀態衝突 */
return False;
}

對以上代碼附加說明如下:
  shapes[ShapeIndex].xy(其中i等於0,2,4,6)表示組成索引號爲ShapeIndex的形狀的某個方塊的x相對座標。(i等於0時,表示第1個方塊的x相對座標;i等於2時,表示第2個方塊的x相對座標;i等於4時,表示第3個方塊的x相對座標;i等於6時,表示第4個方塊的x相對座標。)

  shapes[ShapeIndex].xy(其中i等於1,3,5,7)表示組成索引號爲ShapeIndex的形狀的某個方塊的y相對座標。(i等於1時,表示第1個方塊的y相對座標;i等於3時,表示第2個方塊的y相對座標;i等於5時,表示第3個方塊的y相對座標;i等於7時,表示第4個方塊的y相對座標。)

  shapes[ShapeIndex].xy+x(其中i等於0,2,4,6)表示索引號爲ShapeIndex的形狀的座標爲(x,y)時,組成該形狀的某個方塊的x實際座標。(i等於0時,表示第1個方塊的x實際座標;i等於2時,表示第2個方塊的x實際座標;i等於4時,表示第3個方塊的x實際座標;i等於6時,表示第4個方塊的x實際座標。)

  shapes[ShapeIndex].xy+y(其中i等於1,3,5,7)表示索引號爲ShapeIndex的形狀的座標爲(x,y)時,組成該形狀的某個方塊的y實際坐
標。(i等於1時,表示第1個方塊的y實際座標;i等於3時,表示第2個方塊的y實際座標;i等於5時,表示第3個方塊的y實際座標;i等於7時,表示第4個方塊的y實際座標。)

現在來看看這句是什麼意思吧。
board[shapes[ShapeIndex].xy+x][shapes[ShapeIndex].xy[i+1]+y]

可以這樣理解,把上面一句分開來看::

ActualX=shapes[ShapeIndex].xy+x;/* 其中x爲0,2,4,6 */
表示某個方塊實際的x座標。

ActualY=[shapes[ShapeIndex].xy[i+1]+y;
表示某個方塊實際的y座標。

board[ActualX][ActualY]就是與某個方塊座標相同處的遊戲板的標誌。如果此標誌不爲0(爲1),表示這個方塊與遊戲板發生衝突。如果此標誌爲0,表示這個方塊沒有與遊戲板發生衝突。

這段寫的比較長,但是不是特別難理解。遊戲中很多地方都用到了這種相對座標向實際座標的轉換方式,看懂了這一段對理解其他部分的代碼很有幫助。


仔細看過這段代碼後,你可能會提一個問題:不是已經在遊戲板的左右兩邊都加了“邊”了嗎,爲什麼還要加下面這個對x座標的判斷呢?

/* 如果四個方塊中有任何一個方塊的x座標小於1或大於10,表示超出左邊界或右邊界。
此時,發生衝突。 */
if (shapes[ShapeIndex].xy+x<1 ||
shapes[ShapeIndex].xy+x>10) return True;

這是因爲有一種特殊情況,如下圖所示:

■■
■ 2 3 4 5 6 7 8 910
1■□□□□□□□□□ 這在當前形狀剛出來的時候,是可能發生的。但是我們只給遊戲板
2□□□□□□□□□□ 加了一層“邊”。對於這個形狀的最左邊的那個方塊將失去判斷,
3□□□□□□□□□□ 如果不予理會,這個形狀將會“掛”在遊戲板的左上角!當初我也
4□□□□□□□□□□ 沒有想到這一點,後來發現會有形狀“掛”在最頂層,而導致遊戲
5□□□□□□□□□□ 提前退出。發現了這個問題。
6□□□□□□□□□□
7□□□□□□□□□□
8□□□□□□□□□□ 加了這個判斷後,遊戲板的左右兩個“邊”對衝突的判斷就是去意
9□□□□□□□□□□ 義了。因爲沒有這兩個“邊”,對於衝突的判斷也不會出錯。不過
10□□□□□□□□□□ 爲了程序易於理解,還是保留了遊戲板的左右兩個“邊”。
11□□□□□□□□□□
12□□□□□□□□□□
13□□□□□□□□□□
14□□□□□□□□□□
15□□□□□□□□□□
16□□□□□□□□□□
17□□□□□□□□□□
18□□□□□□□□□□
19□□□□□□□□□□
20□□□□□□□□□□

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章