2
\$\begingroup\$

I created a math quiz/game called "Factions". In the game, there are 2 teams. Red team and Blue team. They take turns answering questions relating to fractions. If they get it correct, they gain points equal to the round number * 100. If not, then they lose points equal to the round number * 100. Each team starts off with 100 points. When a team's points reaches 0, they lose the game. Here is the code for the game.

from tkinter import *
import random
import time


class Game(object):
    def __init__(self):
        global root
        self.round = 1
        self.operators = ["+", "+", "+", "+", "-", "-", "-", "*", "*", "/"]
        self.operator = ""
        # Scores and team labels
        self.blue_team_label = Label(root, text="BLUE TEAM", bg="blue")
        self.blue_team_label.config(font=("Courier", 50))
        self.blue_team_label.grid(row=0, column=0, columnspan=10)
        self.red_team_label = Label(root, text="RED TEAM", bg="red")
        self.red_team_label.config(font=("Courier", 50))
        self.red_team_label.grid(row=0, column=23, columnspan=10)
        self.blue_team_points = 1000
        self.blue_team_points_label = Label(root, text="Points: " + str(self.blue_team_points))
        self.blue_team_points_label.config(font=("Courier", 30))
        self.blue_team_points_label.grid(row=5, column=0, columnspan=10)
        self.red_team_points = 1000
        self.red_team_points_label = Label(root, text="Points: " + str(self.red_team_points))
        self.red_team_points_label.config(font=("Courier", 30))
        self.red_team_points_label.grid(row=5, column=23, columnspan=10)
        self.question = "(self.first_numerator / self.first_denominator) " + self.operator + \
                        "(self.second_numerator / self.second_denominator) "
        self.turn = "BLUE TURN"
        self.round_label = Label(root, text="ROUND " + str(self.round))
        self.round_label.config(font=("courier", 20))
        self.round_label.grid(row=26, column=23, columnspan=10)
        # Questions
        self.generate_question()

    def generate_question(self):
        """
        Generate the questions for the game.
        """
        self.first_numerator = random.randint(1, 5 * self.round)
        self.operator = random.choice(self.operators)
        self.second_numerator = random.randint(1, 5 * self.round)
        self.first_denominator = random.randint(1, 5 * self.round)
        self.second_denominator = random.randint(1, 5 * self.round)
        self.row_1_question = Label(root, text=str(self.first_numerator) + " " + " " + " " + str(self.second_numerator))
        self.row_2_question = Label(root, text="   " + "/" + " " + self.operator + " " + "/" + " = ")
        self.row_3_question = Label(root, text="{0}   {1}".format(str(self.first_denominator),
                                                                  str(self.second_denominator)))
        self.row_1_question.grid(row=25, column=10, columnspan=5)
        self.row_2_question.grid(row=26, column=10, columnspan=5)
        self.row_3_question.grid(row=27, column=10, columnspan=5)
        self.row_1_question.config(font=("courier", 12))
        self.row_2_question.config(font=("courier", 12))
        self.row_3_question.config(font=("courier", 12))
        self.question = "(self.first_numerator / self.first_denominator) " + self.operator + "(self.second_numerator " \
                                                                                             "/ self.second_denominator) "
        self.question_entry_box = Entry(root)
        self.question_entry_box.grid(row=26, pady=12, column=16, columnspan=3)
        self.question_check_button = Button(root, text="ENTER", command=self.check_answer)
        self.question_check_button.grid(row=26, column=20)
        self.turn_label = Label(root, text=self.turn)
        self.turn_label.config(font=("courier", 20))
        self.turn_label.grid(row=26, column=0, columnspan=9)

    def check_answer(self):
        self.answer = eval(self.question)
        self.attempted_answer = self.question_entry_box.get()
        if self.turn == "BLUE TURN":
            if self.answer == float(self.attempted_answer):
                self.blue_team_points += self.round * 100
            else:
                self.blue_team_points -= self.round * 100
        else:
            if self.answer == float(self.attempted_answer):
                self.red_team_points += self.round * 100
            else:
                self.red_team_points -= self.round * 100
        self.update()

    def update(self):
        self.blue_team_label = Label(root, text="BLUE TEAM", bg="blue")
        self.blue_team_label.config(font=("Courier", 50))
        self.blue_team_label.grid(row=0, column=0, columnspan=10)
        self.red_team_label = Label(root, text="RED TEAM", bg="red")
        self.red_team_label.config(font=("Courier", 50))
        self.red_team_label.grid(row=0, column=23, columnspan=10)
        self.blue_team_points_label = Label(root, text="Points: " + str(self.blue_team_points))
        self.blue_team_points_label.config(font=("Courier", 30))
        self.blue_team_points_label.grid(row=5, column=0, columnspan=10)
        self.red_team_points_label = Label(root, text="Points: " + str(self.red_team_points))
        self.red_team_points_label.config(font=("Courier", 30))
        self.red_team_points_label.grid(row=5, column=23, columnspan=10)
        if self.turn == "BLUE TURN":
            self.turn = "RED TURN"
        else:
            self.turn = "BLUE TURN"
            self.round += 1
        if self.blue_team_points < 1:
            game_over_label = Label(root, text="BLUE TEAM LOSES")
            game_over_label.config(font=("courier", 20))
            game_over_label.grid(row=50, column=0, columnspan=10)
            time.sleep(3)
            sys.exit()
        if self.red_team_points < 1:
            game_over_label = Label(root, text="RED TEAM LOSES")
            game_over_label.config(font=("courier", 20))
            game_over_label.grid(row=50, column=0, columnspan=10)
            time.sleep(3)
            sys.exit()
        self.round_label = Label(root, text="ROUND " + str(self.round))
        self.round_label.config(font=("courier", 20))
        self.round_label.grid(row=26, column=23, columnspan=10)
        self.generate_question()


root = Tk()
root.title("Factions")

game = Game()

mainloop()

This is my first project with tkinter, and one of my first in Python. I want feedback on how to make the game better, and any glitches you find.

\$\endgroup\$
2
\$\begingroup\$

Don't mix presentation and business logic

Let's take a look at self.turn. You're using it for two purposes - to talk to humans (that's why it's a string), and for the computer to track which turn it is. These concerns should be separated. If there will always be two players, the turn could be represented by a boolean, or maybe as an integer that's the player ID. It should only be converted to a string when you want to display whose turn it is on the screen.

Your entire game is baked into one Game class, but a bunch of separation needs to be done. A great example of a method that should only appear in the business logic layer is generate_question. It shouldn't interact with the UI at all. Solving this issue will dramatically clean up your code, make debugging and maintenance easier, and generally decrease headaches.

Use modern formatting

Rather than this:

str(self.first_numerator) + " " + " " + " " + str(self.second_numerator)

you can do:

f'{self.first_numerator}   {self.second_numerator}'

Be careful about rounding

This:

== float(

is a great way to create a nasty bug. Sometimes this will evaluate to false even if the numbers seem like they should match -- they're just infinitesimally different. Either track integers as a member of fractions, or if you really need to compare floats, do so with some small tolerance, i.e.

epsilon = 1e-12
if abs(self.answer - self.attempted_answer) < epsilon:
    # ...

Create an upper main function

...to house the code that's currently in global scope.

Don't repeat yourself

This:

    if self.turn == "BLUE TURN":
        if self.answer == float(self.attempted_answer):
            self.blue_team_points += self.round * 100
        else:
            self.blue_team_points -= self.round * 100
    else:
        if self.answer == float(self.attempted_answer):
            self.red_team_points += self.round * 100
        else:
            self.red_team_points -= self.round * 100

can be compressed - make a variable to hold the result of your multiplication:

award = self.round * 100

And you don't need to repeat the entire block based on turn if you make a Player class with an award method.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.