@@ -97,7 +97,7 @@ class Game(models.Model): | |||
def get_fear_to_next_card(self): | |||
total_fear = self.get_current_total_fear() | |||
leftover_fear = total_fear // self.fear_per_card | |||
leftover_fear = total_fear % self.fear_per_card | |||
return self.fear_per_card - leftover_fear | |||
def _fear_phase(self, previous): | |||
@@ -247,6 +247,8 @@ class Fear(models.Model): | |||
player = models.ForeignKey(Player, on_delete=models.CASCADE, db_index=True) | |||
effect = models.IntegerField(default=0) | |||
# "pure" = not from destroying a town or city | |||
pure_fear = models.IntegerField() | |||
towns_destroyed = models.IntegerField() | |||
cities_destroyed = models.IntegerField() | |||
pure_fear = models.IntegerField(default=0) | |||
towns_destroyed = models.IntegerField(default=0) | |||
cities_destroyed = models.IntegerField(default=0) | |||
unique_together = (("phase", "player", "effect")) |
@@ -21,47 +21,135 @@ footer a, footer a:visited { | |||
.player-1 { | |||
background: #f0ba57; | |||
} | |||
.player-1 .player-effects .effect:nth-child(even) { | |||
background: #f0e057; | |||
} | |||
.player-1 .player-effects .effect:nth-child(odd) { | |||
background: #d9f057; | |||
} | |||
.player-2 { | |||
background: #73fcf6; | |||
} | |||
.player-2 .player-effects .effect:nth-child(even) { | |||
background: #73e0fc; | |||
} | |||
.player-2 .player-effects .effect:nth-child(odd) { | |||
background: #73bdfc; | |||
} | |||
.player-3 { | |||
background: #bc9c8b; | |||
} | |||
.player-3 .player-effects .effect:nth-child(even) { | |||
background: #bca88b; | |||
} | |||
.player-3 .player-effects .effect:nth-child(odd) { | |||
background: #bcb58b; | |||
} | |||
.player-4 { | |||
background: #77a0a6; | |||
} | |||
.player-4 .player-effects .effect:nth-child(even) { | |||
background: #7794a6; | |||
} | |||
.player-4 .player-effects .effect:nth-child(odd) { | |||
background: #7788a6; | |||
} | |||
.player-5 { | |||
background: #fff172; | |||
} | |||
.player-5 .player-effects .effect:nth-child(even) { | |||
background: #eaff72; | |||
} | |||
.player-5 .player-effects .effect:nth-child(odd) { | |||
background: #c7ff72; | |||
} | |||
.player-6 { | |||
background: #3adb85; | |||
} | |||
.player-6 .player-effects .effect:nth-child(even) { | |||
background: #3adbad; | |||
} | |||
.player-6 .player-effects .effect:nth-child(odd) { | |||
background: #3adbd5; | |||
} | |||
.player-7 { | |||
background: #99c8e1; | |||
} | |||
.player-7 .player-effects .effect:nth-child(even) { | |||
background: #99b6e1; | |||
} | |||
.player-7 .player-effects .effect:nth-child(odd) { | |||
background: #99a4e1; | |||
} | |||
.player-8 { | |||
background: #f73347; | |||
} | |||
.player-8 .player-effects .effect:nth-child(even) { | |||
background: #f75033; | |||
} | |||
.player-8 .player-effects .effect:nth-child(odd) { | |||
background: #f78133; | |||
} | |||
.player-9 { | |||
background: #cac186; | |||
} | |||
.player-9 .player-effects .effect:nth-child(even) { | |||
background: #c2ca86; | |||
} | |||
.player-9 .player-effects .effect:nth-child(odd) { | |||
background: #b1ca86; | |||
} | |||
.player-10 { | |||
background: #8ca684; | |||
} | |||
.player-10 .player-effects .effect:nth-child(even) { | |||
background: #84a685; | |||
} | |||
.player-10 .player-effects .effect:nth-child(odd) { | |||
background: #84a68d; | |||
} | |||
.player-11 { | |||
background: #f068a9; | |||
} | |||
.player-11 .player-effects .effect:nth-child(even) { | |||
background: #f06887; | |||
} | |||
.player-11 .player-effects .effect:nth-child(odd) { | |||
background: #f06b68; | |||
} | |||
.player-12 { | |||
background: #ed822b; | |||
} | |||
.player-12 .player-effects .effect:nth-child(even) { | |||
background: #edb32b; | |||
} | |||
.player-12 .player-effects .effect:nth-child(odd) { | |||
background: #ede32b; | |||
} | |||
.players .player { | |||
margin-top: 1em; | |||
} | |||
.player-visible { | |||
display: none; | |||
} | |||
.player-body { | |||
display: none; | |||
} | |||
.player-visible:checked ~ .player-body { | |||
display: block; | |||
} |
@@ -5,36 +5,91 @@ | |||
<div class="phase">{{ phase }} phase of turn #{{ turn }}</div> | |||
<div class="fear_summary"> | |||
<span class="available_fear_cards">{{ available_fear_cards }}</span> | |||
fear cards available; | |||
😱🎴; | |||
<span class="fear_to_next_card">{{ fear_to_next_card }}</span> | |||
fear to next fear card; | |||
players have generated | |||
😱 to next 😱🎴; | |||
<span class="fear_this_phase">{{ fear_this_phase }}</span> | |||
fear this phase | |||
😱 this phase | |||
</div> | |||
<div class="access-code"> | |||
<span class="header">Access Code:</span> <a href="{% url 'qr_code' access_code=access_code %}" target="_blank">{{ access_code }}</a> | |||
<span class="header">🔐</span> <a href="{% url 'qr_code' access_code=access_code %}" target="_blank">{{ access_code }}</a> | |||
</div> | |||
</header> | |||
<div class="players"> | |||
{% for order, player in players.items %} | |||
<div class="player player-{{ player.order }}"> | |||
<div class="player-summary"> | |||
{% if player.ready %}(ready){% else %}(waiting...){% endif %} | |||
(<span class="player-total-fear player-{{ player.order }}-total-fear">{{ player.total_fear }}</span> fear) | |||
{{ player.name }} ({{ player.get_spirit_name }}) | |||
</div> | |||
<div class="player-effects"> | |||
{% for effect_num, effect in player.fear.items %} | |||
<div class="effect effect-{{ effect_num }}"> | |||
Effect #{{ effect_num|add:1 }} | |||
{{ effect.pure_fear }} fear | |||
{{ effect.towns }} towns | |||
{{ effect.cities }} cities | |||
<div class="errors"> | |||
{% for error in errors %} | |||
<p class="error"> | |||
{{ error }} | |||
</p> | |||
{% endfor %} | |||
</div> | |||
<form action="#" method="POST"> | |||
{% csrf_token %} | |||
<div class="players"> | |||
{% for order, player in players.items %} | |||
<div class="player player-{{ player.order }}"> | |||
<label for="player-{{ player.order }}-visible"> | |||
<div class="player-summary"> | |||
{% if player.ready %}🏁{% else %}⏳{% endif %} | |||
(<span class="player-total-fear player-{{ player.order }}-total-fear">{{ player.total_fear }}</span> 😱) | |||
{{ player.name }} ({{ player.get_spirit_name }}) | |||
</div> | |||
</label> | |||
<input type="checkbox" | |||
class="player-visible" | |||
name="player-{{ player.order }}-visible" | |||
id="player-{{ player.order }}-visible" | |||
{% if player.visible %} checked{% endif %}/> | |||
<div class="player-body"> | |||
<label> | |||
<input type="hidden" | |||
name="player-{{ player.order }}-ready-orig" | |||
id="player-{{ player.order }}-ready-orig" | |||
value="{{ player.ready|lower }}" /> | |||
<input type="hidden" | |||
name="player-{{ player.order }}-ready" | |||
value="false" /> | |||
<input type="checkbox" | |||
name="player-{{ player.order }}-ready" | |||
id="player-{{ player.order }}-ready" | |||
value="true" | |||
{% if player.ready %} checked{% endif %}/>Done with | |||
<span class="phase">{{ phase }} phase of turn #{{ turn }}</span> | |||
</label> | |||
<div class="player-effects"> | |||
{% for effect_num, effect in player.fear.items %} | |||
<div class="effect effect-{{ effect_num }}"> | |||
Effect #{{ effect_num|add:1 }} | |||
<input type="hidden" name="player-{{ player.order }}-effect-{{ effect_num }}-fear-orig" value="{{ effect.pure_fear }}"> | |||
<select name="player-{{ player.order }}-effect-{{ effect_num }}-fear"{% if player.ready %} disabled{% endif %}> | |||
{% for val in range %} | |||
<option value="{{ val }}"{% if val == effect.pure_fear %} selected="selected"{% endif %}>{{ val }} 😱</option> | |||
{% endfor %} | |||
</select> | |||
<input type="hidden" name="player-{{ player.order }}-effect-{{ effect_num }}-towns-orig" value="{{ effect.towns }}"> | |||
<select name="player-{{ player.order }}-effect-{{ effect_num }}-towns"{% if player.ready %} disabled{% endif %}> | |||
{% for val in range %} | |||
<option value="{{ val }}"{% if val == effect.towns %} selected="selected"{% endif %}>{{ val }} 🏠</option> | |||
{% endfor %} | |||
</select> | |||
<input type="hidden" name="player-{{ player.order }}-effect-{{ effect_num }}-cities-orig" value="{{ effect.cities }}"> | |||
<select name="player-{{ player.order }}-effect-{{ effect_num }}-cities"{% if player.ready %} disabled{% endif %}> | |||
{% for val in range %} | |||
<option value="{{ val }}"{% if val == effect.cities %} selected="selected"{% endif %}>{{ val }} 🏙️</option> | |||
{% endfor %} | |||
</select> | |||
</div> | |||
{% endfor %} | |||
</div> | |||
</div> | |||
{% endfor %} | |||
</div> | |||
{% endfor %} | |||
</div> | |||
{% endfor %} | |||
</div> | |||
<input type="submit" name="update" value="Update fear"> | |||
{% if all_ready %} | |||
<input type="submit" name="advance" value="Advance to next phase"> | |||
{% endif %} | |||
</form> | |||
{% endblock %} |
@@ -8,7 +8,7 @@ from django.views.decorators.http import require_safe, require_http_methods | |||
from django.urls import reverse | |||
from .forms import NewGameForm, JoinGameForm, PlayerFormSet | |||
from .models import Game, Player | |||
from .models import Game, Player, Fear | |||
def lookup_access_code(func): | |||
@@ -89,15 +89,85 @@ def qr_code(request, access_code): | |||
@lookup_access_code | |||
@require_safe | |||
@require_http_methods(["HEAD", "GET", "POST"]) | |||
def game(request, game): | |||
players = OrderedDict() | |||
for player in game.player_set.order_by('order').all(): | |||
player.total_fear = 0 | |||
player.changed_to_ready = False | |||
player.fear = OrderedDict() | |||
players[player.order] = player | |||
for fear in game.get_current_phase().fear_set.order_by('effect').all(): | |||
player[fear.player.order].fear[fear.effect] = { | |||
errors = [] | |||
fear_kind_dict = { | |||
'fear': 'pure_fear', | |||
'towns': 'towns_destroyed', | |||
'cities': 'cities_destroyed', | |||
} | |||
current_phase = game.get_current_phase() | |||
if request.method == 'POST': | |||
for key, value in request.POST.items(): | |||
sections = key.split('-') | |||
if key == 'csrfmiddlewaretoken' or sections[-1] == 'orig': | |||
pass | |||
elif sections[0] == 'player': | |||
orig_value = request.POST.get(key + '-orig', None) | |||
player_ord = int(sections[1]) | |||
player = players[player_ord] | |||
if sections[2] == 'visible': | |||
player.visible = True | |||
elif sections[2] == 'ready': | |||
if value != orig_value: | |||
if value == 'true': | |||
player.ready = True | |||
player.changed_to_ready = True | |||
else: | |||
player.ready = False | |||
player.save() | |||
elif sections[2] == 'effect': | |||
effect_num = int(sections[3]) | |||
effect_kind = sections[4] | |||
if orig_value != value: | |||
amount = int(value) | |||
orig = int(orig_value) | |||
if amount != orig: | |||
if player.ready and not player.changed_to_ready: | |||
errors.append( | |||
f"{player.name} " + | |||
f"({player.get_spirit_name()}) was " + | |||
"marked as ready by another user, so " + | |||
"your attempt to change their effect " + | |||
f"#{effect_num} {effect_kind} " + | |||
f"from {orig} to {amount} " + | |||
"was not processed.") | |||
else: | |||
fear_row, created = Fear.objects.get_or_create( | |||
phase=current_phase, | |||
player=player, | |||
effect=effect_num) | |||
prop_name = fear_kind_dict[effect_kind] | |||
current_value = getattr(fear_row, prop_name) | |||
if current_value == orig: | |||
setattr(fear_row, prop_name, amount) | |||
fear_row.save() | |||
else: | |||
errors.append( | |||
f"{player.name} " + | |||
f"({player.get_spirit_name()})'s " + | |||
"effect " + | |||
f"#{effect_num} {effect_kind} " + | |||
"was changed by another user to " + | |||
f"{current_value}, so " + | |||
f"your attempt to change it from " + | |||
f"{orig} to {amount} " + | |||
"was not processed.") | |||
else: | |||
raise Exception(key + ": " + value) | |||
else: | |||
raise Exception(key + ": " + value) | |||
for fear in current_phase.fear_set.order_by('effect').all(): | |||
players[fear.player.order].fear[fear.effect] = { | |||
'pure_fear': fear.pure_fear, | |||
'towns': fear.towns_destroyed, | |||
'cities': fear.cities_destroyed, | |||
@@ -121,8 +191,12 @@ def game(request, game): | |||
'phase': game.get_current_phase_name(), | |||
'available_fear_cards': game.num_available_fear_cards(), | |||
'fear_to_next_card': game.get_fear_to_next_card(), | |||
'fear_this_phase': game.get_current_phase().fear_this_phase(), | |||
'fear_this_phase': current_phase.fear_this_phase(), | |||
'total_fear': game.get_current_total_fear(), | |||
'players': players, | |||
'all_ready': all(player.ready for player in players.values()), | |||
'range': range(21), | |||
'errors': errors, | |||
}) | |||