This time, I made a Tetris-like game using tkinter of Python. Since the program has become quite long, I will omit the explanation significantly, but I will explain only the points.
There were 4 types in the program of the site that I referred to, but I tried to make it 7 types with a little ingenuity. Tetromino includes: Tミノ Zミノ Iミノ Lミノ Jミノ Oミノ Sミノ
The game screen has 20 squares vertically and 10 squares horizontally. If you press the Start button on the right side, tetrimino will come down and the game will start. Like the original Tetris, if one horizontal row is aligned, one row disappears, and if Tetrimino is piled up to the top, the game is over. ←ゲームオーバー時の表示
However, it should be noted that unlike the original Tetris, Tetrimino cannot be rotated. Therefore, the difficulty of surviving for a long time has increased considerably. By the way, I also tried test play, but I had a hard time.
# -*- coding:utf-8 -*-
import tkinter as tk
import random
#constant
BLOCK_SIZE = 25 #Vertical and horizontal size of the block px
FIELD_WIDTH = 10 #Field width
FIELD_HEIGHT = 20 #Field height
MOVE_LEFT = 0 #A constant that indicates moving the block to the left
MOVE_RIGHT = 1 #A constant that indicates moving the block to the right
MOVE_DOWN = 2 #A constant that indicates moving the block down
#A class of squares that make up a block
class TetrisSquare():
def __init__(self, x=0, y=0, color="gray"):
'Create one square'
self.x = x
self.y = y
self.color = color
def set_cord(self, x, y):
'Set the coordinates of the square'
self.x = x
self.y = y
def get_cord(self):
'Get the coordinates of the square'
return int(self.x), int(self.y)
def set_color(self, color):
'Set the color of the square'
self.color = color
def get_color(self):
'Get the color of the square'
return self.color
def get_moved_cord(self, direction):
'Get the coordinates of the square after moving'
#Get the coordinates of the square before moving
x, y = self.get_cord()
#Calculate the coordinates after moving in consideration of the moving direction
if direction == MOVE_LEFT:
return x - 1, y
elif direction == MOVE_RIGHT:
return x + 1, y
elif direction == MOVE_DOWN:
return x, y + 1
else:
return x, y
#Canvas class for drawing Tetris screen
class TetrisCanvas(tk.Canvas):
def __init__(self, master, field):
'Create a canvas to draw tetris'
canvas_width = field.get_width() * BLOCK_SIZE
canvas_height = field.get_height() * BLOCK_SIZE
# tk.Canvas class init
super().__init__(master, width=canvas_width, height=canvas_height, bg="white")
#Place the canvas on the screen
self.place(x=25, y=25)
#Create a Tetris screen by drawing 10x20 squares
for y in range(field.get_height()):
for x in range(field.get_width()):
square = field.get_square(x, y)
x1 = x * BLOCK_SIZE
x2 = (x + 1) * BLOCK_SIZE
y1 = y * BLOCK_SIZE
y2 = (y + 1) * BLOCK_SIZE
self.create_rectangle(
x1, y1, x2, y2,
outline="white", width=1,
fill=square.get_color()
)
#Set the previously drawn field
self.before_field = field
def update(self, field, block):
'Tetris screen updated'
#Create a drawing field (field + block)
new_field = TetrisField()
for y in range(field.get_height()):
for x in range(field.get_width()):
square = field.get_square(x, y)
color = square.get_color()
new_square = new_field.get_square(x, y)
new_square.set_color(color)
#Combine block square information with fields
if block is not None:
block_squares = block.get_squares()
for block_square in block_squares:
#Get the coordinates and color of the block square
x, y = block_square.get_cord()
color = block_square.get_color()
#Update the color of the square on the field of the acquired coordinates
new_field_square = new_field.get_square(x, y)
new_field_square.set_color(color)
#Draw on canvas using drawing fields
for y in range(field.get_height()):
for x in range(field.get_width()):
# (x,y)Get the color of the coordinate field
new_square = new_field.get_square(x, y)
new_color = new_square.get_color()
# (x,y)Do not draw if the coordinates have not changed since the last drawing
before_square = self.before_field.get_square(x, y)
before_color = before_square.get_color()
if(new_color == before_color):
continue
x1 = x * BLOCK_SIZE
x2 = (x + 1) * BLOCK_SIZE
y1 = y * BLOCK_SIZE
y2 = (y + 1) * BLOCK_SIZE
#Draw a rectangle with the color of each position in the field
self.create_rectangle(
x1, y1, x2, y2,
outline="white", width=1, fill=new_color
)
#Update the information of the field drawn last time
self.before_field = new_field
#Field class that manages information on stacked blocks
class TetrisField():
def __init__(self):
self.width = FIELD_WIDTH
self.height = FIELD_HEIGHT
#Initialize field
self.squares = []
for y in range(self.height):
for x in range(self.width):
#Manage fields as a list of square instances
self.squares.append(TetrisSquare(x, y, "gray"))
def get_width(self):
'Get the number of squares in the field (horizontal)'
return self.width
def get_height(self):
'Get the number of squares in the field (vertical)'
return self.height
def get_squares(self):
'Get a list of the squares that make up the field'
return self.squares
def get_square(self, x, y):
'Get the square with the specified coordinates'
return self.squares[y * self.width + x]
def judge_game_over(self, block):
'Determine if the game is over'
#Create a set of coordinates that are already filled on the field
no_empty_cord = set(square.get_cord() for square
in self.get_squares() if square.get_color() != "gray")
#Creating a set of coordinates with blocks
block_cord = set(square.get_cord() for square
in block.get_squares())
#With a set of block coordinates
#Create an intersection of sets of coordinates that are already filled in the field
collision_set = no_empty_cord & block_cord
#If the intersection is empty, the game is not over
if len(collision_set) == 0:
ret = False
else:
ret = True
return ret
def judge_can_move(self, block, direction):
'Determine if the block can be moved in the specified direction'
#Create a set of coordinates that are already filled on the field
no_empty_cord = set(square.get_cord() for square
in self.get_squares() if square.get_color() != "gray")
#Creating a set of coordinates with the moved block
move_block_cord = set(square.get_moved_cord(direction) for square
in block.get_squares())
#Determine if it is out of the field
for x, y in move_block_cord:
#Cannot move if it sticks out
if x < 0 or x >= self.width or \
y < 0 or y >= self.height:
return False
#With the set of coordinates of the block after moving
#Create an intersection of sets of coordinates that are already filled in the field
collision_set = no_empty_cord & move_block_cord
#Movable if the intersection is empty
if len(collision_set) == 0:
ret = True
else:
ret = False
return ret
def fix_block(self, block):
'Fix block and add to field'
for square in block.get_squares():
#Get the coordinates and colors of the squares contained in the block
x, y = square.get_cord()
color = square.get_color()
#Reflect the coordinates and color in the field
field_square = self.get_square(x, y)
field_square.set_color(color)
def delete_line(self):
'Delete a row'
#Check if all lines can be deleted
for y in range(self.height):
for x in range(self.width):
#It cannot be erased if there is even one empty in the line
square = self.get_square(x, y)
if(square.get_color() == "gray"):
#To the next line
break
else:
#If not broken, the line is full
#Delete this line and move the line above this line down one line
for down_y in range(y, 0, -1):
for x in range(self.width):
src_square = self.get_square(x, down_y - 1)
dst_square = self.get_square(x, down_y)
dst_square.set_color(src_square.get_color())
#The top line is always empty
for x in range(self.width):
square = self.get_square(x, 0)
square.set_color("gray")
#Tetris block class
class TetrisBlock():
def __init__(self):
'Create a block of tetris'
#List of squares that make up the block
self.squares = []
#Randomly determine the shape of the block
block_type = random.randint(1, 7)
#Determine the coordinates and colors of the four squares according to the shape of the block
if block_type == 1:
color = "aqua"
cords = [
[FIELD_WIDTH / 2, 0],
[FIELD_WIDTH / 2, 1],
[FIELD_WIDTH / 2, 2],
[FIELD_WIDTH / 2, 3],
]
elif block_type == 2:
color = "yellow"
cords = [
[FIELD_WIDTH / 2, 0],
[FIELD_WIDTH / 2, 1],
[FIELD_WIDTH / 2 - 1, 0],
[FIELD_WIDTH / 2 - 1, 1],
]
elif block_type == 3:
color = "orange"
cords = [
[FIELD_WIDTH / 2 - 1, 0],
[FIELD_WIDTH / 2, 0],
[FIELD_WIDTH / 2, 1],
[FIELD_WIDTH / 2, 2],
]
elif block_type == 4:
color = "blue"
cords = [
[FIELD_WIDTH / 2, 0],
[FIELD_WIDTH / 2 - 1, 0],
[FIELD_WIDTH / 2 - 1, 1],
[FIELD_WIDTH / 2 - 1, 2],
]
elif block_type == 5:
color = "red"
cords = [
[FIELD_WIDTH / 2, 0],
[FIELD_WIDTH / 2, 1],
[FIELD_WIDTH / 2 - 1, 1],
[FIELD_WIDTH / 2 - 1, 2],
]
elif block_type == 6:
color = "green"
cords = [
[FIELD_WIDTH / 2 - 1, 0],
[FIELD_WIDTH / 2 - 1, 1],
[FIELD_WIDTH / 2, 2],
[FIELD_WIDTH / 2, 1],
]
elif block_type == 7:
color = "purple"
cords = [
[FIELD_WIDTH / 2, 1],
[FIELD_WIDTH / 2 - 1, 0],
[FIELD_WIDTH / 2 - 1, 1],
[FIELD_WIDTH / 2 - 1, 2],
]
#Create a square with the determined color and coordinates and add it to the list
for cord in cords:
self.squares.append(TetrisSquare(cord[0], cord[1], color))
def get_squares(self):
'Get the squares that make up the block'
# return [square for square in self.squares]
return self.squares
def move(self, direction):
'Move blocks'
#Move the squares that make up the block
for square in self.squares:
x, y = square.get_moved_cord(direction)
square.set_cord(x, y)
#Class that controls the Tetris game
class TetrisGame():
def __init__(self, master):
'Tetris instantiation'
#Initialize block management list
self.field = TetrisField()
#Set the fall block
self.block = None
#Set tetris screen
self.canvas = TetrisCanvas(master, self.field)
#Tetris screen update
self.canvas.update(self.field, self.block)
def start(self, func):
'Start tetris'
#Set the function to call at the end
self.end_func = func
#Initialize block management list
self.field = TetrisField()
#New fall block added
self.new_block()
def new_block(self):
'Add new block'
#Create a falling block instance
self.block = TetrisBlock()
if self.field.judge_game_over(self.block):
self.end_func()
print("Game Over!")
#Tetris screen updated
self.canvas.update(self.field, self.block)
def move_block(self, direction):
'Move blocks'
#Move only if you can move
if self.field.judge_can_move(self.block, direction):
#Move blocks
self.block.move(direction)
#Update screen
self.canvas.update(self.field, self.block)
else:
#If the block cannot move downwards
if direction == MOVE_DOWN:
#Fix the block
self.field.fix_block(self.block)
self.field.delete_line()
self.new_block()
#A class that accepts events and controls Tetris in response to those events
class EventHandller():
def __init__(self, master, game):
self.master = master
#Game to control
self.game = game
#A timer that issues events on a regular basis
self.timer = None
#Install a game start button
button = tk.Button(master, text='START', command=self.start_event)
button.place(x=25 + BLOCK_SIZE * FIELD_WIDTH + 25, y=30)
def start_event(self):
'Processing when the game start button is pressed'
#Tetris start
self.game.start(self.end_event)
self.running = True
#Timer set
self.timer_start()
#Start accepting key operation input
self.master.bind("<Left>", self.left_key_event)
self.master.bind("<Right>", self.right_key_event)
self.master.bind("<Down>", self.down_key_event)
def end_event(self):
'Processing at the end of the game'
self.running = False
#Stop accepting events
self.timer_end()
self.master.unbind("<Left>")
self.master.unbind("<Right>")
self.master.unbind("<Down>")
def timer_end(self):
'End timer'
if self.timer is not None:
self.master.after_cancel(self.timer)
self.timer = None
def timer_start(self):
'Start timer'
if self.timer is not None:
#Cancel the timer once
self.master.after_cancel(self.timer)
#Timer starts only when Tetris is running
if self.running:
#Start timer
self.timer = self.master.after(1000, self.timer_event)
def left_key_event(self, event):
'Processing when accepting left key input'
#Move the block to the left
self.game.move_block(MOVE_LEFT)
def right_key_event(self, event):
'Processing when accepting right key input'
#Move the block to the right
self.game.move_block(MOVE_RIGHT)
def down_key_event(self, event):
'Processing when accepting lower key input'
#Move the block down
self.game.move_block(MOVE_DOWN)
#Restart the fall timer
self.timer_start()
def timer_event(self):
'Processing when the timer expires'
#Performs the same processing as when accepting down key input
self.down_key_event(None)
class Application(tk.Tk):
def __init__(self):
super().__init__()
#App window settings
self.geometry("400x600")
self.title("Tetris")
#Tetris generation
game = TetrisGame(self)
#Event handler generation
EventHandller(self, game)
def main():
'main function'
#GUI application generation
app = Application()
app.mainloop()
if __name__ == "__main__":
main()
It's a pity that we couldn't reproduce the rotation of Tetrimino, which is the real thrill of Tetris, but I think it's a game that anyone can enjoy. I tried to make a game using Python for the first time, and the sense of accomplishment when it was completed was wonderful. Next, we will revenge to make a rotatable Tetris!
https://daeudaeu.com/programming/python/tkinter/tetris/
Recommended Posts