Started implementing the RL part, lots of work ahead
|
@ -87,12 +87,14 @@ class UI:
|
||||||
self.display_surface.blit(magic_surf, magic_rect)
|
self.display_surface.blit(magic_surf, magic_rect)
|
||||||
|
|
||||||
def display(self, player):
|
def display(self, player):
|
||||||
|
|
||||||
if player.sprite_type == 'player':
|
if player.sprite_type == 'player':
|
||||||
self.show_bar(player.health, player.stats['health'], self.health_bar_rect, HEALTH_COLOR)
|
self.show_bar(player.health, player.stats['health'], self.health_bar_rect, HEALTH_COLOR)
|
||||||
self.show_bar(player.energy, player.stats['energy'], self.energy_bar_rect, ENERGY_COLOR)
|
self.show_bar(player.energy, player.stats['energy'], self.energy_bar_rect, ENERGY_COLOR)
|
||||||
self.show_exp(player.exp)
|
self.show_exp(player.exp)
|
||||||
self.weapon_overlay(player.weapon_index, player.can_rotate_weapon)
|
self.weapon_overlay(player.weapon_index, player.can_rotate_weapon)
|
||||||
self.magic_overlay(player.magic_index, player.can_swap_magic)
|
self.magic_overlay(player.magic_index, player.can_swap_magic)
|
||||||
|
|
||||||
if player.sprite_type == 'camera':
|
if player.sprite_type == 'camera':
|
||||||
self.show_exp(player.exp)
|
self.show_exp(player.exp)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ class Weapon(pygame.sprite.Sprite):
|
||||||
direction = player.status.split('_')[0]
|
direction = player.status.split('_')[0]
|
||||||
|
|
||||||
# Graphic
|
# Graphic
|
||||||
full_path = f"../Graphics/graphics/weapons/{player.weapon}/{direction}.png"
|
full_path = f"../Graphics/weapons/{player.weapon}/{direction}.png"
|
||||||
self.image = pygame.image.load(full_path).convert_alpha()
|
self.image = pygame.image.load(full_path).convert_alpha()
|
||||||
|
|
||||||
# Sprite Placement
|
# Sprite Placement
|
||||||
|
|
103
Game/level.py
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import pygame
|
||||||
|
from random import choice, randint
|
||||||
|
|
||||||
|
from utils.settings import *
|
||||||
|
from utils.debug import debug
|
||||||
|
from utils.support import *
|
||||||
|
|
||||||
|
from UI.ui import UI
|
||||||
|
|
||||||
|
from effects.particles import AnimationPlayer
|
||||||
|
from effects.magic import MagicPlayer
|
||||||
|
from effects.weapon import Weapon
|
||||||
|
|
||||||
|
from terrain.tiles import Tile
|
||||||
|
|
||||||
|
from view.observer import Observer
|
||||||
|
from view.camera import Camera
|
||||||
|
|
||||||
|
class Level:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
# General Settings
|
||||||
|
self.game_paused = False
|
||||||
|
|
||||||
|
# Get display surface
|
||||||
|
self.display_surface = pygame.display.get_surface()
|
||||||
|
|
||||||
|
# Sprite Group setup
|
||||||
|
self.visible_sprites = Camera()
|
||||||
|
self.obstacle_sprites = pygame.sprite.Group()
|
||||||
|
self.attack_sprites = pygame.sprite.Group()
|
||||||
|
self.attackable_sprites = pygame.sprite.Group()
|
||||||
|
|
||||||
|
# Sprite setup and entity generation
|
||||||
|
self.create_map()
|
||||||
|
|
||||||
|
# UI setup
|
||||||
|
self.ui = UI()
|
||||||
|
|
||||||
|
def create_map(self):
|
||||||
|
layouts = {
|
||||||
|
'boundary': import_csv_layout('../Map/FloorBlocks.csv'),
|
||||||
|
'grass': import_csv_layout('../Map/Grass.csv'),
|
||||||
|
'objects': import_csv_layout('../Map/Objects.csv'),
|
||||||
|
'entities': import_csv_layout('../Map/Entities.csv')
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics = {
|
||||||
|
'grass': import_folder('../Graphics/grass'),
|
||||||
|
'objects': import_folder('../Graphics/objects')
|
||||||
|
}
|
||||||
|
|
||||||
|
for style, layout in layouts.items():
|
||||||
|
for row_index, row in enumerate(layout):
|
||||||
|
for col_index, col in enumerate(row):
|
||||||
|
if col != '-1':
|
||||||
|
x = col_index * TILESIZE
|
||||||
|
y = row_index * TILESIZE
|
||||||
|
if style == 'boundary':
|
||||||
|
Tile((x,y), [self.obstacle_sprites], 'invisible')
|
||||||
|
|
||||||
|
if style == 'grass':
|
||||||
|
random_grass_image = choice(graphics['grass'])
|
||||||
|
Tile((x,y), [self.visible_sprites, self.obstacle_sprites, self.attackable_sprites], 'grass', random_grass_image)
|
||||||
|
|
||||||
|
if style == 'objects':
|
||||||
|
surf = graphics['objects'][int(col)]
|
||||||
|
Tile((x,y), [self.visible_sprites, self.obstacle_sprites], 'object', surf)
|
||||||
|
|
||||||
|
if style == 'entities':
|
||||||
|
# The numbers represent their IDs in the map .csv files generated from TILED.
|
||||||
|
if col == '395':
|
||||||
|
self.observer = Observer((x,y), [self.visible_sprites])
|
||||||
|
|
||||||
|
elif col == '394':
|
||||||
|
pass
|
||||||
|
#player generation
|
||||||
|
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
#monster generation
|
||||||
|
|
||||||
|
def create_attack_sprite(self):
|
||||||
|
self.current_attack = Weapon(self.player, [self.visible_sprites, self.attack_sprites])
|
||||||
|
|
||||||
|
def delete_attack_sprite(self):
|
||||||
|
if self.current_attack:
|
||||||
|
self.current_attack.kill()
|
||||||
|
self.current_attack = None
|
||||||
|
|
||||||
|
def create_magic_sprite(self, style, strength, cost):
|
||||||
|
if style == 'heal':
|
||||||
|
self.magic_player.heal(self.player, strength, cost, [self.visible_sprites])
|
||||||
|
|
||||||
|
if style == 'flame':
|
||||||
|
self.magic_player.flame(self.player, cost, [self.visible_sprites, self.attack_sprites])
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# Draw the game
|
||||||
|
self.visible_sprites.custom_draw(self.observer)
|
||||||
|
self.ui.display(self.observer)
|
||||||
|
|
21
Game/main.py
|
@ -4,7 +4,7 @@ import sys
|
||||||
from utils.settings import *
|
from utils.settings import *
|
||||||
from utils.debug import debug
|
from utils.debug import debug
|
||||||
|
|
||||||
from objects.level import Level
|
from level import Level
|
||||||
|
|
||||||
class Game:
|
class Game:
|
||||||
|
|
||||||
|
@ -18,12 +18,12 @@ class Game:
|
||||||
|
|
||||||
self.level = Level()
|
self.level = Level()
|
||||||
|
|
||||||
# Sound
|
# # Sound
|
||||||
main_sound = pygame.mixer.Sound('../Graphics/audio/main.ogg')
|
# main_sound = pygame.mixer.Sound('../Graphics/audio/main.ogg')
|
||||||
main_sound.set_volume(0.4)
|
# main_sound.set_volume(0.4)
|
||||||
main_sound.play(loops = -1)
|
# main_sound.play(loops = -1)
|
||||||
def run(self):
|
def run(self):
|
||||||
while True:
|
|
||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
if event.type == pygame.QUIT:
|
if event.type == pygame.QUIT:
|
||||||
pygame.quit()
|
pygame.quit()
|
||||||
|
@ -38,5 +38,12 @@ class Game:
|
||||||
self.clock.tick(FPS)
|
self.clock.tick(FPS)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
|
||||||
game = Game()
|
game = Game()
|
||||||
game.run()
|
while True:
|
||||||
|
game.run()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
0
Game/params/__init__.py
Normal file
6
Game/params/rl.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# AI setup
|
||||||
|
N = 20
|
||||||
|
batch_size = 5
|
||||||
|
n_epochs = 4
|
||||||
|
alpha = 0.0003
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import random
|
|
||||||
|
|
||||||
from rl.brain import PPONet
|
|
||||||
|
|
||||||
|
|
||||||
class Agent:
|
|
||||||
|
|
||||||
def __init__(self, actions, inputs, player_info, reward):
|
|
||||||
|
|
||||||
self.input_dim = len(inputs) + len(player_info)
|
|
||||||
|
|
||||||
self.output_dim = len(actions)
|
|
||||||
|
|
||||||
self.reward = reward
|
|
||||||
|
|
||||||
self.net = PPONet(input_dim, output_dim)
|
|
|
@ -1,4 +0,0 @@
|
||||||
import torch
|
|
||||||
|
|
||||||
class PPONet:
|
|
||||||
pass
|
|
0
Game/terrain/__init__.py
Normal file
|
@ -16,7 +16,7 @@ BAR_HEIGHT = 20
|
||||||
HEALTH_BAR_WIDTH = 200
|
HEALTH_BAR_WIDTH = 200
|
||||||
ENERGY_BAR_WIDTH = 140
|
ENERGY_BAR_WIDTH = 140
|
||||||
ITEM_BOX_SIZE = 80
|
ITEM_BOX_SIZE = 80
|
||||||
UI_FONT = '../Graphics/graphics/font/joystix.ttf'
|
UI_FONT = '../Graphics/font/joystix.ttf'
|
||||||
UI_FONT_SIZE = 18
|
UI_FONT_SIZE = 18
|
||||||
|
|
||||||
# general colors
|
# general colors
|
||||||
|
@ -38,16 +38,16 @@ UPGRADE_BG_COLOR_SELECTED = '#EEEEEE'
|
||||||
|
|
||||||
# weapons
|
# weapons
|
||||||
weapon_data = {
|
weapon_data = {
|
||||||
'sword': {'cooldown': 100, 'damage': 15,'graphic':'../Graphics/graphics/weapons/sword/full.png'},
|
'sword': {'cooldown': 100, 'damage': 15,'graphic':'../Graphics/weapons/sword/full.png'},
|
||||||
'lance': {'cooldown': 400, 'damage': 30,'graphic':'../Graphics/graphics/weapons/lance/full.png'},
|
'lance': {'cooldown': 400, 'damage': 30,'graphic':'../Graphics/weapons/lance/full.png'},
|
||||||
'axe': {'cooldown': 300, 'damage': 20, 'graphic':'../Graphics/graphics/weapons/axe/full.png'},
|
'axe': {'cooldown': 300, 'damage': 20, 'graphic':'../Graphics/weapons/axe/full.png'},
|
||||||
'rapier':{'cooldown': 50, 'damage': 8, 'graphic':'../Graphics/graphics/weapons/rapier/full.png'},
|
'rapier':{'cooldown': 50, 'damage': 8, 'graphic':'../Graphics/weapons/rapier/full.png'},
|
||||||
'sai':{'cooldown': 80, 'damage': 10, 'graphic':'../Graphics/graphics/weapons/sai/full.png'}}
|
'sai':{'cooldown': 80, 'damage': 10, 'graphic':'../Graphics/weapons/sai/full.png'}}
|
||||||
|
|
||||||
# magic
|
# magic
|
||||||
magic_data = {
|
magic_data = {
|
||||||
'flame': {'strength': 5,'cost': 20,'graphic':'../Graphics/graphics/particles/flame/fire.png'},
|
'flame': {'strength': 5,'cost': 20,'graphic':'../Graphics/particles/flame/fire.png'},
|
||||||
'heal' : {'strength': 20,'cost': 10,'graphic':'../Graphics/graphics/particles/heal/heal.png'}}
|
'heal' : {'strength': 20,'cost': 10,'graphic':'../Graphics/particles/heal/heal.png'}}
|
||||||
|
|
||||||
# enemy
|
# enemy
|
||||||
monster_data = {
|
monster_data = {
|
||||||
|
|
0
Game/view/__init__.py
Normal file
35
Game/view/camera.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
class Camera(pygame.sprite.Group):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# General Setup
|
||||||
|
self.display_surface = pygame.display.get_surface()
|
||||||
|
self.half_width = self.display_surface.get_size()[0] // 2
|
||||||
|
self.half_height = self.display_surface.get_size()[1] // 2
|
||||||
|
self.offset = pygame.math.Vector2(100, 200)
|
||||||
|
|
||||||
|
# Creating the floor
|
||||||
|
self.floor_surf = pygame.image.load('../Graphics/tilemap/ground.png').convert()
|
||||||
|
self.floor_rect = self.floor_surf.get_rect(topleft = (0,0))
|
||||||
|
|
||||||
|
def custom_draw(self, player):
|
||||||
|
self.sprite_type = player.sprite_type
|
||||||
|
#Getting the offset
|
||||||
|
self.offset.x = player.rect.centerx - self.half_width
|
||||||
|
self.offset.y = player.rect.centery - self.half_height
|
||||||
|
|
||||||
|
# Drawing the floor
|
||||||
|
floor_offset_pos = self.floor_rect.topleft - self.offset
|
||||||
|
self.display_surface.blit(self.floor_surf, floor_offset_pos)
|
||||||
|
|
||||||
|
for sprite in sorted(self.sprites(), key = lambda sprite: sprite.rect.centery):
|
||||||
|
offset_pos = sprite.rect.topleft - self.offset
|
||||||
|
self.display_surface.blit(sprite.image, offset_pos)
|
||||||
|
|
||||||
|
def enemy_update(self, player):
|
||||||
|
enemy_sprites = [sprite for sprite in self.sprites() if hasattr(sprite, 'sprite_type') and sprite.sprite_type == 'enemy']
|
||||||
|
for enemy in enemy_sprites:
|
||||||
|
enemy.enemy_update(player)
|
61
Game/view/observer.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import pygame
|
||||||
|
import random
|
||||||
|
|
||||||
|
from utils.settings import *
|
||||||
|
from utils.support import import_folder
|
||||||
|
|
||||||
|
class Observer(pygame.sprite.Sprite):
|
||||||
|
|
||||||
|
def __init__(self, position, groups):
|
||||||
|
super().__init__(groups)
|
||||||
|
|
||||||
|
self.sprite_type = 'camera'
|
||||||
|
|
||||||
|
self.image = pygame.image.load('../Graphics/observer.png').convert_alpha()
|
||||||
|
self.rect = self.image.get_rect(topleft = position)
|
||||||
|
self.hitbox = self.rect.inflate(HITBOX_OFFSET[self.sprite_type])
|
||||||
|
|
||||||
|
# Stats
|
||||||
|
self.exp = -1 # This prints OBSERVER in the UI
|
||||||
|
self.speed = 10 # Speed for moving around
|
||||||
|
|
||||||
|
#Movement
|
||||||
|
self.direction = pygame.math.Vector2()
|
||||||
|
|
||||||
|
def input(self):
|
||||||
|
keys = pygame.key.get_pressed()
|
||||||
|
|
||||||
|
# Movement Input
|
||||||
|
if keys[pygame.K_w]:
|
||||||
|
self.direction.y = -1
|
||||||
|
self.status = 'up'
|
||||||
|
self.can_move = False
|
||||||
|
elif keys[pygame.K_s]:
|
||||||
|
self.direction.y = 1
|
||||||
|
self.status = 'down'
|
||||||
|
self.can_move = False
|
||||||
|
else:
|
||||||
|
self.direction.y = 0
|
||||||
|
|
||||||
|
if keys[pygame.K_a]:
|
||||||
|
self.direction.x = -1
|
||||||
|
self.status = 'left'
|
||||||
|
self.can_move = False
|
||||||
|
elif keys[pygame.K_d]:
|
||||||
|
self.direction.x = 1
|
||||||
|
self.status = 'right'
|
||||||
|
self.can_move = False
|
||||||
|
else:
|
||||||
|
self.direction.x = 0
|
||||||
|
|
||||||
|
def move(self, speed):
|
||||||
|
if self.direction.magnitude() != 0:
|
||||||
|
self.direction = self.direction.normalize()
|
||||||
|
self.hitbox.x += self.direction.x * speed
|
||||||
|
self.hitbox.y += self.direction.y * speed
|
||||||
|
self.rect.center = self.hitbox.center
|
||||||
|
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.input()
|
||||||
|
self.move(self.speed)
|
Before Width: | Height: | Size: 494 B After Width: | Height: | Size: 494 B |
Before Width: | Height: | Size: 500 B After Width: | Height: | Size: 500 B |
Before Width: | Height: | Size: 575 B After Width: | Height: | Size: 575 B |
Before Width: | Height: | Size: 867 B After Width: | Height: | Size: 867 B |
Before Width: | Height: | Size: 845 B After Width: | Height: | Size: 845 B |
Before Width: | Height: | Size: 961 B After Width: | Height: | Size: 961 B |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 982 B |
Before Width: | Height: | Size: 863 B After Width: | Height: | Size: 863 B |
Before Width: | Height: | Size: 866 B After Width: | Height: | Size: 866 B |
Before Width: | Height: | Size: 919 B After Width: | Height: | Size: 919 B |
Before Width: | Height: | Size: 1,006 B After Width: | Height: | Size: 1,006 B |
Before Width: | Height: | Size: 852 B After Width: | Height: | Size: 852 B |
Before Width: | Height: | Size: 552 B After Width: | Height: | Size: 552 B |
Before Width: | Height: | Size: 798 B After Width: | Height: | Size: 798 B |
Before Width: | Height: | Size: 557 B After Width: | Height: | Size: 557 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 939 B After Width: | Height: | Size: 939 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 939 B After Width: | Height: | Size: 939 B |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 748 B After Width: | Height: | Size: 748 B |
Before Width: | Height: | Size: 994 B After Width: | Height: | Size: 994 B |
Before Width: | Height: | Size: 1,012 B After Width: | Height: | Size: 1,012 B |
Before Width: | Height: | Size: 747 B After Width: | Height: | Size: 747 B |
Before Width: | Height: | Size: 473 B After Width: | Height: | Size: 473 B |
Before Width: | Height: | Size: 468 B After Width: | Height: | Size: 468 B |
Before Width: | Height: | Size: 384 B After Width: | Height: | Size: 384 B |
Before Width: | Height: | Size: 466 B After Width: | Height: | Size: 466 B |
Before Width: | Height: | Size: 489 B After Width: | Height: | Size: 489 B |
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 378 B |
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 342 B |
Before Width: | Height: | Size: 195 B After Width: | Height: | Size: 195 B |
Before Width: | Height: | Size: 224 B After Width: | Height: | Size: 224 B |
Before Width: | Height: | Size: 267 B After Width: | Height: | Size: 267 B |
Before Width: | Height: | Size: 266 B After Width: | Height: | Size: 266 B |
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 268 B |
Before Width: | Height: | Size: 264 B After Width: | Height: | Size: 264 B |
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 269 B |
Before Width: | Height: | Size: 258 B After Width: | Height: | Size: 258 B |
Before Width: | Height: | Size: 245 B After Width: | Height: | Size: 245 B |
Before Width: | Height: | Size: 217 B After Width: | Height: | Size: 217 B |
Before Width: | Height: | Size: 191 B After Width: | Height: | Size: 191 B |
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 181 B |
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 322 B |
Before Width: | Height: | Size: 403 B After Width: | Height: | Size: 403 B |
Before Width: | Height: | Size: 427 B After Width: | Height: | Size: 427 B |
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B |
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 324 B |
Before Width: | Height: | Size: 416 B After Width: | Height: | Size: 416 B |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 971 B After Width: | Height: | Size: 971 B |
Before Width: | Height: | Size: 980 B After Width: | Height: | Size: 980 B |
Before Width: | Height: | Size: 962 B After Width: | Height: | Size: 962 B |
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 884 B |
Before Width: | Height: | Size: 886 B After Width: | Height: | Size: 886 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 811 B After Width: | Height: | Size: 811 B |
Before Width: | Height: | Size: 962 B After Width: | Height: | Size: 962 B |
Before Width: | Height: | Size: 992 B After Width: | Height: | Size: 992 B |
Before Width: | Height: | Size: 1,002 B After Width: | Height: | Size: 1,002 B |