forked from Cryz/battle_royale_sim
231 lines
8.9 KiB
Python
231 lines
8.9 KiB
Python
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
|
|
|
|
|
|
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]
|
|
print(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
|
|
if coord_x > 0 and not self.get_map_matrix()[coord_x - 1][coord_y]:
|
|
avail_directions.append((-1, 0, 'sinistra'))
|
|
if coord_x < self.world_width -1 and not self.get_map_matrix()[coord_x + 1][coord_y]:
|
|
avail_directions.append((1, 0, 'destra'))
|
|
if coord_y > 0 and not self.get_map_matrix()[coord_x][coord_y - 1]:
|
|
avail_directions.append((0, -1, 'su'))
|
|
if coord_y < self.world_height -1 and not self.get_map_matrix()[coord_x][coord_y + 1]:
|
|
avail_directions.append((0, 1, 'giu\''))
|
|
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(): 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(): 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:
|
|
print(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
|