File size: 3,878 Bytes
f8657ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import re


COMMAND_VERBS = {
    "go", "head", "walk", "run", "visit", "check", "rest", "help",
    "tell", "make", "ask", "send", "move", "bring", "look",
}

BASE_LOCATION_ALIASES = {
    "clinic": "nia_clinic",
    "hospital": "nia_clinic",
    "nurse": "nia_clinic",
    "cafe": "cafe",
    "café": "cafe",
    "coffee": "cafe",
    "school": "school",
    "class": "school",
    "park": "park",
    "fountain": "fountain",
    "square": "square",
    "plaza": "square",
    "office": "priya_office",
    "city hall": "priya_office",
    "shop": "shop",
    "store": "shop",
}


def classify(text, world):
    raw = (text or "").strip()
    if not raw:
        return {"type": "noop", "text": raw, "addressees": [], "instruction": "", "goto": None}
    if not re.search(r"[A-Za-z0-9]", raw):
        return {"type": "ambient", "text": raw, "addressees": [], "instruction": raw, "goto": None}

    cast = world.get("cast", [])
    names = _matched_names(raw, cast)
    lowered = raw.lower()
    looks_command = bool(names) and (
        _starts_with_name_command(lowered, names)
        or lowered.startswith("tell ")
        or lowered.startswith("make ")
        or lowered.startswith("ask ")
        or any(re.search(rf"\b{verb}\b", lowered) for verb in COMMAND_VERBS)
    )
    if looks_command:
        instruction = _strip_command_prefix(raw, names)
        goto = resolve_location(instruction, world, cast, names)
        return {
            "type": "directed_command",
            "text": raw,
            "addressees": names,
            "instruction": instruction,
            "goto": goto,
        }

    return {"type": "world_event", "text": raw, "addressees": [], "instruction": raw, "goto": None}


def resolve_location(text, world, cast=None, addressees=None):
    lowered = (text or "").lower()
    hotspots = ((world.get("board", {}) or {}).get("hotspots_tile") or {})

    if "home" in lowered and cast and addressees and len(addressees) == 1:
        char = next((c for c in cast if c["name"] == addressees[0]), None)
        home = char.get("home") if char else None
        if home in hotspots:
            return home

    aliases = dict(BASE_LOCATION_ALIASES)
    if "nurse_office" in hotspots:
        aliases["clinic"] = "nurse_office"
        aliases["nurse"] = "nurse_office"
        aliases["nurse office"] = "nurse_office"
    if "quad" in hotspots:
        aliases["quad"] = "quad"
        aliases["campus"] = "quad"
    if "riverside_walk" in hotspots:
        aliases["river"] = "riverside_walk"
        aliases["riverside"] = "riverside_walk"
    for key in hotspots:
        aliases[key] = key
        aliases[key.replace("_", " ")] = key
        aliases[key.replace("_", "")] = key

    for phrase, hotspot in sorted(aliases.items(), key=lambda item: len(item[0]), reverse=True):
        if phrase in lowered and hotspot in hotspots:
            return hotspot
    return None


def _matched_names(text, cast):
    lowered = text.lower()
    matches = []
    for char in cast:
        first = char["name"].split()[0].lower()
        if re.search(rf"\b{re.escape(first)}\b", lowered):
            matches.append(char["name"])
    return matches


def _starts_with_name_command(lowered, names):
    first_names = [name.split()[0].lower() for name in names]
    for first in first_names:
        if re.match(rf"^\s*{re.escape(first)}\s*[,:\-]\s*", lowered):
            return True
    return False


def _strip_command_prefix(text, names):
    instruction = text.strip()
    first_names = "|".join(re.escape(name.split()[0]) for name in names)
    patterns = [
        rf"^\s*(?:tell|ask|make|send)\s+(?:{first_names})\s+(?:to\s+)?",
        rf"^\s*(?:{first_names})\s*[,:\-]\s*",
    ]
    for pattern in patterns:
        instruction = re.sub(pattern, "", instruction, flags=re.IGNORECASE).strip()
    return instruction or text.strip()