commit 6283d7f321c391412fd5fe1feaf8afb29af8ea0a Author: bel Date: Sat Sep 16 22:30:34 2023 -0600 gogo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac5a70f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +**/*.sw* +**/__pycache__ diff --git a/design.d/assets.d/class_sheets.d/0.pdf b/design.d/assets.d/class_sheets.d/0.pdf new file mode 100644 index 0000000..8d54e01 Binary files /dev/null and b/design.d/assets.d/class_sheets.d/0.pdf differ diff --git a/design.d/assets.d/class_sheets.d/1.pdf b/design.d/assets.d/class_sheets.d/1.pdf new file mode 100644 index 0000000..5cac3eb Binary files /dev/null and b/design.d/assets.d/class_sheets.d/1.pdf differ diff --git a/design.d/assets.d/class_sheets.d/10.pdf b/design.d/assets.d/class_sheets.d/10.pdf new file mode 100644 index 0000000..a8bc358 Binary files /dev/null and b/design.d/assets.d/class_sheets.d/10.pdf differ diff --git a/design.d/assets.d/class_sheets.d/2.pdf b/design.d/assets.d/class_sheets.d/2.pdf new file mode 100644 index 0000000..120d5c7 Binary files /dev/null and b/design.d/assets.d/class_sheets.d/2.pdf differ diff --git a/design.d/assets.d/class_sheets.d/3.pdf b/design.d/assets.d/class_sheets.d/3.pdf new file mode 100644 index 0000000..f79dd4f Binary files /dev/null and b/design.d/assets.d/class_sheets.d/3.pdf differ diff --git a/design.d/assets.d/class_sheets.d/4.pdf b/design.d/assets.d/class_sheets.d/4.pdf new file mode 100644 index 0000000..f56832a Binary files /dev/null and b/design.d/assets.d/class_sheets.d/4.pdf differ diff --git a/design.d/assets.d/class_sheets.d/5.pdf b/design.d/assets.d/class_sheets.d/5.pdf new file mode 100644 index 0000000..6659550 Binary files /dev/null and b/design.d/assets.d/class_sheets.d/5.pdf differ diff --git a/design.d/assets.d/class_sheets.d/6.pdf b/design.d/assets.d/class_sheets.d/6.pdf new file mode 100644 index 0000000..49bb59b Binary files /dev/null and b/design.d/assets.d/class_sheets.d/6.pdf differ diff --git a/design.d/assets.d/class_sheets.d/7.pdf b/design.d/assets.d/class_sheets.d/7.pdf new file mode 100644 index 0000000..183245e Binary files /dev/null and b/design.d/assets.d/class_sheets.d/7.pdf differ diff --git a/design.d/assets.d/class_sheets.d/8.pdf b/design.d/assets.d/class_sheets.d/8.pdf new file mode 100644 index 0000000..02b0911 Binary files /dev/null and b/design.d/assets.d/class_sheets.d/8.pdf differ diff --git a/design.d/assets.d/class_sheets.d/9.pdf b/design.d/assets.d/class_sheets.d/9.pdf new file mode 100644 index 0000000..bdc900f Binary files /dev/null and b/design.d/assets.d/class_sheets.d/9.pdf differ diff --git a/design.d/assets.d/classes_with_icons.pdf b/design.d/assets.d/classes_with_icons.pdf new file mode 100644 index 0000000..1fce888 Binary files /dev/null and b/design.d/assets.d/classes_with_icons.pdf differ diff --git a/design.d/assets.d/dice_distribution.yaml b/design.d/assets.d/dice_distribution.yaml new file mode 100644 index 0000000..259cbdb --- /dev/null +++ b/design.d/assets.d/dice_distribution.yaml @@ -0,0 +1,8 @@ +- color: white + sides: [0, 0, 1, 1, 2, 2C] +- color: yellow + sides: [0, 0, 1, 2, 3, 3C] +- color: red + sides: [0, 0, 2, 3, 3, 4C] +- color: black + sides: [0, 0, 3, 3, 4, 5C] diff --git a/design.d/assets.d/play_tldr.yaml b/design.d/assets.d/play_tldr.yaml new file mode 100644 index 0000000..ef14c71 --- /dev/null +++ b/design.d/assets.d/play_tldr.yaml @@ -0,0 +1,105 @@ +- src: https://s3.amazonaws.com/geekdo-files.com/bgg338940?response-content-disposition=inline%3B%20filename%3D%22Oathsworn_player_aid_v2.pdf%22&response-content-type=application%2Fpdf&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJYFNCT7FKCE4O6TA%2F20230917%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230917T005449Z&X-Amz-SignedHeaders=host&X-Amz-Expires=120&X-Amz-Signature=c2daa6d7c83c99289b2d6698ea0ee102c84800942ebd439bd7ce948ade3595db +- stories: | + Story aid + Setup + Take permanent combat tokens. Full oathsworn health. + Use locations + Send runner - cost LVL coins, send to Banksmith or Apothecary. + Banksmith: buy LVL common cards, sell for half price (round up). + Price is card level. + Apothecary: Buy curatives (max 5) for LVL cost. + Trade coins or equip-items at any time between players and + company bag (bag limit 12 items) + Perform check/survival check: One Oathsworn. 1-10 white dice. + Tokens: Redraw (all checks), Empowered x3 (Might checks). + 2 blanks is fail (do not regain tokens). + Dice value / difficulty = successes. FAIL: See story. + Round of Combat: Everyone. Might dice and 0-10 white dice. + Tokens: Redraw and Empowered 3x, no Ability Cards, Items Abilities, + or Special Abilities. 2 blanks is fail (do not regain tokens). Dice value / + defense = damage (round down). FAIL: -1 health. +- encounters: | + Encounter aid + Setup + If special rules box is checked, place it faceup. Otherwise revealed at + end of setup. + Pick 2 hands of weapons, 1 armor, 1 gear. + Set weapon might cubes, and armor/shield defense number token. + 7 ability cards, choose from * to the current LVL, one with cooldown + (0), two (1), two (2), two (3). + Take max animus and place on left side, place regen tracker token if + necessary. + Pick first player. + 1. Turn start + Regen aminus, pick up cards on cooldown 0. Next stage card is face + up. + 2. Oathsworn take turns + Move: 1 animus per hex. All characters and terrain block movement. + Use ability card: pick card action, pay animus, resolve, place on + cooldown, battleflow. + Pass (does not end your round). + Oathsworn attack + Target closest location on large monsters. + Might value plus any number of white dice. May use combat tokens. + 2+ blank is fail (don’t include critical). + SUCCESS: Damage = dice roll / defense (round down). + FAIL: Regain all used combat tokens + 1 of choice. + Monster attack + Hero may play one card to increase defense, no animus cost (then + battleflow). + Roll might, no critical or miss. + Damage = dice roll / defense (round down) + Oathsworn may spend defense tokens. + 3. Encounter phase + (start here if ambushed) + 3a. Activate large monster(s) following stage card. + Large movement destroys terrain, will bounce on edges, moves + through characters, ending on characters pushes them (no damage). + Change facing after move. + Attacks that display icons of broken locations draw 1 less of the best + Might cards. + Face towards attack target. + 3b. Minion activation order: closest target in range and LOS (ignore + terrain effects) > Target with adjacent enemy with Mob keyword (if + attacker has Mob keyword only) > NW rule. Activate minion and + move, + 3c. Minions attack, mob sum up might dice to one attack. + Targeting priority + Character who broke location (reaction only) > specific target on + Stage card if in range and LOS > closest Oathsworn in range and + LOS (ignore terrain effects) > Oathsworn with adjacent enemy with + Mob keyword (if Mob keyword) > NW rule. (Never targets ally/friendly + character, except reaction) + Keywords + Area Of Effect: may affect each character only once, gain +1 + damage for each extra hex of a large figure, for melee AOEs LOS + to all targets needed, for ranged AOEs only the center hex needs + to be in LOS. One dice roll for all targets. Reactions do not cancel + hits on additional targets. + Battleflow: move all cards on one cooldown position to next. No + cascade. + Caged: can't do anything. See special rules. Each adjacent friendly + character gives +2 on survival check. + Chained attack: choose target in range and LOS, add additional + targets in chain range and LOS of the previous target. See AOE. + Remove lowest (not blank) dice result, once for each additional + target. + Charge Through X: Move X in straight line, attack all enemies moved + through, see AOE. + Consumed: Stuck in body part until broken or 6 damage delt. May + attack once per round for 3 animus. See special rules on start/end + round effects. + Crippled: No move or abilities with movement, until the end of the + next Oathsworn phase. + Empowered: Upgrade dice white>yellow>red>black. + Push: move straight away, or closest NW free hex. + Knockback: push that damages 1 on collision with terrain or + character (both take damage), no damage on board edge. Large is + pushed ½ distance (round up) and destroys terrain. + Line Of Sight: between closest corners. Or if straight tile row; center + to center. Line cannot touch any terrain. Ignore characters. + Thrown: place tracker in any adjacent hex to target, cannot pick up + this round. + Thrown weapon: unarmed, might is zero. Can still attack. + Thrown armor: no defense from items, no use of thrown item. + Wave attacks: 3 hex wide line of attack, nothing blocks it. diff --git a/design.d/assets.d/v1_character_sheet.pdf b/design.d/assets.d/v1_character_sheet.pdf new file mode 100644 index 0000000..870b320 Binary files /dev/null and b/design.d/assets.d/v1_character_sheet.pdf differ diff --git a/design.d/assets.d/v1_company_allies_etc.pdf b/design.d/assets.d/v1_company_allies_etc.pdf new file mode 100644 index 0000000..6f221a4 Binary files /dev/null and b/design.d/assets.d/v1_company_allies_etc.pdf differ diff --git a/design.d/assets.d/v1_free_company_sheet.pdf b/design.d/assets.d/v1_free_company_sheet.pdf new file mode 100644 index 0000000..91e1580 Binary files /dev/null and b/design.d/assets.d/v1_free_company_sheet.pdf differ diff --git a/design.d/cdrt-experiment.d/cdrt.py b/design.d/cdrt-experiment.d/cdrt.py new file mode 100644 index 0000000..cb48304 --- /dev/null +++ b/design.d/cdrt-experiment.d/cdrt.py @@ -0,0 +1,108 @@ +from yaml import safe_load as yaml_load +from yaml import dump as yaml_dump +from sys import argv + +def main(): + path = "./testdata/hello_world.yaml" + if len(argv) > 1: + path = argv[1] + doc = Doc.from_file(path) + print(doc.render()) + print(doc.to_dict()) + +# https://blog.kevinjahns.de/are-crdts-suitable-for-shared-editing/ +class Doc(): + def __init__(self): + self._nodes = [] + + def from_file(path): + doc = Doc() + with open(path, "r") as f: + d = yaml_load(f) + doc._nodes = [Node.from_dict(i) for i in d["nodes"]] + return doc + + def to_dict(self): + return yaml_dump(slim_dict({ + "nodes": [node.to_dict() for node in self._nodes], + })) + + def render(self): + result = [] + nodes = [node for node in self._nodes] + while nodes: + first = self._first_node(nodes) + result.append(first) + nodes = [node for node in nodes if node != first] + return "".join([i.content() for i in result]) + + def _first_node(self, nodes): + for potential_root_node in sorted([node for node in nodes if not node.from_id()], key=lambda x: x.id()): + return potential_root_node + node_ids = [node.id() for node in nodes] + for potential_rootest_node in sorted([node for node in nodes if not node.from_id() in node_ids], key=lambda x: x.id()): + return potential_rootest_node + raise Exception(nodes) + +class Node(): + def __init__(self, client_name, ts, content, is_delete, from_id, to_id): + self._ts = ts + self._client_name = client_name + self._content = content + self._is_delete = is_delete + self._from_id = from_id + self._to_id = to_id + + def __str__(self): + return str(self.to_dict()) + + def from_dict(d): + return Node( + d.get("client_name", ""), + d.get("ts", 0), + d.get("content", ""), + d.get("is_delete", False), + d.get("from", ""), + d.get("to", ""), + ) + + def to_dict(self): + return slim_dict({ + "client_name": self._client_name, + "ts": self._ts, + "content": self._content, + "is_delete": self._is_delete, + "from": self._from_id, + "to": self._to_id, + }) + + def clone(self): + return Node( + self._ts, + self._client_name, + self._content, + self._is_delete, + self._from_id, + self._to_id, + ) + + def id(self): + return f'{self._ts}/{self._client_name}' + + def from_id(self): + return self._from_id + + def to_id(self): + return self._to_id + + def content(self): + if self._is_delete: + self._content = "" + return self._content + +def slim_dict(d): + return {k:v for k,v in d.items() if v or (isinstance(v, int) and not isinstance(v, bool))} + +if __name__ == "__main__": + main() + diff --git a/design.d/cdrt-experiment.d/cdrt1.py b/design.d/cdrt-experiment.d/cdrt1.py new file mode 100644 index 0000000..3ef543d --- /dev/null +++ b/design.d/cdrt-experiment.d/cdrt1.py @@ -0,0 +1,179 @@ +from yaml import safe_load as yaml_load +from yaml import dump as yaml_dump +from sys import argv + +def main(): + path = "./testdata/hello_world.yaml" + if len(argv) > 1: + path = argv[1] + doc = Doc.from_file(path) + print(doc.render()) + print(doc.to_dict()) + +# https://blog.kevinjahns.de/are-crdts-suitable-for-shared-editing/ +class Doc(): + def __init__(self): + self._nodes = [] + self._edges = [] + + def from_file(path): + doc = Doc() + with open(path, "r") as f: + d = yaml_load(f) + doc._nodes = [Node.from_dict(i) for i in d["nodes"]] + doc._edges = [Edge.from_dict(i) for i in d["edges"]] + return doc + + def to_dict(self): + return yaml_dump(slim_dict({ + "nodes": [node.to_dict() for node in self._nodes], + "edges": [edge.to_dict() for edge in self._edges], + })) + + def render(self): + result = [] + sorted_edges = self.sorted_edges() + for edge in sorted_edges: + result.extend([i for i in self._nodes if i.id() == edge.from_id()]) + if sorted_edges: + result.extend([i for i in self._nodes if i.id() == sorted_edges[-1].to_id()]) + for node in sorted(self._nodes, key=lambda x: x.id()): + if not node in result: + if result: + self._edges.append(Edge.between(result[-1], node)) + result.append(node) + return "".join([i.content() for i in result]) + + def sorted_edges(self): + result = [] + edges = [edge for edge in self._edges] + while edges: + first = self._first_edge(edges) + edges = [i for i in edges if i != first] + result.append(first) + return result + + def _first_edge(self, edges): + to_ids = [edge.to_id() for edge in edges] + from_ids = [edge.from_id() for edge in edges] + candidates = [] + for from_id in from_ids: + if not from_id in to_ids: + candidates.append(from_id) + from_edges = [edge for edge in edges if edge.from_id() in candidates] + return sorted(from_edges, key=lambda x: x.from_id())[0] + +class Edge(): + def __init__(self, from_client_name, from_ts, to_client_name, to_ts): + self._from_client_name = from_client_name + self._from_ts = from_ts + self._to_client_name = to_client_name + self._to_ts = to_ts + + def between(a, b): + return Edge( + a.client_name(), + a.ts(), + b.client_name(), + b.ts(), + ) + + def from_dict(d): + return Edge( + d.get("from", {}).get("client_name", ""), + d.get("from", {}).get("ts", 0), + d.get("to", {}).get("client_name", ""), + d.get("to", {}).get("ts", 0), + ) + + def to_dict(self): + return slim_dict({ + "from": slim_dict({ + "client_name": self._from_client_name, + "ts": self._from_ts, + }), + "to": slim_dict({ + "client_name": self._to_client_name, + "ts": self._to_ts, + }), + }) + + def from_id(self): + return f'{self._from_ts}/{self._from_client_name}' + + def to_id(self): + return f'{self._to_ts}/{self._to_client_name}' + + def __str__(self): + return f'{self._from_ts}/{self._from_client_name}..{self._to_ts}/{self._to_client_name}' + +class Node(): + def __init__(self, client_name, ts, content, is_delete): + self._ts = ts + self._client_name = client_name + self._content = content + self._is_delete = is_delete + + def __str__(self): + return str(self.to_dict()) + + def from_dict(d): + return Node( + d.get("client_name", ""), + d.get("ts", 0), + d.get("content", ""), + d.get("is_delete", False), + ) + + def to_dict(self): + return slim_dict({ + "client_name": self._client_name, + "ts": self._ts, + "content": self._content, + "is_delete": self._is_delete, + }) + + def clone(self): + return Node( + self._ts, + self._client_name, + self._content, + self._is_delete, + ) + + def id(self): + return f'{self._ts}/{self._client_name}' + + def content(self): + if self.is_delete(): + return "" + return self._content + + def client_name(self): + return self._client_name + + def ts(self): + return self._ts + + def is_delete(self): + return self._is_delete + + def split(self, idx): + up_to = self.clone() + up_to._content = up_to._content[:idx] + starting_at = self.clone() + starting_at._content = up_to._content[idx:] + return [up_to, starting_at] + + def merge(self, other): + assert(self._client_name == other._client_name) + self._ts = max(self._ts, other._ts) + self._content += other._content + self._is_delete = self._is_delete or other._is_delete + return self + +def slim_dict(d): + return {k:v for k,v in d.items() if v or (isinstance(v, int) and not isinstance(v, bool))} + +if __name__ == "__main__": + main() diff --git a/design.d/cdrt-experiment.d/test_cdrt.py b/design.d/cdrt-experiment.d/test_cdrt.py new file mode 100644 index 0000000..754e383 --- /dev/null +++ b/design.d/cdrt-experiment.d/test_cdrt.py @@ -0,0 +1,18 @@ +import unittest +import cdrt + +class TestTestdata(unittest.TestCase): + def test(self): + from pathlib import Path + for path in Path('./testdata').rglob('*.yaml'): + doc = cdrt.Doc.from_file(path) + result = doc.render() + print(doc.to_dict()) + with open(path, "r") as f: + d = cdrt.yaml_load(f) + if "expect" in d: + print(f'expect "{d["expect"]}", got "{result}"') + self.assertEqual(d["expect"], result) + +if __name__ == "__main__": + unittest.main() diff --git a/design.d/cdrt-experiment.d/testdata/append_dangling.yaml b/design.d/cdrt-experiment.d/testdata/append_dangling.yaml new file mode 100644 index 0000000..1f9150a --- /dev/null +++ b/design.d/cdrt-experiment.d/testdata/append_dangling.yaml @@ -0,0 +1,11 @@ +expect: "hello world ohno" +nodes: +- ts: 0 + content: hello + to: 1/ +- ts: 1 + content: " world" + from: 0/ +- ts: 99 + content: " ohno" + from: 0/ diff --git a/design.d/cdrt-experiment.d/testdata/conflict_resolution.yaml b/design.d/cdrt-experiment.d/testdata/conflict_resolution.yaml new file mode 100644 index 0000000..c52ad56 --- /dev/null +++ b/design.d/cdrt-experiment.d/testdata/conflict_resolution.yaml @@ -0,0 +1,6 @@ +expect: "maybeAmaybeB" +nodes: +- ts: 0 + content: "maybeA" +- ts: 0 + content: "maybeB" diff --git a/design.d/cdrt-experiment.d/testdata/hello_world.yaml b/design.d/cdrt-experiment.d/testdata/hello_world.yaml new file mode 100644 index 0000000..7abae08 --- /dev/null +++ b/design.d/cdrt-experiment.d/testdata/hello_world.yaml @@ -0,0 +1,10 @@ +expect: "hello world" +nodes: +- ts: 0 + content: hello + client_name: x + to: 1/x +- ts: 1 + content: " world" + client_name: x + from: 0/x diff --git a/design.d/cdrt-experiment.d/testdata/is_delete.yaml b/design.d/cdrt-experiment.d/testdata/is_delete.yaml new file mode 100644 index 0000000..b8caa5c --- /dev/null +++ b/design.d/cdrt-experiment.d/testdata/is_delete.yaml @@ -0,0 +1,11 @@ +expect: " world" +nodes: +- ts: 0 + content: hello + client_name: x + to: 1/x + is_delete: true +- ts: 1 + content: " world" + client_name: x + from: 0/x diff --git a/design.d/cdrt-experiment.d/testdata/unordered.yaml b/design.d/cdrt-experiment.d/testdata/unordered.yaml new file mode 100644 index 0000000..58325d3 --- /dev/null +++ b/design.d/cdrt-experiment.d/testdata/unordered.yaml @@ -0,0 +1,16 @@ +expect: "idx0idx1idx2idx3" +nodes: +- ts: 1 + content: "idx0" + to: 0/ +- ts: 0 + content: "idx1" + from: 1/ + to: 2/ +- ts: 2 + content: "idx2" + from: 0/ + to: 3/ +- ts: 3 + content: "idx3" + from: 2/ diff --git a/design.d/data-model.yaml b/design.d/data-model.yaml new file mode 100644 index 0000000..532cd76 --- /dev/null +++ b/design.d/data-model.yaml @@ -0,0 +1,42 @@ +id: xyz +last_modified: 2006-01-02T03:04:05Z +state: + players: + oathsworn: + xyz: + hp: 0 + tokens: + - xyz # defense, 2animus, redraw, Nempower, battleflow + animus: + regen: 0 # 6+- + max: 0 # 6+- + companions: + xyz: {} + free_company: + name: xyz + chapters: + - number: 1 # [1,21] + progress: + story: + complete: false + ambushed: false + special_rules: false + unique_item: false + encounter: false + traits: + - name: xyz # [0,3] stacks per name + knockouts: + - chapter: x + name: xyz + city: + - number: [0, 0] + allies: + - number: 0 + name: xyz + deceased: true + wood: + - number: [0, 0] + notes: + - xyz + keywords: + - xyz