Hướng dẫn viết game Tetris (Xếp gạch) đơn giản (hướng dẫn bằng hình ảnh) - Phần 2

II/ Tổ chức chương trình,chọn cấu trúc cài đặt.
- Phần I chúng ta đã được nói sơ sơ qua ý tưởng,cấu trúc của game Tetris này rồi.Phần II tớ sẽ hướng dẫn cách viết code và tổ chức game.
1/ Các hàm bổ trợ
- Vì trong game mình có sử dụng các hàm như gotoxy(…), textcolor(…) ,delay(…) … mà trong VS 2k8 không có nên phải viết lại các hàm này.Các hàm này chỉ là phụ thôi nên chúng ta chép code về là được ,không cần phải hiểu các hàm này hoạt động ra sao cả.
Sau đây là code mẫu của các hàm này.


PHP Code:
enum{
    
BLACK,
    
BLUE,
    
GREEN,
    
CYAN,
    
RED,
    
PURPLE,
    
GRAY,
    
WHITE,
    
LIGHTGRAY,
    
LIGHTBLUE,
    
LIGHTGREEN,
    
LIGHTCYAN,
    
LIGHTRED,
    
LIGHTPURPLE,
    
LIGHTYELLOW,
    
LIGHTWHITE};void gotoxy(int xint y)
{
    
COORD c;
    
c.1;
    
c.1;
    
SetConsoleCursorPosition (GetStdHandle(STD_OUTPUT_HANDLE), c);
}
void clrscr()
{
    
COORD coord;
    
DWORD written;
    
CONSOLE_SCREEN_BUFFER_INFO info;

    
coord.0;
    
coord.0;
    
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
    
FillConsoleOutputCharacter (GetStdHandle(STD_OUTPUT_HANDLE), ' ',
        
info.dwSize.info.dwSize.Ycoord, &written);
    
gotoxy (11);
}
void textcolor(WORD color)
{
    
HANDLE hConsoleOutput;
    
hConsoleOutput GetStdHandle(STD_OUTPUT_HANDLE);

    
CONSOLE_SCREEN_BUFFER_INFO screen_buffer_info;
    
GetConsoleScreenBufferInfo(hConsoleOutput, &screen_buffer_info);

    
WORD wAttributes screen_buffer_info.wAttributes;
    
color &= 0x000f;
    
wAttributes &= 0xfff0;
    
wAttributes |= color;

    
SetConsoleTextAttribute(hConsoleOutputwAttributes);
}
void SetBGColor(WORD color)
{
    
HANDLE hConsoleOutput;
    
hConsoleOutput GetStdHandle(STD_OUTPUT_HANDLE);

    
CONSOLE_SCREEN_BUFFER_INFO screen_buffer_info;
    
GetConsoleScreenBufferInfo(hConsoleOutput, &screen_buffer_info);

    
WORD wAttributes screen_buffer_info.wAttributes;
    
color &= 0x000f;
    
color <<= 4;
    
wAttributes &= 0xff0f;
    
wAttributes |= color;

    
SetConsoleTextAttribute(hConsoleOutputwAttributes);
}
void delay(int x){ Sleep(x);}

void Nocursortype()
{
    
CONSOLE_CURSOR_INFO Info;
    
Info.bVisible FALSE;
    
Info.dwSize=20;
    
SetConsoleCursorInfo (GetStdHandle (STD_OUTPUT_HANDLE), &Info);
2/ Cấu trúc game
Mình sẽ chọn cách viết code game này dùng Structure.Mặc dù theo mình thấy dùng class sẽ dễ quản lí và nhìn trực quan hơn.Nhưng nhiều bạn chưa học hướng đối tượng (OOP) nên dùng Structure là hợp lí cho các bạn.Dùng structure nên 1 số chỗ mình tổ chức quản lí chưa tốt nên các ban thông cảm nha.Đừng chém em mà tội nghiệp.
Như mình đã nói ở phần I thì game sẽ được quản lí chung bằng 1 ma trận Board[22][10]. Do đó mình sẽ khai báo ma trận này là kiểu int ,và là biến toàn cục.
PHP Code:
  #define MaxI 22
       #define MaxJ 10
       
int Board[MaxI][MaxJ]; 
-Để xác định vị trí vẽ khung màn hình chơi game ( tương ứng với ma trận map) lên màn hình console.Lưu ý rằng như mình đã nói thì Board có kích thước [22][10] nhưng thật sự chỉ vẽ lên màn hình kích thước 18x10 –ta bỏ đi 4 hàng đầu trong ma trận Board đi(có index từ i =0 -->3).
Ta khai báo :
PHP Code:
#define LEFT 4
 #define TOP 4 
Để dễ hình dung thì bạn biết .Màn hình console có kích thước là 25 hàng x 80 cột .

Sau đây là hàm vẽ cái khung để hiển thị lên màn hình .
PHP Code:
void DrawBoard()
{
    
int i,j;
    for(
i=LEFT;i<=LEFT+10+1;i++)
        for(
j=TOP;j<=TOP+18+1;j++)
        {
            if((
j==TOP||j==TOP+18+1)&&i>LEFT&&i<LEFT+10+1)
            {
                
gotoxy(i,j);textcolor(7);cprintf("%c",205);
            }
            if((
i==LEFT||i==LEFT+10+1)&&j>TOP&&j<TOP+18+1)
            {
                
gotoxy(i,j);textcolor(7);cprintf("%c",186);
            }   
        }
        
gotoxy(LEFT,TOP);textcolor(LIGHTRED);cprintf("%c",219);
        
gotoxy(LEFT+10+1,TOP);textcolor(LIGHTRED);cprintf("%c",219);
        
gotoxy(LEFT,TOP+18+1);textcolor(LIGHTRED);cprintf("%c",219);
        
gotoxy(LEFT+10+1,TOP+18+1);textcolor(LIGHTRED);cprintf("%c",219);
       
Cái hàm này sẽ vẽ lên màn hình như thế này:


Hàm vẽ trạng thái ma trận của game.Nếu Board[i][j] =1 (nghĩa là có gạch ở đây ) và i phải >=4 thì vẽ kí tự có mã ASCII = 2 (hình mặt cười) lên màn hình .ngược lại thì không vẽ lên màn hình.
PHP Code:
void DisplayBoard()
{
    
int i,j;
    for(
i=0;i<MaxI;i++)
        for(
j=0;j<MaxJ;j++)
        {
            if(
Board[i][j]==1&&i>=4)
            {
                
gotoxy(j+LEFT+1,i+TOP+1-4);textcolor(15);cprintf("%c",2);
            }
            if(
Board[i][j]==0&&i>=4)
            {
                
textcolor(BLACK);gotoxy(j+LEFT+1,i+TOP+1-4);cprintf(" ");
            }
        }
Đại khái là nó sẽ hoạt động tương tự như ví dụ mô tả bên dưới.


-Khi xếp được 1 hàng đầy thì ta phải xóa hàng đó và cập nhật lại giá trị cho ma trân Board lại cho đúng.
PHP Code:
void CapNhatLaiToaDo(int hang)
{
    
int i,j;
    for(
i=hang;i>0;i--)
        for(
j=0;j<MaxJ;j++)
        {
            
Board[i][j]=Board[i-1][j];
        }



Structure quản lí khối gạch:

-Như đã nói ý tưởng ở trên,mình sẽ code hóa cấu trúc đó như sau:

PHP Code:
typedef struct {
    
int **arr;
    
int Row,Col;
    
int iBoard,jBoard;
}
KhoiGach
+arr là 1 con trỏ để mô tả Ma Trận Trạng Thái cho từng loại khối gạch: 4x1 ,2x2,2x3 . Còn 2 biến Row,Col thể hiện kích thước của ma trân trạng thái ( arr,Row,Col – sẽ cho biết hình dáng của khối gạch là hình j).Ma trận này dùng để kiểm tra va chạm khi di chuyển khối gạch,xoay khối gạch…
+iBoard,jBoard : cho biết vị trí hiện tại của Khối Gạch Đang nằm ở đâu trên ma trận Board[22][10] – chính là vị trí của arr[0][0] so với ma trận Board[22][10].Khi mới khởi tạo khối gạch thì iBoard,jBoard sẽ nằm ở khu vực không đc hiển thị.



-Mình đã phân tích ở phần I rằng mình sẽ dùng 1 con số để tượng trưng cho từng khối gạch.Khi biết số đại diện của nó thì ta chỉ cần dùng phép toán dịch bít để suy ra ma trận trạng thái của chúng.
+15: Thẳng đứng
+31: Hình vuông
…………………..


-Hàm khởi tạo thông số 1 khối gạch tương ứng với số ID của nó:
PHP Code:
KhoiGach *TaoKhoiGach(int ID)
{
    
KhoiGach *pkhoigach=(KhoiGach*)malloc(sizeof(KhoiGach));
    switch(
ID)
    {
        case 
15:
            
pkhoigach->Row=4;
            
pkhoigach->Col=1;
            
pkhoigach->iBoard=0;
            
pkhoigach->jBoard=5;
            break;
        case 
31:
            
pkhoigach->Row=pkhoigach->Col=2;
            
pkhoigach->iBoard=2;
            
pkhoigach->jBoard=5;
            break;
        default:
            
pkhoigach->Row=2;
            
pkhoigach->Col=3;
            
pkhoigach->iBoard=2;
            
pkhoigach->jBoard=5;
            break;
    }
    
pkhoigach->arr=(int**)malloc(pkhoigach->Row*sizeof(int*));
    for(
int i=0;i<pkhoigach->Row;i++)
    {
        
pkhoigach->arr[i]=(int*)malloc(pkhoigach->Col*sizeof(int));
    }
    for(
int k=0;k<pkhoigach->Col*pkhoigach->Row;k++)
    {
        
pkhoigach->arr[k/pkhoigach->Col][k%pkhoigach->Col]=(ID>>(pkhoigach->Col*pkhoigach->Row-1-k))&1;
    }
    return 
pkhoigach;
Đoạn :
pkhoigach->arr=(int**)malloc(pkhoigach->Row*sizeof(int*));
for(int i=0;i<pkhoigach->Row;i++)
{
pkhoigach->arr[i]=(int*)malloc(pkhoigach->Col*sizeof(int));
}

Chỉ là cấp phát bộ nhớ cho ma trận trạng thái thôi .Chắc hẳn các bạn sẽ thắc mắc rằng nếu có ID=58 thì sao có thể tạo ra ma trận trạng thái là:

1 1 1
0 1 0

Rất đơn giản thôi:
Ví dụ : với ID=58 thì khối gạch sẽ có ma trận trạng thái là 2x3 .Row=2,Col=3.
0 0 0
0 0 0
Ban đầu ma trận trạng thái arr:


Vì Row xCol=2x3=6 (bit) .Nên ta đổi 58 sang số nhị phân 6 dài bit là: ‘ 111010 ‘
Đoạn code:
for(int k=0;k<pkhoigach->Col*pkhoigach->Row;k++)
{
pkhoigach->arr[k/pkhoigach->Col][k%pkhoigach->Col]=(ID>>(pkhoigach->Col*pkhoigach->Row-1-k))&1;
}

+Toán tử >> là toán tử dịch bít sang phải.
+arr[k/Col][k%Col]=(ID dịch sang phải (RowxCol-1-k) bit ) & 1 // (phép AND trong tin học chắc ai cũng biết)
Với k=0: [arr[k/Col][k%Col]=arr[0][0].
58 dịch sang phải (6-1-0) bít= ‘111010’ dịch sang phải 5 bit là ‘000001’ .Sau đó kết hợp với 000001&1 thì kết quả sẽ là 1. ->Vậy arr[0][0]=1;
1 0 0
0 0 0



Với k=1: [arr[k/Col][k%Col]=arr[0][1].
58 dịch sang phải (6-1-1) bít= ‘111010’ dịch sang phải 4 bit là ‘000011’ .Sau đó kết hợp với 000011&1 thì kết quả sẽ là 1. ->Vậy arr[0][1]=1;
1 1 0
0 0 0


Với k=2: [arr[k/Col][k%Col]=arr[0][2].
58 dịch sang phải (6-1-2) bít= ‘111010’ dịch sang phải 3 bit là ‘000111’ .Sau đó kết hợp với 000111&1 thì kết quả sẽ là 1. ->Vậy arr[0][2]=1;
1 1 1
0 0 0



Với k=3: [arr[k/Col][k%Col]=arr[1][0].
58 dịch sang phải (6-1-3) bít= ‘111010’ dịch sang phải 2 bit là ‘001110’ .Sau đó kết hợp với (001110 )&1 thì kết quả sẽ là 0. ->Vậy arr[1][0]=0;
1 1 1
0 0 0



Với k=4: [arr[k/Col][k%Col]=arr[1][1].
58 dịch sang phải (6-1-4) bít= ‘111010’ dịch sang phải 1 bit là ‘011101’ .Sau đó kết hợp với (011101 )&1 thì kết quả sẽ là 1. ->Vậy arr[1][1]=1;
1 1 1
0 1 0




Với k=5: [arr[k/Col][k%Col]=arr[1][2].
58 dịch sang phải (6-1-5) bít= ‘111010’ dịch sang phải 0 bit là ‘111010’ .Sau đó kết hợp với (111010 )&1 thì kết quả sẽ là 0. ->Vậy arr[1][2]=0;
1 1 1
0 1 0



Vậy cuối cùng: ta được ma trận trạng thái dúng với con số ID=58. Với các khối gạch mang ID khác thì cách làm cũng tương tự thôi



Hàm kiểm tra va chạm khi di chuyển khối gạch sang trái ,sang phải,rơi xuống:
Ý tưởng:
+ Để xác định xem 1 ô ở vị trí i,j trên ma trận Board có di chuyển sang trái được hay không thì ta xem ô liền kề bên trái(chính là ô ở vị trí i , j-1) của nó có gạch hay chưa,nếu có gạch thì không di chuyển được( a[i][j]=1 thì có gạch và ngược lại a[i][j]=0 là không có gạch) .Di chuyển sang phải hay xét rơi xuống cũng tương tụ như thế thôi( cũng xét ô liền kề bên phải i,j+1 hoặc liền kề bên dưới i+1,j)
+Như vậy để xét nguyên khối gạch (gồm 4 ô gạch nhỏ ) có thể di chuyển được thì tất cả các ô gạch nhỏ fai di chuyển được ,nếu có 1 ô không di chuyển được thì khối gạch đó không thể di chuyển được.



PHP Code:
int Inside(int i,int j)  //Xem i,j có thuộc mảng Board[22][10] hay không?{
    return (
i>=0&&i<MaxI&&j>=0&&j<MaxJ);
}
int Left(int i,int j)
{
    if(
j>0&&Inside(i,j)&&Board[i][j-1]==0) return 1;
    return 
0;
}
int Right(int i,int j)
{
    if(
j<MaxJ-1&&Inside(i,j)&&Board[i][j+1]==0) return 1;
    return 
0;
}
int Down(int i,int j)
{
    if(
i<MaxI-1&&Inside(i,j)&&Board[i+1][j]==0) return 1;
    return 
0;
+Hàm trên mới chỉ xét 1 di chuyển cho 1 ô ( i,j) thôi. Sau đây sẽ là hàm xét di chuyển cho tất cả khối gạch.
Note:
+1 khối gạch chỉ di chuyển sang trái và phải khi không nằm trong khu vực “ không đc hiển thị” (nghĩa là iBoard>3) .
+ Ô i,j trên ma trận trạng thái có vị trí tương ứng trên ma trận Board là :
pkhoigach->iBoard+i,pkhoigach->jBoard+j :



PHP Code:
void SangTrai(KhoiGach *pkhoigach)
{
    for(
int i=0;i<pkhoigach->Row;i++)
        for(
int j=0;j<pkhoigach->Col;j++)
            if(
pkhoigach->arr[i][j]==1)
            {
                if(
Left(pkhoigach->iBoard+i,pkhoigach->jBoard+j)==0||pkhoigach->iBoard<=3) return;
            }

    
pkhoigach->jBoard-=1//Dich vi tri cua bang trang thai sang trai 1 so voi Board[22][10].}void SangPhai(KhoiGach *pkhoigach)
{
    for(
int i=0;i<pkhoigach->Row;i++)
        for(
int j=0;j<pkhoigach->Col;j++)
            if(
pkhoigach->arr[i][j]==1)
            {
                if(
Right(pkhoigach->iBoard+i,pkhoigach->jBoard+j)==0||pkhoigach->iBoard<=3) return;
            }

            
pkhoigach->jBoard+=1//Dich vi tri cua bang trang thai sang phai 1 so voi Board[22][10].}int RoiXuong(KhoiGach *pkhoigach)  //0 : không thể rơi xuống,1 : có thể rơi xuống.{
    for(
int i=0;i<pkhoigach->Row;i++)
        for(
int j=0;j<pkhoigach->Col;j++)
            if(
pkhoigach->arr[i][j]==1)
            {
                if(
Down(pkhoigach->iBoard+i,pkhoigach->jBoard+j)==0) return 0;
            }
    
pkhoigach->iBoard+=1//Roi xuong 1 so voi Board[22][10]
         
return 1

Hàm gán giá trị cho ma trận Board khi khối gạch không rơi xuống được nữa:Khi đó với những arr[i][j] ==1 của ma trận trạng thái thì ta gán giá trị bằng 1 cho Board[i’][j’] .Với (i’,j’) là tọa đô tương ứng của (i,j) trên ma trận Board ,mà cách đổi mình đã nói ở trên rồi.
PHP Code:
void GanGiaTri(KhoiGachpkhoigach)
{
    for(
int i=0;i<pkhoigach->Row;i++)
        for(
int j=0;j<pkhoigach->Col;j++)
            if(
pkhoigach->arr[i][j]==1)
            {
                
Board[pkhoigach->iBoard+i][pkhoigach->jBoard+j]=1;
            }
-Haizz,tới đây thì ta cũng gần xong những cái cơ bản của game Xếp Gạch rồi. Chúng ta sẽ tới hàm xoay khối gạch nữa là coi như xong .Cái xoay khối gạch này rất dễ ,bạn nào đã làm bài “xoay ma trận 2 chiều 1 góc 90o theo chiều kim đồng hồ” rồi thì cái này rất dễ hiểu.
Hàm xoay: Xoay khối gạch thực chất các bạn chỉ cần xoay ma trận trạng thái của khối gạch đó 1 góc 90o theo chiều kim đồng hồ thôi ,vì ma trân ở đây không phải là ma trận vuông nên khi xoay thì kích thước của ma trận sẽ bị thay đổi Row’=Col,Col’=Row.Ta chỉ cần viết 1 hàm xoay duy nhất thì có thể xoay được tất cả các loại khối gạch (7 loại cơ bản và các khối khi được xoay khác) .Chúng ta sẽ xoay thử ma trận trạng thái và sau đó xét xem vị trí của ma trận sau khi xoay tương ứng trên ma trân Board có hợp lệ hay không (không có đè lên ô Board[i][j] ==1 nào và không vượt ra khỏi giới hạn của Board).Nếu hợp lệ thì mới chấp nhận ,còn ngược lại thì vẫn giữ nguyên ma trận trạng thái như ban đầu.
+Và trước và sau khi xoay thì iBoard,jBoard không thay đổi giá trị (vẫn giữ nguyên) – cái này là cách mình chọn cho dễ( và đỡ xử lí ,rắc rối thêm) thôi.Chứ các bạn có thể chọn cách khác để xoay cho đẹp hơn.Và các bạn coi cái hình mô tả dưới đây.


PHP Code:
void XoayKhoiGach(KhoiGachpkhoigach)
{
    
int i,j;
    
int ** tmpArr;
    
int tmpRow=pkhoigach->Col;
    
int tmpCol=pkhoigach->Row;
    
//Cấp phát bộ nhớ cho ma trận phụ tmpArr.
    
tmpArr=(int**)malloc(tmpRow*sizeof(int*));
    for( 
i=0;i<tmpRow;i++)
    {
        
tmpArr[i]=(int*)malloc(tmpCol*sizeof(int));
    }
    
///////////////////////////////////////////////
    
for(i=pkhoigach->Row-1;i>=0;i--)
        for(
j=pkhoigach->Col-1;j>=0;j--)
        {
            
tmpArr[j][pkhoigach->Row-i-1]=pkhoigach->arr[i][j];
        }
    
//Kiểm tra hợp lệ.
        
for (i=0;i<tmpRow;i++)
        {
            for(
j=0;j<tmpCol;j++)
                if(!
Inside(pkhoigach->iBoard+i,pkhoigach->jBoard+j)||Board[pkhoigach->iBoard+i][pkhoigach->jBoard+j]==1)
                    return;
        }
    for(
i=0;i<pkhoigach->Row;i++) free(pkhoigach->arr[i]);
    
free(pkhoigach->arr);
    
//Cập nhật thay đổi sau khi xoay.
    
pkhoigach->Col=tmpCol;
    
pkhoigach->Row=tmpRow;
    
pkhoigach->arr=tmpArr;

Khi làm việc với bộ nhớ động thì các bạn nên cẩn thận xin cấp phát xong và khi không xài nữa thì phải giải phóng bộ nhớ để tránh tình trạng memory leak.

Hàm vẽ ,xóa khối gạch lên màn hình console: Mình đã nói ở trên thì mình dùng biến LEFT,TOP để xác định vị trí để vẽ ma trận Board lên màn hình console.
PHP Code:
void VeKhoiGach(KhoiGachpkhoigach)
{
    
int i;
    
int j;
    for(
i=0;i<pkhoigach->Row;i++)
        for(
j=0;j<pkhoigach->Col;j++)
            if(
pkhoigach->arr[i][j]==1&&(pkhoigach->iBoard+i)>3)
            {
                
textcolor(LIGHTRED);
                
gotoxy(LEFT+pkhoigach->jBoard+j+1,TOP+pkhoigach->iBoard+i-3);
                
cprintf("%c",2);
            }
}
void XoaKhoiGach(KhoiGach *pkhoigach)
{
    
int i;
    
int j;
    for(
i=0;i<pkhoigach->Row;i++)
        for(
j=0;j<pkhoigach->Col;j++)
            if(
pkhoigach->arr[i][j]==1&&(pkhoigach->iBoard+i)>3)
            {
                
textcolor(BLACK);
                
gotoxy(LEFT+pkhoigach->jBoard+j+1,TOP+pkhoigach->iBoard+i-3);
                
cprintf(" ");
            }
3/ Thông tin về điểm,cấp độ,tốc độ.
-Game thì không thể thiếu phần tính điểm và thông tin level .Để tăng tính hấp dẫn cho game.Dưới đây mình demo 1 cách tính điểm và độ khó 1 cách rất đơn giản cho game.Cái phần này các bạn có thể tùy chỉnh theo ý mình.Mình khai báo 1 structure để lưu thông tin.
PHP Code:
typedef struct {
    
int score;
    
int level;
    
float speed;
}
INFO
+Mình sẽ chia game ra làm 2 cấp độ: Cấp độ 1 : 0->200đ Cấp độ 2 :200->300đ
+Mỗi hàng ăn được sẽ được +10 đ.
+Khi tăng Level thì speed cũng nhanh hơn 1 chút .
+Nếu được 300đ trở lên thì Win game.
Các hàm bổ trợ:
PHP Code:
void InitGame(INFO *info)
{
    
info->level=1;
    
info->score=0;
    
info->speed=0.4;
}
int CapNhat(INFO *infoint score)
{
    
info->score+=score;
    if(
info->score>=200&&info->level<2)
    {
        
info->level++;
        
info->speed-=0.1;
    }
    return 
0//Chua win game.}void VeBangDiem(INFO info)
{
    
textcolor(LIGHTYELLOW);
    
gotoxy(LEFT+MaxJ+2,10);cprintf("SCORE:%3d",info.score);
    
gotoxy(LEFT+MaxJ+2,11);cprintf("LEVEL:%3d",info.level);
    
gotoxy(LEFT+MaxJ+2,12);cprintf("SPEED:%3f",info.speed);
Kiểm tra gameover hay không và tính toán số hàng ăn điểm:Khi khối gạch không thể rơi xuống đc nữa thì ta kiểm tra:
+ Game kết thúc khi khối gạch không rơi xuống đc nữa mà iBoard vẫn <=3. --> thì Gameover.
+Nếu chưa gameover,ta xét xem có ăn được hàng nào không? Ta không cần phải xét hết tất cả các hàng của ma trận Board[22][10] ( vì như vậy tốn chi phí ) mà chỉ cần kiểm tra lần lượt các hàng iBoard+0 ,iBoard+ 1 ,… ,…,iBoard+Row-1 là OK.Nếu hàng nào có MaxJ ô có giá trị bằng 1 thì hàng đó ăn điểm được.
+Ăn nhiều nhất chỉ đc 4 hàng/1 lần.
PHP Code:
int KiemTra(KhoiGach *pkhoigach,INFO *infogame//-1 : gameover 0: win{
        
int i,j,count;
        
i=pkhoigach->Row-1;
        if(
pkhoigach->iBoard<=3) return -1;//Gameover
        
if(infogame->score>=300) return 0;//Win
        
do
        {
            
count=0;
            for(
j=0;j<MaxJ;j++)
            {
                if(
Board[pkhoigach->iBoard+i][j]==1)    count++;
            }
            if(
count==MaxJ)
            {
                
CapNhat(infogame,20);
                
CapNhatLaiToaDo(pkhoigach->iBoard+i);
                
DisplayBoard();
            }
            else
            {
                
i=i-1;
            }
        }while(
i>=0);
        return 
1;
Hàm trả về -1 nếu game over,trả về 0 nếu người chơi win(score>=300).Vì trong structure KhoiGach có sử dụng con trỏ(arr-ma trận trạng thái) để cấp phát động nên mĩnh sẽ phải viết 1 hàm giải phóng bộ nhớ để tránh việc memory leak.

PHP Code:
void HuyKhoiGach(KhoiGachpkhoigach)
{
    
int i;
    
//Huy bo nho cua ma tran trang thai arr.
    
for(i=0;i<pkhoigach->Row;i++) free(pkhoigach->arr[i]);
    
free(pkhoigach->arr);
    
/////////////////////////////////////////////////////////
    //Sau do moi free(pkhoigach)
    
free(pkhoigach);
    
pkhoigach=NULL;
4/ Chọn ngẫu nhiên khối gạch và khối gạch tiếp theo sẽ xuất hiện.
Hàm này sẽ random 1 khối gạch để chơi. Trả về số đại diện cho khối gạch đó.
PHP Code:
int Loai()
{
    
int x=rand()%7;
    switch(
x)
    {
    case 
0:
        return 
15;
        break;
    case 
1:
        return 
31;
        break;
    case 
2:
        return 
51;
        break;
    case 
3:
        return 
30;
        break;
    case 
4:
        return 
58;
        break;
    case 
5:
        return 
57;
        break;
    case 
6:
        return 
60;
        break;
    }
Hàm vẽ,xóa khối gạch tiếp theo sẽ xuất hiện:
PHP Code:
void Ve_Next(int ID)
{
    
KhoiGach *pnext=TaoKhoiGach(ID);
    
int iRoot=LEFT+MaxJ+5;
    
int jRoot=TOP;
    for(
int i=0;i<pnext->Row;i++)
    {
        for (
int j=0;j<pnext->Col;j++)
        {
            if(
pnext->arr[i][j]==1)
            {
                
textcolor(LIGHTGREEN);
                
gotoxy(iRoot+j,jRoot+i);cprintf("%c",2);
            }
        }

    }
    
HuyKhoiGach(pnext);
}
void Xoa_Next()
{
    
int iRoot=LEFT+MaxJ+5;
    
int jRoot=TOP;
    for(
int i=0;i<4;i++)
    {
        for (
int j=0;j<4;j++)
        {
           
                
textcolor(BLACK);
                
gotoxy(iRoot+j,jRoot+i);cprintf(" ");
        }

    }
Kết luận:
+Coi như game đã hoàn thành những phần cơ bản.Bây giờ ta ghép các thành phần lại với nhau và viết bắt đầu viết hàm main cho game chạy thôi.


5/Công việc cuối cùng: Viết hàm main và vòng lặp của game.
-Việc tạo hiệu ứng chuyển động là do việc “xóa cũ ,vẽ mới” kết hợp với hàm delay(…) đc đặt trong 1 vòng lặp tạo nên cảm giác chuyển động.
-Để biết đc khi nào bàn phím đc nhấn và phím nào đã đc nhấn ta dùng lệnh _kbhit() và getch() để bắt.Trong game này mình quy định là:
+Phím di chuyển sang trái là : A (ASCII=65)
+Phím di chuyển sang phải là : D
+Phím xoay khối là : W
+Phím tăng tốc độ rơi là : S
-Đoạn code xử lí nhấn bàn phím:
PHP Code:
if(_kbhit())   //Nếu bàn phím đc nhấn
            
{
                
c=toupper(getch());  //Lấy mã phím vừa đc bấm
                
XoaKhoiGach(currKhoi);  //Xóa khối gạch
                
switch(c)
                {
                case 
'W':
                    
XoayKhoiGach(currKhoi);
                    break;
                case 
'A':
                    
SangTrai(currKhoi);
                    break;
                case 
'D':
                    
SangPhai(currKhoi);
                    break;
                case 
'S':
                    
RoiXuong();
                    break;
                }
                
VeKhoiGach(currKhoi);//Vẽ lại khối gạch sau khi cập nhật thay đổi.
            
*Vòng lặp cho game:
-Khởi tạo giá trị ban đầu cho game.Vẽ khung giao diện.Khởi tạo các tham số level,score,speed.
Trích dẫn:
Nguyên bản được gửi bởi tauit_dnmd
Do
{
- Vẽ lại khối gạch và ma trận Board lên màn hinh.
-Trong khi chưa hết INFO.speed thì lặp lại quá trình:
Nếu có sự kiện nhấn bàn phím xảy ra( if(_kbhit()) ) thì
+Xóa Khối gạch.
+Xử lí nhấn bàn phím
+Vẽ lại khối gạch sau khi thay đổi.
- Cho khối gạch rơi xuống:
-Nếu rơi xuống không đc:
+Gán giá trị cho ma trận Board.Sử dụng hàm : void GanGiaTri(KhoiGach* pkhoigach)
+Kiểm tra thắng thua và cập nhật lại điểm.Nếu gameover hoặc win thì kết thúc trò chơi.
+Hủy bộ nhớ của khối gạch hiện tại và sau đó khởi tạo khối gạch mới.
}While(!gameover hoặc !win);
Code demo vòng lặp của game là:
PHP Code:
do
    {
        
VeBangDiem(info);
        
VeKhoiGach(currKhoi);
        
Xoa_Next();
        
Ve_Next(IDKhoiTiepTheo);
        
Start=clock();
        do
        {
           
            if(
_kbhit())
            {
                
c=toupper(getch());
                
XoaKhoiGach(currKhoi);
                switch(
c)
                {
                case 
'W':
                    
XoayKhoiGach(currKhoi);
                    break;
                case 
'A':
                    
SangTrai(currKhoi);
                    break;
                case 
'D':
                    
SangPhai(currKhoi);
                    break;
                case 
'S':
                    
RoiXuong(currKhoi);
                    break;
                }
                
VeKhoiGach(currKhoi);
            }
            
End=clock();
        }while(
float(End-Start)/CLK_TCK<info.speed);
        
XoaKhoiGach(currKhoi);
        if(
RoiXuong(currKhoi)==0//ko di chuyen xuong dc
        
{
            
GanGiaTri(currKhoi);
            
int ketqua=KiemTra(currKhoi,&info);
            if((
ketqua==-1||ketqua==0)) break;
            
HuyKhoiGach(currKhoi); //Giai phong bo nho.
            
currKhoi=TaoKhoiGach(IDKhoiTiepTheo);
            
IDKhoiTiepTheo=Loai();
            
DisplayBoard();
        }
       
    }while(
1); 
OK! Các bạn tạo project và chạy thử game nào.Và đây là hình ảnh của game khi chạy.Mình chỉ hướng dẫn cách quản lí ,xây dựng cấu trúc chương trình game thôi,mình demo rất ít chức năng .Nếu muốn các bạn có thể viết thêm phần save,load game,pause game…



Nguồn: Cộng đồng C-Việt

Đăng nhận xét

Mới hơn Cũ hơn

Bài viết mới nhất

CẦN THIẾT