source: pycgibattler/trunk/index.cgi @ 86

Revision 86, 8.9 KB checked in by atzm, 13 years ago (diff)

changes for r85

  • Property svn:executable set to *
  • Property svn:keywords set to Id
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4#  Copyright (C) 2010 by Atzm WATANABE <atzm@atzm.org>
5#
6#  This program is free software; you can redistribute it and/or modify it
7#  under the terms of the GNU General Public License (version 2) as
8#  published by the Free Software Foundation.  It is distributed in the
9#  hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
10#  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
11#  PURPOSE.  See the GNU General Public License for more details.
12#
13# $Id$
14#
15
16from __future__ import with_statement
17
18import os
19import re
20import sys
21import cgi
22import json
23import glob
24import copy
25import fcntl
26import shutil
27import hashlib
28import ConfigParser
29
30try:
31    import cStringIO as _StringIO
32except ImportError:
33    import StringIO as _StringIO
34
35try:
36    import cPickle as _pickle
37except ImportError:
38    import pickle as _pickle
39
40from Cheetah.Template import Template
41import pycodebattler
42
43
44CONFIG = ConfigParser.SafeConfigParser()
45CONFIG.read('pycgibattler.conf')
46
47
48class ListChooser:
49    def __init__(self, code):
50        self._code = code
51        self._digest = int(hashlib.sha512(code).hexdigest(), 16)
52
53    def lslide(self, i):
54        self._digest <<= i
55
56    def rslide(self, i):
57        self._digest >>= i
58
59    def choose(self, list_):
60        return list_[self._digest % len(list_)]
61
62
63class CharacterManager:
64    VALID_ID = re.compile(r'^[a-f0-9]{128}$')
65    DATA_DIR = os.path.expanduser(CONFIG.get('data', 'basedir'))
66
67    def __init__(self, id_):
68        if not self.VALID_ID.match(id_):
69            raise ValueError('Invalid ID: %s' % id_)
70        self._id = id_
71        self._locker = os.path.join(self.DATA_DIR, '.%s.lck' % id_)
72        self._prefix = os.path.join(self.DATA_DIR, id_)
73        self._codepath = os.path.join(self._prefix, '%s.py' % id_)
74        self._datapath = os.path.join(self._prefix, '%s.dat' % id_)
75
76    def id(self):
77        return self._id
78
79    def mtime(self):
80        with open(self._locker) as fp:
81            fcntl.flock(fp, fcntl.LOCK_SH)
82            return os.fstat(fp.fileno()).st_mtime
83
84    def delete(self):
85        with open(self._locker, 'w') as fp:
86            fcntl.flock(fp, fcntl.LOCK_EX)
87            shutil.rmtree(self._prefix, True)
88            os.remove(self._locker)  # XXX
89
90    def load(self):
91        with open(self._locker) as fp:
92            fcntl.flock(fp, fcntl.LOCK_SH)
93            code = open(self._codepath).read()
94            warrior = _pickle.load(open(self._datapath))
95            return code, warrior
96
97    def entry(self):
98        code, warrior = self.load()
99        return {
100            'id':      self.id(),
101            'mtime':   self.mtime(),
102            'code':    code,
103            'warrior': warrior,
104            'skills':  [warrior.skill(s) for s in warrior.skill_list()],
105        }
106
107    def flatten(self):
108        code, warrior = self.load()
109        skills = []
110        for name in warrior.skill_list():
111            sk = warrior.skill(name)
112            skills.append({
113                'name':  name,
114                'type':  str(sk.skilltype()).split('.')[-1],
115                'level': sk.level(),
116                'point': sk.point(),
117            })
118        return {
119            'id':            self.id(),
120            'mtime':         int(self.mtime()),
121            'name':          warrior.name(),
122            'hitpoint':      warrior.hitpoint(),
123            'skillpoint':    warrior.skillpoint(),
124            'strength':      warrior.strength(),
125            'concentration': warrior.concentration(),
126            'defense':       warrior.defense(),
127            'agility':       warrior.agility(),
128            'luck':          warrior.luck(),
129            'skills':        skills,
130        }
131
132    @classmethod
133    def dump(klass, name, code):
134        self = klass(hashlib.sha512(code).hexdigest())
135        cname = os.path.join(self._prefix, os.path.basename(name))
136        warrior = self.make_warrior(code)
137
138        with open(self._locker, 'w') as fp:
139            fcntl.flock(fp, fcntl.LOCK_EX)
140
141            try:
142                os.mkdir(self._prefix)
143            except OSError:
144                if not os.path.isdir(self._prefix):
145                    raise
146
147            open(self._codepath, 'w').write(code)
148            _pickle.dump(warrior, open(self._datapath, 'w'))
149
150            try:
151                os.symlink(os.path.basename(self._codepath), cname)
152            except OSError:
153                if not os.path.islink(cname):
154                    raise
155
156        return self
157
158    @classmethod
159    def make_warrior(klass, code):
160        chara_names = [x.strip() for x in open(os.path.expanduser(
161            CONFIG.get('character', 'name_list_file'))).xreadlines()]
162        skill_data = json.load(open(os.path.expanduser(
163            CONFIG.get('character', 'skill_list_file'))))
164
165        lc = ListChooser(code)
166        name = lc.choose(chara_names)
167        skills = []
168
169        for i in range(lc.choose(range(1, 4))):
170            lc.lslide(i)
171            sk = lc.choose(skill_data)
172            skname = sk['name'].encode('utf-8', 'replace')
173            skpoint = sk['point']
174            sktype = getattr(pycodebattler.skill, sk['type'])
175            sklevel = sk['level']
176            sk = pycodebattler.skill.Skill(skname, skpoint, sktype, sklevel)
177            skills.append(sk)
178
179        return pycodebattler.warrior.Warrior.make(
180            name, _StringIO.StringIO(code), skills)
181
182    @classmethod
183    def list(klass):
184        return sorted((klass(os.path.basename(d)) for d in
185                       glob.iglob(os.path.join(klass.DATA_DIR, '*'))
186                       if klass.VALID_ID.match(os.path.basename(d))),
187                      cmp=klass.mtimecmp)
188
189    @classmethod
190    def sweep(klass, thresh=CONFIG.getint('limit', 'max_entries')):
191        for self in klass.list()[thresh:]:
192            self.delete()
193
194    @staticmethod
195    def mtimecmp(a, b):
196        return int(b.mtime()) - int(a.mtime())
197
198
199def do_battle(warriors, turn=CONFIG.getint('battle', 'max_turn')):
200    proto = pycodebattler.battle.BattleProto(warriors, turn)
201    result = {
202        'winner':  None,
203        'history': [],
204    }
205
206    try:
207        for turn in proto.battle():
208            actions = []
209            try:
210                for attacker, skname, damages in turn:
211                    actions.append({
212                        'attacker': copy.deepcopy(attacker),
213                        'skill':    copy.deepcopy(skname),
214                        'damages':  copy.deepcopy(damages),
215                    })
216            except pycodebattler.battle.DrawGame:
217                pass
218
219            result['history'].append({
220                'actions':  actions,
221                'warriors': copy.deepcopy(warriors),
222            })
223
224        result['winner'] = proto.winner()
225
226    except pycodebattler.battle.DrawGame:
227        pass
228
229    return result
230
231
232def httpdump(template, entries=[], result={}):
233    tmpl = Template(open(os.path.expanduser(template)).read())
234    tmpl.script = sys.argv[0]
235    tmpl.config = CONFIG
236    tmpl.entries = entries
237    tmpl.result = result
238    sys.stdout.write('Content-type: text/html; charset=UTF-8\r\n\r\n')
239    sys.stdout.write(tmpl.respond().encode('utf-8', 'replace'))
240    sys.stdout.flush()
241
242
243def jsondump(data):
244    sys.stdout.write('Content-type: application/json; charset=UTF-8\r\n\r\n')
245    sys.stdout.write(json.dumps(data, ensure_ascii=False) + '\n')
246    sys.stdout.flush()
247
248
249def main():
250    form = cgi.FieldStorage()
251
252    if 'mode' in form and form.getfirst('mode') == 'json':
253        if 'id' not in form:
254            ids = [cm.id() for cm in CharacterManager.list()]
255        else:
256            tmp = form['id']
257            if type(tmp) is not list:
258                tmp = [form['id']]
259            ids = [id_.value for id_ in tmp]
260
261        data = {}
262        try:
263            for id_ in ids:
264                data[id_] = CharacterManager(id_).flatten()
265        except:
266            return jsondump({'result': {}})
267        return jsondump({'result': data})
268
269    if 'id' in form:
270        entries = [CharacterManager(form.getfirst('id')).entry()]
271        return httpdump(CONFIG.get('template', 'character'), entries=entries)
272
273    if 'warrior' in form:
274        ids = form['warrior']
275        if type(ids) is not list:
276            ids = [ids]
277        if len(ids) > CONFIG.getint('battle', 'max_entries'):
278            raise ValueError('battle warriors too long')
279        warriors = [CharacterManager(id_.value).load()[1] for id_ in ids]
280        result = do_battle(warriors)
281        return httpdump(CONFIG.get('template', 'battle'), result=result)
282
283    if 'filename' in form:
284        item = form['filename']
285        if item.file and item.filename:
286            code = item.file.read(CONFIG.getint('limit', 'max_size'))
287            if code:
288                CharacterManager.dump(item.filename, code)
289
290    CharacterManager.sweep()
291
292    entries = [cm.entry() for cm in CharacterManager.list()]
293    return httpdump(CONFIG.get('template', 'index'), entries=entries)
294
295
296if __name__ == '__main__':
297    try:
298        main()
299    except:
300        httpdump(CONFIG.get('template', 'error'))
Note: See TracBrowser for help on using the repository browser.