Files
python-tdd/src/apps/drama/models.py

137 lines
5.0 KiB
Python
Raw Normal View History

from django.conf import settings
from django.db import models
class GameEvent(models.Model):
# Gate phase
ROOM_CREATED = "room_created"
SLOT_RESERVED = "slot_reserved"
SLOT_FILLED = "slot_filled"
SLOT_RETURNED = "slot_returned"
SLOT_RELEASED = "slot_released"
INVITE_SENT = "invite_sent"
# Role Select phase
ROLE_SELECT_STARTED = "role_select_started"
ROLE_SELECTED = "role_selected"
ROLES_REVEALED = "roles_revealed"
VERB_CHOICES = [
(ROOM_CREATED, "Room created"),
(SLOT_RESERVED, "Gate slot reserved"),
(SLOT_FILLED, "Gate slot filled"),
(SLOT_RETURNED, "Gate slot returned"),
(SLOT_RELEASED, "Gate slot released"),
(INVITE_SENT, "Invite sent"),
(ROLE_SELECT_STARTED, "Role select started"),
(ROLE_SELECTED, "Role selected"),
(ROLES_REVEALED, "Roles revealed"),
]
room = models.ForeignKey(
"epic.Room", on_delete=models.CASCADE, related_name="events",
)
actor = models.ForeignKey(
settings.AUTH_USER_MODEL, null=True, blank=True,
on_delete=models.SET_NULL, related_name="game_events",
)
verb = models.CharField(max_length=30, choices=VERB_CHOICES)
data = models.JSONField(default=dict)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["timestamp"]
def to_prose(self):
"""Return a human-readable action description (actor rendered separately in template)."""
d = self.data
if self.verb == self.SLOT_FILLED:
_token_names = {
"coin": "Coin-on-a-String", "Free": "Free Token",
"tithe": "Tithe Token", "pass": "Backstage Pass", "carte": "Carte Blanche",
}
code = d.get("token_type", "token")
token = d.get("token_display") or _token_names.get(code, code)
days = d.get("renewal_days", 7)
slot = d.get("slot_number", "?")
return f"deposits a {token} for slot {slot} (expires in {days} days)."
if self.verb == self.SLOT_RESERVED:
return "reserves a seat"
if self.verb == self.SLOT_RETURNED:
return "withdraws from the gate"
if self.verb == self.SLOT_RELEASED:
return f"releases slot {d.get('slot_number', '?')}"
if self.verb == self.ROOM_CREATED:
return "opens this room"
if self.verb == self.INVITE_SENT:
return "sends an invitation"
if self.verb == self.ROLE_SELECT_STARTED:
return "Role selection begins"
if self.verb == self.ROLE_SELECTED:
_role_names = {
"PC": "Player", "BC": "Builder", "SC": "Shepherd",
"AC": "Alchemist", "NC": "Narrator", "EC": "Economist",
}
code = d.get("role", "?")
role = d.get("role_display") or _role_names.get(code, code)
return f"elects to start as the {role}, and will enjoy affinity with this Role for the remainder of the game."
if self.verb == self.ROLES_REVEALED:
return "All roles assigned"
return self.verb
def to_activity(self, base_url):
"""Serialise this event as an AS2 Activity dict, or None if unsupported."""
if not self.actor or not self.actor.username:
return None
actor_url = f"{base_url}/ap/users/{self.actor.username}/"
room_url = f"{base_url}/gameboard/room/{self.room_id}/"
if self.verb == self.SLOT_FILLED:
return {
"type": "earthman:JoinGate",
"actor": actor_url,
"object": room_url,
"summary": self.to_prose(),
}
if self.verb == self.ROLE_SELECTED:
return {
"type": "earthman:SelectRole",
"actor": actor_url,
"object": room_url,
"summary": self.to_prose(),
}
if self.verb == self.ROOM_CREATED:
return {
"type": "Create",
"actor": actor_url,
"object": room_url,
"summary": self.to_prose(),
}
return None
def __str__(self):
actor = self.actor.email if self.actor else "system"
return f"[{self.timestamp:%Y-%m-%d %H:%M}] {actor}{self.verb}"
class ScrollPosition(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
related_name="scroll_positions",
)
room = models.ForeignKey(
"epic.Room", on_delete=models.CASCADE,
related_name="scroll_positions",
)
position = models.PositiveIntegerField(default=0)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = [("user", "room")]
def __str__(self):
return f"{self.user.email} @ {self.room.name}: {self.position}px"
def record(room, verb, actor=None, **data):
"""Record a game event in the drama log."""
return GameEvent.objects.create(room=room, actor=actor, verb=verb, data=data)