# Tutorial #4

Subscribe to Tech With Tim!

### Increasing the Difficulty

As time goes on we probably would like to make the game more difficult. To do this we will simply increase the speed at which the pieces fall down the screen.

First we need to add the variable level_time = 0 into our main() function above the while loop.

```level_time = 0
# Inside the main function above the while loop
```

Now at the top of our while loop inside the main() function we will add the following.

```while run:
level_time += clock.get_rawtime()

if level_time/1000 > 5:
level_time = 0
if level_time > 0.12:
level_time -= 0.005

...
```

We will implement a very simple scoring system that simply gives the player 10 points each time they clear a row. We will later be saving this score and displaying the best score for the player as well. This means we need to define score = 0 and last_score = max_score() at the top of our main() function. Then we need to make a slight modification to our clear_rows() function so that it returns how many rows were cleared. Then when we are calling clear_rows() from within the main() function we will simply do score += clear_rows() * 10 so that it adds the returned value to our score.

```score = 0
last_score = max_score() # will will create this function later
```

```def clear_rows(gird, locked):
...
return inc # add this to the bottom of the function
```

```def main():
while run:
...
if change_piece:
for pos in shape_pos:
p = (pos[0], pos[1])
locked_positions[p] = current_piece.color
current_piece = next_piece
next_piece = get_shape()
change_piece = False
score += clear_rows(grid, locked_positions) * 10 # NEW
...
```

### Displaying the Score

We are going to be displaying the current and best score from within the draw_window() function. This means we must somehow pass the scores there. To do this we will add two parameters to draw_window() and we will add two arguments to our call statement.

The draw_window() function should now look like this.

```def draw_window(surface, grid, score=0, last_score = 0):
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))

# current score
font = pygame.font.SysFont('comicsans', 30)
label = font.render('Score: ' + str(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)

pygame.draw.rect(surface, (255, 0, 0), (top_left_x, top_left_y, play_width, play_height), 5)

draw_grid(surface, grid)
```

Now when we call draw_window() we must add the two new arguments.

```draw_window(win, grid, score, last_score) # Modify this from within the main() function so that
# it has score and last_score as arguments
```

### draw_text_middle()

This function is going to aid us in displaying text to the middle of the screen and will used for when we create a main menu and end screen for our game.

```def draw_text_middle(surface, text, size, color):
font = pygame.font.SysFont("comicsans", size, bold=True)
label = font.render(text, 1, color)

surface.blit(label, (top_left_x + play_width /2 - (label.get_width()/2), top_left_y + play_height/2 - label.get_height()/2))
```

### Losing the Game

Right now when we loose the game we simply quit the program. However, we want to first tell the user they lost and then prompt them to play again. Inside our main() function inside the while loop we will add some lines into the block of code below.

```def main():
while run:
...
if check_lost(locked_positions):
draw_text_middle(win, "YOU LOST!", 80, (255,255,255))
pygame.display.update()
pygame.time.delay(1500)
run = False
update_score(score) # We will create this function later
```

### Creating a Main Menu

We are now going to create a main menu so that when the game is first launched the user can press a button to start the game. This main menu will also show up when the user looses the game allowing them to play again.

```def main_menu(win):  # *
run = True
while run:
win.fill((0,0,0))
draw_text_middle(win, 'Press Any Key To Play', 60, (255,255,255))
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
main(win)

pygame.display.quit()
```

Now instead of calling main() to start the game we will call main_menu().

```main_menu()
```

### Saving Score

To save the users best score we are going to update a text file each time the game is played. We will do this using two functions update_file() and max_score.

*IMPORTANT* Please create a new text file called scores.txt and type “0” on the first line. CLICK SAVE OR SAVE AS. If you do not do this the program will crash.

```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:
score = lines[0].strip()

return score
```

### Full Code

```import pygame
import random

pygame.font.init()

# GLOBALS VARS
s_width = 800
s_height = 700
play_width = 300  # meaning 300 // 10 = 30 width per block
play_height = 600  # meaning 600 // 20 = 30 height per block
block_size = 30

top_left_x = (s_width - play_width) // 2
top_left_y = s_height - play_height

# SHAPE FORMATS

S = [['.....',
'.....',
'..00.',
'.00..',
'.....'],
['.....',
'..0..',
'..00.',
'...0.',
'.....']]

Z = [['.....',
'.....',
'.00..',
'..00.',
'.....'],
['.....',
'..0..',
'.00..',
'.0...',
'.....']]

I = [['..0..',
'..0..',
'..0..',
'..0..',
'.....'],
['.....',
'0000.',
'.....',
'.....',
'.....']]

O = [['.....',
'.....',
'.00..',
'.00..',
'.....']]

J = [['.....',
'.0...',
'.000.',
'.....',
'.....'],
['.....',
'..00.',
'..0..',
'..0..',
'.....'],
['.....',
'.....',
'.000.',
'...0.',
'.....'],
['.....',
'..0..',
'..0..',
'.00..',
'.....']]

L = [['.....',
'...0.',
'.000.',
'.....',
'.....'],
['.....',
'..0..',
'..0..',
'..00.',
'.....'],
['.....',
'.....',
'.000.',
'.0...',
'.....'],
['.....',
'.00..',
'..0..',
'..0..',
'.....']]

T = [['.....',
'..0..',
'.000.',
'.....',
'.....'],
['.....',
'..0..',
'..00.',
'..0..',
'.....'],
['.....',
'.....',
'.000.',
'..0..',
'.....'],
['.....',
'..0..',
'.00..',
'..0..',
'.....']]

shapes = [S, Z, I, O, J, L, T]
shape_colors = [(0, 255, 0), (255, 0, 0), (0, 255, 255), (255, 255, 0), (255, 165, 0), (0, 0, 255), (128, 0, 128)]
# index 0 - 6 represent shape

class Piece(object):  # *
def __init__(self, x, y, shape):
self.x = x
self.y = y
self.shape = shape
self.color = shape_colors[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))

for i, pos in enumerate(positions):
positions[i] = (pos[0] - 2, pos[1] - 4)

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(5, 0, random.choice(shapes))

def draw_text_middle(surface, text, size, color):
font = pygame.font.SysFont("comicsans", size, bold=True)
label = font.render(text, 1, color)

surface.blit(label, (top_left_x + play_width /2 - (label.get_width()/2), top_left_y + play_height/2 - label.get_height()/2))

def draw_grid(surface, grid):
sx = top_left_x
sy = top_left_y

for i in range(len(grid)):
pygame.draw.line(surface, (128,128,128), (sx, sy + i*block_size), (sx+play_width, sy+ i*block_size))
for j in range(len(grid[i])):
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 + 50
sy = top_left_y + play_height/2 - 100
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':
pygame.draw.rect(surface, shape.color, (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:
score = lines[0].strip()

return score

def draw_window(surface, grid, score=0, last_score = 0):
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))

# current score
font = pygame.font.SysFont('comicsans', 30)
label = font.render('Score: ' + str(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)

pygame.draw.rect(surface, (255, 0, 0), (top_left_x, top_left_y, play_width, play_height), 5)

draw_grid(surface, grid)
#pygame.display.update()

def main(win):  # *
last_score = max_score()
locked_positions = {}
grid = create_grid(locked_positions)

change_piece = False
run = True
current_piece = get_shape()
next_piece = get_shape()
clock = pygame.time.Clock()
fall_time = 0
fall_speed = 0.27
level_time = 0
score = 0

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

for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.display.quit()

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
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
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
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)
pygame.display.update()

if check_lost(locked_positions):
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):  # *
run = True
while run:
win.fill((0,0,0))
draw_text_middle(win, 'Press Any Key To Play', 60, (255,255,255))
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
main(win)

pygame.display.quit()

win = pygame.display.set_mode((s_width, s_height))
pygame.display.set_caption('Tetris')