funny little 190 lines command line math tutor I wrote for my 7-year old.

DouhetSukd at gmail.com DouhetSukd at gmail.com
Fri Sep 14 09:12:30 CEST 2007


Sadly lacking in multi-media bells and whistles.  But my daughter
actually likes playing with it.

Coded on windows, but no reason it shouldn't work on Linux/OS X.

(hopefully the indentation won't be too mangled by usenet.  apologies
in advance if that is the case)

Enjoy.

Sample session:

D:\user\python>mathtester.py -1 3 -o /+ -r 2
Hello, I am your friendly computer.  I have a math quiz for you

 Problem #1

                 1 +  2 = 3
                                        Answered: 3.  Correct.

 Problem #2

                 3 +  3 = 6
                                        Answered: 6.  Correct.


Congratulations, you got  2 right out of  2


code:

************************************
"""
mathtester.py -h for more help .

HELPS YOUR KIDS TO LEARN MATH WITH INTERACTIVE PROMPTS.  EXAMPLE:

Hello, I am windoze, your friendly computer.  I have a math quiz for
you

 Problem #0

                 4 -  3 = 1
                                        Answered: 1.  Correct.

 Problem #1

                 8 -  6 = 14
                                        Answered:14.  Wrong.  Correct
answer: 2

This will pick two numbers, each between 0 and 10.  It will either add
or substract them.  Any questions with solutions less than zero
or greater than 20 will be rejected and re-created.

You get this by running it with all defaults.  i.e.

c:\>python mathtester.py


to get something like doing the multiplication of 1 to 3 times 10, run

c:\>python mathtester -o * -1 3 -2 10 -H 30

only multiplication '*' will be used, number #1 will be between 0 and
3.  number #2 will be between 0 and 10.  answers greater than 30 will
be rejected.

"""

import os, sys, optparse, random

_module_doc = __doc__

class Writer:
    """write to file and output to console"""
    def  __init__(self,fout):
        self.fout = fout
    def __call__(self,s,echoconsole=True):
        self.fout.write("\n" + s.replace("\t","    "))
        if echoconsole:
            print s

class Worker:

    fout = None
    filename = "test.txt"
    trivial_tolerance_percentage = 40
    accepted_operators = "+-*"

    def formatoptions(self):
        """most of the work is already done by optparser"""
        #filter on acceptable operators.  exit if none found.
        self.operators = [i for i in self.options.operators if i in
self.accepted_operators]
        if not self.operators:
            print ERROR_MSG_OPERATORS % {"supported" :
self.accepted_operators}
            sys.exit()
        #both number range limits are equal.
        if not self.options.numberRange2:
            self.options.numberRange2 = self.options.numberRange


    def  __init__(self):
        self.fout = open(self.filename,"w")
        Worker.writer = Writer(self.fout)
        self.formatoptions()
        self.asked = self.correct = 0
        self.set_asked = set()

    def process(self):
        """main loop - prints greetings, loops on questions, print
result summary and exits"""
        try:
          self.writer(GREETING)
          [self.doTest(cntr) for cntr in
range(0,self.options.repetitions)]
        except KeyboardInterrupt:
          pass
        self.writer(FAREWELL % vars(self))
        return True

    def doTest(self,number):
        """the workhorse function.  randomly picks a question + vets
the solution
        then asks the question and checks the answer"""
        self.writer("\n Problem #%s" % (number+1))

        while True:
            #pick random numbers and operator
            number1  = random.randint(0,self.options.numberRange)
            number2  = random.randint(0,self.options.numberRange2)
            operator =
self.operators[random.randint(0,len(self.operators)-1)]

            if number1 < 2 or number2 < 2 and
random.randint(1,100)>self.trivial_tolerance_percentage:
                #potentially reject trivial problems consisting of
zeros and ones.
                continue

            #flip a coin and put number #1 either right or left -
            #remember that the number ranges can be unequal so there
is a difference
            if random.randint(1,2) % 2:
                problem  = "%2s %s %2s" % (number1,operator,number2)
            else:
                problem  = "%2s %s %2s" % (number2,operator,number1)

            #use eval to get the expected solution
            solution = eval(problem)

            #solution within accepted bounds? if not, build another
problem
            if not ((solution >= self.options.minSolution) and
(solution <= self.options.maxSolution)):
                continue

            #don't ask the same question multiple times...
            if problem in self.set_asked:
                continue
            self.set_asked.add(problem)

            #answers other than digits will be ignored and the prompt
will repeat
            while True:
                try:
                    prompt = "\n\t\t" + problem + " = "
                    self.writer(prompt,False)
                    answer = raw_input(prompt)
                    answer = int("%s" % (answer.strip()))
                except ValueError:
                    continue
                break

            self.asked += 1
            #good, or bad, answer?
            if (answer == solution):
                msg = GOOD_ANSWER % {"answer":answer}
                self.correct += 1
            else:
                msg = BAD_ANSWER % locals()
            self.writer(msg,True)
            return

#not exactly i18n, but it's a start
GOOD_ANSWER         = "\t\t\t\t\tAnswered:%(answer)2s.  Correct."
BAD_ANSWER          = "\t\t\t\t\tAnswered:%(answer)2s.  Wrong.
Correct answer:%(solution)s"
GREETING            = "Hello, I am your friendly computer.  I have a
math quiz for you"
FAREWELL            = "\n\nCongratulations, you got %(correct)2s right
out of %(asked)2s"

HELP_NUMBER1        = "max value for number #1.  this keeps things
from getting too hard or easy. default:%s"
HELP_NUMBER2        = """max value for number #2, defaults to use the
same as number #1.  the reason you may want to have different ranges
is say doing the table of multiplications for 1 to 3 times 10"""
HELP_MATH_OPERATORS = "math operators to use/  defaults: %s"
HELP_REPETITIONS    = "number of questions to ask [%s]"
HELP_MIN_SOLUTION   = "minimum solution you want  [%s].  used to have
simple or hard questions"
HELP_MAX_SOLUTION   = "maximum solution you want  [%s].  used to have
simple or hard questions"

ERROR_MSG_OPERATORS = "no valid operators provided.  supported
operators: %(supported)s"


def getParser():
    """use optparse to prompt on the command line"""
    parser = optparse.OptionParser(usage = _module_doc)

    default = "10"
 
parser.add_option("-1",action="store",type="int",default=default,dest="numberRange",help=HELP_NUMBER1
% (default))

 
parser.add_option("-2",action="store",type="int",dest="numberRange2",help=HELP_NUMBER2)

    default = "+-"
    parser.add_option("-
o",action="store",dest="operators",default=default,help=HELP_MATH_OPERATORS
% default)

    default = "20"
    parser.add_option("-
r",action="store",type="int",dest="repetitions",default=default,help=HELP_REPETITIONS
% default)

    default = "0"
    parser.add_option("-
L",action="store",type="int",dest="minSolution",default=default,help=HELP_MIN_SOLUTION
% default)

    default = "20"
    parser.add_option("-
H",action="store",type="int",dest="maxSolution",default=default,help=HELP_MAX_SOLUTION
% default)

    return parser


def main():
    worker = success = 0
    try:
        (Worker.options, Worker.args) =
getParser().parse_args(sys.argv[1:])
        worker = Worker()
        success = worker.process()
    finally:
        if worker and worker.fout:
            worker.fout.close()
        if success :
            os.startfile(Worker.filename)

if __name__ == "__main__":
    main()




More information about the Python-list mailing list