画像認識を使ったPoke-Controller用プログラムになります。

GBA版FRLG(2004年1月29日発売)でも入手難易度が高いアイテムでしたが、1年半後のポケモンXD(2005年8月4日)で1周で1個が手に入るアイテムとなったことにより以降は研究されなくなっていました。
ところが今回のSwitch版FRLGは、同時期に発表されたNSO(Nintendo Switch Online、ニンテンドー ゲームキューブ Nintendo Classics)版ポケモンXDの情報で「連動しない」と明記されており、この入手方が再び必要となってしまいました。
(でも知らないだけで乱数調整で「アイテムを持っているラッキー」を安定して出す方法があったりするんだろうか?)
いずれにせよ自動化出来るのならオフ会で欲しい人に配れるように大量回収しておこう!という事でこれを制作しました。
前知識など
サファリゾーンのラッキーはエリア2(入って上)が最も出現率が高く、さらに先頭をLv26にする事で出現率が4%から5.3%にアップする。
具体的にはパラスLv23とタマタマLv25の出現をカットできる。
「あまいかおり」はスプレーの効果を無視してエンカウントするので使えない。
日本語版ではサファリの逃走率にバグがあり、石もエサも逃走率が変化しない。捕獲率のみが変化する仕様になっている。
発売の遅かった海外版ではこれが修正され、まずエサを2個投げて逃走率を下げる方法が良いとされてている。
(自分のプログラミングの腕のせいだと思うが)4方向にグルグル向くエンカウント法は何故か移動してしまって使えない。
内角のマスで2方向交互入力はなぜかエンカウントが発生しなくなる(←↑交互入力状態から↓を入れると何故か出たりする)。
結局、↑↓交互に1マス移動が安定して1時間200匹(=1分3.3匹)遭遇できたので、スプレーの消費を考えなければ問題はないという結論に至った。
(↑↓移動なら想定外の動きでも歩数オーバーから復帰できるというのもあります。まぁ、動かずエンカウントできれば正確な歩数を得られて対処できるんですが…)
【以降の作品との違い】アイテム所持率増加のフィールド特製〔ふくがん〕はEm以降でまだ無い。有用なのは〔はっこう〕となる。
【以前の作品との違い】第一世代と違いラッキーが「ハナダのどうくつ」に出現しない。
事前準備
・手持ち
先頭:Lv26〔はっこう〕ヒトデマン系
2匹目:Lv41以上〔あくしゅう〕ベトベター系※事故対策なのであれば
3匹目:「なみのり」役ポケモン※先頭入れ替えの手数が増えるので先の2匹にはフィールド技を覚えさせない事!
他の手持ちは自由。
・道具
『ゴールドスプレー』X個※120個で約24時間動かせる
・所持金
X/2*500 円以上※30,000円で約24時間動かせる
・画像認識に用意する画像

「STARATメニューの『▶ポケモン バッグ』」※メニュー操作の判定に使う
「STARATメニューの『ポケモン ▶バッグ』」※メニュー操作の判定に使う
「サファリの受付」
「主人公の背中」
『アナウンス「ピンポーン』
「4*4の草むら」
「サファリ野生のラッキー」
「サファリ野生のパラス」※Lv23しかいないのでレベル不問
「サファリ野生のタマタマ」「Lv25」※分かれていてよい。Lv27もいるのでレベル部分が必要
「ボール残数の10の位が無い状態」
「サファリ野生のHPバー」※未捕獲がいるならボールマーク部分は入れない
プログラムの流れ
スプレーの残数(spray_stock)=X・効果(spray)=0を設定
以下を繰り返し
|サファリゾーンに入場
|もし、効果が0以下なら使用し残数を1減らし、効果を250にする※効果の値が間違っていても「のこってます!」と出るだけで問題ない。…が実際の効果が52以下だと途中で出る「スプレーの効果が切れ」メッセージでによる歩数ズレで目的地に着けない(上下移動による歩数オーバーで受付に戻る)
|手持ちの先頭を二番目と入れ替える※Lv26から41以上
|エリア2の草むら目標地点に移動
|手持ちの先頭を二番目と入れ替える※Lv41以上からLv26
|
|「サファリの受付」が見えるまで以下を繰り返し
| |「主人公の背中」か「サファリの受付」が見えるまで以下を繰り返し
| | |↑0.2秒、↓0.2秒入力し、効果を2減らす
| | |もし、『アナウンス「ピンポーン』が見えたらA連射(受付へ)
| |もし、「ラッキー」が見えたら効果を12増やし、石を2個投げ、「4*4の草むら」か「サファリの受付」が見えるまでA連射
| |もし、「パラス」か「タマタマ」かつ「Lv25」が見えたら逃げる、スプレーを使用し効果を250にする
| |もし、「ボール残数の10の位が無い状態」が見えたら効果を12増やし、逃げる、リタイア
| |もし、「HPバー」が見えたら効果を12増やし、逃げる※12増やすのはエンカウント発生から「とびだしてきた!▼」のA入力まで約6.0秒間に発生した「効果を2減らす」を戻す為。
|
|スプレーの残数が0個ならHOMEボタンで終了
|もし、効果が52以下なら外に出て52歩以上歩いて、スプレーを使用し効果を250にする
実行結果
・ボール連投のみ(理論上は10.28%)
約5時間半で入場7回。追加スプレーは14回使用。
遭遇数は886匹=158.21匹/時、ラッキー遭遇数は48匹=8.57匹/時、ラッキー捕獲数は3匹=0.54匹/時、ラッキー捕獲率6.25%
・石1個+ボール連投(理論上は10.42%)
未実行。
・石2個+ボール連投(理論上は11.98%)
約8時間10分で入場15回。追加スプレーは25回使用。
遭遇数は1635匹、ラッキー遭遇数は89匹、ラッキー出現率は5.44%、ラッキー捕獲数は7匹、ラッキー捕獲率7.87%
1時間あたり200.20匹に遭遇、ラッキー遭遇数は10.90匹、ラッキー捕獲数は0.86匹、しあわせタマゴは0.12個。
・餌2個+ボール連投
約8時間10分で入場15回。追加スプレーは25回使用。
遭遇数は1696匹、ラッキー遭遇数は74匹、ラッキー出現率は4.38%、ラッキー捕獲数は1匹、ラッキー捕獲率1.35%
1時間あたり206.82匹に遭遇、ラッキー遭遇数は9.06匹、ラッキー捕獲数は0.12匹、しあわせタマゴは0個。
ややこしいですが「ラッキー捕獲率」は「あるターンにおいての捕獲率」ではなく「その方法で投げ続けた結果、捕獲できた確率」を示しています。
※これらは「石・餌の使用により逃走率が変化しないバグ」がある日本語版においての話であり、北米版などではエサを投げて逃走率を下げた方が良いという結果になっています。
https://t.co/C4O1OXDH8g
— Professor Rex🇨🇦 (@RexProfessor) 2026年3月5日
Odds of success with balls only: 10.28%
One rock: 10.42%
Two rocks: 11.98% pic.twitter.com/Lo5rk1zDe1
詳しい計算を教えていただいたので紹介。
結果は「ボール連投」は10.28%で「石2個+ボール連投」は11.98%とのこと。比較すると1.7%上昇して捕獲率1.165倍になる計算。大量回収するなら期待値の高いこちらが良いでしょう。
もちろん通常は1個手に入れば十分なので確率の上振れを考えると「ボール連投」の10.28%は十分許容範囲と言えます(当時、私が入手した時も主流の「ボール連投」を使いました。2週間かかり今も思い出せる位しんどかったです…)。
ソースコード
以下の通り。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-from Commands.PythonCommandBase import PythonCommand, ImageProcPythonCommand
from Commands.Keys import KeyPress, Button, Direction, Stick, Hatclass AutoPickUP(ImageProcPythonCommand):
NAME = 'FRLG自動ラッキー狩り改'def __init__(self,cam):
super().__init__(cam)def do(self):
print("-------------------------------")
print("BDSP自動戦闘【日本語版】")
print("Developed by えらー")
print("FRLGの自動ラッキー狩りプログラムLucky Egg")
print("先頭はLv26〔はっこう〕、2番目はLv41以上とする事!")
print("起動前にメニューカーソルを「プレイヤー名」にして閉じる事!")
print("-------------------------------")
self.wait(0.5)#先頭はLv26〔はっこう〕ヒトデマン、2番目はLv41以上とする事
#他3匹は問わない。
#道具の一番上を『スプレー』にする事
#「あまいかおり」はスプレーの効果を無視するので使用できないspray = 0 # スプレーの残り歩数チェック
spray_stock = 100 # スプレーの残数チェックself.press(Button.B, 0.5, 0.5) # B
self.press(Button.B, 0.5, 0.5) # Bwhile True:
#サファリへの入場
print("入場")
self.press(Hat.TOP, 3.0, 0) # ↑移動(受付中央スタート
for battle in range(50): # 10秒A連射で入場
self.press(Button.A, 0.1, 0.1) # Aボタンを押す
#スプレーの使用
if(spray <= 0):
#メニューカーソルは4番目という情報が記録されているが、「リタイア」が上に挿入され「バッグ」になる
self.press(Button.X, 0.1, 0.8) # Xメニューを開く
if( self.isContainTemplate('FRLG_menu_pokemon.png', 0.99, use_gray=True, show_value=False)):
#メニューカーソルが「ポケモン」なら↓「バッグ」へ
self.press(Hat.BTM, 0.1, 0.8)# ↓
self.press(Button.A, 0.1, 1.3) # Aバッグを開く
self.press(Button.A, 0.1, 0.8) # Aどうしますか?
self.press(Button.A, 0.1, 1.3) # Aつかう
self.press(Button.A, 0.1, 0.8) # Aつかった!
self.press(Button.B, 0.1, 1.3) # B道具を閉じる
self.press(Button.B, 0.1, 0.6) # Bメニューを閉じる
spray = 250 #むしよけ100、シルバー200、ゴールド250によって変更する。
spray_stock -= 1 #残数-1
#先頭をLv41以上に入れ替え
self.press(Button.X, 0.1, 0.8) # Xメニューを開く
if( self.isContainTemplate('FRLG_menu_bag.png', 0.99, use_gray=True, show_value=False)):
#メニューカーソルが「バッグ」なら↑「ポケモン」へ
self.press(Hat.TOP, 0.1, 0.8)# ↓
self.press(Button.A, 0.1, 1.3) # Aポケモンを開く
self.press(Button.A, 0.1, 0.8) # Aどうする?
self.press(Hat.BTM, 0.1, 0.8)# ↓ならびかえ
self.press(Button.A, 0.1, 1.3) # Aどこに いどうしますか?
self.press(Hat.RIGHT, 0.1, 0.8)# →移動
self.press(Button.A, 0.1, 1.3) # A実行
self.press(Button.B, 0.1, 1.3) # Bポケモンを閉じる
self.press(Button.B, 0.1, 0.6) # Bメニューを閉じる
#中央エリア
self.hold(Button.B) # Bを長押し
self.press(Hat.TOP, 1.4, 0) # ↑移動(柵まで
self.press(Hat.RIGHT, 1.2, 0)# →移動
self.press(Hat.TOP, 0.9, 0) # ↑移動(柵まで
self.press(Hat.LEFT, 0.3, 0)# ←移動(水辺まで
self.holdEnd(Button.B) # Bを離す
#なみのり
self.press(Button.A, 0.1, 0.8) # A
self.press(Button.A, 0.1, 0.6) # A
self.press(Button.A, 0.1, 0.8) # A
self.press(Button.A, 0.1, 3.0) # A#中央エリア
self.hold(Button.B) # Bを長押し
self.press(Hat.TOP, 1.9, 0) # ↑移動(壁まで
self.press(Hat.LEFT, 0.9, 0)# ←移動(通路まで【調整重要】1.2x0.8x
self.press(Hat.TOP, 0.5, 0) # ↑移動(柵まで
self.holdEnd(Button.B) # Bを離す#エリア2
self.press(Hat.TOP, 2.4, 0) # ↑移動(階段手前まで 2.1で左手前角の行
self.press(Hat.LEFT, 1.0, 0)# ←移動(壁まで
self.press(Hat.TOP, 0.3, 0.3) # ↑移動(角まで 【0.3待機しないと入れ替えが発生しない!】
spray -= 52 #ここまで無駄なしだと52歩#先頭をLv26に入れ替え
self.press(Button.X, 0.1, 0.8) # Xメニューを開く
if( self.isContainTemplate('FRLG_menu_bag.png', 0.99, use_gray=True, show_value=False)):
#メニューカーソルが「バッグ」なら↑「ポケモン」へ
self.press(Hat.TOP, 0.1, 0.8)# ↑
self.press(Button.A, 0.1, 1.3) # Aポケモンを開く
self.press(Button.A, 0.1, 0.8) # Aどうする?
self.press(Hat.BTM, 0.1, 0.8)# ↓ならびかえ
self.press(Button.A, 0.1, 1.3) # Aどこに いどうしますか?
self.press(Hat.RIGHT, 0.1, 0.8)# →移動
self.press(Button.A, 0.1, 1.3) # A実行
self.press(Button.B, 0.1, 1.3) # Bポケモンを閉じる
self.press(Button.B, 0.1, 0.6) # Bメニューを閉じる#終了する(受付が写る)までループ
while not( self.isContainTemplate('FRLG_counter.png', threshold=0.8, use_gray=True, show_value=False)):#遭遇する(主人公の後ろ姿が写る)までループ
while not (self.isContainTemplate('FRLG_master.png', threshold=0.8, use_gray=False, show_value=False) or
self.isContainTemplate('FRLG_counter.png', threshold=0.8, use_gray=True, show_value=False) ):
self.press(Hat.TOP, 0.2, 0)# ↑移動
self.press(Hat.BTM, 0.2, 0) # ↓移動 0.3で向き+3マス移動 0.2は向き+1マスになる
spray -= 2 # スプレーの残数-2
#600歩オーバーによる終了
if( self.isContainTemplate('FRLG_announce.png', 0.9, use_gray=True, show_value=False)):
print("600歩オーバー")
for battle in range(50): # 10秒A連射 4.3秒は必要
self.press(Button.B, 0.1, 0.1) # B 受付に送られる#ラッキーが写っているなら、
if( self.isContainTemplate('FRLG_Chansey.png', 0.9, use_gray=True, show_value=False)):
print("遭遇アタリ")
spray += 30 # エンカウント処理中に減らしたスプレー残量を加算
#石を2回投げ、マップか受付が写るまでA連射。
self.press(Button.B, 0.1, 2.0) # B とびだしてきた▼
self.press(Hat.BTM, 0.1, 0.1) # ↓「いしころ」へ
self.press(Button.A, 0.1, 5.0) # A いしころ
self.press(Button.A, 0.1, 5.0) # A いしころ
self.press(Hat.TOP, 0.1, 0)# ↑「ボール」へ
while not( self.isContainTemplate('FRLG_area2.png', 0.9, use_gray=True, show_value=False) or
self.isContainTemplate('FRLG_counter.png', 0.9, use_gray=True, show_value=False)):
for battle in range(25): #5秒A連射
self.press(Button.A, 0.1, 0.1) # Aボタンを押す#パラス(Lv23)かタマタマLv25が写っているなら、
if( self.isContainTemplate('FRLG_Paras.png', 0.9, use_gray=True, show_value=False) or
(self.isContainTemplate('FRLG_Exeggcute.png', 0.9, use_gray=True, show_value=False) and
self.isContainTemplate('FRLG_Lv25.png', 0.98, use_gray=True, show_value=False))):
print("遭遇&スプレー追加")
self.press(Button.B, 0.1, 2.0) # B とびだしてきた▼ 1.0でいい?
self.press(Hat.RIGHT, 0.1, 0.1) # →
self.press(Hat.BTM, 0.1, 0.1) # ↓
self.press(Button.A, 0.1, 0.7) # A にげる
self.press(Button.B, 0.1, 2.6) # B うまくにげきれた!
#メニューカーソルは4番目という情報が記録されているが、「リタイア」が上に挿入され「バッグ」になる
self.press(Button.X, 0.1, 0.8) # Xメニューを開く
if( self.isContainTemplate('FRLG_menu_pokemon.png', 0.99, use_gray=True, show_value=False)):#0.870
#メニューカーソルが「ポケモン」なら「バッグ」へ
self.press(Hat.BTM, 0.1, 0.8)# ↓
self.press(Button.A, 0.1, 1.3) # Aバッグを開く
self.press(Button.A, 0.1, 0.8) # Aどうしますか?
self.press(Button.A, 0.1, 1.3) # Aつかう
self.press(Button.A, 0.1, 0.8) # Aつかった!
self.press(Button.B, 0.1, 1.3) # B道具を閉じる
self.press(Button.B, 0.1, 0.6) # Bメニューを閉じる
spray = 250 #むしよけ100、シルバー200、ゴールド250によって変更する。
spray_stock -= 1 #残数-1#そうでない野生なら逃げる。
if(self.isContainTemplate('FRLG_HP.png', 0.9, use_gray=True, show_value=False)):
print("遭遇")
spray += 30 # エンカウント処理中に減らしたスプレー残量を加算
self.press(Button.B, 0.1, 4.0) # B とびだしてきた▼
self.press(Hat.RIGHT, 0.1, 0.1) # →
self.press(Hat.BTM, 0.1, 0.1) # ↓
if(self.isContainTemplate('FRLG_ball9.png', 0.93, use_gray=True, show_value=False)):# のこり30で.900、20で.882、10で.920、一致.999
print("&ボール残り9以下")
self.press(Button.A, 0.1, 0.7) # A にげる
self.press(Button.B, 0.1, 2.6) # B うまくにげきれた!
self.press(Button.X, 0.1, 0.8) # Xメニューを開く※カーソルは3番目のポケモンにある
self.press(Hat.TOP, 0.1, 0.8)# ↑ずかん
self.press(Hat.TOP, 0.1, 0.8)# ↑リタイア
for battle in range(35): #7秒A連射
self.press(Button.A, 0.1, 0.1) # Aボタンを押す
else:
self.press(Button.A, 0.1, 0.7) # A にげる
self.press(Button.B, 0.1, 2.6) # B うまくにげきれた!
#受付でスプレー残数が0ならHOMEに戻って終了する
if(spray_stock <= 0):
print("スプレーが尽きました")
self.press(Button.HOME,0.3,0.3)
self.finish()#受付でスプレーの効果52以下だと目的地に着けないので、外で残量を消費する
print("スプレーの残り",spray, "歩")
if(spray < 0 and
spray <= 60):# 余裕をもって60
self.hold(Button.B) # Bを長押し
self.press(Hat.BTM, 3.5, 0) # ↓移動
for battle in range(10): #20秒↑↓交互連射 60マス
self.press(Hat.TOP, 0.3, 0) # ↑移動 0.5秒ダッシュで3マス
self.press(Hat.BTM, 0.3, 0) # ↓移動 0.5秒ダッシュで3マス
self.press(Hat.TOP, 4.0, 0) # ↑移動(受付中央スタート
self.holdEnd(Button.B) # Bを離す
spray = 0 # スプレーの効果を0に