1
0

Compare commits

...

69 Commits

Author SHA256 Message Date
andrea
5002890540 debug random map generator 2025-07-28 20:08:15 +02:00
andrea
9653965d2b show players and items on the map 2025-07-28 20:03:08 +02:00
andrea
37578437f6 clear error message for missing local_settings.py 2025-07-28 18:55:33 +02:00
frostbite
9b503872e8 fixed code 2025-07-28 14:16:03 +02:00
frostbite
e604743796 added end of the map 2025-07-28 12:50:52 +02:00
frostbite
4dcb87723d added mountain icon 2025-07-28 12:20:21 +02:00
frostbite
5c3e349cb6 added icon dead player 2025-07-28 12:12:43 +02:00
andrea
5e6816269b define players and items coordinates to be used on the map 2025-07-27 23:34:35 +02:00
andrea
a7fd66e9e4 define Map class with basics rendering and parsing methods 2025-07-27 23:23:21 +02:00
andrea
172b387dcc fix duplicate logs 2025-07-27 09:10:09 +02:00
andrea
4639d85f00 better loggings but i need to fix a duplicate log line error 2025-07-27 08:56:40 +02:00
andrea
24f0f3e3e1 daily logs with a new file hanlder 2025-07-27 08:40:01 +02:00
andrea
0bfa74c0e2 cleanings to make code more compact and readable 2025-07-27 08:17:12 +02:00
andrea
c82f31a101 less logs 2025-07-27 00:07:20 +02:00
andrea
b9161a052f fix logs command 2025-07-27 00:06:59 +02:00
andrea
bd0601e115 better log name 2025-07-26 23:59:35 +02:00
andrea
bee2e94f82 item 2025-07-26 23:10:51 +02:00
andrea
da0368d6c5 fix loop requesting seconds 2025-07-26 22:41:24 +02:00
andrea
f8a3e12909 less logs lines 2025-07-26 22:33:56 +02:00
111e5e04df Update README.md 2025-07-26 22:13:36 +02:00
1a0422414c Update README.md 2025-07-26 22:11:59 +02:00
af70d30279 Update README.md 2025-07-26 22:10:40 +02:00
308107fdc7 Update README.md 2025-07-26 22:09:47 +02:00
7c6e7fb7b4 Update README.md 2025-07-26 22:09:29 +02:00
876c55522d Update README.md 2025-07-26 22:08:58 +02:00
76a14d69ce Update README.md 2025-07-26 22:08:02 +02:00
aa46acc812 Update README.md 2025-07-26 22:06:36 +02:00
c1c8301e11 Update README.md 2025-07-26 22:06:04 +02:00
andrea
269724345d show logs inside the telegram bot 2025-07-26 21:58:38 +02:00
andrea
a9cb58100f README 2025-07-26 21:32:52 +02:00
andrea
8f3d6dfaff explicit logs 2025-07-26 21:25:38 +02:00
andrea
460a6ccdec clean bot and fix autostop with scheduled game 2025-07-26 20:58:35 +02:00
andrea
2bf65583b7 cleanings 2025-07-26 20:34:15 +02:00
andrea
5e530e1af1 move upstart code 2025-07-26 19:44:45 +02:00
andrea
ffbaa1c3b5 more logs 2025-07-26 19:30:13 +02:00
andrea
72ec279036 better logs format 2025-07-26 19:24:45 +02:00
andrea
f55afe5959 logging info 2025-07-26 19:13:32 +02:00
andrea
669701dbb7 typo 2025-07-26 19:12:00 +02:00
andrea
9b538e7d2f fix 2025-07-26 19:11:26 +02:00
andrea
4d3dd5c781 fix binary path 2025-07-26 19:09:34 +02:00
andrea
a948db5af3 add logging lib 2025-07-26 19:04:17 +02:00
andrea
11e4f9b2dd upstart bot 2025-07-26 18:38:40 +02:00
andrea
56e5e35b53 upstart bot 2025-07-26 18:28:46 +02:00
f55a6a2cdb Merge pull request 'master' (#5) from green/battle_royale_sim:master into master
Reviewed-on: Cryz/battle_royale_sim#5
2025-07-26 18:07:53 +02:00
andrea
4729a45c30 player stats review and minor bot improvements 2025-07-26 18:07:14 +02:00
5a6810e948 merge upstream 2025-07-26 17:57:18 +02:00
31637dc845 ranking as commands or at the end of each day 2025-07-26 17:56:54 +02:00
andrea
079dcd6639 README 2025-07-26 17:17:21 +02:00
andrea
65688b9100 add random health, random damage and show full player stats with emoji 2025-07-26 15:53:26 +02:00
andrea
308c9e3602 use internal method to get the player's gender 2025-07-26 15:36:22 +02:00
andrea
871b3d2ab1 fix player random extraction number 2025-07-26 15:35:45 +02:00
andrea
5f0d6ad3f5 fix bug where a death player is killed multiple time during the same day 2025-07-26 15:31:28 +02:00
andrea
551c2ef8ef cleans 2025-07-26 15:25:27 +02:00
andrea
4ce19d1a50 add kills counter random agility for each player and random init players 2025-07-26 15:20:03 +02:00
andrea
67804a394d split main bot lib into multiple smaller libs and improve end game message 2025-07-26 14:44:16 +02:00
andrea
415cdfc123 Merge branch 'green-master' 2025-07-26 14:08:24 +02:00
9f745bd451 player reputation for sponsor and alliance 2025-07-26 13:52:08 +02:00
andrea
fe356864c8 debug daily events, now all players plays during a day 2025-07-26 12:52:55 +02:00
andrea
c5a6b886d6 allow multiple players insertion 2025-07-26 12:38:06 +02:00
andrea
a7c6dd25df show winner player and break loop game if you executed the periodically game 2025-07-26 11:52:01 +02:00
andrea
888f22ac1b show better names on telegram message 2025-07-26 11:22:55 +02:00
andrea
0a6deafd32 improve message and add periodically run 2025-07-26 11:15:44 +02:00
andrea
100bb4fe53 implement bot keyboard with basics commands 2025-07-26 11:00:20 +02:00
andrea
f360fe4200 move syms location 2025-07-26 10:13:20 +02:00
andrea
0df960ea22 split player commands from main bot 2025-07-26 10:04:19 +02:00
andrea
634c651cb5 typo fix and better players output 2025-07-26 09:46:04 +02:00
Crystal
07dd5611e5 fix missing message return, and immediate first message of the day 2025-07-26 00:06:29 +02:00
Crystal
09c892a786 unused imports 2025-07-25 23:57:25 +02:00
Crystal
3d2a0bcc70 telegram bot more commands, and use the debug function to start a first day iteration 2025-07-25 23:41:44 +02:00
19 changed files with 817 additions and 143 deletions

View File

@@ -1,3 +1,51 @@
# battle_royale_sim
# Battle Royale Simulator (Hunger Games)
Hunger Games Simulator
Hunger Games Simulator for telegram.
You can find the Official Bot here:
@Brsimgen_Bot - https://t.me/Brsimgen_Bot
Feel free to fork the project and make your own Bot
# Bot Instrictions
1. start a chat with the bot: https://t.me/Brsimgen_Bot
2. open the bot keyboard
3. Press button "Init/Restart" to initialize the world
4. now is time to add players (max players is 70):
- you can manually add new players with button "Add Player" then you'll need to insert the player name (or multiple names comma separated)
- test
5. Now you can start with the game simulation
- press "Simulate Day" to make time elapse till the end of the day, and watch what's happend during the day
- press "Run Periodically" to make the bot do everything, you only need to insert the periodicity of the scheduler: for example 30
now every day will automatically end in 30 seconds
6. you can always check the game status with the buttons:
- Get Players: (see all the players)
- Get Alive Players: (list of alive players)
- Get Death Players: (list of death players)
- Get Ranking Players: (this is the leaderboard, based on number of kills)
# Fork Instructions
if you want to fork this project
remember to create a file
`local_settings.py`
on the same folder of
`bot.py`
file
containing
```
TOKEN = 'your-bot-token'
BOT_PATH= '/the/path/of/the/project'
BOT_EXEC_CMD= 'python3 bot.py' # or any other way you start the bot
SUPER_USERS= [ your_chat_id ]
```

117
bot.py
View File

@@ -1,67 +1,90 @@
import asyncio
import datetime
import pytz
from telegram.ext import Application, CommandHandler, MessageHandler, filters
import bot_syms as _botsyms
import main as _brsim
from telegram.ext import Application
from telegram.ext import CommandHandler
from telegram.ext import MessageHandler
from telegram.ext import filters
from telegram import ReplyKeyboardMarkup
from telegram import ReplyKeyboardRemove
from utils import logs as _log
from entities import arena as _arena
from bot_libs import special_commands as _scmd
from bot_libs import syms as _botsyms
from bot_libs import commands_handling as _cmd
async def loop_game(context):
chat_id = context.job.chat_id
if 'arena' in context.application.bot_data:
print(f'{chat_id}: Guarino ha trovato l\'arena')
pass
else:
print('Arena non trovata')
await update.message.reply_text('Che e\' successo? un Guarino ha rubato l\'arena, avvia una nuova partita con /start')
async def bot_start(update, context):
await update.message.reply_text(_botsyms.START_MSG)
if 'ask_name' in context.application.bot_data:
del(context.application.bot_data['ask_name'])
if 'ask_seconds' in context.application.bot_data:
del(context.application.bot_data['ask_seconds'])
keyboard = [
['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']
]
reply_markup= ReplyKeyboardMarkup(keyboard, one_time_keyboard=False, resize_keyboard=True)
chat_id = update.effective_chat.id
print(f'{chat_id}: Sto costruendo il mondo di gioco...')
Arena= _brsim.init_arena()
players= Arena.get_players()
weapons= Arena.get_weapons()
print(f'Ecco il mondo di gioco, questi sono i giocatori: {players}')
print(f'Ecco le armi disponibili nel mondo: {weapons}')
_log.log_debug(f'bot_start: {chat_id} - I\'m building the world\'s game...')
Arena= _arena.BrSimArena()
await update.message.reply_text('Ho creato il mondo di gioco')
await update.message.reply_text(f'Ecco la lista degli sfortunati avventurieri:\n{players}')
await update.message.reply_text(f'Queste le armi che avranno a disposizione nell\'arena:\n{weapons}')
await update.message.reply_text('Ho creato il mondo di gioco', reply_markup=reply_markup)
context.application.bot_data['arena'] = Arena
#context.job_queue.run_repeating(loop_game, interval=10, first=0, chat_id= chat_id)
timezone = pytz.timezone('Europe/Rome')
context.job_queue.run_daily(
loop_game,
time=datetime.time(hour=13, minute=0, second=0, tzinfo= timezone),
chat_id=chat_id,
name=str(chat_id)
)
print(f'Job giornaliero creato per la chat {chat_id}')
async def bot_commands(update, context):
text= update.message.text
chat_id = update.effective_chat.id
async def echo(update, context):
testo_ricevuto = update.message.text
_log.log_info(f'bot_command: {chat_id} - text received: {text}')
# init or restart the game
if text == 'Init/Restart':
return await bot_start(update, context)
# player game commands
if text == 'Add Player':
return await _cmd.cmd_add_player(context, update, chat_id)
if text == 'Get Players':
return await _cmd.cmd_get_players(context, update, chat_id)
if text == 'Get Alive Players':
return await _cmd.cmd_get_alive_players(context, update, chat_id)
if text == 'Get Death Players':
return await _cmd.cmd_get_death_players(context, update, chat_id)
if text == 'Get Ranking Players':
return await _cmd.cmd_get_ranking_players(context, update, chat_id)
if text == 'Simulate Day':
return await _cmd.cmd_simulate_day(context, update, chat_id)
if text == 'Run Periodically':
return await _cmd.cmd_simulate_day_cron(context, update, chat_id)
if text == 'Add random Players':
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)
# special commands
if text == 'upstart': return await _scmd.update_bot(update, context)
if text == 'logs': return await _scmd.show_logs(update, context)
# get user input
if context.application.bot_data.get('ask_name'):
return await _cmd.cmd_get_player_name(context, update, chat_id, text)
if context.application.bot_data.get('ask_seconds'):
return await _cmd.cmd_get_cron_time(context, update, chat_id, text)
_log.log_debug(f'bot_command: {chat_id} - sent this text: {text}')
await update.message.reply_text(_botsyms.WIP_MSG)
async def add_player(update, context):
name= " ".join(context.args)
print(f'sto aggiungendo il giocatore {name} all\'arena')
_brsim.BrSimArena
Arena= context.application.bot_data['arena']
Arena.add_player(name)
print(f'Giocatori: {Arena.get_players()}')
await update.message.reply_text(f'Ecco i giocatori presenti nel mondo do gioco: \n{Arena.get_players()}')
def main():
application = Application.builder().token(_botsyms.TOKEN).build()
application.add_handler(CommandHandler('start', bot_start))
application.add_handler(CommandHandler('addplayer', add_player))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, bot_commands))
print('Bot in esecuzione...')
_log.log_info('main: Bot is running...')
application.run_polling()
if __name__ == '__main__':

View File

@@ -0,0 +1,68 @@
from utils import logs as _log
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
### parse user input
async def _cmd_list_of_players(context, update, chat_id):
Arena= context.application.bot_data['arena']
players= [p.get_name_and_stats() for p in Arena.get_players()]
players_str= '\n'.join(players)
return await update.message.reply_text(f'Ecco i {len(players)} giocatori presenti nel mondo do gioco: \n{players_str}')
async def cmd_get_player_name(context, update, chat_id, text):
_log.log_info(f'cmd_get_player_name: {chat_id} - Collected Player Name {text}')
if not 'ask_name' in context.application.bot_data: return
del(context.application.bot_data['ask_name'])
players= text.split(',')
for player in players:
await _bot_player.add_player(update, context, chat_id, player.strip())
return await _cmd_list_of_players(context, update, chat_id)
async def cmd_get_cron_time(context, update, chat_id, text):
_log.log_info(f'cmd_get_cron_time: {chat_id} - User Wants to auto-run the game every {text} seconds')
try: text= int(text)
except: return
seconds= max(1, text)
if 'ask_seconds' in context.application.bot_data:
del(context.application.bot_data['ask_seconds'])
return await _bot_repeat.start_loop_game(update, context, seconds)
### basic commands handling
async def cmd_add_random_players(context, update, chat_id):
await _bot_player.add_random_players(update, context, chat_id, colors_names= False)
return await _cmd_list_of_players(context, update, chat_id)
async def cmd_add_random_color_players(context, update, chat_id):
await _bot_player.add_random_players(update, context, chat_id, colors_names= True)
return await _cmd_list_of_players(context, update, chat_id)
async def cmd_add_player(context, update, chat_id):
context.application.bot_data['ask_name'] = 1
if 'ask_seconds' in context.application.bot_data:
del(context.application.bot_data['ask_seconds'])
return await update.message.reply_text('Inserisci il Nome del giocatore (o piu\' nomi separati da virgola)')
async def cmd_get_players(context, update, chat_id):
return await _bot_player.get_players(update, context, chat_id)
async def cmd_get_alive_players(context, update, chat_id):
return await _bot_player.get_alive_players(update, context, chat_id)
async def cmd_get_death_players(context, update, chat_id):
return await _bot_player.get_death_players(update, context, chat_id)
async def cmd_get_ranking_players(context, update, chat_id):
return await _bot_player.get_ranking_players(update, context, chat_id)
async def cmd_simulate_day(context, update, chat_id):
return await _bot_simulation.simulate_day(context, chat_id)
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:
del(context.application.bot_data['ask_name'])
return await update.message.reply_text('Inserisci il numero di secondi, ad esempio \n(60 = 1 minuto)(600 = 10 minuti)\n(3600 = 1 ora)\n(86400 = 1 giorno)')

View File

@@ -0,0 +1,52 @@
import random as _rand
from utils import logs as _log
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
Arena.add_player(name)
async def add_random_players(update, context, chat_id, colors_names= False):
if colors_names: names= _bot_syms.COLORS_NAMES
else: names= _bot_syms.RANDOM_NAMES
max_players= len(names)
min_players= min(7, max_players)
players_num= _rand.randint(min_players, max_players)
_rand.shuffle(names)
lucky_players= _rand.sample(names, players_num)
_log.log_info(f'add_random_players: {chat_id} - extracting {players_num} random players for the game')
for name in lucky_players: await add_player(update, context, chat_id, name)
async def get_players(update, context, chat_id):
Arena= context.application.bot_data['arena']
players= [p.get_name_and_stats() for p in Arena.get_players()]
players_str= '\n'.join(players)
_log.log_info(f'get_players: {chat_id} - {players_str}')
await update.message.reply_text(f'Ecco i giocatori presenti nel mondo do gioco: \n{players_str}')
async def get_alive_players(update, context, chat_id):
Arena= context.application.bot_data['arena']
alive= [a.get_name_and_stats() for a in Arena.get_alive_players()]
alive_str= '\n'.join(alive)
_log.log_info(f'get_alive_players: {chat_id} - {alive_str}')
await update.message.reply_text(f'Ecco i giocatori ancora vivi: \n{alive_str}')
async def get_death_players(update, context, chat_id):
Arena= context.application.bot_data['arena']
death= [d.get_name_and_stats() for d in Arena.get_death_players()]
death_str= '\n'.join(death)
_log.log_info(f'get_death_players: {chat_id} - {death_str}')
await update.message.reply_text(f'Ecco i giocatori morti x.x: \n{death_str}')
async def get_ranking_players(update, context, chat_id):
Arena = context.application.bot_data['arena']
leaderboard_text = Arena.get_ranking()
_log.log_info(f'get_ranking_players: {chat_id} - {leaderboard_text}')
await update.message.reply_text(
f'{leaderboard_text}',
parse_mode='Markdown'
)

24
bot_libs/repeating.py Normal file
View File

@@ -0,0 +1,24 @@
from utils import logs as _log
from bot_libs import simulation as _bot_sim
async def _loop_game(context):
chat_id = context.job.chat_id
_log.log_info(f'_loop_game: {chat_id} - run game simulation day')
return await _bot_sim.simulate_day(context, chat_id)
async def start_loop_game(update, context, seconds):
await update.message.reply_text(f'Ok capo!! giochero\' per te ogni {seconds}s')
chat_id = update.effective_chat.id
if 'arena' not in context.application.bot_data:
_log.log_info(f'start_loop_game: {chat_id} - Arena not found')
await update.message.reply_text(f'Arena non trovata, avviare con /start')
return
Arena= context.application.bot_data['arena']
if len(Arena.get_players()) < 2:
_log.log_info(f'start_loop_game: {chat_id} - Not enough player to start the match')
await update.message.reply_text(f'Servono almeno 2 giocatori. Ecco i giocatori presenti nel mondo do gioco: \n{Arena.get_players()}')
return
context.job_queue.run_repeating(_loop_game, interval= seconds, first=1, chat_id= chat_id)
_log.log_info(f'start_loop_game: {chat_id} - Cron job started')

41
bot_libs/simulation.py Normal file
View File

@@ -0,0 +1,41 @@
import debug as _dbg
from utils import logs as _log
def get_winner(context, Arena, chat_id):
winner= Arena.get_alive_players()[0]
try:
context.job.schedule_removal()
_log.log_info(f'simulate_day: {chat_id} Loop removed')
except: pass
msg= f'{winner.get_name_and_stats()} Vince la cruenta battaglia '
msg+= f'uccidendo {winner.get_kills()} giocatori '
msg+= f'e schivando {winner.get_dodges()} colpi nemici, e vive felice e '
if winner.player_gender_is_male():
msg+= 'contento con Guarino'
elif winner.player_gender_is_female():
msg+= 'contenta con Guarino'
else:
msg+= 'content# con Guarino'
return msg
async def simulate_day(context, chat_id):
if 'arena' not in context.application.bot_data:
_log.log_info('simulate_day: {chat_id} Arena not Found')
await context.bot.send_message(chat_id, 'Che e\' successo? un Guarino ha rubato l\'arena, avvia una nuova partita con /start')
return
Arena= context.application.bot_data['arena']
if len(Arena.get_alive_players()) <= 1: return await context.bot.send_message(chat_id, 'Il gioco e\' finito, Grazie per aver giocato!')
await context.bot.send_message(chat_id, f'Giorno #{Arena.day}')
msg= _dbg.play_one_day_debug(Arena)
await context.bot.send_message(chat_id, msg)
#Print the ranking each day
msg= Arena.get_ranking()
await context.bot.send_message(chat_id, msg)
if len(Arena.get_alive_players()) == 1:
msg= get_winner(context, Arena, chat_id)
return await context.bot.send_message(chat_id, msg)

View File

@@ -0,0 +1,50 @@
import os as _os
from utils import logs as _log
from bot_libs import syms as _botsyms
async def _pull_repo(update):
err= _os.system(f'cd {_botsyms.BOT_PATH}; /usr/bin/git pull')
if err:
_log.log_error(f'update_bot: {chat_id} error {err} while trying to update the app')
await update.message.reply_text('Errore durante l\'aggiornamento del Bot')
return err
async def _upstart_service(update):
err= _os.system(_botsyms.BOT_EXEC_CMD)
# this error is fake, probably due to systemd restart that make the bot istance broke
#if err:
#_log.log_error(f'update_bot: {chat_id} error {err} while trying to upstart the app')
#return await update.message.reply_text('Errore durante il riavvio del Bot')
async def update_bot(update, context):
chat_id = update.effective_chat.id
if update.message.chat.id not in _botsyms.SUPER_USERS:
return _log.log_warning(f'update_bot: user {chat_id} not allowed')
await update.message.reply_text('Sto aggiornando il Bot...')
_log.log_info(f'update_bot: {chat_id} bot is updating...')
err= await _pull_repo(update)
if err: return
err= await _upstart_service(update)
if err: return
_log.log_info(f'update_bot: {chat_id} bot successfully updated')
await update.message.reply_text('Bot aggiornato e riavviato!')
async def show_logs(update, context):
chat_id = update.effective_chat.id
if update.message.chat.id not in _botsyms.SUPER_USERS:
return _log.log_warning(f'open_logs: user {chat_id} not allowed')
await update.message.reply_text('Sto provando ad aprire i log...')
_log.log_debug(f'open_logs: {chat_id} trying opening logs...')
try:
game_log= _os.path.expanduser(f'{_log.get_log_name()}')
with open(game_log, 'r') as _log_file:
lines= _log_file.readlines()[-30:]
log_content= '\n'.join(lines)
await update.message.reply_text(f"Contenuto del log:\n{log_content}")
except Exception as e:
await update.message.reply_text(f"Errore nella lettura del file di log: {str(e)}")

77
bot_libs/syms.py Normal file
View File

@@ -0,0 +1,77 @@
import os as _os
_MISSING_LOCAL_SETTINGS= """
=============== ERROR ===============
Missing local configurations:
create: local_settings.py
with these settings:
TOKEN= '<this_is_your_token_id>' # created using https://t.me/BotFather
BOT_PATH= '/path/of/your/repo/directory' # used for upstart command
LOG_PATH= '/path/where/you/place/logs' # used to save daily logs
BOT_EXEC_CMD= 'python /path/of/your/repo/directory/bot.py' # the command you use to launch the bot instance
SUPER_USERS= [<chat_id>] # this is the superuser, allowed to use special commands (upstart and logs)
=====================================
"""
try:
from local_settings import TOKEN as _token
TOKEN= _token
except: print(_MISSING_LOCAL_SETTINGS)
try:
from local_settings import BOT_PATH as _bot_path
BOT_PATH= _os.path.expanduser(_bot_path)
except: BOT_PATH= ''
try:
from local_settings import LOG_PATH as _log_path
LOG_PATH= _os.path.expanduser(_log_path)
except: LOG_PATH= '/tmp'
try:
from local_settings import BOT_EXEC_CMD as _bot_exec_cmd
BOT_EXEC_CMD= _bot_exec_cmd
except: BOT_EXEC_CMD= None
try:
from local_settings import SUPER_USERS as _superusers
SUPER_USERS= _superusers
except: SUPER_USERS= []
START_MSG= """Benvenuto nel crudele mondo di Battle Royal Simulator,
La tua avventura e quella dei tuoi compagni inizia qui.
Questo Bot simula Hunger Games,
Tu e i tuoi compagni, sarete catapultati in questo mondo,
ma solo 1 di voi riuscira' a salvarsi.
Uccidi o sarai tu ad essere ucciso
"""
WIP_MSG= "Ehi, mio padre mi sta ancora finendo di creare, abbi pazienza, che fretta hai di entrare in questo mondo per morire?"
RANDOM_NAMES = [
"Aeliana", "Thorne", "Kael", "Seraphine", "Jaxon", "Lyra", "Darius", "Elowen",
"Zander", "Nyssa", "Orion", "Vesper", "Kieran", "Isolde", "Riven", "Calista",
"Draven", "Mira", "Zephyr", "Selene", "Ashen", "Talia", "Finnian", "Aria",
"Kaelan", "Liora", "Soren", "Elara", "Thalia", "Jett", "Cressida", "Lucian",
"Freya", "Ronan", "Niamh", "Kellan", "Zara", "Dorian", "Amara", "Jace",
"Elysia", "Caius", "Sable", "Alaric", "Veda", "Quinn", "Thorne", "Lirael",
"Rhea", "Kade", "Isadora", "Ash", "Nyx", "Cassian", "Elowen", "Tamsin",
"Rylan", "Faye", "Jorah", "Sienna", "Kieran", "Astra", "Zane", "Lyric",
"Dax", "Ember", "Orion", "Selah", "Juno", "Kaia", "Thorne", "Vespera",
"Riven", "Caden", "Liora", "Soren", "Elara", "Talia", "Jett", "Freya",
"Ronan", "Niamh", "Kellan", "Zara", "Dorian", "Amara", "Jace", "Elysia",
"Caius", "Sable", "Alaric", "Veda", "Quinn", "Thorne", "Lirael", "Rhea",
"Kade", "Isadora", "Ash", "Nyx",
]
COLORS_NAMES= [
"Yellow", "Grey", "Violet", "Black", "Lime", "Ruby", "Avocado", "Crystal",
"Pink", "Maize", "Coral", "Jade", "Platinum", "Emerald", "Carmine", "Nickel",
"Chocolate", "Slate", "Turquoise", "Silver", "Teal", "Jet", "Ivory", "Cobalt",
"Vermillion", "Aero", "Orange", "Rhythm", "Amber", "Olive", "Sepia", "Cyan",
"Green", "Ochre", "Denim", "Erin", "Fuchsia", "Aqua", "Iceberg", "Blue",
"Canary", "Red", "Mint", "Scarlet", "Coffee", "Indigo", "Mystic", "Rose",
"Pearl", "Pumpkin", "Navy", "Ultramarine", "Sapphire", "Desert", "Cherry",
"Tulip",
]

View File

@@ -1,13 +0,0 @@
from local_settings import TOKEN as _token
TOKEN= _token
START_MSG= """Benvenuto nel crudele mondo di Battle Royal Simulator,
La tua avventura e quella dei tuoi compagni inizia qui.
Questo Bot simula Hunger Games,
Tu e i tuoi compagni, sarete catapultati in questo mondo,
ma solo 1 di voi riuscira' a salvarsi.
Uccidi o sarai tu ad essere ucciso
"""
WIP_MSG= "Ehi, mio padre mi sta ancora finendo di creare, abbi pazienza, che fretta hai di entrare in questo mondo per morire?"

View File

@@ -1,9 +1,8 @@
import time as _time
import random as _rand
import main as _main
from entities import weapon_syms as _wsyms
def init_debug():
def _debug_data():
players= [
{
'name': 'Elara',
@@ -24,14 +23,46 @@ def init_debug():
'name': 'Seraphina',
},
]
#w= _wsyms.KNIFE
##weapons= [{_wsyms.WEAPONS[w]['name' ]: 1}]
#weapons= [{w: 1}]
weapons= []
Arena= _main.init_arena(players, weapons)
print(f'Players: {Arena.get_players()}')
print(f'Weapons: {Arena.get_weapons()}')
return Arena
def _end_game_debug(alive_players, day):
last_player= alive_players[0]
msg= f'{last_player.get_name()} sopravvive e vince dopo {day} lunghi Giorni, conquistando l\'amore eterno di Guarino'
print(msg)
return msg
def play_one_day_debug(Arena):
if not Arena.get_players(): return
print(f'Giorno #{Arena.day}')
alive_players= Arena.get_alive_players()
if len(alive_players) == 1:
day= Arena.day
return _end_game_debug(alive_players, day)
daily_events= []
_rand.shuffle(alive_players)
for p_one in alive_players:
if not p_one.is_alive(): continue # he could be dead during this day cycle
p_two= _rand.sample(Arena.get_alive_players(), 1)[0]
while p_one.get_id() == p_two.get_id():
p_two= _rand.sample(Arena.get_alive_players(), 1)[0]
_dmg, msg= p_one.attack(p_two)
daily_events.append(msg)
Arena.next_day()
res= '\n'.join(daily_events)
return res
#p_one, p_two= _rand.sample(alive_players, 2)
#_dmg, msg= p_one.attack(p_two)
#return msg
def init_debug_loop():
Arena= _debug_data()
while (len(Arena.get_alive_players()) > 1):
alive_players= Arena.get_alive_players()
@@ -47,5 +78,12 @@ def init_debug():
last_player= Arena.get_alive_players()[0]
print(f'{last_player.get_name()} sopravvive e vince dopo {Arena.day} lunghi Giorni, conquistando l\'amore eterno di Guarino')
def debug_random_map():
from entities import map as _map;
from entities import player;
M= _map.BrSimMap(players= [player.BrSimPlayer(i) for i in range(20)]);
res= M.get_renderized_map()
print(res)
if __name__ == '__main__':
init_debug()

View File

@@ -1,18 +1,27 @@
from entities import player as _player
from entities import weapons as _weapons
from entities import weapon_syms as _wsyms
from entities import event_picker as _events
from entities.items import weapons as _weapons
class BrSimArena():
# players = [{'name': name, 'inventory': default_inventory, other_stats}]
# weapons = [{WEAPON.KNIFE: quantity}, etc...] # this is the whole quantity of the items available on the world
def __init__(self, players, weapons):
def __init__(self, players= None, weapons= None):
self.day= 1
self.players= [_player.BrSimPlayer(p['name'], p.get('inventory')) for p in players]
self.players= []
self.weapons= []
self.eventClass = _events.ArenaEventPicker(self.players)
self.init_players(players)
self.init_weapons(weapons)
def init_players(self, players):
if not players: return
for player in players:
self.add_player(player['name'], player.get('inventory'))
def init_weapons(self, weapons):
if not weapons: return
for weapon in weapons:
for wtype, quantity in weapon.items():
for i in range(quantity): self.weapons.append(_weapons.BrSimWeapon(wtype))
@@ -41,6 +50,54 @@ class BrSimArena():
res.append(p)
return res
def sort_players_by_kills_and_health(self, players):
def player_sort_key(player):
kills= player.get_kills()
health= player.get_health()
#Negative values to sort in decr mode
return (-kills, -health)
return sorted(players, key=player_sort_key)
def get_ranking(self):
medals = ['🥇', '🥈', '🥉']
leaderboard = []
leaderboard.append('🏆 *Classifica attuale:* \n')
alive_players= self.get_alive_players();
alive_sorted= self.sort_players_by_kills_and_health(alive_players)
death_players= self.get_death_players()
death_sorted= self.sort_players_by_kills_and_health(death_players)
# Alive Players
alive_players= self.get_alive_players();
alive_sorted= self.sort_players_by_kills_and_health(alive_players)
for index, player in enumerate(alive_sorted):
name = player.get_name()
kills = player.get_kills()
health = '♥️' * player.get_health()
if index < 3:
position = medals[index]
else:
position = f"{index + 1}."
line = f"{position} {name} - {kills} uccision{'i' if kills != 1 else 'e'}, {health}"
leaderboard.append(line)
# Death players:
death_players= self.get_death_players()
death_sorted= self.sort_players_by_kills_and_health(death_players)
if death_sorted:
leaderboard.append("\n-- GIOCATORI ELIMINATI --")
for player in death_sorted:
name = player.get_name()
kills = player.get_kills()
health = player.get_health()
line = f"💀 {name} - {kills} uccision{'i' if kills != 1 else 'e'}"
leaderboard.append(line)
return '\n'.join(leaderboard)
def do_random_event(self):
#XXX random player does random action according to his inventory health, wounds, available weapons on the world, etc...
pass
@@ -54,11 +111,12 @@ class BrSimArena():
player= _player.BrSimPlayer(name, inventory)
self.players.append(player)
def add_weapon(self, weapon_type):
weapon= _weapons.BrSimWeapon(weapon_type)
self.weapons.append(weapon)
def get_players(self):
res= []
for p in self.players:
res.append(p.get_data())
return res
return self.players
def get_weapons(self):
res= []

29
entities/items/item.py Normal file
View File

@@ -0,0 +1,29 @@
import random as _random
class BrSimItem():
def __init__(self):
self.coord_x= 0
self.coord_y= 0
def get_name(self):
return self.name
def get_item_type(self):
return self.item_type
def get_weight(self):
return self.weight
def is_weapon(self):
return False
def is_cure(self):
return False
def get_item_coordinates(self):
return self.coord_x, self.coord_y
def set_item_coordinates(self, x, y):
self.coord_x= x
self.coord_y= y

View File

@@ -1,6 +1,5 @@
KNIFE= 1
GUN= 2
BOMB= 3
ARCH= 2
SHORT_RANGE= 1
FAR_RANGE= 2
@@ -14,7 +13,7 @@ WEAPONS= {
'ammons': -1, # -1, no limit
'range': SHORT_RANGE,
},
GUN: {
ARCH: {
'weight': 2,
'name': 'gun',
'damage': 3,
@@ -22,12 +21,4 @@ WEAPONS= {
'ammons': 10, # -1, no limit
'range': FAR_RANGE,
},
BOMB: {
'weight': 2,
'name': 'bomb',
'damage': 10,
'miss_chance': 5, # from 0 to 100, this is the probably to miss the hit
'ammons': 1,
'range': FAR_RANGE,
},
}

View File

@@ -1,10 +1,11 @@
import random as _random
from entities import weapon_syms as _syms
from entities.items import item as _item
from entities.items import weapon_syms as _syms
class BrSimWeapon():
class BrSimWeapon(_item.BrSimItem):
def __init__(self, wtype):
self.weapon= _syms.WEAPONS[wtype]
def __init__(self, wtype= None):
self.weapon= _syms.WEAPONS[wtype or _random.randint(1,2)]
self.name= self.weapon['name']
self.damage= self.weapon['damage']
self.weight= self.weapon['weight']

91
entities/map.py Normal file
View File

@@ -0,0 +1,91 @@
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

@@ -6,18 +6,41 @@ class BrSimPlayer():
def __init__(self, name, inventory= None):
self.id= str(_uuid.uuid4())
self.name= name
self.health= 1
self.stats= ''
self.health= _random.randint(1,3)
self.inventory= inventory or []
self.damage= 1 # this is the punch damage amount
self.damage= _random.randint(1,2) # this is the punch damage amount
self.max_weight= 5 # this is the max inventory weight
self.agility= 10 # chance to avoid an hit
self.agility= _random.randint(1,3) # chance to avoid an hit
self.kills= 0 # track the number of kills
self.accused_damage= 0
self.survived_days= 0 # track the number of the survived days
self.dodges= 0
self.equipped_weapon= None
self.gender= _random.sample(['m', 'f', '-'], 1)[0] # for now get a random gender
self.reputation= 50 #Like RDR2 the player can be evil(0) or good(100). This should influence the sponsors and internal alliance
self.coord_x= 0
self.coord_y= 0
### control methods
def get_player_coordinates(self):
return self.coord_x, self.coord_y
def set_player_coordinates(self, x, y):
self.coord_x= x
self.coord_y= y
def get_name_and_stats(self):
health= '♥️' * self.health or '☠️'
strength= '⚔️' * self.damage
agility= '🚀' * self.agility
if self.player_gender_is_male(): gender= ''
elif self.player_gender_is_female(): gender= ''
else: gender= ''
name= f'{self.name} {gender} {health} {strength} {agility}'
return name
def get_id(self):
return self.id
@@ -27,98 +50,131 @@ class BrSimPlayer():
def get_gender(self):
return self.gender
def player_gender_is_male(self):
return self.gender == 'm'
def player_gender_is_female(self):
return self.gender == 'f'
def player_gender_is_not_binary(self):
return self.gender == '-'
def get_inventory(self):
return self.inventory
def get_inventory_weight(self):
weight= 0
for inv in self.get_inventory():
weight+= inv.get_weight()
for item in self.get_inventory():
weight+= item.get_weight()
return weight
def get_dodges(self):
return self.dodges
def get_max_weight(self):
return self.max_weight
def get_health(self):
return self.health
def get_equipped_weapon(self):
return self.equipped_weapon
def get_damage(self):
if not self.equipped_weapon: return self.damage
return self.equipped_weapon.damage
weapon= self.get_equipped_weapon()
if not weapon: return self.damage
return weapon.get_damage()
def get_agility(self):
return self.agility
def get_reputation(self):
return self.reputation
def get_data(self):
return {
'id': self.get_id(),
'name': self.get_name(),
'name_stats': self.get_name_and_stats(),
'gender': self.get_gender(),
'inventory': self.get_inventory(),
'inventory_weight': self.get_inventory_weight(),
'health': self.get_health(),
'damage': self.get_damage(),
'agility': self.get_agility(),
'reputation': self.get_reputation(),
}
def is_alive(self):
return self.health > 0
def get_kills(self):
return self.kills
### player actions
def _equip_weapon(self):
if not self.inventory: return
available_weapons= []
for inv in self.get_inventory():
for item in self.get_inventory():
# XXX
# i don't know yet if this is ok,
# we'll see it when weapon and items are defined
if not inv.damage: continue
available_weapons.append(inv)
# maybe we need item.is_weapon() method
if not item.damage: continue
available_weapons.append(item)
self.equipped_weapon= random.sample(available_weapons, 1)[0]
def dodge(self):
# maybe depend on the attack, if it is a gun shot it's quite impossible to dodge
rnd= _random.randint(0, 100)
if rnd < self.agility: return True
rnd= _random.randint(0, 10)
if rnd < self.agility:
self.dodges+= 1
return True
return False
def accuses_damage(self, damage):
self.health-= damage
self.accused_damage+= damage
if self.health > 0: return self.get_health()
self.health= 0
if self.get_gender() == 'm':
print(f'[{self.get_name()}]: Guarino, perdonami se sono morto x.x')
elif self.get_gender() == 'f':
print(f'[{self.get_name()}]: Guarino, perdonami se sono morta x.x')
if self.player_gender_is_male():
print(f'[{self.get_name_and_stats()}]: Guarino, perdonami se sono morto x.x')
elif self.player_gender_is_female():
print(f'[{self.get_name_and_stats()}]: Guarino, perdonami se sono morta x.x')
else:
print(f'[{self.get_name()}]: Guarino, perdonami se sono mort* x.x')
print(f'[{self.get_name_and_stats()}]: Guarino, perdonami se sono mort* x.x')
return damage
def attack(self, target):
self._equip_weapon()
if target.dodge():
if target.get_gender() == 'm':
print(f'Ehhhh voleviiii!!! sei lentoo! {target.get_name()} schiva il colpo di {self.get_name()}')
elif target.get_gender() == 'f':
print(f'Ehhhh voleviiii!!! sei lentaa! {target.get_name()} schiva il colpo di {self.get_name()}')
if target.player_gender_is_male():
msg= f'Ehhhh voleviiii!!! sei lentoo! {target.get_name_and_stats()} schiva il colpo di {self.get_name_and_stats()}'
elif target.player_gender_is_female():
msg= f'Ehhhh voleviiii!!! sei lentaa! {target.get_name_and_stats()} schiva il colpo di {self.get_name_and_stats()}'
else:
print(f'Ehhhh voleviiii!!! sei lent##! {target.get_name()} schiva il colpo di {self.get_name()}')
return 0
msg= f'Ehhhh voleviiii!!! sei lent##! {target.get_name_and_stats()} schiva il colpo di {self.get_name_and_stats()}'
return 0, msg
target.accuses_damage(self.damage)
print(f'{self.get_name()} Colpisce {target.get_name()} in nome di Guarino')
return self.damage
msg= f'{self.get_name_and_stats()} Colpisce {target.get_name_and_stats()}'
weapon= self.get_equipped_weapon()
if weapon: msg+= f' con un {weapon.get_name}'
else: msg+= f' con un pugno'
self.kills+= 1
return self.damage, msg
def get_item(self, item):
if self.get_inventory_weight() + item.get_weight() >= self.get_max_weight():
if self.get_gender() == 'm':
print(f'Sono sovraccarico, {self.get_name} non puo\' prendere questo oggetto')
elif self.get_gender() == 'f':
print(f'Sono sovraccarica, {self.get_name} non puo\' prendere questo oggetto')
if self.player_gender_is_male():
print(f'Sono sovraccarico, {self.get_name_and_stats} non puo\' prendere questo oggetto')
elif self.player_gender_is_female():
print(f'Sono sovraccarica, {self.get_name_and_stats} non puo\' prendere questo oggetto')
else:
print(f'Sono sovraccaric#, {self.get_name} non puo\' prendere questo oggetto')
print(f'Sono sovraccaric#, {self.get_name_and_stats} non puo\' prendere questo oggetto')
return False
self.inventory.append(item)

21
main.py
View File

@@ -1,27 +1,8 @@
import random as _random
from entities import weapon_syms as _wsyms
from entities import arena as _arena
def init_arena(players, weapons):
def init_arena(players= None, weapons= None):
return _arena.BrSimArena(players, weapons)
def run_events(Arena):
#A event for each player:
pass
def local_debug():
players= [{'name': 'Crystal'}, {'name': 'Andrea'}, {'name' : 'giampi'}]
w= _wsyms.KNIFE
#weapons= [{_wsyms.WEAPONS[w]['name' ]: 1}]
weapons= [{w: 1}]
Arena= init_arena(players, weapons)
print(f'Players: {Arena.get_players()}')
print(f'Weapons: {Arena.get_weapons()}')
while (len(Arena.get_alive_players()) > 1):
#Start a day
run_events(Arena)
#At 23:59:
Arena.next_day()
#End of day

View File

@@ -1,2 +1 @@
python-telegram-bot[job-queue]==22.3
pytz==2025.2

60
utils/logs.py Normal file
View File

@@ -0,0 +1,60 @@
import os as _os
import logging as _logging
from datetime import datetime as _dt
from bot_libs import syms as _bot_syms
logger= _logging.getLogger(__name__)
file_handler= None
def get_log_name():
now= _dt.now()
year= now.year
month= now.month
if month < 10: month= f'0{month}'
day= now.day
if day < 10: day= f'0{day}'
fname= f'battle_royale-{now.year}{month}{day}.log'
return _os.path.join(_bot_syms.LOG_PATH, fname)
def _create_file_handler():
global file_handler
print('### create file handler')
current_log_file = get_log_name()
file_handler = _logging.FileHandler(current_log_file, encoding='utf-8')
formatter = _logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.setLevel(_logging.INFO)
return file_handler
def _setup_logging_file():
print('### setup logging')
current_log_file = get_log_name()
if not file_handler: return _create_file_handler()
if file_handler.baseFilename == _os.path.abspath(current_log_file): return
logger.removeHandler(file_handler)
file_handler.close()
_create_file_handler()
def log_debug(txt):
_setup_logging_file()
logger.debug(txt)
def log_info(txt):
_setup_logging_file()
logger.info(txt)
def log_warning(txt):
_setup_logging_file()
logger.warning(txt)
def log_error(txt):
_setup_logging_file()
logger.error(txt)
def log_critical(txt):
_setup_logging_file()
logger.critical(txt)