[GDD2010][devquiz] Google Developer Day 2010 devquizの回答を晒すよ

既に#gdd2010jpでdevquizのソースが色々と晒されていますが、自分も晒します。
今回、全ての回答をPythonで書くことにしました。
自分はJava屋でPythonは前回のDevQuiz以来書いていないのですが、新しいことを覚えるには何か動機が必要な人なので今回もまた縛りを付けてみました。
最後にJava屋から見たPythonの感想を付けておきます。

OAuth

HTTP の POST メソッドを用いて、 hello=world (キーが hello で値が world となるPOSTデータ) というパラメータを送信してください。
OAuth のパラメータは、 Authorization ヘッダに含める形式 で送信してください。 その際、認証の realm には devquiz という文字列を使用してください。
POST データや GET パラメータに OAuth のパラメータを含める形式には対応していません。
OAuth 署名を作成する際に、下記の consumer key と consumer secret を使用してください。
Consumer key: xxxxxxxxxxxxxxxxxxxxxxxx
Consumer secret: xxxxxxxxxxxxxxxxxxxxxxx
OAuth 署名方式は HMAC-SHA1 を使用してください。 その他の署名方式には対応していません。

この問題ですが、検索したらあっとういう間に答が見つかったように思いました。
pythonで署名付きリクエストを送る(2-legged OAuth) – taichino.com


ただ残念なことにこちらのプログラムはGETで書かれていました。
このプログラムを問題に合わせるにはGETをPOSTに変え、Authorizationヘッダを利用せねばなりません。


Pythonには標準ライブラリにoauthがありました。しかしGETのサンプルしか見ていないのでどうすればヘッダに情報を書くことができるのかわかりませんでした。色々と検索して結局OAuthのソースを読んでわかりました。oauthのlibraryにto_headerというメソッドがありそれがヘッダに利用できるリストを吐くというものでした。


PythonにはなぜかHTTPクライアントのライブラリがurllibとurllib2と二つあります。
恐らく歴史的な理由でしょうが、HTTPリクエストヘッダを書き換えるにはurllib2を使用しなければ無理なようです。しかもurllibとurllib2の両方が必要なようです。
そこまでわかってやっと書けたのが以下です。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import urllib
import urllib2
from oauth import oauth

# ユーザ登録時にもらえる情報
consumer_key = ''
secret_key   = ''

# リクエストしたいURLとパラメータ
url   = 'http://gdd-2010-quiz-japan.appspot.com/oauth/************************'
params = {
  'hello'      : 'world'
}

# 1. Consumerオブジェクト作成
consumer = oauth.OAuthConsumer(consumer_key, secret_key)
# 2. HTTPリクエスト組み立て
request  = oauth.OAuthRequest.from_consumer_and_token(consumer, token=None, http_method='POST', http_url=url, parameters=params)
# 3. リクエストに署名して発行
request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, None)

req = urllib2.Request(url, urllib.urlencode(params), request.to_header(realm='devquiz'))
response = urllib2.urlopen(req)
print response.read()

#200 OK
#Congraturations! You solved this problem.

しりとり

サーバーとしりとりで勝負していただきます。
3 つのレベルがありますので、それぞれのレベルでサーバーに勝ってください。サーバーに勝つと、レベルに応じた点数が加算されます。
サーバーが先手です。上のリンクをクリックすると、サーバーから辞書(使用可能な単語のセット)と初手が提示されますので、それに続いてください。
しりとりの途中でブラウザを閉じても、上のリンクをクリックすれば再開できます。
手詰まりなどの理由で、ゲームをやり直したいときはゲームをリセットというボタンを押してください。新しいゲームが始まります。
サーバーに一度勝った後は、そのレベルで新しいゲームを始めることはできません。別のレベルに挑戦してください。

この問題ですが、ソース提出必須にしてほしいとバカを言っていたのは私です。
良く考えればまたこれも個人個人で問題が異なる訳ですね。


Level1とLevel2は全探索で解けました。
私の場合、L1では辞書が22語、L2では46語、L3では187語になります。
L3は単語の頭と別の単語のお尻が同じ字になっている場合が多く、全探索ですと組み合わせの爆発が起こります。L3は全探索だと終わる気配が感じられませんでした。
最初は序盤を適当に答え、終盤だけを読み切れば勝てるかと思いましたがどうしても負けてしまいました。
ある程度負けを重ねたところでサーバー側の手がわかりました。ある文字で始まる単語は何を答えても同じ文字で終わる単語を答えられてしまい、こちらの手が尽きて負けてしまいます。つまりある文字で始まる単語を選ばされたらもう負けな訳です。
さらに絶対に負ける単語がわかれば絶対に勝てる手もわかります。その文字で終わる単語を選べば後は失敗しない限りこちらの勝ちです。それを調べるとある文字で始まる単語は何を選んでも絶対に勝てることがわかりました。
ここから最初に辞書をもらった時に二つのリストを作れば良いことになります。
自分の手では必ず相手が負ける字に誘導し、相手の手ではできるだけ自分が勝てる字が来ることを願う訳です。これを探索中で枝刈りに利用しました。そうすると非常に探索時間が短くなり簡単にL3に勝てました。問題の性質上、絶対に勝てる手が用意されているので当然ですね。

以下がL3に勝ったプログラムです。

#!/usr/bin/env python
import sys

words = ['zhtqjb','bglbwir','binaqofibn','bhfbuwox','riartzn','rljjkx','rotxttg','nriynhitx','nenemhg','nlumqkqltz','xcbwlkisuqg','xxreewewwz','xswzmyq','gaepz','gjcrbgq','ghqhhwql','zfrvjjcncq','znwfl','zehpnlmqiea','qqkptl','qoztsna','qpyuwspeb','leheosfkmsa','lhjmfzeb','lwqir','akidjknb','abhfwqfkr','asmskhfpun','bryvkdqs','sgzdam','sfzbugash','sscolpcrmej','rmficolezt','twedci','tbjctsy','tbztchfbjo','nfglypw','wmrsre','wvjlwpgvik','wnergv','xyqcs','gxlnxvcdbvt','zsmmjypw','qzkls','lftmbhhixmt','abuofnvw','mcagfrixifi','mpaerpkane','mqwnhjmieh','mdgzrzyy','mnkrk','mpgldj','mbviso','mpsdav','maygakc','mbexzu','mazywod','ivnkhtm','isxoke','ijmclfhh','inbxgsy','idcanzk','inhvynxcaj','ibktno','ibruwejv','iiycffthdc','iatdegrwu','iuzjd','eceamplvatm','exaejvi','eptlnh','emjojhqbtty','eovfutxcsvk','ekejlknshj','ereigbo','ejctpwiilv','egelfc','erxintdtru','evtvgvsd','htzjmuom','heuindyxxii','huhethhmaie','hjdmjxueuy','hwvdjgk','hszkgrvgknj','hjtxqio','hutuikev','hvbglnmtc','hjiimohuu','hjqfnbd','yuzwjmrkgm','yrphmi','yiuazmwtwe','yxncvskuwh','yszmtak','ybocj','yvfqsjotpo','yvpgtguwfnv','yzptnedc','ymideu','yhpgjunsd','kfpjraqaxim','klljvqokpmi','knvzoyvae','kpdth','kavxlkmrity','kaixzweqj','kaltcxvcwo','kafnwziv','kjgxiacyjc','kkqcu','kindpgrd','jzqwcem','jxtji','jztipze','jgfyaajgh','jdxonjbsy','jrnotwlvck','jksio','jfltv','jppopvmac','jebwou','jewxwfd','obggm','ocbkoeuzpai','otxgzqcpe','ohdwisfh','obqwsnlrsy','ortvcwssqxk','omopyikhj','oyjwxv','ovnyc','olttxagu','ocrerlikd','vtjpfylqqm','vzczeixi','vlwfaaejxe','vkocpmh','vmladqeiwy','vqfypmk','vxxsognwfj','vyaaso','vxzaezppc','vboatau','vnfdxxcekhd','clpirjcem','cheffvqbui','ccone','ckmnvrebeuh','ccypuy','coihzrk','cspij','coiuoeeuro','ckpditmltv','cqhru','cabqathzfcd','ujuxlvm','uunnevpi','ubwxgwkicxe','ulrych','umotpkowwoy','uthtk','uhjpyltqj','umaqyfjio','ugjrapsyhmv','uirwoac','uasikmcvld','dtchjbgm','dbdaggni','dzmtwoee','dcgrhdh','dnckiky','difrdubmyk','dwerqlthnj','dwkyyo','drwmiknpuv','dhjeuoqc','dliojujlbku','czdwssnb','cxfwudwemkx','cgvmuhztqq','ubjvtuzcr','ugacbg','ufnurlheihl','dkttn','dxofozpvvqz','dgernspxva']
words = sorted(words)
hc = {}
tc = {}
for c in 'abcdefghijklmnopqrstuvwxyz':
  hc[c] = 0
  tc[c] = 0

for l in words:
  l = l.strip()
  head = l[0]
  tail = l[-1]
  hc[head] = hc[head] + 1
  tc[tail] = tc[tail] + 1

print hc,'\n',tc
#print words

dlist = [] # Danger list
for c in 'abcdefghijklmnopqrstuvwxyz':
#  print "Pattern :", c
  hwords = [x for x in words if x[0] == c]
#  print hwords

  hback = []
  for h in hwords:
    hback += [x for x in words if x[-1] == c and x[0] == h[-1]]
#  print hback
  if len(hwords) == len(hback):
    dlist.append(c)
#    print "Danger"
#  else:
#    print "Safe"
print dlist

# Safe list
#for c in 'abcdefghijklmnopqrstuvwxyz':
#  tmp = [x for x in words if x[0] == c and x[-1] in dlist]
#  print c,":",tmp

llist = [] # Lucky List
for c in 'abcdefghijklmnopqrstuvwxyz':
  clist = [x for x in words if x[0] == c]
  flag = True
  if len(clist) > 0:
    for j in clist:
      if not j[-1] in dlist: flag = False
    if flag:
      llist.append(c)
      print c, ":", clist
print llist


calc_memo = {}
def calc_score(words, hc, tc, chosen, myturn, nest):
  key = (tuple(words), chosen)
  if key in calc_memo:
    return calc_memo[key]
#  if nest < 8:
#    for i in range(nest):
#      if myturn:
#        print "+",
#      else:
#        print "-",
#  print chosen,
  ret = 101 if myturn else 1
  head = chosen[0]
  tail = chosen[-1]
  # no left word 
  if hc[tail] == 0 or head in llist or tail in dlist:
    if myturn:
      print " Win!"
    else:
      print " Lose!"
    calc_memo[key] = ret
    return ret

#  print
  words.remove(chosen)
  hc[head] = hc[head] - 1
  tc[tail] = tc[tail] - 1

  score = 0
  win = 0
  lost = 0
  myturn = not myturn
  able = [x for x in words if x[0] == tail]
  for a in able:
    tmp = calc_score(words[:], hc.copy(), tc.copy(), a, myturn, nest + 1)
    if tmp >= 100:
      win += 1
    else:
      lost += 1
    score += tmp
  if len(a) == 1 or (win > 0 and lost == 0):
    calc_memo[key] = score
    return score

  if (win == 0 and lost > 0) or (not myturn and win == 1 and lost == 1):
    calc_memo[key] = score % 100
    return score % 100

  calc_memo[key] = score
  return score

while True:
  while True:
    try:
      w = raw_input('Enter 1st: ')

      head = w[0]
      tail = w[-1]
      print head,tail
      words.remove(w)
      hc[head] = hc[head] - 1
      tc[tail] = tc[tail] - 1
      break
    except:
      print "Not found: ", w

  if head in dlist:
    alist = [x for x in words if x[0] == tail and x[-1] == head]
    if len(alist) > 0:
      print "Choose: ", alist[0]
      continue

  if tail in llist:
    alist = [x for x in words if x[0] == tail]
    if len(alist) > 0:
      print "Choose: ", alist[0]
      continue

  alist = [x for x in words if x[0] == tail]
  print alist

  maxscore = 0
  who = alist[0]
#  if len(words) < 10:
  for a in alist:
    score = calc_score(words[:], hc.copy(), tc.copy(), a, True, 0)
    print a, " : ", score
    if score > maxscore:
      maxscore = score
      who = a

  words.remove(who)
  head = who[0]
  tail = who[-1]
  hc[head] = hc[head] - 1
  tc[tail] = tc[tail] - 1

  print hc,'\n',tc
  print words
  print
  print "Choose: ", who

このプログラムは手抜きで勝ち負けの判定が付いていません。(^^;
敵が選んだ単語を入力し、指定された手を選択していると勝てます。

パックマン


すみません。L1、L2、L3と全部手動で解きました。
シミュレーターはPythoncursesが書けると知って喜んで書いたものです。
問題をファイルに保存して第一引数として与えて下さい。
キーはh,j,k,lと"."とqとuです。
特徴は無限アンドゥ可能で、いつでも初期画面まで戻れます。

#!/usr/bin/env python

import curses, sys, traceback

# Global Timer
TIME_LIMIT = 0
MAP = []
monsters = []
dots = []
pacman = None
timer = 0

# first we must create a window object; it will fill the whole screen
screen = curses.initscr()

class PacMan:
  def __init__(self, x, y):
    self.x = x
    self.y = y
    self.eaten_dots = {}
    self.history = []
    self.keys = []
    self.isAlive = True
  def left(self):
    tmpX = self.x - 1
    if canMove(tmpX, self.y):
      self.history.append((self.x, self.y))
      self.x = tmpX
      return True
    else:
      return False
  def down(self):
    tmpY = self.y + 1
    if canMove(self.x, tmpY):
      self.history.append((self.x, self.y))
      self.y = tmpY
      return True
    else:
      return False
  def up(self):
    tmpY = self.y - 1
    if canMove(self.x, tmpY):
      self.history.append((self.x, self.y))
      self.y = tmpY
      return True
    else:
      return False
  def right(self):
    tmpX = self.x + 1
    if canMove(tmpX, self.y):
      self.history.append((self.x, self.y))
      self.x = tmpX
      return True
    else:
      return False
  def wait(self):
    self.history.append((self.x, self.y))
  def eat(self, timer):
    x = self.x
    y = self.y
    dot = (x, y)
    if dots.count(dot) > 0:
      self.eaten_dots[timer] = dot
      dots.remove(dot)
  def draw(self):
    px,py = self.history[-1]
    screen.addch(py, px, " ")
    screen.addch(self.y, self.x, "@", curses.color_pair(6))
  def getPosition(self):
    x = self.x
    y = self.y
    return (x, y)

def canMove(x, y):
  return False if MAP[y][x] == '#' else True

def getPacManLocation():
  return pacman.getPosition()

def getPacManPreviousLocation():
  return pacman.history[-1]

class Monster:
  (DEAD_END, AISLE, CROSSING) = range(3)
  (DOWN, LEFT, UP, RIGHT) = range(4)
  def __init__(self, x, y):
    self.x = x
    self.y = y
    self.symbol = '*'
    self.color  = curses.color_pair(7)
    self.dir = Monster.DOWN
    self.history = []
    self.walls = [False, False, False, False]

  def move(self):
    raise NotImplementedError

  def checkSquare(self):
    x = self.x
    y = self.y
    c = 0
    self.walls[Monster.DOWN ] = MAP[y+1][x  ] == '#'
    self.walls[Monster.LEFT ] = MAP[y  ][x-1] == '#'
    self.walls[Monster.UP   ] = MAP[y-1][x  ] == '#'
    self.walls[Monster.RIGHT] = MAP[y  ][x+1] == '#'
    c = self.walls.count(True)
    if   c == 3: return Monster.DEAD_END
    elif c == 2: return Monster.AISLE
    else       : return Monster.CROSSING

  def moveDown(self):
    self.y += 1
    self.dir = Monster.DOWN
  def moveLeft(self):
    self.x -= 1
    self.dir = Monster.LEFT
  def moveUp(self):
    self.y -= 1
    self.dir = Monster.UP
  def moveRight(self):
    self.x += 1
    self.dir = Monster.RIGHT

  def firstMove(self):
    x = self.x
    y = self.y
    self.checkSquare()
    self.defaultMove()

  def turnBack(self):
    if   self.dir == Monster.DOWN:
      self.dir = Monster.UP
      self.moveTo(Monster.UP)
    elif self.dir == Monster.LEFT:
      self.dir = Monster.RIGHT
      self.moveTo(Monster.RIGHT)
    elif self.dir == Monster.UP:
      self.dir = Monster.DOWN
      self.moveTo(Monster.DOWN)
    elif self.dir == Monster.RIGHT:
      self.dir = Monster.LEFT
      self.moveTo(Monster.LEFT)
    else:
      print "ERROR: Illegal direction in turnBack", self.dir

  def forward(self):
    x = self.x
    y = self.y
    d = self.dir
    w = self.walls
    if w[d] == False:
      self.moveTo(d)
    else:
      tmpd = d + 1 if d < 3 else 0
      if w[tmpd] == False:
        self.moveTo(tmpd)
      else:
        tmpd = d - 1 if d > 0 else 3
        if w[tmpd] == False:
          self.moveTo(tmpd)
        else:
          print "Error: Can't forward"

  def moveTo(self, d):
    self.history.append((self.x, self.y, self.dir))
    if   d == Monster.DOWN:
      self.moveDown()
    elif d == Monster.LEFT:
      self.moveLeft()
    elif d == Monster.UP:
      self.moveUp()
    elif d == Monster.RIGHT:
      self.moveRight()

  def defaultMove(self):
    if   self.walls[Monster.DOWN ] == False:
      self.moveTo(Monster.DOWN)
    elif self.walls[Monster.LEFT ] == False:
      self.moveTo(Monster.LEFT)
    elif self.walls[Monster.UP   ] == False:
      self.moveTo(Monster.UP)
    elif self.walls[Monster.RIGHT] == False:
      self.moveTo(Monster.RIGHT)
    else:
      print "ERROR: Can't move in defaultMove"
  
  def draw(self):
    (pacx, pacy) = getPacManLocation()
    (px, py, dir) = self.history[-1]
    if not(px == pacx and py == pacy):
      if dots.count((px,py)) == 0:
        screen.addch(py, px, ' ')
      else:
        screen.addch(py, px, '.', curses.color_pair(6))
    screen.addch(self.y, self.x, self.symbol, self.color)

  def rmonMove(self):
    # Try relative-right direction
    tmpd = self.dir + 1 if self.dir < 3 else 0
    if self.walls[tmpd] == False:
      self.moveTo(tmpd)
    elif self.walls[self.dir] == False:
      self.moveTo(self.dir)
    else:
      tmpd = self.dir - 1 if self.dir > 0 else 3
      self.moveTo(tmpd)

  def lmonMove(self):
    # Try relative-left direction
    tmpd = self.dir - 1 if self.dir > 0 else 3
    if self.walls[tmpd] == False:
      self.moveTo(tmpd)
    elif self.walls[self.dir] == False:
      self.moveTo(self.dir)
    else:
      tmpd = self.dir + 1 if self.dir < 3 else 0
      self.moveTo(tmpd)

  def killedIt(self):
    (px, py) = getPacManLocation()
    if self.x == px and self.y == py: return True
    (lx, ly, dir) = self.history[-1]
    (ppx, ppy) = getPacManPreviousLocation()
    if self.x == ppx and self.y == ppy and lx == px and ly == py: return True
    return False

  def undo(self):
    px = self.x
    py = self.y
    (x, y, dir) = self.history.pop()
    self.x = x
    self.y = y
    self.dir = dir
    (pacx, pacy) = getPacManLocation()
    if not(px == pacx and py == pacy):
      if dots.count((px,py)) == 0:
        screen.addch(py, px, ' ')
      else:
        screen.addch(py, px, '.', curses.color_pair(6))
    screen.addch(self.y, self.x, self.symbol, self.color)


class VMon(Monster):
  def __init__(self, x, y):
    Monster.__init__(self, x, y)
    self.symbol = 'V'
    self.color = curses.color_pair(3)

  def move(self):
    s = self.checkSquare()
    if   s == Monster.DEAD_END:
      self.turnBack()
    elif s == Monster.AISLE:
      self.forward()
    else:
      (px, py) = getPacManLocation()

      dy = self.y - py
      if dy > 0 and self.walls[Monster.UP] == False:
        self.moveTo(Monster.UP)
        return
      elif dy < 0 and self.walls[Monster.DOWN] == False:
        self.moveTo(Monster.DOWN)
        return

      dx = self.x - px
      if dx > 0 and self.walls[Monster.LEFT] == False:
        self.moveTo(Monster.LEFT)
        return
      elif dx < 0 and self.walls[Monster.RIGHT] == False:
        self.moveTo(Monster.RIGHT)
        return

      self.defaultMove()

class HMon(Monster):
  def __init__(self, x, y):
    Monster.__init__(self, x, y)
    self.symbol = 'H'
    self.color = curses.color_pair(2)

  def move(self):
    s = self.checkSquare()
    if   s == Monster.DEAD_END:
      self.turnBack()
    elif s == Monster.AISLE:
      self.forward()
    else:
      (px, py) = getPacManLocation()

      dx = self.x - px
      if dx > 0 and self.walls[Monster.LEFT] == False:
        self.moveTo(Monster.LEFT)
        return
      elif dx < 0 and self.walls[Monster.RIGHT] == False:
        self.moveTo(Monster.RIGHT)
        return

      dy = self.y - py
      if dy > 0 and self.walls[Monster.UP] == False:
        self.moveTo(Monster.UP)
        return
      elif dy < 0 and self.walls[Monster.DOWN] == False:
        self.moveTo(Monster.DOWN)
        return

      self.defaultMove()

class LMon(Monster):
  def __init__(self, x, y):
    Monster.__init__(self, x, y)
    self.symbol = 'L'
    self.color = curses.color_pair(4)

  def move(self):
    s = self.checkSquare()
    if   s == Monster.DEAD_END:
      self.turnBack()
    elif s == Monster.AISLE:
      self.forward()
    else:
      self.lmonMove()

class RMon(Monster):
  def __init__(self, x, y):
    Monster.__init__(self, x, y)
    self.symbol = 'R'
    self.color = curses.color_pair(5)

  def move(self):
    s = self.checkSquare()
    if   s == Monster.DEAD_END:
      self.turnBack()
    elif s == Monster.AISLE:
      self.forward()
    else:
      self.rmonMove()

class JMon(Monster):
  def __init__(self, x, y):
    Monster.__init__(self, x, y)
    self.symbol = 'J'
    self.color = curses.color_pair(7)
    self.isL = False

  def move(self):
    s = self.checkSquare()
    if   s == Monster.DEAD_END:
      self.turnBack()
    elif s == Monster.AISLE:
      self.forward()
    else:
      self.isL = not self.isL
      if self.isL:
        self.lmonMove()
      else:
        self.rmonMove()

  def undo(self):
    Monster.undo(self)
    s = self.checkSquare()
    if s == Monster.CROSSING:
      self.isL = not self.isL

def init_screen():
  # turn off keystroke echo
  curses.noecho()
  # keystrokes are honored immediately, rather than waiting for the
  # user to hit Enter
  curses.cbreak()
  # start color display (if it exists; could check with has_colors())
  curses.start_color()
  curses.use_default_colors()
  # set up a foreground/background color pair (can do many)
  curses.init_pair(0,curses.COLOR_BLACK,  curses.COLOR_BLACK)
  curses.init_pair(1,curses.COLOR_BLUE,   curses.COLOR_BLACK)
  curses.init_pair(2,curses.COLOR_RED,    curses.COLOR_BLACK)
  curses.init_pair(3,curses.COLOR_MAGENTA,curses.COLOR_BLACK)
  curses.init_pair(4,curses.COLOR_GREEN,  curses.COLOR_BLACK)
  curses.init_pair(5,curses.COLOR_CYAN,   curses.COLOR_BLACK)
  curses.init_pair(6,curses.COLOR_YELLOW, curses.COLOR_BLACK)
  curses.init_pair(7,curses.COLOR_WHITE,  curses.COLOR_BLACK)
  # clear screen
  screen.clear()
  # implement the actions done so far (just the clear())
  screen.refresh()
#  curses.curs_set(0)

def restore_screen():
  # restore "normal"--i.e. wait until hit Enter--keyboard mode
  curses.nocbreak()
  # restore keystroke echoing
  curses.echo()
  # required cleanup call
  curses.endwin()
#  curses.curs_set(0)

def draw_screen():
  for row, line in enumerate(MAP):
    for col, c in enumerate(line):
      if   c == '#': screen.addch(row, col, c, curses.color_pair(1))
      elif c == '.': screen.addch(row, col, c, curses.color_pair(6))
      elif c == 'H': screen.addch(row, col, c, curses.color_pair(2))
      elif c == 'V': screen.addch(row, col, c, curses.color_pair(3))
      elif c == 'L': screen.addch(row, col, c, curses.color_pair(4))
      elif c == 'R': screen.addch(row, col, c, curses.color_pair(5))
      elif c == 'J': screen.addch(row, col, c, curses.color_pair(7))
      elif c == '@': screen.addch(row, col, c, curses.color_pair(6))
  screen.addstr(len(MAP), 0, "T = 0")

def update_info():
  screen.addstr(len(MAP),     0, " " * curses.COLS)
  if pacman.isAlive:
    screen.addstr(len(MAP),     0, "Left = " + str(TIME_LIMIT - timer))
  else:
    screen.addstr(len(MAP), 0, "Pac-Man is dead.")
  screen.addstr(len(MAP) + 1, 0, " " * curses.COLS)
#  screen.addstr(len(MAP) + 1, 0, "".join(pacman.keys))
  screen.addstr(len(MAP) + 1, 0, str(len(pacman.keys)))

def main():
  global TIME_LIMIT
  f = open(sys.argv[1], 'r')

  TIME_LIMIT = int(f.readline())
  # This is screen width and height. Not used, but have to skip
  pos = f.readline().split()
  #x = int(pos[0])
  #y = int(pos[1])

  init_screen()

  global pacman, timer
  # Read maze
  for lc, line in enumerate(f):
    line = line[:-1]
    MAP.append(line)
    for i,c in enumerate(line):
      if   c == '.': dots.append((i,lc))
      elif c == 'V': monsters.append(VMon(i,lc))
      elif c == 'H': monsters.append(HMon(i,lc))
      elif c == 'L': monsters.append(LMon(i,lc))
      elif c == 'R': monsters.append(RMon(i,lc))
      elif c == 'J': monsters.append(JMon(i,lc))
      elif c == '@': pacman = PacMan(i, lc)

  draw_screen()
  
  while True:
    # read character from keyboard
    c = screen.getch()
    # was returned as an integer (ASCII); make it a character
    c = chr(c)
    if (not pacman.isAlive) and c != 'u':
      continue

    if c == 'u':
      if timer == 0: continue

      pacman.isAlive = True
      timer -= 1
      pacman.keys.pop()

      space = ' '
      if pacman.eaten_dots.has_key(timer):
        dot = pacman.eaten_dots[timer]
        del(pacman.eaten_dots[timer])
        dots.append(dot)
        space = '.'

      (px, py) = pacman.history.pop()
      screen.addch(pacman.y, pacman.x, space, curses.color_pair(6))
      screen.addch(py, px, "@", curses.color_pair(6))
      pacman.x = px
      pacman.y = py
      for m in monsters:
        m.undo()
      update_info()
      continue
 
    if timer == 0:
      for m in monsters:
        m.firstMove()
    else:
      for m in monsters:
        m.move()

    # quit?
    if   c == 'q':
      message = "Quit"
      break
    elif c == 'h':
      if pacman.left():
        pacman.eat(timer)
      else:
        for m in monsters:
          m.undo()
        continue
    elif c == 'j':
      if pacman.down():
        pacman.eat(timer)
      else:
        for m in monsters:
          m.undo()
        continue
    elif c == 'k':
      if pacman.up():
        pacman.eat(timer)
      else:
        for m in monsters:
          m.undo()
        continue
    elif c == 'l':
      if pacman.right():
        pacman.eat(timer)
      else:
        for m in monsters:
          m.undo()
        continue
    elif c == '.':
      pacman.wait()
    else:
      continue
    pacman.keys.append(c)

    # draw the character
    pacman.draw()
    for m in monsters:
      if m.killedIt():
        pacman.isAlive = False
      m.draw()

    timer += 1
    update_info()
    if timer >= TIME_LIMIT:
      message = "TIME OVER"
      break

    if len(dots) == 0 and pacman.isAlive:
      message = "You Win!"
      break

  restore_screen()

  print message
  print "".join(pacman.keys)
  print len(pacman.keys)

if __name__ == '__main__':
  # in case of execution error, have a smooth recovery and clear
  # display of error message (nice example of Python exception
  # handling); it is recommended that you use this format for all of
  # your Python curses programs; you can automate all this (and more)
  # by using the built-in function curses.wrapper(), but we've shown
  # it done "by hand" here to illustrate the issues involved
  try:
    main()
  except:
    restore_screen()
    # print error message re exception
    traceback.print_exc()

Pythoncursesを使う方法は以下のチュートリアル(PDF)を参考にさせて頂きました。
http://heather.cs.ucdavis.edu/~matloff/Python/PyCurses.pdf

また本当でしたら以下のUCBで行われているPAC-MAN Projectを参考にして探索プログラムを書く予定でした。しかし体力の限界に陥り諦めた次第です。
http://www-inst.eecs.berkeley.edu/~cs188/pacman/pacman.html

ここはさすがUCBでして、人口知能の授業にパックマンの解法を取り入れているそうです。
偶然にもプログラムは全てPythonで書かれており非常に参考になるサイトです。

Java屋から見たPython

Python初心者の言うことですのでマジに取らないでくださいw

Pythonの良いところ

  • とにかくリストの内包表記。最高すぎる。Javaにも欲しい
  • enumerate最高。JavaIteratorは結局indexが欲しくて使わない場合有り
  • 文字列がリストなところ
  • ネストするリストを多次元配列としてアクセスできる
  • dirとhelpでマニュアルがすぐに参照できる。JavaDocよりかなり有利
  • リストのインデックスにマイナスが使える。最後の一文字とか超簡単


Pythonの嫌なところ

  • ifでもclassでもforでもコロンがいること。絶対忘れてエラーになる
  • urllibが2つあって、両方使わなければいけない
  • 標準ライブラリとpypiの区別がつかない
  • class変数のアクセスにクラス内でもクラス名がいる
  • privateとかpublicなどのアクセサがない
  • global変数のアクセスにglobal宣言がいる。これが正しい気もするが面倒
  • UTFが文字列と別。u''とか面倒
  • enumがない
  • switch-caseがない
  • List.append()が先頭じゃなくて最後に付ける。List.popも最後から取る。consじゃないの?

慣れの問題ですが色々と異なりすぎて疲れたのも確かです。
JavaSE7ではリテラルでリストとか使えるようになるはずなので、割とPythonでなくて良いんでないとか言ったりして。
今回、Windowsでは専用のIDE入れたりして快適でした。でもMacがちょっとか。
逆にActivePythonではcursesが使えなくて、cygwin限定になったりとかもありましたが。
RPDBが全然動かなくて困ったりとか、Pythoncursesはset_cursが動かんとか色々ありましたけど。
とりあえずもう少しコーディングの体力付けないと駄目だなとは思いました。
次回があったらもうちょっとまともなコードを書きたいです。