#include <LedControl.h>
#include <LiquidCrystal_I2C.h>
//執行迴圈用到的變數,r代表當前方塊的旋轉,c代表當前方塊的種類
int i, j, row, col, r, c;
//分別對應搖桿的X軸和Y軸,sw則為搖桿開關,按下開關sw為高電位
int x_axis, y_axis, sw = 2;
//LCD上顯示的分數
int score = 0;
//Led點矩陣的宣告,格式為(dataPin, clkPin, csPin, numDevices) numDevices代表有幾個矩陣要控制,設定為1即可
LedControl lc = LedControl(8, 9, 10, 1);
//LCD螢幕的宣告,格式為(LCD位址,行數,列數),LCD位址預設為0x27
LiquidCrystal_I2C lcd(0x27, 16, 2);
//儲存方塊的陣列,總共7類方塊,每一類方塊包含4種旋轉、每種方塊共4格,以二維座標儲存
//模擬器的LED點矩陣方塊參數
int Block[7][4][4][2] = {
{ //T形方塊
{{1, 0},{1, 1},{1, 2},{0, 1}},
{{2, 1},{1, 0},{1, 1},{0, 1}},
{{2, 1},{1, 0},{1, 1},{1, 2}},
{{2, 1},{1, 1},{1, 2},{0, 1}}
},
{ //S形方塊
{{1, 1},{1, 2},{0, 0},{0, 1}},
{{2, 0},{1, 0},{1, 1},{0, 1}},
{{1, 1},{1, 2},{0, 0},{0, 1}},
{{2, 0},{1, 0},{1, 1},{0, 1}}
},
{ //z形方塊
{{1, 0},{1, 1},{0, 1},{0, 2}},
{{2, 1},{1, 0},{1, 1},{0, 0}},
{{1, 0},{1, 1},{0, 1},{0, 2}},
{{2, 1},{1, 0},{1, 1},{0, 0}}
},
{ //L形方塊
{{2, 0},{2, 1},{1, 1},{0, 1}},
{{1, 2},{0, 0},{0, 1},{0, 2}},
{{2, 0},{1, 0},{0, 0},{0, 1}},
{{1, 0},{1, 1},{1, 2},{0, 0}}
},
{ //J形方塊
{{2, 0},{2, 1},{1, 0},{0, 0}},
{{1, 0},{1, 1},{1, 2},{0, 2}},
{{2, 1},{1, 1},{0, 0},{0, 1}},
{{1, 0},{0, 0},{0, 1},{0, 2}}
},
{ //I形方塊
{{0, 0},{0, 1},{0, 2},{0, 3}},
{{3, 2},{2, 2},{1, 2},{0, 2}},
{{0, 0},{0, 1},{0, 2},{0, 3}},
{{3, 2},{2, 2},{1, 2},{0, 2}}
},
{ //O形方塊
{{1, 0},{1, 1},{0, 0},{0, 1}},
{{1, 0},{1, 1},{0, 0},{0, 1}},
{{1, 0},{1, 1},{0, 0},{0, 1}},
{{1, 0},{1, 1},{0, 0},{0, 1}}
}
};
/*
//實際LED點矩陣的方塊參數
int Block[7][4][4][2] = {
{ //T形方塊
{{1, 0},{1, 1},{1, 2},{0, 1}},
{{2, 1},{1, 1},{1, 2},{0, 1}},
{{2, 1},{1, 0},{1, 1},{1, 2}},
{{2, 1},{1, 0},{1, 1},{0, 1}}
},
{ //S形方塊
{{1, 0},{1, 1},{0, 1},{0, 2}},
{{2, 1},{1, 0},{1, 1},{0, 0}},
{{1, 0},{1, 1},{0, 1},{0, 2}},
{{2, 1},{1, 0},{1, 1},{0, 0}}
},
{ //z形方塊
{{1, 1},{1, 2},{0, 0},{0, 1}},
{{2, 0},{1, 0},{1, 1},{0, 1}},
{{1, 1},{1, 2},{0, 0},{0, 1}},
{{2, 0},{1, 0},{1, 1},{0, 1}}
},
{ //L形方塊
{{2, 0},{2, 1},{1, 0},{0, 0}},
{{1, 0},{0, 0},{0, 1},{0, 2}},
{{2, 1},{1, 1},{0, 0},{0, 1}},
{{1, 0},{1, 1},{1, 2},{0, 2}}
},
{ //J形方塊
{{2, 0},{2, 1},{1, 1},{0, 1}},
{{1, 0},{1, 1},{1, 2},{0, 0}},
{{2, 0},{1, 0},{0, 0},{0, 1}},
{{1, 2},{0, 0},{0, 1},{0, 2}}
},
{ //I形方塊
{{0, 0},{0, 1},{0, 2},{0, 3}},
{{3, 1},{2, 1},{1, 1},{0, 1}},
{{0, 0},{0, 1},{0, 2},{0, 3}},
{{3, 1},{2, 1},{1, 1},{0, 1}}
},
{ //O形方塊
{{1, 0},{1, 1},{0, 0},{0, 1}},
{{1, 0},{1, 1},{0, 0},{0, 1}},
{{1, 0},{1, 1},{0, 0},{0, 1}},
{{1, 0},{1, 1},{0, 0},{0, 1}}
}
};
*/
//儲存當前正在操作的方塊
int currentBlock[4][4][2] = {
{{0, 0},{0, 0},{0, 0},{0, 0}},
{{0, 0},{0, 0},{0, 0},{0, 0}},
{{0, 0},{0, 0},{0, 0},{0, 0}},
{{0, 0},{0, 0},{0, 0},{0, 0}}
};
//儲存已固定方塊的容器,0代表空,1代表有方塊
int Container[8][8]{
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0}
};
//方塊繪製的起始點,row = -1,是為了抵銷第一次的下移
int startPoint[2] = {-1, 3}, currentPoint[2] = {0, 0};
void setup() {
pinMode(sw, INPUT); // sw代表2,為搖桿的開關
pinMode(A0, INPUT); // A0為搖桿的X軸
pinMode(A1, INPUT); // A1為搖桿的Y軸
lc.shutdown(0,false); // 關閉省電模式
lc.setIntensity(0,0); // 設定亮度為 0 (介於0~15之間)
lc.clearDisplay(0); // 清除螢幕
lcd.init(); // 初始化LCD
lcd.backlight(); // 開啟背光模式
randomSeed(analogRead(0)); // Arduino內建的亂數種子
clearContainer(); // 清空容器
Welcome(); // 歡迎訊息
createBlock(); // 創造出第一個方塊
}
void loop() {
if(IsDown()){
if(CanDown()){
moveDown();
goto label_1;
}
else{
goto label_2;
}
}
if(IsRotate()){
if(CanRotate()){
Rotation();
}
goto label_1;
}
if(IsRight()){
if(CanRight()){
moveRight();
}
goto label_1;
}
if(IsLeft()){
if(CanLeft()){
moveLeft();
}
goto label_1;
}
if(CanDown()){
moveDown();
drawBlock();
delay(800);
eraseBlock();
}
else{
goto label_2;
}
if(false){
label_1 :
drawBlock();
delay(450);
eraseBlock();
}
if(false){
label_2 :
updateContainer();
drawContainer();
if(IsGameOver()){
GameOver();
delay(1500);
nextRound();
}
else{
clearLink();
drawContainer();
createBlock();
}
}
}
//歡迎訊息
void Welcome(){
lcd.clear();
lcd.setCursor(4, 0);
lcd.print("Welcome!");
delay(1000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("You are playing!");
lcd.setCursor(0, 1);
lcd.print("Score: ");
}
//更新分數,每消去一行,增加100分
void updateScore(){
score += 100;
lcd.setCursor(7, 1);
lcd.print(score);
}
//結束訊息,並顯示最終分數
void GameOver(){
lcd.clear();
lcd.setCursor(3, 0);
lcd.print("Game Over!");
lcd.setCursor(3, 1);
lcd.print("Score: ");
lcd.print(score);
}
//是否再來一局,若按下開關,讀取到高電位,則初始化遊戲資料,並脫離while迴圈
void nextRound(){
lcd.clear();
lcd.print("Press joysitck");
lcd.setCursor(0, 1);
lcd.print("for next round");
while(true){
if(digitalRead(sw) == HIGH){
clearContainer();
drawContainer();
score = 0;
Welcome();
createBlock();
break;
}
}
}
//清空容器,將所有元素初始化為0
void clearContainer(){
for(i = 0; i <= 7; i++){
for(j = 0; j <= 7; j++){
Container[i][j] = 0;
}
}
}
//更新容器,將當前固定下來的方塊座標,新增到容器內部
void updateContainer(){
for(i = 0; i<= 3; i++){
row = currentBlock[r][i][0] + currentPoint[0];
col = currentBlock[r][i][1] + currentPoint[1];
Container[row][col] = 1;
}
}
//繪製容器,點亮容器中為1的座標,熄滅為0的座標
void drawContainer(){
for(i = 0; i <= 7; i++){
for(j = 0; j <= 7; j++){
if(Container[i][j] == 1){
lc.setLed(0, i, j, 1);
}
else{
lc.setLed(0, i, j, 0);
}
}
}
}
//創造方塊,隨機選擇方塊種類,以startPoint為起始座標,指派給currentPoint,再將方塊的4種旋轉都儲存進currentBlock,最後從第一種旋轉開始
void createBlock(){
currentPoint[0] = startPoint[0];
currentPoint[1] = startPoint[1];
c = random(7); //Arduiono內建隨機函式
for(r = 0; r <= 3; r++){
for(i = 0; i <= 3; i++){
currentBlock[r][i][0] = Block[c][r][i][0];
currentBlock[r][i][1] = Block[c][r][i][1];
}
}
r = 0;
}
//繪製方塊,將當前方塊座標點亮
void drawBlock(){
for(i = 0; i <= 3; i++){
row = currentBlock[r][i][0] + currentPoint[0];
col = currentBlock[r][i][1] + currentPoint[1];
lc.setLed(0, row, col, 1);
}
}
//清除方塊,將當前方塊座標熄滅
void eraseBlock(){
for(i = 0; i <= 3; i++){
row = currentBlock[r][i][0] + currentPoint[0];
col = currentBlock[r][i][1] + currentPoint[1];
lc.setLed(0, row, col, 0);
}
}
//是否到達頂部,若生成方塊的起始位置已被方塊占滿,則無法再繼續進行遊戲
bool IsGameOver(){
row = startPoint[0] + 1;
col = startPoint[1];
if(Container[row][col] == 1 || Container[row][col + 1] == 1){
return true;
}
return false;
}
//判斷是否有連成一線,即該列每一個元素皆為1
bool IsLink(){
for(i = 0; i <= 7; i++){
if(Container[row][i] == 0){
return false;
}
}
return true;
}
//清除連線,從最下面一列開始往上檢查;若有連線,則每一列皆向下移動,並更新分數;再重複此流程
void clearLink(){
for(row = 7; row >= 0; row--){
if(IsLink()){
MoveDown(row);
updateScore();
}
}
}
//將容器內部連成線以上的每一列往下移動,也就是將上一列指派給下一列,第0列的元素則初始化為0;最後row設為8,以抵銷clearLink內部for迴圈的row--
void MoveDown(int num){
while(num > 0){
for(col = 0; col <= 7; col++){
Container[num][col] = Container[num - 1][col];
}
num--;
}
for(col = 0; col <= 7; col++){
Container[num][col] = 0;
}
row = 8;
}
/*
//是否有用搖桿進行操作,若沒向左、沒向右、沒旋轉、沒向下,則沒有操作
bool IsOperation(){
return (IsLeft() | IsRight() | IsRotate() | IsDown());
}
*/
//偵測搖桿是否有向左,即讀取到的X軸類比訊號小於特定值,備註:100這個值可以改,端看搖桿的電阻值
bool IsLeft(){
x_axis = analogRead(A0);
if(x_axis < 100){
return true;
}
return false;
}
//是否能向左,先將方塊向左移,若超出左邊界或和容器中的方塊重疊,則判斷無法左移
bool CanLeft(){
for(i = 0; i <= 3; i++){
row = currentBlock[r][i][0] + currentPoint[0];
col = currentBlock[r][i][1] + currentPoint[1] - 1;
if(col < 0 || (Container[row][col] == 1)){
return false;
}
}
return true;
}
//方塊左移,列數不變,行數-1
void moveLeft(){
currentPoint[1]--;
}
//偵測搖桿是否有向右,即讀取到的X軸類比訊號大於特定值,備註:800這個值可以改,端看搖桿的電阻值
bool IsRight(){
x_axis = analogRead(A0);
if(x_axis > 800){
return true;
}
return false;
}
//是否能向右,先將方塊向右移,若超出右邊界或和容器中的方塊重疊,則判斷無法右移
bool CanRight(){
for(i = 0; i <= 3; i++){
row = currentBlock[r][i][0] + currentPoint[0];
col = currentBlock[r][i][1] + currentPoint[1] + 1;
if(col > 7 || (Container[row][col] == 1)){
return false;
}
}
return true;
}
//方塊右移,列數不變,行數+1
void moveRight(){
currentPoint[1]++;
}
//偵測搖桿是否有向上,即讀取到的Y軸類比訊號小於特定值,備註:100這個值可以改,端看搖桿的電阻值
bool IsRotate(){
y_axis = analogRead(A1);
if(y_axis > 800){
return true;
}
return false;
}
//是否能旋轉,先將方塊順時針旋轉90度,即temp = (r + 1) % 4,若和容器中的方塊重疊,或超出左右邊界,或超出上下邊界,則判斷無法旋轉
bool CanRotate(){
byte temp = (r + 1) % 4;
for(i = 0; i <= 3; i++){
row = currentBlock[temp][i][0] + currentPoint[0];
col = currentBlock[temp][i][1] + currentPoint[1];
if(Container[row][col] == 1){
return false;
}
else if(col < 0 || col > 7){
return false;
}
else if(row < 0 || row > 7){
return false;
}
}
return true;
}
//方塊旋轉,變更r的值,0->1, 1->2, 2->3, 3->0,如此循環下去
void Rotation(){
r = (r + 1) % 4;
}
//偵測搖桿是否有向下,即讀取到的Y軸類比訊號大於特定值,備註:800這個值可以改,端看搖桿的電阻值
bool IsDown(){
y_axis = analogRead(A1);
if(y_axis < 100){
return true;
}
return false;
}
//是否能向下,先將方塊向下移,若超出下邊界或和容器中的方塊重疊,則判斷無法下移
bool CanDown(){
for(i = 0; i <= 3; i++){
row = currentBlock[r][i][0] + currentPoint[0] + 1;
col = currentBlock[r][i][1] + currentPoint[1];
if(row > 7 || (Container[row][col] == 1)){
return false;
}
}
return true;
}
//方塊下移,列數+1,行數不變
void moveDown(){
currentPoint[0]++;
}