趣味でドラフトシミュレーターを作っている過程を、思い出せる範囲でつらつら書いていく連載です。技術的な話も混ざりますが、基本は日記です。
目次
仕事でAIを使うようになって、ふと思ったこと
気づいたら動くものができていた
選手選びが一番地味に大変だった
くじ引き演出にこだわりすぎた話
最初はAI採点で終わるはずだった
レーダーチャートがなかなか綺麗に出ない
まさかの試合シミュレーションまで作ることになった
公開したら、まず自分がハマった
指名の瞬間が地味すぎる問題
「野球アイコンはダサい」と言われた日
AIがいつも同じ中継ぎ投手を指名する
足の速い選手ばかり評価される、の裏で起きていたこと
佐藤輝明と牧秀悟が全然指名されない事件
コンボを狙うAIをやめることにした
全チームがAランクになる、に気づいた日
遊び方ページと選手名鑑を作った
AIの指名ロジックにはこれからもこだわりたい
独自ドメインを取って、球団紹介とFAQページを作った
似たツールを見て、オーダーカードを作りたくなった
用語集とランキングページを追加した
本職の選手がサブの選手に負ける現象を直した
牧秀悟が二塁で1位になれない、という指摘
パワーとミートを分けて評価するのをやめた
オーダーカードを二度作り直した話
「保存できない」の一言で気づいたこと
検索流入を見ていて気づいた、足りないページ
構造化データというものを初めて触った
アドセンス審査に向けて見直したら、リンク切れを見つけた
シェアボタンに文章を入れるかどうかで迷った話
実績機能で「自己満足」を全肯定することにした
コンボ乱発の実績が簡単すぎた
本物のドラフト会議について書いてみた
「毎回1番になる気がする」で見つかった乱数のバグ
「投手王国」だけ偏りが強すぎた計算式の話
オリックスだけ文字が消えるカードのバグ
ドラッグ操作をプロ目線で見直してもらった話
Pixelでタイトルが見えない、という報告
同じ落とし穴が他に3箇所も埋まっていた
結果画面から攻略ガイドへの導線を足した
ページ同士がぶつ切りだったのをつなげた
ゲーム本体だけ、どこまで進んだか分からなかった
本物のドラフトの盛り上がりに便乗したかった話
ボタンが増えすぎてヘッダーが渋滞していた
実績、実はコンボの半分しか対応してなかった
歴代ドラフト伝説コラムで、自分の記憶違いに気づいた話
山川穂高が盗塁しまくる不具合の原因は、コンボバフだった
「もっとかっこいいゲームにしたい」で追加した、スタメン発表演出
サーチコンソールを見て、タイトルに「AI」を前に出した話
スタメン発表、横並びカードだと打順が分からない問題
スマホだと指名アナウンスのボタンに指が届かない、という報告
スタメン発表、画面外の選手が見えていなかった問題
外国人枠を入れた話
指名アナウンスボタン、まだ位置がしっくりこなかった話
パソコンの大画面だと抽選結果のポップアップが左端に寄っていた話
相手チームのスタメン発表だけ、途中から始まる不具合
DAZNの代わりにスカパーで、3つ目のアフィリエイト提携先が決まった話
楽天のレポートでクリック数を見て、レビューページを作った話
「放送試合数ナンバー1」と書く前に、事実確認をした話
ニューエラキャップ、実は黒ベースを選んでいた話
「カウントダウンが出てない」の正体は、そもそも置いてなかった
文章がびっしり詰まって読みにくい、と言われた話
「link.amazon」という見慣れないリンクを疑ってしまった話
レプリカユニフォーム、12球団分が揃った話
「広島だけDAZNにない」を記事にしてみた話
虎テレは知ってたけど入らなかった、というリアルな話
広島ファン限定の話を、全球団向けに書き直してみた話
「DAZNも二軍対応してるはず」で書き直した話
「DAZNも月契約できる」で3社比較にした話
DAZNの二軍対応、実は広島だけ抜けていた話
中日戦も一部見れないと聞いて、脚注を足した話
キャップに楽天とAmazon、両方のリンクを並べてみた話
Amazonリンク、一気に5球団分揃った話
楽天イーグルスだけAmazonになかった話
プロスピにもAmazonリンクを足した話
パワプロも追加して、野球ゲーム2本立てにした話
パワプロにも楽天リンクが届いた話
見出しがカラフルなリンクに埋もれていた話
AIチームに謎の「代打」が紛れ込んでいた話
選手を82人増やして178人にした話
既存選手のサブポジションが結構抜けていた話
先発しかできない投手が抑えに回されていた話
先発が大量失点しても継投しない話
実績を4つ追加した話
対戦AI球団数を6球団まで増やした話
自分の試合もスキップできるようにした話
対戦AI球団数を8球団まで増やした話
「第1打者」が誤植だった話
中継ぎが一度も投げないまま抑えに交代していた話
近本光司が6球団競合になった、という報告
サーチコンソールを見て、見出しを1文字も直していなかったことに気づいた
「野球っぽさが伝わってこない」と言われてから写真を組み込むまで
スマホでスクロールしたら背景が置いてけぼりになっていた話
「開票中」が背景に溶けて見えなくなっていた話
せっかくの写真を暗くしすぎていた話
オリックスとロッテが写真に溶けて消えていた話
良かれと思って足したリングが逆効果だった話
指名発表画面だけ写真をやめることにした話
写真も球団カラーもどっちも欲しい、をどう両立させたか
半分透明にした自分のパネルが原因だった話
指名発表は黒、抽選は写真、と役割を分けて決着した話
9回を投げきったのに「8.2」と表示されていた話
仮のIDのまま82人分放置していた話
3画面だけ置いてきぼりだった話
127球投げてもまだ交代しない先発
130球は現代野球だと投げすぎ、という指摘
友達と対戦できるようにしたい、と言われて
くじは一緒に選ばないと意味がない、という指摘
ホストの指名ボタンが「押しても反応がない」
直したはずなのに「1巡目のアナウンスが出ない」
7巡目で両方とも「待機中」になったまま止まった
抽選にならないと自分の指名結果が分からない
ドラフトの先、スタメンと試合がまだ繋がっていなかった
試合を「録画」して両方に同じものを見せることにした
似たようなくじサイトが先に公開されて、焦って自分でも作った話
今日の実際の試合でくじを試したら、投手の名前を取り違えかけた
「くじ買っただけなのに勝手にXに飛ばされた」で青ざめた話
大山がDHではなくセカンドを守っていた理由
周東佑京の守備力が49だった、表の見間違い
なぜ最大8球団までしか対戦できなかったのか
堅守の選手が損をする打順適性の計算式
12球団で対戦したら、4球団が黙って消えていた
収益化に向けて、地味な足場を固めた回
ドラフト2026特集を整えて、予想機能を作った回
開始ボタン、実はもう画面の外にいた
「外野ならどこでも」をやめた日
8秒かかっていた原因はフォントだった
スマホで触って初めて分かった2つの引っかかり
遊び方デモを作ったら、デモが「一番大きな表示」になっていた
福永の守備力が、隣の列の数字だった
守備力は「本職」じゃなくて「一番得意な位置」で決めることにした
デモのせいで「表示が終わらないページ」になっていた
ページを開くたびにCSSを作っていたのをやめた
清宮が遊撃、岡林が一塁のスタメン
一番大きな表示は、裏に隠れたロゴだった
「残り何人なら危ないか」は、球団数で全然違った
遊び方ページが、ゲームより先に古くなっていた
シェア画像は、余白より情報だった
検索結果のアイコンが、ずっと地球儀だった
「ドラフトシム」は、よそのゲームの名前だった
延長21回の決着は、ずっと先攻びいきだった
野球くじを、いったんお休みにした
抽選の「あの一瞬」を、テレビっぽくしてみた
友達対戦は、通知が遅れたときに壊れていた
AdSenseにまた落ちたので、全ページを数えてみた
実績を増やす前に、取りやすさを数えてみた
「基本プラン」と「プロ野球セット」は別物だった
「保存ボタンを押し忘れる」を前提にした
右翼手を7人足したら、内野手が混ざっていた
11位のチームが「優勝候補」と名乗っていた
「平均との差」で選んだら、65点に黄色が付いた
フッターの灰色が、32ページ全部で引っかかっていた
ドラフトの日に向けて、数える準備をした
無料枠を使い切ったら、友達対戦は黙って止まる作りだった
2回目の人も、毎回同じ3つを選び直していた
ロサンゼルスでは、ドラフト当日が「あと371日」だった
第1回:仕事でAIを使うようになって、ふと思ったこと
仕事柄、Webサイトの運営に関わっている。パソコンやツールの扱い自体には慣れている方だと思うけど、プログラミングだけはずっと別世界の話だった。興味がなかったわけじゃない。むしろ気になってはいたんだけど、「自分にはさすがに無理だろう」という壁を勝手に感じて、ずっと手を出せずにいた。
このゲームのきっかけになった経験は、何年か前に遡る。友達と「現役選手でドラフト会議をやろう」という話になって、各々が欲しい選手を一人ずつ指名していったことがあった。指名が被ったらルーレットで抽選、というルールにして、最後は組み上がったオーダーを見せ合って「誰のチームが一番強いか」を議論した。あの時の盛り上がりは今でもよく覚えている。
時が経って、仕事でAIを使う機会がどんどん増えて、資料作成やちょっとした調べ物には「これがないと逆に不便」というくらいの感覚になっていた。そんな中で「じゃあ自分でゲームみたいなものも作れたりするのかな」と思い立ったとき、真っ先に頭に浮かんだのが、あの日のドラフトごっこだった。あれをちゃんとしたシミュレーターとして作れたら面白いんじゃないか。それがこのゲームを作り始めた直接のきっかけだった。
第2回:気づいたら動くものができていた
最初の指示は本当にふんわりしたものだった。「現役のNPB選手でドラフト会議をして、チームを組んで対戦できるようなゲームを作ってみたいんだけど、できるかな」。それだけ。仕様書もイメージ図も何もない、思いつきをそのまま投げただけの一言だった。それでも、ある程度ちゃんと形のあるものが返ってきたのには正直驚いた。ずっと感じていた「プログラミングの壁」が、思っていたよりあっさり越えられた瞬間だった。
面白かったのはそこからだった。「ここをこう直したい」「この画面にはこういう情報が欲しい」と伝えるたびに、自分の頭の中にあったイメージにだんだん近づいていく。一文字もコードを書いていないのに、目の前で自分の考えていた「あの日のドラフトごっこ」が少しずつ形になっていく感覚は、正直かなり中毒性があった。手応えを感じて、じゃあ本格的に作り込んでいこう、と決めたのがこの頃。ただ、ここから先は思っていたよりずっと長い道のりになった。まず最初にぶつかった壁が、次に書く「選手選び」の問題だった。
第3回:選手選びが一番地味に大変だった
ドラフトシミュレーターである以上、実在の選手データが必要になる。12球団×8人、とりあえず96人分を用意することにしたんだけど、これが想像以上に時間を食った。「パワー」「ミート」「走力」「守備力」みたいなゲーム的な数値を、自分の感覚だけでゼロから査定するのはさすがに無理があったので、普段よく遊んでいる「プロ野球スピリッツA」の能力値を参考にさせてもらうことにした。あのゲームも似たような項目で選手を数値化してくれているので、いちから自分で査定するよりずっと現実的だった。
とはいえ、参考にできる数値があるとはいえ、96人×4項目、合計384個の数値をひとつずつ入力していく作業自体もそれなりに時間がかかった。大変だったのはむしろそれ以上に「各球団どの8人を選ぶか」という選定の方だった。主力級を並べるだけなら簡単だけど、ポジションのバランスも考えないといけないので、「この選手を入れたら誰を削るか」みたいな取捨選択を12球団分繰り返す地道な作業になった。派手さは全くない工程だけど、ここの選び方がドラフト全体の説得力に直結するので、数値入力と選手選定、どちらも意外と一番時間をかけた部分だったかもしれない。
第4回:くじ引き演出にこだわりすぎた話
最初は「くじ引きこそがドラフトで一番盛り上がる瞬間だ」と思っていて、全ての巡でくじ引きが発生する可能性がある「全巡同時指名」という方式を作った。NPB方式だと抽選が起きるのは基本的に1巡目だけだけど、全巡同時指名は毎巡すべての球団が一斉に指名するので、希望が被ればどの巡でも抽選になる。この方式は今でも選択肢として残っていて、現在は「NPB方式」「全巡同時指名」「完全ウェーバー」の3つの指名方式から選んでプレイできるようになっている。
くじ引き自体の演出にもかなりこだわった。ただ「抽選の結果、〇〇が当たりました」とテキストで出すだけだと、実際のドラフト会議の緊張感の欠片もない。それで、封筒を選んでクリックすると開封されて、当たりなら「交渉権獲得」のスタンプが浮かび上がる、みたいなアニメーションを作り込むことにした。CSSでの見た目の動きなんて指定したこともなかったので、封筒のフタが開く角度やスタンプが出てくるタイミングを何度も微調整することになった。地味に時間がかかったけど、実際に自分の球団の封筒が当たりだったときの演出を見たら結構テンションが上がって、これは頑張ってよかったなと思えた回だった。
第5回:最初はAI採点で終わるはずだった
ドラフトで選手を集めて、スタメンを自分で組んで、最後にAIが採点してランク付けしてくれる。当初思い描いていたゴールはここまでだった。それだけで十分に遊べるミニゲームになると思っていたし、実際ランキング画面に自分のチームがそれっぽい点数で表示された時は、これでもう完成でいいんじゃないかと本気で思っていた。
試しに何度か遊んでみると、「採点は出たけど、実際に試合したらどっちが勝つんだろう」という気持ちがどうしても湧いてくる。採点だけで満足するつもりだったのに、気づけばもっと欲張りたくなっていた。
第6回:レーダーチャートがなかなか綺麗に出ない
採点結果を「パワー・ミート・機動力・守備力・先発陣・救援陣」の6項目でひと目にわかるようにしたくて、レーダーチャートを入れることにした。ところがこれがなかなかうまく描けない。98点みたいな高得点が出ているのに、なぜかチャートの六角形はやけに小さいまま、という点数と見た目が全然噛み合わない状態が続いた。
原因を探すのにかなり時間がかかったんだけど、結局たどり着いたのはコンボシステムだった。当時は、コンボ(長打力が高い選手を揃えると発動するボーナスのような仕組み)が発動すると、その加点が最終スコアにそのまま乗る作りになっていた。ところがレーダーチャートの方は、コンボの加点を一切考慮せず、素の能力値だけをグラフに描いていた。つまり「点数はコンボ込みの98点」「チャートはコンボ抜きの素の実力」という、そもそも別々のものを並べて表示してしまっていたわけで、噛み合わないのは当然だった。気づくまでにかなりの時間を溶かしてしまったけど、原因が分かった瞬間はさすがに脱力した。
第7回:まさかの試合シミュレーションまで作ることになった
採点だけで満足するつもりが、結局「実際に試合をさせてみたい」という欲に負けた。1球ごとの打席結果、盗塁、エラー、ファインプレーまで再現する試合シミュレーション機能を作ることにしたんだけど、これが今までで一番の大工事だった。せっかく選手一人ひとりに能力値を付けているので、それがちゃんと試合に反映されるように組み立てることにこだわった。走力が高い選手ほど盗塁を仕掛けやすくなる、守備力が高い選手ほどファインプレーが出やすくなる、といった具合に、能力値と試合内容が結びつくようにした。守備位置ごとの得意不得意、打順の相性、投手交代のタイミングまで考え出すと、考えることがどんどん増えていく。
もう一つ地味にこだわったのが、試合中の勝率をリアルタイムで計算して、グラフで可視化する機能だった。点差やアウトカウント、走者の状況が変わるたびに勝率が動いていくのを見られるようにしたことで、単なる結果発表ではなく「試合展開そのもの」を楽しんでもらえるようになったと思う。
せっかく試合ができるなら一発勝負より勝ち上がりがある方が盛り上がると思って、参加球団によるプレーオフのトーナメント機能もこのタイミングで追加した。最初にAI採点だけで完成のつもりだった頃からは、想像もしていなかったところまで来てしまった。
第8回:公開したら、まず自分がハマった
もともと個人的に遊ぶためだけに作っていたドラフトシミュレーターだったけど、思ったより形になってきたのでGitHub Pagesで公開してみることにした。リポジトリをパブリックにして、Pagesを有効にして、独自ドメインを取得して、Googleサーチコンソールに登録して、アナリティクスにも登録して……と、揃えるものが想像以上に多くて、正直これが一番地味に大変だった。
ただやってみて感じたのは、「Webサイトを公開するのはもっと難しいものだと思っていた」ということだった。アナリティクスの計測コードを埋め込んだり、広告枠を用意したりといった、普通なら専門知識がないと詰まりそうな部分もAIが問題なくやってくれて、自分がやったのはドメイン業者やGoogleのサービスに個人情報を入力してサイト登録するくらいのことだった。この規模のサイト運営を、専門知識ゼロの自分がここまで持ってこられたのは、正直AIの進歩を実感した瞬間だった。自分一人だったら、間違いなくどこかで挫折していたと思う。
公開した直後、自分でスマホから触っていたら「選手詳細を見る」リンクを押しても何も起きないことに気づいて焦った。バグかと思って一通り調べたんだけど、リンク自体は正しく外部サイトへ飛ぶようになっていて、どうやら自分が使っていたプレビュー環境側が外部リンクを塞いでいただけらしいとわかった。実際にデプロイされたページで試したら普通に動いた。人騒がせな話。
第9回:指名の瞬間が地味すぎる問題
ドラフト方式には抽選ありのNPB方式と、順番に淡々と指名していくウェーバー方式があるんだけど、ウェーバーだと「今誰が指名されたか」が画面上パッと分からないことに気づいた。1巡目の抽選演出は結構作り込んだのに、2巡目以降が味気ないのはもったいない。
というわけで、ウェーバー中もAIが指名するたびに一件ずつ発表画面を挟むようにした。最初は表示時間を1.1秒にしてみたけど、テンポが悪い気がして2秒に伸ばし、結局1.5秒に落ち着いた。この手の「ちょうどいい間」の調整は数字をいじって実際に何度も見てみるしかない。全部の指名をいちいち見たくない人のために、ヘッダーからON/OFF切り替えられるようにもした。
第10回:「野球アイコンはダサい」と言われた日
指名発表のカードに⚾の絵文字を大きく置いていたんだけど、率直に「野球アイコンはダサいからやめてほしい、実際のドラフトのものを参考にしてほしい」と言われて、まあ確かにと納得した。実際のNPBドラフト会議の指名画面を参考にしつつ、そのまま真似するのではなく、球団カラーの帯を入れるといった独自の要素も加えて作り直すことにした。
絵文字を消して、球団カラーのバンドと白いカード本体だけの構成にしたら、思っていたよりそれっぽくなった。ついでに枠のサイズがカードごとにバラバラだったのを統一して、所属チーム名の文字が中央揃えになっていなかったのも直した。地味な調整だけど、見た目の説得力ってこういう細部の積み重ねなんだなと実感した。
第11回:AIがいつも同じ中継ぎ投手を指名する
ここから地味に長いAI調整編が始まる。ドラフト画面を見ていたら、1位でトップクラスの中継ぎ投手が指名されるのはまだ分かるとして、本来ならもっと後の巡目で十分なはずの中継ぎ投手まで、3〜5位くらいの上位で指名されていることに気づいた。明らかにおかしい。
原因を数値で追ってみたら、投手の評価式が野手より構造的に高く出るようになっていた。データセットの平均を比べると投手が76点前後、野手が70点前後で、単純に比較すると常に投手が有利になる。しかも中継ぎ・抑えの評価式が先発よりさらに甘い配分になっていたことも判明した。実測した差(だいたい6.3点)を投手の評価だけ差し引いて、野手と対等なスケールで比較できるように調整した。
第12回:足の速い選手ばかり評価される、の裏で起きていたこと
投手偏重が落ち着いたら、次は自分で何度もゲームを回しているうちに「走力・守備力が高い選手の方が、打力が高い選手より先に指名される」ことに気づいた。現実のスカウティングだと走攻守そろった5ツール型より、打撃力に秀でたタイプの方が評価されやすいはずなので、これも直すことにした。走力・守備力より打力(長打力・確実性)を高めに評価する係数を能力ごとに設定して組み込んだ。
ところがこの調整、最初につけた係数の平均が1.0からわずかにズレていて、野手全体のスコアが知らないうちに沈んでしまい、また投手優位の状態がぶり返すという自分のミスをやらかした。気づいたきっかけは、何度もプレイしているうちにAI3チームが揃って投手を3連続指名している場面に遭遇したこと。数字を計算し直して、係数の平均がちゃんと1.0になるように直した。地味だけど、こういう「平均を保つ」みたいな制約は見落としやすい。
第13回:佐藤輝明と牧秀悟が全然指名されない事件
打力型の評価が上がってきたところで、今度は自分で遊んでいて「佐藤輝明、レイエス、牧秀悟の評価が現実的に見て低すぎる」ことに気づいた。人間がドラフトしたら2位までにはいなくなりそうな選手たちが、なぜか中盤以降まで残ってしまう。
特に分かりやすかったのが、同じ二塁手の牧秀悟と田中幹也の比較だった。牧は打力がかなり抜けている一方、田中幹也は守備・走力型で、プレースタイルはかなり対照的な二人。それなのに評価上は田中幹也の方が高く出てしまっていて、これは投高打低と言われる今の野球界の傾向を考えても現実の感覚とズレていると感じた。そこで「同じポジションのライバル選手と比べてどれだけ頭一つ抜けているか」を測るロジックを追加した。最初は守備寄りの評価式で比較していたせいで、逆に田中幹也のような守備型の選手の方が「傑出している」扱いになってしまうというねじれもあったので、打力そのもの(長打力と確実性の単純平均)で比較するように直した。これでようやく牧の評価が田中幹也より高くなった。
第14回:コンボを狙うAIをやめることにした
ここまでの調整でAIの指名はだいぶ現実的になったんだけど、振り返ってみると「コンボ(長打力75以上の野手4人、みたいな編成ボーナス)を揃えにいく」ロジックがまだ結構強く効いていた。よく考えたら、AIは別にコンボを狙って野球をするわけじゃなく、単純に勝てるチームを作りたいはずだ。
というわけで、チームへの思い入れで無理に選手を集めるボーナスや、ユーザーの編成を妨害するためだけのボーナスを丸ごと削除して、残りのコンボ関連の加点も控えめな数値に減らした。素の実力・枠の必要性・ポジションでの希少性を主な判断材料にして、似たタイプの好選手が結果的に集まってコンボが揃うのはむしろ歓迎、というスタンスに落ち着いた。
第15回:全チームがAランクになる、に気づいた日
採点システムにはS〜Gの8段階のランクがあるんだけど、ふと「Gランクって実際に出るのか?」と気になって調べてみたら、まったく出ないことが判明した。それどころか、わざと一番弱い選手だけを選んで、わざと守備位置もめちゃくちゃに配置してみたら、それでもAランク・85点くらいになってしまった。原因は一つじゃなかった。そもそも選手データが各球団から厳選した選手たちなので、一番弱い選手でも打力60前後・投手も球威70前後はあるくらい全員のレベルが高い。それに加えて、コンボによる加点が大きすぎたことも重なっていた。どんなに変な組み方をしても、コンボの条件さえ満たせばその加点だけで点数が跳ね上がってしまう作りになっていたのだ。
選手データをいじるという発想はそもそもなかった。実際の選手のデータをいじるのは、リアルなプロ野球からかけ離れてしまう。だから直すのは採点の計算式の方一択だった。まずコンボの加点の比重を下げて、変な編成でも加点だけで高得点が出ないようにした。そのうえで、「意図的に最悪の編成で組んだ場合」と「理想的な編成で組んだ場合」の実際の点数を測ってみて、その幅を0点から100点にまるごと引き伸ばすようにした。結果、最悪の編成が14点(Gランク)、理想の編成が91点(Sランク)まで届くようになって、ランダムなAI同士のドラフトでもD〜Bあたりに自然と散らばるようになった。ランクが全部同じだとやっぱり面白くないし、数字としての説得力もないので、これは直してよかったと思う。
ちなみに今の選手データの数値は、プロスピを参考にさせてもらっている部分が結構ある。いつかは打率や防御率、WARみたいな実際の指標を元に、自分なりの算出方法で数値を出せるようにしたいと思っているんだけど、正直まだそこまでの知識はない。野球の指標に詳しい人がいたら、ぜひ教えてほしいです。
第16回:遊び方ページと選手名鑑を作った
ゲーム部分がだいぶ落ち着いてきたので、そろそろミニゲーム以外のコンテンツも増やそうと思って、遊び方ページと選手名鑑ページを作った。遊び方ページはドラフト方式やコンボの条件、採点のランク基準なんかをまとめただけなんだけど、初めて触る人には結構役立つはず。
選手名鑑は全96選手を球団やポジションで絞り込めるページ。データをこっちに複製すると選手を追加するたびに更新し忘れそうだったので、本体のゲーム画面から選手データを直接読み込んで表示するようにした。これで選手を増やしてもここは自動で追従してくれる。次は何を増やそうか考え中。
第17回:AIの指名ロジックにはこれからもこだわりたい
ここまで振り返ってみると、中継ぎ偏重、走力・守備型の過大評価、佐藤輝明や牧秀悟が指名されない問題、コンボ狙いのやめどき……と、結局一番手を入れ続けているのはAIの指名ロジックだった気がする。正直、他の部分よりだいぶしつこく直している自覚がある。でもここが一番「本物のドラフトっぽさ」を左右する部分だと思っていて、だからこそ気になった瞬間に放置できない。
実際、直したと思っても遊んでいるうちにまた別の偏りに気づくことの繰り返しだった。多分これで完成、というゴールはなくて、プレイする中で「これは違うだろ」と思ったらそのたびに調整していくんだと思う。地味な作業だけど、一番思い入れがある部分でもあるので、AIの指名ロジックはこれからも継続して手を入れ続けるつもりでいる。
実はこの一連の計算式、細かい数値まで含めて有料記事として公開してみたらどうか、というアイデアを前にもらったことがある。ここまで沼りながら積み上げてきた部分には自分でも一番愛着があるので、いつか形にして出してみたいと思っている。
第18回:独自ドメインを取って、球団紹介とFAQページを作った
GitHub Pagesの初期URLには自分のアカウント名がそのまま入っていて、よく見ると名前っぽい文字列が含まれているのがちょっと気になっていた。というわけで独自ドメインを取ることにして、色々悩んだ末「dreamteam9.com」に決定。DNSの設定は正直よく分かっておらず、AレコードだのCNAMEだの言われても最初は何のことか全然ピンとこなかったけど、言われた通りに数字を打ち込んで、あとは反映されるのをじりじり待つだけだった。
反映待ちの間手持ち無沙汰だったので、その時間で「球団紹介ページ」と「FAQページ」を作ることにした。球団紹介は、もともとAIの指名ロジックの中に持っていた「球団ごとのスカウト傾向」のデータをそのまま見える化しただけなんだけど、こういう裏側の数値を表に出すのは初めてで、自分で見返しても意外と面白かった。FAQの方は、検索結果に質問と回答がそのまま表示される仕組み(構造化データというらしい)があると聞いて試しに入れてみた。効果があるかはまだ分からないけど、やらない理由もないので入れておいた。ついでに、そろそろ広告も検討したいという話もしていたので、プライバシーポリシーのページも先に用意しておくことにした。
第19回:似たツールを見て、オーダーカードを作りたくなった
ドメインの反映待ちで地味に時間を溶かしていたとき、似たようなドラフト系ツールで「自分で組んだチームをカード風の画像にしてシェアできる」機能があるのを見かけた。スタメンが1番から9番まで縦に並んでいて、各選手にどこの球団の何巡目指名か、みたいなバッジが付いている見た目で、これは単純にかっこいいなと思って、うちにも欲しくなった。
採点結果のシェア画像は前からあったんだけど、あれはランクとレーダーチャートが主役で、実際に自分がどんな並びでチームを組んだかは文字がちょっと添えてあるだけだった。今回は逆に、編成そのものを主役にした縦長のカードを別で作ることにした。地味に手間だったのが、選手が「何巡目で指名されたか」をこれまで記録していなかったこと。指名が確定する場所が抽選ありなしで何箇所かに分かれていて、その全部に記録を仕込む必要があった。あと外国人選手の長い名前がバッジと重なってしまう問題があったので、名前の文字幅を測って、はみ出しそうならフォントサイズを自動で縮める処理を入れた。地味だけど、これがないと一発で見た目が崩れるので大事な部分だったと思う。
第20回:用語集とランキングページを追加した
実はその前に一つ恥ずかしい訂正があって、FAQやプライバシーポリシーに「プロスピAの数値を参考にしつつ独自に調整している」と書いていたんだけど、よく考えたら数値はそのまま使っていて調整なんてしていなかった。書いた自分がなぜか話を盛ってしまっていたことに今さら気づいて、慌てて全部直した。地味だけど、自分の作ったものについての説明が事実と違っているのは良くないので、見つけた瞬間に直せてよかった。
気を取り直して、遊びに来てくれた人がもっと楽しめるように用語集とランキングページを追加した。用語集は「ウェーバー方式って何?」みたいな初心者向けの説明をまとめただけなんだけど、ランキングの方は実際にゲーム内で使っている適性計算式をそのまま流用して「二塁手なら誰が一番強いか」を出せるようにした。作ってみて改めて気づいたのが、牧秀悟が一塁やDHではぶっちぎりの1位に来るのに、二塁だと守備型の選手に負けて5位くらいになること。これはまさに以前AIの指名ロジックで悩んでいた「守備位置ごとの得意不得意」の話がそのまま数字に表れていて、自分で作った計算式なのに見返すとちょっと面白かった。
第21回:本職の選手がサブの選手に負ける現象を直した
作ったばかりのポジション別ランキングを眺めていて「あれ、一塁のランキング1位が本職の一塁手じゃなくて、二塁が本職の牧秀悟になってる」ということに気づいた。牧はどこに置いても打力だけで押し切れてしまうので、守備位置ごとの向き不向きを表す加点・減点が弱すぎて、本職の選手を上回ってしまっていたのが原因だった。
調べてみると、「本職ポジション」は+5点、「守備位置の一つとして持っているサブポジション」も+2点と、どちらもプラスの補正で、しかもその差がたった3点しかなかった。これでは打力に差がありすぎる選手が来ると簡単に逆転してしまう。サブポジションの補正をプラスからマイナスに変えて「少し下がる」ようにし、適性が全く無いポジションのペナルティも軒並み倍近くまで強化した。直した後にランキングを見返したら、ちゃんと本職の選手が上に来るようになっていて、地味だけどちゃんと直った実感があった回だった。
第22回:牧秀悟が二塁で1位になれない、という指摘
本職とサブポジションの逆転現象を直した直後、今度は「セカンド牧が低い」という話になった。牧は日本代表の正二塁手にまでなった選手で、一方でランキング1位になっていた田中幹也は代表に選ばれたことすらない。守備は堅実だけど、それを言うなら牧の打力は守備の差を補ってあまりあるはず、というのはもっともな指摘だった。
原因を見てみたら、さっきの逆転現象の話とは別で、そもそも二塁・遊撃の評価式そのものが守備力と走力だけで8割を占める配分になっていた。これだと守備が並でも打力が飛び抜けている選手は、どうやっても守備型の選手に勝てない。守備の価値がゼロでいいとは思わないので、遊撃の方はそのままでも大きな違和感はなかったんだけど、二塁は牧のような打力型がもっと正当に報われてもいいはずと思い、守備・走力と打力をほぼ五分の配分に見直した。直した後にランキングを見たら、ちゃんと牧がトップに来るようになって、田中幹也や菊池涼介もその下にきちんと残っていたので、極端に振り切りすぎたわけでもなさそうで一安心だった。
第23回:パワーとミートを分けて評価するのをやめた
守備位置ごとの評価式を見返していて、実は一塁はパワー寄り、三塁はミート寄り、みたいに、ポジションごとにパワーとミートを別々の細かい重みで扱っていたことに気づいた。一本足打法の長距離砲と、コツコツ当てる巧打者を守備位置の向き不向きの観点で区別する必要は正直なくて、どちらも「打てる」という一点では同じはず。むしろ細かく分けている分だけ、何をやっているのか自分でも把握しづらくなっていた。
というわけで、パワーとミートを平均した「打撃力」という一つの値にまとめて、各ポジションの配分もその合計値をそのまま引き継ぐ形に整理した。守備・走力とのバランス自体はどこも変えていないので、ランキングの並び順もほとんど変わらなかったんだけど、式が一気に見通し良くなって、次に何かいじるときに楽になったと思う。
第24回:オーダーカードを二度作り直した話
前に紹介した「似たツールのシェア画像を見て作りたくなった」オーダーカード、実際に自分の目で完成品を見返してみたら、思っていた以上に元のツールに寄せすぎていることに気づいてかなり焦った。金色の二重枠、守備位置を囲む小さな枠、選手名の横に「球団・◯巡目」と表示するバッジ……見た目の骨格がほぼそのままだった。しかも冷静に考えると、このゲームは実在のドラフト年度や巡目を再現しているわけではなく、その場でシミュレーションした指名順でしかないので、「◯巡目」という表示自体が意味を持たない、誤解を招く数字だった。参考にするのと真似しすぎるのは全然違う、というのを痛感して、その場でバッジ表示は完全に撤去し、金色の枠や囲み文字もやめて、採点結果のシェア画像で元から使っていた紺のグラデーションと球団カラーのグローに揃え直した。
ところが作り直したものを見返すと、今度は逆の問題に気づいた。余白を持たせすぎて情報量のわりに間延びして見えるし、球団カラーはうっすら光る程度のグローだけだったので、グレーの文字が多いのもあって全体的に地味で暗い印象だった。せっかく直したのに、今度は誰かにシェアしてもらうには弱すぎる見た目になっていた。行の高さやセクション間の余白を詰めて、上部に球団カラーをそのまま帯として敷く形に変えて、チーム名の背景ごと球団カラーで塗りつぶすようにした。阪神やソフトバンクみたいに背景が明るい球団は文字を黒に、それ以外は白にと、球団ごとに元々用意してあった文字色の設定をそのまま流用できたのはラッキーだった。
結果、最初のバッジ入りデザインとは別物になったけど、球団カラーがはっきり主張する分、前より断然「シェアしたくなる」見た目になったと思う。似ないようにするだけじゃなくて、そこからちゃんと自分のサイトらしい強さを出すところまでやらないと意味がないんだな、というのを実感した回だった。
第25回:「保存できない」の一言で気づいたこと
オーダーカードの見た目を調整した直後、「画像を保存を押しても保存されない、写真フォルダに入ってない」という報告をもらった。試してみると、スマホの「保存」ボタンは「<a download>」というよくある方法で実装していたんだけど、これがiPhoneのSafariだとほぼ機能しない。ボタンを押しても画面上は何も起きたように見えず、実際どこにも保存されていなかった。実はモーダルの説明文には前から「長押しで保存してください」と書いてあったんだけど、それはこの問題を根本的に直さずに回避していただけだったんだなと今更ながら気づいた。
調べてみたら、navigator.share()というブラウザ標準の機能を使うと、OSの共有シートを直接呼び出せて、そこから「写真に保存」が選べるらしい。canvasの画像をBlobに変換してファイルとして渡すだけで動くようになったので、保存ボタンはこの共有シートを最優先にして、対応していない環境だけ今まで通りのダウンロード処理にフォールバックする形にした。ついでに、「打順・ポジションはもっと大きくていい、名前は50、所属球団は30くらいで」という具体的な数字での要望ももらったので、そのまま文字サイズを底上げして、絶対に12/12にしかならない「編成12/12」の表示も削除した。数字がでかいだけで、タイムラインを指でスクロールしながらでも中身が読めるようになった気がする。地味な機能ほど、実際に使ってもらうまで壊れてることに気づけないんだなと思った回だった。
第26回:検索流入を見ていて気づいた、足りないページ
暇な時間にGoogleサーチコンソールをぼんやり眺めるのが習慣になっていたんだけど、ある日「ドラフト シミュレーション 攻略」とか「スタメン編成 コツ」みたいな検索語で来てくれている人が地味にいることに気づいた。うれしい反面、よく考えたらそのワードで来た人が着地するページがなかった。遊び方ページはルールの説明はしっかりしているんだけど、「じゃあ実際どう指名すればS〜Aランクが取れるのか」という実戦的な話は一言も書いていなかった。ランキングページと球団紹介ページはあるのに、それをどう組み合わせて使えばいいのかを誰も教えてくれない状態だったんだと思う。
それで攻略ガイドのページを新しく作ることにした。中身は自分がドラフトするときに実際に考えていることをそのまま言語化しただけで、序盤の投手確保、センターラインの守備バランス、コンボは狙いすぎない方がいいこと、あたりを書き出した。書きながら「これ結局遊び方ページと内容がかぶるのでは」と一瞬手が止まったんだけど、ルール自体の説明と、そのルールをどう使えば勝てるかの話は別物だと割り切って進めた。あとナビゲーションのリンクを全ページに足す作業が地味に多くて、1ページ更新し忘れるだけでサイト内の導線が歯抜けになるので、8ページ分くらい機械的に追加して回った。こういう横展開が必要な作業ほど、後で漏れに気づいて青ざめるパターンが多いので、今回はちゃんと一つずつ確認しながらやった。
第27回:構造化データというものを初めて触った
攻略ガイドページを作った流れで、次は何をすれば検索から来てくれる人が増えるか考えていたら、「構造化データ」という言葉が出てきた。正直、名前だけ聞いたことがあって中身は全然知らなかった。調べてみると、ページの中身をGoogleに「これはこういう種類の情報ですよ」とタグ付けして教えてあげる仕組みらしい。FAQページには実はもう昔追加してもらっていたみたいで、見返してみたら`application/ld+json`というやつがひっそり埋め込まれていた。存在を知らずに放置していたのがちょっと恥ずかしかった。
せっかくなので、トップページにはゲームであることを示すタグ、遊び方ページには手順を示すタグ、用語集には用語集であることを示すタグ、攻略ガイドには記事であることを示すタグを追加した。効果が数字にすぐ表れるものではないので地味な作業ではあるんだけど、書いたJSONが構文として正しいかだけは全部チェックしてから公開した。見た目は何も変わらない更新だけど、検索エンジン側にはちゃんと伝わってくれるといいなと思っている。
第28回:アドセンス審査に向けて見直したら、リンク切れを見つけた
そろそろ広告を出せるようにしたいと思って、Googleアドセンスの審査に出す準備を始めた。審査に通りやすくするために何を直すべきか一通り見直してもらったところ、FAQとプライバシーポリシーの「不具合はX(旧Twitter)の公式アカウントまで」という文言、あれリンクが一切貼られていなかったことが発覚した。自分では何年も前に書いた文章だし、まさかリンクを貼り忘れていたなんて思ってもみなかった。実際に踏んでみたら本当にどこにも飛べなくて、これは連絡したくてもできない状態だったんだなと反省した。
すぐにアカウントのURLを教えて、2箇所ともちゃんとしたリンクに直してもらった。ついでにファビコンが1つも設定されていないことにも気づいたので、⚾の絵文字を簡単なアイコンとして全ページに追加した。地味な修正が続くけど、こういう基本的なところが整っていないと、審査云々の前にユーザーとしても不信感を持たれてしまうよなと思う。次はいよいよ審査に出してみる予定。
第29回:シェアボタンに文章を入れるかどうかで迷った話
アドセンス周りが一段落したので、次は流入を増やす話をしていた。今は結果画像を保存するだけで、Xに投稿するには自分で画像を貼って文章を書く手間がある。そこを一つでも減らせないかと思い、Xの投稿画面をワンタップで開くボタンを作ることにした。最初は「〇〇球団でSランク達成!」みたいな結果を自動で文章に入れる案を考えていたんだけど、相談したら「文章は自分の言葉で書きたいから入れないでほしい」と言われて、なるほどと思った。決まり文句が強制的に入っていると、逆に投稿する気が失せる人もいるんだろうなと納得した。
結局、文章は空欄のまま、サイトのURLと「NPBドラフトシミュレーション」というタグだけを入れて投稿画面を開く形に落ち着いた。画像は相変わらず自動添付ができない(Xの仕様上、投稿画面を外部から開くときにファイルを渡す方法がない)ので、そこだけは「保存した画像を貼ってください」と一言添えることにした。全部を自動化しようとせず、ユーザーが自分の言葉で発信できる余地を残す、というのも大事な設計判断なんだなと思った回だった。
第30回:実績機能で「自己満足」を全肯定することにした
Xシェアボタンの次に、もっと遊び込んでもらうための機能として実績(アチーブメント)を作ることにした。ただ最初に「これって何のために作るんだっけ」と自問したとき、正直はっきりした答えがなかった。ランキングに載るわけでもないし、他の人と比較できるわけでもない。相談してみたら「各個人の自己満足的要素が大きいから、とりあえず各個人で保存できてればいい」と言われて、なるほどそれでいいのかと吹っ切れた。全員に見せびらかす機能じゃなくて、自分だけがニヤッとできればいい機能、と割り切ったら設計がすごく楽になった。
会員登録なしで手軽に作りたかったので、実績の記録はブラウザのlocalStorageというその端末だけに残る仕組みにした。サーバーには何も送らないので、スマホで解除した実績はPCには引き継がれない。最初は「それってちゃんと機能として成立するのか」と心配したけど、これも「自己満足でいい」と割り切れば全然問題なかった。Sランク獲得、プレーオフ優勝、コンボ発動、12球団全部でプレー、みたいな条件を10個ほど用意して、達成した瞬間に画面上部にポンとバッジが出るようにした。地味だけど、自分でテストプレイしてSランクのバッジが出た瞬間はちょっと嬉しかった。
第31回:コンボ乱発の実績が簡単すぎた
実績機能を作った直後に「コンボ乱発(3つ同時発動)は普通に何度かやれば達成できちゃう」という指摘をもらった。確かに振り返ってみると、9枠の編成でコンボが3つ揃うのはそんなに珍しくない。実績としては物足りないラインだったと反省した。じゃあどれくらいが「達成した実感」を持てる難易度なんだろうと考えて、思い切って全8種類のコンボのうち8つ同時発動、というほぼ最高難度のラインに引き上げることにした。
ついでに、実績を全部同じ並びで見せていたのも良くなかった気がして、初級・中級・上級の3段階に分けることにした。初めてのプレイで自然に取れるものを初級、腕前や運が絡むものを中級、狙って作り込まないと届かない編成を上級、という区切りにして、モーダルの中でも見出しごとにグループ表示するようにした。上級には新しく「Sランクを獲得したチームでそのままプレーオフも優勝する」という実績も追加した。ただ実績を並べるだけじゃなくて、達成した時にちゃんと「これはすごいことをやった」と思えるかどうかが大事なんだなと実感した回だった。
第32回:本物のドラフト会議について書いてみた
読み物ページをもっと増やしたいと思って何を書こうか考えていたとき、ふと「このサイト、本物のドラフト会議のルールを一切説明してないな」と気づいた。遊び方ページには「本サイトのドラフト方式」の説明はあるけど、それが実際のNPBドラフトのどの部分を参考にしていて、どこが違うのかは誰にも書いていなかった。プレイヤーの中には野球に詳しくない人もいるはずなので、本物の制度を知った上で遊んでもらえたら理解が深まるんじゃないかと思って書くことにした。
ただ書き始めてみると、うろ覚えの部分が結構あることに気づいた。逆指名制度とか自由獲得枠とか、過去にあった制度の細かい年数までは自信がなかったので、そのあたりは思い切って書かないことにした。曖昧な記憶で数字を書いて後で間違いを指摘される方が嫌だし、確実に合っている大枠(1巡目抽選+2巡目以降ウェーバー、支配下と育成の違い、FA制度との違い)だけに絞って書いた方が誠実だと思う。最後に「本物とこのサイトは対象選手も目的も全然違う」とはっきり書いたのも、変な誤解をされないための一文のつもりだ。読み物を増やすほど、書く内容の正確さにも気を遣うようになってきた気がする。
第33回:「毎回1番になる気がする」で見つかった乱数のバグ
「ゲーム機能をプロ目線で見直してほしい」とお願いしたら、指名順を決める処理に本物のバグが見つかった。`array.sort(() => 0.5 - Math.random())`というシャッフルの書き方は、パッと見ランダムっぽいんだけど、実際は配列の先頭や末尾の要素が極端な順位(1番手や最下位)になりやすい、という有名な罠らしい。恥ずかしながら知らずに使っていた。試しに50万回シミュレーションしてもらったら、自分のチームが指名1番手になる確率が本来25%のはずが36%になっていた。「なんかいつも1番か最後になる気がする」というのは気のせいじゃなかったわけで、指摘されて初めて答え合わせができた気分だった。
直し方はシンプルで、Fisher-Yatesというちゃんとした均等シャッフルのアルゴリズムに置き換えるだけ。指名順以外にも、AI対戦相手の決定やくじ引き演出の開封順など、同じ書き方をしていた箇所が全部で4箇所あったので、まとめて直してもらった。ついでに、遊び方ページにも「ウェーバーの指名順は毎回ランダムで決まる」と一言追記した。今まで運要素だと思っていたところに実は偏りがあった、というのは地味に応えるバグだった。
第34回:「投手王国」だけ偏りが強すぎた計算式の話
指名順のバグを直した勢いで、AIの指名ロジック全体もプロ目線で見てもらうことにした。すると今度は、球団の「色」の計算式そのものに歪みが見つかった。野手の評価はパワー・ミート・走力・守備力の4項目を平均する仕組みだから、1項目だけ重視する設定にしても他の3項目に薄まって、最終スコアへの影響は±10%くらいで収まる。でも投手は球団の重視度をそのまま基礎点に掛けているだけだったので、同じ数値の幅(0.8〜1.35)でも±35%も動いてしまっていた。
つまり「投手王国」を名乗るオリックスや阪神・中日は設定通りにガンガン投手を偏重して指名する一方、「長距離砲補強型」の巨人やソフトバンクは、ラベルほど打者を偏重した指名にはなっていなかったということになる。数字で並べてもらって初めて気づいたけど、言われてみれば「なんとなく投手王国系の球団はエースを独占してる気がする」という印象は前からあった気がする。投手側の効き方を野手側と同じくらいの幅に圧縮してもらって、ようやく12球団のスカウト傾向が名前負けしない強さで機能するようになったと思う。
第35回:オリックスだけ文字が消えるカードのバグ
「オリックスのオーダーカードだけ『投手陣』の見出しが見えない」というスクリーンショット付きの報告をもらった。見た瞬間に原因はすぐ分かった。オーダーカードの見出し文字は球団カラーをそのまま使っているんだけど、オリックスの球団カラーとして設定していた紺色(#0f172a)が、カードの背景グラデーションの開始色とまったく同じ値だった。他の11球団はオレンジや黄色みたいなハッキリした色だから気づかなかったけど、オリックスだけ「背景と同じ色のインクで文字を書く」状態になっていたわけで、そりゃ読めない。
指摘してもらった金色(#A47B01)を、背景に溶け込んでしまう色のときだけ差し替える形で直した。オリックスの色を単純に変更してしまうと他の場所(対戦相手一覧のバッジなど)の見た目まで変わってしまうので、「文字として使うときだけ読みやすい色に差し替える」という関数を一つ挟む形にした。1色だけ特別扱いするのは行儀が悪い気もしたけど、見えない文字を放置するよりはずっとマシだと思う。
第36回:ドラッグ操作をプロ目線で見直してもらった話
UI全体をユーザー目線でプロっぽくチェックしてもらったら、スタメン編成画面のドラッグ操作に地味な弱点が見つかった。選手カードの「≡」の持ち手部分がスマホだと結構小さくて、配置先の枠の高さも32pxしかない。しかもスマホは横幅が狭いから、選手一覧と配置先が左右に押し込まれて、どっちも余裕がない状態で運指を求められる。ドラッグ&ドロップ自体、スマホのブラウザだと標準では動かないので外部のポリフィルライブラリに頼っていて、それが読み込めない環境だと配置手段が丸ごと詰む、という指摘ももらった。正直、自分で使っているときは慣れで気にならなかったけど、言われてみればアプリのレビューとかでよく見る「操作しづらい」の典型パターンだったと思う。
直し方は、ドラッグ操作自体はそのまま残しつつ、「選手をタップして選択→置きたい枠をタップ」というタップだけで完結する代替操作を追加する形にした。配置済みの枠を(何も選択していない状態で)タップするとプールに戻せるので、taps操作だけで全部完結する。選択中はどこに置けるかが枠の縁の光り方で分かるようにもした。ドラッグが得意な人はそのまま使えて、苦手な人はタップだけで組める、という二刀流にできたのは良かったと思う。
第37回:Pixelでタイトルが見えない、という報告
「Googleピクセルで開くと、トップページの上の方が切れる」というスクリーンショット付きの報告をもらった。見せてもらった画像は「②対戦するAI球団数」から下しか映っていなくて、タイトルも「①担当球団」も一切見えない。自分は主にiPhoneで確認しながら作っていたので、画面の高さの違いでこんなことが起きるとは思っていなかった。
調べてみたら、CSSの有名な落とし穴だった。中身を画面中央に寄せるために`justify-content: center`を使っていたんだけど、中身が画面より高くなると、はみ出した分の上側が「スクロールしても届かない」場所に押し出されてしまう。中央寄せって、要は上下に均等な余白を作る仕組みだから、画面より中身が大きいと上側の余白がマイナスになって、そのマイナス分がそのまま見えなくなる、ということらしい。iPhoneだと画面が広くてギリギリ収まっていたから気づかなかっただけで、Pixelのように画面がちょっと低い端末だと表面化していたんだと思う。設定項目やリンクをどんどん増やしてきたツケが、こんな形で出るとは思わなかった。
第38回:同じ落とし穴が他に3箇所も埋まっていた
Pixelの見切れ不具合を直した後、「他にも同じパターンで表示が崩れそうな箇所はないか」と全画面を洗い直してもらった。結果、通知ダイアログ(「本当にやり直しますか?」とかのポップアップ)、指名結果の演出画面、くじ引き画面の3つが、まったく同じ組み合わせ(中央寄せ+高さの上限なし)を使っていることが分かった。今のところメッセージは短いから実害は出ていないけど、くじ引き画面は4球団競合だと封筒が2列に折り返すので、画面の低い端末だと理論上は同じことが起きる作りになっていた。
1箇所直して満足せず、同じ書き方をしている場所を全部洗い出す、というのは地味だけど大事な作業だと実感した。今回は該当パターンが3つだけで済んだからよかったものの、思いつきでコピペしながら画面を増やしてきたツケが、こういう形でまとめて出てくるんだなと思う。表示崩れ系のバグは普段のプレイでは気づきにくいから、こうやって横展開でチェックしてもらえるとかなり助かる。
第39回:結果画面から攻略ガイドへの導線を足した
広告が入った時にちゃんと収益につながるように何ができるか相談していて、そもそもゲーム本体には広告を置かない方針にしているぶん、読み物ページへどうやって人を連れて行くかが課題だという話になった。考えてみれば、AI採点の結果画面もプレーオフの結果画面も、シェアボタンはあるのに「もっと知りたい人向け」の導線が一切なかった。せっかく攻略ガイドを書いたのに、遊んだ後にそこへ辿り着く動線がなかったのはもったいなかったと思う。
採点結果の画面には「もっと高いランクを狙うなら攻略ガイドをチェック」の一文を、プレーオフの結果画面には勝ったか負けたかで文言を変えて(優勝したら「次はパーフェクトロマンも狙ってみよう」、負けたら「次こそ優勝を掴むなら」)攻略ガイドへのリンクを添えた。押し付けがましくならない程度の、控えめな一行リンクにしたつもりだけど、遊び終わった直後の「もっと上手くなりたい」という気持ちのタイミングに刺さってくれるといいなと思う。
第40回:ページ同士がぶつ切りだったのをつなげた
アクセス解析を眺めていて気づいたんだけど、読み物ページ(攻略ガイドとか用語集とか)に来た人のほとんどが、そのページだけ見て他のページに行かずに離脱していた。各ページの下の方に「ゲームを始める」ボタンは置いてあるけど、それ以外に他のコンテンツへのリンクがほぼなくて、考えてみれば当然の結果だった。せっかく攻略ガイドや用語集や球団紹介をちゃんと書いても、お互いが完全に孤立していたら、来た人が偶然そのページに辿り着いた1回きりで終わってしまう。
まず全ページの一番下に「こちらのページもどうぞ」という関連リンクを付けて、内容が近いページ同士を回遊できるようにした。それだけだと機械的すぎる気がしたので、用語集の「先発」「中継ぎ」「抑え」の説明文からランキングページの該当ポジションへ直接飛べるようにしたり、球団紹介の各球団カードから「〇〇の選手を見る」で選手名鑑をその球団だけに絞り込んだ状態で開けるようにしたりもした。選手名鑑のカードにも、その選手の一番得意なポジションのランキングへ飛べるリンクを添えている。文脈のある場所に文脈のあるリンクを置く方が、下にリンク集をまとめるより自然に読み進めてもらえるんじゃないかと思う。効果が出るかはもう少し数字を見てみないと分からないけど、少なくとも「見て終わり」の一方通行は減らせたはず。
第41回:ゲーム本体だけ、どこまで進んだか分からなかった
読み物ページの回遊は改善したものの、Googleアナリティクスをよく見返してみると、もっと根本的な穴があった。攻略ガイドや用語集みたいな別ページはちゃんと「どのページが見られたか」が出るのに、肝心のゲーム本体だけそれが分からない。理由は単純で、ゲームはドラフト→指名結果→くじ引き→スタメン編成→採点結果、と画面がどんどん切り替わっていくのに、URLは`index.html`のまま一度も変わらないから、アナリティクス側からは「1ページ見て終わった人」も「最後まで遊び切った人」も同じ1回のページビューにしか見えていなかった。
直したいのはURLを変えることではなく、あくまで内部の状態遷移だから、画面を切り替えている`showScreen()`という共通関数に手を入れて、切り替わるたびに画面ごとのラベル付きでページビューを送るようにした。実際のURLは変えずに「仮想的なページ」として送る形なので、ブラウザの見た目や戻るボタンの挙動には一切影響しない。これで「ドラフト会議までは来るけど、くじ引きの手前で離脱する人が多い」みたいな、ゲームの中の脱落ポイントが初めて数字で見えるようになるはず。今まで感覚でしか語れなかった部分に、ようやく根拠が付けられそうで楽しみにしている。
第42回:本物のドラフトの盛り上がりに便乗したかった話
9月に入って、SNSで本物のNPBドラフト会議(今年は10月22日開催)の話題がちらほら出始めていることに気づいた。せっかく毎年盛り上がる話題があるんだから、乗っからない手はない。まずは日程と放送予定をまとめたページを作った。会場や開始時刻がまだ正式発表されていなかったので、そこは「暫定」と正直に書いて、公式サイトへのリンクも添えた。ついでに今年注目の候補選手を6人紹介するコーナーも足した。候補選手のリンクを教えてもらったんだけど、サンドボックスのネットワーク制限でそのサイトには繋げなかったので、代わりに検索で複数の記事を突き合わせて裏取りしながら書いた。手間はかかったけど、変な情報を載せるよりはずっといい。
ページを作った後、「今年のドラフトが終わったらこのカウンターはどこに行くの?」と聞かれてハッとした。日程ページに開催日までのカウントダウンを仕込んでいたんだけど、日数の計算自体は年をまたいでも自動でロールオーバーするようにしていた一方で、ページの見出しや放送予定の中身は「2026年」を前提にベタ書きしていたから、ドラフトが終わった瞬間にカウンターだけ来年の日付を指して、周りの文章とチグハグになる未来が見えた。注目選手も来年にはもう現役選手になっているだろうから、なおさら古い情報のまま置いておくのはまずい。というわけで、カウントダウンと注目選手だけを新しいページに切り出して、日程・放送予定のページはこれまで通り今年の記録として残すことにした。ページを分けたことで、「随時更新していく場所」と「今年のスナップショットとして残す場所」の役割がはっきりして、自分でも管理しやすくなったと思う。トップ画面にも専用ボタンを置いて、残り日数がそのまま見えるようにしたので、遊びに来るたびに「もうこんなに近いのか」と感じてもらえたら嬉しい。
第43回:ボタンが増えすぎてヘッダーが渋滞していた
用語集、ランキング、ドラフト特集2ページ……と読み物ページをどんどん増やしてきた結果、各ページのヘッダーには気づけば11個ものボタンが並んでいた。パソコンならまだ横一列に収まるからいいんだけど、スマホで開くとピルボタンが3段にも4段にも折り返して、肝心のページタイトルまで下に押し出されてしまう有様だった。「今後もコンテンツは増やしていきたいから、今のうちに直したい」という話になり、さすがにこれは放置できないと腹をくくった。
直し方としては、「遊び方」「選手名鑑」「開発ブログ」の3つだけを今まで通りボタンで常時表示し、残り8つは「☰ メニュー」を押したときだけ開くドロップダウンにまとめることにした。ドロップダウンの中も「遊ぶ」「選手・球団を調べる」「2026年ドラフト特集」「開発情報」とカテゴリ分けして、どこに何があるか探しやすくした。地味に手間だったのは、各ページの自分自身へのリンクは表示しない、という既存のルールをこの新しい構造でも律儀に守ること。例えば更新履歴ページを開いたときは「開発情報」カテゴリの中身が「更新履歴」1つだけになって空っぽになるので、カテゴリごと非表示にする、みたいな細かい調整が12ページ分必要だった。おかげでヘッダーの常時表示ボタンは11個から5個まで減って、これでコンテンツをまだまだ増やしても大丈夫なはず。
第44回:実績、実はコンボの半分しか対応してなかった
「実績の数をもっと増やしたい」という要望をもらって、まず今ある11個を見直してみたら、恥ずかしい抜けを見つけた。コンボは「超重量打線」「機動力野球」「鉄壁の守備陣」「黄金の投手陣」「安打製造機」「勝利の方程式」「センターライン」「ユーティリティ」「○○愛」と全部で9種類実装してあるのに、実績になっていたのは最初の4つだけだった。後から追加したコンボほど実績側への反映を忘れていたらしく、コードを書いた本人も存在を忘れかけていた。
まずはこの取りこぼしていた5コンボ分の実績を追加した。それだけだと「バグを直しただけ」で終わってしまうので、他の切り口でも増やすことにした。Gランクを一度は経験する「伸びしろ全開」は、下手なうちに踏むはずの通過点をあえて実績にした自虐枠。NPB方式・全巡同時指名・完全ウェーバーの3方式を全部試す「方式マスター」と、通算10回プレイする「継続は力なり」は、一つのやり方に固執せずに長く遊んでもらうための実績。AI球団3つ(計4球団)を相手にプレーオフで優勝する「強豪ひしめく頂点」を上級に追加して、11個から20個まで増やした。
公開したら早速「全球団でプレイ、も入れて」というリクエストが来たので確認したら、実はもう「全球団制覇」という名前で12球団すべてを担当するだけの実績が存在していた。ただ聞き返してみると、求められていたのは「12球団すべてで一度はAランク以上を取る」という、もっと骨のある方のやりこみ要素だった。プレーするだけと結果を出すのとでは難易度が全然違うので、これは素直に上級枠に「全球団エース級」として追加した。結果、実績は21個になった。ちょっとしたやり取りで意図がずれることもあるんだなと再確認した回だった。
第45回:歴代ドラフト伝説コラムで、自分の記憶違いに気づいた話
「読み物をもっと増やしたい」という話になって、方向性をいくつか出してもらったら「歴代ドラフト伝説コラム」が選ばれた。江川事件とか松坂大輔の3球団競合とか、野球ファンなら一度は聞いたことがあるようなエピソードを集めて紹介するページを作ることにした。ネタ自体は自分の中にもうっすら記憶があったので、正直「これはサクッと書けるだろう」と思っていた。
ところが裏取りのために調べ直してみたら、自分の記憶がいくつも間違っていたことが発覚した。一番恥ずかしかったのが、2006年夏の甲子園決勝で投げ合った田中将大と斎藤佑樹を、てっきり同じ年にドラフトされたものだと思い込んでいたこと。実際は斎藤佑樹は早稲田大学に進学していたので、ドラフトの対象になったのは4年後の2010年だった。もう一つ、佐々木朗希についても「単独指名だった」と勘違いしていたんだけど、実際は日本ハム・ロッテ・楽天・西武の4球団が競合していて、単独指名だったのは大谷翔平の方の話と頭の中でごっちゃになっていたらしい。うろ覚えのまま書いていたら、堂々と間違った情報を載せるところだった。
幸い、公開前に一つずつ検索して裏取りする工程を挟んでいたおかげで、この2つの勘違いはちゃんと本番前に直せた。田中将大と斎藤佑樹については、むしろ「甲子園では同じ舞台に立ったのに、プロ入りは別々の年だった」という事実の方が話として面白かったので、そのまま1本のエピソードとして書き直した。有名な話ほど「知っているつもり」で裏取りを怠りがちなんだなと反省した回だった。
第46回:山川穂高が盗塁しまくる不具合の原因は、コンボバフだった
「この選手が盗塁するの!?ってのが多い気がする。山川とかサンタナとかレイエスがめっちゃ盗塁する」という報告をもらった。挙げられた3人は揃って長打力自慢のスラッガーで、走力の数値自体もそこまで高くない。試合エンジン側の盗塁判定は前から「走力60未満は仕掛けない」というガードを入れていたはずで、山川の素の走力は58だから理屈上は一度も盗塁を試みないはずだった。
コードを追ってみたら、盗塁の可否判定に使っていた走力の数値が、選手本来の走力ではなく「機動力野球」コンボなどのチーム全体バフがかかった後の数値だったことに気づいた。このコンボは打線内に足の速い選手が3人以上いれば発動する仕様で、発動すると打線全員の走力に一律で+6される。足の速い選手を集めたはずが、そのバフは足の遅い山川やサンタナ、レイエスにも平等にかかってしまい、山川の走力は58+6=64まで底上げされて、60のガードをあっさり超えていた。コンボは「打線全体の機動力の高さ」を表すロマン要素のつもりで入れたバフだったんだけど、まさか個々の選手の盗塁判断まで狂わせているとは思っていなかった。
直し方としては、バフがかかる前の素の走力を選手データ側に別枠で持たせておいて、盗塁の可否・成功率の判定だけはそちらを参照するようにした。バフ後の走力は他の打撃計算にはそのまま使うので、コンボの効果自体が消えるわけではない。修正後にテストで山川の走力データに同じ+6のバフを乗せて2万回試行してみたら、盗塁の試行回数はちゃんと0回になった。一方でサンタナ(走力62)は数%程度の頻度で試みるようになっていて、狙い通り「速い選手ほど仕掛けやすく、遅い選手ほど自重する」という形に落ち着いた。
第47回:「もっとかっこいいゲームにしたい」で追加した、スタメン発表演出
「ゲームのどこかに演出を追加できる箇所はある?もっとかっこいいゲームにしたい」と相談されて、ランク発表・ホームラン演出・プレーオフ優勝演出・スタメン発表の4つを候補に出したら、「スタメン発表からやりたい。あとプレーオフの前に相手のスタメンも知りたい」という話になった。編成を組み終えた瞬間や、プレーオフの緊張感ある一戦の前に、選手が1人ずつ登場してくる感じがあると確かに盛り上がりそうだと思って、作ることにした。
見た目は、以前作った指名発表カード(球団カラーの帯+白いカード本体)をそのまま流用することにした。新しいデザインを一から考えるより、サイト内で見た目の一貫性が保てる方がいいし、実際やってみたら違和感なく馴染んだ。9人の打者と3人の投手、計12枚のカードを、少しずつタイミングをずらしながらパタパタとめくれるように登場させて、最後に「次へ」ボタンがふわっと出てくる作りにした。
仕込む場所は2箇所。1つはスタメン編成を終えた直後、AI採点結果を見る前に自分のスタメンを発表する場面。もう1つはプレーオフで自分が絡む試合の直前、相手チームのスタメンを発表する場面。どちらも実はもう「誰が何番でどこを守るか」というデータ自体は既に採点処理の中で作られていたので、そのデータをカード表示用に使い回すだけで済んだ。新しく画面を1枚追加して、そこに繋ぎ込むだけの、思ったよりシンプルな作業だった。
第48回:サーチコンソールを見て、タイトルに「AI」を前に出した話
アドセンスの審査には落ちてしまったんだけど、気を取り直してサーチコンソールの検索パフォーマンスを眺めていたら気になる数字を見つけた。「aiドラフト」というクエリだけで表示回数254回、他の「ドラフト ai」「ai仮想ドラフト」なども合わせると、AIという単語を含む検索が全体の6割以上を占めていた。もともとサイトのタイトルにも「AI」は入れていたんだけど、「NPBドラフト会議シミュレーション|AI対戦・スタメン編成ゲーム」と、「|」より後ろに置いていたので、検索結果でのタイトル表示的にはあまり目立っていなかったはずだった。
実際の検索データがここまではっきり「AIというワードで探されている」と示しているなら、タイトルの前方にAIを持ってくるべきだと判断して、「AIドラフトシミュレーション|NPBドラフト会議×スタメン編成ゲーム」に変更した。既存の「NPBドラフト会議」「シミュレーション」というブランド的なキーワードは残しつつ、検索需要の大きい語を前に出す形。感覚や思い込みでタイトルを決めるんじゃなくて、実際の検索データを見てから判断する方がいいんだなと実感した回だった。
第49回:スタメン発表、横並びカードだと打順が分からない問題
前回作ったスタメン発表演出、指名発表カードの見た目をそのまま流用して、9人の打者と3人の投手を横に並べてめくれるように出す作りにしていた。ところが実際に触ってもらったら「横並びだと打順が分かりにくい」という率直な指摘をもらった。言われてみると確かに、カードが折り返して2段3段になった瞬間、どれが1番でどれが5番なのか目で追いづらい。指名発表カードの見た目をそのまま持ってきたのが、そもそもこの用途には合っていなかったんだと思う。
作り直すにあたって、指名発表用とは別に、スタメン発表専用のデザインを一から考えることにした。実際の中継のスタメン表みたいに、上から1番、2番、3番……と縦に並ぶリスト形式にして、各行の左側に大きな打順バッジを置いた。カードを横に並べるより、素直に縦のリストにした方が「今何番目を見ているか」が一目で分かる。投手陣も「先」「中」「抑」の1文字バッジにして、同じ見た目のリストの続きとして違和感なく表示されるようにした。見た目のインパクトを狙って作った演出のはずが、結局は情報の分かりやすさを優先してシンプルな形に落ち着いた回だった。
第50回:スマホだと指名アナウンスのボタンに指が届かない、という報告
「僕のスマホだとアナウンスのオンオフボタンが上にありすぎて押せない」という報告をもらった。画面のヘッダー、一番上の行に小さく置いてあるボタンで、パソコンで見ている分には何の問題もなかったんだけど、スマホを片手で持ってプレイしていると、画面の一番上は親指が届きにくい場所になる。よく考えたら当たり前の話で、指名するたびに画面下の方の「指名」ボタンを押しつつ、たまに画面最上部のボタンにも指を伸ばす、というのは片手操作だと結構つらい。
直し方はシンプルで、スマホ表示のときだけ、画面下に元々あった「選手検索」「指名ボード」のボトムナビゲーションに、指名アナウンスの切り替えボタンも並べて置くことにした。パソコン表示ではこれまで通りヘッダーのボタンを使ってもらう形にして、表示を出し分けている。ボタンが2箇所になった分、切り替えロジックは片方のidだけを見て書き換えていたのをやめて、クラス名で両方まとめて更新するように直した。地味な修正だけど、実際にスマホで自分でも操作してみると、下にある方が圧倒的に押しやすくて、やっぱり手元で試してもらえるのはありがたいと思った回だった。
第51回:スタメン発表、画面外の選手が見えていなかった問題
スマホで実際にスタメン発表演出を撮った画面録画を送ってもらったら、「これでいいんだけど、選手が全員1画面に映らないから、選手が画面外に行くときに自動でスクロールされるようにしたい」と言われた。縦並びリストにして打順は分かりやすくなったものの、パソコンの画面サイズを基準に作っていたので、9人の打者と3人の投手、計12行がスマホの画面には収まりきらない。後半の選手はめくれて登場していても、画面の外で誰にも見られていない状態だった。
直し方は、各行に仕込んでいた登場タイミング(アニメーションの遅延時間)と同じタイミングで、その行に向けて自動スクロールする処理を足すだけで済んだ。ちょうどいいタイミングの数値はもう演出用に用意してあったので、それをそのままスクロールのトリガーにも使い回せたのが地味にラッキーだった。最後の「次へ」ボタンが表示される瞬間にも同じ処理を足して、ボタンまでちゃんとスクロールされるようにした。パソコンで作った画面をスマホで確認する、という当たり前の工程をうっかり飛ばしていたんだなと反省した回だった。
第52回:外国人枠を入れた話
「外国人枠を導入したい」という話になった。今のドラフトには実は外国人選手の指名人数に制限がなくて、極端な話1球団が外国人選手を根こそぎ集めることもできてしまう。実際のNPBは1試合の出場枠が最大4人までと決まっているので、それに寄せる形で今回はドラフトの指名時点で1球団最大3人までに制限することにした。今の選手データには外国人選手が全部で10人いるので、まあまあ現実的な人数だと思う。
実装で地味に困ったのが「そもそも誰が外国人選手なのか」を判定する方法だった。選手データに国籍のフィールドはないので、フルネーム表記に含まれる「・」(例:フランミル・レイエス)で判定することにした。実はこれ、指名ボードで外国人選手の名前を姓だけに短縮する処理で前から使っていた判定方法で、そのまま流用できた。あとはユーザーが上限を超えて指名しようとしたら警告を出して弾くのと、AI球団の候補選手プールからも上限に達したら外国人選手を除外するのと、両方に手を入れる必要があった。片方だけ直すとAIだけ無制限に外国人を集められる、みたいな抜け道になってしまうので、ここは両方セットで直すことを意識した回だった。
第53回:指名アナウンスボタン、まだ位置がしっくりこなかった話
スマホで押しやすくするために「選手検索」「指名ボード」のボトムナビゲーションに指名アナウンスのボタンも並べたばかりだったんだけど、「指名ボードの横だと邪魔だな」という感想をもらった。3つのボタンを均等割りにした結果、1つ1つの横幅が狭くなって、画面切り替え用の2つのボタンに紛れて余計な要素に見えてしまっていたんだと思う。押しやすさだけを優先して、置き場所として自然かどうかまで考えられていなかった。
「リストの横とかにして」という提案をもらって、画面上部にある「全選手/★リスト」のタブの並びに置き直すことにした。ボトムナビゲーションからは削除して、ヘッダーにあった元々のボタンも合わせて撤去し、結果的に指名アナウンスのボタンは1箇所だけになった。タブの右端に寄せる形で収まりも良くなったし、ボタンが2箇所に分かれて同期を取る処理をしていたのも1箇所だけで済むようになって、コードもシンプルになった。押しやすさを直したつもりが、今度は置き場所の座りの悪さに気づかされた回だった。
第54回:パソコンの大画面だと抽選結果のポップアップが左端に寄っていた話
パソコンでドラフトを試している画面のスクリーンショットを送ってもらったら、「抽選の結果…」を知らせるポップアップが画面のど真ん中じゃなくて、なぜか左端にぽつんと表示されていた。スマホで自分が確認していたときは何の違和感もなかったので、完全に見落としていたパターンだった。
原因を追ったら単純な話で、このポップアップを囲っている枠に、横方向を中央に寄せるための指定が1つ抜けていた。縦方向の中央寄せは別の仕組みで対応済みだったんだけど、横方向はポップアップ自体の最大幅を指定していただけで、中央に寄せる指定そのものがなかった。スマホだと画面幅が狭くてポップアップがほぼ画面いっぱいに広がるので、中央寄せがなくてもズレがほとんど目立たなかった。パソコンの広い画面で初めて、左端にぴったりくっついている異常な見た目が露呈したというわけだった。指定を1つ足すだけで直る話だったけど、スマホでしか確認していなかったから気づけなかったバグで、画面サイズを変えて確認することの大事さを改めて感じた回だった。
第55回:相手チームのスタメン発表だけ、途中から始まる不具合
「スタメン発表の演出で相手チームの場合だけ、1番上から発表じゃなくて、中途半端な真ん中らへんから始まる」という報告をもらった。自分のスタメン発表では毎回ちゃんと1番から始まっていたので、最初は何が違うのか分からなかった。
よく考えたら、このスタメン発表の画面は1回のプレイの中で2回使い回している。1回目は自分の編成完了直後、2回目はプレーオフで自分の試合の前、相手チームの発表として。1回目の自チーム発表のときに、選手が下の方まで登場するのに合わせて画面がどんどん自動スクロールしていくんだけど、そのスクロール位置をリセットしないまま2回目を開いていたので、前回スクロールし終えた位置(下の方)がそのまま残った状態で相手チームの発表が始まっていた。1回しか使わない画面だったら絶対に気づけなかったバグで、同じ画面を使い回すときは前回の状態がどこかに残っていないか疑ってかかる必要があるんだなと学んだ回だった。直し方は、発表を始める直前に画面のスクロール位置を強制的に先頭へ戻す一行を足すだけで済んだ。
第56回:DAZNの代わりにスカパーで、3つ目のアフィリエイト提携先が決まった話
以前、動画配信の中継サービスとしてDAZNとの提携を狙っていたんだけど、当時登録していたASP(A8.net)にはDAZNの案件がなくて、代わりに見つかった「スカパー!」で提携申請を出していた。しばらく音沙汰がなかったんだけど、無事に審査が通って、実際に広告リンクを作れるようになったという報告をもらった。
もらったリンクを見たら、Rakutenのときのような長いトラッキングURLに加えて、1×1ピクセルの透明な画像タグも一緒に発行されていた。これは成果測定用の画像らしく、A8.net側の慣習としてリンクとセットで埋め込むものなので、そのままページに追加した。設置場所は、FAQページに「本物のNPBの試合も見たいです」という項目を新しく作って、そこにリンクを置くことにした。ゲームはあくまでシミュレーションなので、本物の試合を見たくなった人の導線としてちょうどいい場所だと思う。これでAmazon・楽天・A8.netの3社と提携できたことになり、プライバシーポリシーの記載もA8.netを含む形に更新しておいた。
第57回:楽天のレポートでクリック数を見て、レビューページを作った話
楽天アフィリエイトの管理画面をなんとなく覗いたら、売り上げは0件だったんだけど、リンクのクリック数が7件あった。正直「誰もクリックしてないだろうな」と思っていたので、自分が想像してたより押されていて驚いた。ちょうどAmazonアソシエイトの審査も通っていたので、これは「アフィリエイト専用のページを作るいいタイミングかも」と思って着手した。何系のページにするか少し迷ったけど、レビュー系(球団グッズやゲーム・視聴サービスをまとめて紹介するページ)でいくことにした。
作業自体は、teams.htmlに既にある球団カラーとグッズリンクのオブジェクトをそのまま持ってきて、新しいページ用に組み立て直すだけのつもりだったんだけど、途中で自分のミスに気づいた。teams.htmlではリンクをテンプレート文字列からinnerHTMLで挿入していたのに対して、新ページでは`card.href = goods.url`のようにJSのプロパティへ直接代入していた。URLの中に含まれる`&link_type=...`という部分は、innerHTML経由ならHTMLエンティティとして正しく`&`に変換されるけど、プロパティ代入だとただの文字列としてそのまま入ってしまう。結果、実際のリンク先URLに`&`という文字列がそのまま残ってしまい、トラッキングパラメータが正しく渡らない状態になっていた。同じデータを別の書き方で流用するときはレンダリング方法まで揃えないといけない、というのを再確認した回だった。書き方をinnerHTML方式に直して、実際にブラウザでリンク先URLを確認してから公開した。
第58回:「放送試合数ナンバー1」と書く前に、事実確認をした話
スカパー!の紹介文をもっと魅力的にしたくて、「12球団すべて中継してるのはスカパーだけ」「二軍の試合も見られる」「放送試合数ナンバー1」というポイントを入れようと思った。ただ、事実じゃないことを書くわけにはいかないので、公開前に一つずつ調べることにした。
「12球団すべてを中継しているのはスカパーだけ」と「一軍だけでなく二軍戦も中継している」の2つは、スカパーの公式サイトや複数の比較記事で裏が取れた(DAZNは広島戦を配信していないので12球団は揃わない、というのがよく挙げられる理由だった)。でも「放送試合数ナンバー1」については、スカパー自身がそう名乗っている文言を見つけられなかった。全12球団の公式戦を一軍・二軍とも中継しているサービスが他にない以上、結果的に放送試合数はどこよりも多いはずなんだけど、それを「ナンバー1」と断定して書くのは根拠として弱いと判断した。なので最終的には「12球団すべて中継しているのはスカパーだけで、一軍だけでなく二軍まで見られるから、放送試合数は他のどのサービスよりも多い」という、確認が取れた事実だけをつなげた言い方に落ち着けた。キャッチーな一言より、ちゃんと言える範囲で書く方を選んだ回。
第59回:ニューエラキャップ、実は黒ベースを選んでいた話
おすすめページのニューエラキャップ紹介文について、「今回紹介してるのは派手な球団カラーが入ったものじゃなくて、黒とか普段使いにも着けやすいものを選んでる」という指摘をもらった。言われて紹介文を見返したら、確かに「推し球団のニューエラキャップです」としか書いていなくて、どんなデザインなのかがまったく伝わらない一文になっていた。
球団カラーがドンと入ったキャップだと、正直ファンじゃない人からすると「気合入ってるな」って見られがちだけど、黒ベースのモデルならユニフォームみたいにガッツリ主張しなくても、普段のコーディネートにさりげなく紛れ込ませられる。カジュアルな服にもシンプルな格好にも合わせやすくて、どんなファッションにも一枚足せる、っていうのが今回選んだモデルの推しポイントだったらしい。紹介文にその説明を足して、球団ごとのカードにも小さく「黒ベースキャップ」と添えるようにした。何を売っているか商品名を書くだけじゃなくて、なぜそれを選んだのかまで書かないと魅力が伝わらないんだな、と反省した回だった。
第60回:「カウントダウンが出てない」の正体は、そもそも置いてなかった
「ドラフトまでのカウントダウンがパソコンだと出てない」というスクリーンショット付きの報告をもらった。添付されていたのは「2026年ドラフト放送予定」ページ。てっきりPC版だけカウントダウンが表示されないレイアウト崩れかと思って、まずindex.htmlのホーム画面と「注目選手」ページの2箇所でモバイル幅・PC幅の両方をテストしてみたんだけど、どちらも「あと41日」ときちんと表示されていて、コードにバグは見当たらなかった。
スクリーンショットをよく見返したら、種明かしは単純だった。このページには「開催日までのカウントダウンと注目候補選手はこちら」という案内文とリンクしかなくて、そもそもこのページ自体にはカウントダウンの数字を表示する場所を作っていなかった。つまり「パソコンだと出てない」んじゃなくて、どの環境で見ても最初から置いてなかっただけ。紛らわしい導線を作ってしまっていたということなので、素直に「注目選手」ページと同じカウントダウン表示をこのページにも追加した。バグ報告だと思って調査を始めたら、実は機能不足だった、というオチの回。
第61回:文章がびっしり詰まって読みにくい、と言われた話
おすすめページのスクリーンショットを見せてもらって、「文章が密集しすぎて読みにくい。太字で装飾したりとかもっと読みやすくしてほしい。自分がどういう書き方や見せ方だったら商品を買いたくなるかを考えて」という指摘をもらった。改めて自分の書いた文章を見返すと、確かに1つの商品につき長い一文がずらっと並んでいるだけで、パッと見て何がいいのか全然伝わらない作りになっていた。文章を書くこと自体に必死で、「読んでもらう」という視点がすっぽり抜けていた。
直し方としては、各商品の紹介を「一言で刺さるフック文」→「✅付きの箇条書き3つ(気になる部分だけ太字)」→「目立つボタン」の3段構成に組み替えた。だらだら読ませるんじゃなくて、スクロールしながらでも要点だけ拾えるようにする、という発想。あわせて、それまでただの文字リンクだった「(PR)」付きのリンクを、ゲーム本編のCTAボタンと同じ見た目(丸みのあるボタン+影)にして、押せる場所だとひと目で分かるようにした。球団グッズのカードにも軽くホバーで拡大するアニメーションを足して、触ってみたくなる感じを狙ってみた。自分では「ちゃんと説明できてるつもり」でも、それが「読みやすい・買いたくなる」とは全く別物なんだな、と痛感した回だった。
第62回:「link.amazon」という見慣れないリンクを疑ってしまった話
おすすめページに球団のレプリカユニフォームを追加することになり、Amazonアソシエイトのリンクを送ってもらった。ところが届いたURLが`https://link.amazon/B0iBBgvn7`という見たことのない形。自分が知っているAmazonのリンクは`amazon.co.jp/dp/...`か短縮の`amzn.to`だったので、「これはドメインがおかしい、壊れたリンクかもしれない」と思って、一旦公開を止めて確認をお願いしてしまった。
スクリーンショットを送ってもらったら、Amazonアソシエイトの公式アプリの「商品を共有する」画面で、「ディープリンクが有効:アフィリエイトリンクがAmazonアプリに直接遷移します」という説明とともに生成された、正真正銘の公式リンクだった。自分が知っている形式が全てではなく、アプリ経由で生成する場合は専用の短縮ドメインが使われる、という新しいパターンを知らなかっただけ。壊れたリンクを疑って確認を挟んだこと自体は判断として間違っていなかったと思うけど、知らない形式=怪しいと決めつけず、ちゃんと根拠(今回で言えばスクリーンショット)を見て判断する大事さを再確認した回だった。無事7球団分のユニフォームリンクを「球団公認グッズ」に追加した。
第63回:レプリカユニフォーム、12球団分が揃った話
前回リンク形式で一悶着あったユニフォームだけど、残り5球団(日本ハム・西武・オリックス・ロッテ・楽天)分のリンクも届いて、無事12球団すべて揃った。今回はもう`link.amazon`の形式だと分かっていたので迷わず追加できた。それぞれ「天然芝デザイン ブラック」「アオフェス ブルーモーメント グラデーション」「Bs 夏の陣」「BSW ブラックサマー FUTURE EDITION」「ファンクラブ限定」と、球団ごとに全然違うテーマのデザインが並んでいて見ていて楽しい。
12球団が揃ったタイミングで、紹介文にあった「対応球団は随時追加予定」の一文もついでに「12球団すべて掲載」に書き換えておいた。こういう細かい但し書きを直し忘れると、後から見た人に「まだ揃ってないのかな」と誤解されるので、完成したタイミングでちゃんと直すところまでがセットだなと思う。
第64回:「広島だけDAZNにない」を記事にしてみた話
僕は広島ファンなんだけど、DAZNで中継を見ようとしたら広島の主催試合だけ配信されてないことに前から気づいていた。他球団のファンには当たり前にある選択肢が自分にはない、というのはなかなか地味にモヤモヤするもので、調べてみたら地元局への配慮とか球団独自の方針が理由らしいということが分かった。せっかくなら、この「広島ファンあるある」を記事にして、同じ悩みを持つ人の役に立てないかと思った。
ちょうど2024年シーズンはスカパーのプロ野球セットに入って、広島の試合を一軍も二軍も全部追いかけていた時期があったので、その実体験をそのまま書くことにした。きっかけは、色んな配信サービスに分散して契約していたら、オフシーズンごとにいちいち解約するのが面倒だったこと。1つにまとめられて、しかも二軍戦まで見られるのが決め手だった。個人的には、二軍の選手が一軍で活躍するまでの成長を追うのが好きなので、そこが一番刺さったポイント。今は契約してないけど、「広島も他球団もまとめて、二軍まで見たい」という人には今でも勧められるなと思って書いた。1つのネタを、おすすめページへの追記・新しい記事ページ・noteの3箇所に展開してみた回でもある。
第65回:虎テレは知ってたけど入らなかった、というリアルな話
広島カープの記事を書いたあと、「今はどのサービスで見てるの?」と聞かれて、改めて自分の視聴環境を振り返ってみた。今はスカパーを解約していて、J SPORTSで広島・DeNA・中日、Huluで巨人、FODでヤクルトを見る形に落ち着いている。1つにまとめるんじゃなくて、球団ごとにサービスを分けるやり方に変わっていた。
ただこれだと、阪神の主催試合だけがどうしても見れない。実は阪神には「虎テレ」という公式配信サービス(月額770円)があって、存在は前から知っていた。それでも入らなかった理由は単純で、阪神戦を見る頻度を考えたときに、そのためだけに毎月払うのはちょっとコスパが見合わないと感じたから。逆にHuluとFODは、野球専用じゃなくてドラマや映画も見られるので、野球以外の使い道がある分、契約を続けるハードルが低い。同じ「1球団のためだけの月額課金」でも、他に使い道があるかないかで納得感が全然違うんだな、というのが今回書いてみて改めて気づいたポイントだった。記事にも「今はサービスを分けて見ている」というセクションを追加して、スカパーの一括プランと、球団ごとに分ける今のやり方、両方のメリット・デメリットが伝わるようにしてみた。
第66回:広島ファン限定の話を、全球団向けに書き直してみた話
広島カープの記事はできたものの、「広島ファンじゃない人には関係ない記事」になってしまっている問題があった。もっと広い読者に届くページを作りたいと思い、今回は「まず60点のドラフトを作って、客観的に評価してから100点に仕上げる」というやり方を試してみた。最初のドラフトは、正直よくある紹介記事の域を出ていなくて、自分で読み返しても「これじゃ自分ごと化できないな」と思うものだった。比較表もなく、デメリットにも触れず、CTAも最後に1個だけ。
そこで気づいたのが、広島以外のファンにとっての本当の刺さりどころは「一軍が見れるかどうか」じゃなくて「二軍まで見れるかどうか」だということ。DAZNは11球団の一軍は見れるけど、二軍はどの球団もほぼ対象外。うちのサイトはドラフトゲームなので、「二軍の成長株を見たい」という需要と相性がいいなと思って、そこを軸に記事を組み立て直した。比較表、正直なデメリット、ミニFAQ、CTAを複数箇所に配置、と足していったら、最初のドラフトとは別物になった。作業の途中で、新しく作ったページへのリンクを他の全ページのナビゲーションに追加する作業をしたんだけど、広島カープの記事だけ「自分自身へのリンクを除外する」ルールのせいで見落としてしまい、そこだけリンクが漏れているミスをしてしまった。Playwrightでのチェックで気づけて事なきを得たけど、機械的な置換作業でも自己参照除外のページは個別に見ないといけないな、と改めて学んだ。
第67回:「DAZNも二軍対応してるはず」で書き直した話
中継サブスク比較ページを公開した直後、「DAZNも一応ファームの試合を一部対応してるはず」という指摘をもらった。正直「そうだっけ?」となって調べ直したら、2026年からDAZNに野球専用プラン「DAZN BASEBALL」(月2,300円・年間契約)ができていて、広島を除く11球団の一軍に加えて12球団のファーム戦にも対応していることが分かった。ページを書いた時点ではDAZNは二軍非対応という前提で組み立てていたので、根っこの部分が崩れてしまった。
作り直すにあたって、「二軍が見れるのはスカパーだけ」という当初の売り文句はもう使えないので、正直に書き直すことにした。今のスカパーの本当の強みは、二軍対応そのものではなく「広島の一軍を含めて12球団を完全にカバーしていること」と「年間契約なしで月単位で契約できること」の2点。DAZN BASEBALLの方が月額は安いし二軍も見られるので、正直に「二軍だけなら安いDAZN BASEBALLの方がいい」と書くことにした。都合の悪い情報でも、後から誤りだと分かったら早めに直して、ちゃんと比較できる材料を残しておく方が、結果的に読んでくれる人のためになると思う。
第68回:「DAZNも月契約できる」で3社比較にした話
DAZN BASEBALLが年間契約のみだから「月単位で契約したいならスカパー」と書いたら、すぐに「DAZNも月契約できるよ、年額プランがあるだけで」と指摘をもらった。これも調べ直したら、その通りだった。DAZN BASEBALLは確かに年間契約のみだけど、通常のDAZN STANDARDプラン(月4,200円、野球以外のスポーツも含む)は1ヶ月単位で自由に解約できる。しかも配信されている野球コンテンツ自体はSTANDARDでもBASEBALLでも同じで、価格と契約期間、それに野球以外を見るかどうかが違うだけだった。
「DAZN」とひとまとめにして比較していたのが良くなかった。BASEBALLとSTANDARDを分けて、月契約できるプラン同士(DAZN STANDARD 4,200円 と スカパー 4,483円)で比べ直すと、実はほぼ同料金で、その上でスカパーを選ぶ意味は「広島の試合もまとめて見られること」に絞られる、というのが正しい整理だった。表を3列に増やして、対応球団・二軍対応・月額・契約形態を全部並べ直した。1回直したつもりでも、また別の見落としが出てくる。この記事に関しては、指摘してもらうたびに前提がひっくり返る展開が続いている。
第69回:DAZNの二軍対応、実は広島だけ抜けていた話
比較表を3社に直した直後、「DAZNは広島の二軍も対応してるの?」と聞かれた。表には二軍の欄を「○ 12球団対応」と書いていたんだけど、正直そこまで細かく確認していなかった。慌てて調べ直したら、DAZNの公式発表は「ファーム11球団公式戦」と明記されていて、12球団ではなく11球団だった。除外されているのはやっぱり広島で、一軍だけじゃなく二軍もDAZNの配信対象外だった。
冷静に考えれば当然といえば当然で、放映権を球団単位で管理していて、広島が地元局への配慮からDAZN全体に出さない方針を取っているなら、一軍だけ特別扱いで二軍は出す、という理由がそもそもない。「DAZNもファームは対応した」という一つの事実に気を取られて、「じゃあ全球団分なのか」というところまで確認しないまま表に書いてしまっていた。二軍の欄を「11球団(広島は対象外)」に直して、一軍の欄と同じ表記に揃えた。結果的に「広島は一軍も二軍もDAZNでは一切見れない」というシンプルな話に戻ったんだけど、遠回りしてでも正確な形に落ち着けてよかったと思う。
第70回:中日戦も一部見れないと聞いて、脚注を足した話
広島の話が落ち着いたところで、今度は「中日主催試合も一部見れないはず」という話をもらった。比較表では中日を「対応11球団」の中に含めていたので、また表を直すことになるのかと身構えたんだけど、調べてみたら広島とは事情がだいぶ違った。中日の場合、DAZNで見れないのは「対巨人戦」かつ「東海テレビ・CBC制作」という条件が重なった一部の試合だけで、中日対阪神や中日対DeNAといった他カードの主催試合は普通に見られる。
広島みたいに主催試合を丸ごと配信していない全面除外と、中日みたいに条件が重なった一部の試合だけ見れない部分的な除外は、性質が全然違う話だった。表の「対応11球団」という大枠の分類自体は変えず、そのままだと中日ファンが「じゃあうちも全部見れないのか」と誤解しそうだったので、脚注に「中日は対巨人戦の一部のみ配信対象外」という補足を追加した。1つの表で全部を語ろうとすると、こういう「全面除外」と「部分除外」みたいな粒度の違うルールが混ざってしまうことがあるんだな、と気づいた回だった。
第71回:キャップに楽天とAmazon、両方のリンクを並べてみた話
おすすめページの球団キャップは今まで楽天のリンクだけを貼っていたんだけど、巨人のキャップのAmazonリンクをもらったタイミングで「楽天とAmazon、同じ商品で両方貼れるの?」と聞かれた。よく考えたら別々のアフィリエイトプログラムだから競合する理由はなくて、むしろ普段使ってるサービスが人によって違うので、両方置いておいた方が取りこぼしが減るはず。ただ、12球団全部を一気にAmazon化するのは大変なので、まずは巨人の分だけ試しに追加することにした。
今まではカード全体が1つのリンクになっていて、カードをクリックすると楽天に飛ぶ作りだった。そこに2つ目のリンクを足そうとすると、リンクの中にリンクを入れることになって、それはHTMLとして無効になってしまう。なので、カード自体はただの箱(div)にして、その中に「楽天」「Amazon」のボタンを横に並べる構成に作り替えた。ボタンの色は球団カラーの背景に馴染ませつつ、半透明の黒を重ねて視認性を確保した。今のところリンクが2つあるのは巨人だけで、他の11球団はボタンが1つだけの見た目になっているけど、これは今後Amazonリンクが増えるごとに自然と埋まっていく想定。
第72回:Amazonリンク、一気に5球団分揃った話
巨人分だけ試しに追加した直後、阪神・DeNA・ヤクルト・中日・広島の5球団分のAmazonリンクがまとめて届いた。仕組み自体は巨人の時にすでに作ってあったので、あとはTEAM_GOODS_LINKのオブジェクトに各球団のamazonUrlを追記するだけで済んだ。これで楽天とAmazon両方選べる球団が6つに増えて、ちょうど12球団の半分まで来た。
1球団ずつ試して仕組みを固めてから、まとめて反映する、という流れがうまくはまった回だった。最初から12球団分いっぺんにやろうとしていたら、途中でカードの表示が崩れたときにどこが原因か分かりにくくなっていたと思う。小さく試してから広げる、というやり方の効果を実感できた。残り6球団(ソフトバンク・オリックス・日本ハム・西武・楽天・ロッテ)も、リンクが届き次第同じように追加していく予定。
第73回:楽天イーグルスだけAmazonになかった話
残り6球団のうち、ソフトバンク・西武・日本ハム・オリックス・ロッテの5球団分のAmazonリンクが届いて追加した。ところが楽天(東北楽天ゴールデンイーグルス)だけは「Amazonになかった」とのことで、リンクが来なかった。球団名と通販サービス名がどちらも「楽天」でちょっとややこしいけど、単純に楽天イーグルスのニューエラキャップがAmazonでは今のところ扱われていない、というだけの話らしい。
結果として、楽天以外の11球団は楽天・Amazonの両方から選べるようになり、楽天だけは今まで通り楽天リンクのみになった。楽天イーグルスのグッズを楽天でしか買えない、というのは考えてみれば自然な話で、変に気にするようなことでもない。見つかり次第追加する、くらいのスタンスで残しておくことにした。これで球団公認キャップのAmazon対応はひとまず一区切り。
第74回:プロスピにもAmazonリンクを足した話
キャップのAmazon対応が一区切りついたところで、今度は「プロ野球スピリッツ2026」のAmazonリンクをもらった。こっちは今まで楽天のボタンが1つだけの、いわゆる普通のCTAボタンだったので、キャップみたいなカードのボタン2つ並べる構成をそのまま流用する形で対応した。ボタンを横並びにして「楽天で見る」「Amazonで見る」の2つに分け、それぞれちゃんとPR表記とsponsored属性を付けた。
思えば、最初は楽天のリンクしか置いていなかったこのページも、今ではキャップとプロスピの両方でAmazonと楽天を選べるようになった。1つの仕組みを作ったら、似たような場所に横展開していくだけで済むようになってきていて、地道に積み上げてきた分だけ作業が楽になっているのを感じる回だった。
第75回:パワプロも追加して、野球ゲーム2本立てにした話
プロスピに続いて、今度は「パワフルプロ野球2026-2027」のAmazonリンクをもらった。当サイトの能力値はプロスピA基準だけど、パワプロは野球ゲームとしてはプロスピと並ぶ有名シリーズなので、別の魅力として素直に紹介することにした。調べてみたら、この最新作はサクセスモードが初めて登場した1996年の『実況パワフルプロ野球3』から数えてちょうど30周年にあたる記念作品で、歴代サクセスを巡る「パラレルオールスターズ」編や、実在の侍ジャパンで戦うWBCサクセスが新しく収録されているとのことだった。
紹介文を書くにあたって、プロスピと同じ「当サイトの元ネタ」という切り口は使えないので、パワプロならではの魅力(デフォルメされた見た目、サクセスモード、30周年という節目)を素直に書くことにした。同じ野球ゲームでも別のシリーズとして正直に紹介する方が、変に持ち上げるより誠実だと思う。これで「ゲーム」セクションはプロスピとパワプロの2本立てになった。
第76回:パワプロにも楽天リンクが届いた話
パワプロをAmazonリンクだけで公開した直後、「こっちが楽天」と楽天のリンクも届いた。プロスピの時と同じ流れで、ボタンを楽天・Amazonの2つに分けて並べる形にした。作業自体はもう何度もやっているので迷うところがなく、リンクを差し込んで確認するだけで終わった。
これで「ゲーム」セクションのプロスピ・パワプロ、どちらも楽天とAmazonの両方から選べるようになった。1つずつ地道にリンクをもらって反映する作業が続いているけど、同じパターンの繰り返しになってきたぶん、1件あたりの作業時間はかなり短くなってきている。仕組みを先に固めておいてよかったと改めて思う回だった。
第77回:見出しがカラフルなリンクに埋もれていた話
「キャップ、レプリカユニフォームの文字が小さすぎる。リンクのブロックがカラフルだからそこに目がいって全然気づかない」という指摘をもらった。見出しを見返したら、確かに`text-xs`のグレー文字で、しかもすぐ下に球団カラーのカードや帯がずらっと並んでいる。文字自体は小さくなくても、周りが派手だと相対的に埋もれてしまうんだな、というのを実感した。
直し方としては、他の商品名(プロスピやスカパーの見出し)と同じ太字・黄色の目立つスタイルに揃えて、下線も足した。同じセクション内で見出しの優先度がバラバラだったのが良くなかったんだと思う。派手なコンテンツを置く場所ほど、逆に見出し側もそれに負けない強さを持たせないといけない、という当たり前のことに気づかされた回だった。
第78回:AIチームに謎の「代打」が紛れ込んでいた話
ゲームのコードを見返していて、AIチームのスタメン編成で気になる箇所を見つけた。AIは投手を最低3人確保するようにはなっているものの、それ以上は特に制限がなく、終盤の指名で投手ばかり選んでしまうと野手が9人に届かないことがあった。その場合、足りない枠は能力値50固定の「代打」という架空の選手で穴埋めされる作りになっていて、しかも編成ランクの計算にはこのダミーが含まれないのに、実際の試合にはちゃっかり出場してくる、というズレがあった。
直し方としては、投手の最低人数(3人)をすでに満たしているなら、野手が9人揃うまでは指名候補から投手を外すようにした。ドラフト全12巡のうち、野手9人・投手3人でちょうど枠が埋まる設計なので、投手を余分に取らせないだけで解決する話だった。あわせて、先発投手の交代がイニング経過だけで決まっていて、好調な投手でも7回になれば問答無用で継投していたのも直した。実は球数(スタミナ消耗)はもともと裏で計測していたので、それをちゃんと交代判断にも使うようにして、まだ球数に余裕がある投手は7〜8回でも続投できるようにした。9回は今まで通り必ず抑えを送る。
第79回:選手を82人増やして178人にした話
ずっと96選手(12球団×8人)で回してきたけど、そろそろ選手層を厚くしたいと思って、実在選手のデータを追加することにした。今回は野手36人・投手46人の合計82人を追加して、全178選手まで増やした。作業自体はシンプルで、能力値と守備位置を選手ごとに確認しながら、既存のデータと同じ形式で登録していっただけなんだけど、実在の選手データなので「本当にこの数値・この所属で合っているか」の確認には普段よりだいぶ時間をかけた。
地味に手間だったのが重複チェック。外国人選手は登録名がフルネーム(例:「トレイ・キャベッジ」)になっているので、単純に苗字だけで検索すると別人扱いされてしまい、実はもう登録済みだった選手を新規として二重登録しかけた場面が何度かあった。機械的に名前を照合するスクリプトを書いて事なきを得たけど、これは今後選手データを増やすたびに気をつけないといけないポイントだと思う。
第80回:既存選手のサブポジションが結構抜けていた話
選手を82人追加する作業の途中で、参考にしていたデータには守備位置ごとの数値が細かく載っていることに気づいた。せっかくなので、新規で追加する選手だけでなく、もともと登録済みだった選手についても同じデータで見比べてみることにした。すると、牧秀悟や田中幹也、泉口友汰など、実際は複数のポジションを守れるはずの選手なのに、サブポジションが登録されていない選手がちらほら見つかった。
例えば田中幹也は二塁しか登録されていなかったけど、データ上は遊撃でもかなり良い数値が出ていたので追加した。既存のデータを削ったり書き換えたりするのは怖いので、今回はあくまで「新しく分かったポジションを追加するだけ」に留めて、7人分のサブポジションを更新した。地味な修正だけど、こういう抜け漏れは一度データを整備し始めると芋づる式に見つかるものだなと思う。
第81回:先発しかできない投手が抑えに回されていた話
選手を82人増やしたあと、自分でプレイしていて「あれ、この投手、先発適性しかないのになんで抑えで投げてるんだ」と気になる場面に何度か遭遇した。コードを見返してみたら、AIがスタメンを組むときの投手選びで、中継ぎ・抑え適性を持つ投手が誰もいない場合、ロースターの中で単に余っている投手を先頭から機械的に割り当てているだけだった。選手を増やす前は投手が3人しかいなかったので目立たなかったけど、投手層が厚くなったことで、たまたま先発タイプばかり余ってしまうチームが出てきて、このミスマッチが表に出やすくなったんだと思う。
直し方としては、まず役割適性(bestPos)を持つ投手から探すのは変えずに、それでも見つからない場合の落とし所を「ロースター順で最初に余った投手」から「残った投手の中で、その役割のスコア(getPosScore)が一番マシな投手」に変えた。もともと適性のミスマッチにはペナルティが掛かる評価式があったので、それをそのまま流用する形。地味な直しだけど、これで少なくとも「先発一本の投手が、他にもっと向いている投手がいるのに抑えに回される」というケースはなくなったはず。
第82回:先発が大量失点しても継投しない話
自分でプレイしていて、先発投手が序盤で打ち込まれて大量失点しているのに、なかなか継投してくれないことに気づいた。継投のロジックを見返してみたら、球数(スタミナ消耗)とイニング経過しか見ておらず、何点取られたかは一切判断材料になっていなかった。現実の監督なら、球数が少なくても炎上している投手は早めに諦めるはずなので、これは直したい。
ただ、直す前に「継投を早めると、今度は中継ぎ・抑えの2人だけで残りイニングを背負うことになって、そっちが疲弊してまた失点しやすくなるのでは」という懸念が自分の中で出てきた。中継ぎ陣を厚くする話は選手の指名人数と合わせて別途じっくり考えることにして、今回はあくまで控えめな基準(3回を投げ切らないうちに5失点以上)に絞って導入した。明らかな大炎上のときだけ発動する形なので、中継ぎ・抑えへの負担が急に増えすぎることはないはず。
第83回:実績を4つ追加した話
選手を82人増やしたこともあって、実績もそろそろ増やそうと思い立った。最初に浮かんだのは「新しく追加した選手だけで編成する」みたいな実績だったんだけど、よく考えたら「新規追加選手」というのはこっちの実装の都合でしかなくて、ゲーム内では何も区別されていないことに気づいた。プレイヤーからすると「この選手が新顔かどうか」なんて分からないわけで、実績の条件にするには不自然だなと思ってボツにした。
代わりに、外国人選手を3人フル指名する「フォーリナーズ」、4球団以下の選手だけで編成する「一点集中」、担当球団の選手を1人も使わない「完全アウェー」、能力値の低い選手だけでCランク以上を取る「雑草魂」の4つを追加した。「雑草魂」は条件を決める前に、実際そんな低能力の選手がいるのか一度データで確認してみた。全ステータス60未満の選手は0人だったので、その条件だと誰も達成できない実績になるところだった。平均68未満という基準に落ち着けたところ、対象になる選手は打者33人・投手11人いたので、狙って集めれば十分達成できるはず。
第84回:対戦AI球団数を6球団まで増やした話
選手データを増やした流れで、「AI対戦相手ももっと増やせないか」という話になった。これまで最大3球団(計4球団)までしか選べなかったのは、単純に選手プールが96人しかなくて、それ以上チームを増やすと野手が足りなくなるからだった。今回選手を178人まで増やしたので、まず本当に足りるのか確認してみたところ、捕手だけ見ても23人いて、野手全体で96人ある。9人×10球団=90人なのでギリギリ10球団分くらいはいけそうだったけど、キリよく最大5球団(計6球団)までにしておいた。
実際にやってみると、選手プールよりもプレーオフのトーナメント表の方が壁だった。今までのコードは2球団・3球団・4球団専用の分岐しかなくて、5球団以上を渡すと5位以下のチームがトーナメントに一切登場しないまま消えてしまう作りだった。上位シードほど早いラウンドを不戦勝で免除される、という考え方自体は3球団・4球団の時と同じなので、それを5〜6球団でも成り立つように、各試合の勝者がどこに進出するかを明示的に指定する形に書き直した。地味な内部構造の変更だけど、これで球団数が増えても崩れない土台になったはず。
第85回:自分の試合もスキップできるようにした話
対戦AI球団数を増やした直後、「トーナメント的には8球団の方がブラケットが組みやすいかもしれない」という話と一緒に、「試合数が多くなる分、自分のチームの試合もライブ観戦するかスキップするか選べるようにしたい」という要望をもらった。コードを見返してみたら、これまでは自分のチームが絡む試合は問答無用でライブ観戦画面に飛ぶ作りになっていて、AI同士の試合だけ「⚡ 結果を見る」で一瞬で終わらせられるようになっていた。球団数を増やしたばかりなのに、これでは逆にテンポが悪くなってしまう。
直し方はシンプルで、自分のチームが絡む試合の画面に「⚡ 結果だけ見る(観戦をスキップ)」ボタンを追加して、押したときはAI同士の試合と同じ即時シミュレーション処理に流すようにした。ライブ観戦したいときは今まで通りのボタンを押せばいい。8球団対応の方は、5〜6球団の時よりもブラケットがシンプルになりそうな見込みはあるものの、規模がそこそこ大きい変更になりそうなので、これは別のタイミングでじっくりやろうと思っている。
第86回:対戦AI球団数を8球団まで増やした話
前回、8球団対応は「規模が大きくなりそうだから別のタイミングで」と先送りにしていたんだけど、「最大8にして」とすぐに要望が来たので着手することにした。6球団までの時は上位シードの不戦勝が絡んで、どの試合の勝者がどの試合のどちらの枠に進出するかがチーム数ごとに結構複雑だったんだけど、8球団はちょうど2の累乗なので、実は一番素直に組めるパターンだった。不戦勝が一切なく、8チーム全員が準々決勝から横並びでスタートする、普通のトーナメント表になる。
7球団の方は6球団までと同じ考え方で、1位シードだけ準々決勝を免除する形にした。選手プールの方も、8球団分(野手72人・投手24人)を用意できるか確認したけど、野手だけで96人あるので余裕だった。ブラケットの組み方を各チーム数ごとに直接書き出して、実際に7球団・8球団それぞれでドラフトからプレーオフの全試合を消化するところまでシミュレーションして、意図した組み合わせになっていることを確認してから公開した。
第87回:「第1打者」が誤植だった話
試合シミュレーションのログを見ていて「第1打者」「第4打者」という表記に指摘をもらった。言われてみればその通りで、野球で打順を表すときは「1番打者」「4番打者」であって、「第◯打者」という言い方はしない。コードを見たら、打順のインデックスをそのままログに埋め込むときに、他の「第◯巡」みたいな表記につられてか「第」を付けてしまっていたらしい。まず「1番打者」に直したんだけど、続けて「『打者』もいらない」という指摘が来て、確かに実況では「1番、◯◯」としか言わないなと思って「1番」まで削った。
直す前に他の場所にも同じ表記が残っていないか一応検索してみたけど、この1箇所だけだった。地味なテキストの修正だけど、試合を1球ごとに流し見していると毎打席目に入ってくる言葉なので、直しておいてよかったと思う。
第88回:中継ぎが一度も投げないまま抑えに交代していた話
延長戦で「抑え投手が12回まで投げ続けて打たれて負けた。まだ使っていない中継ぎがいたのに」という報告をもらった。継投のコードを見返してみたら、原因が分かった。「9回になったら抑えを送る」という予定表の判定が、先発がまだ一度も交代していない状態でもそのまま素通りしてしまい、先発→抑えへ2段階いきなり飛んでしまうケースがあった。先発が疲労もせず大量失点もせずに、たまたま9回まで無交代で来てしまうと、中継ぎが一度も登板しないまま抑えに交代してしまう、という抜け道だった。
直し方は、「9回だから抑えへ」という判定に「すでに中継ぎ以降に進んでいる場合のみ」という条件を付け足しただけ。中継ぎを経由してからでないと抑えには進めないようにした。Playwrightで実際に何十試合もシミュレーションして、中継ぎが一度も使われないまま抑えが長いイニングを投げ続けるケースが0件になったことを確認した。ただ、今の3人体制(先発・中継ぎ・抑え)だと延長が長引いたときに頼れる投手がそれ以上いないのは変わらないので、これは前に保存しておいた「投手の指名人数を増やす」アイデアの方でいずれ手を入れたいと思っている。
第89回:近本光司が6球団競合になった、という報告
野球好きの友人から「8球団でドラフトやったら近本光司が6球団競合になった。ファン目線でもさすがにここまで集中するのは違和感がある」という報告をもらった。辰己涼介もハズレでかなり競合する、逆に山川穂高は実績の割に評価が低すぎる、佐藤輝明も競合が起きていいはず、とも言われた。感覚だけで直すのは怖いので、実際に近本光司・辰己涼介・佐藤輝明・山川穂高の4人を12球団それぞれのスカウト傾向でスコア計算し直してみたら、近本光司は12球団すべてで「4人中1位」、山川穂高は12球団すべてで「4人中最下位」になっていた。長距離砲を欲しがっているはずの球団の評価軸で計算しても結果が変わらない。
原因は、打力・確実性・走力・守備力を単純に4等分で平均するスコア式にあった。走力と守備力の2項目で頭一つ抜けている選手が、長打力1項目だけ突出している選手に平均点で勝ってしまう。実はこれ、第12回で一度似た調整をしていたんだけど、その時つけた係数だとまだ差が足りていなかったらしい。打力側の重みをさらに強めて、走力・守備力の重みを下げるように調整して、12球団チェックをやり直したら、近本光司が「4人中1位」になる球団は12球団中1球団まで減った。実際に12球団分のAIドラフトを何度もシミュレーションしても、以前はほぼ全球団が近本光司に集中していたのが、牧秀悟のような他の好打者にも分散するようになったのを確認できた。ただ山川穂高だけは、走力・守備力の重みを下げてもミートの数値がそもそも他の選手よりかなり低いせいで、順位がほとんど動かなかった。これは重み付けというより山川穂高個人のステータス自体の妥当性の話になりそうなので、今回は手を付けずに保留にしている。
第90回:サーチコンソールを見て、見出しを1文字も直していなかったことに気づいた
アナリティクスとサーチコンソールの画面を見せてもらって、一緒に数字を眺めていた。検索パフォーマンスのクエリ一覧を見ると「aiドラフト」が表示回数530回でクリック13回、「ドラフトai」系も合わせるとトータル700回以上表示されているのにクリック率が2〜3%しかない。一方で「ドラフト会議シュミレーター」のような具体的なフレーズだとクリック率が20%を超えている。同じサイトなのにここまで差が出るのは変だと思って、トップページのソースを見直してみた。
気づいたのは、titleタグでは「AIドラフトシミュレーション」を一番前に置いていたのに、実際に画面に表示される見出し(h1)は英語の「DRAFT SIMULATOR」のままで、サブテキストにも「AI」という文字が一度も出てきていなかったこと。検索エンジンはページのタイトルだけでなく本文の中身も見て「このページが本当に何についてのページか」を判断するので、これでは「AIドラフト」というキーワードでの評価がタイトルタグだけに頼った弱いものになっていたはずだ。見出しを「⚾ AIドラフト」に変え、サブテキストも「AI球団と取り合う」という言い方に直した。最初は「AIドラフトシミュレーション」とフルで入れてみたらスマホ幅で2行に折り返してレイアウトが崩れたので、短く「AIドラフト」に削った。効果が出るまでには時間がかかると思うけど、まずは足元の当たり前のところが抜けていたのを直せてよかった。
第91回:「野球っぽさが伝わってこない」と言われてから写真を組み込むまで
個人開発の別のゲームのスタート画面(濃紺×赤のロゴタイプ、革やスタジアムの質感が効いたかっこいい画面)を見せてもらって、「うちのスタート画面やドラフトの画面ももっと野球っぽくしたい」と言われた。まずはCSSのグラデーションと破線のステッチ風の縁取り、コンデンスされた太字フォントだけで作ってみたんだけど、出来上がったものを見せたら「全然野球っぽさが伝わってこない」と一発で返された。言われてみれば確かにそうで、暗くてスポーティな雰囲気は出せても、革の質感やスタジアムの空気感はCSSだけでは無理がある。ChatGPTで画像を生成してもらう方向に切り替えて、「抽選箱」「ボールの縫い目」あたりのプロンプトを一緒に考えた。
実際に写真を組み込む過程で、地味だけど厄介なCSSのバグを見つけた。背景用に作った`.stadium-bg`や`.lottery-bg`というクラスにposition: relativeを書いていたんだけど、これが同じ要素についていたTailwindのfixedユーティリティと詳細度が同点になり、CSSのカスケードで後に読み込まれた方が勝つルールによって、抽選画面が画面全体を覆わずヘッダーの下に潜り込んでしまっていた。写真を全画面表示にして初めて気づいたバグで、地味な指定でもこういう事故は起きるんだなと思った。その後も「指名発表は抽選箱の方がいい」「スタメン編成の緑カードが背景から浮いて見える」というフィードバックをもらいながら、画像の配置を入れ替えたり、オーバーレイの色を緑がかったものにして写真自体を色シフトさせたり、カードにも他の画面と同じステッチ風の縁取りとアクセント帯をつけたりして、少しずつ馴染ませていった。
第92回:スマホでスクロールしたら背景が置いてけぼりになっていた話
新しいスタート画面をマージしてしばらくして、実機のスマホで見たスクリーンショットが送られてきた。「なんかくらいレイヤーみたいなのがかかってる」。見せてもらうと、画面の下の方、「その他のページ」ボタンやフッターのリンクのあたりに、うっすら黒い帯が被さって文字が読みにくくなっていた。パソコンの画面で確認していた時は気づかなかった不具合で、実機で長くスクロールして初めて再現するタイプのやつだった。
原因を追ってみたら、背景の写真と暗くするグラデーションを、スクロールする画面(position: fixedで中身がoverflow-y-autoのブロック)に直接つけていたのが良くなかった。こういう要素は「中身は内側でスクロールするけど、要素自体の箱の大きさは画面いっぱいのまま動かない」という性質があって、背景やグラデーションもその動かない箱を基準に配置される。つまり中身をスクロールしても背景だけは最初の位置に置いてけぼりになり、本来は見出しの下あたりにだけ効かせたかった暗いグラデーションが、スクロールで下に出てきた全然違うボタンやフッターに被って見えていた、というのが正体だった。直し方は、背景を画面全体ではなく、中身と一緒に流れる内側のブロックだけに付け替えること。見出しの写真は見出しのブロックだけを囲むようにして、その下の設定カードやボタンは普通の紺色の背景に戻した。抽選画面やスタメン編成の写真でも同じ作りをしていたので、念のため全部同じ直し方をしておいた。
第93回:「開票中」が背景に溶けて見えなくなっていた話
スクロールの不具合を直したその流れで、今度は指名発表画面のスクリーンショットをもらった。AI球団の指名内容をまだ隠している「開票中」の表示が、新しい写真背景と同化して見づらいという指摘だった。見返してみたら、このカバーはずっと`bg-gray-800`という暗いグレー一色で、以前の単色の暗い背景だと十分コントラストが出ていたのに、写真の暗い部分と重なると輪郭ごと馴染んで消えかけていた。カバーの色を一段明るいグレーに変えて、文字も少し大きく太くした。地味な色調整だけど、見た目を派手にした分だけこういうところの見直しも一緒に必要になるんだなと思った回だった。
第94回:せっかくの写真を暗くしすぎていた話
「バナーの画像をもうちょっと見せてもいいな」と言われて、改めてスタート画面を見返してみた。確かに、せっかく良い写真を用意したのに、見出しのブロック自体が窮屈な高さしかなく、しかも上に重ねていた暗いグラデーションがかなり強めで、抽選箱の写真はほとんど見えていなかった。文字を読みやすくすることばかり気にして、写真そのものの見せ場を削ってしまっていたんだと思う。
ブロックの高さをしっかり確保して、暗くするグラデーションも中央だけに軽くかける程度まで薄めた。文字の後ろに影をつけているので、多少薄くしても読みやすさは保てるはず、という判断だった。直してみたら、写真の中の抽選箱がちゃんと存在感を持って見えるようになって、最初にイメージしていた「ドラフト会場の空気感」にようやく近づいた気がする。
第95回:オリックスとロッテが写真に溶けて消えていた話
抽選画面と指名発表画面のスクリーンショットをもらって、「文字が読みづらい、球団カラーも暗くなってる」という指摘をもらった。見てみると、抽選の封筒の上に出る球団名が薄い影付きの白文字だけで、写真の明るい部分に重なると文字が溶けて見えなくなっていた。さらに、指名カードの球団カラーの帯を見て気づいたんだけど、これは色の値自体が変わったわけじゃなくて、オリックス(ほぼ黒に近い濃紺)やロッテ(グレー)みたいにもともと暗めの球団カラーが、新しい暗い写真の背景と重なって輪郭ごと沈んで見えなくなっていた、というのが正体だった。巨人のオレンジみたいな明るい色は平気だったので、球団ごとの色によって見え方の差が出てしまっていたことになる。
直し方は単純で、抽選の球団名には黒背景の丸いバッジを追加して、どんな写真の上でも文字が確実に読めるようにした。指名カードの方は、カード全体に白いリングの縁取りを追加して、球団カラーがどんな色でもカードの輪郭だけは背景から浮き上がって見えるようにした。特定の球団の色そのものを変えるんじゃなくて、周りとの区切りを別のレイヤーで足す形にしたので、今後どの球団カラーでも同じように安心して使えるやり方だと思う。
第96回:良かれと思って足したリングが逆効果だった話
前回、指名カードの球団カラーが背景に溶けて見えなくなる問題に白いリングの縁取りで対処したんだけど、公開してすぐに「指名カードの球団名に影みたいなのがかかって読みにくい、せっかくのカラフルな球団カラーがもったいない」という指摘が来た。見比べてみると確かにその通りで、鮮やかな球団カラーのすぐ外側に半透明の白を足すと、色自体が濁って見えてしまっていた。良かれと思って足した縁取りが、逆に見せたかった色を犠牲にしていたことになる。
リングはあっさりやめて、代わりにカード自体の影を濃く・大きくすることにした。背景から浮かせたいなら、色に触れずに影の力だけで浮かせればよかった、という単純な話だった。ついでに指名発表の見出し文字の影も、これまでよりくっきり出るように二重にして強めておいた。直してみたら、球団カラーは元の鮮やかさのまま、カードだけがちゃんと背景から浮いて見えるようになった。
第97回:指名発表画面だけ写真をやめることにした話
リングをやめて影に変えた直後、「リングを足す前から球団カラーのラベルが暗い、背景を追加する前と同じようにしてくれ」と言われて、ようやく話の根っこに気づいた。自分はずっと「縁取り」や「影」といった枠の付け方で解決しようとしていたんだけど、そもそもの原因は写真背景そのものだった。指名発表画面の主役は12球団ぶんの鮮やかな色分けされたカードで、これは元々ほぼ黒一色の背景の上でこそ一番映える作りだった。そこに暖色の写真を敷いた時点で、周りの色との対比でどうしても地味に見えてしまう。枠を白くしても影を濃くしても、写真という土台自体は変えられないので、対症療法にしかなっていなかった。
というわけで、指名発表画面だけ写真をやめて、元のほぼ黒一色の背景に戻すことにした。抽選画面やスタメン編成では写真がうまく効いていたので、全部の画面を一律に同じ見た目に揃えようとしたのがそもそもの無理筋だったんだと思う。画面ごとに「何を一番目立たせたいか」で背景の作り方を変えるべきだった、という当たり前のことに、何度か直しを重ねてやっと気づいた回だった。
第98回:写真も球団カラーもどっちも欲しい、をどう両立させたか
指名発表画面の写真をやめて黒一色に戻したら、今度は「色は直ったけど、背景画像が無くなったよ」と言われた。よく考えたら当たり前で、写真をなくせば当然写真は見えなくなる。でも聞いてみると、「写真は前みたいに全面に出しつつ、カードの色はそのまま保ってほしい」という話だった。両方いっぺんに叶えたいというのは無茶な注文に聞こえたけど、よく考えたら別に矛盾する話じゃなかった。
写真とカードが直接触れ合っている部分だけが問題だったわけで、それなら指名カードが並ぶエリアの下にだけ、黒に近い半透明のパネルを敷けばいい。カードのすぐ後ろは実質黒背景と同じ状態になるので色は鮮やかなまま保たれるし、パネルの外側(見出しの周りや画面の余白)は写真がそのまま見える。ギャラリーで絵を飾るときに黒いマット紙を敷くのと同じ発想だと思う。直してみたら、写真の雰囲気とカードの鮮やかさ、両方とも欲しかった通りになった。「どっちかを諦める」で終わらせずに、もう一段階考えられたのは良かったと思う。
第99回:半分透明にした自分のパネルが原因だった話
黒いパネルで解決したつもりが、次のラウンドのスクリーンショットで「全然戻ってない、また球団カラーが暗くなった」と言われた。最初は何が起きているのか分からなかったんだけど、よく考えたら自分で書いたコードを見返して単純な見落としに気づいた。パネルの不透明度を`bg-opacity-50`にしていて、これだと写真が50%の強さでそのまま透けて見えてしまう。せっかく黒いパネルを敷いたつもりが、実質「写真を薄めただけ」の状態になっていて、球団カラーを濁らせる原因を半分しか消せていなかった。
不透明度を90%まで上げて、ほぼ完全に光を通さない黒にした。あわせて、見出しの「第◯巡 選択希望選手」も影だけでは読みにくいという指摘をもらったので、こちらにも黒い背景のチップを敷いて確実に読めるようにした。数値を一つ変えるだけの話ではあるんだけど、「薄い黒」と「ほぼ真っ黒」の違いがこんなに見た目に効いてくるとは思っていなかった。中途半端に対策したつもりで実は半分しか効いていない、というのは自分の作業でもよくあることだと再確認した。
第100回:指名発表は黒、抽選は写真、と役割を分けて決着した話
パネルの不透明度を上げてもまだ「また直ってない」と言われて、さすがに一度立ち止まった。「指名時は黒背景のままで、くじ引きの時だけ画像を入れて」と、はっきり言われてようやく腹落ちした。自分はずっと「写真を活かしつつ球団カラーも守る」という一石二鳥を狙って、パネルの色や不透明度を少しずつ調整するという小手先の対応を繰り返していたんだけど、それ自体が間違っていた。指名発表画面の主役は12球団ぶんの鮮やかな色分けカードで、この画面に関しては写真を諦めて黒一色にするのが一番正解に近かった。
指名発表画面から写真を完全に外し、抽選画面とスタメン編成画面だけに写真を残す形で決着させた。振り返ると、最初に「野球っぽさを出したい」と言われた時、全部の画面を同じトーンで統一しようとしたのがそもそもの出発点のズレだったんだと思う。画面ごとに「本当に見せたいもの」が違う以上、背景の作り方も画面ごとに変えるべきだった。何度も同じ場所を直しながら、ようやくその結論にたどり着いた。
第101回:9回を投げきったのに「8.2」と表示されていた話
「完投したはずなのに投球回が8.2になってる」というスクリーンショット付きの報告が来た。見てみると確かに、相手打線が最後までしっかり打席に立っている(打数の合計がちゃんと9回ぶんある)のに、投手成績の回だけ26アウト分、つまり1個足りていない。3アウトごとに攻守交代してゲーム自体は正常に進んでいるはずなのに、投手個人の記録だけがズレる、という原因が絞りづらいタイプの不具合だった。
アウトを加算している箇所を一通り洗い出していったら、盗塁の処理だけ`state.outs++`(攻守交代用のアウトカウント)はちゃんと増やしているのに、投手の成績オブジェクト側の`stats.out`を増やし忘れていることに気づいた。三振やゴロアウトのところはちゃんと両方セットで書いていたのに、盗塁死のところだけ「ゲームを先に進める」ことだけ考えて成績表示のことを忘れていたらしい。盗塁死はそんなに頻発するイベントじゃないので、たまたま1試合に1回起きるかどうかくらいの確率でしか表面化せず、見つかりにくかったんだと思う。抜けていた1行を足して、盗塁死でアウトになった分もちゃんと投手の回数に加わるように直した。
第102回:仮のIDのまま82人分放置していた話
「新規で追加した選手の詳細を見るボタンを押しても、該当ページに飛ばない」という報告が来た。調べてみたら、以前「実在選手を78人追加した」回で、各選手のIDに仮の連番(90000001から)を振っていたことを思い出した。選手詳細リンクは週刊ベースボールONLINEというサイトの実在の選手ページIDをそのままこのIDに使う作りになっていて、既存の選手はちゃんと本物のIDが入っていたんだけど、後から追加した82人分だけ仮番号のまま本物のIDに置き換えるのを忘れていた。ネットワーク越しにそのサイトへ直接アクセスすることはできない環境だったので、検索で1人ずつ「site:(サイトドメイン) 選手名」のように調べて、URLに含まれる本物のIDを1人ずつ拾っていった。地道だけど確実な方法で、82人ぶん全部の実在IDに差し替えられた。
同じ報告の中で、「打順適性がおかしいかも」という指摘ももらった。吉川尚輝を例に見てみると、9打順のうち○が付くのは1番と9番の2つだけで、残りは△か▲か✕。計算ロジック自体は正しく動いていて、パワーの数値が平均よりやや低い選手は、パワーを重視する打順(3番・4番・5番あたり)でどうしても不利に出る、という仕様通りの挙動だった。ただ全96野手で確認してみると、11人はどの打順にも○以上が一つも付かない状態になっていて、これはさすがに厳しすぎるなと感じた。しきい値を少し緩めて調整したところ、11人だったゼロ判定は6人まで減り、吉川尚輝も1番・8番・9番の3枠で○が付くようになった。数字をいじるだけの単純な修正だけど、判定基準を緩めすぎず、それでいてちゃんと「得意な打順」が見える程度に着地させるバランスは意外と悩んだ。
第103回:3画面だけ置いてきぼりだった話
「ヘッダーとドラフト検索画面とランキング画面だけ見劣りする」という指摘とともに、CSSとHTMLのBefore/Afterがセットになったデザイン案をもらった。setup画面・くじ引き・スタメン編成はすでに紺×赤×金の「革・スタジアム」テイストで作り込んでいたのに、その3画面だけタダのTailwindデフォルトっぽい見た目のまま放置されていたのは自分でも気づいていなかった盲点だった。案自体は的確で、ヘッダーのグラデーション、検索・絞り込みのセレクトボックスを金の矢印つきダークテーマに、選手カードに立体感を、指名ボタンに`.btn-mvp`と同系統の押し込み演出を、Sランクのカードに光彩を、と狙いどころがどれも筋が良かった。
そのまま丸ごと当てはめても悪くはなかったんだけど、1箇所だけ気になるところがあった。提案では検索・絞り込みエリアの背景を丸ごと紺色にする話だったんだけど、実際のHTML構造を見ると、その紺色にする対象の中に「全選手/★リスト」の切り替えタブまで含まれていて、タブの非選択時の文字色(グレー)が紺の上だとかなり読みにくくなりそうだった。なのでタブ行は今の背景のまま残して、検索窓とセレクトボックスが並ぶ内側のブロックだけを紺色にするよう範囲を絞った。あとは提案のクラス名`.font-sport-header`が中身的には「フォント」じゃなくて「背景」の指定だったので、既存の`.stadium-bg`や`.lineup-bg`と揃えて`.header-bg`に直した。提案を鵜呑みにせず、既存のコードとの整合性をちゃんと見てから当てはめる、という当たり前のことをやっただけなんだけど、地味に大事な一手間だったと思う。ロジックには一切触れていないので、見た目だけがスッと揃った。
第104回:127球投げてもまだ交代しない先発
試合結果のスクリーンショットとともに「先発が5失点もしてるのに継投しなかった」という報告が来た。見ると確かに、8回127球を投げて5失点、しかもチームは負けている状況なのに、ずっと同じ投手が投げ続けている。中継ぎがロースターにいるのに全く使われていないのは、さすがに変だなと自分でも思った。
継投判定のコードを見返すと、原因はすぐわかった。大量失点による緊急降板の仕組みはもともとあったんだけど、「3回を投げ切らないうちに5失点」という早い回限定の条件になっていた。要するに序盤の大炎上だけを想定していて、試合の中盤から終盤にかけてじわじわ5失点した場合は対象外だった。しかもスタミナの計算式は球数だけを見ているので、球威のある投手だと127球投げてもまだ「万全」判定のままになる。結果、負けている試合でも「早い回の大炎上でもないし、疲れてもいない」という理由で、いつまでも先発を引っ張り続けてしまっていた。
直したのはシンプルで、「負けている試合なら、5失点した時点で回数に関係なくいつでも代えられる」という条件を追加しただけ。実際に床田投手と同じ状況(8回・127球・5失点・チームが負けている)を再現するテストコードを書いて、修正前は先発のまま、修正後はちゃんと中継ぎに交代することを確認できた。逆に同じ5失点でもチームが勝っている場合はテストしても交代しないままで、そっちまで巻き込んで過剰に継投してしまう心配もなさそうだった。負けている時くらい、いる中継ぎ陣をちゃんと頼っていいはずなので、実感としてもしっくりくる修正になったと思う。
第105回:130球は現代野球だと投げすぎ、という指摘
前回の修正を入れてすぐ、別のスクリーンショットが届いた。今度は先発が8回130球、もう一方は7.2回136球という試合結果。「現代野球だと130球は投げすぎ。先発は100球から110球くらいが目処かな」という指摘で、確かに言われてみるとその通りだった。前回直したのはあくまで「負けている試合で5失点したら」という条件で、球数そのものの上限は一切見ていなかった。スタミナの計算式は球数をスタミナ比でしか見ていないので、スタミナが高い投手だと130球を超えてもまだ「万全」判定のままになってしまう。今回のケースはどちらも失点は3〜4点程度で、前回直した「負けている・大量失点」のどちらの条件にも引っかからないまま、スタミナの数値だけを頼りに投げ続けていたのが原因だった。
スタミナに関係なく球数そのもので降板を判断する条件を新しく追加した。110球を超えたら無条件で交代対象、100球を超えていて、かつ3失点以上か負けている試合なら、その時点で交代対象にした。数字は「現代野球の目安は100〜110球」という指摘をほぼそのまま採用した形になる。5パターンくらいのテストケース(球数と失点と点差の組み合わせ)を作って、110球超えは無条件で交代する・95球ならまだ交代しない・102球でも2失点だけなら様子見・102球で3失点なら交代する、というのを一つずつ確認してから直した。前回は失点や試合状況だけを見ていて、今回は球数そのものを見る、と別の軸を追加した形になったんだけど、両方揃って初めて「現実の継投感覚」に近づいた気がする。
第106回:友達と対戦できるようにしたい、と言われて
「人対人対戦できるようにしたい」と言われて、正直ちょっと身構えた。今までずっとAI対戦しかなかったので、根っこの部分から作り直しが必要そうだったからだ。ただ「費用はかけたくない。収益化ができてから」という前提は最初にはっきりしていたので、まずバックエンド選びから始めた。無料枠だけで足りる構成にしたくて、クレジットカード登録なしで使えるFirebaseのFirestore(無料のSparkプラン)に決めた。ビルド不要の1枚のHTMLというこのサイトの作りを崩したくなかったので、Firebase側だけESモジュールの`<script type="module">`に閉じ込めて、`window.MP`というオブジェクト経由で今までのグローバル関数(`onclick`から直接呼ぶやつ)と橋渡しする形にした。地味だけどここの設計判断が一番時間がかかった気がする。
進め方を決めるのに役立ったのが、くじ引き画面のコードを読み直していて見つけた発見だった。抽選の封筒って、てっきりクリックした封筒によって当選確率が変わると思っていたら、実際のコードでは`Math.random()`で完全にランダムに勝者を決めていて、クリックはただ自分の封筒に「あなた」というラベルを貼るだけの演出だった。これに気づいたことで「対戦相手の封筒クリックを待つ同期処理」を作らなくて済むとわかり、設計がだいぶシンプルになった。ルームを作った側(ホスト)だけがこれまで通りのロジックをそのまま動かし、参加した側(ゲスト)は自分の手番だけ指名を送って、確定した結果を受け取って画面に映すだけの「薄いクライアント」にする、という役割分担に落ち着いた。まずは1対1だけの対応で、ゲスト側の画面はドラフト中の指名や指名ボードの更新はホストと全く同じように見えるはずだけど、抽選の封筒演出とスタメン発表のカード演出はホスト側だけの見た目になっている。本物のFirestoreに繋いだ2台での動作確認はこっちの作業環境からはできないので、疑似的なFirestoreを用意して2画面を同時に動かすテストまではやったけど、実際に公開してから友達同士で試してもらうのが本当の意味での初テストになりそうだと思う。
第107回:くじは一緒に選ばないと意味がない、という指摘
前回の続き。「参加者も一緒にくじを選んで誰が当たるかのドキドキ感が1番重要で盛り上がるポイントだから、そこは簡略化したらダメ」という指摘をもらった。図星だった。前回、抽選の演出はホスト側だけで再生してゲストには結果だけ届ける設計にしていたんだけど、それはまさに一番盛り上がる瞬間を削っていたことになる。抽選の封筒クリックが当選確率に影響しないコスメティックな演出だという発見に頼りすぎて、「じゃあゲストは見てるだけでいいか」と安易に結論づけてしまっていた。当選確率への関与と、参加者としての体験は別の話だったのに、そこを混同していた。作り直して、ホスト・ゲスト両方に同じ封筒画面を出し、両方が選び終えるまで開票を待つ形にした。片方が先に選んでも「相手が選ぶのを待っています」と表示されるだけで、両者揃って初めて一斉に封筒が開く。
作り直して2画面同時のテストを回していたら、抽選に負けた側がハズレ指名をやり直すケースで、なぜか両チームのロースターに同じ選手が入ってしまうバグに遭遇した。原因を追うと、ゲストが指名を送る「郵便受け」(Firestoreの1フィールド)が、抽選で確定せず持ち越しになったときに空にならないまま残っていて、次のハズレ指名のタイミングでその古い投函をホスト側が「新しい指名が届いた」と誤認して即座に確定させてしまっていた。単独プレイでは指名のたびに必ず新しい値で上書きされるので気づきようがない類のバグで、実際に2ブラウザを同時に動かして何度も試さないと表面化しなかったと思う。郵便受けを読み取った時点でこちらから空にしておくよう直したところ、8回中8回、抽選の勝ち負けどちらのパターンでも両者のロースターがぴったり一致するようになった。
第108回:ホストの指名ボタンが「押しても反応がない」
公開してすぐ、実際に友達と対戦を試してもらったら「ルームの作成に失敗しました」と出た。原因はFirestoreのセキュリティルールを設定画面で公開し忘れていたことで、そこはルール文をそのまま貼って公開してもらったら直った。次に来た報告が「参加者側は指名を送信できるけど、ホスト側は指名ボタンが押せない」。調べてみると、ボタンは押せていて指名もちゃんと送信されているのに、相手の指名がまだ届いていない間、画面が何も変わらないまま止まっているだけだった。参加者側は自分が送信すると「送信済み」という表示に切り替わるようにしていたのに、ホスト側にはその表示を作っていなかった。単独プレイだとAIは一瞬で指名を返すので気づかなかったけど、対人戦だと相手が考えている数秒〜数十秒、画面が固まって見えるのは致命的だった。ホストが指名を送った時点で自分の画面も「送信済み」に切り替えるようにして直した。
続けて「参加者側が指名をするとアナウンスがホスト側に流れるけど、ホスト側が指名しても、参加者側にアナウンスが流れない」という指摘も来た。これは設計時点から抜けていた不備で、ウェーバー中の指名アナウンス演出をホスト側の処理にしか組み込んでいなかった。ゲスト側は指名ログを受け取って静かに反映するだけの「薄いクライアント」という位置づけにしていたんだけど、抽選のくじの件で学んだはずの「体験を削るな」という教訓を、また別の場所で繰り返してしまっていた。ゲスト側でも、相手の指名が届いたときは同じアナウンス演出を挟んでから反映するように直した。自分の指名は分かっているのでアナウンスなしで即反映、相手の指名だけ演出を挟む、という単独プレイのAI表示と同じ区別をそのままゲストにも当てはめる形になった。
第109回:直したはずなのに「1巡目のアナウンスが出ない」
前回、ウェーバー中の指名アナウンスをゲスト側にも出るように直したはずだったのに、「1位のアナウンスがホスト側しか表示されない」という指摘が続けて来た。「1位」というのは1巡目のことで、言われて気づいたんだけど、前回の修正はウェーバー(2巡目以降)限定の条件で書いていて、1巡目の同時指名はそもそも対象外にしていた。1巡目は競合が起きたときの抽選演出だけはすでにゲスト側にも見せる作りにしていたので、そっちで満足してしまっていたんだと思う。競合しなかった普通の1巡目の指名には、結局何のアナウンスも出ていなかった。
直し方はシンプルで、「ウェーバー中かどうか」で判定していたところを外して、相手の指名が届いたときは巡目に関係なく同じアナウンス演出を挟むようにした。ラベルだけ「ウェーバー指名」固定になっていたのを、1巡目なら「選択希望選手」と出し分けるようにする必要もあった。あと、抽選で決着した指名まで二重にアナウンスされると鬱陶しいので、抽選の開票演出で一度見せた選手はもう一度アナウンスしないよう区別を入れた。細かい話だけど、「相手の指名は必ず何らかの形で見せる」という筋を1本通しておかないと、こういう漏れが後から後から出てくるんだと思う。
第110回:7巡目で両方とも「待機中」になったまま止まった
「7巡目で両方とも待機中になって指名できなくなった」という報告が来た。これは正直まだ原因が特定できていない。回線状況を悪くしたシミュレーション(わざとタイミングをバラバラにした疑似Firestoreで2画面を同時に動かす)で12巡フルのドラフトを何度も再現テストしてみたけど、一度も同じ状態にならなかった。テストの途中で自分のテストコード自体のバグ(封筒のクリック判定がアラート画面に邪魔されて30秒タイムアウトするとか)を2つ見つけて直す一幕もあったけど、直した後は毎回きれいに最後まで進んだ。実機のFirestoreとの通信は開発環境から検証できないので、iPhoneの画面がちょっと暗くなっただけの一瞬の通信遅延とか、この環境では再現しようがない要因が絡んでいる可能性もある。
原因を追い切れないまま放置するのも良くないので、せめて次に同じことが起きたときにその場で回復できる手段を用意することにした。ドラフト画面に「🔄 同期し直す」ボタンを追加して、押すとFirestoreの最新状態を1回だけ取り直して自分の画面に強制的に反映する。原因が通信の取りこぼしであれ何であれ、これを押せば直るはずという保険。根本原因を潰せていないのは心残りだけど、ユーザーが実際に困った時にすぐ試せる逃げ道があるのとないのとでは全然違うと思うので、今回はこの形で一旦区切りをつけた。また同じ状況が起きたら、今度はスクリーンショットと合わせて教えてもらうことにしている。
第111回:抽選にならないと自分の指名結果が分からない
「くじ引きになったらくじ引き演出に移るけど、そうじゃなかったら何の知らせもない。自分の指名ができたのか分かりにくい」という指摘が来た。第109回で「相手の指名が届いたらアナウンスする」ところまでは直していたんだけど、よく考えたら自分自身の指名は対象外のままだった。単独プレイでは自分の指名は「今まさに自分で選んだ」ことが明白だからアナウンス不要、という判断をそのまま対人戦にも持ち込んでいたんだけど、対人戦は「送信済み」ボタンを押してから確定するまでにタイムラグがあるので、その間ずっと「本当に通ったのか」が分からない不安な時間になっていた。単独プレイの前提をそのまま横流しにしたのが良くなかった。相手の指名だけでなく自分の指名も同じアナウンス演出を出すように直して、これで指名が届くたびに必ず何かしらの合図が出るようになったはず。
同じタイミングで「3回やって3回ともホストが抽選に当たっている、確率に問題がないか確認して」とも言われた。当選判定のコードを読み返して、Math.random()で0か1かを引いて、封筒のどこに自分がいるかとは無関係に勝敗が決まる作りになっていることを確認した上で、念のため同じロジックを取り出して20万回シミュレーションしてみたところ、ホスト49.8%・ゲスト50.2%とほぼ完全に五分五分だった。理屈の上でも「自分がどの封筒を選ぶか」と「誰が当たるか」は独立しているはずなので、3連続は数学的にも8分の1(12.5%)の確率で普通に起こりうる範囲。ちゃんと疑わしいと思ったらコードを読み返して検証する、というのは今回も大事だった。
第112回:ドラフトの先、スタメンと試合がまだ繋がっていなかった
「AI採点がホスト側と参加者側で違う。ホストで88点が参加者側だと90点になる」という報告が来た。調べてみて自分でも驚いたんだけど、実はドラフトより先(スタメン編成・AI採点・試合シミュレーション)は、対人戦の仕組みを作り始めた時から一度も同期させていなかった。相手チームのスタメンは、お互い「相手ならこう組むだろう」と自動生成した推測で採点していたので、本人が実際に組んだスタメンとズレて当然だった。幸い採点そのものには乱数が一切絡んでいないことをコードを読んで確認できたので、直し方はシンプルで済んだ。確定ボタンを押したら即座に採点するのをやめて、まず自分のスタメンをFirestoreに送り、相手のぶんも届くまで「相手を待っています」という待機状態にする。両方揃った時点で、初めて両方のブラウザが同じ入力で採点を計算する。指名の時のような「早い者勝ちの権威役」は不要で、単純に両者が揃うのを待つだけの対称な作りにした。
実際に12巡のドラフトを最後まで自動で走らせて、両者が別々のスタメンを組んで確定ボタンを押す一連のテストをしたところ、採点結果はぴったり一致した。ただし試合シミュレーションの方はまだ手つかずで残っている。1打席ごとに200〜500回サイコロを振っている本格的なライブシミュレーションなので、「せーの」で同じ試合を見るには、ホスト側で1回だけ試合を計算してプレーの記録をまるごと参加者側に送り、同じ間合いで再生する仕組みが必要になる。次はそっちに取り掛かる。
第113回:試合を「録画」して両方に同じものを見せることにした
前回、スタメン編成とAI採点を同期させたところまでは良かったんだけど、「試合のシミュレーションもそれぞれ独立してて、違う結果になった」という指摘の後半がまだ残っていた。採点は乱数が一切絡まない決定的な計算だったからこそ「両者が同じ入力で待ち合わせて計算する」という対称な作りで済んだんだけど、試合シミュレーションはそうはいかない。1打席ごとに安打・四球・盗塁・失策の判定でMath.random()を何回も引いていて、1試合通すと200〜500回くらい乱数を消費する。これを両方のブラウザで独立に走らせたら、同じ入力でも結果が変わるのは当然だった。だから今回は指名のときと同じ「早い者勝ちの権威役」方式に戻すことにした。ホストが1回だけ試合をまるごと瞬時に計算して、打席ごとの実況ログと点数・回・アウト・走者の状態をティックごとに全部ためて"録画"にする。それをFirestoreへ送り、参加者側はその録画を同じ間隔(1秒ごと)で順番に再生するだけにした。乱数を送るんじゃなくて、乱数を引いた"結果"をまるごと送ってしまう発想。
実装していて一箇所ヒヤッとしたのが、同点のまま延長で決着がつかなかったときの勝敗判定用Math.random()だった。ホストが録画を作るときに1回引いたはずなのに、既存のrecordPlayoffLiveResult()側にも同じような乱数判定が残っていて、うっかりすると再生時にもう一度引いてしまい、ホストと参加者で勝者がズレる余地があった。試合終了時の勝敗はrecordPlayoffLiveResult()側では二度と引かず、録画作成時に一度だけ決めた結果をそのまま渡すように直した(実際には延長が続きすぎたときの安全弁が先に決着をつけるので、このケース自体めったに起きないはずだけど、起きたときにズレるのは嫌だったので念のため)。あと、早送りボタンは片方だけ押すと再生ペースがズレて同期が崩れてしまうので、対人戦中は思い切って隠すことにした。ここは将来ちゃんと同期できたら復活させたい。実際に両ブラウザで12巡ドラフト→スタメン確定→試合開始まで通しでテストしたところ、実況ログのテキストが1文字も違わず一致して、最終スコアも打撃成績も投手成績も優勝チームも完全に一致した。ようやく「一緒に同じ試合を見ている」感じになったと思う。
第114回:似たようなくじサイトが先に公開されて、焦って自分でも作った話
NPBのオーナー会議で「野球にもtoto的なくじを導入するか検討する」というニュースが出た数日後、もう似たコンセプトのサイトが公開されているのを見つけて焦った。しかも登録なしですぐ遊べる作りで、UIもかなり作り込まれていた。悔しかったけど、よく見ると向こうは「予想して当てる」オッズ型で、しかもニュースでは実際に検討されているのは「非予想系」(要するにtotoBIGと同じ、当たり外れに自分の予想が関与しない抽選型)だと分かった。だったら両方作ってしまえばいいと思って、まず架空ポイントだけで完結する予想くじを別リポジトリでサクッと作り、そのあとBIG方式の「ロトくじ」も追加した。ロトくじの配当を最初は固定倍率のテーブルにしていたんだけど、それだと当選者が何人いても配当が変わらないのは実物のtotoと違うと気づいて、購入総額をプールして当選口数で山分けする方式に作り直した。ここで還元率と1等の配分比率をくじ回ごとに管理者が変えられるようにしたので、地味に一番手こずった部分になった。
ある程度形になったところで、「これ、うちのドラフトシミュレーターに組み込めるじゃん」と思い立った。ただ、ドラフトの方はマルチプレイの部屋機能でFirebaseをすでに使っていて、しかも認証は使わずlocalStorageのIDだけで部屋を識別する作りだった。くじの方は匿名認証でユーザーごとのポイントを持ち回る前提だったので、無理に同じFirebaseプロジェクトに統合すると、せっかく動いている対戦部屋機能を壊しかねない。なので今回はFirebaseプロジェクトは分けたまま、見た目とナビだけこのサイトに合わせて`lottery.html`・`lottery-big.html`・`lottery-shop.html`の3ページとして追加することにした。ヘッダーの「☰ メニュー」がなんと27個のHTMLファイルに同じコードでコピペされていることに気づいて一瞬めまいがしたけど、今回はタイトル画面と遊び方ページの2箇所だけ直して様子見にした。他のページへの追加はまた今度。
第115回:今日の実際の試合でくじを試したら、投手の名前を取り違えかけた
Firebaseの設定がようやく一段落したので、「くじを今日の実際の試合で試してみたい」と言われて、架空の対戦カードを今日(9/22)の本物の6カードに差し替える作業をやった。ついでに「予告先発と成績も入れてほしい」というリクエストももらって、うちの選手データでいつも参照している週刊ベースボールONLINEのURLを教えてもらったんだけど、開いてみたら自分の作業環境からはそのサイトに繋がらなかった。しょうがないので検索エンジン経由で拾うことにしたんだけど、これが思った以上に危なかった。広島の先発を最初「九里亜蓮」で拾ってしまい、もう少し絞り込んだら実際は「玉村昇悟」だと分かって差し替えた。ここまではまだいい。
本当にヒヤッとしたのはDeNAの先発で、速報記事には「DeNA・東」としか出てこなかった。名前で検索し直すと、同じ「東」で始まる投手が最低でも2人いて、しかも紛らわしいことに一人はロッテの投手(東妻勇輔)、もう一人はDeNAの選手だけど投手じゃなくて捕手(東妻純平)だった。危うくロッテの投手をDeNAの先発として載せるところだった。結局「東克樹が12勝目を懸けて」という見出しの記事に辿り着いて、DeNAのエースである東克樹だと確定できた。ついでに戸郷翔征の防御率も記事によって2.24と3.24でブレていて、これはさすがに信用できる数字がどれか自分では判断がつかないので、成績表示は勝敗だけに絞ることにした。それでも6人の先発投手のうち、勝敗の両方が分かったのは4人だけで、残り2人は「◯勝目を懸けて」というその日の見出しから勝ち数だけ分かって負け数は不明、さらに2人は何も見つからなかった。分からない部分を無理に埋めずに空欄のまま出すことにしたのは、変な数字を実在の選手の名前と一緒に表示してしまう方が怖いと思ったからだった。
最後にFirestoreを覗いたら、前に動作確認で入れた架空の6試合(g1〜g6)が本物の6試合と一緒くたに残っていて、くじ画面が12試合表示のカオスになっていることに気づいた。慌てて削除しようとしたら、今度は自分の作業環境側の安全装置に「クラウド上のデータの一括削除」として止められてしまった。ローカルのファイルを消すのとはわけが違う操作だと判断されたらしい。これは自分では突破しないほうがいい類のブロックだと思ったので、ひとまずコード側の変更だけ先に反映して、ダミーデータの掃除は許可をもらってからにすることにした。
第116回:「くじ買っただけなのに勝手にXに飛ばされた」で青ざめた話
ロトくじを実際の試合で動かし始めたその日のうちに、「ロトくじ購入したら、かってにXに飛ばされた。これはうざいからやめて」という報告が来た。コードを見返して、自分がやらかしていたのに気づいた。的中して配当が出たときの「𝕏 結果をシェア」ボタンはちゃんとボタンを押した人だけが飛ぶ作りにしていたのに、購入直後にも同じshareOnX()を呼んでいて、しかもそっちはボタンなしでいきなり呼んでいた。多分「盛り上がってる感じを出したい」くらいのノリで書いたんだと思うけど、買っただけで強制的に外部サイトに連れて行かれるのは普通に迷惑な動きで、指摘されて初めて「これはひどい」と我に返った。しかも購入成功の合図をこの外部リンク頼みにしていたので、削除するだけだと今度は「本当に買えたのかどうかも分からない画面」になってしまう。代わりに、他のエラー表示で使っていたバナー領域に「○口購入しました」と出すように直した。これで買った瞬間に画面外に飛ばされることもなく、ちゃんと買えたことも分かるようになったはず。
同じ報告の中で「オーバー、アンダーってなに?特に説明もないから分からない」とも言われて、確認したら本当に何の説明もなかった。予想くじの画面には「勝利チーム」「合計得点」のどちらかを選ぶ、という一文はあったんだけど、そもそも「オーバー/アンダー」という言葉自体がブックメーカー的な言い回しで、初見だと合計得点の予想だとは分からない。開発している側は毎日触っているから「知ってて当然」の感覚になってしまっていたんだけど、フォームの下にあった説明文に「両チームの合計得点がそのラインを上回るか・下回るかを予想するもので、勝ち負けとは関係ない」という一文を足しただけで、多分だいぶ分かりやすくなったと思う。他人が触って初めて気づく分かりにくさは、自分でコードを書いているだけだと本当に見えない。
第117回:大山がDHではなくセカンドを守っていた理由
「AIのオーダーの組み方がおかしい。適性のない大山を二塁でつかったり、本来守備走塁型の岡林を打撃力重視のDHに入れたりしてる」という報告が来た。言われてみれば確かにおかしい。大山悠輔は一塁・三塁専門で、二塁の適性なんて一切ないはずだった。原因を辿っていくと、スタメン編成ロジックの中の`DEF_PRIORITY`という配列に行き着いた。「捕→遊→二→中→三→右→左→一→DH」という順番で、各守備位置に「まだ使われていない選手の中で一番点数の高い1人」を貪欲法で確定させていく処理だったんだけど、DHがこの配列の一番最後に置かれていた。
これがまずかった。DHの点数計算自体は「守備適性を問わず純粋な打撃力」で正しく作ってあったんだけど、処理順が最後だと、他の8つの守備位置を先に貪欲に埋め終わった後の「たまたま余った選手」がそのままDHに入ってしまう。本来DHに置かれるべき最強打者は、その前の段階で別の守備位置(適性があろうがなかろうが)に先に消費されてしまっていた。実際にPlaywrightで12球団分のドラフト→スタメン編成を内部シミュレートしてみたら、牧秀悟のようなチーム最強打者が適性のない遊撃で使われ、代わりに控え捕手がDHに収まっている、みたいなケースが何球団分も出てきた。直し方は拍子抜けするくらい単純で、配列の並びを「DH→捕→遊→二→中→三→右→左→一」に変えるだけ。DHを最初に確定させてしまえば、そこで一番打てる選手が正しく抜き取られ、残りの8人で守備位置を埋めていく形になる。
修正後にもう一度同じシミュレーションを回したら、近本光司や柳田悠岐、佐藤輝明のような本物の強打者がちゃんとDHに入るようになった。1行の並び替えだけでここまで挙動が変わるのは、自分でもちょっと拍子抜けした。ただ、まだ完全ではない。特定のポジションの専門選手がロースターに1人もいない球団だと、今度は別の非専門選手がそこに押し込まれるケースが残っていて、これは貪欲法という仕組み自体の限界に近い。とりあえず今回報告された「DHの逆転現象」は直せたので、ここで一区切りにした。
第118回:周東佑京の守備力が49だった、表の見間違い
「周東の守備力が72のはずなのに49になってる」という指摘が来た。周東佑京と言えば足と守備で飯を食っているタイプの選手で、49という数字はさすがに低すぎる。参照していた元データの表を見直したら、原因は自分の読み違いだった。表には「ミート・パワー・走力・捕球・スロ・肩力」という6項目の生スタッツとは別に、右側に「守備適性」という守備位置ごと(捕・一・二・三・遊・左・中・右)の数値の列がちゃんと分かれて存在していた。このゲームの「守備力」に本来使うべきなのは後者(ポジションごとの適性数値)だったのに、なぜか前者の「捕球」という汎用スタッツの数値をそのまま使ってしまっていた。周東の場合、捕球は49、実際の中堅手としての守備適性は72で、指摘された数字とぴったり一致した。
1人だけの間違いなら直して終わりだったんだけど、「同じような選手が他にいないか調べていたなら直して」と言われて青ざめた。実際に調べ直したら、ここ数日で追加・更新した二塁手・捕手・外野手・三塁手のほぼ全員が同じ間違い方をしていた。表を1枚ずつ見直して、正しい守備適性の数値に53人分まとめて差し替える羽目になった。ついでに見直している最中に、IDはもらっていたのに挿入スクリプトに入れ忘れていた選手が1人いたことにも気づいた(平川蓮、広島の外野手)。数字をたくさん扱う作業は、1個所のミスが実は全体に広がっている典型例だなと反省した。
第119回:なぜ最大8球団までしか対戦できなかったのか
「選手を増やせたなら、対戦AI球団数も最大7から11(計12球団・NPB全球団)に増やせないか」と言われた。実はNPBは12球団あるのに、このゲームはずっと「AI最大7球団+自分=計8球団」までしか選べない作りになっていた。なぜそんな中途半端な数だったのか正直よく覚えていなかったんだけど、多分理由は単純で、昔の選手データ(今より数十人少なかった)だと12球団フルでドラフトすると、特に捕手や二塁手のような層の薄いポジションで枯渇し、変なロースターになる球団が出ていたんだと思う。安全マージンとして8球団止めにしていたんだろう。
ここ数日でポジションごとの選手層を厚くしたばかりだったので、今なら12球団フルでも大丈夫なんじゃないかと思って、実際にPlaywrightで12球団・12巡のドラフトを5回シミュレーションしてみた。結果、毎回エラーなく完走して、しかもどの球団も捕手を最低1人は確保でき、スタメンも9人分きっちり組めていた。選手データベースの後ろ盾があってこそ言えることだけど、拍子抜けするくらいあっさり通った。UIのプルダウンに8〜11球団の選択肢を足すだけで、あとは既存のロジックがそのまま動いてくれた。地味な作業の積み重ねが後から効いてくる瞬間があるんだなと思う。
第120回:堅守の選手が損をする打順適性の計算式
「DHに守備適性のバッジが出るのはおかしい」という指摘と一緒に、「田中幹也や吉川尚輝を二番に置いても不向き判定になる。俊足タイプは上位打線向き、打力が低い選手は下位打線向きでいいのに」という打順適性そのものへの指摘をもらった。前者は単純な見た目のバグで、DHの守備欄をスキップするだけで直った。後者を調べていくうちに、もう少し根深い計算式の問題が見つかった。
打順適性は「その打順が重視する能力の配点で計算したスコア」と「その選手の素の平均」を比べて、平均より活きていれば○、活きていなければ▲、という仕組みになっている。ところがこの「素の平均」が、ミート・パワー・走力・守備力を単純に25%ずつ均等に足しただけの数字だった。守備力なんて8番(守備重視の打順)でしか大きく効かないのに、平均を出す時点では他の3項目と同じ重みで扱われてしまう。結果、田中幹也や吉川尚輝のような堅守の二塁手は、守備力の高さで「素の平均」自体が底上げされてしまい、守備をほとんど見ない2番のような打順では「平均より活きていない」という判定になってしまっていた。守備が上手いことが、逆に足を引っ張っていた形。
直し方は、比較基準を「4項目均等25%」から「実際の9打順の重み付けを平均した値」に変えるだけだった。守備力は9打順平均で見ると実質12%程度しか効いていないので、基準点の計算もそれに合わせて守備の影響を弱める。これで吉川尚輝は2番で○が付くようになり、田中幹也も▲から△まで改善した(田中幹也がそれでも○まで届かないのはミートの数値が吉川尚輝より低いからで、これは実力差として納得できる差だと思う)。ついでに全野手を対象に「どの打順にも○以上が一つも付かない選手」の数を数えてみたら、以前の96人中11人という水準から、選手が増えた今の124人中7人まで改善していた。数字がおかしいという指摘は、だいたい計算式のどこかに理由がある。
第121回:12球団で対戦したら、4球団が黙って消えていた
対戦AI球団数を最大11(計12球団)まで増やした直後に、「12球団のトーナメントで8球団しかいないのは、上位8組が予選落ちを免れたってこと?」という質問が来た。自分でもドキッとしたんだけど、これは仕様のつもりで答えていい話ではなく、単純に見落としていた不具合だった。プレーオフのブラケットを組む関数は2球団から7球団まではそれぞれ専用の分岐が用意されていたのに、8球団以上は全部まとめて「8球団のフラットなトーナメント」という1つの分岐で処理していた。要するに、9〜12球団で対戦しても上位8球団分の枠しか使わず、9位以下のチームはブラケットに一切登場せず、何の説明もなく消えていた。
対戦球団数を増やす機能を作ったときに、まさかプレーオフ側がこんな決め打ちになっているとは思わずチェックし忘れていた。ブラケットを9〜12球団分まで正しく拡張するのは、不戦勝の組み方まで含めて設計し直す必要があってそこそこ大きい作業になりそうだったので、今回は「上位8球団のみプレーオフ進出、9位以下は予選落ち」という扱いをそのまま仕様として採用することにした。実際のプロ野球でもクライマックスシリーズは上位チームだけが進出する仕組みなので、これ自体は不自然ではないと思う。ただし何の説明もなく自分のチームが画面から消えるのは不親切すぎるので、プレーオフ画面の一番上に全球団のAI採点順位を表示して、どこからが予選落ちかをはっきり分かるようにした。機能を1つ足すと、その機能が既存の別の場所の前提を壊していないか確認する範囲がじわじわ広がっていくなと実感した回だった。
第122回:収益化に向けて、地味な足場を固めた回
月10万円までのロードマップを整理して、まず着手したのがAdSense審査対策だった。運営者情報ページとお問い合わせページを新設して、全ページのメニューからリンクを足す作業をしたんだけど、これがとにかく地味に骨が折れた。全34ページ分の「☰メニュー」ブロックが少しずつコピペで増えてきた構造なので、フッターの文言を1行足すだけでも全ファイルを機械的に置換して回る羽目になった。ついでにプライバシーポリシーも読み返したら、Cookieの利用については既にそこそこ書けていたけど、「Google アナリティクス」「Google AdSense」という固有名詞をどこにも書いていなかったことに気づいた。審査する側は名指しでの記載を見ているはずなので、そこだけ追記した。
アクセス解析まわりも手を付けた。ドラフト完了・結果画面表示・シェア・試合終了のタイミングでイベントを飛ばすようにしたんだけど、地味に時間がかかったのはアフィリエイトリンクのクリック計測。最初はリンク1つ1つに目印の属性を付けて回ろうかと思ったけど、新しいリンクを足すたびに同じ作業が発生するのが目に見えていたので、hrefのドメイン名を見て自動判定する共通スクリプトに変えた。実装しながらコードを読み返していたら、Amazonのリンクが軒並み`link.amazon`という見慣れないドメインになっていて一瞬「壊れてる?」と焦ったんだけど、これは前に自分がリンク形式を勘違いして騒いだやつだったのを思い出した。画像も6枚をWebP化して、大きいものだと3割くらい軽くなった。地味な作業ばかりの回だったけど、この後の独自能力値の話につながる下準備だと思ってやっている。
第123回:ドラフト2026特集を整えて、予想機能を作った回
収益化の下準備が終わったので、次はドラフト当日に向けたコンテンツ強化に移った。まず手を付けたのが2026年ドラフト会議ページのテレビ・ラジオ・配信情報。実はここ、去年までの実績をもとに「TBS系列で中継」「ニッポン放送」みたいに書いてしまっていたんだけど、今年の分はまだ正式発表されていないことに気づいて、確定していない情報をあたかも決まったことのように載せているのはさすがにまずいと思い直した。せっかく書いた文章を消すのは少しもったいない気もしたけど、「発表待ち」表示に切り替えて、正式発表され次第埋める枠として整理し直した。あわせてシェアカードにストーリー用の縦長サイズも足したんだけど、実装直後のスクリーンショットを見たら「ソフトバンク」の球団名バッジだけ文字がはみ出して読めなくなっていて、他の球団は2〜3文字なのに気づかず90px固定幅で作っていたのが原因だった。文字幅を測って収まるまでフォントを縮める処理を足して直した。
その勢いで「12球団1位指名予想」機能も作ってみた。候補選手のデータをJSONで別ファイルに持たせて、名前・所属・ポジションだけを載せ、能力値は付けない方針にした。地味に悩んだのが保存形式で、ドラフト当日以降に答え合わせ機能を足すことを見越して、候補選手ごとに固定のIDを振っておき、球団名をキーにしたオブジェクトでlocalStorageに保存するようにした。予想のシェア画像も、結果発表画面のシェアカードと同じ紺グラデーションのトーンに合わせて作ったんだけど、これもPlaywrightで実際に画面を操作させてスクリーンショットを撮って確認したら、案の定さっきと同じ「ソフトバンク」のバッジがはみ出していたので、同じ直し方を先に当てておいた。毎回似たようなところでつまずくので、次からは球団名を扱う新しいUIを作るときは真っ先に確認しようと思う。
第124回:開始ボタン、実はもう画面の外にいた
ドラフト会議まで1か月を切ったので、トップページの一番上に「10/22 ドラフト会議まであと◯日|あなたの12球団1位予想は?」という細い帯を足すことにした。条件は一つだけで、スマホで開いたときに「ドラフト会議を開始する」ボタンがスクロールしなくても見えていること。帯を足す前に念のため今の位置を測ってみたら、iPhone 13くらいの画面(ブラウザのバーを除くと高さ664px)でボタンの下端が685pxの位置にあって、つまり帯を足すまでもなく、もう画面の外にはみ出していた。自分のスマホだと少しスクロールしてから押すのが当たり前になっていて、全然気づいていなかった。結局、スマホのときだけ上の余白やタイトル画像の高さを少しずつ詰めて、帯を足しても649pxに収まるようにした。帯の文言も最初は2行に折り返してしまって、それだけでまたボタンが押し出されていたので、1行に収まるよう文字を詰めている。
ついでにサイトマップと構造化データも見直したんだけど、ここでも一つ危ないことをしかけた。サイトマップに球団別中継ガイドの13ページが入っていなかったので「漏れてるな」と思って足したら、そのページは以前AdSenseの再審査のためにわざとnoindexにしていたものだった。検索に出さないと決めたページをサイトマップで「見に来て」と言うのはちぐはぐなので、慌てて元に戻した。FAQページの構造化データも、10問中4問の回答がページの文章とずれていて、ページ側を書き換えたときに構造化データの方を更新し忘れていたらしい。どちらも表からは見えない場所なので、こういうのは定期的に突き合わせるしかないんだろうなと思う。
第125回:「外野ならどこでも」をやめた日
ドラフト画面に「ポジションごとに守れる選手があと何人残っているか」を出す機能を作ろうとして、下調べで選手データを数えていたら、守備位置に「外」とだけ書かれている選手が18人いることに気づいた。「外」は左翼・中堅・右翼のどれでも守れる、というざっくりした扱いで、丸佳浩も近藤健介も万波中正も、外野3か所すべてを守れることになっていた。このままだと残り人数が実態より多く出てしまうので、守備適性の表を見ながら一人ずつ、実際に守れる位置だけに書き換えた。やってみると意外な選手がいて、たとえばサンタナは本職が右翼のはずなのに、表では左翼にしか値が入っていなかった。近藤健介も中堅は空欄で、左翼と右翼だけだった。ついでに森友哉とレイエスの守備位置に入っていた「DH」も外している。
書き換えたあと、12球団すべてをAIに任せたドラフトを旧データと新データで40回ずつ回して、指名順が変に崩れていないか比べてみた。18人ともほとんど変わらなかったんだけど、レイエスだけ平均の指名順が14番目から22番目まで下がっていて、一瞬何か壊したかと思った。調べてみると、AIには「同じポジションに代わりがいない選手ほど早めに獲る」という加点があって、これまでレイエスは本職がDHという珍しい扱いだったせいで、比べる相手がほぼいない"希少な選手"として実力以上に早く指名されていたらしい。一塁手として普通に比べられるようになった今の方が正しいと思うので、このままにしている。データを直したら、それまで隠れていた別のズレが見えてくる、というのはこれからもありそうだなと思う。
第126回:8秒かかっていた原因はフォントだった
PageSpeed Insightsでトップページをスマホ設定で測ってみたら、パフォーマンスが57で、最初に何かが表示されるまで8.4秒もかかっていた。ドラフト当日に初めて来てくれた人が、真っ白な画面を8秒眺めてくれるとは思えない。まずは怪しいものから潰そうと、結果画面のレーダーチャートでしか使っていないChart.jsを最初に読むのをやめたり、フォントの読み込み方を変えたりしてみた。ところが再計測すると57が58になっただけで、ほとんど変わらなかった。ついでにCLS(画面のずれ)が0から0.014に増えていて、直したつもりが別のところを悪くしていた。
そこで条件を一つずつ変えて測り直したら、犯人は日本語フォントのNoto Sans JPだった。きれいなフォントなんだけど、Googleから読み込むと文字の範囲ごとに約380個もの定義に分かれていて、それを画面に当てはめる計算だけで遅いスマホだと3秒くらいかかっていたらしい。試しにフォントを読まない状態で測ると、手元の計測で81点が98点まで上がった。画面のずれも、このフォントが後から届いて文字が差し替わる瞬間に起きていたことが分かった。結局、日本語はiPhoneならヒラギノ、AndroidならNoto系、Windowsなら游ゴシックと、それぞれの端末に最初から入っているフォントで表示することにした。Androidの標準フォントはNoto Sans JPとほぼ同じデザインなので、スクリーンショットを並べてもほとんど見分けがつかなかった。見た目のこだわりが、実は一番の足かせになっていたというのは、ちょっと意外だった。
第127回:スマホで触って初めて分かった2つの引っかかり
自分のiPhoneで12球団1位指名予想のページを開いてみたら、ページ全体がなぜか横にずれていて、上のボタンや球団名が左に見切れていた。調べてみると原因は選手の選択欄で、選択欄は一番長い選択肢の文字幅を自分の幅にしようとする性質があるらしい。候補には名前や所属が長い選手もいるので、選択欄が350pxくらいまで広がって、横幅375pxの画面から100px近くはみ出していた。パソコンの画面だと余裕があって気づかず、スマホで触って初めて分かった。選択欄が縮めるように指定して、長い選択肢は欄の中で省略して見せるようにした。ついでに他の36ページも同じ幅で測り直したけど、はみ出しているのはこのページだけだった。
もう一つはトップページの「その他のページ」のリンク一覧で、リンクの上に指を置いたまま少し長押ししてからスクロールしようとすると、リンクの半透明の複製が指にくっついてきて、画面がまったく動かなかった。iPhoneの、リンクをドラッグして別のアプリに渡せる機能が働いていたみたいだ。トップページはゲーム中に文字が選択されないよう文字選択を切っているので、長押しがそのままドラッグに回りやすかったんだと思う。リンクと画像はドラッグできないようにして、長押ししたまま動かしてもそのままスクロールできるようにした。スタメン編成で選手を引っ張って並べ替える操作は、別の仕組みで動いているので影響はない。
第128回:遊び方デモを作ったら、デモが「一番大きな表示」になっていた
トップページに来た人が、このサイトで何ができるのかを一目で分かるように、遊び方のデモを作った。最初は短い動画にするつもりだったけど、表示速度を落としたくないので、実際のゲーム画面を4枚切り出したスライドショーにした。「指名」「競合したら封筒で抽選(交渉権確定のハンコが出ているところ)」「スタメン」「採点結果」の4枚で、1枚15〜21KBくらいまで軽くできた。置き場所はトップのタイトル画像の枠の中だけにしたので、スマホで「ドラフト会議を開始する」ボタンの位置は1pxも動いていない。初めて来た人にだけ自動で流して、2回目からは右下の小さな「▶ 遊び方デモ」ボタンにたたむようにしている。
ところが、できたものを計測してみたら、LCP(一番大きな表示が出るまでの時間)が2.4秒から2.7秒に延びていた。調べると、ページの読み込みが終わってから流れ始めるデモの1枚目の画像が、タイトルよりも「大きな表示」として数えられてしまい、LCPがデモの出たタイミングに置き換わっていた。本物のスマホは読み込みがもっと遅いので、放っておいたら数秒単位で悪化していたと思う。スライドの画像を、LCPの計測対象にならない描き方(canvas)に変えたら、LCPはちゃんとタイトルに戻った。見た目は同じなのに測り方の都合で数字が変わる、というのは少し不思議な気分だけど、デモのために最初の表示が遅く見えるのは本末転倒なので、直せてよかった。
第129回:福永の守備力が、隣の列の数字だった
「中日の福永の守備力が48になってるけど、本来なら64のはず。サブポジションもついてない」という報告が来た。守備力は本職(福永なら三塁)の守備適性の値を使う決まりにしているのに、送ってもらった画面では三塁は64、一塁55、二塁48、左翼41。つまり二塁の48が入っていた。過去のやり取りを遡ってみたら、最初の登録では左翼の41を入れていて、翌日に「直した」つもりで二塁の48にしていた。どちらも隣の列を読んでいただけで、2回続けて同じ間違いをしていたことになる。表の数字が詰まっていると列がずれやすいのは分かっていたはずなのに、ちょっと恥ずかしい。
福永だけとは思えなかったので、これまでに送ってもらった表の画像を全部掘り起こして、野手127人を1人ずつ書き起こし直した。表に「ポジ」の列があるものはそれを本職として(ない表はいまの本職のまま)、守備力は本職の値、サブポジションはそれ以外に数字が入っている位置を高い順、というルールで機械的に突き合わせたら、58人が食い違っていた。守備力そのものがずれていたのは13人で、滝澤夏央は三塁の74が入っていて本当は二塁の81、矢澤宏太は左翼の61が入っていて本当は中堅の56、みたいな感じ。サブポジションのほうは、以前「妥当そうな組み合わせ」で推測して入れた分が悪さをしていて、近本光司や周東佑京に表にない右翼がついていたり、熊谷敬宥に捕手がついていたりした。AIだけで12球団のドラフトを40回ずつ回して比べてみたら、指名順は少し動いたけど、特定のタイプに偏るようなことはなかった。丸や西川史礁みたいに、本職より別の外野のほうが数字が高い選手は守備力が下がったので、本職の置き方はもう一度見直したほうがいいかもしれない。
第130回:守備力は「本職」じゃなくて「一番得意な位置」で決めることにした
前回、福永の件をきっかけに野手127人の守備力を「本職の数値」にそろえたら、丸佳浩が59から52、佐野恵太が61から51、西川史礁が68から57と、本職より別のポジションのほうが得意な選手の守備力がガクッと下がってしまった。一番極端だったのは三森大貴で、表の本職は中堅の51なのに二塁は67もあって、守備力51だとほとんど指名されない選手になっていた。ルールどおりに直したつもりが、別のところでおかしな数字を作っていたわけだ。
そこで「守備は本職の数字じゃなくて、各守備位置の数値の中で一番高いものを採用して」と決めてもらった。たしかに、守備力はその選手の守備のうまさを表す数字なので、一番得意な位置で見るほうが感覚にも合う。全員を計算し直したら、守備力が変わったのは12人で、三森が51から67、丸が52から59、佐野が51から61、柳田悠岐も左翼の52から右翼の58に上がった。本職(守備位置の先頭)はそのままなので、スタメンでどこを守らせたときにプラスが付くかは変わらない。AIだけの12球団ドラフトを40回ずつ回して比べたら、三森の平均指名順が125番目から85番目まで上がったくらいで、外野手ばかりが先に取られるような偏りは出なかった。同じ迷い方をしないように、このルールはプロジェクトの決まりごとにも書き足しておいた。
第131回:デモのせいで「表示が終わらないページ」になっていた
遊び方デモを公開した翌朝に、PageSpeed Insightsで測り直してもらった。パフォーマンスは64から63でほぼ横ばい、TBTは150ミリ秒から80ミリ秒に減っていたけど、Speed Index(画面がどれだけ早く描き終わるか)だけが5.0秒から6.1秒に悪化していた。手元で公開前と公開後を並べて測ると、画面の最後の変化のタイミングが0.7秒から3.0秒に延びていた。どうやら計測ツールは、計測の最後のコマを「完成形」として、それまでのコマがどれだけ完成形に近いかで点を付けているらしい。デモは読み込みの0.5秒後に出始めるので、最後のコマがデモのスライドになり、その前に表示されていたタイトル画像の画面が全部「描きかけ」扱いになっていた。前回LCP対策でcanvasに描くようにしたところまでは気にしていたのに、こっちは見落としていた。
最初の5秒はタイトル画像のまま見せて、計測が終わったあとでデモを始めるようにしたら、手元の数値は公開前と同じところまで戻った。おまけに、ユーザー補助の点が69から78に上がっていた理由も調べてみたら、デモに付けた読み上げ用の説明で採点項目が8つ増えて、それが全部合格していただけだった。つまり中身がよくなったわけではなく、しかもデモの下の丸いボタンが22pxしかなくて「押しにくい」という不合格が1つ増えていた。ボタンは24pxに広げたけど、デモが出る前に採点されるようになるので、次の計測ではたぶん69前後に戻ると思う。見かけの点数に喜ぶ前に確認しておいてよかった。
第132回:ページを開くたびにCSSを作っていたのをやめた
このサイトは最初から、見た目の指定にTailwind CSSを「CDN版」で読み込んでいた。手軽な代わりに、ページを開くたびにブラウザが約37万文字のスクリプトを読んで、ページの中身を見ながらその場でCSSを組み立てる仕組みで、公式にも「本番では使わないで」と書かれている。前から気にはなっていたけど、手元の計測ではCSSだけを差し込んだ代用品で測っていたので、影響は小さく見えていた。今回、同じCDNのスクリプトをnpmで見つけて本物で測り直したら、トップページで画面が固まる時間(TBT)が約0.5秒、ビルド済みのCSSに替えると約0.13秒で、最初の表示も2.2秒から1.2秒になった。代用品で測って「たいしたことない」と思っていたのは、完全に見積もりが甘かったと思う。
切り替えで一番怖かったのは、見た目がどこか崩れることだった。CDN版は画面に実際に出てきたクラスだけを見てCSSを作るので、JavaScriptで後から描くドラフト画面や抽選、スタメン、採点結果、試合画面などで、ビルド版に拾い漏れがあると気づきにくい。そこで、乱数と時計を固定して、切り替え前(本物のCDN)と切り替え後で37ページをスマホ幅とPC幅で撮り、ゲームも1本最後まで流して、抽選や試合を含む20画面を撮り比べた。最初はゲーム画面だけ全部違っていて焦ったけど、原因はCDNのスクリプト自体も乱数を使っていて、ドラフトの展開がずれていただけだった。そこを分けて撮り直したら、全部で114枚が1ピクセルも違わなかった。CDN版はCSSを<head>の最後に足す作りだったので、読み込む位置も同じ最後にそろえて、どちらのルールが勝つかの順番も変えていない。
第133回:清宮が遊撃、岡林が一塁のスタメン
「AIのポジション設定が意味不明」という報告が来た。スクショのAI球団のスタメンは、一塁を守れる清宮幸太郎が遊撃で、一塁には一塁を守れない岡林勇希、二塁には外野手の塩見泰隆が入っていた。塩見を中堅に回して、空いた枠を詰めれば、だいぶまともになるはずの並びだった。同じ選手で組み直してみたら、手元でもまったく同じスタメンが出てきた。原因は2つあった。1つはスタメンの組み方で、決めた順番(捕手→遊撃→二塁…)に「その枠で一番点が高い人」を貪欲に埋めていたので、先に遊撃で清宮を使ってしまい、後の一塁には守れない選手しか残らなかった。もう1つは指名の方で、「どこかの枠が一応埋まる」かどうかしか見ていなかったので、外野手ばかり5人集めて内野が足りなくなっていた。AIだけでドラフトを40回回して数えてみたら、全480チームのうち7割のスタメンに、守れない位置に置かれた選手が1人以上いた。菊池涼介が一塁、宗山塁が一塁なんてのもあって、ちょっと恥ずかしい。
そこで、まず守備位置の適性、その中で能力、という順番に全部組み直した。スタメンは9つの枠と打者の組み合わせを全部比べて、適性のある選手が就く枠が一番多くなる並びを選ぶ。指名は、残りの野手枠と空いている守備位置の数が同じになったら、そこを守れる選手しか取らないようにした。そのうえで、適性のない位置に置いたときの採点の減点を倍くらいに強めて、試合でもその位置ではエラーしやすくした(今までは一塁手を遊撃に置いても、エラーの起きやすさは変わらなかった)。もう一度40回回すと、守れない位置に置かれた選手は517人から0人になって、本職の位置に就いている選手も6割から8割5分に増えた。1巡目の顔ぶれや、広島が走力・守備の高い選手を集めがちといった球団ごとの色はほとんど変わらず、捕手や遊撃手が少し早めに指名されるようになったくらいだった。
第134回:一番大きな表示は、裏に隠れたロゴだった
Tailwindをビルド版に切り替えたあと、本番で測り直してもらったら、予想ページはパフォーマンス85で目標に届いた。でもトップページはLCP(一番大きな表示が出るまで)が5秒台のままで、最初の表示から2秒くらい遅れていた。てっきりタイトル画像が重いせいだと思って計測の中身を見たら、LCPの対象になっていたのはタイトル画像ではなく、画面の左上にある「⚾ DRAFT SIM」という幅100px程度の小さなロゴだった。しかもこのロゴは、トップ画面の後ろ(ゲーム中に出るヘッダー)にあって、実際には誰の目にも見えていない。原因はトップ画面の「ふわっと出てくる」フェードインで、画面全体が透明な状態から始まるので、その間に描かれたタイトル画像がLCPの候補から外れてしまい、裏に隠れたロゴが一番大きな表示として数えられていたらしい。
ページを開いた直後だけフェードインをやめたら、LCPの対象はちゃんとタイトル画像に戻って、手元の計測では2.1秒から1.5秒になった。ついでに、スマホではタイトル画像を1672px幅(60KB)から960px幅(24KB)の版に切り替えた。iPhoneの3倍密度で並べて見比べても違いは分からなかった。それから、友達対戦のためのFirebaseの読み込み(数百KB)と、スタメン編成のドラッグ用の部品を、ページを開いた瞬間ではなく、それぞれロビーを開いたとき・ゲームを始めたときに読むようにした。最初の読み込みの通信は9件から6件に減っている。デモは5秒後だと見てもらう前に離れてしまう人が多そうなので、2.5秒後に短くしてみた。こっちは手元の計測では表示の速さに影響が出なかった。見た目のためのフェードインが計測上いちばんの足かせになっていたというのは、フォントのときと同じパターンで、ちょっと笑ってしまった。
第135回:「残り何人なら危ないか」は、球団数で全然違った
「気づいたら遊撃を守れる選手がいなくなっていた」を防ぐために、ドラフト画面の上に、各ポジションを守れる選手があと何人いるかを出すボードを作った。最初は「2人以下なら危険、5人以下なら注意」という固定の人数で考えていたけど、2球団対戦と12球団対戦では、次の自分の番までに取られる人数がまるで違う。12球団の端の席だと、次の番まで他球団が22人も指名するので、5人残っていても平気で消える。そこで「次の自分の番までに他球団が何人指名するか」を実際の指名順から数えて、その人数で危険・注意を決めることにした。最初は「1回の指名で平均何人減るか」から計算したら、危険の見逃しは0件だったのに、注意では右翼が見逃されていた。7〜8巡目は多くの球団が同時に外野手を取りにいくので、平均よりずっと速く減る場面があったらしい。
結局、シミュレーションで「N回の指名で何人減ったか」を数えて、99%の場合はそれ以下に収まる人数を、ポジションごと・人数ごとの表にした。これをNPB方式・全巡同時指名・完全ウェーバーの3つで、12球団×150回ずつ、表を作ったのとは別の回で試したら、危険も注意も見逃しは0件になった。全巡同時指名は全球団が同時に指名するので、次の巡の他球団を「自分より先」に数えてしまっていて、途中でNが倍になっていたのを直したりもした。選手データやAIの指名の仕方を変えると表も変わるので、1つのコマンドで作り直せるようにして、関係するところを変えたPRでは「作り直しが必要かも」と警告が出るようにしておいた。
第136回:遊び方ページが、ゲームより先に古くなっていた
機能を足すたびにゲーム本体ばかり触っていたので、一度サイト全体をチェックリストに沿って見直してみた。すると、説明ページのほうがだいぶ古くなっていた。遊び方ページには「参加全球団によるトーナメント」と書いてあったけど、実際は9〜12球団で遊ぶと上位8球団しかプレーオフに進めなくて、9位以下は予選落ちになる。しかも4球団のときの説明が「準決勝→決勝の総当たり方式」になっていて、総当たりでもなんでもない。同点のときにどっちが上のシードになるかも書いていなかったので、コードを読んで「ドラフト開始時の並び順で上の球団(1人なら担当球団)」だと確認してから書き足した。スタメン編成も、スマホでは選手カード→枠の順にタップして置けるのに、遊び方にもFAQにも「ドラッグ&ドロップ」としか書いていなかった。タップ配置を足したときに、ゲーム内の説明だけ直して満足していたらしい。
FAQの「保存機能はありません」も半分しか合っていなくて、ドラフトの途中経過は確かに残らないけど、実績とプレイ回数、1位指名予想はブラウザに保存されているし、野球くじのポイントはサーバー側にある。なので「何が保存されて、何が消えるか」を分けて書き直した。選手名鑑のタイトルは「全96選手」のままで、実際は230人いた。人数は今後も変わるので、タイトルからは数字を外して、読み込み後に「230 / 230 名」と出る表示に任せることにした。ついでに、名鑑とランキングで選手データの読み込みに失敗したときに「GitHub Pages等のWebサーバー経由で〜」という開発者向けのメッセージが出ていたのを、普通の案内と「もう一度読み込む」ボタンに直した。試合画面の最初の一瞬だけ「オウト0」と出ていたのも見つかって、履歴をさかのぼったら、最初のバージョンからずっとそうだった。
第137回:シェア画像は、余白より情報だった
自分でも1位指名を予想して、シェア画像をXに貼ろうとしたら、思ったより地味だった。1200×675の画像なのに、選手名は22px、所属は15pxで、画面の半分くらいが紺色の余白。しかも上のほうに「私の予想はこれだ!」という大きめの見出しがあって、一番場所を取っているのがそこだった。タイムラインで流れてくる画像は一瞬しか見られないので、きれいに余白を取るより、12球団ぶんの名前がパッと目に入るほうが大事だと思う。
そこで見出しは消して、「2026ドラフト 1位指名予想」とサイト名を上の細い帯1本にまとめ、残りの高さを全部12行に使うようにした。球団名のバッジは行の高さいっぱいに広げて、選手名は44px、所属は22pxと、前の倍くらいの大きさになった。ついでに、同じ選手を何球団で予想したかを数えて「3球団競合」の札を付けてみた。自分の予想だと織田翔希・渡部海・菰田陽生がそれぞれ3球団ずつ重なっていて、画像を見ただけで「この3人に集中する読みなんだな」と分かるようになったのが、思ったより面白かった。オリックスのバッジは背景とほぼ同じ紺色で埋もれていたので、細い枠を付けている。
第138回:検索結果のアイコンが、ずっと地球儀だった
Googleで「AIドラフト」を検索してみたら、サイト名の横のアイコンが、ほかのサイトみたいなロゴじゃなくて灰色の地球儀のマークになっていた。ブラウザのタブにはちゃんと⚾が出ていたので、てっきり設定できているものだと思っていた。調べてみると、⚾は絵文字をHTMLの中に直接書き込んだ「その場で描くアイコン」で、ブラウザは表示してくれるけど、Googleはアイコンを独立した画像ファイルとして取りに来るので、拾いようがなかったらしい。全37ページ、最初からずっとこの書き方だった。
なので、紺の背景に白いボール、赤い縫い目、真ん中に「AI」と入れたアイコンを作って、16・32・48pxを1つにまとめたfavicon.icoと、96・192・512pxの画像、iPhoneのホーム画面用の180pxを用意して、全ページから指定し直した。16pxまで縮めると「AI」の文字はさすがに少しつぶれるけど、紺と白と赤の配色で「あの野球ボールのやつ」とは分かるくらいには残ってくれた。ついでに、検索結果のサイト名が「AIドラフト」と出るように、トップページにサイト名を伝える情報も足しておいた。Googleに反映されるまでは少し時間がかかるみたいなので、しばらく様子を見てみる。
第139回:「ドラフトシム」は、よそのゲームの名前だった
Search Consoleを見ていたら、「ドラフトシム」というキーワードでこのサイトが表示されていた。タイトルに入れたら人が来るかなと一瞬考えたけど、念のため調べてみたら、大学野球.comの「ドラフトシム2026」という別のゲームの名前だった。そっちを探している人をこっちに連れてきても、がっかりさせるだけだと思う。なのでタイトルには入れないことにした。ついでに自分のサイトを見直したら、ゲーム画面の左上のヘッダーがずっと「⚾ DRAFT SIM」のままで、遊び方ページの見出しも同じだった。作り始めた頃の仮の名前を、そのまま使い続けていたらしい。
ヘッダーは「⚾ AIドラフト」に直して、トップの小さい英字も「DRAFT SIMULATOR」から「NPB DRAFT GAME」に変えた。ほかにも、シェア画像の下に入るサイト名が「NPBドラフトAIシミュレーター」「NPBドラフト会議シミュレーション」と画像ごとにバラバラだったので、全部「AIドラフト|dreamteam9.com」にそろえた。一番びっくりしたのは、XやLINEでリンクを貼ったときに出る画像で、「BASEBALL DRAFT SIMULATOR」という英語のタイトルに、ドメインを取る前のgithub.ioのURLが入ったままだった。33ページがこの画像を使っていたので、新しいアイコンと「AIドラフト」のロゴを入れた画像に作り直した。名前がそろうと、やっと1つのゲームになった感じがする。
第140回:延長21回の決着は、ずっと先攻びいきだった
サイト全体を見直してもらったときに、試合のシミュレーションで気になる行が見つかった。延長が21回まで続いても同点だったら、無限に続かないように「先攻に1点を足して終わらせる」という安全弁が入っていた。最初に作ったときに、とりあえず止まればいいと思って書いたものだと思う。600試合を回してみたら一度も21回までもつれた試合はなかったので、実際に誰かが損をしたことはたぶんない。でも、起きたら必ず先攻の勝ちというのは、さすがに不公平だなと思った。
なので「21回を終えても同点なら、総合ptが高い方の勝ち」という延長規定にして、そのときは実況とトーナメント表に「延長規定により総合力で勝利」と出すようにした。総合ptまで同じだったら、プレーオフのシード順位が上の方の勝ち。テストでは、ありえない展開を無理やり作って、21回裏まで同点のまま進めて確かめた。ついでに、ドラフト当日に使う答え合わせの画像も、前に作り直した1位予想の画像と同じ見た目にそろえた。的中した球団の行が緑に光るので、何球団当てたかがぱっと分かると思う。
第141回:野球くじを、いったんお休みにした
9月に別のリポジトリで試作していた「野球くじ(β)」をこのサイトに移してきたけど、しばらくお休みすることにした。くじは毎日の対象試合を登録して、試合が終わったら結果を入れて精算して…と、ドラフトのゲームと違って毎日手を動かさないと回らない。そのうえ、サイト全体を見直してもらったときに、ポイントの扱いのルールにいくつか穴が見つかって、直したルールをFirebaseに反映する作業も残っていた。10月はドラフト会議に向けてやりたいことが詰まっているので、中途半端に動かし続けるより、一度止めた方がいいと思った。
3つのページは「休止中です」という案内だけにして、トップと遊び方のメニューからもリンクを外した。プログラムやデータは消していないので、落ち着いたら戻せるようにしてある。せっかく作ったものを止めるのはちょっと寂しいけど、手が回らないまま置いておくより誠実かなと思う。
第142回:抽選の「あの一瞬」を、テレビっぽくしてみた
本物のドラフト会議で一番盛り上がるのは、やっぱり抽選で監督が紙を開いてガッツポーズする瞬間だと思う。このゲームにも封筒の抽選はあったけど、全部の封筒が同時にパッと開いて、すぐ「獲得しました」のアラートが出るだけだった。そこで、封筒が開く→紙をすっと引き抜く→当たりの紙にだけ「交渉権確定」の判が押される→画面の下から結果のテロップが出る、という順番に分けて、1.5秒くらいかけて見せるようにした。画面の上にも「● LIVE」の帯と、選手名と「3球団競合」のテロップを付けてみた。最初は球団名を封筒の上に出していたら、引き抜いた紙でちょうど隠れてしまって、どの封筒が誰のものか分からなくなったので、名前は封筒の下に移した。急いでいる人のために「スキップ」も付けていて、動きを減らす設定にしている端末では最初から結果だけを出す。
あわせて、自分の1位指名をテレビ中継のテロップみたいな画像にできる「1位指名カード」を作った。左に球団カラーの帯、真ん中に選手名を大きく、下に「現所属:阪神」のように今の所属球団を入れて、抽選で引き当てたときだけ赤い「交渉権確定」の判と「4球団競合」の札が付く。競合しなかったときは「単独指名」、一度外してから取ったときは「外れ1位」と出るようにした。写真や本物の球団ロゴは使わず、球団名の文字と色だけで作っている。抽選に当たったときのアラートと、ドラフト中の「📺 1位カード」ボタン、採点結果の画面から作れるので、10/22の本番前の予行演習として、自分の1位を貼ってもらえたらうれしい。
第143回:友達対戦は、通知が遅れたときに壊れていた
友達対戦を4回試してみたら、ちゃんと最後まで遊べたのが1回もなかった。2回はホストが開始を押しても参加者の画面が変わらず、残りの2回も、参加者側には1位指名の読み上げが出ないし、1回は参加者が抽選に入れずに止まり、もう1回は3巡目で2人とも「待機中」のまま動かなくなった。開発中は2つのブラウザで同時に動かして確かめたつもりだったけど、通知がすぐ届く前提でしか試していなかった。そこで、通知がランダムに遅れたり、いくつかまとめて届いたり、たまに届かなかったりする「にせのデータベース」を作って、ホストと参加者の2つの画面を自動で最後まで遊ばせてみた。
そうしたら原因がいくつも出てきた。参加者側では、相手の指名を1件ずつアナウンスしてから反映しているあいだに「次はあなたの番」という情報が先に当たり、そのあと自分の指名を反映した瞬間に自分の番が消えてしまって、2人とも待ち続けていた。アナウンスを閉じるタイマーが、その間に始まった抽選の画面を隠してしまうこともあった。1位指名の読み上げは、そもそもホストの画面にしか出していなかった。それから、同じブラウザの2つのタブで試すと、参加者が「自分の席に戻る」処理でホストの席を自分の席だと思い込んで乗っ取っていた。指名は1件ずつ順番に反映して、そのたびに最新の手番を当て直すようにし、読み上げは参加者にも配信して、通知が止まっても5秒ごとに最新の状態を取りに行く保険も付けた。実際のスマホ2台での確認はまだなので、また試してもらうことになる。
第144回:AdSenseにまた落ちたので、全ページを数えてみた
AdSenseの審査にまた落ちた。理由は「有用性の低いコンテンツ」。前は球団別の中継ガイドを検索から外して出し直したんだけど、それでもだめだったので、今回は感覚で直すのをやめて、くじ以外の34ページを全部ブラウザで開き、メニューやフッターを除いた本文の文字数と、ほかのページとどれくらい同じ文章かを数えてもらった。結果はけっこう分かりやすくて、広島以外の11球団の中継ページは、文章の7〜8割がほかの球団のページと同じで、そのページにしかない文章は250〜460字しかなかった。選手名鑑とランキングも、自分で書いた説明は143字と185字だけで、残りは全部ゲームから読み込んだ能力値の一覧だった。トップページに至っては、サイトの説明が240字くらいしかなく、しかも「© 2026」のさらに下に小さい文字で置いてあった。一番見てほしいはずのページで、一番目立たない場所に置いていたことになる。
ついでに、ドラフト会議のページがまだ「テレビ・ラジオ・配信は発表待ち」「9月時点で正式発表されていません」のままだったことにも気づいた。NPBは9/30に開催要項を出していて、16:50開始、会場はグランドプリンスホテル新高輪、テレビはTBS系列とスカイA、ラジオはニッポン放送、配信はDAZNとU-NEXTと決まっていた。審査の人が見たら、ただの放置されたページに見えたと思う。というわけで、トップには遊び方・特徴・おすすめの人をまとめた1,600字くらいの説明を、開始ボタンの位置を1pxも動かさずに足して、ドラフトのページは開催要項どおりに書き直し、注目選手のページもそこにまとめた。名鑑とランキングは、能力値を自前の計算に切り替える11月まで検索から外して、メニューでも一番下に回した。中継ガイドはメニューから外して、10月下旬に中継比較のページへまとめるつもり。あと、1位指名予想を保存したときに「どの球団に誰を予想したか」を集計できるようにしたので、ドラフトが終わったら「みんなの予想」と答え合わせの記事を書いてみたいと思う。
第145回:実績を増やす前に、取りやすさを数えてみた
実績を16個増やしたい、という案をもらった。ドラフト前に期間限定のものを4つ、ドラフト後に12個。さっそく作り始めようとしたんだけど、並べてみたら「全球団制覇」は今ある実績と名前も条件もまったく同じで、「日本一」も既存の「頂点に立つ者」と同じ条件だった。自分で作った実績なのに覚えていなかった。残りも、普通に遊んでどれくらいの確率で取れるのかが分からないまま入れると、1回で取れてしまうものや、絶対に取れないものが混ざりそうだったので、ゲーム本体を使ってドラフトからプレーオフまでを4球団で2,000回、12球団で600回まわして数えてみた。すると「先発・中継ぎ・抑えの3人とも◎」は1回で7〜8割が取れてしまうし、「スタメン8人全員が守◎」は12球団だと0%だった。投手の方は「完封リレー(相手を0点に抑えて勝つ)」に差し替えて、こちらは1回あたり13〜19%くらいに収まった。
途中で一度、延長戦になる試合が57%もあるという結果が出て焦った。試合エンジンが壊れているのかと思ったら、9回裏が終わったあとに回の数字が10に進み、次の処理で「点差があるので終了」と判定する作りになっていて、その最後の判定だけの処理を自分の集計が「10回」と数えていたのが原因だった。そこを除いたら延長は13%くらいで、ほっとした。今回はまず期間限定の4つを入れた。「名スカウト」と「千里眼」は1位指名予想の答え合わせで判定するんだけど、結果を見てから予想を保存し直せば全部当たってしまうので、指名が始まる10/22 16:50より前に保存した予想だけを対象にしている。期間が終わると誰も取れなくなる実績なので、全体の数(25個)とは別枠で数えるようにした。
第146回:「基本プラン」と「プロ野球セット」は別物だった
別のAIにもサイト全体をレビューしてもらったら、説明の食い違いをいくつか指摘された。一番恥ずかしかったのは、おすすめページのスカパー!の紹介で、見出しが「スカパー!基本プラン」なのに、その下に「12球団すべての公式戦を完全中継」と書いていたこと。12球団の中継が見られるのは「プロ野球セット」という別の契約で、FAQの方はとっくに直していたのに、おすすめページだけ古い見出しのまま残っていた。同じ商品を何ページにも書いていると、1か所直して安心してしまうのは前にもやった失敗だと思う。見出しをプロ野球セットに直して、料金や条件は公式サイトで確認してほしい、という一文も足した。
ほかにも、CS・日本シリーズのページの「応援球団がCSに出るか、どこで確認できますか?」という質問に、うちのランキングページを「ゲーム内のAI球団ランキング」として案内していた。実際のランキングはポジション別の選手の並びで、球団の順位なんてどこにも載っていない。順位はNPB公式サイトへ案内して、ランキングはゲーム内の評価だとはっきり書いた。お問い合わせページには、フォームの受け取り先をまだ用意していないのに「フォームを開く(準備中)」という押せないボタンが置きっぱなしになっていたので、ボタンをなくして、実際に連絡できるXのアカウントを前に出した。ドラフト会議のページには、いつ確認した情報なのかが分かるように確認日を入れて、会議が始まる16:50と、テレビや配信の番組が始まる時刻は別物だと書き分けた。
第147回:「保存ボタンを押し忘れる」を前提にした
12球団1位指名予想は、これまで「予想を保存する」ボタンを押さないと保存されない作りだった。12球団ぶん選手を選んで満足して、そのまま画面を閉じてしまうと全部消える。レビューで「選ぶたびに自動保存して、未選択の球団も見せた方がいい」と言われて、言われてみれば自分でも一回やったことがある気がした。というわけで、選手を選ぶたびに端末に保存するようにして、「✅ 3/12球団を保存済み」「未選択:9球団(ヤクルト・中日・…)」のように、どこまで終わっているかを表示するようにした。
迷ったのは「みんなの予想」の集計との兼ね合いだった。選ぶたびにGA4へ送ると、巨人の1位を3回選び直した人は3人分の予想として数えられてしまう。なので集計への送信は、保存ボタンを押したときと、ページを離れるときに最終的な予想だけを送る形にした。もう一つ、名スカウトや千里眼の実績は「指名開始前に保存した予想」が対象なので、10/22 16:50を過ぎたら自動保存は止めている。答え合わせを見ながらうっかり選択欄に触っただけで、対象から外れてしまうのはかわいそうだと思ったので。ついでに、テスト環境で保存時刻が「03:00」と出て一瞬混乱したんだけど、端末の時間帯で表示していたせいだったので、日本時間で出すようにした。
第148回:右翼手を7人足したら、内野手が混ざっていた
右翼手の一覧表をもらって、選手の追加と能力の更新をした。12人のうち、ゲームにいなかったのが正木智也・佐藤直樹・カナリオ・度会隆輝・板山祐太郎・名原典彦・増田珠の7人で、残りの万波中正・中川圭太・西川史礁・森下翔太・中山礼都の5人は能力の更新だけ。守備適性の列は、前に福永裕基で2回も隣の列を読み違えたので、今回は画像の守備の欄だけを切り出して拡大し、どの数字がどの位置の列に入っているかを1人ずつ確かめた。選手ページのIDは、手元の12球団の選手リストと検索結果の両方で突き合わせた。名原だけはリストに載っていなくて、去年までは育成選手だったからだと思う。
面白かったのは板山祐太郎で、NPBの登録は内野手なのに、表では本職が右翼になっていた。しかも守備の数字は二塁が一番高くて、一塁・二塁・三塁・左翼・中堅・右翼の6か所を守れる。最初は表のとおり右翼を本職にして外野手で登録したんだけど、「板山は内野手登録で」と言われて、NPBの登録に合わせて内野手にし、一番数字の高い二塁を本職にした。表をそのまま写すのと、実際の登録に合わせるのとで、どちらを正とするかは毎回ちゃんと決めた方がよさそうだ。中川圭太も、これまで中堅を本職にしていたのが右翼に変わっていた。中山礼都は内外野の7か所に数字がある万能型で、右翼の値が前より上がった分、守備力も上がっている。選手は230人から237人になったので、トップページの説明文の人数も直した。選手名鑑はゲームの選手データを読み込んで表示しているので、何もしなくても新しい7人が並んでいた。名鑑にデータを複製しなかった昔の自分に、ちょっと感謝した。
第149回:11位のチームが「優勝候補」と名乗っていた
採点結果のシェア画像を作り直した。今までは紺の背景に丸いランクのバッジと6本の棒グラフが並ぶ、どちらかというと説明書っぽい画像だったんだけど、完成イメージをもらって、左に球団カラーの斜めの帯、そこに大きな斜体のランク文字、右に「このチームの特徴」というひと言を置く形にした。ひと言は6項目の上位2つの組み合わせで決まる15通りの文言で、長打力と機動力なら「一発も足もある/破壊力抜群の打線」みたいな感じ。最下位だけは「伸びしろしかない/下剋上の準備は整った」になる。ランク文字は端末の標準フォントのまま大きく描いて横に詰めているので、CやSのような丸い文字だけ線が細く見えてしまい、同じ色で縁取りして太らせた。
確認用に12球団ぶんとS〜Gの全ランクの画像を作って見てもらったら、いくつか笑えない食い違いを指摘された。まず、Cランクで12球団中11位のロッテに「王道の優勝候補」と出ていた。上位2項目が長打力と先発陣なら必ずこの文言になるので、順位とまったく関係なく出てしまう。2行目を「投打の柱がそろうチーム」に変えた。次に、確認用に作った「5位から勝ち上がってプレーオフで優勝した阪神」の画像に「ここから伸びるチーム」と出ていた。日本一になったのに伸びしろ扱いはさすがにかわいそうなので、優勝の画像では必ず15通りの文言を使うようにした。もう一つ、1万回シミュレーションで数えてみたら、12球団では「大砲とエースがそろう」が全体の半分を占めていた。長打力がそもそも高く出やすい点数の付け方なので、どのチームも同じことを言われてしまう。ここは次の回で直すつもり。
第150回:「平均との差」で選んだら、65点に黄色が付いた
前回の続き。シェア画像のひと言が「大砲とエースがそろう」ばかりになる件を直した。長打力はそもそも高く出やすいので、点数そのものではなく「平均よりどれだけ高いか」で上位2項目を選ぶことにしたんだけど、ここでまず一つつまずいた。平均を取るのに1万回のシミュレーションを使ったら、平均が遊ぶ球団の数でまるで違っていた。長打力の平均は12球団だと78なのに、4球団だと91もある。少ない球団で取り合うほど強い選手が回ってくるので、当たり前といえば当たり前なんだけど、12球団の平均を4球団にそのまま使ったら「確実性+救援陣」に4割以上が集まってしまった。結局、2球団から12球団まで11通りを1万回ずつ回して、球団の数ごとの平均を持たせた。
これで一番多い文言は2割くらいまで減ったんだけど、確認画像を見てもらったら、今度は阪神に「光るのは守備力」と出ていて、その守備力は65点で6項目中5番目だった。平均よりは高いから選ばれているんだけど、黄色く光っているのが下から2番目の数字なので、見た人はバグだと思うよね、という指摘で、まったくそのとおりだと思った。数字としては正しくても、見た目として正しくなかった。候補を点数の上位3項目に絞って、その中から平均との差で選ぶ形にしたら、偏りは3割前後まで少し戻ったものの、変な黄色はなくなった。あと、人が実際に組んだチームでどの文言が出ているかはまだ分からないので、シェアのときに文言の番号とランクも記録するようにした。ドラフトが終わったら数えてみたい。
第151回:フッターの灰色が、32ページ全部で引っかかっていた
PageSpeed Insightsの「ユーザー補助」の点数を上げる作業をした。前に計測ツールに振り回された反省から、今回は直す前に、くじ以外の33ページ全部をLighthouseで測って、指摘を一覧にしてみた。すると、トップが75点で、ほかのページも81〜93点。中身を見ていくと、一番多かったのは文字の色の薄さで、しかも原因はほぼ一つだった。フッターの「プライバシーポリシー | 運営者情報」や「こちらのページもどうぞ」に使っていた灰色が、紺の背景だとコントラスト比が3.7しかなく、基準の4.5に届いていなかった。全ページに同じ書き方でコピーしていたので、32ページ全部で同じ指摘が出ていたわけだ。1段明るい灰色に変えたら、それだけでほとんどのページの指摘が消えた。
ほかには、文章の中のリンクが色だけで区別されていて下線がない、トップのドラフト設定の3つの選択欄に読み上げ用の名前がない、比較表の行の見出しが普通のセル扱いになっている、などを直した。選択欄は、上に見出しの文字がちゃんとあるのに、それがどの欄の説明なのかがプログラム上つながっていなかった。見えている人には当たり前でも、読み上げで使う人には「選択欄」としか伝わっていなかったんだと思う。結果、33ページ中28ページが100点になって、トップも90点まで上がった。トップの残りは、ドラッグ操作のために画面の拡大を止めている設定で、ここは分かったうえで残している。名鑑や球団紹介に残った指摘は、球団カラーの上に白い文字を乗せているところで、色そのものを変えることになるので相談中。
第152回:ドラフトの日に向けて、数える準備をした
前回残っていた、球団カラーの上の白い文字を直した。巨人のオレンジ、DeNAと西武の水色、ヤクルトの緑は、白い文字だとコントラストが足りなかった。球団の色を暗くする案もあったけど、色は球団の顔なのでそのままにして、文字のほうを黒にした。シェア画像の帯で、阪神やソフトバンクの文字を黒にしたのと同じ考え方。おすすめページのグッズの欄は、黒い文字の上に暗い下地のボタンを置くと今度はボタンの文字が読みにくくなったので、そこだけ下地を白っぽくした。これで名鑑・球団紹介・おすすめ・予想ページも、ユーザー補助の指摘がなくなった。
もう一つは、ドラフト会議の後で「何が起きたか」を数えるための準備。シェアされた画像が自分のチームなのかAIのチームなのかを区別できるようにして、友達対戦かどうかも記録するようにした。あと、Xのアプリの中でリンクを開くと、どこから来たかの情報が残らないことが多いらしく、せっかくシェアしてもらっても「直接来た人」に紛れてしまう。そこで、シェア文のURLに目印を付けて、結果画面と予想ページのどちらのシェアから来たかを数えられるようにした。答え合わせが何回見られたかも記録する。ドラフトの日は一度きりなので、終わってから「数えておけばよかった」とならないように、先に仕込んでおきたかった。
第153回:無料枠を使い切ったら、友達対戦は黙って止まる作りだった
友達対戦はFirebaseの無料枠で動かしていて、読み取りは1日5万回、書き込みは1日2万回まで。普段はまず届かないけど、ドラフト会議の日にもし人が集まったらどうなるんだろう、と思ってコードを読み直したら、上限を超えたときの受け取り口がどこにもなかった。リアルタイムの通知は失敗しても何も言わずに止まるだけで、画面は相手の指名を待ったまま固まる。遊んでいる人からすると、ただ壊れているようにしか見えない。そこで、通信の失敗を全部1か所で受け止めて、上限超えなら「混み合っているため、友達対戦は一時的に使えません。AI対戦で遊べます」と出すようにした。対戦の途中で起きても、この案内から設定画面に戻れる。わざとエラーを起こすテスト用のURLも用意して、作った瞬間と対戦の途中の両方で、案内が出ることを確かめた。
ついでに、通信の回数そのものも減らした。以前、通知が届かなくなる不具合の保険として「5秒ごとに最新の状態を取りに行く」処理を入れていたんだけど、これは通知がちゃんと届いているときも律儀に取りに行っていて、1台で1分に12回も読み取りをしていた。2台なら24回。保険のはずが、一番の大食いだったかもしれない。通知が12秒以上来ていないときだけ取りに行く形にしたら、静かに待っている40秒の間の読み取りが8回から3回に減って、通知が届いている間は0回になった。無料枠の1日の区切りは太平洋時間の0時で、10月22日は日本時間の16時。指名開始の16時50分の直前にちょうど数字がリセットされるのは、少しだけ心強い。
第154回:2回目の人も、毎回同じ3つを選び直していた
トップの設定画面は、担当球団・球団の数・ドラフト方式の3つを選んでから「ドラフト開始」を押す作りになっている。初めての人にはこれでいいんだけど、自分で何回も遊んでいて、毎回ほぼ同じ組み合わせを選び直していることに気づいた。阪神で、4球団で、NPB方式。たぶん何回も遊んでくれている人ほど、この3つはもう決まっている。しかもスマホだと選択欄の下にボタンがあるので、毎回少しスクロールしてから押していた。そこで、遊び始めたときの設定を端末に覚えておいて、2回目からは「前回の設定:阪神/計4球団/NPB方式」とまとめて表示し、ボタンも「すぐに始める」にした。390×664のスマホの画面で測ると、ボタンの位置が上から588pxのところから365pxのところまで上がって、スクロールしなくても押せるようになった。
気をつけたのは、初めての人の画面を1ピクセルも変えないこと。覚えている設定がないときは、これまでと同じ選択欄がそのまま出る。2回目の人も、「設定を変える」を押せばいつもの選択欄が開くので、違う球団で遊びたいときに困らないはず。あと、これが本当に使われているのかを後で確かめたいので、「すぐに始める」から始めたのか、選択欄から始めたのか、「設定を変える」を開いたのかも数えるようにした。便利にしたつもりで誰も使っていなかった、というのはよくある話なので、ドラフト会議の日の数字を見てみたいと思う。
第155回:ロサンゼルスでは、ドラフト当日が「あと371日」だった
外部のAIにサイト全体を見てもらったら、予想ページの締め切りは海外の時間帯の端末でも正しく判定できているか、という指摘が来た。締め切りの判定自体は「10月22日16時50分(日本時間)」という時刻そのもので比べていたので大丈夫だったんだけど、ついでにテスト用のブラウザで時間帯をロサンゼルスにして、当日の16時49分に開いてみたら、カウントダウンが「あと371日」になっていた。端末の時計の「今日」と、日本の「今日」がずれていて、ロサンゼルスの10月22日0時の時点で、もう来年のドラフトを数え始めていたらしい。直前・ちょうど・直後の3つの時刻を、東京・ロサンゼルス・ロンドンで試して、全部「本日開催!」と出るようになったのを確かめた。
同じ指摘の中で、CS・日本シリーズのページが「要確認」だらけなのも気になっていた。中身を埋めたいところだけど、公式の発表をまだ自分で確かめられていないものを、ほかのサイトの情報や去年の放送局から推測して書くのはやめておきたい。なので今回は、「このサイトではまだ確かめていない」と正直に書いて、NPBの公式のお知らせへのリンクを置く形にした。終わったステージには「終了」の印と、結果を見られるページへのリンクが出るようにしてある。地味な直し方だけど、間違った放送局を載せるよりはいいと思う。
— 続く —