Tkinter 1 Player Tetris game

Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
8
down vote
favorite
README.md
Tetris game
I am working towards making a Tetris game where you can challenge an AI. Multiple parts have been finished, but a lot is still under construction. However I would like an intermediate review. The code I will post is a fully working 1 player Tetris game made with python3.6 & tkinter. If you are interested, Check the full github online.
Controls
- To change rotation of the current Piece w
- To move Left a
- To move Right d
- Hard drop (The piece will fall down instantly) s
Features
- See the next piece
- Hard drop
- Multiple rows cleared at once give more score
- [! Missing] Update in speed after certain amount of time/blocks
Code
#!/usr/bin/python3
from tkinter import Canvas, Label, Tk, StringVar, Button, LEFT
from random import choice, randint
class GameCanvas(Canvas):
def clean_line(self, boxes_to_delete):
for box in boxes_to_delete:
self.delete(box)
self.update()
def drop_boxes(self, boxes_to_drop):
for box in boxes_to_drop:
self.move(box, 0, Tetris.BOX_SIZE)
self.update()
def completed_lines(self, y_coords):
cleaned_lines = 0
y_coords = sorted(y_coords)
for y in y_coords:
if sum(1 for box in self.find_withtag('game') if self.coords(box)[3] == y) ==
((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE):
self.clean_line([box
for box in self.find_withtag('game')
if self.coords(box)[3] == y])
self.drop_boxes([box
for box in self.find_withtag('game')
if self.coords(box)[3] < y])
cleaned_lines += 1
return cleaned_lines
def game_board(self):
board = [[0] * ((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE)
for _ in range(Tetris.GAME_HEIGHT // Tetris.BOX_SIZE)]
for box in self.find_withtag('game'):
x, y, _, _ = self.coords(box)
board[int(y // Tetris.BOX_SIZE)][int(x // Tetris.BOX_SIZE)] = 1
return board
def boxes(self):
return self.find_withtag('game') == self.find_withtag(fill="blue")
class Shape():
def __init__(self, coords = None):
if not coords:
self.__coords = choice(Tetris.SHAPES)
else:
self.__coords = coords
@property
def coords(self):
return self.__coords
def rotate(self):
self.__coords = self.__rotate()
def rotate_directions(self):
rotated = self.__rotate()
directions = [(rotated[i][0] - self.__coords[i][0],
rotated[i][1] - self.__coords[i][1]) for i in range(len(self.__coords))]
return directions
@property
def matrix(self):
return [[1 if (j, i) in self.__coords else 0
for j in range(max(self.__coords, key=lambda x: x[0])[0] + 1)]
for i in range(max(self.__coords, key=lambda x: x[1])[1] + 1)]
def drop(self, board, offset):
# print("nnn")
# print('n'.join(''.join(map(str, b)) for b in board))
# print("nnn")
off_x, off_y = offset
# print(off_x,off_y)
last_level = len(board) - len(self.matrix) + 1
for level in range(off_y, last_level):
for i in range(len(self.matrix)):
for j in range(len(self.matrix[0])):
if board[level+i][off_x+j] == 1 and self.matrix[i][j] == 1:
return level - 1
return last_level - 1
def __rotate(self):
max_x = max(self.__coords, key=lambda x:x[0])[0]
new_original = (max_x, 0)
rotated = [(new_original[0] - coord[1],
new_original[1] + coord[0]) for coord in self.__coords]
min_x = min(rotated, key=lambda x:x[0])[0]
min_y = min(rotated, key=lambda x:x[1])[1]
return [(coord[0] - min_x, coord[1] - min_y) for coord in rotated]
class Piece():
def __init__(self, canvas, start_point, shape = None):
self.__shape = shape
if not shape:
self.__shape = Shape()
self.canvas = canvas
self.boxes = self.__create_boxes(start_point)
@property
def shape(self):
return self.__shape
def move(self, direction):
if all(self.__can_move(self.canvas.coords(box), direction) for box in self.boxes):
x, y = direction
for box in self.boxes:
self.canvas.move(box,
x * Tetris.BOX_SIZE,
y * Tetris.BOX_SIZE)
return True
return False
def rotate(self):
directions = self.__shape.rotate_directions()
if all(self.__can_move(self.canvas.coords(self.boxes[i]), directions[i]) for i in range(len(self.boxes))):
self.__shape.rotate()
for i in range(len(self.boxes)):
x, y = directions[i]
self.canvas.move(self.boxes[i],
x * Tetris.BOX_SIZE,
y * Tetris.BOX_SIZE)
@property
def offset(self):
return (min(int(self.canvas.coords(box)[0]) // Tetris.BOX_SIZE for box in self.boxes),
min(int(self.canvas.coords(box)[1]) // Tetris.BOX_SIZE for box in self.boxes))
def predict_movement(self, board):
level = self.__shape.drop(board, self.offset)
min_y = min([self.canvas.coords(box)[1] for box in self.boxes])
return (0, level - (min_y // Tetris.BOX_SIZE))
def predict_drop(self, board):
level = self.__shape.drop(board, self.offset)
self.remove_predicts()
min_y = min([self.canvas.coords(box)[1] for box in self.boxes])
for box in self.boxes:
x1, y1, x2, y2 = self.canvas.coords(box)
box = self.canvas.create_rectangle(x1,
level * Tetris.BOX_SIZE + (y1 - min_y),
x2,
(level + 1) * Tetris.BOX_SIZE + (y1 - min_y),
fill="yellow",
tags = "predict")
def remove_predicts(self):
for i in self.canvas.find_withtag('predict'):
self.canvas.delete(i)
self.canvas.update()
def __create_boxes(self, start_point):
boxes =
off_x, off_y = start_point
for coord in self.__shape.coords:
x, y = coord
box = self.canvas.create_rectangle(x * Tetris.BOX_SIZE + off_x,
y * Tetris.BOX_SIZE + off_y,
x * Tetris.BOX_SIZE + Tetris.BOX_SIZE + off_x,
y * Tetris.BOX_SIZE + Tetris.BOX_SIZE + off_y,
fill="blue",
tags="game")
boxes += [box]
return boxes
def __can_move(self, box_coords, new_pos):
x, y = new_pos
x = x * Tetris.BOX_SIZE
y = y * Tetris.BOX_SIZE
x_left, y_up, x_right, y_down = box_coords
overlap = set(self.canvas.find_overlapping((x_left + x_right) / 2 + x,
(y_up + y_down) / 2 + y,
(x_left + x_right) / 2 + x,
(y_up + y_down) / 2 + y))
other_items = set(self.canvas.find_withtag('game')) - set(self.boxes)
if y_down + y > Tetris.GAME_HEIGHT or
x_left + x < 0 or
x_right + x > Tetris.GAME_WIDTH or
overlap & other_items:
# print("y_down + y > Tetris.GAME_HEIGHT : ".format(y_down + y > Tetris.GAME_HEIGHT))
# print("x_left + x < 0 : ".format(x_left + x < 0))
# print("x_right + x > Tetris.GAME_WIDTH : ".format(x_right + x > Tetris.GAME_WIDTH))
# print("overlap & other_items : ".format(overlap & other_items))
return False
return True
class Tetris():
SHAPES = ([(0, 0), (1, 0), (0, 1), (1, 1)], # Square
[(0, 0), (1, 0), (2, 0), (3, 0)], # Line
[(2, 0), (0, 1), (1, 1), (2, 1)], # Right L
[(0, 0), (0, 1), (1, 1), (2, 1)], # Left L
[(0, 1), (1, 1), (1, 0), (2, 0)], # Right Z
[(0, 0), (1, 0), (1, 1), (2, 1)], # Left Z
[(1, 0), (0, 1), (1, 1), (2, 1)]) # T
BOX_SIZE = 20
GAME_WIDTH = 300
GAME_HEIGHT = 500
GAME_START_POINT = GAME_WIDTH / 2 / BOX_SIZE * BOX_SIZE - BOX_SIZE
def __init__(self, predictable = False):
self._level = 1
self._score = 0
self._blockcount = 0
self.speed = 500
self.predictable = predictable
self.root = Tk()
self.root.geometry("500x550")
self.root.title('Tetris')
self.root.bind("<Key>", self.game_control)
self.__game_canvas()
self.__level_score_label()
self.__next_piece_canvas()
def game_control(self, event):
if event.char in ["a", "A", "uf702"]:
self.current_piece.move((-1, 0))
self.update_predict()
elif event.char in ["d", "D", "uf703"]:
self.current_piece.move((1, 0))
self.update_predict()
elif event.char in ["s", "S", "uf701"]:
self.hard_drop()
elif event.char in ["w", "W", "uf700"]:
self.current_piece.rotate()
self.update_predict()
def new_game(self):
self.level = 1
self.score = 0
self.blockcount = 0
self.speed = 500
self.canvas.delete("all")
self.next_canvas.delete("all")
self.__draw_canvas_frame()
self.__draw_next_canvas_frame()
self.current_piece = None
self.next_piece = None
self.game_board = [[0] * ((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE)
for _ in range(Tetris.GAME_HEIGHT // Tetris.BOX_SIZE)]
self.update_piece()
def update_piece(self):
if not self.next_piece:
self.next_piece = Piece(self.next_canvas, (20,20))
self.current_piece = Piece(self.canvas, (Tetris.GAME_START_POINT, 0), self.next_piece.shape)
self.next_canvas.delete("all")
self.__draw_next_canvas_frame()
self.next_piece = Piece(self.next_canvas, (20,20))
self.update_predict()
def start(self):
self.new_game()
self.root.after(self.speed, None)
self.drop()
self.root.mainloop()
def drop(self):
if not self.current_piece.move((0,1)):
self.current_piece.remove_predicts()
self.completed_lines()
self.game_board = self.canvas.game_board()
self.update_piece()
if self.is_game_over():
return
else:
self._blockcount += 1
self.score += 1
self.root.after(self.speed, self.drop)
def hard_drop(self):
self.current_piece.move(self.current_piece.predict_movement(self.game_board))
def update_predict(self):
if self.predictable:
self.current_piece.predict_drop(self.game_board)
def update_status(self):
self.status_var.set(f"Level: self.level, Score: self.score")
self.status.update()
def is_game_over(self):
if not self.current_piece.move((0,1)):
self.play_again_btn = Button(self.root, text="Play Again", command=self.play_again)
self.quit_btn = Button(self.root, text="Quit", command=self.quit)
self.play_again_btn.place(x = Tetris.GAME_WIDTH + 10, y = 200, width=100, height=25)
self.quit_btn.place(x = Tetris.GAME_WIDTH + 10, y = 300, width=100, height=25)
return True
return False
def play_again(self):
self.play_again_btn.destroy()
self.quit_btn.destroy()
self.start()
def quit(self):
self.root.quit()
def completed_lines(self):
y_coords = [self.canvas.coords(box)[3] for box in self.current_piece.boxes]
completed_line = self.canvas.completed_lines(y_coords)
if completed_line == 1:
self.score += 400
elif completed_line == 2:
self.score += 1000
elif completed_line == 3:
self.score += 3000
elif completed_line >= 4:
self.score += 12000
def __game_canvas(self):
self.canvas = GameCanvas(self.root,
width = Tetris.GAME_WIDTH,
height = Tetris.GAME_HEIGHT)
self.canvas.pack(padx=5 , pady=10, side=LEFT)
def __level_score_label(self):
self.status_var = StringVar()
self.status = Label(self.root,
textvariable=self.status_var,
font=("Helvetica", 10, "bold"))
#self.status.place(x = Tetris.GAME_WIDTH + 10, y = 100, width=100, height=25)
self.status.pack()
def __next_piece_canvas(self):
self.next_canvas = Canvas(self.root,
width = 100,
height = 100)
self.next_canvas.pack(padx=5 , pady=10)
def __draw_canvas_frame(self):
self.canvas.create_line(10, 0, 10, self.GAME_HEIGHT, fill = "red", tags = "line")
self.canvas.create_line(self.GAME_WIDTH-10, 0, self.GAME_WIDTH-10, self.GAME_HEIGHT, fill = "red", tags = "line")
self.canvas.create_line(10, self.GAME_HEIGHT, self.GAME_WIDTH-10, self.GAME_HEIGHT, fill = "red", tags = "line")
def __draw_next_canvas_frame(self):
self.next_canvas.create_rectangle(10, 10, 90, 90, tags="frame")
#set & get
def __get_level(self):
return self._level
def __set_level(self, level):
self.speed = 500 - (level - 1) * 25
self._level = level
self.update_status()
def __get_score(self):
return self._score
def __set_score(self, score):
self._score = score
self.update_status()
def __get_blockcount(self):
return self._blockcount
def __set_blockcount(self, blockcount):
self.level = blockcount // 5 + 1
self._blockcount = blockcount
level = property(__get_level, __set_level)
score = property(__get_score, __set_score)
blockcount = property(__get_blockcount, __set_blockcount)
if __name__ == '__main__':
game = Tetris(predictable = True)
game.start()
python python-3.x game tkinter tetris
add a comment |Â
up vote
8
down vote
favorite
README.md
Tetris game
I am working towards making a Tetris game where you can challenge an AI. Multiple parts have been finished, but a lot is still under construction. However I would like an intermediate review. The code I will post is a fully working 1 player Tetris game made with python3.6 & tkinter. If you are interested, Check the full github online.
Controls
- To change rotation of the current Piece w
- To move Left a
- To move Right d
- Hard drop (The piece will fall down instantly) s
Features
- See the next piece
- Hard drop
- Multiple rows cleared at once give more score
- [! Missing] Update in speed after certain amount of time/blocks
Code
#!/usr/bin/python3
from tkinter import Canvas, Label, Tk, StringVar, Button, LEFT
from random import choice, randint
class GameCanvas(Canvas):
def clean_line(self, boxes_to_delete):
for box in boxes_to_delete:
self.delete(box)
self.update()
def drop_boxes(self, boxes_to_drop):
for box in boxes_to_drop:
self.move(box, 0, Tetris.BOX_SIZE)
self.update()
def completed_lines(self, y_coords):
cleaned_lines = 0
y_coords = sorted(y_coords)
for y in y_coords:
if sum(1 for box in self.find_withtag('game') if self.coords(box)[3] == y) ==
((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE):
self.clean_line([box
for box in self.find_withtag('game')
if self.coords(box)[3] == y])
self.drop_boxes([box
for box in self.find_withtag('game')
if self.coords(box)[3] < y])
cleaned_lines += 1
return cleaned_lines
def game_board(self):
board = [[0] * ((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE)
for _ in range(Tetris.GAME_HEIGHT // Tetris.BOX_SIZE)]
for box in self.find_withtag('game'):
x, y, _, _ = self.coords(box)
board[int(y // Tetris.BOX_SIZE)][int(x // Tetris.BOX_SIZE)] = 1
return board
def boxes(self):
return self.find_withtag('game') == self.find_withtag(fill="blue")
class Shape():
def __init__(self, coords = None):
if not coords:
self.__coords = choice(Tetris.SHAPES)
else:
self.__coords = coords
@property
def coords(self):
return self.__coords
def rotate(self):
self.__coords = self.__rotate()
def rotate_directions(self):
rotated = self.__rotate()
directions = [(rotated[i][0] - self.__coords[i][0],
rotated[i][1] - self.__coords[i][1]) for i in range(len(self.__coords))]
return directions
@property
def matrix(self):
return [[1 if (j, i) in self.__coords else 0
for j in range(max(self.__coords, key=lambda x: x[0])[0] + 1)]
for i in range(max(self.__coords, key=lambda x: x[1])[1] + 1)]
def drop(self, board, offset):
# print("nnn")
# print('n'.join(''.join(map(str, b)) for b in board))
# print("nnn")
off_x, off_y = offset
# print(off_x,off_y)
last_level = len(board) - len(self.matrix) + 1
for level in range(off_y, last_level):
for i in range(len(self.matrix)):
for j in range(len(self.matrix[0])):
if board[level+i][off_x+j] == 1 and self.matrix[i][j] == 1:
return level - 1
return last_level - 1
def __rotate(self):
max_x = max(self.__coords, key=lambda x:x[0])[0]
new_original = (max_x, 0)
rotated = [(new_original[0] - coord[1],
new_original[1] + coord[0]) for coord in self.__coords]
min_x = min(rotated, key=lambda x:x[0])[0]
min_y = min(rotated, key=lambda x:x[1])[1]
return [(coord[0] - min_x, coord[1] - min_y) for coord in rotated]
class Piece():
def __init__(self, canvas, start_point, shape = None):
self.__shape = shape
if not shape:
self.__shape = Shape()
self.canvas = canvas
self.boxes = self.__create_boxes(start_point)
@property
def shape(self):
return self.__shape
def move(self, direction):
if all(self.__can_move(self.canvas.coords(box), direction) for box in self.boxes):
x, y = direction
for box in self.boxes:
self.canvas.move(box,
x * Tetris.BOX_SIZE,
y * Tetris.BOX_SIZE)
return True
return False
def rotate(self):
directions = self.__shape.rotate_directions()
if all(self.__can_move(self.canvas.coords(self.boxes[i]), directions[i]) for i in range(len(self.boxes))):
self.__shape.rotate()
for i in range(len(self.boxes)):
x, y = directions[i]
self.canvas.move(self.boxes[i],
x * Tetris.BOX_SIZE,
y * Tetris.BOX_SIZE)
@property
def offset(self):
return (min(int(self.canvas.coords(box)[0]) // Tetris.BOX_SIZE for box in self.boxes),
min(int(self.canvas.coords(box)[1]) // Tetris.BOX_SIZE for box in self.boxes))
def predict_movement(self, board):
level = self.__shape.drop(board, self.offset)
min_y = min([self.canvas.coords(box)[1] for box in self.boxes])
return (0, level - (min_y // Tetris.BOX_SIZE))
def predict_drop(self, board):
level = self.__shape.drop(board, self.offset)
self.remove_predicts()
min_y = min([self.canvas.coords(box)[1] for box in self.boxes])
for box in self.boxes:
x1, y1, x2, y2 = self.canvas.coords(box)
box = self.canvas.create_rectangle(x1,
level * Tetris.BOX_SIZE + (y1 - min_y),
x2,
(level + 1) * Tetris.BOX_SIZE + (y1 - min_y),
fill="yellow",
tags = "predict")
def remove_predicts(self):
for i in self.canvas.find_withtag('predict'):
self.canvas.delete(i)
self.canvas.update()
def __create_boxes(self, start_point):
boxes =
off_x, off_y = start_point
for coord in self.__shape.coords:
x, y = coord
box = self.canvas.create_rectangle(x * Tetris.BOX_SIZE + off_x,
y * Tetris.BOX_SIZE + off_y,
x * Tetris.BOX_SIZE + Tetris.BOX_SIZE + off_x,
y * Tetris.BOX_SIZE + Tetris.BOX_SIZE + off_y,
fill="blue",
tags="game")
boxes += [box]
return boxes
def __can_move(self, box_coords, new_pos):
x, y = new_pos
x = x * Tetris.BOX_SIZE
y = y * Tetris.BOX_SIZE
x_left, y_up, x_right, y_down = box_coords
overlap = set(self.canvas.find_overlapping((x_left + x_right) / 2 + x,
(y_up + y_down) / 2 + y,
(x_left + x_right) / 2 + x,
(y_up + y_down) / 2 + y))
other_items = set(self.canvas.find_withtag('game')) - set(self.boxes)
if y_down + y > Tetris.GAME_HEIGHT or
x_left + x < 0 or
x_right + x > Tetris.GAME_WIDTH or
overlap & other_items:
# print("y_down + y > Tetris.GAME_HEIGHT : ".format(y_down + y > Tetris.GAME_HEIGHT))
# print("x_left + x < 0 : ".format(x_left + x < 0))
# print("x_right + x > Tetris.GAME_WIDTH : ".format(x_right + x > Tetris.GAME_WIDTH))
# print("overlap & other_items : ".format(overlap & other_items))
return False
return True
class Tetris():
SHAPES = ([(0, 0), (1, 0), (0, 1), (1, 1)], # Square
[(0, 0), (1, 0), (2, 0), (3, 0)], # Line
[(2, 0), (0, 1), (1, 1), (2, 1)], # Right L
[(0, 0), (0, 1), (1, 1), (2, 1)], # Left L
[(0, 1), (1, 1), (1, 0), (2, 0)], # Right Z
[(0, 0), (1, 0), (1, 1), (2, 1)], # Left Z
[(1, 0), (0, 1), (1, 1), (2, 1)]) # T
BOX_SIZE = 20
GAME_WIDTH = 300
GAME_HEIGHT = 500
GAME_START_POINT = GAME_WIDTH / 2 / BOX_SIZE * BOX_SIZE - BOX_SIZE
def __init__(self, predictable = False):
self._level = 1
self._score = 0
self._blockcount = 0
self.speed = 500
self.predictable = predictable
self.root = Tk()
self.root.geometry("500x550")
self.root.title('Tetris')
self.root.bind("<Key>", self.game_control)
self.__game_canvas()
self.__level_score_label()
self.__next_piece_canvas()
def game_control(self, event):
if event.char in ["a", "A", "uf702"]:
self.current_piece.move((-1, 0))
self.update_predict()
elif event.char in ["d", "D", "uf703"]:
self.current_piece.move((1, 0))
self.update_predict()
elif event.char in ["s", "S", "uf701"]:
self.hard_drop()
elif event.char in ["w", "W", "uf700"]:
self.current_piece.rotate()
self.update_predict()
def new_game(self):
self.level = 1
self.score = 0
self.blockcount = 0
self.speed = 500
self.canvas.delete("all")
self.next_canvas.delete("all")
self.__draw_canvas_frame()
self.__draw_next_canvas_frame()
self.current_piece = None
self.next_piece = None
self.game_board = [[0] * ((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE)
for _ in range(Tetris.GAME_HEIGHT // Tetris.BOX_SIZE)]
self.update_piece()
def update_piece(self):
if not self.next_piece:
self.next_piece = Piece(self.next_canvas, (20,20))
self.current_piece = Piece(self.canvas, (Tetris.GAME_START_POINT, 0), self.next_piece.shape)
self.next_canvas.delete("all")
self.__draw_next_canvas_frame()
self.next_piece = Piece(self.next_canvas, (20,20))
self.update_predict()
def start(self):
self.new_game()
self.root.after(self.speed, None)
self.drop()
self.root.mainloop()
def drop(self):
if not self.current_piece.move((0,1)):
self.current_piece.remove_predicts()
self.completed_lines()
self.game_board = self.canvas.game_board()
self.update_piece()
if self.is_game_over():
return
else:
self._blockcount += 1
self.score += 1
self.root.after(self.speed, self.drop)
def hard_drop(self):
self.current_piece.move(self.current_piece.predict_movement(self.game_board))
def update_predict(self):
if self.predictable:
self.current_piece.predict_drop(self.game_board)
def update_status(self):
self.status_var.set(f"Level: self.level, Score: self.score")
self.status.update()
def is_game_over(self):
if not self.current_piece.move((0,1)):
self.play_again_btn = Button(self.root, text="Play Again", command=self.play_again)
self.quit_btn = Button(self.root, text="Quit", command=self.quit)
self.play_again_btn.place(x = Tetris.GAME_WIDTH + 10, y = 200, width=100, height=25)
self.quit_btn.place(x = Tetris.GAME_WIDTH + 10, y = 300, width=100, height=25)
return True
return False
def play_again(self):
self.play_again_btn.destroy()
self.quit_btn.destroy()
self.start()
def quit(self):
self.root.quit()
def completed_lines(self):
y_coords = [self.canvas.coords(box)[3] for box in self.current_piece.boxes]
completed_line = self.canvas.completed_lines(y_coords)
if completed_line == 1:
self.score += 400
elif completed_line == 2:
self.score += 1000
elif completed_line == 3:
self.score += 3000
elif completed_line >= 4:
self.score += 12000
def __game_canvas(self):
self.canvas = GameCanvas(self.root,
width = Tetris.GAME_WIDTH,
height = Tetris.GAME_HEIGHT)
self.canvas.pack(padx=5 , pady=10, side=LEFT)
def __level_score_label(self):
self.status_var = StringVar()
self.status = Label(self.root,
textvariable=self.status_var,
font=("Helvetica", 10, "bold"))
#self.status.place(x = Tetris.GAME_WIDTH + 10, y = 100, width=100, height=25)
self.status.pack()
def __next_piece_canvas(self):
self.next_canvas = Canvas(self.root,
width = 100,
height = 100)
self.next_canvas.pack(padx=5 , pady=10)
def __draw_canvas_frame(self):
self.canvas.create_line(10, 0, 10, self.GAME_HEIGHT, fill = "red", tags = "line")
self.canvas.create_line(self.GAME_WIDTH-10, 0, self.GAME_WIDTH-10, self.GAME_HEIGHT, fill = "red", tags = "line")
self.canvas.create_line(10, self.GAME_HEIGHT, self.GAME_WIDTH-10, self.GAME_HEIGHT, fill = "red", tags = "line")
def __draw_next_canvas_frame(self):
self.next_canvas.create_rectangle(10, 10, 90, 90, tags="frame")
#set & get
def __get_level(self):
return self._level
def __set_level(self, level):
self.speed = 500 - (level - 1) * 25
self._level = level
self.update_status()
def __get_score(self):
return self._score
def __set_score(self, score):
self._score = score
self.update_status()
def __get_blockcount(self):
return self._blockcount
def __set_blockcount(self, blockcount):
self.level = blockcount // 5 + 1
self._blockcount = blockcount
level = property(__get_level, __set_level)
score = property(__get_score, __set_score)
blockcount = property(__get_blockcount, __set_blockcount)
if __name__ == '__main__':
game = Tetris(predictable = True)
game.start()
python python-3.x game tkinter tetris
add a comment |Â
up vote
8
down vote
favorite
up vote
8
down vote
favorite
README.md
Tetris game
I am working towards making a Tetris game where you can challenge an AI. Multiple parts have been finished, but a lot is still under construction. However I would like an intermediate review. The code I will post is a fully working 1 player Tetris game made with python3.6 & tkinter. If you are interested, Check the full github online.
Controls
- To change rotation of the current Piece w
- To move Left a
- To move Right d
- Hard drop (The piece will fall down instantly) s
Features
- See the next piece
- Hard drop
- Multiple rows cleared at once give more score
- [! Missing] Update in speed after certain amount of time/blocks
Code
#!/usr/bin/python3
from tkinter import Canvas, Label, Tk, StringVar, Button, LEFT
from random import choice, randint
class GameCanvas(Canvas):
def clean_line(self, boxes_to_delete):
for box in boxes_to_delete:
self.delete(box)
self.update()
def drop_boxes(self, boxes_to_drop):
for box in boxes_to_drop:
self.move(box, 0, Tetris.BOX_SIZE)
self.update()
def completed_lines(self, y_coords):
cleaned_lines = 0
y_coords = sorted(y_coords)
for y in y_coords:
if sum(1 for box in self.find_withtag('game') if self.coords(box)[3] == y) ==
((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE):
self.clean_line([box
for box in self.find_withtag('game')
if self.coords(box)[3] == y])
self.drop_boxes([box
for box in self.find_withtag('game')
if self.coords(box)[3] < y])
cleaned_lines += 1
return cleaned_lines
def game_board(self):
board = [[0] * ((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE)
for _ in range(Tetris.GAME_HEIGHT // Tetris.BOX_SIZE)]
for box in self.find_withtag('game'):
x, y, _, _ = self.coords(box)
board[int(y // Tetris.BOX_SIZE)][int(x // Tetris.BOX_SIZE)] = 1
return board
def boxes(self):
return self.find_withtag('game') == self.find_withtag(fill="blue")
class Shape():
def __init__(self, coords = None):
if not coords:
self.__coords = choice(Tetris.SHAPES)
else:
self.__coords = coords
@property
def coords(self):
return self.__coords
def rotate(self):
self.__coords = self.__rotate()
def rotate_directions(self):
rotated = self.__rotate()
directions = [(rotated[i][0] - self.__coords[i][0],
rotated[i][1] - self.__coords[i][1]) for i in range(len(self.__coords))]
return directions
@property
def matrix(self):
return [[1 if (j, i) in self.__coords else 0
for j in range(max(self.__coords, key=lambda x: x[0])[0] + 1)]
for i in range(max(self.__coords, key=lambda x: x[1])[1] + 1)]
def drop(self, board, offset):
# print("nnn")
# print('n'.join(''.join(map(str, b)) for b in board))
# print("nnn")
off_x, off_y = offset
# print(off_x,off_y)
last_level = len(board) - len(self.matrix) + 1
for level in range(off_y, last_level):
for i in range(len(self.matrix)):
for j in range(len(self.matrix[0])):
if board[level+i][off_x+j] == 1 and self.matrix[i][j] == 1:
return level - 1
return last_level - 1
def __rotate(self):
max_x = max(self.__coords, key=lambda x:x[0])[0]
new_original = (max_x, 0)
rotated = [(new_original[0] - coord[1],
new_original[1] + coord[0]) for coord in self.__coords]
min_x = min(rotated, key=lambda x:x[0])[0]
min_y = min(rotated, key=lambda x:x[1])[1]
return [(coord[0] - min_x, coord[1] - min_y) for coord in rotated]
class Piece():
def __init__(self, canvas, start_point, shape = None):
self.__shape = shape
if not shape:
self.__shape = Shape()
self.canvas = canvas
self.boxes = self.__create_boxes(start_point)
@property
def shape(self):
return self.__shape
def move(self, direction):
if all(self.__can_move(self.canvas.coords(box), direction) for box in self.boxes):
x, y = direction
for box in self.boxes:
self.canvas.move(box,
x * Tetris.BOX_SIZE,
y * Tetris.BOX_SIZE)
return True
return False
def rotate(self):
directions = self.__shape.rotate_directions()
if all(self.__can_move(self.canvas.coords(self.boxes[i]), directions[i]) for i in range(len(self.boxes))):
self.__shape.rotate()
for i in range(len(self.boxes)):
x, y = directions[i]
self.canvas.move(self.boxes[i],
x * Tetris.BOX_SIZE,
y * Tetris.BOX_SIZE)
@property
def offset(self):
return (min(int(self.canvas.coords(box)[0]) // Tetris.BOX_SIZE for box in self.boxes),
min(int(self.canvas.coords(box)[1]) // Tetris.BOX_SIZE for box in self.boxes))
def predict_movement(self, board):
level = self.__shape.drop(board, self.offset)
min_y = min([self.canvas.coords(box)[1] for box in self.boxes])
return (0, level - (min_y // Tetris.BOX_SIZE))
def predict_drop(self, board):
level = self.__shape.drop(board, self.offset)
self.remove_predicts()
min_y = min([self.canvas.coords(box)[1] for box in self.boxes])
for box in self.boxes:
x1, y1, x2, y2 = self.canvas.coords(box)
box = self.canvas.create_rectangle(x1,
level * Tetris.BOX_SIZE + (y1 - min_y),
x2,
(level + 1) * Tetris.BOX_SIZE + (y1 - min_y),
fill="yellow",
tags = "predict")
def remove_predicts(self):
for i in self.canvas.find_withtag('predict'):
self.canvas.delete(i)
self.canvas.update()
def __create_boxes(self, start_point):
boxes =
off_x, off_y = start_point
for coord in self.__shape.coords:
x, y = coord
box = self.canvas.create_rectangle(x * Tetris.BOX_SIZE + off_x,
y * Tetris.BOX_SIZE + off_y,
x * Tetris.BOX_SIZE + Tetris.BOX_SIZE + off_x,
y * Tetris.BOX_SIZE + Tetris.BOX_SIZE + off_y,
fill="blue",
tags="game")
boxes += [box]
return boxes
def __can_move(self, box_coords, new_pos):
x, y = new_pos
x = x * Tetris.BOX_SIZE
y = y * Tetris.BOX_SIZE
x_left, y_up, x_right, y_down = box_coords
overlap = set(self.canvas.find_overlapping((x_left + x_right) / 2 + x,
(y_up + y_down) / 2 + y,
(x_left + x_right) / 2 + x,
(y_up + y_down) / 2 + y))
other_items = set(self.canvas.find_withtag('game')) - set(self.boxes)
if y_down + y > Tetris.GAME_HEIGHT or
x_left + x < 0 or
x_right + x > Tetris.GAME_WIDTH or
overlap & other_items:
# print("y_down + y > Tetris.GAME_HEIGHT : ".format(y_down + y > Tetris.GAME_HEIGHT))
# print("x_left + x < 0 : ".format(x_left + x < 0))
# print("x_right + x > Tetris.GAME_WIDTH : ".format(x_right + x > Tetris.GAME_WIDTH))
# print("overlap & other_items : ".format(overlap & other_items))
return False
return True
class Tetris():
SHAPES = ([(0, 0), (1, 0), (0, 1), (1, 1)], # Square
[(0, 0), (1, 0), (2, 0), (3, 0)], # Line
[(2, 0), (0, 1), (1, 1), (2, 1)], # Right L
[(0, 0), (0, 1), (1, 1), (2, 1)], # Left L
[(0, 1), (1, 1), (1, 0), (2, 0)], # Right Z
[(0, 0), (1, 0), (1, 1), (2, 1)], # Left Z
[(1, 0), (0, 1), (1, 1), (2, 1)]) # T
BOX_SIZE = 20
GAME_WIDTH = 300
GAME_HEIGHT = 500
GAME_START_POINT = GAME_WIDTH / 2 / BOX_SIZE * BOX_SIZE - BOX_SIZE
def __init__(self, predictable = False):
self._level = 1
self._score = 0
self._blockcount = 0
self.speed = 500
self.predictable = predictable
self.root = Tk()
self.root.geometry("500x550")
self.root.title('Tetris')
self.root.bind("<Key>", self.game_control)
self.__game_canvas()
self.__level_score_label()
self.__next_piece_canvas()
def game_control(self, event):
if event.char in ["a", "A", "uf702"]:
self.current_piece.move((-1, 0))
self.update_predict()
elif event.char in ["d", "D", "uf703"]:
self.current_piece.move((1, 0))
self.update_predict()
elif event.char in ["s", "S", "uf701"]:
self.hard_drop()
elif event.char in ["w", "W", "uf700"]:
self.current_piece.rotate()
self.update_predict()
def new_game(self):
self.level = 1
self.score = 0
self.blockcount = 0
self.speed = 500
self.canvas.delete("all")
self.next_canvas.delete("all")
self.__draw_canvas_frame()
self.__draw_next_canvas_frame()
self.current_piece = None
self.next_piece = None
self.game_board = [[0] * ((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE)
for _ in range(Tetris.GAME_HEIGHT // Tetris.BOX_SIZE)]
self.update_piece()
def update_piece(self):
if not self.next_piece:
self.next_piece = Piece(self.next_canvas, (20,20))
self.current_piece = Piece(self.canvas, (Tetris.GAME_START_POINT, 0), self.next_piece.shape)
self.next_canvas.delete("all")
self.__draw_next_canvas_frame()
self.next_piece = Piece(self.next_canvas, (20,20))
self.update_predict()
def start(self):
self.new_game()
self.root.after(self.speed, None)
self.drop()
self.root.mainloop()
def drop(self):
if not self.current_piece.move((0,1)):
self.current_piece.remove_predicts()
self.completed_lines()
self.game_board = self.canvas.game_board()
self.update_piece()
if self.is_game_over():
return
else:
self._blockcount += 1
self.score += 1
self.root.after(self.speed, self.drop)
def hard_drop(self):
self.current_piece.move(self.current_piece.predict_movement(self.game_board))
def update_predict(self):
if self.predictable:
self.current_piece.predict_drop(self.game_board)
def update_status(self):
self.status_var.set(f"Level: self.level, Score: self.score")
self.status.update()
def is_game_over(self):
if not self.current_piece.move((0,1)):
self.play_again_btn = Button(self.root, text="Play Again", command=self.play_again)
self.quit_btn = Button(self.root, text="Quit", command=self.quit)
self.play_again_btn.place(x = Tetris.GAME_WIDTH + 10, y = 200, width=100, height=25)
self.quit_btn.place(x = Tetris.GAME_WIDTH + 10, y = 300, width=100, height=25)
return True
return False
def play_again(self):
self.play_again_btn.destroy()
self.quit_btn.destroy()
self.start()
def quit(self):
self.root.quit()
def completed_lines(self):
y_coords = [self.canvas.coords(box)[3] for box in self.current_piece.boxes]
completed_line = self.canvas.completed_lines(y_coords)
if completed_line == 1:
self.score += 400
elif completed_line == 2:
self.score += 1000
elif completed_line == 3:
self.score += 3000
elif completed_line >= 4:
self.score += 12000
def __game_canvas(self):
self.canvas = GameCanvas(self.root,
width = Tetris.GAME_WIDTH,
height = Tetris.GAME_HEIGHT)
self.canvas.pack(padx=5 , pady=10, side=LEFT)
def __level_score_label(self):
self.status_var = StringVar()
self.status = Label(self.root,
textvariable=self.status_var,
font=("Helvetica", 10, "bold"))
#self.status.place(x = Tetris.GAME_WIDTH + 10, y = 100, width=100, height=25)
self.status.pack()
def __next_piece_canvas(self):
self.next_canvas = Canvas(self.root,
width = 100,
height = 100)
self.next_canvas.pack(padx=5 , pady=10)
def __draw_canvas_frame(self):
self.canvas.create_line(10, 0, 10, self.GAME_HEIGHT, fill = "red", tags = "line")
self.canvas.create_line(self.GAME_WIDTH-10, 0, self.GAME_WIDTH-10, self.GAME_HEIGHT, fill = "red", tags = "line")
self.canvas.create_line(10, self.GAME_HEIGHT, self.GAME_WIDTH-10, self.GAME_HEIGHT, fill = "red", tags = "line")
def __draw_next_canvas_frame(self):
self.next_canvas.create_rectangle(10, 10, 90, 90, tags="frame")
#set & get
def __get_level(self):
return self._level
def __set_level(self, level):
self.speed = 500 - (level - 1) * 25
self._level = level
self.update_status()
def __get_score(self):
return self._score
def __set_score(self, score):
self._score = score
self.update_status()
def __get_blockcount(self):
return self._blockcount
def __set_blockcount(self, blockcount):
self.level = blockcount // 5 + 1
self._blockcount = blockcount
level = property(__get_level, __set_level)
score = property(__get_score, __set_score)
blockcount = property(__get_blockcount, __set_blockcount)
if __name__ == '__main__':
game = Tetris(predictable = True)
game.start()
python python-3.x game tkinter tetris
README.md
Tetris game
I am working towards making a Tetris game where you can challenge an AI. Multiple parts have been finished, but a lot is still under construction. However I would like an intermediate review. The code I will post is a fully working 1 player Tetris game made with python3.6 & tkinter. If you are interested, Check the full github online.
Controls
- To change rotation of the current Piece w
- To move Left a
- To move Right d
- Hard drop (The piece will fall down instantly) s
Features
- See the next piece
- Hard drop
- Multiple rows cleared at once give more score
- [! Missing] Update in speed after certain amount of time/blocks
Code
#!/usr/bin/python3
from tkinter import Canvas, Label, Tk, StringVar, Button, LEFT
from random import choice, randint
class GameCanvas(Canvas):
def clean_line(self, boxes_to_delete):
for box in boxes_to_delete:
self.delete(box)
self.update()
def drop_boxes(self, boxes_to_drop):
for box in boxes_to_drop:
self.move(box, 0, Tetris.BOX_SIZE)
self.update()
def completed_lines(self, y_coords):
cleaned_lines = 0
y_coords = sorted(y_coords)
for y in y_coords:
if sum(1 for box in self.find_withtag('game') if self.coords(box)[3] == y) ==
((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE):
self.clean_line([box
for box in self.find_withtag('game')
if self.coords(box)[3] == y])
self.drop_boxes([box
for box in self.find_withtag('game')
if self.coords(box)[3] < y])
cleaned_lines += 1
return cleaned_lines
def game_board(self):
board = [[0] * ((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE)
for _ in range(Tetris.GAME_HEIGHT // Tetris.BOX_SIZE)]
for box in self.find_withtag('game'):
x, y, _, _ = self.coords(box)
board[int(y // Tetris.BOX_SIZE)][int(x // Tetris.BOX_SIZE)] = 1
return board
def boxes(self):
return self.find_withtag('game') == self.find_withtag(fill="blue")
class Shape():
def __init__(self, coords = None):
if not coords:
self.__coords = choice(Tetris.SHAPES)
else:
self.__coords = coords
@property
def coords(self):
return self.__coords
def rotate(self):
self.__coords = self.__rotate()
def rotate_directions(self):
rotated = self.__rotate()
directions = [(rotated[i][0] - self.__coords[i][0],
rotated[i][1] - self.__coords[i][1]) for i in range(len(self.__coords))]
return directions
@property
def matrix(self):
return [[1 if (j, i) in self.__coords else 0
for j in range(max(self.__coords, key=lambda x: x[0])[0] + 1)]
for i in range(max(self.__coords, key=lambda x: x[1])[1] + 1)]
def drop(self, board, offset):
# print("nnn")
# print('n'.join(''.join(map(str, b)) for b in board))
# print("nnn")
off_x, off_y = offset
# print(off_x,off_y)
last_level = len(board) - len(self.matrix) + 1
for level in range(off_y, last_level):
for i in range(len(self.matrix)):
for j in range(len(self.matrix[0])):
if board[level+i][off_x+j] == 1 and self.matrix[i][j] == 1:
return level - 1
return last_level - 1
def __rotate(self):
max_x = max(self.__coords, key=lambda x:x[0])[0]
new_original = (max_x, 0)
rotated = [(new_original[0] - coord[1],
new_original[1] + coord[0]) for coord in self.__coords]
min_x = min(rotated, key=lambda x:x[0])[0]
min_y = min(rotated, key=lambda x:x[1])[1]
return [(coord[0] - min_x, coord[1] - min_y) for coord in rotated]
class Piece():
def __init__(self, canvas, start_point, shape = None):
self.__shape = shape
if not shape:
self.__shape = Shape()
self.canvas = canvas
self.boxes = self.__create_boxes(start_point)
@property
def shape(self):
return self.__shape
def move(self, direction):
if all(self.__can_move(self.canvas.coords(box), direction) for box in self.boxes):
x, y = direction
for box in self.boxes:
self.canvas.move(box,
x * Tetris.BOX_SIZE,
y * Tetris.BOX_SIZE)
return True
return False
def rotate(self):
directions = self.__shape.rotate_directions()
if all(self.__can_move(self.canvas.coords(self.boxes[i]), directions[i]) for i in range(len(self.boxes))):
self.__shape.rotate()
for i in range(len(self.boxes)):
x, y = directions[i]
self.canvas.move(self.boxes[i],
x * Tetris.BOX_SIZE,
y * Tetris.BOX_SIZE)
@property
def offset(self):
return (min(int(self.canvas.coords(box)[0]) // Tetris.BOX_SIZE for box in self.boxes),
min(int(self.canvas.coords(box)[1]) // Tetris.BOX_SIZE for box in self.boxes))
def predict_movement(self, board):
level = self.__shape.drop(board, self.offset)
min_y = min([self.canvas.coords(box)[1] for box in self.boxes])
return (0, level - (min_y // Tetris.BOX_SIZE))
def predict_drop(self, board):
level = self.__shape.drop(board, self.offset)
self.remove_predicts()
min_y = min([self.canvas.coords(box)[1] for box in self.boxes])
for box in self.boxes:
x1, y1, x2, y2 = self.canvas.coords(box)
box = self.canvas.create_rectangle(x1,
level * Tetris.BOX_SIZE + (y1 - min_y),
x2,
(level + 1) * Tetris.BOX_SIZE + (y1 - min_y),
fill="yellow",
tags = "predict")
def remove_predicts(self):
for i in self.canvas.find_withtag('predict'):
self.canvas.delete(i)
self.canvas.update()
def __create_boxes(self, start_point):
boxes =
off_x, off_y = start_point
for coord in self.__shape.coords:
x, y = coord
box = self.canvas.create_rectangle(x * Tetris.BOX_SIZE + off_x,
y * Tetris.BOX_SIZE + off_y,
x * Tetris.BOX_SIZE + Tetris.BOX_SIZE + off_x,
y * Tetris.BOX_SIZE + Tetris.BOX_SIZE + off_y,
fill="blue",
tags="game")
boxes += [box]
return boxes
def __can_move(self, box_coords, new_pos):
x, y = new_pos
x = x * Tetris.BOX_SIZE
y = y * Tetris.BOX_SIZE
x_left, y_up, x_right, y_down = box_coords
overlap = set(self.canvas.find_overlapping((x_left + x_right) / 2 + x,
(y_up + y_down) / 2 + y,
(x_left + x_right) / 2 + x,
(y_up + y_down) / 2 + y))
other_items = set(self.canvas.find_withtag('game')) - set(self.boxes)
if y_down + y > Tetris.GAME_HEIGHT or
x_left + x < 0 or
x_right + x > Tetris.GAME_WIDTH or
overlap & other_items:
# print("y_down + y > Tetris.GAME_HEIGHT : ".format(y_down + y > Tetris.GAME_HEIGHT))
# print("x_left + x < 0 : ".format(x_left + x < 0))
# print("x_right + x > Tetris.GAME_WIDTH : ".format(x_right + x > Tetris.GAME_WIDTH))
# print("overlap & other_items : ".format(overlap & other_items))
return False
return True
class Tetris():
SHAPES = ([(0, 0), (1, 0), (0, 1), (1, 1)], # Square
[(0, 0), (1, 0), (2, 0), (3, 0)], # Line
[(2, 0), (0, 1), (1, 1), (2, 1)], # Right L
[(0, 0), (0, 1), (1, 1), (2, 1)], # Left L
[(0, 1), (1, 1), (1, 0), (2, 0)], # Right Z
[(0, 0), (1, 0), (1, 1), (2, 1)], # Left Z
[(1, 0), (0, 1), (1, 1), (2, 1)]) # T
BOX_SIZE = 20
GAME_WIDTH = 300
GAME_HEIGHT = 500
GAME_START_POINT = GAME_WIDTH / 2 / BOX_SIZE * BOX_SIZE - BOX_SIZE
def __init__(self, predictable = False):
self._level = 1
self._score = 0
self._blockcount = 0
self.speed = 500
self.predictable = predictable
self.root = Tk()
self.root.geometry("500x550")
self.root.title('Tetris')
self.root.bind("<Key>", self.game_control)
self.__game_canvas()
self.__level_score_label()
self.__next_piece_canvas()
def game_control(self, event):
if event.char in ["a", "A", "uf702"]:
self.current_piece.move((-1, 0))
self.update_predict()
elif event.char in ["d", "D", "uf703"]:
self.current_piece.move((1, 0))
self.update_predict()
elif event.char in ["s", "S", "uf701"]:
self.hard_drop()
elif event.char in ["w", "W", "uf700"]:
self.current_piece.rotate()
self.update_predict()
def new_game(self):
self.level = 1
self.score = 0
self.blockcount = 0
self.speed = 500
self.canvas.delete("all")
self.next_canvas.delete("all")
self.__draw_canvas_frame()
self.__draw_next_canvas_frame()
self.current_piece = None
self.next_piece = None
self.game_board = [[0] * ((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE)
for _ in range(Tetris.GAME_HEIGHT // Tetris.BOX_SIZE)]
self.update_piece()
def update_piece(self):
if not self.next_piece:
self.next_piece = Piece(self.next_canvas, (20,20))
self.current_piece = Piece(self.canvas, (Tetris.GAME_START_POINT, 0), self.next_piece.shape)
self.next_canvas.delete("all")
self.__draw_next_canvas_frame()
self.next_piece = Piece(self.next_canvas, (20,20))
self.update_predict()
def start(self):
self.new_game()
self.root.after(self.speed, None)
self.drop()
self.root.mainloop()
def drop(self):
if not self.current_piece.move((0,1)):
self.current_piece.remove_predicts()
self.completed_lines()
self.game_board = self.canvas.game_board()
self.update_piece()
if self.is_game_over():
return
else:
self._blockcount += 1
self.score += 1
self.root.after(self.speed, self.drop)
def hard_drop(self):
self.current_piece.move(self.current_piece.predict_movement(self.game_board))
def update_predict(self):
if self.predictable:
self.current_piece.predict_drop(self.game_board)
def update_status(self):
self.status_var.set(f"Level: self.level, Score: self.score")
self.status.update()
def is_game_over(self):
if not self.current_piece.move((0,1)):
self.play_again_btn = Button(self.root, text="Play Again", command=self.play_again)
self.quit_btn = Button(self.root, text="Quit", command=self.quit)
self.play_again_btn.place(x = Tetris.GAME_WIDTH + 10, y = 200, width=100, height=25)
self.quit_btn.place(x = Tetris.GAME_WIDTH + 10, y = 300, width=100, height=25)
return True
return False
def play_again(self):
self.play_again_btn.destroy()
self.quit_btn.destroy()
self.start()
def quit(self):
self.root.quit()
def completed_lines(self):
y_coords = [self.canvas.coords(box)[3] for box in self.current_piece.boxes]
completed_line = self.canvas.completed_lines(y_coords)
if completed_line == 1:
self.score += 400
elif completed_line == 2:
self.score += 1000
elif completed_line == 3:
self.score += 3000
elif completed_line >= 4:
self.score += 12000
def __game_canvas(self):
self.canvas = GameCanvas(self.root,
width = Tetris.GAME_WIDTH,
height = Tetris.GAME_HEIGHT)
self.canvas.pack(padx=5 , pady=10, side=LEFT)
def __level_score_label(self):
self.status_var = StringVar()
self.status = Label(self.root,
textvariable=self.status_var,
font=("Helvetica", 10, "bold"))
#self.status.place(x = Tetris.GAME_WIDTH + 10, y = 100, width=100, height=25)
self.status.pack()
def __next_piece_canvas(self):
self.next_canvas = Canvas(self.root,
width = 100,
height = 100)
self.next_canvas.pack(padx=5 , pady=10)
def __draw_canvas_frame(self):
self.canvas.create_line(10, 0, 10, self.GAME_HEIGHT, fill = "red", tags = "line")
self.canvas.create_line(self.GAME_WIDTH-10, 0, self.GAME_WIDTH-10, self.GAME_HEIGHT, fill = "red", tags = "line")
self.canvas.create_line(10, self.GAME_HEIGHT, self.GAME_WIDTH-10, self.GAME_HEIGHT, fill = "red", tags = "line")
def __draw_next_canvas_frame(self):
self.next_canvas.create_rectangle(10, 10, 90, 90, tags="frame")
#set & get
def __get_level(self):
return self._level
def __set_level(self, level):
self.speed = 500 - (level - 1) * 25
self._level = level
self.update_status()
def __get_score(self):
return self._score
def __set_score(self, score):
self._score = score
self.update_status()
def __get_blockcount(self):
return self._blockcount
def __set_blockcount(self, blockcount):
self.level = blockcount // 5 + 1
self._blockcount = blockcount
level = property(__get_level, __set_level)
score = property(__get_score, __set_score)
blockcount = property(__get_blockcount, __set_blockcount)
if __name__ == '__main__':
game = Tetris(predictable = True)
game.start()
python python-3.x game tkinter tetris
asked May 2 at 19:35
Ludisposed
5,68621656
5,68621656
add a comment |Â
add a comment |Â
1 Answer
1
active
oldest
votes
up vote
2
down vote
This is a quick answer, maybe I will add more things later (but I do not promise).
My suggestion for improvements is rather from the UX standpoint:
- While your program seems doing the job, it has been hard for me to adapt my mind to the 4 control keys you use. It is more natural to use the arrow direction keys ↓, →, and ← in place of s, d and a respectively.
- For the rotation key, I would also prefer the arrow direction â instead of w. This is good in case you decide to opt for the previous suggestion.
- The yellow indicator is is a little noisy (and to be honest, it irritates me because somehow it confuses me). It would be good to remove it totally: after all, it is a factor that will impact the gamer who will have to focus where he has to place the corresponding shape.
- When the shape is a line, I noticed it is drown a little out of the box (and cuts its right wall) where you display the levels and scores.
- I think you can still improve the colors you used: that red container is a little bit .... how to say ... even the blue and the background. I know this is subjective, but subjectivity is a part of the user experience, especially when it comes to games.
But the overall is good, even better when we see you managed to do that in tkinter. Good continuation.
1
I think I can agree on most points. I am not a game designer, so the UX is really flawed (I know). But, the predictable drop can be disabled bygame = Tetris(predictable = False).Falseis also the default. Secondly, maybe it is preference, butwasdare my goto gaming keys :)
â Ludisposed
May 3 at 12:26
1
Yeah, I am sure you are going to focus more on the AI aspect of the game, UX is too subjective and you can not do it alone. For the keys, I understand what you mean, but coming back to the UX world, I live in France, so my keyboard is AZERTY, not QWERTY, so it is not very easy for me to pressw:)
â Billal BEGUERADJ
May 3 at 12:32
1
The different layout of the keys didn't even cross my mind. Very good point, I will do a UX update eventually, but not that high on the priority list. Great answer nonetheless.
â Ludisposed
May 3 at 12:33
add a comment |Â
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
This is a quick answer, maybe I will add more things later (but I do not promise).
My suggestion for improvements is rather from the UX standpoint:
- While your program seems doing the job, it has been hard for me to adapt my mind to the 4 control keys you use. It is more natural to use the arrow direction keys ↓, →, and ← in place of s, d and a respectively.
- For the rotation key, I would also prefer the arrow direction â instead of w. This is good in case you decide to opt for the previous suggestion.
- The yellow indicator is is a little noisy (and to be honest, it irritates me because somehow it confuses me). It would be good to remove it totally: after all, it is a factor that will impact the gamer who will have to focus where he has to place the corresponding shape.
- When the shape is a line, I noticed it is drown a little out of the box (and cuts its right wall) where you display the levels and scores.
- I think you can still improve the colors you used: that red container is a little bit .... how to say ... even the blue and the background. I know this is subjective, but subjectivity is a part of the user experience, especially when it comes to games.
But the overall is good, even better when we see you managed to do that in tkinter. Good continuation.
1
I think I can agree on most points. I am not a game designer, so the UX is really flawed (I know). But, the predictable drop can be disabled bygame = Tetris(predictable = False).Falseis also the default. Secondly, maybe it is preference, butwasdare my goto gaming keys :)
â Ludisposed
May 3 at 12:26
1
Yeah, I am sure you are going to focus more on the AI aspect of the game, UX is too subjective and you can not do it alone. For the keys, I understand what you mean, but coming back to the UX world, I live in France, so my keyboard is AZERTY, not QWERTY, so it is not very easy for me to pressw:)
â Billal BEGUERADJ
May 3 at 12:32
1
The different layout of the keys didn't even cross my mind. Very good point, I will do a UX update eventually, but not that high on the priority list. Great answer nonetheless.
â Ludisposed
May 3 at 12:33
add a comment |Â
up vote
2
down vote
This is a quick answer, maybe I will add more things later (but I do not promise).
My suggestion for improvements is rather from the UX standpoint:
- While your program seems doing the job, it has been hard for me to adapt my mind to the 4 control keys you use. It is more natural to use the arrow direction keys ↓, →, and ← in place of s, d and a respectively.
- For the rotation key, I would also prefer the arrow direction â instead of w. This is good in case you decide to opt for the previous suggestion.
- The yellow indicator is is a little noisy (and to be honest, it irritates me because somehow it confuses me). It would be good to remove it totally: after all, it is a factor that will impact the gamer who will have to focus where he has to place the corresponding shape.
- When the shape is a line, I noticed it is drown a little out of the box (and cuts its right wall) where you display the levels and scores.
- I think you can still improve the colors you used: that red container is a little bit .... how to say ... even the blue and the background. I know this is subjective, but subjectivity is a part of the user experience, especially when it comes to games.
But the overall is good, even better when we see you managed to do that in tkinter. Good continuation.
1
I think I can agree on most points. I am not a game designer, so the UX is really flawed (I know). But, the predictable drop can be disabled bygame = Tetris(predictable = False).Falseis also the default. Secondly, maybe it is preference, butwasdare my goto gaming keys :)
â Ludisposed
May 3 at 12:26
1
Yeah, I am sure you are going to focus more on the AI aspect of the game, UX is too subjective and you can not do it alone. For the keys, I understand what you mean, but coming back to the UX world, I live in France, so my keyboard is AZERTY, not QWERTY, so it is not very easy for me to pressw:)
â Billal BEGUERADJ
May 3 at 12:32
1
The different layout of the keys didn't even cross my mind. Very good point, I will do a UX update eventually, but not that high on the priority list. Great answer nonetheless.
â Ludisposed
May 3 at 12:33
add a comment |Â
up vote
2
down vote
up vote
2
down vote
This is a quick answer, maybe I will add more things later (but I do not promise).
My suggestion for improvements is rather from the UX standpoint:
- While your program seems doing the job, it has been hard for me to adapt my mind to the 4 control keys you use. It is more natural to use the arrow direction keys ↓, →, and ← in place of s, d and a respectively.
- For the rotation key, I would also prefer the arrow direction â instead of w. This is good in case you decide to opt for the previous suggestion.
- The yellow indicator is is a little noisy (and to be honest, it irritates me because somehow it confuses me). It would be good to remove it totally: after all, it is a factor that will impact the gamer who will have to focus where he has to place the corresponding shape.
- When the shape is a line, I noticed it is drown a little out of the box (and cuts its right wall) where you display the levels and scores.
- I think you can still improve the colors you used: that red container is a little bit .... how to say ... even the blue and the background. I know this is subjective, but subjectivity is a part of the user experience, especially when it comes to games.
But the overall is good, even better when we see you managed to do that in tkinter. Good continuation.
This is a quick answer, maybe I will add more things later (but I do not promise).
My suggestion for improvements is rather from the UX standpoint:
- While your program seems doing the job, it has been hard for me to adapt my mind to the 4 control keys you use. It is more natural to use the arrow direction keys ↓, →, and ← in place of s, d and a respectively.
- For the rotation key, I would also prefer the arrow direction â instead of w. This is good in case you decide to opt for the previous suggestion.
- The yellow indicator is is a little noisy (and to be honest, it irritates me because somehow it confuses me). It would be good to remove it totally: after all, it is a factor that will impact the gamer who will have to focus where he has to place the corresponding shape.
- When the shape is a line, I noticed it is drown a little out of the box (and cuts its right wall) where you display the levels and scores.
- I think you can still improve the colors you used: that red container is a little bit .... how to say ... even the blue and the background. I know this is subjective, but subjectivity is a part of the user experience, especially when it comes to games.
But the overall is good, even better when we see you managed to do that in tkinter. Good continuation.
answered May 3 at 12:19
Billal BEGUERADJ
1
1
1
I think I can agree on most points. I am not a game designer, so the UX is really flawed (I know). But, the predictable drop can be disabled bygame = Tetris(predictable = False).Falseis also the default. Secondly, maybe it is preference, butwasdare my goto gaming keys :)
â Ludisposed
May 3 at 12:26
1
Yeah, I am sure you are going to focus more on the AI aspect of the game, UX is too subjective and you can not do it alone. For the keys, I understand what you mean, but coming back to the UX world, I live in France, so my keyboard is AZERTY, not QWERTY, so it is not very easy for me to pressw:)
â Billal BEGUERADJ
May 3 at 12:32
1
The different layout of the keys didn't even cross my mind. Very good point, I will do a UX update eventually, but not that high on the priority list. Great answer nonetheless.
â Ludisposed
May 3 at 12:33
add a comment |Â
1
I think I can agree on most points. I am not a game designer, so the UX is really flawed (I know). But, the predictable drop can be disabled bygame = Tetris(predictable = False).Falseis also the default. Secondly, maybe it is preference, butwasdare my goto gaming keys :)
â Ludisposed
May 3 at 12:26
1
Yeah, I am sure you are going to focus more on the AI aspect of the game, UX is too subjective and you can not do it alone. For the keys, I understand what you mean, but coming back to the UX world, I live in France, so my keyboard is AZERTY, not QWERTY, so it is not very easy for me to pressw:)
â Billal BEGUERADJ
May 3 at 12:32
1
The different layout of the keys didn't even cross my mind. Very good point, I will do a UX update eventually, but not that high on the priority list. Great answer nonetheless.
â Ludisposed
May 3 at 12:33
1
1
I think I can agree on most points. I am not a game designer, so the UX is really flawed (I know). But, the predictable drop can be disabled by
game = Tetris(predictable = False). False is also the default. Secondly, maybe it is preference, but wasd are my goto gaming keys :)â Ludisposed
May 3 at 12:26
I think I can agree on most points. I am not a game designer, so the UX is really flawed (I know). But, the predictable drop can be disabled by
game = Tetris(predictable = False). False is also the default. Secondly, maybe it is preference, but wasd are my goto gaming keys :)â Ludisposed
May 3 at 12:26
1
1
Yeah, I am sure you are going to focus more on the AI aspect of the game, UX is too subjective and you can not do it alone. For the keys, I understand what you mean, but coming back to the UX world, I live in France, so my keyboard is AZERTY, not QWERTY, so it is not very easy for me to press
w :)â Billal BEGUERADJ
May 3 at 12:32
Yeah, I am sure you are going to focus more on the AI aspect of the game, UX is too subjective and you can not do it alone. For the keys, I understand what you mean, but coming back to the UX world, I live in France, so my keyboard is AZERTY, not QWERTY, so it is not very easy for me to press
w :)â Billal BEGUERADJ
May 3 at 12:32
1
1
The different layout of the keys didn't even cross my mind. Very good point, I will do a UX update eventually, but not that high on the priority list. Great answer nonetheless.
â Ludisposed
May 3 at 12:33
The different layout of the keys didn't even cross my mind. Very good point, I will do a UX update eventually, but not that high on the priority list. Great answer nonetheless.
â Ludisposed
May 3 at 12:33
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f193495%2ftkinter-1-player-tetris-game%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password