ScratchとGamebuino を連携して一人ポーカー

#はじめに

　Arduino の応用機器である Gamebuino を入手したので、以前から興味があった Scratch との連携をやってみました。
Gamebuino は、言ってしまえば、Arduino UNO にLCDやプッシュボタン、バッテリー等を付加して、コンパクトにしたものです。
一方、Scratch は、ブロックを組み合わせてインタラクティブにプログラミングを体験できる環境です。
題材を考えた結果、今回は、簡単な一人ポーカーを作ってみました。

#具体的に

　Scratch でランダムにカードの山札を作り、その中から5枚のカードをGamubuinoに配布します。Gamebuinoには、配布されたカードが表示されます。
　ユーザは、その中の不要なカードをカーソルで選択し、Bボタンをクリックして Scratch へ伝えます。Scratchは、指定されたカードを破棄し、山札から次のカードをGamebuinoへ渡します。
　ユーザは、役がそろったと思えば、Aボタンをクリックして、Scratchへ役の判定を依頼します。Scratchは、判定結果をGamebuinoへ通知しますが、その時、役ができていなければ、プレイを継続します。
　また、より高い役を目指す場合は、Aボタンをクリックせずに、Bボタンでカードを破棄することができます。ただし、5回を超えてカードを変えた時点で、役を自動判定し、ゲームは終了します。

#環境
　
　環境としては、下記を使用します。
　
##１．PC
　・Windows 10
　・Scratch オフラインエディタ 2.0
　・Python 3.6　（Anaconda を利用すればより簡単だと思います）
　・Arduino IDE 1.8.5　（ビルドができれば、他のバージョンでもOKだと思います）
　		なお、事前に Gamebuino ライブラリをインストールしておいてください。
　		
##２．Gamebuino
　PCとUSBケーブルで接続したまま、シリアル通信でやり取りをします。もし、PCに他のシリアル通信機器が接続してある場合には、あらかじめ外しておいてください。

#Gamebuino
　　Gamebuino に、PCとのシリアル通信、カード表示、メッセージ表示、サウンド鳴、ボタンクリックなどの機能を提供するスケッチを格納します。
　任意のフォルダに、次のソースを格納してください。

```c++:carduino.ino
//================================================
//                  Carduino
//
//  Gamebuino: Arduino UNO compatible
//------------------------------------------------
#include  <SPI.h>
#include  <Gamebuino.h>

#include  "GBtones.h"
#include  "Cardset.h"

//------------------------------------------------
//                    card info
//------------------------------------------------
struct CardInfo {
    int   suit;
    int   number;
};

//------------------------------------------------
//                    define
//------------------------------------------------
#define   GAME_TITLE    "Carduino"
#define   VERSION       1

//------------------------------------------------
//                button
//------------------------------------------------
#define   PRC_UP        0
#define   PRC_RIGHT     1
#define   PRC_DOWN      2
#define   PRC_LEFT      3
#define   PRC_A         4
#define   PRC_B         5

//------------------------------------------------
#define   CNT_MESS      200
#define   LEN_MESS      12
#define   SX_MESS       10
#define   SY_MESS       10

//------------------------------------------------
#define   ID_CHANNEL    0

#define   SND_SLOW      8
#define   SND_FAST      2
#define   DLY_SLOW      12
#define   DLY_FAST      3

//------------------------------------------------
//#define   RAD_BASE      7

#define   NUM_CARD      5
#define   WID_CARD      16
#define   HGH_CARD      24

#define   WID_SUIT      10
#define   HGH_SUIT      9

#define   WID_NUMBER    10
#define   HGH_NUMBER    9

#define   WID_CURSOR    10
#define   HGH_CURSOR    4

#define   SX_SUIT       3
#define   SY_SUIT       2

#define   SX_NUMBER     3
#define   SY_NUMBER     13

#define   SX_CURSOR     4
#define   SY_CURSOR     25

#define   CNT_CURSOR    20

//------------------------------------------------
#define   COM_SPEED     57600
#define   CMD_LENGTH    3

//------------------------------------------------
//                  music
//------------------------------------------------
const int p00[]  = { NOTE_C4, 2, NOTE_E4, 2, NOTE_G4, 2 ,NOTE_C4, 2, NOTE_E4, 2, NOTE_G4, 2 ,0 };
const int p01[]  = { NOTE_G4, 2, NOTE_E4, 2, NOTE_C4, 2 ,NOTE_G4, 2, NOTE_E4, 2, NOTE_C4, 2 ,0 };
const int p02[]  = { NOTE_C4, 2, NOTE_D4, 2, NOTE_E4, 2 ,NOTE_F4, 2, NOTE_G4, 2, NOTE_A4, 2 ,0 };
const int p03[]  = { NOTE_C5, 2, NOTE_G5, 2, NOTE_C5, 2 ,NOTE_G5, 2, NOTE_C5, 2, NOTE_G5, 2 ,0 };
const int p04[]  = { NOTE_C5, 4, NOTE_E5, 2, NOTE_G5, 2 ,NOTE_C5, 4, NOTE_E5, 2, NOTE_G5, 2 ,0 };

//------------------------------------------------
//                      font
//------------------------------------------------
extern const byte font5x7[];                    // get the default large font

//------------------------------------------------
//                        field
//------------------------------------------------
Gamebuino   pb;                                 // Gamebuino instance

boolean paused    = false;
boolean flgCursor = false;
boolean flgMusic  = false;

int     cntProc   = 0;
int     numProc   = 0;
int     inxProc   = 0;
int     inxBttn   = 0;
int     inxCursor = 0;
int     cntCursor = 0;
int     cntMess   = 0;

int*    pntMusic  = 0;
int     cntMusic  = 0;

//------------------------------------------------
//    Gamebuino: 84 x 48
//------------------------------------------------
int     crdX[]      = {  0, 17, 34, 51, 68 };
int     crdY[]      = { 19, 19, 19, 19, 19 };
int*    musicSet[]  = { p00, p01, p02, p03, p04 };

bool    btnPressed  = false;

String    ms;                                 // message
CardInfo  cf[NUM_CARD];                       // cards        

//------------------------------------------------
//                    setup
//------------------------------------------------
void setup() {
  
    // serial communication
    Serial.begin(COM_SPEED);
  
    // initialize Gamebuino object
    pb.begin();

    //change the font to the large one
    pb.display.setFont  (font5x7);

    // display title and main menu
    initialize();
    
    //hide the battery indicator
    pb.battery.show = false;

    // frame count
    pb.frameCount   = LCDWIDTH * 8;
    pb.setFrameRate (30);

    // sound command
    pb.sound.command(CMD_INSTRUMENT, 1, 0, 3);
    pb.sound.setVolume(7);
}

//------------------------------------------------
//                    loop
//------------------------------------------------
void loop() {
  
    // from scratch
    procCommand();

    // game board procedure
    if(pb.update()){
        // sound proc
        procButton();

        // show header
        showHeader();

        // scan check
        cntProc++;
        if (cntProc > numProc - 1) {
            cntProc = 0;
            inxProc = -1;
        }
        // sound
        procSound();

        // show message
        showMess();
        
        // show card
        showCard();

        // blink cursor
        blinkCursor();

        // end of game ?
        if(pb.buttons.pressed(BTN_C)) {
            pb.sound.stopNote();
            showTitle();
            initialize();
        }
    }
}

//************************************************
//                  functions
//------------------------------------------------
//                  read command
//------------------------------------------------
String  readCommand() {
    String  s;
  
    // read string from serial port
    if (Serial.available()) {
        s = Serial.readString();
        s.replace("\n", " ");
        s.replace("\r", " ");
        s.trim();
    }
    return  s;
}

//------------------------------------------------
//                  write status
//------------------------------------------------
void  writeStatus(String str) {
    Serial.println(str);
}

//------------------------------------------------
//                  command process
//------------------------------------------------
void  procCommand() {
    // read command from PC
    String  s = readCommand();
    
    if (s.length() > 0) {
        String  c = s.substring(0, CMD_LENGTH);           // get command
        String  t = s.substring(CMD_LENGTH);              // get option
    
        // parse command
        do {
            if (c.equals("INQ")) {
                cmdINQ(t);
                break;
            }
            if (c.equals("CRD")) {
                cmdCards(t);
                break;
            }
            if (c.equals("MES")) {
                cmdMess(t);
                break;
            }
            if (c.equals("SND")) {
                cmdSound(t);
                break;
            }
        } while (false);
    }
}

//---------------------------------------------------
//                  INQ
//---------------------------------------------------
void  cmdINQ(String str) {
    String  s = String("REP");

    s.concat(String(VERSION));
    writeStatus(s);
}

//------------------------------------------------
//                  messages
//------------------------------------------------
void  cmdMess(String str) {
    ms      = str;
    cntMess = CNT_MESS;
}

//------------------------------------------------
//                  show message
//------------------------------------------------
void  showMess() {

    if (cntMess > 0) {
        cntMess--;
        pb.display.cursorX  = SX_MESS;
        pb.display.cursorY  = SY_MESS;
        pb.display.print (ms);
    }
}

//------------------------------------------------
//                 distribute cards
//
//  s1nn1s2nn2s3nn3s4nn4s5nn5
//    s=0: card none
//------------------------------------------------
void  cmdCards(String str) {
    int     s, n;
    int     i, j;

    for (i = 0; i < NUM_CARD; i++) {
        s = getSuit  (str);
        n = getNumber(str);
        cf[i].suit    = s;
        cf[i].number  = n;
    }
}

//------------------------------------------------
//                get suit
//------------------------------------------------
int   getSuit(String& str) {
    String  s = str.substring(0, 1);
    int     n = SUIT_NONE;
    int     t;

    str = str.substring(1);
    if (s.equals("C"))
        n = SUIT_CLUB;
    else if (s.equals("H"))
        n = SUIT_HEART;
    else if (s.equals("S"))
        n = SUIT_SPADE;
    else if (s.equals("D"))
        n = SUIT_DIAMOND;
    return  n;
}

//------------------------------------------------
//                get number
//------------------------------------------------
int   getNumber(String& str) {
    String  s = str.substring(0, 1);
    int     n = 0;

    do {
        if (s.equals("1")) {
            String  t = str.substring(1, 2);
            if (t.equals("0") || t.equals("1") || t.equals("2") || t.equals("3")) {
                s = str.substring(0, 2);
                str = str.substring(2);
                break;
            }
        }
        str = str.substring(1);
    } while (false);

    if (s.equals("A"))
        n = 0;
    else if (s.equals("J"))
        n = NUM_J;
    else if (s.equals("Q"))
        n = NUM_Q;
    else if (s.equals("K"))
        n = NUM_K;
    else
        n = s.toInt() - 1;

    return  n;
}

//------------------------------------------------
//                    show card
//------------------------------------------------
void  showCard() {
    int   i;

    for (i = 0; i < NUM_CARD; i++) {
        if (cf[i].suit != SUIT_NONE) {
            drawCard(crdX[i], crdY[i], cf[i].suit, cf[i].number);
        }
    }
}

//------------------------------------------------
//                  draw card
//  suit:
//    0:Club, 1:Heart, 2:Spade, 3:Diamond
//------------------------------------------------
void  drawCard(int sx, int sy, int suit, int number) {
    uint8_t*  p;
    
    // set color
    pb.display.setColor(7);

    // outer line
    pb.display.drawRect  (sx, sy, WID_CARD, HGH_CARD);

    p = pgm_read_word_near (suitSet + suit);
    drawImage (sx + SX_SUIT, sy + SY_SUIT, HGH_NUMBER, p);

    p = pgm_read_word_near (numberSet + number);
    drawImage (sx + SX_NUMBER, sy + SY_NUMBER, HGH_NUMBER, p);
}

//------------------------------------------------
//                    blink cursor
//------------------------------------------------
void  blinkCursor() {

    cntCursor++;
    if (cntCursor > CNT_CURSOR - 1) {
        cntCursor = 0;
        flgCursor = !flgCursor;
    }

    if (flgCursor) {
        drawCursor(crdX[inxCursor], crdY[inxCursor]);
    }
}

//------------------------------------------------
//                    left cursor
//------------------------------------------------
void  leftCursor() {

    if (inxCursor > 0)
        inxCursor--;
}

//------------------------------------------------
//                    right cursor
//------------------------------------------------
void  rightCursor() {

    if (inxCursor < NUM_CARD - 1)
        inxCursor++;
}

//------------------------------------------------
//                    draw cursor
//------------------------------------------------
void  drawCursor(int sx, int sy) {
  
    drawImage (sx + SX_CURSOR, sy + SY_CURSOR, HGH_CURSOR, pCursor);

}

//------------------------------------------------
//                    draw image
//------------------------------------------------
void  drawImage(int sx, int sy, int hy, uint16_t pt) {
    uint16_t  im, mk;
    int       x,  y;

    for (y = sy; y < sy + hy; y++) {
      im = pgm_read_byte_near (pt++);
      im <<= 8;
      im += pgm_read_byte_near (pt++);
      mk = 0x200;
      for (x = sx; x < sx + WID_SUIT; x++) {
          if (im & mk) {
              pb.display.drawPixel(x, y) ;
          }
          mk >>= 1;
      }
  }
}

//------------------------------------------------
//                button procedure
//------------------------------------------------
void  procButton() {
  
    scanButton();
    if (inxBttn > -1) {
        if (!btnPressed) {
            btnPressed = true;

            // set button index
            inxProc = inxBttn;
            cntProc = 0;
    
            // process
            btnProc();
        }
    }
    else {
        if (btnPressed) {
            String  s = "BTNN" + String(inxCursor + 1);
            writeStatus (s);
            btnPressed = false;
        }
    }
}

//------------------------------------------------
//                    scan button
//------------------------------------------------
void  scanButton() {

    inxBttn = -1;
    if (pb.buttons.repeat(BTN_UP, 1)) {
        inxBttn = PRC_UP;
    }

    if (pb.buttons.repeat(BTN_RIGHT, 1)) {
        inxBttn = PRC_RIGHT;
    }

    if (pb.buttons.repeat(BTN_DOWN, 1)) {
        inxBttn = PRC_DOWN;  
    }

    if (pb.buttons.repeat(BTN_LEFT, 1)) {
        inxBttn = PRC_LEFT;
    }

    if (pb.buttons.repeat(BTN_A, 1)) {
        inxBttn = PRC_A;
    }

    if (pb.buttons.repeat(BTN_B, 1)) {
        inxBttn = PRC_B;
    }
}

//------------------------------------------------
//                    button process
//------------------------------------------------
void  btnProc() {
    String    s;
    
    switch (inxProc) {
        case PRC_RIGHT:
            rightCursor();
            pb.sound.playNote(NOTE_C4, SND_FAST, 0);
            numProc = DLY_FAST;
            break;
            
        case PRC_LEFT:
            leftCursor();
            pb.sound.playNote(NOTE_C4, SND_FAST, 0);
            numProc = DLY_FAST;
            break;

        case PRC_UP:
            // transmission
            pb.sound.playNote(NOTE_E4, SND_FAST, 0);
            s = "BTNU" + String(inxCursor + 1);
            writeStatus (s);
            break;

        case PRC_DOWN:
            // transmission
            pb.sound.playNote(NOTE_E4, SND_FAST, 0);
            s = "BTND" + String(inxCursor + 1);
            writeStatus (s);
            break;

        case PRC_A:
            // transmission
            pb.sound.playNote(NOTE_G4, SND_FAST, 0);
            s = "BTNA" + String(inxCursor + 1);
            writeStatus (s);
            break;
            
        case PRC_B:
            // transmission
            pb.sound.playNote(NOTE_G4, SND_FAST, 0);
            s = "BTNB" + String(inxCursor + 1);
            writeStatus (s);
            break;
    }
}

//------------------------------------------------
//                sound command
//------------------------------------------------
void  cmdSound(String str) {

    int   i = str.toInt() - 1;
    pntMusic = musicSet[i];
    pb.sound.playNote(*pntMusic++, *pntMusic, 0);
    cntMusic = *pntMusic++ + 2;
}

//------------------------------------------------
//                sound process
//------------------------------------------------
void  procSound() {

    if (cntMusic > 0) {
        cntMusic--;
        if ((cntMusic == 0) && (*pntMusic)) {
            pb.sound.playNote(*pntMusic++, *pntMusic, 0);
            cntMusic = *pntMusic++ + 2;
        }
    }
}

//------------------------------------------------
//        display title and set random seed
//------------------------------------------------
void  showTitle() {
    // display title and main menu
    pb.titleScreen      (F(GAME_TITLE));

    //pick a different random seed each time for games to be different
    pb.pickRandomSeed();
}

//------------------------------------------------
//            initialization
//------------------------------------------------
void  initialize() {

    for (int i = 0; i < NUM_CARD; i++) {
        cf[i].suit    = SUIT_NONE;
        cf[i].number  = 0;
    }
}

//------------------------------------------------
//                  show header
//------------------------------------------------
void  showHeader() {
      pb.display.print(F(GAME_TITLE));  
}
```

　また、下記の2つのヘッダファイルを同一フォルダに格納してください。


```c++:CardSet.h
//------------------------------------------------
//                    cards
//------------------------------------------------
#define   SUIT_CLUB     0
#define   SUIT_HEART    1
#define   SUIT_SPADE    2
#define   SUIT_DIAMOND  3
#define   SUIT_NONE     99

#define   NUM_A         0
#define   NUM_2         1
#define   NUM_3         2
#define   NUM_4         3
#define   NUM_5         4
#define   NUM_6         5
#define   NUM_7         6
#define   NUM_8         7
#define   NUM_9         8
#define   NUM_10        9
#define   NUM_J         10
#define   NUM_Q         11
#define   NUM_K         12

//------------------------------------------------
//                  card images
//------------------------------------------------
// Club
const uint8_t pClub[] PROGMEM = {
    B00000000, B01111000,
    B00000000, B11111100,
    B00000000, B11111100,
    B00000001, B01111010,
    B00000011, B11111111,
    B00000011, B11111111,
    B00000001, B10110110,
    B00000000, B00110000,
    B00000000, B11111100
};

// Heart
const uint8_t pHeart[] PROGMEM = {
    B00000001, B10000110,
    B00000011, B11001111,
    B00000011, B11111111,
    B00000011, B11111111,
    B00000011, B11111111,
    B00000000, B11111100,
    B00000000, B01111000,
    B00000000, B00110000,
    B00000000, B00110000
};

// Spade
const uint8_t pSpade[] PROGMEM = {
    B00000000, B00110000,
    B00000000, B00110000,
    B00000000, B01111000,
    B00000001, B11111110,
    B00000011, B11111111,
    B00000011, B11111111,
    B00000001, B10110110,
    B00000000, B00110000,
    B00000000, B11111100
};

// Diamond
const uint8_t pDiamond[] PROGMEM = {
    B00000000, B00110000,
    B00000000, B00110000,
    B00000000, B01111000,
    B00000000, B11111100,
    B00000011, B11111111,
    B00000000, B11111100,
    B00000000, B01111000,
    B00000000, B00110000,
    B00000000, B00110000
};

// A
const uint8_t pCA[] PROGMEM = {
    B00000000, B01111000,
    B00000000, B01001000,
    B00000000, B10000100,
    B00000000, B10000100,
    B00000001, B00000010,
    B00000001, B11111110,
    B00000010, B00000001,
    B00000010, B00000001,
    B00000010, B00000001
};

// 2
const uint8_t pC2[] PROGMEM = {
    B00000000, B11111100,
    B00000001, B00000010,
    B00000010, B00000001,
    B00000000, B00000001,
    B00000000, B00111110,
    B00000001, B11000000,
    B00000010, B00000000,
    B00000010, B00000000,
    B00000011, B11111111
};

// 3
const uint8_t pC3[] PROGMEM = {
    B00000000, B11111100,
    B00000001, B00000010,
    B00000010, B00000001,
    B00000000, B00000010,
    B00000000, B00111100,
    B00000000, B00000010,
    B00000010, B00000001,
    B00000001, B00000010,
    B00000000, B11111100
};

// 4
const uint8_t pC4[] PROGMEM = {
    B00000000, B00001100,
    B00000000, B00010100,
    B00000000, B00100100,
    B00000000, B01000100,
    B00000000, B10000100,
    B00000001, B00000100,
    B00000011, B11111111,
    B00000000, B00000100,
    B00000000, B00000100
};

// 5
const uint8_t pC5[] PROGMEM = {
    B00000011, B11111111,
    B00000010, B00000000,
    B00000010, B00000000,
    B00000010, B01111100,
    B00000011, B10000010,
    B00000000, B00000001,
    B00000000, B00000001,
    B00000010, B00000010,
    B00000001, B11111100
};

// 6
const uint8_t pC6[] PROGMEM = {
    B00000000, B11111110,
    B00000001, B00000001,
    B00000010, B00000000,
    B00000010, B01111100,
    B00000011, B10000010,
    B00000010, B00000001,
    B00000010, B00000001,
    B00000001, B00000010,
    B00000000, B11111100
};

// 7
const uint8_t pC7[] PROGMEM = {
    B00000011, B11111111,
    B00000010, B00000001,
    B00000010, B00000010,
    B00000000, B00000100,
    B00000000, B00001000,
    B00000000, B00010000,
    B00000000, B00100000,
    B00000000, B00100000,
    B00000000, B00100000
};

// 8
const uint8_t pC8[] PROGMEM = {
    B00000000, B11111100,
    B00000001, B00000010,
    B00000010, B00000001,
    B00000001, B00000010,
    B00000000, B11111100,
    B00000001, B00000010,
    B00000010, B00000001,
    B00000001, B00000010,
    B00000000, B11111100
};

// 9
const uint8_t pC9[] PROGMEM = {
    B00000000, B11111100,
    B00000001, B00000010,
    B00000010, B00000001,
    B00000010, B00000001,
    B00000001, B00000011,
    B00000000, B11111101,
    B00000000, B00000001,
    B00000010, B00000010,
    B00000001, B11111100
};

// 10
const uint8_t pC10[] PROGMEM = {
    B00000001, B00011110,
    B00000011, B00100001,
    B00000001, B00100001,
    B00000001, B00100001,
    B00000001, B00100001,
    B00000001, B00100001,
    B00000001, B00100001,
    B00000001, B00100001,
    B00000011, B10011110
};

// J
const uint8_t pCJ[] PROGMEM = {
    B00000000, B00011111,
    B00000000, B00000100,
    B00000000, B00000100,
    B00000000, B00000100,
    B00000000, B00000100,
    B00000011, B10000100,
    B00000001, B00000100,
    B00000001, B00000100,
    B00000000, B11111000
};

// Q
const uint8_t pCQ[] PROGMEM = {
    B00000000, B11111100,
    B00000001, B00000010,
    B00000010, B00000001,
    B00000010, B00000001,
    B00000010, B00000001,
    B00000010, B01111001,
    B00000010, B10000110,
    B00000001, B00000100,
    B00000000, B11111011
};

// K
const uint8_t pCK[] PROGMEM = {
    B00000011, B10000011,
    B00000001, B00000010,
    B00000001, B00000100,
    B00000001, B00001000,
    B00000001, B11110000,
    B00000001, B00001000,
    B00000001, B00000100,
    B00000001, B00000010,
    B00000011, B10000011
};

// cursor
const uint8_t pCursor[] PROGMEM = {
    B00000000, B00110000,
    B00000000, B00110000,
    B00000000, B11001100,
    B00000001, B10000110
};

//------------------------------------------------
//                  card set          
//------------------------------------------------
const uint8_t* const suitSet[]   PROGMEM = { pClub, pHeart, pSpade, pDiamond };         
const uint8_t* const numberSet[] PROGMEM = { pCA, pC2, pC3, pC4, pC5, pC6, pC7, pC8, pC9, pC10, pCJ, pCQ, pCK };  
```
       
```c++:GBTones.h
#ifndef GAMEBUINO_TONES
#define GAMEBUINO_TONES

#define 	NOTE_AS2 	0
#define 	NOTE_B2  	1
#define 	NOTE_C3  	2
#define 	NOTE_CS3 	3
#define 	NOTE_D3  	4
#define 	NOTE_DS3 	5
#define 	NOTE_E3  	6
#define 	NOTE_F3  	7
#define 	NOTE_FS3 	8
#define 	NOTE_G3  	9
#define 	NOTE_GS3 	10
#define 	NOTE_A3  	11
#define 	NOTE_AS3 	12
#define 	NOTE_B3  	13
#define 	NOTE_C4  	14
#define 	NOTE_CS4 	15
#define 	NOTE_D4  	16
#define 	NOTE_DS4 	17
#define 	NOTE_E4  	18
#define 	NOTE_F4  	19
#define 	NOTE_FS4 	20
#define 	NOTE_G4  	21
#define 	NOTE_GS4 	22
#define 	NOTE_A4  	23
#define 	NOTE_AS4 	24
#define 	NOTE_B4  	25
#define 	NOTE_C5  	26
#define 	NOTE_CS5 	27
#define 	NOTE_D5  	28
#define 	NOTE_DS5 	29
#define 	NOTE_E5  	30
#define 	NOTE_F5  	31
#define 	NOTE_FS5 	32
#define 	NOTE_G5  	33
#define 	NOTE_GS5 	34
#define 	NOTE_A5  	35
#define 	NOTE_AS5 	36
#define 	NOTE_B5  	37
#define 	NOTE_C6  	38
#define 	NOTE_CS6 	39
#define 	NOTE_D6  	40
#define 	NOTE_DS6 	41
#define 	NOTE_E6  	42
#define 	NOTE_F6  	43
#define 	NOTE_FS6 	44
#define 	NOTE_G6  	45
#define 	NOTE_GS6 	46
#define 	NOTE_A6  	47
#define 	NOTE_AS6 	48
#define 	NOTE_B6  	49
#define 	NOTE_C7  	50
#define 	NOTE_CS7 	51
#define 	NOTE_D7  	52
#define 	NOTE_DS7 	53
#define 	NOTE_E7  	54
#define 	NOTE_F7  	55
#define 	NOTE_FS7 	56
#define 	NOTE_G7  	57
#define 	NOTE_GS7  	58

#define 	NOTE_NONE  	63

#endif
```

　これらのソースを格納したら、ArduinoIDEでビルドし、Gamebuinoへ書き込んでください。
　なお、　PCとGamebuino 間のコマンドシーケンスは、次の通りです。

##１．PCからGamebuinoへ
```
- INQ
	バージョンを確認する。

- CRDsnnsnnsnnsnnsnn		（1枚目から5枚目までのスートと数字）
	カードを配布する。
s：スート
C：クラブ
H：ハート
S：スペード
D：ダイアモンド
N：カードなし（ダミーの数字が必要）
n：数字 A、2、 ・・・、10、J、Q、K

- MESmessage
 メッセージを5秒程度表示する
　
- SNDn
 1秒程度音を鳴らす。 n：パターン1 ～ 5
```
##２．GamebuinoからPCへ

```
- REP1
 INQ に対する回答
　
- BTNbn
ボタンが押された
 b：ボタン種別
 U：上
 D：下
 A：A
 B：B
 n：ボタンが押された時のカーソルの位置 1 ～ 5
```

#Scratch
　Scratch とGamebuino は、Scratch の拡張機能で接続します。
　流れとしては、Scratch に追加した拡張ブロックから Pythonで作成したサーバを経由して、Gamebuinoと連携します。
　　拡張ブロックとサーバ間のコマンドシーケンスは、次の通りです。

```
- dist_card
 カードを配布する。

- show_message
 メッセージを表示する。
		
- play_beep
 ビープを鳴らす。
		
- read_button
 押されたボタンを取得する。
		
- read_position
 ボタンが押された時のカーソルの位置を取得する。
```
　Scratchからのコマンドシーケンスを受け取ったサーバは、Gamebuinoのコマンドシーケンスに変換して送信、または受信を行います。

##１．フォルダ構成
　サーバを実行するためには、任意のフォルダに、以下のフォルダ構成を作成し、それぞれのファイルを格納します。（- で始まるものがフォルダです）
　なお、\_\_init\_\_.py は空のファイルです。

```
 -carduino
     -s2carduino
         -configuration
               __init__.py
               carduino.cfg
         -poker4alone
               poker4alone.sb2
         -ScratchFiles
               -ExtensionDescriptors
                   __init__.py
                   s2carduino_JA.s2e
      __init__.py
      __main__.py
      s2carduino_serial.py
```
##２．Config
　
　　Python で作成したサーバの定義情報を格納します。
　scratch_project を指定すると、Scratchを起動する際に自動起動します。なお、現状では日本語のみに対応しています。

```Ini:carduino.cfg
[scratch_info]
scratch_language = 2
windows_wait_time = 3
scratch_executable = default
# scratch_project = 
```

##３．拡張ブロック
　Scratchのプロジェクトとサーバ間の橋渡しをするために追加したブロックです。
　　現状は、日本語のみに対応しています。
　　また、先頭のボード番号は、複数のGamebuinoが接続された場合に、区別するためのものです。
　　今回のプロジェクトでは、複数のGamebuinoには対応していません。

```JSON:s2carduino_JA.s2e
{
    "extensionName": "s2carduino",
    "extensionPort": 50209,
    "url": "http://localhost",
    "blockSpecs": [
        [
            "",
            "ボード%m.board のカードを %s %n %s %n %s %n %s %n %s %n にする",
            "dist_card",
            "0",
            "N",
            "0",
            "N",
            "0",
            "N",
            "0",
            "N",
            "0",
            "N",
            "0"
        ],
        [
            "",
            "ボード%m.board に %s を表示",
            "show_message",
            "0",
            "message"
        ],
        [
            "",
            "ボード%m.board のビープ %m.pattern を鳴らす",
            "play_beep",
            "0",
            "1"
        ],
        [
            "r",
            "ボード%m.board のボタンを読む",
            "read_button",
            "0"
        ],
        [
            "r",
            "ボード%m.board の位置を読む",
            "read_position",
            "0"
        ],
    ],
    "menus": {
        "board": [
            "0",
            "1",
            "2",
            "3"
        ],
        "suit": [
            "C",
            "H",
            "S",
            "D",
            "N"
        ],
        "number": [
            "A",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "10",
            "J",
            "Q",
            "K"
        ],
        "pattern": [
            "1",
            "2",
            "3",
            "4",
            "5"
    	]
    }
}
```
##４．Scratch プロジェクト
　Scratch プロジェクトについては、画像を参照してください。
　各ブロックは、次の機能を持ちます。
###０）リスト
　　リストは、固定長で使用しているので、作成時に長さを確保しておく必要があります。
　　　- SUIT_BASE：サーバへのコマンドシーケンスで使用するスート文字へ変換するためのリストです。
　　　　　　　　　　値は、あらかじめ格納しておく必要があります。
　　　- SUIT：Gamebuinoへ配布したカードのスートを格納します。
　　　- NUMBER：Gamebuinoへ配布したカードの番号を格納します。
　　　- STOCK_SUIT：山札のスートを格納します。
　　　- STOCK_NUMBER：山札の番号を格納します。
　　　- TEMP：役の検査をする際などに一時的に使用します。
　　　　　　　　リストの6番目には、14を事前に格納しておく必要があります。（ストレートの検査用です）
　　　- USED：山を作る際に、山に積み上げ済みか否かをチェックするために使用します。
　　　- COUNTER：役を検査する際に、同一番号のカードの枚数を格納します。
###１）開始ブロック
　　緑色の旗がクリックされると処理を開始します。
　　　- カードの積み上げで、山を作ります。
　　　- カードの配布で、山の上から5枚をGamubuinoへ配布します。
　　　- カードをGamebuinoに表示します。
　　　- ボタン検査を使って、Gamebuinoのボタンがクリックされたか否かを検査します。もし、クリックされた場合は、下記の処理を行います。
　　　　　Aボタンがクリックされると、役の検査を行います。
　　　　　　もし役ができていると、役を表示して処理を終了します。
　　　　　Bボタンがクリックされると、カーソルの位置にあるカードを破棄して、山の次の1枚を渡します。
　　　- カード交換（破棄と配布）が5回を超えた場合、役の判定を行い処理を終了します。
###２）カード積上げ
　　52枚のカードをランダムに山に積みあげます。
　　ただし、すべてのカードを積みあげると時間がかかりすぎるため、使用する。11枚のみを積みあげます。
###３）カード配布
　　カード分配を5回呼び出すことにより、山からカードを5枚配布します。
###４）カード分配
　　山から1枚カードを取り出し、Gamebuinoのリストへ格納します。
###５）カード表示
　　Gamebuinoのカードリスト（SUIT、NUMBER）に格納されているカードを拡張ブロックを使用してGamebuinoへ渡します。
###６）ボタン検査
　　拡張ブロックからクリックされたボタンとクリックされた際の位置を読み取ります。
　　ここでは、ボタンが押されたままの場合も1回と判定するための処理をしています。
###７）役検査
　　Gamebuino のカード（SUIT、NUMBERリスト）の役を検査します。
　　役は、ストレート、フラッシュ。4カード、フルハウス、3カード、2ペア、1ペアです。
　　（ロイヤルストレートフラッシュは、未確認です。）
###８）整列
　　NUMBERリストの内容をTEMPリストへ複製したのち、昇順に整列します。
###９）ストレート
　　TEMPリストの値が順番に並んでいるかどうかを検査します。
　　もし、1番目がAで、2番目が10の場合は、10、J、Q、K、A の順番になっているかどうかを検査します。
###１０）フラッシュ
　　SUITリストがすべて同じであるかどうかを検査します。
###１１）カウント
　　TEMPリストの値ごとの個数をカウントしてCOUNTリストへ格納します。
###１２）ペア
　　COUNTリストの内容から、4カード、フルハウス、3カード、2ペア、1ペアを判定します。
###１３）役決定
　　役を検査した結果から、最終的な役を決定します。

![img01.png](https://qiita-image-store.s3.amazonaws.com/0/245347/4ade0567-beed-053e-3757-fa2ba05b5a62.png)

![img02.png](https://qiita-image-store.s3.amazonaws.com/0/245347/4b653119-7307-e5de-2e6f-a9c26d00f69f.png)

![img03.png](https://qiita-image-store.s3.amazonaws.com/0/245347/5ab65efd-5477-0da8-0972-7609be0b121f.png)

##５．サーバ
　Python で作成したサーバは、本体とシリアル通信用のプログラムの2本があります。
　　
　１）サーバ機能
　　コンフィグ情報を読込み、接続されているGamebuinoを有効なシリアルポートから検索します。
　　もし、コンフィグファイルにScratch のプロジェクトが指定されていた場合には、起動します。
　　サーバは、Scratchからのリクエストを受けるスレッドとScratchからの応答を受け取るスレッドからなります。
　　Scratchの拡張ブロックからのリクエストは、Webサーバにて受け取り、リクエストを受けると、Gamebuino へのコマンドシーケンスを組み立てて、シリアル通信で送信します。
　　別のスレッドでは、Gamebuinoからの応答があるとScratchのコマンドシーケンスをキューイングし、Scratchからのポーリング時に、拡張ブロックへ渡します。
　
　２）通信機能
　　通信機能は、サーバ機能からの要求によりGamebuinoとシリアル通信を行います。
　　初期処理用の同期処理とScratchとの通信時に使用する非同期処理があります。


```Python:__main__.py
#!/usr/bin/env python3

#=============================================================================
#                Carduino for scrach & arduino game board
#-----------------------------------------------------------------------------

import argparse
import asyncio
import configparser
import os
import os.path
import re
import signal
import subprocess
import sys
import time
import webbrowser

from aiohttp                import web

import s2carduino_serial    as ss

from socket                 import socket, AF_INET, SOCK_STREAM

HOST        = 'localhost'
PORTWEB     = 50209
PORTSCK     = 51000
MAX_MESSAGE = 200

OK          = "ok".encode('utf-8')

the_loop    = None

#    import pdb; pdb.set_trace()        ### break point

#*****************************************************************************
#                                Class
#-----------------------------------------------------------------------------
class S2CARDUINO:

    #-----------------------------------------------------------------------------
    #                                 initialize
    #-----------------------------------------------------------------------------
    def __init__(self):

        print('s2carduino version 1.0 - 2019/1/1')

        # variables
        self.poll_reply        = ''
        self.poll_time_stamp   = 0.0
        self.loop              = None
        self.carduino          = []

        try:
            # set carduino board com port
            self.com_port = ss.CarduinoSerial.get_ports()
            if len(self.com_port) == 0:
                print ('no available communication port')
                sys.exit(0)

            for p in self.com_port:
                b = ss.CarduinoSerial(com_port = p)
            
                # 1'st communication for carduino
                b.writeString('INQ')
                rep = b.readString()
                self.carduino.append(b)
                print('carduino %s version:%s\n' % (p, rep[3:]))

            # set base path
            self.base_path = os.getcwd()

            # get configuration
            config           = configparser.ConfigParser()
            config_file_path = str(self.base_path + '\\configuration\\carduino.cfg')
            config.read(config_file_path, encoding = 'utf8')
            
            # get language
            scratch_block_language_dict = { '1':'s2carduino.s2e',    '2':'s2carduino_JA.s2e'}
            self.scratch_language  = config.get('scratch_info', 'scratch_language')

            # get wait time
            self.windows_wait_time = int(config.get('scratch_info', 'windows_wait_time'))

            # get scratch path
            self.scratch_executable = config.get('scratch_info', 'scratch_executable')
            if self.scratch_executable == 'default':
                self.scratch_executable = '"C:\\Program Files (x86)\\Scratch 2\\Scratch 2.exe"'

            # get scratch project path
            s = config.get('scratch_info', 'scratch_project', fallback=None)
            if s == None:
                self.scratch_project   = ''
            else:
                self.scratch_project   = self.base_path + '\\' + s

        except Exception as ex:
            print (ex)
            sys.exit(0)
            
    #-----------------------------------------------------------------------------
	#                             serial procedure
    #-----------------------------------------------------------------------------
    async def proc_serial(self, my_loop, inx):
        
        try:
            while True:
                data = await self.carduino[inx].readline()
                self.serial_input(inx, data)

        except Exception as ex:
            print (ex)
            print ('end of procedure\n')
            print ('Please, press Control-C')

    #-----------------------------------------------------------------------------
    #                            serial input
    #-----------------------------------------------------------------------------
    def serial_input(self, n, m):
    
        if m[0:3] == 'BTN':
            self.poll_reply += 'read_button/'   + str(n) + ' ' + str(m[3:4]) + '\n'
            self.poll_reply += 'read_position/' + str(n) + ' ' + str(m[4:5]) + '\n'

    #-----------------------------------------------------------------------------
    #                             kick off
    #-----------------------------------------------------------------------------
    async def kick_off(self, my_loop):
        global START
        
        self.loop = my_loop
        
        try:
            # start up the HTTP server
            app = web.Application(loop = my_loop)

            #-----------------------------------------------------------------------------------------
            #                              command handlers
            #-----------------------------------------------------------------------------------------
            app.router.add_route('GET', '/dist_card/{board}/{s1}/{n1}/{s2}/{n2}/{s3}/{n3}/{s4}/{n4}/{s5}/{n5}', self.dist_cards)
            app.router.add_route('GET', '/show_message/{board}/{value}',	self.show_message)
            app.router.add_route('GET', '/play_beep/{board}/{value}',       self.play_beep)
            app.router.add_route('GET', '/poll',                                           self.poll)

            # wake up web server
            srv = await my_loop.create_server(app.make_handler(), HOST, PORTWEB)

            # start scratch
            if self.scratch_executable:
                if self.scratch_project:
                    os_string = self.scratch_executable + ' ' + self.scratch_project
                else:
                    os_string = self.scratch_executable
                await asyncio.sleep(self.windows_wait_time)
                subprocess.Popen(os_string)

            print('=== s2carduino started (port: %s) ===' % PORTWEB) 

            await self.keep_alive()
            return srv

        except Exception as ex:
            print ('** An error occurred: ')
            print (ex)
            self.loop.stop()
            the_loop.stop()
            pass

    #-----------------------------------------------------------------------------
    #                      distribute cards
    #-----------------------------------------------------------------------------
    async def dist_cards(self, request):

        board = self.get_board(request)
        s1  = request.match_info['s1']
        n1  = request.match_info['n1']
        s2  = request.match_info['s2']
        n2  = request.match_info['n2']
        s3  = request.match_info['s3']
        n3  = request.match_info['n3']
        s4  = request.match_info['s4']
        n4  = request.match_info['n4']
        s5  = request.match_info['s5']
        n5  = request.match_info['n5']
        try:
            str = 'CRD' + s1 + n1 + s2 + n2 + s3 + n3 + s4 + n4 + s5 + n5
            await self.carduino[board].writeline(str)
            
        except ValueError:
            print ('problem dist_cards\n')
            return web.Response(body = OK)

        return web.Response(body = OK)

    #-----------------------------------------------------------------------------
    #                         show message
    #-----------------------------------------------------------------------------
    async def show_message(self, request):

        board = self.get_board(request)
        value = request.match_info['value']
        try:
            str = 'MES' + value
            await self.carduino[board].writeline(str)
            
        except ValueError:
            print ('show_message\n')
            return web.Response(body = OK)

        return web.Response(body = OK)

    #-----------------------------------------------------------------------------
    #                          play beep
    #-----------------------------------------------------------------------------
    async def play_beep(self, request):

        board = self.get_board(request)
        value = request.match_info['value']
        try:
            str = 'SND' + value
            await self.carduino[board].writeline(str)
            
        except ValueError:
            print ('play_beep\n')
            return web.Response(body = OK)

        return web.Response(body = OK)

    #-----------------------------------------------------------------------------
    #                          get board number
    #-----------------------------------------------------------------------------
    def get_board(self, req):
        n = int(req.match_info['board'])
        if n >= len(self.carduino):
            n = 0

        return n
 
    #-----------------------------------------------------------------------------
    #                               poll
    #-----------------------------------------------------------------------------
    async def poll(self, request):

        # refresh the poll watch dog timer
        self.poll_time_stamp = self.loop.time()

        # save the reply to a temporary variable
        total_reply = self.poll_reply

        # clear the poll reply string for the next reply set
        self.poll_reply = ''

        # send the HTTP response
        return web.Response(headers = {"Access-Control-Allow-Origin": "*"}, content_type = "text/html", charset = "ISO-8859-1", text = total_reply)

    #-----------------------------------------------------------------------------
    #                               noinspection
    #-----------------------------------------------------------------------------
    async def keep_alive(self):

        while True:
            await asyncio.sleep(1)

#-----------------------------------------------------------------------------
# Control-C handling
def signal_handler(signal, frame):
    print('Control-C detected.')
    for t in asyncio.Task.all_tasks(the_loop):
        t.cancel()
        the_loop.run_until_complete(asyncio.sleep(.1))
        the_loop.stop()
        the_loop.close()
    sys.exit(0)


#*****************************************************************************
#                               main procedure
#-----------------------------------------------------------------------------
def main():

    # listen for SIGINT
    signal.signal(signal.SIGINT, signal_handler)

    # create instance
    s2carduino = S2CARDUINO()

    # event loop
    global the_loop
    the_loop = asyncio.get_event_loop()
    
    try:
        asyncio.ensure_future(s2carduino.kick_off (the_loop))
        for i in range(len(s2carduino.com_port)):
            asyncio.ensure_future(s2carduino.proc_serial (the_loop, i))
        
        the_loop.run_forever()
    
    except:
        sys.exit(0)
        
    asyncio.sleep(2)


#*****************************************************************************
#                            default procedure
#-----------------------------------------------------------------------------
if __name__ == '__main__':
    try:
        main()
        
    except KeyboardInterrupt:
        sys.exit(0)

    # noinspection PyBroadException
    loop = asyncio.get_event_loop()
    try:
        loop.run_forever()
        loop.stop()
        loop.close()
        
    except:
        pass
```

```Python:s2carduino_serial.py
# -*- coding: utf-8 -*-

#=================================================================================
#                        Scratch to Carduino interface
#=================================================================================

import asyncio
import sys

import serial

from serial.tools import list_ports
from time         import sleep

#*****************************************************************************
#                             class
#-----------------------------------------------------------------------------
class CarduinoSerial:

    #-----------------------------------------------------------------------------
    #                               define
    #-----------------------------------------------------------------------------
    SER_SPEED    = 57600
    SER_SLEEP    =  0.001
    
    #-----------------------------------------------------------------------------
    #                           get serial port
    #-----------------------------------------------------------------------------
    @staticmethod
    def get_ports():
        ports = list_ports.comports()        # get ports
        l     = []
        for i in ports:
            l.append(i[0])                   # get port name

        if len(l) == 0:
            print ("no comport")
            sys.exit(1)

        return l

    #-----------------------------------------------------------------------------
    #                           constructor
    #-----------------------------------------------------------------------------
    def __init__(self, com_port = 'COM12'):
   
        print('Initializing now\n')
        
        self.my_serial  = serial.Serial(com_port, self.SER_SPEED, timeout = 2, writeTimeout = 2)
        self.com_port   = com_port
        self.sleep_tune = self.SER_SLEEP
        sleep(3)

    #-----------------------------------------------------------------------------
    #                           sync write to serial
    #-----------------------------------------------------------------------------
    def writeString(self, str):

        self.my_serial.write(bytes(str, 'UTF-8'))

    #-----------------------------------------------------------------------------
    #                           sync read from serial
    #-----------------------------------------------------------------------------
    def readString(self):
    
        s  = self.my_serial.readline()
        if len(s) > 0:
            rslt = str(s, 'UTF-8')
            rslt = rslt.rstrip('\n')
            rslt = rslt.rstrip('\r')
        else:
            rslt = ""

        return rslt

    #-----------------------------------------------------------------------------
    #                          arync write to serial
    #-----------------------------------------------------------------------------
    async def writeline(self, data):
    
        rslt = self.my_serial.write(bytes(data, 'UTF-8'))
        return rslt
    
    #-----------------------------------------------------------------------------
    #                         async read from serial
    #-----------------------------------------------------------------------------
    async def readline(self):

        future         = asyncio.Future()
        data_available = False
        
        while True:
            if not data_available:
                if not self.my_serial.inWaiting():
                    await asyncio.sleep(self.sleep_tune)
                    
                else:
                    data_available = True
                    data           = self.my_serial.readline()
                    rslt           = str(data, 'UTF-8')
                    rslt           = rslt.rstrip('\n')
                    rslt           = rslt.rstrip('\r')
                    future.set_result(rslt)
                    
            else:
                if not future.done():
                    await asyncio.sleep(self.sleep_tune)
                    
                else:
                    return future.result()
```
#プロジェクトの実行
 Scratchプロジェクトを実行するには、以下の手順で実施してください。
１）PCとGamebuino をUSBケーブルで接続する。
２）Gamebuino の電源を入れる。
３）PC のPython で、__main__.py を実行する。
　　　　　例えば、Anaconda のコンソールを起動して、s2carduinoフォルダへ移動して、下記コマンドを実行する。

```　　　　　
　　　　　　>Python __main__.py
```
４）Scratch からScratch プロジェクトを読込実行する。
　　緑色の旗をクリックする。
５）Gamebuinoを操作する。
　　4つある青いボタンの中段の2つのボタンをクリックして、カーソルを左右に動かします。
　　赤いBボタンをクリックするとカーソル位置のカードが破棄され、新たなカードが配布されます。
　　緑のAボタンをクリックすると役ができているかどうかを判定します。もし、役ができていれば、役を表示しゲーム終了です。
　　役ができていなければ、ゲームは続行されます。（このとき、特に何も表示しません）
　　5回を超えて、カードを交換すると役を判定して、ゲーム終了です。この時、役ができていなければ Game Over を表示します。
　　
　　再度、実行する際には、Scratch の緑の旗をクリックしてください。

#さいごに

　ひととり、動作はしましたが、処理速度に難があることがわかりした。（想定はしていましたが）
　特に、リスト処理において、途中で条件が確定した場合でも最後まで回さざるを得ないため、無駄な時間がかかっていると思います。
　BREAKブロックが欲しいところです。また、変数やリストの内容をリアルタイムに表示させているのも処理速度低下の原因だと思います。
　（ここには隠すことができますが、まとめてはできないようなので、あえて試しませんでした）
　
　なお、今回の実験において、下記および関連するサイトを参考にさせていただきました。
　
https://pypi.org/project/s2aio/
https://memakura.github.io/dialogsystem/