Some time ago with C language to do a character version of the push box, is really relatively simple. I happened to use Python recently and wanted to make a graphical interface push box in Python. This time it is not as easy as C. First of all, I have never used The Graphical interface of Python. I found a lot of textbooks on the Internet and finally chose Tkinter.

Next to share with you, the main share two points, the first is the implementation process of this program, the second point is some of my thinking in the process of writing.

Introduce a,

Development language: Python3.7Development tool: PyCharm2019.24.Date:2019years10month2By ZackSockCopy the code

This version is different from the C version, which first uses a graphical interface, then adds background music, and can handle a variety of different maps. I have built in three maps, the renderings are as follows:

Much better than last time lol.

Second, development environment

I don’t know if that’s the right name, but it’s all about the modules that are used. Since Python is not my strong suit, I’ll just say it briefly.

First I used Python3.7, using two main modules, tkinter and pygame. Tkinter is used primarily, while PyGame is used to play music. (I wrote all the interfaces in Tkinter because I didn’t know PyGame). I used PyCharm to import the library, which is very easy to import. If you use other software can consider to use PIP to install modules, specific operation see blog: www.cnblogs.com/banzhen/p/i… .

pip install tkinter
pip install pygame
Copy the code

Three, principle analysis

1, maps,

Maps don’t change much in the way of thinking, and are represented in the same two-dimensional array as before. I don’t think that’s a very efficient way to do it, but that idea came to me after I wrote it

2, mobile

In terms of movement, I changed it several times, starting with the original algorithm. This did work, but only for the first level, after I made changes to the map, I found a series of problems and then realized that the actual situation was much more complicated. I hope you’ll forgive me if the code is a little hard to watch because Python uses forced indentation instead of {}.

The idea of movement is as follows:

/ * * *0Represents blank *1Said wall *2Said one *3Represents the box *4End point *5Represents a completed box *6The person at the finish line1And the moving direction is blank2The current position is0
	2And the direction of movement is the wall directlyreturn
	3And the moving direction is set as before the end point6The current location is set to0
	4, the direction of movement is the completed box4.1In front of the box is the boxreturn
		4.2The finished box is in front of the finished boxreturn
		4.3In front of the box is the wallreturn
		4.4The front of the box is blank. The front of the box is set3The front position is set to6The current location is set to0
		4.5, the front of the completed box is the end point5The front position is set to6The current location is set to0
	5In front of the box5.1The front of the box is blank. The front of the box is set to3The front position is set to2The current location is set to0
		5.2The front of the box is the wallreturn
		5.3In front of the box is the boxreturn
		5.4The completed box is in front of the boxreturn
		5.5, the front of the box is the end point. The front of the box is set as5The front position is set to2The current location is set to0Two, the people at the finish line1And the moving direction is blank2The current location is set to4
	2And the direction of movement is the wall directlyreturn
	3And the moving direction is set as before the end point6The current location is set to4
	4, the direction of movement is the completed box4.1In front of the box is the boxreturn
		4.2The finished box is in front of the finished boxreturn
		4.3In front of the box is the wallreturn
		4.4The front of the box is blank. The front of the box is set3The front position is set to6The current location is set to4
		4.5, the front of the completed box is the end point5The front position is set to6The current location is set to4
	5In front of the box5.1The front of the box is blank. The front of the box is set to3The front position is set to2The current location is set to4
		5.2The front of the box is the wallreturn
		5.3In front of the box is the boxreturn
		5.4The completed box is in front of the boxreturn
		5.5, the front of the box is the end point. The front of the box is set as5The front position is set to2The current location is set to4
Copy the code

First of all, people have two states, people can stand in the blank, or they can stand in the finish line. Later, I found that the only difference between people in the blank and people in the end is that after people move, one of the original positions of people is set to 0, namely blank, and the other is set to 4, namely the end. So I judge what’s behind the person before I move, so I don’t have to do the usual code. The above logic can be changed to the following:

/ * * *0Represents blank *1Said wall *2Said one *3Represents the box *4End point *5Represents a completed box *6Represents the person at the end */if(The current position is2) :# is the person in the blank
	back = 0
elif(The current position is6) :# is people at the finish line
	back = 4

1And the moving direction is blank (removable)2The current position is back2And the direction of movement is the wall directlyreturn
3And the moving direction is the end point (moveable)6The current position is set to back4, the direction of movement is the completed box4.1In front of the box is the boxreturn
	4.2The finished box is in front of the finished boxreturn
	4.3In front of the box is the wallreturn
	4.4The front of the box is blank (removable) The front of the box is set3The front position is set to6The current position is set to back4.5, the front of the completed box is the end point (removable) The front of the completed box is set as5The front position is set to6The current position is set to back5In front of the box5.1The front of the box is blank (movable). The front position of the box is set as3The front position is set to2The current position is set to back5.2The front of the box is the wallreturn
	5.3In front of the box is the boxreturn
	5.4The completed box is in front of the boxreturn
	5.5, the front of the box is the end point (movable)5The front position is set to2The current position is set to backCopy the code

Iv. Document analysis

The directory structure is as follows, with three main files BoxGame, initGame, and Painter. The test file is used for testing purposes. Then I will talk about the functions of each file:

  1. BoxGame: As the main entrance to the game, the main flow of the game is inside. To be honest, I have learned little Python and am not very familiar with object orientation of Python, so this process is more process-oriented.
  2. InitGame: Initializes or stores some data, such as map data, people’s locations, map size, levels, etc
  3. Painter: I define a Painter object in this file, which is mainly used to draw maps

In addition to this is the picture resources and music resources.

5. Code analysis

1, BoxGame
from tkinter import *
from initGame import *
from Painter import Painter
from pygame import mixer

Create the interface and set the properties
Create a window
root = Tk()	
# Set the window title
root.title("Push the box")
# set the window size. If the parentheses are "widhtxheight", it is determined that the width is set. Note that "x" is important
root.geometry(str(width*step) + "x" + str(height*step))
# set the margin. If the parentheses are "+left+top", the margin is set
root.geometry("+ 400 + 200")
# resizable(False, False) # resizable(False, False)
root.resizable(0.0)

# Play background music
mixer.init()
mixer.music.load('bgm.mp3')	# Loading music
mixer.music.play()		# Play music, the song will automatically stop after playing

Create a white artboard with the parameters parent window, background, height and width
cv = Canvas(root, bg='white', height=height*step, width=width*step)

# Map
painter = Painter(cv, map, step)
painter.drawMap()

# associated Canvas
cv.pack()

Define a listening method
def move(event) :
	pass
	

       
        
         
          
           
           
          
         
        
       
      
root.bind("<Key>", move)
# enter the loop
root.mainloop()
Copy the code

Because the move code is quite long, I will not write it, and I will explain it later. The main flow of BoxGame is as follows:

  1. The import module
  2. Create the window and set the properties
  3. Play background music
  4. Create a drawing board
  5. Draw a map on a sketchpad
  6. Lay the drawing board over the window
  7. Have a window associated with listening events
  8. Game loop
2, initGame
# Some metrics the game needs
mission = 0
mapList = [
    [
        [0.0.1.1.1.0.0.0],
        [0.0.1.4.1.0.0.0],
        [0.0.1.0.1.1.1.1],
        [1.1.1.3.0.3.4.1],
        [1.4.0.3.2.1.1.1],
        [1.1.1.1.3.1.0.0],
        [0.0.0.1.4.1.0.0],
        [0.0.0.1.1.1.0.0]], [[0.0.0.1.1.1.1.1.1.0],
        [0.1.1.1.0.0.0.0.1.0],
        [1.1.4.0.3.1.1.0.1.1],
        [1.4.4.3.0.3.0.0.2.1],
        [1.4.4.0.3.0.3.0.1.1],
        [1.1.1.1.1.1.0.0.1.0],
        [0.0.0.0.0.1.1.1.1.0]], [[0.0.1.1.1.1.0.0],
        [0.0.1.4.4.1.0.0],
        [0.1.1.0.4.1.1.0],
        [0.1.0.0.3.4.1.0],
        [1.1.0.3.0.0.1.1],
        [1.0.0.1.3.3.0.1],
        [1.0.0.2.0.0.0.1],
        [1.1.1.1.1.1.1.1]], [[1.1.1.1.1.1.1.1],
        [1.0.0.1.0.0.0.1],
        [1.0.3.4.4.3.0.1],
        [1.2.3.4.5.0.1.1],
        [1.0.3.4.4.3.0.1],
        [1.0.0.1.0.0.0.1],
        [1.1.1.1.1.1.1.1]]]map = mapList[3]

# Things behind people
back = 0
# Width and height of the map
width, height = 0.0
# Number of boxes in the map
boxs = 0
# Coordinates of people on the map
x = 0
y = 0
# screen size
step = 30

def start() :
    global width, height, boxs, x, y, map
    # Loop variables
    m, n = 0.0
    for i in map:
        for j in i:
            The number of inner loops is the same, just record width for the first time
            if (n == 0):
                width += 1
            # Number of boxes traversed +1
            if (j == 3):
                boxs += 1
            # When the value is 2 or 6, it means traversing people
            if (j == 2 or j == 6):
                x, y = m, n
            m += 1
        m = 0
        n += 1
    height = n
start()
Copy the code

Since I haven’t implemented the level switch yet, the mapList and Mission parameters are of little use here. The main parameters are as follows:

  1. Back: The thing behind a person (as previously analyzed)
  2. Width, height: indicates the width and height
  3. Boxs: Number of boxes
  4. X, y: human coordinates
  5. Step: The side length of each square grid. Since I am not familiar with drawing pictures on Canvas, I fixed the picture to 30px

Because there is no class defined in initGame, the code is executed when referenced.

3, Painter
from tkinter import PhotoImage, NW

When drawing a picture with Canvas, the picture must be a global variable
img = []
class Painter() :
    def __init__(self, cv, map, step) :
    	""Painter's constructor, on the CV palette, draws a step map based on the map.
    	# pass in the artboard to draw on
        self.cv = cv
        # Pass in map data
        self.map = map
        # incoming map size
        self.step = step
    def drawMap(self) :
        """ Used to draw a map from a map list """
        #img The length of the list
        imgLen = 0
        global img
        # loop variables
        x, y = 0.0
        for i in self.map:
            for j in list(i):
            	# Record the actual location
                lx = x * self.step
                ly = y * self.step

                # Draw blanks
                if (j == 0):
                    self.cv.create_rectangle(lx, ly, lx + self.step, ly+self.step,
                                             fill="white", width=0)
                # HuaQiang
                elif (j == 1):
                    img.append(PhotoImage(file="imgs/wall.png"))
                    self.cv.create_image(lx, ly, anchor=NW, image=img[imgLen - 1])
                elif (j == 2):
                    img.append(PhotoImage(file="imgs/human.png"))
                    self.cv.create_image(lx, ly, anchor=NW, image=img[imgLen - 1])
                # picture box
                elif (j == 3):
                    img.append(PhotoImage(file="imgs/box.png"))
                    self.cv.create_image(lx, ly, anchor=NW, image=img[imgLen - 1])
                elif (j == 4):
                    img.append(PhotoImage(file="imgs/terminal.png"))
                    self.cv.create_image(lx, ly, anchor=NW, image=img[imgLen - 1])
                elif (j == 5):
                    img.append(PhotoImage(file="imgs/star.png"))
                    self.cv.create_image(lx, ly, anchor=NW, image=img[imgLen - 1])
                elif (j == 6):
                    img.append(PhotoImage(file="imgs/t_man.png"))
                    self.cv.create_image(lx, ly, anchor=NW, image=img[imgLen - 1])
                x += 1
            x = 0
            y += 1
Copy the code

Rectangle = “image”; rectangle = “image”; rectangle = “image”;

# Draw rectangle
cv.create_rectangle(sx, sy, ex, ey, key=value...)
1The first two parameters sx and SY (S stands for start) are the coordinates of the upper left corner2The last two parameters ex and EY (e stands for end) represent the coordinates of the lower right corner3Key =value... Represents multiple parameters of the form key=value (in a variable order). For example:# Fill with red
fill = "red"
# The border color is black
outline = "black"
The border width is 5
width = 5Specific uses are as follows:# Draw a black rectangle with sides of 30 in the upper left corner
cv.create_rectangle(0.0.30.30, fill="black", width=0)
Copy the code

Then draw the picture:

Img must be a global object
self.cv.create_image(x, y, anchor=NW, img)
1The first two parameters are still coordinates, but they are not necessarily the upper-left coordinates. X and y are the center coordinates of the image by default2, anchor=NW, after setting anchor, x and y are the coordinates of the upper left corner of the picture3Img is a PhotoImage object (a PhotoImage object is an object in tkinter). A PhotoImage object is created as followsCreate a PhotoImage object from the file path
img = PhotoImage(file="img/img1.png")
Copy the code

Since I don’t know much about it myself, I can’t say anything more specific.

Then there is the question of the actual coordinates, which are based on arrays. When you actually draw, you need to use specific pixels. In the drawing process, you need to draw two kinds of rectangle, picture.

  1. Rectangle: Rectangle requires two coordinates. When the array coordinate is (1,1), the corresponding pixel coordinate is (30, 30) because the unit interval is step (30). (2,2) corresponds to (60,60), that is, (x*step, y*step), and the terminal position is (x*step+step, y*step+step).
  2. Image: Only one coordinate is needed to draw the image, the upper-left coordinate, which is the same as before (x*step, y*step).

Another important point is that I defined the IMG list at the very beginning to hold image objects. At first I tried using a single image object, but only one was displayed when drawing the image, then I thought of using an IMG list instead, and it worked. (Because my study is not very solid, also can not explain clearly).

There are two steps to draw an image:

Create a picture object based on the array elements and add it to the end of the list
img.append(PhotoImage(file="imgs/wall.png"))

Img [imgLen -1], imgLen is the current length of the list, and imgLen-1 is the last element, which is the newly created image object
self.cv.create_image(lx, ly, anchor=NW, image=img[imgLen - 1])
Copy the code
4, move
def move(event) :
    global x, y, boxs, back, mission,mapList, map
    direction = event.char

    # Judge what's behind people
    # The person in the blank
    if (map[y][x] == 2):
        back = 0	Set back to blank
    # People at the finish line
    elif (map[y][x] == 6):
        back = 4	# set back to destination
	
	# if I press W
    if(direction == 'w') :Get the coordinates ahead of the movement direction
        ux, uy = x, y-1
        # If there is a wall in front, return directly
        if(map[uy][ux] == 1) :return
        # Blank in front (removable)
        if (map[uy][ux] == 0) :map[uy][ux] = 2		# Set the front to human
        # The end is ahead
        elif (map[uy][ux] == 4) :map[uy][ux] = 6		# Set the front as the destination

        # The completed box is in front
        elif (map[uy][ux] == 5) :No boxes or walls can be moved
            if (map[uy - 1][ux] == 3 or map[uy - 1][ux] == 5 or map[uy - 1][ux] == 1) :return
            # Blank before completed (removable)
            elif (map[uy - 1][ux] == 0) :map[uy - 1][ux] = 3		# Move the box forward
                map[uy][ux] = 6			# Finished box was originally the end point, after the person moved on it will be 6
                boxs += 1				# Remove boxes, number of boxes +1
            # Finish in front of finished box (removable)
            elif (map[uy - 1][ux] == 4) :map[uy - 1][ux] = 5		# The front of the front is set to the finished box
                map[uy][ux] = 6			# The box in front of us was originally the end point, but when the person moved up, it was 6
        # Boxes in front
        elif (map[uy][ux] == 3) :# Boxes cannot be moved
            if (map[uy - 1][ux] == 1 or map[uy - 1][ux] == 3 or map[uy - 1][ux] == 5) :return
            The front of the box is blank
            elif (map[uy - 1][ux] == 0) :map[uy - 1][ux] = 3
                map[uy][ux] = 2
            # Finish in front of the box
            elif (map[uy - 1][ux] == 4) :map[uy - 1][ux] = 5
                map[uy][ux] = 2
                boxs -= 1
        
        # change the current position to back (2 or 6)
        map[y][x] = back
        # Record the position after the move
        y = uy

    # Clear the screen and draw a map
    cv.delete("all")
    painter.drawMap()
    if(boxs == 0) :print("Game over.")
Copy the code

I’m only showing you in one direction, because the other directions are very similar. The only difference is that the coordinates of the front and the front are as follows:

  • Forward: forward UX,uy=x,y-1, forward UX,uy-1
  • Down: forward UX,uy=x,y+1, forward UX, Yu +1
  • Left: Forward UX,uy=x-1,y, forward UX-1,uy
  • Right: forward UX,uy=x+1,y, forward UX +1,uy

Six, summarized

Due to my lack of understanding of Python language, it is inevitable that THERE will be some unclear or wrong explanations in my blog. I am very sorry and hope you can forgive me.

The game is much more process-oriented, and there’s a lot of room for improvement. I also got Clever_Hui, the big Python developer, to help me with the improvements, because I don’t know much about the modified code, so all I’m sharing is my original code. Source code two copies I will upload, thank you for your support.

  • Original: link: pan.baidu.com/s/1NSmOeSlp… Extraction code: R0BA

  • Improved version: Link: pan.baidu.com/s/1-_uiZKRd… Extraction code: 53YT