TETRIS with M5STACK の改造メモ

スケッチサンプルにある”TETRIS with M5STACK”を改造し、
ゲームシステム、操作方法の改良及び、画像、音声を追加したモノを作りました。

次項目の改良をした、バイナリとソースを公開していましたが、
ふと思い立って、現状を調べた所(2012の判例含めて)、
クローンゲーの氾濫の認識から想像したより、著作権的に不味そうなので、公開を取り下げました。

回避の為に枠サイズの調整などは可能ですが、
元のソースにテトリスの名前が有り、これは変更できない事もあります。

公式サンプルに有った為、バイナリ、ソースの一般的な公開も問題ないと判断してしまいました。
割と遊べる形(ガラケーアプリ前期なら売れるレベル)にしてしまったという事もありますが……
ま、現実的にそう大きな問題もないでしょうが、私も別の所では著作権者ですので、念の為。

GPL界隈や、諸々の調査/認識不足に関する反省は別として、
それなりに遊べるゲームも作れる端末で有る事の検証、
バイナリ公開の問題点の把握を含めた実験、
自身のシステム把握と、環境構築という目的は達していますので、作った事は良しとします。
(私的(フォロワー様)にでしたら、ソース他を出すことも構いませんので、TWで連絡ください)

改造のポイントだけ、増加して記載しておきます。
ご参考になれば幸いです。
(<記号は、&lt記載になっていますので悪しからず。)

*改良点

・MP3複数トラック再生実装。
+音質は極めて悪く、ノイズも問題なので何とかしたいが、ライブラリ/ハード的には無理っぽい?
+スレッド化でなんとかならないか?
・効果音及びBGMを実装。
+SD/SPIFFS両対応。
・SDメニュー(M5Stack-SD-Menu)対応
・起動時にBボタン同時押しで、BGM再生追加 (デフォルトはSEのみ)
・同、Cボタンで同時押しで、全ミュート追加
・Aボタンで左移動、Cボタンで右移動。移動ボタン長押しで、連続移動可。
・Bボタンで回転。Bボタン長押し後に離すと、瞬時落下。
・レベル及びスコア実装。
・『http://shikarunochi.matrix.jp/?p=2296』(しかるのち)様のソースより、
 ネクストブロック表示部分を移植。スコア表示部も引用。
・ブロックデザインを変更。
●2018/10/11
・ソースにコメントを追加し、すこし整理。
・スコアを変更時のみ描画するように改修。
・Bボタン長押し時に、ブロックが光る要素追加。
 +落下モードがわかりやすくなった筈。
・ブート時に、Aボタンと、他の何れかのボタンを同時押しで、
 キーチェーンゲームっぽい見た目になる要素を追加。
 +Sound選択も有効、全押しで、SEのみのモードとなる。

*改造のポイント
(<記号は、&lt記載になっていますので悪しからず。)

・ボタン長押で落下/連続移動
・冒頭変数宣言

boolean but_B = false;
byte LONG_P_CT =8;//200ms

・void loop()内

//
Point next_pos;
int next_rot = rot;
GetNextPosRot(&next_pos, &next_rot);
//2018/10/9 FALL Add
if (started&&but_B){
while(-1){
if(ReviseScreen(next_pos, next_rot)){ClearKeys();break;}
next_pos.X = pos.X;
next_pos.Y = pos.Y;
next_pos.Y += 1;
}
//snd_play(1,”/fall.mp3″,0.4f);
}
else
ReviseScreen(next_pos, next_rot);

・キー判定部

//========================================================================
//2018/10/9 T.K Change
void ClearKeys() { but_A=false; but_B=false; but_LEFT=false; but_RIGHT=false;}
//========================================================================
//KEY INFO (A=bit3,C=bit2,B=bit4)
//2018/10/9 T.K Replacement
short Key=0,R_Key=0,Btn_A_ct=0,Btn_B_ct=0,Btn_C_ct=0;
bool KeyPadLoop(){
if(M5.BtnA.read()){Key|=8;Btn_A_ct++;if(Btn_A_ct>LONG_P_CT)Key|=64;}//Acquire long press.
else
{if((Key&8)&&!(Key&64))R_Key|=8;Key&=(~8);Key&=(~64);Btn_A_ct=0;}
//
if(M5.BtnC.read()){Key|=4;Btn_C_ct++;if(Btn_C_ct<LONG_P_CT)Key|=128;}//Acquire long press.
else
{if((Key&4)&&!(Key&128))R_Key|=4; Key&=(~4);Key&=(~128);Btn_C_ct=0;}
//
if(M5.BtnB.read()){Key|=16;Btn_B_ct++;}//Acquire long press.
else
{if(Key&16){if(Btn_B_ct<LONG_P_CT)R_Key|=16;
else
R_Key|=32;}
Key&=(~16);Btn_B_ct=0;}
//
if((R_Key&8)||Key&64){R_Key&=(~8);ClearKeys();but_LEFT =true;}
if((R_Key&4)||Key&128){R_Key&=(~4);ClearKeys();but_RIGHT=true;}
if((R_Key&16)){R_Key&=(~16);ClearKeys();but_A =true;}
if((R_Key&32)){R_Key&=(~32);ClearKeys();but_B =true;}
if(but_LEFT||but_RIGHT||but_A)return true;
return false;
}

・落下受付時フラッシュ
・冒頭変数宣言

unsigned long systimer = 0;

・void loop()内ラスト

systimer++;

・void setup(void)内

//—————————-// Make Block —————————-
make_block( 0, BLACK); // Type No, Color
make_block( 1, 0x00F0); // DDDD RED 0000000011110000 1111000000000000
make_block( 2, 0xFBE4); // DD,DD PUPLE 1111101111100100 1110010011111011
make_block( 3, 0xFF00); // D__,DDD BLUE 1111111100000000 0000000011111111
make_block( 4, 0xFF87); // DD_,_DD GREEN 1111111110000111 1000011111111111
make_block( 5, 0x87FF); // __D,DDD YELLO 1000011111111111 1111111110000111
make_block( 6, 0xF00F); // _DD,DD_ LIGHT GREEN 1111000000001111 1111000000001111
make_block( 7, 0xF8FC); // _D_,DDD PINK 1111100011111100 1111100011111100
make_block( 8, 0xFFFF); // _D_,DDD FLASH //2018/10/11 T.K Add
//———————————————————————-

・bool ReviseScreen(Point next_pos, int next_rot)内

bool fall=false;
for (int i = 0; i &lt 4; ++i) screen[pos.X +
block.square[rot][i].X][pos.Y + block.square[rot][i].Y] = 0;
if (GetSquares(block, next_pos, next_rot, next_squares)) {
for (int i = 0; i &lt 4; ++i){
//2018/10/11 T.K Flash(When long pressed) Add
if(Btn_B_ct>LONG_P_CT&&(systimer>>1)&1)
screen[next_squares[i].X][next_squares[i].Y] = 8;
else
screen[next_squares[i].X][next_squares[i].Y] = block.color;
}
pos = next_pos; rot = next_rot;
}
else {

・ブロックパターン設定/モノクロモード対応
 +元の色コードの解釈は反転している?
 縦ブロックが赤指定だとすると、コメントアウトした色の方が正しいが……
・冒頭変数宣言

byte Color_mode=0;//0:ORIGINAL 1:Monotone(unused) 2:liquid crystal

・make_block( int n , uint16_t color )置き換え。

//2018/10/9 T.K Change
float block_pattern[]={
0.9f,1.0f,1.1f,1.2f,1.3f,1.2f,
1.0f,1.1f,1.2f,1.3f,1.2f,1.1f,
1.1f,1.2f,1.3f,1.2f,1.1f,1.0f,
1.2f,1.3f,1.2f,1.1f,1.0f,0.9f,
1.3f,1.2f,1.1f,1.0f,0.9f,1.0f,
1.2f,1.1f,1.0f,0.9f,1.0f,1.1f,
};
void make_block( int n , uint16_t color ){ // Make Block color
color=(color >> 8) | (color &lt&lt 8);
for ( int i =0 ; i &lt 12; i++ ) for ( int j =0 ; j &lt 12; j++ ){
short r=(short)((float)(color>>11)*block_pattern[(j>>1)*6+(i>>1)]);
if(r>0x1f)r=0x1f;//5bit
short g=(short)((float)((color>>5)&0x3f)*block_pattern[(j>>1)*6+(i>>1)]);
if(g>0x3f)g=0x3f;//6bit
short b=(short)((float)(color&0x1f)*block_pattern[(j>>1)*6+(i>>1)]);
if(b>0x1f)b=0x1f;//5bit
//BlockImage[n][i][j] = (r&lt&lt11) | (g&lt&lt5) | b; // Block color //2018/10/9 This one is closer to the original source comment ??? T.K
//2018/10/11 T.K Monotone Mode Add
if(Color_mode!=0){
if(Color_mode==1)//Monotone
{
g=(g+(r&lt&lt1)+(b&lt&lt1)+0x1f)>>2;
BlockImage[n][i][j] = ((g>>1)&lt&lt11) | (g&lt&lt5) | (g>>1); // Block color //
}
else
{//liquid crystal
g=(g+(r&lt&lt1)+(b&lt&lt1)+0x1f)>>2;
g=(short)((float)g*1.1f);
if(g>0x3f)g=0x3f;//6bit
r=(short)((float)g*0.9f)>>1;
if(r>0x1f)r=0x1f;//5bit
b=(short)((float)g*0.5f)>>1;
if(b>0x1f)b=0x1f;//5bit
BlockImage[n][i][j] = (r&lt&lt11) | (g&lt&lt5) | b; // Block color //
}
}
else
BlockImage[n][i][j] = (b&lt&lt11) | (g&lt&lt5) | r; // Block color //
if ( i == 0 || j == 0 ) BlockImage[n][i][j] = 0; // BLACK Line
}
}

*音周り+調速システム変更
 +音周りは、クオリティが確保出来ていないので、むしろ、調速システム部分の方が効果高いかも。

・冒頭 インクルード/変数宣言
 +BGMは、snd_play(0,”/km_bgm_01.mp3″,0.3f);
  SEは、snd_play(1,”/fall.mp3″,0.4f);という形で発声させる。

//音周りの宣言
#include “SPIFFS.h”
#include “AudioFileSourceSD.h”
#include “AudioFileSourceSPIFFS.h”
#include “AudioFileSourceID3.h”
#include “AudioGeneratorMP3.h”
#include “AudioOutputI2S.h”
#include “AudioOutputMixer.h”
//
AudioGeneratorMP3 *mp3[2];
AudioFileSourceSD *file[2];
AudioFileSourceSPIFFS *fileSP[2];
AudioOutputI2S *out;
AudioFileSourceID3 *id3[2];
AudioOutputMixer *mixer;
AudioOutputMixerStub *stub[2];
//
byte Sound_mode=0;//0:SE/BGM全再生 1:BGMミュート 2:ミュート
unsigned long FrameTime = 0;

・void setup()ラスト

//2018/10/9 T.K Add
snd_init();
//
if(Sound_mode==0)snd_play(0,”/km_bgm_01.mp3″,0.3f);
}

・void loop()冒頭

void loop() {
FrameTime = millis();//調速

・void loop()内ラスト近辺
 +delay(25);は削除

//
M5.update();
Sound_Loop(0);
//

・追加コード

//========================================================================
//10/11 T.K Add
void Sound_Loop(byte m){//and Speed adjustment m=0:Add BGM Rekick
//Sound Loop & Rekick
while(-1){
if(Sound_mode==0&&mp3[0]!=0&&mp3[0]->isRunning()) {
if (!mp3[0]->loop()){
if(m==0)snd_play(0,”/km_bgm_01.mp3″,0.3f);
else
snd_stop(0);out->stop();}
}
if (mp3[1]!=0&&mp3[1]->isRunning()) {
if (!mp3[1]->loop()){snd_stop(1);}
}
//調速 サウンドを使わない場合は、delay(1);を後に入れる。
if(FrameTime+game_speed &lt millis())break;
//delay(1);
}
}

//========================================================================
//
//sound_system
//
//2018/10/9 T.K Add
//
void snd_init(){
mp3[0]=mp3[1]=nullptr;stub[0]=stub[1]=nullptr;
if(Sound_mode==2)return;
out = new AudioOutputI2S(0, 1); // Output to builtInDAC
out->SetOutputModeMono(true);
out->stop();
mixer = new AudioOutputMixer(32, out);
delay(300);
}
void snd_play(int nm,const char *filename,float v){
if(Sound_mode==2)return;
snd_stop(nm);
//
File f = SPIFFS.open(filename, “r”);
if(!f||f.isDirectory()){
f.close();
if(file[nm]!=nullptr)delete file[nm];//2018/10/28修正 リークが蓄積すると落ちる
char t_name[48] = {‘/’,’m’,’p’,’3′, ‘\0’};
strcat(t_name, filename);
file[nm] = new AudioFileSourceSD(t_name);
id3[nm] = new AudioFileSourceID3(file[nm]);
}
else
{
f.close();
if(fileSP[nm]!=nullptr)delete fileSP[nm];//2018/10/28修正 リークが蓄積すると落ちる
fileSP[nm] = new AudioFileSourceSPIFFS(filename);
id3[nm] = new AudioFileSourceID3(fileSP[nm]);
}
stub[nm] = mixer->NewInput();
stub[nm]->SetGain(v);
mp3[nm] = new AudioGeneratorMP3();
mp3[nm]->begin(id3[nm], stub[nm]);
}
void snd_stop(int nm){
if(Sound_mode==2)return;
if (stub[nm]!=nullptr){stub[nm]->SetGain(0);}//発声を止めさせるだけなら、これだけで良い。
if (mp3[nm]!=nullptr){mp3[nm]->stop();delete mp3[nm];mp3[nm]=nullptr;id3[nm]->close();delete id3[nm];}//2018/10/27修正 リークが蓄積すると落ちる
if (stub[nm]!=nullptr){stub[nm]->stop();delete stub[nm];stub[nm]=nullptr;}
}