Source code for AffichPygame

#!/usr/bin/env python
# -*- encoding: utf-8 -*-

""" Generical classe and functions from Pygame to make the graphical user interface (GUI).
This script provides shortcuts and utilities to *other modules*, 
but it can also be used to check the compatibility with *pygame*.

 In a terminal, launching the scrit, with **$ ./AffichPygame.py**, will launch a demo.
 
 .. warning::
    The *Python* binding to **SDL**, **Pygame**, is **required** for the project as well as for this demo.

Example:
========
  This show how the **graphical window** *looks like during the game* (for the *client*) :
  
.. image:: images/exemplegraphical.png
   :scale: 80 %
   :align: center

About:
------
 This module use the datas store in **./datas/** folder.
  * The sprites are made with '.png' pictures, usually this is supported on every platform;
  * The soundtrack is made with '.mp3' tracks.
 
 .. warning::
    On some Linux distribution *mp3* are not supported ! 
     Maybe we have to install some *extra* libs.
    Or maybe you don't have a sound server.

 .. hint::
    You can disable music for the client and the bot with the *--nomusic* and *--nosoundeffect* options.

Soundtrack:
===========

.. For more details about soundtrack, the file `sound/README <../../datas/sound/README.md>`_ can be usefull.
.. include:: datas/sound/README.md

Pictures:
=========

.. For more details about the pictures, the file `48_48/README <../../datas/48_48/README.md>`_ can be usefull.
.. include:: datas/48_48/README.md

.. warning::
   When the main window is initialized, this module try to set her *icon*.
    Sometime it works : so you can see a nice *bomberman* player as an icon for your window.
    Sometime it doesn't.
    I wasn't able to understand why this seems to act *randomly*, sorry.

TODOs:
======
 * Try to update the sprite only when it changed.
 * Find a cool sound for game over.
"""

__author__='Lilian BESSON'	#: Automatically update with update__date__.sh, a Naereen Corp. (c) bash script.
__email__='lilian.besson[AT]normale.fr'
__version__='1.0a'	#: Version of this module
__date__='dim. 17/02/2013 at 17h:24m:38s '	#: The date of the file, automatically update with update__date__.sh, a Naereen Corp. (c) bash script.

try:
	#1###############
	# Usual Modules #
	import os, sys, random, copy, time
	import thread
	#2#################
	# Project Modules #
	from Constants import *
	import ANSIColors
	import KeyBinding
	import Player
	import ParseMessageIn, ParseMessageOut
	#####################
	# Pygame
	import pygame
	# Initialization of the pygame window.
	pygame.init()
	# Initialization of the music mixer.
	if USE_MUSIC: pygame.mixer.init()
	from pygame.locals import *
except ImportError, err:
	sys.stderr.write(ANSIColors.sprint("\t<warning> <ERROR> <red>Fail<white> to launch module %s." % (err)))
	sys.exit(2)

########################################################
##### All resources (picture & music) for the game #####

#:Make your own `here <http://www.softicons.com/icon-tools/icon-converter>`_
#: Two different mode will be available : **48x48** and **32x32**.
#:
#:.. warning::
#:   By now, the 48x48 is **the only one** valid.
RESOLUTION_X, RESOLUTION_Y = 48, 48

# List of pictures (to make sprites).

PICTURE_block_destr = "block_destr.png"	#: For a destructible spot.
PICTURE_block_solid = "block_solid.png"	#: For an undestructible spot.
PICTURE_explosion = "explosion.png"
PICTURE_player = "player%i.png"		#: For players.
PICTURE_player_direction = "player_%s.png"		#: For players.
PICTURE_bomb = "bomb%i.png"		#: For bombs.
PICTURE_bonus = "bonus_%s.png"		#: For bonuses.
PICTURE_block_bonus = "block_bonus.png"	#: For a spot with a bonus.

# List of music (to make soundtrack).

MUSIC_world_clear = "world_clear.mp3"		#: When a player (not the active one) win the game.
MUSIC_bonus_main = "item.mp3"			#: When the active player get a bonus.
MUSIC_explosion = "explosion.mp3"		#: When a bomb is exploding.
MUSIC_loop_list = ["battle.mp3", "oeuf.mp3", "menu.mp3"]	#: Random : choose from this list !
#MUSIC_loop = random.choice(MUSIC_loop_list)	#: For the main loop.

[docs]def MUSIC_loop(): """ MUSIC_loop() -> music file name """ return random.choice(MUSIC_loop_list) #: For the main loop. ######################################################## #### Tool functions to interact with datas database ####
[docs]def color_to_number(color): """ Convert one of the following color ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'], to an integer between 1 and 8 (to choose the right picture).""" if color == "black": return 1 #: Bomberman classic ! elif color == "red": return 2 #: Mario 2 ! elif color == "green": return 8 #: Link ! elif color == "yellow": return 4 #: Pikachu ! elif color == "blue": return 7 #: Sonic ! elif color == "magenta": return 6 #: Pac-Man ! elif color == "cyan": return 5 #: Wizard ! elif color == "white": return 3 #: Mario 1 ! else: return 0 #: This case **cannot** happen.
[docs]def load_tile_table(filename, width, height): """ A test to use a picture as an array of sub pictures. """ image = pygame.image.load(filename).convert() image_width, image_height = image.get_size() tile_table = [] for tile_x in range(0, image_width/width): line = [] tile_table.append(line) for tile_y in range(0, image_height/height): rect = (tile_x*width, tile_y*height, width, height) line.append(image.subsurface(rect)) return tile_table
[docs]def namePicture_of_SpriteSpot(state, verb = False): """ Give the name of the picture used to print the *state*. If more than one player is in the same spot, return the first one. See how return more than one pictures, and make a blur effect to show all of the returned pictures. """ if verb: print "namePicture_of_state : state = %s." % str(state) if state.explosion and not( state.destr and state.wall ): return PICTURE_explosion if state.bomb: return (PICTURE_bomb % state.bomb.power) if state.wall: if state.destr: return PICTURE_block_destr return PICTURE_block_solid #: if state.bonus: # FIXME not implemented #: kind = state.bonus.kind[1:] #: return (PICTURE_bonus % kind) #: ex. kind = ice --> 'bonus_ice.png' if state.players: if USE_DIRECTION_FOR_PLAYER: return (PICTURE_player_direction % state.players[0].direction) # FIXME experimental. return (PICTURE_player % ( 1+(state.players[0].id % 8) )) #: return (PICTURE_player % (color_to_number(state.players[0].color))) else: #: ANSIColors.printc("\t/pygame/ <WARNING> Fail when trying to put a picture name for the state: %s <reset> (repr=%s)<white>" % (str(state), repr(state))) return "!! NOTHING_TO_LOAD !!" #################################### ##### Load music and pictures ######
[docs]def load_png(name, verb = False): """ Load a picture, and return a image object. The picture **have to be** in the folder *./datas/48_48/*, or *./datas/32_32/*, otherwise, error might occur quickly :s ! About: A lot of pre set pictures are from softicons.com, and a few of them where made by their tools [http://www.softicons.com/icon-tools/icon-converter].""" fullname = os.path.join('datas/%i_%i/' % (RESOLUTION_X, RESOLUTION_Y), name) if verb: ANSIColors.printc("\t/pygame/ <INFO> Trying to load the picture : %s <reset><white>" % fullname) try: image = pygame.image.load(fullname) if image.get_alpha is None: image = image.convert() else: image = image.convert_alpha() except: if verb: ANSIColors.printc("\t/pygame/ <ERROR> Fail when loading the picture :%s <reset><white>" % fullname) raise return image, image.get_rect()
[docs]def play_music(name, number=-1, volume=1.0, time=0.0, verb = True): """ Load a music. Set the volume to *volume*, and play it *number* time. To play in loop, set *number=-1*. The music **have to be** in the folder *./sound/*, otherwise, error might occur quickly :s ! **About the soundtrack:** The default soundtrack is from many different sources. This is an exhaustive list : * Pokemon Red (Nintendo (c) 1995 - GameFreak); * The Legend of Zelda - Ocarina of Time, Link's awakening (Nintendo (c) 1991 & 1997); * Super Mario World 1 (Nintendo (c) 1985); * Super Smash Bros Melee (Nintendo (c) 2001 - HAL Laboratory). **Copyrigths:** The game is released under the *GPLv3 Licence,* but the soundtrack **is not** distributed freely with the game (at least, not under a free licence). Although, you can download it from the same source that the game (just not in the same archive). """ fullname = os.path.join('datas/sound/', name) if verb: ANSIColors.printc("\t/pygame/ <INFO> Trying to load the music : %s <reset><white>" % fullname) try: pygame.mixer.music.set_volume(volume) pygame.mixer.music.load(fullname) pygame.mixer.music.play(number, time) except: if verb: ANSIColors.printc("\t/pygame/ <ERROR> Fail when loading the music :%s <reset><white>" % fullname) raise ############################################## #### One classe for the SDL printed board ####
[docs]class SpriteSpot(pygame.sprite.Sprite): """ A simple class for a spot (instance of Board.State) in the board. This one is more oriented around moves by direct order. FIXME: use update ? """
[docs] def __init__(self, state, pos=(0, 0)): """ Spot is a wrapper around Board.State, and state is attend to be one of this.""" self.state = state #: The Spot contain a state. pygame.sprite.Sprite.__init__(self) self.image, self.rect = load_png(namePicture_of_SpriteSpot(state)) screen = pygame.display.get_surface() self.area = screen.get_rect() self.speed = RESOLUTION_X self.movestate = "still" self.movepos = [0, 0] self.reinit(pos)
[docs] def reinit(self, pos): """ Initialize the sprite, make it stable.""" self.rect.move_ip(pos) self.movestate = "still" self.movepos = [0, 0]
[docs] def update(self): """ Update the position of the sprite. For now, it is done **stupidly** : the sprites are all recomputed each turn. FIXME: make sure this is done more cleverly.""" newpos = self.rect.move(self.movepos) if self.area.contains(newpos): self.rect = newpos pygame.event.pump() ################################################################# #### Convert a Board.Board to a list of pygame.sprite.Sprite ####
TRY_UPDATE_CLEVERLY = False #: If true, try to update ony the changed sprites. oldboard = "not yet :("
[docs]def listsprite_of_board(board, verb = False): """ listsprite_of_board(board, verb = False) -> pygame.Sprites """ spots = [] if verb: print_clear(board) for i, j, spot in board: #: ok. update = True if TRY_UPDATE_CLEVERLY: try: update = (spot != oldboard[i, j]) # if possible, try to not update the sprite. except: pass if update: try: if namePicture_of_SpriteSpot(spot, verb=verb) != "!! NOTHING_TO_LOAD !!": spots.append(SpriteSpot(spot, pos=(RESOLUTION_Y*j, RESOLUTION_X*i))) except Exception as e: ANSIColors.printc("\t/pygame/ <ERROR> PB when converting %s to a sprite. Cause : %s.<reset><white>" % (repr(spot), e)) pass oldboard = board #: FIXME return pygame.sprite.RenderPlain(spots) ###############################################################################
[docs]def toggle_explosion(board, pl, clock, MAKE_DESTROY=False, num_thread=0, FORCE=False, \ print_on_all=None, list_clients=None, origin=None, player = None): """toggle_explosion(board, pl, clock, MAKE_DESTROY=False, num_thread=0) -> infinite loop A small function to toggle explosions tag when they have to be erased. On the *server*, **MAKE_DESTROY** is True, and is False on the client. Have to be threaded too. """ try: ANSIColors.printc(""" /toggle_explosion/ <neg>Thread number %i<Neg> : initialized. Infinite loop (freq=%i) to handle explosion... %s """ % ( num_thread, CLOCK_FREQUENCY, str(board) )) while True: clock.tick(TIME_EXPLOSION) for itmp ,jtmp, spottmp in board: board[itmp, jtmp].explosion = False board.tic(toc=1, MAKE_DESTROY=MAKE_DESTROY, FORCE=FORCE, \ print_on_all=print_on_all, str_of_blowbomb=ParseMessageOut.str_of_blowbomb,\ list_clients=list_clients, origin=origin) print_clear(board) print_pvs_player(pl) ############################################################################### # End of the game (GameOver). except ParseMessageIn.GameOver as e: player2, msg = e.player, e.msg if player: if player2.id == player.id: ANSIColors.printc(""" <warning>\t/end of the game/ <green> You win !!<white> You received the message '%s'. """ % msg) ANSIColors.printc(""" <warning>\t/end of the game/ <green> The game is closing now.<white>""") os._exit(0) else: ANSIColors.printc(""" <warning>\t/end of the game/ <magenta> The player <neg>%s<Neg> wan ! (he was represented as : %s). """ % (player2, str(player2))) ANSIColors.printc(""" <INFO>\t/end of the game/ <red> You lost...<white> Try again ! You received the message '%s'. """ % msg) ANSIColors.printc(""" <warning>\t/end of the game/ <red> The game is closing now.<white>""") os._exit(1) else: ANSIColors.printc("<warning> <red>on toggle_explosion (server side), I picked up a GameOver exception... <neg>%s<Neg>.\n/restarting toggle_explosion/" % str(e)) print_on_all( ParseMessageOut.str_of_gameover(player2), list_clients, origin ) # End of the game. return True #: toggle_explosion(board, pl, clock, MAKE_DESTROY, num_thread, FORCE, \ #: print_on_all, list_clients, origin, player) ############################################################################### # Death of players. except Player.PlayerDeath as e: player2, ingury, msg = e.player, e.ingury, e.msg if player: if player2.id == player.id: ANSIColors.printc(""" <warning>\t/your death/ <red> You are dying !!<white> You received %i ingur%s, and the message '%s'.""" % (ingury, "y" if ingury<2 else "ies", msg)) ANSIColors.printc(""" <warning>\t/your death/ <red> The game is closing now.<white>""") os._exit(2) else: ANSIColors.printc(""" <INFO>\t/other death/ <yellow> One of your opponent is dying !!<white> He received %i ingur%s, and the message '%s'.\n\t/other death/ Good job ! """ % (ingury, "y" if ingury<2 else "ies", msg)) else: ANSIColors.printc("<warning> <red>on toggle_explosion (server side), I picked up a PlayerDeath exception... <neg>%s<Neg>.\n/restarting toggle_explosion/" % str(e)) toggle_explosion(board, pl, clock, MAKE_DESTROY, num_thread, FORCE, \ print_on_all, list_clients, origin, player) ############################################################################### # End of the main loop (shall not happen). except: sys.stderr.write(ANSIColors.sprint(""" <warning> /toggle_explosion/ <neg>Thread number %i<Neg> : failed with exception <neg>%s<Neg>. /toggle_explosion/ Now it will try to kill the caller (with <red>thread.interrupt_main()<white>). """ % ( num_thread, str(sys.exc_info()[1]) ))) sys.stderr.flush() thread.interrupt_main() #: raise os._exit(1) ###################################################################################### #### Main loop, for tests only (in fact, this have to be done in BombermanClient) ####
[docs]def main(nbmax, lx, ly, pl, board, Mi, Mj, verb = False): """ The main loop for **testing**. Notice that every thing here will have to be put **in the correct spot** in BombermanClient.""" # Initialization of the pygame window. pygame.init() # Initialization of music mixer. if USE_MUSIC: pygame.mixer.init() # Resolution of the screen. resX, resY = RESOLUTION_X*lx, RESOLUTION_Y*ly if USE_FULLSCREEN: screen = pygame.display.set_mode((resX, resY), pygame.FULLSCREEN) else: screen = pygame.display.set_mode((resX, resY)) pygame.display.set_caption('.: Bomberman %ix%i (Res %ix%i) | MPRI 1.21 | (c) Lilian BESSON :.' % (lx, ly, resX, resY)) pygame.display.set_icon( load_png("player.gif", verb = True)[0] ) # Background background = pygame.Surface(screen.get_size()) background = background.convert() background.fill((20, 0, 0)) #: A dark brown. # Initialization of sprites for the game. spotsSprites = listsprite_of_board(board, verb=verb) # Make the window. screen.blit(background, (0, 0)) pygame.display.flip() # Initialization of the clock frequency. clock = pygame.time.Clock() #: table = load_tile_table("datas/48_48/bomb_tiles.png", 24,24) #: for x, row in enumerate(table): #: for y, tile in enumerate(row): #: screen.blit(tile, (x*9, y*5)) #: pygame.display.flip() ANSIColors.printc("\t/pygame/ <INFO> The screen is initializing with resolution (%i,%i) (in pixels: %i,%i).<white>" % (lx,ly, resX,resY)) # Launching the music. if USE_MUSIC: time_main_loop = 0.0 play_music(MUSIC_loop, time=time_main_loop, volume=0.50) ANSIColors.printc("\t/pygame/ <INFO> The Pygame music mixer is initialized, playing datas/sound/%s.<white>" % (MUSIC_loop)) # This show what to put in the BombermanClient.main loop. while 1: #: To ensure that the printing is not too quick. clock.tick(CLOCK_FREQUENCY) for event in pygame.event.get(): if event.type == QUIT: if USE_MUSIC: ANSIColors.printc("\t/pygame/ <INFO> The Pygame music mixer is now playing datas/sound/%s.<white>" % (MUSIC_world_clear)) play_music(MUSIC_world_clear, number=1, volume=0.40) pygame.mixer.music.fadeout(TIME_FADEOUT * 1000) return (nbmax, lx, ly, pl, board, Mi, Mj) elif event.type == KEYDOWN: ANSIColors.printc("\t/pygame/ <warning> A key have been pressed : %i => %s.<white>" % (event.key, KeyBinding.print_keynum_as_str(event.key))) if event.key == K_ESCAPE: if USE_MUSIC: ANSIColors.printc("\t/pygame/ <INFO> The Pygame music mixer is now playing datas/sound/%s.<white>" % (MUSIC_world_clear)) play_music(MUSIC_world_clear, number=1, volume=0.40) pygame.mixer.music.fadeout(TIME_FADEOUT * 1000) return (nbmax, lx, ly, pl, board, Mi, Mj) if event.key == K_RIGHT: ANSIColors.printc("\t/pygame/ <INFO> Moving right.<white>") if event.key == K_LEFT: ANSIColors.printc("\t/pygame/ <INFO> Moving left.<white>") if event.key == K_UP: ANSIColors.printc("\t/pygame/ <INFO> Moving up.<white>") if event.key == K_DOWN: ANSIColors.printc("\t/pygame/ <INFO> Moving down.<white>") if event.key == K_SPACE: ANSIColors.printc("\t/pygame/ <INFO> Dropping bomb.<white>") pygame.mixer.music.pause() time_main_loop = pygame.mixer.music.get_pos() play_music(MUSIC_explosion, number=1, volume=0.50) pygame.mixer.music.queue('datas/sound/' + MUSIC_loop()) spotsSprites = listsprite_of_board(board) # Update. screen.blit(background, (0, 0)) pygame.display.flip() spotsSprites.update() spotsSprites.draw(screen) # Print. pygame.display.flip()
if __name__ == '__main__': import SimpleGame # Create the board, try: from ParseMessageIn import try_unpickling nbmax, lx, ly, pl, board, Mi, Mj=try_unpickling("(nbmax, lx, ly, pl, board, Mi, Mj)") for i in range(lx): for j in range(ly): board[i,j] = copy.copy(board[i,j]) board[i,j].players = copy.copy(board[i,j].players) ANSIColors.printc("\t/load/ <green>Succeed to load<reset><white> \tthe game from a save file<reset><white>...") except Exception as e: print e server, port = SERVEUR_INIT, PORT_INIT nbmax, lx, ly, pl, board, Mi, Mj = SimpleGame.initGame(server=(server, port)) try: nbmax, lx, ly, pl, board, Mi, Mj = main(nbmax, lx, ly, pl, board, Mi, Mj) except Exception as e: print "ERROR:%s" % e if USE_MUSIC: time.sleep(TIME_FADEOUT) pygame.mixer.music.stop() if USE_WINDOW: pygame.display.quit() ANSIColors.printc("\n\t/end/ <green>The game is done.<reset><white>\n") #END#