#!/bin/env python3 import abc import dataclasses import math import random import sys from typing import List import pygame # TODO replace with pygame.Rect @dataclasses.dataclass class Entity: pos: pygame.Vector2 size: int @property def top(self) -> int: return self.pos.y @property def bottom(self) -> int: return self.pos.y + self.size @property def left(self) -> int: return self.pos.x @property def right(self) -> int: return self.pos.x + self.size @abc.abstractmethod def draw(self, screen: pygame.Surface) -> None: pass @dataclasses.dataclass class Food(Entity): color: str = "white" growth: int = 5 size: int = 10 @classmethod def new(cls): return cls( pos=pygame.Vector2(random.randint(0, sw), random.randint(0, sh)), ) def draw(self, screen: pygame.Surface) -> None: p = pygame.Vector2(self.pos.x + self.size / 2, self.pos.y + self.size / 2) pygame.draw.circle(screen, self.color, p, self.size / 2) @dataclasses.dataclass class Man(Entity): name: str = None speed: float = 500 color: str = "yellow" alive: bool = True size: int = 40 enabled: bool = True key_up: int = pygame.K_w key_down: int = pygame.K_s key_left: int = pygame.K_a key_right: int = pygame.K_d def handle_input(self, inputs: pygame.key.ScancodeWrapper, dt: float) -> None: if self.enabled: horz, vert = 0, 0 if keys[self.key_up]: vert = -1 if keys[self.key_down]: vert = 1 if keys[self.key_left]: horz = -1 if keys[self.key_right]: horz = 1 self.walk(horz, vert) def walk(self, horizontal, vertical): horizontal = horizontal / abs(horizontal) if horizontal else 0 vertical = vertical / abs(vertical) if vertical else 0 mov = self.speed * dt if horizontal != 0 and vertical != 0: mov = math.sqrt(2 * mov**2) / 2 self.move(mov * horizontal, mov * vertical) def move(self, dx: int, dy: int) -> None: self.pos.x += dx self.pos.y += dy def draw(self, screen: pygame.Surface) -> None: p = pygame.Vector2(self.pos.x + self.size / 2, self.pos.y + self.size / 2) pygame.draw.circle(screen, self.color, p, self.size / 2) def can_eat(self, other: Entity) -> bool: overlaps_x = self.left <= other.left and self.right >= other.right overlaps_y = self.top <= other.top and self.bottom >= other.bottom return overlaps_x and overlaps_y and self.size >= other.size @dataclasses.dataclass class AI: man: Man = None enemies: List[Man] = None target = None def decide(self): if self.man is None: return target = self.target if random.random() * 100 < 10: target = None if target is None: if targets := [e for e in self.enemies or [] if e.size < self.man.size]: target = random.choice(targets) if target is None: target = food self.target = target horz = (target.pos.x + target.size / 2) - (self.man.pos.x + self.man.size / 2) vert = (target.pos.y + target.size / 2) - (self.man.pos.y + self.man.size / 2) self.man.walk(horz, vert) def hook(self, player): player.enabled = False self.man = player self.target = None self.enemies = [p for p in players if p != player] def release(self): self.man.enabled = True self.man = None self.target = None self.enemies = players # set up pygame pygame.init() pygame.display.set_caption("Maxman") screen = pygame.display.set_mode([1000, 1000]) clock = pygame.time.Clock() running = True paused = True font = pygame.font.Font("freesansbold.ttf", 32) space_was_pressed = False dt = 0 # set up board sw, sh = screen.get_width(), screen.get_height() ai = AI() maxman = Man(pygame.Vector2(sw / 4, sh / 4), name="Maxman") blackman = Man( pygame.Vector2(sw * 0.75, sh * 0.25), name="Blackman", color="black", key_up=pygame.K_i, key_down=pygame.K_k, key_left=pygame.K_j, key_right=pygame.K_l, ) chrisman = Man( pygame.Vector2(sw * 0.25, sh * 0.75), name="Chrisman", color="red", key_up=pygame.K_UP, key_down=pygame.K_DOWN, key_left=pygame.K_LEFT, key_right=pygame.K_RIGHT, ) manman = Man( pygame.Vector2(sw * 0.75, sh * 0.75), name="Manman", color="green", key_up=pygame.K_KP_8, key_down=pygame.K_KP_2, key_left=pygame.K_KP_4, key_right=pygame.K_KP_6, ) players = [maxman, blackman, chrisman, manman] food = Food.new() if any([arg in ["--ai", "-a"] for arg in sys.argv]): ai.hook(blackman) while running: # check recent events for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # draw players in order of size screen.fill("purple") food.draw(screen) for p in sorted(players, key=lambda p: p.size): p.draw(screen) keys = pygame.key.get_pressed() if len(players) == 1: text = font.render( f"{players[0].name} wins", True, "black", ) text_rect = text.get_rect() screen.blit( text, pygame.Vector2((sw - text_rect.width) / 2, (sh - text_rect.height) / 2), ) if keys[pygame.K_SPACE]: running = False else: for p in players: for op in players: if p != op and p.can_eat(op): players.remove(op) if p.can_eat(food): p.size += food.growth food = food.new() # handle inputs if paused: text = font.render("Pause", True, "black") text_rect = text.get_rect() screen.blit( text, pygame.Vector2((sw - text_rect.width) / 2, (sh - text_rect.height) / 2), ) else: ai.decide() for p in players: p.handle_input(keys, dt) if keys[pygame.K_SPACE]: if not space_was_pressed: paused = not paused space_was_pressed = True else: space_was_pressed = False pygame.display.flip() dt = clock.tick(100) / 1000 pygame.quit()