2012-03-20 19:53:06 +00:00
|
|
|
#!/usr/bin/python
|
|
|
|
#
|
|
|
|
# battleman.py - RPG Battle Manager
|
|
|
|
#
|
|
|
|
# A table-top RPG battle flow manager
|
|
|
|
# Tuned pretty specifically to D&D 4e for now... need a templatized system
|
|
|
|
# to do anything fancier... may develop that at some point.
|
2012-03-22 16:03:44 +00:00
|
|
|
#
|
|
|
|
# future features:
|
|
|
|
# * an option for passing in multiple files that contain combatant definitions
|
2012-03-23 22:34:57 +00:00
|
|
|
|
2012-03-29 22:28:21 +00:00
|
|
|
import sys
|
|
|
|
sys.path.append('lib/')
|
2012-03-23 16:14:00 +00:00
|
|
|
|
2012-03-29 21:07:18 +00:00
|
|
|
import cPickle as pickle
|
2012-03-29 22:06:09 +00:00
|
|
|
import argparse
|
2012-03-30 04:55:34 +00:00
|
|
|
import cmd
|
2012-03-29 21:07:18 +00:00
|
|
|
import os.path
|
2012-03-29 22:28:21 +00:00
|
|
|
import battle
|
|
|
|
from battle import CombatGroup
|
|
|
|
from battle import Combatant
|
|
|
|
import easyinput
|
2012-03-22 22:07:25 +00:00
|
|
|
|
|
|
|
|
2012-03-29 22:06:09 +00:00
|
|
|
def main():
|
2012-03-29 22:28:21 +00:00
|
|
|
btl = battle.Battle()
|
2012-03-29 21:07:18 +00:00
|
|
|
|
2012-03-29 22:06:09 +00:00
|
|
|
### This is the pickling jar
|
|
|
|
battle_pickle = None
|
|
|
|
bp_io_failed = False
|
|
|
|
BP_FILE = os.path.expanduser('~/.config/4etools/battleman/battle.pickle')
|
|
|
|
###
|
2012-03-29 21:07:18 +00:00
|
|
|
|
|
|
|
# Make sure config directory exists
|
|
|
|
if not os.path.exists(os.path.dirname(BP_FILE)):
|
|
|
|
os.makedirs(os.path.dirname(BP_FILE))
|
|
|
|
|
2012-03-29 22:06:09 +00:00
|
|
|
# Get command-line args
|
|
|
|
settings = parse_args()
|
2012-03-21 22:31:11 +00:00
|
|
|
|
2012-03-20 19:53:06 +00:00
|
|
|
|
2012-03-22 17:25:02 +00:00
|
|
|
print "Welcome to 4e Battle Manager.\n"
|
2012-03-29 22:06:09 +00:00
|
|
|
|
|
|
|
# Resume battle if needed
|
2012-04-03 03:42:31 +00:00
|
|
|
# fixme: critical: indexes get reused when restoring! Maybe switch to shelve?
|
2012-03-29 22:06:09 +00:00
|
|
|
if settings.resume:
|
|
|
|
try:
|
|
|
|
with open(BP_FILE, 'r') as f:
|
2012-03-29 22:28:21 +00:00
|
|
|
btl = pickle.load(f)
|
|
|
|
battle_pickle = pickle.dumps(btl)
|
2012-03-29 22:06:09 +00:00
|
|
|
except:
|
|
|
|
print "Error: Couldn't resume. Quitting to preserve our pickle."
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
else:
|
|
|
|
# hard-coding test cases for now.
|
2012-04-03 03:42:31 +00:00
|
|
|
# fixme: Eventually, use a state-saving text file that's easy to edit, or at least copy...
|
|
|
|
adele = Combatant("Adele", hp=26, pc=True, surges=8, sw=1)
|
|
|
|
adele_dict = {adele.index: adele}
|
|
|
|
aristaire = Combatant("Aristaire", hp=20, pc=True, surges=6, sw=1)
|
|
|
|
aristaire_dict = {aristaire.index: aristaire}
|
|
|
|
|
|
|
|
foobolds = {}
|
|
|
|
for i in range(5):
|
|
|
|
c = Combatant("Foobold", hp=50)
|
|
|
|
foobolds[c.index] = c
|
|
|
|
|
|
|
|
barglins = {}
|
|
|
|
for i in range(2):
|
|
|
|
c = Combatant("Barglin", hp=50)
|
|
|
|
barglins[c.index] = c
|
|
|
|
|
|
|
|
orcs = {}
|
|
|
|
for i in range(2):
|
|
|
|
c = Combatant("Orc", hp=50)
|
|
|
|
orcs[c.index] = c
|
|
|
|
|
|
|
|
btl.add_group(CombatGroup("Adele", adele_dict, 2))
|
|
|
|
btl.add_group(CombatGroup("Aristaire", aristaire_dict, 0))
|
|
|
|
btl.add_group(CombatGroup("Foobolds", foobolds, 20))
|
|
|
|
btl.add_group(CombatGroup("Barglins", barglins, 3))
|
|
|
|
btl.add_group(CombatGroup("Orcs of Baz", orcs, 1))
|
2012-03-22 16:03:44 +00:00
|
|
|
|
2012-03-29 22:28:21 +00:00
|
|
|
print btl
|
2012-03-30 04:55:34 +00:00
|
|
|
|
|
|
|
cmd_parser = CommandParser(btl, battle_pickle, BP_FILE)
|
|
|
|
cmd_parser.cmdloop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CommandParser(cmd.Cmd):
|
|
|
|
"""Parse the commands from the command-line."""
|
|
|
|
|
|
|
|
def __init__(self, btl, battle_pickle, BP_FILE):
|
|
|
|
cmd.Cmd.__init__(self)
|
|
|
|
|
|
|
|
self.btl = btl
|
|
|
|
self.battle_pickle = battle_pickle
|
|
|
|
self.BP_FILE = BP_FILE
|
2012-04-01 21:09:37 +00:00
|
|
|
self.doc_header = 'Available commands (type help <command> for more help)'
|
2012-03-30 04:55:34 +00:00
|
|
|
self.prompt = '\n> '
|
|
|
|
|
|
|
|
|
|
|
|
def postloop(self):
|
|
|
|
# Re-pickle and write if changed after every query. It's cheap
|
|
|
|
# and we only have to run at user-speed anyway
|
|
|
|
old_bp = self.battle_pickle
|
2012-04-01 21:09:37 +00:00
|
|
|
self.battle_pickle = pickle.dumps(self.btl)
|
2012-03-30 04:55:34 +00:00
|
|
|
|
|
|
|
if old_bp != self.battle_pickle:
|
|
|
|
try:
|
|
|
|
with open(self.BP_FILE, 'w') as f:
|
|
|
|
f.write(self.battle_pickle)
|
|
|
|
except Exception:
|
|
|
|
if not self.bp_io_failed:
|
|
|
|
print("Warning: can't write the battle pickle. Resuming later will fail.")
|
|
|
|
self.bp_io_failed = True
|
|
|
|
|
|
|
|
|
2012-03-31 21:38:01 +00:00
|
|
|
# This allows us to do partial command completion without <tab>,
|
|
|
|
# as long as
|
|
|
|
def default(self, line):
|
|
|
|
cmd, data, line = self.parseline(line)
|
|
|
|
cmds = self.completenames(cmd)
|
|
|
|
num_cmds = len(cmds)
|
|
|
|
if num_cmds == 1:
|
|
|
|
getattr(self, 'do_'+cmds[0])(data)
|
|
|
|
elif num_cmds > 1:
|
|
|
|
sys.stdout.write('Error: Ambiguous command: {}'.format(cmd))
|
|
|
|
else:
|
|
|
|
print 'Error: Unrecognized command {}'.format(cmd)
|
|
|
|
|
|
|
|
|
2012-04-01 21:09:37 +00:00
|
|
|
# We are overriding do_help to avoid printing info about
|
|
|
|
# undocumented commands
|
|
|
|
def do_help(self, arg):
|
|
|
|
if arg:
|
|
|
|
Cmd.cmd.do_help(arg)
|
|
|
|
else:
|
|
|
|
# Everything from here to the end is lifted straight
|
|
|
|
# out of Cmd.cmd.do_help()
|
|
|
|
names = self.get_names()
|
|
|
|
cmds_doc = []
|
|
|
|
cmds_undoc = []
|
|
|
|
help = {}
|
|
|
|
for name in names:
|
|
|
|
if name[:5] == 'help_':
|
|
|
|
help[name[5:]]=1
|
|
|
|
names.sort()
|
|
|
|
# There can be duplicates if routines overridden
|
|
|
|
prevname = ''
|
|
|
|
for name in names:
|
|
|
|
if name[:3] == 'do_':
|
|
|
|
if name == prevname:
|
|
|
|
continue
|
|
|
|
prevname = name
|
|
|
|
cmd=name[3:]
|
|
|
|
if cmd in help:
|
|
|
|
cmds_doc.append(cmd)
|
|
|
|
del help[cmd]
|
|
|
|
elif getattr(self, name).__doc__:
|
|
|
|
cmds_doc.append(cmd)
|
|
|
|
else:
|
|
|
|
cmds_undoc.append(cmd)
|
|
|
|
self.stdout.write("%s\n"%str(self.doc_leader))
|
|
|
|
self.print_topics(self.doc_header, cmds_doc, 15,80)
|
|
|
|
self.print_topics(self.misc_header, help.keys(),15,80)
|
|
|
|
# self.print_topics(self.undoc_header, cmds_undoc, 15,80)
|
|
|
|
|
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
def do_add(self, line):
|
|
|
|
"""add [N]
|
|
|
|
Add the specified number of groups"""
|
|
|
|
|
2012-03-31 20:20:44 +00:00
|
|
|
data = parse_data(line)
|
2012-03-30 04:55:34 +00:00
|
|
|
|
|
|
|
if len(data) >= 1:
|
|
|
|
num_groups = int(data[0])
|
|
|
|
else:
|
|
|
|
num_groups = easyinput.input_int('number of groups')
|
|
|
|
|
|
|
|
for i in range(1, num_groups+1):
|
|
|
|
print "Adding group {}".format(i)
|
|
|
|
self.btl.add_group(CombatGroup.from_input())
|
|
|
|
|
|
|
|
|
|
|
|
def do_begin(self, line):
|
|
|
|
"""begin
|
|
|
|
Begins the battle. Rolls initiative for NPCs and prompts for PCs"""
|
|
|
|
|
|
|
|
self.btl.begin()
|
|
|
|
|
|
|
|
|
|
|
|
def do_print(self, line):
|
|
|
|
"""print [index]
|
|
|
|
Print detailed info for combatant with index, or combatant or group with initiative"""
|
|
|
|
|
2012-03-31 20:20:44 +00:00
|
|
|
data = parse_data(line)
|
2012-03-30 04:55:34 +00:00
|
|
|
|
|
|
|
if len(data) >= 1:
|
|
|
|
c = self.btl.get_combatant(int(data[0]))
|
|
|
|
if not c:
|
|
|
|
print 'Error: Invalid combatant index.'
|
|
|
|
else:
|
|
|
|
print c.format_full_info()
|
|
|
|
else:
|
|
|
|
print self.btl.format_current_group()
|
|
|
|
|
|
|
|
|
|
|
|
def do_list(self, line):
|
|
|
|
"""list
|
|
|
|
Lists a summary of all of the combat groups and their members"""
|
2012-03-29 21:07:18 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
print self.btl.format_combatants()
|
2012-03-21 17:58:59 +00:00
|
|
|
|
2012-03-29 21:07:18 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
def do_damage(self, line):
|
|
|
|
"""damage [index] [amount]
|
|
|
|
Deals damage to the specified combatant"""
|
2012-03-26 21:38:08 +00:00
|
|
|
|
2012-03-31 20:20:44 +00:00
|
|
|
data = parse_data(line)
|
2012-03-24 01:46:53 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
c = battle.do_combatant_select(self.btl, data)
|
|
|
|
if not c:
|
|
|
|
return
|
2012-03-24 01:46:53 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
if len(data) >= 2:
|
|
|
|
amount = int(data[1])
|
|
|
|
else:
|
|
|
|
amount = easyinput.input_int('damage')
|
|
|
|
|
2012-03-31 20:20:44 +00:00
|
|
|
c.damage(amount)
|
2012-03-30 04:55:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
def do_heal(self, line):
|
|
|
|
"""heal [index] [amount]
|
|
|
|
Heal hit points for the specified combatant"""
|
|
|
|
|
2012-03-31 20:20:44 +00:00
|
|
|
data = parse_data(line)
|
2012-03-30 04:55:34 +00:00
|
|
|
|
|
|
|
c = battle.do_combatant_select(self.btl, data)
|
2012-03-25 18:35:20 +00:00
|
|
|
if not c:
|
2012-03-30 04:55:34 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
if len(data) >= 2:
|
|
|
|
amount = int(data[1])
|
2012-03-25 18:35:20 +00:00
|
|
|
else:
|
2012-03-30 04:55:34 +00:00
|
|
|
amount = easyinput.input_int('amount')
|
|
|
|
|
|
|
|
c.heal(amount)
|
2012-03-25 18:35:20 +00:00
|
|
|
|
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
def do_temp(self, line):
|
|
|
|
"""temp [index] [amount]
|
|
|
|
Add temporary hit points to the specified combatant"""
|
2012-03-23 20:54:27 +00:00
|
|
|
|
2012-03-31 20:20:44 +00:00
|
|
|
data = parse_data(line)
|
2012-03-23 20:54:27 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
c = battle.do_combatant_select(self.btl, data)
|
|
|
|
if not c:
|
|
|
|
return
|
|
|
|
|
|
|
|
if len(data) >= 2:
|
|
|
|
amount = int(data[1])
|
|
|
|
else:
|
|
|
|
amount = easyinput.input_int('amount')
|
2012-03-22 03:27:45 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
c.add_temp_hp(amount)
|
2012-03-22 03:27:45 +00:00
|
|
|
|
2012-03-23 20:54:27 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
def do_rmtemp(self, line):
|
|
|
|
"""rmtemp [index] [amount]
|
|
|
|
Remove temporary hit points from the specified combatant"""
|
2012-03-23 20:54:27 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
do_stub()
|
2012-03-23 16:14:00 +00:00
|
|
|
|
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
def do_surge(self, line):
|
|
|
|
"""surge [index] [heal]
|
|
|
|
Combatant with index uses a healing surge. If heal is 0, don't heal the combatant"""
|
2012-03-23 21:09:23 +00:00
|
|
|
|
2012-03-31 20:20:44 +00:00
|
|
|
data = parse_data(line)
|
2012-03-30 04:55:34 +00:00
|
|
|
|
|
|
|
c = battle.do_combatant_select(self.btl, data)
|
|
|
|
if not c:
|
|
|
|
return
|
|
|
|
|
|
|
|
heal = True
|
|
|
|
if len(data) >= 2 and data[1] == '0':
|
|
|
|
heal = False
|
|
|
|
c.use_surge(heal)
|
2012-03-23 21:09:23 +00:00
|
|
|
|
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
def do_wind(self, line):
|
|
|
|
"""wind [index]
|
|
|
|
Use Second Wind for combatant"""
|
2012-03-23 21:09:23 +00:00
|
|
|
|
2012-03-31 20:20:44 +00:00
|
|
|
data = parse_data(line)
|
2012-03-24 01:57:08 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
c = battle.do_combatant_select(self.btl, data)
|
|
|
|
if not c:
|
|
|
|
return
|
|
|
|
c.use_second_wind()
|
2012-03-24 01:57:08 +00:00
|
|
|
|
2012-03-22 22:21:17 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
def do_cond(self, line):
|
|
|
|
"""cond [index] [name] [type] [duration] [start|end]
|
|
|
|
Add a temporary condition to a combatant, optionally specifying the condition name, type (s or t), duration and what phase of the combatant's turn it expires on"""
|
2012-03-22 22:21:17 +00:00
|
|
|
|
2012-03-31 20:20:44 +00:00
|
|
|
data = parse_data(line)
|
2012-03-23 21:09:23 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
duration = None
|
|
|
|
end_type = 'e'
|
2012-03-23 21:09:23 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
c = battle.do_combatant_select(self.btl, data)
|
|
|
|
if not c:
|
|
|
|
return
|
2012-03-23 20:54:27 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
name = easyinput.do_data_input_str(data, 1, 'condition name')
|
|
|
|
ctype = easyinput.do_data_input_str(data, 2, 'condition type', default='s', show_default=True)
|
2012-03-24 01:06:46 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
if ctype == 't':
|
|
|
|
duration = easyinput.do_data_input_int(data, 3, 'duration')
|
|
|
|
end_type = easyinput.do_data_input_str(data, 4, '(s)tart|(e)nd', default='e', show_default=True)
|
2012-03-24 01:06:46 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
c.add_condition(name, ctype, duration, end_type)
|
2012-03-25 05:08:56 +00:00
|
|
|
|
2012-03-23 19:06:13 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
def do_rmcond(self, line):
|
|
|
|
"""rmcond [index] [condition_index]
|
|
|
|
Remove a condition from a combatant early."""
|
2012-03-23 19:06:13 +00:00
|
|
|
|
2012-03-31 20:20:44 +00:00
|
|
|
data = parse_data(line)
|
2012-03-23 21:44:06 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
c = battle.do_combatant_select(self.btl, data)
|
|
|
|
if not c:
|
|
|
|
return
|
2012-03-23 21:44:06 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
if len(data) >= 2:
|
|
|
|
index = int(data[1])
|
|
|
|
else:
|
|
|
|
cond = c.choose_condition()
|
|
|
|
index = None
|
|
|
|
if cond:
|
|
|
|
index = cond['index']
|
2012-03-23 21:44:06 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
if index != None:
|
|
|
|
c.remove_condition(index)
|
2012-03-23 21:44:06 +00:00
|
|
|
|
2012-03-25 05:53:32 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
def do_recharge(self, line):
|
|
|
|
"""recharge [index] [recharge_index]
|
|
|
|
Use a rechargable power"""
|
2012-03-25 05:53:32 +00:00
|
|
|
|
2012-03-31 20:20:44 +00:00
|
|
|
data = parse_data(line)
|
2012-03-25 05:53:32 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
c = battle.do_combatant_select(self.btl, data)
|
|
|
|
if not c:
|
|
|
|
return
|
2012-03-25 05:53:32 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
if len(data) >= 2:
|
|
|
|
index = int(data[1])
|
|
|
|
else:
|
|
|
|
r = c.choose_recharge_power()
|
|
|
|
index = None
|
|
|
|
if r:
|
|
|
|
index = r['index']
|
2012-03-24 01:57:08 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
if index != None:
|
|
|
|
c.use_recharge_power(index)
|
2012-03-24 01:57:08 +00:00
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
|
|
|
|
def do_wait(self, line):
|
|
|
|
"""wait
|
2012-04-03 03:42:31 +00:00
|
|
|
Removes the specified combatant from the initiative roster and add them to the wait list."""
|
2012-03-30 04:55:34 +00:00
|
|
|
|
2012-04-03 03:42:31 +00:00
|
|
|
data = parse_data(line)
|
|
|
|
|
|
|
|
c = battle.do_combatant_select(self.btl, data)
|
|
|
|
if not c:
|
|
|
|
return
|
|
|
|
|
|
|
|
self.btl.wait(c.index)
|
2012-03-30 04:55:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
def do_unwait(self, line):
|
|
|
|
"""unwait
|
2012-04-03 03:42:31 +00:00
|
|
|
Removes the specified combatant from the wait list and adds them back into the initiative roster."""
|
2012-03-30 04:55:34 +00:00
|
|
|
|
2012-04-03 03:42:31 +00:00
|
|
|
data = parse_data(line)
|
|
|
|
|
|
|
|
c = battle.do_combatant_select(self.btl, data)
|
|
|
|
if not c:
|
|
|
|
return
|
|
|
|
|
|
|
|
self.btl.unwait(c.index)
|
2012-03-30 04:55:34 +00:00
|
|
|
|
|
|
|
|
2012-03-31 21:41:10 +00:00
|
|
|
def do_next(self, line):
|
|
|
|
"""next
|
|
|
|
Steps to the next combatant in initiative order. This handles saving throws, effects that end at beginning and ends of turns, and round incrementing."""
|
|
|
|
self.btl.next_combatant()
|
|
|
|
|
|
|
|
|
2012-03-30 04:55:34 +00:00
|
|
|
def do_sync(self, line):
|
|
|
|
"""sync
|
|
|
|
This function is still a stub"""
|
2012-03-31 21:41:10 +00:00
|
|
|
|
|
|
|
# Since the postloop pickles & writes, we don't actually need to
|
|
|
|
# do a damn thing here - just let it be an 'empty' command.
|
|
|
|
pass
|
2012-03-30 04:55:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
def do_EOF(self, line):
|
|
|
|
self.do_quit(line)
|
|
|
|
|
|
|
|
|
|
|
|
def do_quit(self, line):
|
|
|
|
"""quit
|
|
|
|
Exits the program. If a battle is in progress, it is temporarily saved and can be resumed by running the program with --resume next time."""
|
|
|
|
|
2012-04-01 21:09:37 +00:00
|
|
|
return True
|
2012-03-24 01:57:08 +00:00
|
|
|
|
|
|
|
|
2012-03-31 20:20:44 +00:00
|
|
|
|
|
|
|
def parse_data(line):
|
|
|
|
data = line.split(' ')
|
|
|
|
if data == ['']:
|
|
|
|
data = []
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
2012-03-24 01:57:08 +00:00
|
|
|
def do_stub():
|
2012-03-25 18:20:09 +00:00
|
|
|
print "Sorry, this is a stub function"
|
2012-03-24 01:57:08 +00:00
|
|
|
|
|
|
|
|
2012-03-29 22:06:09 +00:00
|
|
|
def parse_args():
|
|
|
|
parser = argparse.ArgumentParser(description='Command-line interface to manage battle data for D&D 4e', formatter_class=argparse.RawTextHelpFormatter)
|
|
|
|
parser.add_argument('--resume', '-r', action='store_true', help='Resume the battle from the last run of the program')
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
|
|
|
2012-03-20 19:53:06 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|