feat: add option to disable pixel placement
This commit is contained in:
@@ -7,8 +7,8 @@ from .models import PixelPlacement, PlaceSettings
|
||||
|
||||
@admin.register(PlaceSettings)
|
||||
class PlaceSettingsAdmin(DjangoObjectActions, admin.ModelAdmin):
|
||||
list_display = ("event", "width", "height")
|
||||
list_editable = ("width", "height")
|
||||
list_display = ("event", "width", "height", "placement_enabled")
|
||||
list_editable = ("width", "height", "placement_enabled")
|
||||
list_filter = ("event",)
|
||||
change_actions = ("resync_canvas",)
|
||||
|
||||
|
||||
@@ -45,6 +45,10 @@ class PlaceConsumer(AsyncWebsocketConsumer):
|
||||
await self.send(text_data=encode("r", "a"))
|
||||
return
|
||||
|
||||
if not place_settings.placement_enabled:
|
||||
await self.send(text_data=encode("r", "d"))
|
||||
return
|
||||
|
||||
parts = text_data.split("|")
|
||||
if len(parts) != 4:
|
||||
await self.send(text_data=encode("r", "i"))
|
||||
@@ -142,6 +146,7 @@ class PlaceConsumer(AsyncWebsocketConsumer):
|
||||
next_placement_at,
|
||||
place_settings.width,
|
||||
place_settings.height,
|
||||
int(place_settings.placement_enabled),
|
||||
)
|
||||
|
||||
@database_sync_to_async
|
||||
@@ -152,4 +157,11 @@ class PlaceConsumer(AsyncWebsocketConsumer):
|
||||
await self.send(text_data=encode("p", event["x"], event["y"], event["c"]))
|
||||
|
||||
async def settings_changed(self, event):
|
||||
await self.send(text_data=encode("e", event["width"], event["height"]))
|
||||
await self.send(
|
||||
text_data=encode(
|
||||
"e",
|
||||
event["width"],
|
||||
event["height"],
|
||||
int(event["placement_enabled"]),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.5 on 2026-10-25 09:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("place", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="placesettings",
|
||||
name="placement_enabled",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
@@ -47,6 +47,9 @@ class PlaceSettings(models.Model):
|
||||
)
|
||||
width = models.PositiveSmallIntegerField(default=DEFAULT_GRID_WIDTH)
|
||||
height = models.PositiveSmallIntegerField(default=DEFAULT_GRID_HEIGHT)
|
||||
# Freezes the board without taking the game down: clients can still
|
||||
# connect and watch, but every placement is rejected.
|
||||
placement_enabled = models.BooleanField(default=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"PlaceSettings({self.event}, size={self.width}x{self.height})"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
"""Reacts to an admin editing a PlaceSettings row (grid size) by:
|
||||
"""Reacts to an admin editing a PlaceSettings row (grid size, placement
|
||||
toggle) by:
|
||||
|
||||
1. Rebuilding that event's Redis canvas from history at the (possibly new)
|
||||
dimensions - a resize invalidates the old Redis bytes (wrong length,
|
||||
wrong meaning), so this keeps Redis self-healing instead of relying on
|
||||
the admin remembering to click "Resync canvas from DB" after every
|
||||
resize.
|
||||
2. Pushing the new size to that event's already-connected clients. Unlike
|
||||
2. Pushing the new settings to that event's already-connected clients. Unlike
|
||||
the clock app, place has no periodic heartbeat broadcast to piggyback
|
||||
this onto, so it's broadcast explicitly here instead.
|
||||
"""
|
||||
@@ -33,5 +34,6 @@ def on_settings_saved(sender, instance, **kwargs):
|
||||
"type": "settings_changed",
|
||||
"width": instance.width,
|
||||
"height": instance.height,
|
||||
"placement_enabled": instance.placement_enabled,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -6,8 +6,10 @@ either). Each message is a short string: a one-letter type tag followed by
|
||||
'|'-separated fields, in a fixed order the client already knows from this
|
||||
table:
|
||||
|
||||
s|<next>|<width>|<height> initial personal state, once per connection
|
||||
(next = epoch ms cooldown ends, or empty if free now)
|
||||
s|<next>|<width>|<height>|<on>
|
||||
initial personal state, once per connection
|
||||
(next = epoch ms cooldown ends, or empty if free now;
|
||||
on = 1 if placing pixels is allowed, 0 if frozen)
|
||||
p|<x>|<y>|<c> a pixel was placed - broadcast to everyone,
|
||||
including the placer (no separate ack needed)
|
||||
k|<next> your own placement was accepted; carries your
|
||||
@@ -15,10 +17,13 @@ table:
|
||||
r|c|<next> rejected: still on cooldown
|
||||
r|a rejected: not logged in
|
||||
r|i rejected: invalid x/y/color
|
||||
e|<width>|<height> an admin resized the grid for this event -
|
||||
broadcast to everyone. A client that sees this
|
||||
should re-fetch /api/.../place/canvas rather than
|
||||
try to remap its local pixels.
|
||||
r|d rejected: placement is disabled by an admin
|
||||
e|<width>|<height>|<on>
|
||||
an admin edited this event's settings (grid size
|
||||
and/or placement toggle) - broadcast to everyone.
|
||||
If the size changed, a client should re-fetch
|
||||
/api/.../place/canvas rather than try to remap its
|
||||
local pixels.
|
||||
|
||||
Client -> server has exactly one message shape, the same as the broadcast:
|
||||
p|<x>|<y>|<c>
|
||||
|
||||
Reference in New Issue
Block a user