From 52cc55e611f6cfe891647d47b053b88cd79a77ee1b7d4b38652927676b8e1ea5 Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 28 Jul 2025 22:29:27 +0200 Subject: [PATCH] added image map generator, map cell's color and emoji legend, death players emoji and color --- bot.py | 8 ++-- bot_libs/commands_handling.py | 14 +++++- bot_libs/syms.py | 37 ++++++++++++++++ entities/gamemap.py | 81 ++++++++++++++++++++++++++++------- 4 files changed, 120 insertions(+), 20 deletions(-) diff --git a/bot.py b/bot.py index 119f81c..38ebbf1 100644 --- a/bot.py +++ b/bot.py @@ -24,7 +24,7 @@ async def bot_start(update, context): ['Init/Restart'], ['Add Player', 'Add random Players', 'Add random color Players'], ['Get Players', 'Get Alive Players', 'Get Death Players', 'Get Ranking Players',], - ['Simulate Day', 'Run Periodically', 'Show Map'] + ['Simulate Day', 'Run Periodically', 'Show Map UTF8', 'Show Map Image'] ] reply_markup= ReplyKeyboardMarkup(keyboard, one_time_keyboard=False, resize_keyboard=True) @@ -64,8 +64,10 @@ async def bot_commands(update, context): return await _cmd.cmd_add_random_players(context, update, chat_id) if text == 'Add random color Players': return await _cmd.cmd_add_random_color_players(context, update, chat_id) - if text == 'Show Map': - return await _cmd.cmd_show_game_map(context, update, chat_id) + if text == 'Show Map UTF8': + return await _cmd.cmd_show_game_map_unicode(context, update, chat_id) + if text == 'Show Map Image': + return await _cmd.cmd_show_game_map_image(context, update, chat_id) # special commands if text == 'upstart': return await _scmd.update_bot(update, context) diff --git a/bot_libs/commands_handling.py b/bot_libs/commands_handling.py index a6ac0a6..09c9fad 100644 --- a/bot_libs/commands_handling.py +++ b/bot_libs/commands_handling.py @@ -1,4 +1,5 @@ from utils import logs as _log +from bot_libs import syms as _bot_syms from bot_libs import player_handling as _bot_player from bot_libs import simulation as _bot_simulation from bot_libs import repeating as _bot_repeat @@ -61,11 +62,20 @@ async def cmd_get_ranking_players(context, update, chat_id): async def cmd_simulate_day(context, update, chat_id): return await _bot_simulation.simulate_day(context, chat_id) -async def cmd_show_game_map(context, update, chat_id): +async def cmd_show_game_map_unicode(context, update, chat_id): Arena= context.application.bot_data['arena'] Gamemap= Arena.get_map() rendered_map= Gamemap.get_renderized_map() - return await update.message.reply_text(rendered_map) + await update.message.reply_text(rendered_map) + return await update.message.reply_text(_bot_syms.MAP_UTF8_LEGEND, parse_mode='MarkdownV2') + +async def cmd_show_game_map_image(context, update, chat_id): + Arena= context.application.bot_data['arena'] + Gamemap= Arena.get_map() + image_map= Gamemap.get_image_map() + bot= update.get_bot() + await bot.send_photo(chat_id= chat_id, photo= image_map) + return await update.message.reply_text(_bot_syms.MAP_IMAGE_LEGEND, parse_mode='MarkdownV2') async def cmd_simulate_day_cron(context, update, chat_id): context.application.bot_data['ask_seconds'] = 1 diff --git a/bot_libs/syms.py b/bot_libs/syms.py index e46a3a1..e301cfb 100644 --- a/bot_libs/syms.py +++ b/bot_libs/syms.py @@ -75,3 +75,40 @@ COLORS_NAMES= [ "Pearl", "Pumpkin", "Navy", "Ultramarine", "Sapphire", "Desert", "Cherry", "Tulip", ] + + +MAP_UTF8_FIELD= '🟩' +MAP_UTF8_MOUNTAIN= '⛰️' +MAP_UTF8_PLAYER_MALE= '👨' +MAP_UTF8_PLAYER_FEMALE= '👧🏻' +MAP_UTF8_PLAYER_NONBINARY= '⚧️' +MAP_UTF8_DEATH_PLAYER= '💀' +MAP_UTF8_ITEM= '📦' + +MAP_UTF8_LEGEND= f"""*Legenda*: +\- *{MAP_UTF8_FIELD}*: Cella *libera* per muoversi +\- *{MAP_UTF8_MOUNTAIN}*: Bordo della mappa, *non raggiungibile* +\- *{MAP_UTF8_PLAYER_MALE}*: Posizione di un *giocatore Maschio* +\- *{MAP_UTF8_PLAYER_FEMALE}*: Posizione di una *giocatorice Femmina* +\- *{MAP_UTF8_PLAYER_NONBINARY}*: Posizione di un *giocatore non binario* +\- *{MAP_UTF8_DEATH_PLAYER}*: Posizione di un *giocatore morto* +\- *{MAP_UTF8_ITEM}*: Posizione di un *oggetto* \(non ancora implementato\) +""" + +MAP_IMAGE_FIELD= (0, 255, 0) # green +MAP_IMAGE_MOUNTAIN= (0, 0, 0) # black +MAP_IMAGE_PLAYER_MALE= (0, 0, 255) # blue +MAP_IMAGE_PLAYER_FEMALE= (255, 0, 0) # red +MAP_IMAGE_PLAYER_NONBINARY= (255, 255, 0) # yellow +MAP_IMAGE_DEATH_PLAYER= (160, 160, 160) # grey +MAP_IMAGE_ITEM= (255, 255, 255) # white + +MAP_IMAGE_LEGEND= """*Legenda*: +\- *Verde*: Cella *libera* per muoversi +\- *Nero*: Bordo della mappa, *non raggiungibile* +\- *Blue*: Posizione di un *giocatore Maschio* +\- *Rosso*: Posizione di una *giocatorice Femmina* +\- *Giallo*: Posizione di un *giocatore non binario* +\- *Grigio*: Posizione di un *giocatore morto* +\- *Bianco*: Posizione di un *oggetto* \(non ancora implementato\) +""" diff --git a/entities/gamemap.py b/entities/gamemap.py index 6dfb390..2f0b49c 100644 --- a/entities/gamemap.py +++ b/entities/gamemap.py @@ -1,6 +1,10 @@ import random as _random import copy as _copy +import io as _io +from PIL import Image as _Image +from PIL import ImageDraw as _ImageDraw from utils import logs as _logs +from bot_libs import syms as _bot_syms class BrSimMap(): @@ -11,13 +15,13 @@ class BrSimMap(): self.world_width= 10 #seems a reasonable width for smartphones larger maps would go on a new line self.world_height= 25 self.game_map= [] - self.field_sym= '🟩' - self.player_male_sym= '♂️' - self.player_female_sym= '♀️' - self.player_nonbinary_sym= '⚧️' - self.dead_player_sym= '💀' - self.item_sym= '📦' - self.mountain_sym = '⛰️' + self.field_sym= _bot_syms.MAP_UTF8_FIELD + self.player_male_sym= _bot_syms.MAP_UTF8_PLAYER_MALE + self.player_female_sym= _bot_syms.MAP_UTF8_PLAYER_FEMALE + self.player_nonbinary_sym= _bot_syms.MAP_UTF8_PLAYER_NONBINARY + self.dead_player_sym= _bot_syms.MAP_UTF8_DEATH_PLAYER + self.item_sym= _bot_syms.MAP_UTF8_ITEM + self.mountain_sym = _bot_syms.MAP_UTF8_MOUNTAIN self.init_map_matrix() self.init_players_coordinates() self.init_items_coordinates() @@ -47,7 +51,8 @@ class BrSimMap(): def populate_map(self): for player in self.players: p_coord_x, p_coord_y= player.get_player_coordinates() - if player.player_gender_is_male(): self.game_map[p_coord_y][p_coord_x]= self.player_male_sym + if not player.is_alive(): self.game_map[p_coord_y][p_coord_x]= self.dead_player_sym + elif player.player_gender_is_male(): self.game_map[p_coord_y][p_coord_x]= self.player_male_sym elif player.player_gender_is_female(): self.game_map[p_coord_y][p_coord_x]= self.player_female_sym else: self.game_map[p_coord_y][p_coord_x]= self.player_nonbinary_sym for item in self.items: @@ -55,23 +60,19 @@ class BrSimMap(): self.game_map[i_coord_y][i_coord_x]= self.item_sym def _set_coordinates(self, target): - x= _random.randint(1, self.world_width -2) # -2 because 1 cell is occupied by the mountain + x= _random.randint(1, self.world_width -2) # from 1 to width-2 because 1 cell is occupied by the mountain y= _random.randint(1, self.world_height -2) while self.get_map_matrix()[y][x] != self.field_sym: - print('init_players_coordinates: collision, regenerate coordinates') + _logs.log_debug('_set_coordinates: collision, regenerate coordinates') x= _random.randint(1, self.world_width -2) y= _random.randint(1, self.world_height -2) target.set_player_coordinates(x, y) def init_players_coordinates(self): - # XXX init random player.coord_x and player.coord_y (of course not already used coords) - # parse all self.players and define random coordinates (player.coord_x, and player.coord_y) for player in self.players: - self._set_coordinates(target) + self._set_coordinates(player) def init_items_coordinates(self): - # XXX init random item.coord_x and item.coord_y (of course not already used coords) - # parse all self.items and define random coordinates (item.coord_x, and item.coord_y) for item in self.items: self._set_coordinates(item) @@ -95,3 +96,53 @@ class BrSimMap(): res+= x res+= '\n' return res + + def get_image_map(self): + self.populate_map() + scale_x= 20 + scale_y= 20 + final_x= self.world_width * scale_x + final_y= self.world_height * scale_y + image = _Image.new('RGB', (final_x, final_y)) + draw = _ImageDraw.Draw(image) + + for y in range(self.world_height): + for x in range(self.world_width): + pixel= self.game_map[y][x] + if pixel == self.field_sym: + pixel_color= _bot_syms.MAP_IMAGE_FIELD + elif pixel == self.mountain_sym: + pixel_color= _bot_syms.MAP_IMAGE_MOUNTAIN + elif pixel == self.item_sym: + pixel_color= _bot_syms.MAP_IMAGE_ITEM + elif pixel == self.player_male_sym: + pixel_color= _bot_syms.MAP_IMAGE_PLAYER_MALE + elif pixel == self.player_female_sym: + pixel_color= _bot_syms.MAP_IMAGE_PLAYER_FEMALE + elif pixel == self.player_nonbinary_sym: + pixel_color= _bot_syms.MAP_IMAGE_PLAYER_NONBINARY + elif pixel == self.dead_player_sym: + pixel_color= _bot_syms.MAP_IMAGE_DEATH_PLAYER + + scaled_x_coord= x * scale_x + scaled_y_coord= y * scale_y + # if x == 1 distance from top-right is 20 (because everything is 20x bigger) + # then we want to draw a rectanghe 20x20 (instead of 1x1) + # this mean that if x == 1 (20px from top-right), x+1 == 2 (40px from top-right) + # the same for y + # this means that i keep the same factor proportions but 20x bigger + scaled_x_width= (x + 1) * scale_x + scaled_y_height= (y + 1) * scale_y + + draw.rectangle([scaled_x_coord, scaled_y_coord, scaled_x_width, scaled_y_height], + fill= pixel_color + ) + + # debug + #image.save('/tmp/battle_royale_map.png') + #image.show() + + bio = _io.BytesIO() + image.save(bio, 'PNG') + bio.seek(0) + return bio