source: pycgibattler/trunk/index.cgi @ 57

Revision 57, 7.1 KB checked in by atzm, 13 years ago (diff)

add pycgibattler

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