326 lines
12 KiB
Python
326 lines
12 KiB
Python
from os import path
|
|
import random
|
|
import pygame.draw
|
|
from mlgame.utils.enum import get_ai_name
|
|
from mlgame.view.view_model import create_asset_init_data, create_image_view_data, create_rect_view_data
|
|
from .env import TURN_LEFT_CMD, TURN_RIGHT_CMD, FORWARD_CMD, BACKWARD_CMD, \
|
|
AIM_LEFT_CMD, AIM_RIGHT_CMD, SHOOT, SHOOT_COOLDOWN, IMAGE_DIR, ORANGE, BLUE, IS_DEBUG
|
|
from .Gun import Gun
|
|
|
|
Vec = pygame.math.Vector2
|
|
|
|
|
|
class Player(pygame.sprite.Sprite):
|
|
def __init__(self, construction, **kwargs):
|
|
super().__init__()
|
|
"""
|
|
初始化玩家資料
|
|
construction可直接由TiledMap打包地圖資訊後傳入
|
|
:param construction:
|
|
:param kwargs:
|
|
"""
|
|
self.id = construction["_id"]
|
|
self.no = construction["_no"]
|
|
self.rect = pygame.Rect(construction["_init_pos"], construction["_init_size"])
|
|
self.play_rect_area = kwargs["play_rect_area"]
|
|
self.origin_xy = self.rect.topleft
|
|
self.origin_center = self.rect.center
|
|
self.origin_size = (self.rect.width, self.rect.height)
|
|
self.original_rect = self.rect.copy()
|
|
self.draw_pos = self.rect.topleft
|
|
self.surface = pygame.Surface(self.origin_size)
|
|
self.angle = 0
|
|
self.score = 0
|
|
self.used_frame = 0
|
|
self.last_shoot_frame = 0
|
|
self.lives = 3
|
|
self.power = 10
|
|
self.vel = Vec(0, 0)
|
|
|
|
self.speed = 8
|
|
self.sqrt2 = 1.414
|
|
# TODO refactor use vel
|
|
self.move_dict = {"left_up": Vec(-self.speed/self.sqrt2, -self.speed/self.sqrt2),
|
|
"right_up": Vec(self.speed/self.sqrt2, -self.speed/self.sqrt2),
|
|
"left_down": Vec(-self.speed/self.sqrt2, self.speed/self.sqrt2),
|
|
"right_down": Vec(self.speed/self.sqrt2, self.speed/self.sqrt2),
|
|
"left": Vec(-self.speed, 0), "right": Vec(self.speed, 0), "up": Vec(0, -self.speed),
|
|
"down": Vec(0, self.speed)}
|
|
self.rot = 0
|
|
self.last_shoot_frame = self.used_frame
|
|
self.last_turn_frame = self.used_frame
|
|
self.rot_speed = 45
|
|
self.oil = 100
|
|
self.is_alive = True
|
|
self.is_shoot = False
|
|
self.is_forward = False
|
|
self.is_backward = False
|
|
self.is_turn_right = False
|
|
self.is_turn_left = False
|
|
self.collided = False
|
|
self.action_history = []
|
|
self.act_cd = kwargs["act_cd"]
|
|
self.quadrant = 0
|
|
|
|
self.gun = Gun(self.id, self.rect.topleft, (self.rect.width, self.rect.height), **kwargs)
|
|
|
|
self.calculate_quadrant()
|
|
self.pre_rect = self.rect
|
|
|
|
def calculate_quadrant(self):
|
|
mid_x = self.play_rect_area.width // 2
|
|
mid_y = (self.play_rect_area.height - 100) // 2
|
|
self.quadrant = (
|
|
1 if self.rect.x >= mid_x and self.rect.y < mid_y else
|
|
2 if self.rect.x < mid_x and self.rect.y < mid_y else
|
|
3 if self.rect.x < mid_x and self.rect.y >= mid_y else
|
|
4
|
|
)
|
|
|
|
def update(self, command: dict):
|
|
self.pre_rect = self.rect
|
|
self.used_frame += 1
|
|
if self.lives <= 0:
|
|
self.is_alive = False
|
|
self.gun.is_alive = False
|
|
self.lives = 0
|
|
|
|
if not self.is_alive:
|
|
self.rect.topleft = Vec(self.play_rect_area.left + self.origin_size[0]*self.no
|
|
, self.play_rect_area.top - self.origin_size[1]*2)
|
|
return
|
|
|
|
self.rotate()
|
|
self.gun.update(self.rect.center)
|
|
|
|
if not self.act_cd:
|
|
self.is_turn_right = False
|
|
self.is_turn_left = False
|
|
elif self.used_frame - self.last_turn_frame > self.act_cd:
|
|
self.is_turn_right = False
|
|
self.is_turn_left = False
|
|
|
|
self.act(command[get_ai_name(self.no - 1)])
|
|
# check tank if out of playground
|
|
self.check_if_outofplayground()
|
|
|
|
def check_if_outofplayground(self):
|
|
self.rect.clamp_ip(self.play_rect_area)
|
|
|
|
def rotate(self):
|
|
self.rot = self.rot % 360
|
|
self.angle = 3.14 / 180 * self.rot
|
|
new_sur = pygame.transform.rotate(self.surface, self.rot)
|
|
origin_center = self.rect.center
|
|
self.rect = new_sur.get_rect()
|
|
self.rect.center = origin_center
|
|
self.draw_pos = self.rect.topleft
|
|
|
|
def act(self, commands: list):
|
|
if not commands or self.collided:
|
|
return None
|
|
|
|
# Only one action from the three categories can be executed in one frame
|
|
move_flag = False
|
|
shoot_flag = False
|
|
aim_flag = False
|
|
|
|
while commands:
|
|
command = commands.pop()
|
|
|
|
# Shoot
|
|
if not shoot_flag:
|
|
if self.power and SHOOT == command:
|
|
self.shoot()
|
|
|
|
shoot_flag = True
|
|
|
|
# Aiming
|
|
# TODO: Maybe the oil should be consumed when aiming
|
|
if not aim_flag:
|
|
if command == AIM_LEFT_CMD:
|
|
self.gun.turn_left()
|
|
elif command == AIM_RIGHT_CMD:
|
|
self.gun.turn_right()
|
|
|
|
aim_flag = True
|
|
|
|
if self.oil <= 0:
|
|
self.oil = 0
|
|
self.lives = 0
|
|
continue
|
|
|
|
# Movement
|
|
if not move_flag:
|
|
if TURN_LEFT_CMD == command and not self.is_turn_left and TURN_RIGHT_CMD != command:
|
|
self.oil -= 0.1
|
|
self.turn_left()
|
|
self.is_turn_left = True
|
|
self.is_forward = False
|
|
self.is_backward = False
|
|
self.is_turn_right = False
|
|
self.action_history.append(TURN_LEFT_CMD)
|
|
elif TURN_RIGHT_CMD == command and not self.is_turn_right and TURN_LEFT_CMD != command:
|
|
self.oil -= 0.1
|
|
self.turn_right()
|
|
self.is_turn_right = True
|
|
self.is_forward = False
|
|
self.is_backward = False
|
|
self.is_turn_left = False
|
|
self.action_history.append(TURN_RIGHT_CMD)
|
|
elif FORWARD_CMD == command and BACKWARD_CMD != command:
|
|
self.oil -= 0.1
|
|
self.forward()
|
|
self.is_forward = True
|
|
self.is_backward = False
|
|
self.is_turn_right = False
|
|
self.is_turn_left = False
|
|
self.action_history.append(FORWARD_CMD)
|
|
elif BACKWARD_CMD == command and FORWARD_CMD != command:
|
|
self.oil -= 0.1
|
|
self.backward()
|
|
self.is_backward = True
|
|
self.is_forward = False
|
|
self.is_turn_right = False
|
|
self.is_turn_left = False
|
|
self.action_history.append(BACKWARD_CMD)
|
|
|
|
move_flag = True
|
|
|
|
self.action_history = self.action_history[-1:]
|
|
|
|
def shoot(self):
|
|
if self.last_shoot_frame == 0 or self.used_frame - self.last_shoot_frame > SHOOT_COOLDOWN:
|
|
self.last_shoot_frame = self.used_frame
|
|
self.power -= 1
|
|
self.is_shoot = True
|
|
|
|
def forward(self):
|
|
if self.id != 1:
|
|
rot = self.rot + 180
|
|
if rot >= 360:
|
|
rot -= 360
|
|
else:
|
|
rot = self.rot
|
|
if rot == 0:
|
|
self.rect.center += self.move_dict["left"]
|
|
elif rot == 315:
|
|
self.rect.center += self.move_dict["left_up"]
|
|
elif rot == 270:
|
|
self.rect.center += self.move_dict["up"]
|
|
elif rot == 225:
|
|
self.rect.center += self.move_dict["right_up"]
|
|
elif rot == 180:
|
|
self.rect.center += self.move_dict["right"]
|
|
elif rot == 135:
|
|
self.rect.center += self.move_dict["right_down"]
|
|
elif rot == 90:
|
|
self.rect.center += self.move_dict["down"]
|
|
elif rot == 45:
|
|
self.rect.center += self.move_dict["left_down"]
|
|
|
|
def backward(self):
|
|
if self.id != 1:
|
|
rot = self.rot + 180
|
|
if rot >= 360:
|
|
rot -= 360
|
|
else:
|
|
rot = self.rot
|
|
if rot == 0:
|
|
self.rect.center += self.move_dict["right"]
|
|
elif rot == 315:
|
|
self.rect.center += self.move_dict["right_down"]
|
|
elif rot == 270:
|
|
self.rect.center += self.move_dict["down"]
|
|
elif rot == 225:
|
|
self.rect.center += self.move_dict["left_down"]
|
|
elif rot == 180:
|
|
self.rect.center += self.move_dict["left"]
|
|
elif rot == 135:
|
|
self.rect.center += self.move_dict["left_up"]
|
|
elif rot == 90:
|
|
self.rect.center += self.move_dict["up"]
|
|
elif rot == 45:
|
|
self.rect.center += self.move_dict["right_up"]
|
|
|
|
def turn_left(self):
|
|
self.last_turn_frame = self.used_frame
|
|
self.rot += self.rot_speed
|
|
|
|
def turn_right(self):
|
|
self.last_turn_frame = self.used_frame
|
|
self.rot -= self.rot_speed
|
|
|
|
def collide_with_walls(self):
|
|
self.rect = self.pre_rect
|
|
|
|
def collide_with_bullets(self):
|
|
self.lives -= 1
|
|
|
|
def get_power(self, power: int):
|
|
self.power += power
|
|
if self.power > 10:
|
|
self.power = 10
|
|
elif self.power < 0:
|
|
self.power = 0
|
|
|
|
def get_oil(self, oil: int):
|
|
self.oil += oil
|
|
if self.oil > 100:
|
|
self.oil = 100
|
|
elif self.oil < 0:
|
|
self.oil = 0
|
|
|
|
def get_rot(self):
|
|
if self.id == 2:
|
|
return (self.rot + 180) % 360
|
|
return self.rot
|
|
|
|
def get_data_from_obj_to_game(self) -> dict:
|
|
info = {"id": f"{self.no}P"
|
|
, "x": self.rect.x
|
|
, "y": self.rect.y
|
|
, "speed": self.speed
|
|
, "score": self.score
|
|
, "power": self.power
|
|
, "oil": self.oil
|
|
, "lives": self.lives
|
|
, "angle": self.get_rot()
|
|
, "gun_angle": self.gun.get_rot()
|
|
, "cooldown": 0
|
|
if self.last_shoot_frame == 0
|
|
or self.used_frame - self.last_shoot_frame > SHOOT_COOLDOWN
|
|
else SHOOT_COOLDOWN - self.used_frame + self.last_shoot_frame,
|
|
}
|
|
return info
|
|
|
|
def get_obj_progress_data(self) -> dict:
|
|
if not self.is_alive:
|
|
return []
|
|
image_data = create_image_view_data(f"{self.id}P", *self.draw_pos, *self.origin_size, self.angle)
|
|
return image_data
|
|
|
|
def get_obj_init_data(self) -> list:
|
|
img_data = {"1P": "https://raw.githubusercontent.com/Jesse-Jumbo/TankMan/main/asset/image/1P_body.svg",
|
|
"2P": "https://raw.githubusercontent.com/Jesse-Jumbo/TankMan/main/asset/image/2P_body.svg"}
|
|
image_init_data = []
|
|
for id, url in img_data.items():
|
|
image_init_data.append(create_asset_init_data(id, self.origin_size[0], self.origin_size[1],
|
|
path.join(IMAGE_DIR, f"{id}_body.png"), url))
|
|
return image_init_data
|
|
|
|
def get_info_to_game_result(self) -> dict:
|
|
info = {"no": f"{self.no}P"
|
|
, "score": self.score
|
|
, "lives": self.lives
|
|
}
|
|
if IS_DEBUG:
|
|
if self.rect.right > self.play_rect_area.right \
|
|
or self.rect.left < self.play_rect_area.left \
|
|
or self.rect.bottom > self.play_rect_area.bottom \
|
|
or self.rect.top < self.play_rect_area.top:
|
|
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!【OUT】!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
else:
|
|
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!【SAFE】!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
return info
|