Snake Game
13&14 - Danh sách liên kết
Nội dung
●
●
Trị chơi: Snake
Kỹ thuật
○
○
○
Mảng 2 chiều
Bắt phím với SDL_PollEvent()
Hàng đợi
■
○
xử lý hiện tượng rớt phím
Danh sách liên kết
■
thêm, chèn, xoá trên danh sách hiệu quả
Trị chơi Snake
●
Sân chơi hình chữ nhật
○
●
Rắn lúc đầu
○
●
●
dài 4 ơ (tính cả đầu), ở giữa màn hình, đi xuống
Người chơi điều khiển rắn di chuyển bằng các phím mũi tên
Mỗi lần rắn ăn 1 quả cherry thì dài thêm 1 ô
○
●
Trên sân chơi xuất hiện các quả cherry ngẫu nhiên
Thử sức: nhiều loại quả, mỗi loại một tác dụng
Rắn va phải tường hoặc chính nó → thua
○
/>
Demo - Start Screen
Demo - Midgame screen
Các tác vụ của trị chơi
●
Hiển thị hình vẽ giới thiệu
○
●
●
Có nút hiển thị bảng xếp hạng các lần chơi
Khởi tạo: sân chơi, con rắn, vị trí quả
Game loop, tại mỗi bước:
○
○
Xử lý sự kiện bàn phím để đổi hướng đi bước tiếp theo
Xử lý game logic: di chuyển rắn theo hướng đi hiện tại, va chạm tường, va chạm thân rắn,
ăn quả dài thân và tăng điểm số
○
Hiển thị màn hình trị chơi
Lộ trình xây dựng trị chơi
Các phiên bản
0.1: vẽ sân chơi và rắn đơn giản (dùng ơ vng hoặc hình tròn), điều khiển được
rắn di chuyển
0.2: thêm quả vào sân chơi, rắn ăn quả dài ra
0.3: xử lý va chạm với cạnh sân và thân rắn
0.4: Vẽ các đốt rắn đẹp bằng ảnh JPG
1.0: Thêm màn hình khởi động, điểm số, bảng xếp hạng
Chuẩn bị
●
●
●
●
Tạo project Snake
Cài đặt thư viện SDL2, SDL2_image
Đưa main.cpp, painter.h, painter.cpp từ bài giảng về SDL vào project
Sửa main.cpp
○
○
○
Xoá các hàm vẽ
Sửa tiêu đề cửa sổ
Chỉ để lại mã khởi tạo và giải phóng SDL
■
cửa sổ và bút vẽ
Chuẩn bị
Hàm main()
in t m ain (in t argc,ch ar* argv[])
{
srand(tim e(0));
SD L_W indow * w indow ;
SD L_Renderer* renderer;
initSD L(w indow ,renderer);
Painter painter(w indow , renderer);
// TO D O : gam e code here
quitSD L(w indow ,renderer);
retu rn 0;
}
Mã giả
render splash screen;
initialize play-ground size = (w idth, height)
render play-ground (save tim estam p)
w h ile (gam e is running) {
get user input
update snake direction using user input (turn up, dow n,left, right)
if elapsed tim e > required delay betw een steps
m ove the gam e (snake craw l,generate cherry) to the next step
render play-ground
save new tim estam p
}
render gam e-over screen
update score and ranking table to fi
le
Code C++
renderSplashScreen();
PlayG round p layG rou n d (G R O U N D _W ID TH , G R O U N D _H EIG H T);
SD L_Event e;
renderG am ePlay(painter,playG round);
au to start = C LO C K_N O W ();
au to end = C LO C K_N O W ();
w h ile (playG round.isG am eRunning()) {
Elap sed T im e elapsed = end-start;
w h ile (SD L_PollEvent(& e) != 0 ) {
if (elapsed.count() > STEP_D ELAY) {
U serInput input = interpretEvent(e);
playG round.nextStep();
playG round.processU serInput(input);
renderG am ePlay(painter,
} // non-blocking event detection
playG round);
start = end;
// gam e logic here
SD L_D elay(1); // to prevent high C PU usage because of SD L_PollEvent()
}
renderG am eO ver(painter, playG round);
updateRankingTable(playG round);
}
Một số tiện ích
// sốốgiây giữa hailâầ
n vẽ
const dou b le STEP_D ELAY = 0.5;
// tên ngắố
n cu
ủa hàm lâố
y thờigian
# defi
ne CLO CK_N O W chrono::system _clock::now
// Kiêủ
u đạidiện cho khoa
ủng thờigian (tính theo giây)
typ edef chrono::duration< dou b le> ElapsedTim e;
Nhập liệu và hiển thị
const in t G RO U N D _W ID TH = 30;
const in t G RO U N D _H EIG H T = 20;
void ren derS p lash S creen ();
void ren derG am eP lay(Painter& , con st PlayG round& playG round);
void ren derG am eO ver(Painter& , const PlayG round& playG round);
U serInput in terp retEven t(SD L_Event e);
void u p dateR an kin gTab le(con st PlayG round& playG round);
●
PlayGround: lớp biểu diễn sân chơi
○
●
Xử lý logic của game
UserInput: các hành động của người chơi
en u m U serInput { N O _IN PU T = 0, KEY_U P, KEY_D O W N ,KEY_LEFT, KEY_RIG H T };
Tạo các hàm rỗng để lấy chỗ
void ren derS p lash S creen ()
{
Đợi 1 phím trước khi bắt đầu
w aitU ntilKeyPressed();
}
void ren derG am eP lay(Painter& , con st PlayG round& playG round) { }
void ren derG am eO ver(Painter& , const PlayG round& playG round) { }
U serInput in terp retEven t(SD L_Event e)
{
retu rn N O _IN PU T;
}
void u p dateR an kin gTab le(con st PlayG round& playG round) { }
chơi
Biểu diễn sân chơi
●
●
Tìm cách biểu diễn mỗi đối tượng trong trị chơi bằng Lớp (dữ liệu + hàm)
Sân chơi
○
○
Hình chữ nhật các ơ vng
Mỗi ơ có thể trống, vị trí của rắn, vị trí của quả
■
○
Có thể mở rộng sau này để có nhiều loại quả
Các chức năng chính (mình có thể nghĩ ra bây giờ)
■
■
■
Khởi tạo (và các Getters đọc trạng thái)
Thêm quả vào chỗ trống
Thay đổi trạng thái các ô
Biểu diễn sân chơi (PlayGround.*)
●
Enum loại ô trong sân
en u m C ellType { C ELL_EM PTY = 0, CELL_SN AKE, CELL_C H ERRY };
●
Dữ liệu của lớp PlayGround
○
Hình chữ nhật → mảng 2 chiều trạng thái
std::vector< std::vector< CellType> > squares;
○
Con rắn
Snake snake;
○
Cần tạo lớp Snake
■
■
○
tạo lớp rỗng trong Snake.*
#include trong PlayGround.h để tạm đấy
Điểm số: in t score;
Biểu diễn sân chơi (PlayGround.*)
●
Trạng thái trò chơi: sử dụng các bít 0, 1, 2, 3
en u m G am eStatus {
G A M E_R U N N IN G = 1 ,
G A M E_STO P = 2,
G A M E_W O N = 4 | G AM E_STO P, // G AM E_W O N tức là G A M E_STO P
G A M E_LO ST = 8 | G A M E_STO P, // tương tự cho G A M E_LO ST
};
●
Trong lớp PlayGround
Đến đây chương trình dịch được và ta đã lên được
G am eStatus status;
...
p u b lic:
...
b oo l isG am eRunning() con st { retu rn status = = G A M E_R U N N IN G ; }
vo id processU serInput(U serInput input) { }
vo id n extS tep () { }
khung chương trình
Thay đổi trạng thái ơ vng
●
Có thể khai báo
void ch an g eC ellS tate(in t x, in t y, C ELL_TYPE type);
●
●
Một vị trí ln có cả 2 biến x và y
Tạo một struct Position để tiện quản lý
○
Sẽ có các hàm thay đổi, so sánh, tính tốn vị trí
stru ct Position
{
in t x, y;
};
class P layG rou n d {
...
vo id changeC ellState(Position pos, C ellType type);
};
Khởi tạo sân chơi
●
●
●
Khởi tạo ô vuông: dựa vào số dòng, số cột
Khởi tạo rắn
Thêm 1 quả cherry
PlayG round::PlayG round(in t w idth, in t height)
: squares(height, vector< CellType> (w idth, CELL_EM PTY)),
snake(this), // rắố
n phụ thuộc vào sân chơi,sưủ
a hàm khơ
ủ itạo Snake
status(G AM E_RU N N IN G ),
score(0)
{
addCherry(); // thêm 1 hàm đặt squ ares[0][0] = C ELL_C H ER RY
// đêủthưủnghiệm
}
Sửa hàm khởi tạo Snake
●
Cần sửa hàm khởi tạo Snake thành
Snake(PlayG round* playG round);
●
Như vậy,
○
○
○
●
trong PlayGround.h có include Snake.h
trong Snake.h lại include PlayGround.h
cái nào trước, cái nào sau ? có lỗi ?
Giải pháp: forward declaration
/>
●
Khai báo class PlayGround; trước khai báo lớp Snake và #include “PlayGround.h” trong
Snake.cpp
Phiên bản 0.1
●
Hiển thị đơn giản
○
○
○
●
Sân chơi: nền tím, ơ vng kẻ màu trắng
Rắn: chỉ có 1 đốt hình trịn màu đỏ
Quả cherry: hình vng nhỏ màu cam
Điều khiển bằng phím
○
○
Lúc đầu rắn ở giữa sân chơi, chạy sang phải
Nhận phím mũi tên, chỉnh hướng đi của rắn
renderGamePlay(): vẽ sân chơi
vo id ren d erG am eP lay(Painter& painter, con st PlayG round& playG round)
{
in t top = 0,left = 0;
Vẽ hình tương đối với điểm
in t w idth = playG round.getW idth();
(top, left)
in t height = playG round.getH eight();
painter.clearW ithBgC olor(PU RPLE_C O LO R);
painter.setC olor(W H ITE_C O LO R);
for (in t i= 0;i< = w idth; i+ + ) {
painter.setAngle(-90);
Các đường kẻ dọc
painter.setPosition(left+ i* C ELL_SIZE,top+ 0);
painter.m oveForw ard(height * C ELL_SIZ E);
}
for (in t i= 0;i< = height; i+ + ) {
painter.setAngle(0);
painter.setPosition(left+ 0,top+ i* C ELL_SIZE);
painter.m oveForw ard(w idth * C ELL_SIZ E);
}
Các đường kẻ ngang
renderGamePlay(): continue
co n st vector< vector< C ellType> > & squares = playG round.getSquares();
fo r (in t i= 0; i< height; i+ + ) {
Duyệt mảng 2 chiều
for (in t j= 0 ; j< w idth; j+ + ) {
if (squares[i][j] = = C ELL_C H ER RY) {
painter.setC olor(O RAN G E_C O LO R );
Tìm ơ có cherry
painter.setAngle(-9 0);
painter.setPosition(left+ j*C ELL_SIZE+ 5, top+ i*C ELL_SIZE+ 5 );
painter.createSquare(C ELL_SIZE-1 0 );
} else if (squares[i][j] = = C ELL_SN A KE) {
painter.setC olor(R ED _C O LO R );
painter.setAngle(0 );
painter.setPosition(left+ j*C ELL_SIZE+ 5, top+ i*C ELL_SIZE+ C ELL_SIZE/2);
painter.createC ircle(C ELL_SIZE/2-5 );
}
}
}
SD L_RenderPresent(painter.getRenderer());
}
Tìm các đốt rắn
Biểu diễn con rắn
●
Dữ liệu của Snake
○
○
stru ct Position
{
Position position;
in t x;
PlayG round* playG round;
in t y;
Position(in t x_,in t y_) : x(x_),y(y_) {}
●
};
Đưa khai báo struct Position sang Position.h
○
●
thêm hàm khởi tạo bằng 2 toạ độ
#include Position.h trong Snake.h và PlayGround.h
Khởi tạo rắn
●
●
Khởi tạo đốt ở giữa sân chơi
Thay đổi trạng thái ở ô này: CELL_SNAKE
Snake::Snake(PlayG round* playG round)
: position(playG round-> getW idth() / 2, playG round-> getH eight() / 2),
th is-> playG round(playG round)
{
playG round-> changeC ellState(position, C ELL_SN AKE);
}
void PlayG round::changeC ellState(Position pos, C ellType type)
{
squares[pos.y][pos.x] = type;
}