作者: Y さ
日時: 2003/7/17(19:52)
(1.2 続き)

TDDは途中でリファクタリングの余地が無いか見直します。
例えば、コードに重複があるなら解消しましょう、という事です。
コードも読みづらくなってきたので取り敢えず計算処理をループに直してみます。
-----
function calcScore(data,  sc,tbl,cnt, i){
  cnt=split(data, tbl, ",");
  if(cnt==0) return -1;

  sc=0;
  for(i=1; i<=cnt; i+=2){
    if(tbl[i]+tbl[i+1]!=10){ # ストライク/スペアでは無い
      sc+=(tbl[i]+tbl[i+1]);
    }else if(tbl[i]+tbl[i+1]==10 && tbl[i+1]!=0){ # スペア
      sc+=(tbl[i]+tbl[i+1]+tbl[i+2]);
    }else if(tbl[i]+tbl[i+1]==10 && tbl[i+1]==0){ # ストライク
      if(tbl[i+2]+tbl[i+3]==10 && tbl[i+3]==0){ # 連続ストライク
        sc+=(tbl[i]+tbl[i+2]+tbl[i+4]);
      }else{
        sc+=(tbl[i]+tbl[i+2]+tbl[i+3]);
      }
    }
  }

  return sc;
}
-----

-----
C:\>mawk32 -f b.awk
OK : calcScore("")=-1 == -1
OK : calcScore("5,4")=9 == 9
OK : calcScore("9,1,5,4")=24 == 24
OK : calcScore("10,0,5,4")=28 == 28
OK : calcScore("10,0,10,0,5,4")=53 == 53
-----
<<Green>>


全てのテストが通ったという事は、プログラムの出力を変えるようなことを何もしな
かった、事が確認できるわけです。

うまくいったようなので、もう少し続けてプログラム構造を見直すことにします。
2投分を加算する場合と3投分を加算する場合にまとめ、ストライクやスペアかどうか
を判定する関数をくくりだしてみます。
-----
# ストライク、スペア、その他判定
function getType(p1,p2){
  if(p1+p2!=10) return 0;  # その他
  if(p2) return 1;         # スペア
  return 2;                # ストライク
}

function calcScore(data,  sc,tbl,cnt, i, p1,p2,p3, type){
  cnt=split(data, tbl, ",");
  if(cnt==0) return -1;

  sc=0;
  for(i=1; i<=cnt; i+=2){
    type=getType(tbl[i],tbl[i+1]);
    p1=i;  p2=i+1;  p3=i+2;
    if(type==2){ # ストライク
      p2=i+2;
      if(getType(tbl[i+2],tbl[i+3])==2){ # 連続ストライクか?
        p3=i+4;
      }else{
        p3=i+3;
      }
    }
    sc+=(tbl[p1]+tbl[p2]);
    if(type!=0) sc+=tbl[p3];  # ストライクorスペア時
  }

  return sc;
}
-----

-----
C:\>mawk32 -f b.awk
OK : calcScore("")=-1 == -1
OK : calcScore("5,4")=9 == 9
OK : calcScore("9,1,5,4")=24 == 24
OK : calcScore("10,0,5,4")=28 == 28
OK : calcScore("10,0,10,0,5,4")=53 == 53
-----
<<Green>>


テストは成功しました。
では続けて、1投しかない場合は計算できない事を判断し -1を返す、といったケース
を確認するとしましょう。
-----
 #05:1投、投球不足により計算不能
 test("0","==",-1);
-----

-----
C:\>mawk32 -f b.awk
 (中略)
err: calcScore("0")=0 == -1
-----
<<Red>>

処理を定義し、テストを成功させます。
※なお、テストケースとは関係ないのですが、関数getType()を呼ぶ際にチェック
 しておいた方が良さそうな個所に気がついたのでついでに修正しています(;^^ゞ
-----
function calcScore(data,  sc,tbl,cnt, i, p1,p2,p3, type){
  cnt=split(data, tbl, ",");
  if(cnt==0) return -1;

  sc=0;
  for(i=1; i<=cnt; i+=2){
    if(i+1>cnt) return -1;  # 投球不足により計算不能
    type=getType(tbl[i],tbl[i+1]);
    p1=i;  p2=i+1;  p3=i+2;
    if(type==2){ # ストライク
      p2=i+2;
      if(i+3<=cnt && getType(tbl[i+2],tbl[i+3])==2){ # 連続ストライクか?
        p3=i+4;
      }else{
        p3=i+3;
      }
    }
    sc+=(tbl[p1]+tbl[p2]);
    if(type!=0) sc+=tbl[p3];  # ストライクorスペア時
  }

  return sc;
}
-----

-----
C:\>mawk32 -f b.awk
 (中略)
OK : calcScore("0")=-1 == -1
-----
<<Green>>


テストは成功しました。
今度はスペア後、投げ足りないので計算できないケースを試してみましょう。
-----
 #06:2投、スペア後、投球不足により計算不能
 test("9,1","==",-1);
-----

-----
C:\>mawk32 -f b.awk
 (中略)
err: calcScore("9,1")=10 == -1
-----
<<Red>>

処理を定義します。
-----
function calcScore(data,  sc,tbl,cnt, i, p1,p2,p3, type){
 :
    if(type!=0){
      if(p3>cnt) return -1;  # 投球不足により計算不能
      sc+=tbl[p3];  # ストライクorスペア時
    }
 :
}
-----

-----
C:\>mawk32 -f b.awk
 (中略)
OK : calcScore("9,1")=-1 == -1
-----
<<Green>>


次はストライク後、投げ足りないので計算できないケースを試してみましょう。
-----
 #07:4投、ダブル後、投球不足により計算不能
 test("10,0,10,0","==",-1);
-----

-----
C:\>mawk32 -f b.awk
 (中略)
OK : calcScore("10,0,10,0")=-1 == -1
-----
<<Green>>


そのままでうまくいくようです。(こんな場合もあり)

次は全部ストライクの場合をテストしてみます。
-----
 #08:フルスコア
 test("10,0,10,0,10,0,10,0,10,0,10,0,10,0,10,0,10,0,10,10,10","==",300);
-----

-----
C:\>mawk32 -f b.awk
 (中略)
err: calcScore("10,0,10,0,10,0,10,0,10,0,10,0,10,0,10,0,10,0,10,10,10")=-1 == 300
-----
<<Red>>

多分10フレーム目を区別して扱うようにすれば良さそうです。
処理を定義します。
-----
function calcScore(data,  sc,tbl,cnt, i, p1,p2,p3, type, limit){
 :
  sc=0;
  limit=(cnt<2*10)?(cnt):(2*(10-1)); # 10フレーム目だけ例外とする
  for(i=1; i<=limit; i+=2){
 :
  }
  #10フレーム目
  if(cnt>=2*10){
    for(i=2*10-1; i<=cnt; ++i) sc+=tbl[i];
  }

  return sc;
}
-----

-----
C:\>mawk32 -f b.awk
 (中略)
OK : calcScore("10,0,10,0,10,0,10,0,10,0,10,0,10,0,10,0,10,0,10,10,10")=300 == 300
-----
<<Green>>


うまくいきました。引き続き、10フレームの途中のケースをテストしてみます。
-----
 #09:実データ風、10フレーム目投球不足により計算不能
 test("6,3,0,7,4,4,7,3,6,2,10,0,8,2,10,0,10,0,7,3","==",-1);
-----

-----
C:\>mawk32 -f b.awk
 (中略)
err: calcScore("6,3,0,7,4,4,7,3,6,2,10,0,8,2,10,0,10,0,7,3")=145 == -1
-----
<<Red>>

処理を追加します。
-----
# ボウリングのスコア計算
function calcScore(data,  sc,tbl,cnt, i, p1,p2,p3, type, limit){
 :
  #10フレーム目
  if(cnt==2*10 && getType(tbl[2*10-1],tbl[2*10])!=0)
    return -1; # 投球不足により計算不能
  if(cnt>=2*10){
    for(i=2*10-1; i<=cnt; ++i) sc+=tbl[i];
  }

  return sc;
}
-----

-----
C:\>mawk32 -f b.awk
 (中略)
OK : calcScore("6,3,0,7,4,4,7,3,6,2,10,0,8,2,10,0,10,0,7,3")=-1 == -1
-----
<<Green>>


うまくいきました。
もう一つ、実データ風のケースをテストしてみましょう。
-----
 #10:実データ風
 test("6,3,0,7,4,4,7,3,6,2,10,0,8,2,10,0,10,0,7,2","==",143);
-----

-----
C:\>mawk32 -f b.awk
 (中略)
OK : calcScore("6,3,0,7,4,4,7,3,6,2,10,0,8,2,10,0,10,0,7,2")=143 == 143
-----
<<Green>>


うまくいきましたので、この辺で終わりにしようと思います。
最終的なコードは以下です。
-----
# ストライク、スペア、その他判定
function getType(p1,p2){
  if(p1+p2!=10) return 0;  # その他
  if(p2) return 1;         # スペア
  return 2;                # ストライク
}


# ボウリングのスコア計算
#  6,3,0,7,4,4,7,3,6,2,10,0,8,2,10,0,10,0,7,3,10
#  といったデータを入力するとtotalスコアを計算する
#
function calcScore(data,  sc,tbl,cnt, i, p1,p2,p3, type, limit){
  cnt=split(data, tbl, ",");
  if(cnt==0) return -1;

  sc=0;
  limit=(cnt<2*10)?(cnt):(2*(10-1)); # 10フレーム目だけ例外とする
  for(i=1; i<=limit; i+=2){
    if(i+1>cnt) return -1;  # 投球不足により計算不能
    type=getType(tbl[i],tbl[i+1]);
    p1=i;  p2=i+1;  p3=i+2;
    if(type==2){ # ストライク
      p2=i+2;
      if(i+3<=cnt && getType(tbl[i+2],tbl[i+3])==2){ # 連続ストライクか?
        p3=i+4;
      }else{
        p3=i+3;
      }
    }
    sc+=(tbl[p1]+tbl[p2]);
    if(type!=0){
      if(p3>cnt) return -1;  # 投球不足により計算不能
      sc+=tbl[p3];  # ストライクorスペア時
    }
  }
  #10フレーム目
  if(cnt==2*10 && getType(tbl[2*10-1],tbl[2*10])!=0)
    return -1; # 投球不足により計算不能
  if(cnt>=2*10){
    for(i=2*10-1; i<=cnt; ++i) sc+=tbl[i];
  }

  return sc;
}



# テストルーチン
function test(data,stat,val,  ret,err){
  err=0;
  ret=calcScore(data);
  if(stat=="==" && !(ret==val)) err=1;

  printf("%s: ", ((err)?("err"):("OK ")));
  print "calcScore(\"" data "\")=" ret,stat,val;
}
BEGIN{
 test("","==",-1);    #00:スコア無し(呼び出しテスト)
 test("5,4","==",9);  #01:2投、スペア無し
 #02:4投、スペア後、スペア無し
 test("9,1,5,4","==",24);
 #03:4投、ストライク後、スペア無し
 test("10,0,5,4","==",28);
 #04:6投、ダブル後、スペア無し
 test("10,0,10,0,5,4","==",53);
 #05:1投、投球不足により計算不能
 test("0","==",-1);
 #06:2投、スペア後、投球不足により計算不能
 test("9,1","==",-1);
 #07:4投、ダブル後、投球不足により計算不能
 test("10,0,10,0","==",-1);
 #08:フルスコア
 test("10,0,10,0,10,0,10,0,10,0,10,0,10,0,10,0,10,0,10,10,10","==",300);
 #09:実データ風、10フレーム目投球不足により計算不能
 test("6,3,0,7,4,4,7,3,6,2,10,0,8,2,10,0,10,0,7,3","==",-1);
 #10:実データ風
 test("6,3,0,7,4,4,7,3,6,2,10,0,8,2,10,0,10,0,7,2","==",143);
}
-----

いかがでしたでしょうか? TDDの雰囲気が掴めたのではないでしょうか?
それでは(^.^)/