「オフラインリアルタイムどう書く第29回(サイコロの展開図)」の問題を解いてみた


[横浜へなちょこプログラミング勉強会](https://yhpg.doorkeeper.jp/)にて過去に出題された[サイコロの展開図](http://nabetani.sakura.ne.jp/hena/ord29devdice/)を解いてみた。
回答にかかった時間は120分程度と大幅オーバー。

ほとんどは自前で全パターンを列挙する`ExpandedDice#patterns`を実装する時間なので、全パターンをハードコーディングすればもっと早く出来上がったものと思われる。
メソッドの名前は上段が正面、右側、裏面、下段が下側、左側、上側と仮定した場合に回す方向で考えている。

- `ExpandedDice#rotate_topdown`: 上を12時、右側を3時、下を6時、左側を9時方向として時計回りに90度回転させる
- `ExpandedDice#rotate_side`: 裏面を12時、右側を3時、正面を6時、左側を9時方向として時計回りに90度 * 指定回数回転させる
- `ExpandedDice#rotate_face`: 上を12時、裏面を3時、下を6時、正面を9時方向として時計回りに90度回転させる

多分手元にサイコロがあればもっと考えがまとまりやすかったと思われる。

```ruby
class ExpandedDice
  Dice = "12S/453"

  def initialize
    @patterns = patterns
  end

  def masked_cell input
    r = /#{input.gsub(/[wxyz]/, ".")}/
    list = @patterns.select{|pattern| r === pattern}
    case list.size
    when 0
      "none"
    when 1
      input.scan(/[wxyz]/).map{|m|
        "#{m}=#{list[0][input.index(m)]}"
      }.join(",")
    else
      "many"
    end
  end

  private
  def patterns
    [Dice, rotate_topdown(Dice)].each_with_object([]){|dice, r|
      3.times{|i|
        pattern = rotate_side dice, i
        rev_pattern = pattern.reverse
        [
          pattern, rev_pattern,
          rotate_face(pattern), rotate_face(rev_pattern)
        ].each{|pat| r << pat}
      }
    }
  end

  def rotate_cell cell
    case cell
    when "2" then "D"
    when "D" then "2"
    when "3" then "T"
    when "T" then "3"
    when "6" then "S"
    when "S" then "6"
    else          cell
    end
  end

  def rotate_topdown dice
    [
      dice[0], dice[6], dice[2],
      "/",
      dice[1], dice[4], dice[5]
    ].map{|c| rotate_cell(c)}.join
  end

  def rotate_side dice, count
    side = (dice[0..2] + dice[5]).chars.rotate(count)
    [
      side[0..2],
      "/",
      (count % 2).zero? ? dice[4] : rotate_cell(dice[4]),
      (count % 2).zero? ? rotate_cell(side.last) : side.last,
      (count % 2).zero? ? dice[6] : rotate_cell(dice[6])
    ].flatten.join
  end

  def rotate_face dice
    dice[4..6] + "/" + dice[0..2]
  end
end

test = <<_TEST
/*0*/ test( "Tx4/5yz", "x=1,y=S,z=2" );    
/*1*/ test( "14S/xyz", "none" );    
/*2*/ test( "1w6/xyz", "many" );    
/*3*/ test( "4w3/12S", "w=5" );    
/*4*/ test( "4w3/S51", "w=D" );    
/*5*/ test( "15S/wD4", "w=3" );    
/*6*/ test( "54D/6Tw", "w=1" );    
/*7*/ test( "S21/35w", "w=4" );    
/*8*/ test( "w2x/354", "w=S,x=1" );    
/*9*/ test( "wx1/54D", "w=6,x=T" );    
/*10*/ test( "45w/12x", "w=3,x=S" );    
/*11*/ test( "5w2/x14", "w=S,x=T" );    
/*12*/ test( "Dw5/x41", "w=3,x=6" );    
/*13*/ test( "w4x/1y6", "w=D,x=5,y=T" );    
/*14*/ test( "15w/xy4", "w=S,x=3,y=D" );    
/*15*/ test( "D35/wxy", "w=6,x=4,y=1" );    
/*16*/ test( "4wx/51y", "w=6,x=T,y=2" );    
/*17*/ test( "wTx/D4y", "w=1,x=6,y=5" );    
/*18*/ test( "wxy/z3D", "w=1,x=4,y=6,z=5" );    
/*19*/ test( "wx5/1yz", "w=D,x=4,y=T,z=6" );    
/*20*/ test( "w53/xyz", "w=4,x=1,y=2,z=S" );    
/*21*/ test( "wx1/yzD", "w=6,x=T,y=5,z=4" );    
/*22*/ test( "wxS/3yz", "w=1,x=5,y=D,z=4" );    
/*23*/ test( "wx2/y1z", "w=5,x=S,y=T,z=4" );    
/*24*/ test( "4wx/2yz", "w=1,x=T,y=S,z=5" );    
/*25*/ test( "T6w/xyz", "w=4,x=2,y=1,z=5" );    
/*26*/ test( "Swx/yDz", "w=5,x=1,y=4,z=3" );    
/*27*/ test( "wDx/yzS", "w=3,x=4,y=1,z=5" );    
/*28*/ test( "wxy/5Sz", "w=T,x=1,y=4,z=2" );    
/*29*/ test( "wSx/4yz", "w=2,x=5,y=1,z=T" );    
/*30*/ test( "wxS/y5z", "w=1,x=2,y=4,z=3" );    
/*31*/ test( "wxy/35z", "w=S,x=2,y=1,z=4" );    
/*32*/ test( "wxy/T6z", "w=2,x=1,y=5,z=4" );    
/*33*/ test( "wxD/yz1", "w=5,x=4,y=6,z=T" );    
/*34*/ test( "1wx/yz5", "w=T,x=6,y=D,z=4" );    
/*35*/ test( "wx3/y5z", "w=4,x=D,y=S,z=1" );    
/*36*/ test( "6wx/y3z", "w=4,x=1,y=D,z=5" );    
/*37*/ test( "5wx/4yz", "w=1,x=2,y=6,z=T" );    
/*38*/ test( "wx4/Syz", "w=3,x=5,y=2,z=1" );    
/*39*/ test( "w3D/xyz", "w=5,x=1,y=4,z=6" );    
/*40*/ test( "w3x/6yz", "w=D,x=5,y=4,z=1" );    
/*41*/ test( "wxy/z12", "w=4,x=6,y=T,z=5" );    
/*42*/ test( "1wS/xyz", "many" );    
/*43*/ test( "wxy/Dz5", "many" );    
/*44*/ test( "3w4/xyz", "many" );    
/*45*/ test( "wxy/5zD", "many" );    
/*46*/ test( "wxy/Tz4", "many" );    
/*47*/ test( "5wD/xyz", "many" );    
/*48*/ test( "wDx/y5z", "many" );    
/*49*/ test( "wxy/3z4", "many" );    
/*50*/ test( "wxy/5z2", "many" );    
/*51*/ test( "Dyz/S1x", "none" );    
/*52*/ test( "w1z/xyS", "none" );    
/*53*/ test( "15x/T6y", "none" );    
/*54*/ test( "zy4/5x6", "none" );    
/*55*/ test( "2xy/4Tz", "none" );    
/*56*/ test( "xzS/y1w", "none" );    
/*57*/ test( "Syx/4z5", "none" );    
/*58*/ test( "xwS/Tzy", "none" );    
/*59*/ test( "D5z/xwy", "none" );    
/*60*/ test( "yxD/z35", "none" );
_TEST

require 'minitest/autorun'

describe 'ExpandedDice' do
  dice = ExpandedDice.new
  test.split("\n").each do |line|
    t, n, input, expect = line.match(/^\/\*(\d+)\*\/\s*test\(\s*"([^"]+)",\s*"([^"]+)"\s*\);*\s*$/).to_a
    it input do
      assert_equal expect, dice.masked_cell(input)
    end
  end
end
```