| import json |
| import os |
| import random |
| import time |
| from dataclasses import dataclass, asdict |
| from typing import List, Dict, Optional, Tuple |
| from enum import Enum |
| import math |
| from datetime import datetime |
|
|
| from rng_system import get_rng |
|
|
| class AlienType(Enum): |
| """এলিয়েনের ধরণ""" |
| SCOUT = "scout" |
| FIGHTER = "fighter" |
| BOMBER = "bomber" |
| BOSS = "boss" |
|
|
| class AlienBehavior(Enum): |
| """এলিয়েনের আচরণের ধরণ (ডিটারমিনিস্টিক)""" |
| DIVE = "dive" |
| CIRCLE = "circle" |
| STRAIGHT = "straight" |
| ZIGZAG = "zigzag" |
| FOLLOW = "follow" |
| RANDOM = "random" |
|
|
| @dataclass |
| class AlienPattern: |
| """এলিয়েন আক্রমণের প্যাটার্ন (ডিটারমিনিস্টিক)""" |
| behavior: AlienBehavior |
| speed: float |
| amplitude: float |
| frequency: float |
| duration: float |
| next_pattern: Optional['AlienPattern'] = None |
|
|
| @dataclass |
| class Alien: |
| """এলিয়েন অবজেক্ট""" |
| id: int |
| type: AlienType |
| x: float |
| y: float |
| health: int |
| pattern: AlienPattern |
| pattern_start_time: float |
| speed_multiplier: float |
| |
| def to_dict(self): |
| return { |
| 'id': self.id, |
| 'type': self.type.value, |
| 'x': round(self.x, 2), |
| 'y': round(self.y, 2), |
| 'health': self.health, |
| 'pattern': self.pattern.behavior.value |
| } |
|
|
| @dataclass |
| class SpawnEvent: |
| """স্পন ইভেন্ট""" |
| time: float |
| alien_type: AlienType |
| x: float |
| y: float |
| pattern: AlienPattern |
|
|
|
|
| class AlienPatternLibrary: |
| """ |
| এলিয়েন প্যাটার্ন লাইব্রেরি - ডিটারমিনিস্টিক প্যাটার্নের সেট |
| PDF অনুযায়ী: এলিয়েনরা ডিটারমিনিস্টিক, র্যান্ডম নয় |
| """ |
| |
| def __init__(self, patterns_file: str = "data/alien_patterns.json"): |
| self.patterns_file = patterns_file |
| self.patterns: Dict[AlienType, List[AlienPattern]] = {} |
| self._load_patterns() |
| |
| def _load_patterns(self): |
| """প্যাটার্ন লোড করা""" |
| if os.path.exists(self.patterns_file): |
| with open(self.patterns_file, 'r') as f: |
| data = json.load(f) |
| for alien_type, patterns in data.items(): |
| self.patterns[AlienType(alien_type)] = [ |
| AlienPattern(**p) for p in patterns |
| ] |
| else: |
| |
| self._create_default_patterns() |
| |
| def _create_default_patterns(self): |
| """ডিফল্ট প্যাটার্ন তৈরি (PDF-এর মতো)""" |
| |
| |
| self.patterns[AlienType.SCOUT] = [ |
| AlienPattern(AlienBehavior.DIVE, 5.0, 0, 0, 2.0), |
| AlienPattern(AlienBehavior.STRAIGHT, 4.0, 0, 0, 1.5), |
| AlienPattern(AlienBehavior.CIRCLE, 3.0, 50, 1, 3.0), |
| AlienPattern(AlienBehavior.ZIGZAG, 4.5, 30, 2, 2.5) |
| ] |
| |
| |
| self.patterns[AlienType.FIGHTER] = [ |
| AlienPattern(AlienBehavior.FOLLOW, 3.5, 0, 0, 3.0), |
| AlienPattern(AlienBehavior.DIVE, 4.0, 0, 0, 2.5), |
| AlienPattern(AlienBehavior.STRAIGHT, 3.0, 0, 0, 2.0), |
| AlienPattern(AlienBehavior.ZIGZAG, 3.5, 40, 1.5, 3.0) |
| ] |
| |
| |
| self.patterns[AlienType.BOMBER] = [ |
| AlienPattern(AlienBehavior.STRAIGHT, 2.0, 0, 0, 4.0), |
| AlienPattern(AlienBehavior.CIRCLE, 1.8, 80, 0.5, 5.0), |
| AlienPattern(AlienBehavior.FOLLOW, 2.2, 0, 0, 3.5) |
| ] |
| |
| |
| self.patterns[AlienType.BOSS] = [ |
| AlienPattern(AlienBehavior.CIRCLE, 1.5, 100, 0.3, 8.0), |
| AlienPattern(AlienBehavior.FOLLOW, 2.0, 0, 0, 5.0), |
| AlienPattern(AlienBehavior.DIVE, 2.5, 0, 0, 4.0), |
| AlienPattern(AlienBehavior.STRAIGHT, 1.8, 0, 0, 6.0) |
| ] |
| |
| def get_pattern_cycle(self, alien_type: AlienType) -> List[AlienPattern]: |
| """একটি এলিয়েনের জন্য প্যাটার্ন সাইকেল রিটার্ন করে""" |
| return self.patterns.get(alien_type, self.patterns[AlienType.FIGHTER]) |
|
|
|
|
| class AlienSpawner: |
| """ |
| এলিয়েন স্পনার - PDF-এর SpawnAlien রুটিনের ইমপ্লিমেন্টেশন |
| র্যান্ডম অবস্থান তৈরি করতে RNG ব্যবহার করে, কিন্তু এলিয়েনের আচরণ ডিটারমিনিস্টিক |
| """ |
| |
| def __init__(self, pattern_library: AlienPatternLibrary, |
| screen_width: int = 800, screen_height: int = 600): |
| self.rng = get_rng() |
| self.pattern_library = pattern_library |
| self.screen_width = screen_width |
| self.screen_height = screen_height |
| |
| self.aliens: List[Alien] = [] |
| self.spawn_history: List[SpawnEvent] = [] |
| self.next_alien_id = 0 |
| |
| |
| self.spawn_rate = 0.5 |
| self.last_spawn_time = time.time() |
| self.max_aliens = 20 |
| |
| |
| self.total_spawned = 0 |
| self.total_killed = 0 |
| |
| def _get_random_spawn_position(self) -> Tuple[float, float]: |
| """ |
| এলোমেলো স্পন অবস্থান (র্যান্ডম - PDF অনুযায়ী) |
| """ |
| side = self.rng.next_random_range(0, 3) |
| |
| if side == 0: |
| x = self.rng.next_random_float(0, self.screen_width) |
| y = -50 |
| elif side == 1: |
| x = self.rng.next_random_float(0, self.screen_width) |
| y = self.screen_height + 50 |
| elif side == 2: |
| x = -50 |
| y = self.rng.next_random_float(0, self.screen_height) |
| else: |
| x = self.screen_width + 50 |
| y = self.rng.next_random_float(0, self.screen_height) |
| |
| return x, y |
| |
| def _select_alien_type(self) -> AlienType: |
| """ |
| এলোমেলো এলিয়েন টাইপ সিলেক্ট (র্যান্ডম) |
| """ |
| r = self.rng.next_random_float() |
| if r < 0.5: |
| return AlienType.SCOUT |
| elif r < 0.8: |
| return AlienType.FIGHTER |
| elif r < 0.95: |
| return AlienType.BOMBER |
| else: |
| return AlienType.BOSS |
| |
| def _create_alien(self, alien_type: AlienType, x: float, y: float) -> Alien: |
| """ |
| নতুন এলিয়েন তৈরি |
| """ |
| |
| health_map = { |
| AlienType.SCOUT: 1, |
| AlienType.FIGHTER: 3, |
| AlienType.BOMBER: 5, |
| AlienType.BOSS: 20 |
| } |
| |
| |
| patterns = self.pattern_library.get_pattern_cycle(alien_type) |
| pattern = patterns[0] |
| |
| |
| speed_multiplier = 0.9 + self.rng.next_random_float() * 0.2 |
| |
| alien = Alien( |
| id=self.next_alien_id, |
| type=alien_type, |
| x=x, |
| y=y, |
| health=health_map[alien_type], |
| pattern=pattern, |
| pattern_start_time=time.time(), |
| speed_multiplier=speed_multiplier |
| ) |
| |
| self.next_alien_id += 1 |
| return alien |
| |
| def update(self, dt: float, player_x: float = 400, player_y: float = 300): |
| """ |
| প্রতি ফ্রেমে আপডেট |
| """ |
| current_time = time.time() |
| |
| |
| if (len(self.aliens) < self.max_aliens and |
| current_time - self.last_spawn_time > 1.0 / self.spawn_rate): |
| |
| alien_type = self._select_alien_type() |
| x, y = self._get_random_spawn_position() |
| alien = self._create_alien(alien_type, x, y) |
| |
| self.aliens.append(alien) |
| self.spawn_history.append(SpawnEvent( |
| time=current_time, |
| alien_type=alien_type, |
| x=x, |
| y=y, |
| pattern=alien.pattern |
| )) |
| self.total_spawned += 1 |
| self.last_spawn_time = current_time |
| |
| |
| aliens_to_remove = [] |
| for alien in self.aliens: |
| self._update_alien_position(alien, dt, player_x, player_y) |
| |
| |
| if (alien.x < -200 or alien.x > self.screen_width + 200 or |
| alien.y < -200 or alien.y > self.screen_height + 200): |
| aliens_to_remove.append(alien) |
| |
| |
| for alien in aliens_to_remove: |
| if alien in self.aliens: |
| self.aliens.remove(alien) |
| self.total_killed += 1 |
| |
| def _update_alien_position(self, alien: Alien, dt: float, |
| player_x: float, player_y: float): |
| """ |
| এলিয়েনের অবস্থান আপডেট (প্যাটার্ন অনুযায়ী - ডিটারমিনিস্টিক) |
| """ |
| time_in_pattern = time.time() - alien.pattern_start_time |
| |
| |
| if time_in_pattern > alien.pattern.duration: |
| patterns = self.pattern_library.get_pattern_cycle(alien.type) |
| current_index = patterns.index(alien.pattern) if alien.pattern in patterns else 0 |
| next_index = (current_index + 1) % len(patterns) |
| alien.pattern = patterns[next_index] |
| alien.pattern_start_time = time.time() |
| time_in_pattern = 0 |
| |
| speed = alien.pattern.speed * alien.speed_multiplier * dt * 60 |
| |
| |
| if alien.pattern.behavior == AlienBehavior.DIVE: |
| |
| dx = player_x - alien.x |
| dy = player_y - alien.y |
| dist = math.sqrt(dx*dx + dy*dy) |
| if dist > 0: |
| alien.x += (dx / dist) * speed |
| alien.y += (dy / dist) * speed |
| |
| elif alien.pattern.behavior == AlienBehavior.CIRCLE: |
| |
| angle = time_in_pattern * alien.pattern.frequency |
| alien.x += math.cos(angle) * speed |
| alien.y += math.sin(angle) * speed |
| |
| elif alien.pattern.behavior == AlienBehavior.STRAIGHT: |
| |
| alien.x -= speed |
| |
| elif alien.pattern.behavior == AlienBehavior.ZIGZAG: |
| |
| alien.x -= speed |
| alien.y += math.sin(time_in_pattern * alien.pattern.frequency) * alien.pattern.amplitude * dt |
| |
| elif alien.pattern.behavior == AlienBehavior.FOLLOW: |
| |
| dx = player_x - alien.x |
| dy = player_y - alien.y |
| dist = math.sqrt(dx*dx + dy*dy) |
| if dist > 100: |
| if dist > 0: |
| alien.x += (dx / dist) * speed * 0.5 |
| alien.y += (dy / dist) * speed * 0.5 |
| |
| |
| elif alien.pattern.behavior == AlienBehavior.RANDOM: |
| alien.x += (self.rng.next_random_float() - 0.5) * speed * 2 |
| alien.y += (self.rng.next_random_float() - 0.5) * speed * 2 |
| |
| def damage_alien(self, alien_id: int, damage: int = 1) -> bool: |
| """এলিয়েনকে আঘাত করা""" |
| for alien in self.aliens: |
| if alien.id == alien_id: |
| alien.health -= damage |
| if alien.health <= 0: |
| self.aliens.remove(alien) |
| self.total_killed += 1 |
| return True |
| break |
| return False |
| |
| def get_aliens_data(self) -> List[dict]: |
| """UI-এর জন্য এলিয়েন ডাটা""" |
| return [alien.to_dict() for alien in self.aliens] |
| |
| def get_stats(self) -> dict: |
| """পরিসংখ্যান""" |
| return { |
| 'total_spawned': self.total_spawned, |
| 'total_killed': self.total_killed, |
| 'active_aliens': len(self.aliens), |
| 'spawn_rate': self.spawn_rate |
| } |
| |
| def get_spawn_history(self, limit: int = 50) -> List[dict]: |
| """স্পন হিস্ট্রি""" |
| return [ |
| { |
| 'time': datetime.fromtimestamp(e.time).strftime('%H:%M:%S'), |
| 'type': e.alien_type.value, |
| 'position': f"({int(e.x)},{int(e.y)})" |
| } |
| for e in self.spawn_history[-limit:] |
| ] |
|
|
|
|
| class AlienAIManager: |
| """ |
| এলিয়েন এআই ম্যানেজার - ডিটারমিনিস্টিক প্যাটার্ন ম্যানেজ করে |
| PDF অনুযায়ী: এলিয়েনরা ডিটারমিনিস্টিক |
| """ |
| |
| def __init__(self): |
| self.pattern_library = AlienPatternLibrary() |
| self.current_cycle_index = 0 |
| self.cycle_length = 4 |
| |
| def get_attack_wave_pattern(self, wave_number: int) -> List[AlienPattern]: |
| """ |
| ওয়েব নম্বর অনুযায়ী অ্যাটাক প্যাটার্ন |
| ডিটারমিনিস্টিক - প্রতি ওয়েভে একই প্যাটার্ন রিপিট |
| """ |
| base_pattern = self.pattern_library.get_pattern_cycle(AlienType.FIGHTER) |
| |
| |
| wave_index = (wave_number // self.cycle_length) % len(base_pattern) |
| |
| return [base_pattern[wave_index]] |
| |
| def predict_next_attack(self, current_wave: int) -> str: |
| """ |
| পরবর্তী অ্যাটাকের ধরণ প্রেডিক্ট (ডিটারমিনিস্টিক) |
| """ |
| next_pattern = self.get_attack_wave_pattern(current_wave + 1) |
| return f"পরবর্তী অ্যাটাক: {next_pattern[0].behavior.value}" |