pytron/tron/game.py

205 lines
6.1 KiB
Python

"""
This module contains everything related to the game.
"""
from time import sleep
from enum import Enum
from tron.map import Map, Tile
class Winner(Enum):
PLAYER_ONE = 1
PLAYER_TWO = 2
class PositionPlayer:
"""
A container to store a player with is id, position and boolean indicating
whether it's still alive or not.
"""
def __init__(self, id, player, position):
self.id = id
self.player = player
self.position = position
self.alive = True
def body(self):
"""
Returns the body type of the PP depending on its id.
"""
if self.id == 1:
return Tile.PLAYER_ONE_BODY
elif self.id == 2:
return Tile.PLAYER_TWO_BODY
def head(self):
"""
Returns the head type of the PP depending of its id.
"""
if self.id == 1:
return Tile.PLAYER_ONE_HEAD
elif self.id == 2:
return Tile.PLAYER_TWO_HEAD
class HistoryElement:
"""
An element from an history.
It contains the map, but also the direction of each player during the frame.
The directions of the players will be None at the first frame.
"""
def __init__(self, mmap, player_one_direction, player_two_direction):
self.map = mmap
self.player_one_direction = player_one_direction
self.player_two_direction = player_two_direction
class Game:
"""
This class contains the map of the game, and the players.
It allows to update the player depending on their strategies, and run the game.
"""
def __init__(self, width, height, pps):
"""
Returns a new game from its width, height, and number of players.
Width and height are the number of blocs available in the tron map.
"""
self.width = width
self.height = height
mmap = Map(width, height, Tile.EMPTY, Tile.WALL)
self.history = [HistoryElement(mmap, None, None)]
self.pps = pps
self.winner = None
for pp in self.pps:
self.history[-1].map[pp.position[0], pp.position[1]] = pp.head()
def map(self):
"""
Returns a clone of the current map, the last map in the history.
"""
return self.history[-1].map.clone()
def next_frame(self, window = None):
"""
Computes the next frame of the game.
If a window is passed as parameter, events are polled and passed to the
players, so you can play the game.
Then, all the players are updated and if a player lands on a square
that is already occupied or that is outside of the map, the players
dies.
If there is an error during the evaluation of a player strategy, the
player will automatically lose and this function will return False.
"""
map_clone = self.map()
# Set previous heads to body
for pp in self.pps:
map_clone[pp.position[0], pp.position[1]] = pp.body()
# Play next move
for id, pp in enumerate(self.pps):
try:
(pp.position, pp.player.direction) = pp.player.next_position_and_direction(pp.position, id + 1, self.map())
except:
# An error occured during the evaluation of pp.player strategy
if id == 0:
self.winner = 2
elif id == 1:
self.winner = 1
return False
# Update history with newly played move
self.history[-1].player_one_direction = self.pps[0].player.direction
self.history[-1].player_two_direction = self.pps[1].player.direction
# Manage the events
if window:
import pygame
while True:
event = pygame.event.poll()
if event.type == pygame.NOEVENT:
break
for pp in self.pps:
try:
pp.player.manage_event(event)
except:
# An error occured during the evaluation of pp.player strategy
if id == 0:
self.winner = 2
elif id == 1:
self.winner = 1
return False
for (id, pp) in enumerate(self.pps):
# Check boundaries
if pp.position[0] < 0 or pp.position[1] < 0 or \
pp.position[0] >= self.width or pp.position[1] >= self.height:
pp.alive = False
map_clone[pp.position[0], pp.position[1]] = pp.head()
# Check collision
elif map_clone[pp.position[0], pp.position[1]] is not Tile.EMPTY:
pp.alive = False
map_clone[pp.position[0], pp.position[1]] = pp.head()
else:
map_clone[pp.position[0], pp.position[1]] = pp.head()
# Append to history
self.history.append(HistoryElement(map_clone, None, None))
return True
def main_loop(self, window = None):
"""
Loops until the game is finished
If a window is passed as parameter, the game is rendered on the window.
"""
if window:
window.render_map(self.map())
while True:
alive_count = 0
alive = None
if window:
sleep(0.1)
if not self.next_frame(window):
break
for pp in self.pps:
if pp.alive:
alive_count += 1
alive = pp.id
if alive_count <= 1:
if alive_count == 1:
# If player one and two share the same position, it will
# mean that they reached the same tile at the same
# moment, so it's really a draw.
if self.pps[0].position[0] != self.pps[1].position[0] or \
self.pps[0].position[1] != self.pps[1].position[1]:
# Otherwise, the winner is the player that is still alive.
self.winner = alive
break
if window:
window.render_map(self.map())