Week 5 A Major Project

Math Game

brain_tuner_lite.jpg

Overview

  • This assignment will incorporate both python algorithm development and GUI design using Glade into one project
  • The goal is to create an educational program that tests and sharpens arithmetic skill and speed
  • Very similar to the Brain Age game on the Nintendo DS or Brain Tuner on the iPhone
  • Your program should look something like (but not exactly the same as the picture shown above)

Steps

  • Find a way to generate simple arithmetic equations (limit your operands to 0-10) that may be correct or wrong. and display them one by one
  • Accept input from the user (example 'y' and 'n') to say whether they think the equations are right or not and print a message indicating if they gave a right answer
  • Your wrong inputs may be off by 1 or 2 for addition and subtraction or up to multiples of one of the operands for multiplication
    • Try to make them 'look correct'
  • Use the python random library to generate randomness in your program
  • Each gaming session may contain up to 20 questions and the user's score should be displayed to the at the end of the session
  • Here are some examples:
(1)
\begin{align} $5\times7=35$ \\* $6\times8=48$ \\* $6\times3=16$ \\* $8\times3=5$ \\* $9\times9=18$ \\* $1\times8=-7$ \\* $4\times4=13$ \\* $6\times3=6$ \\* $8\times7=-1$ \\* $3\times9=-6$ \\* $6\times8=15$ \end{align}

Suggested milestones

  • Working random equation generator
  • Command-line implementation of program
  • Successful display of equations on screen sequentially
  • Final Program

Teacher solution

  • Backend code: brainage.py
#!/usr/bin/env python

import random
import time

import sys
try:
        import pygtk
        pygtk.require("2.0")
except:
        pass
try:
        import gtk
        import gtk.glade
except:
        sys.exit(1)

class BrainAge:
        def getFakeAdd(self,a,b):
                offset=random.randint(1,3)
                neg=random.randint(0,1)
                if neg==0:
                        return a+b+offset
                else:
                        return a+b-offset

        def getFakeSub(self,a,b):
                offset=random.randint(1,3)
                neg=random.randint(0,1)
                if neg==0:
                        return a-b+offset
                else:
                        return a-b-offset

        def getFakeMult(self,a,b):
                while True:
                        offset=random.randint(1,2)
                        neg=random.randint(0,1)
                        which=random.randint(0,1)
                        if which==0:
                                if neg==0:
                                        answ = (a+offset)*b
                                else:
                                        answ = (a-offset)*b
                        else:
                                if neg==0:
                                        answ = a*(b+offset)
                                else:
                                        answ = a*(b-offset)

                        if answ != (a*b):
                                return answ

        def __init__(self):
                #Set the Glade file
                self.started=False

                self.gladefile = "brainage.glade"  
                self.wTree = gtk.glade.XML(self.gladefile) 

                self.scrolledproblems=self.wTree.get_widget("scrolledproblems")
                self.statuslabel=self.wTree.get_widget("statuslabel")

                #Create our dictionay and connect it
                dic = { "on_start_clicked" : self.startGame,
                        "on_BrainAge_key_press_event" : self.keyPressed,
                        "on_MainWindow_destroy" : gtk.main_quit }
                self.wTree.signal_autoconnect(dic)

        def keyPressed(self, widget, event):
                if self.started != True:
                        return

                if self.problem_num > self.num_questions:
                        return

                inp=gtk.gdk.keyval_name(event.keyval)

                if (self.fake==1 and inp=='n') or (self.fake==0 and inp=='y'):
                        self.right_questions += 1
                        templabel=gtk.Label("O")
                        self.problembox.pack_end(templabel)
                        templabel.show()
                        self.problembox.show()
                        self.problemsvbox.show()
                else:
                        templabel=gtk.Label("X")
                        self.problembox.pack_end(templabel)
                        templabel.show()
                        self.problembox.show()
                        self.problemsvbox.show()

                if self.problem_num==self.num_questions:
                        self.finish_time = time.time()
                        self.statuslabel.set_text("You got "+str(100*self.right_questions/self.num_questions)+"% right in "+str(self.finish_time-self.init_time)+" seconds.")
                        self.statuslabel.show()
                        self.started=False
                else:
                        self.nextProblem()

        def nextProblem(self):
                self.statuslabel.set_text("Problem #" + str(self.problem_num+1) + " of " + str(self.num_questions) + ".")

                a = random.randint(0,10)
                b = random.randint(0,10)
                choice = random.randint(0,2)
                self.fake=random.randint(0,1)

                if choice == 0:
                        if self.fake==0:
                                answ = a+b
                        else:
                                answ=self.getFakeAdd(a,b)
                        operation = "+"
                elif choice == 1:
                        if self.fake==0:
                                answ=a-b
                        else:
                                answ=self.getFakeSub(a,b)
                        operation = "-"
                else  :
                        if self.fake==0:
                                answ=a*b
                        else:
                                answ=self.getFakeMult(a,b)
                        operation = "x"

                templabel=gtk.Label(str(a)+" "+operation+" "+str(b)+" = "+str(answ)+" ")
                templabel.set_size_request(400,100)
                self.problembox=gtk.HBox()
                self.problembox.pack_start(templabel)

                self.problemsvbox.pack_end(self.problembox)
                templabel.show()
                self.problembox.show()
                self.problemsvbox.show()

                self.problem_num+=1

        def startGame(self,widget):
                self.init_time = time.time()
                self.num_questions = 15
                self.problem_num = 0
                self.right_questions = 0
                self.started=True

                self.problemsvbox=gtk.VBox()
                self.scrolledproblems.add_with_viewport(self.problemsvbox)
                self.problemsvbox.show()

                self.nextProblem()

        #def btnHelloWorld_clicked(self, widget):
        #       print "Hello World!"

if __name__ == "__main__":
        hwg = BrainAge()
        gtk.main()
  • Glade file: brainage.py
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">

<glade-interface>

<widget class="GtkWindow" id="BrainAge">
  <property name="width_request">640</property>
  <property name="height_request">480</property>
  <property name="visible">True</property>
  <property name="title" translatable="yes">BrainAge</property>
  <property name="type">GTK_WINDOW_TOPLEVEL</property>
  <property name="window_position">GTK_WIN_POS_NONE</property>
  <property name="modal">False</property>
  <property name="resizable">True</property>
  <property name="destroy_with_parent">False</property>
  <property name="decorated">True</property>
  <property name="skip_taskbar_hint">False</property>
  <property name="skip_pager_hint">False</property>
  <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
  <property name="focus_on_map">True</property>
  <property name="urgency_hint">False</property>
  <signal name="key_press_event" handler="on_BrainAge_key_press_event" last_modification_time="Sun, 19 Oct 2008 23:15:01 GMT"/>
  <signal name="destroy" handler="on_MainWindow_destroy" last_modification_time="Sun, 19 Oct 2008 23:45:09 GMT"/>

  <child>
    <widget class="GtkVBox" id="vbox1">
      <property name="visible">True</property>
      <property name="homogeneous">False</property>
      <property name="spacing">0</property>

      <child>
    <widget class="GtkScrolledWindow" id="scrolledproblems">
      <property name="visible">True</property>
      <property name="can_focus">True</property>
      <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
      <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
      <property name="shadow_type">GTK_SHADOW_NONE</property>
      <property name="window_placement">GTK_CORNER_TOP_LEFT</property>

      <child>
        <placeholder/>
      </child>
    </widget>
    <packing>
      <property name="padding">0</property>
      <property name="expand">True</property>
      <property name="fill">True</property>
    </packing>
      </child>

      <child>
    <widget class="GtkHBox" id="hbox1">
      <property name="visible">True</property>
      <property name="homogeneous">False</property>
      <property name="spacing">0</property>

      <child>
        <widget class="GtkLabel" id="statuslabel">
          <property name="visible">True</property>
          <property name="label" translatable="yes"></property>
          <property name="use_underline">False</property>
          <property name="use_markup">False</property>
          <property name="justify">GTK_JUSTIFY_LEFT</property>
          <property name="wrap">False</property>
          <property name="selectable">False</property>
          <property name="xalign">0.5</property>
          <property name="yalign">0.5</property>
          <property name="xpad">0</property>
          <property name="ypad">0</property>
          <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
          <property name="width_chars">-1</property>
          <property name="single_line_mode">False</property>
          <property name="angle">0</property>
        </widget>
        <packing>
          <property name="padding">0</property>
          <property name="expand">True</property>
          <property name="fill">False</property>
        </packing>
      </child>

      <child>
        <widget class="GtkButton" id="button1">
          <property name="visible">True</property>
          <property name="can_focus">True</property>
          <property name="label" translatable="yes">Start!</property>
          <property name="use_underline">True</property>
          <property name="relief">GTK_RELIEF_NORMAL</property>
          <property name="focus_on_click">True</property>
          <signal name="clicked" handler="on_start_clicked" last_modification_time="Sun, 19 Oct 2008 23:01:45 GMT"/>
        </widget>
        <packing>
          <property name="padding">0</property>
          <property name="expand">False</property>
          <property name="fill">False</property>
        </packing>
      </child>
    </widget>
    <packing>
      <property name="padding">0</property>
      <property name="expand">False</property>
      <property name="fill">False</property>
    </packing>
      </child>
    </widget>
  </child>
</widget>

</glade-interface>
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License