Skip to content

Instantly share code, notes, and snippets.

@silvasur
Last active September 2, 2024 17:49
Show Gist options
  • 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

@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