Nick Chung gửi vào
- 16476 lượt xem
Nói tới Game này thì ai cũng biết, là một trong số những Game của người Việt có tiếng vang lớn trong vài năm trước, cách chơi đơn giản,đồ họa 2D basic... vậy còn lập trình nó với ARDUINO thì sao nhỉ ?
FLAPPY BIRD
Vượt qua cái ngưỡng “đơn giản” đó đã khó , dành điểm số để đạt TOP thì còn hơn cả thử thách là những gì người chơi sẽ trải qua, chúng đã thực sự đem lại nhiều cung bậc cảm xúc cho người chơi.
May mắn thay, việc lập trình Game này còn dễ hơn cả chơi nó.
Hãy cùng mình bắt đầu nhé.
![]()
Phần cứng
Tải về thư viện đồ họa
Bạn hãy tải về thư viện tại bài viết
ST7565 | Hướng dẫn sử dụng glcd ST7565 homephone và chia sẻ thư viện
Chuẩn bị phần cứng
- Arduino chip atm328 trở lên, mình sử dụng arduino uno r3
- Lcd st7565 128x64 homephone spi
- Nút bấm: 6 nút
Nối mạch
Bạn hãy tham khảo cách nối mạch và nút bấm tại bài viết giới thiệu lcd nhé:

Quản lí đối tượng
Bạn có thể cần đọc các bài viết về quản lí đối tượng Game :
- Phần 1: Hiệu ứng đồ họa Game trên lcd
- Phần 2: Chuyển động và lập trình cơ bản
- Phần 3: Quản lí các đối tượng Game
- Phần 4: Xử lí va chạm của của các đối tượng Game
Chương trình viết theo phong cách hướng đối tượng.
Chỉ có 2 loại đối tượng cần quan tâm.
|
Đối tượng
|
Chức năng, nhiệm vụ
|
Thuộc tính hình học
|
Thuộc tính khác.
|
|
Chú chim
|
Người dùng nhấn chạm vào màn hình ( với Project này là nhấn vào nút ấn), điều khiển chim bay lên, vượt qua các chướng ngại vật.
|
Tọa độ : X,Y
|
Sự tồn tại.: Sống/Chết.
Hướng : Bay lên/ bay xuống.
|
|
Ống nước
|
Là các chướng ngại vật, xuất hiện cả ở trên và dưới màn hình, chú chim bay và vượt qua các khe hở tạo bởi 2 ống nước.
|
Tọa độ: X,Y.
|
Sự tồn tại: Còn hoặc không còn ảnh hưởng tới chú chim.
Hướng: di chuyển từ phải sang trái.
|
Sự tồn tại
Như đã biết để điều khiển các ống nước và chú chim (các đối tượng) ta cần phải tìm cách quản lí chúng, bằng cách sử dụng bảng thống kê( biến X,Y,…) hoặc thực thể hóa thành với các thuộc tính gần gũi (hướng, tồn tại, kích thước, vị trí.). Mỗi đối tượng luôn mang theo những thuộc tính của riêng nó, việc tạo ra các đối tượng sẽ đi cùng với việc sử dụng thêm tài nguyên trên lưu trữ của ARDUINO (máy tính), cụ thể ở đây là tăng thêm các biến giá trị để lưu và tính toán trên chúng.
Trên hành trình của chú chim, chú phải bay qua rất nhiều ống nước rồi ghi điểm mỗi khi vượt qua, ngoài đời thực, ngoài đời thực, nếu như có n ống nước, thì đó đương nhiên là n đối tượng , mỗi đối tượng cần 1 trang giấy để ghi lại thông tin thì ta cần n trang giấy cho từng đó đối tượng.
Trong lập trình Game, ta cần cụ thể hơn khi xem xét chúng:
|
Đối tượng cần quan tâm.
|
Đối tượng không cần quan tâm.
|
|
Chú chim .
|
Với những chiếc ống ống nước mà chim đã bay qua .
|
|
Chiếc ống nước gần chim nhất.
|
Với những ống nước ở quá xa so với chim.
|
|
Những gì cần vẽ lên màn hình.
|
Những đối tượng bị gắn mác là “ không tồn tại”
|
Đó là ý nghĩa của “sự tồn tại”, nó giúp ta đơn giản hóa và tiết kiệm dữ liệu nhớ khi chỉ quan tâm quản lí đến các đối tượng "gần gũi" nhất.
Đơn giản hóa vấn đề:
- Hình dáng chi tiết của ống nước được phác họa bởi một hình chữ nhật đơn giản dạng ống .
- Hệ quan sát của ta (Camera) gắn với hệ của chú chim, do đó , khi biểu diễn quá trình di chuyển của chim , đối tượng này luôn giữ một hoành độ cố định, trong khi đó những chiếc ống nước sẽ di chuyển từ phải qua trái, tạo ra chuyển động tương đối giữa chim và cột.
- Sẽ không phải là n chiếc ống di chuyển, mà sẽ là chú chim vượt qua chướng ngại vật n lần.

Khi lập trình, điều này có nghĩa là chỉ có một số ít ống nước cần quan tâm, cụ thể 6 ống nước di chuyển cách đều. Sau mỗi chu kì di chuyển (đi hết từ phải qua trái), chúng lại trở về vị trí xuất phát rồi lại di chuyển nhưng với độ cao thay đổi. Chúng ta dùng hàm Random() để khởi tạo giá trị độ cao một cách ngẫu nhiên tạo ra những chiếc ống khác nhau trên hành trình. Đây là ý tưởng để giải quyết vấn đề quản lí đối tượng như đã nêu.

Phần ý tưởng đã khá chi tiết, dù sao cũng chỉ là ý tưởng của mình mà thui. Bạn sẽ hiểu kĩ hơn khi đọc các đoạn Code bên dưới.
Class cơ sở
Chứa các thuộc tính chung nhất và phương thức truy cập (đọc/ghi) .
Các thuộc tính chung của các đối tượng Game (chim/ ống nước) bao gồm:
- Vị trí : tọa độ x,y
- Sự tồn tại.
- Hướng.(đang di chuyển theo hướng nào)
Class “cột”
Ý mình “cột” là các cột ống nước.
Mỗi cột là một bộ ống nước xếp trên dưới . (Tuy vẽ là 2 ống nước nhưng chỉ quy định là 1 đối tượng mà thôi).
Phương thức
- Vẽ/xóa cột: vẽ và xóa hình ảnh trên màn hình.
- Tạo cột: đặt một độ cao Random() khi nó quay về điểm xuất phát.
- Tạo một biến có tên “met” (mét) để đo quãng đường di chuyển của các ống nước, chúng ta dãn khoảng cách và xét tồn tại dựa vào quãng đường “met” này.
- Gọi Serial.println(met); // để kiểm tra mét
- Gọi Serial.print(chiec_cot[i].get_ton_tai()); // để kiểm tra sự tồn tại của các đối tượng
Code tạo 3 cột di chuyển auto trên màn hình
#include "ST7565_homephone.h"
// add a bitmap library of a 16x16 fruit icon
#include "bmps.h"
ST7565 lcd(3, 4, 5, 6);
//cài đặt chân input
#define fight_b A5
/*
#define select_b A4
#define right_b A3
#define up_b A2
#define left_b A1
#define down_b A0*/
#ifdef __AVR__
#include <avr/io.h>
#include <avr/pgmspace.h>
#endif
unsigned int met; // quãng đường đi được
// Chương trình chạy 1 lần
void setup()
{
Serial.begin(9600);
lcd.ON();
lcd.SET(23, 0, 0, 0, 4);
pinMode(fight_b, INPUT_PULLUP);
lcd.clear();
}
class object {
// lớp cơ sở
public:
// biến toàn cục (dùng ở mọi nơi)
byte Xmin = 0, Ymin = 0, Xmax = 125, Ymax = 63; //biên
byte cot_max = 3; // số lượng cột trên màn hình
byte do_rong = 21; //khoảng cách 2 cột trên và dưới
// tập hàm ghi thông tin
void set_ton_tai(boolean value) { ton_tai = value; }
void set_x(byte value) { x = value; }
void set_y(byte value) { y = value; }
void set_huong(byte value) { huong = value; }
//tập hàm lấy thông tin
boolean get_ton_tai() { return ton_tai; }
byte get_x() { return x; }
byte get_y() { return y; }
byte get_huong() { return huong; }
private:
// chỉ 2 tập hàm thành viên viên trên mới sử đụng được 4 tham số này:
boolean ton_tai;
byte x;
byte y;
byte huong;
};
/*
class bird: public object{//kế thừa lớp object
//...........
};//class
*/
class cot : public object { // kế thùa lớp object
public:
void ve_cot(byte x, byte y, byte do_rong)
{
//do_rong là khoảng cách của 2 cột trên-dưới
lcd.rect(x, Ymin, 7, y - Ymin, BLACK); //vẽ cột trên
lcd.rect(x, y + do_rong, 7, Ymax - y, BLACK); //vẽ cột dưới
lcd.display();
}
void xoa_cot(byte x, byte y, byte do_rong)
{
lcd.rect(x, Ymin, 7, y - Ymin, WHITE);
lcd.rect(x, y + do_rong, 7, Ymax - y, WHITE);
}
cot()
{
// tạo đối tượng bằng constructor
}
void tao_cot()
{
//tạo random với tham số truyền vào là đồng hồ hệ thống
unsigned int seed;
seed = millis() % 1000;
randomSeed(seed);
byte y_random;
y_random = random((Ymin + 10), (Ymax - 10));
//lưu thông tin cho cột "đó"
set_x(Xmax); //hoành độ ban đầu
set_y(y_random); //tung độ đầu
set_ton_tai(1); //cấp tồn tại
}
void di_chuyen_cot()
{ // chính cột đó
//vẫn tồn tại
//lấy thông tin
byte x_cu = get_x(), y_cu = get_y(), x_moi; //tọa độ cũ-mới
// dịch sang bên trái
x_moi = x_cu - 1;
//xóa hình cũ
xoa_cot(x_cu, y_cu, do_rong);
//vẽ hình mới
ve_cot(x_moi, y_cu, do_rong);
//lưu thông tin
set_x(x_moi);
if (get_x() == 250) {
// nếu cột đi đến biên trái màn hình
//thì không cho cột chạy nữa
// x nhỏ nhất là 0, tuy nhiên nếu tiếp tục trừ x thì nó sẽ đém từ 254 ->0
// đây là hiện tượng tràn số
set_ton_tai(0); // tước quyền tồn tại
}
} //di chuyen cot
}; //class
object data; // lấy biến toàn cục
cot chiec_cot[3]; //tạo cột
void loop()
{
for (byte i = 0; i < data.cot_max; i++) {
if ((chiec_cot[i].get_ton_tai() == 0)) {
if (met >= (127 / data.cot_max)) {
//mõi cột được tạo khi đi được 1 đoạn cố định:127/(data.cot_max),
//xắp sếp đều trên màn hình
chiec_cot[i].tao_cot();
met = 0; // dat lai
}
}
else {
chiec_cot[i].di_chuyen_cot();
}
Serial.print(chiec_cot[i].get_ton_tai());
}
delay(10);
Serial.println(met);
met++;
}
Kiểm tra một vài thông số

OK, thuật toán của chúng ta có tính tổng quát, ta có thể tùy chỉnh số lượng ống nước.
Ví dụ trên Cot_max = 3, ở đây mình cài Cot_max = 10 xem như thế nào ^^.
Đến dòng 37 sửa Cot_max=10; Đến dòng 124 sửa khởi tạo đối tượng bằng 10 ;


Kết quả

Có vẻ ARDUINO của chúng ta đã hơi Lag? Điều này có thể dễ hiểu vì nó phải xử lý 10 đối tượng cùng lúc !
Không sao, đó chỉ là vấn đề cấu hình máy chơi Game mà thôi.
Chúng ta có thể tùy chỉnh tốc độ chuyển khung hình bằng cách sửa lại giá trị của Delay();
Xem Serial có gì nào :

Class “bird”
Cũng giống như class “cot”, class”bird” sẽ kế thừa từ class cơ sở những thuộc tính chung nhất.
Các phương thức
- Vẽ / xóa ảnh bitmap.
- chim_bay(): dựa vào các thông số cũ về hướng và tọa độ để cho chim bay.
- dk_chim(): nhấn nút ấn để điều khiển hướng ( đặt hướng cho phương thức chim_bay())
- “Sự tồn tại” của chim là thuộc tính giúp ta kiểm soát tiếp tục Game / đặt “GAME OVER”.
Chim bay lên :
Chim bay xuống:
Code
#include "ST7565_homephone.h"
ST7565 lcd(3, 4, 5, 6);
//cài đặt chân input
#define fight_b A5
// Chương trình chạy 1 lần
void setup()
{
lcd.ON();
lcd.SET(21, 0, 0, 0, 4);
pinMode(fight_b, INPUT_PULLUP);
}
#ifdef __AVR__
#include <avr/io.h>
#include <avr/pgmspace.h>
#endif
const static unsigned char __attribute__((progmem)) bird_up_15x8[] = {
0x30, 0x58, 0x5E, 0xFB, 0x81, 0x85, 0x9B, 0x91, 0x91, 0x7D, 0x71, 0x7E, 0x60, 0x60, 0x60,
};
const static unsigned char __attribute__((progmem)) bird_down_15x8[] = {
0x1E, 0x32, 0x22, 0x6E, 0x99, 0x85, 0x9B, 0x91, 0x91, 0x7D, 0x71, 0x7E, 0x60, 0x60, 0x60,
};
class object {
// lớp cơ sở
public:
// biến toàn cục (dùng ở mọi nơi)
byte Xmin = 0, Ymin = 0, Xmax = 125, Ymax = 63; //biên
// tập hàm ghi thông tin
void set_ton_tai(boolean value) { ton_tai = value; }
void set_x(byte value) { x = value; }
void set_y(byte value) { y = value; }
void set_huong(byte value) { huong = value; }
//tập hàm lấy thông tin
boolean get_ton_tai() { return ton_tai; }
byte get_x() { return x; }
byte get_y() { return y; }
byte get_huong() { return huong; }
private:
// chỉ 2 tập hàm thành viên viên trên mới sử đụng được 4 tham số này:
boolean ton_tai;
byte x;
byte y;
byte huong;
};
class bird : public object { //kế thừa lớp object
public:
bird()
{
// hàm tạo constructor
set_x((Xmax - Xmin) / 3); //hoành độ ban đàu
set_y(Ymin + 20); // tung độ ban đàu
set_huong(4); // hướng ban đầu(xuống)
}
//vẽ chim
void ve_chim(byte x, byte y, byte huong)
{
if (huong == 2) {
//hướng lên
lcd.bitmap(x, y, 15, 8, bird_up_15x8, BLACK);
}
if (huong == 4) {
//hướng xuống
lcd.bitmap(x, y, 15, 8, bird_down_15x8, BLACK);
//xoay ảnh 270 độ
}
lcd.display();
} //
void xoa_chim(byte x, byte y)
{
lcd.fillrect(x, y, 15, 8, DELETE);
lcd.display();
} //
void chim_bay()
{
//lấy thông tiin
byte x_cu = get_x(), y_cu = get_y(), huong_cu = get_huong();
byte x_moi, y_moi, huong_moi;
if (huong_cu == 2) {
//bay lên
y_moi = y_cu - 4;
}
if (huong_cu == 4) {
//bay xuống
y_moi = y_cu + 2;
}
//xóa ảnh cũ:
xoa_chim(x_cu, y_cu);
//vẽ ảnh mới
ve_chim(x_cu, y_moi, huong_cu);
//lưu thông tin
set_y(y_moi);
}
void dk_chim()
{
//điều khiển chim
byte bay_len;
bay_len = digitalRead(fight_b); // đọc giá trị button
if (bay_len == 0) { //nhấn nút fight
//bay lên
//ghi thông tin
set_huong(2);
}
if (bay_len == 1) { //ko nhấn
//bay lên
//ghi thông tin
set_huong(4);
}
//cho chim bay
chim_bay();
}
}; //class bird
bird con_chim; // tạo thực thể con_chim
void loop()
{
con_chim.dk_chim();
delay(200);
}
TEST VA CHẠM VÀ HOÀN THIỆN GAME
Bước cuối là xét va chạm.
Vì có chúng ta có ít đối tượng ống nước (từ 3 -> 10 cột tùy bạn) nên chúng ta sẽ xét cả thảy tọa độ và độ cao của tất cả các ống nước với chim.
Copy đoạn code này và chơi thôi !
//GAME FLAPPY BIRD
// Viết bởi: Thái Sơn , 6/7/2016
// Chia sẻ tại arduino.vn - 3/2/2017
#include "ST7565_homephone.h"
// add a bitmap library of a 16x16 fruit icon
#include "bmps.h"
#ifdef __AVR__
#include <avr/io.h>
#include <avr/pgmspace.h>
#endif
ST7565 lcd(3, 4, 5, 6);
//cài đặt chân input
#define fight_b A5
/*
#define select_b A4
#define right_b A3
#define up_b A2
#define left_b A1
#define down_b A0*/
const static unsigned char __attribute__((progmem)) bird_up_15x8[] = {
0x30, 0x58, 0x5E, 0xFB, 0x81, 0x85, 0x9B, 0x91, 0x91, 0x7D, 0x71, 0x7E, 0x60, 0x60, 0x60,
};
const static unsigned char __attribute__((progmem)) bird_down_15x8[] = {
0x1E, 0x32, 0x22, 0x6E, 0x99, 0x85, 0x9B, 0x91, 0x91, 0x7D, 0x71, 0x7E, 0x60, 0x60, 0x60,
};
unsigned int met; // quãng đường đi được
// Chương trình chạy 1 lần
void setup()
{
lcd.ON();
lcd.SET(23, 0, 0, 0, 4);
pinMode(fight_b, INPUT_PULLUP);
lcd.clear();
start_game();
}
//=================================================
class object {
// lớp cơ sở
public:
// biến toàn cục (dùng ở mọi nơi)
byte Xmin = 0, Ymin = 0, Xmax = 125, Ymax = 63; //biên
byte cot_max = 4; // số lượng cột trên màn hình
byte do_rong = 28; //khoảng cách 2 cột trên và dưới
static unsigned int diem, diem_cao; // biến tĩnh
// tập hàm ghi thông tin
void set_ton_tai(boolean value) { ton_tai = value; }
void set_x(byte value) { x = value; }
void set_y(byte value) { y = value; }
void set_huong(byte value) { huong = value; }
//tập hàm lấy thông tin
boolean get_ton_tai() { return ton_tai; }
byte get_x() { return x; }
byte get_y() { return y; }
byte get_huong() { return huong; }
private:
// chỉ 2 tập hàm thành viên viên trên mới sử đụng được 4 tham số này:
boolean ton_tai;
byte x;
byte y;
byte huong;
};
unsigned int object::diem = 0;
unsigned int object::diem_cao = 0;
//=================================================
class bird : public object { //kế thừa lớp object
public:
bird()
{ // hàm tạo constructor
tao_chim();
}
void tao_chim()
{
set_x((Xmax - Xmin) / 3); //hoành độ ban đàu
set_y(Ymin + 20); // tung độ ban đàu
set_huong(4); // hướng ban đầu(xuống)
set_ton_tai(1);
}
//vẽ chim
void ve_chim(byte x, byte y, byte huong)
{
if (huong == 2) {
lcd.bitmap(x, y, 15, 8, bird_up_15x8, BLACK);
}
if (huong == 4) {
lcd.bitmap(x, y, 15, 8, bird_down_15x8, BLACK);
}
lcd.display();
} //
void xoa_chim(byte x, byte y)
{
lcd.fillrect(x, y, 15, 8, DELETE);
lcd.display();
} //
void chim_bay()
{
if (get_ton_tai() == 0) {
tao_chim();
}
else {
//lấy thông tiin
byte x_cu = get_x(), y_cu = get_y(), huong_cu = get_huong();
byte x_moi, y_moi, huong_moi;
if (huong_cu == 2) {
y_moi = y_cu - 4;
} // bay lên
if (huong_cu == 4) {
y_moi = y_cu + 2;
} //bay xuống
xoa_chim(x_cu, y_cu); //xóa ảnh cũ:
ve_chim(x_cu, y_moi, huong_cu); //vẽ ảnh mới
set_y(y_moi); //lưu thông tin
}
}
void dk_chim()
{ //điều khiển chim
byte bay_len;
bay_len = digitalRead(fight_b); // đọc giá trị button
if (bay_len == 0) {
set_huong(2);
}
if (bay_len == 1) {
set_huong(4);
}
chim_bay(); //cho chim bay
}
}; //class
//================================================
class cot : public object { // kế thùa lớp object
public:
void ve_cot(byte x, byte y, byte do_rong)
{
//do_rong là khoảng cách của 2 cột trên-dưới
lcd.rect(x, Ymin, 7, y - Ymin, BLACK); //vẽ cột trên
lcd.rect(x, y + do_rong, 7, Ymax - y, BLACK); //vẽ cột dưới
lcd.display();
}
void xoa_cot(byte x, byte y, byte do_rong)
{
lcd.rect(x, Ymin, 7, y - Ymin, DELETE);
lcd.rect(x, y + do_rong, 7, Ymax - y, DELETE);
lcd.display();
}
cot()
{
// tạo đối tượng bằng constructor
}
void tao_cot()
{
//tạo random với tham số truyền vào là đồng hồ hệ thống
unsigned int seed;
seed = millis() % 1000;
randomSeed(seed);
byte y_random;
y_random = random((Ymin + 10), (Ymax - 10));
//lưu thông tin cho cột "đó"
set_x(Xmax); //hoành độ ban đầu
set_y(y_random); //tung độ đầu
set_ton_tai(1); //cấp tồn tại
}
void di_chuyen_cot()
{ // chính cột đó
//lấy thông tin
byte x_cu = get_x(), y_cu = get_y(), x_moi; //tọa độ cũ-mới
// dịch sang bên trái
x_moi = x_cu - 1;
xoa_cot(x_cu, y_cu, do_rong);
ve_cot(x_moi, y_cu, do_rong);
set_x(x_moi);
if (get_x() == 250) {
set_ton_tai(0); // tước quyền tồn tại
}
} //di chuyen cot
boolean va_cham(object& chim)
{
//hàm kiểm tra chạm của cột i với chim
//lấy thông tin
int hieu_x, hieu_y;
hieu_x = chim.get_x() - get_x(); // x chim trừ x cột i, 12 là độ rộng ảnh bitmap
hieu_y = chim.get_y() - get_y(); // y chim trừ y cột i
// và nếu chim không rơi xuống đất thì cũng ko có va chạm
if ((hieu_x >= (-12)) && (hieu_x <= 7)) { // chim đang bay trong cột i
//(12 là độ rộng ảnh bitmap, 7 là đường kính cột)
if ((hieu_y > 0) && (hieu_y < (do_rong - 8))) {
// chim đang bay trong vùng không có cột
return 0; // không va trạm
}
else {
return 1; // trả về có
}
}
else { // chim không bay trong cột i
return 0; // không va chạm
}
} // va cham
}; //class
//======================================================
class computer : public object { // lớp này để tính toán
public:
void in_diem()
{
if (diem >= diem_cao) {
diem_cao = diem;
}
lcd.number_ulong(Xmin + 5, 0, diem, ASCII_NUMBER, BLACK);
lcd.display();
lcd.number_ulong(Xmax - 15, 0, diem_cao, ASCII_NUMBER, BLACK);
lcd.display();
}
void lap_vo_han()
{
// dừng dòng chảy chính bằng vòng lặp vô hạn
//thoát lặp khi nút Fight được nhấn
int y = 55;
lcd.Asc_String(10, y + 2, Asc("Fight!"), BLACK);
lcd.display();
while (digitalRead(fight_b) != 0) {
lcd.rect(8, 55, 40, 10, BLACK);
lcd.display();
if (digitalRead(fight_b) == 0) {
break; // thoát ngay
}
delay(250);
lcd.rect(8, y, 40, 10, WHITE);
lcd.display();
if (digitalRead(fight_b) == 0) {
break; // thoát ngay
}
delay(250);
}
} //đóng lặp vô hạn
void gameover()
{
//reset điểm
diem = 0;
// chờ nhấn nut fight
lap_vo_han();
//reset màn hình
lcd.clear();
}
};
//============================================
computer co;
bird con_chim; // tạo thực thể con_chim
cot chiec_cot[4]; //tạo cột
object data; // lấy biến toàn cục
void loop()
{
//di chuyển chim
con_chim.dk_chim();
//di chuyển cột
for (byte i = 0; i < data.cot_max; i++) {
if ((chiec_cot[i].get_ton_tai() == 0)) {
if (met >= (127 / data.cot_max)) {
//mõi cột được tạo khi đi được 1 đoạn cố định:127/(data.cot_max), xắp sếp đều trên màn hình
chiec_cot[i].tao_cot();
met = 0; // dat lai
}
}
else {
chiec_cot[i].di_chuyen_cot();
}
// mở rộng hàm for
////////////va chạm và tính điểm//////////////
//khi hoành độ của chim == hoành độ cột i, cộng điểm 1 điểm
if (con_chim.get_x() == chiec_cot[i].get_x()) {
data.diem++;
}
// nếu có va chạm hoặc chim chạm đát thì gameover
if ((chiec_cot[i].va_cham(con_chim) == 1) || (con_chim.get_y() >= data.Ymax)) {
co.gameover();
con_chim.set_ton_tai(0);
for (byte i = 0; i < data.cot_max; i++) {
chiec_cot[i].set_ton_tai(0);
chiec_cot[i].set_x(0);
}
break;
}
// hiển thj điểm
} //for
co.in_diem();
delay(10);
met++;
}
void start_game()
{
lcd.rect(8, 22, 118, 11, BLACK);
lcd.Asc_String(10, 24, Asc("A R D U I N O . V N"), BLACK);
lcd.rect(58, 54, 70, 11, BLACK);
lcd.Asc_String(60, 55, Asc("By Thai Son"), BLACK);
lcd.display();
co.lap_vo_han();
lcd.clear();
}Kết
Game là để giải trí, với Flappy Bird, nó còn cho ta một thông điệp đáng quý trong cuộc sống về sự kiên trì: “Có công mài sắt, có ngày nên kim”
Chúc các bạn gặt hái được nhiều kĩ năng với C++ và Gaming với arduino 

![]()

<Thái Sơn>




