#!/usr/bin/env python # # The Dice Machine # Copyright (C) 2005 Nicholas J. M. Haggin # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program 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. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import sys import os try: import readline except ImportError: print 'Warning: Could not load module readline.' print 'Tab completion and command history not available.\n' pass import string import cStringIO import re import cmd import random # A parser for D&D-style dice expressions. class DiceExpressionParser(object): # States for our lexer. START = 0 INT = 1 # Token values for our lexer. DTOK = 256 ARITHTOK = 257 INTTOK = 258 EOS = 259 # Constructor. def __init__(self): self.cursign = "+" # Lexer for D&D-style dice roll expressions. def lexer(self, input): lexeme = '' token = -1 state = self.START # Loop to construct tokens. while True: # Read a character. nextchar = input.read(1) # This lexer has become an unwashed n00b. if state == self.START: # We're out of stuff; return end-of-input. if not nextchar: return (self.EOS, '') # We have a rolls/sides separator. elif nextchar == 'd': lexeme = nextchar token = self.DTOK break # We have an arithmetic operator. elif nextchar in '+-': lexeme = nextchar token = self.ARITHTOK break # We eat whitespace. elif nextchar in string.whitespace: continue # Assemble an integer. elif nextchar in string.digits: lexeme = nextchar state = self.INT # This lexer has slain a digit and gained skill. elif state == self.INT: # We're out of string. Token is complete. if not nextchar: token = self.INTTOK break # We're at the end of an integer. if nextchar in string.whitespace + 'd+-': if nextchar in 'd+-': input.seek(-1, 1) token = self.INTTOK break # Keep assembling an integer. elif nextchar in string.digits: lexeme = lexeme + nextchar return (token, lexeme); # Top-level grammar production. Match a "roll sequence." def parse(self, input, pairs): self.cursign = "+" # Match a roll. baseroll = self.match_roll(input) if not baseroll: return None pairs.append((self.cursign, baseroll[0], baseroll[1])) next_tok, lexeme = self.lexer(input) if next_tok == self.EOS: return True # Match an arithmetic operator and then a roll. else: if next_tok == self.ARITHTOK: self.cursign = lexeme self.parse(input, pairs) return True else: print 'Parse error' return None # Other grammar production. Match a single roll. def match_roll(self, input): # Match int or d. next_tok, lexeme = self.lexer(input) if next_tok == self.INTTOK: rolls = lexeme elif next_tok == self.DTOK: rolls = '1' else: print 'Parse error' return None # Match d or int. next_tok, lexeme = self.lexer(input) if next_tok == self.DTOK: next_tok, lexeme = self.lexer(input) if next_tok != self.INTTOK: print 'Parse error' return None elif next_tok == self.INTTOK: pass else: print 'Parse error' return None sides = lexeme return (rolls, sides) # The command interpreter. For each command foo, there is: # # -A do_foo() function that does the operation # -If help is provided, a help_foo() function that shows the help class dice_cmd(cmd.Cmd): pattern = re.compile('([0-9]*)d([0-9]+)') def __init__(self): cmd.Cmd.__init__(self) self.intro = """ The Dice Machine Copyright (C) 2005 Nicholas J. M. Haggin The Dice Machine comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. See http://www.gnu.org/licenses/gpl.html for more information. Type 'help' to see a list of commands, or 'help command' for help on an individual command. """ self.prompt = '$ ' self.ruler = '-' self.parser = DiceExpressionParser() random.seed(26401) # Don't do anything when the user gives an empty line. def emptyline(self): return None def help_help(self): print """ Syntax: help Display a list of commands. Syntax: help Get help on a particular command. """ # Parse and evaluate the roll string. def make_rolls(self, args, outputlevel = 0): # Initialize. io = cStringIO.StringIO(args) rollpairs = [] # Parse and evaluate. if self.parser.parse(io, rollpairs): accum = 0 for pair in rollpairs: iter = int(pair[1]) sides = int(pair[2]) if outputlevel == 1: print 'Rolling ' + pair[1] + 'd' + pair[2] for i in range(0, iter): foo = random.randint(1, sides) if outputlevel == 1: print foo exec('accum = accum ' + pair[0] + ' foo') print 'Final Result:', accum def do_roll(self, args): self.make_rolls(args) def help_roll(self): print """ Syntax: roll Generate a roll of the dice according to said pattern. The pattern is a single D&D-style d, i.e., 4d8 = four rolls of an octahedral die, or a series of such expressions added or subtracted. """ def do_rollv(self, args): self.make_rolls(args, outputlevel = 1) def help_rollv(self): print """ Syntax: rollv Operates identically to roll, but shows each individual roll as well as the combined result. """ def do_shell(self, args): os.system(args) def help_shell(self): print """ Syntax: ! Execute the command under the system shell. """ def do_exit(self, args): sys.exit(0) def help_exit(self): print '\nSyntax: exit | quit\nQuit the program.\n' def do_quit(self, args): sys.exit(0) def help_quit(self): self.help_exit() # Run the command-line interface. def run_cli(): cmd_interp = dice_cmd() cmd_interp.cmdloop() if __name__ == '__main__': run_cli()