This commit is contained in:
Thomas Forgione 2019-03-13 17:27:56 +01:00
parent baf286a48e
commit 2dd72ff011
No known key found for this signature in database
GPG Key ID: 203DAEA747F48F41
5 changed files with 120 additions and 87 deletions

View File

@ -5,9 +5,9 @@ with contextlib.redirect_stdout(None):
import pygame
from snake.map import Map
from snake.game import Game
from snake.game import Game, PositionPlayer
from snake.window import Window
from snake.player import Player, Direction, ConstantDecision, KeyboardDecision
from snake.player import Player, Direction, ConstantPlayer, KeyboardPlayer
def main():
pygame.init()
@ -16,8 +16,8 @@ def main():
height = 40
game = Game(width, height, [
Player(0, 0, (255, 0, 0), KeyboardDecision(Direction.RIGHT)),
Player(width - 1, height - 1, (0, 255, 0), ConstantDecision(Direction.LEFT)),
PositionPlayer(1, KeyboardPlayer(Direction.RIGHT), [0, 0]),
PositionPlayer(2, ConstantPlayer(Direction.LEFT), [width - 1, height - 1]),
])
window = Window(game, 20)

View File

@ -3,10 +3,54 @@ This module contains everything related to the game.
"""
from time import sleep
from enum import Enum
import pygame
from snake.map import Map
class Case(Enum):
EMPTY = 0
WALL = -1
PLAYER_ONE_BODY = 1
PLAYER_ONE_HEAD = 2
PLAYER_TWO_BODY = 3
PLAYER_TWO_HEAD = 4
def color(self):
if self == Case.EMPTY:
return (255, 255, 255)
elif self == Case.WALL:
return (0, 0, 0)
elif self == Case.PLAYER_ONE_BODY:
return (128, 0, 0)
elif self == Case.PLAYER_ONE_HEAD:
return (255, 0, 0)
elif self == Case.PLAYER_TWO_BODY:
return (0, 128, 0)
elif self == Case.PLAYER_TWO_HEAD:
return (0, 255, 0)
else:
return None
class PositionPlayer:
def __init__(self, id, player, position):
self.id = id
self.player = player
self.position = position
self.alive = True
def body(self):
if self.id == 1:
return Case.PLAYER_ONE_BODY
elif self.id == 2:
return Case.PLAYER_TWO_BODY
def head(self):
if self.id == 1:
return Case.PLAYER_ONE_HEAD
elif self.id == 2:
return Case.PLAYER_TWO_HEAD
class Game:
"""
This class contains the map of the game, and the players.
@ -14,7 +58,7 @@ class Game:
It allows to update the player depending on their strategies, and run the game.
"""
def __init__(self, width, height, players = []):
def __init__(self, width, height, pps):
"""
Returns a new game from its width, height, and number of players.
@ -22,11 +66,14 @@ class Game:
"""
self.width = width
self.height = height
self.map = Map(width, height)
self.players = players
self.history = [Map(width, height, Case.EMPTY, Case.WALL)]
self.pps = pps
for player in self.players:
self.map[player.position[1],player.position[0]] = player
for pp in self.pps:
self.map()[pp.position[0], pp.position[1]] = pp.head()
def map(self):
return self.history[-1]
def next_frame(self, window = None):
"""
@ -38,39 +85,40 @@ class Game:
that is already occupied or that is outside of the map, the players
dies.
"""
# Clone map
self.history.append(self.map().clone())
# Set previous heads to body
for pp in self.pps:
self.map()[pp.position[0], pp.position[1]] = pp.body()
# Manage the events
if window:
while True:
event = pygame.event.poll()
if event.type == pygame.NOEVENT:
break
for player in self.players:
player.manage_event(event)
for pp in self.pps:
pp.player.manage_event(event)
for player in self.players:
for pp in self.pps:
if not player.alive:
continue
player.position = player.next_position(self.map)
pp.position = pp.player.next_position(pp.position, self)
# Check boundaries
if player.position[0] < 0 or player.position[1] < 0 or \
player.position[0] >= self.map.width or player.position[1] >= self.map.height:
if pp.position[0] < 0 or pp.position[1] < 0 or \
pp.position[0] >= self.width or pp.position[1] >= self.height:
player.alive = False
pp.alive = False
# Check collision
elif self.map.data[player.position[0]][player.position[1]] is not None:
# Player lose
player.alive = False
elif self.map()[pp.position[0], pp.position[1]] is not Case.EMPTY:
pp.alive = False
else:
self.map.data[player.position[0]][player.position[1]] = player
self.map()[pp.position[0], pp.position[1]] = pp.head()
def main_loop(self, window = None):
@ -81,7 +129,7 @@ class Game:
"""
if window:
window.render_map(self.map)
window.render_map(self.map())
while True:
alive_count = 0
@ -90,14 +138,14 @@ class Game:
if window:
sleep(0.1)
for player in self.players:
if player.alive:
player.direction = player.decision.action(self.map) or player.direction
for pp in self.pps:
if pp.alive:
pp.player.direction = pp.player.action(self) or pp.direction
self.next_frame(window)
for (index, player) in enumerate(self.players):
if player.alive:
for (index, pp) in enumerate(self.pps):
if pp.alive:
alive_count += 1
alive = index
@ -110,5 +158,5 @@ class Game:
break
if window:
window.render_map(self.map)
window.render_map(self.map())

View File

@ -2,28 +2,38 @@
This module contains the Map class.
"""
def is_on_border(i, j, w ,h):
return i == 0 or i == w - 1 or j == 0 or j == h - 1
class Map:
"""
The map of the game.
It basically consists of a matrix with a certain width and height. You can
access its items by using the [] operator. This matrix can contain players
or None if the square is empty.
access its items by using the [] operator.
"""
def __init__(self, width, height):
def __init__(self, w, h, empty, wall):
"""
Creates a new map from its width and its height.
The matrix is initialized with Nones
"""
self.width = width
self.height = height
self.data = [[None for i in range(height)] for j in range(width)]
self.width = w
self.height = h
self._data = [[wall if is_on_border(i, j, w + 2, h + 2) else empty for i in range(h + 2)] for j in range(w + 2)]
def clone(self):
"""
Creates a clone of the map.
"""
map = Map(self.width, self.height, 0, 0)
map._data = [[ self._data[j][i] for i in range(self.height + 2)] for j in range(self.width + 2) ]
return map
def __getitem__(self, index):
(i, j) = index
return self.data[j][i]
return self._data[i+1][j+1]
def __setitem__(self, position, other):
(i, j) = position
self.data[j][i] = other
self._data[i+1][j+1] = other

View File

@ -15,7 +15,7 @@ class Direction(Enum):
DOWN = 3
LEFT = 4
class Decision:
class Player:
"""
This is the base class for decisions.
@ -25,7 +25,18 @@ class Decision:
def __init__(self):
pass
def action(self, game_map):
def next_position(self, current_position, game):
direction = self.action(game)
if direction == Direction.UP:
return (current_position[0] - 1, current_position[1])
if direction == Direction.RIGHT:
return (current_position[0], current_position[1] + 1)
if direction == Direction.DOWN:
return (current_position[0] + 1, current_position[1])
if direction == Direction.LEFT:
return (current_position[0], current_position[1] - 1)
def action(self, game):
"""
This function is called each time to ask the player his action.
@ -42,7 +53,7 @@ class Decision:
"""
pass
class KeyboardDecision(Decision):
class KeyboardPlayer(Player):
""""
This is the key board interaction.
@ -52,7 +63,7 @@ class KeyboardDecision(Decision):
"""
Creates a keyboard decision with a default direction.
"""
super(KeyboardDecision, self).__init__()
super(KeyboardPlayer, self).__init__()
self.direction = initial_direction
def manage_event(self, event):
@ -69,55 +80,19 @@ class KeyboardDecision(Decision):
if event.key == pygame.K_s:
self.direction = Direction.DOWN
def action(self, game_map):
def action(self, game):
"""
Returns the direction of the snake.
"""
return self.direction
class ConstantDecision(Decision):
class ConstantPlayer(Player):
"""
This is a class that always returns the same decision.
"""
def __init__(self, direction):
super(ConstantDecision, self).__init__()
super(ConstantPlayer, self).__init__()
self.direction = direction
def action(self, game_map):
def action(self, game):
return self.direction
class Player:
"""
This class represents a snake, with its current position, its starting
position, its direction, and the fact that its alive of not.
"""
def __init__(self, row, col, color, decision):
"""
Creates a new player from its initial position, its color and its
decision algorithm.
"""
self.color = color
self.position = (row, col)
self.start = (row, col)
self.decision = decision
self.alive = True
def next_position(self, map):
"""
Updates the position of the snake depending on its decision algorithm.
"""
direction = self.decision.action(map)
if direction == Direction.UP:
return (self.position[0] - 1, self.position[1])
if direction == Direction.RIGHT:
return (self.position[0], self.position[1] + 1)
if direction == Direction.DOWN:
return (self.position[0] + 1, self.position[1])
if direction == Direction.LEFT:
return (self.position[0], self.position[1] - 1)
def manage_event(self, event):
"""
Updates the decision depending on the event.
"""
self.decision.manage_event(event)

View File

@ -19,7 +19,7 @@ class Window:
(width, height) = (factor * (game.width + 2), factor * (game.height + 2))
self.screen = pygame.display.set_mode((width, height))
self.render_map(game.map)
self.render_map(game.map())
def scale_box(self, row, col, width, height):
"""
@ -44,7 +44,7 @@ class Window:
if block:
pygame.draw.rect(
self.screen,
block.color,
self.scale_box(row+1.1, col+1.1, 0.9, 0.9))
block.color(),
self.scale_box(col+1.1, row+1.1, 0.9, 0.9))
pygame.display.flip()