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