1
0

Compare commits

...

6 Commits

Author SHA256 Message Date
andrea
e0dc558c27 try using a smaller map and no mountain to get more available space 2025-07-28 23:06:19 +02:00
andrea
29157e22d4 fix kills counter 2025-07-28 22:56:46 +02:00
andrea
53189e3fd8 outline cell on the image map to make is more clear 2025-07-28 22:39:17 +02:00
andrea
87c0cefb56 requirements 2025-07-28 22:30:31 +02:00
andrea
52cc55e611 added image map generator, map cell's color and emoji legend, death players emoji and color 2025-07-28 22:29:27 +02:00
andrea
56da6031f2 show map with players on telegram bot 2025-07-28 20:45:42 +02:00
9 changed files with 220 additions and 94 deletions

6
bot.py
View File

@@ -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']
['Simulate Day', 'Run Periodically', 'Show Map UTF8', 'Show Map Image']
]
reply_markup= ReplyKeyboardMarkup(keyboard, one_time_keyboard=False, resize_keyboard=True)
@@ -64,6 +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 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)

View File

@@ -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,6 +62,21 @@ 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_unicode(context, update, chat_id):
Arena= context.application.bot_data['arena']
Gamemap= Arena.get_map()
rendered_map= Gamemap.get_renderized_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
if 'ask_name' in context.application.bot_data:

View File

@@ -5,7 +5,8 @@ from bot_libs import syms as _bot_syms
async def add_player(update, context, chat_id, name):
_log.log_info(f'add_player: {chat_id} - {name}')
Arena= context.application.bot_data['arena']
if len(Arena.get_players()) >= 70: return # prevent message too long error
#if len(Arena.get_players()) >= 70: return # prevent message too long error
if len(Arena.get_players()) >= 20: return # maybe this should depend on the map dimension
Arena.add_player(name)
async def add_random_players(update, context, chat_id, colors_names= False):

View File

@@ -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\)
"""

View File

@@ -1,5 +1,6 @@
from entities import player as _player
from entities import event_picker as _events
from entities import gamemap as _map
from entities.items import weapons as _weapons
class BrSimArena():
@@ -14,6 +15,7 @@ class BrSimArena():
self.eventClass = _events.ArenaEventPicker(self.players)
self.init_players(players)
self.init_weapons(weapons)
self.Map= _map.BrSimMap(self.players, self.weapons)
def init_players(self, players):
if not players: return
@@ -110,10 +112,12 @@ class BrSimArena():
def add_player(self, name, inventory= None):
player= _player.BrSimPlayer(name, inventory)
self.players.append(player)
self.Map.add_player_to_map(player)
def add_weapon(self, weapon_type):
weapon= _weapons.BrSimWeapon(weapon_type)
self.weapons.append(weapon)
self.Map.add_item_to_map(item)
def get_players(self):
return self.players
@@ -124,3 +128,6 @@ class BrSimArena():
#XXX implement me
res.append(w)
return res
def get_map(self):
return self.Map

151
entities/gamemap.py Normal file
View File

@@ -0,0 +1,151 @@
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():
def __init__(self, players= None, items= None):
self.players= players or []
self.items= items or []
self.world_width= 10 #seems a reasonable width for smartphones larger maps would go on a new line
self.world_height= 10
self.game_map= []
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()
self.populate_map()
def init_map_matrix(self):
# show a matrix representing the game's map
# 🟩 is and empty cell
# (tomorrow we can choose different colors for different locations
# 🟠 this is a player (we could use different colors for different genders)
# 📦 this is an item (weapon or another item)
# 💀 this is icon when the player is dead
# ⛰️ this is icon for the mountain (We can prevent players from passing through the mountains and thus use them for map boundaries.)
width= []
#mon = []
#for i in range(self.world_width):
#mon.append(self.mountain_sym)
for i in range(self.world_width):
#if i == 0 or i == self.world_width - 1: width.append(self.mountain_sym)
#else: width.append(self.field_sym)
width.append(self.field_sym)
for i in range(self.world_height):
#if i == 0 or i == self.world_height - 1: self.game_map.append(mon)
#else: self.game_map.append(_copy.deepcopy(width))
self.game_map.append(_copy.deepcopy(width))
_logs.log_debug(f'init_map_matrix: {self.game_map}')
def populate_map(self):
for player in self.players:
p_coord_x, p_coord_y= player.get_player_coordinates()
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:
i_coord_x, i_coord_y= item.get_item_coordinates()
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) # 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:
_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):
for player in self.players:
self._set_coordinates(player)
def init_items_coordinates(self):
for item in self.items:
self._set_coordinates(item)
def add_player_to_map(self, player):
self.players.append(player)
self._set_coordinates(player)
def add_item_to_map(self, item):
self.items.append(item)
self._set_coordinates(item)
def get_map_matrix(self):
return self.game_map
def get_renderized_map(self):
res= ''
self.populate_map()
game_map= self.get_map_matrix()
for y in game_map:
for x in y:
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)
outline= '#000000'
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, outline= outline
)
# debug
#image.save('/tmp/battle_royale_map.png')
#image.show()
bio = _io.BytesIO()
image.save(bio, 'PNG')
bio.seek(0)
return bio

View File

@@ -1,91 +0,0 @@
import random as _random
import copy as _copy
from utils import logs as _logs
class BrSimMap():
def __init__(self, players= None, items= None):
self.players= players or []
self.items= items or []
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.init_map_matrix()
self.init_players_coordinates()
self.init_items_coordinates()
self.populate_map()
def init_map_matrix(self):
# show a matrix representing the game's map
# 🟩 is and empty cell
# (tomorrow we can choose different colors for different locations
# 🟠 this is a player (we could use different colors for different genders)
# 📦 this is an item (weapon or another item)
# 💀 this is icon when the player is dead
# ⛰️ this is icon for the mountain (We can prevent players from passing through the mountains and thus use them for map boundaries.)
width= []
mon = []
for i in range(self.world_width):
mon.append(self.mountain_sym)
for i in range(self.world_width):
if i == 0 or i == self.world_width - 1: width.append(self.mountain_sym)
else: width.append(self.field_sym)
for i in range(self.world_height):
if i == 0 or i == self.world_height - 1: self.game_map.append(mon)
else: self.game_map.append(_copy.deepcopy(width))
_logs.log_debug(f'init_map_matrix: {self.game_map}')
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
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:
i_coord_x, i_coord_y= item.get_item_coordinates()
self.game_map[i_coord_y][i_coord_x]= self.item_sym
def get_map_matrix(self):
return self.game_map
def get_renderized_map(self):
res= ''
game_map= self.get_map_matrix()
for y in game_map:
for x in y:
res+= x
res+= '\n'
return res
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:
x= _random.randint(1, self.world_width -2) # -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')
x= _random.randint(1, self.world_width -2)
y= _random.randint(1, self.world_height -2)
player.set_player_coordinates(x, y)
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:
x= _random.randint(1, self.world_width -2) # -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_items_coordinates: collision, regenerate coordinates')
x= _random.randint(1, self.world_width -2)
y= _random.randint(1, self.world_height -2)
item.set_item_coordinates(x, y)

View File

@@ -164,7 +164,7 @@ class BrSimPlayer():
weapon= self.get_equipped_weapon()
if weapon: msg+= f' con un {weapon.get_name}'
else: msg+= f' con un pugno'
self.kills+= 1
if not target.is_alive(): self.kills+= 1
return self.damage, msg
def get_item(self, item):

View File

@@ -1 +1,2 @@
python-telegram-bot[job-queue]==22.3
pillow==11.3.0