Загрузить файлы в «/»
This commit is contained in:
202
ai_decision.py
Normal file
202
ai_decision.py
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
|
||||
import random
|
||||
|
||||
from typing import List, Dict
|
||||
|
||||
|
||||
|
||||
class AIDecision:
|
||||
|
||||
def __init__(self, difficulty: str = "medium"):
|
||||
|
||||
self.difficulty = difficulty
|
||||
|
||||
self.last_decision_time = 0
|
||||
|
||||
self.decision_cooldown = self._get_decision_cooldown()
|
||||
|
||||
|
||||
|
||||
def decide(self, snapshot: Dict, dt: float) -> List[Dict]:
|
||||
|
||||
self.last_decision_time += dt
|
||||
|
||||
if self.last_decision_time < self.decision_cooldown:
|
||||
|
||||
return []
|
||||
|
||||
|
||||
|
||||
commands = []
|
||||
|
||||
commands.extend(self.plan_expand(snapshot))
|
||||
|
||||
commands.extend(self.plan_attack(snapshot))
|
||||
|
||||
commands.extend(self.plan_build(snapshot))
|
||||
|
||||
|
||||
|
||||
self.last_decision_time = 0
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
|
||||
def plan_expand(self, snapshot: Dict) -> List[Dict]:
|
||||
|
||||
commands = []
|
||||
|
||||
resources = snapshot.get('resources', {})
|
||||
|
||||
minerals = resources.get('minerals', 0)
|
||||
|
||||
|
||||
|
||||
if minerals >= 400 and random.random() < 0.3:
|
||||
|
||||
commands.append({
|
||||
|
||||
'type': 'create_entity',
|
||||
|
||||
'entity_type': 'supply_depot',
|
||||
|
||||
'position': self._find_expansion_position(snapshot)
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
|
||||
def plan_attack(self, snapshot: Dict) -> List[Dict]:
|
||||
|
||||
commands = []
|
||||
|
||||
entities = snapshot.get('entities', [])
|
||||
|
||||
ai_units = [e for e in entities if e.get('owner') == 'ai']
|
||||
|
||||
player_units = [e for e in entities if e.get('owner') == 'player']
|
||||
|
||||
|
||||
|
||||
if len(ai_units) >= 5 and player_units and random.random() < 0.4:
|
||||
|
||||
target_unit = random.choice(player_units)
|
||||
|
||||
for unit in ai_units:
|
||||
|
||||
if unit.get('type') == 'unit' and unit.get('state') == 'idle':
|
||||
|
||||
commands.append({
|
||||
|
||||
'type': 'attack',
|
||||
|
||||
'entity_id': unit['id'],
|
||||
|
||||
'target_id': target_unit['id']
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
|
||||
def plan_build(self, snapshot: Dict) -> List[Dict]:
|
||||
|
||||
commands = []
|
||||
|
||||
resources = snapshot.get('resources', {})
|
||||
|
||||
minerals = resources.get('minerals', 0)
|
||||
|
||||
entities = snapshot.get('entities', [])
|
||||
|
||||
ai_units = [e for e in entities if e.get('owner') == 'ai']
|
||||
|
||||
|
||||
|
||||
if minerals >= 150 and len(ai_units) < 8 and random.random() < 0.5:
|
||||
|
||||
commands.append({
|
||||
|
||||
'type': 'create_entity',
|
||||
|
||||
'entity_type': 'worker',
|
||||
|
||||
'position': self._find_build_position(snapshot)
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
if minerals >= 300 and random.random() < 0.2:
|
||||
|
||||
commands.append({
|
||||
|
||||
'type': 'create_entity',
|
||||
|
||||
'entity_type': 'barracks',
|
||||
|
||||
'position': self._find_build_position(snapshot)
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
|
||||
def _find_expansion_position(self, snapshot: Dict) -> tuple:
|
||||
|
||||
base_x = snapshot.get('ai_base_x', 100)
|
||||
|
||||
base_y = snapshot.get('ai_base_y', 100)
|
||||
|
||||
offset_x = random.randint(-200, 200)
|
||||
|
||||
offset_y = random.randint(-200, 200)
|
||||
|
||||
return (base_x + offset_x, base_y + offset_y)
|
||||
|
||||
|
||||
|
||||
def _find_build_position(self, snapshot: Dict) -> tuple:
|
||||
|
||||
base_x = snapshot.get('ai_base_x', 100)
|
||||
|
||||
base_y = snapshot.get('ai_base_y', 100)
|
||||
|
||||
offset_x = random.randint(-50, 50)
|
||||
|
||||
offset_y = random.randint(-50, 50)
|
||||
|
||||
return (base_x + offset_x, base_y + offset_y)
|
||||
|
||||
|
||||
|
||||
def _get_decision_cooldown(self) -> float:
|
||||
|
||||
if self.difficulty == "easy":
|
||||
|
||||
return 3.0
|
||||
|
||||
elif self.difficulty == "medium":
|
||||
|
||||
return 2.0
|
||||
|
||||
elif self.difficulty == "hard":
|
||||
|
||||
return 1.0
|
||||
|
||||
else:
|
||||
|
||||
return 2.0
|
||||
|
||||
250
entity_behavior.py
Normal file
250
entity_behavior.py
Normal file
@@ -0,0 +1,250 @@
|
||||
|
||||
|
||||
from typing import List, Dict, Tuple
|
||||
|
||||
import math
|
||||
|
||||
|
||||
|
||||
class EntityBehavior:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def process_behaviors(self, state, dt: float) -> List[Dict]:
|
||||
|
||||
actions = []
|
||||
|
||||
if not hasattr(state, 'entities'):
|
||||
|
||||
return actions
|
||||
|
||||
|
||||
|
||||
for entity in state.entities:
|
||||
|
||||
if entity.get('type') == 'unit':
|
||||
|
||||
entity_actions = self._process_unit_behavior(state, entity, dt)
|
||||
|
||||
actions.extend(entity_actions)
|
||||
|
||||
elif entity.get('type') == 'building':
|
||||
|
||||
entity_actions = self._process_building_behavior(state, entity, dt)
|
||||
|
||||
actions.extend(entity_actions)
|
||||
|
||||
|
||||
|
||||
return actions
|
||||
|
||||
|
||||
|
||||
def _process_unit_behavior(self, state, entity: Dict, dt: float) -> List[Dict]:
|
||||
|
||||
actions = []
|
||||
|
||||
current_state = entity.get('state', 'idle')
|
||||
|
||||
|
||||
|
||||
if current_state == 'moving':
|
||||
|
||||
if self._has_reached_target(entity):
|
||||
|
||||
actions.append({'type': 'change_state', 'entity_id': entity['id'], 'new_state': 'idle'})
|
||||
|
||||
else:
|
||||
|
||||
self._continue_movement(entity, dt)
|
||||
|
||||
|
||||
|
||||
elif current_state == 'attacking':
|
||||
|
||||
target_id = entity.get('target_id')
|
||||
|
||||
target = self._find_entity_by_id(state, target_id)
|
||||
|
||||
if not target or target.get('health', 0) <= 0:
|
||||
|
||||
actions.append({'type': 'change_state', 'entity_id': entity['id'], 'new_state': 'idle'})
|
||||
|
||||
else:
|
||||
|
||||
if self._is_in_attack_range(entity, target):
|
||||
|
||||
actions.append({'type': 'deal_damage', 'attacker_id': entity['id'], 'target_id': target_id, 'damage': entity.get('attack_power', 10)})
|
||||
|
||||
else:
|
||||
|
||||
entity['target_x'] = target['x']
|
||||
|
||||
entity['target_y'] = target['y']
|
||||
|
||||
actions.append({'type': 'change_state', 'entity_id': entity['id'], 'new_state': 'moving'})
|
||||
|
||||
|
||||
|
||||
elif current_state == 'gathering':
|
||||
|
||||
target_id = entity.get('target_id')
|
||||
|
||||
target = self._find_entity_by_id(state, target_id)
|
||||
|
||||
if not target or target.get('type') != 'resource':
|
||||
|
||||
actions.append({'type': 'change_state', 'entity_id': entity['id'], 'new_state': 'idle'})
|
||||
|
||||
else:
|
||||
|
||||
if self._is_in_gather_range(entity, target):
|
||||
|
||||
gather_rate = entity.get('gather_rate', 5)
|
||||
|
||||
actions.append({'type': 'gather_resources', 'gatherer_id': entity['id'], 'resource_id': target_id, 'amount': gather_rate})
|
||||
|
||||
else:
|
||||
|
||||
entity['target_x'] = target['x']
|
||||
|
||||
entity['target_y'] = target['y']
|
||||
|
||||
actions.append({'type': 'change_state', 'entity_id': entity['id'], 'new_state': 'moving'})
|
||||
|
||||
|
||||
|
||||
return actions
|
||||
|
||||
|
||||
|
||||
def _process_building_behavior(self, state, entity: Dict, dt: float) -> List[Dict]:
|
||||
|
||||
actions = []
|
||||
|
||||
if not entity.get('completed', False):
|
||||
|
||||
build_progress = entity.get('build_progress', 0) + dt
|
||||
|
||||
build_time = entity.get('build_time', 0)
|
||||
|
||||
if build_progress >= build_time:
|
||||
|
||||
actions.append({'type': 'building_completed', 'entity_id': entity['id']})
|
||||
|
||||
else:
|
||||
|
||||
actions.append({'type': 'building_progress', 'entity_id': entity['id'], 'progress': build_progress})
|
||||
|
||||
|
||||
|
||||
return actions
|
||||
|
||||
|
||||
|
||||
def pathfind(self, entity: Dict, target_pos: Tuple[float, float], map_tiles: List[List[Dict]]) -> List[Tuple[float, float]]:
|
||||
|
||||
start_pos = (entity['x'], entity['y'])
|
||||
|
||||
path = [start_pos, target_pos]
|
||||
|
||||
return path
|
||||
|
||||
|
||||
|
||||
def _has_reached_target(self, entity: Dict) -> bool:
|
||||
|
||||
if 'target_x' not in entity or 'target_y' not in entity:
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
dx = entity['target_x'] - entity['x']
|
||||
|
||||
dy = entity['target_y'] - entity['y']
|
||||
|
||||
distance = math.sqrt(dx*dx + dy*dy)
|
||||
|
||||
|
||||
|
||||
return distance < 5.0
|
||||
|
||||
|
||||
|
||||
def _continue_movement(self, entity: Dict, dt: float) -> None:
|
||||
|
||||
if 'target_x' not in entity or 'target_y' not in entity:
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
dx = entity['target_x'] - entity['x']
|
||||
|
||||
dy = entity['target_y'] - entity['y']
|
||||
|
||||
distance = math.sqrt(dx*dx + dy*dy)
|
||||
|
||||
|
||||
|
||||
if distance > 0:
|
||||
|
||||
speed = entity.get('speed', 50.0)
|
||||
|
||||
move_dist = speed * dt
|
||||
|
||||
entity['x'] += (dx / distance) * move_dist
|
||||
|
||||
entity['y'] += (dy / distance) * move_dist
|
||||
|
||||
|
||||
|
||||
def _is_in_attack_range(self, entity: Dict, target: Dict) -> bool:
|
||||
|
||||
dx = target['x'] - entity['x']
|
||||
|
||||
dy = target['y'] - entity['y']
|
||||
|
||||
distance = math.sqrt(dx*dx + dy*dy)
|
||||
|
||||
attack_range = entity.get('attack_range', 50.0)
|
||||
|
||||
|
||||
|
||||
return distance <= attack_range
|
||||
|
||||
|
||||
|
||||
def _is_in_gather_range(self, entity: Dict, target: Dict) -> bool:
|
||||
|
||||
dx = target['x'] - entity['x']
|
||||
|
||||
dy = target['y'] - entity['y']
|
||||
|
||||
distance = math.sqrt(dx*dx + dy*dy)
|
||||
|
||||
gather_range = entity.get('gather_range', 20.0)
|
||||
|
||||
|
||||
|
||||
return distance <= gather_range
|
||||
|
||||
|
||||
|
||||
def _find_entity_by_id(self, state, entity_id: int) -> Dict:
|
||||
|
||||
if hasattr(state, 'entities'):
|
||||
|
||||
for entity in state.entities:
|
||||
|
||||
if entity.get('id') == entity_id:
|
||||
|
||||
return entity
|
||||
|
||||
return None
|
||||
|
||||
140
entity_creation.py
Normal file
140
entity_creation.py
Normal file
@@ -0,0 +1,140 @@
|
||||
|
||||
|
||||
class EntityCreation:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.entity_templates = {
|
||||
|
||||
'worker': {
|
||||
|
||||
'type': 'unit',
|
||||
|
||||
'name': 'Worker',
|
||||
|
||||
'health': 100,
|
||||
|
||||
'movable': True,
|
||||
|
||||
'speed': 40.0,
|
||||
|
||||
'gather_rate': 10,
|
||||
|
||||
'build_power': 5
|
||||
|
||||
},
|
||||
|
||||
'barracks': {
|
||||
|
||||
'type': 'building',
|
||||
|
||||
'name': 'Barracks',
|
||||
|
||||
'health': 500,
|
||||
|
||||
'movable': False,
|
||||
|
||||
'build_time': 30.0
|
||||
|
||||
},
|
||||
|
||||
'supply_depot': {
|
||||
|
||||
'type': 'building',
|
||||
|
||||
'name': 'Supply Depot',
|
||||
|
||||
'health': 400,
|
||||
|
||||
'movable': False,
|
||||
|
||||
'build_time': 20.0
|
||||
|
||||
},
|
||||
|
||||
'mineral_field': {
|
||||
|
||||
'type': 'resource',
|
||||
|
||||
'name': 'Mineral Field',
|
||||
|
||||
'resources': {'minerals': 1500}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
def create_entity(self, state, create_cmd: dict) -> dict:
|
||||
|
||||
entity_type = create_cmd.get('entity_type')
|
||||
|
||||
position = create_cmd.get('position', (0, 0))
|
||||
|
||||
|
||||
|
||||
if entity_type not in self.entity_templates:
|
||||
|
||||
return None
|
||||
|
||||
|
||||
|
||||
entity_def = self.entity_templates[entity_type]
|
||||
|
||||
entity = self.init_entity(entity_def, position)
|
||||
|
||||
|
||||
|
||||
return {
|
||||
|
||||
'type': 'add_entity',
|
||||
|
||||
'entity': entity
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
def init_entity(self, entity_def: dict, position: tuple) -> dict:
|
||||
|
||||
entity = entity_def.copy()
|
||||
|
||||
entity['x'] = position[0]
|
||||
|
||||
entity['y'] = position[1]
|
||||
|
||||
|
||||
|
||||
if entity['type'] == 'unit':
|
||||
|
||||
entity['id'] = self._generate_id()
|
||||
|
||||
entity['state'] = 'idle'
|
||||
|
||||
|
||||
|
||||
elif entity['type'] == 'building':
|
||||
|
||||
entity['id'] = self._generate_id()
|
||||
|
||||
entity['build_progress'] = 0.0
|
||||
|
||||
entity['completed'] = False
|
||||
|
||||
|
||||
|
||||
elif entity['type'] == 'resource':
|
||||
|
||||
entity['id'] = self._generate_id()
|
||||
|
||||
|
||||
|
||||
return entity
|
||||
|
||||
|
||||
|
||||
def _generate_id(self) -> int:
|
||||
|
||||
return id(self)
|
||||
|
||||
92
input_commands.py
Normal file
92
input_commands.py
Normal file
@@ -0,0 +1,92 @@
|
||||
|
||||
|
||||
from typing import List, Dict, Tuple
|
||||
|
||||
|
||||
|
||||
class InputCommands:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def generate_commands(self, events: List, snapshot: Dict) -> List[Dict]:
|
||||
|
||||
commands = []
|
||||
|
||||
camera = snapshot.get('camera', {})
|
||||
|
||||
|
||||
|
||||
for event in events:
|
||||
|
||||
command = self._process_event(event, camera)
|
||||
|
||||
if command:
|
||||
|
||||
commands.append(command)
|
||||
|
||||
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
|
||||
def _process_event(self, event, camera: Dict) -> Dict:
|
||||
|
||||
if event.type == event.MOUSEBUTTONDOWN:
|
||||
|
||||
if event.button == 1: # Left click
|
||||
|
||||
world_x, world_y = self.screen_to_world(event.pos[0], event.pos[1], camera)
|
||||
|
||||
return {'type': 'select', 'world_pos': (world_x, world_y)}
|
||||
|
||||
elif event.button == 3: # Right click
|
||||
|
||||
world_x, world_y = self.screen_to_world(event.pos[0], event.pos[1], camera)
|
||||
|
||||
return {'type': 'move', 'world_pos': (world_x, world_y)}
|
||||
|
||||
|
||||
|
||||
elif event.type == event.KEYDOWN:
|
||||
|
||||
if event.key == event.K_b:
|
||||
|
||||
return {'type': 'build', 'building_type': 'barracks'}
|
||||
|
||||
elif event.key == event.K_s:
|
||||
|
||||
return {'type': 'build', 'building_type': 'supply_depot'}
|
||||
|
||||
elif event.key == event.K_g:
|
||||
|
||||
return {'type': 'gather'}
|
||||
|
||||
|
||||
|
||||
return None
|
||||
|
||||
|
||||
|
||||
def screen_to_world(self, x: int, y: int, camera: Dict) -> Tuple[float, float]:
|
||||
|
||||
camera_x = camera.get('x', 0)
|
||||
|
||||
camera_y = camera.get('y', 0)
|
||||
|
||||
zoom = camera.get('zoom', 1.0)
|
||||
|
||||
|
||||
|
||||
world_x = (x / zoom) + camera_x
|
||||
|
||||
world_y = (y / zoom) + camera_y
|
||||
|
||||
|
||||
|
||||
return world_x, world_y
|
||||
|
||||
56
input_events.py
Normal file
56
input_events.py
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
|
||||
import pygame
|
||||
|
||||
from typing import List
|
||||
|
||||
|
||||
|
||||
class InputEvents:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.quit_requested = False
|
||||
|
||||
|
||||
|
||||
def poll_events(self) -> List[pygame.event.Event]:
|
||||
|
||||
raw_events = pygame.event.get()
|
||||
|
||||
filtered_events = []
|
||||
|
||||
|
||||
|
||||
for event in raw_events:
|
||||
|
||||
if self._handle_quit(event):
|
||||
|
||||
continue
|
||||
|
||||
filtered_events.append(event)
|
||||
|
||||
|
||||
|
||||
return filtered_events
|
||||
|
||||
|
||||
|
||||
def _handle_quit(self, event: pygame.event.Event) -> bool:
|
||||
|
||||
if event.type == pygame.QUIT:
|
||||
|
||||
self.quit_requested = True
|
||||
|
||||
return True
|
||||
|
||||
elif event.type == pygame.KEYDOWN:
|
||||
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
|
||||
self.quit_requested = True
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user