【Arduino自動化12】全自動タマゴ受け取り&孵化

ますたーです。こんにちは。

今回は、預かり屋からタマゴを自動で受け取り、孵化までを自動で行う記事です。ついに全自動記事も第12回。引き続きよろしくお願いします。

 

なお、Arduino Leonardo自動化の導入・機材構成については導入記事を参考にしてください。

導入記事:【Arduino自動化01】Arduino開発環境の導入

※本ブログに初めてお越しの方は「本ブログについて」もぜひ、ご覧ください。

 

概要

本記事では、ワイルドエリアの「預かり屋」でタマゴの自動受け取り&自動孵化を紹介します。完全放置で孵化厳選や色厳選作業をできるようにしてみました。

f:id:tangential_star:20210116214618g:plain
f:id:tangential_star:20210116214601p:plain
完全自動でタマゴの受け取りと孵化をひたすら行ってくれる

では、目次です。

ソースコードだけほしい方は読み飛ばしてください⇒読み飛ばす

 

本ブログの紹介環境で自動孵化を作りたかった

始めに、本ブログで「自動孵化」を取り扱おうと思ったきっかけについてです。

今まで、本ブログもまだまだ駆け出しということもあり、世間で公開されているArduino自動化には「無いもの」「新しいもの」をとにかく作ってきました(個人的には、第9回の全自動カレーづくりは大変な力作でした)。そして、本ブログも公開してから3ヶ月が過ぎ、自動化に関わる記事数も10を超え、月間1000アクセスも超えるようになりました。

本ブログでポケモン剣盾のArduino自動化にチャレンジを始めた人もいらっしゃるようなので、せっかくなら「同じ導入環境」で「使えるもの」を増やしたほうが親切かと思い、本ブログでも比較的メジャーな「孵化作業の自動化」に踏み切った次第です。

すなわち、本ブログの「導入記事」での環境で動いていることが(少なくとも筆者は)確認済のプログラムになります。そういった意味では需要があるのかと思います。

 

ほのおのからだ」を持つポケモンで孵化時間は半減

まず、孵化厳選で重要なことですが、必ず「ほのおのからだ」「じょうききかん」「マグマのよろい」のいずれかの特性を持つポケモンをパーティーに加えて孵化作業をしましょう。これらの特性をもつポケモンは、手持ちにいるだけで孵化に必要な歩数が半分になります。10匹以上孵化するなら、今から捕まえに行ってもお釣りが来るほど時間効率が変わります。持っていない人は今すぐ捕まえてください。

f:id:tangential_star:20210117010932g:plain
f:id:tangential_star:20210117011001g:plain

同じタマゴ・条件で「ほのおのからだ」を手持ちに加えた場合、孵化時間が半減する
※かかった時間はそれぞれ53秒、1分35秒だった(注:計測値/タマゴ:ニャビー

f:id:tangential_star:20210117011309p:plain
f:id:tangential_star:20210117011314p:plain
上記検証に使った手持ちポケモン。「ほのおのからだ」は高速孵化に効果てきめんだ

孵化の歩数を半減できる特性3つのうちいずれかを持っており、かつ、剣盾内(DLCなし)で捕まえやすいポケモンと言えば、タンドン系(タンドン・トロッゴン・セキタンザン)でしょうか。一応、他にもエンジンシティのジムミッションで出会える「ヒトモシ」もいますが、「もらいび」と確率半々なので、DLC無しで確実に捕まえるにはタンドン系が最安定と思います。なお、同特性を持つ剣盾内で出現するポケモンは、ポケモン徹底攻略で検索できますので合わせて参考にしてください。

ところで、剣盾にはDLC含めて「マグマのよろい」を持つポケモンマグマッグおよびマグカルゴ)は登場しませんでしたね。悲しい…。マグカルゴは私の好きなポケモンの1匹でした。当時、対戦はもちろん、ウルガモスファイアローが登場してからも孵化のお供に使っており、思い入れ深いポケモンでした。次作では帰ってきてほしいです。

 

受け取り作業と孵化作業の自動化

自動化に限らず、孵化作業において特筆して準備すべきものは、上記の通り「ほのおのからだ」持ちポケモンだけです。今回の自動化はタマゴを1匹ずつ「受け取り」「孵化」を繰り返すので、1回ごとの試行回数を増やすべく、手持ちポケモンは「ほのおのからだ」持ち1匹のみにしておいてください。

 

一応、効率的な孵化作業には「自転車」「連続した空きボックス」は必須かと思いますので、本ブログで紹介するプログラムもそれに準じます。なお、本ブログで扱う自動孵化は「ワイルドエリア」の預かり屋を活用します。理由は自転車でくるくると一周できるからです。

f:id:tangential_star:20210117014822p:plain
f:id:tangential_star:20210117014830p:plain
作業としての準備は「自転車」を準備し、「空きボックス」を連続させるだけだ

 

プログラムの使い方と修正箇所

本ブログのプログラムを使うにあたっては、下記冒頭箇所を必要に応じて修正してください。プログラム全文は後述します

// ↓タマゴ孵化にかかる時間[秒](実測でお願いします)
#define HATCH_TIME (57) // 何秒でタマゴが割れるかを計測して入れてください
#define ADV_OFFSET (3) // 育て屋前から何秒間はずれたところで自転車くるくるするか

// 何試行するか?
#define MAX_HATCH_POKEMON (30*9) 

// Xでメニュー開いたときのボタン配置
#define TOWNMAP_ROW (1) // 「タウンマップ」の行
#define TOWNMAP_COL (0) // 「タウンマップ」の列
#define POKEMON_ROW (0) // 「ポケモン」の行
#define POKEMON_COL (1) // 「ポケモン」の列

■1匹の孵化にかかる時間を入力する

計測は難しいですが、ポケモンの孵化歩数などを参考に入力しましょう。数字は「HATCH_TIME」に指定してください。現在デフォルトで入っている57はニャビー(孵化歩数:3840歩?に対して「ほのおのからだ」適用の数字)ですが、かなりざっくりアバウトな数字なので気にせず入力ください。その下の「ADV_OFFSET」は変更しなくて大丈夫です。

// ↓タマゴ孵化にかかる時間[秒](実測でお願いします)
#define HATCH_TIME (57) // 何秒でタマゴが割れるかを計測して入れてください
#define ADV_OFFSET (3) // 育て屋前から何秒間はずれたところで自転車くるくるするか

■空きボックスの数を指定

まず、前述の通り、空きボックス(30匹空白のボックス)を連続して配置してください。その個数を「MAX_HATCH_POKEMON」に適用します。具体的には、例えば、9ボックス連続で空っぽなら、30×9=「270」で指定すればOKです(あるいは計算式で「30*9」でもOK)。

// 何試行するか? 
#define MAX_HATCH_POKEMON (30*9)  

■Xボタンメニュー画面の位置を入力

Xで開くメニュー画面の「タウンマップ」「ポケモン」のボタンの位置情報を必要に応じて修正(プログラム全文は後述)してください。行(英語でrow)は、上の段が0、下の行が1という決まりで入れています。列(英語でcolumn)は、左端から順番に0, 1, 2, 3, 4になります。

具体的には、タウンマップの行列はそれぞれ「TOWNMAP_ROW」「TOWNMAP_COL」、ポケモンの行列は「POKEMON_ROW」「POKEMON_COL」で指定しているので、これらを修正ください。

 

// Xでメニュー開いたときのボタン配置 
#define TOWNMAP_ROW (1) // 「タウンマップ」の行
#define TOWNMAP_COL (0) // 「タウンマップ」の列
#define POKEMON_ROW (0) // 「ポケモン」の行
#define POKEMON_COL (1) // 「ポケモン」の列

よくわからない人は、プログラムではなくゲームのメニュー画面を、下記画像の順番で並び替えればOKです(この順番は、剣盾のシナリオを進めた際のそのままです)。

書き換え作業が終わったら、「タウンマップ」にカーソルをあわせてください。

f:id:tangential_star:20210117020954p:plain

「タウンマップ」「ポケモン」の位置(行列)をプログラムの指定箇所に書き込もう

 

ソースコード

使う前に、前述の通りXボタンで開くメニュー画面はプログラム上で編集しておき、Xボタンで開いたメニューのカーソルは「タウンマップ」に合う状態にしておいてください。

ワイルドエリアの「預かり屋」にポケモンを預け、手持ちは1匹・自転車に乗った状態で、かつ、「タマゴができている状態」でArduinoを挿すだけで、ひたすら「タマゴを受け取っては孵す」作業を繰り返してくれます。

/* 
 *  タマゴ自動受取@ワイルドエリア
 *  【手持ち1匹、ボックスは空き指定のこと】タウンマップにカーソルをあわせて
 *  自転車に乗り、タマゴが出来上がっている状態でワイルドエリア預かり屋手前で
 *  Arduinoを挿すだけで準備OK。
 *  
 *  (c) 2021 ますたーの忘備録
 *  https://tangential-star.hatenablog.jp/
*/

#include <SwitchControlLibrary.h>

#define HOLDTIME (95) // 1回のキー入力の長押し時間
#define ROW 0
#define COL 1


// ↓タマゴ孵化にかかる時間[秒](実測でお願いします)
#define HATCH_TIME (57) // 何秒でタマゴが割れるかを計測して入れてください
#define ADV_OFFSET (3) // 育て屋前から何秒間はずれたところで自転車くるくるするか

// 何試行するか?
#define MAX_HATCH_POKEMON (30*9) 

// Xでメニュー開いたときのボタン配置
#define TOWNMAP_ROW (1) // 「タウンマップ」の行
#define TOWNMAP_COL (0) // 「タウンマップ」の列
#define POKEMON_ROW (0) // 「ポケモン」の行
#define POKEMON_COL (1) // 「ポケモン」の列


// ★下表を参考に、↑の数字を書き換えてください★
// (Xでメニューを開いたときのボタンの位置のイメージ)
//  +     +     +     +     +  ←0段目(ROW=0)
//  +     +     +     +     +  ←1段目(ROW=1)
// 左橋から何個ずれるか(COLを0~4に)
// COL=0 COL=1 COL=2 COL=3 COL=4
//
// 例えば、メニューで「タウンマップ」が「下の段の1番目」にあるなら
// TOWNMAP_ROWを「1」に、TOWNMAP_COLを「0」に書き換えてください

void getEgg(void);
void hatchEgg(void);
int *SetCursor(int *cursor_position, int row, int col);
void SoratobuTaxi(int cursor_position[]);
void SendBox(int* cursor_position, int *hatched_num);

void PushRL(int delay_time_ms);
int PushKey(char* keyname, int holdtime, int delaytime);
void TiltLeftStick(int direction_deg, double power, int holdtime, int delaytime);
void TiltRightStick(int direction_deg, double power, int holdtime, int delaytime);
void TiltStick(int leftdeg, double leftpower, int rightdeg, double rightpower, int holdtime, int delaytime);
void NextDayInCheatMode(void);



int current_cursor[2]; // Xボタンを開いたときのカーソルの位置
int hatched_num;       // タマゴからかえしたポケモンの数


void setup() {
  // コントローラーとして認識されるためにRLを7回ほどカチャカチャする
  for(int i=0;i<7;i++)PushRL(300);
  delay(1000);

  // Xボタンカーソルの位置を登録
  //(初期位置は、開いた時にタウンマップに合っている想定)
  current_cursor[ROW] = TOWNMAP_ROW;
  current_cursor[COL] = TOWNMAP_COL;
  // ★上記は環境に応じて書き換えてください★

  hatched_num = 0; // 何匹預けているか
  // もし、ボックスの続きからやる場合は、上記調整してください(非推奨)
}


void loop() {

  // 1匹タマゴ受け取り
  getEgg();
  // 1匹タマゴ孵化
  hatchEgg();
  // 孵化したポケモンの数を追加する
  hatched_num++;

  //上記5回繰り返すたびにボックス送り
  if(hatched_num>0&&hatched_num%5==0){ 
    SendBox(current_cursor, &hatched_num);
  }

  // 上限数に達したら処理終了
  if(hatched_num > MAX_HATCH_POKEMON)for(;;);
  delay(1000);

}

void PushRL(int delay_time_ms){
  SwitchControlLibrary().PressButtonR();
  SwitchControlLibrary().PressButtonL();
  delay(HOLDTIME);
  SwitchControlLibrary().ReleaseButtonR();
  SwitchControlLibrary().ReleaseButtonL();
  delay(delay_time_ms);
  return;
}
int PushKey(char* keyname, int holdtime, int delaytime){
  // ホームボタン・方向キーはRight, Left, Up, Down, Homeなど2文字以上で入力。
  // その他ボタン入力は1文字(A,B,X,Y,R,L,+,-)ZR・ZLにも対応
  // 同時押しは非対応
  
  if(strlen(keyname)==1){
    switch(keyname[0]){
      case 'A': case 'a': // A
        SwitchControlLibrary().PressButtonA(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonA(); delay(delaytime);
      break;
      case 'B': case 'b': // B
        SwitchControlLibrary().PressButtonB(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonB(); delay(delaytime);
      break;
      case 'X': case 'x': // X
        SwitchControlLibrary().PressButtonX(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonX(); delay(delaytime);
      break;
      case 'Y': case 'y': // Y
        SwitchControlLibrary().PressButtonY(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonY(); delay(delaytime);
      break;
      case 'L': case 'l': // L
        SwitchControlLibrary().PressButtonL(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonL(); delay(delaytime);
      break;
      case 'R': case 'r': // R
        SwitchControlLibrary().PressButtonR(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonR(); delay(delaytime);
      break;
      case 'H': case 'h': // Home
        SwitchControlLibrary().PressButtonHome(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonHome(); delay(delaytime);
      break;
      case '+': case 'p': case 'P': // Plus
        SwitchControlLibrary().PressButtonPlus(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonPlus(); delay(delaytime);
      break;
      case '-': case 'm': case 'M': // Minus
        SwitchControlLibrary().PressButtonMinus(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonMinus(); delay(delaytime);
      break;
      default:
      break;
    }
  }else if(strlen(keyname)>=2){
    switch(keyname[0]){
      case 'z': case 'Z': // ZR/ZL
        if(keyname[1]=='R'||keyname[1]=='r'){
          SwitchControlLibrary().PressButtonZR(); delay(holdtime);
          if(holdtime>0)SwitchControlLibrary().ReleaseButtonZR(); delay(delaytime);
        }
        if(keyname[1]=='L'||keyname[1]=='l'){
          SwitchControlLibrary().PressButtonZL(); delay(holdtime);
          if(holdtime>0)SwitchControlLibrary().ReleaseButtonZL(); delay(delaytime);
        }
      break;
      case 'r': case 'R': // right
        SwitchControlLibrary().MoveHat(2); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'l': case 'L': // left
        SwitchControlLibrary().MoveHat(6); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'u': case 'U': // up
        SwitchControlLibrary().MoveHat(0); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'd': case 'D': // down
        SwitchControlLibrary().MoveHat(4); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().MoveHat(8); delay(delaytime);
      break;
      case 'H': case 'h': // Home
        SwitchControlLibrary().PressButtonHome(); delay(holdtime);
        if(holdtime>0)SwitchControlLibrary().ReleaseButtonHome(); delay(delaytime);
      default:
      break;  
    }
  }else{
    return -1;
  }
  return strlen(keyname);
}
void TiltLeftStick(int direction_deg, double power, int holdtime, int delaytime){
  double rad = (double)direction_deg*PI/180.0; // 弧度法(ラジアン)変換
  int x, y;
  x = (double)128*sin(rad)*power;
  y = (double)-128*cos(rad)*power;
  x += 128; y += 128;
  if(x >= 255) x=255; if(x <= 0) x=0;
  if(y >= 255) y=255; if(y <= 0) y=0;

  SwitchControlLibrary().MoveLeftStick(x,y);
  if(holdtime> 0){ // holdtime=0のときは押しっぱなし。
    delay(holdtime);
    SwitchControlLibrary().MoveLeftStick(128,128); // 傾きを直す
  }
  if(delaytime>0) delay(delaytime);
  return;
}
void TiltRightStick(int direction_deg, double power, int holdtime, int delaytime){
  double rad = (double)direction_deg*PI/180.0; // 弧度法(ラジアン)変換
  int x, y;
  x = (double)128*sin(rad)*power;
  y = (double)-128*cos(rad)*power;
  x += 128; y += 128;
  if(x >= 255) x=255; if(x <= 0) x=0;
  if(y >= 255) y=255; if(y <= 0) y=0;

  SwitchControlLibrary().MoveRightStick(x,y);
  if(holdtime> 0){ // holdtime=0のときは押しっぱなし。
    delay(holdtime);
    SwitchControlLibrary().MoveRightStick(128,128); // 傾きを直す
  }
  if(delaytime>0) delay(delaytime);
  return;
}

void TiltStick(int leftdeg, double leftpower, int rightdeg, double rightpower, int holdtime, int delaytime){
  double rad;
  int x, y, rx, ry, lx, ly;

  rad = (double)leftdeg*PI/180.0; // 弧度法(ラジアン)変換
  x = (double)128*sin(rad)*leftpower;
  y = (double)-128*cos(rad)*leftpower;
  x += 128; y += 128;
  if(x >= 255) x=255; if(x <= 0) x=0;
  if(y >= 255) y=255; if(y <= 0) y=0;
  lx=x;ly=y;
  rad = (double)rightdeg*PI/180.0; // 弧度法(ラジアン)変換
  x = (double)128*sin(rad)*rightpower;
  y = (double)-128*cos(rad)*rightpower;
  x += 128; y += 128;
  if(x >= 255) x=255; if(x <= 0) x=0;
  if(y >= 255) y=255; if(y <= 0) y=0;
  rx=x;ry=y;

  SwitchControlLibrary().MoveLeftStick(lx,ly);
  SwitchControlLibrary().MoveRightStick(rx,ry);
  if(holdtime> 0){ // holdtime=0のときは押しっぱなし。
    delay(holdtime);
    SwitchControlLibrary().MoveLeftStick(128,128); // 傾きを直す
    SwitchControlLibrary().MoveRightStick(128,128); // 傾きを直す
  }
  if(delaytime>0) delay(delaytime);
  return;
}


void NextDayInCheatMode(){
  // ★日付変更
  // Homeボタンを押して設定の画面へ移動
  PushKey("Home", HOLDTIME, 1000);

  // Home画面で「設定」を選ぶ
  PushKey("down", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 1500);

  // 設定画面で「日付と時刻」を開く
  PushKey("down", 1500, 105); // 1.5秒「↓」長押し
  PushKey("right", HOLDTIME, 105); 

  for(int i=0; i<4; i++){
    PushKey("down", HOLDTIME, 55);
  }
  PushKey("A", HOLDTIME, 500);

  // 時間設定(現在の日付と時刻を選ぶ)
  PushKey("down", HOLDTIME, 105);
  PushKey("down", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 500);

  // 時刻設定(日付の部分のみ回していく
  // 時間の変更
  PushKey("right", HOLDTIME, 105);
  PushKey("right", HOLDTIME, 105);
  PushKey("up",    HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("A", HOLDTIME, 105);
  PushKey("Home", HOLDTIME, 2000);
  PushKey("A", HOLDTIME, 1000);
  return;
}
int *SetCursor(int *cursor_position, int row, int col){

  if(cursor_position[COL] < col){
     for( ; cursor_position[COL] != col ; cursor_position[COL]++ )PushKey("right", HOLDTIME, 100);  
  }else if(cursor_position[COL] > col){
     for( ; cursor_position[COL] != col ; cursor_position[COL]-- )PushKey("left", HOLDTIME, 100);  
  }
  if(cursor_position[ROW] < row){
     for( ; cursor_position[ROW] != row ; cursor_position[ROW]++ )PushKey("down", HOLDTIME, 100);  
  }else if(cursor_position[ROW] > row){
     for( ; cursor_position[ROW] != row ; cursor_position[ROW]-- )PushKey("up", HOLDTIME, 100);  
  }

  return cursor_position;
}


void SoratobuTaxi(int* cursor_position){

  // タウンマップを開く
  PushKey("X", HOLDTIME, 700);  // メニューを開き

  // カーソルをタウンマップに揃える
  SetCursor(cursor_position, TOWNMAP_ROW,  TOWNMAP_COL);
  PushKey("A", HOLDTIME, 2700); // タウンマップを開く

  // TiltLeftStick( 45, 1.0, 50, 170); // カーソル合わせが必要な場所なら調整ください。

  // 空を飛ぶ(A)
  PushKey("A", HOLDTIME, 650);  // ○○まで そらとぶタクシーで 移動しますか?
  PushKey("A", HOLDTIME, 3800); // はい

  return;
}

void getEgg(void){
  SoratobuTaxi(current_cursor);
  TiltLeftStick( 185, 1.0, 50, 200); // ブリーダーおばさんの方向を向く
  PushKey("L", HOLDTIME, 190); // チラッと
  TiltLeftStick( 0, 1.0, 500, 240); // ブリーダーおばさんに向かう
  PushKey("A", HOLDTIME, 420);  // きみのポケモンがタマゴを持っていたんです!
  PushKey("B", HOLDTIME, 420);  // ほしいですよね?
  PushKey("A", HOLDTIME, 2800); // はい
  PushKey("B", HOLDTIME, 1300); // ○○は 預かり屋さん から タマゴを もらった!
  PushKey("B", HOLDTIME, 1100); // タマゴを 手持ちに 加えました!
  PushKey("B", HOLDTIME, 300); // 予備
  PushKey("B", HOLDTIME, 1600); // 大事に 育ててくださいね!
  PushKey("B", HOLDTIME, 300); // 予備

  return;
}


void hatchEgg(){
  // 時間を扱うための変数たち
  unsigned long int current_time=0;
  unsigned long int start_time=0;
  unsigned long int temp_time=0;
  
  // 向き修正(ブリーダーおばさんの反対側を向く)
  TiltLeftStick( 195, 1.0, 50, 200); // ちょっとだけ右側を向くと野生ポケモンと戦闘になりにくい
  PushKey("L", HOLDTIME, 190); // チラッと
  // SoratobuTaxi(current_cursor); // ※タクシーでも可

  TiltStick(0, 1.0, 0, 0, ADV_OFFSET*1000, 100); // 前にOFFSET秒だけ進んで(3秒ほど推奨)
  TiltStick(0, 1.0, 90, 1.0, 0, 0); // スティック傾けて
  for( start_time=millis(), current_time=start_time ; current_time - start_time < (HATCH_TIME-ADV_OFFSET)*1000UL ; current_time=millis() );
  TiltStick(0, 0, 0, 0, 0, 0); // 時間が経ったらスティックを初期位置に戻す

  // ・・・おや? ▼

  PushKey("A", HOLDTIME, 18500); // タマゴが かえって ○○が うまれた!
  PushKey("B", HOLDTIME, 4200); 

  return;
}

void SendBox(int* cursor_position, int *hatched_num){

  // ポケモンを開く
  PushKey("X", HOLDTIME, 700);  // メニューを開き
  // カーソルをポケモンに揃える
  SetCursor(cursor_position, POKEMON_ROW,  POKEMON_COL);
  PushKey("A", HOLDTIME, 1500); // ポケモンを開く
  PushKey("R", HOLDTIME, 1800); // ボックスへ
  
  if(*hatched_num > 30 && *hatched_num %30 == 5){
    PushKey("R", HOLDTIME, 800); // 30匹埋まっていれば次のボックスへ
  }

  PushKey("left", HOLDTIME, 150); // 手持ちにカーソルをあわせて
  PushKey("down", HOLDTIME, 150); // 手持ち2匹目にカーソル
  PushKey("Y", HOLDTIME, 150);
  PushKey("Y", HOLDTIME, 150); // 範囲指定モードに
  PushKey("A", HOLDTIME, 150);
  PushKey("down", HOLDTIME, 150);
  PushKey("down", HOLDTIME, 150);
  PushKey("down", HOLDTIME, 150);
  PushKey("down", HOLDTIME, 150);
  PushKey("A", HOLDTIME, 150); // ホールド

  PushKey("right", HOLDTIME, 150); 
  PushKey("down", HOLDTIME, 150);
  PushKey("down", HOLDTIME, 150);
  PushKey("down", HOLDTIME, 150);
  PushKey("down", HOLDTIME, 150);
  PushKey("A", HOLDTIME, 800); // いちらん をクリック
  PushKey("A", HOLDTIME, 400); // 今いるボックスに左詰めで5匹を入れる。

  PushKey("B", HOLDTIME, 600);  // いちらんを閉じ、ボックスに戻る
  PushKey("B", HOLDTIME, 2000); // ボックスを閉じ、手持ち画面に戻る
  PushKey("B", HOLDTIME, 2000); // 手持ち画面を閉じ、メニューに戻る
  PushKey("B", HOLDTIME, 2000); // メニュー画面を閉じ、フィールドに戻る
  
  return;
}

f:id:tangential_star:20210117025454g:plain

30匹でボックスが埋まっても右のボックスに自動に送るように実装しているので安心だ


あとがき

今回は、タマゴの自動受取と自動孵化を実装しました。これでよりこのブログも、偉大なる先駆者たちに近づけたかなぁと思います。何よりも自分の手でちゃんと実装ができてよかったです。これからも皆様、応援のほどよろしくお願いいたします

 

 

ところで、今回は、予想外のところでプログラミングにつまずきました。

あまり本ブログではプログラムの実装に関する話をする気は無いのですが、今後のプログラミングでも気をつけなきゃなと感じたのでここに「あとがき」として載せます。

Arduinoのint型は2バイトなので32767までしか扱えない

C言語で組み込みやマイコン以外しか触ってこなかった人にとってはかなり衝撃的というか、違和感があるのですが、なんと、int型なのに2バイト(16bit)しかありません。つまり、扱える整数は216=65536通りしかないため、unsigned(マイナスを考えないモード)でも65535までしか扱えないことになります。無論、プログラム中のリテラル(定数)も同様なので、演算の結果intの範囲を超えるとオーバーフローします。

具体的に何に困ったかというと、要するにそのままだと、Arduinoでは「32767ミリ秒以上」待機させることができない(⇒32秒までしか待機できない)ということでした。

…というか10本以上記事を書くだけプログラムしておきながら、今まで一切オーバーフローの可能性に気がつかなかった(=その桁数を扱わずに済んでいた)というのは、奇跡と言わざるを得ません。 一応、ケーススタディとして下記に具体例を載せて解説します。

うまく行かないプログラムソース例:「57秒待機させようとしたらオーバーフローする」

#define WAITTIME (57) // 57秒待機を予定

loop(){
  // 時間を扱うための変数たち
  unsigned long int current_time=0, start_time=0;

  // 57秒待機
  for( start_time=millis(), current_time=start_time ; current_time - start_time < WAITTIME*1000 ; current_time=millis() );
  // ↑このソースコードだとうまく行かずに無限ループする
}

このソースでは、「WAITTIME*1000」の計算結果が「57000」になりますが、これが「int型」で演算され、最大値32768以上になることで意図した結果とは異なる動きをしてしまいます。

結論:long intにキャストすればArduinoでも4バイトになる

前述のオーバーフローについて、原因は「文字定数もint型で演算されていること」です。結論、「long型(4バイト)」にキャストしておけば期待した通りに動作します。すなわち、リテラルに接尾辞「L」を付け加えることで解決できます。

改善したプログラムソース例(うまく動く例):「57秒待機するにはlong型にキャストしておく」

#define WAITTIME (57) // 57秒待機を予定

loop(){
  // 時間を扱うための変数たち
  unsigned long int current_time=0, start_time=0;

  // 57秒待機
  for( start_time=millis(), current_time=start_time ; current_time - start_time < WAITTIME*1000UL ; current_time=millis() );
  // ↑このソースコードだとうまく行く(1000を1000ULに変えただけ)
}

上記例では、1000の後に「UL」をつけてunsigned long int型にキャストしています。

接尾辞「L」をつけることで「long型」として扱われます。ちなみに「U」は「unsigned」の意なので、負の数を扱わないことを示します。

 

 *

 

以上、Arduinoのハードウェア仕様に関する内容にも触れたあとがきでした。この知見も、見る人が見るとためになる内容だと思います。

 

いずれにしても、皆様のポケモン自動化ライフがよりよいものになることを願っております。

  

追記:ニャビーの色違い無事に出せました! (1/17 11:27追記)

本ブログを執筆したのが本日1/17の午前4:09。ブログ投稿からわずか6時間、色違いに出会えました。また、このプログラムを動かし続けて7時間以上、ループの破綻なく、無事に動いておりました。これは個人的に安心できました。

なお、今回はわずか189試行で出会えました。これは強運。

f:id:tangential_star:20210117112445g:plain
f:id:tangential_star:20210117112553p:plain
7時間、ループ破綻なくプログラムは動き続け、その最中、無事に色違いに出会えました

 

改めて、皆様も良い自動化ライフを。

ではではc⌒っ.ω.)っ

 

導入記事: 【Arduino自動化01】Arduino開発環境の導入

前記事:【Arduino自動化11】5番道路で全自動羽集め【DLC不要】

次記事:【Arduino自動化13】全自動ポケモン逃がし