This commit is contained in:
Eason
2024-06-12 16:09:12 +08:00
commit d8b781011b
156 changed files with 26489 additions and 0 deletions

80
TankMan/src/Bullet.py Normal file
View File

@ -0,0 +1,80 @@
from os import path
import pygame
from mlgame.view.view_model import create_asset_init_data, create_image_view_data
from .env import WINDOW_HEIGHT, WINDOW_WIDTH, IMAGE_DIR
Vec = pygame.math.Vector2
class Bullet(pygame.sprite.Sprite):
def __init__(self, construction, **kwargs):
super().__init__()
self.id = construction["_id"]
self.no = construction["_no"]
self.rect = pygame.Rect((0, 0), construction["_init_size"])
self.rect.center = construction["_init_pos"]
self.rot = kwargs["rot"]
self.play_rect_area = kwargs["play_rect_area"]
self.speed = kwargs["bullet_speed"]
self.map_width = WINDOW_WIDTH
self.map_height = WINDOW_HEIGHT
self.angle = 3.14 / 180 * (self.rot + 90)
# Refactor
if 7 > self.angle > 6:
self.angle = 0
self.sqrt2 = 1.414
self.move = {"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.max_travel_distance = (kwargs["bullet_travel_distance"] // self.speed + 1) * self.speed
self.travel_distance = 0
def update(self):
self.travel_distance += self.speed
if self.play_rect_area.top < self.rect.centery < self.play_rect_area.bottom \
and self.play_rect_area.left < self.rect.centerx < self.play_rect_area.right:
is_out = False
else:
is_out = True
if is_out or self.travel_distance >= self.max_travel_distance:
self.kill()
if self.rot == 0 or self.rot == 360:
self.rect.center += self.move["left"]
elif self.rot == 315 or self.rot == -45:
self.rect.center += self.move["left_up"]
elif self.rot == 270 or self.rot == -90:
self.rect.center += self.move["up"]
elif self.rot == 225 or self.rot == -135:
self.rect.center += self.move["right_up"]
elif self.rot == 180 or self.rot == -180:
self.rect.center += self.move["right"]
elif self.rot == 135 or self.rot == -225:
self.rect.center += self.move["right_down"]
elif self.rot == 90 or self.rot == -270:
self.rect.center += self.move["down"]
elif self.rot == 45 or self.rot == -315:
self.rect.center += self.move["left_down"]
def get_obj_progress_data(self):
img_id = "team_a_bullet" if self.id == 1 else "team_b_bullet"
return create_image_view_data(img_id, self.rect.x, self.rect.y, self.rect.width, self.rect.height,
self.angle)
def get_data_from_obj_to_game(self) -> dict:
info = {"id": f"{self.no}P_bullet",
"x": self.rect.x,
"y": self.rect.y,
"speed": self.speed,
"rot": self.rot
}
return info

116
TankMan/src/Game.py Normal file
View File

@ -0,0 +1,116 @@
from os import path
import pygame
from mlgame.game.paia_game import PaiaGame, GameStatus
from mlgame.view.view_model import Scene
from .TeamBattleMode import TeamBattleMode
from .game_module.fuctions import get_sprites_progress_data
MAP_WIDTH = 1000
MAP_HEIGHT = 600
GAME_DIR = path.dirname(__file__)
MAP_DIR = path.join(GAME_DIR, "..", "asset", "maps")
SOUND_DIR = path.join(GAME_DIR, "..", "asset", "sound")
IMAGE_DIR = path.join(GAME_DIR, "..", "asset", "image")
class Game(PaiaGame):
def __init__(self, user_num: int, green_team_num: int, blue_team_num: int, is_manual: str, frame_limit: int, sound: str):
super().__init__(user_num)
# init game
self.green_team_num = green_team_num
self.blue_team_num = blue_team_num
self.is_paused = False
self.is_debug = False
self.is_sound = False
self.is_manual = False
if sound == "on":
self.is_sound = True
if is_manual:
self.is_manual = True
self.attachements = []
self.frame_limit = frame_limit
self.game_mode = self.set_game_mode()
self.scene = Scene(width=self.game_mode.scene_width, height=self.game_mode.scene_height, color="#ffffff",
bias_y=50)
def get_data_from_game_to_player(self) -> dict:
to_players_data = self.game_mode.get_ai_data_to_player()
return to_players_data
def update(self, commands: dict):
self.handle_event(commands)
self.game_mode.debugging(self.is_debug)
if not self.is_paused:
self.frame_count += 1
self.game_mode.update(commands)
if not self.is_running():
return "RESET"
def reset(self):
self.frame_count = 0
self.game_mode.reset()
# self.rank()
def get_scene_init_data(self) -> dict:
"""
Get the scene and object information for drawing on the web
"""
game_info = {'scene': self.scene.__dict__,
'assets': self.game_mode.get_init_image_data()}
return game_info
def get_scene_progress_data(self) -> dict:
"""
Get the position of src objects for drawing on the web
"""
scene_progress = {'background': [],
'object_list': [*self.game_mode.background, *self.get_obj_progress_data()],
'toggle_with_bias': [*self.game_mode.get_toggle_with_bias_data()],
'toggle': self.game_mode.get_toggle_progress_data(),
'foreground': [],
'user_info': [],
'game_sys_info': {}}
return scene_progress
def get_obj_progress_data(self):
obj_list = []
for sprites in self.game_mode.obj_list:
obj_list.extend(get_sprites_progress_data(sprites))
obj_list.extend(self.game_mode.obj_rect_list)
return obj_list
def get_game_result(self):
"""
Get the src result for the web
"""
self.rank()
return {"frame_used": self.frame_count,
"state": self.game_result_state,
"attachment": self.attachements
}
def is_running(self):
return self.game_mode.status == GameStatus.GAME_ALIVE
def rank(self):
self.game_result_state = self.game_mode.state
self.attachements = self.game_mode.get_player_result()
return self.attachements
def handle_event(self, commands):
if ["DEBUG"] in commands.values():
self.is_debug = not self.is_debug
if ["PAUSED"] in commands.values():
self.is_paused = not self.is_paused
def set_game_mode(self):
sound_path = ""
if self.is_sound:
sound_path = SOUND_DIR
play_rect_area = pygame.Rect(0, 0, MAP_WIDTH, MAP_HEIGHT)
game_mode = TeamBattleMode(self.green_team_num, self.blue_team_num, self.is_manual, self.frame_limit, sound_path, play_rect_area)
return game_mode

153
TankMan/src/GenerateMap.py Normal file
View File

@ -0,0 +1,153 @@
from os import path
import random
import math
PLAYER_NUM = 1
OIL_NUM = 2
BULLET_NUM = 2
MAP_DIR = path.join(path.dirname(__file__), "..", "asset", 'maps')
MAP_VERSION = "1.9"
TILED_VERSION = "1.9.2"
class MapGenerator:
def __init__(self, green_team_num : int, blue_team_num : int, width : int, height : int) -> None:
self.green_team_num = green_team_num
self.blue_team_num = blue_team_num
# height should be a factor of 600
# default is 20, 12
self.width = width
self.height = height
self.height_per_tile = math.floor(600/self.height)
self.width_per_tile = self.height_per_tile
self.screen_width = self.width * self.height_per_tile
self.screen_height = 700
def getTileSize(self) -> int:
return self.height_per_tile
def getScreeenSize(self) -> tuple:
return self.screen_width, self.screen_height
def pos2index(self, x : int, y : int) -> int:
return y * (self.width * 2 + 1) + x * 2
def mirrored_pos(self, x : int, y : int) -> tuple:
return self.width - x - 1, self.height - y - 1
def random_pos(self, map_arr) -> tuple:
random_x, random_y = random.randint(1, self.width-2), random.randint(1, self.height-2)
mir_x, mir_y = self.mirrored_pos(random_x, random_y)
while map_arr[random_y][random_x] != 0 or map_arr[mir_y][mir_x] != 0:
random_x, random_y = random.randint(1, self.width-2), random.randint(1, self.height-2)
mir_x, mir_y = self.mirrored_pos(random_x, random_y)
return random_x, random_y
def generate_map_str(self) -> str:
# generate default map[y][x]
map_arr = [[0 for _ in range(self.width)] for _ in range(self.height)]
for x in range(self.width):
map_arr[0][x] = 3
map_arr[self.height-1][x] = 3
for y in range(self.height):
map_arr[y][0] = 3
map_arr[y][self.width-1] = 3
if self.width % 2 == 1:
for y in range(1, self.height-1):
map_arr[y][self.width//2] = 3
else:
for y in range(1, self.height):
if y < math.ceil(self.height/2):
map_arr[y][self.width//2-1] = 3
else:
map_arr[y][self.width//2] = 3
# add player
for _ in range(min(self.green_team_num, self.blue_team_num)):
rand_x, rand_y = self.random_pos(map_arr)
while rand_x < self.width // 2:
rand_x, rand_y = self.random_pos(map_arr)
mir_x, mir_y = self.mirrored_pos(rand_x, rand_y)
map_arr[rand_y][rand_x] = 1
map_arr[mir_y][mir_x] = 2
# add remaining green team
for _ in range(self.green_team_num - self.blue_team_num):
rand_x, rand_y = self.random_pos(map_arr)
while rand_x < self.width // 2:
rand_x, rand_y = self.random_pos(map_arr)
map_arr[rand_y][rand_x] = 1
# add remaining blue team
for _ in range(self.blue_team_num - self.green_team_num):
rand_x, rand_y = self.random_pos(map_arr)
while rand_x >= self.width // 2:
rand_x, rand_y = self.random_pos(map_arr)
map_arr[rand_y][rand_x] = 2
# add bullet station
for _ in range(BULLET_NUM):
rand_x, rand_y = self.random_pos(map_arr)
mir_x, mir_y = self.mirrored_pos(rand_x, rand_y)
map_arr[rand_y][rand_x] = 4
map_arr[mir_y][mir_x] = 4
# add oil station
for _ in range(OIL_NUM):
rand_x, rand_y = self.random_pos(map_arr)
mir_x, mir_y = self.mirrored_pos(rand_x, rand_y)
map_arr[rand_y][rand_x] = 5
map_arr[mir_y][mir_x] = 5
map_str = ""
for row in map_arr:
for id in row:
map_str += str(id) + ","
map_str += "\n"
map_str = map_str[:-2]
return map_str
def generate_map(self):
map_name = f"map_{self.green_team_num}_v_{self.blue_team_num}.tmx"
map_path = path.join(MAP_DIR, map_name)
print(f'generate map at : {map_path}', flush=True)
with open(map_path, "w") as file:
# file.write("test")
file.write(f"""\
<?xml version="1.0" encoding="UTF-8"?>
<map version="{MAP_VERSION}" tiledversion="{TILED_VERSION}" orientation="orthogonal" renderorder="right-down" width="{self.width}" height="{self.height}" tilewidth="{self.width_per_tile}" tileheight="{self.height_per_tile}" infinite="0" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" name="TankManObj" tilewidth="{self.width_per_tile}" tileheight="{self.height_per_tile}" tilecount="5" columns="5">
<image source="../image/TankManObj.png" width="250" height="50"/>
</tileset>
<layer id="1" name="layer 1" width="{self.width}" height="{self.height}">
<data encoding="csv">
{self.generate_map_str()}
</data>
</layer>
</map>
""")
if __name__ == "__main__":
map_generator = MapGenerator(1, 1)
map_generator.generate_map()

106
TankMan/src/Gun.py Normal file
View File

@ -0,0 +1,106 @@
from os import path
import pygame
from mlgame.view.view_model import (create_asset_init_data,
create_image_view_data)
from .env import IMAGE_DIR
class Gun(pygame.sprite.Sprite):
def __init__(self, id, pos, size, **kwargs):
super().__init__()
self.id = id
self.rect = pygame.Rect(pos, size)
self.origin_size = (self.rect.width, self.rect.height)
self.draw_pos = self.rect.topleft
self.surface = pygame.Surface(self.origin_size)
self.rot = 0
self.rot_speed = 45
self.used_frame = 0
self.last_turn_frame = self.used_frame
self.act_cd = kwargs["act_cd"]
self.is_alive = True
self.is_turn_left = False
self.is_turn_right = False
if self.id == 1:
self.pivot_offset = pygame.Vector2(-8, 0)
else:
self.pivot_offset = pygame.Vector2(8, 0)
def update(self, gun_pos):
self.used_frame += 1
self.rotate()
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.rect.center = gun_pos + self.pivot_offset.rotate(-self.rot)
self.draw_pos = self.rect.topleft
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 turn_left(self):
if self.is_turn_left:
return
self.rot += self.rot_speed
self.last_turn_frame = self.used_frame
self.is_turn_left = True
self.is_turn_right = False
def turn_right(self):
if self.is_turn_right:
return
self.rot -= self.rot_speed
self.last_turn_frame = self.used_frame
self.is_turn_left = False
self.is_turn_right = True
def get_rot(self):
if self.id == 2:
return (self.rot + 180) % 360
return self.rot
def get_obj_progress_data(self) -> dict:
if not self.is_alive:
return {}
image_data = create_image_view_data(
f"{self.id}P_gun", *self.draw_pos, *self.origin_size, self.angle
)
return image_data
def get_obj_init_data(self) -> list:
img_data = {
"1P_gun": "https://raw.githubusercontent.com/Jesse-Jumbo/TankMan/main/asset/image/1P_gun.svg",
"2P_gun": "https://raw.githubusercontent.com/Jesse-Jumbo/TankMan/main/asset/image/2P_gun.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}.png"),
url,
)
)
return image_init_data

325
TankMan/src/Player.py Normal file
View File

@ -0,0 +1,325 @@
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

65
TankMan/src/Station.py Normal file
View File

@ -0,0 +1,65 @@
from os import path
import pygame
from mlgame.view.view_model import create_asset_init_data, create_image_view_data
from .env import IMAGE_DIR, WINDOW_HEIGHT, WINDOW_WIDTH
class Station(pygame.sprite.Sprite):
def __init__(self, construction, **kwargs):
super().__init__()
self.id = construction["_id"]
self.rect = pygame.Rect(construction["_init_pos"], construction["_init_size"])
self.power = kwargs["capacity"]
self.angle = 0
if self.rect.x >= WINDOW_WIDTH // 2 and self.rect.y < (WINDOW_HEIGHT - 100) // 2:
self.quadrant = 1
elif self.rect.x < WINDOW_WIDTH // 2 and self.rect.y < (WINDOW_HEIGHT - 100) // 2:
self.quadrant = 2
elif self.rect.x < WINDOW_WIDTH // 2 and self.rect.y >= (WINDOW_HEIGHT - 100) // 2:
self.quadrant = 3
else:
self.quadrant = 4
self.spawn_cd = kwargs["spawn_cd"]
self.cooldown = 0
self.is_alive = True
def update(self):
if not self.is_alive:
self.cooldown -= 1
if self.cooldown <= 0:
self.is_alive = True
def collect(self):
self.is_alive = False
self.cooldown = self.spawn_cd
def get_data_from_obj_to_game(self):
if 5 == self.id:
info = {"id": "oil", "x": self.rect.x, "y": self.rect.y, "power": self.power if self.is_alive else 0}
else:
info = {"id": "bullets", "x": self.rect.x, "y": self.rect.y, "power": self.power if self.is_alive else 0}
return info
def get_obj_progress_data(self):
if not self.is_alive:
return []
if 5 == self.id:
return create_image_view_data(f"oil", self.rect.x, self.rect.y
, self.rect.width, self.rect.height, self.angle)
else:
return create_image_view_data(f"bullets", self.rect.x, self.rect.y
, self.rect.width, self.rect.height, self.angle)
def get_obj_init_data(self):
bullets_id = "bullets"
oil_id = "oil"
bullets_url = f"https://raw.githubusercontent.com/Jesse-Jumbo/TankMan/main/asset/image/{bullets_id}.svg"
oil_url = f"https://raw.githubusercontent.com/Jesse-Jumbo/TankMan/main/asset/image/{oil_id}.svg"
image_init_data = [create_asset_init_data(bullets_id, self.rect.width, self.rect.height,
path.join(IMAGE_DIR, f"{bullets_id}.png"), bullets_url)
, create_asset_init_data(oil_id, self.rect.width, self.rect.height,
path.join(IMAGE_DIR, f"{oil_id}.png"), oil_url)]
return image_init_data

View File

@ -0,0 +1,508 @@
import random
import time
import pygame.event
import pygame.event
from src.game_module.SoundController import create_sounds_data, create_bgm_data, SoundController
from src.game_module.TiledMap import create_construction, TiledMap
from mlgame.game.paia_game import GameResultState, GameStatus
from mlgame.utils.enum import get_ai_name
from mlgame.view.view_model import create_asset_init_data, create_text_view_data, \
create_rect_view_data, create_line_view_data
from mlgame.view.view_model import create_image_view_data
from .Bullet import Bullet
from .Gun import Gun
from .Player import Player
from .Station import Station
from .Wall import Wall
from .collide_hit_rect import *
from .env import *
from .game_module.fuctions import set_topleft, add_score, set_shoot
class TeamBattleMode:
def __init__(self, green_team_num: int, blue_team_num: int, is_manual: bool, frame_limit: int, sound_path: str,
play_rect_area: pygame.Rect):
# init game
pygame.init()
self.sound_path = sound_path
self.green_team_num = green_team_num
self.blue_team_num = blue_team_num if (6 - (green_team_num + blue_team_num)) >= 0 else (6 - green_team_num)
self.map_name = f"map_{green_team_num}_v_{self.blue_team_num}.tmx" if not IS_DEBUG else f"test_map_{green_team_num}_v_{self.blue_team_num}.tmx"
self.map_path = path.join(MAP_DIR, self.map_name)
self.map = TiledMap(self.map_path)
self.scene_width = self.map.map_width
self.scene_height = self.map.map_height + 100
self.width_center = self.scene_width // 2
self.height_center = self.scene_height // 2
self.play_rect_area = play_rect_area
self.used_frame = 0
self.state = GameResultState.FAIL
self.status = GameStatus.GAME_ALIVE
self.sound_controller = SoundController(sound_path, self.get_sound_data())
self.sound_controller.play_music(self.get_bgm_data())
self.frame_limit = frame_limit
self.is_manual = is_manual
self.obj_rect_list = []
self.team_green_score = 0
self.team_blue_score = 0
# control variables
self.is_invincible = False
self.is_through_wall = False
# initialize sprites group
self.all_sprites = pygame.sprite.Group()
self.players_a = pygame.sprite.Group()
self.players_b = pygame.sprite.Group()
self.all_players = pygame.sprite.Group()
self.guns = pygame.sprite.Group()
self.walls = pygame.sprite.Group()
self.bullets = pygame.sprite.Group()
self.bullet_stations = pygame.sprite.Group()
self.oil_stations = pygame.sprite.Group()
# init players
act_cd = 0
if self.is_manual:
act_cd = 10
# init obj data
self.map.add_init_obj_data(PLAYER_1_IMG_NO, Player, act_cd=act_cd, play_rect_area=self.play_rect_area)
self.map.add_init_obj_data(PLAYER_2_IMG_NO, Player, act_cd=act_cd, play_rect_area=self.play_rect_area)
self.map.add_init_obj_data(WALL_IMG_NO, Wall, margin=8, spacing=8)
self.map.add_init_obj_data(BULLET_STATION_IMG_NO, Station, spawn_cd=30, margin=2, spacing=2, capacity=5,
quadrant=1)
self.map.add_init_obj_data(OIL_STATION_IMG_NO, Station, spawn_cd=30, margin=2, spacing=2, capacity=30,
quadrant=1)
# create obj
all_obj = self.map.create_init_obj_dict()
# init players
self.players_a.add(all_obj[PLAYER_1_IMG_NO])
self.players_b.add(all_obj[PLAYER_2_IMG_NO])
no = 1
for player in self.players_a:
player.no = no
no += 1
for player in self.players_b:
player.no = no
no += 1
self.all_players.add(*self.players_a, *self.players_b)
self.guns.add(*[player.gun for player in self.all_players])
self.all_sprites.add(*self.players_a, *self.players_b)
self.all_sprites.add(*[player.gun for player in self.all_players])
# init walls
self.walls.add(all_obj[WALL_IMG_NO])
self.all_sprites.add(*self.walls)
# init bullet stations
self.bullet_stations.add(all_obj[BULLET_STATION_IMG_NO])
self.all_sprites.add(*self.bullet_stations)
# init oil stations
self.oil_stations.add(all_obj[OIL_STATION_IMG_NO])
self.all_sprites.add(*self.oil_stations)
# init pos list
self.all_pos_list = self.map.all_pos_list
self.empty_quadrant_pos_dict = self.map.empty_quadrant_pos_dict
self.background = []
for pos in self.all_pos_list:
no = random.randrange(3)
self.background.append(
create_image_view_data(f"floor_{no}", pos[0], pos[1], 50, 50, 0))
self.obj_list = [self.oil_stations, self.bullet_stations, self.bullets, self.all_players, self.guns, self.walls]
self.background.append(create_image_view_data("border", 0, -50, self.scene_width, WINDOW_HEIGHT, 0))
# init play get new score time
self.team_green_maxScoreTime = time.time()
self.team_blue_maxScoreTime = time.time()
self.team_green_maxScore = 0
self.team_blue_maxScore = 0
self.change_player_pos()
def update(self, command: dict):
# refactor
self.team_green_score = sum([player.score for player in self.players_a if isinstance(player, Player)])
self.team_blue_score = sum([player.score for player in self.players_b if isinstance(player, Player)])
self.used_frame += 1
self.check_collisions()
self.walls.update()
self.create_bullet(self.all_players)
self.bullets.update()
self.bullet_stations.update()
self.oil_stations.update()
self.all_players.update(command)
self.get_player_end()
if self.used_frame >= self.frame_limit:
self.get_game_end()
# check if getting new score
if self.team_green_score > self.team_green_maxScore:
self.team_green_maxScore = self.team_green_score
self.team_green_maxScoreTime = time.time()
if self.team_blue_score > self.team_blue_maxScore:
self.team_blue_maxScore = self.team_blue_score
self.team_blue_maxScoreTime = time.time()
def reset(self):
# reset init game
self.__init__(self.green_team_num, self.blue_team_num, self.is_manual, self.frame_limit, self.sound_path,
self.play_rect_area)
# reset player pos
self.change_player_pos()
def get_player_end(self):
is_alive_team_green = False
is_alive_team_blue = False
for player in self.all_players:
if isinstance(player, Player) and player.is_alive:
if player.no > self.green_team_num and not is_alive_team_blue:
is_alive_team_blue = True
elif player.no <= self.green_team_num:
is_alive_team_green = True
if not is_alive_team_blue:
self.set_result(GameResultState.FINISH, "GREEN_TEAM_WIN")
elif not is_alive_team_green:
self.set_result(GameResultState.FINISH, "BLUE_TEAM_WIN")
def get_game_end(self):
if self.team_green_score > self.team_blue_score:
self.set_result(GameResultState.FINISH, "GREEN_TEAM_WIN")
elif self.team_green_score < self.team_blue_score:
self.set_result(GameResultState.FINISH, "BLUE_TEAM_WIN")
else:
# if both the teams have a score of 0
if self.team_green_maxScore == 0 and self.team_blue_maxScore == 0:
conditions = ["GREEN_TEAM_WIN","BLUE_TEAM_WIN"]
chosen_condition = random.choice(conditions)
self.set_result(GameResultState.FINISH, chosen_condition)
else:
# if both teams have scored and their scores are equal
if self.team_green_maxScoreTime > self.team_blue_maxScoreTime:
self.set_result(GameResultState.FINISH, "BLUE_TEAM_WIN")
else:
self.set_result(GameResultState.FINISH, "GREEN_TEAM_WIN")
def set_result(self, state: str, status: str):
self.state = state
self.status = status
def get_player_result(self) -> list:
"""Define the end of game will return the player's info for user"""
res = []
for player in self.all_players:
if isinstance(player, Player):
if player.no > self.green_team_num:
team_id = "blue"
else:
team_id = "green"
get_res = player.get_info_to_game_result()
get_res["no"] = f"{team_id}_{player.no}P"
get_res["player"] = f"{player.no}P"
get_res["state"] = self.state
get_res["status"] = self.status
get_res["used_frame"] = self.used_frame
if team_id == "green":
timestamp = self.team_green_maxScoreTime
else:
timestamp = self.team_blue_maxScoreTime
get_res["latestScoreTime"] = "{:.3f}".format(timestamp)
res.append(get_res)
for player in res:
if player["no"].find("green")!=-1:
player["rank"] = 1 if player["status"] == "GREEN_TEAM_WIN" else 2
elif player["no"].find("blue")!=-1:
player["rank"] = 1 if player["status"] == "BLUE_TEAM_WIN" else 2
sorted_res = sorted(res, key=lambda x: x["rank"])
# Result
return sorted_res
def check_collisions(self):
if not self.is_through_wall:
collide_with_walls(self.all_players, self.walls)
if not self.is_invincible:
player_score_data = collide_with_bullets(self.all_players, self.bullets, self.green_team_num)
for player, score in player_score_data.items():
self.add_player_score(player, score)
# TODO refactor stations
supply_stations = []
# Check collision between player and supply stations
supply_stations.extend(collide_with_supply_stations(self.all_players, self.bullet_stations))
supply_stations.extend(collide_with_supply_stations(self.all_players, self.oil_stations))
# Check collision between bullet and supply stations
supply_stations.extend(collide_with_supply_stations(self.bullets, self.bullet_stations))
supply_stations.extend(collide_with_supply_stations(self.bullets, self.oil_stations))
# Update stations position
self.change_obj_pos(supply_stations)
player_score_data = collide_with_bullets(self.walls, self.bullets)
for player, score in player_score_data.items():
self.add_player_score(player, score)
def change_player_pos(self):
for player in self.all_players:
quadrant = player.quadrant
self.empty_quadrant_pos_dict[quadrant].append(player.rect.topleft)
if quadrant == 2 or quadrant == 3:
player.quadrant = random.choice([2, 3])
else:
player.quadrant = random.choice([1, 4])
quadrant = player.quadrant
new_pos = self.empty_quadrant_pos_dict[quadrant].pop(
random.randrange(len(self.empty_quadrant_pos_dict[quadrant])))
set_topleft(player, new_pos)
set_topleft(player.gun, new_pos)
# TODO move method to Station
def change_obj_pos(self, objs=None):
if objs is None:
return
for obj in objs:
quadrant = obj.quadrant
self.empty_quadrant_pos_dict[quadrant].append(obj.rect.topleft)
if quadrant == 2 or quadrant == 3:
obj.quadrant = random.choice([2, 3])
else:
obj.quadrant = random.choice([1, 4])
quadrant = obj.quadrant
new_pos = self.empty_quadrant_pos_dict[quadrant].pop(
random.randrange(len(self.empty_quadrant_pos_dict[quadrant])))
set_topleft(obj, new_pos)
def create_bullet(self, sprites: pygame.sprite.Group):
for sprite in sprites:
if not sprite.is_shoot:
continue
self.sound_controller.play_sound("shoot", 0.03, -1)
init_data = create_construction(sprite.id, sprite.no, sprite.rect.center, (BULLET_SIZE[0], BULLET_SIZE[1]))
bullet = Bullet(init_data, rot=sprite.gun.get_rot(), margin=2, spacing=2, bullet_speed=BULLET_SPEED,
bullet_travel_distance=BULLET_TRAVEL_DISTANCE
, play_rect_area=self.play_rect_area)
self.bullets.add(bullet)
self.all_sprites.add(bullet)
set_shoot(sprite, False)
def get_init_image_data(self):
init_image_data = []
for i in range(3):
init_image_data.append(create_asset_init_data(f"floor_{i}", 50, 50
, path.join(IMAGE_DIR, f"grass_{i}.png"),
f"https://raw.githubusercontent.com/Jesse-Jumbo/TankMan/main/asset/image/grass_{i}.png"))
for i in range(15):
init_image_data.append(create_asset_init_data(f"hourglass_{i}", 42, 42
, path.join(IMAGE_DIR, f"hourglass_{i}.png"),
f"https://raw.githubusercontent.com/Jesse-Jumbo/TankMan/main/asset/image/hourglass_{i}.png"))
for station in self.bullet_stations:
if isinstance(station, Station):
for data in station.get_obj_init_data():
init_image_data.append(data)
break
for wall in self.walls:
if isinstance(wall, Wall):
for data in wall.get_obj_init_data():
init_image_data.append(data)
break
img_id = ["team_a_bullet", "team_b_bullet"]
for id in img_id:
img_url = f"https://raw.githubusercontent.com/Jesse-Jumbo/TankMan/main/asset/image/{id}.svg"
bullet_image_init_data = create_asset_init_data(id, BULLET_SIZE[0], BULLET_SIZE[1],
path.join(IMAGE_DIR, f"{id}.png"), img_url)
init_image_data.append(bullet_image_init_data)
border_image_init_data = create_asset_init_data("border", self.scene_width, WINDOW_HEIGHT,
path.join(IMAGE_DIR, "border.png"),
f"https://raw.githubusercontent.com/Jesse-Jumbo/TankMan/main/asset/image/border.svg")
init_image_data.append(border_image_init_data)
for player in self.all_players:
if isinstance(player, Player):
data = player.get_obj_init_data()
init_image_data.append(data[0])
init_image_data.append(data[1])
break
for gun in self.guns:
if isinstance(gun, Gun):
data = gun.get_obj_init_data()
init_image_data.append(data[0])
init_image_data.append(data[1])
break
for i in range(1, 4):
team_a_lives = "team_a_lives"
team_a_lives_image_init_data = create_asset_init_data(f"{team_a_lives}_{i}", LIVES_SIZE[0], LIVES_SIZE[1],
path.join(IMAGE_DIR, f"{team_a_lives}_{i}.png"),
f"https://raw.githubusercontent.com/Jesse-Jumbo/TankMan/main/asset/image/{team_a_lives}_{i}.svg")
init_image_data.append(team_a_lives_image_init_data)
team_b_lives = "team_b_lives"
team_b_lives_image_init_data = create_asset_init_data(f"{team_b_lives}_{i}", LIVES_SIZE[0], LIVES_SIZE[1],
path.join(IMAGE_DIR, f"{team_b_lives}_{i}.png"),
f"https://raw.githubusercontent.com/Jesse-Jumbo/TankMan/main/asset/image/{team_b_lives}_{i}.svg")
init_image_data.append(team_b_lives_image_init_data)
return init_image_data
def get_toggle_progress_data(self):
toggle_data = []
hourglass_index = 0
if self.is_manual:
hourglass_index = self.used_frame // 10 % 15
toggle_data.append(
create_image_view_data(image_id=f"hourglass_{hourglass_index}", x=0, y=2, width=20, height=20, angle=0))
x = 23
y = 8
for frame in range((self.frame_limit - self.used_frame) // int((30 * 2))):
toggle_data.append(create_rect_view_data("frame", x, y, 3, 10, RED))
x += 3.5
toggle_data.append(create_text_view_data(f"Frame: {self.frame_limit - self.used_frame}",
self.width_center + self.width_center // 2 + 85, 8, RED,
"24px Arial BOLD"))
x = 24
y = 20
for score in range(min(self.team_green_score, self.team_blue_score)):
toggle_data.append(create_rect_view_data(name="score", x=x, y=y, width=1, height=10, color=ORANGE))
x += 1.5
if x > self.width_center:
if y == 32:
y = 44
else:
y = 32
x = 24
for score in range(abs(self.team_green_score - self.team_blue_score)):
if self.team_green_score > self.team_blue_score:
toggle_data.append(create_rect_view_data("score", x, y, 1, 10, DARKGREEN))
else:
toggle_data.append(create_rect_view_data("score", x, y, 1, 10, BLUE))
x += 1.5
if x > self.width_center:
if y == 32:
y = 44
else:
y = 32
x = 24
# 1P
x = WINDOW_WIDTH - 125
y = WINDOW_HEIGHT - 40
toggle_data.append(create_text_view_data(f"Score: {self.team_green_score}", x, y, DARKGREEN, "24px Arial BOLD"))
# 2P
x = 5
y = WINDOW_HEIGHT - 40
toggle_data.append(create_text_view_data(f"Score: {self.team_blue_score}", x, y, BLUE, "24px Arial BOLD"))
for player in self.all_players:
if isinstance(player, Player) and player.is_alive:
# lives
team_id = "team_a_lives" if player.id == 1 else "team_b_lives"
color = DARKGREEN if player.id == 1 else BLUE
x = player.play_rect_area.midbottom[0] + 7 + (player.no - 1) * 60 if player.id == 1 \
else player.play_rect_area.midbottom[0] - (player.no - self.green_team_num) * 60
y = player.play_rect_area.height + 73
toggle_data.append(
create_text_view_data(f"{player.no}P", x - 5, y - 25, color, "22px Arial BOLD"))
for live in range(1, player.lives + 1):
toggle_data.append(create_image_view_data(f"{team_id}_{live}", x, y, LIVES_SIZE[0], LIVES_SIZE[1]))
x += 10
y -= 10
return toggle_data
def get_toggle_with_bias_data(self):
toggle_with_bias_data = []
color = WHITE
for player in self.all_players:
if isinstance(player, Player) and player.is_alive:
# number
if player.no > self.green_team_num:
color = WHITE
x = player.rect.x
y = player.rect.y - 18
toggle_with_bias_data.append(create_text_view_data(f"{player.no}P", x, y, color, "16px Arial BOLD"))
team_id = "team_a"
if player.no > self.green_team_num:
team_id = "team_b"
# oil
y = player.rect.bottom
toggle_with_bias_data.append(
create_rect_view_data(f"{team_id}_oil", x, y, int(player.oil * 0.5), 8, ORANGE))
# power
y = player.rect.bottom + 10
for power in range(player.power):
toggle_with_bias_data.append(create_rect_view_data(f"{team_id}_power", x + 1, y, 3, 8, BLUE))
x += 5
return toggle_with_bias_data
def get_ai_data_to_player(self):
to_player_data = {}
num = 0
competitor_info = {
1: [player.get_data_from_obj_to_game() for player in self.players_a if isinstance(player, Player)]
, 2: [player.get_data_from_obj_to_game() for player in self.players_b if isinstance(player, Player)]
}
walls_info = [wall.get_data_from_obj_to_game() for wall in self.walls if isinstance(wall, Wall)]
bullet_stations_info = [bullst_station.get_data_from_obj_to_game() for bullst_station in self.bullet_stations if
isinstance(bullst_station, Station)]
oil_stations_info = [oil_station.get_data_from_obj_to_game() for oil_station in self.oil_stations if
isinstance(oil_station, Station)]
bullets_info = [bullet.get_data_from_obj_to_game() for bullet in self.bullets if
isinstance(bullet, Bullet)]
for player in self.players_a:
if isinstance(player, Player):
to_game_data = player.get_data_from_obj_to_game()
to_game_data["used_frame"] = self.used_frame
to_game_data["status"] = self.status
to_game_data["teammate_info"] = competitor_info[1]
to_game_data["competitor_info"] = competitor_info[2]
to_game_data["walls_info"] = walls_info
to_game_data["bullets_info"] = bullets_info
to_game_data["bullet_stations_info"] = bullet_stations_info
to_game_data["oil_stations_info"] = oil_stations_info
to_player_data[get_ai_name(num)] = to_game_data
num += 1
for player in self.players_b:
if isinstance(player, Player):
to_game_data = player.get_data_from_obj_to_game()
to_game_data["used_frame"] = self.used_frame
to_game_data["status"] = self.status
to_game_data["teammate_info"] = competitor_info[2]
to_game_data["competitor_info"] = competitor_info[1]
to_game_data["walls_info"] = walls_info
to_game_data["bullets_info"] = bullets_info
to_game_data["bullet_stations_info"] = bullet_stations_info
to_game_data["oil_stations_info"] = oil_stations_info
to_player_data[get_ai_name(num)] = to_game_data
num += 1
return to_player_data
def get_bgm_data(self):
return create_bgm_data("BGM.ogg", 0.1)
def get_sound_data(self):
return [create_sounds_data("shoot", "shoot.wav")
, create_sounds_data("touch", "touch.wav")]
def add_player_score(self, player_no: int, score: int):
if not player_no or not score:
return
for player in self.all_players:
if isinstance(player, Player) and player_no == player.no and player.lives >= 0:
add_score(player, score)
def debugging(self, is_debug: bool):
self.obj_rect_list = []
if not is_debug:
return
play_rect_area_points = [self.play_rect_area.topleft, self.play_rect_area.topright
, self.play_rect_area.bottomright, self.play_rect_area.bottomleft
, self.play_rect_area.topleft]
for sprite in self.all_sprites:
if isinstance(sprite, pygame.sprite.Sprite):
top_left = sprite.rect.topleft
points = [top_left, sprite.rect.topright, sprite.rect.bottomright
, sprite.rect.bottomleft, top_left]
for index in range(len(points) - 1):
self.obj_rect_list.append(create_line_view_data("rect", *points[index], *points[index + 1], RED))
self.obj_rect_list.append(create_line_view_data("play_rect_area", *play_rect_area_points[index]
, *play_rect_area_points[index + 1], RED))

41
TankMan/src/Wall.py Normal file
View File

@ -0,0 +1,41 @@
from os import path
import pygame
from mlgame.view.view_model import create_asset_init_data, create_image_view_data
from .env import IMAGE_DIR, WALL_LIVE
class Wall(pygame.sprite.Sprite):
def __init__(self, construction, **kwargs):
super().__init__()
self.id = construction["_id"]
self.no = 0
self.rect = pygame.Rect(construction["_init_pos"], construction["_init_size"])
self.angle = 0
self.is_alive = True
self.lives = WALL_LIVE
def update(self, *args, **kwargs) -> None:
if self.lives <= 0:
self.kill()
def collide_with_bullets(self):
if self.lives > 0:
self.lives -= 1
def get_data_from_obj_to_game(self):
info = {"id": f"wall_{self.lives}", "x": self.rect.x, "y": self.rect.y, "lives": self.lives}
return info
def get_obj_progress_data(self):
return create_image_view_data(f"wall_{self.lives}", self.rect.x, self.rect.y
, self.rect.width, self.rect.height, self.angle)
def get_obj_init_data(self):
image_init_data = []
for i in range(1, self.lives+1):
image_init_data.append(create_asset_init_data(f"wall_{i}", self.rect.width, self.rect.height,
path.join(IMAGE_DIR, f"wall_{min(i,3)}.png"),
f"https://raw.githubusercontent.com/Jesse-Jumbo/TankMan/main/asset/image/wall_{min(i,3)}.png"))
return image_init_data

0
TankMan/src/__init__.py Normal file
View File

View File

@ -0,0 +1,58 @@
from typing import Optional
import pygame.sprite
from src.Player import Player
from src.Bullet import Bullet
from src.Wall import Wall
def collide_with_walls(group1: pygame.sprite.Group, group2: pygame.sprite.Group):
hits = pygame.sprite.groupcollide(group1, group2, False, False, pygame.sprite.collide_rect_ratio(0.8))
for sprite, walls in hits.items():
sprite.collide_with_walls()
def collide_with_bullets(group1: pygame.sprite.Group, group2: pygame.sprite.Group, green_team_num: Optional[int] = None):
hits = pygame.sprite.groupcollide(group1, group2, False, False, pygame.sprite.collide_rect_ratio(0.8))
player_score_data = {}
for sprite, bullets in hits.items():
for bullet in bullets:
if bullet.no != sprite.no and sprite.lives > 0:
bullet.kill()
if isinstance(sprite, Player):
assert green_team_num is not None
if (bullet.no - 1) // green_team_num == (sprite.no - 1) // green_team_num:
# -20 for friendly damage
score = -20
else:
score = 20
elif isinstance(sprite, Wall):
score = 1
if sprite.lives == 1:
score += 5
else:
continue
sprite.collide_with_bullets()
if player_score_data.get(bullet.no) is None:
player_score_data[bullet.no] = 0
player_score_data[bullet.no] += score
return player_score_data
def collide_with_supply_stations(sprites: pygame.sprite.Group, supply_stations: pygame.sprite.Group):
hits = pygame.sprite.groupcollide(sprites, supply_stations, False, False, pygame.sprite.collide_rect_ratio(0.8))
for sprite, supply_station in hits.items():
if isinstance(sprite, Player):
if supply_station[0].id == 5:
sprite.get_oil(supply_station[0].power)
else:
sprite.get_power(supply_station[0].power)
elif isinstance(sprite, Bullet):
sprite.kill()
supply_station[0].collect()
return [supply_station[0] for supply_station in hits.values()]

85
TankMan/src/env.py Normal file
View File

@ -0,0 +1,85 @@
from os import path
import pygame
IS_DEBUG = False
# TODO remove width and height setting
'''width and height'''
WINDOW_WIDTH = 1000
WINDOW_HEIGHT = 700
'''environment data'''
FPS = 30
SHOOT_COOLDOWN = 15
'''color'''
BLACK = "#000000"
WHITE = "#ffffff"
RED = "#ff0000"
YELLOW = "#ffff00"
GREEN = "#00ff00"
DARKGREEN = "#006400"
GREY = "#8c8c8c"
BLUE = "#0000ff"
LIGHT_BLUE = "#21A1F1"
CYAN_BLUE = "#00FFFF"
PINK = "#FF00FF"
DARKGREY = "#282828"
LIGHTGREY = "#646464"
BROWN = "#643705"
FOREST = "#22390A"
MAGENTA = "#FF00FF"
MEDGRAY = "#4B4B4B"
ORANGE = "#FFA500"
'''command'''
TURN_LEFT_CMD = "TURN_LEFT"
TURN_RIGHT_CMD = "TURN_RIGHT"
FORWARD_CMD = "FORWARD"
BACKWARD_CMD = "BACKWARD"
AIM_LEFT_CMD = "AIM_LEFT"
AIM_RIGHT_CMD = "AIM_RIGHT"
SHOOT = "SHOOT"
'''data path'''
GAME_DIR = path.dirname(__file__)
IMAGE_DIR = path.join(GAME_DIR, "..", "asset", "image")
SOUND_DIR = path.join(GAME_DIR, "..", "asset", "sound")
MAP_DIR = path.join(GAME_DIR, "..", "asset", 'maps')
'''BG View'''
TITLE = "TankMan!"
BG_COLOR = DARKGREY
TILE_X_SIZE = 50
TILE_Y_SIZE = 50
TILE_SIZE = 50
TEXT_SIZE = 100
'''object size'''
ALL_OBJECT_SIZE = pygame.Rect(0, 0, 50, 50)
BULLET_SIZE = (13, 16)
LIVES_SIZE = (30, 25)
"""all setting"""
DOWN_IMG = 'down'
RIGHT_IMG = 'right'
UP_IMG = 'up'
LEFT_IMG = 'left'
"""collide setting"""
WITH_PLAYER = 'player'
"""map data numbers"""
PLAYER_1_IMG_NO = 1
PLAYER_2_IMG_NO = 2
WALL_IMG_NO = 3
BULLET_STATION_IMG_NO = 4
OIL_STATION_IMG_NO = 5
WALL_LIVE = 4
BULLET_SPEED = 30
BULLET_TRAVEL_DISTANCE = 300
"""music"""
BGM = 'background_music.ogg/.wav/.mp3'
MENU_SND = 'MenuTheme.ogg/.wav/.mp3'

View File

@ -0,0 +1,44 @@
from os import path
import pygame.mixer
def create_sounds_data(id: str, name: str):
return {
"_id": id
, "_name": name
}
def create_bgm_data(name: str, volume: float):
return {
"_name": name
, "_volume": volume
}
class SoundController:
def __init__(self, sound_path: str, sounds_data_list: list):
self._sound_path = sound_path
if not self._sound_path:
return
self._sounds_obj = {}
pygame.mixer.init()
for sounds_data in sounds_data_list:
sound_data = path.join(self._sound_path, sounds_data["_name"])
self._sounds_obj[sounds_data["_id"]] = pygame.mixer.Sound(sound_data)
def play_music(self, bgm_data: dict) -> None:
if not self._sound_path:
return
pygame.mixer.init()
pygame.mixer.music.load(path.join(self._sound_path, bgm_data["_name"]))
pygame.mixer.music.set_volume(bgm_data["_volume"])
pygame.mixer.music.play(-1)
def play_sound(self, id: str, volume: float, maz_time: int) -> None:
if not self._sound_path:
return
sound_obj = self._sounds_obj[id]
sound_obj.set_volume(volume)
sound_obj.play(maxtime=maz_time)

View File

@ -0,0 +1,68 @@
import pytmx
def create_construction(_id: int or str, _no: int, _init_pos: tuple, _init_size: tuple):
return {
"_id": _id
, "_no": _no
, "_init_pos": _init_pos
, "_init_size": _init_size
}
# Map 讀取地圖資料
class TiledMap:
def __init__(self, filepath: str):
tm = pytmx.TiledMap(filepath)
self.tile_width = tm.tilewidth
self.tile_height = tm.tileheight
self.width = tm.width
self.height = tm.height
self.map_width = self.tile_width * self.width
self.map_height = self.tile_height * self.height
self.tmx_data = tm
self._is_record = False
self.all_pos_list = []
self.empty_pos_list = []
self.empty_quadrant_pos_dict = {1: [], 2: [], 3: [], 4: []}
self.all_obj_data_dict = {}
# TODO refactor
self.all_obj = {}
def add_init_obj_data(self, img_id: int, cls, **kwargs):
obj_data = {img_id: {"cls": cls,
"kwargs": kwargs
}
}
self.all_obj_data_dict.update(obj_data)
self.all_obj[img_id] = []
def create_init_obj_dict(self) -> dict:
obj_no = 0
for layer in self.tmx_data.visible_layers:
for x, y, gid, in layer:
if isinstance(layer, pytmx.TiledTileLayer):
pos = (x * self.tile_width, y * self.tile_height)
if not self._is_record:
self.all_pos_list.append(pos)
if not self._is_record and not gid: # 0代表空格無圖塊
self.empty_pos_list.append(pos)
if pos[0] >= self.map_width // 2 and pos[1] < self.map_height // 2:
self.empty_quadrant_pos_dict[1].append(pos)
elif pos[0] < self.map_width // 2 and pos[1] < self.map_height // 2:
self.empty_quadrant_pos_dict[2].append(pos)
elif pos[0] < self.map_width // 2 and pos[1] >= self.map_height // 2:
self.empty_quadrant_pos_dict[3].append(pos)
else:
self.empty_quadrant_pos_dict[4].append(pos)
elif gid:
img_id = layer.parent.tiledgidmap[gid]
kwargs = self.all_obj_data_dict[img_id]["kwargs"]
obj_no += 1
img_info = {"_id": img_id, "_no": obj_no
, "_init_pos": pos
, "_init_size": (self.tile_width, self.tile_height)
}
self.all_obj[img_id].append(self.all_obj_data_dict[img_id]["cls"](img_info, **kwargs))
self._is_record = True
return self.all_obj

View File

View File

@ -0,0 +1,26 @@
import pygame.sprite
def get_size(sprite: pygame.sprite.Sprite):
return sprite.rect.width, sprite.rect.height
def set_topleft(sprite: pygame.sprite.Sprite, top_left: tuple):
sprite.rect.topleft = top_left
def add_score(sprite: pygame.sprite.Sprite, score: int):
sprite.score += score
def set_shoot(sprite: pygame.sprite.Sprite, is_shoot: bool):
sprite.is_shoot = is_shoot
def get_sprites_progress_data(sprites: pygame.sprite.Group):
data_list = []
for sprite in sprites:
data = sprite.get_obj_progress_data()
if data:
data_list.append(data)
return data_list

View File

@ -0,0 +1,11 @@
from src.template.Player import Player
class TestPlayer(object):
construction = {"_id": 0, "_no": 0, "x": 0, "y": 0, "width": 60, "height": 60}
player = Player(construction)
def test_get_xy_pos(self):
assert type(self.player.get_xy_pos()) == tuple
assert type(self.player.get_xy_pos()[0]) == int
assert type(self.player.get_xy_pos()[1]) == int