|
。。。上一次和同學(xué)們(同學(xué)=一同學(xué)習(xí)者,同游=一同云游者,同居? 。。。 以此類推,不一而論)瞎聊了一點(diǎn)有關(guān)二進(jìn)制數(shù)的宏定義以及用 bit map 手工輸入點(diǎn)陣圖形的事(http://www.zg4o1577.cn/bbs/dpj-237651-1.html), 但沒有提及如何玩 51 單板機(jī)上那個(gè) 8x8 LED 矩陣。 BBC Micro:bit 上有一組 5 x 5 的LED 矩陣,官方很認(rèn)證地開發(fā)了一個(gè)"喜怒哀樂"表情包,意欲把那個(gè)稀缺資源的利用發(fā)揮到極致,考慮到Micro:bit不過是兒童的玩具,我就不把那個(gè)表情包port到51 單板機(jī)上了。對于認(rèn)真的程序員,把 PONG 這個(gè)古老的游戲port過來,以表示對前輩程序員的敬意,視乎更有一點(diǎn)意思?
。。。閑話少敘,我們單刀直入,先在那個(gè) 8x8LED 矩陣上實(shí)現(xiàn)單個(gè) LED 的動(dòng)畫,而后展開相應(yīng)的數(shù)據(jù)結(jié)構(gòu),以實(shí)現(xiàn)“乒乓球”和“乒乓板”之間的互動(dòng)。為此,我們來看看如何實(shí)現(xiàn)用(x,y)一對坐標(biāo)來實(shí)現(xiàn)對64個(gè)LED當(dāng)中任何一個(gè)LED的“尋址” 。。。 以下是N種方案當(dāng)中的一種:
u8 data bitBuffer []= {
b00111000, //x(7,0) *(7,7)
b01111100, // |
b01111110, // |
b00111111, // |
b00111111, // | *(x,y)
b01111110, // |
b01111100, // |
b00111000 //(0,0)-------->y(0,7)
};
按照單板機(jī)上具體的硬件設(shè)計(jì),我們可以把(x,y)映射為8x8LED矩陣的行列端口控制信號,以我手上那塊單板機(jī)為例(http://www.zg4o1577.cn/bbs/dpj-237407-1.html),其映射關(guān)系如下:
(x,y) --> ( LedMatrixPort_Col = ~(1<<(7-x)) ,74hc595_Dout = 1<<(7-y)) )
這里,x 和 y 的值域都是整數(shù)閉區(qū)間 [0,7].
如你想要點(diǎn)亮(x,y)位置上單獨(dú)一顆LED,調(diào)用以下函數(shù)即可:
//void _8x8ledMatrixShow(u8 x,u8 y) compact{
//
// LedMatrixPort_Col = ~(1<<(7-x));
// hc595_write_data(1<<(7-y));
// delay_10us(100); //延時(shí)一段時(shí)間,等待顯示穩(wěn)定
// hc595_write_data(0x00);//消影
//}
有了這個(gè)函數(shù),你可以讓這顆被點(diǎn)亮的LED在各種(數(shù)學(xué))軌跡上游走,實(shí)現(xiàn)任意動(dòng)畫效果, piece of cake?
細(xì)心的同學(xué)會(huì)注意到我把上述函數(shù)注釋屏蔽掉了,表示我在實(shí)際的PONG游戲代碼里并沒有使用這個(gè)函數(shù)。在游戲更新“乒乓球”位置的代碼中,我直接利用了(x,y)和行-列端口的映射關(guān)系,同時(shí)把球的位置信息和“乒乓板”的位置信息一起緩存在bitBuffer 里面,當(dāng)所有需要更新的數(shù)據(jù)都準(zhǔn)備好之后才一次性地“刷新” LED矩陣的顯示,這是幾乎所有游戲編程普遍使用的所謂double buffer 技術(shù),大家可以誰便參考一本游戲編程的書籍了解其原委,我不講了。
避免調(diào)用上述函數(shù),對于51 單片機(jī)這樣的系統(tǒng)還考慮到硬件資源問題。51單片機(jī)的片內(nèi) 數(shù)據(jù)RAM 非常貧乏,只能實(shí)現(xiàn)一個(gè)很淺的stack, 如果在函數(shù)調(diào)用時(shí)使用很多參數(shù),最糟的情況下(如遞歸調(diào)用帶參數(shù)的函數(shù)),stack 很容易o(hù)verflow, 導(dǎo)致程序崩潰。因此,大家在不影響程序代碼架構(gòu)清晰的情況下,盡量使用全局變量為上策。
okay,我們繼續(xù)PONG的編程。。。在游戲世界里,“球”和“板”都是所謂的 objetcs (東東? sprite ? whatever ),如果開發(fā)環(huán)境比較豪華,我們應(yīng)該用類似C++ 的class 來封裝這些東東的屬性和行為,但這里比較簡陋,我們就用struct 來簡單湊合一下吧。。。
typedef struct {
signed char pTop; // 球板的頂端 y 坐標(biāo)
signed char pShift; // 球板的頂端 x 坐標(biāo),缺省值為 7,如果<7, 表示迎著球的方向擊球,如果值不變,就是簡單的擋球。。。
u8 score; // 用于計(jì)分
} sPaddle;
sPaddle myPaddle; // 左側(cè)球板,手工通過按鈕實(shí)現(xiàn)上下移動(dòng)或由 MPU 判斷球的來路自動(dòng)移動(dòng)
sPaddle mpuPaddle; // 右側(cè)球板,由 MPU 判斷球的來路自動(dòng)移動(dòng)
struct {
signed char x, y; // 球的位置
signed char vx,vy; // 球的速度
}ball;
在游戲初始化時(shí),我們必須給這些東東一些初始值,例如:
if(bGameReStart)// 如果全局變量 bGameReStart ==1 , 從新開局
{
LED_PORT=0xff; // 熄滅 8x8 LED 矩陣
srand (rand()); // 隨機(jī)數(shù)下種, 要在main.c 開頭處加上 #inlude <stdlib.h> 才能調(diào)用 srand 和 rand 函數(shù)
myPaddle.pTop = 3; myPaddle.pShift = 0;myPaddle.score = 0;
mpuPaddle.pTop = rand() % 6;mpuPaddle.pShift = 7;mpuPaddle.score = 0;
ball.x = 6;
ball.y = myPaddle.pTop + rand() % 3; //ball start by human
ball.vx = 1 + rand()%2; // 相當(dāng)于 random(1,2);
ball.vy = -2 + rand()%5;// 相當(dāng)于 random(-2,2); avoid use function stack...
bGameReStart =0; // don't keep running the code inside {}
}
世上所有游戲的靈魂是隨機(jī)數(shù),包括你在“真實(shí)”世界里面玩現(xiàn)實(shí)版的“真人游戲”, 所以我對隨機(jī)數(shù)多啰嗦幾句。。。由于硬件隨機(jī)數(shù)發(fā)生器非常昂貴,大多數(shù)數(shù)字系統(tǒng)都采用軟件偽隨機(jī)數(shù),其原理大家自行wikipeida, 我只解釋上面的代碼里,我為何免用 random(上限,下限) 這樣的函數(shù),而是直接用 % 運(yùn)算符來取指定范圍的隨機(jī)數(shù)。
在<stdlib.h>庫里,rand() 的16位整數(shù)值域是 [0, 32767], random(上限,下限) 的定義通常類似以下形式:
unsigned int random(unsigned int lo,unsigned int hi) {
return ( lo + (hi - lo)* rand()/32768 );
}
或者,限于返回8位(signed)整數(shù):
signed char random(signed char lo, signed char hi){
return (lo + rand()%((hi-lo+1)) );
}
但這樣的函數(shù)定義都有參數(shù)調(diào)用的開銷,前一個(gè)還涉及“昂貴”的除法,這些都是資源匱乏的單板機(jī)需要盡量避免的。通常在C語言里,我們可以采用宏定義來重寫函數(shù)定義,調(diào)用時(shí)采用 inline 宏定義,這樣可以避免運(yùn)行時(shí)的開銷,把計(jì)算的負(fù)擔(dān)分配到編譯時(shí),由編譯器來代勞。同學(xué)們可以自行實(shí)驗(yàn)利用宏來修改上面的代碼,提高程序的可讀性。
接下來就是游戲的主循環(huán)。。。
L_GAME_START:
bGameReStart =1;
while(1)
{
key_matrix_flip_scan(key_value); // 4 x 4 按鈕矩陣掃描
if (key_value ==2 || key_value == 5 )OE_74HC595 = 1; //turn off 8x8 LED matrix
else OE_74HC595 = 0; // enable it otherwsie
switch (key_value)
{
case 1: // 按鈕- 1
_8x8ledMatrixDisplay(mPONG);
bGameReStart = 1; // 從新開始游戲
continue;
case mPADDLE_UPP-1:
case mPADDLE_UPP:
myPaddle.pShift = mPADDLE_UPP - key_value;
myPaddle.pTop -=2;
if (myPaddle.pTop < 0)myPaddle.pTop =0;
goto L_GAME_UPDATE;
case mPADDLE_UP-1:
case mPADDLE_UP:
myPaddle.pShift = mPADDLE_UP - key_value;
myPaddle.pTop -=1;
if (myPaddle.pTop < 0)myPaddle.pTop =0;
goto L_GAME_UPDATE;
case mPADDLE_DOWN-2:
case mPADDLE_DOWN-1:
case mPADDLE_DOWN:
myPaddle.pShift = mPADDLE_DOWN - key_value;
myPaddle.pTop +=1;
if (myPaddle.pTop > 4)myPaddle.pTop =5;
goto L_GAME_UPDATE;
case mPADDLE_DOWND-3:
case mPADDLE_DOWND-2:
case mPADDLE_DOWND-1:
case mPADDLE_DOWND:
myPaddle.pShift = mPADDLE_DOWND - key_value;
myPaddle.pTop +=2;
if (myPaddle.pTop > 4)myPaddle.pTop =5;
goto L_GAME_UPDATE;
case mUPDATE_PONG:
if(bGameReStart)// re-start game
{
// 游戲初始化代碼,前面已經(jīng)講解,此處不再重復(fù)
}
L_GAME_UPDATE:
_8x8clearBitBuffer(); // 清除緩沖區(qū)內(nèi)容
// 畫球板
bitBuffer[myPaddle.pShift] = b11100000;
bitBuffer[myPaddle.pShift] = bitBuffer[myPaddle.pShift]>>myPaddle.pTop;
// 更新球的位置
ball.x += ball.vx;
ball.y += ball.vy;
// 更新球板的位置
if (ball.vx > 0 ) // ball coming to human
{
// 如果 MPU 控制球板,計(jì)算球板如何判斷球路做相應(yīng)的移動(dòng)
}
if(myPaddle.pTop <0) myPaddle.pTop =0;
if (myPaddle.pTop > 4)myPaddle.pTop =5;
// 判斷球板是否接住球,沒有接住,對方就加分。。。
if(ball.x >= (7 - myPaddle.pShift) )
{
ball.x = 7;
ball.vx=-ball.vx; //左側(cè) X方向反彈
if( abs(ball.vy)<=1)
{
if ( ball.y < myPaddle.pTop || ball.y > (myPaddle.pTop+2))
{
mpuPaddle.score++; // 球板沒有擋住球,對方加分
}
}
else
{
// 等等其它判斷和計(jì)分邏輯 。。。
}
}
if( ball.y <=0 )
{
ball.y = 0; // 頂端 Y- 方向反彈
ball.vy=-ball.vy;
}
if( ball.y >= 7)
{
ball.y = 7; // 底端 Y- 方向反彈
ball.vy=-ball.vy;
}
if( ball.x <=0 )
{
ball.x = 0; //右側(cè) X方向反彈
ball.vx=-ball.vx;
}
bitBuffer[7-ball.x] = 128>>ball.y; // 把球的位置“寫” 入緩存區(qū)域
_8x8ledMatrixDisplay(bitBuffer); // 刷新 8x8 LED 顯示
key_value = mUPDATE_PONG; // repeat run mUPDATE_PONG section
// 在 7 段數(shù)碼管上 顯示計(jì)分
ired_buf[0]=gsmg_code[mpuPaddle.score];
ired_buf[3]=gsmg_code[myPaddle.score];
smg_display(ired_buf,1);
continue;
}
}
//END OF GAME
同學(xué)們可以看到我在這個(gè)循環(huán)里大大冒犯了一下 Goto 語句的批評者,我是實(shí)用主義者,goto 語句在這里的使用,既有效也不影響程序的可讀性。C就是高級的匯編語言啊,匯編語言大量使用各種 JMP 指令,C語言里適當(dāng)使用 goto 語句,大家可以理直氣壯。
這個(gè)循環(huán)的上面有一小段和 PZ 單板機(jī)設(shè)計(jì)缺陷有關(guān)的代碼:
if (key_value ==2 || key_value == 5 )OE_74HC595 = 1; //turn off 8x8 LED matrix
else OE_74HC595 = 0; // enable it otherwsie
板子的設(shè)計(jì)者提示玩家,如果要禁止 8x8 LED 矩陣,可以使用跳針,其實(shí)沒有這個(gè)必要,這個(gè)跳針設(shè)計(jì)是多余的。8x8 LED 矩陣的開關(guān),完全可以通過軟件加以控制。我在板子上加了一根飛線,用 sbit OE_74HC595 = P1^7 定義了其用法,接下來就可以用P1^7 端口控制74HC595芯片,從而實(shí)現(xiàn)8x8 LED 矩陣的開和關(guān),大家留意一下下面視頻和圖片里的飛線即可。
..... ...... ...... let's call it a day. .................. .................... ................... ........................ ........... .......... .............
20241008_081915.gif (7.16 MB, 下載次數(shù): 2)
下載附件
自動(dòng)回球
2024-10-8 14:55 上傳
20241008_081950.jpg (438.37 KB, 下載次數(shù): 1)
下載附件
飛線
2024-10-8 14:55 上傳
|
評分
-
查看全部評分
|