Source code for IA_Bomberman

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

""" This program is an experimental **BOT** for our *Bomberman multiplayer game*.

Textual example
---------------
  This show how the **textual mode** *looks like during the game* with *one* server and *3 bots*.
  The server is in the left top corner.
  In this example, the server have been *interrupted* with *Ctrl+C*.

.. image:: images/exemple_ia.png
   :scale: 78 %
   :align: center

Graphical example
------------------
  This show how the **graphical mode** *looks like during the game* with *one* server and *3 bots*.
  The server is in the left top corner.

.. image:: images/exemple_ia3.png
   :scale: 78 %
   :align: center

Why ?
=====

.. tip::
  This program is **not** designed to *kiss Kasparov ass*... :)

 It is design to be able to **play alone**, with *IA* for opponents.
  * That means, you can launch a few on your computer and then launch you client (and play with it),
  * Or, a server can launch in on his side, to reduce the *required number* of players, or simply make an automatic opponent (like the *"play against the computer"* options of *multiplayer* games).

How to launch it
----------------

 **Exactly like the client** (see `the BombermanClient module <BombermanClient.html>`_).
  For instance, to get the description :
   $ ./IA_Bomberman.py --help

  On option is available : the **frequency** of the bot.
  The frequency of the server is limited, but you can change the number of action send *each seconds* by the bot.
  For example, a reasonable value is 10 :
   $ ./IA_Bomberman.py --frequency 10

  And, like for the *client*, you can change the **port** and the **address** of the server to play with :
   $ ./IA_Bomberman.py --frequency 10 --server 137.124.67.13 --port 12882

About
-----

 For now, it is quite limited : the *bot* plays *randomly* !

 No complex analysis of the board is done, neither that complex heuristic to attack others players or breaking free walls.

 .. seealso::
    But this is in study !
"""

__author__='Lilian BESSON'	#: Automatically update with update__date__.sh, a Naereen Corp. (c) bash script.
__email__='lilian.besson[AT]normale.fr'
__version__='0.7b'	#: Version of this module
__date__='dim. 17/02/2013 at 17h:20m:13s '	#: The date of the file, automatically update with update__date__.sh, a Naereen Corp. (c) bash script.
try:
	import os, sys, time
	__date__ = time.strftime("%a %d/%m/%Y at %Hh:%Mm:%Ss", time.localtime(os.lstat(sys.argv[0]).st_mtime))
	del os, sys, time
except:	pass

from BombermanClient import *

##################################
#    Playing with directions     #
##################################

DIR = ['UP', 'DOWN', 'LEFT', 'RIGHT']

def randDir(no = []):
	tmpDir = copy.copy(DIR)
	for a in no:
		if a in tmpDir:
			tmpDir.remove(a)
	return random.choice(tmpDir)

def verti(move_to_send):
	return move_to_send in ['UP', 'DOWN']

def horiz(move_to_send):
	return move_to_send in ['RIGHT', 'LEFT']

def orthog_of_move(move_to_send):
	if   verti(move_to_send):	return random.choice(['RIGHT', 'LEFT'])
	elif horiz(move_to_send):	return random.choice(['UP', 'DOWN'])
	else: return randDir()

def opposite_of_move(move_to_send):
	if   move_to_send == 'UP':	return 'DOWN'
	elif move_to_send == 'RIGHT':	return 'LEFT'
	elif move_to_send == 'LEFT':	return 'RIGHT'
	elif move_to_send == 'DOWN':	return 'UP'
	else: return randDir()

def mean(l):
	if l:
		return sum(l) / len(l)
	else:
		return 0.0

def indices_of_players(board, player=None):
	indices_i = []
	indices_j = []
	for i, j, spot in board:
		for p in spot.players:
		    if player:
		      if p.id != player.id:
			indices_i.append(p.x)
			indices_y.append(p.y)
		    else:
			indices_i.append(p.x)
			indices_y.append(p.y)
	return (indices_i, indices_j)

def indice_moyen(board, player):
	i, j = indices_of_players(board, player)
	return int(mean(i)), int(mean(j))

def direction_moyenne(board, player):
	i, j = indice_moyen(board, player)
	x, y = player.x, player.y
	a = i - x
	b = j - y
	# vecteur (a,b)
	if abs(a) > abs(b):
		if a>0:
			return 'RIGHT'
		elif a<0:
			return 'LEFT'
		else:
			return random.choice(['RIGHT', 'LEFT'])
	elif abs(a) < abs(b):
		if b<0:
			return 'DOWN'
		elif b>0:
			return 'UP'
		else:
			return random.choice(['DOWN', 'UP'])
	else:
		if a<0: return randDir(no='RIGHT')
		if a>0: return randDir(no='LEFT')
		if b<0: return randDir(no='UP')
		if b>0: return randDir(no='DOWN')
		else:	randDir()

PROBA_BOMB = 0.15

FREQUENCE_IA = 5

##################################
##### Main loop for the game #####

[docs]def run_bot(board, pl, clock, player, num_thread=0): """ run_bot(board, pl, clock, player, num_thread=0) -> Exception raised. An experiment function (designed to be threaded), to make a *non*-interactive Bomberman Player. The goal is to have an automatic player. """ try: ANSIColors.printc(""" /run_bot/ <neg>Thread number %i<Neg> : initialized. Infinite loop (freq=%i) to display the board : %s """ % ( num_thread, CLOCK_FREQUENCY, str(board) )) ############################################################################### time_after = time.time() last_move = randDir() next_move = [] # FIXME ? assert( FREQUENCE_IA >= 1.0) assert( FREQUENCE_IA < 80.0) # Loop start ! while True: clock.tick(FREQUENCE_IA) time_before = time_after time_now = time.time() if (time_now - time_before) > (1.0 / FREQUENCE_IA): time_after = time_now ####################################################################### # Here is done the "IA" computation. if next_move: move_to_send = next_move.pop() else: if random.random() < PROBA_BOMB: move_to_send = 'BOMB' d = randDir() next_move.append(d) #: next_move.append(orthog_of_move(d)) else: move_to_send = randDir() #: move_to_send = direction_moyenne(board, player) #: next_move.append( randDir(no=move_to_send) ) ANSIColors.printc("<neg> IA : <Neg> I chosed move_to_send=<neg><u>%s<U><Neg>.\n" % move_to_send) ####################################################################### ANSIColors.printc("/str_of_move/ I'm transforming the move '<neg>%s<Neg>' into a valid message." % move_to_send) move_to_send = ParseMessageOut.str_of_move(move_to_send) ANSIColors.printc("/#/ <green>Player %s<white> try to send a message (<neg>%s<Neg>) to his server %s." % (player, move_to_send, str(player.info_server))) try: # Here is sent the move. player.send(move_to_send, verb or verb2) except Exception as useless3: ANSIColors.printc("<warning> <red>Failure<default>, when you (%s) try to send the message (<neg>%s<Neg>) (I received the exception %s)." % (str(player), move_to_send, str(useless3))) ############################################################################### # End of the main loop (shall not happen). except: sys.stderr.write(ANSIColors.sprint(""" <warning> /run_bot/ <neg>Thread number %i<Neg> : failed with exception <neg>%s<Neg>. /run_bot/ 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) ############### ##### End #####
if __name__ == '__main__': import ParseCommandArgs #: This variable is the preprocessor, given to description and epilog by ParseCommandArgs, #: * erase: to print with no colors. #: * sprint: to print with colors. preprocessor = ANSIColors.sprint if ANSIColors.ANSISupported else ANSIColors.erase #:preprocessor = __builtin__.str #:, if you want to *see* the balises. #: Generate the parser, with another module. parser = ParseCommandArgs.parser_default(\ description = '<green>IA_Bomberman<red>module<reset> and <blue>script<reset>.',\ epilog = """\n\ <yellow>About: ======<reset> This program is an experimental BOT for our <neg>multiplayer Bomberman Game<Neg> (MPRI 1-21 projet, 2013). This project is hosted here <u>https://bitbucket.org/vcohen/projet_reseau<U>. The doc for this project can be find here <u>http://perso.crans.org/~besson/publis/Bomberman/_build/html/<U>.""", \ version = __version__, date = __date__, author = __author__, \ preprocessor = preprocessor) #: Description for the part with '--file' and '--generate' options. group = parser.add_argument_group(preprocessor('<yellow>About network connection<reset>'), preprocessor("""\ <b>This program <u>is a client<U>. So, it *have* to be connected to a server, with a TCP port : <reset> """)) #: Remember that action can be used to many things. #: FIXME make required. group.add_argument("-s","--server", help = preprocessor("The <neg>address of the server<Neg> (eg '138.231.139.1', or 'bomberman-server.crans.org'). Default is %s." % SERVEUR_INIT)) #:, required = True) group.add_argument("-p","--port", type = int, help = preprocessor("The <neg>port<Neg> on which the connection to the server will be established (eg 9312, or 12882).\n\t This port *have* to be an <default>open port on your machine<reset> and have to be <default>the listened port of the server<reset> ! Default is %s." % PORT_INIT)) #:, required = True) #: Description for pseudo and color options. group = parser.add_argument_group(preprocessor('<yellow>About customisation<reset>'), preprocessor("""\ <b>With this program, <u>you will launch<U> a Bomberman BOT. You can set his frequency : """)) group.add_argument("-f","--frequency", type = int, help = preprocessor("The <neg>frequency<Neg> of moves for the IA player. Between 1 and 80. Default is %i." % FREQUENCE_IA)) #: For music. group = parser.add_mutually_exclusive_group() group.add_argument('--music', action='store_true', default = False, help = preprocessor(""" If <yellow>present<reset>, the program will <blue>launch some music<reset> during the game. <red>Still experimental.<reset>""")) group.add_argument('--nomusic', action='store_true', default = True, help = preprocessor(""" If <yellow>present<reset>, music will <blue>not be used<reset>. This is the <neg>default<Neg> comportment.""")) #: For X Window of PyGame. FIXME #: group = parser.add_mutually_exclusive_group() #: group.add_argument('--window', action='store_true', default = True, help = preprocessor(""" If <yellow>present<reset>, the program will <blue>use a graphical Window<reset> during the game. Still not perfect (update freezes sometime). This is the <neg>default<Neg> comportment.""")) #: group.add_argument('--nowindow', action='store_true', default = False, help = preprocessor(""" If <yellow>present<reset>, <blue>no window will be used<reset>. Can be usefull to play in textmod (but <red>PyGame is still required<reset> to Keybinding, sound etc), for example if you don't have a X Server.""")) #: For music effect. group = parser.add_mutually_exclusive_group() group.add_argument('--soundeffect', action='store_true', default = False, help = preprocessor(""" If <yellow>present<reset>, the program will <blue>use some music effect<reset> during the game, mainly when a bomb blows.<reset>""")) group.add_argument('--nosoundeffect', action='store_true', default = False, help = preprocessor(""" If <yellow>present<reset>, music effect will <blue>not be used<reset>. This is the <neg>default<Neg> comportment.""")) #: The parser is done, #: Use it to extract the args from the command line. args = parser.parse_args() ANSIColors.xtitle(".: IA Bomberman, v%s, made by %s :." % (__version__, __author__)) print_clear(".: Welcome in the IA Bomberman, v%s, made by %s. Last modification %s :." % (__version__, __author__, __date__)) verb = False verb2 = False # Print with ANSI escape code for colors if possible ANSIColors.ANSISupported = (not(args.noANSI) and ANSIColors.ANSISupported) or args.ANSI # Disable all escape codes for color to be generated if not(ANSIColors.ANSISupported): ColorOff() else: ColorOn() ANSIColors.printc("/initialization/ ANSI escape code for colors supports = <green>%s<white>." % ANSIColors.ANSISupported) # Print with non ASCII caracters for boxes if possible Board.UTFSupported = not(args.noUTF) and Board.UTFSupported ANSIColors.printc("/initialization/ UTF escape code for boxes supports = <green>%s<white>." % Board.UTFSupported) # Try to know if music is wanted. Default = False USE_MUSIC = (USE_MUSIC or args.music) and not(args.nomusic) # Try to know if window is wanted. Default = True. FIXME # USE_WINDOW = (USE_WINDOW or args.window) and not(args.nowindow) # Try to know if music effects are wanted. Default = False USE_SOUND_EFFECT = (USE_SOUND_EFFECT or args.soundeffect) and not(args.nosoundeffect) #: Set the server and the port. server = args.server if args.server else SERVEUR_INIT port = args.port if args.port else PORT_INIT try: FREQUENCE_IA = max(1, min(int(args.frequency), 80)) except: pass try: assert( FREQUENCE_IA >= 1.0) assert( FREQUENCE_IA < 80.0) except: ANSIColors.writec("\n<warning> <ERROR> I told you, the frequency <neg>have<Neg> to be between 1 and 80... You gave me this %s ! I'm using 1 instead....\n" % str(FREQUENCE_IA), file=sys.stderr) sys.stderr.flush() FREQUENCE_IA = 1 # Pseudo and color. pseudo = pseudos_IA[ (random.randint(1, FREQUENCE_IA) + os.getpid()) % len(pseudos_IA)]+"_IA_"+str(os.getpid()) color = ANSIColors.simpleColorList[ (random.randint(1, FREQUENCE_IA) + os.getpid()) % len(ANSIColors.simpleColorList)] ANSIColors.printc("/init/ <yellow>The game will run on the server : (%s:%i)<reset><white>." % (server, port)) if USE_NOTIFY: ANSIColors.notify("Phasis #1 of IA Bomberman is going to start (with the server %s:%i)" % (server, port), obj=".: Bomberman Bot :.", icon="bomberman.gif" ) ANSIColors.xtitle(".: Phasis #1 of IA Bomberman (connected to %s:%i) Freq=%i -- Pseudo=%s, Color=%s :." % (server, port, FREQUENCE_IA, pseudo, color)) # Launch the step 1. try: player, nbmax, lx, ly, pl, board, Mi, Mj, List_ReceivedMessages, List_SentMessages = waiting_room(server = (server, port), pseudo = pseudo, color = color) except Exception as e: ANSIColors.printc("<ERROR> <red> The waiting room <neg>died<Neg> !<default>. I received the last exception <neg>%s<Neg>." % str(e)) raise e # The game is initialized if USE_NOTIFY: ANSIColors.notify("Phasis #2 of IA Bomberman is going to start (with the server %s:%i). Frequency=%i." % (server, port, FREQUENCE_IA), obj=".: Bomberman Bot :.", icon="bomberman.gif" ) ANSIColors.xtitle(".: Phasis #2 of IA Bomberman (connected to %s:%i) Freq=%i -- Pseudo=%s, Color=%s, Id=%i :." % (server, port, FREQUENCE_IA, player.pseudo, player.color, player.id)) try: thread.start_new_thread( run_bot, (board, pl, pygame.time.Clock()), {'num_thread':2, 'player':player}) main(player, nbmax, lx, ly, pl, board, Mi, Mj, List_ReceivedMessages, List_SentMessages) ############################################################################### except KeyboardInterrupt: ANSIColors.printc(""" <warning> <red> The game is done<default>. <warning> I <yellow>guess<default> you closed it, probably with an EOF (<black>Ctrl+D<default>) or a SIGTERM signal (<black>Ctrl+C<default>). <warning> <green>Feel free to send any comment, suggestion or bug : <white> <u><neg>%s<reset>. """ % (__email__)) sys.stdout.flush() sys.stderr.flush() os._exit(1) ####################################################################### except socket.error as e: #FIXME ? ANSIColors.printc(""" <ERROR> <magenta>Connection closed !<default>. So the game died. The possible cause might be <neg>%s<Neg>. """ % str(e)) try: player.close() except: ANSIColors.printc(""" <ERROR> <magenta> I failed when I tried to close the player <neg>%s<Neg>. Cause nb2 = <neg>%s<Neg>... """ % (player, str(sys.exc_info()[1])) ) #: raise # FIXME. #: raise e sys.stderr.write(" The game is closing now... Thanks for using IA_Bomberman ! ... Exit code=2...") sys.stderr.flush() os._exit(2) ####################################################################### except Exception as e: ANSIColors.printc(""" <ERROR> <red><u> The game died badly !<U><default> I received the last exception <neg>%s<Neg>. """ % str(e)) raise e except: ANSIColors.printc(""" <ERROR> <red> The game is done<default>... I received the last exception <neg>%s<Neg>. """ % str(sys.exc_info()[1]) ) sys.stdout.flush() sys.stderr.flush() #: raise ####################################################################### # End that's it. ANSIColors.printc(""" <green><neg> The game is done<white>, and I didn't receive any unhandled exceptions in the end<reset> (good job !)... The second phasis seems to give <neg>you<Neg> as the winner. <black>I'm quiting nicely now... <green>Feel free to send any comment, suggestion or bug : <white> <u><neg>%s<reset>. """ % ( __email__ ) ) # Now, quit. os._exit(0)