Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.pyc
67 changes: 67 additions & 0 deletions actions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
-- FORMAT:title
B Clause: Actions


-- FORMAT_LINES:bodylist
(1) Engaging in a difficult enterprise when promised a reward for high achievement,
(2) Falling in love at a time when certain obligations forbid love,
(3) Seeking to demonstrate the power of love by a test of courage.
(4) Being impelled by inordinate fancy to exercise mistaken judgment in a love affair
(5) Becoming involved in a hopeless love affair, and seeking to make the best of a disheartening situation,
(6) Challenging, in a quest of love, the relentless truth that “East is East, and West is West, and never the twain shall meet,”
(7) Becoming involved in a love affair that encounters unforeseen obstacles,
(8) Confronting a situation in which wealth is made conditional upon a certain course of action in a love affair,
(9) Being put to a test in which love will be lost if more material fortunes are advanced,
(10) Suffering an estrangement due to mistaken judgment,
(11) Confronting a situation in which courage and devotion alone can save the fortunes of one beloved,
(12) Falling into misfortune through disloyalty in love,
(13) Seeking by craftiness to escape misfortune,
(14) Falling into misfortune through the wiles of a crafty schemer,
(15) Finding a sustaining power in misfortune,
(16) Being delivered from misfortune by one who, in confidence, confesses a secret of transgression,
(17) Bearing patiently with misfortunes and seeking to attain cherished aims honorably,
(18) Rebelling against a power that controls personal abilities and holds them in subjection.
(19) Meeting with misfortune and being cast away in a primitive, isolated and savage environment,
(20) Becoming involved with conditions in which misfortune is indicated,
(21) Falling into misfortune through mistaken judgment,
(22) Following a wrong course through mistaken judgment,
(23) Becoming involved in a complication that has to do with mistaken judgment and suspicion,
(24) Becoming the victim of mistaken judgment in carrying out an enterprise,
(25) Seeking to save a person who is accused of transgression,
(26) Seeking secretly to preserve another from danger,
(27) Refusing to betray another’s secret and calmly facing persecution because of the refusal,
(28) Facing a situation in which the misfortunes of one greatly esteemed call for courage and sagacious enterprise,
(29) Aiding another to hide from the world a fateful secret,
(30) Enlisting whole-heartedly in the service of a needy unfortunate and conferring aid of the utmost value,
(31) Living a lonely, cheerless life and seeking companionship
(32) Seeking to conceal identity because of a lofty idealism,
(33) Resisting secretly and from an honorable motive a mandate considered discreditable,
(34) Embarking upon an enterprise of insurrection in the hope of ameliorating certain evil conditions,
(35) Becoming involved in a complication that challenges the value of cherished ideals,
(36) Undergoing an experience that results in a remarkable character change,
(37) Seeking against difficulties to realize a cherished ideal.
(38) Committing a grievous mistake and seeking in secret to live down its evil results,
(39) Forsaking cherished ambitions to carry out an obligation,
(40) Embarking upon an enterprise in which one obligation is opposed by another obligation.
(41) Finding an obligation at variance with ambition, inclination or necessity,
(42) Falling into misfortune while seeking honorably to discharge an obligation,
(43) Seeking to overcome personal limitations in carrying out an enterprise,
(44) Seeking by unusual methods to conquer personal limitations.
(45) Seeking to forward an enterprise and encountering family sentiment as an obstacle.
(46) Seeking retaliation for a grievous wrong that is either real or fancied,
(47) Finding (apparently) an object greatly coveted, and obtaining (apparently) the object,
(48) Assuming the character of a criminal in a perfectly honest enterprise.
(49) Assuming a fictitious character when embarking upon a certain enterprise,
(50) Being impelled by an unusual motive to engage in crafty enterprise,
(51) Devising a clever and plausible delusion in order to forward certain ambitious plans,
(52) Encountering a would-be transgressor and seeking to prevent a transgression,
(53) Opposing the plans of a crafty schemer,
(54) Becoming involved in a puzzling complication that has to do with an object possessing mysterious powers,
(55) Becoming involved in a mysterious complication and seeking to make the utmost of a bizarre experience,
(56) Seeking to test the value of a mysterious communication and becoming involved in weird complexities,
(57) Seeking to unravel a puzzling complication,
(58) Engaging in an enterprise and then mysteriously disappearing,
(59) Engaging in an mysterious enterprise and becoming involved with the occult and the fantastic.
(60) Becoming involved, through curiosity aroused by mystery, in a strange enterprise.
(61) Becoming aware of an important secret that calls for decisive action,
(62) Becoming involved in any sort of complication,
290 changes: 290 additions & 0 deletions api_scripts/conflict_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
#!/usr/bin/env python3
"""
Plotto Conflict Registry - A system for managing Plotto conflicts with prelinks and postlinks.

Usage example:
# Load all conflicts from HTML
load_conflicts_from_html('plotto-mf.html')

# Get a specific conflict
conflict = Conflict.get_by_id('347a')

# Get a conflict with its prelinks and postlinks
conflict_set = Conflict.get_conflict_set('347a')
main = conflict_set['main']
prelinks = conflict_set['prelinks'] # List of Conflict objects
postlinks = conflict_set['postlinks'] # List of Conflict objects
"""

import json
import re
from datetime import datetime

from bs4 import BeautifulSoup


class Conflict:
_registry = {} # Class variable to store all conflicts

def __init__(self, conflict_id, description, prelinks=None, postlinks=None):
self.conflict_id = conflict_id
self.description = description
# Store full link objects with instructions
self.prelinks = prelinks or []
self.postlinks = postlinks or []

# Auto-register when created
Conflict._registry[conflict_id] = self

@classmethod
def get_conflict_set(cls, conflict_id):
"""Get a conflict and its prelinks/postlinks as Conflict objects.

Returns:
dict with keys: 'main', 'prelinks', 'postlinks'
or None if conflict not found
"""
main = cls._registry.get(conflict_id)
if not main:
return None

return {
"main": main,
"prelinks": [
cls._registry.get(link["id"] if isinstance(link, dict) else link)
for link in main.prelinks
if cls._registry.get(link["id"] if isinstance(link, dict) else link)
],
"postlinks": [
cls._registry.get(link["id"] if isinstance(link, dict) else link)
for link in main.postlinks
if cls._registry.get(link["id"] if isinstance(link, dict) else link)
],
}

@classmethod
def get_by_id(cls, conflict_id):
"""Get a conflict by its ID."""
return cls._registry.get(conflict_id)

@classmethod
def get_all_ids(cls):
"""Get all registered conflict IDs."""
return list(cls._registry.keys())

@classmethod
def clear_registry(cls):
"""Clear all registered conflicts (useful for testing)."""
cls._registry.clear()

def __repr__(self):
return f"Conflict('{self.conflict_id}', prelinks={len(self.prelinks)}, postlinks={len(self.postlinks)})"

def __str__(self):
return f"{self.conflict_id}: {self.description[:50]}..."


def load_conflicts_from_html(filename):
"""Load all conflicts from Plotto HTML file and create Conflict objects.

Args:
filename: Path to the Plotto HTML file

Returns:
int: Number of conflicts loaded
"""
print(f"Loading conflicts from {filename}...")

# Clear existing registry
Conflict.clear_registry()

# Parse HTML and create Conflict objects
conflicts_data = _parse_html(filename)

created_count = 0
for conflict_data in conflicts_data:
try:
Conflict(
conflict_id=conflict_data["id"],
description=conflict_data["description"],
prelinks=conflict_data["prelinks"],
postlinks=conflict_data["postlinks"],
)
created_count += 1
except Exception as e:
print(f"Error creating conflict {conflict_data.get('id', 'unknown')}: {e}")

print(f"Successfully loaded {created_count} conflicts")
return created_count


def _parse_html(filename):
"""Internal function to parse the Plotto HTML file."""

with open(filename, "r", encoding="utf-8") as f:
html_content = f.read()

soup = BeautifulSoup(html_content, "html.parser")
conflicts = []

# Find all conflict divs directly (they have both id and class="conflict")
conflict_divs = soup.find_all("div", class_="conflict")

for conflict_div in conflict_divs:
conflict_num = conflict_div.get("id")
if not conflict_num:
continue

# Find the conflictid div to confirm the number
conflictid_div = conflict_div.find("div", class_="conflictid")
if conflictid_div:
conflict_num = conflictid_div.get_text().strip()

# Find description
desc_div = conflict_div.find("div", class_="desc")
if not desc_div:
continue

description = desc_div.get_text().strip()
description = re.sub(r"\s+", " ", description) # Normalize whitespace

if not description or len(description) < 10:
continue

# Find prelinks and postlinks
prelinks_div = conflict_div.find("div", class_="prelinks")
postlinks_div = conflict_div.find("div", class_="postlinks")

# Extract links
prelinks = _extract_links_from_div(prelinks_div) if prelinks_div else []
postlinks = _extract_links_from_div(postlinks_div) if postlinks_div else []

# Create conflict with 'a' suffix (Plotto convention)
conflict_id = f"{conflict_num}a"

conflicts.append(
{
"id": conflict_id,
"description": description,
"prelinks": prelinks,
"postlinks": postlinks,
}
)

return conflicts


def _extract_links_from_div(div):
"""Extract conflict links from a div, including any instructions."""
if not div:
return []

links = []
link_elements = div.find_all("a", class_="clink")

for link_elem in link_elements:
link_text = link_elem.get_text().strip()
link_objects = _parse_conflict_references(link_text)
links.extend(link_objects)

return links


def _parse_conflict_references(text):
"""Parse conflict references from text, returning objects with id and instructions.

Examples:
- '1296' -> [{'id': '1296a', 'instructions': None}]
- '1244b' -> [{'id': '1244b', 'instructions': None}]
- '1054 ch A to B' -> [{'id': '1054a', 'instructions': 'ch A to B'}]
- '23a, b' -> [{'id': '23a', 'instructions': None}, {'id': '23b', 'instructions': None}]
"""
# Clean up the text
text = text.strip()

# Check for instructions (like "ch A to B")
instructions = None
if " ch " in text:
parts = text.split(" ch ", 1)
text = parts[0].strip()
instructions = "ch " + parts[1].strip()

refs = []

# Handle comma-separated cases like "23a, b"
if "," in text:
parts = [part.strip() for part in text.split(",")]
base_match = re.match(r"^(\d+)([a-z]?)$", parts[0])
if base_match:
base_num = base_match.group(1)
first_letter = base_match.group(2) or "a"

# First reference
refs.append(
{"id": f"{base_num}{first_letter}", "instructions": instructions}
)

# Additional letters
for part in parts[1:]:
if re.match(r"^[a-z]$", part):
refs.append(
{"id": f"{base_num}{part}", "instructions": instructions}
)
return refs

# Handle simple cases like "1296", "1244b"
if re.match(r"^\d+[a-z]?$", text):
# If it's just a number, add 'a' suffix
if re.match(r"^\d+$", text):
conflict_id = f"{text}a"
else:
conflict_id = text

return [{"id": conflict_id, "instructions": instructions}]

# If nothing matches, return empty list
return []


def save_to_json(filename="conflicts.json"):
"""Save all conflicts to a JSON file for later use."""
import os

# Ensure the file is saved in the api_scripts directory
if not os.path.dirname(filename):
script_dir = os.path.dirname(os.path.abspath(__file__))
filename = os.path.join(script_dir, filename)

data = {
"conflicts": {},
"metadata": {
"total_count": len(Conflict._registry),
"generated_at": datetime.now().isoformat(),
},
}

for conflict_id, conflict in Conflict._registry.items():
data["conflicts"][conflict_id] = {
"description": conflict.description,
"prelinks": conflict.prelinks,
"postlinks": conflict.postlinks,
}

with open(filename, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)

print(f"Saved {len(Conflict._registry)} conflicts to {filename}")


if __name__ == "__main__":
# Example usage
count = load_conflicts_from_html("plotto-mf.html")
print(f"Loaded {count} conflicts")

# Test a specific conflict
conflict = Conflict.get_by_id("364d")
if conflict:
print(f"\nExample conflict: {conflict}")

# Export to JSON
save_to_json()
Loading