ポケモンの種族値を秒速で確認するCLIツールを作ってみる
ポケモンの種族値なんて全部暗記できない!
よほどのポケモンガチ勢でもない限り、約600種類のポケモンの種族値を丸暗記することはかなり難しいでしょう。
「せめてメジャーなポケモンの素早さだけでも」と思っても、ポケモン対戦では一手のミスが命取りです。もし覚え間違えてたら?それが敗因となることもあるでしょう。
もちろん既存のアプリケーションもあるでしょうが、対戦時間は非常にシビアなので速さも重要になってきます。
そこで、ファジーファインダー的にポケモンの名前を入力して種族値をサッと確認できるツールを作ってみました(実際には昔作って忘れてたのを、記事を書く練習がてらネタにしています)。
sqlite3で種族値テーブルを作ろう
種族値テーブルをテキストで作ってpecoで絞り込むみたいな方法でも簡易に作れますが、折角なので「HCDの合計が一番高いポケモンは?」なども疑問にもサッと答えられるように、データベースを作っておくと、後で別のツールを作る夢も広がります。
テーブルはこんな感じにしてみました。ポケモン名のローマ字で検索できるように、ローマ字名をprimary keyに、種族値合計も利用頻度が高くSQLを書くのがめんどくさいので、あらかじめ計算して入れておくと便利です。hとかaとかの意味は分かりますよね?
create table status_list(
  jpromaji VARCHAR(255) primary key,
  jpname VARCHAR(255) not null,
  no integer not null,
  h integer not null,
  a integer not null,
  b integer not null,
  c integer not null,
  d integer not null,
  s integer not null,
  sum integer not null
);
データソースは伝統のポケ徹(yakkun.com)を使います。
https://yakkun.com/swsh/stats_list.htm をスクレイピングして、SQLに変換する pokemon.rbを書いてみます。
スクレイピングにはnokogiriを使ってみました。またローマ字を扱うのでromajiも便利でした。
こうやって curl https://yakkun.com/swsh/stats_list.htm | ruby pokemon.rb | sqlite3 pokemon としていつでも作り直せるようにしておくと、あとあと便利です。(アプデでポケモンが増えたりするので……。)
#! /usr/bin/env ruby
# curl https://yakkun.com/swsh/stats_list.htm | ruby pokemon.rb | sqlite3 pokemon
require 'nokogiri'
require 'romaji'
Encoding.default_external='euc-jp'
html = $stdin.read
doc = Nokogiri::HTML(html)
doc.css('.stupidtable > tbody > tr > td').each_slice(9) do |line|
  no, jpname, h, a, b, c, d, s, sum = line.map { |td| td.children.first.text }
  jpromaji = Romaji.kana2romaji(jpname)
  values = [
    no.to_i,
    "\"#{jpname}\"",
    "\"#{jpromaji}\"",
    h.to_i,
    a.to_i,
    b.to_i,
    c.to_i,
    d.to_i,
    s.to_i,
    sum.to_i,
  ]
  puts "insert into status_list (no, jpname, jpromaji, h, a, b, c, d, s, sum) values (#{values.join(',')}) on conflict(jpromaji) do nothing;"
end
CLIツールを作ろう
データベースだけでも結構便利ですが、元々の目的だったCLIツールを作ってみましょう。
キーボード一打ごとに動作してほしいのでio/consoleを使います。
また、sqlite3のコマンドを叩くのでopen3も使っています。sqlite3に対してLIKE検索するだけです。
ANSIエスケープシーケンスは覚えるのが面倒なのでclassを作ってます。
ちなみにANSIエスケープシーケンスについては、こちらの記事がわかりやすくてありがたかったです。
スクリプトはいくらでも凝れそうですが、表示の仕方を変えれるオプションを付けるぐらいにとどめています。
#! /usr/bin/env ruby
require 'io/console'
require 'open3'
sqlite_argument = ARGV[0] || '-list'
class Cursor
  def move_top
    print "\e[0H"
  end
  def line_on(i)
    print "\e[#{i}G"
  end
  def clear_line
    line_on(0)
    print "\e[2K"
  end
  def clear_after
    print "\e[0J"
  end
  def clear_all
    print "\e[2J"
  end
end
cursor = Cursor.new
columns = "jpname,jpromaji,h,a,b,c,d,s"
query_template = "select #{columns} from status_list where jpromaji LIKE \"%%%s%%\" limit 10"
cursor.clear_all
buff = []
$stdin.raw do |stdin|
  while true
    cursor.move_top
    cursor.clear_line
    print "> #{buff.join}"
    case c = stdin.getc
    when "\r", "\n"
      buff.clear
      cursor.clear_after
      next
    when "\u007F"
      # backspace
      buff.pop
    when "\u0003", "\u0004"
      # CTRL+C or CTRL+D
      exit 0
    else
      buff << c
    end
    puts
    query = sprintf(query_template, buff.join)
    ## debug
    # cursor.clear_line
    # puts query
    cursor.clear_line
    puts columns
    out, _status = Open3.capture2e("sqlite3 #{sqlite_argument} pokemon", stdin_data: query)
    out.each_line do |line|
      cursor.clear_line
      puts line
    end
    cursor.clear_after
  end
end
いいわけ
いい感じのgifアニメを貼れればよかったけど、ttygifなどではいい感じの操作イメージが作れなかった……。
Discussion