source: pycgibattler/trunk/index.cgi @ 74

Revision 74, 6.8 KB checked in by atzm, 13 years ago (diff)

fixed bug around char encoding

  • Property svn:executable set to *
  • Property svn:keywords set to Id
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# $Id$
4
5from __future__ import with_statement
6
7import os
8import re
9import sys
10import cgi
11import json
12import glob
13import copy
14import fcntl
15import shutil
16import hashlib
17import ConfigParser
18
19try:
20    import cStringIO as _StringIO
21except ImportError:
22    import StringIO as _StringIO
23
24try:
25    import cPickle as _pickle
26except ImportError:
27    import pickle as _pickle
28
29from Cheetah.Template import Template
30import pycodebattler
31
32
33CONFIG = ConfigParser.SafeConfigParser()
34CONFIG.read('pycgibattler.conf')
35
36
37class ListChooser:
38    def __init__(self, code):
39        self._code = code
40        self._digest = int(hashlib.sha512(code).hexdigest(), 16)
41
42    def lslide(self, i):
43        self._digest <<= i
44
45    def rslide(self, i):
46        self._digest >>= i
47
48    def choose(self, list_):
49        return list_[self._digest % len(list_)]
50
51
52class CharacterManager:
53    VALID_ID = re.compile(r'^[a-f0-9]{128}$')
54    DATA_DIR = os.path.expanduser(CONFIG.get('data', 'basedir'))
55
56    def __init__(self, id_):
57        if not self.VALID_ID.match(id_):
58            raise ValueError('Invalid ID: %s' % id_)
59        self._id = id_
60        self._locker = os.path.join(self.DATA_DIR, '.%s.lck' % id_)
61        self._prefix = os.path.join(self.DATA_DIR, id_)
62        self._codepath = os.path.join(self._prefix, '%s.py' % id_)
63        self._datapath = os.path.join(self._prefix, '%s.dat' % id_)
64
65    def id(self):
66        return self._id
67
68    def mtime(self):
69        with open(self._locker) as fp:
70            fcntl.flock(fp, fcntl.LOCK_SH)
71            return os.fstat(fp.fileno()).st_mtime
72
73    def delete(self):
74        with open(self._locker, 'w') as fp:
75            fcntl.flock(fp, fcntl.LOCK_EX)
76            shutil.rmtree(self._prefix, True)
77            os.remove(self._locker)  # XXX
78
79    def load(self):
80        with open(self._locker) as fp:
81            fcntl.flock(fp, fcntl.LOCK_SH)
82            code = open(self._codepath).read()
83            warrior = _pickle.load(open(self._datapath))
84            return code, warrior
85
86    def flatten(self):
87        code, warrior = self.load()
88        return {
89            'id':      self.id(),
90            'mtime':   self.mtime(),
91            'code':    code,
92            'warrior': warrior,
93            'skills':  [warrior.skill(s) for s in warrior.skill_list()],
94        }
95
96    @classmethod
97    def dump(klass, name, code):
98        self = klass(hashlib.sha512(code).hexdigest())
99        cname = os.path.join(self._prefix, os.path.basename(name))
100        warrior = self.make_warrior(code)
101
102        with open(self._locker, 'w') as fp:
103            fcntl.flock(fp, fcntl.LOCK_EX)
104
105            try:
106                os.mkdir(self._prefix)
107            except OSError:
108                if not os.path.isdir(self._prefix):
109                    raise
110
111            open(self._codepath, 'w').write(code)
112            _pickle.dump(warrior, open(self._datapath, 'w'))
113
114            try:
115                os.symlink(os.path.basename(self._codepath), cname)
116            except OSError:
117                if not os.path.islink(cname):
118                    raise
119
120        return self
121
122    @classmethod
123    def make_warrior(klass, code):
124        chara_names = [x.strip() for x in open(os.path.expanduser(
125            CONFIG.get('character', 'name_list_file'))).xreadlines()]
126        skill_data = json.load(open(os.path.expanduser(
127            CONFIG.get('character', 'skill_list_file'))))
128
129        lc = ListChooser(code)
130        name = lc.choose(chara_names)
131        skills = []
132
133        for i in range(lc.choose(range(1, 4))):
134            lc.lslide(i)
135            sk = lc.choose(skill_data)
136            skname = sk['name'].encode('utf-8')
137            skpoint = sk['point']
138            sktype = getattr(pycodebattler.skill, sk['type'])
139            sklevel = sk['level']
140            sk = pycodebattler.skill.Skill(skname, skpoint, sktype, sklevel)
141            skills.append(sk)
142
143        return pycodebattler.warrior.Warrior(
144            name, _StringIO.StringIO(code), skills)
145
146    @classmethod
147    def list(klass):
148        return sorted((klass(os.path.basename(d)) for d in
149                       glob.iglob(os.path.join(klass.DATA_DIR, '*'))
150                       if klass.VALID_ID.match(os.path.basename(d))),
151                      cmp=klass.mtimecmp)
152
153    @classmethod
154    def sweep(klass, thresh=CONFIG.getint('limit', 'max_entries')):
155        for self in klass.list()[thresh:]:
156            self.delete()
157
158    @staticmethod
159    def mtimecmp(a, b):
160        return int(b.mtime()) - int(a.mtime())
161
162
163def do_battle(warriors, turn=CONFIG.getint('battle', 'max_turn')):
164    proto = pycodebattler.battle.BattleProto(warriors, turn)
165    result = {
166        'winner':  None,
167        'history': [],
168    }
169
170    try:
171        for turn in proto.battle():
172            actions = []
173            try:
174                for attacker, skname, damages in turn:
175                    actions.append({
176                        'attacker': copy.deepcopy(attacker),
177                        'skill':    copy.deepcopy(skname),
178                        'damages':  copy.deepcopy(damages),
179                    })
180            except pycodebattler.battle.DrawGame:
181                pass
182
183            result['history'].append({
184                'actions':  actions,
185                'warriors': copy.deepcopy(warriors),
186            })
187
188        result['winner'] = proto.winner()
189
190    except pycodebattler.battle.DrawGame:
191        pass
192
193    return result
194
195
196def httpdump(template, entries=[], result={}):
197    tmpl = Template(open(os.path.expanduser(template)).read())
198    tmpl.script = sys.argv[0]
199    tmpl.config = CONFIG
200    tmpl.entries = entries
201    tmpl.result = result
202    sys.stdout.write('Content-type: text/html; charset=UTF-8\r\n\r\n')
203    sys.stdout.write(tmpl.respond().encode('utf-8'))
204    sys.stdout.flush()
205
206
207def main():
208    form = cgi.FieldStorage()
209
210    if 'id' in form:
211        entries = [CharacterManager(form.getfirst('id')).flatten()]
212        return httpdump(CONFIG.get('template', 'character'), entries=entries)
213
214    if 'warrior' in form:
215        ids = form['warrior']
216        if type(ids) is list:
217            if len(ids) > CONFIG.getint('battle', 'max_entries'):
218                raise ValueError('battle warriors too long')
219            warriors = [CharacterManager(id_.value).load()[1] for id_ in ids]
220            result = do_battle(warriors)
221            return httpdump(CONFIG.get('template', 'battle'), result=result)
222
223    if 'filename' in form:
224        item = form['filename']
225        if item.file and item.filename:
226            code = item.file.read(CONFIG.getint('limit', 'max_size'))
227            if code:
228                CharacterManager.dump(item.filename, code)
229
230    CharacterManager.sweep()
231
232    entries = [cm.flatten() for cm in CharacterManager.list()]
233    return httpdump(CONFIG.get('template', 'index'), entries=entries)
234
235
236if __name__ == '__main__':
237    try:
238        main()
239    except:
240        httpdump(CONFIG.get('template', 'error'))
Note: See TracBrowser for help on using the repository browser.