Skip to content

Instantly share code, notes, and snippets.

@silvasur
Last active May 4, 2024 08:11
Show Gist options
  • Star 85 You must be signed in to star a gist
  • Fork 74 You must be signed in to fork a gist
  • Save silvasur/565419 to your computer and use it in GitHub Desktop.
Save silvasur/565419 to your computer and use it in GitHub Desktop.
Tetris implementation in Python
#!/usr/bin/env python3
#-*- coding: utf-8 -*-
# Very simple tetris implementation
#
# Control keys:
# Down - Drop stone faster
# Left/Right - Move stone
# Up - Rotate Stone clockwise
# Escape - Quit game
# P - Pause game
# Return - Instant drop
#
# Have fun!
# NOTE: If you're looking for the old python2 version, see
# <https://gist.github.com/silvasur/565419/45a3ded61b993d1dd195a8a8688e7dc196b08de8>
# Copyright (c) 2010 "Laria Carolin Chabowski"<me@laria.me>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from random import randrange as rand
import pygame, sys
# The configuration
cell_size = 18
cols = 10
rows = 22
maxfps = 30
colors = [
(0, 0, 0 ),
(255, 85, 85),
(100, 200, 115),
(120, 108, 245),
(255, 140, 50 ),
(50, 120, 52 ),
(146, 202, 73 ),
(150, 161, 218 ),
(35, 35, 35) # Helper color for background grid
]
# Define the shapes of the single parts
tetris_shapes = [
[[1, 1, 1],
[0, 1, 0]],
[[0, 2, 2],
[2, 2, 0]],
[[3, 3, 0],
[0, 3, 3]],
[[4, 0, 0],
[4, 4, 4]],
[[0, 0, 5],
[5, 5, 5]],
[[6, 6, 6, 6]],
[[7, 7],
[7, 7]]
]
def rotate_clockwise(shape):
return [
[ shape[y][x] for y in range(len(shape)) ]
for x in range(len(shape[0]) - 1, -1, -1)
]
def check_collision(board, shape, offset):
off_x, off_y = offset
for cy, row in enumerate(shape):
for cx, cell in enumerate(row):
try:
if cell and board[ cy + off_y ][ cx + off_x ]:
return True
except IndexError:
return True
return False
def remove_row(board, row):
del board[row]
return [[0 for i in range(cols)]] + board
def join_matrixes(mat1, mat2, mat2_off):
off_x, off_y = mat2_off
for cy, row in enumerate(mat2):
for cx, val in enumerate(row):
mat1[cy+off_y-1 ][cx+off_x] += val
return mat1
def new_board():
board = [
[ 0 for x in range(cols) ]
for y in range(rows)
]
board += [[ 1 for x in range(cols)]]
return board
class TetrisApp(object):
def __init__(self):
pygame.init()
pygame.key.set_repeat(250,25)
self.width = cell_size*(cols+6)
self.height = cell_size*rows
self.rlim = cell_size*cols
self.bground_grid = [[ 8 if x%2==y%2 else 0 for x in range(cols)] for y in range(rows)]
self.default_font = pygame.font.Font(
pygame.font.get_default_font(), 12)
self.screen = pygame.display.set_mode((self.width, self.height))
pygame.event.set_blocked(pygame.MOUSEMOTION) # We do not need
# mouse movement
# events, so we
# block them.
self.next_stone = tetris_shapes[rand(len(tetris_shapes))]
self.init_game()
def new_stone(self):
self.stone = self.next_stone[:]
self.next_stone = tetris_shapes[rand(len(tetris_shapes))]
self.stone_x = int(cols / 2 - len(self.stone[0])/2)
self.stone_y = 0
if check_collision(self.board,
self.stone,
(self.stone_x, self.stone_y)):
self.gameover = True
def init_game(self):
self.board = new_board()
self.new_stone()
self.level = 1
self.score = 0
self.lines = 0
pygame.time.set_timer(pygame.USEREVENT+1, 1000)
def disp_msg(self, msg, topleft):
x,y = topleft
for line in msg.splitlines():
self.screen.blit(
self.default_font.render(
line,
False,
(255,255,255),
(0,0,0)),
(x,y))
y+=14
def center_msg(self, msg):
for i, line in enumerate(msg.splitlines()):
msg_image = self.default_font.render(line, False,
(255,255,255), (0,0,0))
msgim_center_x, msgim_center_y = msg_image.get_size()
msgim_center_x //= 2
msgim_center_y //= 2
self.screen.blit(msg_image, (
self.width // 2-msgim_center_x,
self.height // 2-msgim_center_y+i*22))
def draw_matrix(self, matrix, offset):
off_x, off_y = offset
for y, row in enumerate(matrix):
for x, val in enumerate(row):
if val:
pygame.draw.rect(
self.screen,
colors[val],
pygame.Rect(
(off_x+x) *
cell_size,
(off_y+y) *
cell_size,
cell_size,
cell_size),0)
def add_cl_lines(self, n):
linescores = [0, 40, 100, 300, 1200]
self.lines += n
self.score += linescores[n] * self.level
if self.lines >= self.level*6:
self.level += 1
newdelay = 1000-50*(self.level-1)
newdelay = 100 if newdelay < 100 else newdelay
pygame.time.set_timer(pygame.USEREVENT+1, newdelay)
def move(self, delta_x):
if not self.gameover and not self.paused:
new_x = self.stone_x + delta_x
if new_x < 0:
new_x = 0
if new_x > cols - len(self.stone[0]):
new_x = cols - len(self.stone[0])
if not check_collision(self.board,
self.stone,
(new_x, self.stone_y)):
self.stone_x = new_x
def quit(self):
self.center_msg("Exiting...")
pygame.display.update()
sys.exit()
def drop(self, manual):
if not self.gameover and not self.paused:
self.score += 1 if manual else 0
self.stone_y += 1
if check_collision(self.board,
self.stone,
(self.stone_x, self.stone_y)):
self.board = join_matrixes(
self.board,
self.stone,
(self.stone_x, self.stone_y))
self.new_stone()
cleared_rows = 0
while True:
for i, row in enumerate(self.board[:-1]):
if 0 not in row:
self.board = remove_row(
self.board, i)
cleared_rows += 1
break
else:
break
self.add_cl_lines(cleared_rows)
return True
return False
def insta_drop(self):
if not self.gameover and not self.paused:
while(not self.drop(True)):
pass
def rotate_stone(self):
if not self.gameover and not self.paused:
new_stone = rotate_clockwise(self.stone)
if not check_collision(self.board,
new_stone,
(self.stone_x, self.stone_y)):
self.stone = new_stone
def toggle_pause(self):
self.paused = not self.paused
def start_game(self):
if self.gameover:
self.init_game()
self.gameover = False
def run(self):
key_actions = {
'ESCAPE': self.quit,
'LEFT': lambda:self.move(-1),
'RIGHT': lambda:self.move(+1),
'DOWN': lambda:self.drop(True),
'UP': self.rotate_stone,
'p': self.toggle_pause,
'SPACE': self.start_game,
'RETURN': self.insta_drop
}
self.gameover = False
self.paused = False
dont_burn_my_cpu = pygame.time.Clock()
while 1:
self.screen.fill((0,0,0))
if self.gameover:
self.center_msg("""Game Over!\nYour score: %d
Press space to continue""" % self.score)
else:
if self.paused:
self.center_msg("Paused")
else:
pygame.draw.line(self.screen,
(255,255,255),
(self.rlim+1, 0),
(self.rlim+1, self.height-1))
self.disp_msg("Next:", (
self.rlim+cell_size,
2))
self.disp_msg("Score: %d\n\nLevel: %d\
\nLines: %d" % (self.score, self.level, self.lines),
(self.rlim+cell_size, cell_size*5))
self.draw_matrix(self.bground_grid, (0,0))
self.draw_matrix(self.board, (0,0))
self.draw_matrix(self.stone,
(self.stone_x, self.stone_y))
self.draw_matrix(self.next_stone,
(cols+1,2))
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.USEREVENT+1:
self.drop(False)
elif event.type == pygame.QUIT:
self.quit()
elif event.type == pygame.KEYDOWN:
for key in key_actions:
if event.key == eval("pygame.K_"
+key):
key_actions[key]()
dont_burn_my_cpu.tick(maxfps)
if __name__ == '__main__':
App = TetrisApp()
App.run()
@silvasur
Copy link
Author

@frazor: No, not a typo. In Python 2.x (see the #!/usr/bin/env python2 shebang line at the top) xrange was a thing and did the same thing as range does in Python 3.x. In 2.x range (without the x) returned a list instead of an iterator (list(range(...)) in 3.x terms), making it less performant / memory efficient if you would not store the list (as it's done here, after we iterated over the range, we don't need that range itself any more, so we use xrange and not range).

@silvasur
Copy link
Author

@AireneHA: Really difficult to tell without knowing more of the "next task" you're talking about, sorry :(. If you don't need to return to your tetris game after the next task, you probably could just get the current system time (look into the documentation of pythons time module) and calculate the difference to the time when the tetris game started (store the time into self.started_at or something like that). Then, in the main loop of the game, check if the time difference is greater or equal to 3 minutes and the abort the game. You could then just glue the next task below the final App.run() call. This is a very hackish solution, but perhaps it is good enough ar at least gave you some ideas on how to proceed. good luck!

@silvasur
Copy link
Author

@FlyingDutchman42: Just removing the self.draw_matrix(self.next_stone, (cols+1,2)) in the game drawing logic should do the trick.

@10150042
Copy link

10150042 commented Aug 31, 2018

It's a good code. Thanks 👍
How can I make '.py' to '.exe' file?
Of course I can download through the URL you wrote up there.
Just wonder.

@ozayozay
Copy link

error Traceback (most recent call last)
in ()
155
156 if name == 'main':
--> 157 App = TetrisApp()
158 App.run()

in init(self)
2 def init(self):
3 pygame.init()
----> 4 pygame.key.set_repeat(250,25)
5 self.width = config['cell_size']*config['cols']
6 self.height = config['cell_size']*config['rows']

error: video system not initialized

If you help video gives error I would appreciate

@aridab123
Copy link

aridab123 commented Oct 9, 2018

@silvasur hi how could i use triangles as the shape for the tetris pieces instead of using squares? like in this example
ari schischa

@pvcraven
Copy link

Thanks! Helped me get started with my Arcade implementation:

https://github.com/pvcraven/arcade/blob/master/arcade/examples/tetris.py

@Error68
Copy link

Error68 commented Feb 25, 2019

Hey, Why is my program crash because of pygame?

@bayych
Copy link

bayych commented Mar 18, 2019

@Error68 Not exactly sure, but you might need to change either your loop or check your IDE for any errors. I highly reccomend you using Pycharm when coding. It gives you a good overlook on whats wrong with your code. Especially if its crashing. Thanks.

@saidberk27
Copy link

if you cant execute the code try type "range" insted of "xrange"

@silvasur
Copy link
Author

It's a good code. Thanks +1
How can I make '.py' to '.exe' file?
Of course I can download through the URL you wrote up there.
Just wonder.

To be honest, I'm not completely sure, it's been a while... But IIRC, I used py2exe, probably with a similar configuration I used in this project, see mkexe.py and exefying.bat

@silvasur
Copy link
Author

Hey, Why is my program crash because of pygame?

Do you have pygame for python2 installed? Try starting a python2 interpreter and type "import pygame". If you get an error there, you need to install pygame on your system first.

@saidberk27
Copy link

saidberk27 commented Sep 16, 2019 via email

@hmpython89
Copy link

This is an excellent implementation of Tetris! However, I also got errors for the "xrange" and ended up having to change all of them to just "range". This was for Python 3.5<. Well done otherwise!

@silvasur
Copy link
Author

This is an excellent implementation of Tetris! However, I also got errors for the "xrange" and ended up having to change all of them to just "range". This was for Python 3.5<. Well done otherwise!

Yes, it's for python2, as seen in the hashbang line (#!/usr/bin/env python2) at the top.

I should probably rewrite it for python3 some time, also improve / simplify the code, while I'm at it. There are some things I would do different now (as it should be, I was still in school back then, a lot of time has passed).

@sydneymulenga
Copy link

import pygame, sys
ModuleNotFoundError: No module named 'pygame'

The above is the error message i got.

@sydneymulenga
Copy link

Hey, Why is my program crash because of pygame?

Do you have pygame for python2 installed? Try starting a python2 interpreter and type "import pygame". If you get an error there, you need to install pygame on your system first.

I may have the same requirement shown above heh?

@silvasur
Copy link
Author

Do you have pygame for python2 installed? Try starting a python2 interpreter and type "import pygame". If you get an error there, you need to install pygame on your system first.

I may have the same requirement shown above heh?

Yes, the error message tells you that python can't find the pygame library. So I would try to install it and then try again.

@sydneymulenga
Copy link

sydneymulenga commented Nov 12, 2019 via email

Copy link

ghost commented Nov 28, 2019

Thanks for sharing

@silvasur
Copy link
Author

I finally converted the game to Python 3 🎉

@hmpython89
Copy link

hmpython89 commented Mar 3, 2020 via email

@JoshuaRose-Github
Copy link

don't burn my cpu
.
.
.
.
okay

Funny variable names is noice

@sydneymulenga
Copy link

sydneymulenga commented Sep 21, 2020 via email

@tri419
Copy link

tri419 commented Jun 5, 2021

Hi. Can You give me the code of AI tetris

@Dilovan200
Copy link

can somone tell me why i get tons of error messagees and can`t run the game .? what did i forget to istall on visual studio ?

@silvasur
Copy link
Author

silvasur commented Jan 7, 2022

@Dilovan200 Without seeing the error messages, nobody will be able to help you. Can you copy the error messages here or post a screenshot? Maybe then someone might be able to help you. Also not sure, what this has to do with visual studio?

@Wax30d
Copy link

Wax30d commented Feb 16, 2022

is it possible to make that .py file to desktop app??

@silvasur
Copy link
Author

silvasur commented Feb 19, 2022

@Wax30d I mean, it's an application that runs on a desktop, so it kinda is a desktop app, right? 😃

If you want to bundle the whole application up, so users don't have to install python and pygame, here are some possible options: On Linux you might try to make it a Flatpak app, for Windows there is py2exe. No idea about macOS or other operating systems.

@R3DHULK
Copy link

R3DHULK commented Jul 23, 2022

import pygame
import random

colors = [
(0, 0, 0),
(120, 37, 179),
(100, 179, 179),
(80, 34, 22),
(80, 134, 22),
(180, 34, 22),
(180, 34, 122),
]

class Figure:
x = 0
y = 0

figures = [
    [[1, 5, 9, 13], [4, 5, 6, 7]],
    [[4, 5, 9, 10], [2, 6, 5, 9]],
    [[6, 7, 9, 10], [1, 5, 6, 10]],
    [[1, 2, 5, 9], [0, 4, 5, 6], [1, 5, 9, 8], [4, 5, 6, 10]],
    [[1, 2, 6, 10], [5, 6, 7, 9], [2, 6, 10, 11], [3, 5, 6, 7]],
    [[1, 4, 5, 6], [1, 4, 5, 9], [4, 5, 6, 9], [1, 5, 6, 9]],
    [[1, 2, 5, 6]],
]

def __init__(self, x, y):
    self.x = x
    self.y = y
    self.type = random.randint(0, len(self.figures) - 1)
    self.color = random.randint(1, len(colors) - 1)
    self.rotation = 0

def image(self):
    return self.figures[self.type][self.rotation]

def rotate(self):
    self.rotation = (self.rotation + 1) % len(self.figures[self.type])

class Tetris:
level = 2
score = 0
state = "start"
field = []
height = 0
width = 0
x = 100
y = 60
zoom = 20
figure = None

def __init__(self, height, width):
    self.height = height
    self.width = width
    self.field = []
    self.score = 0
    self.state = "start"
    for i in range(height):
        new_line = []
        for j in range(width):
            new_line.append(0)
        self.field.append(new_line)

def new_figure(self):
    self.figure = Figure(3, 0)

def intersects(self):
    intersection = False
    for i in range(4):
        for j in range(4):
            if i * 4 + j in self.figure.image():
                if i + self.figure.y > self.height - 1 or \
                        j + self.figure.x > self.width - 1 or \
                        j + self.figure.x < 0 or \
                        self.field[i + self.figure.y][j + self.figure.x] > 0:
                    intersection = True
    return intersection

def break_lines(self):
    lines = 0
    for i in range(1, self.height):
        zeros = 0
        for j in range(self.width):
            if self.field[i][j] == 0:
                zeros += 1
        if zeros == 0:
            lines += 1
            for i1 in range(i, 1, -1):
                for j in range(self.width):
                    self.field[i1][j] = self.field[i1 - 1][j]
    self.score += lines ** 2

def go_space(self):
    while not self.intersects():
        self.figure.y += 1
    self.figure.y -= 1
    self.freeze()

def go_down(self):
    self.figure.y += 1
    if self.intersects():
        self.figure.y -= 1
        self.freeze()

def freeze(self):
    for i in range(4):
        for j in range(4):
            if i * 4 + j in self.figure.image():
                self.field[i + self.figure.y][j + self.figure.x] = self.figure.color
    self.break_lines()
    self.new_figure()
    if self.intersects():
        self.state = "gameover"

def go_side(self, dx):
    old_x = self.figure.x
    self.figure.x += dx
    if self.intersects():
        self.figure.x = old_x

def rotate(self):
    old_rotation = self.figure.rotation
    self.figure.rotate()
    if self.intersects():
        self.figure.rotation = old_rotation

Initialize the game engine

pygame.init()

Define some colors

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (128, 128, 128)

size = (400, 500)
screen = pygame.display.set_mode(size)

pygame.display.set_caption("Tetris")

Loop until the user clicks the close button.

done = False
clock = pygame.time.Clock()
fps = 25
game = Tetris(20, 10)
counter = 0

pressing_down = False

while not done:
if game.figure is None:
game.new_figure()
counter += 1
if counter > 100000:
counter = 0

if counter % (fps // game.level // 2) == 0 or pressing_down:
    if game.state == "start":
        game.go_down()

for event in pygame.event.get():
    if event.type == pygame.QUIT:
        done = True
    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_UP:
            game.rotate()
        if event.key == pygame.K_DOWN:
            pressing_down = True
        if event.key == pygame.K_LEFT:
            game.go_side(-1)
        if event.key == pygame.K_RIGHT:
            game.go_side(1)
        if event.key == pygame.K_SPACE:
            game.go_space()
        if event.key == pygame.K_ESCAPE:
            game.__init__(20, 10)

if event.type == pygame.KEYUP:
        if event.key == pygame.K_DOWN:
            pressing_down = False

screen.fill(WHITE)

for i in range(game.height):
    for j in range(game.width):
        pygame.draw.rect(screen, GRAY, [game.x + game.zoom * j, game.y + game.zoom * i, game.zoom, game.zoom], 1)
        if game.field[i][j] > 0:
            pygame.draw.rect(screen, colors[game.field[i][j]],
                             [game.x + game.zoom * j + 1, game.y + game.zoom * i + 1, game.zoom - 2, game.zoom - 1])

if game.figure is not None:
    for i in range(4):
        for j in range(4):
            p = i * 4 + j
            if p in game.figure.image():
                pygame.draw.rect(screen, colors[game.figure.color],
                                 [game.x + game.zoom * (j + game.figure.x) + 1,
                                  game.y + game.zoom * (i + game.figure.y) + 1,
                                  game.zoom - 2, game.zoom - 2])

font = pygame.font.SysFont('Calibri', 25, True, False)
font1 = pygame.font.SysFont('Calibri', 65, True, False)
text = font.render("Score: " + str(game.score), True, BLACK)
text_game_over = font1.render("Game Over", True, (255, 125, 0))
text_game_over1 = font1.render("Press ESC", True, (255, 215, 0))

screen.blit(text, [0, 0])
if game.state == "gameover":
    screen.blit(text_game_over, [20, 200])
    screen.blit(text_game_over1, [25, 265])

pygame.display.flip()
clock.tick(fps)

pygame.quit()

tetris

@R3DHULK
Copy link

R3DHULK commented Jul 23, 2022

Hey if you like my code, you can also visit my repo

@gabrielsbadenas
Copy link

gabrielsbadenas commented Aug 7, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment