ICFPC2008感想

7/12(土) AM 4:00 - 7/15(火) AM 4:00まではICFP Programming Contest 2008に参加していました。「ICFPコンテストて何?」てむきの方とかはk.inabaさんによる紹介とか読むといいんじゃないかなと思います。一応簡単に説明すると、参加資格、使用プログラミング言語も定めず世界中から参加人を募る毎年開催のプログラミング祭みたいなもんです。
k.inabaさんの紹介にもありますが、2006年と2007年の主催者が用意したシステムの気合の入りっぷりが尋常じゃなく、おもしろすぎることあたりからも割と評判が広まったような印象がありますけど気のせいかもしれません。

というわけで今年。そういやハチロク世代とかだと参加する人多いのかなーと思って適当に「集まらないか?」的なこと書いたりしたのですが、まあ適当過ぎて集まりませんでした。というわけで個人で非常に気楽にダラダラと参加していました。好成績とかそういうのを意識すらしてない怠けた参加態度だったんですが、これはこれでけっこう楽しかったです。

1日目

まず問題文を読み解くところが難関です。難関なのでこれだけで1日目をだいたい終えたような記憶があります。まず今年の問題はでっかい岩とかクレーターとか、襲いかかってくる火星人がいたりする火星の地にて探査機を基地にたどりつくよう走らせるというものなんですが、ここまで読み解くのにもずいぶん時間がかかった。あとTCP/IPソケット通信をする云々書いてあったけどその辺さっぱりわからなかったので慌てて調べたり。でもRubyのTCPSocketあたりとか特に難しくもなさそうだったので自然とRubyで書き初めました。たぶんこの日は

#!/usr/bin/env ruby

require 'socket'

port = ARGV[1].to_i
host = ARGV[0]
rounds = 5

sock = TCPSocket.open(host, port)
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)

$/ = ';'
count = 0
sock.each_line do |line|
  puts line
  case line[0]
  when ?I
  when ?T
  when ?B
  when ?S
  when ?C
  when ?K
  when ?E
    if (count += 1) == rounds
      sock.close
      exit
    end
  end
end
sock.close

こういう正真正銘何もしない(探査機を動かさない)プログラムを書いて試しにサブミットしたあたりまでで終えてた気がします。

2日目

しばらくは

#!/usr/bin/env ruby

require 'socket'

port = ARGV[1].to_i
host = ARGV[0]

sock = TCPSocket.open(host, port)
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)

while op=$stdin.gets
  sock.write(op.chomp+';')
end
sock.close

みたいなプログラム書いて、ジョイパッドとJoyToKey使って手動で探査機走らせて遊んだりしていました。だいたいこの遊びが2日目にした作業の半分くらいのような……

もう半分は障害物を完全無視して基地へと進むようにしました。頭左右に振りながらヨタヨタしながらも一応基地へと進むことに感動したりしてました。

この後にLiveCD上で書いてたソースコードを紛失したりしましたが、たいして書いてなかったので被害は小さかったです。この時点でもプログラムはたぶんこんな感じ。

#!/usr/bin/env ruby

require 'socket'

port = ARGV[1].to_i
host = ARGV[0]
rounds = ARGV[2] ? ARGV[2].to_i : 5

sock = TCPSocket.open(host, port)
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)

$/ = ';'
init = sock.gets
dx, dy, time_limit, min_sensor, max_sensor,
  max_speed, max_turn, max_hard_turn =
  init.scan(/-?[\d\.]+/).map!{|f|f.to_f}

count = 0
sock.each_line do |line|
  puts line
  case line[0]
  when ?T
    time, ctrl, x, y, dir, speed =
      line[1..-1].scan(/[^\s]+/)
    if time=='0'
      sock.write('a;a;')
    else
      x = x.to_f
      y = y.to_f
      dir = dir.to_f
      pos_dir = Math.atan2(y, x)*180/Math::PI

      if (pos_dir > 0 && pos_dir-180 < dir && dir < pos_dir) ||
        (pos_dir < 0 && (dir < pos_dir || pos_dir+180 < dir))
        sock.write('r;')
      else
        sock.write('l;')
      end
    end
  when ?B
  when ?S
  when ?C
  when ?K
  when ?E
    if (count += 1) == rounds
      sock.close
      exit
    end
  end
end
sock.close

3日目

2日目のプログラムの時点で、運が良ければ基地にたどりつくようになってたので割と満足していたのですが、さすがに障害物避けようとする努力の影すら無いのはなー、と思いちょっとごちゃごちゃ書き足しました。Tメッセージを読んで、だいたい探査機の向いてる方向ある障害物のうち、一番近いものを避けようとする感じです。左の方にあったら右に向いて、右の方にあったら左を向く、という。障害物の半径も読んでないので、当たるときは普通に当たります。あとなんか正面から障害物に突っ込むことあるので普通にダメダメだと思う。火星人はあんまし当たらないので完全無視。

まあ、なんとなく2日目よりは良くなってるのかなあと思えるような思えないような。最終提出のプログラムはこんなの。ひどいので誰の参考にもならないと思います。ruby知らない人に「rubyのプログラムは汚ないなあ」と思われないように言っておきますと、処理を全部mainの中に書いてるCプログラムみたいなもんです。

#!/usr/bin/env ruby

require 'socket'

class AngleRange
  DIR_R = 1
  DIR_L = 2
  DIR_RL = 3
  def initialize(base, range)
    @base = base
    @range = range
  end
  def include?(dir, rl)
    in_flag = 0
    if @base - @range <= -180
      if dir <= @base || 360 + @base - @range <= dir
        in_flag |= DIR_R
      elsif @base < dir && dir < @base + @range
        in_flag |= DIR_L
      end
    elsif @base + @range >= 180
      if @base - @range <= dir && dir <= @base
        in_flag |= DIR_R
      elsif dir <= -360 + @base + @range || @base <= dir
        in_flag |= DIR_L
      end
    elsif @base - @range < dir && dir < @base
      in_flag |= DIR_R
    elsif @base < dir && dir < @base + @range
      in_flag |= DIR_L
    end
    rl&in_flag != 0
  end
end

def nearest(objs, x, y, dir, range)
  near_dir = nil
  near_dis = Float::MAX
  near_obj = nil
  objs.each{|c|
    rx = c[0]-x
    ry = c[1]-y
    rdis = rx**2+ry**2
    cdir = Math.atan2(ry, rx)*180/Math::PI
    if range.include?(cdir, AngleRange::DIR_RL) && rdis < near_dis
      near_dir = cdir
      near_dis = rdis
    end
  }
  near_dir
end

counter = 0
port = ARGV[1].to_i
host = ARGV[0]
rounds = ARGV[2] ? ARGV[2].to_i : 5

$/ = ';'
sock = TCPSocket.open(host, port)
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)

init = sock.gets
dx, dy, time_limit, min_sensor, max_sensor,
  max_speed, max_turn, max_hard_turn =
  init.scan(/-?[\d\.]+/).map!{|f|f.to_f}

count = 0
aa = false

sock.each_line do |line|
  #puts line
  case line[0]
  when ?T
    time, ctrl, x, y, dir, speed =
      line[1..-1].scan(/[^\s]+/)
    if time=='0'
      sock.write('a;a;')
      aa = true
    else
      x = x.to_f
      y = y.to_f
      dir = dir.to_f
      pos_dir = Math.atan2(y,x)*180/Math::PI
      acc = ctrl[0]
      turn = ctrl[1]

      dis = x**2 + y**2
      if dis > 1000
        sock.write('a')
        aa = true
      elsif aa
        aa = false
        sock.write('b')
      end

      craters_dir = 0
      boulders_dir = 0

      if line.scan(/h/).length == 0
        craters = line.scan(/c (-?[\d\.]+) (-?[\d\.]+) (-?[\d\.]+)/)
        range = AngleRange.new(dir, 20.0)
        if craters.length > 0
          craters.map!{|c|c.map!{|f|f.to_f}}
          near_dir = nearest(craters, x, y, dir, range)
          if near_dir
            if range.include?(near_dir, AngleRange::DIR_R)
              craters_dir += 1
            elsif range.include?(near_dir, AngleRange::DIR_L)
              craters_dir -= 1
            end
          end
        else
          boulders = line.scan(/b (-?[\d\.]+) (-?[\d\.]+) (-?[\d\.]+)/)
          range = AngleRange.new(dir, 10.0)
          if boulders.length > 0
            boulders.map!{|c|c.map!{|f|f.to_f}}
            near_dir = nearest(boulders, x, y, dir, range)
            if near_dir
              if range.include?(near_dir, AngleRange::DIR_R)
                boulders_dir += 1
              elsif range.include?(near_dir, AngleRange::DIR_L)
                boulders_dir -= 1
              end
            end
          end
        end
      end
      if craters_dir > 0
        sock.write('l')
      elsif craters_dir < 0
        sock.write('r')
      elsif boulders_dir > 0
        sock.write('l')
      elsif boulders_dir < 0
        sock.write('r')
      else
        range = AngleRange.new(pos_dir, 180)
        if range.include?(dir, AngleRange::DIR_R)
          if turn != ?r && turn != ?R
            sock.write('r')
          end
        else
          if turn != ?l && turn != ?L
            sock.write('l')
          end
        end
      end
      sock.write(';')
    end
  when ?B
  when ?S
  when ?C
  when ?K
  when ?E
    if (count += 1) == rounds
      exit
    end
  end
  sock.flush
end
sock.close

言いたかったこと。

見てわかるように、やってることのショボさは尋常じゃないです。でも最初に問題文読んだとき「こんなの俺程度じゃどうしようもないじゃん……」という印象ほどじゃあなかったなと。なんだかんだとダラダラ書いて、動いてるプログラム見てりゃ結構楽しいんですよ。ICFPCはプログラミングスキルそんな高くない人でも割と楽しめましたよ、というのが言いたかったことです。どっとはらい

test