-
Notifications
You must be signed in to change notification settings - Fork 22
/
tetris.py
134 lines (114 loc) · 5.98 KB
/
tetris.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import tkinter as tk
import random
from threading import Lock
COLORS = ['gray', 'lightgreen', 'pink', 'blue', 'orange', 'purple']
class Tetris():
FIELD_HEIGHT = 20
FIELD_WIDTH = 10
SCORE_PER_ELIMINATED_LINES = (0, 40, 100, 300, 1200)
TETROMINOS = [
[(0, 0), (0, 1), (1, 0), (1,1)], # O
[(0, 0), (0, 1), (1, 1), (2,1)], # L
[(0, 1), (1, 1), (2, 1), (2,0)], # J
[(0, 1), (1, 0), (1, 1), (2,0)], # Z
[(0, 1), (1, 0), (1, 1), (2,1)], # T
[(0, 0), (1, 0), (1, 1), (2,1)], # S
[(0, 1), (1, 1), (2, 1), (3,1)], # I
]
def __init__(self):
self.field = [[0 for c in range(Tetris.FIELD_WIDTH)] for r in range(Tetris.FIELD_HEIGHT)]
self.score = 0
self.level = 0
self.total_lines_eliminated = 0
self.game_over = False
self.move_lock = Lock()
self.reset_tetromino()
def reset_tetromino(self):
self.tetromino = random.choice(Tetris.TETROMINOS)[:]
self.tetromino_color = random.randint(1, len(COLORS)-1)
self.tetromino_offset = [-2, Tetris.FIELD_WIDTH//2]
self.game_over = any(not self.is_cell_free(r, c) for (r, c) in self.get_tetromino_coords())
def get_tetromino_coords(self):
return [(r+self.tetromino_offset[0], c + self.tetromino_offset[1]) for (r, c) in self.tetromino]
def apply_tetromino(self):
for (r, c) in self.get_tetromino_coords():
self.field[r][c] = self.tetromino_color
new_field = [row for row in self.field if any(tile == 0 for tile in row)]
lines_eliminated = len(self.field)-len(new_field)
self.total_lines_eliminated += lines_eliminated
self.field = [[0]*Tetris.FIELD_WIDTH for x in range(lines_eliminated)] + new_field
self.score += Tetris.SCORE_PER_ELIMINATED_LINES[lines_eliminated] * (self.level + 1)
self.level = self.total_lines_eliminated // 10
self.reset_tetromino()
def get_color(self, r, c):
return self.tetromino_color if (r, c) in self.get_tetromino_coords() else self.field[r][c]
def is_cell_free(self, r, c):
return r < Tetris.FIELD_HEIGHT and 0 <= c < Tetris.FIELD_WIDTH and (r < 0 or self.field[r][c] == 0)
def move(self, dr, dc):
with self.move_lock:
if self.game_over:
return
if all(self.is_cell_free(r + dr, c + dc) for (r, c) in self.get_tetromino_coords()):
self.tetromino_offset = [self.tetromino_offset[0] + dr, self.tetromino_offset[1] + dc]
elif dr == 1 and dc == 0:
self.game_over = any(r < 0 for (r, c) in self.get_tetromino_coords())
if not self.game_over:
self.apply_tetromino()
def rotate(self):
with self.move_lock:
if self.game_over:
self.__init__()
return
ys = [r for (r, c) in self.tetromino]
xs = [c for (r, c) in self.tetromino]
size = max(max(ys) - min(ys), max(xs)-min(xs))
rotated_tetromino = [(c, size-r) for (r, c) in self.tetromino]
wallkick_offset = self.tetromino_offset[:]
tetromino_coord = [(r+wallkick_offset[0], c + wallkick_offset[1]) for (r, c) in rotated_tetromino]
min_x = min(c for r, c in tetromino_coord)
max_x = max(c for r, c in tetromino_coord)
max_y = max(r for r, c in tetromino_coord)
wallkick_offset[1] -= min(0, min_x)
wallkick_offset[1] += min(0, Tetris.FIELD_WIDTH - (1 + max_x))
wallkick_offset[0] += min(0, Tetris.FIELD_HEIGHT - (1 + max_y))
tetromino_coord = [(r+wallkick_offset[0], c + wallkick_offset[1]) for (r, c) in rotated_tetromino]
if all(self.is_cell_free(r, c) for (r, c) in tetromino_coord):
self.tetromino, self.tetromino_offset = rotated_tetromino, wallkick_offset
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.tetris = Tetris()
self.pack()
self.create_widgets()
self.update_clock()
def update_clock(self):
self.tetris.move(1, 0)
self.update()
self.master.after(int(1000*(0.66**self.tetris.level)), self.update_clock)
def create_widgets(self):
PIECE_SIZE = 30
self.canvas = tk.Canvas(self, height=PIECE_SIZE*self.tetris.FIELD_HEIGHT,
width = PIECE_SIZE*self.tetris.FIELD_WIDTH, bg="black", bd=0)
self.canvas.bind('<Left>', lambda _: (self.tetris.move(0, -1), self.update()))
self.canvas.bind('<Right>', lambda _: (self.tetris.move(0, 1), self.update()))
self.canvas.bind('<Down>', lambda _: (self.tetris.move(1, 0), self.update()))
self.canvas.bind('<Up>', lambda _: (self.tetris.rotate(), self.update()))
self.canvas.focus_set()
self.rectangles = [
self.canvas.create_rectangle(c*PIECE_SIZE, r*PIECE_SIZE, (c+1)*PIECE_SIZE, (r+1)*PIECE_SIZE)
for r in range(self.tetris.FIELD_HEIGHT) for c in range(self.tetris.FIELD_WIDTH)
]
self.canvas.pack(side="left")
self.status_msg = tk.Label(self, anchor='w', width=11, font=("Courier", 24))
self.status_msg.pack(side="top")
self.game_over_msg = tk.Label(self, anchor='w', width=11, font=("Courier", 24), fg='red')
self.game_over_msg.pack(side="top")
def update(self):
for i, _id in enumerate(self.rectangles):
color_num = self.tetris.get_color(i//self.tetris.FIELD_WIDTH, i % self.tetris.FIELD_WIDTH)
self.canvas.itemconfig(_id, fill=COLORS[color_num])
self.status_msg['text'] = "Score: {}\nLevel: {}".format(self.tetris.score, self.tetris.level)
self.game_over_msg['text'] = "GAME OVER.\nPress UP\nto reset" if self.tetris.game_over else ""
root = tk.Tk()
win = Application(master=root)
win.mainloop()