Files
maxman/maxman.py
Maximilian Moser dedf852090 Make AI smarter
2024-03-04 23:27:49 +01:00

275 lines
6.8 KiB
Python
Executable File

#!/bin/env python3
import abc
import argparse
import dataclasses
import math
import random
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 (target and target.size > self.man.size) or random.random() * 100 < 10:
target = None
if target is None:
targets = [
e for e in self.enemies or [] if e.alive and e.size < self.man.size
]
if targets:
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()
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()
parser = argparse.ArgumentParser()
parser.add_argument(
"--ai",
type=int,
choices=[0, 1, 2, 3, 4],
default=1,
help="number of AI players",
)
args = parser.parse_args()
ais = []
for i in range(args.ai):
ai = AI()
ai.hook(players[-i - 1])
ais.append(ai)
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):
op.alive = False
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:
for ai in ais:
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()