Skip to content

Instantly share code, notes, and snippets.

@Diederikjh
Last active April 26, 2023 19:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Diederikjh/72d7cdd626b718a50ceb37040e981bda to your computer and use it in GitHub Desktop.
Save Diederikjh/72d7cdd626b718a50ceb37040e981bda to your computer and use it in GitHub Desktop.
#!/bin/python
import pygame
import random
import unittest
# Define the size of the game window and the size of each block
WINDOW_WIDTH = 200
WINDOW_HEIGHT = 600
BLOCK_SIZE = 20
BACKGROUND_COLOR = (255, 255, 255)
OUTLINE_WIDTH = 1
OUTLINE_COLOR = (0, 0, 0)
FPS = 20
# Define the colors for the shapes
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLACK = (0, 0, 0)
PINK = (255, 128, 171)
MINT = (0, 255, 127)
PERIWINKLE = (0, 191, 255)
PEACH = (235, 235, 20)
ORANGE = (255, 165, 0)
PURPLE = (153, 50, 204)
CREAM = (20, 235, 235)
BLOCK_COLORS = [
PINK,
MINT,
PERIWINKLE,
PEACH,
ORANGE,
PURPLE,
CREAM
]
# Define the shapes as a list of lists of blocks
SHAPES = [
[[1, 1, 1],
[0, 1, 0]],
[[2, 2],
[2, 2]],
[[0, 3, 3],
[3, 3, 0]],
[[4, 4, 0],
[0, 4, 4]],
[[5, 0, 0],
[5, 5, 5]],
[[0, 0, 6],
[6, 6, 6]],
[[7, 7, 7, 7]]
]
GRID_WIDTH = WINDOW_WIDTH // BLOCK_SIZE
GRID_HEIGHT = WINDOW_HEIGHT // BLOCK_SIZE
SCORE_WIDTH = 150
# Create the game window
screen = pygame.display.set_mode((WINDOW_WIDTH + SCORE_WIDTH, WINDOW_HEIGHT))
running = True
# Define the initial piece speed in grid moves per second
piece_speed = 5
fall_delay = 1000 // piece_speed
score = 0
game_over = False
# Initialize the grid to all zeros
grid = []
for i in range(GRID_HEIGHT):
row = [0] * GRID_WIDTH
grid.append(row)
# Define a function to check for collisions between a piece and the grid
def check_collision(piece, grid):
shape = piece["shape"]
x = piece["x"]
y = piece["y"]
for i in range(len(shape)):
for j in range(len(shape[i])):
if shape[i][j] != 0:
pos_x = x + j
pos_y = y + i
if pos_y >= GRID_HEIGHT or grid[pos_y][pos_x] != 0:
return True
return False
# Define a function to add a piece to the grid
def add_piece_to_grid(piece, grid):
shape = piece["shape"]
x = piece["x"]
y = piece["y"]
color = BLOCK_COLORS.index(piece["color"]) + 1
for i in range(len(shape)):
for j in range(len(shape[i])):
if shape[i][j] != 0:
pos_x = x + j
pos_y = y + i
grid[pos_y][pos_x] = color
# Define a function to generate a new piece
def new_piece():
shape = random.choice(SHAPES)
color = random.choice(BLOCK_COLORS)
x = random.randint(0, GRID_WIDTH - len(shape[0]))
y = 0
piece = {
"shape": shape,
"color": color,
"x": x,
"y": y,
}
return piece
# Define a function to draw a piece on the screen
def draw_piece_at(piece, x, y):
shape = piece["shape"]
# x = piece["x"]
# y = piece["y"]
color = piece["color"]
for i in range(len(shape)):
for j in range(len(shape[i])):
if shape[i][j] != 0:
pos_x = (x + j) * BLOCK_SIZE
pos_y = (y + i) * BLOCK_SIZE
rect = pygame.Rect(pos_x, pos_y, BLOCK_SIZE, BLOCK_SIZE)
pygame.draw.rect(screen, color, rect)
pygame.draw.rect(screen, OUTLINE_COLOR, rect, OUTLINE_WIDTH)
def draw_piece(piece):
draw_piece_at(piece, piece["x"], piece["y"])
# Define a function to draw the grid on the screen
def draw_grid(grid):
for i in range(GRID_HEIGHT):
for j in range(GRID_WIDTH):
color = BLOCK_COLORS[grid[i][j] - 1] if grid[i][j] != 0 else BACKGROUND_COLOR
pos_x = j * BLOCK_SIZE
pos_y = i * BLOCK_SIZE
rect = pygame.Rect(pos_x, pos_y, BLOCK_SIZE, BLOCK_SIZE)
pygame.draw.rect(screen, color, rect)
pygame.draw.rect(screen, OUTLINE_COLOR, rect, OUTLINE_WIDTH)
def get_left_edge(piece):
shape = piece["shape"]
left_edge = len(shape[0])
for row in shape:
# Find the index of the first non-zero element in the row
row_edge = next((i for i, x in enumerate(row) if x != 0), len(row))
# Update the left edge if necessary
if row_edge < left_edge:
left_edge = row_edge
return left_edge
def get_right_edge(piece):
shape = piece["shape"]
right_edge = 0
for row in shape:
# Find the index of the last non-zero element in the row
row_edge = next((i for i, x in reversed(list(enumerate(row))) if x != 0), -1)
# Update the right edge if necessary
if row_edge > right_edge:
right_edge = row_edge
return right_edge
def move_shape_right(current_piece):
current_piece["x"] += 1
if get_right_edge(current_piece) + current_piece["x"] >= GRID_WIDTH:
current_piece["x"] -= 1
if check_collision(current_piece, grid):
current_piece["x"] -= 1
def move_shape_left(current_piece):
current_piece["x"] -= 1
if check_collision(current_piece, grid):
current_piece["x"] += 1
if get_left_edge(current_piece) + current_piece["x"] < 0:
current_piece["x"] += 1 # move back to previous position
def rotate_clockwise(shape):
return [list(col) for col in zip(*shape[::-1])]
def check_line_completion(grid):
# TODO maybe make top row zeros always
global score
grid_height = len(grid)
grid_width = len(grid[0])
completed_lines = []
for row in range(grid_height):
if all(grid[row][col] != 0 for col in range(grid_width)):
completed_lines.append(row)
for line in sorted(completed_lines, reverse=False):
for row in range(line, 0, -1):
for col in range(grid_width):
grid[row][col] = grid[row-1][col]
score += len(completed_lines) * 10
def handle_piece_collided():
global running, piece_speed, fall_delay, score, game_over, current_piece, next_piece
add_piece_to_grid(current_piece, grid)
check_line_completion(grid)
current_piece = next_piece
next_piece = new_piece()
if check_collision(current_piece, grid):
game_over = True
# Reset speed if it was made faster with down arrow
if piece_speed != 5:
piece_speed = 5
fall_delay = 1000 // piece_speed
score += 1
def render_score(screen, score):
global smallFont
text = smallFont.render("Score: " + str(score), True, BLACK)
text_rect = text.get_rect()
text_rect.right = WINDOW_WIDTH + (SCORE_WIDTH) - 5
text_rect.top = 10
screen.blit(text, text_rect)
def render_next(screen, next_piece):
global smallFont
text = smallFont.render("Next", True, BLACK)
text_rect = text.get_rect()
text_rect.left = WINDOW_WIDTH + 38
text_rect.top = 65
screen.blit(text, text_rect)
draw_piece_at(next_piece, GRID_WIDTH + 2, 5)
# Define the main function
def main():
global running, piece_speed, fall_delay, score, game_over, current_piece, next_piece, smallFont
# Initialize Pygame
pygame.init()
largeFont = pygame.font.SysFont("arial", 32)
smallFont = pygame.font.SysFont("arial", 22)
# Set the title of the game window
pygame.display.set_caption("TLG")
# Create a clock to control the frame rate
clock = pygame.time.Clock()
current_piece = new_piece()
next_piece = new_piece()
# Define the initial fall delay and counter
fall_counter = 0
paused = False
while running:
moved = False
# Handle events
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
move_shape_left(current_piece)
moved = True
elif event.key == pygame.K_RIGHT and not moved:
move_shape_right(current_piece)
moved = True
elif event.key == pygame.K_DOWN and not moved:
piece_speed = 20
fall_delay = 1000 // piece_speed
elif event.key == pygame.K_UP:
current_piece["shape"] = rotate_clockwise(current_piece["shape"])
right_edge = get_right_edge(current_piece)
# Check if the rotated shape is out of bounds
if right_edge + current_piece["x"] >= GRID_WIDTH:
# If so, and move the shape off the right edge
current_piece["x"] = GRID_WIDTH - (right_edge +1)
if get_left_edge(current_piece) + current_piece["x"] < 0:
# If so, move the shape shape off the left edge
current_piece["x"] = 0
elif event.key == pygame.K_SPACE and not moved:
# Move the current piece down, checking for collisions
while True:
current_piece["y"] += 1
if check_collision(current_piece, grid):
current_piece["y"] -= 1
handle_piece_collided()
break
elif event.key == pygame.K_p or event.key == pygame.K_PAUSE:
paused = not paused
elif event.key == pygame.K_ESCAPE:
running = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and not moved:
move_shape_left(current_piece)
moved = True
if keys[pygame.K_RIGHT] and not moved:
move_shape_right(current_piece)
moved = True
if game_over:
game_over_text = largeFont.render("GAME OVER", True, RED)
screen.blit(game_over_text, (20, WINDOW_HEIGHT // 2))
moved = True
elif paused:
paused_text = largeFont.render("PAUSED", True, GREEN)
screen.blit(paused_text, (20, WINDOW_HEIGHT // 2))
moved = True
else:
# Move the current piece down
fall_counter += clock.tick(FPS)
if fall_counter >= fall_delay:
# Move the current piece down
current_piece["y"] += 1
if check_collision(current_piece, grid):
current_piece["y"] -= 1
handle_piece_collided()
fall_counter = 0
# Clear the screen
screen.fill((255, 255, 255))
# Draw the pieces and the grid
# for piece in current_pieces:
draw_grid(grid)
draw_piece(current_piece)
render_score(screen, score)
render_next(screen, next_piece)
# Update the screen
pygame.display.flip()
# Tick the clock
clock.tick(FPS)
pygame.quit()
class TestCheckLineCompletion(unittest.TestCase):
def setUp(self):
pass
def test_multiple_line_completion(self):
global score
self.grid = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
]
check_line_completion(self.grid)
expected_grid = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
]
self.assertEqual(score, 30)
self.assertEqual(self.grid, expected_grid)
def test_multiple_line_completion_non_contious(self):
global score
score = 0
self.grid = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 1, 1, 1],
[1, 1, 0, 1],
[1, 1, 1, 1],
]
check_line_completion(self.grid)
expected_grid = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 1, 0, 1],
]
self.assertEqual(score, 20)
self.assertEqual(self.grid, expected_grid)
if __name__ == '__main__':
#unittest.main()
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment