File size: 5,781 Bytes
52a4f3c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
"""
SQLAlchemy ORM models for player profiles and conversation history.

Persistent storage for:
- Player profiles (names, stats, relationship state)
- Conversation messages (for context retrieval)
"""

from datetime import datetime
from typing import Optional, List

from sqlalchemy import create_engine, Column, String, Integer, Float, DateTime, ForeignKey, Text, Index
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, Session

Base = declarative_base()


class PlayerProfile(Base):
    """
    Persistent profile for a player across multiple games.

    Tracks relationship state, skill level, play patterns, and history
    so the Chess Master can remember and adapt to individual players.
    """

    __tablename__ = "player_profiles"

    player_id = Column(String(255), primary_key=True)
    player_name = Column(String(255), nullable=False)

    # Timeline
    first_seen = Column(DateTime, default=datetime.now, nullable=False)
    last_played = Column(DateTime, default=datetime.now, nullable=False)

    # Stats
    total_games = Column(Integer, default=0)
    wins_against_agent = Column(Integer, default=0)
    losses_against_agent = Column(Integer, default=0)
    draws = Column(Integer, default=0)

    # Preferences and skill
    preferred_difficulty = Column(String(50), default="intermediate")
    estimated_elo = Column(Integer, default=1400)

    # Relationship state: "new" -> "familiar" -> "rival"
    relationship_state = Column(String(50), default="new")

    # Notes
    notes = Column(Text, nullable=True)

    # Relationship to conversations
    conversations = relationship(
        "ConversationMessage",
        back_populates="player",
        cascade="all, delete-orphan",
    )

    # Timestamps
    created_at = Column(DateTime, default=datetime.now, nullable=False)
    updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)

    def __repr__(self) -> str:
        return (
            f"<PlayerProfile("
            f"player_id={self.player_id}, "
            f"player_name={self.player_name}, "
            f"total_games={self.total_games}, "
            f"relationship_state={self.relationship_state}"
            ")>"
        )

    def to_dict(self) -> dict:
        """Convert to dictionary for easy serialization."""
        return {
            "player_id": self.player_id,
            "player_name": self.player_name,
            "first_seen": self.first_seen.isoformat() if self.first_seen else None,
            "last_played": self.last_played.isoformat() if self.last_played else None,
            "total_games": self.total_games,
            "wins": self.wins_against_agent,
            "losses": self.losses_against_agent,
            "draws": self.draws,
            "preferred_difficulty": self.preferred_difficulty,
            "estimated_elo": self.estimated_elo,
            "relationship_state": self.relationship_state,
            "notes": self.notes,
        }

    def update_last_played(self) -> None:
        """Update last_played timestamp."""
        self.last_played = datetime.now()

    def record_game(self, outcome: str) -> None:
        """
        Record a game outcome.

        Args:
            outcome: "win", "loss", or "draw"
        """
        self.total_games += 1
        if outcome == "win":
            self.wins_against_agent += 1
        elif outcome == "loss":
            self.losses_against_agent += 1
        elif outcome == "draw":
            self.draws += 1
        self.update_last_played()

    def get_win_rate(self) -> float:
        """Calculate win rate against agent."""
        if self.total_games == 0:
            return 0.0
        return self.wins_against_agent / self.total_games

    def update_relationship(self) -> None:
        """Update relationship state based on interaction history."""
        if self.total_games == 0:
            self.relationship_state = "new"
        elif self.total_games < 5:
            self.relationship_state = "familiar"
        else:
            self.relationship_state = "rival"


class ConversationMessage(Base):
    """
    A single message in a conversation between Chess Master and a player.

    Used to store conversation history per player so the agent can
    reference recent interactions and build relationships.
    """

    __tablename__ = "conversation_messages"

    id = Column(Integer, primary_key=True)
    player_id = Column(String(255), ForeignKey("player_profiles.player_id"), nullable=False)
    timestamp = Column(DateTime, default=datetime.now, nullable=False)
    speaker = Column(String(50), nullable=False)  # "chess_master" or "player"
    content = Column(Text, nullable=False)

    # Optional context (game state, move info, etc.) as JSON string
    context_json = Column(Text, nullable=True)

    # Relationship to player
    player = relationship(
        "PlayerProfile",
        back_populates="conversations",
        foreign_keys=[player_id],
    )

    # Timestamp for ordering
    created_at = Column(DateTime, default=datetime.now, nullable=False)

    __table_args__ = (
        Index("idx_player_timestamp", "player_id", "timestamp"),
    )

    def __repr__(self) -> str:
        return (
            f"<ConversationMessage("
            f"player_id={self.player_id}, "
            f"speaker={self.speaker}, "
            f"timestamp={self.timestamp}"
            ")>"
        )

    def to_dict(self) -> dict:
        """Convert to dictionary."""
        return {
            "id": self.id,
            "player_id": self.player_id,
            "timestamp": self.timestamp.isoformat(),
            "speaker": self.speaker,
            "content": self.content,
            "context": self.context_json,
        }