mizdra's blog

ぽよぐらみんぐ

Headless Chrome を使って自動車学校の技能教習の予約が空いたら通知するスクリプトを書いた

最近, 免許を取るために自動車学校に通っていたのですが, 技能講習の予約が一杯で中々教習が進まず困っていました. 通っていた教習所ではオンラインで技能講習の予約を取れるサービスが公開されていたので, また最近話題の Headless Chrome を触ってみたいとも思っていたので, 勢いでスクレイピングして自動車学校の技能教習の予約が空いたら通知するスクリプトを書いてみました.

この記事ではどのようにコードを書いたのか (主に GoogleChrome/puppeteer 周り) を知見を含めて紹介します.

スクレイピング

Headless ChromeAPI を wrap したライブラリである GoogleChrome/puppeteer を使ってスクレイピングしました.

スクレイピングする対象のサービスについて説明しておくと, トップのログインフォームでform認証するとメニューに飛び, そこから技能教習予約ページと予約キャンセルページに飛べる仕様となっています.

# 例
reservation.car.com ... formによるログイン認証ページ
  /menu             ... メニュー
  /reservation      ... 技能教習予約ページ
  /cancel           ... 予約キャンセルページ

セットアップ

スクレイピングを始める前に, まずはpuppeteerのセットアップをします.

const setup = async () => {
  const browser = await puppeteer.launch()    // HeadlessモードでChromeを起動
  const page    = await browser.newPage()     // 新しいタブを開く
  page.setViewport({width: 900, height: 800}) // Viewportの大きさを指定
  return {browser, page}
}

const scrape = async () => {
  const {browser, page} = await setup()
  
  // スクレイピング

  await browser.close() // Chromeを終了する
}

// 開発時は unhandledRejection を subscribe する
process.on('unhandledRejection', (e) => console.log(e))

scrape()

puppeteer のAPIの多くは Promise を返すので async/await を使うと楽に書けます. また, puppeteer内部で発生した unhandledRejection はそのままだとエラーの詳細が出力されないため, 開発時は subscribe しておくと良いでしょう.*1

ちなみに puppeteer.launch() にオプションを渡すと Headless モードをオフにして Chrome を起動したり, ブラウザの操作を一定間隔空けて実行することができます. これによって実際の挙動を画面で, 目で追いやすい速度で確認することができます.

const browser = await puppeteer.launch({
    headless: false, // 画面を表示
    slowMo: 500      // 500ms間隔でブラウザを操作
})

f:id:mizdra:20171001193642g:plain

form認証

page.$eval でクエリにマッチした要素をコールバック関数で受け取ることができるので, これを使ってフォームの値を書き換えます. 値を入力し終えたら page.click を用いて送信ボタンをクリックし, page.waitForNavigation で遷移後のページで load イベントが発火するまで待機します.

const login = async (page) => {
  // 予約サービスに移動
  await page.goto('http://reservation.car.com')

  // Node を取得し, フォームの値を書き換える
  await page.$eval('input[name="username"]', (el) => { el.value = 'mizdra' })
  await page.$eval('input[name="pass"]', (el) => { el.value = 'password' })

  // submit ボタンをクリック
  await page.click('input[type="submit"]')

  // ページの遷移が完了するまで待機
  await page.waitForNavigation({waitUntil: 'load'})
}

予約に空きのある時間帯の取得

page.evaluate は引数で渡した関数をブラウザ上で実行するため, DOM APIにアクセスすることができます. 以下では page.evaluate を使って予約に空きのある時間帯を取得しつつ, 予約ページのスクリーンショットを作成しています.

const screenshot = async (page) => {
  // ページの読み込みが終わるまで待機
  await page.waitForNavigation({waitUntil: 'load'})
  await page.screenshot({path: `screenshots/${Date.now()}.png`})
}

const getFreeClassList = async (page, classList) => {
  const freeClassList = await page.evaluate(() => {
    const buttons = Array.from(document.querySelectorAll('button.free'))
    // value プロパティから日付 (ex. '2017/09/28 16時') を取り出す
    return buttons.map(button => button.value)
  })
  return freeClassList
}

const filterClassListByFree = async (page) => {
  await gotoReservation() // 技能教習予約ページに移動
  await screenshot(page)  // 予約状況をスクリーンショットする

  // 予約に空きのあるクラスのリストを取得
  const freeClassList = await getFreeClassList(page, classList)

  await gotoMenu(page) // メニューに戻る

  // 予約に空きのあるクラスのみを返す
  return classList.filter(cls => freeClassList.includes(cls))
}

ここで注意ですが, デバッグのために page.evaluate に渡した関数の中で console.log を呼び出してもログはブラウザのコンソールに表示されるだけで, Node.js のコンソールには何も出力されません.*2

await page.evaluate(() => {
   // ブラウザのコンソールには出力されるが, Node.js のコンソールには出力されない
  console.log('Hello world!')
})

通常ブラウザのコンソールにログを出力することは無いので次のように console イベントを subscribe して警告を出すようにしておくと良いでしょう.

page.on('console', (...args) => {
  console.warn('Warning: Console API methods is called in browser context.')  
  console.log(...args)
})
await page.evaluate(() => {
  // ブラウザと Node.js の両方のコンソールに出力
  console.log('Hello world!')
})

通知

取得した時間帯を nodemailer/nodemailer を使ってメールで送信します. 今回はメールアカウントに Gmail を使い, SMTP で送信します.

import nodemailer from 'nodemailer'

// 本文のレンダリング関数
const renderHTML = (freeClassList) => `
<p>次の時間帯の予約が空きました. <a href="http://reservation.car.com">ここをクリック</a>して予約を完了して下さい.</p>
<ul>${freeClassList.map(cls => `<li>${cls}</li>`).join('')}</ul>
`

// 予約の空いたクラスをメールで通知する
const sendMail = (freeClassList) => {
  const transporter = nodemailer.createTransport({
    host: 'smtp.gmail.com',
    port: 465,
    secure: true,
    auth: {
      user: '<Gmail アドレス>',
      pass: '<Google アカウントのパスワード>'
    }
  })
  transporter.sendMail({
    from: '"free-driving-class-notification-bot"',
    to: '<通知先のメールアドレス>',
    subject: '技能教習の予約が空きました',
    html: renderHTML(freeClassList)
  }, (err) => {
    if (err) return console.log(err)
  })
}

予約が開くと, 以下のようにメールが飛んできます. これにて完成です 🎉🎉🎉

f:id:mizdra:20171001184759j:plain

その他: 遭遇した問題

puppeteer を弄っているといくつか怪しい挙動を見つけたのでIssue&PRを出しました.

おわりに

*1:http://yosuke-furukawa.hatenablog.com/entry/2016/07/12/103734

*2:これのせいでn時間吸われた

*3:これのせいでn時間吸われた

Dockerを学んだ際の備忘録

概要

お盆の間にDockerについて勉強した序にDockerをどう学んでいったか (どの記事を読んだか, どういう流れで学んだか) を軽く纏めておきます.

目標

以前作ったWebアプリケーション (タイマー) をDockerに載せる.

学習の流れ

実際にDockerizeしてみる

DockerでのNodeアプリ構築で学んだこと | インフラ・ミドルウェア | POSTD で紹介されている手法をベースにDockerizeしてみました.

コンテナの起動

$ git clone https://github.com/mizdra/emtimer.git
$ git checkout 41d2e3e

# imagesのビルド
$ docker-compose build

# プロダクション用
## ソースコードをプロダクション向けにビルドしてhttp-serverでserveする
$ docker-compose -f docker-compose.prod.yml up

# 開発用
## webpack-dev-serverが立ち上がる
## ファイルの変更を検知したら再ビルド&自動リロードされる
$ docker-compose -f docker-compose.yml up

とりあえずやってみましたが設定ファイルを書く際に考えることが多いかなと感じました. まあでもキャッシュを活用しようとすると少し複雑になるのは仕方なさそう. ちょろっとDockerizeするだけならもっと設定ファイルをシンプルにしても良いかもしれません. あと気になったのは COPYUSER の影響を受けないので別途 RUN chown -R app:app $HOME/* する必要があるところ. ただ, この問題については既にPRが立っているのでその内改善されるかも.

ちなみにコンテナ起動時に CMD ["yarn", "run", "prod:start"] の代わりにshellを差し込めば対話的に好きなように作業できます. 便利.

# shellを差し込んで起動
$ docker-compose -f docker-compose.yml run emtimer bash

# 試しにpackageを追加してみる
app@XXXX:~/emtimer$ yarn add moment

# 追加されていることを確認
app@XXXX:~/emtimer$ ls -1 node_modules | grep moment
moment

app@XXXX:~/emtimer$ exit

# imageのnode_modulesはvolumeなのでホストOS上からは見えない
$ ls -1 node_modules | grep moment

# package.jsonやyarn.lockはbind mountsの機能により更新されているので
# yarn installで追加されたパッケージをインストールできる
$ yarn install
$ ls -1 node_modules | grep moment
moment

多くの場合, 開発時はdocker上で開発サーバを立ててホストOS上からエディタでソースコードを編集するような形を取ることになります. その際にホストOS上に node_modules の中身が存在しないとエディタのプラグイン(eslintなど)がエラーを吐くので, 適時ホストOS上でも yarn install すると良いと思います. 多分… *1

おわりに

ヨッシャDockerやるぞという気持ちが湧いてきたので1からDocker学んでみました. 2日くらい掛かりましたがそれなりに勉強になったので満足度高めでした.

おわり.

*1:知見が殆どないのでこれで合っているか分からず… もっと良い解決法あれば教えて頂けると :pray:

2016年を振り返って

"今年"は2016年, "来年"は2017年のことを指します.


去年同様, 今年も1年の振り返りをします. プログラミングの話題が中心ですが, 割りと雑です.

今年も年が明ける前に投稿出来なかった. 残念.

1月, 2月

Pro Gitを読みました. それ以前にもGitの入門資料に目を通し, 軽くgitに触れていたので(大変でしたが)6章まで読み切ることができました. Pro GitはGitの仕組みを紹介した上でコマンドの使い方等を解説しています. 自分は仕組みや内部の実装を知ってからツールを使うという学習方法が好きだったので, この資料は自分にとって非常に合っていました.

それとPokeSugarという「ポケモンを一行で表現する構文」を作りました. 作ったは良いけどまだどのプロジェクトでも使用していません. 機運があればこれを使って何か作ろうかな…

3月

VSCodeが1.0になったので本格的にVSCodeを使い始めました. VSCode便利ですよね. 僕は大好きです. 皆さんも使いましょう.

ポケモン関連では 反動ダメージに関する調査 を行いました. 端数処理にちょっとだけ詳しくなりました.

4月

某多摩の大学に入学し, そこでMMAという技術系サークルに入部します. 総合格闘技ではありません. この頃は忙しかったので特筆することはありません. 敢えて書くとしたら, MMAに入部したことで普通の大学生活を送ることが出来なくなった, ということでしょうか. 皆さんもサークル選びは気をつけましょう.

5月, 6月

大学のコンピュータ上に 開発環境を構築 していました. make やら ./configure やらをしてちょっとだけCLIに詳しくなりました. ビルドしんどい.

7月, 8月

Pokémon GOです. 大宮, 池袋, 新宿, 渋谷, 調布など色々な所に行きました. 人が沢山いる公園でプレイすると一体感があって中々楽しかったです. 今の公園は最盛期と比べると寂しいですね. でも, 何だかんだ言って続けてますが.

8月は西日本に住んでいるフォロワーさん2人とお会いしてきました. 貴重な体験でした.

9月

この頃は色々あって疲れていて少し精神が不安定でした. そのせいか mizdra/shiny-text という訳のわからないアプリケーションを作るなどしていました.

なんもわからん.

10月, 11月

Dentoo.LT #15, Dentoo.LT #15.5 に参加しました. Dentoo.LT #15.5では登壇して発表をしました. この辺のことは MMAに入って1年でやったこと - mizdra's blog にまとめてあります.

www.slideshare.net

12月

Advent Calendarの月です. 今まで僕はAdvent Calendarを見るだけで参加すらしたことが無かったのですが, 今年は MMA Advent Calendar というものに寄稿させてもらいました.

12月後半はReact+Reduxでツールを作っていました. これは現在も製作中で, 暫くしたら公開できるかと思います. どうぞお楽しみに.

GitHubを振り返る

去年

f:id:mizdra:20160103192801p:plain

今年

f:id:mizdra:20170101003302p:plain

良い感じに芝を生やせました. この調子で来年も頑張っていきます.

おわりに

今年も色々なことを学びました. 2016年で個人的に最も大きかった出来事は大学への入学, そして技術系サークルのMMAへの入部です. 技術系の人々が多くいる環境に身を置くと何もしなくても(聞いたり読んだりはしているが)様々な情報が入ってくるというのは本当に良いですね. 先程も書きましたが, サークル関係でやったことは MMAに入って1年でやったこと - mizdra's blog にまとめてあります.

プログラミング以外で自分が成長したと感じたこととして, メンタル面があります. 今年に入ってエンジニアとしての心持ち, 精神論, 考え方など, メンタルに関する記事を読むようになりました. 去年まではあまり興味は無かったのですが, 技術系の人々が多くいる環境にいるために, そうした知識はどうしても必要になってきます.

来年はReact+Redux, Go*1, ソフトウェアのテストについて学びたいですね. やっていきましょう.

*1:去年もGoやりたいって言ってた気がする…

Pokémon RNG Advent Calendar の主催をした話

Pokémon RNG Advent Calendar 2016 24日目の記事です.

www.adventar.org

今日は一部の界隈ではクリスマス・イブと呼ばれる日だそうですが, 僕はゆゆ式のニコ生一挙放送が行われる日だと認識しています. この記事も画面左半分でゆゆ式を視聴しながら書いている訳です.

さて, 駄文はこれくらいにしてタイトル通り「Pokémon RNG Advent Calendar の主催をした話」について書いていこうと思います.

先に断っておきますが, これはポエムです.

開催をしようとおもったきっかけ

そもそも僕がPokémon RNG Advent Calendarを開催しようと思ったのは以下のツイートを見たのがきっかけです.

当時僕がこのツイートを見た時, 「乱数調整Advent Calendarか. 需要あるのかな〜?」などと考えていた気がします. その時点では開催するつもりは無かったのですが, 後ほど述べる諸所の理由からAdvent Calendarを開催することを決めました.

背景

ここで, 僕が今まで乱数調整にどう関わってきたかについて書いていこうと思います. 僕が乱数調整に初めて関わったのが2010年くらいで, PHSを使ってHGSSで色乱数(調整)をしていました . その後暫くHGSSやBW1, BW2で高個体値ポケモンを生成したり, 色違いポケモンを生成したりと色々な乱数調整をしていました.

時は2013年, 当時BWのレーティングバトルでは地球投げを覚えた輝石ラッキーが人気でした. ラッキーに地球投げを覚えさせるには第三世代の教え技を利用するしかないのですが, 僕はEmを持っていなかったので, FRLGを使って乱数調整をするしかありませんでした. 補足ですが, Emでは乱数の仕組みがほぼ解析され, 乱数調整が容易に出来ることが判明しています. 一方で, FRLGは初期Seedの決定方法が解明されていないため(再現性はとれますが, 計算式が不明 *1 ), 乱数調整を行うのは少々困難です. その為, FRLG乱数調整界隈の人口は非常に少なく, FRLG用の乱数調整補助ツールも殆ど出回っていないので, 既存のツールでは出来ない乱数調整をするにはツールを自作する必要がありました. 幸いなことに, 僕はそれ以前からプログラミングを学習しており, 簡単なプログラムを作成する程度のスキルを持っていたので, ネット上の解析情報を頼りにFRLGの乱数調整を作成することができました.

その後も乱数調整補助ツールを幾つか作るなどして今に至るわけですが, 「この乱数調整補助ツール」というのは小さな界隈でしか通じないキーワードで, 非常に奥が深く, 乱数調整という言葉を知らない人に僕がやっていることを上手く伝えられないという問題がありました. 乱数調整をいう言葉を一言で説明するのは非常に難しく, 乱数の種類や乱数生成法, ゲーム内でどう乱数が扱われているかという乱数調整の原理を全て押さえてから, 乱数調整について説明しなければなりません. しかし, 驚くべきことに乱数調整をこうした原理を含めて解説されている (日本語の) 記事は今までこの世の中に1つも無かったのです. こうした背景から, 乱数調整という言葉を初めて聞いた人向けに乱数調整について解説する「乱数調整 入門」を書こうと思い, Pokémon RNG Advent Calendarの開催を決意した訳です.

http://mizdra.hatenablog.com/entry/2016/12/01/235954mizdra.hatenablog.com

勿論, Pokémon RNG Advent Calendarを開催しようと思った理由に「乱数調整の楽しさについて純粋に皆に知って欲しいから」というのもあります. ですから, 1日目の記事は乱数調整の原理も説明しつつ, 乱数調整の楽しさも同時に伝えるということを意識して書いています.

開催した後の話

初め僕は「マイナーな分野だしどうせ全日埋まらないだろうなあ」という気持ちでこのAdvent Calendarを開催したのですが, なんと12/24の時点で全日埋まっております. めでたい!!! 🎉🎉🎉

SM乱数調整が盛り上がったおかげですね. ありがたい 🙏

また, 僕がLTイベントで登壇した際にこのAdvent Calendarの宣伝をさせて頂きました. LTイベントでは「乱数調整と謎乱数の話」, 「乱数調整 入門」について話しました.

http://www.slideshare.net/mizdra/ss-70392889www.slideshare.net

http://www.slideshare.net/mizdra/ss-70230261www.slideshare.net

イベントに同席された方に話を伺うと, 「乱数調整に興味はあるが, やり方がよく分からない」「最新作での乱数調整はできるのか」などの質問を頂くことがあり, それなりにこういう話をする需要はあるようです.

今後LTで話す乱数調整ネタですが, SM乱数調整関連で何か話せればいいなあ程度に考えています. お楽しみに.

おわりに

書いた文章を読み返すと, 文脈もクソもないし, 結論もアレで色々終わっています. まさにポエムという感じですね.

まあ, これはポエムだと一番初めに宣言しているので許されたい.

おわり.

明日は @quan_dra さんが担当です!

*1:当時は全くの不明でしたが, 現在ではある程度解析されています

続ウツギ博士乱数

Pokémon RNG Advent Calendar 12日目の記事です.

www.adventar.org

昨日の11日目は水連さんの http://suiren1350.blog.fc2.com/blog-entry-125.html でした. FRLGの初期Seedに関する日本語の情報がまとまったサイトが無かったので, 非常にありがたいです.

http://suiren1350.blog.fc2.com/blog-entry-125.htmlsuiren1350.blog.fc2.com

概要

1日目の「乱数調整 入門」で扱ったウツギ博士乱数の続きです. (1日目の記事は乱数調整という言葉を初めて聞いた人にも分かるように頑張って書いたので皆読んでね!!)

http://mizdra.hatenablog.com/entry/2016/12/01/235954mizdra.hatenablog.com

「乱数調整 入門」では

  • ウツギ博士が「ポケモンの しんかというのは~」というメッセージを15回以上連続で話す

ことを乱数調整しましたが, 折角なので同一メッセージの最大連続数及びその時の乱数 S[n], 初期Seed S[0] を求めておきましょう.

乱数を求める

という訳で乱数をプログラムを書きました. 前回の記事で使ったプログラムを再利用すればサクッと出来ます.

gist.github.com

実行

実行するぞ.

# 🙏 不慮の事故に備えてコンパイルする前に祈る 🙏
$ g++ AbuseMaxChainDrUtsugi.cpp -o AbuseMaxChainDrUtsugi.exe -lm -std=c++11
$ ./AbuseMaxChainDrUtsugi.exe
    r[n] % 3   max chain        S[n]
           0          20  0X97024D60
           1          19   0X15A9302
           2          19  0XD62B4730

結果から「ポケモンの しんかというのは~」は最大20連続, 「カントーには まだ ぼくの〜」「ポケルスが くっついた~」は最大19連続であることがわかります.

初期Seedを求める

乱数からそれを狙うことができる初期Seedを求めましょう. これも前回のプログラムを再利用してサクッと書き上げます *1.

gist.github.com

frameは800 ~ 10000F(約3分)に収まるようにしました.

# 🙏🙏🙏 祈る 🙏🙏🙏
$ g++ CalcInitialSeed.cpp -o CalcInitialSeed.exe -lm -std=c++11
$ ./CalcInitialSeed.exe
    n        S[n]        S[0]
   27  0X97024D60  0X961424C7
  184   0X15A9302  0XC60C148A
   88  0XD62B4730  0X4C1514D8

結果

結果を表にまとめます.

r[n] % 3 メッセージ max chain n S[n] S[0]
0 ポケモンの しんかというのは~ 20 27 0x97024D60 0x961424C7
1 カントーには まだ ぼくの〜 19 184 0x15A9302 0xC60C148A
2 ポケルスが くっついた~ 19 88 0xD62B4730 0x4C1514D8

r[n] % 3 == 0 で最大20連続. ちなみに初期Seedは 乱数逆算ツール を使うと自動で求めることが出来ます. 便利〜.

ツールの使い方は以下を参考に.

http://d.hatena.ne.jp/milk4724/20140804/1407121138d.hatena.ne.jp

乱数調整する際の問題点

今回の問題点は消費数が多すぎるものがあることです (184消費とか). 一般的に使われる消費法で消費数が多いものは「連れ歩いている眠り状態のポケモンに話しかける(消費数: 4)」ですが, この消費法を用いても40回以上は眠り状態のポケモンに話しかけなければなりません. しんどいですね.

そこで, オーキドはかせのポケモンこうざを用いた消費法をお勧めします. この消費法であれば毎秒30消費以上を実現することができます *2. 便利〜.

こちらもツールの使い方は以下の外部サイトを参考に. ameblo.jp

ちなみに僕はウツギ博士に電話をするのに飽きたので乱数調整はしません *3. 誰か代わりにやって動画を上げてくれると @mizdra が喜びます.

おわり.

最後に

Pokémon RNG Advent Calendar 2016が全日埋まりそうでで嬉しさと驚きを感じています. 非常にマイナーな分野でそれに関わる人も少ないので全日埋まるとは全く想像もしていなかった…

それとつい先程, 最新作のSMの孵化乱数調整が実機で可能になったようです. めでたい 🎉🎉🎉

これに関する詳細な記事は後ほど公開されることでしょう. 楽しみですね.

明日は oupo さん担当の「TinyMTの逆算」です.

*1:任意の乱数から初期Seedを求めるライブラリ無かったので手書きする羽目になった. 誰か作ってくれ.

*2:ちなみにこれは自分の図鑑のコンプ状況をツールに入力しなければならないのでそこだけは面倒です. 一度ツールに入力してしまえば後はそれを再利用することが可能なのでやっておくと良いと思います.

*3:電話掛けてる時よりプログラム書いてる時のほうが5000兆倍楽しい

乱数調整 入門

はじめに

この記事はPokémon RNG Advent Calendar 2016 一日目の記事です🎉🎉🎉

www.adventar.org

この記事では主にコンピュータゲームで話題になっている乱数調整について, ポケモンを例に説明します. 以下の方々を対象にした入門的な記事になっています.

  • 乱数調整という言葉を初めて聞いた人
  • 乱数調整という言葉を聞いたことはあるが, その意味や具体的な手順を知らない人
  • 乱数調整をやってみたいと思っているが, 手を出せずにいる人

ポケモンのゲームシステム, 16進数表記, ビット演算を知っている/理解できる前提で書いています. ご了承下さい.

わかりやすさを優先して一部現実と異なる事が書かれている場合があります. 誤字や重大な間違いを発見した場合は修正しますのでこの記事のコメントもしくはTwitterのリプライにてお気軽にご連絡下さい.

「乱数調整」とは

記事のタイトルを見て, 「乱数調整って何だろう?」と疑問に思った方もいるでしょう. 本記事では「乱数調整」の仕組みを理解し, 実際に乱数調整を行ってみることを想定したものとなっています. 「乱数調整」について説明する前に「乱数」について軽く学習しましょう.

「乱数」とは

「乱数」とはサイコロの出目のように規則性がなく予測不能な数のことです. サイコロを例にとって考えてみます. サイコロを数回振ると, 1, 3, 4, 3, 2, 6, 1, ... というように1~6の出目を得ることができます. 一般に, サイコロの出目には規則性が無く, 得られる値はでたらめに並んでいます. このように, でたらめに並んだ数列を「乱数列」と言い, その数列の元を「乱数」と言います*1. 乱数列はそれ以前の数から次の数を予測することが出来ないという特徴があります.

1回目 2回目 3回目 4回目 5回目 6回目
2 4 6 4 4 1
7回目 8回目 9回目 10回目 11回目 12回目
3 4 5 5 6 ?

※ サイコロを11回振った時の出目を表にしました*2. 1 ~ 11回目の出目に規則性が無く, 12回目の出目を予測することは出来ません.

乱数の種類

ここで, コンピュータで乱数を扱う方法を紹介します. 詳しい説明は省きますが *3, コンピュータは確定的な計算を用いてしか乱数を生成することが出来ません. 以下にコンピュータ上での具体的な乱数の計算例を示します.

S[0] = 0
S[n+1] = (3 * S[n] + 5) % 7
S = { 5, 6, 2, 4, 3, 0, 5, 6, 2, 4, 3, 0, 5, 6, 2, 4, 3, 0, 5, 6, ... }

この乱数生成法は「線形合同法(LCG)」と呼ばれ, 漸化式を用いた簡易的な乱数生成法の1つです*4.

線形合同法は適当な初期値からスタートして、次の漸化式で乱数を生成します。


X[i] = (A * X[i-1] + C) mod M


これで 0 以上 M 未満の値を生成することができます。A, C, M の選び方は 参考文献 [2] より引用します。


M が 2 の累乗なら A mod 8 を 5 または 3 とし(5 の方が安全)、定数項 C を奇数とする。このとき、周期はちょうど M となり、その 1 周期分には 0 から M - 1 までの整数が 1 個ずつ現れる。


高速にするには C = 0 とし、初期値 X0 を奇数にする。ただし、周期は M / 4 になる。


サービス終了のお知らせ

S[0] は乱数列の初項で, 初期Seed, もしくはSeedと呼ばれます. 乱数列は初期Seedと漸化式を用いて逐次的に求められます. このように確定的な計算によって得た乱数を擬似乱数*5, 対してサイコロのような確定的な計算以外で得た乱数を自然乱数と区別して呼びます. 擬似乱数生成法には漸化式以外を用いたものもありますが, 漸化式を用いた擬似乱数生成法には以下のようなメリットがあり, コンピュータと相性が良いのでこの方法が主流です.

  • 漸化式と初期Seedを記録しておけば、誰でも同じ数列を再現できる (注: ある乱数列を再現するために全ての項を記録しておく必要がない)
  • (コンピュータ上での計算が)高速で低コスト


あなたの使っている乱数、大丈夫?-危ない標準乱数と、メルセンヌ・ツイスター開発秘話-

初期Seedは乱数列を決定する重要な要素の1つで, 出来るだけ乱雑な値になるように設定されます. 通常は乱数生成器を起動した時刻などを元に設定されます.

擬似乱数列は確定的な計算によって生成されているので, それを構成する各乱数の値を算定することが可能です. 事前に擬似乱数列を構成する個々の乱数を求めることができれば, それらを基に決定される事象を予測し, 狙った事象を発生させることも可能となります.

改めて「乱数調整」とは

乱数を軽く理解できたところで改めて乱数調整の意味について説明します. 乱数調整とは「 次に得られる乱数を予測し, 狙った乱数を得ること 」を指します. 先程のサイコロの例で考えるならば, 何かしらの手法で12回目のサイコロの出目を事前に予測し, 実際に予測した出目を得ること, これが乱数調整です.

実際にはサイコロの出目のような自然乱数を予測することは神でもない限り出来ないので, 算定可能な擬似乱数を対象に乱数調整が行われます.

乱数調整は「 事象を予測し, 狙った事象を発生させること 」といったようにもっと広義的に捉えられる場合もあります*6. これは調整する対象を乱数から事象(将来発生する事象)に拡張しただけです. これは調整する対象をミクロな見方をするか/マクロな見方をするかの違いです. この記事ではポケモンにおける乱数調整で一般に利用されている後者の意味で乱数調整を捉えます.

ポケモンで使用される乱数生成法

乱数調整の言葉の理解が深まったところでポケモンでどのように乱数が扱われているかを見ていきましょう!

使用するハードウェア, ソフトごとに乱数生成法が異なりますが, 今回は DS Lite + ポケットモンスター ハートゴールド(HG)を使用します. ポケモンシリーズで使用される乱数生成法の多くはネットのえらい人たちが既に調べてくれているのでわざわざ自分で調査する必要はありません. えらい人たちに感謝 🙏

HGでは以下で定義される線形合同法によって乱数が生成されています.

frame  := フレーム (0 < frame)
year   := 年 (2000 ≦ year ≦ 2099)
month  := 月 (1 ≦ month ≦ 12)
day    :=  日 (1 ≦ day ≦ 31)
hour   := 時間 (0 ≦ hour ≦ 23)
minute := 分 (0 ≦ minute ≦ 59)
second := 秒 (0 ≦ second ≦ 59)

S[0]   = (((month * day + minute + second) * 0x1000000)
         + (hour * 0x10000)
         + (frame + year - 2000)) % 0x100000000

S[n+1] = (0x41C64E6D * S[n] + 0x6073) % 0x100000000

年, 月, 時間, 分などの日時はゲームで「つづきからはじめる」を押した瞬間のハードに設定されている日時が用いられ, frame はDSを起動してソフトを選択し, ゲームが起動した瞬間から「つづきからはじめる」を押した瞬間までの時間をフレーム*7(単位の記号はF, 1秒≒60F)という単位に換算したものです. ただし実際には, DSのメニューからソフトを選択し, 起動するまでに350F(約5.8秒)のラグが発生 *8 しており, その時間は frame に含まれないことに注意して下さい. また, ゲームの仕様によりフレームは2ずつしか増加しません.

f:id:mizdra:20161206214959j:plain

例えば, 僕が今この文章を書いている2016年11月26日3時10分10秒*9にDSのメニューからゲームを選択し, 2016年11月26日3時10分40秒に「つづきからはじめる」を押したとすると, 初期Seedは以下のようになります .

frame  = (40s - 10s) * 60F - 350F = 1450
year   = 2016
month  = 11
day    = 26
hour   = 3
minute = 10
second = 40

S_0    = (((month * day + minute + second) * 0x1000000)
         + (hour * 0x10000)
         + (frame + year - 2000)) % 0x100000000
       = 0x500305BA

実擬似乱数列

LCGで生成される乱数にはとても不思議な特徴があります. ここでLCGの漸化式を見てみましょう.

S[n+1] = (0x41C64E6D * S[n] + 0x6073) % 0x100000000

0x41C64E6D, 0x6073 は両方とも奇数であることに注目して下さい.

  • 奇数*偶数+奇数 は奇数
  • 奇数*奇数+奇数 は偶数

より, なんとこの擬似乱数列は 偶数, 奇数, 偶数, 奇数, ... を繰り返してしまいます! 不思議ですね! これはよく知られたLCGの欠点の1つで, 最下位bitが 0, 1 を繰り返すというものです.

UNIX の /usr/ucb/cc を解析した結果、 rand( ) は以下の動作をすることがわかった。


static long x=1;

void srand(long s) { x=s; }

long rand() { x=x*1103515245+12345; return x&2147483647; }


これは、非常にシンプルな線形合同法である。 この乱数の最下位ビットは0と1の繰り返しになる。 すなわち、偶数と奇数が交互に生成される。 このことから、この乱数で下位ビットを乱数として使うのは危険であることがわかる。 また、ある乱数が得られたら、次に現れる乱数が1種類しかないという欠点も持つ。


良い乱数・悪い乱数

これを知らずにLCGで生成された生の乱数を使用すると大きな偏りが生じてしまうので *10 , ポケモンでは S[n] >>> 16 (>>> は0 埋め右シフト) を擬似乱数として使用しています. 本記事では説明のために実擬似乱数列 r[n] = S[n] >>> 16 と定義しておきます (様々な乱数調整関連の記事で r[n] という式自体は出てきますが, それに名前が付いてなくて不便なので勝手に僕が名付けました).

乱数の消費

ポケモンでは新しい乱数が生成されたとき, 「乱数が消費された」と言い, 乱数生成器で乱数が生成される度に乱数が消費されていきます. 乱数が2度生成された場合は2回乱数が消費され, 「乱数が2消費された」「2F消費された*11」といった表現が使われます.

以下に乱数を消費する事象の代表的な例を示します.

消費数 事象
1 ウツギ博士に電話を掛ける(通話内容の決定で乱数が消費される)
1 NPCが振り向く/移動する*12 (NPCが振り向く向き, 静止時間の決定)
1 録音した音声を鳴き声として設定したペラップの鳴き声を聞く(鳴き声を聞く度に音の高さが変わる, 音の高さの決定で乱数が消費される)
2 たんパンこぞう ゴロウに電話を掛ける(電話内容自体に疑似ランダムな要素はありませんが, 内部で乱数生成器によって2F消費されています)
4 連れ歩いている眠り状態のポケモンに話しかける(上と同じく疑似ランダムな要素はありませんが, 内部で乱数生成器によって4F消費されています)
徘徊ポケモンの数 以上 「つづきからはじめる」を押してゲームを再開する(徘徊ポケモン*13の出現ポイント決定に乱数が消費される, ゲーム再開前後で徘徊ポケモンの出現ポイントが同じになったとき出現ポイントの再決定が行われる, ゲーム再開時に自動的に消費が行われるので強制消費と呼ばれます)
手持ちポケモンの数 128歩歩く(128毎歩くたびに行われる手持ちポケモンのなつき度判定で乱数が消費される)
12以上 ラジオのチャンネルをオーキドはかせのポケモンこうざに切り替える(詳細: オーキドはかせのポケモンこうざ - oupoの日記)

これらの乱数消費法を用いることで, 自分の好きな回数だけ乱数を消費できます.

ウツギ博士との電話で乱数調整

「消費」という概念, 上の説明だけでは分かりにくいですね... 実際にウツギ博士に電話を掛けて消費がどんな操作であるのかを確認しましょう.

準備

ウツギ博士が電話で話す内容はストーリーの進行具合によって変わってくるのですが, 今回は以下の条件の元でやってみます.

  • 殿堂入り前にウツギ博士にトゲピーを見せ, 変わらずの石を貰っている
  • 殿堂入りをしている
  • ポケルスに感染した個体、もしくは感染した後完治した個体をポケモンセンターで回復させたことがある

この条件下では, ウツギ博士が電話で話す内容が3種類に固定され, 電話をする度に r[n] % 3 で対応するメッセージが決定されるようになります.

r[n] % 3 メッセージ(先頭のみ)
0 ポケモンの しんかというのは~
1 カントーには まだ ぼくの〜
2 ポケルスが くっついた~

今回は先程計算した初期Seed 0x500305BA で乱数調整を行います. 初期Seed 0x500305BA での n = 20 までの乱数列は以下のようになります.

n S[n] r[n] r[n] % 3 メッセージ(先頭のみ)
0 0x500305BA 0x5003 2 ポケルスが くっついた~
1 0xA4E47CA5 0xA4E4 2 ポケルスが くっついた~
2 0x1FE1B8B4 0x1FE1 1 カントーには まだ ぼくの〜
3 0xE89ADD17 0xE89A 2 ポケルスが くっついた~
4 0x1017853E 0x1017 0 ポケモンの しんかというのは~
5 0xD090FFD9 0xD090 1 カントーには まだ ぼくの〜
6 0xF6876DD8 0xF687 0 ポケモンの しんかというのは~
7 0x0831F56B 0x0831 0 ポケモンの しんかというのは~
8 0xB1CE7902 0xB1CE 2 ポケルスが くっついた~
9 0xB754824D 0xB754 0 ポケモンの しんかというのは~
10 0x233D513C 0x233D 0 ポケモンの しんかというのは~
11 0xC0443EFF 0xC044 2 ポケルスが くっついた~
12 0x207AE506 0x207A 1 カントーには まだ ぼくの〜
13 0xEEBFB801 0xEEBF 0 ポケモンの しんかというのは~
14 0xA37806E0 0xA378 1 カントーには まだ ぼくの〜
15 0x5D738DD3 0x5D73 1 カントーには まだ ぼくの〜
16 0x439C0D4A 0x439C 1 カントーには まだ ぼくの〜
17 0x66BA94F5 0x66BA 0 ポケモンの しんかというのは~
18 0x015272C4 0x0152 2 ポケルスが くっついた~
19 0x36AAF5E7 0x36AA 2 ポケルスが くっついた~
20 0x356175CE 0x3561 0 ポケモンの しんかというのは~

ポケモンでは S[0] は疑似ランダムな事象を決定するためには用いられず, S[1] 以降が用いられます. つまり, ゲームを再開して初めて生成される乱数は S[1] となります. また, 僕が乱数調整を行うHGは徘徊ポケモンが3匹いるので, ゲーム再開時に自動的に3~6F消費されます. よって初めにウツギ博士に電話を掛けるときに使われる乱数は S[4], S[5], S[6] のどれかです.

乱数調整する際は出来るだけ不定NPC (先程の消費の例で挙げた乱数を消費するNPCのこと) がいない所でやると良いでしょう. 自分が消費している最中にNPCに勝手に消費されてしまっても困りますからね. 不定NPCがいない所であればどこでも良いです. 僕は主人公の部屋でやることにしました. ここで一度セーブしてハードの電源を切りましょう.

f:id:mizdra:20161204182703j:plain

トロフィーコンプしてないことがバレてしまう…

初期Seedを合わせる

初期Seed 0x5003048F に合わせるには2016年11月26日3時10分10秒にDSのメニューからゲームを選択し, 2016年11月26日3時10分40秒に「つづきからはじめる」を押す, でしたね. 以下の手順で初期Seedを合わせましょう.

  1. エメタイマーで30秒間のタイマーをセットする
  2. ハードの電源を入れ, 本体設定パネル > 日付&時刻パネル > 日付パネル 及び 本体設定パネル > 日付&時刻パネル > 時刻パネル からハードの日時・時刻を2016年11月26日3時10分0秒に設定する
  3. 直ぐにハードを再起動し, 上画面の時計の針が10秒を指した直後にHGを選択する. 同時にエメタイマーを起動する.
  4. 「つづきからはじめる」ボタンの画面で待機し, エメタイマーのカウントが0になったらボタンを押す
  5. ゲームが再開したらポケギアのマップを開き, 徘徊ポケモンの位置から初期Seedを特定する
  6. 初期Seedが目標のものと異なっていたらずれを確認し, タイマーにずれを反映させて 2 に戻る. 一致すればそこで終了.

「エメタイマー」, 「徘徊ポケモンの位置」のあたりを解説します.

1. エメタイマーで30秒間のタイマーをセットする

ポケモンでは frame を合わせるためにエメタイマーと呼ばれるタイマーをよく使います *14. これは乱数調整用に開発されたタイマーでフレーム⇛秒変換機能など乱数調整を補助する様々な機能が付いています.

使い方はサイトの下部にある説明を読めばわかります. 今回は30秒間待機したいので「時間」欄に「30」と入力しました.

f:id:mizdra:20161204190438p:plain

5. ゲームが再開したらポケギアのマップを開き, 徘徊ポケモンの位置から初期Seedを特定する

「乱数の消費」の項でも取り上げましたが, 徘徊ポケモンの位置の決定には S[1] 以降の任意の個数の乱数が使われます (徘徊ポケモンが3匹の場合). これを利用して徘徊ポケモンの位置からどの初期Seedを引いたのかある程度絞ることが出来ます. 「HGSS徘徊 初期seed確認」というツールを使ってみましょう.

セーブ直前の位置

セーブした直前での徘徊ポケモンの位置を <ライコウの位置>,<エンテイの位置>,<ラティアスの位置> の形式で入力します. 位置は 29ばんどうろ だったら 29, 21ばんすいどう だったら 21 といったように数値で指定します. 今回は 32ばんどうろ,29ばんどうろ,5ばんどうろ だったので 32,29,5 と入力しました.

ロード後の位置

ソフトを再開したときの徘徊ポケモンの位置を入力します. 形式は「セーブ直前の位置」欄と同じです.

目標初期seed

合わせたい初期Seedを入力します. 今回は 0x500305BA と入力しました.

下4桁の範囲

徘徊ポケモンの位置を検索するの初期Seedの範囲を, 初期Seedの下4桁の範囲で決めます. 「つづきからはじめる」を押した日時・時刻が固定であれば初期Seedの上位4桁は固定なので, 基本的に下4桁のみが変動します. 今回は 0x500 ~ 0x5D0 と入力しました.


実際にこの手順でやると, ゲーム再開後の徘徊ポケモンの位置は 29,45,3 となりました. これを先程の「ロード後の位置」に入力して「計算」ボタンを押すと以下の出力が得られました.

f:id:mizdra:20161204203327p:plain

これより, 目標の初期Seed 0x500305BA に対して 0x500305A0 を引いていたことがわかります. 下4桁の差を取ると 0x05BA - 0x05A0 = 26 となり, 待機時間が 26F 短かったことになります. ちなみに括弧の中に表示されている数はその初期Seedでの強制消費数を表しています. つまり, 目標の初期Seed 0x500305BA での強制消費数は3F, 初めにウツギ博士に電話を掛けるときに使われる乱数は S[4] となります.

次に待機時間のずれをタイマー側にも反映させましょう. 30秒間の待機時間を 26F だけ延長したいので 30s * 60 + 26F = 1826F1826F を待機時間として設定すれば良いことになります.

f:id:mizdra:20161204204323p:plain

この設定でもう一度 2~5 の操作をやってみたところ, 見事徘徊ポケモンの位置が 33,30,22 となり, 初期Seedを 0x500305BA に合わせることに成功しました!

めでたい! 🎉🎉🎉

f:id:mizdra:20161204210441j:plain

今回は2回目のチャレンジで初期Seedを合わせることに成功しましたが, 1/60秒間隔での操作が要求されるので割りとシビアです. いつかは成功するのでめげずに何度も挑戦してみましょう.

ここまでの操作をおさらいします.

  1. エメタイマーで30秒間のタイマーをセットする
  2. ハードの電源を入れ, 本体設定パネル > 日付&時刻パネル > 日付パネル 及び 本体設定パネル > 日付&時刻パネル > 時刻パネル からハードの日時・時刻を2016年11月26日3時10分0秒に設定する
  3. 直ぐにハードを再起動し, 上画面の時計の針が10秒を指した直後にHGを選択する. 同時にエメタイマーを起動する.
  4. 「つづきからはじめる」ボタンの画面で待機し, エメタイマーのカウントが0になったらボタンを押す
  5. ゲームが再開したらポケギアのマップを開き, 徘徊ポケモンの位置から初期Seedを特定する
  6. 初期Seedが目標のものと異なっていたらずれを確認し, タイマーにずれを反映させて 2 に戻る. 一致すればそこで終了.

乱数を消費する

初期Seedを合わせ終わったので後は乱数を消費するだけです. 17回電話を掛けた場合 *15, 上で計算した乱数列から次の順番でメッセージが流れるはずです.

n S[n] r[n] r[n] % 3 メッセージ(先頭のみ)
4 0x1017853E 0x1017 0 ポケモンの しんかというのは~
5 0xD090FFD9 0xD090 1 カントーには まだ ぼくの〜
6 0xF6876DD8 0xF687 0 ポケモンの しんかというのは~
7 0x0831F56B 0x0831 0 ポケモンの しんかというのは~
8 0xB1CE7902 0xB1CE 2 ポケルスが くっついた~
9 0xB754824D 0xB754 0 ポケモンの しんかというのは~
10 0x233D513C 0x233D 0 ポケモンの しんかというのは~
11 0xC0443EFF 0xC044 2 ポケルスが くっついた~
12 0x207AE506 0x207A 1 カントーには まだ ぼくの〜
13 0xEEBFB801 0xEEBF 0 ポケモンの しんかというのは~
14 0xA37806E0 0xA378 1 カントーには まだ ぼくの〜
15 0x5D738DD3 0x5D73 1 カントーには まだ ぼくの〜
16 0x439C0D4A 0x439C 1 カントーには まだ ぼくの〜
17 0x66BA94F5 0x66BA 0 ポケモンの しんかというのは~
18 0x015272C4 0x0152 2 ポケルスが くっついた~
19 0x36AAF5E7 0x36AA 2 ポケルスが くっついた~
20 0x356175CE 0x3561 0 ポケモンの しんかというのは~

動画を取りました.


ウツギ博士乱数

確かに上の表の通りにメッセージが流れています. 乱数調整成功です. 👏👏

ウツギ博士との電話で乱数調整 その2

ただ予想通りにメッセージが流れただけではつまらないのでもう少し高度なことをしてみましょう. 今度は,

  • ウツギ博士が「ポケモンの しんかというのは~」というメッセージを15回以上連続で話す

ことを乱数調整してみます. ウツギ博士に進化について熱く語ってもらいましょう.

乱数と初期Seedの計算

まず目的のメッセージを15回以上連続で話す乱数 S[n], 及びその初期Seedを計算しなければなりませんね. 本来であれば既存のツールを使って計算…といきたいところですかこんなどうでもいい乱数調整のためのツールなんかあるはずがありません. ではどうするか. 無いなら作ってしまいば良いのですよ. ✨😉☝️

という訳でサクッとC++で目的の乱数と初期Seed, 必要な消費数を出力するプログラムを書きました. プログラミング チョットデキルな皆さんなら当然書けますよね???

ウツギ博士にポケモンの進化について熱く語ってもらう.

# ソースコードをclone
$ git clone https://gist.github.com/faf3c48f67d999fc8072795e6c67a901.git abuse-dr-utsugi

# コンパイル
$ cd abuse-dr-utsugi
$ g++ AbuseDrUtsugi.cpp -o AbuseDrUtsugi.exe -std=c++11

# 実行
$ ./AbuseDrUtsugi.exe
  n        S[n]        S[0]
 10  0X23E5E254  0XD2160812
  5  0X2A991401  0X89070502
  9  0X3B4F3C64  0X96010A6D
  4  0X72038E62  0X4114049E
  5  0X7B215E11  0XE8040852
  6  0X810C32E0  0X89070502
  5  0XA2B1DC2D  0X4114049E

いくつか初期Seedの候補か出力されましたが, ここでは初期Seed 0x89070502 を狙います. 先程と同様に手計算で起動時刻を求めました.

  • DSメニューからゲームを選択: 2099年5月20日7時2分10秒
  • 待機時間: 1533F
  • 「つづきからはじめる」を選択: 2099年5月20日7時2分35秒

初期Seedを合わせる

エメタイマーは以下のように設定しておきます.

f:id:mizdra:20161205191833p:plain

僕の場合はセーブ直前での徘徊ポケモンの位置が 32,29,5 だったので「HGSS徘徊 初期seed確認」には以下のように設定しました.

f:id:mizdra:20161205193404p:plain

1回目

徘徊ポケモンの位置は 30,31,12 でした.

f:id:mizdra:20161205194342p:plain

0x502 - 0x4ee = 20 より, 待機時間を 20F 延長し, エメタイマーに 1553F を設定しました.

f:id:mizdra:20161205193712p:plain

2回目

徘徊ポケモンの位置は 30,32,9 でした.

f:id:mizdra:20161205194006p:plain

おや…? 徘徊ポケモンの位置を正しく入力したのに初期Seed候補が出力されませんね. ここで左下にある「秒のずれを許容」にチェックを入れて秒や分のずれを考慮した初期Seed候補検索をしてみましょう.

f:id:mizdra:20161205194559p:plain

初期Seed候補 0x8a070500 が出力されました. ここで, 0x8a - 0x89 = 1 より, 「つづきからはじめる」を押した時刻が1秒遅れたと推測されます. エメタイマーの設定はそのままにして, DSメニューからゲームを選択する時刻を1秒早めて(2099年5月20日7時2分9秒)みます.

3回目

徘徊ポケモンの位置は 29,35,2 でした. 成功です 👍

乱数を消費する

強制消費数は3Fなので, 初めにウツギ博士に電話を掛けるときに使われる乱数は S[4] となります. 乱数列を計算すると, 以下のようになっています.

n S[n] r[n] r[n] % 3 メッセージ(先頭のみ)
0 0x89070502 0x8907 0 ポケモンの しんかというのは~
1 0xDD101E4D 0xDD10 0 ポケモンの しんかというのは~
2 0x05A6BD3C 0x05A6 0 ポケモンの しんかというのは~
3 0xCE0F3AFF 0xCE0F 2 ポケルスが くっついた~
4 0xC0B03106 0xC0B0 2 ポケルスが くっついた~
5 0x2A991401 0x2A99 0 ポケモンの しんかというのは~
6 0x810C32E0 0x810C 0 ポケモンの しんかというのは~
7 0xE2F249D3 0xE2F2 0 ポケモンの しんかというのは~
8 0x1FDA194A 0x1FDA 0 ポケモンの しんかというのは~
9 0x5CCDB0F5 0x5CCD 0 ポケモンの しんかというのは~
10 0x41FD5EC4 0x41FD 0 ポケモンの しんかというのは~
11 0x5B5971E7 0x5B59 0 ポケモンの しんかというのは~
12 0xE57441CE 0xE574 0 ポケモンの しんかというのは~
13 0x51E12929 0x51E1 0 ポケモンの しんかというのは~
14 0xB61F64E8 0xB61F 0 ポケモンの しんかというのは~
15 0x0F8D073B 0x0F8D 0 ポケモンの しんかというのは~
16 0x29E26E92 0x29E2 0 ポケモンの しんかというのは~
17 0x6A05F09D 0x6A05 0 ポケモンの しんかというのは~
18 0xEA45A94C 0xEA45 0 ポケモンの しんかというのは~
19 0x36069DCF 0x3606 0 ポケモンの しんかというのは~
20 0xA200A396 0xA200 0 ポケモンの しんかというのは~
21 0xC821BB51 0xC821 2 ポケルスが くっついた~

よって16連続でウツギ博士がポケモンの進化についての話をしてくれます. 熱いですね.

動画を取りました.


ウツギ博士乱数 (16連続同一メッセージ)

確かに16連続で熱い話をしてくれていますね. 乱数調整成功です 👏👏

この記事で行う乱数調整はこれで終わりです *16. 乱数列を意識しながら乱数調整を行ったので, 疑似ランダムな事象の裏ではどんな計算が行われているのかということをイメージする力が付いたのではないでしょうか. 以下で他にどんなことに対して乱数調整できるのか軽くリストアップしたので是非ご覧になって下さい (色乱数や高個体値ポケモンの乱数はありきたりすぎるのでカットで 👋).

他にどんなことが乱数調整できるの?

面倒になってきたので雑にいきます.

d.hatena.ne.jp

僕がパッと思いついたのはこれくらいですが, これは入れるべきというものがあれば教えてください 🙏

乱数調整の勉強する際に参考になるサイト

上に同じく, これは入れるべきというものがあれば教えてください 🙏🙏🙏

おわりに

以上が Pokémon RNG Advent Calendar 2016 1日目「乱数調整 入門」となります. ここまで読んでくださった方々, ありがとうございます! お疲れ様でした! 😃

期限までに書ききることができなかったのは残念 *17 ですが, Pokémon RNG Advent Calendar 2016 では興味深い記事が次々と投稿されてきています. 残りの日も Pokémon RNG Advent Calendar 2016 で楽しんでいきましょう!

www.adventar.org

2日目は @sub_827 さんの担当です!

*1:つまり, 「乱数」とは規則性がなく予測不能な数のことです.

*2:家からサイコロ探し出して1人で転がしてました

*3:僕はこのあたりのことについて詳しくないので何も言えません

*4:他にもメルセンヌ・ツイスタ法やカオス乱数などがあります. (参考: http://www.nt-s.ne.jp/product/campain/knowledge/missing-number.html)

*5:後ほど述べますが, 擬似乱数は算定可能なので予測不能な数では無い, 乱数ではないとも言われています

*6:調整するのは「乱数」であり「事象」ではないので厳密には間違っています

*7:Frameの原意は動画を構成する一枚一枚の静止画(コマ)のことで, ポケモンにおける乱数調整では経過時間を表す単位として用いられます

*8:参考: 雪の舞う夜に。 : 【乱数調整】空白時間の測定【HGSS・DPPt】

*9:何でこんな時間に記事を書いているんでしょうね...

*10:うっかり「乱数ではない乱数のようなもの」を使わないよう, 乱数を扱うときはその乱数生成法の利点と欠点についてよく調べておきましょう

*11:描画処理1Fにつき乱数が1消費されることから、消費される乱数の個数を数える際にFを単位として用いる慣習があります

*12:一定時間ごとに決まった向きに振り向くなど周期的な動作をするNPCは乱数を消費しません

*13:"徘徊系ポケモン(はいかいけい-)とは、出現ポイントがある条件によってマップ上を移動する伝説のポケモンのこと。" http://wiki.xn--rckteqa2e.com/wiki/%E5%BE%98%E5%BE%8A%E7%B3%BB%E3%83%9D%E3%82%B1%E3%83%A2%E3%83%B3

*14:Flash製のツールです. 時代を感じますね…

*15:17回も連続で電話をかけるだなんて主人公も迷惑な野郎ですね

*16:撮影しながらの乱数調整しんどかった…

*17:今日 (記事公開日) は12/6 です. 大遅刻してしまって本当に申し訳ありません 🙇

巻き爪の治療

たまには技術以外のテーマの記事を。

長年悩まされていた巻き爪が治りつつある。

巻き爪については以下のサイトで簡単に把握できる。 爪の端が内側に巻く症状で、皮膚に食い込んで痛みを伴ったりする。

dr-nail.jp

僕の場合は巻き爪の中でも陥入爪に当たるもので、(記憶が定かではないが)3年以上前に左足の親指に発症した。 当時授業で行っていたサッカーで負荷を掛け過ぎたり、(運悪く)よく足をモノにぶつけたりして患部が腫れ、そこに爪が食い込んだのが原因だと思う。

歩いたり走ったりする分には痛みはないが、親指に体重を乗せたり、患部をモノにぶつけたりするとガラスの破片で刺されたような痛みが伴う。 特に体育なんかは最悪で、親指に異常なくらい負荷が掛かるので毎回痛みに怯えながらやってる。サッカーとかまともに出来ない。というか情報系の学部なのに何で体育あるんだ。わけわからん。

発症した当初は放っておけば治るだろうという安直な考えでいたが、次第に悪化していったので発症から一年経過した頃に病院で診てもらった。そこでは毎日テープで爪が食い込んでいる皮膚を爪から引き離し、開いた隙間に薬を塗るよう指導された。この治療を数ヶ月間継続した結果、少々腫れてはいたものの痛みが殆ど無いレベルまで落ち着いた。

が、当時の僕はそこで満足して以降通院をやめてしまった。これが失敗だった。通院をやめてからというものの少しずつ腫れが酷くなり、数カ月後には元に戻るどころか以前より強い痛みを伴う状態になってしまった(今から半年前の話)。これではマズいと思った僕は再び病院で診てもらい、再びテープによる治療を再開した。この時、応急処置として患部に食い込んでいた爪の端を切ってもらった*1。痛みの強さも頻度も低下してすごく楽になった。以降テープによる治療を継続し、今に至る。

現状、患部に触れても痛みが無い程度まで回復することができた。実を言うとテープを貼る作業を結構サボっていたので毎日きちんとやっていれば治療に3年なんかも掛かってなかったと思う。サボり癖って言うのかな。改善していきたい。

怪我とか病気とかって放置すると時間も金もその他諸々消費するし、ろくな事無いなって改めて思った。この調子で今度こそ完治させるぞ。

「痛いけど我慢できる、いつか自然に治るだろう」という自己判断をするのは危険です。放置したことによって更に悪化する場合は決して少なくはありません。 http://dr-nail.jp/treatment/makidume

別の話になるけど巻き爪と同じく3年前くらいに発症したアレルギーが原因で林檎、梨、桃、柿、枇杷、生海老を食べると喉が痒くなる症状があったが、梨であれば食べても痒くなっていたことが数週間前に判明した*2。梨は大好物だったので本当に嬉しい。これが徳を積んだ結果か。:pray:

*1:切断に使ったハサミが「力こそパワー💪」みたいなやつでちょっと怖かった

*2:梨以外も今度試したい

ポケットモンスター・ポケモン・Pokémon・は任天堂・クリーチャーズ・ゲームフリークの登録商標です.

当ブログは @mizdra 個人により運営されており, 株式会社ポケモン及びその関連会社とは一切関係ありません.