#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
This module implement a class (Board.Board) for Bomberman board, which is basically a **matrix** of states, represented with the class Board.State.
Specials states:
================
* empty -- an empty spot, with NOTHING on it.
* dmur -- a destructible wall, with JUST the wall on it.
* umur -- an undestructible wall, with JUST the wall on it.
* expl -- an empty spot, with JUST an explosion tag on it.
Warning:
========
.. warning::
**Use those constants state only with copy.copy(..).**
**overwise it will work with references over those constants.**
Example :
=========
>>> for i, j, spot in board:
>>> spot=Board.empty
All spots of [board] are now the same, so all modification like
>>> board[i,j].explosion=True
will be done on *ALL spot of [board]*.
**And that's not what we want to**.
"""
__author__='Lilian BESSON (mailto:lilian.besson[AT]normale.fr)' # Automatically update with update__date__.sh, a Naereen Corp. (c) bash script.
__version__='1.1a'
__date__='jeu. 14/02/2013 at 02h:59m:40s ' #: The date of the file, automatically update with update__date__.sh, a Naereen Corp. (c) bash script.
#1###############
# Usual Modules #
import sys, copy
#2#################
# Project Modules #
import Bonus # Bonus.py : implement the simple bonus system. (actions, representation etc)
import Bomb # Bomb.py : implement the simple bomb system. (actions, representation etc)
import ANSIColors # ANSIColors.py : just some colors definition.
import Matrix # Matrix.py : simple module to manipulate matrix, for the board.
##############################################################################
from Constants import *
import ParseMessageOut
####################################################
#: Try to know if the encoding is UTF-8 supported.
UTFSupported = False
try:
UTFSupported=('UTF-8' == sys.stdout.encoding) or ('UTF-8' == sys.stdin.encoding)
except Exception as e:
try:
if sys.stdout.isatty():
ANSIColors.writec("\t<WARNING> /UTF-8/ seems to <red>not be supported<white> (and the detection ends <u><red>badly<U> : %s<white> !).\n" % str(e), file=sys.stderr)
except:
sys.stderr.write("\t<WARNING> /UTF-8/ seems to <red>not be supported<white> (and the detection ends <u><red>badly<U><white> !).\n")
if not UTFSupported:
try:
if sys.stdout.isatty():
ANSIColors.writec("\t<INFO> /UTF-8/ seems to <red>not be supported<white> (but the detection ends <u><green>nicely<U><white> !).\n", file=sys.stderr)
except: pass
####################################################
# Defines the state of one spot on the board
[docs]class State:
""" A Class for the element of the board.
Attributes:
* wall -- True if the case is a wall, false if it is empty (ie : can be occupyed by a player, or/and a bomb, or/and a bonus).
* destr -- True or False, if the case can be destroy (CONVENTION: a piece of wall is destroid in one explosion).
* bomb -- A Bomb.Bomb instance (no need for more than 1).
* players -- List of Player.noPlayer if there is a player, a Player.Player instance if there is one.
* explosion -- A boolean for a visual effect of an explosion !
* bonus -- None if there is no bonus, or a Bonus.Bonus instance if there is one (only one bonus in a state).
This class can be used both by the client and the server.
But, for the server each players in the attribute players are instances of Player.PlayerServer; and for the client, the client's player is a Player.Player instance, and all other players are Player.Player.
"""
[docs] def __init__(self, wall=True, destr=False, bomb=None, players=copy.copy([]), explosion=False, bonus=None): ##, offsets=copy.copy([])):
""" Simple constructor of state, assigned with default value (an undestructible piece of wall).
Each parameters can be directly assigned.
For example, myState=State(wall=False, players=[myself]) return a State initializing as containing the player [myself]."""
self.wall = wall #: True if the case is a wall, false if it is empty (ie : can be occupyed by a player, or/and a bomb, or/and a bonus).
self.destr = destr #: True or False, if the case can be destroy (CONVENTION: a piece of wall is destroid in one explosion).
self.bomb = bomb #: A Bomb.Bomb instance (no need for more than 1).
self.players = players #: List of Player.noPlayer if there is a player, a Player.Player instance if there is one.
self.explosion = explosion #: A boolean for a visual effect of an explosion !
self.bonus = copy.copy(bonus) #: None if there is no bonus, or a Bonus.Bonus instance if there is one (only one bonus in a state).
[docs] def to_send(self):
""" This method is imported from ParseMessageOut."""
return ParseMessageOut.str_of_state(self)
[docs] def is_free(self):
""" self.is_free() -> True|False
Determine if a player can be placed in the spot *self*. """
return not(self.wall or self.bomb)
[docs] def strNoUtf(self):
""" Simple transformation of a state to a universal (no UTF, only ASCII) string of 3 caraters."""
if self.wall:
if self.destr: return '[D]'
return '[U]'
s1, s2, s3=" ", " ", " "
if self.bonus: s1=self.bonus.strNoUtf()[0]
if self.players:
s2=str(self.players[0].pseudo[0])
try:
s2.decode('ascii')
except:
s2 = str( self.players[0].id )
# BUG: if pseudo contains UTF caracters, there is a problem.
# FIXED
if self.bomb: s3=str(max(0, self.bomb.timer))[0]
if s3==" " and self.explosion: s3="*"
if s1==" " and self.explosion: s1="*"
return s1 + s2 + s3
[docs] def strUtf(self):
u""" Simple transformation of a state to a non ASCII (*i.e.* with UTF caracters) **string of 3 caraters**.
For a wall, 3 grey blocks if it can be brocken, or 3 black blocks.
For an explosion, a *little sun* is used.
If those caracters do not seems pretty, try to change the encoding *used* **here** to **UTF-8**.
**Sorry, I didn't succeed in putting those caracters here** in this docstring, because pyDoc doesn't have compatibility with UTF8.
And I like pyDoc.
I really do :)
By the way, I wrote the script **makePydoc.sh**.
Take a look !
"""
if self.wall:
if self.destr: return '░░░'
return '▓▓▓'
s1, s2, s3=" ", " ", " "
if self.bonus:
s1=self.bonus.strNoUtf()[0]
#: s1 = repr( self.bonus )[0]
#: print "Bonus: "+s1, s1, str(s1)
if self.players: s2="%s%s%s" % (ANSIColors.tocolor(self.players[0].color), self.players[0].pseudo[0], ANSIColors.white)
if self.bomb: s3=str(max(0, self.bomb.timer))[0]
if s3==" " and self.explosion: s3="☼"
if s1==" " and self.explosion: s1="☼"
return s1 + s2 + s3 # FIXME: s2.encode('utf-8') ?
[docs] def __str__(self):
""" __str__(self) <=> str(self) -> 'str'
Generic conversion to a string, choose an UTF8 representation if it is **available**."""
if UTFSupported:
return self.strUtf()
else:
return self.strNoUtf()
[docs] def __repr__(self):
"""Toplevel representation of [self], is str(self)."""
return ("wall=%s; destr=%s; bomb=%s; players=%s; bonus=%s; explosion=%s" % (self.wall, self.destr, self.bomb, self.players, self.bonus, self.explosion) )
[docs] def __eq__(self, s):
""" self.__eq__(s) <==> s == self"""
return s.__dict__ == self.__dict__
[docs] def hit(self, ingury=1):
""" Destroy the element in case [self] with [ingury] damages.
Cases:
* If it's a destructible wall, break it, return SIGNAL_WALL_BREAKE=2.
* If it's a undestructible wall, break it, return SIGNAL_WALL_NOT_BREAKE=4.
* If it's a bomb, destroy it, return SIGNAL_BOMB_HURT.
* If it's a player, hurt it with [ingury] ingury(ies), return SIGNAL_PLAYER_HURT=3.
The player in this case can die, raising Player.PlayerDeath.
"""
if self.destr: # Become empty if it's a destructible wall.
if self.wall: ANSIColors.printc("\t/D/ <red> A destructible wall have been founded. Now the spot is empty !<white>")
self.wall = False
self.destr = False
return SIGNAL_WALL_BREAKE
for p in self.players:
p.hurt(ingury) # Eventually, player die.
if self.bomb:
self.bomb.timer=0
ANSIColors.printc("\t/b/ <red> A bomb have been founded, his timer is getting <u>reduce to 0<U> immediatly !<white>")
return SIGNAL_BOMB_HURT
if self.players: # if one players was hurted
return SIGNAL_PLAYER_HURT
if self.wall: # if the wall is undestructible
return SIGNAL_WALL_NOT_BREAKE
return 0 #: Nothing special happened.
#: Special States:
empty = State(wall=False) #: An *empty spot*, with NOTHING on it.
dmur = State(wall=True, destr=True) #: A *destructible wall*, with JUST the wall on it.
umur = State(wall=True, destr=False) #: An *undestructible wall*, with JUST the wall on it.
expl = State(wall=False, destr=False, explosion=True) #: An empty spot, with JUST an **explosion tag** on it.
[docs]class Board:
""" A Class to define a Bomberman board.
Attributes:
* lx -- his dimension over horizontal axe x.
* ly -- his dimension over vertical axe y.
* nb -- max number of player, (for usual boards, it's 4).
* mat -- the map of State instances, coded as a matrix.
For call convention, take a look at [Matrix] module.
[mat.box] will be used for printing, so it can be
modified by other stuff.
This class can be used both by the client and the server.
"""
[docs] def __init__(self, state=copy.copy(State()), lx=lx_Max, ly=ly_Max, nb=nbmax_Max, maxStr=3, utf=UTFSupported):
""" Simple constructor of an empty board.
Contains a [map] attribute initialized with all elements equal to a copy of [state].
The board is a rectangle, of length [lx] over [ly].
[maxStr] is passed throw the underlying matrix, and will be used for printing (see Matrix.tostr).
[utf] is true to use non ASCII caracters for printing the matrix, false otherwise."""
self.lx = lx #: his dimension over horizontal axe x.
self.ly = ly #: his dimension over vertical axe y.
self.nb = nb #: max number of player, (for usual boards, it's 4).
self.mat = Matrix.Matrix(state, lx, ly, maxStr = maxStr) #: the map of State instances, coding as a matrix (ie. Matrix.Matrix instance).
if utf:
self.mat.box = Matrix.boxnoASCII
else:
self.mat.box = Matrix.boxASCII
[docs] def bombs(self):
""" Simply return a list of Bomb.Bomb associated with their position.
Example : [(0,1,<bomb>), (1,3,<bomb>)]
FIXME: it shall be useless now.
"""
bombs=[]
for i in range(self.lx):
for j in range(self.ly):
if self.mat[i,j].bomb:
bombs.append((i,j, self.mat[i,j].bomb))
return bombs
[docs] def players(self):
""" Simply return a list of Player.Player associated with their position."""
res=[]
for i in range(self.lx):
for j in range(self.ly):
if self.mat[i,j].players:
for p in self.mat[i,j].players:
res.append(p)
return res
[docs] def str_of_players(self, pl):
""" This method is imported from ParseMessageOut."""
return ParseMessageOut.str_of_players(self, pl)
[docs] def __str__(self):
""" Transformation to a string, using Matrix.Matrix.__str__ method."""
return str(self.mat)
[docs] def __repr__(self):
""" Toplevel representation of a Board, to a string."""
return ("lx=%i; ly=%i; nb=%i; %s" % (self.lx, self.ly, self.nb, repr(self.mat)))
[docs] def __getitem__(self, (i,j)):
""" A shortcut to self.mat[i,j].
Allow self[i,j]."""
return self.mat[i,j]
[docs] def __setitem__(self, (i,j), val):
""" A shortcut to self.mat[i,j]=val
Allow self[i,j] = v."""
self.mat[i,j]=val
[docs] def __contains__(self, u):
""" self.__contains__(u) <==> u in self"""
return u in self.mat
[docs] def __eq__(self, b):
""" self.__eq__(b) <==> b == self"""
return b.mat == self.mat
[docs] def fill(self, val):
""" Set all state of the board to [val]."""
for i in range(self.lx):
for j in range(self.ly):
self.mat[i,j] = copy.copy(val)
###############################################################################
[docs] def destroy_bomb(self, i, j, toc = toc_default, radius = force_default,\
print_on_all=None, str_of_blowbomb=None, list_clients=None, origin=None,\
FORCE = False, verb = False):
""" Just decrease the timer of the bomb in the board[i,j], [toc] is the time spent during ticking.
FIXME: make sure it spreads from the spot, in the right direction. Seems ok.
FORCE is here to allow the destruction of a bomb even if its timer is not ok...
.. warning::
This function is quite **long**, make sure it works correctly !
"""
if (self.mat[i,j].bomb) or FORCE:
if (self.mat[i,j].bomb.timer > 0) and not(FORCE):
if verb: ANSIColors.printc("\t/b/ One bomb \t[%s] found in <black>(%i, %i)<white> : it's <u>not<U> explosing..." % (self.mat[i,j].bomb, i,j))
else:
ANSIColors.printc("\t/B/ One bomb [%s] found in <red>(%i, %i)<white> : it's explosing..." % (self.mat[i,j].bomb, i,j))
try:
self.mat[i,j].bomb.owner.nb_bomb -= 1
ANSIColors.printc("\t/B/ <green>Hehehe : this bomb have an <u>owner<U> (<neg>%s<Neg>) !<white>. He now possess %i bomb(s) in the board !" % (str(self.mat[i,j].bomb.owner), self.mat[i,j].bomb.owner.nb_bomb))
except Exception as e:
ANSIColors.printc("\t/B/ <red>Ohohoh : this bomb doesn't have an <u>owner<U> attribute !<white>. Cause : %s." % str(e))
# the owner of the bomb lost one of his bomb(s).
localbomb=self.mat[i,j].bomb
if radius > 0:
localbomb.force = radius
ANSIColors.printc("\t/B/ Overpassing this bomb's force with the function argument <neg>%i<Neg> !" % radius)
else:
radius = max(min(localbomb.force, 10), 1)
ANSIColors.printc("\t/B/ Using a new bomb's force : <neg>%i<Neg> !" % radius)
############################ For BombermanServer
if list_clients:
try:
tmp = ""
for s in list_clients: tmp += "[%s:%i], " % s.getsockname()
ANSIColors.printc("""
/destroy_bomb/ I'm trying to informs the clients in <neg>%s<Neg> that I found an exploding bomb in (%i,%i).
""" % ( tmp, i, j ))
print_on_all( str_of_blowbomb(i,j,radius) , list_clients, origin) # FIXED ?
except:
ANSIColors.printc("/destroy_bomb/ <red> Fails <white> when I tried to inform my clients.")
############################ For BombermanServer
self.mat[i,j].bomb = copy.copy(None) # the bomb is deleted after inguried all spots.
ii=i
# Top
for jj in range(j, min(self.ly, j+1+localbomb.force)):
return_signal = self.mat[ii, jj].hit(localbomb.power)
self.mat[ii, jj].explosion=True
if SIGNAL_BOMB_HURT == return_signal:
# there is a bomb in (ii, jj), with timer = 0 now
ANSIColors.printc("\t/!/ The explosion <red>touched an other bomb<white> : it's gonna to explode to !<white>")
if jj!=j or ii!=i: self.destroy_bomb(ii, jj, toc=toc, verb=verb, radius=radius,\
print_on_all=print_on_all, str_of_blowbomb=str_of_blowbomb,\
list_clients=list_clients, origin=origin, FORCE=FORCE)
# make the bomb in (ii, jj) explode
if SIGNAL_WALL_BREAKE == return_signal:
ANSIColors.printc("\t/D/ The explosion have been stoped by a destructible wall in (%i,%i) !<white>" % (ii, jj))
#: if BREAK_ON_WALL: break
if SIGNAL_WALL_NOT_BREAKE == return_signal:
ANSIColors.printc("\t/U/ The explosion have been stoped by a undestructible wall in (%i,%i) !<white>" % (ii, jj))
if BREAK_ON_WALL: break
if verb: ANSIColors.printc("\t/h/ The bomb found in <green>(%i, %i)<white> hurts the case <red>(%i, %i)<white> with power %i because this spot is clos enough (force=%i)." % (i, j, ii, jj, localbomb.power, localbomb.force))
# Bottom
for jj in range(j-1, -1+max(0, j-1-localbomb.force), -1):
if j>1 and jj==j-1-localbomb.force: continue
return_signal = self.mat[ii, jj].hit(localbomb.power)
if verb: ANSIColors.printc("\t/h/ The bomb found in <green>(%i, %i)<white> hurts the case <red>(%i, %i)<white> with power %i because this spot is clos enough (force=%i)." % (i, j, ii, jj, localbomb.power, localbomb.force))
self.mat[ii, jj].explosion=True
if SIGNAL_BOMB_HURT == return_signal:
# there is a bomb in (ii, jj), with timer = 0 now
ANSIColors.printc("\t/!/ The explosion <red>touched an other bomb<white> : it's gonna to explode to !<white>")
if jj!=j or ii!=i: self.destroy_bomb(ii, jj, toc=toc, verb=verb, radius=radius,\
print_on_all=print_on_all, str_of_blowbomb=str_of_blowbomb,\
list_clients=list_clients, origin=origin, FORCE=FORCE)
# make the bomb in (ii, jj) explode
if SIGNAL_WALL_BREAKE == return_signal:
ANSIColors.printc("\t/D/ The explosion have been stoped by a destructible wall in (%i,%i) !<white>" % (ii, jj))
#: if BREAK_ON_WALL: break
if SIGNAL_WALL_NOT_BREAKE == return_signal:
ANSIColors.printc("\t/U/ The explosion have been stoped by a undestructible wall in (%i,%i) !<white>" % (ii, jj))
if BREAK_ON_WALL: break
jj=j
# Right
for ii in range(i+1, min(self.lx, i+1+localbomb.force)):
return_signal = self.mat[ii, jj].hit(localbomb.power)
if verb: ANSIColors.printc("\t/h/ The bomb found in <green>(%i, %i)<white> hurts the case <red>(%i, %i)<white> with power %i because this spot is clos enough (force=%i)." % (i, j, ii, jj, localbomb.power, localbomb.force))
self.mat[ii, jj].explosion=True
if SIGNAL_BOMB_HURT == return_signal:
# there is a bomb in (ii, jj), with timer = 0 now
ANSIColors.printc("\t/!/ The explosion <red>touched an other bomb<white> : it's gonna to explode to !<white>")
if jj!=j or ii!=i: self.destroy_bomb(ii, jj, toc=toc, verb=verb, radius=radius,\
print_on_all=print_on_all, str_of_blowbomb=str_of_blowbomb,\
list_clients=list_clients, origin=origin, FORCE=FORCE)
# make the bomb in (ii, jj) explode
if SIGNAL_WALL_BREAKE == return_signal:
ANSIColors.printc("\t/D/ The explosion have been stoped by a destructible wall in (%i,%i) !<white>" % (ii, jj))
#: if BREAK_ON_WALL: break
if SIGNAL_WALL_NOT_BREAKE == return_signal:
ANSIColors.printc("\t/U/ The explosion have been stoped by a undestructible wall in (%i,%i) !<white>" % (ii, jj))
if BREAK_ON_WALL: break
# Left
for ii in range(i-1, -1+max(0, i-1-localbomb.force), -1):
if i>1 and ii==i-1-localbomb.force: continue
return_signal = self.mat[ii, jj].hit(localbomb.power)
if verb: ANSIColors.printc("\t/h/ The bomb found in <green>(%i, %i)<white> hurts the case <red>(%i, %i)<white> with power %i because this spot is clos enough (force=%i)." % (i, j, ii, jj, localbomb.power, localbomb.force))
self.mat[ii, jj].explosion=True
if SIGNAL_BOMB_HURT == return_signal:
# there is a bomb in (ii, jj), with timer = 0 now
ANSIColors.printc("\t/!/ The explosion <red>touched an other bomb<white> : it's gonna to explode to !<white>")
if jj!=j or ii!=i: self.destroy_bomb(ii, jj, toc=toc, verb=verb, radius=radius,\
print_on_all=print_on_all, str_of_blowbomb=str_of_blowbomb,\
list_clients=list_clients, origin=origin, FORCE=FORCE)
# make the bomb in (ii, jj) explode
if SIGNAL_WALL_BREAKE == return_signal:
ANSIColors.printc("\t/D/ The explosion have been stoped by a destructible wall in (%i,%i) !<white>" % (ii, jj))
#: if BREAK_ON_WALL: break
if SIGNAL_WALL_NOT_BREAKE == return_signal:
ANSIColors.printc("\t/U/ The explosion have been stoped by a undestructible wall in (%i,%i) !<white>" % (ii, jj))
if BREAK_ON_WALL: break
# ok now the end
ANSIColors.printc("\t/B/ The old bomb found in <green>(%i, %i)<white> : it has just exploded, so it has been deleted." % (i,j))
return 0
[docs] def tic(self, toc=toc_default, MAKE_DESTROY=True, verb=False, FORCE=False,\
print_on_all=None, str_of_blowbomb=None, list_clients=None, origin=None):
""" Just decrease the timer of all bombs in the board, [toc] is the time spent during ticking.
MAKE_DESTROY is here to allow ticking the board without triggering any explosion.
Because for the client, timers are just for priting.
Call *destroy_bomb* whenever it is needed (*i.e.* when a timer is going non positive (<= 0).)"""
nb_bomb=0
# for all spot
for i in range(self.lx):
for j in range(self.ly):
# tic the bomb
if self.mat[i,j].bomb:
nb_bomb+=1 # a new bomb !
if self.mat[i,j].bomb.tic(toc) and MAKE_DESTROY:
self.destroy_bomb(i,j, toc=toc, verb=verb, FORCE=FORCE, \
print_on_all=print_on_all, str_of_blowbomb=str_of_blowbomb, \
list_clients=list_clients, origin=origin)
ANSIColors.printc("\t/t/ The board have been ticked, %i bomb(s) founded !<white>" % nb_bomb)
[docs] def __iter__(self):
""" Special method to allow an iteration with a board.
.. warning::
This seems to be wrong, use *board[i,j]* and not *spot*,
**if you want to modify board[i,j]**.
Example:
>>> for i,j,spot in board:
>>> spot.destr=False; spot.wall=False; spot.explosion=True
>>> print "Spot at place (%i, %i)" % (i,j)
The map is explored from (0,0) to (self.lx - 1, self.ly - 1),
LINE by LINE (x loop first) then COLUMN by COLUMN (then y loop)."""
return IterBoard(self)
###############################################################################
[docs]class IterBoard:
""" A class for build **an iterator for Board**.
It's mainly done here to *learn* about iterator.
This *workaround* allow the following shortcut :
Example:
>>> for i,j,spot in board:
>>> spot.destr=False; spot.wall=False; spot.explosion=True
>>> print "Spot at place (%i, %i)" % (i,j)
This is useful, to avoid double loop with i, j.
And moreover, this is interesting from the user : write 'for spot in board' is intuitive."""
[docs] def __init__(self, board):
""" A wrapper around Board.Board class to build an iteratoir for a map."""
self.board=board #: the board used for iteration
self.i = 0 #: current value of i (over axis x)
self.j = -1 #: current value of j (over axis y)
[docs] def next(self):
""" Iterator for Board.Board !"""
if self.j < self.board.ly - 1:
self.j += 1
else:
self.j = 0
self.i += 1
if (self.i >= self.board.lx) or (self.j == self.board.ly):
raise StopIteration
return self.i, self.j, self.board[self.i, self.j]
#END