[Arduino] 一個完整的俄羅斯方塊遊戲範例
-
範例打包在此:wbTetrisV2.zip
LCD 圖型處理 Library 打包在此:Adafruit-GFX-Library.zip Adafruit-ST7735-Library-WiFiboy.zip
原始程式:wbTetrisV2.ino
<p>/*************************************************** Tetris Demo -- WiFiBoy for Arduino v1.0 -- Sep 28, 2016 tklua@wifiboy.org v1.01 -- Oct 28, 2016 for WiFiBoy v1.1 new key-def v1.02 -- Jun 5, 2017 for WiFiBoy v2 ****************************************************/</p> <p>#include <Adafruit_GFX.h> // Core graphics library #include <Adafruit_ST7735.h> // Hardware-specific library, optimized for WiFiBoy #include <SPI.h> #include <Ticker.h></p> <p>#define TFT_CS 15 #define TFT_DC 2</p> <p>Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC);</p> <p>#include "wbTetrisMelody.h" #include "wbTetrisColors.h"</p> <p>char blk_t[28][4] = { // seven shapes {0,10,20,30},{0,1,2,3},{0,10,20,30},{0,1,2,3}, {1, 10, 11, 12},{0,10,11,20},{0,1,2,11},{1,10,11,21}, {0, 1, 10, 11},{0,1,10,11},{0,1,10,11},{0,1,10,11}, {0,10,20, 21},{0,1,2,10},{0,1,11,21},{2,10,11,12}, {1,11,20,21},{0,10,11,12},{0,1,10,20},{0,1,2,12}, {0,1,11,12},{1,10,11,20},{0,1,11,12},{1,10,11,20}, {1,2,10,11},{0,10,11,21},{1,2,10,11},{0,10,11,21} }; char board[20][10], offboard[20][10]; uint16 cx, cy, rot, smode, fall_time, fall_limit, stage_limit; uint16 ctype, nctype, level, pts, pn, i, j, k, pos, px, py, last_key, cline; char levelspeed[12]={12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1};</p> <p>void ICACHE_FLASH_ATTR draw_preview() { tft.fillRect(85, 35, 28, 28, wbBLACK); for(i=0; i<4; i++) { pos = blk_t[nctype*4][i]; py = pos/10; px = pos%10; tft.fillRect(85+px*7, 35+py*7, 6, 6, bcolor[nctype+1]); } }</p> <p>void ICACHE_FLASH_ATTR update_score() { tft.fillRect(85, 80, 50, 15, wbBLACK); draw_number(cline, 85, 80, 1); if (cline > 150) level = 9; else if (cline > 120) level=8; else if (cline > 100) level=7; else if (cline > 80) level=6; else if (cline > 50) level=5; else if (cline > 25) level=4; else if (cline > 15) level=3; else if (cline > 10) level=2; else if (cline > 5) level=1; fall_limit = stage_limit = levelspeed[level]; tft.fillRect(85, 110, 50, 15, wbBLACK); draw_number(level+1, 85, 110, 1); tft.fillRect(85, 140, 50, 15, wbBLACK); draw_number(pts, 85, 140, 1); }</p> <p>int ICACHE_FLASH_ATTR check_line() { int pt; for(i=19; i>0; i--) { pt=0; for(j=0; j<10; j++) if (board[i][j]!=0) pt++; else break; if (pt==10) { for(j=i; j>0; j--) for(k=0; k<10; k++) board[j][k]=board[j-1][k]; return 1; } } return 0; }</p> <p>void ICACHE_FLASH_ATTR bfall() { for(i=0; i<4; i++) { pos = (cy+1)*10 + cx + blk_t[ctype*4+rot][i]; py = pos/10; px = pos%10; if (pos < 200) { board[py-1][px]=0; } else { for(j=0; j<4; j++) { pos = (cy)*10 + cx + blk_t[ctype*4+rot][j]; py = pos/10; px = pos%10; board[py][px]=ctype+1; } pn=0; while(check_line()) { // check all stacked line cline++; update_score(); pts+=10*(2^pn); pn++; } if (pn) { sfxn=5; sfxc=0; } else { sfxn=2; sfxc=0; } ctype=nctype; nctype=rand()%7; pts+=10; cx = 4; cy = 0; rot=rand()%4; for(j=0; j<4; j++) { pos = (cy)*10 + cx + blk_t[ctype*4+rot][j]; py = pos/10; px = pos%10; board[py][px]=ctype+1; } draw_preview(); return; } } cy++; for(i=0; i<4; i++) { pos = (cy)*10 + cx + blk_t[ctype*4+rot][i]; py = pos/10; px = pos%10; if (board[py][px]!=0) { for(j=0; j<4; j++) { pos = (cy-1)*10 + cx + blk_t[ctype*4+rot][j]; py = pos/10; px = pos%10; board[py][px]=ctype+1; } if (cy==1) { smode=2; tft.fillRect(2, 55, 77, 27, wbRED); tft.fillRect(5, 58, 71, 21, wbBLACK); tft.setTextColor(wbWHITE); draw_string("Game Over", 14, 65, 1); sfxn=6; sfxc=0; return; } pn=0; while(check_line()) { cline++; update_score(); pts+=10*(2^pn); pn++; } if (pn) { sfxn=5; sfxc=0; } else { sfxn=2; sfxc=0; } ctype=nctype; nctype=rand()%7; pts+=10; cx = 4; cy = 0; rot=rand()%4; for(j=0; j<4; j++) { pos = (cy)*10 + cx + blk_t[ctype*4+rot][j]; py = pos/10; px = pos%10; board[py][px]=ctype+1; } draw_preview(); return; } } for(i=0; i<4; i++) { pos = (cy)*10 + cx + blk_t[ctype*4+rot][i]; py = pos/10; px = pos%10; board[py][px]=ctype+1; } }</p> <p>void ICACHE_FLASH_ATTR check_left() { for(i=0; i<4; i++) { pos = cy*10 + cx-1 + blk_t[ctype*4+rot][i]; px = pos%10; if (px>cx+4) return; //left bound } for(i=0; i<4; i++) { pos = cy*10 + cx + blk_t[ctype*4+rot][i]; py = pos/10; px = pos%10; board[py][px]=0; // clear block } for(i=0; i<4; i++) { pos = cy*10 + cx-1 + blk_t[ctype*4+rot][i]; py = pos/10; px = pos%10; if (board[py][px]!=0) { for(j=0; j<4; j++) { pos = cy*10 + cx + blk_t[ctype*4+rot][j]; py = pos/10; px = pos%10; board[py][px]=ctype+1; } return; } } cx--; for(i=0; i<4; i++) { pos = cy*10 + cx + blk_t[ctype*4+rot][i]; py = pos/10; px = pos%10; board[py][px]=ctype+1; } }</p> <p>void ICACHE_FLASH_ATTR check_right() { for(i=0; i<4; i++) { pos = (cy)*10 + cx+1 + blk_t[ctype*4+rot][i]; py = pos/10; px = pos%10; if (px>cx-1) { board[py][px-1]=0; } else { for(j=0; j<4; j++) { pos = (cy)*10 + cx + blk_t[ctype*4+rot][j]; py = pos/10; px = pos%10; board[py][px]=ctype+1; } return; } } for(i=0; i<4; i++) { pos = (cy)*10 + cx+1 + blk_t[ctype*4+rot][i]; py = pos/10; px = pos%10; if (board[py][px]!=0) { for(j=0; j<4; j++) { pos = (cy)*10 + cx + blk_t[ctype*4+rot][j]; py = pos/10; px = pos%10; board[py][px]=ctype+1; } return; } } cx++; for(i=0; i<4; i++) { pos = (cy)*10 + cx + blk_t[ctype*4+rot][i]; py = pos/10; px = pos%10; board[py][px]=ctype+1; } }</p> <p>int ICACHE_FLASH_ATTR check_rotate() { for(i=0; i<4; i++) { pos = cy*10 + cx + blk_t[ctype*4+rot][i]; py = pos/10; px = pos%10; if (pos<200) { if (board[py][px]!=0) return -2; if (px < cx) { cx--; return -1; } if (px > cx+4) { cx++; return -1; } } } return 0; } uint16 color1, color2, color3, color4, sa_count;</p> <p>void splash_animation() { color4 = color3; color3 = color2; color2 = color1; color1 = bcolor[rand()%7+1]; // scrolling effect tft.fillRect(49, 65, 14, 14, color1); tft.fillRect(49, 80, 14, 14, color2); tft.fillRect(49, 95, 14, 14, color3); tft.fillRect(64, 95, 14, 14, color4); }</p> <p>void draw_number(uint16_t num, uint8_t x, uint8_t y, uint8_t size) { tft.setCursor(x, y); tft.setTextSize(size); tft.print(num); }</p> <p>void draw_string(char *str, uint8_t x, uint8_t y, uint8_t size) { tft.setCursor(x, y); tft.setTextSize(size); tft.print(str); }</p> <p>void draw_splash() { tft.fillRect(0, 0, 128, 160, wbBLACK); tft.setTextColor(wbWHITE); draw_string("WiFiBoy for Arduino", 7, 0, 1); tft.setTextColor(wbYELLOW); draw_string("Tetris", 10, 25, 3); tft.setTextColor(wbGREEN); //draw_string("one", 1, 132, 1); //draw_string("two", 104, 132, 1); draw_string("Play", 1, 144, 1); //draw_string("players", 82, 144, 1); tft.fillRect(49, 65, 14, 14, color1=wbRED); tft.fillRect(49, 80, 14, 14, color2=wbYELLOW); tft.fillRect(49, 95, 14, 14, color3=wbBLUE); tft.fillRect(64, 95, 14, 14, color4=wbGREEN); sa_count = 0; }</p> <p>void draw_board() { for(i=0; i<20; i++) for(j=0; j<10; j++) if (offboard[i][j]!=board[i][j]) // dirty rectangle check tft.fillRect(j*8+1,i*8,7,7, bcolor[offboard[i][j]=board[i][j]]); }</p> <p>void refresh_cb() { switch(smode) { case 0: // for splash sa_count++; if (sa_count>15) { splash_animation(); sa_count=0; } break; case 1: draw_board(); break; // update board (dirty-rectangle argorithm) case 2: break; // end of game default: break; } }</p> <p>void clear_board() { for(i=0; i<20; i++) for(j=0; j<10; j++) board[i][j]=0; }</p> <p>void gameloop_cb() { uint16 key; int ret;</p> <p> switch(smode) { case 0: // game menu key=analogRead(A0)/240; if (key==1) key=2; if (digitalRead(5)==0) key=1; if (digitalRead(0)==0) key=3; switch(key) { case 2: case 1: break; case 0: // game start smode=1; tft.fillRect(0, 0, 128, 160, wbBLACK); tft.drawFastHLine(0,159,80,wbRED); tft.drawFastVLine(0,0,160,wbRED); tft.drawFastVLine(80,0,160,wbRED); tft.setTextColor(wbWHITE); draw_string("lines", 84, 65, 1); draw_string("level", 84, 95, 1); draw_string("score", 84, 125, 1); tft.setTextColor(wbYELLOW); draw_string("Tetris", 85, 0, 1); draw_string("Game", 105, 12, 1); cline=0; level=0; pts=0; fall_limit = stage_limit = levelspeed[level]; cx=4; cy=0; rot=rand()%4; ctype=rand()%7; nctype=rand()%7; for(i=0; i<4; i++) { pos = (cy)*10 + cx + blk_t[ctype*4+rot][i]; py = pos/10; px = pos%10; board[py][px]=ctype+1; // draw blocks } last_key=0; draw_preview(); update_score(); analogWrite(4,500); // enable pwm, start sound engine melody_c=0; break; case 3: analogWrite(4,0); // disable pwm, stop sound engine break; } break; case 1: // game playing key=analogRead(A0)/240; if (key==1) key=2; if (digitalRead(5)==0) key=1; if (digitalRead(0)==0) key=3; switch(key) { case 2: last_key=1; break; case 1: last_key=2; break; case 0: fall_limit=1; last_key=4; break; case 3: last_key=3; break; case 4: // release all keys switch (last_key) { case 1: check_left(); last_key=0; fall_limit=stage_limit; break; case 2: check_right(); last_key=0; fall_limit=stage_limit; break; case 3: for(i=0; i<4; i++) { pos = cy*10 + cx + blk_t[ctype*4+rot][i]; py = pos/10; px = pos%10; board[py][px]=0; // clean blocks } rot++; if (rot>3) rot=0; while(1) { ret = check_rotate(); if (ret==-2) { rot++; if (rot>3) rot=0; } else if (ret==0) break; // pick next legal rotation } for(i=0; i<4; i++) { pos = cy*10 + cx + blk_t[ctype*4+rot][i]; py = pos/10; px = pos%10; board[py][px]=ctype+1; // draw blocks } last_key=0; break; case 4: fall_limit=stage_limit; last_key=0; break; default: break; } break; } fall_time++; if (fall_time > fall_limit) { bfall(); fall_time=0; } break; case 2: // end of game key=analogRead(A0)/240; if (digitalRead(0)==0) key=3; switch(key) { case 0: case 1: case 2: break; case 3: smode=0; clear_board(); draw_splash(); analogWrite(4,0); melody_c=0; break; } break; } }</p> <p>Ticker soundEngine; // melody and sound-effect sequencer (player) Ticker gameLooper; // game state controller Ticker Refresher; // display controller</p> <p>inline uint16_t swapcolor(uint16_t x) { return (x << 11) | (x & 0x07E0) | (x >> 11); }</p> <p>void setup() { tft.initR(INITR_GREENTAB); // this means ST7735S driver used in WiFiBoy tft.fillScreen(ST7735_BLACK); tft.setTextWrap(false); draw_splash(); for(i=0; i<8; i++) bcolor[i] = swapcolor(bcolor[i]); soundEngine.attach_ms(50, melody_cb); analogWrite(4,0); // stop sound (pwm) no_music=0; no_sfx=0; gameLooper.attach_ms(20, gameloop_cb); // 50fps Refresher.attach_ms(20, refresh_cb); // 50fps pinMode(0, INPUT); pinMode(5, INPUT); }</p> <p>void loop() { // nothing to do here, we use Ticker to do concurrent jobs }</p>
其他檔案:
wbTetrisColor.h
<p>#define wbBLACK 0x0000 #define wbBLUE 0x001F #define wbRED 0xF800 #define wbGREEN 0x07E0 #define wbCYAN 0x07FF #define wbMAGENTA 0xF81F #define wbYELLOW 0xFFE0 #define wbWHITE 0xFFFF</p> <p>uint16 bcolor[8]={ wbBLACK, wbBLUE, wbRED, wbGREEN, wbCYAN, wbMAGENTA, wbYELLOW, wbWHITE };</p>
wbTetrisMelody.h
<p>#define NOTE_B0 31 #define NOTE_C1 33 #define NOTE_CS1 35 #define NOTE_D1 37 #define NOTE_DS1 39 #define NOTE_E1 41 #define NOTE_F1 44 #define NOTE_FS1 46 #define NOTE_G1 49 #define NOTE_GS1 52 #define NOTE_A1 55 #define NOTE_AS1 58 #define NOTE_B1 62 #define NOTE_C2 65 #define NOTE_CS2 69 #define NOTE_D2 73 #define NOTE_DS2 78 #define NOTE_E2 82 #define NOTE_F2 87 #define NOTE_FS2 93 #define NOTE_G2 98 #define NOTE_GS2 104 #define NOTE_A2 110 #define NOTE_AS2 117 #define NOTE_B2 123 #define NOTE_C3 131 #define NOTE_CS3 139 #define NOTE_D3 147 #define NOTE_DS3 156 #define NOTE_E3 165 #define NOTE_F3 175 #define NOTE_FS3 185 #define NOTE_G3 196 #define NOTE_GS3 208 #define NOTE_A3 220 #define NOTE_AS3 233 #define NOTE_B3 247 #define NOTE_C4 262 #define NOTE_CS4 277 #define NOTE_D4 294 #define NOTE_DS4 311 #define NOTE_E4 330 #define NOTE_F4 349 #define NOTE_FS4 370 #define NOTE_G4 392 #define NOTE_GS4 415 #define NOTE_A4 440 #define NOTE_AS4 466 #define NOTE_B4 494 #define NOTE_C5 523 #define NOTE_CS5 554 #define NOTE_D5 587 #define NOTE_DS5 622 #define NOTE_E5 659 #define NOTE_F5 698 #define NOTE_FS5 740 #define NOTE_G5 784 #define NOTE_GS5 831 #define NOTE_A5 880 #define NOTE_AS5 932 #define NOTE_B5 988</p> <p>uint16_t tetris[] = { NOTE_E4, NOTE_E1, NOTE_E4, NOTE_E2, NOTE_B3, NOTE_E1, NOTE_C4, NOTE_E2, NOTE_D4, NOTE_E1, NOTE_D4, NOTE_E2, NOTE_C4, NOTE_E1, NOTE_B3, NOTE_D2, NOTE_A3, NOTE_A1, NOTE_A3, NOTE_A2, NOTE_A3, NOTE_A1, NOTE_C4, NOTE_A2, NOTE_E4, NOTE_A1, NOTE_E4, NOTE_A2, NOTE_D4, NOTE_A1, NOTE_C4, NOTE_A2, NOTE_B3, NOTE_GS1, NOTE_B3, NOTE_GS2, NOTE_B3, NOTE_GS1, NOTE_C4, NOTE_GS2, NOTE_D4, NOTE_E1, NOTE_D4, NOTE_E2, NOTE_E4, NOTE_E1, NOTE_E4, NOTE_E2, NOTE_C4, NOTE_A1, NOTE_C4, NOTE_A1, NOTE_A3, NOTE_A1, NOTE_A3, NOTE_A2, NOTE_A3, NOTE_A1, NOTE_A3, NOTE_A2, NOTE_A3, NOTE_B1, NOTE_A3, NOTE_C2, NOTE_D4, NOTE_D1, NOTE_D4, NOTE_D2, NOTE_D4, NOTE_D1, NOTE_F4, NOTE_D2, NOTE_A4, NOTE_D1, NOTE_A4, NOTE_D2, NOTE_G4, NOTE_D1, NOTE_F4, NOTE_D2, NOTE_E4, NOTE_C1, NOTE_E4, NOTE_C2, NOTE_E4, NOTE_C1, NOTE_C4, NOTE_C2, NOTE_E4, NOTE_C1, NOTE_E4, NOTE_C2, NOTE_D4, NOTE_C1, NOTE_C4, NOTE_C2, NOTE_B3, NOTE_GS1, NOTE_B3, NOTE_GS2, NOTE_B3, NOTE_GS1, NOTE_C4, NOTE_GS2, NOTE_D4, NOTE_E1, NOTE_D4, NOTE_E2, NOTE_E4, NOTE_E1, NOTE_E4, NOTE_E2, NOTE_C4, NOTE_A1, NOTE_C4, NOTE_A2, NOTE_A3, NOTE_A1, NOTE_A3, NOTE_A2, NOTE_A3, NOTE_A1, NOTE_A3, NOTE_A2, NOTE_A3, NOTE_A1, NOTE_A3, NOTE_G1 };</p> <p>uint16_t sfx[8][8]={ {NOTE_C4, 0, 0, 0, 0, 0, 0, 0}, {NOTE_C4, NOTE_E4, NOTE_G4, NOTE_C5, 1, 1, 1, 1}, {NOTE_C2, NOTE_G2, 1, 1, 1, 1, 1, 1}, {NOTE_C3, 0, 0, 0, 0, 0, 0, 0}, {NOTE_C4, 0, 0, 0, 0, 0, 0, 0}, {NOTE_C3, NOTE_B2, NOTE_A2, NOTE_G2, NOTE_F2, NOTE_E2, 1, 1}, {NOTE_C4, NOTE_G4, NOTE_E4, NOTE_C4, NOTE_G3, NOTE_E3, NOTE_C3, 1}, {NOTE_B5, 0, 0, 0, 0, 0, 0, 0} };</p> <p>uint16_t melody_c; uint16_t sfxn, sfxc; uint16_t period; uint8_t no_sfx, no_music;</p> <p>void melody_cb() { if ((melody_c % 2)==1) { if (sfxn != 0) { period=50000/sfx[sfxn][sfxc]; sfxc++; if (sfxc>7 || period>10000) sfxn=0; //analogWrite(4,300); } else { period=10; } //analogWrite(4,0); } } else { period=tetris[melody_c/2]; }//analogWrite(4,300); }</p> <p> analogWriteFreq(period*1.5);</p> <p> if (++melody_c>255) melody_c=0; }</p>