PyCodeBattler β

Pythonista 達の熱き闘いが,

今,始まる...!!

[2010/12/20 00:08:00] 登録

名前

春日野さくら

ステータス

HP SP 攻撃力 集中力 防御力 素早さ
1789 30 112 43 11 6 2

必殺技

名前 タイプ レベル 消費 SP
地走り RangeAttackType 2 5
大いずな落とし SingleAttackType 2 14
ローリングスクラッチ RangeAttackType 1 9

コード

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  Copyright (C) 2010 by Atzm WATANABE <atzm@atzm.org>
#
#  This program is free software; you can redistribute it and/or modify it
#  under the terms of the GNU General Public License (version 2) as
#  published by the Free Software Foundation.  It is distributed in the
#  hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
#  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
#  PURPOSE.  See the GNU General Public License for more details.
#
# $Id: index.cgi 77 2010-12-23 17:16:07Z atzm $
#

from __future__ import with_statement

import os
import re
import sys
import cgi
import json
import glob
import copy
import fcntl
import shutil
import hashlib
import ConfigParser

try:
    import cStringIO as _StringIO
except ImportError:
    import StringIO as _StringIO

try:
    import cPickle as _pickle
except ImportError:
    import pickle as _pickle

from Cheetah.Template import Template
import pycodebattler


CONFIG = ConfigParser.SafeConfigParser()
CONFIG.read('pycgibattler.conf')


class ListChooser:
    def __init__(self, code):
        self._code = code
        self._digest = int(hashlib.sha512(code).hexdigest(), 16)

    def lslide(self, i):
        self._digest <<= i

    def rslide(self, i):
        self._digest >>= i

    def choose(self, list_):
        return list_[self._digest % len(list_)]


class CharacterManager:
    VALID_ID = re.compile(r'^[a-f0-9]{128}$')
    DATA_DIR = os.path.expanduser(CONFIG.get('data', 'basedir'))

    def __init__(self, id_):
        if not self.VALID_ID.match(id_):
            raise ValueError('Invalid ID: %s' % id_)
        self._id = id_
        self._locker = os.path.join(self.DATA_DIR, '.%s.lck' % id_)
        self._prefix = os.path.join(self.DATA_DIR, id_)
        self._codepath = os.path.join(self._prefix, '%s.py' % id_)
        self._datapath = os.path.join(self._prefix, '%s.dat' % id_)

    def id(self):
        return self._id

    def mtime(self):
        with open(self._locker) as fp:
            fcntl.flock(fp, fcntl.LOCK_SH)
            return os.fstat(fp.fileno()).st_mtime

    def delete(self):
        with open(self._locker, 'w') as fp:
            fcntl.flock(fp, fcntl.LOCK_EX)
            shutil.rmtree(self._prefix, True)
            os.remove(self._locker)  # XXX

    def load(self):
        with open(self._locker) as fp:
            fcntl.flock(fp, fcntl.LOCK_SH)
            code = open(self._codepath).read()
            warrior = _pickle.load(open(self._datapath))
            return code, warrior

    def entry(self):
        code, warrior = self.load()
        return {
            'id':      self.id(),
            'mtime':   self.mtime(),
            'code':    code,
            'warrior': warrior,
            'skills':  [warrior.skill(s) for s in warrior.skill_list()],
        }

    def flatten(self):
        code, warrior = self.load()
        skills = []
        for name in warrior.skill_list():
            sk = warrior.skill(name)
            skills.append({
                'name':  name,
                'type':  str(sk.skilltype()).split('.')[-1],
                'level': sk.level(),
                'point': sk.point(),
            })
        return {
            'id':            self.id(),
            'mtime':         int(self.mtime()),
            'name':          warrior.name(),
            'hitpoint':      warrior.hitpoint(),
            'skillpoint':    warrior.skillpoint(),
            'strength':      warrior.strength(),
            'concentration': warrior.concentration(),
            'defense':       warrior.defense(),
            'agility':       warrior.agility(),
            'luck':          warrior.luck(),
            'skills':        skills,
        }

    @classmethod
    def dump(klass, name, code):
        self = klass(hashlib.sha512(code).hexdigest())
        cname = os.path.join(self._prefix, os.path.basename(name))
        warrior = self.make_warrior(code)

        with open(self._locker, 'w') as fp:
            fcntl.flock(fp, fcntl.LOCK_EX)

            try:
                os.mkdir(self._prefix)
            except OSError:
                if not os.path.isdir(self._prefix):
                    raise

            open(self._codepath, 'w').write(code)
            _pickle.dump(warrior, open(self._datapath, 'w'))

            try:
                os.symlink(os.path.basename(self._codepath), cname)
            except OSError:
                if not os.path.islink(cname):
                    raise

        return self

    @classmethod
    def make_warrior(klass, code):
        chara_names = [x.strip() for x in open(os.path.expanduser(
            CONFIG.get('character', 'name_list_file'))).xreadlines()]
        skill_data = json.load(open(os.path.expanduser(
            CONFIG.get('character', 'skill_list_file'))))

        lc = ListChooser(code)
        name = lc.choose(chara_names)
        skills = []

        for i in range(lc.choose(range(1, 4))):
            lc.lslide(i)
            sk = lc.choose(skill_data)
            skname = sk['name'].encode('utf-8', 'replace')
            skpoint = sk['point']
            sktype = getattr(pycodebattler.skill, sk['type'])
            sklevel = sk['level']
            sk = pycodebattler.skill.Skill(skname, skpoint, sktype, sklevel)
            skills.append(sk)

        return pycodebattler.warrior.Warrior(
            name, _StringIO.StringIO(code), skills)

    @classmethod
    def list(klass):
        return sorted((klass(os.path.basename(d)) for d in
                       glob.iglob(os.path.join(klass.DATA_DIR, '*'))
                       if klass.VALID_ID.match(os.path.basename(d))),
                      cmp=klass.mtimecmp)

    @classmethod
    def sweep(klass, thresh=CONFIG.getint('limit', 'max_entries')):
        for self in klass.list()[thresh:]:
            self.delete()

    @staticmethod
    def mtimecmp(a, b):
        return int(b.mtime()) - int(a.mtime())


def do_battle(warriors, turn=CONFIG.getint('battle', 'max_turn')):
    proto = pycodebattler.battle.BattleProto(warriors, turn)
    result = {
        'winner':  None,
        'history': [],
    }

    try:
        for turn in proto.battle():
            actions = []
            try:
                for attacker, skname, damages in turn:
                    actions.append({
                        'attacker': copy.deepcopy(attacker),
                        'skill':    copy.deepcopy(skname),
                        'damages':  copy.deepcopy(damages),
                    })
            except pycodebattler.battle.DrawGame:
                pass

            result['history'].append({
                'actions':  actions,
                'warriors': copy.deepcopy(warriors),
            })

        result['winner'] = proto.winner()

    except pycodebattler.battle.DrawGame:
        pass

    return result


def httpdump(template, entries=[], result={}):
    tmpl = Template(open(os.path.expanduser(template)).read())
    tmpl.script = sys.argv[0]
    tmpl.config = CONFIG
    tmpl.entries = entries
    tmpl.result = result
    sys.stdout.write('Content-type: text/html; charset=UTF-8\r\n\r\n')
    sys.stdout.write(tmpl.respond().encode('utf-8', 'replace'))
    sys.stdout.flush()


def jsondump(data):
    sys.stdout.write('Content-type: application/json; charset=UTF-8\r\n\r\n')
    sys.stdout.write(json.dumps(data, ensure_ascii=False) + '\n')
    sys.stdout.flush()


def main():
    form = cgi.FieldStorage()

    if 'mode' in form and form.getfirst('mode') == 'json':
        if 'id' in form:
            data = {}
            ids = form['id']

            if type(ids) is not list:
                ids = [ids]

            if len(ids) > CONFIG.getint('battle', 'max_entries'):
                return jsondump({})

            try:
                for id_ in ids:
                    data[id_.value] = CharacterManager(id_.value).flatten()
            except:
                return jsondump({})

            return jsondump(data)

        return jsondump([cm.id() for cm in CharacterManager.list()])

    if 'id' in form:
        entries = [CharacterManager(form.getfirst('id')).entry()]
        return httpdump(CONFIG.get('template', 'character'), entries=entries)

    if 'warrior' in form:
        ids = form['warrior']
        if type(ids) is not list:
            ids = [ids]
        if len(ids) > CONFIG.getint('battle', 'max_entries'):
            raise ValueError('battle warriors too long')
        warriors = [CharacterManager(id_.value).load()[1] for id_ in ids]
        result = do_battle(warriors)
        return httpdump(CONFIG.get('template', 'battle'), result=result)

    if 'filename' in form:
        item = form['filename']
        if item.file and item.filename:
            code = item.file.read(CONFIG.getint('limit', 'max_size'))
            if code:
                CharacterManager.dump(item.filename, code)

    CharacterManager.sweep()

    entries = [cm.entry() for cm in CharacterManager.list()]
    return httpdump(CONFIG.get('template', 'index'), entries=entries)


if __name__ == '__main__':
    try:
        main()
    except:
        httpdump(CONFIG.get('template', 'error'))