mjaiをプロコンっぽい入出力でやれるpythonスクリプト

標準入力から取って、標準出力に出すだけでよくなりました。
python2.7で動作確認。*1
こんな感じで起動します。

python mjai-stdio.py mjsonp://localhost:11600/default ./a.out

a.outのソースコードは、こんな感じで書ける。

#include <iostream>
#include <sstream>

using namespace std;

int id;

int main() {

  while(true) {
    string line;
    getline(cin, line);

    stringstream ss(line);

    string type;
    ss >> type;
    if(type == "hello") {
      string protocol;
      int protocol_version;
      ss >> protocol >> protocol_version;
      cout << "join tsumogiri default" << endl;
    }
    else if(type == "start_game") {
      ss >> id;
      cout << "none" << endl;
    }
    else if(type == "end_game") {
      break;
    }
    else if(type == "tsumo") {
      int actor;
      string pai;
      ss >> actor >> pai;
      if(actor == id) {
        cout << "dahai " << id << " " << pai << " true" << endl; 
      }
      else {
        cout << "none" << endl;
      }
    }
    else if(type == "error") {
      break;
    }
    else {
      cout << "none" << endl;
    }
  }

  return 0;
}

入力のフォーマットは(書く元気が無いので)、mjai-stdio.pyを見るか、上のC++コードのを動かした時のログhttps://gist.github.com/ca9db0581fef1c231520を見れば微妙にわかるかも。*2
元のプロトコル自体は、http://d.hatena.ne.jp/wistery_k/20120915/1347733419を参照してください。
mjai-stdio.pyは以下。

import urlparse
import json
import sys
from socket import *

import subprocess
from collections import OrderedDict

def m(list):
  return "%d %s" % (len(list), ' '.join(list))

def b(bool):
  return 'true' if bool else 'false'

def eb(str):
  if str == 'true':
    return True
  elif str == 'false':
    return False
  else:
    assert false, "should not happen"

def decode(a):
  t = a['type']
  if t == 'hello':
    return "hello %s %d" % ("mjptp", a['protocol_version'])
  elif t == 'start_game':
    return "start_game %d %s" % (a['id'], m(a['names']))
  elif t == "start_kyoku":
    return "start_kyoku %s %d %d %d %d %s %s" % (a['bakaze'], a['kyoku'], a['honba'], a['kyotaku'], a['oya'], a['dora_marker'], m(map(m, a['tehais'])))
  elif t == 'tsumo':
    return "tsumo %d %s" % (a['actor'], a['pai'])
  elif t == 'dahai':
    return "dahai %d %s %s" % (a['actor'], a['pai'], b(a['tsumogiri']))
  elif t == 'reach':
    return "reach %d" % a['actor']
  elif t == 'reach_accepted':
    return "reach_accepted %d %s %s" % (a['actor'], m(map(str, a['deltas'])), m(map(str, a['scores'])))
  elif t == 'pon':
    return "pon %d %d %s %s" % (a['actor'], a['target'], a['pai'], m(a['consumed']))
  elif t == 'chi':
    return "chi %d %d %s %s" % (a['actor'], a['target'], a['pai'], m(a['consumed']))
  elif t == 'ankan':
    return "ankan %d %d %s %s" % (a['actor'], a['target'], a['pai'], m(a['consumed']))
  elif t == 'daiminkan':
    return "daiminkan %d %d %s %s" % (a['actor'], a['target'], a['pai'], m(a['consumed']))
  elif t == 'kakan':
    return "kakan %d %s" % (a['actor'], a['pai'])
  elif t == 'hora':
    return "hora %d %d %s %s %s %s %d %d %d %s %s" % (a['actor'], a['target'], a['pai'], m(a['uradora_markers']), m(a['hora_tehais']), m(map(lambda yn: "%s %d" % (yn[0], yn[1]), a['yakus'])), a['fu'], a['fan'], a['hora_points'], m(map(str, a['deltas'])), m(map(str, a['scores'])))
  elif t == 'ryukyoku':
    return "ryukyoku %s %s %s %s %s" % (a['reason'], m(map(m, a['tehais'])), m(map(b, a['tenpais'])), m(map(str, a['deltas'])), m(map(str, a['scores'])))
  elif t == 'end_kyoku':
    return "end_kyoku"
  elif t == 'end_game':
    return "end_game"
  elif t == 'error':
    return "error %s" % a['message']
  else:
    assert false, "should not happen"

def encode(line):
  st = line.split()
  t = st[0]
  
  if t == 'none':
    return OrderedDict([('type','none')])
  elif t == 'join':
    return OrderedDict([('type','join'), ('name',st[1]), ('room',st[2])])
  elif t == 'dahai':
    return OrderedDict([('type','dahai'), ('actor',int(st[1])), ('pai',st[2]), ('tsumogiri',eb(st[3]))])
  elif t == 'reach':
    return OrderedDict([('type','reach'), ('actor',int(st[1]))])
  elif t == 'hora':
    return OrderedDict([('type','hora'), ('actor',int(st[1])), ('target',int(st[2])), ('pai',st[3])])
  elif t == 'pon':
    return OrderedDict([('type','pon'), ('actor',int(st[1])), ('target',int(st[2])), ('pai',st[3]), ('consumed',st[4:])])
  elif t == 'chi':
    return OrderedDict([('type','chi'), ('actor',int(st[1])), ('target',int(st[2])), ('pai',st[3]), ('consumed',st[4:])])
  elif t == 'ankan':
    return OrderedDict([('type','ankan'), ('actor',int(st[1])), ('target',int(st[2])), ('pai',st[3]), ('consumed',st[4:])])
  elif t == 'daiminkan':
    return OrderedDict([('type','daiminkan'), ('actor',int(st[1])), ('target',int(st[2])), ('pai',st[3]), ('consumed',st[4:])])
  elif t == 'kakan':
    return OrderedDict([('type','kakan'), ('actor',int(st[1])), ('pai',st[2])])
  else:
    assert false, "should not happen"

def start(host, port, room, program):

  p = subprocess.Popen(program, stdin=subprocess.PIPE, stdout=subprocess.PIPE)

  sock = socket(AF_INET, SOCK_STREAM)
  sock.connect((host, port))

  try:
    for line in sock.makefile():
      if not line:
        break
      print "<-\t%s" % line.rstrip()
      action = json.loads(line)
      p.stdin.write(decode(action) + '\n')
      resline = p.stdout.readline()
      if not resline:
        break
      res = json.dumps(encode(resline), sort_keys=False)
      print "->\t%s" % res
      sock.send(res + '\n')
  except:
    sock.close()
    p.kill()
    raise

def main(args):
  if len(args) < 2:
    print "please specify an URI and a command."
    print "usage: python mjai-stdio.py mjsonp://localhost:11600/default ./a.out"
    return
  o = urlparse.urlparse(args[0])
    
  start(o.hostname, o.port, o.path[1:], args[1:])

main(sys.argv[1:])

decode, encodeあたりのタイピングゲーが辛かった。。

*1:といってもバグってる可能性は結構ある。

*2:一つだけ補足してみると、配列の前には必ず要素数が来ます。