hex position indicators: chair icons at hex edge midpoints replace gate-slot circles
- Split .gate-overlay into .gate-backdrop (z-100, blur) + .gate-overlay modal (z-120) so .table-position elements (z-110) render above backdrop but below modal - New _table_positions.html partial: 6 .table-position divs with .fa-chair, role label, and .fa-ban/.fa-circle-check status icons; included unconditionally in room.html - New epic:room view at /gameboard/room/<uuid>/; gatekeeper redirects there when table_status set; pick_roles redirects there - role-select.js: adds .active glow to position on selectRole(); swaps .fa-ban→.fa-circle-check in placeCard onComplete; handleTurnChanged clears stale .active from all positions - FTs: PositionIndicatorsTest (5 tests) + RoleSelectTest 8a/8b (glow + check state) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -71,6 +71,17 @@ def _notify_sig_selected(room_id, card_id, role, deck_type='levity'):
|
||||
)
|
||||
|
||||
|
||||
SLOT_ROLE_LABELS = {1: "PC", 2: "NC", 3: "EC", 4: "SC", 5: "AC", 6: "BC"}
|
||||
|
||||
|
||||
def _gate_positions(room):
|
||||
"""Return list of dicts [{slot, role_label}] for _table_positions.html."""
|
||||
return [
|
||||
{"slot": slot, "role_label": SLOT_ROLE_LABELS.get(slot.slot_number, "")}
|
||||
for slot in room.gate_slots.order_by("slot_number")
|
||||
]
|
||||
|
||||
|
||||
def _expire_reserved_slots(room):
|
||||
cutoff = timezone.now() - RESERVE_TIMEOUT
|
||||
room.gate_slots.filter(
|
||||
@@ -135,6 +146,7 @@ def _gate_context(room, user):
|
||||
"carte_slots_claimed": carte_slots_claimed,
|
||||
"carte_nvm_slot_number": carte_nvm_slot_number,
|
||||
"carte_next_slot_number": carte_next_slot_number,
|
||||
"gate_positions": _gate_positions(room),
|
||||
}
|
||||
|
||||
|
||||
@@ -186,6 +198,7 @@ def _role_select_context(room, user):
|
||||
.values_list("slot_number", flat=True)
|
||||
) if user.is_authenticated else [],
|
||||
"active_slot": active_slot,
|
||||
"gate_positions": _gate_positions(room),
|
||||
}
|
||||
if room.table_status == Room.SIG_SELECT:
|
||||
user_seat = room.table_seats.filter(gamer=user).first() if user.is_authenticated else None
|
||||
@@ -215,9 +228,16 @@ def create_room(request):
|
||||
def gatekeeper(request, room_id):
|
||||
room = Room.objects.get(id=room_id)
|
||||
if room.table_status:
|
||||
ctx = _role_select_context(room, request.user)
|
||||
else:
|
||||
ctx = _gate_context(room, request.user)
|
||||
return redirect("epic:room", room_id=room_id)
|
||||
ctx = _gate_context(room, request.user)
|
||||
ctx["room"] = room
|
||||
ctx["page_class"] = "page-gameboard"
|
||||
return render(request, "apps/gameboard/room.html", ctx)
|
||||
|
||||
|
||||
def room_view(request, room_id):
|
||||
room = Room.objects.get(id=room_id)
|
||||
ctx = _role_select_context(room, request.user)
|
||||
ctx["room"] = room
|
||||
ctx["page_class"] = "page-gameboard"
|
||||
return render(request, "apps/gameboard/room.html", ctx)
|
||||
@@ -390,17 +410,20 @@ def select_role(request, room_id):
|
||||
if request.method == "POST":
|
||||
room = Room.objects.get(id=room_id)
|
||||
if room.table_status != Room.ROLE_SELECT:
|
||||
return redirect("epic:gatekeeper", room_id=room_id)
|
||||
return redirect(
|
||||
"epic:room" if room.table_status else "epic:gatekeeper",
|
||||
room_id=room_id,
|
||||
)
|
||||
role = request.POST.get("role")
|
||||
valid_roles = [code for code, _ in TableSeat.ROLE_CHOICES]
|
||||
if not role or role not in valid_roles:
|
||||
return redirect("epic:gatekeeper", room_id=room_id)
|
||||
return redirect("epic:room", room_id=room_id)
|
||||
with transaction.atomic():
|
||||
active_seat = room.table_seats.select_for_update().filter(
|
||||
role__isnull=True
|
||||
).order_by("slot_number").first()
|
||||
if not active_seat or active_seat.gamer != request.user:
|
||||
return redirect("epic:gatekeeper", room_id=room_id)
|
||||
return redirect("epic:room", room_id=room_id)
|
||||
if room.table_seats.filter(role=role).exists():
|
||||
return HttpResponse(status=409)
|
||||
active_seat.role = role
|
||||
@@ -416,7 +439,7 @@ def select_role(request, room_id):
|
||||
record(room, GameEvent.ROLES_REVEALED)
|
||||
_notify_roles_revealed(room_id)
|
||||
return HttpResponse(status=200)
|
||||
return redirect("epic:gatekeeper", room_id=room_id)
|
||||
return redirect("epic:room", room_id=room_id)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -433,7 +456,7 @@ def pick_roles(request, room_id):
|
||||
slot_number=slot.slot_number,
|
||||
)
|
||||
_notify_role_select_start(room_id)
|
||||
return redirect("epic:gatekeeper", room_id=room_id)
|
||||
return redirect("epic:room", room_id=room_id)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -487,7 +510,10 @@ def select_sig(request, room_id):
|
||||
return redirect("epic:gatekeeper", room_id=room_id)
|
||||
room = Room.objects.get(id=room_id)
|
||||
if room.table_status != Room.SIG_SELECT:
|
||||
return redirect("epic:gatekeeper", room_id=room_id)
|
||||
return redirect(
|
||||
"epic:room" if room.table_status else "epic:gatekeeper",
|
||||
room_id=room_id,
|
||||
)
|
||||
active_seat = active_sig_seat(room)
|
||||
if active_seat is None or active_seat.gamer != request.user:
|
||||
return HttpResponse(status=403)
|
||||
|
||||
Reference in New Issue
Block a user