maxman: add initial implementation
This commit is contained in:
237
maxman.py
Normal file
237
maxman.py
Normal file
@@ -0,0 +1,237 @@
|
||||
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.75),
|
||||
name="Blackman",
|
||||
color="black",
|
||||
key_up=pygame.K_i,
|
||||
key_down=pygame.K_k,
|
||||
key_left=pygame.K_j,
|
||||
key_right=pygame.K_l,
|
||||
)
|
||||
players = [maxman, blackman]
|
||||
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()
|
Reference in New Issue
Block a user