在Windows控制台下使用C語言實現俄羅斯方塊
在Windows的控制台下使用C語言實現俄羅斯方塊
這是CSAPP課程的作業,助教問了我情況之後就愉快的決定了
稍微思考了一下,因為是經典的遊戲程序,本身邏輯並不是很複雜,而且肯定也有很多類似的程序了。這裡僅僅提供自己的思路。
簡單思路
俄羅斯方塊本質上來講,是一個在二維地圖上進行操作的遊戲,自然我們需要一張二維圖來表示這個遊戲地圖。然而我們使用的是C語言。。。。好吧我們定義一個int二維數組的全局變量作為這張地圖的表示。
二維地圖也要遵守基本法,自然我們需要給這張圖上每個點做一個定義。當這個點為0的時候,表示這個地方沒有方塊。這個點為1的時候,表示這個點存在一個已經落地的方塊。當這個點大於1的時候,表示這是一個可以活動的點。至於這個可以活動的點具體數值代表什麼,我們之後討論。
我們需要一個對方塊進行操作的函數。這個函數用於處理可活動方塊,包括左右移動和向下活動。如果按下↑鍵,則循環調用向下運動直到觸底。
當進行方塊操作時,我們需要一些判定。比如方塊是否觸底,觸底之後方塊是否足夠一行(一整行就消去然後進行分數累加)等等操作。
我們需要一個對時間進行計數的計數器。為了處理這個時間,我開了一個線程用於統計。每當時間間隔超過1s,自動調用向下移動的函數。
之後是翻轉。這個是最難的操作。稍微將俄羅斯方塊的方塊類型分個類,其中2x2的方塊無需翻轉;正反L形方塊可以放在3x3的矩陣中翻轉,並且翻轉方向是90°x4次;正反閃電形方塊也可以放在3x3的矩陣中進行翻轉,翻轉方向是一次順時針,一次逆時針;“凸”形的方塊和L形類似,可以翻轉四次,並且在3x3的矩陣中判斷;長條形方塊由於有四個,可以單獨進行一次判斷,翻轉方向為順時針、逆時針各一次。具體可以參考後文“翻轉詳解”
文件構成
一個好的程序,就算代碼量不多,也應該具有良好的可讀性和可維護性。所以我將文件分為
- main.c
- action.c
- block.c
- renderer.c
- timer.c
main.c
這裡用作處理主線程,包括定義全局變量,處理控制台,開啟後台進程,程式啟動參數設置,隨機一個方塊類型,重置地圖,監聽鍵盤事件等等。
其中監聽鍵盤事件是通過conio.h中的getch()函數實現,並且使用while (1)進行持續監聽。捕捉鍵盤事件之後進行操作。
action.c
這裡主要處理地圖的一些操作。比如封裝了一個操作用來給地圖x,y坐標處賦值n:void set(int xn, int yn, int n)。方塊的移動函數就放在這裡面void moveAction(int d)。
block.c
這裡主要放置用於處理方塊的函數,比如新建方塊:newTetris();凍住方塊(把所有值大於1的坐標值設置成1):frozen()。
renderer.c
這裡用於存放繪製界面的函數。應用了雙重緩衝技術,可以有效的避免閃屏現象,也無需使用goto函數進行坐標定位。
雙重緩衝的原理是這樣的,先新建一個console窗體用作緩衝區,但是此時緩衝區不會顯示。待繪製完成之後,將緩衝區顯示在屏幕上,這樣就避免了print字符時的單行刷新感。
HANDLE hNewConsole = CreateConsoleScreenBuffer(GENERIC_WRITE | GENERIC_READ,
0,
NULL,
CONSOLE_TEXTMODE_BUFFER,
NULL);
CONSOLE_CURSOR_INFO cci = {1, 0};
SetConsoleCursorInfo(hNewConsole, &cci);
/* ......Other code...... */
WriteConsole(hNewConsole, *statusInfo, strlen(*statusInfo), NULL, NULL);
WriteConsole(hNewConsole, *scoreInfo, strlen(*scoreInfo), NULL, NULL);
WriteConsole(hNewConsole, scoreStr, strlen(scoreStr), NULL, NULL);
SetConsoleActiveScreenBuffer(hNewConsole);
renderer.c
這裡用於運行後台線程(計時用)。因為我只需要以秒為最小單位,所以我調用了函數time()來獲取UNIX時間戳。實際應用中可以調用stdlib.h庫中的clock(),最小單位是ms。
翻轉詳解
翻轉是一個讓人頭疼的事,我定義了幾種處理方法。除了2x2的方塊以外,其他方塊都包含一個中心塊。中心塊的值為3, 4, 5, 6。
當中心塊為3或者4的時候,對以此為中心塊的3x3矩陣進行一次順時針旋轉。旋轉之前會判斷一次是否會與已經固定的方塊碰撞或者撞墻(即無法翻轉)。如果無法翻轉,則不進行操作。中心塊為3和4的區別是,當中心塊為4的方塊進行翻轉后,將4改為5。
那麼就可以很容易知道了,中心塊為5的方塊是進行逆時針翻轉判定用的。
中心塊為6時,表示對長條形進行特殊的判斷,判斷能否翻轉,邏輯與上文類似。
以下是rotate()的源碼
void rotate() {
//If is lost, return
if (gameStatus == 2) {
return;
}
bool isFind = false;
//central x and y (1, 1)
int cenX, cenY;
//Find where is the central block
for (cenY=0; cenY<y; cenY++){
for (cenX=0; cenX<x; cenX++){
if (tetMap[cenY][cenX] > 2){
isFind = true;
break;
}
}
if (isFind){
break;
}
}
int tempMap[10][10];
int i, j;
switch (tetMap[cenY][cenX]){
/* ClockWice */
case 3:
case 4:
if (cenX == 0 || cenX == x-1){
break;
}
/* Generate a temp map which size is(3x3) */
for (j=cenY-1; j<cenY+2; j++){
for (i=cenX-1; i<cenX+2; i++){
tempMap[i-(cenX-1)][2+(cenY-1)-j] = tetMap[j][i];
}
}
/* Compare two maps and transport values */
for (j=cenY-1; j<cenY+2; j++){
for (i=cenX-1; i<cenX+2; i++){
if (tempMap[j-(cenY-1)][i-(cenX-1)] == 2 && tetMap[j][i] == 1){
return;
}
}
}
/* If can, rotate */
for (j=cenY-1; j<cenY+2; j++){
for (i=cenX-1; i<cenX+2; i++){
if (tetMap[j][i] == 2){
tetMap[j][i] = 0;
}
}
}
for (j=cenY-1; j<cenY+2; j++){
for (i=cenX-1; i<cenX+2; i++){
if (tempMap[j-(cenY-1)][i-(cenX-1)] == 2){
tetMap[j][i] = tempMap[j-(cenY-1)][i-(cenX-1)];
}
}
}
//If the central block is 4, set it to 5
if (tetMap[cenY][cenX] == 4){
tetMap[cenY][cenX] = 5;
}
break;
/* AntiClockWice */
case 5:
if (cenX == 0 || cenX == x-1){
break;
}
/* Generate a temp map which size is(3x3) */
for (j=cenY-1; j<cenY+2; j++){
for (i=cenX-1; i<cenX+2; i++){
tempMap[2+(cenX-1)-i][j-(cenY-1)] = tetMap[j][i];
}
}
/* Compare two maps and transport values */
for (j=cenY-1; j<cenY+2; j++){
for (i=cenX-1; i<cenX+2; i++){
if (tempMap[j-(cenY-1)][i-(cenX-1)] == 2 && tetMap[j][i] == 1){
return;
}
}
}
/* If can, rotate */
for (j=cenY-1; j<cenY+2; j++){
for (i=cenX-1; i<cenX+2; i++){
if (tetMap[j][i] == 2){
tetMap[j][i] = 0;
}
}
}
for (j=cenY-1; j<cenY+2; j++){
for (i=cenX-1; i<cenX+2; i++){
if (tempMap[j-(cenY-1)][i-(cenX-1)] == 2){
tetMap[j][i] = tempMap[j-(cenY-1)][i-(cenX-1)];
}
}
}
//If the central block is 5, set it to 4
if (tetMap[cenY][cenX] == 5){
tetMap[cenY][cenX] = 4;
}
break;
/* Resolve rectantangle */
case 6:
if (cenX == 0 || cenX > x-3 && tetMap[cenY-1][cenX] == 2){
break;
}
if (tetMap[cenY-1][cenX] == 2){
if (
tetMap[cenY][cenX-1]+
tetMap[cenY][cenX+1]+
tetMap[cenY][cenX+2] == 0
){
tetMap[cenY][cenX-1] = 2;
tetMap[cenY][cenX+1] = 2;
tetMap[cenY][cenX+2] = 2;
tetMap[cenY-1][cenX] = 0;
tetMap[cenY+1][cenX] = 0;
tetMap[cenY+2][cenX] = 0;
}
}
else{
if (
tetMap[cenY-1][cenX]+
tetMap[cenY+1][cenX]+
tetMap[cenY+2][cenX] == 0
){
tetMap[cenY][cenX-1] = 0;
tetMap[cenY][cenX+1] = 0;
tetMap[cenY][cenX+2] = 0;
tetMap[cenY-1][cenX] = 2;
tetMap[cenY+1][cenX] = 2;
tetMap[cenY+2][cenX] = 2;
}
}
/* No action */
default:
break;
}
}
源碼地址
這玩意兒已經在Github上開源,地址為https://github.com/HoshinoTouko/Tetris
- 上一篇: Debian / Ubuntu快速更换内核
- 下一篇: 2017微軟學生夏令營&微軟編程之美小記