Graphical interface application case – lights out game (and extension) (python)

7.8 Graphical interface application case – lights out game

Title:

[Case] Preliminary game – lights out game.

Lights Out is an interesting puzzle game in which players turn off (or turn on) a light by clicking on it. If you turn off (or turn on) a light, the lights around it (up, down, left, right) will also touch the switch. You can pass the level by successfully turning off all the lights.


Figure 7-43 The effect of turning off the lights game
Analysis: The game uses a two-dimensional list to store the status of the lights. ‘you’ means that the light is on (yellow circle), and ‘wu’ means that the light is off (the background color circle). In the Canvas canvas click event, obtain the mouse click position and convert it into a checkerboard position (x1, y1), and handle the state transition of the surrounding lights.
Case code:

from tkinter import *
from tkinter import messagebox
root = Tk()
l= [['wu', 'wu', 'you', 'you', 'you'] ,
    ['wu', 'you', 'wu', 'wu', 'wu'],
    ['wu', 'wu', 'wu', 'wu', 'wu'],
    ['wu', 'wu', 'wu', 'you', 'wu'],
    ['you', 'you', 'you', 'wu', 'wu']]
#Draw the status diagram of the light
def huaqi():
    for i in range (0,5):
        for u in range (0,5):
            if l[i][u]=='you':
                cv.create_oval(i * 40 + 10,u * 40 + 10,(i + 1)* 40 + 10,(u + 1)*40 + 10,outline='white', fill='yellow\ ', width=2)#Light on
            else:
                cv.create_oval(i*40 + 10,u*40 + 10,(i + 1) *40 + 10,(u + 1)*40 + 10,outline='white', fill='white\ ', width=2) #turn off the lights
#Reverse the state of the light at (x1,yl)
def reserve(x1,y1):
    if l[x1][y1] =='wu':
        l[x1][y1]='you'
    else:
        l[x1][y1] = 'wu'
#Click event function
def luozi(event):
    x1 = (event.x - 10) // 40
    y1 = (event.y - 10) // 40
    print(x1, y1)
    reserve(x1, y1) # Flip the state of the light at (x1, y1)
    # The following flips the status of the lights around (x1, yI) #The status of the left light is reversed
    if x1 !=0:
        reserve(x1 - 1, y1)
    # Invert the status of the right light
    if x1!=4:
        reserve(x1 + 1, y1)
    # Invert the status of the upper side light
    if y1!=0:
        reserve(x1, y1 - 1)
    # Invert the status of the lower side light
    if y1!=4:
        reserve(x1, y1 + 1)
        huaqi()
# Main program
cv = Canvas(root, bg='white', width=210, height=210)
for i in range(0, 6): # Draw grid lines
    cv.create_line(10, 10 + 1 * 40,210, 10 + i * 40, arrow = 'none')
    cv.create_line(10 + i * 40,10,10 + 1 * 40, 210, arrow = 'none' )
huaqi() # Draw the status diagram of the light
p = 0
for i in range(0, 5):
    for u in l[i]:
        if u == 'wu':
            p=p+1
if p == 25:
    messagebox.showinfo('win', 'You passed the level') # The message window that displays win information
cv.bind('<Button-1>', luozi)
cv.pack()
root.mainloop ()

This code is a light bulb game based on the Tkinter library. The game interface is a 5×5 grid, each grid represents a light bulb. In the initial state, all light bulbs are off (white). The player’s goal is to light up all the bulbs (yellow) by clicking on them.

The main functions and operations in the code include:

1. `huaqi()` function: Draw the shape of the light bulb according to the status of the light bulb. If the light bulb is on, draw a yellow circle; if the light bulb is off, draw a white circle.

2. `reserve(x1, y1)` function: According to the given coordinates `(x1, y1)`, invert the state of the light bulb at that position. If the light bulb is on, it becomes off; if the light bulb is off, it becomes on.

3. `luozi(event)` function: handles mouse click events. According to the clicked position, the corresponding light bulb is determined and the status is reversed. It also inverts the state of the bulbs surrounding that bulb.

4. Main program part: Create a Canvas object and set the background color to white. Then, use a loop to draw the gridlines. Next, call the `huaqi()` function to draw the initial state of the light bulb. Finally, bind the left mouse button click event to the `luozi()` function and display the canvas in the window.

During the game, the player clicks on a light bulb to change its state and invert the state of surrounding light bulbs. When all the light bulbs are lit, a message window will pop up showing the player’s victory.

Note (Tkinter library):

Tkinter is Python’s standard graphical user interface (GUI) toolkit, which provides the components and functionality needed to create and manage GUI applications. Tkinter is a Python interface based on the Tcl/Tk toolkit, Tcl is a scripting language, and Tk is a toolkit for creating graphical user interfaces.

The Tkinter library contains many commonly used GUI components, such as buttons, labels, text boxes, check boxes, radio buttons, menus, etc. It also supports layout managers to help developers design and layout interfaces. Developers can use Tkinter to create various types of applications, from simple tools to complex desktop applications.

The advantages of Tkinter include:

1. **Easy to learn and use**: Tkinter is a standard library for Python, so no additional installation is required to use it. Its interface is simple and intuitive, suitable for beginners to get started.

2. **Cross-platform**: Tkinter can run on multiple platforms, including Windows, Linux and Mac OS.

3. **Rich components**: Tkinter provides a rich set of GUI components that can meet the needs of most applications.

4. **Flexibility**: Tkinter supports custom components and styles, and developers can customize them according to their own needs.

In short, Tkinter is a powerful and easy-to-use GUI toolkit suitable for developing Python graphical interface applications.

Extended title:

Topic requirements:

Please complete “7.8 Lights Out Game” (Textbook p.170) and complete the following expansion content:

Extended content:

1. Please design and implement interface functions that allow players to choose the size of the initial map, which are: small (5×5), medium (8×8), and large (12×10). After the player selects, immediately refresh the window interface and reset the map.

2. Please design and implement interface functions to allow players to choose game difficulty, as follows:

Easy: 20% of the grid states are flipped at the beginning

Medium: 40% of the grid states are flipped at the beginning

Difficulty: 60% of the grid states are flipped at the beginning

3. Before the game starts, the player is prompted to enter a unique username to retain the player’s level record.

4. Start the game according to the map size and difficulty selected by the player. During the game, the total time taken by the player to clear the level and the number of flips (i.e. the number of turning lights on and off) are recorded.

5. After the player successfully clears the level, save relevant information to the database. The table structure can be customized, but it should contain at least the following information:

– Player selected map size

– Player selected difficulty

– The date and time the player cleared the level

– The total time it took the player to complete the level

– The total number of clicks it took the player to complete the level

6. Design the window interface to display the clearance ranking list. The specific instructions are as follows:

– Display respective rankings according to different map sizes and different difficulties. For example: the rankings of medium difficulty for large maps and difficult difficulty for medium maps are separate.

– You can choose to display the total ranking (all players’ records) and personal ranking (all the player’s own records)

– You can choose to rank according to the clearance time and the number of clicks. Both rankings are arranged from small to large.

Extended code for the first function

(1. Please design and implement the interface function to allow players to choose the size of the initial map, which are: small (5×5), medium (8×8), large (12×10). After the player selects, immediately refresh the window interface and reset the map):

from tkinter import *
from tkinter import messagebox
import random

classLightsOutGame:
    def __init__(self, master=None, size=5):
        self.master = master
        self.size = size
        self.lights = [['wu' for _ in range(size)] for _ in range(size)]
        self.create_widgets()
        self.random_open_lights() # Randomly turn on some lights

    def create_widgets(self):
        self.cv = Canvas(self.master, bg='white', width=self.size*40 + 10, height=self.size*40 + 10)
        self.cv.pack()

        self.draw_grid() # Draw grid
        self.huaqi() # Draw the status diagram of the light

        self.cv.bind('<Button-1>', self.luozi)

    def draw_grid(self):
        self.cv.delete("grid") # Clear the previous grid

        for i in range(self.size + 1): # Draw grid lines
            self.cv.create_line(10, 10 + i * 40, 10 + self.size * 40, 10 + i * 40, arrow='none', tags="grid")
            self.cv.create_line(10 + i * 40, 10, 10 + i * 40, 10 + self.size * 40, arrow='none', tags="grid")

    def huaqi(self):
        self.cv.delete("lights") # Clear previous lights

        for i in range(self.size):
            for u in range(self.size):
                if self.lights[i][u] == 'you':
                    self.cv.create_oval(i * 40 + 10, u * 40 + 10, (i + 1) * 40 + 10, (u + 1) * 40 + 10,
                                        outline='white', fill='yellow', width=2, tags="lights") # Turn on the lights
                else:
                    self.cv.create_oval(i * 40 + 10, u * 40 + 10, (i + 1) * 40 + 10, (u + 1) * 40 + 10,
                                        outline='white', fill='white', width=2, tags="lights") # Turn off lights

    def reserve(self, x1, y1):
        if self.lights[x1][y1] == 'wu':
            self.lights[x1][y1] = 'you'
        else:
            self.lights[x1][y1] = 'wu'

    def luozi(self, event):
        x1 = (event.x - 10) // 40
        y1 = (event.y - 10) // 40
        print(x1, y1)
        self.reserve(x1, y1) # Flip the state of the light at (x1, y1)
        # The following flips the status of the lights around (x1, yI) #The status of the left light is reversed
        if x1 != 0:
            self.reserve(x1 - 1, y1)
        # Invert the status of the right light
        if x1 != self.size - 1:
            self.reserve(x1 + 1, y1)
        # Invert the status of the upper side light
        if y1 != 0:
            self.reserve(x1, y1 - 1)
        # Invert the status of the lower side light
        if y1 != self.size - 1:
            self.reserve(x1, y1 + 1)
        self.huaqi()

    def random_open_lights(self):
        num_of_lights = random.randint(1, self.size*self.size) # Randomly select the number of lights to turn on
        positions = random.sample(range(self.size*self.size), num_of_lights) # Randomly select the position of the light to be turned on

        for pos in positions:
            x = pos // self.size
            y = pos % self.size
            self.reserve(x, y)

def reset_game(size):
    game.size = size
    game.lights = [['wu' for _ in range(size)] for _ in range(size)]
    game.cv.config(width=size*40 + 10, height=size*40 + 10)
    game.draw_grid()
    game.random_open_lights()
    game.huaqi()

def on_small():
    reset_game(5)

def on_medium():
    reset_game(8)

def on_large():
    reset_game(12)

root = Tk()
game = LightsOutGame(root)

menu_frame = Frame(root)
menu_frame.pack()

small_button = Button(menu_frame, text="small", command=on_small)
small_button.pack(side=LEFT)

medium_button = Button(menu_frame, text="中", command=on_medium)
medium_button.pack(side=LEFT)

large_button = Button(menu_frame, text="large", command=on_large)
large_button.pack(side=LEFT)

root.mainloop()

Extended code for function 2:

Please design and implement interface functions that allow players to choose game difficulty, as follows:

Easy: 20% of the grid states are flipped at the beginning

Medium: 40% of the grid states are flipped at the beginning

Difficulty: 60% of the grid states are flipped at the beginning

from tkinter import *
from tkinter import messagebox
import random

classLightsOutGame:
    def __init__(self, master=None, size=5, difficulty='medium'):
        self.master = master
        self.size = size
        self.lights = [['wu' for _ in range(size)] for _ in range(size)]
        self.create_widgets()
        self.random_open_lights(difficulty) # Randomly turn on some lights

    def create_widgets(self):
        self.cv = Canvas(self.master, bg='white', width=self.size*40 + 10, height=self.size*40 + 10)
        self.cv.pack()

        self.draw_grid() # Draw grid
        self.huaqi() # Draw the status diagram of the light

        self.cv.bind('<Button-1>', self.luozi)

    def draw_grid(self):
        self.cv.delete("grid") # Clear the previous grid

        for i in range(self.size + 1): # Draw grid lines
            self.cv.create_line(10, 10 + i * 40, 10 + self.size * 40, 10 + i * 40, arrow='none', tags="grid")
            self.cv.create_line(10 + i * 40, 10, 10 + i * 40, 10 + self.size * 40, arrow='none', tags="grid")

    def huaqi(self):
        self.cv.delete("lights") # Clear previous lights

        for i in range(self.size):
            for u in range(self.size):
                if self.lights[i][u] == 'you':
                    self.cv.create_oval(i * 40 + 10, u * 40 + 10, (i + 1) * 40 + 10, (u + 1) * 40 + 10,
                                        outline='white', fill='yellow', width=2, tags="lights") # Turn on the lights
                else:
                    self.cv.create_oval(i * 40 + 10, u * 40 + 10, (i + 1) * 40 + 10, (u + 1) * 40 + 10,
                                        outline='white', fill='white', width=2, tags="lights") # Turn off lights

    def reserve(self, x1, y1):
        if self.lights[x1][y1] == 'wu':
            self.lights[x1][y1] = 'you'
        else:
            self.lights[x1][y1] = 'wu'

    def luozi(self, event):
        x1 = (event.x - 10) // 40
        y1 = (event.y - 10) // 40
        print(x1, y1)
        self.reserve(x1, y1) # Flip the state of the light at (x1, y1)