Tkinter 1 Player Tetris game

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
8
down vote

favorite
1












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()






share|improve this question

























    up vote
    8
    down vote

    favorite
    1












    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()






    share|improve this question





















      up vote
      8
      down vote

      favorite
      1









      up vote
      8
      down vote

      favorite
      1






      1





      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()






      share|improve this question











      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()








      share|improve this question










      share|improve this question




      share|improve this question









      asked May 2 at 19:35









      Ludisposed

      5,68621656




      5,68621656




















          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 &downarrow;, &rightarrow;, and &leftarrow; 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.






          share|improve this answer

















          • 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






          • 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






          • 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










          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          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






























          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 &downarrow;, &rightarrow;, and &leftarrow; 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.






          share|improve this answer

















          • 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






          • 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






          • 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














          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 &downarrow;, &rightarrow;, and &leftarrow; 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.






          share|improve this answer

















          • 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






          • 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






          • 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












          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 &downarrow;, &rightarrow;, and &leftarrow; 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.






          share|improve this answer













          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 &downarrow;, &rightarrow;, and &leftarrow; 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.







          share|improve this answer













          share|improve this answer



          share|improve this answer











          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 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




            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




            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




            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




            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




            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












           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          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













































































          Popular posts from this blog

          Python Lists

          Aion

          JavaScript Array Iteration Methods