307 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			307 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from __future__ import annotations
 | |
| 
 | |
| import re
 | |
| import random
 | |
| import itertools
 | |
| from typing import ClassVar, Any
 | |
| 
 | |
| from .config import Config
 | |
| from .utils import Utils
 | |
| from .storage import Storage
 | |
| 
 | |
| 
 | |
| class Ant:
 | |
|     def __init__(self) -> None:
 | |
|         now = Utils.now()
 | |
|         self.created = now
 | |
|         self.updated = now
 | |
|         self.name = ""
 | |
|         self.status = ""
 | |
|         self.method = "hatched"
 | |
|         self.triumph = 0
 | |
|         self.hits = 0
 | |
| 
 | |
|     def to_dict(self) -> dict[str, Any]:
 | |
|         return {
 | |
|             "created": self.created,
 | |
|             "updated": self.updated,
 | |
|             "name": self.name,
 | |
|             "status": self.status,
 | |
|             "method": self.method,
 | |
|             "hits": self.hits,
 | |
|             "triumph": self.triumph,
 | |
|         }
 | |
| 
 | |
|     def from_dict(self, data: dict[str, Any]) -> None:
 | |
|         self.created = data["created"]
 | |
|         self.updated = data["updated"]
 | |
|         self.name = data["name"]
 | |
|         self.status = data["status"]
 | |
|         self.method = data["method"]
 | |
|         self.hits = data["hits"]
 | |
|         self.triumph = data["triumph"]
 | |
| 
 | |
|     def get_name(self) -> str:
 | |
|         return self.name or "Nameless"
 | |
| 
 | |
|     def get_age(self) -> str:
 | |
|         now = Utils.now()
 | |
|         return Utils.time_ago(self.created, now)
 | |
| 
 | |
|     def describe(self) -> None:
 | |
|         Utils.print(f"Name is {self.get_name()}")
 | |
|         Utils.print(f"It hatched {self.get_age()}")
 | |
| 
 | |
|     def get_score(self) -> int:
 | |
|         return self.triumph - self.hits
 | |
| 
 | |
|     def tooltip(self) -> str:
 | |
|         tooltip = ""
 | |
|         tooltip += f"Updated: {Utils.to_date(self.updated)}"
 | |
|         tooltip += f"\nCreated: {Utils.to_date(self.created)}"
 | |
|         tooltip += f"\nTriumph: {self.triumph} | Hits: {self.hits}"
 | |
|         tooltip += "\nClick to Terminate"
 | |
|         tooltip += "\nMiddle Click to Merge"
 | |
|         return tooltip
 | |
| 
 | |
|     def get_status(self) -> str:
 | |
|         from .game import Method
 | |
| 
 | |
|         if (not self.status) and (not self.method):
 | |
|             return "No update yet"
 | |
| 
 | |
|         status = self.status
 | |
| 
 | |
|         if self.method == Method.triumph:
 | |
|             total = f"({self.triumph} total)"
 | |
|             status = f"{Config.triumph_icon} {Config.triumph_message} {total}"
 | |
| 
 | |
|         elif self.method == Method.hit:
 | |
|             total = f"({self.hits} total)"
 | |
|             status = f"{Config.hit_icon} {Config.hit_message} {total}"
 | |
| 
 | |
|         elif self.method == Method.think:
 | |
|             status = f"Thinking about {status}"
 | |
| 
 | |
|         elif self.method == Method.travel:
 | |
|             status = f"Traveling to {status}"
 | |
| 
 | |
|         return status
 | |
| 
 | |
| 
 | |
| class Ants:
 | |
|     ants: ClassVar[list[Ant]] = []
 | |
| 
 | |
|     @staticmethod
 | |
|     def prepare() -> None:
 | |
|         Ants.get()
 | |
|         Ants.check()
 | |
| 
 | |
|     @staticmethod
 | |
|     def check() -> None:
 | |
|         if not Ants.ants:
 | |
|             Ants.populate(Config.default_population)
 | |
| 
 | |
|     @staticmethod
 | |
|     def hatch(
 | |
|         num: int = 1, on_change: bool = True, ignore: list[str] | None = None
 | |
|     ) -> None:
 | |
|         from .game import Game
 | |
| 
 | |
|         for _ in range(num):
 | |
|             ant = Ant()
 | |
|             ant.name = Ants.random_name(ignore)
 | |
|             Ants.ants.append(ant)
 | |
|             Game.update(ant)
 | |
| 
 | |
|         if on_change:
 | |
|             Ants.on_change()
 | |
| 
 | |
|     @staticmethod
 | |
|     def on_change() -> None:
 | |
|         from .game import Game
 | |
| 
 | |
|         Ants.save()
 | |
|         Game.info()
 | |
| 
 | |
|     @staticmethod
 | |
|     def random_ant(ignore: list[Ant] | None = None) -> Ant | None:
 | |
|         if ignore:
 | |
|             ants = [a for a in Ants.ants if a not in ignore]
 | |
|         else:
 | |
|             ants = Ants.ants
 | |
| 
 | |
|         return random.choice(ants)
 | |
| 
 | |
|     @staticmethod
 | |
|     def get_names() -> list[str]:
 | |
|         return [ant.name for ant in Ants.ants]
 | |
| 
 | |
|     @staticmethod
 | |
|     def save() -> None:
 | |
|         Storage.save_ants(Ants.ants)
 | |
| 
 | |
|     @staticmethod
 | |
|     def get_next() -> Ant | None:
 | |
|         now = Utils.now()
 | |
|         ages = [(now - ant.updated) for ant in Ants.ants]
 | |
| 
 | |
|         # Normalize ages to create weights
 | |
|         total_age = sum(ages)
 | |
| 
 | |
|         if total_age == 0:
 | |
|             weights = [1] * len(Ants.ants)  # If all ages are zero, use equal weights
 | |
|         else:
 | |
|             weights = [
 | |
|                 int((age / total_age) * 1000) for age in ages
 | |
|             ]  # Scale and cast to int
 | |
| 
 | |
|         # Perform weighted random selection
 | |
|         return random.choices(Ants.ants, weights=weights, k=1)[0]
 | |
| 
 | |
|     @staticmethod
 | |
|     def get_current() -> Ant | None:
 | |
|         return max(Ants.ants, key=lambda ant: ant.updated)
 | |
| 
 | |
|     @staticmethod
 | |
|     def set_status(ant: Ant, status: str, method: str) -> None:
 | |
|         from .game import Game
 | |
| 
 | |
|         status = status.strip()
 | |
|         ant.status = status
 | |
|         ant.method = method
 | |
|         ant.updated = Utils.now()
 | |
| 
 | |
|         Game.update(ant)
 | |
|         Game.info()
 | |
|         Ants.save()
 | |
| 
 | |
|     @staticmethod
 | |
|     def get() -> None:
 | |
|         objs = Storage.get_ants()
 | |
| 
 | |
|         for obj in objs:
 | |
|             ant = Ant()
 | |
|             ant.from_dict(obj)
 | |
|             Ants.ants.append(ant)
 | |
| 
 | |
|     @staticmethod
 | |
|     def populate(num: int) -> None:
 | |
|         Ants.clear()
 | |
|         Ants.hatch(num)
 | |
| 
 | |
|     @staticmethod
 | |
|     def random_name(ignore: list[str] | None = None) -> str:
 | |
|         names = Ants.get_names()
 | |
| 
 | |
|         if ignore:
 | |
|             for name in ignore:
 | |
|                 if name not in names:
 | |
|                     names.append(name)
 | |
| 
 | |
|         return Utils.random_name(names)
 | |
| 
 | |
|     @staticmethod
 | |
|     def get_top() -> tuple[Ant, int] | None:
 | |
|         top = None
 | |
|         top_score = 0
 | |
| 
 | |
|         for ant in Ants.ants:
 | |
|             score = ant.get_score()
 | |
| 
 | |
|             if (not top) or (score > top_score):
 | |
|                 top = ant
 | |
|                 top_score = score
 | |
|             elif score == top_score:
 | |
|                 if ant.created < top.created:
 | |
|                     top = ant
 | |
| 
 | |
|         if not top:
 | |
|             return None
 | |
| 
 | |
|         return top, top_score
 | |
| 
 | |
|     @staticmethod
 | |
|     def merge(ant_1: Ant | None = None) -> bool:
 | |
|         from .game import Game
 | |
| 
 | |
|         def split(ant: Ant) -> list[str]:
 | |
|             return re.split(r"[ -]", ant.name)
 | |
| 
 | |
|         def remove(words: list[str], ignore: list[str]) -> list[str]:
 | |
|             return [word for word in words if word.lower() not in ignore]
 | |
| 
 | |
|         def fill(words: list[str]) -> list[str]:
 | |
|             words = remove(words, ["of", "de", "da", "the"])
 | |
| 
 | |
|             if len(words) < 2:
 | |
|                 words.extend(Utils.random_word(2 - len(words)))
 | |
| 
 | |
|             return [Utils.capitalize(word) for word in words]
 | |
| 
 | |
|         if not ant_1:
 | |
|             ant_1 = Ants.random_ant()
 | |
| 
 | |
|         if not ant_1:
 | |
|             return False
 | |
| 
 | |
|         ant_2 = Ants.random_ant([ant_1])
 | |
| 
 | |
|         if not ant_2:
 | |
|             return False
 | |
| 
 | |
|         words_1 = split(ant_1)
 | |
|         words_2 = split(ant_2)
 | |
|         words_1 = fill(words_1)
 | |
|         words_2 = fill(words_2)
 | |
| 
 | |
|         name = ""
 | |
|         names = Ants.get_names()
 | |
|         combinations = list(itertools.product(words_1, words_2))
 | |
|         random.shuffle(combinations)
 | |
| 
 | |
|         for combo in combinations:
 | |
|             possible = f"{combo[0]} {combo[1]}"
 | |
| 
 | |
|             if (possible == ant_1.name) or (possible == ant_2.name):
 | |
|                 continue
 | |
| 
 | |
|             if (possible in names) or (possible in Utils.names):
 | |
|                 continue
 | |
| 
 | |
|             name = possible
 | |
|             break
 | |
| 
 | |
|         if not name:
 | |
|             return False
 | |
| 
 | |
|         Ants.set_terminated(ant_1)
 | |
|         Ants.set_terminated(ant_2)
 | |
| 
 | |
|         ant = Ant()
 | |
|         ant.name = name
 | |
|         ant.triumph = ant_1.triumph + ant_2.triumph
 | |
|         ant.hits = ant_1.hits + ant_2.hits
 | |
| 
 | |
|         Ants.ants.append(ant)
 | |
|         Game.update(ant)
 | |
|         Ants.hatch(ignore=[ant_1.name, ant_2.name])
 | |
|         return True
 | |
| 
 | |
|     @staticmethod
 | |
|     def clear() -> None:
 | |
|         Ants.ants = []
 | |
| 
 | |
|     @staticmethod
 | |
|     def terminate(ant: Ant) -> None:
 | |
|         Ants.set_terminated(ant)
 | |
|         Ants.hatch(ignore=[ant.name])
 | |
| 
 | |
|     @staticmethod
 | |
|     def set_terminated(ant: Ant) -> None:
 | |
|         from .game import Game
 | |
| 
 | |
|         ant.method = "terminated"
 | |
|         Game.update(ant)
 | |
|         Ants.ants.remove(ant)
 | 
