Skip to content
Merged
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
2 changes: 2 additions & 0 deletions backend/poker_tool/adapters/sqlalchemy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class RangeModel(db.Model):
description = db.Column(db.Text, default='')
range_type = db.Column(db.String(20), default='preflop')
position = db.Column(db.String(20), default='undefined')
effective_stack_bb = db.Column(db.Integer, nullable=True)
hands = db.Column(db.JSON, default={}) # Dict[str, str] (hand_str -> action_str)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
created_at = db.Column(db.DateTime, default=_utcnow_naive)
Expand All @@ -48,6 +49,7 @@ def to_domain(self):
hands=hands,
user_id=self.user_id,
range_id=self.id,
effective_stack_bb=self.effective_stack_bb,
)


Expand Down
2 changes: 2 additions & 0 deletions backend/poker_tool/adapters/sqlalchemy/ranges.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def add(self, range_obj: Range) -> Range:
model.description = range_obj.description
model.range_type = range_obj.type.name.lower()
model.position = range_obj.position.name
model.effective_stack_bb = range_obj.effective_stack_bb
model.hands = hands_dict
model.user_id = range_obj.user_id
else:
Expand All @@ -32,6 +33,7 @@ def add(self, range_obj: Range) -> Range:
description=range_obj.description,
range_type=range_obj.type.name.lower(),
position=range_obj.position.name,
effective_stack_bb=range_obj.effective_stack_bb,
hands=hands_dict,
user_id=range_obj.user_id,
)
Expand Down
9 changes: 9 additions & 0 deletions backend/poker_tool/objects/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class ActionType(Enum):
CALL = auto()
FOLD = auto()
ALL_IN = auto()
DEFENSE = auto()
DEFENSE_3BET = auto()
DEFENSE_4BET = auto()
UNDEFINED = auto()


Expand All @@ -34,6 +37,9 @@ def color(self) -> str:
ActionType.CALL: "#FF9800",
ActionType.FOLD: "#F44336",
ActionType.ALL_IN: "#9C27B0",
ActionType.DEFENSE: "#FF5722",
ActionType.DEFENSE_3BET: "#E91E63",
ActionType.DEFENSE_4BET: "#9C27B0",
ActionType.UNDEFINED: "#607D8B",
}
return colors[self._type]
Expand All @@ -47,6 +53,9 @@ def label(self) -> str:
ActionType.CALL: "Suivre",
ActionType.FOLD: "Passer",
ActionType.ALL_IN: "All-In",
ActionType.DEFENSE: "Défense",
ActionType.DEFENSE_3BET: "Défense 3Bet",
ActionType.DEFENSE_4BET: "Défense 4Bet",
ActionType.UNDEFINED: "Non défini",
}
return labels[self._type]
Expand Down
77 changes: 77 additions & 0 deletions backend/poker_tool/objects/effective_stack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
Immutable effective stack value object (Elegant Objects).
"""

from enum import Enum

# Valeurs de stack effectif en BB (Big Blinds)
EFFECTIVE_STACK_VALUES = [
50, 100, 30, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10,
9, 8, 7, 6, 5
]


class EffectiveStack(Enum):
"""Effective stack sizes in BB."""

BB_5 = 5
BB_6 = 6
BB_7 = 7
BB_8 = 8
BB_9 = 9
BB_10 = 10
BB_11 = 11
BB_12 = 12
BB_13 = 13
BB_14 = 14
BB_15 = 15
BB_16 = 16
BB_17 = 17
BB_18 = 18
BB_19 = 19
BB_20 = 20
BB_30 = 30
BB_50 = 50
BB_100 = 100

@property
def value(self) -> int:
"""Numeric value in BB."""
return self._value_ # type: ignore

@property
def label(self) -> str:
"""Human-readable label."""
return f"{self.value} BB"

@classmethod
def from_int(cls, stack_value: int) -> 'EffectiveStack':
"""Factory method from integer value."""
for stack in cls:
if stack.value == stack_value:
return stack
# Default to 100 BB if not found
return cls.BB_100

@classmethod
def from_string(cls, stack_str: str) -> 'EffectiveStack':
"""Factory method from string."""
try:
# Try to parse as integer first
stack_value = int(stack_str)
return cls.from_int(stack_value)
except ValueError:
try:
return cls[stack_str.upper()]
except KeyError:
return cls.BB_100

@classmethod
def all_values(cls) -> list[int]:
"""Return all available stack values as integers."""
return [stack.value for stack in cls]

@classmethod
def all_labels(cls) -> list[str]:
"""Return all available stack labels."""
return [stack.label for stack in cls]
44 changes: 40 additions & 4 deletions backend/poker_tool/objects/position.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@

class Position(Enum):
"""Table positions in poker."""

UTG = auto()
MP = auto()
UTG_PLUS_1 = auto()
LJ = auto()
HJ = auto()
CO = auto()
BTN = auto()
SB = auto()
Expand All @@ -19,7 +22,9 @@ def label(self) -> str:
"""Human-readable label."""
labels = {
Position.UTG: "UTG",
Position.MP: "MP",
Position.UTG_PLUS_1: "UTG+1",
Position.LJ: "LJ",
Position.HJ: "HJ",
Position.CO: "CO",
Position.BTN: "BTN",
Position.SB: "SB",
Expand All @@ -33,8 +38,10 @@ def color(self) -> str:
"""Color associated with this position."""
colors = {
Position.UTG: "#FF5722",
Position.MP: "#FF9800",
Position.CO: "#FFC107",
Position.UTG_PLUS_1: "#FF7043",
Position.LJ: "#FF9800",
Position.HJ: "#FFC107",
Position.CO: "#FFE0B2",
Position.BTN: "#4CAF50",
Position.SB: "#2196F3",
Position.BB: "#9C27B0",
Expand All @@ -45,6 +52,35 @@ def color(self) -> str:
@classmethod
def from_string(cls, position_str: str) -> 'Position':
"""Factory method from string."""
# Map common string representations to enum names
string_to_enum = {
"UTG": "UTG",
"UTG+1": "UTG_PLUS_1",
"LJ": "LJ",
"HJ": "HJ",
"CO": "CO",
"BTN": "BTN",
"SB": "SB",
"BB": "BB",
}

# Try to normalize the input string
normalized = position_str.upper().replace("+", "_PLUS_").replace("1", "_1")

# Try direct enum access first
try:
return Position[normalized]
except KeyError:
pass

# Try with the string mapping
try:
enum_name = string_to_enum[position_str.upper()]
return Position[enum_name]
except KeyError:
pass

# Try with normalized string
try:
return Position[position_str.upper()]
except KeyError:
Expand Down
15 changes: 14 additions & 1 deletion backend/poker_tool/objects/range.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def __init__(
hands: dict[str, Action] | None = None,
user_id: int | None = None,
range_id: int | None = None,
effective_stack_bb: int | None = None,
):
self._name = name
self._description = description
Expand All @@ -28,6 +29,7 @@ def __init__(
self._hands = hands or {}
self._user_id = user_id
self._id = range_id
self._effective_stack_bb = effective_stack_bb

@property
def id(self) -> int | None:
Expand Down Expand Up @@ -64,6 +66,11 @@ def user_id(self) -> int | None:
"""User ID."""
return self._user_id

@property
def effective_stack_bb(self) -> int | None:
"""Effective stack in BB."""
return self._effective_stack_bb

def with_hand(self, hand_str: str, action: Action) -> 'Range':
"""Return new Range with added/updated hand (immutable)."""
new_hands = dict(self._hands)
Expand All @@ -76,6 +83,7 @@ def with_hand(self, hand_str: str, action: Action) -> 'Range':
hands=new_hands,
user_id=self._user_id,
range_id=self._id,
effective_stack_bb=self._effective_stack_bb,
)

def without_hand(self, hand_str: str) -> 'Range':
Expand All @@ -90,6 +98,7 @@ def without_hand(self, hand_str: str) -> 'Range':
hands=new_hands,
user_id=self._user_id,
range_id=self._id,
effective_stack_bb=self._effective_stack_bb,
)

def grid(self) -> list[list[dict]]:
Expand Down Expand Up @@ -123,7 +132,7 @@ def statistics(self) -> dict:

def to_dict(self) -> dict:
"""Serialize to dictionary."""
return {
result = {
"id": self._id,
"name": self._name,
"description": self._description,
Expand All @@ -132,6 +141,9 @@ def to_dict(self) -> dict:
"hands": {k: str(v) for k, v in self._hands.items()},
"user_id": self._user_id,
}
if self._effective_stack_bb is not None:
result["effective_stack_bb"] = self._effective_stack_bb
return result

@classmethod
def from_dict(cls, data: dict) -> 'Range':
Expand All @@ -148,4 +160,5 @@ def from_dict(cls, data: dict) -> 'Range':
hands=hands,
user_id=data.get("user_id"),
range_id=data.get("id"),
effective_stack_bb=data.get("effective_stack_bb"),
)
27 changes: 27 additions & 0 deletions backend/poker_tool/tests/unit/objects/test_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ def test_action_type_values(self):
self.assertEqual(ActionType.CALL.name, "CALL")
self.assertEqual(ActionType.FOLD.name, "FOLD")
self.assertEqual(ActionType.ALL_IN.name, "ALL_IN")
self.assertEqual(ActionType.DEFENSE.name, "DEFENSE")
self.assertEqual(ActionType.DEFENSE_3BET.name, "DEFENSE_3BET")
self.assertEqual(ActionType.DEFENSE_4BET.name, "DEFENSE_4BET")
self.assertEqual(ActionType.UNDEFINED.name, "UNDEFINED")


Expand All @@ -35,6 +38,9 @@ def test_action_color(self):
ActionType.CALL: "#FF9800",
ActionType.FOLD: "#F44336",
ActionType.ALL_IN: "#9C27B0",
ActionType.DEFENSE: "#FF5722",
ActionType.DEFENSE_3BET: "#E91E63",
ActionType.DEFENSE_4BET: "#9C27B0",
ActionType.UNDEFINED: "#607D8B",
}

Expand All @@ -50,6 +56,9 @@ def test_action_label(self):
ActionType.CALL: "Suivre",
ActionType.FOLD: "Passer",
ActionType.ALL_IN: "All-In",
ActionType.DEFENSE: "Défense",
ActionType.DEFENSE_3BET: "Défense 3Bet",
ActionType.DEFENSE_4BET: "Défense 4Bet",
ActionType.UNDEFINED: "Non défini",
}

Expand All @@ -65,6 +74,15 @@ def test_action_string_representation(self):
action = Action(ActionType.RAISE)
self.assertEqual(str(action), "raise")

action = Action(ActionType.DEFENSE)
self.assertEqual(str(action), "defense")

action = Action(ActionType.DEFENSE_3BET)
self.assertEqual(str(action), "defense_3bet")

action = Action(ActionType.DEFENSE_4BET)
self.assertEqual(str(action), "defense_4bet")

def test_action_equality(self):
"""Test equality comparison."""
action1 = Action(ActionType.OPEN)
Expand Down Expand Up @@ -100,6 +118,15 @@ def test_action_from_string(self):
action = Action.from_string("Raise")
self.assertEqual(action.type, ActionType.RAISE)

action = Action.from_string("DEFENSE")
self.assertEqual(action.type, ActionType.DEFENSE)

action = Action.from_string("defense_3bet")
self.assertEqual(action.type, ActionType.DEFENSE_3BET)

action = Action.from_string("DEFENSE_4BET")
self.assertEqual(action.type, ActionType.DEFENSE_4BET)

# Invalid action string
action = Action.from_string("INVALID")
self.assertEqual(action.type, ActionType.UNDEFINED)
Expand Down
Loading
Loading