Summary. ダメージ計算スクリプトの実装のうち、コマンド解析の部分についてです。


やること

今回は「コマンド解析」の部分を見ます。フォームに入力されたテキストを1文字ずつ読み取って、レシピを解析します。ダメージ計算スクリプトの入口ともいえる大事な場所ですが、まじめに作ろうとすると結構面倒です。

Function summary
parse(input, prefix) コマンド1個分の input を解析します。
prefix は派生技の実装のための引数で、普段は "" を指定します
parse_command(name, args) コマンド名と引数リストを受け取り、適切な処理を実行します
parse_attack(name, args) Skills に登録されているコマンドの処理を行います
parse_range(args, ind, max) コマンド技の段指定を解析して、配列 ind に結果を挿入します。
成功時は0を、失敗時は問題のある段指定(int)を返します

なんですが、今回はちょっと乱暴な方法でやっています。正規表現をべったり使っているので、よく知らない方は先に正規表現のページをご覧下さい。

コマンド処理の仕組みをもっと標準的な方法で書いたものが別ページにあります。今回のプログラムは応用もへったくれもないので、もし興味があればそちらも見てみてください。

下準備

今回扱う入力データは「コマンドをセミコロンで区切って並べたもの」です。なので、まず最初に match メソッドでこれらをバラバラに分解してしまいます。ついでに余計な空白類も取り除いてしまいます。
update = function(){
  var text = document.prompt.recipe.value;
  var inputs = text.replace(/\s+/g, "").match(/[^;]+/g);
  var i;
 
  init_status();
  for(i = 0; i < inputs.length; i++){
    if(inputs[i].length > 0){
      parse(inputs[i], "");
    }
  }
 
  document.prompt.result.value = HP_SETTING[0] - HP;
  save_history(15);
} 
最初の2行で分解が完了します。そのあと、init_status() でステータスを初期化おきます。あとは for ループで1つずつコマンドを実行していきます。

パターン

コマンド

「コマンド」はその定義から
コマンド名 ( 引数の羅列 ) または コマンド名だけ
という形をしています。いずれにせよ、まず最初にコマンド名があって、段数指定ver.ではその後に括弧が続くはずです。これにマッチする正規表現を考えると
[\w\[\]@]+ または [\w\[\]@]+ \( [\d\-,]+ \)
みたいになります(見やすいようにスペースを挟んでますが実際はくっつけます)。?記号を使えばこの2つをまとめることができます。ついでにグループ化も仕込んでおきましょう:
( [\w\[\]@]+ )  ( \( [\d\-,]+ \) )?
上では端折りましたが、歴史的(?)事情からコマンド名にはアルファベット・数字・角括弧・@だけでなく+や-も使えるようにしています。

引数

個々の引数は
数字の列 または 数字-数字
という形をしています。したがって正規表現で書くと
\d+ または \d+ \- \d+
となります。

派生技

パターンではないですが一応。派生技は次のルールで2つのコマンドに置換されます:
X1(a1)>X2(a2)  →  X1(a1); X1X2(a2)
ここで X1X2 は2つのコマンド名を連結した文字列です。実際のコードでは、まず X1(a1) の部分だけ先に処理してしまって、その後 ">" を取り除いた文字列を作って再度 parse() 関数をやり直すようにしています。

個々のコマンドの解析

parse 関数(コード略)の中でコマンド文字列と前項のパターンとのパターンマッチングを行います。そうやって抽出されたコマンド名や引数を元に、実際にどんな計算処理を行うか決定するのが parse_command() です。
parse_command = function(name, args){
  if(name in Skills){
    parse_attack(name, args);
  }
  else if(name in STATE_COR){
    State = name;
  }
  else if(name in SP && (args == null || args[0].match(/^\d+$/))){
    SP[name](args != null ? parseInt(args[0]) : null);
  }
  else{
    window.alert("コマンドを解析できません...);
  }
} 
コマンド名から種類を判断し、適切な処理を行います。Skills に登録されているコマンドの場合はちょっと仕事が多いので、parse_attack() という別の関数に任せています。
parse_attack = function(name, args){
  var ind = new Array();
  var i;
 
  if(args == null){
    for(i = 1; i <= Skills.HC[name]; ind[i-1] = i++);
  } else {
    var r = parse_range(args, ind, Skills.HC[name]);
    if(r > 0){
      window.alert(name + "に" + r + "段目は存在しません... ");
      return;
    }
  }
 
  for(i = 0; i < ind.length; Skills[name](ind[i++]));
} 
本質的には最後の行だけが大事です。ind という配列に「何段目を当てるか」をメモしておき、それを見ながらこの技を一括処理します。

parse_range() は引数として受け取った配列へ解析結果を挿入します。返り値は、成功時に0を、失敗時には問題の起きた段を返します。





最終更新:2014年10月21日 17:42