さらに思考ゲームを作ってみよう
マインスイーパー
ゲーム概要
マインスイーパーとは『地雷除去者』の意味です。
ルール
格子状のマスで構成されるフィールド上のいくつかのマスには『地雷のマス』が存在します。
地雷がないマスの周囲に1つ以上の地雷のマスが隣接している場合、そのマスには上下左右斜め周囲に隣接する地雷のマスの個数が書かれた『数字のマス』となります。
地雷がなく、地雷のマスと隣接もしていないマスは『空白のマス』となります。
最初はすべてのマスが隠されていて、どこに何があるか判りません。
プレイヤーはフィールド内のいずれかのマスをクリックして『開く』ことができます。
クリックしたのが『空白のマス』ならば、そこから上下左右に連続する空白のマスおよびそれらの空白のマスに隣接する数字のマスがすべて開かれ、見えるようになります。
クリックしたのが『数字のマス』ならば、そのマスのみ開かれ、見えるようになります。
クリックしたのが地雷のマスだった場合、失敗となります。
見えた数字を手がかりに、プレイヤーはまだ見えていないマスのいずれかをクリックする…と言うことを繰り返し、地雷のマス以外のすべてのマスが見えるようになれば成功です。
9x時代のWindowsには標準添付されていたので、遊んだことがある人も多いのではないでしょうか。
Windows版ではプレイヤーが『地雷かも?』と思ったマスに目印の旗を立てることができるのですが、今回はプログラムを簡単にするために旗の機能は省略しました。
プログラム
#include "Elegoo_GFX.h"
#include "Elegoo_TFTLCD.h"
#include "TouchScreen.h"
// ピンの定義
// 表示機能用
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RST A4
// タッチ機能用
#define YP A3
#define XM A2
#define YM 9
#define XP 8
// タッチ検出および座標変換用
#define MINPRESSURE 10
#define MAXPRESSURE 1000
#define TS_MINX 120
#define TS_MAXX 900
#define TS_MINY 70
#define TS_MAXY 900
// 色の定義
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
#define LIGHTGRAY 0xC618
#define DARKGRAY 0x7BEF
// インスタンスの生成
Elegoo_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RST);
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
#define BOARD_WIDTH 15
#define BOARD_HEIGHT 15
#define CELL_SIZE 16
#define MINE 64
#define FREE 0
#define COVERED 128
#define WALL 32
int mine_count = 15; // mineの個数
int board[BOARD_WIDTH+2][BOARD_HEIGHT+2];
int mask[BOARD_WIDTH+2][BOARD_HEIGHT+2];
bool first;
// 初期化
void game_init() {
for(int y=0; y<=BOARD_HEIGHT+1; y++) {
for(int x=0; x<=BOARD_WIDTH+1; x++) {
board[x][y] = FREE;
mask[x][y]=COVERED;
}
}
for(int y=0; y<=BOARD_HEIGHT+1; y++) {
board[0][y]=WALL;
board[BOARD_WIDTH+1][y]=WALL;
}
for(int x=0; x<=BOARD_WIDTH; x++) {
board[x][0]=WALL;
board[x][BOARD_HEIGHT+1]=WALL;
}
first=true;
}
// 地雷をセットする
void set_mine(int x0, int y0) {
randomSeed(micros());
for(int i=0; i<mine_count; i++) {
while(true) {
int x=random(BOARD_WIDTH)+1;
int y=random(BOARD_HEIGHT)+1;
if(x==x0 && y==y0)continue;
if(board[x][y]==FREE) {
board[x][y]=MINE;
break;
}
}
}
for(int y=1; y<=BOARD_HEIGHT; y++) {
for(int x=1; x<=BOARD_WIDTH; x++) {
if(board[x][y]==MINE)continue;
int m=0;
for(int dy=-1; dy<=1; dy++) {
for(int dx=-1; dx<=1; dx++) {
if(dx==0 && dy==0)continue;
if(board[x+dx][y+dy]==MINE)m++;
}
}
board[x][y]=m;
}
}
}
// 終了チェック
int end_check() {
for(int y=1; y<=BOARD_HEIGHT; y++) {
for(int x=1; x<=BOARD_WIDTH; x++) {
if(mask[x][y]!=FREE && board[x][y]!=MINE) {
return 0;
}
}
}
return 1;
}
// 盤面の1マスだけ表示
void display_cell(int x, int y){
int x0=(x-1)*CELL_SIZE;
int y0=(y-1)*CELL_SIZE;
if(mask[x][y]==COVERED) {
tft.fillRect(x0+1, y0+1, CELL_SIZE-1, CELL_SIZE-1, DARKGRAY);
}else {
tft.fillRect(x0+1, y0+1, CELL_SIZE-1, CELL_SIZE-1, LIGHTGRAY);
tft.setTextSize(2);
tft.setCursor(x0+2, y0+1);
switch(board[x][y]) {
case FREE:
break;
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
tft.setTextColor(BLUE);
tft.print(board[x][y]);
break;
case MINE:
tft.setTextColor(RED);
tft.print("*");
break;
}
}
}
// 盤面の表示
void display() {
tft.fillScreen(LIGHTGRAY);
for(int y=0; y<=BOARD_HEIGHT; y++) {
tft.drawFastHLine(0, y*CELL_SIZE, BOARD_HEIGHT*CELL_SIZE, BLACK);
}
for(int x=0; x<=BOARD_WIDTH; x++){
tft.drawFastVLine(x*CELL_SIZE, 0, BOARD_WIDTH*CELL_SIZE, BLACK);
}
for(int y=1; y<=BOARD_HEIGHT; y++){
for(int x=1; x<=BOARD_WIDTH; x++) {
display_cell(x,y);
}
}
}
// 指定された座標を中心にカバーを開く。ただしゲームオーバー判定はしない
void open(int x0, int y0) {
if(mask[x0][y0]==FREE) {
return;
}
openSub(x0,y0);
int i=0;
while(true) {
boolean flag=false;
for(int y=1; y<=BOARD_HEIGHT; y++) {
for(int x=1; x<=BOARD_WIDTH; x++) {
if(board[x][y]!=MINE && mask[x][y]==COVERED) {
if( (board[x-1][y]==FREE && mask[x-1][y]==FREE) ||
(board[x+1][y]==FREE && mask[x+1][y]==FREE) ||
(board[x][y-1]==FREE && mask[x][y-1]==FREE) ||
(board[x][y+1]==FREE && mask[x][y+1]==FREE) ) {
openSub(x,y);
flag=true;
}
}
}
}
if(flag==false)break;
}
for(int y=1; y<=BOARD_HEIGHT; y++) {
for(int x=1; x<=BOARD_WIDTH; x++) {
if(board[x][y]==MINE || board[x][y]==FREE || mask[x][y]!=COVERED)continue;
for(int dy=-1; dy<=1; dy++) {
for(int dx=-1; dx<=1; dx++) {
if(dx==0 && dy==0)continue;
if(board[x+dx][y+dy]==FREE && mask[x+dx][y+dy]!=COVERED) {
open_cell(x,y);
}
}
}
}
}
}
void open_cell(int x0, int y0){
mask[x0][y0]=FREE;
display_cell(x0,y0);
}
void openSub(int x0, int y0) {
int x=x0,y=y0;
while(true) {
open_cell(x,y);
if(board[x][y]==FREE) {
if(board[x][y-1]==FREE && mask[x][y-1]==COVERED) {
y=y-1;
}else if(board[x][y+1]==FREE && mask[x][y+1]==COVERED) {
y=y+1;
}else if(board[x-1][y]==FREE && mask[x-1][y]==COVERED) {
x=x-1;
}else if(board[x+1][y]==FREE && mask[x+1][y]==COVERED) {
x=x+1;
}else {
break;
}
}else {
break;
}
}
}
// タップ位置の取得(タップされるまで待つ)
void getTapPoint(int *x, int *y){
while(true){
digitalWrite(13, HIGH);
TSPoint p = ts.getPoint();
digitalWrite(13, LOW);
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
if(p.z>MINPRESSURE && p.z<MAXPRESSURE){
*x = map(p.x, TS_MINX, TS_MAXX, 0, tft.width()-1);
*y = tft.height()-map(p.y, TS_MINY, TS_MAXY, 0, tft.height()-1);
return;
}
}
}
// 文字列表示
void drawText(int x, int y, int size, int color, char* text){
tft.setTextColor(color);
tft.setTextSize(size);
tft.setCursor(x, y);
tft.println(text);
}
// ゲーム終了後のタップ待ち
void waitTap(){
tft.drawRect(72, 300, 96, 16, RED);
drawText(72, 301, 2, RED, "TAP HERE");
while(true){
int x, y;
getTapPoint(&x, &y);
if(x>72 && x<168 && y>300){
break;
}
}
game_init();
}
void setup() {
// TFTの初期化
tft.reset();
uint16_t identifier = tft.readID();
if(identifier==0x0101){
identifier=0x9341;
}else if(identifier==0x1111){
identifier=0x9328;
}
tft.begin(identifier);
tft.setRotation(0);
game_init();
}
void all_open() {
for(int y=1; y<=BOARD_HEIGHT; y++) {
for(int x=1; x<=BOARD_WIDTH; x++) {
mask[x][y]=FREE;
}
}
}
void loop() {
game_init();
display();
int x,y;
while(true){
while(true){
int px, py;
getTapPoint(&px,&py);
x=(px/CELL_SIZE) +1;
y=(py/CELL_SIZE) +1;
if(1<=x && x<=BOARD_WIDTH && 1<=y && y<=BOARD_HEIGHT)break;
}
if(first){
set_mine(x,y);
first=false;
}
if(board[x][y]==MINE) {
all_open();
display();
drawText(0,245,2,RED,"GAME OVER");
break;
}else {
open((byte)x,(byte)y);
if(end_check()==1) {
all_open();
display();
drawText(0,245,2,BLUE,"CONGRATURATIONS!");
break;
}
}
}
waitTap();
}
プログラムの説明
今回は対コンピュータ戦ではないので、思考ルーチンの類いはありません。
ライブラリ・定数・グローバル変数
#include "Elegoo_GFX.h"
#include "Elegoo_TFTLCD.h"
#include "TouchScreen.h"
// ピンの定義
// 表示機能用
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RST A4
// タッチ機能用
#define YP A3
#define XM A2
#define YM 9
#define XP 8
// タッチ検出および座標変換用
#define MINPRESSURE 10
#define MAXPRESSURE 1000
#define TS_MINX 120
#define TS_MAXX 900
#define TS_MINY 70
#define TS_MAXY 900
// 色の定義
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
#define LIGHTGRAY 0xC618
#define DARKGRAY 0x7BEF
// インスタンスの生成
Elegoo_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RST);
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
#define BOARD_WIDTH 15
#define BOARD_HEIGHT 15
#define CELL_SIZE 16
#define MINE 64
#define FREE 0
#define COVERED 128
#define WALL 32
int mine_count = 15; // mineの個数
int board[BOARD_WIDTH+2][BOARD_HEIGHT+2];
int mask[BOARD_WIDTH+2][BOARD_HEIGHT+2];
bool first;
25行目あたりまでは五目並べとまったく同じです。
色の定義では LIGHTGRAY と DARKGRAY の2色を追加しています。
盤面の状態は MINE (地雷)または FREE(なにもない)の2種です。WALL はゲーム画面上には登場しませんが、表示範囲外をWALLで囲むようなデータにすることで盤面外の判定を簡単にしています。
COVEREDはマス目が表示されていない状態を表します。
フィールドは15×15、地雷の数は15個で固定です。変更する場合はプログラムの定数を書き換えて再度コンパイルする必要があります。フィールドのサイズは定数 BOARD_WIDTH と BOARD_HEIGHT、地雷の数はグローバル変数 mine_count で表しています。
盤面の状態を表すのは五目並べなどと同じく配列変数 board[][] 、各マスが表示されているかどうかを表すのが配列変数 mask[][] です。mask[][] の役割も board[][] に取り込むことも可能ですがコードが判りにくくなりそうなのでやめました。
変数 first は、ゲーム開始後はじめてタップするのかどうかのフラグです。何の手がかりもないまま最初にタップした位置が運悪く地雷で即ゲームオーバー、ということを避けるために使用します。
ゲームデータの初期設定
// 初期化
void game_init() {
for(int y=0; y<=BOARD_HEIGHT+1; y++) {
for(int x=0; x<=BOARD_WIDTH+1; x++) {
board[x][y] = FREE;
mask[x][y]=COVERED;
}
}
for(int y=0; y<=BOARD_HEIGHT+1; y++) {
board[0][y]=WALL;
board[BOARD_WIDTH+1][y]=WALL;
}
for(int x=0; x<=BOARD_WIDTH; x++) {
board[x][0]=WALL;
board[x][BOARD_HEIGHT+1]=WALL;
}
first=true;
}
いままでは表示されている盤面の左上隅が (0, 0) ~ 右下隅が (BOARD_WIDTH-1, BOARD_HEIGHT-1) でしたが、今回は左上隅が (1, 1) ~右下隅が (BOARD_WIDTH, BOARD_HEIGHT) になっています。その外側上下左右は画面に表示されませんがマス1つ分ずつの幅で囲んでいます。
この初期化関数では、まず board[][] の全マスをすべて FREE(なにもないマス)、mask[][] はすべて COVERED(非表示)にしてから、board[][] の画面に表示されない最外周のマスを WALL(壁)にしています。
地雷の設置
// 地雷をセットする
void set_mine(int x0, int y0) {
randomSeed(micros());
for(int i=0; i<mine_count; i++) {
while(true) {
int x=random(BOARD_WIDTH)+1;
int y=random(BOARD_HEIGHT)+1;
if(x==x0 && y==y0)continue;
if(board[x][y]==FREE) {
board[x][y]=MINE;
break;
}
}
}
for(int y=1; y<=BOARD_HEIGHT; y++) {
for(int x=1; x<=BOARD_WIDTH; x++) {
if(board[x][y]==MINE)continue;
int m=0;
for(int dy=-1; dy<=1; dy++) {
for(int dx=-1; dx<=1; dx++) {
if(dx==0 && dy==0)continue;
if(board[x+dx][y+dy]==MINE)m++;
}
}
board[x][y]=m;
}
}
}
地雷の設置はゲームの初期化ではなく、ゲームが開始されてプレイヤーが初めて画面のどこかをタップしたタイミングで行われます。これは『何の手がかりもないまま最初にタップした位置が運悪く地雷で即ゲームオーバー』という展開を避けるためです。
引数としてプレイヤーがタップした位置 (x0, y0) を与えられます。
randomSeed()関数は、乱数を初期化します。多くのコンピュータ言語に実装されている乱数とは、実は結果が一見デタラメに見えるだけで一定の法則をもった数列で、初期化しないと起動後最初のゲームはまったく同じ展開になってしまいます。初期化のためのシード(元になる値)として異なる値を与えれば『乱数』数列も異なる物になり、違うゲーム展開になります。ここではArduinoが起動してからの時間をマイクロ秒単位で返す micros()関数を使用することで、人間には感知できないタイミングの違いによって違う乱数を発生するようにしています。
その後、mine_count回のループでランダムに座標を決め、地雷を配置します。このとき、既に地雷を設置してしまった座標または引数で与えられた座標が出てしまった場合には座標を決め直しています。
関数後半では、地雷のあるマスに隣接している空のマスに、周囲の地雷の個数を記入しています。
終了チェック
// 終了チェック
int end_check() {
for(int y=1; y<=BOARD_HEIGHT; y++) {
for(int x=1; x<=BOARD_WIDTH; x++) {
if(mask[x][y]!=FREE && board[x][y]!=MINE) {
return 0;
}
}
}
return 1;
}
『成功』で終了したかをチェックしています。盤面の表示部分をすべて調べ『「表示されていない」かつ「地雷ではない」マス』が1つでもみつかれば『まだ成功していない(返却値=0)』、1つも見つからなければ『成功(返却値=1)』です。
盤面の1マスを表示
// 盤面の1マスだけ表示
void display_cell(int x, int y){
int x0=(x-1)*CELL_SIZE;
int y0=(y-1)*CELL_SIZE;
if(mask[x][y]==COVERED) {
tft.fillRect(x0+1, y0+1, CELL_SIZE-1, CELL_SIZE-1, DARKGRAY);
}else {
tft.fillRect(x0+1, y0+1, CELL_SIZE-1, CELL_SIZE-1, LIGHTGRAY);
tft.setTextSize(2);
tft.setCursor(x0+2, y0+1);
switch(board[x][y]) {
case FREE:
break;
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
tft.setTextColor(BLUE);
tft.print(board[x][y]);
break;
case MINE:
tft.setTextColor(RED);
tft.print("*");
break;
}
}
}
今回は、いちいち全画面の書き換えをしなくて済むように、1マス単位で描画できるようにしました。
処理は簡単で、
mask[x][y]の値がCOVEREDなら(まだ開いていないマスなら)、DARKGLAYの長方形を描画
もう開いているマスなら、LIGHTGLAYの長方形を描画してから、
board[x][y]の値がFREEなら、なにも描画しない
board[x][y]の値が1~8なら、青色で数字を描画
board[x][y]の値がMINEなら、赤色で『*』(地雷を表す)を描画
となっています。
盤面全体の描画
// 盤面の表示
void display() {
tft.fillScreen(LIGHTGRAY);
for(int y=0; y<=BOARD_HEIGHT; y++) {
tft.drawFastHLine(0, y*CELL_SIZE, BOARD_HEIGHT*CELL_SIZE, BLACK);
}
for(int x=0; x<=BOARD_WIDTH; x++){
tft.drawFastVLine(x*CELL_SIZE, 0, BOARD_WIDTH*CELL_SIZE, BLACK);
}
for(int y=1; y<=BOARD_HEIGHT; y++){
for(int x=1; x<=BOARD_WIDTH; x++) {
display_cell(x,y);
}
}
}
今回は、盤面全体の表示関数は開始時とゲーム終了時にしか使用しません。処理は、まず画面全体をLIGHTGRAYで塗りつぶしてから格子線を描画、その後1マス描画関数をつかって各マスを描画していきます。
見えないマスを見えるようにする
// 指定された座標を中心にカバーを開く。ただしゲームオーバー判定はしない
void open(int x0, int y0) {
if(mask[x0][y0]==FREE) {
return;
}
openSub(x0,y0);
int i=0;
while(true) {
boolean flag=false;
for(int y=1; y<=BOARD_HEIGHT; y++) {
for(int x=1; x<=BOARD_WIDTH; x++) {
if(board[x][y]!=MINE && mask[x][y]==COVERED) {
if( (board[x-1][y]==FREE && mask[x-1][y]==FREE) ||
(board[x+1][y]==FREE && mask[x+1][y]==FREE) ||
(board[x][y-1]==FREE && mask[x][y-1]==FREE) ||
(board[x][y+1]==FREE && mask[x][y+1]==FREE) ) {
openSub(x,y);
flag=true;
}
}
}
}
if(flag==false)break;
}
for(int y=1; y<=BOARD_HEIGHT; y++) {
for(int x=1; x<=BOARD_WIDTH; x++) {
if(board[x][y]==MINE || board[x][y]==FREE || mask[x][y]!=COVERED)continue;
for(int dy=-1; dy<=1; dy++) {
for(int dx=-1; dx<=1; dx++) {
if(dx==0 && dy==0)continue;
if(board[x+dx][y+dy]==FREE && mask[x+dx][y+dy]!=COVERED) {
open_cell(x,y);
}
}
}
}
}
}
void open_cell(int x0, int y0){
mask[x0][y0]=FREE;
display_cell(x0,y0);
}
void openSub(int x0, int y0) {
int x=x0,y=y0;
while(true) {
open_cell(x,y);
if(board[x][y]==FREE) {
if(board[x][y-1]==FREE && mask[x][y-1]==COVERED) {
y=y-1;
}else if(board[x][y+1]==FREE && mask[x][y+1]==COVERED) {
y=y+1;
}else if(board[x-1][y]==FREE && mask[x-1][y]==COVERED) {
x=x-1;
}else if(board[x+1][y]==FREE && mask[x+1][y]==COVERED) {
x=x+1;
}else {
break;
}
}else {
break;
}
}
}
連続した『空のマス』を開いていくアルゴリズムは、再帰を使えればもっと短くて直感的に判りやすいコードで書けるのですが、 Arduinoのメモリ量だとスタックが溢れてしまい動きません。よって、コードは少々長くなりますが再帰しないアルゴリズムで処理しています。
再帰が使える場合は、以下のようなコードで用が足りてしまいます。
void open(int x, int y) {
if(mask[x][y]==FREE) {
return;
}
if(board[x][y]!=MINE && board[x][y]!=WALL) {
mask[x][y]=FREE;
if(board[x][y]==FREE) {
for(byte dy=-1; dy<=1; dy++) {
for(byte dx=-1; dx<=1; dx++) {
if(dx==0 && dy==0)continue;
open(x+dx, y+dy);
}
}
}
}
}
諸々下請け関数
// タップ位置の取得(タップされるまで待つ)
void getTapPoint(int *x, int *y){
while(true){
digitalWrite(13, HIGH);
TSPoint p = ts.getPoint();
digitalWrite(13, LOW);
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
if(p.z>MINPRESSURE && p.z<MAXPRESSURE){
*x = map(p.x, TS_MINX, TS_MAXX, 0, tft.width()-1);
*y = tft.height()-map(p.y, TS_MINY, TS_MAXY, 0, tft.height()-1);
return;
}
}
}
// 文字列表示
void drawText(int x, int y, int size, int color, char* text){
tft.setTextColor(color);
tft.setTextSize(size);
tft.setCursor(x, y);
tft.println(text);
}
// ゲーム終了後のタップ待ち
void waitTap(){
tft.drawRect(72, 300, 96, 16, RED);
drawText(72, 301, 2, RED, "TAP HERE");
while(true){
int x, y;
getTapPoint(&x, &y);
if(x>72 && x<168 && y>300){
break;
}
}
game_init();
}
void setup() {
// TFTの初期化
tft.reset();
uint16_t identifier = tft.readID();
if(identifier==0x0101){
identifier=0x9341;
}else if(identifier==0x1111){
identifier=0x9328;
}
tft.begin(identifier);
tft.setRotation(0);
game_init();
}
このあたりは五目並べとまったく同じです。
全部のマスを開ける
void all_open() {
for(int y=1; y<=BOARD_HEIGHT; y++) {
for(int x=1; x<=BOARD_WIDTH; x++) {
mask[x][y]=FREE;
}
}
}
ゲームオーバー時に地雷のある場所を含めてすべてのマスを開ける関数です。やっていることは単純で、mask[x][y]をすべてFREEにしています。
void loop() {
game_init();
display();
int x,y;
while(true){
while(true){
int px, py;
getTapPoint(&px,&py);
x=(px/CELL_SIZE) +1;
y=(py/CELL_SIZE) +1;
if(1<=x && x<=BOARD_WIDTH && 1<=y && y<=BOARD_HEIGHT)break;
}
if(first){
set_mine(x,y);
first=false;
}
if(board[x][y]==MINE) {
all_open();
display();
drawText(0,245,2,RED,"GAME OVER");
break;
}else {
open((byte)x,(byte)y);
if(end_check()==1) {
all_open();
display();
drawText(0,245,2,BLUE,"CONGRATURATIONS!");
break;
}
}
}
waitTap();
}
今回は対戦ではないので、loop()関数内がちょっと違います。コンピュータ側の手を選ぶ処理がないため、
ユーザー入力
↓
タップした場所が地雷でなければ盤面に反映、地雷ならゲームオーバー
↓
終了(成功)判定
を繰り返しています。なお、そのゲームでの最初にタップした時のみ『地雷を設置する』という処理が行われます。
実行結果
おなじみのゲームですがArduinoで動くとは…何事もやってみるものです。
コメント