Forráskód Böngészése

Implemented a full game of tetris

Alexey Dorokhov 3 éve
szülő
commit
ce5c93c806
4 módosított fájl, 249 hozzáadás és 340 törlés
  1. 54 193
      competitive_tetris.py
  2. 66 26
      game.py
  3. 0 1
      scores.txt
  4. 129 120
      tetrominos.py

+ 54 - 193
competitive_tetris.py

@@ -6,7 +6,8 @@ Competitive tetris server
 import random
 import pygame
 
-import shapes
+import tetrominos
+import game
 
 pygame.font.init()
 
@@ -21,69 +22,6 @@ TOP_LEFT_X = (S_WIDTH - PLAY_WIDTH) // 2
 TOP_LEFT_Y = S_HEIGHT - PLAY_HEIGHT
 
 
-# SHAPE FORMATS
-
-# index 0 - 6 represent shape
-
-
-class Piece:
-    def __init__(self, x, y, shape):
-        self.x = x
-        self.y = y
-        self.shape = shape
-        self.color = shapes.SHAPE_COLORS[shapes.SHAPES.index(shape)]
-        self.rotation = 0
-
-
-def create_grid(locked_pos={}):
-    grid = [[(0,0,0) for _ in range(10)] for _ in range(20)]
-
-    for i in range(len(grid)):
-        for j in range(len(grid[i])):
-            if (j, i) in locked_pos:
-                c = locked_pos[(j,i)]
-                grid[i][j] = c
-    return grid
-
-
-def convert_shape_format(shape):
-    positions = []
-    format = shape.shape[shape.rotation % len(shape.shape)]
-
-    for i, line in enumerate(format):
-        row = list(line)
-        for j, column in enumerate(row):
-            if column == '0':
-                positions.append((shape.x + j, shape.y + i))
-    return positions
-
-
-def valid_space(shape, grid):
-    accepted_pos = [[(j, i) for j in range(10) if grid[i][j] == (0,0,0)] for i in range(20)]
-    accepted_pos = [j for sub in accepted_pos for j in sub]
-
-    formatted = convert_shape_format(shape)
-
-    for pos in formatted:
-        if pos not in accepted_pos:
-            if pos[1] > -1:
-                return False
-    return True
-
-
-def check_lost(positions):
-    for pos in positions:
-        x, y = pos
-        if y < 1:
-            return True
-
-    return False
-
-
-def get_shape():
-    return Piece(0, 0, random.choice(shapes.SHAPES))
-
-
 def draw_text_middle(surface, text, size, color):
     font = pygame.font.SysFont("comicsans", size, bold=True)
     label = font.render(text, 1, color)
@@ -101,134 +39,78 @@ def draw_grid(surface, grid):
             pygame.draw.line(surface, (128, 128, 128), (sx + j*BLOCK_SIZE, sy),(sx + j*BLOCK_SIZE, sy + PLAY_HEIGHT))
 
 
-def clear_rows(grid, locked):
-
-    inc = 0
-    for i in range(len(grid)-1, -1, -1):
-        row = grid[i]
-        if (0,0,0) not in row:
-            inc += 1
-            ind = i
-            for j in range(len(row)):
-                try:
-                    del locked[(j,i)]
-                except:
-                    continue
-
-    if inc > 0:
-        for key in sorted(list(locked), key=lambda x: x[1])[::-1]:
-            x, y = key
-            if y < ind:
-                newKey = (x, y + inc)
-                locked[newKey] = locked.pop(key)
-
-    return inc
-
-
 def draw_next_shape(shape, surface):
     font = pygame.font.SysFont('comicsans', 30)
     label = font.render('Next Shape', 1, (255,255,255))
 
     sx = TOP_LEFT_X + PLAY_WIDTH + 0
     sy = TOP_LEFT_Y + PLAY_HEIGHT/2 - 100
-    format = shape.shape[shape.rotation % len(shape.shape)]
+    shape_raw = tetrominos.SHAPES_RAW[shape][0]
 
-    for i, line in enumerate(format):
+    for i, line in enumerate(shape_raw):
         row = list(line)
         for j, column in enumerate(row):
             if column == '0':
-                pygame.draw.rect(surface, shape.color, (sx + j*BLOCK_SIZE, sy + i*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE), 0)
-
+                pygame.draw.rect(
+                        surface, (255,0,0),
+                        (sx + j*BLOCK_SIZE, sy + i*BLOCK_SIZE,
+                         BLOCK_SIZE, BLOCK_SIZE),
+                        0)
     surface.blit(label, (sx + 10, sy - 30))
 
 
-def update_score(nscore):
-    score = max_score()
-
-    with open('scores.txt', 'w') as f:
-        if int(score) > nscore:
-            f.write(str(score))
-        else:
-            f.write(str(nscore))
-
-
-def max_score():
-    with open('scores.txt', 'r') as f:
-        lines = f.readlines()
-        score = lines[0].strip()
-
-    return score
-
-
-def draw_window(surface, grid, score=0, last_score = 0):
+def draw_game_state_to_window(surface, game_state):
     surface.fill((0, 0, 0))
 
     pygame.font.init()
     font = pygame.font.SysFont('comicsans', 60)
     label = font.render('Tetris', 1, (255, 255, 255))
-
-    surface.blit(label, (TOP_LEFT_X + PLAY_WIDTH / 2 - (label.get_width() / 2), 30))
+    surface.blit(label, 
+                 (TOP_LEFT_X + PLAY_WIDTH / 2 - (label.get_width() / 2), 30))
 
     # current score
     font = pygame.font.SysFont('comicsans', 30)
-    label = font.render('Score: ' + str(score), 1, (255,255,255))
-
+    label = font.render(f'Score: {game_state.score}', 1, (255,255,255))
     sx = TOP_LEFT_X + PLAY_WIDTH + 50
     sy = TOP_LEFT_Y + PLAY_HEIGHT/2 - 100
-
     surface.blit(label, (sx + 20, sy + 160))
-    # last score
-    label = font.render('High Score: ' + last_score, 1, (255,255,255))
-
-    sx = TOP_LEFT_X - 200
-    sy = TOP_LEFT_Y + 200
 
-    surface.blit(label, (sx + 20, sy + 160))
 
-    for i in range(len(grid)):
-        for j in range(len(grid[i])):
-            pygame.draw.rect(surface, grid[i][j], (TOP_LEFT_X + j*BLOCK_SIZE, TOP_LEFT_Y + i*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE), 0)
+    field = game.get_game_state_field(game_state)
+    for row in range(field.shape[0]):
+        for col in range(field.shape[1]):
+            color = (0,0,0)
+            tile = field[row,col]
+            if tile == 1:
+                color = (0,255,0)
+            elif tile == 2:
+                color = (255,0,0)
 
-    pygame.draw.rect(surface, (255, 0, 0), (TOP_LEFT_X, TOP_LEFT_Y, PLAY_WIDTH, PLAY_HEIGHT), 5)
+            pygame.draw.rect(surface, color,
+                             (TOP_LEFT_X + col*BLOCK_SIZE,
+                              TOP_LEFT_Y + row*BLOCK_SIZE,
+                              BLOCK_SIZE, BLOCK_SIZE), 0)
 
-    draw_grid(surface, grid)
-    #pygame.display.update()
+    pygame.draw.rect(surface, (255, 0, 0),
+                     (TOP_LEFT_X, TOP_LEFT_Y, PLAY_WIDTH, PLAY_HEIGHT), 5)
 
+    draw_grid(surface, field)
 
-def main(win):  # *
-    last_score = max_score()
-    locked_positions = {}
-    grid = create_grid(locked_positions)
 
-    change_piece = False
+def main(win):
     run = True
-    current_piece = get_shape()
-    current_piece.y = -4
-    current_piece.x = 0
-    next_piece = get_shape()
+    frame_time = 0
     clock = pygame.time.Clock()
-    fall_time = 0
-    fall_speed = 0.27
-    level_time = 0
-    score = 0
+
+    def _next_piece_func():
+        return random.choice(range(len(tetrominos.SHAPES)))
+    game_state = game.initial_game_state(_next_piece_func(),
+                                         _next_piece_func())
 
     while run:
-        grid = create_grid(locked_positions)
-        fall_time += clock.get_rawtime()
-        level_time += clock.get_rawtime()
         clock.tick()
-
-        if level_time/1000 > 5:
-            level_time = 0
-            if level_time > 0.12:
-                level_time -= 0.005
-
-        if fall_time/1000 > fall_speed:
-            fall_time = 0
-            current_piece.y += 1
-            if not(valid_space(current_piece, grid)) and current_piece.y > 0:
-                current_piece.y -= 1
-                change_piece = True
+        dt_millis = clock.get_time()
+        frame_time += dt_millis
 
         for event in pygame.event.get():
             if event.type == pygame.QUIT:
@@ -237,53 +119,32 @@ def main(win):  # *
 
             if event.type == pygame.KEYDOWN:
                 if event.key == pygame.K_LEFT:
-                    current_piece.x -= 1
-                    if not(valid_space(current_piece, grid)):
-                        current_piece.x += 1
+                    game_state = game.game_state_shift(game_state, -1)
                 if event.key == pygame.K_RIGHT:
-                    current_piece.x += 1
-                    if not(valid_space(current_piece, grid)):
-                        current_piece.x -= 1
-                if event.key == pygame.K_DOWN:
-                    current_piece.y += 1
-                    if not(valid_space(current_piece, grid)):
-                        current_piece.y -= 1
+                    game_state = game.game_state_shift(game_state, 1)
                 if event.key == pygame.K_UP:
-                    current_piece.rotation += 1
-                    if not(valid_space(current_piece, grid)):
-                        current_piece.rotation -= 1
-
-        shape_pos = convert_shape_format(current_piece)
-
-        for i in range(len(shape_pos)):
-            x, y = shape_pos[i]
-            if y > -1:
-                grid[y][x] = current_piece.color
-
-        if change_piece:
-            for pos in shape_pos:
-                p = (pos[0], pos[1])
-                locked_positions[p] = current_piece.color
-            current_piece = next_piece
-            current_piece.x = 0
-            current_piece.y = -4
-            next_piece = get_shape()
-            change_piece = False
-            score += clear_rows(grid, locked_positions) * 10
-
-        draw_window(win, grid, score, last_score)
-        draw_next_shape(next_piece, win)
+                    game_state = game.game_state_rotate(game_state)
+                if event.key == pygame.K_DOWN:
+                    game_state = game.game_state_land(game_state)
+
+        is_game_running = True
+        if frame_time >= 500:
+            game_state, is_game_running = game.advance_game_state(
+                    game_state, _next_piece_func)
+            frame_time = 0
+
+        draw_game_state_to_window(win, game_state)
+        draw_next_shape(game_state.next_piece, win)
         pygame.display.update()
 
-        if check_lost(locked_positions):
+        if not is_game_running:
             draw_text_middle(win, "YOU LOST!", 80, (255,255,255))
             pygame.display.update()
             pygame.time.delay(1500)
             run = False
-            update_score(score)
 
 
-def main_menu(win):  # *
+def main_menu(win):
     run = True
     while run:
         win.fill((0,0,0))

+ 66 - 26
game.py

@@ -40,7 +40,7 @@ def tetromino_to_field_at(tetromino, row, col):
 
 GameState = collections.namedtuple('GameState',
                                    ['grid', 'current_piece', 'next_piece',
-                                    'rotation', 'row', 'col'])
+                                    'rotation', 'row', 'col', 'score'])
 
 
 def initial_game_state(current_piece, next_piece):
@@ -48,7 +48,7 @@ def initial_game_state(current_piece, next_piece):
                                    dtype=np.int32, order="C"),
                      current_piece=current_piece,
                      next_piece=next_piece,
-                     rotation=0, row=0, col=0)
+                     rotation=0, row=0, col=0, score=0)
 
 
 def is_game_state_valid(game_state):
@@ -64,7 +64,7 @@ def is_game_state_valid(game_state):
     return True
 
 
-def game_state_field(game_state):
+def get_game_state_field(game_state):
     tetromino_array = tetromino_to_field_at(
             tetrominos.SHAPES[game_state.current_piece][game_state.rotation],
             game_state.row, game_state.col)
@@ -73,28 +73,46 @@ def game_state_field(game_state):
     return total_field
 
 
-def advance_game_state_shift(game_state, col_offset):
+def game_state_shift(game_state, col_offset):
     next_game_state = GameState(grid=game_state.grid,
                                 current_piece=game_state.current_piece,
                                 next_piece=game_state.next_piece,
                                 rotation=game_state.rotation,
                                 row=game_state.row,
-                                col=game_state.col + col_offset)
+                                col=game_state.col + col_offset,
+                                score=game_state.score)
     if is_game_state_valid(next_game_state):
         return next_game_state
-    return None
-
-
-def advance_game_state_rotate(game_state):
+    return game_state
+
+
+def game_state_land(game_state):
+    while True:
+        next_game_state = GameState(grid=game_state.grid,
+                                    current_piece=game_state.current_piece,
+                                    next_piece=game_state.next_piece,
+                                    rotation=game_state.rotation,
+                                    row=game_state.row + 1,
+                                    col=game_state.col,
+                                    score=game_state.score)
+        if is_game_state_valid(next_game_state):
+            game_state = next_game_state
+        else:
+            break
+    return game_state
+
+
+def game_state_rotate(game_state):
     next_game_state = GameState(grid=game_state.grid,
                                 current_piece=game_state.current_piece,
                                 next_piece=game_state.next_piece,
                                 rotation=(game_state.rotation + 1) % 4,
                                 row=game_state.row,
-                                col=game_state.col)
+                                col=game_state.col,
+                                score=game_state.score)
     if is_game_state_valid(next_game_state):
         return next_game_state
-    return None
+    return game_state
 
 
 def advance_game_state_down(game_state):
@@ -103,7 +121,8 @@ def advance_game_state_down(game_state):
                                 next_piece=game_state.next_piece,
                                 rotation=game_state.rotation,
                                 row=game_state.row + 1,
-                                col=game_state.col)
+                                col=game_state.col,
+                                score=game_state.score)
     if is_game_state_valid(next_game_state):
         return next_game_state
     return None
@@ -117,23 +136,44 @@ def advance_game_state_piece(game_state, next_piece):
     next_game_state = GameState(grid=next_grid,
                                 current_piece=game_state.next_piece,
                                 next_piece=next_piece,
-                                rotation=0, row=0, col=0)
+                                rotation=0, row=0, col=0,
+                                score=game_state.score)
+    return next_game_state
+
+
+def advance_game_state_purge_lines(game_state):
+    next_grid_row = FIELD_HEIGHT-1
+    next_grid = np.zeros(shape=(FIELD_HEIGHT,FIELD_WIDTH),
+                                dtype=np.int32, order="C")
+    next_score = game_state.score
+    for row in range(FIELD_HEIGHT-1, -1,-1):
+        if np.all(game_state.grid[row,:]):
+            next_score += 1
+        else:
+            next_grid[next_grid_row,:] = game_state.grid[row,:]
+            next_grid_row -= 1
+
+    next_game_state = GameState(grid=next_grid,
+                                current_piece=game_state.current_piece,
+                                next_piece=game_state.next_piece,
+                                rotation=game_state.rotation,
+                                row=game_state.row,
+                                col=game_state.col,
+                                score=next_score)
     return next_game_state
 
 
-def advance_game_state(game_state, command):
-    command_game_state = None
-    if command == COMMAND_LEFT:
-        command_game_state = advance_game_state_shift(game_state, -1)
-    elif command == COMMAND_RIGHT:
-        command_game_state = advance_game_state_shift(game_state, 1)
-    elif command == COMMAND_ROTATE:
-        command_game_state = advance_game_state_rotate(game_state)
 
-    if command_game_state is None:
-        command_game_state = game_state
 
-    next_game_state = advance_game_state_down(command_game_state)
+def advance_game_state(game_state, next_piece_func):
+    next_game_state = advance_game_state_down(game_state)
+    is_game_running = True
     if next_game_state is None:
-        next_game_state = advance_game_state_piece(command_game_state, 0)
-    return next_game_state
+        next_game_state = advance_game_state_piece(game_state, next_piece_func())
+        next_game_state = advance_game_state_purge_lines(next_game_state)
+        if not is_game_state_valid(next_game_state):
+            is_game_running = False
+
+    if not is_game_running:
+        return game_state, False
+    return next_game_state, True

+ 0 - 1
scores.txt

@@ -1 +0,0 @@
-130

+ 129 - 120
tetrominos.py

@@ -89,127 +89,136 @@ def crop(tetromino):
 def convert_to_tuples(tetrominos):
     return list(map(lambda x: crop(convert_to_array(x)), tetrominos))
 
-
-
-I = convert_to_tuples([['..........',
-                        '...0000...',
-                        '..........',
-                        '..........'],
-                       ['.....0....',
-                        '.....0....',
-                        '.....0....',
-                        '.....0....'],
-                       ['..........',
-                        '..........',
-                        '...0000...',
-                        '..........'],
-                       ['....0.....',
-                        '....0.....',
-                        '....0.....',
-                        '....0.....']])
-
-O = convert_to_tuples([['..........',
-                        '....00....',
-                        '....00....',
-                        '..........'],
-                       ['..........',
-                        '....00....',
-                        '....00....',
-                        '..........'],
-                       ['..........',
-                        '....00....',
-                        '....00....',
-                        '..........'],
-                       ['..........',
-                        '....00....',
-                        '....00....',
-                        '..........']])
-
-T = convert_to_tuples([['....0.....',
-                        '...000....',
-                        '..........',
-                        '..........'],
-                       ['....0.....',
-                        '....00....',
-                        '....0.....',
-                        '..........'],
-                       ['..........',
-                        '...000....',
-                        '....0.....',
-                        '..........'],
-                       ['....0.....',
-                        '...00.....',
-                        '....0.....',
-                        '..........']])
-
-S = convert_to_tuples([['....00....',
-                        '...00.....',
-                        '..........',
-                        '..........'],
-                       ['....0.....',
-                        '....00....',
-                        '.....0....',
-                        '..........'],
-                       ['..........',
-                        '....00....',
-                        '...00.....',
-                        '..........'],
-                       ['...0......',
-                        '...00.....',
-                        '....0.....',
-                        '..........']])
-
-Z = convert_to_tuples([['...00.....',
-                        '....00....',
-                        '..........',
-                        '..........'],
-                       ['.....0....',
-                        '....00....',
-                        '....0.....',
-                        '..........'],
-                       ['..........',
-                        '...00.....',
-                        '....00....',
-                        '..........'],
-                       ['....0.....',
-                        '...00.....',
-                        '...0......',
-                        '..........']])
-
-J = convert_to_tuples([['...0......',
-                        '...000....',
-                        '..........',
-                        '..........'],
-                       ['....00....',
-                        '....0.....',
-                        '....0.....',
-                        '..........'],
-                       ['..........',
-                        '...000....',
-                        '.....0....',
-                        '..........'],
-                       ['....0.....',
-                        '....0.....',
-                        '...00.....',
-                        '..........']])
-
-L = convert_to_tuples([['.....0....',
-                        '...000....',
-                        '..........',
-                        '..........'],
-                       ['....0.....',
-                        '....0.....',
-                        '....00....',
-                        '..........'],
-                       ['..........',
-                        '...000....',
-                        '...0......',
-                        '..........'],
-                       ['...00.....',
-                        '....0.....',
-                        '....0.....',
-                        '..........']])
+I_RAW = [['..........',
+          '...0000...',
+          '..........',
+          '..........'],
+         ['.....0....',
+          '.....0....',
+          '.....0....',
+          '.....0....'],
+         ['..........',
+          '..........',
+          '...0000...',
+          '..........'],
+         ['....0.....',
+          '....0.....',
+          '....0.....',
+          '....0.....']]
+
+O_RAW = [['..........',
+          '....00....',
+          '....00....',
+          '..........'],
+         ['..........',
+          '....00....',
+          '....00....',
+          '..........'],
+         ['..........',
+          '....00....',
+          '....00....',
+          '..........'],
+         ['..........',
+          '....00....',
+          '....00....',
+          '..........']]
+
+T_RAW = [['....0.....',
+          '...000....',
+          '..........',
+          '..........'],
+         ['....0.....',
+          '....00....',
+          '....0.....',
+          '..........'],
+         ['..........',
+          '...000....',
+          '....0.....',
+          '..........'],
+         ['....0.....',
+          '...00.....',
+          '....0.....',
+          '..........']]
+
+S_RAW = [['....00....',
+          '...00.....',
+          '..........',
+          '..........'],
+         ['....0.....',
+          '....00....',
+          '.....0....',
+          '..........'],
+         ['..........',
+          '....00....',
+          '...00.....',
+          '..........'],
+         ['...0......',
+          '...00.....',
+          '....0.....',
+          '..........']]
+
+Z_RAW = [['...00.....',
+          '....00....',
+          '..........',
+          '..........'],
+         ['.....0....',
+          '....00....',
+          '....0.....',
+          '..........'],
+         ['..........',
+          '...00.....',
+          '....00....',
+          '..........'],
+         ['....0.....',
+          '...00.....',
+          '...0......',
+          '..........']]
+
+J_RAW = [['...0......',
+         '...000....',
+         '..........',
+         '..........'],
+        ['....00....',
+         '....0.....',
+         '....0.....',
+         '..........'],
+        ['..........',
+         '...000....',
+         '.....0....',
+         '..........'],
+        ['....0.....',
+         '....0.....',
+         '...00.....',
+         '..........']]
+
+L_RAW = [['.....0....',
+          '...000....',
+          '..........',
+          '..........'],
+         ['....0.....',
+          '....0.....',
+          '....00....',
+          '..........'],
+         ['..........',
+          '...000....',
+          '...0......',
+          '..........'],
+         ['...00.....',
+          '....0.....',
+          '....0.....',
+          '..........']]
+
+
+
+I = convert_to_tuples(I_RAW)
+O = convert_to_tuples(O_RAW)
+T = convert_to_tuples(T_RAW)
+S = convert_to_tuples(S_RAW)
+Z = convert_to_tuples(Z_RAW)
+J = convert_to_tuples(J_RAW)
+L = convert_to_tuples(L_RAW)
 
 SHAPES = [S, Z, I, O, J, L, T]
+SHAPES_RAW = [S_RAW, Z_RAW, I_RAW, O_RAW, J_RAW, L_RAW, T_RAW]
 SHAPE_COLORS = [(0, 255, 0), (255, 0, 0), (0, 255, 255), (255, 255, 0),
                 (255, 165, 0), (0, 0, 255), (128, 0, 128)]