Last active
October 12, 2021 02:47
-
-
Save icarob-eng/ed778a745e757938688f182131857090 to your computer and use it in GitHub Desktop.
Um pequeno projeto de Tetris em terminal baseado no jogo orginal (ainda em andamento)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import time | |
import os | |
from random import randint | |
from pynput.keyboard import Key, Listener | |
import json | |
# bordas esquerda e direta da tela | |
ESQ = '<!' | |
DIR = '!>' | |
# quadrados vazios ou cheios | |
CLR = ' .' | |
BLK = '[]' | |
# título | |
TITLE = '*' + '-'*8 + 'TETRIS' + '-'*8 + '*' | |
# borda inferior da tela | |
BASE = ESQ + '==' * 10 + DIR + '\n ' + '\\/' * 10 | |
# dicionário com textos laterais ao jogo {linha: conteúdo} | |
SIDE_TEXTS = {0: ' '*5 + 'Pressione `esc` para sair', | |
1: ' '*5 + 'Blocos inseridos: ', | |
2: ' '*5 + 'Linhas completadas: ', | |
3: ' '*5 + 'Tempo de jogo: '} | |
# mapa de estados de cada quadrado | |
MAP_WIDTH = 10 | |
MAP_HEIGHT = 20 | |
state_map = [[False for _ in range(MAP_WIDTH)] for _ in range(MAP_HEIGHT)] | |
# variáveis de estado | |
cont = True # variável de continuar o loop | |
new = False # variável de criar novo bloco | |
blocos = 0 | |
linhas = 0 | |
tempo = 0 | |
# posição do bloco na mão | |
block = [0, 3] | |
H = 4 # altura do bloco | |
W = 4 # largura do bloco | |
# estão definidos com 4 como padrão, pois ainda precisaria do mapa de rotação do bloco | |
# mapas dos blocos: | |
shapes = { | |
0: [[1, 1, 1, 1], | |
[0, 0, 0, 0], | |
[0, 0, 0, 0], | |
[0, 0, 0, 0]], | |
1: [[1, 1, 1, 1], | |
[1, 0, 0, 0], | |
[0, 0, 0, 0], | |
[0, 0, 0, 0]], | |
2: [[1, 1, 0, 0], | |
[1, 1, 0, 0], | |
[0, 0, 0, 0], | |
[0, 0, 0, 0]], | |
3: [[1, 1, 1, 0], | |
[0, 1, 0, 0], | |
[0, 0, 0, 0], | |
[0, 0, 0, 0]], | |
4: [[1, 1, 0, 0], | |
[0, 1, 1, 0], | |
[0, 0, 0, 0], | |
[0, 0, 0, 0]] | |
} | |
# os outros são meras simetrias de 1 ou 4 | |
cshape = [[True for _ in range(W)] for _ in range(H)] # caixa de colisão do bloco | |
def cls(): os.system('clear') # função simples para limpar a tela | |
def new_block(): | |
# apaga as linhas completadas e cria um novo bloco no topo do jogo | |
global cshape, block, new, blocos, linhas, cont | |
for i, line in enumerate(state_map): | |
if i == 0 and True in line: | |
cont = False | |
return False | |
# se tiver um quadrado ocupando a primeira linha quando se cria um novo, o jogo acaba | |
if False not in line: | |
state_map.pop(i) | |
state_map.insert(0, [False for _ in range(MAP_WIDTH)]) | |
# se não tiver um quadrado vazio na linha, apaga a linha e põe outra em branco no topo | |
linhas += 1 | |
block = [0, 3] # reseta a posição | |
tipo = randint(0, 6) | |
if tipo < 5: | |
cshape = shapes[tipo].copy() | |
elif 5 <= tipo < 7: # estes são os blocos invertidos | |
tipo = 1 if tipo == 5 else 4 | |
# troca o tipo pelo equivalente simétrico | |
cshape = shapes[tipo] | |
for i in range(W): | |
cshape[i].reverse() | |
# inverte todas as linhas | |
new = False | |
blocos += 1 | |
return True | |
def blockfiller(filling): | |
# função para redesenhar o bloco na lista | |
x = block[0] | |
y = block[1] | |
for i in range(H): | |
for j in range(W): | |
try: | |
state_map[i+x][j+y] = (cshape[i][j] and filling) or \ | |
(state_map[i+x][j+y] and not cshape[i][j]) | |
except IndexError: | |
pass | |
# se value for falso qualquer valor no mapa de estados vai ser | |
# falso, caso contrário, só os valores da forma escolhida | |
def printer(): | |
# imprime um frame na tela com base na lista | |
global blocos, linhas | |
cls() | |
print(TITLE) | |
for x, line in enumerate(state_map): | |
print(ESQ, end='') | |
for sqr in line: | |
print(BLK if sqr else CLR, end='') | |
print(DIR, end='') | |
if x in SIDE_TEXTS: | |
print(SIDE_TEXTS[x], end='') | |
if x == 1: | |
print(blocos) | |
elif x == 2: | |
print(linhas) | |
elif x == 3: | |
print(tempo) | |
else: | |
print('') | |
print(BASE) | |
def actualiser(func, arg=None): | |
# executa uma função sobre o bloco e atualiza ele | |
blockfiller(False) | |
func() if arg is None else func(arg) | |
blockfiller(True) | |
printer() | |
def on_press(key): | |
# key listener | |
global cont | |
try: | |
if key == Key.esc: | |
cont = False | |
return False | |
elif key == Key.right: | |
actualiser(lateral, 1) | |
elif key == Key.left: | |
actualiser(lateral, -1) | |
elif key == Key.space: | |
actualiser(rotate) | |
elif key == Key.down: | |
actualiser(vertical, 1) | |
else: | |
printer() | |
except Exception: | |
cont = False | |
raise Exception | |
def lateral(value): | |
# move o bloco lateralmente e | |
# checa colisões com os blocos pelo chão ou com as paredes e | |
# e bloqueia se o movimento lateral for causar coisão | |
x = block[0] | |
y = block[1] | |
result = True | |
for i in range(H): | |
for j in range(W): | |
if cshape[i][j]: | |
if j + value < W: | |
neighbor = cshape[i][j + value] | |
else: | |
neighbor = False | |
# checa se o quadrado tem vizinhos no próprio bloco | |
if not neighbor: | |
if not 0 <= y + j + value < MAP_WIDTH: | |
result = False # se não estiver no mapa, não permite o movimento | |
break | |
elif state_map[x + i][y + j + value]: | |
result = False | |
break | |
if result: | |
block[1] += value | |
def vertical(value): | |
# checa colisões com os blocos pelo chão ou com o chão e | |
# retorna se o movimento lateral causaria ou não colisão | |
x = block[0] | |
y = block[1] | |
result = True | |
for i in range(H): | |
for j in range(W): | |
if cshape[i][j]: # só checa o movimento se o quadrado estiver preenchido | |
if i + value < H: | |
neighbor = cshape[i + value][j] | |
else: | |
neighbor = False | |
# checa se o quadrado tem vizinhos no próprio bloco | |
if not neighbor: | |
if not 0 < x + i + value < MAP_HEIGHT: | |
result = False | |
break | |
elif state_map[x + i + value][y + j]: | |
result = False | |
break | |
if result: | |
block[0] += value | |
else: | |
global new | |
new = True | |
def rotate(): | |
# rotaciona o bloco no sentido anti-horário | |
global cshape | |
if cshape == shapes[2]: | |
pass # checa se não é spo um quadrado (que não precisa girar) | |
else: | |
x = block[0] | |
y = block[1] | |
preview = [list(r) for r in zip(*cshape[::-1])] # previzualização de giro | |
permit = True | |
for i in range(H): | |
for j in range(W): | |
if preview[i][j] and (state_map[i+x][j+y] and not cshape[i][j]): | |
permit = False # testa se o giro vai colidir com algo | |
break | |
elif preview[i][j] and (not 0 <= x+i < MAP_HEIGHT or not 0 <= y+j < MAP_WIDTH): | |
permit = False # testa se o giro vai fugir da tela | |
break | |
if permit: | |
cshape = preview.copy() | |
def save(): | |
name = input('~~Fim de jogo!~~\nEntre com seu nome para salvar sua pontuação\n->') | |
dados = {'Nome': name, 'Linhas completadas': linhas, 'Blocos inseridos': blocos, 'Tempo de jogo': tempo} | |
with open('tetris.json', 'w+') as f: | |
try: | |
lista = list(json.load(f)) | |
except json.decoder.JSONDecodeError: | |
lista = [] | |
lista.append(dados) | |
json.dump(lista, f) | |
def main(step): | |
global tempo | |
printer() | |
new_block() | |
listener = Listener(on_press=on_press) | |
listener.start() | |
try: | |
while cont: | |
tempo += 1 | |
actualiser(vertical, 1) | |
time.sleep(step) | |
if new: | |
if not new_block(): | |
listener.stop() | |
except KeyboardInterrupt: | |
listener.stop() | |
finally: | |
cls() | |
save() | |
print('Obrigado por jogar Tetris') | |
if input('... Gostaria de jogar de novo? (s/n)\n->') == 's': | |
main(step) | |
if __name__ == '__main__': | |
main(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment