【BDSP自動化04】特性「ものひろい」自動化【ふしぎなアメ・ぎんのおうかん】

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

 

今回は、特性「ものひろい」による道具集めを自動化しました。

ダイパリメイク自動化の記事、第4弾になります。

 

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

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

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

 

概要

とくせい「ものひろい」による道具集めを自動化しました。条件付き*1・低確率ながら「ぎんのおうかん」「ふしぎなアメ」などを自動で収集することができます。実測値として、筆者は約13時間放置で「ぎんのおうかん」9個が入手できました*2

f:id:tangential_star:20220102210809g:plain
f:id:tangential_star:20220102210907p:plain
「ものひろい」による道具集めを自動化。ごく低確率で「ぎんのおうかん」も入手可能だ

目次です。

手動で周回する方は→効率的な「ぎんのおうかん」の集め方まで、プログラムのソースコードだけ欲しいという方は→ソースコードまで、それぞれ読み飛ばしてください。

 

「ぎんのおうかん」「ふしぎなアメ」を量産したい!

まずは、目的の整理から。

言わずもがな、「ぎんのおうかん*3」や「ふしぎなアメ」は、対戦用ポケモンの育成およびレベル上げに欠かせないアイテムです。しかし、今作は、ポケモン剣盾と違い、「ぎんのおうかん」や「ふしぎなアメ」の入手手段が非常に限られています。正攻法で複数入手するためにはいずれもバトルタワーの周回が必須です。

対戦初心者やエンジョイ勢にとってバトルタワーは敷居が高く、挑戦するのも大変ですし、「最初の挑戦のための3匹/4匹育成」も一苦労です。また、ぎんのおうかんは「25BP」・ふしぎなアメは「20BP」と、それぞれ決して安くはない絶妙な交換レートとなっており、日常的にバトルタワーを攻略している人以外は、気軽に交換できるものとは言えません。

f:id:tangential_star:20220102211059p:plain
f:id:tangential_star:20220102211031g:plain
25BPで交換できる「ぎんのおうかん」を使えば、ポケモンの能力を鍛えることができる

一方、バトルタワー「以外」での複数入手方法としては、特性「ものひろい」が挙げられます。特性「ものひろい」であれば、野生ポケモンやトレーナーとの戦闘を行うだけで良いため、比較的気楽です。また、確率入手とはなりますが、副次的に換金アイテムや進化アイテムなど、別アイテムの収集も一緒に行えるため、旨味も大きいです。

f:id:tangential_star:20220102211820p:plain

「ものひろい」でも「ぎんのおうかん」を拾ってきてくれる(要 Lv.71以上)

したがって、今回は「ものひろい」による道具集めに着目して、道具の効率的な収集を目指していきます。バトルタワーの周回は目を離すわけにいきませんし、相手が結構強いのがストレスなんですよね。

 

特性「ものひろい」で、「ふしぎなアメ」が1%~の確率で拾える

まずは、特性「ものひろい」についておさらいです。すでに知っているよ!という方は→効率的に野生ポケモンと出会うには「あまいかおり」まで読み飛ばしOK。

「ものひろい」とは、「戦闘終了後に10%の確率で何かしらの道具を拾ってきてくれる」効果を持つ特性で、拾ってきてくれるアイテムはそのポケモンのレベルによって決まっています。例えば、Lv.32のポケモンなら「Lv.31~Lv.40」のアイテムテーブルからランダムに拾ってきてくれます。

「ものひろい」で拾えるアイテムの一覧

下記の表が、レベルごとに拾えるアイテムの一覧ですが、要するに

  • ふしぎなアメ」はLv.11以上で拾える(確率は1%。Lv.51超なら確率は4%~5%)
  • 「ぎんのおうかん」はLv.71以上で拾える(確率は1%)

ということを抑えておけばOKです。

ポケモンのレベル毎に「ものひろい」で拾えるアイテムの確率
どうぐ ~Lv.10 ~20 ~30 ~40 ~50 ~60 ~70 ~80 ~90 ~100

f:id:tangential_star:20220102215555p:plain

キズぐすり
35%                  

f:id:tangential_star:20220102215606p:plain

ちいさなキノコ
25% 8%                

f:id:tangential_star:20220102215613p:plain

むしよけスプレー
8% 30%                

f:id:tangential_star:20220102215618p:plain

いいキズぐすり
8% 8% 30%              

f:id:tangential_star:20220102215625p:plain

ピッピにんぎょう
8% 8% 9% 30%            

f:id:tangential_star:20220102215631p:plain

おおきなキノコ
3% 8% 9%              

f:id:tangential_star:20220102215638p:plain

シルバースプレー
3% 8% 9% 9% 30%          

f:id:tangential_star:20220102215645p:plain

なんでもなおし
3% 4% 8% 9% 9% 30%        

f:id:tangential_star:20220102215651p:plain

げんきのかけら
3% 4% 3% 8% 9% 10% 30%      

f:id:tangential_star:20220102215657p:plain

すごいキズぐすり
3% 4% 3% 4% 8% 9% 10% 30%    

f:id:tangential_star:20220102215703p:plain

ピーピーエイド
1% 1% 3% 4% 4%          

f:id:tangential_star:20220102215708p:plain

ゴールドスプレー
  4% 3% 4% 4% 9% 9% 10% 30%  

f:id:tangential_star:20220102215714p:plain

やみのいし
  4% 3% 4% 4% 4% 4% 5% 9% 10%

f:id:tangential_star:20220102215719p:plain

ひかりのいし
  4% 3% 4% 4% 4% 4% 5% 9% 10%

f:id:tangential_star:20220102215725p:plain

めざめいし
  4% 3% 4% 4% 4% 4% 5% 9% 10%

f:id:tangential_star:20220102215731p:plain

ふしぎなアメ
  1% 1% 1% 1% 4% 4% 5% 4% 5%

f:id:tangential_star:20220102215738p:plain

きんのたま
    3% 4% 4% 4% 4% 5% 4% 5%

f:id:tangential_star:20220102215744p:plain

まんたんのくすり
    3% 4% 4% 4% 9% 9% 8% 30%

f:id:tangential_star:20220102215750p:plain

ピーピーリカバー

    1% 1% 4% 4% 4%      

f:id:tangential_star:20220102215756p:plain

ポイントアップ
    1% 1% 1% 4% 4% 5% 4% 5%

f:id:tangential_star:20220102215801p:plain

あかいいと
    1% 1% 1% 1% 1% 1% 1% 1%

f:id:tangential_star:20220102215807p:plain

たべのこし
    1% 1% 1% 1% 1% 1% 1% 1%

f:id:tangential_star:20220102215813p:plain

メンタルハーブ
    1% 1% 1% 1% 1% 1% 1% 1%

f:id:tangential_star:20220102215819p:plain

パワフルハーブ
    1% 1% 1% 1% 1% 1% 1% 1%

f:id:tangential_star:20220102215824p:plain

しろいハーブ
    1% 1% 1% 1% 1% 1% 1% 1%

f:id:tangential_star:20220102215831p:plain

げんきのかたまり
      4% 4% 4% 4% 9% 9% 9%

f:id:tangential_star:20220102215836p:plain

ピーピーエイダー
        1% 1% 4% 5% 4% 5%

f:id:tangential_star:20220102215841p:plain

ピーピーマックス
            1% 1% 4% 5%

f:id:tangential_star:20220102215846p:plain

ぎんのおうかん
              1% 1% 1%

※確率情報はポケモンWiki拾うアイテム一覧」より引用・一部改変。

レベル91~100が「ものひろい」の最高効率

前節では、レベル11以上で「ふしぎなアメ」、レベル71以上あれば「ぎんのおうかん」が拾えることを紹介しました。

上記の表からも明白ですが、「ものひろい」の特性をもつポケモンのレベルが高いほど、拾えるアイテムの内容が充実してきます。そして、言わずもがなレベル91以上の時のアイテムリストが最も充実しています

また、Lv.91~100は、ラインナップも然ることながら、それぞれの入手確率も極大値を取ります。具体的には、「ひかりのいし」「やみのいし」「めざめいし」の確率がそれぞれ10%まで跳ね上がったり、ふしぎなアメ」「ポイントアップ」がそれぞれ5%になったりします。進化アイテムや育成に必要なアイテムが10~20回に1度の割合で入手できる、ということですね。

f:id:tangential_star:20220102234531p:plain

「ものひろい(Lv.91~100)」で拾えるレアアイテムの一覧
ひかりのいし」「やみのいし」「めざめいし」がそれぞれ10%で入手できるぞ

要するに「ものひろい」の要員はなるべく高レベルが望ましい、ということです。

 

効率的に野生ポケモンと出会うには「あまいかおり」

さて、ここからは「ものひろい」の発動条件である戦闘についてです。あくまでも余談ですので、不要な方は→「効率的な「ぎんのおうかん」の集め方」まで読み飛ばす

「特性『ものひろい』の発動条件」と書くと仰々しいですが、要するに「トレーナーとバトル」または「野生ポケモンとバトル」でOKです。

トレーナーとの再戦はBDSP自動化01(自動お金稼ぎ)でもお世話になった「バトルサーチャー」を使うことで実現できますが、「チャージ歩数がかかること」「確率で再戦に応じてくれない場合があること」を鑑みると、試行回数を稼ぎたい「ものひろい」用途には不適と言えます。

そこで、野生ポケモンとの戦闘に軍配が上がりますが、エンカウントの度に草むらを走り回るのは非常にストレスです。特に、狭い草むらになると、意図せず草むらから出てしまったり、自転車の動きが遮られるなどの阻害要因があるからです。また「すごいつりざお」など、その場を動かずに野生ポケモンとエンカウントする方法もありますが、最大でも5割しかエンカウントできず、シビアなボタン押下が要求される点がマイナスです。これらは共通して「目が離せない」という欠点を抱えています。

f:id:tangential_star:20220103003624g:plain
f:id:tangential_star:20220103003603g:plain
バトルサーチャーは100歩のチャージが必要。つりざおは待ち時間が発生。
いずれも「短時間」での連続戦闘には向いておらず、「ものひろい」周回には不適

一方、「あまいかおり」を使うと、一歩も動かずに野生ポケモンと確定でエンカウントができます。すなわち、効率的なエンカウントが可能です。

手順としては、手持ちポケモンを開いて「あまいかおり」を使うだけのわずか2ステップですし、2回目以降は「秘伝技エフェクト」がスキップされるため、まさに誂え向きと言えます。

f:id:tangential_star:20220103004712g:plain

「あまいかおり」なら、わずか3秒でエンカウントできる ※GIF動画は等倍速

効率的な「ぎんのおうかん」の集め方(ものひろい周回方法)

以上の話を踏まえると、手持ち6匹を「ものひろい」で埋めつつ、「あまいかおり」が使えるポケモンを用意するのが最高効率であると言えます。

まずは「ものひろい」要員5匹と「あまいかおり」要員1匹を準備

BDSP環境において、比較的入手が容易な「ものひろい」持ちポケモンは、「パチリス」「エイパム」「ヒメグマ(シャイニングパールのみ出現)」などが挙げられます。地下大洞窟のポケモンは出現時のレベルも高く、拾えるアイテムがレベルに依存する「ものひろい」要員としてはまさにうってつけです。

特にパチリスは入手が容易であり、レベルにこだわらなければ「たにまのはつでんしょ」で簡単に出会うことができます(出現率25%)。

f:id:tangential_star:20220103124631g:plain

まずは「ものひろい」持ち5匹と、「あまいかおり」持ち1匹を準備しよう。
パチリスなどは「ものひろい」とは限らないため、特性をしっかり確認しよう

なお、特性「ものひろい」のポケモンはすべて2種類以上の特性をもつため、捕まえたら必ず特性を確認しておきましょう。ものひろいの「つもり」だった、では時間がもったいないです。

「あまいかおり」要員には「ヒメグマLv.100」が最適解

「あまいかおり」はほとんどの植物ポケモンが利用可能です。BDSPだと、ノモセ大湿原で出会える「マスキッパ」や、比較的序盤で手に入りやすい「ロゼリア」「ミツハニー」などがレベルわざとして習得可能です。ただし、数ある「あまいかおり」要員の中でも、特筆すべき採用候補は「ヒメグマでしょう。

ヒメグマは、BDSPにいるポケモンの中で唯一、特性「ものひろい」と「あまいかおり」を両立できるポケモンです*4シャイニングパール版でのみ「岩石の空洞」に低確率で出現します。残念ながらブリリアントダイヤモンドでは出現しませんので、パール版から連れてくるか、あまいかおり要員を1匹と割り切るか(=ものひろいを5匹でやるか)が必要です。

f:id:tangential_star:20220103124148g:plain

ヒメグマは特性「ものひろい」と、「あまいかおり」を両立できる。
進化してリングマになると特性が変わるため、Lv.100にしておこう

f:id:tangential_star:20220103122823g:plain

「がんせきのくうどう」に向かうには、ヨスガシティでたんけんセットを使おう
シャイニングパール版なら「ヒメグマ」に低確率(体感5%くらい)で会えるぞ

地下大洞窟でのエンカウントになるため、殿堂入り後であれば初期レベルも60超と高く、オススメです。なお、「あまいかおり」はLv.22で習得可能なため、捕まえたらノモセシティの「技思い出し」で「あまいかおり」を思い出させてあげましょう

ただし、「リングマ」に進化すると特性が変わってしまいますので、ヒメグマを利用する場合は、進化させないように注意しましょう。もっとも、「ものひろい」要員として利用する所以で「かわらずのいし」をもたせることはできないため、Lv.100まで育てるのが大変であれば、無理にヒメグマを使う必要はありません(あまいかおり要員として割り切って使いましょう)。

あとは序盤の草むらでひたすら周回

ここまで準備ができれば、あとは簡単。

201番道路など、野生ポケモンのレベルがあまり高くなく、こちらの攻撃でワンパンで倒せる場所に向かいます。この時、使うわざは命中100または必中技を使い、必ず1撃で倒せるようにしておきましょう。

草むらに入ったら、「あまいかおり」を使って野生ポケモンをおびき寄せ、それをひたすら倒し続けます。一連の流れを大体4~5回繰り返したら、手持ちポケモンのどうぐを回収しましょう。

f:id:tangential_star:20220103135121g:plain

手持ちポケモンが準備できたら、201番道路の草むらに向かおう
f:id:tangential_star:20220103135223g:plain
f:id:tangential_star:20220103135240g:plain
「あまいかおり」を使ってポケモンをおびき寄せ、ひたすら倒していこう

f:id:tangential_star:20220103135420g:plain

4~5回繰り返したら、手持ちポケモンの道具を回収しよう

あとは、これをひたすら繰り返すだけです。

前述の通り、「ものひろい」で拾えるアイテムは、そのポケモンのレベルが参照されますので、「ぎんのおうかん」を集めるためには、少なくともLv.71以上で周回をしましょう。「ものひろい」6匹で、1~2時間ほどで1個の「ぎんのおうかん」が手に入ります。

なお、手持ちポケモンのPPが無くなったら、Yボタンでコミュニケーションルームに入る(レポートを書くかの確認でBボタン)ことで、道具を消費せずにその場で回復することができますので活用しましょう。

f:id:tangential_star:20220103135532g:plain

Yボタンでコミュニケーションに入れば、手持ちのHP・PPが全回復する
(「レポートへ記録しますか?」で「いいえ」を押そう)

 

Arduino Leonardoによる自動化の準備

おまたせしました。ここからはプログラムを使った自動周回の方法について解説です。

とは言っても、上記の手順をプログラムで実装しているだけなので、大きな準備は手動周回のときと変わりません。ソースコードの冒頭に書き換えができる場所がありますので、それに合わせて修正いただくだけでOKです。

手持ちの「あまいかおり」要員と「ものひろい」要員の位置を入力

まずは、手持ちポケモンに「あまいかおり」要員1匹と「ものひろい」要員を準備します。そして、そのポケモンの手持ちでの順番に応じて、プログラムを書き換えます。

具体的には、「あまいかおり」が使えるポケモンが何匹目かをAMAI_KAORIに1~6で指定します。また、手持ち6匹について、それぞれの特性が「ものひろい」かどうかを配列monohiroiに「true」または「false」のいずれかで入力していきます。

// ★「あまいかおり」を覚えたポケモンが手持ちの何匹目かを指定
const int AMAI_KAORI = (1); // 手持ち先頭:「1」~ 最後尾:「6」

// ★とくせい「ものひろい」を持つポケモンの位置
// 「ものひろい」では無いポケモンには「false」に書き換えてください
const bool monohiroi[] = {
  true, // 1番目(先頭)
  true, // 2番目
  true, // 3番目
  true, // 4番目
  true, // 5番目
  true, // 6番目(最後尾) 
};

なお、特別な事由がない場合は、「ものひろい」の要員は5匹または6匹集めておくことを強くオススメします。また、あまいかおり要員は何匹目でも構いませんが、「きりばらい」「あなをほる」などのフィールドで使える技を複数持っていないことが前提となります。

先頭ポケモンの1つ目のわざ「PP」を入力

続いて、先頭(1匹目)のポケモンの1番目のわざのPPをプログラムに入力します。

使う技は、必ず命中100または必中技を使うようにしてください。

// ★先頭ポケモンの1つ目の技のPPを入れてください。
// ※これが0になると、YボタンからHP回復を行います
const int PP = (20); 

これにより、PPが0になった時に、Yボタンから「コミュニケーションルーム」に入ってPPの回復を行ってくれます。

周回の単位回数(アイテム収集の周期)を入力

続いて、何試行ごとに手持ちポケモンのどうぐを回収するかを「NUM_OF_BATTLE」に数字で入力してください。手持ち6匹が「ものひろい」であれば、オススメは「4」です。理由は後述します。

// ★道具回収を、何回の戦闘ごとに行うか?
// ※1戦闘ごとに10%の確率で「ものひろい」が発動します
const int NUM_OF_BATTLE = (4); 

「ものひろい」の発動確率は1匹につき10%ずつですから、手持ちの「ものひろい」要員の数や、確率のお好みに応じて調整してください。参考までに、戦闘回数ごとの、「ものひろい」の発動確率を下図の通り、計算してまとめてみました。

f:id:tangential_star:20220103155833p:plain

「ものひろい」の発動確率は1匹につき10%。ものひろい要員はたくさん集めよう。
効率よく道具を獲得するために「6匹で4回戦闘」「5匹で5回戦闘」をワンセットにしよう

少なくとも1個以上のアイテムを拾うためには、特性「ものひろい」を6匹集めて「4回」の戦闘をこなすことで、確率が9割を超えます。同様に、「ものひろい」が5匹しかいない場合でも、「5回」の戦闘を行えば、確率が9割を超えます。この数字を参考に、NUM_OF_BATTLEをご決定ください。

戦闘時間を入力

基本的に戦闘中はA連打で周回をしていきますので、充分に長い時間を入力してください。戦闘前の主人公カットインや、登場時のジャンプ(なつき)、色違いエフェクト・「いかく」などの発動特性、「こちらを向いてうなずいた」「きゅうしょにあたった!」「褒めてもらおうときゅうしょに当てた!」「こうかはばつぐんだ!」などなど、あらゆる遅延を考慮して十分に長い数字を入力してください。

特に「レベルアップ」とそれに伴う「進化」「技覚え」などが発生する恐れがある場合は、これらも想定いただき、ご利用いただければと思います。極力、各ポケモンのレベルは100にしておき、余計な時間がかからないようにしておきましょう。

// ★何秒間、A連打で戦い続けるか?(戦闘~フィールドに戻るまでの時間)
const int BATTLE_ELAPSED_TIME = (25); // (秒) ※最速25秒(手持ち全員レベル100)

 

自動化にむけてゲーム側の準備

最後に、下記を確認すればOKです。

  • 「あまいかおり」が使える条件下である(草むらの中・雨などが降っていない)こと
  • 目の前に話しかけられるもの・人・ポケモン(連れ歩き含む)などがいないこと
  • Xボタンでメニューを開いた時に「ポケモン」にカーソルがあっていること
  • Yボタンでコミュニケーションルームに入れる(ポケセン地下1階を訪れておく)こと

f:id:tangential_star:20220103162521p:plain

Xボタンを押した時に「ポケモン」にカーソルがあっていればOK

 

ソースコード

ソースコードです。Xボタンで「ポケモン」にカーソルが合う状態でArduino Leonardoを挿せば動きます。

基本的には、手動周回と同様の準備が必要です。概要はソースコード冒頭のコメントに記載の通りです。よくわからなければ本ブログ記事を見返して修正の上、ご使用ください。

/* 
 *  ★ポケモンBDSP Arduino Leonardoによる自動「ものひろい」道具集め★
 *  
 *  手持ちポケモンに「あまいかおり」を使ってもらい、野生ポケモンとエンカウント。
 *  そのポケモンを倒すことで、「ものひろい」が確率で発動、道具を拾ってくれます。
 *  ※手持ちをものひろい要員で埋めている前提です。
 *  
 *  
 *  事前準備
 *  ・201番道路の草むら(雨や雪がふらず、霧や砂嵐が出ていない状態で、野生ポケモンがワンパンで倒せる)で実行推奨
 *  ・手持ちの先頭を十分に強いポケモンにしておく(1つ目の技でワンパンできること)
 *  ・「あまいかおり」が使えるポケモンをてもちに加えておくこと(当該ポケモンが何匹目か、プログラムを書き換えてお使いください)
 *  ※レベルアップが起こり得るので、可能なら手持ちはすべてLv100にしておくのが望ましいです  
 *  ※レベルアップの際、1番目の技はA連打で消える可能性が大なので、注意してください。
 *  【重要:特に「あまいかおり」は、技の1番目を絶対に避けてください】
 *  
 *  注意事項
 *  ・草むらの中で使うこと
 *  ・天候が悪い状態だと「あまいかおり」が使えません
 *  ・Xボタンで「ポケモン」にカーソルをあわせておくこと【重要】
 *  ・Yボタンで「コミュニケーションルーム」に入れるように、ポケセン地下を一度訪れておくこと
 *  ・眼の前に話しかけるものが無いこと(連れ歩きポケモンを含む)を確認してください
 *  ・エムリットが徘徊していると、ごく低確率で遭遇する可能性があります(筆者経験談)
 *  ・「ものひろい」要員は進化しないように注意ください(例えばLv.100に予めしておくなど対策を)。
 *   ※例えば「ヒメグマ」は進化して「リングマ」になると「ものひろい」ではなくなります。
 *  
 *  設定
 *  ・はなしのはやさ:はやい
 *  ・もじモード:ひらがな
 *  ・せんとうアニメ:みない
 *  
 *  (c) 2020-2022 ますたーの忘備録
 *  https://tangential-star.hatenablog.jp/
*/

#include <SwitchControlLibrary.h>

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

// ★修正ここから★-----------------

// ★先頭ポケモンの1つ目の技のPPを入れてください。
// ※これが0になると、YボタンからHP回復を行います
const int PP = (20); 

// ★道具回収を、何回の戦闘ごとに行うか?
// ※1戦闘ごとに10%の確率で「ものひろい」が発動します
// ※このあたりを勘案して回数を検討してください。
// 未所持6匹の「ものひろい」発動確率は、46.85%(= 1-(0.9)^6 )です。
// 未所持5匹の「ものひろい」発動確率は、40.95%(= 1-(0.9)^5 )です。
// 未所持4匹の「ものひろい」発動確率は、34.39%(= 1-(0.9)^4 )です。
const int NUM_OF_BATTLE = (4); 


// ★「あまいかおり」を覚えたポケモンが手持ちの何匹目かを指定
const int AMAI_KAORI = (1); // 手持ち先頭:「1」~ 最後尾:「6」

// ★とくせい「ものひろい」を持つポケモンの位置
// 「ものひろい」では無いポケモンには「false」に書き換えてください(筆者は未検証。時間効率を考えると6匹すべてを「ものひろい」にしておきたいですね)
// ※BDSPにて「ものひろい」と「あまいかおり」の両立は「ヒメグマ」しかいません(リングマはNG)。
// ※ヒメグマは、シャイニングパールにしか出現しません
const bool monohiroi[] = {
  true, // 1番目(先頭)
  true, // 2番目
  true, // 3番目
  true, // 4番目
  true, // 5番目
  true, // 6番目(最後尾) 
};

// ★何秒間、A連打で戦い続けるか?(戦闘~フィールドに戻るまでの時間)
// ※戦闘前の主人公カットイン・色違いエフェクト・「いかく」「あめふらし」などの発動特性など、には注意してください。
// ※登場時のジャンプ(なつき)、「こちらを向いてうなずいた」「きゅうしょにあたった!」「褒めてもらおうときゅうしょに当てた!」「こうかはばつぐんだ!」など
//  あらゆる遅延を考慮して十分に長い数字を入力してください。
//   ●特に、控えポケモンを含む【レベルアップ】には十分に注意してください(わざを覚えるのも含む)。
// ※25秒は、手持ちすべてがLv100・なつき無しの想定(レベルアップしない前提)の「ギリギリ」の数字です(筆者環境で10時間の連続稼働確認実績あり)
const int BATTLE_ELAPSED_TIME = (25); // (秒) ※最速25秒(手持ち全員レベル100)

// ★「あまいかおり」を当該フィールドで初めて使う場合はtrueに。
// ※ひでんわざの発動バンクが表示されるため
bool isFirstTimeSweetScent = (true);


// ★修正ここまで★-----------------


// ループカウンタ更新(初期化)
volatile int curPP = PP;
volatile int curBattle = NUM_OF_BATTLE;


// 関数のプロトタイプ宣言
void battle_barrage(double second, char* keyname, int cycle);
int pickItems(bool* party_list, size_t len);
void gotoUnionRoom(void);

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 die(void);


void setup() {

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

  // Rを長押しして、ポケッチを収納
  PushKey("R", 1300, 1000);

  // Rを押してポケッチ出現
  PushKey("R", HOLDTIME, 1000);

}

void loop() {

  // ★「あまいかおり」を使う
  if( useSweetScent(AMAI_KAORI) == 0 ) die();

  // ★「あまいかおり」初回利用時のみ、秘伝技のバンクが再生される
  if(isFirstTimeSweetScent){
    delay(2500);
    isFirstTimeSweetScent=false;
  }
  
  // ★戦闘(指定時間だけA連打)
  battle_barrage( (double)BATTLE_ELAPSED_TIME, "A", 150);

  // ★戦闘終了
  curPP--;     // 現在の残りPPを更新
  curBattle--; // 道具回収までの残りバトル回数を更新

  // ★PP回復(ユニオンルーム利用)
  if(curPP <= 0){
    gotoUnionRoom();
    curPP = PP; // PPをもとに戻す
  }

  // ★どうぐ回収
  if(curBattle <= 0){
    if( pickItems(monohiroi, sizeof(monohiroi)/sizeof(monohiroi[0]) ) == 0 ) die();
    curBattle = NUM_OF_BATTLE; // 残り回数をもとに戻す
  }

}

void battle_barrage( double second, char* keyname = "A", int cycle = 150){
  unsigned long int current_time=0;
  unsigned long int start_time=0;
  unsigned long int temp_time=0;
  volatile bool isholding = false;
  volatile float holdA = 0;
  unsigned long int battle_msec = (unsigned long)second*1000UL;

  for( start_time=millis(), current_time=start_time, isholding=false ; current_time - start_time < battle_msec ; current_time=millis() ){

     // RENDA_CYCLEミリ秒ごとにAを押す
     temp_time = (current_time - start_time) % (cycle+1); // 経過ミリ秒のcycle剰余を計算
     holdA = (float)temp_time / (float)cycle * 100.0; // 剰余から100%に変換
     if( holdA <= 35.0 ){ // RENDA_CYCLEの3.5割の時間はA押し、それ以外はAを離す
       if(!isholding){
         SwitchControlLibrary().PressButtonA();
         isholding = true;
       }
     }else{
       if(isholding){
         SwitchControlLibrary().ReleaseButtonA();
         isholding = false;
       }
     }
  }
  if(isholding) SwitchControlLibrary().ReleaseButtonA();

  return;
}


int useSweetScent(int who_can_use){
  if(who_can_use <= 0 || who_can_use > 6) return 0;

  int lpcnt = 0;
  PushKey("X",HOLDTIME, 800); // メニューを開く
  PushKey("A",HOLDTIME, 1300); // 「ポケモン」を開く
 // カーソルをあわせる
  for(lpcnt = 1; lpcnt < who_can_use ; lpcnt++ ) PushKey("Down",HOLDTIME, 400);
  PushKey("A",HOLDTIME,    500); // →つよさをみる
  PushKey("Down",HOLDTIME, 400); // →あまいかおり
  PushKey("A",HOLDTIME,    500); // (あまいかおりを使う)
  return lpcnt;
}

int pickItems(bool* party_list, size_t len = 6){
  
  if(party_list == NULL ) return 0;

  int lpcnt = 0;

  PushKey("X",HOLDTIME, 800); // メニューを開く
  PushKey("A",HOLDTIME, 1300); // 「ポケモン」を開く
  for( lpcnt = 0 ; lpcnt < len ; lpcnt++ ){
    if(party_list[lpcnt] == false){
      PushKey("Down",HOLDTIME, 400); // 次のポケモンにカーソルをあわせる
      continue;
    }
    PushKey("A",HOLDTIME,    500); // →つよさをみる
    PushKey("Up",HOLDTIME,   400); // →やめる
    PushKey("Up",HOLDTIME,   400); // →もちもの
    PushKey("A",HOLDTIME,    500); // →バッグをひらく
    PushKey("Down",HOLDTIME, 400); // →バッグへもどす or やめる
    PushKey("A",HOLDTIME,    600); // ●●から ●●を あずかりました! or →つよさをみる
    PushKey("B",HOLDTIME,    600); // (ポケモン選択終了)

    PushKey("Down",HOLDTIME, 400); // 次のポケモンにカーソルをあわせる
  }
  PushKey("B",HOLDTIME, 900); // メニューに戻る
  PushKey("B",HOLDTIME, 900); // メニューを閉じる(フィールドに戻る)
  return lpcnt;
}

void gotoUnionRoom(void){
  // ユニオンルームに入ることでHP・PPが回復する仕様を生かして無限ループへ。
  PushKey("Y",HOLDTIME, 600); // ユニオンルーム
  PushKey("A",HOLDTIME, 600); // ユニオンルームへ ようこそ!▼
  PushKey("A",HOLDTIME, 600); // ポケモン コミュニティクラブ です▼
  PushKey("A",HOLDTIME, 600); // こちらでは ちかくにいる ともだちと つうしんで あそべます
  PushKey("A",HOLDTIME, 600); // へやに はいりますか?
  PushKey("A",HOLDTIME, 600); // →はじめる
  PushKey("A",HOLDTIME, 600); // ここまでの ぼうけんを レポートに きろくしますか?
  PushKey("A",HOLDTIME, 600); // →はい
  PushKey("Down",HOLDTIME, 600); // →いいえ
  PushKey("B",HOLDTIME, 600); // それでは また おこしください
  PushKey("B",HOLDTIME, 600); // ユニオンルーム
  PushKey("B",HOLDTIME, 800);
  return;
}


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 die(void){
  PushKey("Home",HOLDTIME, 800);
  for(;;)delay(1000);
}

 

プログラムのあとがき(テクニカルな人向け)

※プログラムに興味無いよ、という人は→あとがきまで読み飛ばす

普段、私はC言語などについてブログで書くことはめったに無いのですが、実装中に大きなコーディングミスをしたので忘備録として残します。C言語を普段から触っている人・読める人は「何をいまさらww」と思われるかもしれませんが、私は趣味なので…大目に見てください。

仮引数の配列はsizeofで要素数が計算できない

結論から言うと、配列のサイズをsizeof演算子で計算できるのは、配列を宣言したスコープ内に限られる、という言語仕様(よく考えれば当たり前)に注意しましょう、ということです。より厳密には、sizeofにポインタを与えると、そのサイズを計算する、ということです。具体例を交えます。

 

(意図通りに動かないプログラム:配列の長さを、外部の関数内で計算しようとしている)

#include <stdio.h>

void print_int_array(int *arr);

int main(void){
    int array[] = {0,1,2,3,4,5,6,7,8,9,}; // 要素が何個かの配列
    print_int_array(array); // 配列を印字する関数にarrayを渡す
    return 0;
}

void print_int_array(int *arr){

    // 配列のサイズを計算
    int length = sizeof(arr) / sizeof(arr[0]); // ←これが「意図通り」に動かない

    // 配列のサイズ分だけ、1つずつ印字
    for(int i=0; i < length ; i++ ){
        printf("%d,", arr[i]);
    }
    printf("\n");
    return;
}

このソースコードでは、main関数内で定義された配列「array」を、外部の関数void print_int_array()に渡し、要素を1つずつprintfで印字(しようと)しています。

 

print_int_arrayの中では、引数の配列のサイズ(長さ)が分からないため、sizeof演算子を駆使して、配列arrのメモリサイズを、同じく配列arrの要素1つ分のサイズで除することで配列長を計算(しようと)しています*5。一見、うまくいきそうです。

  int length = sizeof(arr) / sizeof(arr[0]); 

しかしながら、これが意図通りに動作しません

 

理由は明白で、仮引数「arr」の実態がintのポインタであり、それ以上の情報がないからです(arrという名前がミスリードを誘いますが、仮引数の名前が「int *ptr」だったら気付ける仕様ですね)。

もっと詳しく言えば、この実装において「sizeof(arr)」で計算されるのは、配列のメモリ上の長さではなくint型のポインタのサイズになるからです。つまり、上記の1行は、下記と同等であると言えます(これを見れば、意図どおりでは無いことが明白です)。

  int length = sizeof( int* ) / sizeof(arr[0]); 

sizeof(arr)と書いていた部分が、sizeof(int*)に書き換わっています。これは、「arr」の宣言が、配列ではなく仮引数として指定したポインタだからです。この一文の計算結果は、その環境(アーキテクチャ)によりけりですが、少なくとも、(意図した)arrの情報同士を比較しているわけでは無いので、正しくないことは自明ですよね。

つまり、この仕様を踏まえると、外部の関数に配列を渡したら、その関数からはその配列の長さを知る術は無い、ということです。冷静に考えれば、だからこそ文字列には終端記号(\0)が入るわけですし、ポインタもNULLが用意されているのですよね。このあたりの理解が浅かったなぁ、と反省しました。

ちなみに、上記のプログラムを意図通りに動くように実装するには、下記のように、配列を定義したスコープ内でsizeofを使うほかありません。

 

(意図通りに動くプログラム:配列を定義したスコープ内でsizeofを使って配列長を計算)

#include <stdio.h>

void print_int_array(int *arr, int len);

int main(void){
    int array[] = {0,1,2,3,4,5,6,7,8,9,}; // 要素が何個かの配列
    print_int_array(array, sizeof(array) / sizeof(array[0])); // 配列を印字する関数にarrayと配列長を渡す
    return 0;
}

void print_int_array(int *arr, int len){

    // 配列のサイズ分だけ、1つずつ印字
    for(int i=0; i < len ; i++ ){
        printf("%d,", arr[i]);
    }
    printf("\n");
    return;
}

 

結論、何がまずかったのかといえば、「『sizeof(配列名) / sizeof(配列名[0]);』で、配列の長さが計算できる!」と(プログラマが)盲信していることです。特に、プログラミング解説サイトが有象無象に乱立する現代において、上記の認識で説明・紹介をするサイトもいくつか見受けられるので、注意が必要です*6

なぜ気づいたか

今回、loop()にベタ打ちで書いていた処理をすべて関数にして、可読性を上げようとしたのが発端でした。

手持ちポケモンの「ものひろい」要員かどうかを判別する配列「bool monohiroi[]」の要素数、すなわち手持ちポケモンの数が「6匹」とは限らない場合を想定し、上記と同様の実装でpickItems()関数を実装した時に、何故か「2匹分しか道具を回収してくれない」という事象が発生したのです。

Arduinoには数字を表示したりするディスプレイが無いため、プログラミングにおけるデバッグ作業は動いている様子から確認するしかありません。6回のループが回る「はず」の部分で、5回や7回ならループ判定、0回なら条件判定部分、1回ならループカウンタ、といった具合に、実装エラー箇所が特定できるのですが、今回は決まって「2回」という中途半端な回数であり、少し悩みました。一方で、「ループ回数」を指定しているソースは当該sizeof演算子で記述した部分しかなかったため、これについて、C言語の関連仕様をWebで調べて読み直し、上記を理解できました。

上記を踏まえると、2回ループが回っていたことからArduino Leonardoのint型は2byte、ポインタは4byteの実装系だと推察できます。現に過去、筆者のコーディングミスによりint型の最大値は32767で2byteだったことが判明したことからも、おそらく、そういう仕様なのだろうと思われます。

以上の知見は、見る人が見ればためになるかも、と思いつつ、今後自分が同様のミスをしないよう、ここに「あとがき」として記載しておきます。

 

あとがき

今回はダイパリメイク自動化第4弾として、ふしぎなアメ」「ぎんのおうかん」「ポイントアップ」の無限回収を実装しました。いやぁ~ついにできました!

これでやっと地獄のようなBDSP厳選環境から脱出できますね…!(厳選も育成もまだ0匹。やっとやる気が出てきます←おい)

育成環境を整えるために「バトルタワーに挑戦」する、というのが非常に億劫でして、なかなか殿堂入り後のシナリオを進める気が起こらないという状況でした。特に、今は雑に「コンテスト攻略もしたいなぁ~」と思っていて、前回・前々回記事でも書いたとおり、シール用のきのみを集めたりふれあい広場のきのみを集めたり、といったのんびりプレイ状況でした。

その一方で、先月にはお陰様で本ブログの歴代最高PV数を叩き出し、PV数や検索流入を鑑みると、やはりというべきかBDSP環境においても「厳選」「育成」に関わる自動化を望む声も多く、それに応える形での実装を考えた、という次第でした。

確かに、今作は「レベル上げ」がかなりめんどくさい上に、「厳選」も簡単では無いことから、エンジョイ勢の自分としても「ふしぎなアメ」の増産体制を望んでいたこともあり、今回の実装チャレンジは我ながら大成功だったと思います。

f:id:tangential_star:20220103192849p:plain

もったいなくてなかなか使えない「ふしぎなアメ」「ポイントアップ」がまさかの100個超え!

f:id:tangential_star:20220103193441p:plain

「あかいいと」は流石に18個もいらない…(笑)


今後も、ゆるっとブログを更新しつつ、自動化もチャレンジしつつ、マイペースに遊んでいけたらと思いますので、引き続きよろしくおねがいいたします。

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

 

前回記事:【BDSP自動化03】低レアきのみ自動購入【クラボのみ】

次の記事:【BDSP自動化05】地下大洞窟でディグダ探し【ポケモンの石像✨】

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

ポケモン剣盾の記事:ポケモン剣盾Arduino自動化 カテゴリーの記事一覧

ポケモンBDSPの記事:ダイパリメイクArduino自動化 カテゴリーの記事一覧

YouTubeチャンネル:ますたーの忘備録 - YouTube 

---

*1:「ものひろい」で拾えるアイテムは、そのポケモンのレベルによって変わります。ぎんのおうかんを拾うためには、「ものひろい」のポケモンのレベルが「71以上」必要です

*2:13時間放置で「ぎんのおうかん」9個:手持ち全員Lv100・1戦闘あたり25秒・手持ち1匹目「あまいかおり」・手持ちの「ものひろい」6匹・4戦ごとにアイテム回収・20戦ごとにYY通信でHP回復の条件下での実測値

*3:ぎんのおうかん:手持ちのLv100ポケモンのどれか1つののうりょく(個体値)を「さいこう!」(最大値)まで鍛えることができるアイテム。厳密には「すごいとっくん」を行うためのアイテム

*4:余談ですが、「ものひろい」「あまいかおり」を両立できるポケモンは「ヒメグマ」が文字通り唯一無二です。剣盾はもちろん、USUM以前の環境においても他にいません

*5:同一スコープ内で宣言された配列の長さを求めるには、「sizeof(配列名)/sizeof(配列名[0])」で計算できます。例えば、int型が4byteの環境で、int array[10]で宣言したとすれば、sizeof(array)で、arrayのメモリ長=40byte, sizeof(array[0])で4byte、これらで除算すれば40/4=10で配列長が求められる、というロジックです

*6:余談ですが、sizeof演算子による配列サイズ計算にポインタを渡した実装について、一般社団法人JPCERTコーディネーションセンターがコーディングエラー例として解説しています