「 今週のお題:アタック25に挑戦!」問題解答 ( CodeIQ )

はじめに

CodeIQという所で週次で出題される「今週のお題」シリーズに解答しましたので、ネタバレです。

codeiq.jp

問題

今回の問題は、クイズ番組「アタック25」をネタにしたものでした。

アタック25ルールに則り、与えられた盤面の状態でそれぞれが選べるパネルを調べ、出力します。

入力 ( 盤面の状態 ) は赤青白緑 ( RBWG ) の順に、取得済みのパネルの番号が昇順カンマ区切りで与えられますので、同じ形式で選べるパネルを出力します。

例えば、盤面が次の図のような状況のとき。

f:id:ange1:20160330100913p:plain

入力が次のように与えられます。

R,14
B,8,13,18
W,3
G

出力は次のようにします。

R,2,12,22
B,10,15,20
W,23
G,2,4,7,9,10,12,15,17,19,20,22,24

なお、問題文には明記していませんが、「誰もパネルを取得できない」( 既に盤面が埋まっている ) 状況はケースとして出てきません。「誰もパネルを取得していない」( 13番しか選べない ) というケースは対応する必要があります。

解説

提出したのは次のRubyのコードです。特に今回golfはしていません。

ob=->b,d{
  n=b+d
  n<0||n>24||(b%5-n%5).abs>1
}
m=[]
$<.each{|s|
  c,*x=s.split ?,
  x.each{|i|m[i.to_i-1]=c}
}
%W(R B W G).each{|c|
  puts (
    [c]+(1..25).group_by{|i|
      i==13 ? 1
      : [-6,-5,-4,-1,1,4,5,6].map{|d|
          m[j=i-1] ? 0
          : 5.times{|s|
              break s==0 ? 0 : 2 if ob[j,d]
              break s==0 ? 0 : 3 if !m[j+=d]
              break s==0 ? 2 : 4 if m[j]==c
            }
        }.max
    }.max[1]
  )*?,
}

それぞれの色に対して、1~25のパネルを分類することを考えます。
優先順位としては、

  • 4:いずれかのパネルを挟んで獲れる
  • 3:次にパネルを挟んで獲る手が生まれる
  • 2:いずれかのパネルに隣接している
  • 1:13である
  • 0:既にパネルがある、隣接するパネルがない等、置くことができない

の5段階です。図解すると次の通り。

f:id:ange1:20160330103534p:plain

上位のパネルが存在すれば下位のパネルは選択できないため、Ruby の group_by と max の組み合わせがピタリはまります。
※なお、全てのパネルが埋まっている場合、「すべてのパネルが最低優先順位」として選択可能と判断されてしまいますが、ケアしてません。

パネルの分類については、8方向それぞれでの隣接マスの状況を調べ ( 必要に応じて、同じ方向に進んで調べ )、その中で最も高優先順位のものを採用することで決定できます。

なお関数として分離している ob は、調べる隣接マスが枠外であるかどうかの判定用です。縦方向だけならパネルの番号が範囲内にあるかどうかだけで済みますが、横方向で単純に数値を増減させると前/次の行に移ってしまいますので、mod でも判定する必要があります。

終わりに

Rubyは色んな場面で使えるメソッドが細かく用意されていて便利な感じしますね。
なお、最初「盤面が空」のケースを考えてなくて1WA食らってしまったのは秘密です。