範例打包在此: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>