def compile(self): ''' Compile all the blocks of the ship into one surface. Set all signals. ''' # create a surface that contains all the blocks dim_surf = Spec.DIM_BLOCK * Spec.SIZE_GRID_SHIP surface = pygame.Surface(dim_surf) surface.set_colorkey(C.WHITE) surface.fill(C.WHITE) for block in self.blocks.values(): pos = block.coord * Spec.DIM_BLOCK # get image of block block_surf = block.compile() # add block to surface surface.blit(block_surf, pos) surface = surface.convert() # create Form object with created surface self.form = Form(dim_surf, self.pos, surface=surface) self.set_signals() # create first mask self._mask = pygame.mask.from_surface(self.form.get_surface('main'))
def _add_friend_line(self, username): ''' Add a line to the friend scroll list. ''' cadre = Cadre(DIM_CADRE_LINE, (0, 0)) text_username = TextBox(Spec.DIM_MEDIUM_TEXT, (20, 10), text=username, font=Font.f(35)) color = self._get_color_connected(username) form_connected = Form(DIM_CONN, POS_CONN, color) button_profil = Button(Spec.DIM_SMALL_BUTTON, POS_PROFIL, color=C.LIGHT_BLUE, text="Profil", font=Font.f(30)) button_play = Button(Spec.DIM_SMALL_BUTTON, POS_PLAY, color=C.LIGHT_GREEN, text="Play", font=Font.f(30)) notif = TextBox(Spec.DIM_NOTIF, POS_NOTIF, color=C.LIGHT_RED, text_color=C.WHITE, text='0', font=Font.f(30)) # add line scroll = self.get_component('s frs') scroll.add_line([ cadre, text_username, form_connected, button_profil, button_play, notif ]) # set buttons logic line = scroll.get_line(-1) button_profil.set_logic(self._get_profil_logic(line)) button_play.set_logic(self._get_play_logic(line)) # set notif state scroll.set_active_state(False, line=line, element=notif)
def _add_game_demand_line(self, username): ''' Add a line to the game demand scroll list. ''' cadre = Cadre(DIM_CADRE_LINE_DFR, (0, 0), color=C.XLIGHT_GREY) icon = Form(DIM_IMG, POS_IMG, color=C.XLIGHT_GREY, surface=img_game, with_font=True) text_username = TextBox(Spec.DIM_MEDIUM_TEXT, POS_DFR_TEXT, color=C.XLIGHT_GREY, text=username, font=Font.f(35), centered=False) button_yes = Button(DIM_DFR_BUTT, POS_YES, color=C.LIGHT_GREEN, surface=vu, with_font=True) button_no = Button(DIM_DFR_BUTT, POS_NO, color=C.LIGHT_RED, surface=cross, with_font=True) # add line scroll = self.get_component('s dfrs') scroll.add_line([cadre, icon, text_username, button_yes, button_no]) line = scroll.get_line(-1) # set buttons logic button_yes.set_logic(self._get_demand_game_logic(line, True)) button_no.set_logic(self._get_demand_game_logic(line, False))
class Ship: has_blocks = False color = C.BLUE blocks_priority = ['Generator', 'Engine', 'Shield', 'Turret', 'Block'] def __init__(self, team, color=None): self.team = team # auxiliar acceleration self.is_aux_acc = False self.aux_acc = 0 self.aux_timer = 0 # movement vectors self.speed = np.array([0, 0], dtype=float) self.acc = np.array([0, 0], dtype=float) self.orien = 0 # rad self.circular_speed = 0 self.circular_acc = 0 self.mass = 0 self.pos = np.array([0, 0], dtype='int32') # blocks self.typed_blocks = { 'Block': [], 'Generator': [], 'Engine': [], 'Shield': [], 'Turret': [] } self.abs_centers = {} self._mask = None if color: self.color = color @classmethod def from_grid(cls, grid, team, color=None): ''' Create a ship given a grid representing the ship (see block map). ''' if color == None: color = cls.color ship = cls(team, color) ship.set_blocks(grid) return ship def get_block_by_coord(self, coord, blocks=None): ''' Return the block with the corresponding coord. If `blocks` is specified, loop through the specified blocks. ''' if blocks == None: blocks = self.blocks.values() for block in blocks: if np.all(block.coord == coord): return block def set_pos(self, pos, scaled=False): ''' Set the position of the ship (ship's form). `scaled` parameter specify if the given position is scaled to the current window's dimension. ''' if scaled: pos = Dimension.inv_scale(pos) self.pos[:] = pos self.form.set_pos(self.pos, scale=(not scaled)) def get_pos(self, scaled=False, center=False): ''' Return the position of the ship (ship's form). If `scaled=True`, the position will be scaled to the current window's dimension. If `center=True`, return the center of the ship. ''' if center: return np.array(self.form.get_center(scale=scaled)) else: return np.array(self.form.get_pos(scaled=scaled)) def get_surface(self, surf_type): ''' Return the specified surface, can be: - original: the unscaled & unrotated surface - main: the scaled & rotated main displayed surface - font: if set, the font surface ''' return self.form.get_surface(surf_type=surf_type) def get_mask(self) -> pygame.mask.Mask: ''' Return the pygame.mask.Mask object of the ship. ''' if self._mask is None: self._process_mask() indicator.display() return self._mask def get_speed(self, scalar=False): ''' Return the speed of the ship, if `scalar=False`: return the vector speed, else return the norm of the vector. ''' if scalar: return get_norm(self.speed) else: return self.speed def get_acc(self, scalar=False): ''' Return the acceleration of the ship, if `scalar=False`: return the vector acceleration, else return the norm of the vector. ''' if scalar: return get_norm(self.acc) else: return self.acc def set_color(self, color): '''Set the color of the ship''' if self.has_blocks: self.color = color # change color of every block for block in self.blocks.values(): block.set_color(self.color, update_original=True) else: self.color = color def set_auxiliary_acc(self, acc): ''' Set an auxiliary source of acceleration, `acc` is a vector (`np.dnarray`). It will last for `Spec.AUX_TIMER` frames. ''' self.is_aux_acc = True self.aux_acc = acc self.aux_timer = 0 def get_auxiliary_acc(self): ''' If a auxiliary force of acceleration is set, will return it and update the timer. ''' if not self.is_aux_acc: return np.zeros(2) if self.aux_timer == Spec.AUX_TIMER: self.is_aux_acc = False self.aux_timer = 0 self.aux_acc = 0 self.aux_timer += 1 return self.aux_acc def set_blocks(self, grid): ''' Create the blocks, set up the blocks' grid. Args: - grid: an array representing the ship with value of block map ''' self.has_blocks = True self.blocks = {} # contains the blocks indexs on corresponding the cases self.blocks_grid = np.zeros(Spec.DIM_SHIP, dtype='int16') # correspond to the self.blocks indexs -> start at 1 n_block = 0 # iterate over every case for x, y in itertools.product(range(grid.shape[0]), repeat=2): # create block corresponding to grid case value if grid[x, y] > 0: n_block += 1 self.blocks_grid[x, y] = n_block # create the blocks block = map_block[grid[x, y]]((x, y), color=self.color) block.ship = self self.blocks[n_block] = block # set the mass of the ship self.mass = 4 * n_block self.initial_n_block = n_block self.create_blocks_lists() def create_blocks_lists(self): ''' Create lists of each type of blocks. Store them in `.typed_blocks`. ''' for block in self.blocks.values(): self.typed_blocks[block.name].append(block) def remove_block(self, key): ''' Remove one of the blocks of the ship, given the the key of the block, compile the entire ship. ''' block = self.blocks.pop(key) block.on_death() # update mass of ship self.mass -= 4 self.typed_blocks[block.name].remove(block) self.abs_centers.pop(key) self.compile() def update_block(self, block=None, index=None): ''' Update one block of the ship form surface, given the block object or its index, compile it and blit it on the ship surface ''' if index: block = self.blocks[index] pos = block.coord * Spec.SIZE_BLOCK surf = block.compile() self.form.get_surface('original').blit(surf, pos) def update_signal(self, block=None, shield=False, index=None): ''' Update one signal of the ship form surface, given the block object or its index, blit its signal on the ship surface. If shield is True, update the shield signal ''' if index: block = self.blocks[index] if shield: pos = Spec.SIZE_BLOCK * block.coord + Spec.POS_SIGNAL_SHIELD signal = block.get_signal_shield() if signal is None: return else: pos = Spec.SIZE_BLOCK * block.coord + Spec.POS_SIGNAL signal = block.get_signal_form() # blit signal surf on form's surface signal.display(surface=self.form.get_surface('original'), pos=pos) def compile(self): ''' Compile all the blocks of the ship into one surface. Set all signals. ''' # create a surface that contains all the blocks dim_surf = Spec.DIM_BLOCK * Spec.SIZE_GRID_SHIP surface = pygame.Surface(dim_surf) surface.set_colorkey(C.WHITE) surface.fill(C.WHITE) for block in self.blocks.values(): pos = block.coord * Spec.DIM_BLOCK # get image of block block_surf = block.compile() # add block to surface surface.blit(block_surf, pos) surface = surface.convert() # create Form object with created surface self.form = Form(dim_surf, self.pos, surface=surface) self.set_signals() # create first mask self._mask = pygame.mask.from_surface(self.form.get_surface('main')) def set_signals(self): ''' Set the signal of all the blocks of the ship. Update the ship form surface. ''' for block in self.blocks.values(): # don't display basic block signal -> it's useless if block.name != 'Block': self.update_signal(block) self.update_signal(block, shield=True) def rotate_surf(self, angle: float): ''' Rotate the ship's surface to a given angle (rad). ''' # converts the angle into deg angle = -get_deg(angle) self.form.rotate(angle) @Counter.add_func def display(self): ''' Display the ship ''' self.form.display() @Counter.add_func def run(self, remote_control=False): ''' Execute all the ship's method that need to be executed during a frame. ''' self.run_blocks() if not remote_control: self._update_local() self.form.set_pos(self.pos, scale=True) self._compute_blocks_abs_centers() # update main surface self._update_surf() # udapte mask self._mask = None @Counter.add_func def _update_local(self): self.control_power_level() self.update_turrets() self.update_shields() self.update_state() @Counter.add_func def _update_surf(self): self.form.set_surface(surface=self.form.get_surface('original')) self.rotate_surf(self.orien) @Counter.add_func def _process_mask(self): # get white pixels self._mask = pygame.mask.from_threshold(self.form.get_surface('main'), C.WHITE, (1, 1, 1)) # set mask to other pixels self._mask.invert() @Counter.add_func def update_turrets(self): ''' Update all the turrets of the ships. ''' for turret in self.typed_blocks['Turret']: turret.update_state() def update_shields(self): ''' Update all the shields of the ships. ''' for shield in self.typed_blocks['Shield']: shield.update_state() def update_state(self): ''' Update the accelerations, speeds. Update the position and orientation of the ship. ''' self.compute_acc() self.compute_speed() self.compute_circular_speed() self.orien += self.circular_speed #self.orien %= 2 * np.pi self.pos += self.speed.astype(int) @Counter.add_func def run_blocks(self): ''' Call the run method of each block, check if the color of the block has changed ''' for block in self.blocks.values(): block.run() if block.has_color_changed: block.has_color_changed = False self.update_block(block=block) if block.name != 'Block': self.update_signal(block=block) self.update_signal(block=block, shield=True) def compute_speed(self): '''Update the speed of the ship''' self.speed += self.acc norm_speed = get_norm(self.speed) if norm_speed == 0: return self.speed *= Spec.AIR_RESISTANCE**max(1, np.log10(norm_speed)) def compute_circular_speed(self): '''Update the circular speed of the ship''' self.circular_speed *= Spec.AIR_RESISTANCE self.circular_speed += self.circular_acc if abs(self.circular_speed) > Spec.MAX_CIRCULAR_SPEED: self.circular_speed = np.sign( self.circular_speed) * Spec.MAX_CIRCULAR_SPEED def compute_acc(self): ''' Compute the acceleration of the ship. ''' # get total motor force total_force = self.get_engines_power() total_force = to_vect(total_force, self.orien) # compute acceleration self.acc = total_force / self.mass self.acc += self.get_auxiliary_acc() def get_engines_power(self): ''' Return the sum of the power of all the engines. ''' force = 0 for block in self.typed_blocks['Engine']: force += block.get_engine_force() return force def get_power_level(self): ''' Return the power level of the ship. The power level is the sum of all the power outputs. ''' power_level = 0 for block in self.blocks.values(): power_level += block.get_power_output() return power_level def control_power_level(self): ''' Check that there is enough power to feed all the blocks. If not, deactivate blocks randomly (according to the blocks priority) until there is enough power. ''' power_level = self.get_power_level() if power_level >= 0: return else: # disable blocks until the power level is positive # goes trough every type except the Generator blocks for block_type in self.blocks_priority[:0:-1]: # shuffle the blocks blocks = self.typed_blocks[block_type].copy() np.random.shuffle(blocks) for block in blocks: block.set_activate(False) if self.get_power_level() >= 0: return @Counter.add_func def _compute_blocks_abs_centers(self): ''' Compute the absolute (scaled) center of all the blocks ''' abs_center = np.array(self.form.get_center(scale=False), dtype=float) rel_center = abs_center - self.pos for key, block in self.blocks.items(): coord = np.array(block.coord, dtype=float) coord *= Spec.SIZE_BLOCK coord += Spec.SIZE_BLOCK // 2 coord -= rel_center length, alpha = get_polar(coord) alpha += self.orien coord = get_cartesian(length, alpha) coord += rel_center + self.pos self.abs_centers[key] = coord.astype(int) def get_key_by_pos(self, pos): ''' Given a (unscaled) position, return the key of the nearest block. ''' distances = {} pos = np.array(pos) # compute every distances for key, center in self.abs_centers.items(): distances[key] = get_norm(center - pos) # get key of min distance key = min(distances.keys(), key=distances.get) return key def handeln_collision(self, bullet, intersect): ''' Handeln collision with a bullet. ''' # get coord of touched block key = self.get_key_by_pos(Dimension.inv_scale(intersect)) self.blocks[key].hit(bullet.damage) if self.blocks[key].hp <= 0: self.remove_block(key)
import pygame import numpy as np import itertools from game.block import Block, Generator, Shield, Turret, Engine from lib.plougame import Interface, Form, Dimension, C from lib.perfeval import Counter from game.geometry import get_deg, get_rad, get_polar, get_cartesian, get_norm, to_vect from data.spec import Spec map_block = {1: Block, 2: Generator, 3: Shield, 4: Turret, 5: Engine} indicator = Form((50, 50), (3100, 1700), color=C.YELLOW) indicator2 = Form((10, 10), (0, 0), color=C.YELLOW) class Ship: has_blocks = False color = C.BLUE blocks_priority = ['Generator', 'Engine', 'Shield', 'Turret', 'Block'] def __init__(self, team, color=None): self.team = team # auxiliar acceleration self.is_aux_acc = False self.aux_acc = 0 self.aux_timer = 0
POS_CADRE1 + POS_USER, text_color=Spec.DCOLOR_P1, font=Font.f(60)) text_username2 = TextBox(DIM_USER, POS_CADRE2 + POS_USER, text_color=Spec.DCOLOR_P2, font=Font.f(60)) button_quit = Button(Spec.DIM_MEDIUM_BUTTON, POS_EXIT, color=C.LIGHT_BLUE, text="Quit", font=Font.f(30)) form_red_hp1 = Form(DIM_HP, POS_CADRE1 + POS_HP, color=C.RED) form_green_hp1 = Form(DIM_HP, POS_CADRE1 + POS_HP, color=C.GREEN) form_red_hp2 = Form(DIM_HP, POS_CADRE2 + POS_HP, color=C.RED) form_green_hp2 = Form(DIM_HP, POS_CADRE2 + POS_HP, color=C.GREEN) form_red_shield_hp1 = Form(DIM_HP, POS_CADRE1 + POS_SHIELD_HP, color=C.RED) form_blue_shield_hp1 = Form(DIM_HP, POS_CADRE1 + POS_SHIELD_HP, color=C.NEO_BLUE) form_red_shield_hp2 = Form(DIM_HP, POS_CADRE2 + POS_SHIELD_HP, color=C.RED) form_blue_shield_hp2 = Form(DIM_HP, POS_CADRE2 + POS_SHIELD_HP, color=C.NEO_BLUE)
class Block(Form): name = 'Block' ship = None # activate/deactivate signal signal_red = Form(Spec.DIM_SIGNAL, (0, 0), color=C.LIGHT_RED) signal_green = Form(Spec.DIM_SIGNAL, (0, 0), color=C.LIGHT_GREEN) signal_shield = Form(Spec.DIM_SIGNAL, (0, 0), color=Spec.COLOR_SIGNAL_SHIELD) def __init__(self, coord, color=None, image=None, hp=None): if hp == None: self.hp = Spec.HP_BLOCK else: self.hp = hp self.active = True self.power_output = 0 # shield self.hp_shield = 0 self.has_shield = False self.has_signal_shield = False # for the ship to know if it needs to update the block on the surface self.has_color_changed = False # hit self.is_hit = False self.hit_effect_time = 0 self.coord = np.array(coord, dtype='int16') # set up the surface if color == None: self.color = C.BLUE else: self.color = color if image == None: with_font = False else: with_font = True # the position doesn't matter as the block's surface will be compiled in the ship image super().__init__(Spec.DIM_BLOCK, (0, 0), color=self.color, surface=image, with_font=with_font, marge=True) self.set_marge_width(Spec.DIM_BLOCK_MARGE, scale=True) def hit(self, damage): ''' Hit the block, with a damage amound, start the hit effect. ''' # start by hiting the shield self.hp_shield -= damage if self.hp_shield <= 0: # if shield got over hit, report damage on hp self.hp -= -round(self.hp_shield) self.hp_shield = 0 self.set_color(C.RED) else: self.set_color(C.NEO_BLUE) self.is_hit = True self.hit_effect_time = 0 def set_color(self, color, update_original=False): ''' Set the color of the block, set a call to update color. If `update_original=True`, change permanent color of the block. ''' super().set_color(color, marge=True) self.has_color_changed = True if update_original: self.color = color def set_signal_shield(self): ''' Update the shield signal ''' self.ship.update_signal(self, shield=True) def run(self): ''' ''' self.run_hit_effect() # check shield signal if not self.has_signal_shield and self.hp_shield > 0: self.has_signal_shield = True self.set_signal_shield() elif self.has_signal_shield and self.hp_shield == 0: self.has_signal_shield = False self.set_signal_shield() def run_hit_effect(self): ''' In case of hit effect, update the effect duration and potentialy stop the effect. ''' # check for hit effect if self.is_hit: self.hit_effect_time += 1 if self.hit_effect_time == Spec.HIT_DURATION: self.is_hit = False self.set_color(self.color) def get_power_output(self): if self.active: return self.power_output else: return 0 def get_activate(self): ''' Return if the block is activate. ''' return self.active def set_activate(self, value: bool): ''' Set if the block is activate. ''' # don't update signal if new value is equal to old one if self.active == value: return self.active = value # update the signal on the ship if self.name != 'Block': self.ship.update_signal(self) def get_signal_form(self): ''' Return the signal Form object with the correct color. ''' if self.active: return self.signal_green else: return self.signal_red def get_signal_shield(self): ''' Return the signal Form object, if doesn't have an active shield return a see-through surface. ''' if self.hp_shield > 0: return self.signal_shield else: return None def on_death(self): ''' Function executed when the block is removed at runtime, handeln the effect of the removal of the block (by ex: for Shield block remove shield hps) ''' pass def __str__(self): return f'{self.name} object at {self.coord}' def __repr__(self): return self.__str__() def display(self): super().display(marge=True)