CodeIQ:キュラゲファイターコンボ計算プログラム

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「キュラゲファイターコンボ計算プログラム」(CodeIQ)を参照してください。

問題の概要

問題を引用します。
対戦格闘ゲーム「キュラゲファイター」を発売することになりました。
コンボ(連続技)のダメージ計算をするプログラムを作成してください。

■技表
技名,ダメージ,種別
a,10,normal
b,20,down
c,20,normal
d,30,normal

a-dの4つの技があります。
種別がnormalの技はダウンさせない技です。
種別がdownの技は相手がダウンします。以降の攻撃は倒れている相手に対する追撃になります。
ダウンさせたら二度と起き上がれないとか格闘ゲームとして破綻してない?というお気持ちは心の奥底にしまっておいていただけると大変嬉しいです。

■仕様
前提としてコンボ開始時に対戦相手は地上にいるものとします。

【標準入力】
・標準入力は技名がカンマ区切りで列挙されます
技名1,技名2,技名3,技名4,技名5
・2~5個の技名が半角カンマ区切りで入力されます
・技表に存在する技以外は入力されません(a, b, c, d のみが入力される)

【標準出力】
・標準出力に合計ダメージを出力してください
・合計ダメージは整数部のみを出力してください。入力データの制約上、小数点以下に関する取り扱いの考慮は不要です
50
※50.0 にした場合は不正解

【通常ヒット:1.0倍(補正なし)】
以降で説明するダウンへの追撃ヒット、同一技連続ヒットの条件に該当しない場合は補正なしとします。

【ダウンへの追撃ヒット:0.8倍】
相手がダウンしている場合はダメージが0.8倍になります。
種別がdownの技を当てると次の技から相手はダウンしていることになり、以後の攻撃はダウンしている相手への追撃になります。
一度ダウンした相手は二度と立ち上がることができません。
種別がdownの技を当てた時点では補正は適用されないことに注意してください。

例えば技a(normal), b(down), c(normal), d(normal)を順に当てた場合
1発目のa => 10 x 1.0 = 10
2発目のb(ここで相手をダウンさせる) => 20 x 1.0 = 20
3発目のc(ダウンへの追撃ヒット) => 20 x 0.8 = 16
4発目のd(ダウンへの追撃ヒット) => 30 x 0.8 = 24
10 + 20 + 16 + 24 = 70
で合計 70 ダメージになります。

【同一技連続ヒット:0.5倍】
同じ技を連続して当てた場合は、連続して当てた技以降のダメージが0.5倍になります。

例えば技c, c, cを順に当てた場合
1発目のc => 20 x 1.0 = 20
2発目のc(同一技連続ヒット) => 20 x 0.5 = 10
3発目のc(同一技連続ヒット) => 20 x 0.5 = 10
20 + 10 + 10 = 40
で合計 40 ダメージになります。

c, a, c を順に当てた場合は、cが2回あたりますが連続ではないため同一技連続ヒット扱いにはなりません。

【ダウンへの追撃ヒット+同一技連続ヒット:0.8倍 * 0.5倍 = 0.4倍】
ダウンへの追撃ヒットと同一技連続ヒットが両方成立した場合、両方の補正が適用されてダメージが0.4倍になります。

例えばa, b, c, c, dを順に当てた場合
1発目のa => 10 x 1.0 = 10
2発目のb(ここで相手をダウンさせる) => 20 x 1.0 = 20
3発目のc(ダウンへの追撃ヒット) => 20 x 0.8 = 16
4発目のc(ダウンへの追撃ヒット+同一技連続ヒット) => 20 x 0.8 x 0.5 = 8
5発目のd(ダウンへの追撃ヒット) => 30 x 0.8 = 24
で合計 78 ダメージになります。

【その他の仕様】
・標準入力の末尾には改行があります
・標準入力には技名がカンマ区切りで1行だけ入力されます
・標準出力の末尾に改行をつけてください
・カンマ区切り以外の不正なフォーマットの入力はありません

■Samples
・Sample1
Input
a,c,d
Output
60

・Sample2
Input
b,a,c,d
Output
68

・Sample3
Input
a,a,a,c,c
Output
50

・Sample4
Input
b,b,c,c,c
Output
60

私のプログラム

Rubyで解答しています。

#!/usr/bin/ruby

Waza = {
	#名前 => [ダメージ, ダウン]
	"a" => [10, false],
	"b" => [20, true],
	"c" => [20, false],
	"d" => [30, false],
}

def solve(attack)
	down = false
	waza = ""
	total = 0

	for a in attack
		dmg = Waza[a][0]

		if down then dmg = 4 * dmg / 5 end
		if waza == a then dmg /= 2 end

		total += dmg
		if !down then down = Waza[a][1] end
		waza = a
	end

	return total
end

# main
while line = gets
	line.strip!
	if line.empty? then next end

	attack = line.split(",")
	p solve(attack)
end

解説

わかりやすく、簡単な問題です。
特に悩むところはないと思います。

solve()

引数に入力値を配列に変換したものをもらって結果を返します。
この問題では状態としてダウンしているかと連続で同じ技になっているかを管理する必要があります(連続は一つ前のをみれば良いので管理しなくても良いですが、管理した方がプログラムの見通しが良いと思います)。
ローカル変数のdownがダウンしている(true)かしていないか(false)を管理します。
wazaは一つ前の技です。
totalはダメージの合計です。

引数に渡された技の順にダメージを計算してゆきます(16〜25行目)。
基本ダメージは定数Wazaから引きます。
Wazaは技名をキーとしてダメージとダウン攻撃かどうかを値とした廉造配列になっています。
状態がダウン状態ならダメージを0.8倍します。ただし答えが整数なので4倍してから5で割った商を求めます。
次に同じ技が連続していたらダメージを半分にします。
この2つは両方成立するので個別のif文で判断します。
ダメージが確定したら積算します。
相手が立っている時にダウン攻撃だったらdownをtrueにします。
wazaに技名を保持します。

雑感

簡単な上、説明が丁寧でサンプルも充実しているので悩むところは何もありません。
ここのところかなり難しい問題が続いていたので非常にホッとしました。