You know that boring old beginner snake game that people make when they first start out? That's what this is but upgraded and multiplayer. Admittedly it's only on one computer with 'wasd' and 'up down left right' control, but it's a lot of fun. I am concerned with code styling and readability rather than performance (the code is a mess).
mps.py:
import pygame
import random
class game:
def __init__(self, title, size):
pygame.init()
self.size = size
self.screen = pygame.display.set_mode(self.size)
pygame.display.set_caption(title)
pygame.event.set_allowed([pygame.KEYDOWN, pygame.QUIT])
self.clock = pygame.time.Clock()
self.done = 0
self.view_rect = pygame.Rect((0, 0), self.size)
self.camx, self.camy = 0, 0
self.cell = 10
self.cols = self.size[0] // self.cell
self.rows = self.size[1] // self.cell
self.num_foods = 200
self.reset()
self.score1 = 0
self.score2 = 0
self.directions_clockwise = [
(1, 0),
(0, 1),
(-1, 0),
(0, -1),
]
def reset(self):
start_y = self.rows // 2
start_x1 = self.cols // 3
self.snake = [(start_x1 - i, start_y) for i in range(50)]
self.direction = (1, 0)
start_x2 = (self.cols * 2) // 3
self.snake2 = [(start_x2 + i, start_y) for i in range(50)]
self.direction2 = (-1, 0)
self.foods = []
self.spawn_foods()
self.dead1 = False
self.dead2 = False
self.next_direction1 = None
self.next_direction2 = None
self.frame_count1 = 0
self.frame_count2 = 0
self.frames_per_move1 = 12
self.frames_per_move2 = 12
self.started = False
self.game_over = False
self.winner = None
def spawn_food_one(self):
tries = 0
while True:
fx = random.randrange(0, self.cols)
fy = random.randrange(0, self.rows)
pos = (fx, fy)
if pos in self.foods:
tries += 1
if tries > 200:
return
continue
if pos in self.snake or pos in getattr(self, "snake2", []):
tries += 1
if tries > 200:
return
continue
self.foods.append(pos)
return
def spawn_foods(self):
self.foods = []
for _ in range(self.num_foods):
self.spawn_food_one()
def run_game(self):
while not self.done:
self.handle_events()
if not self.started:
keys = pygame.key.get_pressed()
Red_hold = keys[pygame.K_q]
Blue_hold = keys[pygame.K_p]
if Red_hold and Blue_hold:
self.started = True
self.frame_count1 += 1
self.frame_count2 += 1
if self.started and not self.game_over:
ready1 = self.frame_count1 >= self.frames_per_move1
ready2 = self.frame_count2 >= self.frames_per_move2
if ready1:
self.frame_count1 = 0
self.move_one1()
if ready2:
self.frame_count2 = 0
self.move_one2()
if self.dead1 or self.dead2:
self.game_over = True
self.score1 += self.dead2 * 1
self.score2 += self.dead1 * 1
if self.dead1 and not self.dead2:
self.winner = "Blue wins"
elif self.dead2 and not self.dead1:
self.winner = "Red wins"
elif self.dead1 and self.dead2:
self.winner = "Tie"
self.draw()
pygame.display.update(self.view_rect)
self.clock.tick(120)
pygame.quit()
def handle_events(self):
for e in pygame.event.get():
if e.type == pygame.QUIT:
self.done = 1
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_r and self.game_over:
self.reset()
if not self.started or self.game_over:
continue
if e.key == pygame.K_UP:
self.change_dir((0, -1), 2)
elif e.key == pygame.K_DOWN:
self.change_dir((0, 1), 2)
elif e.key == pygame.K_LEFT:
self.change_dir((-1, 0), 2)
elif e.key == pygame.K_RIGHT:
self.change_dir((1, 0), 2)
elif e.key == pygame.K_w:
self.change_dir((0, -1), 1)
elif e.key == pygame.K_s:
self.change_dir((0, 1), 1)
elif e.key == pygame.K_a:
self.change_dir((-1, 0), 1)
elif e.key == pygame.K_d:
self.change_dir((1, 0), 1)
def change_dir(self, new_dir, player):
ndx, ndy = new_dir
if player == 1:
dx, dy = self.direction
if (dx, dy) != (-ndx, -ndy):
self.next_direction1 = new_dir
else:
dx, dy = self.direction2
if (dx, dy) != (-ndx, -ndy):
self.next_direction2 = new_dir
def move_one1(self):
if self.next_direction1 is not None:
self.direction = self.next_direction1
self.next_direction1 = None
self.frames_per_move1 = min(
15, self.frames_per_move1 + abs(self.frames_per_move1 - 20) / 15
)
else:
self.frames_per_move1 = max(
2, self.frames_per_move1 - (self.frames_per_move1**2) / 500
)
new_head1 = None
head = self.snake[0]
dx, dy = self.direction
new_head1 = (head[0] + dx, head[1] + dy)
if new_head1 is not None:
if not (0 <= new_head1[0] < self.cols and 0 <= new_head1[1] < self.rows):
self.dead1 = True
elif new_head1 in self.snake:
self.dead1 = True
elif new_head1 in self.snake2:
self.dead1 = True
if not self.dead1:
self.snake.insert(0, new_head1)
for food_pos in self.foods:
food_poses = [
(food_pos[0] + x, food_pos[1] + y)
for x in range(3)
for y in range(3)
]
if new_head1 in food_poses:
try:
self.foods.remove(food_pos)
except ValueError:
pass
self.spawn_food_one()
break
else:
self.snake.pop()
def move_one2(self):
if self.next_direction2 is not None:
self.direction2 = self.next_direction2
self.next_direction2 = None
self.frames_per_move2 = min(
15, self.frames_per_move2 + abs(self.frames_per_move2 - 20) / 15
)
else:
self.frames_per_move2 = max(
2, self.frames_per_move2 - (self.frames_per_move2**2) / 500
)
new_head2 = None
head2 = self.snake2[0]
dx2, dy2 = self.direction2
new_head2 = (head2[0] + dx2, head2[1] + dy2)
if new_head2 is not None:
if not (0 <= new_head2[0] < self.cols and 0 <= new_head2[1] < self.rows):
self.dead2 = True
elif new_head2 in self.snake2:
self.dead2 = True
elif new_head2 in self.snake:
self.dead2 = True
if not self.dead2:
self.snake2.insert(0, new_head2)
for food_pos in self.foods:
food_poses = [
(food_pos[0] + x, food_pos[1] + y)
for x in range(3)
for y in range(3)
]
if new_head2 in food_poses:
try:
self.foods.remove(food_pos)
except ValueError:
pass
self.spawn_food_one()
break
else:
self.snake2.pop()
def draw(self):
self.screen.fill((255, 255, 255))
for fx, fy in self.foods:
pygame.draw.rect(
self.screen,
(0, 200, 0),
pygame.Rect(
fx * self.cell, fy * self.cell, self.cell * 3, self.cell * 3
),
)
for i, (sx, sy) in enumerate(self.snake):
color = (255, 0, 0) if i != 0 else (200, 0, 0)
pygame.draw.rect(
self.screen,
color,
pygame.Rect(sx * self.cell, sy * self.cell, self.cell, self.cell),
)
for i, (sx, sy) in enumerate(self.snake2):
color = (0, 0, 255) if i != 0 else (0, 0, 200)
pygame.draw.rect(
self.screen,
color,
pygame.Rect(sx * self.cell, sy * self.cell, self.cell, self.cell),
)
font = pygame.font.SysFont(None, 20)
score_surf = font.render(
f"Red (WASD) score: {int(self.score1)}", True, (20, 20, 20)
)
self.screen.blit(score_surf, (5, 5))
score2_surf = font.render(
f"Blue (arrows) score: {int(self.score2)}", True, (20, 20, 20)
)
self.screen.blit(score2_surf, (5, 25))
if not self.started:
instr = "Hold q + p to start"
instr_s = font.render(instr, True, (20, 20, 20))
self.screen.blit(
instr_s,
(self.size[0] // 2 - instr_s.get_width() // 2, instr_s.get_height()),
)
if self.game_over:
msg = self.winner if self.winner else "Tie"
go_surf = font.render(f"{msg} - press R to restart", True, (20, 20, 20))
self.screen.blit(
go_surf,
(self.size[0] // 2 - go_surf.get_width() // 2, go_surf.get_height()),
)
game_play = game("MPS", (1530, 760))
game_play.run_game()
