Google Developer Day 2010 Japan DevQuiz

なんだかいろいろなところでGoogle Developer Day 2010 Japanで出題されたDevQuizのソースコード公開が流行っているようなのでとりあえず晒しておく。Super Hackers枠の問題を選択問題を含めて全部解いた。使用言語はPython

Google Maps API

Google Maps APIの問題は巡回セールスマン問題だけどたかだか10カ所を廻るだけなので簡単だった。一応行きと帰りは所用時間が変わることもあるから有方向グラフ問題なのかな。

import sys, urllib
import simplejson

url = "http://maps.google.com/maps/api/directions/json?origin=%s&destination=%s&sensor=false&key=MAPS_API_KEY"

class Node:
    def __init__(self, data):
        self.place = data[0]
        self.latlng = "%s,%s" % tuple(data[1:])

def get_duration(start, end):
    json = urllib.urlopen(url % (start, end)).read()
    data = simplejson.loads(json)
    return data["routes"][0]["legs"][0]["duration"]["value"]

def find_route(current_node, nodes):
    global duration, min_duration, current_duration, route, min_route, start_node

    if len(nodes) == 0:
        if current_duration + duration[(current_node, start_node)] < min_duration:
            min_duration = current_duration + duration[(current_node, start_node)]
            min_route = route[:]
            min_route.append(current_node)
            min_route.append(start_node)
        return

    route.append(current_node)
    for node in nodes:
        if current_duration + duration[(current_node, node)] > min_duration:
            continue
        current_duration += duration[(current_node, node)]
        new_nodes = nodes[:]
        new_nodes.remove(node)
        find_route(node, new_nodes)
        current_duration -= duration[(current_node, node)]
    route.pop()

def main(args):
    global duration, min_duration, current_duration, route, min_route, start_node

    data = [l.strip().split() for l in file(args[1])]
    nodes = []
    for d in data:
        nodes.append(Node(d))
    start_node = nodes[0]

    duration = {}
    for node1 in nodes:
        for node2 in nodes:
            if node1 == node2: continue
            duration[(node1, node2)] = get_duration(node1.latlng, node2.latlng)

    min_duration = 0
    for i in range(len(nodes) - 1):
        min_duration += duration[(nodes[i], nodes[i+1])]
    min_duration += duration[(nodes[-1], start_node)]
    min_route = [node for node in nodes]
    min_route.append(start_node)

    current_duration = 0
    route = []
    find_route(start_node, nodes[1:])
    print min_duration
    print " ".join([node.place for node in min_route])

if __name__ == "__main__": main(sys.argv)

2-legged OAuth

OAuthは何回か利用したことがあるから余裕だろうと思ったら、くだらないミスをしていて何回かリトライしてしまった。Pythonで書いたけどコード自体は短い。

import urllib, urllib2, oauth

consumer_key = "CONSUMER_KEY"
secret_key   = "SECRET_KEY"
realm        = "devquiz"
url          = "http://gdd-2010-quiz-japan.appspot.com/oauth/CONSUMER_KEY"
params       = { "hello": "world" }

consumer = oauth.OAuthConsumer(consumer_key, secret_key)
request = oauth.OAuthRequest.from_consumer_and_token(consumer, None, http_method="POST", http_url=url, parameters=params)
request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, None)
req = urllib2.Request(url=url, data=request.to_postdata(), headers=request.to_header(realm))
print urllib2.urlopen(req).read()

Shiritori

しりとりはコード書いたけど補助として使っただけ。Nodeクラス作ってグラフ問題として扱った。でも人力でも解けそうな感じだった。どうせなら全自動で綺麗なコードが書きたかったな。

PAC-MAN

パックマンはまずシミュレータを書いた。これはゲームクラス、敵クラス、状態クラスを作っておいて、ゲームクラスで敵クラスと状態クラスのリストを保持しておく。ゲームクラスにnextメソッドを実装し、そのメソッドの引数としてパックマンの動作を渡し、メソッド内で敵クラスのmoveメソッドを呼び出す。ついでに敵の次に動く位置を表示したり、undo/redoや再開なども追加。因みにシミュレータだけなら状態クラスはいらない。

そして試しに手動で動かしたら簡単に解けてしまったので(しかも楽しい)、ソルバ実装のやる気がみるみる下がってしまうことに(得点は41,247,523)。でも全く手をつけないのもどうかと思ったので、簡単なソルバだけ実装。レベル1は瞬時に(多分)最適解を出力。レベル2は解くだけなら瞬時だけどスコアは良くない。レベル3は短時間では解けない感じ。枝刈りとかアルゴリズムの改良とかは、もともと時間がなかったことに加えてやる気が下がってしまったので結局やらなかった。

import sys, copy

class State:
    def __init__(self, pacman, prev_pacman, path, dots, enemies):
        self.pacman = pacman
        self.prev_pacman = prev_pacman
        self.path = path[:]
        self.dots = dots[:]
        self.enemies = copy.deepcopy(enemies)

class Enemy:
    def __init__(self, char, pos, W, WH, types):
        self.char = char
        self.pos = pos
        self.prev_pos = -1
        self.W = W
        self.WH = WH
        self.types = types
        self.ncross = 0

    def move(self, pacman):
        dirs = [self.W, -1, -self.W, 1]  # 下、左、上、右.
        if self.prev_pos == -1:
            for d in dirs:  
                x = self.pos + d
                if x < 0 or x >= self.WH or self.types[x] == '#':
                    continue
                self.pos, self.prev_pos = x, self.pos
                break
        else:
            if self.types[self.pos] == 'W':
                for d in dirs:
                    x = self.pos + d
                    if self.prev_pos == x or x < 0 or x >= self.WH or self.types[x] == '#':
                        continue
                    self.pos, self.prev_pos = x, self.pos
                    break
            elif self.types[self.pos] == 'E':
                self.pos, self.prev_pos = self.prev_pos, self.pos
            elif self.char in ('V', 'H'):
                dx = pacman % self.W - self.pos % self.W
                dy = pacman // self.W - self.pos // self.W
                f, df, s, ds = (dy, self.W, dx, 1) if self.char == 'V' else (dx, 1, dy, self.W)
                self.prev_pos = self.pos
                if f < 0 and self.types[self.pos-df] != '#':
                    self.pos -= df
                elif f > 0 and self.types[self.pos+df] != '#':
                    self.pos += df
                elif s < 0 and self.types[self.pos-ds] != '#':
                    self.pos -= ds
                elif s > 0 and self.types[self.pos+ds] != '#':
                    self.pos += ds
                else:
                    for d in dirs:
                        x = self.pos + d
                        if x < 0 or x >= self.WH or self.types[x] == '#':
                            continue
                        self.pos = x
                        break
            elif self.char in ('L', 'R', 'J'):
                if self.char == 'R':
                    dirs.reverse()
                elif self.char == 'J':
                    if self.ncross % 2 == 1: dirs.reverse()
                    self.ncross += 1
                idx = (dirs.index(self.prev_pos - self.pos) + 1) % 4
                dirs = dirs[idx:] + dirs[:idx]
                for d in dirs:
                    x = self.pos + d
                    if self.prev_pos == x or x < 0 or x >= self.WH or self.types[x] == '#':
                        continue
                    self.pos, self.prev_pos = x, self.pos
                    break

class Game:
    def __init__(self, T, W, H, data):
        self.T = T
        self.W = W
        self.H = H
        self.WH = W * H
        self.data = data
        self.types = []
        self.dots = []
        self.enemies = []
        self.path = []
        self.is_quit = False
        self.t = 0
        self.prev_pacman = -1
        for i in range(self.WH):
            if self.data[i] == '#':
                self.types.append('#')
            else:
                if self.data[i] == '.':
                    self.dots.append(i)
                elif self.data[i] == '@':
                    self.pacman = i
                cnt = 0
                for d in (-1, 1, -self.W, self.W):
                    x = i + d
                    if x < 0 or x >= self.WH or self.data[x] == '#':
                        continue
                    cnt += 1
                if cnt == 1:
                    self.types.append('E')  # dead end
                elif cnt == 2:
                    self.types.append('W')  # way
                else:
                    self.types.append('C')  # crossing
        self.ndots = len(self.dots)
        for i in range(self.WH):
            if self.data[i] in ('V', 'H', 'L', 'R', 'J'):
                self.enemies.append(Enemy(self.data[i], i, self.W, self.WH, self.types))
        for i in range(self.WH):
            if self.data[i] != '#':
                self.data[i] = ' '
        self.states = []

    def next(self, D='.'):
        if D == 'q':
            self.is_quit = True
            print len(self.path), "".join(self.path)
            return True
        dirs = { 'h': -1, 'j': self.W, 'k': -self.W, 'l': 1, '.': 0 }
        self.states.append(State(self.pacman, self.prev_pacman, self.path, self.dots, self.enemies))
        for d in D:
            if d in ('h', 'j', 'k', 'l'):
                if self.data[self.pacman + dirs[d]] == '#':
                    return False
            elif d != '.':
                return False
            self.pacman, self.prev_pacman = self.pacman + dirs[d], self.pacman
            self.path.append(d)
            for e in self.enemies:
                e.move(self.prev_pacman)
            if self.conflict() or len(self.path) > self.T:
                print "Game Over!"
                self.show_data()
                print len(self.path), "".join(self.path)
                self.is_quit = True
                return True
            if self.pacman in self.dots:
                self.dots.remove(self.pacman)
            if len(self.dots) == 0:
                print "Game Clear!"
                self.show_data()
                print len(self.path), "".join(self.path)
                self.is_quit = True
                return True
        return True

    def prev(self):
        state = self.states.pop()
        self.pacman = state.pacman
        self.prev_pacman = state.prev_pacman
        self.path = state.path[:]
        self.dots = state.dots[:]
        self.enemies = copy.deepcopy(state.enemies)

    def conflict(self):
        for e in self.enemies:
            if e.pos == self.pacman or (e.pos == self.prev_pacman and e.prev_pos == self.pacman):
                return True
        return False

    def show_data(self):
        data = self.data[:]
        for d in self.dots:
            data[d] = '.'
        for e in self.enemies:
            data[e.pos] = e.char.lower() if e.pos in self.dots else e.char
        enemies = copy.deepcopy(self.enemies)
        for e in enemies: e.move(self.pacman)
        for e in enemies:
            if e.pos in self.dots:
                data[e.pos] = '*'
            else:
                data[e.pos] = '+'
        data[self.pacman] = '@'
        if len(self.path) > 0:
            print "Num of dots (rate): %d (%f)" % (len(self.dots), (self.ndots - len(self.dots)) / float(len(self.path)))
        for i in range(0, self.WH, self.W):
            print "".join(data[i:i+self.W])
        print

def main(args):
    f = file(args[1])
    T = int(f.readline().strip())
    W, H = map(int, f.readline().strip().split())
    data = list("".join([l.strip() for l in f]))
    game = Game(T, W, H, data[:])

    game.show_data()
    path = []
    while True:
        d = raw_input("Move (h,j,k,l,.%s): " % (",number" if path and len(game.path) == 0 else "")).strip()
        if d == 'x': break
        if path and len(game.path) == 0 and (d.isdigit() or (len(d) > 1 and d[0] == '-' and d[1:].isdigit())):
            print len(path[:int(d)]), "".join(path[:int(d)])
            d = path[:int(d)]
        if not game.next(d):
            game.prev()
            continue
        if game.is_quit:
            path = game.path[:]
            print "*" * 50
            game = Game(T, W, H, data[:])
            game.show_data()
            continue
        print len(game.path)
        game.show_data()

if __name__ == "__main__": main(sys.argv)