patternpythonMinor
Transform Python math game to OOP
Viewed 0 times
gamemathpythontransformoop
Problem
Background: I'm trying to learn basic OOP with Python. I have this program I wrote to help my son practice math problems. It does addition, subtraction, multiplication, and division. He can choose easy, medium, or hard for each operator.
Here's an excerpt of the code. It's the "easy" addition function. (The "medium" and "hard" functions simply choose bigger random numbers.)
And here's the "easy" Subtraction function. (The multiplication and division functions are very similar.)
My Question: Could this script be shortened and/or simplified by re-writing it in OOP style? If so, can you show me an example of what that might look like?
Here's an excerpt of the code. It's the "easy" addition function. (The "medium" and "hard" functions simply choose bigger random numbers.)
def addition_problems_1():
global name
score = 0
while score < 30:
a = randint(1,10)
b = randint(1,10)
sum = a + b
answer = int(raw_input("%i + %i = " % (a, b)))
if answer == sum:
score = score + 1
print "Good job. Current score is %d" % score
elif answer != sum:
print "Oops, the correct answer is %i. Try another one." % sum
print "Good job, %s. You passed this course!" % name
enter_lobby()And here's the "easy" Subtraction function. (The multiplication and division functions are very similar.)
def subtraction_problems_1():
global name
score = 0
while score b:
sum = a - b
answer = int(raw_input("%i - %i = " % (a, b)))
else:
sum = b - a
answer = int(raw_input("%i - %i = " % (b, a)))
if answer == sum:
score = score + 1
print "Good job. Current score is %d" % score
elif answer != sum:
print "Oops, the correct answer is %i. Try another one." % sum
print "Good job, %s. You passed this course!" % name
enter_lobby()My Question: Could this script be shortened and/or simplified by re-writing it in OOP style? If so, can you show me an example of what that might look like?
Solution
It is rather difficult to provide a complete review of the code without having access to it as a whole. For instance, the
Abstract the problem
Do not repeat yourself, it is bad practice and impair maintainability. Instead try to extract a pattern and parametrize it with the different values you need. Not accounting for the
You can thus define
And call it using one of three ways:
Using the operator module is neater since it allows you to reuse existing code.
Account for sorting operands
We left aside the problem of sorting the operands for the substraction problem. This can be incorporated back using a third parameter to tell the function whether or not we should try to sort the operands:
You can then call your function using
You can also choose to sort unconditionally, it won't change the expected output for
Expand on greater difficulty
I guess that the
Again, don't repeat yourself¹. Parametrize your function instead. If all you need to change is the range of the
And that's it. This single tiny change allows you to remove at least 8, all look-alike, functions.
The call then becomes:
Coding standards
Now that we removed a whole bunch of your code at once, let's write it properly. For starter, you should have noticed that I changed your
Second, the use of `
global name line in both functions is rather bad. Firstly because you're not modifying but just accessing name's value so you don't need it. Secondly because using this kind of global variables is bad anyway, and this might have different ways of solving depending on what you're doing with name in other parts of your code (to which OOP might be a way, but not necessarily the way).Abstract the problem
Do not repeat yourself, it is bad practice and impair maintainability. Instead try to extract a pattern and parametrize it with the different values you need. Not accounting for the
if a > b test in the substraction for now, the only thing that differ between the two functions is:- The operation to perform (both taking two arguments);
- The representation of this operation.
You can thus define
def problems_1(operation, symbol):
score = 0
while score < 30:
a = randint(1,10)
b = randint(1,10)
result = operation(a, b)
answer = int(raw_input("%i %s %i = " % (a, symbol, b)))
if answer == result:
score = score + 1
print "Good job. Current score is %d" % score
elif answer != result:
print "Oops, the correct answer is %i. Try another one." % result
print "Good job, %s. You passed this course!" % name
enter_lobby()And call it using one of three ways:
def add(a,b):
return a + b
def sub(a,b):
return a - b
...
problems_1(add, '+')
problems_1(sub, '-')problems_1(lambda a,b: a+b, '+')
problems_1(lambda a,b: a-b, '-')import operator
problems_1(operator.add, '+')
problems_2(operator.sub, '-')Using the operator module is neater since it allows you to reuse existing code.
Account for sorting operands
We left aside the problem of sorting the operands for the substraction problem. This can be incorporated back using a third parameter to tell the function whether or not we should try to sort the operands:
def problems_1(operation, symbol, sort=False):
score = 0
while score < 30:
a = randint(1,10)
b = randint(1,10)
if sort and a < b:
a, b = b, a # Swap variables using tuple unpacking
result = operation(a, b)
answer = int(raw_input("%i %s %i = " % (a, symbol, b)))
if answer == result:
score = score + 1
print "Good job. Current score is %d" % score
elif answer != result:
print "Oops, the correct answer is %i. Try another one." % result
print "Good job, %s. You passed this course!" % name
enter_lobby()You can then call your function using
import operator
problems_1(operator.add, '+')
problems_1(operator.sub, '-', True)You can also choose to sort unconditionally, it won't change the expected output for
+ or *; and it will force results to be greater than 1 for / (asuming your “divisions” functions ask for integral division). But having this optional parameter can come in handy when dealing with more difficult problems that can potentially benefit from having substraction produce negative results.Expand on greater difficulty
I guess that the
_1 at the end of the function name is for “easy” and that you have _2 and _3 types of functions which all look the same.Again, don't repeat yourself¹. Parametrize your function instead. If all you need to change is the range of the
randint calls, then you should use:def problems(operation, symbol, min_op, max_op, sort=False):
score = 0
while score < 30:
a = randint(min_op, max_op)
b = randint(min_op, max_op)
if sort and a < b:
a, b = b, a # Swap variables using tuple unpacking
result = operation(a, b)
answer = int(raw_input("%i %s %i = " % (a, symbol, b)))
if answer == result:
score = score + 1
print "Good job. Current score is %d" % score
elif answer != result:
print "Oops, the correct answer is %i. Try another one." % result
print "Good job, %s. You passed this course!" % name
enter_lobby()And that's it. This single tiny change allows you to remove at least 8, all look-alike, functions.
The call then becomes:
import operator
problems(operator.sub, '-', 1, 10, True) # easy
problems(operator.sub, '-', 10, 100, True) # medium
problems(operator.sub, '-', 100, 1000) # hardCoding standards
Now that we removed a whole bunch of your code at once, let's write it properly. For starter, you should have noticed that I changed your
sum variable into result. This is both because it better express the intent and because sum is a builtin function in Python that you are shadowing. Try to avoid using builtin functions names for your variables, it makes the code less understandable.Second, the use of `
Code Snippets
def problems_1(operation, symbol):
score = 0
while score < 30:
a = randint(1,10)
b = randint(1,10)
result = operation(a, b)
answer = int(raw_input("%i %s %i = " % (a, symbol, b)))
if answer == result:
score = score + 1
print "Good job. Current score is %d" % score
elif answer != result:
print "Oops, the correct answer is %i. Try another one." % result
print "Good job, %s. You passed this course!" % name
enter_lobby()def add(a,b):
return a + b
def sub(a,b):
return a - b
...
problems_1(add, '+')
problems_1(sub, '-')problems_1(lambda a,b: a+b, '+')
problems_1(lambda a,b: a-b, '-')import operator
problems_1(operator.add, '+')
problems_2(operator.sub, '-')def problems_1(operation, symbol, sort=False):
score = 0
while score < 30:
a = randint(1,10)
b = randint(1,10)
if sort and a < b:
a, b = b, a # Swap variables using tuple unpacking
result = operation(a, b)
answer = int(raw_input("%i %s %i = " % (a, symbol, b)))
if answer == result:
score = score + 1
print "Good job. Current score is %d" % score
elif answer != result:
print "Oops, the correct answer is %i. Try another one." % result
print "Good job, %s. You passed this course!" % name
enter_lobby()Context
StackExchange Code Review Q#112798, answer score: 7
Revisions (0)
No revisions yet.