clawtan 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/clawtan/cli.py +90 -13
  2. package/package.json +1 -1
package/clawtan/cli.py CHANGED
@@ -390,6 +390,15 @@ def _print_opponents(opponents: list):
390
390
  print(line)
391
391
 
392
392
 
393
+ def _format_trade_tuple(val: list) -> str:
394
+ """Decode a 10-int trade tuple into 'give X for Y' text."""
395
+ giving = {RESOURCES[i]: val[i] for i in range(5) if val[i]}
396
+ wanting = {RESOURCES[i]: val[i + 5] for i in range(5) if val[i + 5]}
397
+ give_str = ", ".join(f"{n}x {r}" for r, n in giving.items()) or "nothing"
398
+ want_str = ", ".join(f"{n}x {r}" for r, n in wanting.items()) or "nothing"
399
+ return f"{give_str} for {want_str}"
400
+
401
+
393
402
  _ACTION_HINTS = {
394
403
  "RELEASE_CATCH": (
395
404
  "Discard half your cards (server selects randomly).\n"
@@ -408,6 +417,29 @@ _ACTION_HINTS = {
408
417
  "Year of Plenty: pick 2 free resources.\n"
409
418
  " CLI: clawtan act PLAY_BOUNTIFUL_HARVEST '[\"DRIFTWOOD\",\"CORAL\"]'"
410
419
  ),
420
+ "OFFER_TRADE": (
421
+ "Offer a player-to-player trade. Value = 10-element list:\n"
422
+ " first 5 = resources you GIVE [DRIFTWOOD,CORAL,SHRIMP,KELP,PEARL],\n"
423
+ " last 5 = resources you WANT [DRIFTWOOD,CORAL,SHRIMP,KELP,PEARL].\n"
424
+ " CLI: clawtan act OFFER_TRADE '[0,0,0,1,0,0,1,0,0,0]' # give 1 KELP, want 1 CORAL"
425
+ ),
426
+ "ACCEPT_TRADE": (
427
+ "Accept a trade offer. Value = the 10-int trade tuple (echoed from the offer).\n"
428
+ " CLI: clawtan act ACCEPT_TRADE '[0,0,0,1,0,0,1,0,0,0]'"
429
+ ),
430
+ "REJECT_TRADE": (
431
+ "Reject a trade offer. Value = the 10-int trade tuple (echoed from the offer).\n"
432
+ " CLI: clawtan act REJECT_TRADE '[0,0,0,1,0,0,1,0,0,0]'"
433
+ ),
434
+ "CONFIRM_TRADE": (
435
+ "Confirm trade with a specific acceptee. Value = 11-element list:\n"
436
+ " the 10-int trade tuple + the accepting player's color.\n"
437
+ " CLI: clawtan act CONFIRM_TRADE '[0,0,0,1,0,0,1,0,0,0,\"BLUE\"]'"
438
+ ),
439
+ "CANCEL_TRADE": (
440
+ "Cancel your pending trade offer.\n"
441
+ " CLI: clawtan act CANCEL_TRADE"
442
+ ),
411
443
  }
412
444
 
413
445
 
@@ -671,6 +703,30 @@ def _format_live_action(color, action, val, state=None, pre_resources=None):
671
703
  if action == "PLAY_CURRENT_BUILDING":
672
704
  return f" [{ts}] {color} played Road Building"
673
705
 
706
+ if action == "OFFER_TRADE":
707
+ if isinstance(val, list) and len(val) == 10:
708
+ return f" [{ts}] {color} offered trade: {_format_trade_tuple(val)}"
709
+ return f" [{ts}] {color} offered a trade"
710
+
711
+ if action == "ACCEPT_TRADE":
712
+ if isinstance(val, list) and len(val) == 10:
713
+ return f" [{ts}] {color} accepted trade: {_format_trade_tuple(val)}"
714
+ return f" [{ts}] {color} accepted a trade"
715
+
716
+ if action == "REJECT_TRADE":
717
+ if isinstance(val, list) and len(val) == 10:
718
+ return f" [{ts}] {color} rejected trade: {_format_trade_tuple(val)}"
719
+ return f" [{ts}] {color} rejected a trade"
720
+
721
+ if action == "CONFIRM_TRADE":
722
+ if isinstance(val, list) and len(val) == 11:
723
+ partner = val[10]
724
+ return f" [{ts}] {color} confirmed trade with {partner}: {_format_trade_tuple(val[:10])}"
725
+ return f" [{ts}] {color} confirmed a trade"
726
+
727
+ if action == "CANCEL_TRADE":
728
+ return f" [{ts}] {color} cancelled their trade offer"
729
+
674
730
  if action == "OCEAN_TRADE":
675
731
  if isinstance(val, list) and len(val) >= 2:
676
732
  giving = val[:-1]
@@ -831,13 +887,8 @@ def cmd_wait(args):
831
887
  print(f"Waiting for players ({pj}/{np})...", file=sys.stderr)
832
888
  phase_shown = "lobby"
833
889
  else:
834
- cur = status.get("current_color", "?")
835
- if phase_shown != "turn" or cur != prev_current:
836
- print(f"Waiting for your turn (current: {cur})...", file=sys.stderr)
837
- phase_shown = "turn"
838
- prev_current = cur
839
-
840
890
  # Live action feed: detect and display new game actions
891
+ # (fetched BEFORE the waiting message so actions print in order)
841
892
  try:
842
893
  live_state = _get(f"/game/{game_id}")
843
894
  records = live_state.get("action_records", [])
@@ -860,6 +911,12 @@ def cmd_wait(args):
860
911
  except (APIError, Exception):
861
912
  pass
862
913
 
914
+ cur = status.get("current_color", "?")
915
+ if phase_shown != "turn" or cur != prev_current:
916
+ print(f"Waiting for your turn (current: {cur})...", file=sys.stderr)
917
+ phase_shown = "turn"
918
+ prev_current = cur
919
+
863
920
  # Our turn!
864
921
  if status.get("your_turn"):
865
922
  break
@@ -980,17 +1037,30 @@ def cmd_act(args):
980
1037
  except APIError as e:
981
1038
  print(f"ERROR: {args.action} failed.", file=sys.stderr)
982
1039
  if "not a valid action" in e.detail.lower():
983
- print(
984
- f" '{args.action}' is not available right now.",
985
- file=sys.stderr,
986
- )
987
- # Fetch current state to show what IS available
988
1040
  try:
989
1041
  state = _get(f"/game/{game_id}")
990
1042
  prompt = state.get("current_prompt", "?")
991
1043
  current = state.get("current_color", "?")
992
- print(f" Current turn: {current} | Prompt: {prompt}", file=sys.stderr)
993
1044
  actions = state.get("current_playable_actions", [])
1045
+
1046
+ available_types = set()
1047
+ for a in actions:
1048
+ if isinstance(a, list) and len(a) > 1:
1049
+ available_types.add(a[1])
1050
+
1051
+ if args.action in available_types:
1052
+ val_str = json.dumps(value, separators=(",", ":")) if value is not None else "(none)"
1053
+ print(
1054
+ f" '{args.action}' is available, but the value {val_str} is not a valid option.",
1055
+ file=sys.stderr,
1056
+ )
1057
+ else:
1058
+ print(
1059
+ f" '{args.action}' is not available right now.",
1060
+ file=sys.stderr,
1061
+ )
1062
+
1063
+ print(f" Current turn: {current} | Prompt: {prompt}", file=sys.stderr)
994
1064
  if actions:
995
1065
  _print_actions(actions, my_color=color, state=state)
996
1066
  print(
@@ -1340,7 +1410,14 @@ def main():
1340
1410
  " PLAY_BOUNTIFUL_HARVEST <r> Year of Plenty, e.g. '[\"DRIFTWOOD\",\"CORAL\"]'\n"
1341
1411
  " PLAY_TIDAL_MONOPOLY <res> Monopoly, e.g. SHRIMP\n"
1342
1412
  " PLAY_CURRENT_BUILDING Road Building\n"
1343
- " OCEAN_TRADE <val> Trade, e.g. '[\"KELP\",\"KELP\",\"KELP\",\"KELP\",\"SHRIMP\"]'\n"
1413
+ " OFFER_TRADE <val> Player trade: 10-element list [give5, want5]\n"
1414
+ " e.g. '[0,0,0,1,0,0,1,0,0,0]' = give 1 KELP, want 1 CORAL\n"
1415
+ " ACCEPT_TRADE <val> Accept a trade offer (echo the 10-int tuple)\n"
1416
+ " REJECT_TRADE <val> Reject a trade offer (echo the 10-int tuple)\n"
1417
+ " CONFIRM_TRADE <val> Confirm with acceptee: 10 ints + color, e.g.\n"
1418
+ " '[0,0,0,1,0,0,1,0,0,0,\"BLUE\"]'\n"
1419
+ " CANCEL_TRADE Cancel your pending trade offer\n"
1420
+ " OCEAN_TRADE <val> Maritime trade, e.g. '[\"KELP\",\"KELP\",\"KELP\",\"KELP\",\"SHRIMP\"]'\n"
1344
1421
  " END_TIDE End your turn\n"
1345
1422
  "\n"
1346
1423
  "VALUE is parsed as JSON. Bare words (e.g. SHRIMP) are treated as strings.\n"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawtan",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "CLI for AI agents playing Settlers of Clawtan -- a lobster-themed Catan board game",
5
5
  "bin": {
6
6
  "clawtan": "./bin/clawtan.js"