pytron/tron/game.py

205 lines
6.1 KiB
Python
Raw Normal View History

2018-12-13 11:17:35 +01:00
"""
This module contains everything related to the game.
"""
2018-10-23 16:27:27 +02:00
from time import sleep
2019-03-13 17:27:56 +01:00
from enum import Enum
2018-10-23 16:27:27 +02:00
from tron.map import Map, Tile
2018-10-23 16:27:27 +02:00
2019-03-13 20:45:36 +01:00
class Winner(Enum):
PLAYER_ONE = 1
PLAYER_TWO = 2
2019-03-13 17:27:56 +01:00
class PositionPlayer:
2019-03-13 20:45:36 +01:00
"""
A container to store a player with is id, position and boolean indicating
whether it's still alive or not.
"""
2019-03-13 17:27:56 +01:00
def __init__(self, id, player, position):
self.id = id
self.player = player
self.position = position
self.alive = True
def body(self):
2019-03-13 20:45:36 +01:00
"""
Returns the body type of the PP depending on its id.
"""
2019-03-13 17:27:56 +01:00
if self.id == 1:
2019-03-13 22:26:39 +01:00
return Tile.PLAYER_ONE_BODY
2019-03-13 17:27:56 +01:00
elif self.id == 2:
2019-03-13 22:26:39 +01:00
return Tile.PLAYER_TWO_BODY
2019-03-13 17:27:56 +01:00
def head(self):
2019-03-13 20:45:36 +01:00
"""
Returns the head type of the PP depending of its id.
"""
2019-03-13 17:27:56 +01:00
if self.id == 1:
2019-03-13 22:26:39 +01:00
return Tile.PLAYER_ONE_HEAD
2019-03-13 17:27:56 +01:00
elif self.id == 2:
2019-03-13 22:26:39 +01:00
return Tile.PLAYER_TWO_HEAD
2019-03-13 17:27:56 +01:00
2019-03-22 09:57:45 +01:00
2019-03-13 20:45:36 +01:00
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
2018-10-23 16:27:27 +02:00
class Game:
2018-12-13 11:17:35 +01:00
"""
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.
"""
2019-03-13 17:27:56 +01:00
def __init__(self, width, height, pps):
2018-12-13 11:17:35 +01:00
"""
Returns a new game from its width, height, and number of players.
2018-10-23 16:27:27 +02:00
2019-03-13 18:21:04 +01:00
Width and height are the number of blocs available in the tron map.
2018-12-13 11:17:35 +01:00
"""
2018-10-23 16:27:27 +02:00
self.width = width
self.height = height
2019-03-13 22:26:39 +01:00
mmap = Map(width, height, Tile.EMPTY, Tile.WALL)
2019-03-13 20:45:36 +01:00
self.history = [HistoryElement(mmap, None, None)]
2019-03-13 17:27:56 +01:00
self.pps = pps
2019-03-13 20:45:36 +01:00
self.winner = None
2019-03-13 17:27:56 +01:00
for pp in self.pps:
2019-03-22 09:57:45 +01:00
self.history[-1].map[pp.position[0], pp.position[1]] = pp.head()
2018-10-23 16:27:27 +02:00
2019-03-13 17:27:56 +01:00
def map(self):
2019-03-13 20:45:36 +01:00
"""
2019-03-27 08:19:22 +01:00
Returns a clone of the current map, the last map in the history.
2019-03-13 20:45:36 +01:00
"""
2019-03-22 09:57:45 +01:00
return self.history[-1].map.clone()
2018-10-23 16:27:27 +02:00
def next_frame(self, window = None):
2018-12-13 11:17:35 +01:00
"""
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.
2019-04-04 11:38:31 +02:00
If there is an error during the evaluation of a player strategy, the
player will automatically lose and this function will return False.
2018-12-13 11:17:35 +01:00
"""
2019-03-13 17:27:56 +01:00
2019-03-22 09:57:45 +01:00
map_clone = self.map()
2019-03-13 17:27:56 +01:00
# Set previous heads to body
for pp in self.pps:
2019-03-22 09:57:45 +01:00
map_clone[pp.position[0], pp.position[1]] = pp.body()
2018-10-23 16:27:27 +02:00
2019-03-24 11:29:54 +01:00
# Play next move
for id, pp in enumerate(self.pps):
2019-04-04 11:38:31 +02:00
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
2019-04-04 11:42:35 +02:00
if id == 0:
2019-04-04 11:44:16 +02:00
self.winner = 2
2019-04-04 11:42:35 +02:00
elif id == 1:
2019-04-04 11:44:16 +02:00
self.winner = 1
2019-04-04 11:38:31 +02:00
return False
2019-03-24 11:29:54 +01:00
# 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
2018-10-23 16:27:27 +02:00
# Manage the events
if window:
2019-03-13 18:00:46 +01:00
import pygame
2018-10-23 16:27:27 +02:00
while True:
event = pygame.event.poll()
if event.type == pygame.NOEVENT:
break
2019-03-13 17:27:56 +01:00
for pp in self.pps:
2019-04-04 11:38:31 +02:00
try:
pp.player.manage_event(event)
except:
# An error occured during the evaluation of pp.player strategy
2019-04-04 11:42:35 +02:00
if id == 0:
2019-04-04 11:44:16 +02:00
self.winner = 2
2019-04-04 11:42:35 +02:00
elif id == 1:
2019-04-04 11:44:16 +02:00
self.winner = 1
2019-04-04 11:38:31 +02:00
return False
2018-10-23 16:27:27 +02:00
2019-03-22 14:45:46 +01:00
for (id, pp) in enumerate(self.pps):
2018-10-23 16:27:27 +02:00
# Check boundaries
2019-03-13 17:27:56 +01:00
if pp.position[0] < 0 or pp.position[1] < 0 or \
pp.position[0] >= self.width or pp.position[1] >= self.height:
2018-10-23 16:27:27 +02:00
2019-03-13 17:27:56 +01:00
pp.alive = False
2019-03-22 09:57:45 +01:00
map_clone[pp.position[0], pp.position[1]] = pp.head()
2018-10-23 16:27:27 +02:00
# Check collision
2019-03-22 09:57:45 +01:00
elif map_clone[pp.position[0], pp.position[1]] is not Tile.EMPTY:
2019-03-13 17:27:56 +01:00
pp.alive = False
2019-03-22 09:57:45 +01:00
map_clone[pp.position[0], pp.position[1]] = pp.head()
2019-03-13 17:27:56 +01:00
2018-10-23 16:27:27 +02:00
else:
2019-03-22 09:57:45 +01:00
map_clone[pp.position[0], pp.position[1]] = pp.head()
2018-10-23 16:27:27 +02:00
2019-03-13 20:45:36 +01:00
# Append to history
2019-03-22 09:57:45 +01:00
self.history.append(HistoryElement(map_clone, None, None))
2019-03-13 20:45:36 +01:00
2019-04-04 11:38:31 +02:00
return True
2018-10-23 16:27:27 +02:00
def main_loop(self, window = None):
2018-12-13 11:17:35 +01:00
"""
Loops until the game is finished
If a window is passed as parameter, the game is rendered on the window.
"""
2018-10-23 16:27:27 +02:00
if window:
2019-03-13 17:27:56 +01:00
window.render_map(self.map())
2018-10-23 16:27:27 +02:00
while True:
alive_count = 0
alive = None
if window:
sleep(0.1)
2019-04-04 11:38:31 +02:00
if not self.next_frame(window):
break
2018-10-23 16:27:27 +02:00
for pp in self.pps:
2019-03-13 17:27:56 +01:00
if pp.alive:
2018-10-23 16:27:27 +02:00
alive_count += 1
alive = pp.id
2018-10-23 16:27:27 +02:00
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
2018-10-23 16:27:27 +02:00
break
if window:
2019-03-13 17:27:56 +01:00
window.render_map(self.map())
2018-10-23 16:27:27 +02:00