feat: implement tweak_archetype() and manual_adjustments() (#12)
Closes #12 - tweak_archetype(): prompts user for updated archetype stats (avg/obp/slg/bb%/k% vs L and R, power and batted-ball profile, baserunning for batters), then recalculates D20 card ratings via the existing calculator - manual_adjustments(): prompts user to choose a split (vs L or vs R), displays all 22 D20 chance fields with running total, accepts field-number + value edits, and warns if total deviates from 108 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f1ca14791d
commit
9584daee57
@ -6,6 +6,7 @@ baseball archetypes with iterative review and refinement.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import copy
|
||||
import sys
|
||||
from typing import Literal
|
||||
from datetime import datetime
|
||||
@ -347,7 +348,7 @@ class CustomCardCreator:
|
||||
vs_hand = rating["vs_hand"]
|
||||
print(f"\nVS {vs_hand}{'HP' if player_type == 'batter' else 'HB'}:")
|
||||
print(
|
||||
f" AVG: {rating['avg']:.3f} OBP: {rating['obp']:.3f} SLG: {rating['slg']:.3f} OPS: {rating['obp']+rating['slg']:.3f}"
|
||||
f" AVG: {rating['avg']:.3f} OBP: {rating['obp']:.3f} SLG: {rating['slg']:.3f} OPS: {rating['obp'] + rating['slg']:.3f}"
|
||||
)
|
||||
|
||||
# Show hit distribution
|
||||
@ -364,7 +365,7 @@ class CustomCardCreator:
|
||||
+ rating["bp_single"]
|
||||
)
|
||||
print(
|
||||
f" Hits: {total_hits:.1f} (HR: {rating['homerun']:.1f} 3B: {rating['triple']:.1f} 2B: {rating['double_pull']+rating['double_two']+rating['double_three']:.1f} 1B: {total_hits - rating['homerun'] - rating['bp_homerun'] - rating['triple'] - rating['double_pull'] - rating['double_two'] - rating['double_three']:.1f})"
|
||||
f" Hits: {total_hits:.1f} (HR: {rating['homerun']:.1f} 3B: {rating['triple']:.1f} 2B: {rating['double_pull'] + rating['double_two'] + rating['double_three']:.1f} 1B: {total_hits - rating['homerun'] - rating['bp_homerun'] - rating['triple'] - rating['double_pull'] - rating['double_two'] - rating['double_three']:.1f})"
|
||||
)
|
||||
|
||||
# Show walk/strikeout
|
||||
@ -389,7 +390,7 @@ class CustomCardCreator:
|
||||
)
|
||||
)
|
||||
print(
|
||||
f" Outs: {outs:.1f} (K: {rating['strikeout']:.1f} LD: {rating['lineout']:.1f} FB: {rating['flyout_a']+rating['flyout_bq']+rating['flyout_lf_b']+rating['flyout_rf_b']:.1f} GB: {rating['groundout_a']+rating['groundout_b']+rating['groundout_c']:.1f})"
|
||||
f" Outs: {outs:.1f} (K: {rating['strikeout']:.1f} LD: {rating['lineout']:.1f} FB: {rating['flyout_a'] + rating['flyout_bq'] + rating['flyout_lf_b'] + rating['flyout_rf_b']:.1f} GB: {rating['groundout_a'] + rating['groundout_b'] + rating['groundout_c']:.1f})"
|
||||
)
|
||||
|
||||
# Calculate and display total OPS
|
||||
@ -420,10 +421,68 @@ class CustomCardCreator:
|
||||
print("-" * 70)
|
||||
print("\nAdjust key percentages (press Enter to keep current value):\n")
|
||||
|
||||
# TODO: Implement percentage tweaking
|
||||
# For now, return unchanged
|
||||
print("(Feature coming soon - manual adjustments available in option 3)")
|
||||
return card_data
|
||||
def prompt_float(label: str, current: float) -> float:
|
||||
val = input(f" {label} [{current:.3f}]: ").strip()
|
||||
if not val:
|
||||
return current
|
||||
try:
|
||||
return float(val)
|
||||
except ValueError:
|
||||
print(" Invalid value, keeping current.")
|
||||
return current
|
||||
|
||||
def prompt_int(label: str, current: int) -> int:
|
||||
val = input(f" {label} [{current}]: ").strip()
|
||||
if not val:
|
||||
return current
|
||||
try:
|
||||
return int(val)
|
||||
except ValueError:
|
||||
print(" Invalid value, keeping current.")
|
||||
return current
|
||||
|
||||
arch = copy.copy(archetype)
|
||||
|
||||
print("--- vs RHP/RHB ---")
|
||||
arch.avg_vs_r = prompt_float("AVG vs R", arch.avg_vs_r)
|
||||
arch.obp_vs_r = prompt_float("OBP vs R", arch.obp_vs_r)
|
||||
arch.slg_vs_r = prompt_float("SLG vs R", arch.slg_vs_r)
|
||||
arch.bb_pct_vs_r = prompt_float("BB% vs R", arch.bb_pct_vs_r)
|
||||
arch.k_pct_vs_r = prompt_float("K% vs R", arch.k_pct_vs_r)
|
||||
|
||||
print("\n--- vs LHP/LHB ---")
|
||||
arch.avg_vs_l = prompt_float("AVG vs L", arch.avg_vs_l)
|
||||
arch.obp_vs_l = prompt_float("OBP vs L", arch.obp_vs_l)
|
||||
arch.slg_vs_l = prompt_float("SLG vs L", arch.slg_vs_l)
|
||||
arch.bb_pct_vs_l = prompt_float("BB% vs L", arch.bb_pct_vs_l)
|
||||
arch.k_pct_vs_l = prompt_float("K% vs L", arch.k_pct_vs_l)
|
||||
|
||||
print("\n--- Power Profile ---")
|
||||
arch.hr_per_hit = prompt_float("HR/Hit", arch.hr_per_hit)
|
||||
arch.triple_per_hit = prompt_float("3B/Hit", arch.triple_per_hit)
|
||||
arch.double_per_hit = prompt_float("2B/Hit", arch.double_per_hit)
|
||||
|
||||
print("\n--- Batted Ball Profile ---")
|
||||
arch.gb_pct = prompt_float("GB%", arch.gb_pct)
|
||||
arch.fb_pct = prompt_float("FB%", arch.fb_pct)
|
||||
arch.ld_pct = prompt_float("LD%", arch.ld_pct)
|
||||
|
||||
if player_type == "batter":
|
||||
print("\n--- Baserunning ---")
|
||||
arch.speed_rating = prompt_int("Speed (1-10)", arch.speed_rating) # type: ignore[arg-type]
|
||||
arch.steal_jump = prompt_int("Jump (1-10)", arch.steal_jump) # type: ignore[arg-type]
|
||||
arch.xbt_pct = prompt_float("XBT%", arch.xbt_pct) # type: ignore[union-attr]
|
||||
|
||||
# Recalculate card ratings with the modified archetype
|
||||
if player_type == "batter":
|
||||
calc = BatterRatingCalculator(arch) # type: ignore[arg-type]
|
||||
ratings = calc.calculate_ratings(battingcard_id=0)
|
||||
baserunning = calc.calculate_baserunning()
|
||||
return {"ratings": ratings, "baserunning": baserunning}
|
||||
else:
|
||||
calc_p = PitcherRatingCalculator(arch) # type: ignore[arg-type]
|
||||
ratings = calc_p.calculate_ratings(pitchingcard_id=0)
|
||||
return {"ratings": ratings}
|
||||
|
||||
async def manual_adjustments(
|
||||
self, player_type: Literal["batter", "pitcher"], card_data: dict
|
||||
@ -434,10 +493,99 @@ class CustomCardCreator:
|
||||
print("-" * 70)
|
||||
print("\nDirectly edit D20 chances (must sum to 108):\n")
|
||||
|
||||
# TODO: Implement manual adjustments
|
||||
# For now, return unchanged
|
||||
print("(Feature coming soon)")
|
||||
return card_data
|
||||
D20_FIELDS = [
|
||||
"homerun",
|
||||
"bp_homerun",
|
||||
"triple",
|
||||
"double_three",
|
||||
"double_two",
|
||||
"double_pull",
|
||||
"single_two",
|
||||
"single_one",
|
||||
"single_center",
|
||||
"bp_single",
|
||||
"hbp",
|
||||
"walk",
|
||||
"strikeout",
|
||||
"lineout",
|
||||
"popout",
|
||||
"flyout_a",
|
||||
"flyout_bq",
|
||||
"flyout_lf_b",
|
||||
"flyout_rf_b",
|
||||
"groundout_a",
|
||||
"groundout_b",
|
||||
"groundout_c",
|
||||
]
|
||||
|
||||
# Choose which split to edit
|
||||
print("Which split to edit?")
|
||||
for i, rating in enumerate(card_data["ratings"]):
|
||||
vs = rating["vs_hand"]
|
||||
print(f" {i + 1}. vs {vs}{'HP' if player_type == 'batter' else 'HB'}")
|
||||
|
||||
while True:
|
||||
choice = input("\nSelect split (1-2): ").strip()
|
||||
try:
|
||||
idx = int(choice) - 1
|
||||
if 0 <= idx < len(card_data["ratings"]):
|
||||
break
|
||||
else:
|
||||
print("Invalid choice.")
|
||||
except ValueError:
|
||||
print("Invalid input.")
|
||||
|
||||
result = copy.deepcopy(card_data)
|
||||
rating = result["ratings"][idx]
|
||||
|
||||
while True:
|
||||
vs = rating["vs_hand"]
|
||||
print(
|
||||
f"\n--- VS {vs}{'HP' if player_type == 'batter' else 'HB'} D20 Chances ---"
|
||||
)
|
||||
total = 0.0
|
||||
for i, field in enumerate(D20_FIELDS, 1):
|
||||
val = rating[field]
|
||||
print(f" {i:2d}. {field:<20s}: {val:.2f}")
|
||||
total += val
|
||||
print(f"\n Total: {total:.2f} (target: 108.00)")
|
||||
|
||||
user_input = input(
|
||||
"\nEnter field number and new value (e.g. '1 3.5'), or 'done': "
|
||||
).strip()
|
||||
if user_input.lower() in ("done", "q", ""):
|
||||
break
|
||||
|
||||
parts = user_input.split()
|
||||
if len(parts) != 2:
|
||||
print(" Enter a field number and a value separated by a space.")
|
||||
continue
|
||||
|
||||
try:
|
||||
field_idx = int(parts[0]) - 1
|
||||
new_val = float(parts[1])
|
||||
except ValueError:
|
||||
print(" Invalid input.")
|
||||
continue
|
||||
|
||||
if not (0 <= field_idx < len(D20_FIELDS)):
|
||||
print(f" Field number must be between 1 and {len(D20_FIELDS)}.")
|
||||
continue
|
||||
|
||||
if new_val < 0:
|
||||
print(" Value cannot be negative.")
|
||||
continue
|
||||
|
||||
rating[D20_FIELDS[field_idx]] = new_val
|
||||
|
||||
total = sum(rating[f] for f in D20_FIELDS)
|
||||
if abs(total - 108.0) > 0.01:
|
||||
print(
|
||||
f"\nWarning: Total is {total:.2f} (expected 108.00). "
|
||||
"Ratings saved but card probabilities may be incorrect."
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
async def create_database_records(
|
||||
self,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user