オフラインリアルタイムどう書くE27の参考問題の実装例(ruby)

オフラインリアルタイムどう書くE27の参考問題「**灯りと鏡**」の実装例を、まずは ruby で。

問題 : http://nabetani.sakura.ne.jp/hena/orde27ligmir/
実装リンク集 : https://qiita.com/Nabetani/items/0b2f459ec128c89948f4

10/6 のイベント : https://yhpg.doorkeeper.jp/events/79705

```ruby:ruby2.5
require "json"

def reflect(x)
  case x
  when 0+1i; 1+0i
  when 0-1i; -1+0i
  when 1+0i; 0+1i
  when -1+0i; 0-1i
  end
end

def solve(map)
  you = Complex(*map.index("Y").divmod(5+1)) # +1 は「/」の分
  light = [you, -1+0i] # 位置と方向のペア
  visited = []
  (5*5*2).times do # 高々 5×5×2 しか進まない
    visited << light[0]
    newpos = light[0]+light[1]
    break unless (0...5)===newpos.real && (0...5)===newpos.imag
    cell = map[newpos.real*(5+1)+newpos.imag]
    case cell
    when ".", "Y"
      light=[newpos, light[1]]
    when "0"
      light=[newpos, reflect(light[1])]
    when "1"
      light=[newpos, -reflect(light[1])]
    when "x"
      break
    end
  end
  visited.map{ |c|
    ((c.real*5+c.imag)+?a.ord).chr
  }.sort.uniq.join
end

if $0==__FILE__
  data = JSON.parse(DATA.read, symbolize_names:true)
  data[:test_data].map{ | number:, src:, expected: |
    actual = solve( src )
    okay = actual == expected
    puts [ number, (okay ? "ok" : "**NG**"), actual, expected ].join(" ")
    okay
  }.all?.yield_self{ |x| puts( x ? "okay" : "SOMETHING WRONG" ) }
end

__END__
{"event_id":"E27","event_url":"https://yhpg.doorkeeper.jp/events/79705","test_data":[
  {"number":0,"src":"x...x/.1.0./..0../.Y.../0..x.","expected":"ghilnqs"},
  {"number":1,"src":"..Y../...../...../...../.....","expected":"c"},
  {"number":2,"src":"..x../..Y../...../...../.....","expected":"h"},
  {"number":3,"src":"..Y.x/..1x0/11.../....0/1..1.","expected":"c"},
  {"number":4,"src":"....1/....Y/...../...../.....","expected":"ej"},
  {"number":5,"src":".10../x.1../x.1x./.Y.1./...0.","expected":"bcghlq"},
  {"number":6,"src":"0.x10/00..x/x0x.0/....0/...Y1","expected":"deinsx"},
  {"number":7,"src":"1.01./01Y.1/..1.1/..10./0.0..","expected":"abcfgh"},
  {"number":8,"src":"x..../x1x../0...0/....Y/.1..0","expected":"klmnot"},
  {"number":9,"src":"...../..10./.1Y1./.01../.....","expected":"hilmnqr"},
  {"number":10,"src":"...../..10./x.11./...../..Y..","expected":"hilmnrw"},
  {"number":11,"src":"...../x.10x/...../x.Y1x/.....","expected":"himnqrs"},
  {"number":12,"src":"..010/...Y1/..0../0.x../.....","expected":"defghij"},
  {"number":13,"src":"1.0../...../.0x../Y.1x./..1..","expected":"abcfhkp"},
  {"number":14,"src":"...../101../0.0../..Y../.....","expected":"fgklmqrv"},
  {"number":15,"src":"1.0../00.../.x..0/0.Y1./...10","expected":"abcfghmr"},
  {"number":16,"src":"x101./1..../.Y.x./..01./.00.1","expected":"bcghlmrs"},
  {"number":17,"src":"x11../x.x../.0.01/..x../...Y.","expected":"bcglmnsx"},
  {"number":18,"src":"..1.0/x0.x./0.0../x...Y/.10.1","expected":"cdehjmnot"},
  {"number":19,"src":"..x.0/.0.../1..0x/1..1./Y.00.","expected":"klmnpqrsu"},
  {"number":20,"src":"0.1.0/.0.xY/0...0/01..1/x00.x","expected":"cdehjmrwx"},
  {"number":21,"src":"...0./.0.0./..101/...10/..01Y","expected":"mnpqrstwxy"},
  {"number":22,"src":"10..0/.Y.0./0..1./....x/000..","expected":"abfghiklmn"},
  {"number":23,"src":"10..1/...../.1010/110.1/x..Yx","expected":"lmnopqrstx"},
  {"number":24,"src":"110../....1/x1..x/0.0.0/....Y","expected":"bcghlmrsty"},
  {"number":25,"src":"x.101/1..../..001/010Yx/..1.1","expected":"cdehijmnos"},
  {"number":26,"src":"x.111/x10../...0./00.1x/x.Y.1","expected":"ghklmnqrsw"},
  {"number":27,"src":"11.../....0/11..1/1.1../.Y..1","expected":"fghijlmnoqv"},
  {"number":28,"src":"...x1/.1.0./11.1./.01../Y..x.","expected":"cghiklmnpqru"},
  {"number":29,"src":".0.../110x./11..0/01.x./..Y.x","expected":"ghklmnopqrtw"},
  {"number":30,"src":".01.0/.110x/0...0/.01Y./x.1x.","expected":"cdeghilmnqrs"},
  {"number":31,"src":".1100/..1.0/1.11Y/0..1./.0..0","expected":"hijklmnopqrs"},
  {"number":32,"src":"1..00/..11./.100./1..Y1/.....","expected":"abcdfhikmnps"},
  {"number":33,"src":"1.0../.11x0/.00.x/Y.10./.10x0","expected":"abcfghklmpqr"},
  {"number":34,"src":"11110/11.../.x.../.0111/0.Y0.","expected":"deijnorstwxy"},
  {"number":35,"src":"...1./.1.0x/10..0/0Y.11/.0.x0","expected":"ghiklmnopqrst"},
  {"number":36,"src":"...10/x111./0x.11/.0.../0.0Y.","expected":"dehijmnorswxy"},
  {"number":37,"src":".1x../.x1.0/0x.x./x11.1/x0Y.1","expected":"hijmoqrstvwxy"},
  {"number":38,"src":"x.x../x110./1.1.0/0.Y.1/0.00x","expected":"hiklmnopqrstx"},
  {"number":39,"src":"...0./11.00/10..x/..0.1/Y0.10","expected":"ghiklmnpqsuvwx"},
  {"number":40,"src":".110./....0/x..../.0001/11.Y.","expected":"cdfghijmnorstx"},
  {"number":41,"src":"1.00./....1/.1.../0...0/0..1Y","expected":"abcfhkmpqrstwy"},
  {"number":42,"src":".1.01/..x../..100/..Y../...01","expected":"bcdgilmnoqrstvxy"},
  {"number":43,"src":"1...0/Y..../...../...../0...1","expected":"abcdefjkoptuvwxy"},
  {"number":44,"src":"x1..0/1..0./.Yx../0...1/.0.1.","expected":"bcdefghijklnopqrstvwx"},
  {"number":45,"src":"1...0/.1.0./..1../..01./Y0..1","expected":"abcdefghijklmnopqrstuvwxy"}
]}
```

たぶん方針は光の流れを素直にシミュレートするしかないと思う。
この実装では、地図の持ち方は文字列のまま。自分の位置は複素数で持つという方針にした。

悩みどころは終了条件をどうするか。
この実装の選択は、光は高々 2×5×5 マスしか進まないということを利用して、`50.times` で済ませた。
[9番のケース]( http://nabetani.sakura.ne.jp/hena/orde27ligmir/figs.html#9 )なんかでだいぶ無駄な処理が進むことになるけど、気にしない方向で。

`reflect` メソッドをもう少し簡単にしたいところだけど、いい方法が思いつかなかった。
共役とか使えばシンプルになりそうな気もする。

テストを走らせるコードで

```ruby:ruby2.5
  data[:test_data].map{ | number:, src:, expected: |
    #略
  }
```

とかけるのがちょっと嬉しかった。（シンタックスハイライト...）
@yancya さんありがとう( see https://qiita.com/yancya/items/c557864f307d429bbde4 )