forked from Cryz/battle_royale_sim
Compare commits
118 Commits
d140e726b4
...
master
| Author | SHA256 | Date | |
|---|---|---|---|
| 05e219ad18 | |||
| 0d36f1dd52 | |||
| 13bb11eb8c | |||
| af7f0019ec | |||
| 804a3961c9 | |||
|
|
cdb69699ab | ||
|
|
6c3fe6326f | ||
|
|
2550f0b262 | ||
|
|
3cc6966d86 | ||
| 62c7c7f2c2 | |||
|
|
aa245700c6 | ||
|
|
7f36158c40 | ||
|
|
171b9fe787 | ||
|
|
eb3b7da07a | ||
|
|
8ed1bd3c4f | ||
|
|
0379a3f935 | ||
|
|
18bccfded8 | ||
|
|
c6c53caeee | ||
|
|
a5831085ba | ||
|
|
9ad177b459 | ||
|
|
f2c8acb80a | ||
|
|
47729ceeb9 | ||
| c30c46e20f | |||
|
|
51e3367a1c | ||
|
|
e0dc558c27 | ||
|
|
29157e22d4 | ||
|
|
53189e3fd8 | ||
|
|
87c0cefb56 | ||
|
|
52cc55e611 | ||
|
|
56da6031f2 | ||
|
|
5002890540 | ||
|
|
9653965d2b | ||
|
|
37578437f6 | ||
|
|
9b503872e8 | ||
|
|
e604743796 | ||
|
|
4dcb87723d | ||
|
|
5c3e349cb6 | ||
|
|
5e6816269b | ||
|
|
a7fd66e9e4 | ||
|
|
172b387dcc | ||
|
|
4639d85f00 | ||
|
|
24f0f3e3e1 | ||
|
|
0bfa74c0e2 | ||
|
|
c82f31a101 | ||
|
|
b9161a052f | ||
|
|
bd0601e115 | ||
|
|
bee2e94f82 | ||
|
|
da0368d6c5 | ||
|
|
f8a3e12909 | ||
| 111e5e04df | |||
| 1a0422414c | |||
| af70d30279 | |||
| 308107fdc7 | |||
| 7c6e7fb7b4 | |||
| 876c55522d | |||
| 76a14d69ce | |||
| aa46acc812 | |||
| c1c8301e11 | |||
|
|
269724345d | ||
|
|
a9cb58100f | ||
|
|
8f3d6dfaff | ||
|
|
460a6ccdec | ||
|
|
2bf65583b7 | ||
|
|
5e530e1af1 | ||
|
|
ffbaa1c3b5 | ||
|
|
72ec279036 | ||
|
|
f55afe5959 | ||
|
|
669701dbb7 | ||
|
|
9b538e7d2f | ||
|
|
4d3dd5c781 | ||
|
|
a948db5af3 | ||
|
|
11e4f9b2dd | ||
|
|
56e5e35b53 | ||
| f55a6a2cdb | |||
|
|
4729a45c30 | ||
| 5a6810e948 | |||
| 31637dc845 | |||
|
|
079dcd6639 | ||
|
|
65688b9100 | ||
|
|
308c9e3602 | ||
|
|
871b3d2ab1 | ||
|
|
5f0d6ad3f5 | ||
|
|
551c2ef8ef | ||
|
|
4ce19d1a50 | ||
|
|
67804a394d | ||
|
|
415cdfc123 | ||
| 9f745bd451 | |||
|
|
fe356864c8 | ||
|
|
c5a6b886d6 | ||
|
|
a7c6dd25df | ||
|
|
888f22ac1b | ||
|
|
0a6deafd32 | ||
|
|
100bb4fe53 | ||
|
|
f360fe4200 | ||
|
|
0df960ea22 | ||
|
|
634c651cb5 | ||
|
|
07dd5611e5 | ||
|
|
09c892a786 | ||
|
|
3d2a0bcc70 | ||
|
|
c10872a47e | ||
|
|
e8f2dbbc29 | ||
|
|
62ac0e83a5 | ||
|
|
82be14e603 | ||
|
|
b12db42c0b | ||
| 5986e0f880 | |||
|
|
0bb2c840f9 | ||
| 526668ed5d | |||
|
|
1c284bad97 | ||
|
|
761cf794b4 | ||
|
|
f193464006 | ||
|
|
c5a6f4ea36 | ||
|
|
5a672b336f | ||
|
|
05d1718775 | ||
| e66a6fbd9c | |||
|
|
e038f711d5 | ||
|
|
82141d89d6 | ||
| 0098861e54 | |||
| db86f3dd06 |
63
README.md
63
README.md
@@ -1,3 +1,62 @@
|
|||||||
# 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
|
||||||
|
|
||||||
|
This game is inspired to this Hunger Games Simulator: https://brantsteele.net/hungergames/reaping.php
|
||||||
|
|
||||||
|
# Bot Instructions (Telegram)
|
||||||
|
|
||||||
|
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)
|
||||||
|
- Show Map UTF8 or Show Map Image (show Players/Items position on the map)
|
||||||
|
|
||||||
|
# Bot Instructions (CLI)
|
||||||
|
|
||||||
|
1. open a python shell
|
||||||
|
2. `import debug`
|
||||||
|
3. `debug.init_debug_simulation()`
|
||||||
|
|
||||||
|
# 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'
|
||||||
|
|
||||||
|
LOG_PATH= '/the/path/where/you/want/put/game/daily/logs'
|
||||||
|
|
||||||
|
BOT_EXEC_CMD= 'python3 bot.py' # or any other way you start the bot
|
||||||
|
|
||||||
|
SUPER_USERS= [ your_chat_id ]
|
||||||
|
```
|
||||||
27
assets/events.py
Normal file
27
assets/events.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
EVENTS = [
|
||||||
|
{
|
||||||
|
'id' : 'ATTACK',
|
||||||
|
'text' : '{Player1} ha attacato {player2}',
|
||||||
|
'resolve_text' : '{Player1} ha causato {effetto_collaterale}',
|
||||||
|
'fail_text' : '{Player1} ha fallito, {player2} è indenne',
|
||||||
|
'success_percentage' : 80,
|
||||||
|
'fail_percentage' : 19,
|
||||||
|
'extreme_fail_percentage' : 1,
|
||||||
|
'requirements' : {},
|
||||||
|
'weight' : 1,
|
||||||
|
'affected_players' : 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id' : 'BOMB_EXPLOSION',
|
||||||
|
'text' : '{Player1} ha attacato {player2}',
|
||||||
|
'resolve_text' : '{Player1} ha causato {effetto_collaterale}',
|
||||||
|
'fail_text' : '{Player1} ha fallito, {player2} è indenne',
|
||||||
|
'success_percentage' : 80,
|
||||||
|
'fail_percentage' : 19,
|
||||||
|
'extreme_fail_percentage' : 1,
|
||||||
|
'requirements' : {
|
||||||
|
'weapons' : ['BOMB']
|
||||||
|
},
|
||||||
|
'affected_players' : [2,3,4],
|
||||||
|
},
|
||||||
|
]
|
||||||
130
bot.py
130
bot.py
@@ -1,64 +1,104 @@
|
|||||||
import asyncio
|
from telegram.ext import Application
|
||||||
import datetime
|
from telegram.ext import CommandHandler
|
||||||
from telegram import Update
|
from telegram.ext import MessageHandler
|
||||||
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
|
from telegram.ext import filters
|
||||||
import bot_syms as _botsyms
|
from telegram import ReplyKeyboardMarkup
|
||||||
import main as _brsim
|
from telegram import ReplyKeyboardRemove
|
||||||
|
|
||||||
async def loop_game(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
from utils import logs as _log
|
||||||
chat_id = update.effective_chat.id
|
|
||||||
if 'arena' in context.application.bot_data:
|
|
||||||
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: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
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 bot_start(update, context):
|
||||||
await update.message.reply_text(_botsyms.START_MSG)
|
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', 'Show Map UTF8', 'Show Map Image']
|
||||||
|
]
|
||||||
|
reply_markup= ReplyKeyboardMarkup(keyboard, one_time_keyboard=False, resize_keyboard=True)
|
||||||
|
|
||||||
chat_id = update.effective_chat.id
|
chat_id = update.effective_chat.id
|
||||||
print(f'{chat_id}: Sto costruendo il mondo di gioco...')
|
_log.log_debug(f'bot_start: {chat_id} - I\'m building the world\'s game...')
|
||||||
Arena= _brsim.init_arena()
|
Arena= _arena.BrSimArena()
|
||||||
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}')
|
|
||||||
|
|
||||||
await update.message.reply_text('Ho creato il mondo di gioco')
|
await update.message.reply_text('Ho creato il mondo di gioco', reply_markup=reply_markup)
|
||||||
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}')
|
|
||||||
context.application.bot_data['arena'] = Arena
|
context.application.bot_data['arena'] = Arena
|
||||||
|
|
||||||
context.job_queue.run_daily(
|
async def bot_commands(update, context):
|
||||||
loop_game,
|
text= update.message.text
|
||||||
time=datetime.time(hour=0, minute=0, second=5, tzinfo=datetime.timezone.utc),
|
chat_id = update.effective_chat.id
|
||||||
chat_id=chat_id,
|
username= update.effective_chat.username
|
||||||
name=str(chat_id)
|
first_name= update.effective_chat.first_name
|
||||||
)
|
last_name= update.effective_chat.last_name
|
||||||
print(f'Job giornaliero creato per la chat {chat_id}')
|
chat_type= update.effective_chat.type.name # PRIVAT|BOT|ETC..
|
||||||
|
|
||||||
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
||||||
testo_ricevuto = update.message.text
|
_log.log_info(f'bot_command: \
|
||||||
|
user id="{chat_id}" \
|
||||||
|
username="{username}" \
|
||||||
|
first_name="{first_name}" \
|
||||||
|
last_name="{last_name}" \
|
||||||
|
chat_type="{chat_type}"')
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
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)
|
||||||
|
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)
|
await update.message.reply_text(_botsyms.WIP_MSG)
|
||||||
|
|
||||||
async def add_player(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
||||||
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():
|
def main():
|
||||||
application = Application.builder().token(_botsyms.TOKEN).build()
|
application = Application.builder().token(_botsyms.TOKEN).build()
|
||||||
|
|
||||||
application.add_handler(CommandHandler('start', bot_start))
|
application.add_handler(CommandHandler('start', bot_start))
|
||||||
application.add_handler(CommandHandler('addplayer', add_player))
|
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, bot_commands))
|
||||||
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
|
|
||||||
|
|
||||||
print('Bot in esecuzione...')
|
_log.log_info('main: Bot is running...')
|
||||||
application.run_polling()
|
application.run_polling()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
84
bot_libs/commands_handling.py
Normal file
84
bot_libs/commands_handling.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
### 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_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:
|
||||||
|
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)')
|
||||||
53
bot_libs/player_handling.py
Normal file
53
bot_libs/player_handling.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
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
|
||||||
|
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):
|
||||||
|
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
24
bot_libs/repeating.py
Normal 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
41
bot_libs/simulation.py
Normal 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)
|
||||||
50
bot_libs/special_commands.py
Normal file
50
bot_libs/special_commands.py
Normal 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)}")
|
||||||
115
bot_libs/syms.py
Normal file
115
bot_libs/syms.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import os as _os
|
||||||
|
from utils import logs as _logs
|
||||||
|
|
||||||
|
_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: _logs.log_debug(_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",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
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= r"""*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)
|
||||||
|
"""
|
||||||
11
bot_syms.py
11
bot_syms.py
@@ -1,11 +0,0 @@
|
|||||||
TOKEN = "7670066927:AAG8jI5n9NcyxPksYky7LPYqA08BThs07c4"
|
|
||||||
|
|
||||||
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?"
|
|
||||||
119
debug.py
Normal file
119
debug.py
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import time as _time
|
||||||
|
import random as _rand
|
||||||
|
import main as _main
|
||||||
|
from utils import logs as _logs
|
||||||
|
from bot_libs import syms as _syms
|
||||||
|
|
||||||
|
def _debug_data():
|
||||||
|
players= _syms.COLORS_NAMES
|
||||||
|
weapons= []
|
||||||
|
|
||||||
|
Arena= _main.init_arena()
|
||||||
|
for player in players:
|
||||||
|
Arena.add_player(player)
|
||||||
|
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'
|
||||||
|
_logs.log_debug(msg)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def _random_action(Arena, Player_one):
|
||||||
|
_RANDOM_ACTIONS= {
|
||||||
|
1: 'attack',
|
||||||
|
2: 'move',
|
||||||
|
}
|
||||||
|
Map= Arena.get_map()
|
||||||
|
avail_actions= Map.get_player_available_actions(Player_one)
|
||||||
|
_logs.log_debug(f'{Player_one.get_name()}:{Player_one.get_coordinates()}, avail_actions: {avail_actions}')
|
||||||
|
msg= ''
|
||||||
|
if 1 in avail_actions:
|
||||||
|
# XXX maybe in future this action is available only if you are near to another player
|
||||||
|
# so Player_two is no more random, but will be a random near player
|
||||||
|
preys= avail_actions[1]
|
||||||
|
Player_two= _rand.sample(preys, 1)[0]
|
||||||
|
while Player_one.get_id() == Player_two.get_id() and not Player_two.is_alive():
|
||||||
|
Player_two= _rand.sample(preys, 1)[0]
|
||||||
|
_dmg, msg= Player_one.attack(Player_two)
|
||||||
|
elif 2 in avail_actions:
|
||||||
|
Map= Arena.get_map()
|
||||||
|
available_movements= Map.get_player_available_directions(Player_one)
|
||||||
|
_logs.log_debug(f'{Player_one.get_name()}:{Player_one.get_coordinates()}, avail_movements: {available_movements}')
|
||||||
|
if not available_movements:
|
||||||
|
# XXX probably should skip this action and look for another action
|
||||||
|
return f'{Player_one.get_name()} Pensa a Guarino tutto il giorno'
|
||||||
|
_rand.shuffle(available_movements)
|
||||||
|
x, y, direction= available_movements[0]
|
||||||
|
Player_one.move(x, y)
|
||||||
|
Map.init_map_matrix()
|
||||||
|
_logs.log_debug(Map.get_renderized_map())
|
||||||
|
msg= f'{Player_one.get_name()} Si muove verso »»» {direction}'
|
||||||
|
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def play_one_day_debug(Arena):
|
||||||
|
if not Arena.get_players(): return
|
||||||
|
_logs.log_debug(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 Player_one in alive_players:
|
||||||
|
if not Player_one.is_alive(): continue # he could be dead during this day cycle
|
||||||
|
msg= _random_action(Arena, Player_one)
|
||||||
|
#p_two= _rand.sample(Arena.get_alive_players(), 1)[0]
|
||||||
|
#while Player_one.get_id() == p_two.get_id():
|
||||||
|
#p_two= _rand.sample(Arena.get_alive_players(), 1)[0]
|
||||||
|
#_dmg, msg= Player_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_simulation():
|
||||||
|
Arena= _debug_data()
|
||||||
|
while (len(Arena.get_alive_players()) > 1):
|
||||||
|
events= play_one_day_debug(Arena)
|
||||||
|
_logs.log_debug('#################')
|
||||||
|
_logs.log_debug('#################')
|
||||||
|
_logs.log_debug('#################')
|
||||||
|
_logs.log_debug(events)
|
||||||
|
_logs.log_debug('#################')
|
||||||
|
_logs.log_debug('#################')
|
||||||
|
_logs.log_debug('#################')
|
||||||
|
_time.sleep(0.3)
|
||||||
|
|
||||||
|
def init_debug_loop():
|
||||||
|
Arena= _debug_data()
|
||||||
|
while (len(Arena.get_alive_players()) > 1):
|
||||||
|
alive_players= Arena.get_alive_players()
|
||||||
|
|
||||||
|
p_one, p_two= _rand.sample(alive_players, 2)
|
||||||
|
p_one.attack(p_two)
|
||||||
|
|
||||||
|
#Start a day
|
||||||
|
#At 23:59:
|
||||||
|
Arena.next_day()
|
||||||
|
_time.sleep(0.3)
|
||||||
|
#End of day
|
||||||
|
|
||||||
|
last_player= Arena.get_alive_players()[0]
|
||||||
|
_logs.log_debug(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()
|
||||||
|
_logs.log_debug(res)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
init_debug()
|
||||||
134
entities/arena.py
Normal file
134
entities/arena.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
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
|
||||||
|
from utils import logs as _logs
|
||||||
|
|
||||||
|
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= None, weapons= None):
|
||||||
|
self.day= 1
|
||||||
|
self.players= []
|
||||||
|
self.weapons= []
|
||||||
|
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
|
||||||
|
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))
|
||||||
|
|
||||||
|
def next_day(self):
|
||||||
|
self.day+= 1
|
||||||
|
_logs.log_debug(f'Giorno: {self.day}')
|
||||||
|
alive_players_str= ', '.join([p.get_name() for p in self.get_alive_players()])
|
||||||
|
_logs.log_debug(f'Giocatori vivi: {alive_players_str}')
|
||||||
|
death_players= self.get_death_players()
|
||||||
|
if (death_players):
|
||||||
|
death_players_str= ', '.join([p.get_name() for p in death_players])
|
||||||
|
_logs.log_debug(f'Giocatori morti: {death_players_str}')
|
||||||
|
|
||||||
|
def get_alive_players(self):
|
||||||
|
res= []
|
||||||
|
for p in self.players:
|
||||||
|
if not p.is_alive(): continue
|
||||||
|
res.append(p)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def get_death_players(self):
|
||||||
|
res= []
|
||||||
|
for p in self.players:
|
||||||
|
if p.is_alive(): continue
|
||||||
|
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
|
||||||
|
|
||||||
|
def supporter_donation(self):
|
||||||
|
#XXX supporter donate a random item or weapon to a random player
|
||||||
|
#TODO maybe in future a player can have charism stats that can influence the chance to get a donation
|
||||||
|
pass
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def get_weapons(self):
|
||||||
|
res= []
|
||||||
|
for w in self.weapons:
|
||||||
|
#XXX implement me
|
||||||
|
res.append(w)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def get_map(self):
|
||||||
|
return self.Map
|
||||||
5
entities/event.py
Normal file
5
entities/event.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
class Event():
|
||||||
|
def __init__(self, event_settings, the_player, affected_players = []):
|
||||||
|
# this will be the class that manage the event, so the result and what ahppens to the players
|
||||||
|
# will do it later
|
||||||
|
pass
|
||||||
81
entities/event_picker.py
Normal file
81
entities/event_picker.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
from assets.events import EVENTS
|
||||||
|
import random as _random
|
||||||
|
|
||||||
|
class ArenaEventPicker():
|
||||||
|
def __init__(self, players):
|
||||||
|
self.players = players
|
||||||
|
self.event_list = EVENTS
|
||||||
|
self.already_picked_players = []
|
||||||
|
|
||||||
|
def pick_a_player():
|
||||||
|
the_player = _random.choice(self.players)
|
||||||
|
the_player_id = the_player.get('id')
|
||||||
|
|
||||||
|
self.players = list(filter(lambda x : x.get('id') != the_player_id))
|
||||||
|
|
||||||
|
self.already_picked_players.append(the_player)
|
||||||
|
|
||||||
|
return the_player
|
||||||
|
|
||||||
|
def pick_event(the_player):
|
||||||
|
|
||||||
|
player_inventory = the_player.get('inventory') or []
|
||||||
|
status = the_player['health']
|
||||||
|
reputation = the_player['reputation']
|
||||||
|
|
||||||
|
elegible_events = []
|
||||||
|
for event in EVENTS:
|
||||||
|
requirements = event['requirements']
|
||||||
|
|
||||||
|
keys_to_check = ['item', 'weapon', 'status', 'reputation', 'affected_players']
|
||||||
|
|
||||||
|
for check in keys_to_check:
|
||||||
|
if requirements.get(check) and check == 'item':
|
||||||
|
needed_items = requirements.get('check')
|
||||||
|
if needed_items in player_inventory:
|
||||||
|
elegible_events.append(event)
|
||||||
|
|
||||||
|
if requirements.get(check) and check == 'weapon':
|
||||||
|
needed_weapons = requirements.get('check')
|
||||||
|
if needed_items in player_inventory:
|
||||||
|
elegible_events.append(event)
|
||||||
|
|
||||||
|
if requirements.get(check) and check == 'status':
|
||||||
|
needed_health = requirements.get('check')
|
||||||
|
if '>' in requirements.get(check) and requirements.get(check) > needed_health:
|
||||||
|
elegible_events.append(event)
|
||||||
|
if '<' in requirements.get(check) and requirements.get(check) < needed_health:
|
||||||
|
elegible_events.append(event)
|
||||||
|
|
||||||
|
if requirements.get(check) and check == 'reputation':
|
||||||
|
needed_reputation = requirements.get('reputation')
|
||||||
|
if '>' in requirements.get(check) and requirements.get(check) > needed_reputation:
|
||||||
|
elegible_events.append(event)
|
||||||
|
if '<' in requirements.get(check) and requirements.get(check) < needed_reputation:
|
||||||
|
elegible_events.append(event)
|
||||||
|
|
||||||
|
if check == 'affected_players':
|
||||||
|
needed_players = event.get('affected_players')
|
||||||
|
if needed_players < len(self.players):
|
||||||
|
elegible_events.append(event)
|
||||||
|
|
||||||
|
the_event = _random.choice(elegible_events)
|
||||||
|
return the_event
|
||||||
|
|
||||||
|
def pick_targets(number_of_targets):
|
||||||
|
random.shuffle(self.players)
|
||||||
|
return list(self.players[0:number_of_targets])
|
||||||
|
|
||||||
|
def resolve_event():
|
||||||
|
playing_player = self.pick_a_player()
|
||||||
|
assigned_event = self.pick_event(playing_player)
|
||||||
|
|
||||||
|
affected_players = assigned_event['affected_players']
|
||||||
|
if isinstance(affected_players,list):
|
||||||
|
affected_players = _random.choice(affected_players)
|
||||||
|
|
||||||
|
targeted_players = self.pick_targets(affected_players)
|
||||||
|
|
||||||
|
event_instance = _event.Event(assigned_event, playing_player)
|
||||||
|
|
||||||
|
|
||||||
235
entities/gamemap.py
Normal file
235
entities/gamemap.py
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
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
|
||||||
|
from entities import resource as _resource
|
||||||
|
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= 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.)
|
||||||
|
self.game_map= []
|
||||||
|
|
||||||
|
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(None)
|
||||||
|
#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_coordinates()
|
||||||
|
self.game_map[p_coord_y][p_coord_x]= player
|
||||||
|
#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_coordinates()
|
||||||
|
self.game_map[p_coord_y][p_coord_x]= item
|
||||||
|
#self.game_map[i_coord_y][i_coord_x]= self.item_sym
|
||||||
|
|
||||||
|
def _put_resource_on_map(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)
|
||||||
|
x= _random.randint(0, self.world_width -1)
|
||||||
|
y= _random.randint(0, self.world_height -1)
|
||||||
|
resource= self.get_map_matrix()[y][x]
|
||||||
|
while resource:
|
||||||
|
#while self.get_map_matrix()[y][x] != self.field_sym:
|
||||||
|
_logs.log_debug('_put_resource_on_map: collision, regenerate coordinates')
|
||||||
|
x= _random.randint(0, self.world_width -1)
|
||||||
|
y= _random.randint(0, self.world_height -1)
|
||||||
|
resource= self.get_map_matrix()[y][x]
|
||||||
|
_logs.log_debug(f'{target.get_name()} >>> ({x},{y})')
|
||||||
|
target.set_coordinates(x, y)
|
||||||
|
self.get_map_matrix()[y][x]= target
|
||||||
|
|
||||||
|
def init_players_coordinates(self):
|
||||||
|
for player in self.players:
|
||||||
|
self._put_resource_on_map(player)
|
||||||
|
|
||||||
|
def init_items_coordinates(self):
|
||||||
|
for item in self.items:
|
||||||
|
self._put_resource_on_map(item)
|
||||||
|
|
||||||
|
def add_player_to_map(self, player):
|
||||||
|
self.players.append(player)
|
||||||
|
self._put_resource_on_map(player)
|
||||||
|
|
||||||
|
def add_item_to_map(self, item):
|
||||||
|
self.items.append(item)
|
||||||
|
self._put_resource_on_map(item)
|
||||||
|
|
||||||
|
def get_map_matrix(self):
|
||||||
|
return self.game_map
|
||||||
|
|
||||||
|
def get_player_available_directions(self, Player):
|
||||||
|
coord_x, coord_y= Player.get_coordinates()
|
||||||
|
avail_directions= []
|
||||||
|
#XXX for now move only on available cells, no over other players/items
|
||||||
|
for shift in [-1, 1]:
|
||||||
|
x= coord_x + shift
|
||||||
|
if x < 0 or x > self.world_width -1: continue
|
||||||
|
resource= self.get_map_matrix()[coord_y][x]
|
||||||
|
direction= shift == -1 and 'sinistra' or 'destra'
|
||||||
|
if not resource: avail_directions.append((shift, 0, direction))
|
||||||
|
for shift in [-1, 1]:
|
||||||
|
y= coord_y + shift
|
||||||
|
if y < 0 or y > self.world_height -1: continue
|
||||||
|
resource= self.get_map_matrix()[y][coord_x]
|
||||||
|
direction= shift == -1 and 'su' or 'giu\''
|
||||||
|
if not resource: avail_directions.append((0, shift, direction))
|
||||||
|
return avail_directions
|
||||||
|
|
||||||
|
def check_near_players(self, Player):
|
||||||
|
# TODO Implement me
|
||||||
|
# 1. range weapons like arch can attack from distance
|
||||||
|
# 2. knife, sword and punch can attack only on immediate near cell
|
||||||
|
|
||||||
|
coord_x, coord_y= Player.get_coordinates()
|
||||||
|
attackable_players= []
|
||||||
|
for shift in [-1, 1]:
|
||||||
|
x= coord_x + shift
|
||||||
|
if x < 0 or x >= self.world_width -1: continue
|
||||||
|
resource= self.get_map_matrix()[coord_y][x]
|
||||||
|
if resource and resource.is_player() and resource.is_alive(): attackable_players.append(resource)
|
||||||
|
for shift in [-1, 1]:
|
||||||
|
y= coord_y + shift
|
||||||
|
if y < 0 or y >= self.world_height -1: continue
|
||||||
|
resource= self.get_map_matrix()[y][coord_x]
|
||||||
|
if resource and resource.is_player() and resource.is_alive(): attackable_players.append(resource)
|
||||||
|
|
||||||
|
return attackable_players
|
||||||
|
|
||||||
|
def check_near_items(self, Player):
|
||||||
|
# TODO Implement me
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_player_available_actions(self, Player):
|
||||||
|
# TODO: define actions list
|
||||||
|
coord_x, coord_y= Player.get_coordinates()
|
||||||
|
avail_actions= {}
|
||||||
|
|
||||||
|
attack= self.check_near_players(Player)
|
||||||
|
if attack:
|
||||||
|
_logs.log_debug(f'{Player.get_name()} can attack {[a.get_name() for a in attack]}')
|
||||||
|
#avail_actions.append(1) #XXX replace with attack action (or maybe other actions on players)
|
||||||
|
avail_actions[1]= attack #XXX replace with attack action (or maybe other actions on players)
|
||||||
|
if self.get_player_available_directions(Player):
|
||||||
|
avail_actions[2]= True #XXX replace with action move
|
||||||
|
items= self.check_near_items(Player)
|
||||||
|
if items:
|
||||||
|
avail_actions[3]= items #XXX replace with get item action
|
||||||
|
|
||||||
|
return avail_actions
|
||||||
|
|
||||||
|
def get_renderized_map(self):
|
||||||
|
res= ''
|
||||||
|
self.populate_map()
|
||||||
|
game_map= self.get_map_matrix()
|
||||||
|
for y in game_map:
|
||||||
|
for x in y:
|
||||||
|
if not x: el= self.field_sym
|
||||||
|
#XXX how to manage mountains?
|
||||||
|
elif x.is_player():
|
||||||
|
if not x.is_alive(): el= self.dead_player_sym
|
||||||
|
elif x.player_gender_is_male(): el= self.player_male_sym
|
||||||
|
elif x.player_gender_is_female(): el= self.player_female_sym
|
||||||
|
else: el= self.player_nonbinary_sym
|
||||||
|
elif x.is_item():
|
||||||
|
el= self.item_sym
|
||||||
|
res+= el
|
||||||
|
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):
|
||||||
|
resource= self.game_map[y][x]
|
||||||
|
if not resource:
|
||||||
|
pixel_color= _bot_syms.MAP_IMAGE_FIELD
|
||||||
|
# XXX how to manage mountains? maybe another class?
|
||||||
|
#elif resource == self.mountain_sym:
|
||||||
|
#pixel_color= _bot_syms.MAP_IMAGE_MOUNTAIN
|
||||||
|
elif resource.is_item():
|
||||||
|
pixel_color= _bot_syms.MAP_IMAGE_ITEM
|
||||||
|
elif resource.is_player():
|
||||||
|
if not resource.is_alive():
|
||||||
|
pixel_color= _bot_syms.MAP_IMAGE_DEATH_PLAYER
|
||||||
|
elif resource.player_gender_is_male():
|
||||||
|
pixel_color= _bot_syms.MAP_IMAGE_PLAYER_MALE
|
||||||
|
elif resource.player_gender_is_female():
|
||||||
|
pixel_color= _bot_syms.MAP_IMAGE_PLAYER_FEMALE
|
||||||
|
elif resource.player_gender_is_not_binary():
|
||||||
|
pixel_color= _bot_syms.MAP_IMAGE_PLAYER_NONBINARY
|
||||||
|
|
||||||
|
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
|
||||||
27
entities/items/item.py
Normal file
27
entities/items/item.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import random as _random
|
||||||
|
from entities import resource as _resource
|
||||||
|
|
||||||
|
class BrSimItem(_resource.BrSimResource):
|
||||||
|
|
||||||
|
# test
|
||||||
|
def __init__(self):
|
||||||
|
self.coord_x= 0
|
||||||
|
self.coord_y= 0
|
||||||
|
|
||||||
|
def is_item(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
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
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
KNIFE= 1
|
KNIFE= 1
|
||||||
GUN= 2
|
ARCH= 2
|
||||||
BOMB= 3
|
|
||||||
|
|
||||||
SHORT_RANGE= 1
|
SHORT_RANGE= 1
|
||||||
FAR_RANGE= 2
|
FAR_RANGE= 2
|
||||||
@@ -14,7 +13,7 @@ WEAPONS= {
|
|||||||
'ammons': -1, # -1, no limit
|
'ammons': -1, # -1, no limit
|
||||||
'range': SHORT_RANGE,
|
'range': SHORT_RANGE,
|
||||||
},
|
},
|
||||||
GUN: {
|
ARCH: {
|
||||||
'weight': 2,
|
'weight': 2,
|
||||||
'name': 'gun',
|
'name': 'gun',
|
||||||
'damage': 3,
|
'damage': 3,
|
||||||
@@ -22,12 +21,4 @@ WEAPONS= {
|
|||||||
'ammons': 10, # -1, no limit
|
'ammons': 10, # -1, no limit
|
||||||
'range': FAR_RANGE,
|
'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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import random as _random
|
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):
|
def __init__(self, wtype= None):
|
||||||
self.weapon= _syms.WEAPONS[wtype]
|
self.weapon= _syms.WEAPONS[wtype or _random.randint(1,2)]
|
||||||
self.name= self.weapon['name']
|
self.name= self.weapon['name']
|
||||||
self.damage= self.weapon['damage']
|
self.damage= self.weapon['damage']
|
||||||
self.weight= self.weapon['weight']
|
self.weight= self.weapon['weight']
|
||||||
@@ -1,71 +1,208 @@
|
|||||||
import random as _random
|
import random as _random
|
||||||
|
import uuid as _uuid
|
||||||
|
from entities import resource as _resource
|
||||||
|
from utils import logs as _logs
|
||||||
|
|
||||||
class BrSimPlayer():
|
class BrSimPlayer(_resource.BrSimResource):
|
||||||
|
|
||||||
def __init__(self, name, inventory= None):
|
def __init__(self, name, inventory= None):
|
||||||
|
super()
|
||||||
|
self.id= str(_uuid.uuid4())
|
||||||
self.name= name
|
self.name= name
|
||||||
self.health= 1
|
self.stats= ''
|
||||||
|
self.health= _random.randint(1,1)
|
||||||
self.inventory= inventory or []
|
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.max_weight= 5 # this is the max inventory weight
|
||||||
self.is_alive= True
|
self.agility= _random.randint(1,3) # chance to avoid an hit
|
||||||
self.agility= 10 # 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
|
||||||
|
|
||||||
def is_alive(self):
|
def is_player(self):
|
||||||
return self.is_alive
|
return True
|
||||||
|
|
||||||
def attack(self, target):
|
### control methods
|
||||||
if not self.is_alive(): return
|
|
||||||
if not target.is_alive(): return
|
|
||||||
if target.try_to_avoid_hit(): return # print something like 'enemy doges the attacl'
|
|
||||||
|
|
||||||
target.accuses_damage(self.damage)
|
|
||||||
|
|
||||||
def accuses_damage(self, damage):
|
def get_name_and_stats(self):
|
||||||
self.health -= damage
|
health= '♥️' * self.health or '☠️'
|
||||||
if self.health <= 0:
|
strength= '⚔️' * self.damage
|
||||||
self.health = 0
|
agility= '🚀' * self.agility
|
||||||
self.is_alive = False
|
if self.player_gender_is_male(): gender= '♂'
|
||||||
# show something like 'player is dead'
|
elif self.player_gender_is_female(): gender= '♀'
|
||||||
else:
|
else: gender= '⚩'
|
||||||
# show something like 'get hit'
|
name= f'{self.name} {gender} {health} {strength} {agility}'
|
||||||
pass
|
return name
|
||||||
|
|
||||||
def try_to_avoid_hit(self):
|
def get_id(self):
|
||||||
# maybe depend on the attack, if it is a gun shot it's quite impossible to dodge
|
return self.id
|
||||||
rnd= _random.randint(0, 100)
|
|
||||||
# if rnd > self.agility: return True ## XXX this is strange, if the agility is high the chances to dodge are lower
|
|
||||||
if rnd < self.agility: return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def steal(self):
|
|
||||||
#XXX can steal from death players or from sleeping players
|
|
||||||
pass
|
|
||||||
|
|
||||||
def heal(self):
|
|
||||||
#XXX if you have a wound and you have a medikit item, you can heal your wound or sickness
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_inventory(self):
|
|
||||||
return self.inventory
|
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
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 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):
|
def get_health(self):
|
||||||
return self.health
|
return self.health
|
||||||
|
|
||||||
|
def get_equipped_weapon(self):
|
||||||
|
return self.equipped_weapon
|
||||||
|
|
||||||
def get_damage(self):
|
def get_damage(self):
|
||||||
return self.damage
|
weapon= self.get_equipped_weapon()
|
||||||
|
if not weapon: return self.damage
|
||||||
|
return weapon.get_damage()
|
||||||
|
|
||||||
def get_agility(self):
|
def get_agility(self):
|
||||||
return self.agility
|
return self.agility
|
||||||
|
|
||||||
|
def get_reputation(self):
|
||||||
|
return self.reputation
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return {
|
return {
|
||||||
|
'id': self.get_id(),
|
||||||
'name': self.get_name(),
|
'name': self.get_name(),
|
||||||
|
'name_stats': self.get_name_and_stats(),
|
||||||
|
'gender': self.get_gender(),
|
||||||
'inventory': self.get_inventory(),
|
'inventory': self.get_inventory(),
|
||||||
|
'inventory_weight': self.get_inventory_weight(),
|
||||||
'health': self.get_health(),
|
'health': self.get_health(),
|
||||||
'damage': self.get_damage(),
|
'damage': self.get_damage(),
|
||||||
'agility': self.get_agility(),
|
'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 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
|
||||||
|
# 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, 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.player_gender_is_male():
|
||||||
|
_logs.log_debug(f'[{self.get_name_and_stats()}]: Guarino, perdonami se sono morto x.x')
|
||||||
|
elif self.player_gender_is_female():
|
||||||
|
_logs.log_debug(f'[{self.get_name_and_stats()}]: Guarino, perdonami se sono morta x.x')
|
||||||
|
else:
|
||||||
|
_logs.log_debug(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.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:
|
||||||
|
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)
|
||||||
|
|
||||||
|
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'
|
||||||
|
if not target.is_alive(): 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.player_gender_is_male():
|
||||||
|
_logs.log_debug(f'Sono sovraccarico, {self.get_name_and_stats} non puo\' prendere questo oggetto')
|
||||||
|
elif self.player_gender_is_female():
|
||||||
|
_logs.log_debug(f'Sono sovraccarica, {self.get_name_and_stats} non puo\' prendere questo oggetto')
|
||||||
|
else:
|
||||||
|
_logs.log_debug(f'Sono sovraccaric#, {self.get_name_and_stats} non puo\' prendere questo oggetto')
|
||||||
|
return False
|
||||||
|
self.inventory.append(item)
|
||||||
|
|
||||||
|
def move(self, delta_x, delta_y):
|
||||||
|
# XXX maps limits:
|
||||||
|
# probably this isn't player's business
|
||||||
|
# game orchestror should manage it
|
||||||
|
# to avoid that the player can go out from the map
|
||||||
|
# or can reach unaccessible points
|
||||||
|
# also because the player doens't know the Map (entities/gamemap.py)
|
||||||
|
self.coord_x += delta_x
|
||||||
|
self.coord_y += delta_y
|
||||||
|
|
||||||
|
def move_right(self):
|
||||||
|
self._move(1, 0)
|
||||||
|
|
||||||
|
def move_left(self):
|
||||||
|
self._move(-1, 0)
|
||||||
|
|
||||||
|
def move_top(self):
|
||||||
|
self._move(0, -1)
|
||||||
|
|
||||||
|
def move_bottom(self):
|
||||||
|
self._move(0, 1)
|
||||||
|
|
||||||
|
def escape(self):
|
||||||
|
# TODO It can run away from the fighting
|
||||||
|
return
|
||||||
|
|
||||||
|
def heal(self):
|
||||||
|
# TODO heal system
|
||||||
|
# if you have a wound and you have a medikit item,
|
||||||
|
# you can heal your wound or sickness
|
||||||
|
return
|
||||||
|
|||||||
18
entities/resource.py
Normal file
18
entities/resource.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
class BrSimResource():
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.coord_x= 0
|
||||||
|
self.coord_y= 0
|
||||||
|
|
||||||
|
def is_player(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_item(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_coordinates(self):
|
||||||
|
return self.coord_x, self.coord_y
|
||||||
|
|
||||||
|
def set_coordinates(self, x, y):
|
||||||
|
self.coord_x= x
|
||||||
|
self.coord_y= y
|
||||||
84
main.py
84
main.py
@@ -1,82 +1,8 @@
|
|||||||
from entities import player as _player
|
from entities import arena as _arena
|
||||||
from entities import weapons as _weapons
|
|
||||||
from entities import weapon_syms as _wsyms
|
|
||||||
|
|
||||||
class BrSimArena():
|
def init_arena(players= None, weapons= None):
|
||||||
|
return _arena.BrSimArena(players, weapons)
|
||||||
|
|
||||||
# players = [{'name': name, 'inventory': default_inventory, other_stats}]
|
def run_events(Arena):
|
||||||
# weapons = [{WEAPON.KNIFE: quantity}, etc...] # this is the whole quantity of the items available on the world
|
#A event for each player:
|
||||||
|
|
||||||
def __init__(self, players, weapons):
|
|
||||||
self.day= 1
|
|
||||||
self.players= [_player.BrSimPlayer(p['name'], p.get('inventory')) for p in players]
|
|
||||||
self.weapons= []
|
|
||||||
for weapon in weapons:
|
|
||||||
for wtype, quantity in weapon.items():
|
|
||||||
for i in range(quantity): self.weapons.append(_weapons.BrSimWeapon(wtype))
|
|
||||||
|
|
||||||
def next_day(self):
|
|
||||||
self.day+= 1
|
|
||||||
print(f'Giorno: {self.day}')
|
|
||||||
print(f'Giocatori vivi: {self.get_alive_players()}')
|
|
||||||
death_players= self.get_death_players()
|
|
||||||
if (death_players):
|
|
||||||
print(f'Giocatori morti: {death_players}')
|
|
||||||
|
|
||||||
def get_alive_players(self):
|
|
||||||
res= []
|
|
||||||
for p in self.players:
|
|
||||||
if not p.is_alive(): continue
|
|
||||||
res.append(p)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def get_death_players(self):
|
|
||||||
res= []
|
|
||||||
for p in self.players:
|
|
||||||
if p.is_alive(): continue
|
|
||||||
res.append(p)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def do_random_event(self):
|
|
||||||
#XXX random player does random action according to his inventory health, wounds, available weapons on the world, etc...
|
|
||||||
pass
|
|
||||||
|
|
||||||
def supporter_donation(self):
|
|
||||||
#XXX supporter donate a random item or weapon to a random player
|
|
||||||
#TODO maybe in future a player can have charism stats that can influence the chance to get a donation
|
|
||||||
pass
|
|
||||||
|
|
||||||
def add_player(self, name, inventory= None):
|
|
||||||
player= _player.BrSimPlayer(name, inventory)
|
|
||||||
self.players.append(player)
|
|
||||||
|
|
||||||
def get_players(self):
|
|
||||||
res= []
|
|
||||||
for p in self.players:
|
|
||||||
res.append(p.get_data())
|
|
||||||
return res
|
|
||||||
|
|
||||||
def get_weapons(self):
|
|
||||||
res= []
|
|
||||||
for w in self.weapons:
|
|
||||||
#XXX implement me
|
|
||||||
res.append(w)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def init_arena():
|
|
||||||
players= [{'name': 'Crystal'}, {'name': 'Andrea'}]
|
|
||||||
w= _wsyms.KNIFE
|
|
||||||
#weapons= [{_wsyms.WEAPONS[w]['name' ]: 1}]
|
|
||||||
weapons= [{w: 1}]
|
|
||||||
return BrSimArena(players, weapons)
|
|
||||||
|
|
||||||
|
|
||||||
def run_event(Arena):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def local_debug():
|
|
||||||
Arena= init_arena()
|
|
||||||
print(f'Players: {Arena.get_players()}')
|
|
||||||
print(f'Weapons: {Arena.get_weapons()}')
|
|
||||||
run_event(Arena)
|
|
||||||
|
|
||||||
|
|||||||
2
requirements/requirements.txt
Normal file
2
requirements/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
python-telegram-bot[job-queue]==22.3
|
||||||
|
pillow==11.3.0
|
||||||
60
utils/logs.py
Normal file
60
utils/logs.py
Normal 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
|
||||||
|
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)
|
||||||
|
logger.setLevel(_logging.DEBUG)
|
||||||
|
return file_handler
|
||||||
|
|
||||||
|
def _setup_logging_file():
|
||||||
|
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):
|
||||||
|
print(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)
|
||||||
Reference in New Issue
Block a user