#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" This module implement two classes for Bomberman players, and one exception for their deaths.
PlayerServer:
-------------
The class PlayerServer is a high-level representation of a Bomberman player, for the server.
Player:
=======
The class Player is the same thing, but without additionnal *plumbing*, for the client.
All the plumbing about connection to the server is *hidden*, using the socket module.
The following method are availables to use network :
* connect -- to initialize the connection,
* send -- to send a message,
* close -- to close the connection.
Example:
--------
And those methods *have to be used*, I don't want to see anything *durty* like :
>>> f = player0.socket_player.mfile()
>>> f.write("I can send messages to my server !")
>>> f.close()
>>> # Don't do this !
The good way to do is :
>>> player0.send("I can send messages to my server !")
>>> # Do this !
.. warning::
The player's pseudos are checked to be small enough, so they **have to be smaller than 32 caracters** (otherwise they will be cut).
"""
__author__='Lilian BESSON (mailto:lilian.besson[AT]normale.fr)' # Automatically update with update__date__.sh, a Naereen Corp. (c) bash script.
__version__='1.4c'
__date__='ven. 15/02/2013 at 22h:24m:29s ' #: The date of the file, automatically update with update__date__.sh, a Naereen Corp. (c) bash script.
#1###############
# Usual Modules #
import socket #, sys, atexit, thread
#2#################
# Project Modules #
import ANSIColors # ANSIColors.py : just some colors definition.
import Bomb # Bomb.py : implement the simple bomb system. (actions, representation etc)
##############################################################################
## Lists of constants for building a player.
from Constants import *
import ParseMessageOut
[docs]class PlayerDeath(Exception):
""" Exception Class for handling the death of a Player.
Attributes:
* player -- the player which is dying.
* ingury -- the ingury receive by the player.
(ex: player had 2 pv, and receive 3 damages : ingury=3).
* msg -- the message annoucing the death, which have to be printed to the player.
Example:
You can annouce the death of a player *player0* with this :
>>> raise PlayerDeath(player0, ingury=3, msg='You died :(')
>>> # In this example, player0 had 2 pv, and receive 3 damages : so he died.
"""
[docs] def __init__(self, player, ingury=1, msg=death_message_Init):
""" Construction of a PlayerDeath exception.
"""
self.player = player #: the player which is dying.
self.ingury = ingury #: the ingury receive by the player.
self.msg = msg #: the message annoucing the death, which have to be printed to the player.
[docs] def __str__(self):
"""__str__(self) -> str
A pretty string for the exception. (with colors if self.player supports color)."""
return "The player %s received %i ingur%s, so he is dying... The server sent the following message %s." % (self.player, self.ingury, "y" if self.ingury<2 else "ies", self.msg)
[docs]class PlayerServer(object):
""" A Class to define a Bomberman player, **for the server**.
Attributes:
(About the player)
* pseudo -- his pseudo.
will be used for printing the player in the board in textual mode.
It has to begin with a ASCII caracter, the other are free.
* color -- his favorite color.
can be one of the following : black, red, green, yellow, blue, magenta, cyan, white.
(About his connection : here the player is **not yet** connected to the server)
* id -- an integer representing the player uniquely both in the client and the server.
(for now, it's just PORT2 if there is a connection, or -1 if not).
PORT2 is the port of the socket of the client, as it is saw **FROM THE SERVER**.
But, no worry, everything is hidden !
(About the party)
* pv -- number of point of life (PV).
* nb_bomb -- number of bomb dropped in the field (by default, this is limited to 1).
* x -- his position in x axis,
(integer between 0 and Nx-1, dimension of the board, typically Nx=8 or 12).
* y -- his position in y axis,
(integer between 0 and Ny-1, dimension of the board, typically Ny=8 or 12).
* templatebomb -- the default bomb's caracteristic. This is a Bomb.BombNoOwner instance.
* direction -- one of 'right', 'left', 'front', 'back'. Represent the last direction's move for the player.
"""
id_global = -1
[docs] def __init__(self, pseudo=pseudo_Init, color=color_Init, pv=pv_Init,\
nb_bomb=nb_bomb_Init, x=x_Init, y=y_Init, templatebomb = Bomb.BombNoOwner()):
""" Simple constructor of a player, not yet connected to anything.
About:
* [id] is PORT2, the port of the socket assuring the connection between the player and his server,
as soon as available; and is an increasing integer (from 0) otherwise (increase by one for each new Player).
* *templatebomb* is the default bomb which will be dropped by the player.
Example:
>>> # On the server, for example hosted on the computer naereen-corp.crans.org,
>>> # while listening to the port 9312, detect a new client, which claimed to be Naereen,
>>> # and said to like green. So the server is creating a local version of the player, to the local version of the game.
>>> player0=PlayerServer("Naereen", "green")
"""
# Increase this class attribute
PlayerServer.id_global = PlayerServer.id_global + 1
# All Player.PlayerServer instance attributes
self.pseudo = pseudo #: his pseudo.
self.color = color #: his favorite color.
self.pv = pv #: number of point of life (PV).
self.nb_bomb = nb_bomb #: number of bomb dropped in the field (by default, this is limited to 1).
self.x = x #: his position in x axis,
self.y = y #: his position in y axis,
self.templatebomb = templatebomb #: the default bomb which will be dropped by the player.
self.id = PlayerServer.id_global #: an integer representing the player uniquely both in the client and the server.
self.direction = 'front' # Default.
[docs] def __str__(self):
""" A simple conversion from a Player to a string.
The string returned can be used to print informations about the player.
"""
return ("%s%s%s%s" % (ANSIColors.tocolor(self.color), self.pseudo, ANSIColors.reset, ANSIColors.white))
[docs] def __repr__(self):
""" Toplevel representation of [self]."""
return ("pseudo=%s; id=%i; color=%s; pv=%i; nb_bomb=%i; x=%i; y=%i; direction=%s; default bomb=(%s)" % (self.pseudo, self.id, self.color, self.pv, self.nb_bomb, self.x, self.y, self.direction, repr(self.templatebomb)))
[docs] def drop(self):
""" Give the bomb dropped by a player, as a Bomb.Bomb instance.
Increase the current number of bomb owns by the player.
The returned Bomb.Bomb have a symlink to the Player in attribute *owner*.
Example:
>>> board[player1.x, player1.y].bomb = player1.drop()
>>> # Ok
>>> player1.move( i = player1.x + 1)
>>> # Move the player to the right by one (warning without checking !)
>>> board[player1.x, player1.y].bomb = player1.drop()
>>> # **NOT OK** the player cannot drop an other bomb (the max number is currently 1).
"""
self.nb_bomb += 1
ANSIColors.printc("\t/B/ The player %s <green>dropped a bomb<white>. It's his %i nst." % (self, self.nb_bomb))
return Bomb.add_owner(self.templatebomb, self)
[docs] def move(self, i=None, j=None):
""" A simple way to move the player from his spot to (i,j).
.. warning::
The move **have to be valid**, **IT IS NOT RE CHECK HERE**.
Therefore, i,j are checked to be **valid positive integers** here.
Example:
Move the player to the right by one (without checking !) :
>>> player1.move(player1.x + 1, player1.y)
Example 2:
You can also specify only the direction to change :
>>> player1.move(i = player1.x + 1)
"""
if i==None: i=self.x
if j==None: j=self.y
if type(i)!=type(0) or type(j)!=type(0): return SIGNAL_TYPE_WRONG
if i<0: return SIGNAL_I_NEGATIVE
if j<0: return SIGNAL_J_NEGATIVE
else:
if i < self.x: self.direction = 'back'
if i > self.x: self.direction = 'front'
if j > self.y: self.direction = 'right'
if j < self.y: self.direction = 'left'
self.x = i
self.y = j
return 0
[docs] def hurt(self, ingury=1):
""" A shortcut to reduce the [pv] attribute of a Player by a number [ingury] of ingury.
This method trigger here the eventual death of the player.
Print an *ANSIColored* message to signal the death of the player in case.
.. warning::
Might raise a PlayerDeath exception if the player dies after suffering [ingury].
Example :
>>> i,j = player0.x, player0.y
>>> if board[i, j].bomb:
>>> player0.hurt(ingury = board[i, j].bomb.force)
>>> else:
>>> print 'No ingury ! Ouf...'
"""
self.pv -= ingury
ANSIColors.printc("\t<red>/h/ The player [%s<red>] have suffered %i damages.<white>" % (self, ingury))
if self.pv <= 0:
# Here, the player is dead.
raise PlayerDeath(self, ingury, ("{%s} is dead because he suffered %i damages." % (str(self), ingury)))
# On the server, this exception have to be handle nicely
# (e.g. by simply informing all other (alive) clients that [self] is dead.)
return ingury != 0
[docs]class Player(PlayerServer):
""" A Class to define a Bomberman player, **for the client**.
Attributes:
(About the player)
* pseudo -- his pseudo.
will be used for printing the player in the board in textual mode.
It **have to begin with a ASCII caracter**, the other are free.
* color -- his favorite color.
can be one of the following : black, red, green, yellow, blue, magenta, cyan, white.
(About his connection : here the player **is** connected to the server)
(Currently, pickling is in test, and __setstate__ and __getstate__ are used to save and restore the connection.)
* info_server -- the information about a server on which he is connected to (a tuple (HOTS, PORT)). (new from Player.PlayerServer)
* socket_player -- the socket identifying his connection, (HOST, PORT2), with PORT2 != PORT. (new from Player.PlayerServer)
* info_connection -- a simple string containing (HOST, PORT2).
* id -- an integer representing the player uniquely both in the client and the server.
(About the party)
* pv -- number of point of life (PV).
* nb_bomb -- number of bomb dropped in the field (by default, this is limited to 1).
* x -- his position in x axis,
(integer between 0 and Nx-1, dimension of the board, typically Nx=8 or 12).
* y -- his position in y axis,
(integer between 0 and Ny-1, dimension of the board, typically Ny=8 or 12).
* templatebomb -- the default bomb's caracteristic. This is a Bomb.BombNoOwner instance.
Examples:
On each intersting methods here defined, an example is shown,
from creating a player, stored in 'player1', to several examples of methods.
"""
#: A strictly increasing integer.
#: Each new instance of *Player* have this number in his attribute *id*.
id_global = -1
[docs] def __init__(self, info_server=info_server_Init, pseudo=pseudo_Init, color=color_Init, pv=pv_Init,\
nb_bomb=nb_bomb_Init, x=x_Init, y=y_Init, templatebomb = Bomb.BombNoOwner()):
""" Simple constructor of a player.
He/She will be named [pseudo], and colorised [color], and connected on the server [info_server].
The connection is established with a socket, given by [socket_player] attribute.
** This step could failed : so creating a connected player could failed. For now, it can't.
About:
* [info_server] is a tuple (HOST, PORT), giving the information about the server.
While there is no connection, it is ('None', id).
* [id] is PORT2 if there is a connection,
otherwise it is a unique integer (strictly increasing for each new Player.PlayerServer).
* *templatebomb* is the default bomb which will be dropped by the player.
Example:
>>> # For example on the computer client.crans.org, user 'Naereen', who likes green,
>>> # want to play on Bomberman with the server hosted at naereen-corp.crans.org, on the port 9312.
>>> player1=Player(('naereen-corp.crans.org', 9312), color="green", pseudo="Naereen")
"""
# Increase this class attribute
Player.id_global = Player.id_global + 1
# All Player.PlayerServer instance attributes
self.pseudo = pseudo #: his pseudo.
self.color = color #: his favorite color.
self.pv = pv #: number of point of life (PV).
self.nb_bomb = nb_bomb #: number of bomb dropped in the field (by default, this is limited to 1).
self.x = x #: his position in x axis,
self.y = y #: his position in y axis,
self.templatebomb = templatebomb #: the default bomb which will be dropped by the player.
self.direction = 'front' # Default.
#: About the id :
#: an integer representing the player **uniquely both** in the client and the server.
#: A good client shall receive it from the server (when phasis #2 begin).
self.id = Player.id_global
self.info_connection = ('<not yet connected>', self.id) #: a simple string containing (HOST, PORT2).
#: the information about a server on which he is connected to (a tuple (HOTS, PORT)). (new from Player.PlayerServer)
self.info_server = info_server
self.socket_player = None #: The connection is done in *connect*.
self.connect(info_server)
[docs] def __repr__(self):
""" __repr__(self) <=> repr(self)
Representation of [self] as a string."""
return ("pseudo=%s; id=%i; color=%s; pv=%i; nb_bomb=%i; x=%i; y=%i; server=(%s); info_connection=(%s); default bomb=(%s)" % (self.pseudo, self.id, self.color, self.pv, self.nb_bomb, self.x, self.y, str_of_InfoServer(self.info_server), str_of_InfoServer(self.info_connection), repr(self.templatebomb)))
[docs] def hurt(self, ingury=1):
""" A shortcut to reduce the [pv] attribute of a Player by a number [ingury] of ingury.
Handle here the death of the player.
Print an *ANSIColored* message to signal the death of the player in case.
Might raise a PlayerDeath exception if the player **dies** after suffering [ingury].
Example:
>>> i, j = player0.x, player0.y
>>> if board[i, j].bomb:
>>> player0.hurt(ingury = board[i, j].bomb.force)
>>> else:
>>> print "No ingury ! Ouf..."
"""
self.pv -= ingury
ANSIColors.printc("\t<red>/h/ <u>YOU<U> [<white>%s<red>] have suffered %i damages.<white>" % (self, ingury))
if self.pv <= 0:
# Here, the player is dead.
raise PlayerDeath(self, ingury, ("<u>YOU<U> [<white>%s<red>] are dead because you suffered %i damages.\nGAME OVER !" % (str(self), ingury)))
return ingury != 0
[docs] def connect(self, info_server=info_server_Init):
""" Try to connect to the server.
Exactly the same way it's done when creating the player.
except it raise exception properly here.
Example:
>>> # From the client, trying to connect to a new server.
>>> player1.connect('bomberman-server.crans.org', 13882)
"""
if self.info_connection[0] != '<not yet connected>':
ANSIColors.printc("\t/n/ Old connection was %s. <blue>Trying a new one to the server %s<white> ..." % (self.info_connection, info_server))
try:
self.socket_player = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
self.socket_player.connect(info_server)
self.info_connection = self.socket_player.getsockname()
#: self.id = self.info_connection[1]
# Try to send the player's pseudo
self.send(ParseMessageOut.str_of_newplayer(self), True)
ANSIColors.printc("\t/!/ <green>Connection succeed to server %s<white> for player pseudo=%s; color=%s; pv=%i; <green>now seen as (%s), with id=%i.<white>" % (self.info_server, self.pseudo, self.color, self.pv, self.info_connection, self.id))
except socket.error as e:
ANSIColors.printc("\t/!/ <red><u>Connection refused (%s)<U> for the server %s<white> for player pseudo=%s; color=%s; pv=%i !" % (str(e), self.info_server, self.pseudo, self.color, self.pv))
raise e #: have to do this for a Client player : the game
#: cannot start (and don't have to) if the server is unavailable.
[docs] def close(self):
""" Try to close nicely the connection with the server.
"""
self.socket_player.shutdown(socket.SHUT_RDWR)
return self.socket_player.close()
[docs] def send(self, msg="", v=True):
""" A try to send a message [msg] to the server.
About:
* [msg] have not to terminate by a new line caracter (backslash + n), it is added automatically.
* [msg] is not send if it's non empty.
* [v] if true, print debug informations.
Example:
>>> # send a new color to the server, and change it locally
>>> player1.color='red'
>>> # but don't do this, use ParseMessage[In|Out] modules for this
>>> player1.send('color=%s' % player1.color)
"""
try:
if msg:
mfile = self.socket_player.makefile()
if msg[-1]!='\n':
mfile.write(msg+'\n') # FIXME ?
else:
mfile.write(msg) # FIXME ?
mfile.flush()
#: if v:
ANSIColors.printc("<INFO> <neg>/Player.Player.send/ The player [%s, id=%i] <green>sent the message<white> [%s]<white> to his server [%s]." % (str(self), self.id, msg, str_of_InfoServer(self.info_server)))
return True
else:
return False
except Exception as e:
if v:
ANSIColors.printc("<warning>\t/S/ <red>The player [%s<red>, id=%i] <u>fail<U> when trying to send<white> the message [%s] to his server <blue><u>[%s]<U><white>.\n\t/S/ <red>%s<white>." % (str(self), self.id, msg, str_of_InfoServer(self.info_server), e))
return False
[docs] def __getstate__(self):
""" Used to save the Player.Player instance, for example with pickle.
Warning:
.. warning::
The socket (attribute *socket_player*) is **not destroyed** by this operation,
and the Player.Player *self* is **still** connected to his server.
Example of Saving:
>>> output = open('player1.pkl', 'wb')
>>> pickle.dump(player1, output, -1)
>>> output.close()
"""
odict = self.__dict__.copy() # copy the dict since we change it
del odict['socket_player'] # remove socket entry
ANSIColors.printc("\t/pickle/ Pickling the player %s. This player has id=%i, <yellow>but his un-pickling will change his id<reset><white> ..." % (str(self), self.id))
ANSIColors.printc("\t/pickle/ His dictionary is : <neg>%s<Neg><white>." % str(odict))
return odict
[docs] def __setstate__(self, dict):
""" Used to reload the Player.Player instance, for example with pickle.
Warning:
.. warning::
The socket (attribute *socket_player*) is **re created** by this operation,
and the Player.Player *self* is **re connected** to his server.
Example of Loading:
>>> pkl_file = open('player1.pkl', 'rb')
>>> player1 = pickle.load(pkl_file)
>>> pkl_file.close()
>>> print player1 # !warning! : the connection is a new one.
"""
ANSIColors.printc("\t/pickle/ Remaking a player, with the dictionary : <black>%s<white>." % (str(dict)))
self.__dict__.update(dict) # update attributes
ANSIColors.printc("\t/pickle/ Remaking the player %s. This player have now id=%i, <red>but his un-pickling had change his id<reset><white> ..." % (str(self), self.id))
self.connect(self.info_server) # Re connect the player.
#END