Python Forum
How to test the high-score in my Python-game
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to test the high-score in my Python-game
#1
This is my first coding project, where I am doing a 6 x 6-matrix matching game ("Memory"). Now I have tried applying a high-score in the end of my game. The problem is, to be able to try if my high-score works I need to complete the whole game which takes long time.

How do I come around this problem? I'm happy to share my whole code to get feedback.

Thanks in advance!
Reply
#2
(Oct-02-2023, 09:23 AM)Pluviometer Wrote: How do I come around this problem?
Suppose you programmed a chess game and you want to test the checkmate feature. You could inject a chess position where there is a checkmate at the next move, then continue from here.

You could do the same with your game. Design it so that you can inject a predefined state of the whole game just before the high-score is reached. This is interesting because it forces you to define a structure that can store the whole state of the game.

Another thing you could consider is to add the capability to play a whole game without human interaction, with the computer playing predefined moves for example.
Reply
#3
For a program as complex as a game, you must have organized your code into functions and/or classes. To test these functions/classes you should write a test program.

This is a minesweeper game that I have laying around.
"""Minesweeper game.  Find all safe squares while avoiding bombs."""
import random
import time
import datetime
import tkinter as tk
from tkinter import messagebox
from collections import defaultdict


class Cell(tk.Button):
    """A button for the Minsweeper game.  Press me to reveal a bomb or
    information about surrounding bombs.
    """

    def __init__(self, parent, row, column, count):
        super().__init__(
            parent, text=" ", width=2, relief=tk.RAISED, command=self.press
        )
        self.parent = parent
        self.row = row
        self.column = column
        self.count = count
        self.pressed = False
        self.flagged = False
        self.bind("<Button-3>", self.flag)

    def press(self):
        """Button pressed.  Show bomb info"""
        if not self.pressed:
            self.configure(relief=tk.SUNKEN)
            self.pressed = True
            self["text"] = "X" if self.count < 0 else str(self.count)
            self.parent.clear_cell(self)

    def flag(self, _):
        """Right mouse button pressed.  Toggle flag marker"""
        if not self.pressed:
            self.flagged = not self.flagged
            self["text"] = "F" if self.flagged else " "


class Board(tk.Tk):
    """Minesweeper playing surface.  I make a grid of buttons that are pressed
    to reveal bombs or information about surrounding bombs.
    """

    neighbors = ((-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1))

    def __init__(self, rows, columns, bomb_count):
        super().__init__()
        self.title("Minesweeper")
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)

        self.rows = rows
        self.columns = columns
        self.safe_count = rows * columns - bomb_count

        # Make all the bombs
        bombs = defaultdict(lambda: 0)
        for index in random.sample(range(rows * columns), k=bomb_count):
            bombs[(index // columns, index % columns)] = 1

        # Make the map
        self.cells = []
        for row in range(rows):
            for column in range(columns):
                # Count bombs in surrounding cells.  Use -1 to indicate
                # cell contains a bomb.
                if bombs[(row, column)]:
                    count = -1
                else:
                    count = sum(bombs[(row + r, column + c)] for c, r in self.neighbors)
                cell = Cell(self, row, column, count)
                cell.grid(row=row, column=column)
                self.cells.append(cell)

        # Add status display to bottom of window
        self.done = False
        self.status = tk.Label(self, text="")
        self.status.grid(row=rows, column=0, columnspan=columns)
        self.start_time = time.time()
        self.update_status()

    def cell(self, row, column):
        """Return cell at (row, column)"""
        if 0 <= row < self.rows and 0 <= column < self.columns:
            return self.cells[row * self.columns + column]
        raise IndexError

    def clear_cell(self, cell):
        """Cell was selected"""
        if cell.count < 0:
            self.done = True
            messagebox.showinfo("Today marks the passing of you.")
        else:
            self.safe_count -= 1
            if self.safe_count <= 0:
                self.done = True
                messagebox.showinfo("Winner, winner, chicken dinner!")
            elif cell.count == 0:
                # All surrounding cells are save.  Open them
                for c, r in self.neighbors:
                    try:
                        self.cell(cell.row + r, cell.column + c).press()
                    except IndexError:
                        pass

    def update_status(self):
        """Update status display"""
        delta = str(datetime.timedelta(seconds=int(time.time() - self.start_time)))
        self.status["text"] = f"{delta}   Remaining {self.safe_count}"
        if not self.done:
            self.after(1000, self.update_status)


Board(10, 10, 10).mainloop()
The first step to writing a test is take the code on the bottom of the file and reorganize so it is not run when the file is imported in a test program.
if __name__ == "__main__":
    Board(10, 10, 10).mainloop()
If this was more than a single line I would write a main() function.
def main():
    board = Board(10, 10, 10)
    board.mainloop()


if __name__ == "__main__":
    main()
Now I can import my game module into my test program. Lets say I was having a problem with the Cell class not showing the correct count. I can write a program that creates a cell and tests the functions.

test.py
import tkinter as tk
from minesweeper import Cell

# Need to make a tkinter root window to test Cell
root = tk.Tk()

# Make a cell that has a bomb.  Press the cell and verify
# the button shows "X"
cell = Cell(root, 1, 1, -1)
cell.press()
print(cell["text"])
When I run the program I get an error.
Error:
AttributeError: '_tkinter.tkapp' object has no attribute 'clear_cell'
My Cell clas calls parent.clear_cell(). My test program needs to make a "test fixture" that provides this functionality.
import tkinter as tk
from minesweeper import Cell


# Need to make a tkinter root window to test Cell
class TestWindow(tk.Tk):
    def __init__(self):
        super().__init__()
        self.cell = Cell(self, 1, 1, 0)

    def clear_cell(self, cell):
        """Dummy method to make Cell happy."""

    def test(self, count):
        """Veify that Cell displays the correct text when pressed."""
        self.cell.count = count
        self.cell.pressed = False
        self.cell.press()
        expected_text = "X" if count < 0 else str(count)
        return self.cell["text"] == expected_text


testWindow = TestWindow()
for i in range(-1, 9):
    print("Test", i, testWindow.test(i))
Output:
Test -1 True Test 0 True Test 1 True Test 2 True Test 3 True Test 4 True Test 5 True Test 6 True Test 7 True Test 8 True
All my tests passed. If I am having problems with the button labels, it must be something outside Cell.

While testing I decide to write a test to verify Cell.flag() works.
# Need to make a tkinter root window to test Cell
class TestWindow(tk.Tk):
    def __init__(self):
        super().__init__()
        self.cell = Cell(self, 1, 1, 0)

    def testFlag(self, flag):
        """Veify that toggling the flag works."""
        self.cell.pressed = False
        self.cell.flagged = flag == "F"
        self.cell["text"] = flag
        self.cell.flag(None)
        expected_text = " " if flag == "" else "F"
        return self.cell["text"] == expected_text


testWindow = TestWindow()
print('testFlag("F")', testWindow.testFlag("F"))
print('testFlag(" ")', testWindow.testFlag(" "))
Output:
testFlag("F") True testFlag(" ") True
I like this way of testing. I do some research and see there are python packages to simplify writing tests. I find something called unittest that is included with my python distribution.

https://docs.python.org/3/library/unittest.html

I restructure my test code to be compatible with unittest.
import tkinter as tk
from minesweeper import Cell
import unittest


# Need to make a tkinter root window to test Cell
class TestWindow(tk.Tk):
    def __init__(self):
        super().__init__()
        self.withdraw()
        self.cell = Cell(self, 1, 1, 0)

    def clear_cell(self, cell):
        """Dummy method to make Cell happy."""

    def set_count(self, count):
        self.cell.count = count
        self.cell.pressed = False
        self.cell.press()
        return self.cell["text"]

    def toggle_flag(self, flag):
        self.cell.pressed = False
        self.cell.flagged = flag == "F"
        self.cell["text"] = flag
        self.cell.flag(None)
        return self.cell["text"]


class CellTestCase(unittest.TestCase):
    def setUp(self):
        self.window = TestWindow()

    def tearDown(self):
        self.window.destroy()

    def test_cell_press(self):
        self.assertEqual(self.window.set_count(-1), "X")
        for i in range(8):
            self.assertEqual(self.window.set_count(i), str(i))

    def test_toggle_flag(self):
        self.assertEqual(self.window.toggle_flag("F"), " ")
        self.assertEqual(self.window.toggle_flag(" "), " ")
I run the unittest from the commandline.
Output:
(venv)>python -m unittest test_cell .F ====================================================================== FAIL: test_toggle_flag (test_cell.CellTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "...test_cell.py", line 47, in test_toggle_flag self.assertEqual(self.window.toggle_flag(" "), " ") AssertionError: 'F' != ' ' - F
The test find the error I intentionally left in the test code. I correct the test and run again.
Output:
(venv)>python -m unittest test_cell .. ---------------------------------------------------------------------- Ran 2 tests in 0.162s OK
Another popular testing framework is pytest.

https://docs.pytest.org/en/7.4.x/

I change my test program to be compatible with pytest.
import tkinter as tk
from minesweeper import Cell
import pytest


# Need to make a tkinter root window to test Cell
class Window(tk.Tk):
    def __init__(self):
        super().__init__()
        self.withdraw()
        self.cell = Cell(self, 1, 1, 0)

    def clear_cell(self, cell):
        """Dummy method to make Cell happy."""

    def set_count(self, count):
        self.cell.count = count
        self.cell.pressed = False
        self.cell.press()
        return self.cell["text"]

    def toggle_flag(self, flag):
        self.cell.pressed = False
        self.cell.flagged = flag == "F"
        self.cell["text"] = flag
        self.cell.flag(None)
        return self.cell["text"]


@pytest.fixture
def window():
    return Window()


def test_cell_press(window):
    assert window.set_count(-1) == "X"
    for i in range(8):
        assert window.set_count(i) == str(i)


def test_toggle_flag(window):
    assert window.toggle_flag("F") == " "
    assert window.toggle_flag(" ") == " "
Notice I put the error back in the test_togle_flag test.

I run the test.
Output:
(venv)>pytest =========================================================== test session starts =========================================================== platform win32 -- Python 3.10.7, pytest-7.4.2, pluggy-1.3.0 rootdir: C:\Users\hystadd\Documents\python\sandbox\games plugins: anyio-3.6.1 collected 2 items test_cell.py .F [100%] ================================================================ FAILURES ================================================================= ____________________________________________________________ test_toggle_flag _____________________________________________________________ window = <test_cell.Window object .> def test_toggle_flag(window): assert window.toggle_flag("F") == " " > assert window.toggle_flag(" ") == " " E AssertionError: assert 'F' == ' ' E Strings contain only whitespace, escaping them using repr() E - ' ' E + 'F' test_cell.py:43: AssertionError ========================================================= short test summary info ========================================================= FAILED test_cell.py::test_toggle_flag - AssertionError: assert 'F' == ' ' ======================================================= 1 failed, 1 passed in 0.41s =======================================================
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  How to test and import a model form computer to test accuracy using Sklearn library Anldra12 6 3,143 Jul-03-2021, 10:07 AM
Last Post: Anldra12
  High-Precision Board Voltage Reading from Python into PD Liam484 1 2,094 Mar-29-2021, 02:57 PM
Last Post: Marbelous
  hello, i was trying a testdome python test twinstars2020 10 8,298 Aug-26-2020, 07:15 AM
Last Post: twinstars2020
  cannot build python 3.8.2 (make test fails on test_imprtlib) borabora 1 2,765 May-08-2020, 09:10 PM
Last Post: Larz60+
  How to write test cases for a init function by Unit test in python? binhduonggttn 2 3,132 Feb-24-2020, 12:06 PM
Last Post: Larz60+
  How to write test cases by Unit test for database configuration file? binhduonggttn 0 2,569 Feb-18-2020, 08:03 AM
Last Post: binhduonggttn
  GPIO time in HIGH LOW boris_za 1 2,283 Dec-07-2019, 01:48 PM
Last Post: Larz60+
  new python package test ramkrishna 0 1,871 Feb-25-2019, 12:18 PM
Last Post: ramkrishna
  Any idea on how to keep score of My Tic Tac Toe game? Minindis02 1 3,155 Jul-11-2018, 07:58 AM
Last Post: Larz60+
  Python test failed CodingMaster111 1 3,860 May-09-2018, 04:24 PM
Last Post: Larz60+

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020