Arduinoで五目並べ・重力四目並べを作る

Arduino

他にも思考ゲーム(ボードゲーム)を作ってみよう

マルバツができたら、少しのプログラム変更でいろいろ思考ゲームのバリエーションを作ることができます。

五目並べ

ゲームの概要

みなさんご存じの通り、五目並べは『おなじコマがタテ・ヨコ・ナナメに5つ並ぶ』ことを目指すゲームです。正式には囲碁の碁盤を使うので19×19のマス目を用意するのですが、Arduinoでは表示もメモリ的にも厳しい(変数領域に使えるメモリ容量が最大で2kBしかない)ので、今作では8×8のマス目で行うことにします。

プログラム

C++
#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 920

// 色の定義
#define	BLACK   0x0000
#define	BLUE    0x001F
#define	RED     0xF800
#define	GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF

// インスタンスの生成
Elegoo_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RST);
TouchScreen ts=TouchScreen(XP, YP, XM, YM, 300);

// ゲーム用定義
#define FREE 0
#define PLAYER 1
#define COMP -1
#define MAX_DEPTH 1

#define BOARD_WIDTH 8
#define BOARD_HEIGHT 8
#define MAX_LINE 5
#define CELL_SIZE 30

#define MYSELF_WIN 32767
#define ENEMY_WIN -32767
#define PLAYER_WIN 1
#define COMP_WIN -1
#define DRAW 255
#define CONT 0

// ゲーム用変数
int board[BOARD_WIDTH][BOARD_HEIGHT];

// ラインができているかチェック
int check_line(int x0, int y0, int dx, int dy, int side){
  int _m=0,_e=0;
  for(int i=0; i<MAX_LINE; i++){
    if(x0+dx*i>=BOARD_WIDTH || y0+dy*i>=BOARD_HEIGHT || x0+dx*i<0 || y0+dy*i<0){
      _m=_e=0;
      break;
    }
    if(board[x0+dx*i][y0+dy*i]==side){
      _m++;
    }else if(board[x0+dx*i][y0+dy*i]==-side){
      _e++;
    }
  }
  if(_e==0){
    return _m;
  }else if(_m==0){
    return -_e;
  }
  return 0;
}

// 現在の盤面の状態がside側にとってどれくらい有利かを評価する。
int eval_board(int side){
  int m[MAX_LINE+1],e[MAX_LINE+1],eval=0;
  memset(m, 0, sizeof(int)*(MAX_LINE+1));
  memset(e, 0, sizeof(int)*(MAX_LINE+1));
  for(int y=0; y<BOARD_HEIGHT; y++){
    for(int x=0; x<BOARD_WIDTH; x++){
      int c;
      c=check_line(x,y,1,0,side);
      if(c>0){ m[c]++; }
      if(c<0){ e[-c]++; }
      c=check_line(x,y,0,1,side);
      if(c>0){ m[c]++; }
      if(c<0){ e[-c]++; }
      c=check_line(x,y,1,1,side);
      if(c>0){ m[c]++; }
      if(c<0){ e[-c]++; }
      c=check_line(x,y,1,-1,side);
      if(c>0){ m[c]++; }
      if(c<0){ e[-c]++; }
    }
  }
  if(m[MAX_LINE]>0){
    eval=MYSELF_WIN;
  }else if(e[MAX_LINE]>0){
    eval=ENEMY_WIN;
  }else{
    for(int i=1; i<MAX_LINE; i++){
      eval += m[i]*pow(2,(i*2)) - e[i]*pow(2,(i*2-1));
    }
  }
  return eval;
}

// 終了チェック
int end_check(){
  int eval=eval_board(PLAYER);
  if(eval==MYSELF_WIN){ return PLAYER_WIN; }
  if(eval==ENEMY_WIN){ return COMP_WIN; }
  for(int x=0; x<BOARD_WIDTH; x++){
    for(int y=0; y<BOARD_HEIGHT; y++){
      if(board[x][y]==FREE){ return CONT; }
    }
  }
  return DRAW;
}

// COMP側の思考
int think_comp(int side, int depth){
  int maxscore=ENEMY_WIN;
  int hand_x=-1;
  int hand_y=-1;
  int score;
  if(depth==0){
    return eval_board(side);
  }
  for(int y=0; y<BOARD_HEIGHT; y++){
    for(int x=0; x<BOARD_WIDTH; x++){
      if(board[x][y]==FREE){
        board[x][y]=side;
        score=-think_comp(-side, depth-1);
        if(score>maxscore){
          maxscore=score;
          hand_x=x;
          hand_y=y;
        }
        board[x][y]=FREE;
      }
    }
  }
  if(hand_x!=-1 && depth==MAX_DEPTH){
    board[hand_x][hand_y]=side;
  }else if(hand_x==-1){
    return eval_board(side);
  }else{
    return maxscore;
  }
}

// 盤面の表示
void disp(){
  tft.fillScreen(WHITE);
  for(int i=0; i<=BOARD_WIDTH; i++){
    tft.drawFastVLine(i*CELL_SIZE, 0, CELL_SIZE*BOARD_HEIGHT, BLACK);
  }
  for(int i=0;i<=BOARD_HEIGHT;i++){
    tft.drawFastHLine(0, i*CELL_SIZE, CELL_SIZE*BOARD_WIDTH, BLACK);
  }

  for(int y=0; y<BOARD_HEIGHT; y++){
    for(int x=0; x<BOARD_WIDTH; x++){
      switch(board[x][y]){
        case PLAYER:
          tft.fillCircle((x+0.5)*CELL_SIZE, (y+0.5)*CELL_SIZE, CELL_SIZE*0.4, BLUE);
          break;
        case COMP:
          tft.fillCircle((x+0.5)*CELL_SIZE, (y+0.5)*CELL_SIZE, CELL_SIZE*0.4, RED);
      }
    }
  }
}

// タップ位置の取得(タップされるまで待つ)
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 game_init(){
  memset(board,0,sizeof(int)*BOARD_WIDTH*BOARD_HEIGHT);
  disp();
}

// 文字列表示
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() {
  Serial.begin(9600);
  delay(1000);
  // 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 loop() {
  int x, y;

  while(true){
    int px, py;
    getTapPoint(&px,&py);
    x=px/CELL_SIZE;
    y=py/CELL_SIZE;
    if(0<=x && x<BOARD_WIDTH && 0<=y && y<BOARD_HEIGHT && board[x][y]==FREE)break;
  }
  board[x][y]=PLAYER;
  disp();

  int score=end_check();
  if(score==CONT){
    think_comp(COMP,MAX_DEPTH);
    disp();
    score=end_check();
  }
  if(score==PLAYER_WIN){
    drawText(0,260,2,BLUE,"PLAYER WIN!");
    waitTap();
  }else if(score==COMP_WIN){
    drawText(0,260,2,RED,"Arduino WIN!");
    waitTap();
  }else if(score==DRAW){
    drawText(0,260,2,GREEN,"DRAW!");
    waitTap();
  }
}

プログラムの解説

かなりの部分がマルバツと共通していますので、説明は簡単に。

定数の定義など
C++
#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

// インスタンスの生成
Elegoo_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RST);
TouchScreen ts=TouchScreen(XP, YP, XM, YM, 300);

// ゲーム用定義
#define FREE 0
#define PLAYER 1
#define COMP -1
#define MAX_DEPTH 1

#define BOARD_WIDTH 8
#define BOARD_HEIGHT 8
#define MAX_LINE 5
#define CELL_SIZE 30

#define MYSELF_WIN 32767
#define ENEMY_WIN -32767
#define PLAYER_WIN 1
#define COMP_WIN -1
#define DRAW 255
#define CONT 0

マルバツとだいたい同じです。

実は定数MAX_DEPTHを2以上にすれば先読みをするようになっているのですが、Arduinoだとあまりに遅いです。表示&入力部分のみ差し替えてPCで動かすと5くらいにしてもさくさく動くのですが。これなら再帰の思考関数をやめて盤面評価関数だけのプログラムにした方が軽くて良いかもしれません(今更)。

ゲーム盤を表す変数
C++
// ゲーム用変数
int board[BOARD_WIDTH][BOARD_HEIGHT];

マルバツのときは3×3の盤面を9要素の一次元配列で表現しましたが、今回は盤面のイメージ通り8×8の二次元配列に変更しました。1要素が1マスの状態を表します。

n個の並びができているかを確認する
C++
// ラインができているかチェック
int check_line(int x0, int y0, int dx, int dy, int side){
  int _m=0,_e=0;
  for(int i=0; i<MAX_LINE; i++){
    if(x0+dx*i>=BOARD_WIDTH || y0+dy*i>=BOARD_HEIGHT || x0+dx*i<0 || y0+dy*i<0){
      _m=_e=0;
      break;
    }
    if(board[x0+dx*i][y0+dy*i]==side){
      _m++;
    }else if(board[x0+dx*i][y0+dy*i]==-side){
      _e++;
    }
  }
  if(_e==0){
    return _m;
  }else if(_m==0){
    return -_e;
  }
  return 0;
}

指定されたマスから指定された方向へ5マス分の石の並びを確認します。
side側のコマがn個+空白ならばn、
side側ではないコマがn個+空白ならば-n、
すべて空白またはside側とside側ではないコマが混在している場合は0を返します。

盤面の評価関数
C++
// 現在の盤面の状態がside側にとってどれくらい有利かを評価する。
int eval_board(int side){
  int m[MAX_LINE+1],e[MAX_LINE+1],eval=0;
  memset(m, 0, sizeof(int)*(MAX_LINE+1));
  memset(e, 0, sizeof(int)*(MAX_LINE+1));
  for(int y=0; y<BOARD_HEIGHT; y++){
    for(int x=0; x<BOARD_WIDTH; x++){
      int c;
      c=check_line(x,y,1,0,side);
      if(c>0){ m[c]++; }
      if(c<0){ e[-c]++; }
      c=check_line(x,y,0,1,side);
      if(c>0){ m[c]++; }
      if(c<0){ e[-c]++; }
      c=check_line(x,y,1,1,side);
      if(c>0){ m[c]++; }
      if(c<0){ e[-c]++; }
      c=check_line(x,y,1,-1,side);
      if(c>0){ m[c]++; }
      if(c<0){ e[-c]++; }
    }
  }
  if(m[MAX_LINE]>0){
    eval=MYSELF_WIN;
  }else if(e[MAX_LINE]>0){
    eval=ENEMY_WIN;
  }else{
    for(int i=1; i<MAX_LINE; i++){
      eval += m[i]*pow(2,(i*2)) - e[i]*pow(2,(i*2-1));
    }
  }
  return eval;
}

現在の盤面の状態がside側にとってどれだけ有利かを数値化する評価関数です。盤が大きく、確認すべきラインが桁違いに増えたため行数が多くなっていますが、基本的な考え方はマルバツの時と同じです。

終了チェック
C++
// 終了チェック
int end_check(){
  int eval=eval_board(PLAYER);
  if(eval==MYSELF_WIN){ return PLAYER_WIN; }
  if(eval==ENEMY_WIN){ return COMP_WIN; }
  for(int x=0; x<BOARD_WIDTH; x++){
    for(int y=0; y<BOARD_HEIGHT; y++){
      if(board[x][y]==FREE){ return CONT; }
    }
  }
  return DRAW;
}

どちらかの勝ち、またはもうコマを置く場所がなければ引き分けという状態の判定をします。

コンピュータ側の手の思考関数
C++
// COMP側の思考
int think_comp(int side, int depth){
  int maxscore=ENEMY_WIN;
  int hand_x=-1;
  int hand_y=-1;
  int score;
  if(depth==0){
    return eval_board(side);
  }
  for(int y=0; y<BOARD_HEIGHT; y++){
    for(int x=0; x<BOARD_WIDTH; x++){
      if(board[x][y]==FREE){
        board[x][y]=side;
        score=-think_comp(-side, depth-1);
        if(score>maxscore){
          maxscore=score;
          hand_x=x;
          hand_y=y;
        }
        board[x][y]=FREE;
      }
    }
  }
  if(hand_x!=-1 && depth==MAX_DEPTH){
    board[hand_x][hand_y]=side;
  }else if(hand_x==-1){
    return eval_board(side);
  }else{
    return maxscore;
  }
}

盤面を二次元配列で表すようになったので、次の手の候補を考えるループが2重になりました。

盤面の表示
C++
// 盤面の表示
void disp(){
  tft.fillScreen(WHITE);
  for(int i=0; i<=BOARD_WIDTH; i++){
    tft.drawFastVLine(i*CELL_SIZE, 0, CELL_SIZE*BOARD_HEIGHT, BLACK);
  }
  for(int i=0;i<=BOARD_HEIGHT;i++){
    tft.drawFastHLine(0, i*CELL_SIZE, CELL_SIZE*BOARD_WIDTH, BLACK);
  }

  for(int y=0; y<BOARD_HEIGHT; y++){
    for(int x=0; x<BOARD_WIDTH; x++){
      switch(board[x][y]){
        case PLAYER:
          tft.fillCircle((x+0.5)*CELL_SIZE, (y+0.5)*CELL_SIZE, CELL_SIZE*0.4, BLUE);
          break;
        case COMP:
          tft.fillCircle((x+0.5)*CELL_SIZE, (y+0.5)*CELL_SIZE, CELL_SIZE*0.4, RED);
      }
    }
  }
}

ここも基本的な流れは同じですが、盤面サイズや1マスのピクセル数などの変更が簡単にできるように変更しました。

タップ位置の取得などはまったく同じ
C++
// タップ位置の取得(タップされるまで待つ)
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 game_init(){
  memset(board,0,sizeof(int)*BOARD_WIDTH*BOARD_HEIGHT);
  disp();
}

// 文字列表示
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() {
  Serial.begin(9600);
  delay(1000);
  // 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 loop() {
  int x, y;

  while(true){
    int px, py;
    getTapPoint(&px,&py);
    x=px/CELL_SIZE;
    y=py/CELL_SIZE;
    if(0<=x && x<BOARD_WIDTH && 0<=y && y<BOARD_HEIGHT && board[x][y]==FREE)break;
  }
  board[x][y]=PLAYER;
  disp();

  int score=end_check();
  if(score==CONT){
    think_comp(COMP,MAX_DEPTH);
    disp();
    score=end_check();
  }
  if(score==PLAYER_WIN){
    drawText(0,260,2,BLUE,"PLAYER WIN!");
    waitTap();
  }else if(score==COMP_WIN){
    drawText(0,260,2,RED,"Arduino WIN!");
    waitTap();
  }else if(score==DRAW){
    drawText(0,260,2,GREEN,"DRAW!");
    waitTap();
  }
}

このあたりは、loop()関数の中でタップした画面上の座標がどのマスを指すのかを計算している式が若干変わっただけで、あとはまったく同じです。

実行結果

途中のバージョンなので盤面最下部の水平線が描画されていません…。

重力四目並べ

ゲームの概要

名前の通りコマを4マス並べることを目的とするのですが、マルバツ(三目並べ)や五目並べが(空いてさえいれば)盤面の好きな位置にコマを置けるのに対して、重力四目並べでは盤面の下方にむかって重力が働いている設定で(実在のゲーム盤では机の上に盤を垂直に立てて遊ぶようになっている)、各列の最下段のマスから順に積み上げていく必要があるのが特徴です。商品として販売されているものは盤面は7×6のものが多いようなので、このプログラムもそうしてあります。

『コネクトフォー』という名で紹介されていることもありますが、これは商品名なのかな?

プログラム

C++
#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

// インスタンスの生成
Elegoo_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RST);
TouchScreen ts=TouchScreen(XP, YP, XM, YM, 300);

// ゲーム用定義
#define FREE 0
#define PLAYER 1
#define COMP -1
#define MAX_DEPTH 1

#define BOARD_WIDTH 7
#define BOARD_HEIGHT 6
#define MAX_LINE 4
#define CELL_SIZE 30

#define MYSELF_WIN 32767
#define ENEMY_WIN -32767
#define PLAYER_WIN 1
#define COMP_WIN -1
#define DRAW 255
#define CONT 0

// ゲーム用変数
int board[BOARD_WIDTH][BOARD_HEIGHT];

// ラインができているかチェック
int check_line(int x0, int y0, int dx, int dy, int side){
  int _m=0,_e=0;
  for(int i=0; i<MAX_LINE; i++){
    if(x0+dx*i>=BOARD_WIDTH || y0+dy*i>=BOARD_HEIGHT || x0+dx*i<0 || y0+dy*i<0){
      _m=_e=0;
      break;
    }
    if(board[x0+dx*i][y0+dy*i]==side){
      _m++;
    }else if(board[x0+dx*i][y0+dy*i]==-side){
      _e++;
    }
  }
  if(_e==0){
    return _m;
  }else if(_m==0){
    return -_e;
  }
  return 0;
}

// 現在の盤面の状態がside側にとってどれくらい有利かを評価する。
int eval_board(int side){
  int m[MAX_LINE+1],e[MAX_LINE+1],eval=0;
  memset(m, 0, sizeof(int)*(MAX_LINE+1));
  memset(e, 0, sizeof(int)*(MAX_LINE+1));
  for(int y=0; y<BOARD_HEIGHT; y++){
    for(int x=0; x<BOARD_WIDTH; x++){
      int c;
      c=check_line(x,y,1,0,side);
      if(c>0){ m[c]++; }
      if(c<0){ e[-c]++; }
      c=check_line(x,y,0,1,side);
      if(c>0){ m[c]++; }
      if(c<0){ e[-c]++; }
      c=check_line(x,y,1,1,side);
      if(c>0){ m[c]++; }
      if(c<0){ e[-c]++; }
      c=check_line(x,y,1,-1,side);
      if(c>0){ m[c]++; }
      if(c<0){ e[-c]++; }
    }
  }
  if(m[MAX_LINE]>0){
    eval = MYSELF_WIN;
  }else if(e[MAX_LINE]>0){
    eval = ENEMY_WIN;
  }else{
    for(int i=1; i<MAX_LINE; i++){
      eval += m[i]*pow(2,(i*2)) - e[i]*pow(2,(i*2-1));
    }
  }
  return eval;
}

// 終了チェック
int end_check(){
  int eval=eval_board(PLAYER);
  if(eval==MYSELF_WIN){ return PLAYER_WIN; }
  if(eval==ENEMY_WIN){ return COMP_WIN; }
  for(int x=0; x<BOARD_WIDTH; x++){
    for(int y=0; y<BOARD_HEIGHT; y++){
      if(board[x][y]==FREE){ return CONT; }
    }
  }
  return DRAW;
}

// COMP側の思考
int think_comp(int side, int depth){
  int maxscore=ENEMY_WIN;
  int hand_x=-1;
  int hand_y=-1;
  int score;
  if(depth==0){
    return eval_board(side);
  }
  for(int x=0; x<BOARD_WIDTH; x++){
    if(board[x][0]==FREE){
      int y;
      for(y=BOARD_HEIGHT-1;y>=0;y--){
        if(board[x][y]==FREE){
          board[x][y]=side;
          break;
        }
      }
      score = -think_comp(-side, depth-1);
      if(score>maxscore){
        maxscore=score;
        hand_x=x;
        hand_y=y;
      }
      board[x][y]=FREE;
    }
  }
  if(hand_x!=-1 && depth==MAX_DEPTH){
    board[hand_x][hand_y]=side;
  }else if(hand_x==-1){
    return eval_board(side);
  }else{
    return maxscore;
  }
}

// 盤面の表示
void disp(){
  tft.fillScreen(WHITE);
  for(int i=0; i<=BOARD_WIDTH; i++){
    tft.drawFastVLine(i*CELL_SIZE, 0, CELL_SIZE*BOARD_HEIGHT, BLACK);
  }
  for(int i=0;i<=BOARD_HEIGHT;i++){
    tft.drawFastHLine(0, i*CELL_SIZE, CELL_SIZE*BOARD_WIDTH, BLACK);
  }

  for(int y=0; y<BOARD_HEIGHT; y++){
    for(int x=0; x<BOARD_WIDTH; x++){
      switch(board[x][y]){
        case PLAYER:
          tft.fillCircle((x+0.5)*CELL_SIZE, (y+0.5)*CELL_SIZE, CELL_SIZE*0.4, BLUE);
          break;
        case COMP:
          tft.fillCircle((x+0.5)*CELL_SIZE, (y+0.5)*CELL_SIZE, CELL_SIZE*0.4, RED);
      }
    }
  }
}

// タップ位置の取得(タップされるまで待つ)
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 game_init(){
  memset(board,0,sizeof(int)*BOARD_WIDTH*BOARD_HEIGHT);
  disp();
}

// 文字列表示
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() {
  Serial.begin(9600);
  delay(1000);
  // 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 loop() {
  int x, y;

  while(true){
    int px, py;
    getTapPoint(&px,&py);
    x=px/CELL_SIZE;
    if(0<=x && x<BOARD_WIDTH && board[x][0]==FREE)break;
  }
  for(int y=BOARD_HEIGHT-1; y>=0; y--){
    if(board[x][y]==FREE){
      board[x][y]=PLAYER;
      break;
    }
  }
  disp();

  int score=end_check();
  if(score==CONT){
    think_comp(COMP,MAX_DEPTH);
    disp();
    score = end_check();
  }
  if(score==PLAYER_WIN){
    drawText(0,260,2,BLUE,"PLAYER WIN!");
    waitTap();
  }else if(score==COMP_WIN){
    drawText(0,260,2,RED,"Arduino WIN!");
    waitTap();
  }else if(score == DRAW){
    drawText(0,260,2,GREEN,"DRAW!");
    waitTap();
  }
}

五目並べとほとんど同じです。ルールの違いによりプレイヤー、コンピュータ双方のコマを置く部分だけちょっと違います…と言いたいところですが例によって考えなしに作り始めたので他にも五目並べから若干修正しています…。

実行結果

まとめ

我ながら、いくらなんでもゲーム弱すぎだろ…いやタップミスもあるんだけど…

コメント

タイトルとURLをコピーしました