Đăng ký
Cộng đồng phát triển game Việt - kết nối đam mê !

bởi


 
Phần 1: Game Logic
Mọi người chắc không lạ gì cái trò này và sẽ có người bảo là cái trò này thì có gì đâu mà phải viết tut hướng dẫn, nhưng mà em biết là có cả đống người không làm được, em thề em hứa em đảm bảo!  Em không có ý gây sự đâu 04
Trò này đơn giản chỉ là một bàn cờ 3x3 ô, có 2 người chơi (bài này em chỉ đề cập tới phần người đấu với người, không có đấu với máy) sẽ thay phiên nhau đánh vô từng ô, người đánh X, người đánh O cho tới khi nào 1 trong 2 người hoàn thành được 3 ô liên tiếp (theo hàng dọc, ngang hay chéo) giống nhau thì sẽ thắng.Trong bài này em sẽ trình bày cách làm một trò chơi như thế này, đây là trò đơn giản nhất và thích hợp cho những ai mới bắt đầu làm quen với gamedev. Vì nội dung chú trọng vào vấn đề game hoạt động thế nào nên em bỏ qua phần đồ họa, game sẽ được trình bày trên cửa sổ console đen xì, phản cảm nhưng các bác chịu khó theo dõi.
Về nội dung trò chơi thì em đã giới thiệu trên kia, có điều em muốn nhắc lại là ở đây em bỏ qua vấn đề đồ họa, chỉ tập trung thể hiện gameplay, và sẽ thể hiện dưới dạng text-based
Để tiện theo dõi thì em khuyên các bác học chút món C++ căn bản đã rồi đọc. Cũng không phức tạp mấy.

Giao diện trò chơi ta sẽ làm:

Qui trình trò chơi này nó như thế này:
- Đầu tiên vô là khởi tạo một số dữ liệu dùng cho game.
- Tiếp theo là vòng lặp chính, bao gồm

+ vẽ bàn cờ ra
+ người chơi nhập nước đi vào (nhập dưới dạng tọa độ [x;y] )
+ xử lý nước đi của người chơi và kiểm tra điều kiện thắng cuộc của game
+ nếu vẫn chưa thắng thì ta quay về lại bước vẽ bàn cờ, nếu thắng rồi thì ta in kết quả và thoát ra ngoài


Với một trò chơi dạng này, ta cần một mảng 2 chiều để lưu bàn cờ, mỗi phần tử trong mảng sẽ có các giá trị khác nhau như 

* nếu ô tương ứng là ô trống
X nếu ô tương ứng được đánh quân X
O nếu ô tương ứng được đánh quân O

sau đó ta cần thêm biến winner để trả về người chơi nào thắng, biến winner có giá trị ứng với

0 nếu hòa
1 nếu player 1 thắng
2 nếu player 2 thắng


và biến endGame để biết được lúc nào game kết thúc.
Trò chơi gồm có các hàm và thủ tục:

void initBoard() - khởi tạo một số giá trị trong game
bool blank() - kiểm tra xem còn ô trống nào không
void checkWin() - kiểm tra xem ai thắng hoặc đã có ai thắng chưa
void drawBoard() - in ra màn hình bàn cờ
void playGame() - vòng lặp chính kiểm soát quá trình game


Đấy là với chế độ chơi 2 người, và trong bài này em cũng chỉ nói tới phần đó.
lát nữa em sẽ đưa full code lên vì trong đó em đã comment sẵn, giờ mà em ghi ra nữa thì nó dài quá.
em chỉ giải thích tí về cái thủ tục playGame
thủ tục này tóm tắt như sau

playGame()


- Kiểm tra điều kiện endGame, nếu bằng false thì tiếp tục


- Vào trong này, ta vẫn kiểm tra tiếp nếu endGame = false thì làm tiếp, vì nếu không kiểm tra trong này, sẽ xảy ra tình trạng người chơi đã thẳng rồi nhưng vẫn cho người kia tiếp tục đi tiếp.
Bước này thực hiện 2 lần vì ta xử lý cho 2 người chơi


+ Người chơi nhập tọa độ ô muốn đi
+ Kiểm tra xem ô vừa nhập có hợp lệ không (hợp lệ ở đây là ô còn trống và chưa có ai đánh vào)
+ Nếu hợp lệ thì đi tiếp, nếu không thì báo lỗi và nhập lại
+ Đã hợp lệ, ta đánh vào ô vừa nhập giá trị X hoặc O, tùy vào người chơi 1 hay 2 đang đi
+ Đánh xong thì ta kiểm tra điều kiện thắng cuộc và in bàn cờ ra màn hình



- Nếu endGame bằng true thì ta kết thúc và in ra kết quả


về thủ tục kiểm tra điều kiện thắng cuộc
checkWin()


- Ban đầu, ta cho biến winner bằng 0, tức là hòa
- Ta tạo một biến key, biến này để lấy giá trị dãy các quân cờ giống nhau và so sánh
- Ta chạy vòng lặp để duyệt qua các hàng, cột, đường chéo, nếu các ô trong hàng/cột/đường chéo bằng nhau thì ta sẽ ghi nhận giá trị của chúng vào biến key


- Ta sẽ kiểm tra giá trị của key, nếu bằng X thì đặt winner là 1 và endGame = true (*)
- Nếu key = O thì đặt winner là 2 và endGame cũng bằng true (**)


- Nếu blank = false, tức là đã không còn ô trống nào trên bàn cờ, thì ta cũng đặt endGame = true luôn
Khi endGame được đặt bằng true thì game sẽ kết thúc, lúc này chương trình sẽ kiểm tra winner để đưa ra kết quả game, như ta thấy, nếu kết thúc game mà hòa, tức là (*) hoặc (**) không được thực hiện, biến winner vẫn là 0.


Sau đây là mã nguồn đầy đủ, có comment: ( nếu phần code dưới đây hiển thị lỗi, bạn có thể xem tại đây http://pastebin.com/vxzSrP5b )




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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#include "iostream.h"
 
char board[3][3]; // Mảng 2 chiều lưu bàn cờ
int winner; // cho biết ai là người thắng, nếu bằng 1 là player 1, 2 là player 2, 0 là hòa
bool endGame; // nếu bằng true thì kết thúc game
 
// khởi tạo bàn cờ và một số dữ liệu khác trong game
void initBoard()
{
    endGame = false; // đầu tiên thì ta đặt endGame là false vì lúc này mới bắt đầu game mà
    /* cái bàn cờ thì nó sẽ trông như thế này
     * +--------------+
     * | *    | *     |    X |
     * +--------------+
     * | X    | O     |    * |
     * +--------------+
     * | *    | X     |    * |
     * +--------------+
     * quân X ứng với ô có giá trị là X, O ứng với ô có giá trị O
     * ô nào trống thì có giá trị *
     */
    for (int e=0; e
    {
        for (int j=0; j
        {
            board [e] [j] = '*';
        }
    }
}
 
// hàm này kiểm tra xem bàn cờ còn ô nào trống khôn
// nếu còn thì trả về false, không thì true
bool blank()
{
    for (int e=0; e
    {
        for (int j=0; j
        {
            if (board [e] [j] == '*') return true;
        }
    }
    return false;
}
 
// hàm này kiểm tra điều kiện thắng và cho biết ai thắng
// nguyên tắc thế này:
// kiểm tra nếu các giá trị ở các hàng/ cột/ đường chéo bằng nhau thì lấy ra một giá trị chung
// là key rồi kiểm tra key, nếu key = 'X' thì player 1 thắng, key = 'O' thì player 2 thắng
void checkWin(){
  int e;
  char key = '*';
  winner = 0;
  endGame = false;
 
     for (e=0; e
        if (board [e] [0] == board [e] [1] && board [e] [0] == board [e] [2] && board [e] [0] != '*') key = board [e] [0];
       
     for (e=0; e
        if (board [0] [e] == board [1] [e] && board [0] [e] == board [2] [e] && board [0] [e] != '*') key = board [0] [e];
             
     if (board [0] [0] == board [1] [1] && board [1] [1] == board [2] [2] && board [1] [1] != '*') key = board [1] [1];
     if (board [0] [2] == board [1] [1] && board [1] [1] == board [2] [0] && board [1] [1] != '*') key = board [1] [1];
           
    if (key == 'X')
    {
      winner = 1; // key = 'X' nên player1 thắng
      endGame = true; // thắng rồi thì end game
    }
    if (key == 'O')
    {
      winner = 2; // player 2 thắng
      endGame = true; // cũng end game luôn
    }
 
    // còn nếu mà hết ô trống rồi thì cũng end game, nếu winner vẫn bằng 0 thì là hòa
    if (blank() == false)
    {                       
        endGame = true;
    }
}
 
// in ra cái bàn cờ
void drawBoard()
{
     
    for (int e=0; e
    {
        for (int j=0; j
        {
            printf("%c | ", board[e][j]);
        }
        printf("\n");
    }
}
 
// đây chính là phần quan trọng nhất, vòng lặp chính
void playGame()
{
    int x,y; // ta tạo 2 biến x và y để nhập nước đi
 
    // lặp chừng nào endGame vẫn còn false
    while (endGame == false)
    {
        // phải thêm cái này vì trước mỗi nước đi ta sẽ kiểm tra xem đã kết thúc game chưa
        // nếu chưa thì cho người chơi nhập tiếp, còn end rồi thì thôi, dừng.
        if (endGame == false)
        {
            // ta làm một cái mốc et để nhập nước đi cho player 1
        et:    printf("Player 1 nhap nuoc di: ");
            std::cin >> x;
            std::cin >> y;
 
            if ( (x // nếu mà giá trị nhập vào hợp lệ thì xử lý tiếp
            {
                if (board[x-1][y-1] == '*'){ // nếu ô người chơi nhập vào còn trống
                    board[x-1][y-1] = 'X'; // thì đánh vào quân X
                    checkWin(); // đồng thời kiểm tra xem đã thắng chưa
                }else{
                    // nếu ô người chơi nhập vào không còn trống (tức là đã có X hoặc O)
                    // thì nhảy về mốc er1 để báo lỗi và cho người chơi nhập lại
                    goto er1;
                }
                printf("--> Player 1: %d - %d\n", x, y); // ghi lại các nước đi cho dễ theo dõi
            }else
            {
                // mốc er1 để báo lỗi game với người chơi 1
                er1: printf("Nhap sai!\n");
                goto et; // báo lỗi xong thì nhảy về et để nhập nước đi lại
            }
             
            drawBoard(); // đi xong thì in bàn cờ ra
         }
 
        // tương tự, trước khi player 2 ra nước đi, ta cũng kiểm tra xem game đã kết thúc chưa
        if (endGame == false)
        {
            // mốc et2 là để player 2 nhập nước đi
            // dưới đây cũng giống như đối với player 1 nên không cần comment nữa
        et2: printf("Player 2 nhap nuoc di: ");
             std::cin >> x;
            std::cin >> y;
            if ( (x
            {
                if (board[x-1][y-1] == '*'){
                    board[x-1][y-1] = 'O';
                    checkWin();
                }else{
                    goto er2;
                }
                printf("--> Player 2: %d - %d\n", x, y);
            }else
            {
                er2: printf("Nhap sai!\n");
                goto et2;
            }
             
            drawBoard();
        }
    }
    // Nếu endGame = true thì kết thúc trò chơi, in ra kết quả
    printf("Ket thuc!\n");
    switch(winner) // kiểm tra winner
    {
    case 0: // nếu bằng 0 l&agrav e; hòa
        printf("Hoa!\n"); break;
    case 1: // nếu bằng 1 là player 1 thắng
        printf("Player 1 thang!\n"); break;
    case 2: // bằng 2 là player 2 thắng
        printf("Player 2 thang!\n"); break;
    }
    getchar();
}
 
int main(int argc, char* argv[])
{
    initBoard();
    drawBoard();
    playGame();
    getchar();
    return 0;
}



Phần 2: Đồ họa
Ở trên, ta đã biết cách thiết kế gameplay cho trò chơi tictactoe đơn giản, nhưng chỉ đến đó thì vẫn chưa hấp dẫn được con mèo nào cả, vì lý do là giao diện quá ư là xấu, ai mà thèm chơi một trò như thế, bằng chứng là từ hôm post bài đến nay vẫn chưa ai bình luận gì cái bài viết này (chú thích: lúc trước em viết bài này bên forum GameDev.VN ạ)
Bây giờ ta sẽ nâng cấp cái game này, thiết kế nó thành một game 3D đơn giản. Sử dụng engine Irrlicht.
Cũng không phức tạp mấy, phần chính vẫn là gameplay, và cái này ta đã thực hiện ở trên kia, bây giờ ta sẽ dùng lại nó để viết thành một game 3D. Có một số thủ tục ta cần viết lại hoặc chỉnh sửa lại cho phù hợp với môi trường đồ họa 3D, đó là các thủ tục drawBoard(), playGame() (ta không cần thủ tục playgame như thế này nữa vì bản thân Irrlicht đã có mainloop, ta chỉ cần chỉnh sửa thủ tục playGame() và đưa nó vào trong mainloop là được),...
Post trước screenshot để các bác xem:

Các bác có thể download game tại đây: https://sites.google.com/site/kingno1host1/TicTacT...
Mã nguồn: https://sites.google.com/site/kingno1host1/TicTac3...

Mã nguồn em post lên không kèm theo dữ liệu, vì thế để chạy được thì cần tải game về để lấy dữ liệu mà chạy 
Để build bộ source này, cần có:
- Irrlicht 1.5
- IrrKlang 1.3
- Microsoft VC++ 2008 (hoặc nếu đang dùng bản 2005 thì xóa file sln đi, mở file vcproj thôi, dùng VC++6 thì xóa luôn cái vcproj luôn, tạo project mới và copy cái file main.cpp, 2 file irrGameEventReceiver.h và irrGameInput.h vào  Wink
Chủ đề: hướng dẫn, cơ bản, game
Trần Phong Phú
Bài viết hay quá. Về nhà làm theo thôi. Porting sang HTML5 đi anh em?
  • tháng 4 2, 2012
  • ·
  • Thích
  • ·
Nguyễn Tài Hải
Thích bài này. Đơn giản dễ tiếp cận.
  • tháng 4 2, 2012
  • ·
  • Thích
  • ·
Trần Thiện KHiêm
Port qua HTML5 rồi nha http://games.1or2.info/19.game
  • tháng 4 2, 2012
  • ·
  • Thích
  • ·
Thái Thanh Phong
Kool quá >.<
  • tháng 4 2, 2012
  • ·
  • Thích
  • ·
Huy Trần
hay hay 04
  • tháng 4 2, 2012
  • ·
  • Thích
  • ·
2close
like wé
  • tháng 4 3, 2012
  • ·
  • Thích
  • ·
Thái Thanh Phong
Cần thêm bài viết về AI đơn giản cho tic-tac-toe
  • tháng 4 3, 2012
  • ·
  • Thích
  • ·
Nguyễn Quang Vinh
Khiêm quất AI roài kìa anh, rất đơn giản là random thoai, hehe...
  • tháng 4 3, 2012
  • ·
  • Thích
  • ·
Thái Thanh Phong
À há. Nếu có 1 bài viết riêng, đơn giản, dễ hiểu về 1 số dạng AI cơ bản nhất cho board game như tic-tac-toe, v.v. sẽ thú vị đó.
  • tháng 4 3, 2012
  • ·
  • Thích
  • ·
κανένα συναίσθημα
Nhìn mấy cái thùng muốn đập quá. ~>"<~ Tại sao khi 1 player chọn 1 nước đi ta không đập nát cái thùng mà chỉ làm theo cách cổ điển là đổi màu cho nó?
  • tháng 6 18, 2012
  • ·
  • Thích
  • ·
κανένα συναίσθημα
Bổ sung âm thanh, rầm rầm, đùng đùng thì càng thích.
  • tháng 6 18, 2012
  • ·
  • Thích
  • ·
κανένα συναίσθημα
for (int i=0; i<=2; i++)
{
for (int j=0; j<=2; j++)
{
board[j] = '*';
}
}
Phải là board[i][j] chứ nhỉ?
  • tháng 6 18, 2012
  • ·
  • Thích
  • ·
Huy Trần
Lúc post bài này thì forum còn đang bị lỗi hiển thị nên những kí tự [ ] nó bị chuyển thành < > nên nội dung bị thay đổi, bạn để ý thấy cái [j] nó bị in nghiên là vì thế đấy :"> mình sẽ sửa lại code sớm
  • tháng 6 18, 2012
  • ·
  • Thích
  • ·
Captcha Challenge