Skip to content
Snippets Groups Projects
Commit 3b42f6e6 authored by Boris OUYA's avatar Boris OUYA
Browse files

Merge branch 'comm' into 'main'

Comm

See merge request !23
parents b9430c43 cf9c7636
Branches main
No related tags found
1 merge request!23Comm
...@@ -36,15 +36,24 @@ class AbstractBall(pg.sprite.Sprite, ABC): ...@@ -36,15 +36,24 @@ class AbstractBall(pg.sprite.Sprite, ABC):
@abstractmethod @abstractmethod
def _init_paddles(self,data): def _init_paddles(self,data):
"""
Initialise les raquêtes
"""
... ...
def getSpeed(self) -> float: def getSpeed(self) -> float:
return self.speed return self.speed
def launch(self) -> None: def launch(self) -> None:
self.launched = True self.launched = True
def choose_ball_orientation(self, dep : str): def choose_ball_orientation(self, dep : str):
"""Déplace l'orientation de la balle dans le sens spécifée par dep: up -> vers la droite de la
verticale vers le haut; down -> vers la gauche de la verticale vers le haut;
"""
current_angle = math.radians( current_angle = math.radians(
self.orientation.angle_to(pg.Vector2(1, 0))) self.orientation.angle_to(pg.Vector2(1, 0)))
...@@ -70,18 +79,21 @@ class AbstractBall(pg.sprite.Sprite, ABC): ...@@ -70,18 +79,21 @@ class AbstractBall(pg.sprite.Sprite, ABC):
def move(self,ignore=False): def move(self,ignore=False):
self.rect.x = round(self.rect.x + self.speed * self.orientation.x) self.rect.x = round(self.rect.x + self.speed * self.orientation.x)
self.rect.y = round(self.rect.y + self.speed * self.orientation.y) self.rect.y = round(self.rect.y + self.speed * self.orientation.y)
return self.collision(self.tiles) return self.collision(self.tiles)
def reflect(self, normal_vector): def reflect(self, normal_vector):
"""Réfléchit le vecteur orientation selon la normale spécifiée"""
if normal_vector.magnitude() != 0: if normal_vector.magnitude() != 0:
self.orientation = self.orientation.reflect(normal_vector) self.orientation = self.orientation.reflect(normal_vector)
def get_orientation(self): return self.orientation.x,self.orientation.y def get_orientation(self): return self.orientation.x,self.orientation.y
def collision_mur(self): def collision_mur(self):
"""Gère la collision avec les murs"""
normal_vector = pg.math.Vector2() normal_vector = pg.math.Vector2()
collision = False collision = False
if self.rect.top < 3: # haut if self.rect.top < 3: # haut
...@@ -106,6 +118,8 @@ class AbstractBall(pg.sprite.Sprite, ABC): ...@@ -106,6 +118,8 @@ class AbstractBall(pg.sprite.Sprite, ABC):
def _reflect_on_paddle(self,paddle): def _reflect_on_paddle(self,paddle):
"""Gère la collision sur la barre spécifiée"""
self.rect.left -= round(self.orientation.x * self.speed) self.rect.left -= round(self.orientation.x * self.speed)
self.rect.top -= round(self.orientation.y * self.speed) self.rect.top -= round(self.orientation.y * self.speed)
...@@ -127,6 +141,7 @@ class AbstractBall(pg.sprite.Sprite, ABC): ...@@ -127,6 +141,7 @@ class AbstractBall(pg.sprite.Sprite, ABC):
@abstractmethod @abstractmethod
def collision_barre(self): def collision_barre(self):
"""vérifie et gère la collion avec une barre"""
... ...
def get_pos(self): def get_pos(self):
...@@ -134,6 +149,7 @@ class AbstractBall(pg.sprite.Sprite, ABC): ...@@ -134,6 +149,7 @@ class AbstractBall(pg.sprite.Sprite, ABC):
def collision_tile(self, tiles): def collision_tile(self, tiles):
"""Gère la collision avec les tuiles"""
hits = pg.sprite.spritecollide(self, tiles,False) hits = pg.sprite.spritecollide(self, tiles,False)
collision = False collision = False
...@@ -201,6 +217,7 @@ class AbstractBall(pg.sprite.Sprite, ABC): ...@@ -201,6 +217,7 @@ class AbstractBall(pg.sprite.Sprite, ABC):
return (collision, hit) return (collision, hit)
def up_speed(self): def up_speed(self):
"""Augmente la vitesse et borne l'angle de la direction"""
self.speed = min(self.speed + 0.01, self.max_speed) self.speed = min(self.speed + 0.01, self.max_speed)
if abs(self.orientation.y) < math.sin(math.pi/16): if abs(self.orientation.y) < math.sin(math.pi/16):
self.orientation.y = -math.sin(math.pi/16) if self.orientation.y < 0 else math.sin(math.pi/16) self.orientation.y = -math.sin(math.pi/16) if self.orientation.y < 0 else math.sin(math.pi/16)
...@@ -210,7 +227,8 @@ class AbstractBall(pg.sprite.Sprite, ABC): ...@@ -210,7 +227,8 @@ class AbstractBall(pg.sprite.Sprite, ABC):
return self.rect.y > DISPLAY_HEIGHT return self.rect.y > DISPLAY_HEIGHT
def collision(self, tiles): def collision(self, tiles):
"""Gère toutes les collisions et renvoie un tuple
(la coliision a eu lieu?, la tuile touchée éventuellement"""
data_coll_barre = self.collision_barre() data_coll_barre = self.collision_barre()
if data_coll_barre[0]: if data_coll_barre[0]:
return True,data_coll_barre[1] return True,data_coll_barre[1]
...@@ -222,6 +240,7 @@ class AbstractBall(pg.sprite.Sprite, ABC): ...@@ -222,6 +240,7 @@ class AbstractBall(pg.sprite.Sprite, ABC):
return coll_tile[0] or coll_mur, coll_tile[1] return coll_tile[0] or coll_mur, coll_tile[1]
def main_menu_init(self): def main_menu_init(self):
"""Initialisation pour l'écran de début"""
self.launched = True self.launched = True
self.orientation.x = 1 self.orientation.x = 1
self.orientation.y = -1 self.orientation.y = -1
...@@ -232,7 +251,7 @@ class AbstractBall(pg.sprite.Sprite, ABC): ...@@ -232,7 +251,7 @@ class AbstractBall(pg.sprite.Sprite, ABC):
def main_menu_collision(self, collision): def main_menu_collision(self, collision):
"""Collision pour l'écran de début"""
normal_vector = pg.math.Vector2() normal_vector = pg.math.Vector2()
if self.rect.top < 3: # haut if self.rect.top < 3: # haut
...@@ -277,6 +296,7 @@ class AbstractBall(pg.sprite.Sprite, ABC): ...@@ -277,6 +296,7 @@ class AbstractBall(pg.sprite.Sprite, ABC):
def main_menu(self): def main_menu(self):
"""Gère la balle dans l'écran de menu"""
collision = False collision = False
for _ in range(4): for _ in range(4):
self.rect.x += self.orientation.x self.rect.x += self.orientation.x
...@@ -286,6 +306,7 @@ class AbstractBall(pg.sprite.Sprite, ABC): ...@@ -286,6 +306,7 @@ class AbstractBall(pg.sprite.Sprite, ABC):
def draw(self): def draw(self):
self.screen.blit(self.img, pg.Rect( self.screen.blit(self.img, pg.Rect(
self.rect.x, self.rect.y, self.width, self.height)) self.rect.x, self.rect.y, self.width, self.height))
......
...@@ -63,6 +63,7 @@ class AbstractGamePhase(ABC): ...@@ -63,6 +63,7 @@ class AbstractGamePhase(ABC):
@abstractmethod @abstractmethod
def _init_entities(self,player_perk=None): def _init_entities(self,player_perk=None):
"""Initialise les entités de la classe"""
... ...
def getStatus(self): def getStatus(self):
...@@ -79,10 +80,12 @@ class AbstractGamePhase(ABC): ...@@ -79,10 +80,12 @@ class AbstractGamePhase(ABC):
@classmethod @classmethod
@abstractmethod @abstractmethod
def constructFromTiles(cls, game, tiles,player_perk=None): def constructFromTiles(cls, game, tiles,player_perk=None):
"""Retourne la scène de jeu construite à partir des tuile en paramètre"""
... ...
@classmethod @classmethod
def getReprFromFile(cls,screen,filename): def getReprFromFile(cls,screen,filename):
"""Retourne les tuiles représentées dans filename"""
repr_tile = [] repr_tile = []
with open(filename, "rb") as f: with open(filename, "rb") as f:
repr_tile = pickle.load(f) repr_tile = pickle.load(f)
...@@ -105,16 +108,20 @@ class AbstractGamePhase(ABC): ...@@ -105,16 +108,20 @@ class AbstractGamePhase(ABC):
@classmethod @classmethod
def loadSceneFrom(cls, game,screen, filename,player_perk=None): def loadSceneFrom(cls, game,screen, filename,player_perk=None):
"""Charge la scène de jeu à partir du fichier représentatif
des tuiles"""
tiles = cls.getReprFromFile(screen,filename) tiles = cls.getReprFromFile(screen,filename)
return cls.constructFromTiles(game,tiles,player_perk) return cls.constructFromTiles(game,tiles,player_perk)
@abstractmethod @abstractmethod
def _process_horizontal_movement(self, key_state): def _process_horizontal_movement(self, key_state):
"""Gère les mouvements horizontaux des joueurs"""
... ...
def _prelaunch_on_space_pressed(self): def _prelaunch_on_space_pressed(self):
"""Action quand la barre espace est pressé pendant la phase de lancement"""
self._has_started = True self._has_started = True
for barre in self._barres: for barre in self._barres:
barre.inplay = True barre.inplay = True
...@@ -124,6 +131,7 @@ class AbstractGamePhase(ABC): ...@@ -124,6 +131,7 @@ class AbstractGamePhase(ABC):
def _process_pre_launch_ball_orientation(self, key_state): def _process_pre_launch_ball_orientation(self, key_state):
"""S'occupe de la réponse à la barre espace pendant la phase de lancement"""
if key_state["space"][0] == True: if key_state["space"][0] == True:
self._prelaunch_on_space_pressed() self._prelaunch_on_space_pressed()
...@@ -141,6 +149,7 @@ class AbstractGamePhase(ABC): ...@@ -141,6 +149,7 @@ class AbstractGamePhase(ABC):
@abstractmethod @abstractmethod
def _manage_ball_hit(self,hit): def _manage_ball_hit(self,hit):
"""S'occupe des actions quand une tuile est touchée"""
... ...
...@@ -161,6 +170,7 @@ class AbstractGamePhase(ABC): ...@@ -161,6 +170,7 @@ class AbstractGamePhase(ABC):
self.spawnParticlesAt(ball.get_pos(),color) self.spawnParticlesAt(ball.get_pos(),color)
def _process_ball_logic_in_game(self): def _process_ball_logic_in_game(self):
for ball in self._balls: for ball in self._balls:
collision_happened, hit = ball.move() collision_happened, hit = ball.move()
......
...@@ -35,10 +35,12 @@ class AbstractLevelLoader(State,ABC): ...@@ -35,10 +35,12 @@ class AbstractLevelLoader(State,ABC):
@abstractmethod @abstractmethod
def _init_perks(self): def _init_perks(self):
"""initialise les avantages du joueur"""
... ...
@abstractmethod @abstractmethod
def _load_next_game_scene(self): def _load_next_game_scene(self):
"""charge la scène de jeu suivante"""
... ...
def loadNextScene(self,shop=False) -> Union[GamePhase, None]: def loadNextScene(self,shop=False) -> Union[GamePhase, None]:
...@@ -59,6 +61,7 @@ class AbstractLevelLoader(State,ABC): ...@@ -59,6 +61,7 @@ class AbstractLevelLoader(State,ABC):
def _update_keystate(self,keystate): def _update_keystate(self,keystate):
"""Altère le dictionnaire des clés"""
pass pass
def _manage_ongoing_scene(self, key_state: Dict[str, KeyData]) -> None: def _manage_ongoing_scene(self, key_state: Dict[str, KeyData]) -> None:
...@@ -104,6 +107,7 @@ class AbstractLevelLoader(State,ABC): ...@@ -104,6 +107,7 @@ class AbstractLevelLoader(State,ABC):
@abstractmethod @abstractmethod
def _isGamePhase(self,scene): def _isGamePhase(self,scene):
"""Vérifie si la scène du jeu actuelle est une scène de jeu"""
... ...
def update(self, key_state: Dict[str, KeyData]) -> None: def update(self, key_state: Dict[str, KeyData]) -> None:
......
...@@ -25,68 +25,3 @@ class Balle(AbstractBall): ...@@ -25,68 +25,3 @@ class Balle(AbstractBall):
def _init_paddles(self,data): def _init_paddles(self,data):
self.barre = data self.barre = data
def main_menu_init(self):
self.launched = True
self.orientation.x = 1
self.orientation.y = -1
while 162 < self.rect.x < 177 + 251 and 45 < self.rect.y < 59 + 294:
self.rect.x = random.randint(0, 605)
self.rect.y = random.randint(0, 420)
def main_menu_collision(self, collision):
normal_vector = pg.math.Vector2()
if self.rect.top < 3: # haut
normal_vector = pg.math.Vector2(0, -1)
self.rect.top = 3
collision = True
if self.rect.left < 3: # gauche
normal_vector = pg.math.Vector2(1, 0)
self.rect.left = 3
collision = True
if self.rect.right > self.display_width - 3: # droite
normal_vector = pg.math.Vector2(-1, 0)
self.rect.right = self.display_width - 3
collision = True
if self.rect.top > DISPLAY_HEIGHT - 14:
normal_vector = pg.math.Vector2(0, 1)
self.rect.top = DISPLAY_HEIGHT - 14
collision = True
if self.rect.x == 163 and 45 < self.rect.y < 59 + 294:
normal_vector = pg.math.Vector2(-1, 0)
collision = True
if self.rect.x == 177 + 251 and 45 < self.rect.y < 59 + 294:
normal_vector = pg.math.Vector2(1, 0)
collision = True
if self.rect.y == 45 and 162 < self.rect.x < 177 + 251:
normal_vector = pg.math.Vector2(0, -1)
collision = True
if self.rect.y == 59 + 294 and 162 < self.rect.x < 177 + 251:
normal_vector = pg.math.Vector2(0, 1)
collision = True
self.reflect(normal_vector)
return collision
def main_menu(self):
collision = False
for _ in range(4):
self.rect.x += self.orientation.x
self.rect.y += self.orientation.y
collision = self.main_menu_collision(collision)
return collision
\ No newline at end of file
...@@ -68,6 +68,7 @@ class Barre(pg.sprite.Sprite): ...@@ -68,6 +68,7 @@ class Barre(pg.sprite.Sprite):
self.delay_shoot = 600 self.delay_shoot = 600
def _manage_speed(self): def _manage_speed(self):
"""Change la vitesse selon la direction actuelle de la barre"""
if self.dir == "left": if self.dir == "left":
self.vitesse -= self.freinage if self.vitesse > 0 else self.acceleration self.vitesse -= self.freinage if self.vitesse > 0 else self.acceleration
if (self.vitesse < -1 * self.vitesseMax): if (self.vitesse < -1 * self.vitesseMax):
...@@ -139,6 +140,7 @@ class Barre(pg.sprite.Sprite): ...@@ -139,6 +140,7 @@ class Barre(pg.sprite.Sprite):
def receiveSignal(self,signal): def receiveSignal(self,signal):
"""Reçoit le type d'un item et réagit en conséquence """
if signal == 1: if signal == 1:
self.shooting = False self.shooting = False
if self.sprites == self.short_sprites: if self.sprites == self.short_sprites:
......
...@@ -83,6 +83,9 @@ class Game: ...@@ -83,6 +83,9 @@ class Game:
height: int height: int
) -> pygame.surface.Surface: ) -> pygame.surface.Surface:
"""Renvoi l'image sous forme d'une surface avec la hauteur et la
largeur spécifiée"""
img_file = os.path.join("assets", image_filename) img_file = os.path.join("assets", image_filename)
img = pygame.image.load(img_file).convert().convert_alpha() img = pygame.image.load(img_file).convert().convert_alpha()
img = pygame.transform.scale(img, (width, height)) img = pygame.transform.scale(img, (width, height))
...@@ -92,6 +95,13 @@ class Game: ...@@ -92,6 +95,13 @@ class Game:
def _processMultipleKeyDown(self,event : pygame.event.Event) -> None: def _processMultipleKeyDown(self,event : pygame.event.Event) -> None:
"""
Rempli le dictionnaire des inputs avec les actions de l'utilisateur.
Supporte plusieurs inputs à la fois.
"""
if event.type != pygame.KEYDOWN: if event.type != pygame.KEYDOWN:
return return
keys = pygame.key.get_pressed() keys = pygame.key.get_pressed()
...@@ -123,34 +133,11 @@ class Game: ...@@ -123,34 +133,11 @@ class Game:
self._key_states["z"] = (True, None) self._key_states["z"] = (True, None)
"""def _processKeyDown(self,event : pygame.event.Event): def _processMouseEvent(self , event : pygame.event.Event) -> None:
if event.type != pygame.KEYDOWN:
return
if event.unicode.isalnum():
self._key_states["characters"] = (True, event.unicode)
if event.key == pygame.K_RIGHT:
self._key_states["right"] = (True, None)
if event.key == pygame.K_LEFT:
self._key_states["left"] = (True, None)
if event.key == pygame.K_UP:
self._key_states["up"] = (True, None)
if event.key == pygame.K_DOWN:
self._key_states["down"] = (True, None)
if event.key == pygame.K_SPACE:
self._key_states["space"] = (True, None)
if event.key == pygame.K_BACKSPACE:
self._key_states["backspace"] = (True, None)
if event.key == pygame.K_ESCAPE:
self._key_states["escape"] = (True, None)
if event.key == pygame.K_q:
self._key_states["q"] = (True, None)
if event.key == pygame.K_d:
self._key_states["d"] = (True, None)
""" """
Gère les inputs de type clic de souris
def _processMouseEvent(self , event : pygame.event.Event) -> None: """
if event.type == pygame.MOUSEBUTTONDOWN: if event.type == pygame.MOUSEBUTTONDOWN:
mouseStates = pygame.mouse.get_pressed(num_buttons=3) mouseStates = pygame.mouse.get_pressed(num_buttons=3)
...@@ -202,6 +189,11 @@ class Game: ...@@ -202,6 +189,11 @@ class Game:
show_rect: bool = False, rot: bool = False show_rect: bool = False, rot: bool = False
) -> None: ) -> None:
"""
Ecrit le texte spécifié à la position spécifiée dans la
bonne couleur. si show_rect est mis à vrai, le rectangle englobant
est montré. Si rot est vrai, le texte est écrit dans l'axe des y.
"""
rect = pygame.Rect(position[0], position[1], rect = pygame.Rect(position[0], position[1],
MIN_TEXT_RECT, TEXT_HEIGHT) MIN_TEXT_RECT, TEXT_HEIGHT)
text_surface = self.font.render(text, True, color) text_surface = self.font.render(text, True, color)
...@@ -220,21 +212,38 @@ class Game: ...@@ -220,21 +212,38 @@ class Game:
self._key_states[key] = (False, None) self._key_states[key] = (False, None)
def popStateStack(self) -> None: def popStateStack(self) -> None:
"""
Retire l'état en sommet de la pile
"""
self._states_stack.pop() self._states_stack.pop()
def stack(self, state) -> None: def stack(self, state) -> None:
"""
ajoute un état au sommet de la pile
"""
self._states_stack.append(state) self._states_stack.append(state)
def blitOnScreen(self, surface: pygame.Surface, rect: pygame.Rect) -> None: def blitOnScreen(self, surface: pygame.Surface, rect: pygame.Rect) -> None:
"""
Affiche une surface à l'écran à la position spécifiée par rect
"""
self._screen.blit(surface, rect) self._screen.blit(surface, rect)
def get_column(self, x: int) -> int: def get_column(self, x: int) -> int:
"""
Retourne la colonne correspondante à l'abcisse spécifiée
"""
return x // (TILE_WIDTH + GAP) return x // (TILE_WIDTH + GAP)
def get_row(self, y: int) -> int: def get_row(self, y: int) -> int:
"""
Retourne la ligne correspondante à l'ordonnée spécifiée
"""
return y // (TILE_HEIGHT + GAP) return y // (TILE_HEIGHT + GAP)
def get_coords(self, pos: Position) -> Position: def get_coords(self, pos: Position) -> Position:
"""Retourne la colonne et la ligne de la position spécifiée"""
return self.get_column(pos[0]), self.get_row(pos[1]) return self.get_column(pos[0]), self.get_row(pos[1])
......
...@@ -122,6 +122,7 @@ class GameIATrain(GameIA): ...@@ -122,6 +122,7 @@ class GameIATrain(GameIA):
del hit del hit
def return_closest_item_info(self): def return_closest_item_info(self):
"""Retourne un coefficient selon les informations de l'item le plus proche"""
if len(self._items) == 0: if len(self._items) == 0:
return 0 return 0
......
...@@ -32,6 +32,7 @@ class GameLauncherTrain(GameIA): ...@@ -32,6 +32,7 @@ class GameLauncherTrain(GameIA):
def _initialize(self, net, screen=None, tiles=None, lives=3, rendering=False, decr=0,allow_items=False): def _initialize(self, net, screen=None, tiles=None, lives=3, rendering=False, decr=0,allow_items=False):
super()._initialize(net, screen, tiles, lives, rendering, decr) super()._initialize(net, screen, tiles, lives, rendering, decr)
self._balls[0].launched = False self._balls[0].launched = False
self.gaming_net = GAME_NET self.gaming_net = GAME_NET
......
...@@ -22,6 +22,7 @@ class GamePhase(AbstractGamePhase): ...@@ -22,6 +22,7 @@ class GamePhase(AbstractGamePhase):
def _init_entities(self,player_perk=None): def _init_entities(self,player_perk=None):
barre = Barre(self, self.display_width//2 - barre = Barre(self, self.display_width//2 -
60, self.display_height - 40) 60, self.display_height - 40)
......
...@@ -74,5 +74,5 @@ class GamePhaseIA(AbstractGamePhase): ...@@ -74,5 +74,5 @@ class GamePhaseIA(AbstractGamePhase):
self._barres[0].rect.w // 2 - self._balls[0].rect.w // 2 self._barres[0].rect.w // 2 - self._balls[0].rect.w // 2
def return_scaled_inputs(self): def return_scaled_inputs(self):
"""Retourne les inputs du réseau de neuronnes"""
return self.inputGen.return_scaled_inputs() return self.inputGen.return_scaled_inputs()
\ No newline at end of file
...@@ -37,6 +37,9 @@ class InputGenerator: ...@@ -37,6 +37,9 @@ class InputGenerator:
def return_closest_item_info(self): def return_closest_item_info(self):
"""
Retourne un coefficient pour l'item le plus proche
"""
if len(self._items) == 0: if len(self._items) == 0:
return 0 return 0
...@@ -68,6 +71,8 @@ class InputGenerator: ...@@ -68,6 +71,8 @@ class InputGenerator:
return score return score
def getClosestBall(self): def getClosestBall(self):
"""Retourne la balle la plus prohce du joueur"""
closest = self._balls[0] closest = self._balls[0]
min_distance = (closest.rect.x - self._barres[0].rect.x) ** 2 + (closest.rect.y - self._barres[0].rect.y) ** 2 min_distance = (closest.rect.x - self._barres[0].rect.x) ** 2 + (closest.rect.y - self._barres[0].rect.y) ** 2
...@@ -81,6 +86,7 @@ class InputGenerator: ...@@ -81,6 +86,7 @@ class InputGenerator:
return closest return closest
def return_scaled_inputs(self): def return_scaled_inputs(self):
""" Retourne les inputs"""
ball = self.getClosestBall() ball = self.getClosestBall()
......
...@@ -155,6 +155,8 @@ class LevelGestionState(State): ...@@ -155,6 +155,8 @@ class LevelGestionState(State):
self.bouton9.render() self.bouton9.render()
def _render_selection(self) -> None: def _render_selection(self) -> None:
"""Affiche la sélection à l'écran"""
for i in range(9): for i in range(9):
color = (0, 0, 0) color = (0, 0, 0)
mouse_pos = pygame.mouse.get_pos() mouse_pos = pygame.mouse.get_pos()
......
...@@ -89,6 +89,9 @@ class LevelSelectionToEditState(State): ...@@ -89,6 +89,9 @@ class LevelSelectionToEditState(State):
return pygame.time.get_ticks() - self._last_click > self._delay return pygame.time.get_ticks() - self._last_click > self._delay
def _selections(self, pos: Position): def _selections(self, pos: Position):
"""Gère les clics de la souris"""
if self._return_button.rect.collidepoint(*pos): if self._return_button.rect.collidepoint(*pos):
self.game.popStateStack() self.game.popStateStack()
......
No preview for this file type
No preview for this file type
No preview for this file type
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment