Compare commits
4 Commits
b92159d4de
...
28ab47e5e4
Author | SHA1 | Date | |
---|---|---|---|
28ab47e5e4 | |||
11f661fd8a | |||
b5c689915f | |||
f8f963ce51 |
|
@ -6,6 +6,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
<link rel="stylesheet" href="{% static 'css/styles.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/styles.css' %}" type="text/css">
|
||||||
<title>Spirit Island Fear Tracker</title>
|
<title>Spirit Island Fear Tracker</title>
|
||||||
|
{% block header_script %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<header>
|
<header>
|
||||||
<div class="phase">{{ phase }} phase of turn #{{ turn }}</div>
|
<div class="phase"><span class="status-phase">{{ status.phase }}</span> phase of turn #<span class="status-turn">{{ status.turn }}</span></div>
|
||||||
<div class="fear_summary">
|
<div class="fear_summary">
|
||||||
<span class="available_fear_cards">{{ available_fear_cards }}</span>
|
<span class="available_fear_cards">{{ status.available_fear_cards }}</span>
|
||||||
😱🎴;
|
😱🎴;
|
||||||
<span class="fear_to_next_card">{{ fear_to_next_card }}</span>
|
<span class="fear_to_next_card">{{ status.fear_to_next_card }}</span>
|
||||||
😱 to next 😱🎴;
|
😱 to next 😱🎴;
|
||||||
<span class="fear_this_phase">{{ fear_this_phase }}</span>
|
<span class="fear_this_phase">{{ status.fear_this_phase }}</span>
|
||||||
😱 this phase
|
😱 this phase
|
||||||
</div>
|
</div>
|
||||||
<div class="access-code">
|
<div class="access-code">
|
||||||
|
@ -24,15 +24,15 @@
|
||||||
</div>
|
</div>
|
||||||
<form action="#" method="POST">
|
<form action="#" method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="game_turn" value="{{ turn }}">
|
<input type="hidden" name="game_turn" value="{{ status.turn }}">
|
||||||
<input type="hidden" name="game_phase" value="{{ phase_id }}">
|
<input type="hidden" name="game_phase" value="{{ status.phase_id }}">
|
||||||
<input type="hidden" name="phase_name" value="{{ phase }} phase of turn #{{ turn }}">
|
<input type="hidden" name="phase_name" value="{{ status.phase }} phase of turn #{{ status.turn }}">
|
||||||
<div class="players">
|
<div class="players">
|
||||||
{% for order, player in players.items %}
|
{% for order, player in players.items %}
|
||||||
<div class="player player-{{ player.order }}">
|
<div class="player player-{{ player.order }}">
|
||||||
<label for="player-{{ player.order }}-visible">
|
<label for="player-{{ player.order }}-visible">
|
||||||
<div class="player-summary">
|
<div class="player-summary">
|
||||||
{% if player.ready %}🏁{% else %}⏳{% endif %}
|
<span class="player-{{ player.order }}-ready">{% if player.ready %}🏁{% else %}⏳{% endif %}</span>
|
||||||
(<span class="player-total-fear player-{{ player.order }}-total-fear">{{ player.total_fear }}</span> 😱)
|
(<span class="player-total-fear player-{{ player.order }}-total-fear">{{ player.total_fear }}</span> 😱)
|
||||||
{{ player.name }} ({{ player.get_spirit_name }})
|
{{ player.name }} ({{ player.get_spirit_name }})
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,9 +56,9 @@
|
||||||
id="player-{{ player.order }}-ready"
|
id="player-{{ player.order }}-ready"
|
||||||
value="true"
|
value="true"
|
||||||
{% if player.ready %} checked{% endif %}/>Done with
|
{% if player.ready %} checked{% endif %}/>Done with
|
||||||
<span class="phase">{{ phase }} phase of turn #{{ turn }}</span>
|
<span class="phase"><span class="status-phase">{{ status.phase }}</span> phase of turn #<span class="status-turn">{{ status.turn }}</span></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="player-effects">
|
<div class="player-effects" id="player-{{ player.order }}-effects">
|
||||||
{% for effect_num, effect in player.fear.items %}
|
{% for effect_num, effect in player.fear.items %}
|
||||||
<div class="effect effect-{{ effect_num }}">
|
<div class="effect effect-{{ effect_num }}">
|
||||||
Effect #{{ effect_num|add:1 }}
|
Effect #{{ effect_num|add:1 }}
|
||||||
|
@ -90,10 +90,300 @@
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<input type="submit" name="update" value="Update fear">
|
<input type="submit" name="update" class="update-button" value="Update fear">
|
||||||
{% if all_ready %}
|
<input type="submit" name="advance" class="advance-button" value="Advance to next phase"{% if not status.all_ready %} disabled{% endif %}>
|
||||||
<input type="submit" name="advance" value="Advance to next phase">
|
<input type="submit" name="revert" class="revert-button" value="Revert to previous phase">
|
||||||
{% endif %}
|
|
||||||
<input type="submit" name="revert" value="Revert to previous phase">
|
|
||||||
</form>
|
</form>
|
||||||
|
{% block game_refresh %}
|
||||||
|
{% if not results_only %}
|
||||||
|
<div class="button-container">
|
||||||
|
<a href="{% url 'game' access_code=access_code %}" class="button" id="button-refresh">Refresh</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block header_script %}
|
||||||
|
{% if not results_only %}
|
||||||
|
<template id="effect-template">
|
||||||
|
<div class="effect">
|
||||||
|
Effect #<span class="effect-num">?</span>
|
||||||
|
|
||||||
|
<input type="hidden" class="fear-orig" value="0">
|
||||||
|
<select class="fear">
|
||||||
|
{% for val in range %}
|
||||||
|
<option value="{{ val }}"{% if val == 0 %} selected="selected"{% endif %}>{{ val }} 😱</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input type="hidden" class="towns-orig" value="0">
|
||||||
|
<select class="towns">
|
||||||
|
{% for val in range %}
|
||||||
|
<option value="{{ val }}"{% if val == 0 %} selected="selected"{% endif %}>{{ val }} 🏠</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input type="hidden" class="cities-orig" value="0">
|
||||||
|
<select class="cities">
|
||||||
|
{% for val in range %}
|
||||||
|
<option value="{{ val }}"{% if val == 0 %} selected="selected"{% endif %}>{{ val }} 🏙️</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
var statusObj = JSON.parse("{{ status_json|escapejs }}");
|
||||||
|
function ensureEffectRowExists(playerOrd, effectNum) {
|
||||||
|
if(document.querySelector(".player-" + playerOrd + " .effect-" + effectNum)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(effectNum > 0) {
|
||||||
|
ensureEffectRowExists(playerOrd, effectNum-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let effDiv = document.getElementById("player-" + playerOrd + "-effects");
|
||||||
|
let effectTemplate = document.querySelector("#effect-template");
|
||||||
|
let clone = effectTemplate.content.cloneNode(true);
|
||||||
|
clone.querySelector("div.effect").classList.add("effect-" + effectNum);
|
||||||
|
clone.querySelector(".effect-num").innerText = effectNum + 1;
|
||||||
|
let prefix = "player-" + playerOrd + "-effect-" + effectNum + "-";
|
||||||
|
["fear", "towns", "cities"].forEach(suffix => {
|
||||||
|
clone.querySelector("." + suffix).name = prefix + suffix;
|
||||||
|
clone.querySelector("." + suffix + "-orig").name = prefix + suffix + "-orig";
|
||||||
|
});
|
||||||
|
for(let el of clone.querySelectorAll('select')) {
|
||||||
|
el.addEventListener("change", formElementChanged);
|
||||||
|
}
|
||||||
|
effDiv.appendChild(clone);
|
||||||
|
}
|
||||||
|
function addEmptyEffects(clearAll) {
|
||||||
|
for(let effDiv of document.getElementsByClassName("player-effects")) {
|
||||||
|
if(clearAll) {
|
||||||
|
while(effDiv.lastElementChild) {
|
||||||
|
effDiv.removeChild(effDiv.lastElementChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var newEffectNum = null;
|
||||||
|
if(effDiv.childElementCount == 0) {
|
||||||
|
newEffectNum = 0;
|
||||||
|
} else {
|
||||||
|
let lastEffectDiv = effDiv.children[effDiv.children.length-1];
|
||||||
|
var lastEffectNum = null;
|
||||||
|
lastEffectDiv.classList.forEach(cls => {
|
||||||
|
if(cls.startsWith("effect-")) {
|
||||||
|
lastEffectNum = parseInt(cls.substring(7));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
lastEffectDiv.querySelectorAll("select").forEach(child => {
|
||||||
|
if(child.value != "0") {
|
||||||
|
newEffectNum = lastEffectNum + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(newEffectNum != null) {
|
||||||
|
let playerOrd = effDiv.id.split("-")[1];
|
||||||
|
ensureEffectRowExists(playerOrd, newEffectNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleNewStatus(oldStatus, newStatus) {
|
||||||
|
{% block game_handle_new_status %}
|
||||||
|
if(oldStatus.hash == newStatus.hash) return true;
|
||||||
|
if(oldStatus.phase_id != newStatus.phase_id
|
||||||
|
|| oldStatus.turn != newStatus.turn) {
|
||||||
|
addEmptyEffects(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let form = document.forms[0];
|
||||||
|
for(let key in newStatus) {
|
||||||
|
if(key == "hash" || key == "phase_id" || key == "total_fear") {
|
||||||
|
// ignored
|
||||||
|
} else if(key == "all_ready") {
|
||||||
|
let all_ready = newStatus[key]
|
||||||
|
for(let el of document.getElementsByClassName("advance-button")) {
|
||||||
|
el.disabled = !all_ready;
|
||||||
|
}
|
||||||
|
} else if(key == "players") {
|
||||||
|
let players = newStatus[key];
|
||||||
|
for(let ord in players) {
|
||||||
|
let prefix = "player-" + ord + "-";
|
||||||
|
let player = players[ord];
|
||||||
|
for(let pkey in player) {
|
||||||
|
if(pkey == "ready") {
|
||||||
|
let ready = player[pkey];
|
||||||
|
for(let el of document.getElementsByClassName(prefix + "ready")) {
|
||||||
|
el.innerText = ready ? "🏁" : "⏳";
|
||||||
|
}
|
||||||
|
document.getElementById(prefix + "ready-orig").value = ready;
|
||||||
|
document.getElementById(prefix + "ready").checked = ready;
|
||||||
|
|
||||||
|
for(let el of document.querySelectorAll(".player-" + ord + " select")) {
|
||||||
|
el.disabled = ready;
|
||||||
|
}
|
||||||
|
} else if(pkey == "total_fear") {
|
||||||
|
let total_fear = player[pkey];
|
||||||
|
for(let el of document.getElementsByClassName(prefix + "total-fear")) {
|
||||||
|
el.innerText = total_fear;
|
||||||
|
}
|
||||||
|
} else if(pkey == "fear") {
|
||||||
|
let fear = player[pkey];
|
||||||
|
for(let effect_num in fear) {
|
||||||
|
ensureEffectRowExists(ord, effect_num);
|
||||||
|
let fprefix = prefix + "effect-" + effect_num + "-";
|
||||||
|
let effect = fear[effect_num];
|
||||||
|
for(let ekey in effect) {
|
||||||
|
let value = effect[ekey];
|
||||||
|
let eclass = ekey == "pure_fear" ? "fear" : ekey;
|
||||||
|
let eprefix = fprefix + eclass;
|
||||||
|
form.elements[eprefix].value = value;
|
||||||
|
form.elements[eprefix + "-orig"].value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert("Unknown player status key: " + pkey + "=" + player[pkey]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addEmptyEffects(false);
|
||||||
|
} else {
|
||||||
|
var matching_elements = document.getElementsByClassName("status-"
|
||||||
|
+ key);
|
||||||
|
if(matching_elements.length == 0) {
|
||||||
|
matching_elements = document.getElementsByClassName(key);
|
||||||
|
}
|
||||||
|
if(matching_elements.length == 0) {
|
||||||
|
alert("Unknown status key: " + key + "=" + newStatus[key]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = newStatus[key];
|
||||||
|
for(let el of matching_elements) {
|
||||||
|
el.innerText = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
{% endblock %}
|
||||||
|
}
|
||||||
|
var activeRequests = new Set();
|
||||||
|
function formElementChanged(e) {
|
||||||
|
let form = document.forms[0];
|
||||||
|
let el = e.target;
|
||||||
|
let name = el.name;
|
||||||
|
let origName = name + "-orig";
|
||||||
|
let origEl = form.elements[origName];
|
||||||
|
|
||||||
|
let newValue = el.type == "checkbox" ? el.checked : el.value;
|
||||||
|
let oldValue = origEl.value;
|
||||||
|
|
||||||
|
el.disabled = true;
|
||||||
|
if(el.name.endsWith("ready") && newValue) {
|
||||||
|
let ord = name.split("-")[1];
|
||||||
|
// If marking a player ready, disable all selects immediately.
|
||||||
|
for(let el of document.querySelectorAll(".player-" + ord + " select")) {
|
||||||
|
el.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activeRequests.add(el);
|
||||||
|
statusObj.hash = '';
|
||||||
|
|
||||||
|
// From https://stackoverflow.com/a/5588435
|
||||||
|
function getCookie(name) {
|
||||||
|
var cookieValue = null;
|
||||||
|
if (document.cookie && document.cookie != '') {
|
||||||
|
var cookies = document.cookie.split(';');
|
||||||
|
for (var i = 0; i < cookies.length; i++) {
|
||||||
|
var cookie = cookies[i];
|
||||||
|
// Does this cookie string begin with the name we want?
|
||||||
|
if (cookie.substring(0, name.length + 1) == (name + '=')) {
|
||||||
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cookieValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch("{% url 'update_game' access_code=access_code %}",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
"X-CSRFToken": getCookie("csrftoken"),
|
||||||
|
},
|
||||||
|
body: name + "=" + newValue + "&" + origName + "=" + oldValue
|
||||||
|
+ "&game_turn=" + statusObj["turn"]
|
||||||
|
+ "&game_phase=" + statusObj["phase_id"]
|
||||||
|
+ "&phase_name=" + statusObj["phase"]
|
||||||
|
+ "&csrfmiddlewaretoken=" + form.elements["csrfmiddlewaretoken"].value,
|
||||||
|
}).then(response => response.json()
|
||||||
|
).then(data => {
|
||||||
|
if("success" in data && data["success"]) {
|
||||||
|
form.elements[origName].value = newValue;
|
||||||
|
} else {
|
||||||
|
var updatedValue = oldValue;
|
||||||
|
if("value" in data) {
|
||||||
|
let updatedValue = data['value'];
|
||||||
|
origEl.value = updatedValue;
|
||||||
|
}
|
||||||
|
if(el.type == "checkbox") {
|
||||||
|
el.checked = updatedValue == "true";
|
||||||
|
} else {
|
||||||
|
el.value = updatedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if("errors" in data) {
|
||||||
|
alert(data["errors"]);
|
||||||
|
} else {
|
||||||
|
alert("Unknown error: " + data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
el.disabled = false;
|
||||||
|
activeRequests.delete(el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.addEventListener("DOMContentLoaded", e => {
|
||||||
|
for(let updateButton of document.getElementsByClassName("update-button")) {
|
||||||
|
updateButton.disabled = true;
|
||||||
|
updateButton.style.display = "none";
|
||||||
|
}
|
||||||
|
let form = document.forms[0];
|
||||||
|
for(let el of form.elements) {
|
||||||
|
if(el.type == 'checkbox' && !el.name.endsWith("visible")) {
|
||||||
|
el.addEventListener("change", formElementChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let el of document.querySelectorAll('select')) {
|
||||||
|
el.addEventListener("change", formElementChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(function() {
|
||||||
|
if(activeRequests.size != 0) return;
|
||||||
|
fetch(new Request("{% url 'status' access_code=access_code %}"
|
||||||
|
+ (statusObj.hash != ""
|
||||||
|
? statusObj.hash + "/"
|
||||||
|
: "")))
|
||||||
|
.then(response => {
|
||||||
|
if(response.status === 304) {
|
||||||
|
// TODO Just skip the next step?
|
||||||
|
return statusObj;
|
||||||
|
} else {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if(activeRequests.size != 0) return;
|
||||||
|
if(!handleNewStatus(statusObj, data)) {
|
||||||
|
document.getElementById("button-refresh").click();
|
||||||
|
} else {
|
||||||
|
statusObj = data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -24,7 +24,10 @@ urlpatterns = [
|
||||||
path('new/', views.new_game, name='new_game'),
|
path('new/', views.new_game, name='new_game'),
|
||||||
url(r'^(?P<access_code>[a-zA-Z]{6})/', include([
|
url(r'^(?P<access_code>[a-zA-Z]{6})/', include([
|
||||||
path('', views.game, name='game'),
|
path('', views.game, name='game'),
|
||||||
|
path('update/', views.update_game, name='update_game'),
|
||||||
path('qr/', views.qr_code, name='qr_code'),
|
path('qr/', views.qr_code, name='qr_code'),
|
||||||
path('status/', views.status, name='status'),
|
path('status/', views.status, name='status'),
|
||||||
|
url('^status/(?P<hashcode>[a-z0-9]{64})/',
|
||||||
|
views.status, name='status'),
|
||||||
])),
|
])),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from http import HTTPStatus
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
import qrcode
|
import qrcode
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.views.decorators.http import require_safe, require_http_methods
|
from django.views.decorators.http import require_safe, require_http_methods,\
|
||||||
|
require_POST
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from .forms import NewGameForm, JoinGameForm, PlayerFormSet
|
from .forms import NewGameForm, JoinGameForm, PlayerFormSet
|
||||||
|
@ -89,16 +93,87 @@ def qr_code(request, access_code):
|
||||||
return HttpResponse(output.getvalue(), content_type='image/png')
|
return HttpResponse(output.getvalue(), content_type='image/png')
|
||||||
|
|
||||||
|
|
||||||
|
def load_players(game):
|
||||||
|
players = OrderedDict()
|
||||||
|
for player in game.player_set.order_by('order').all():
|
||||||
|
player.total_fear = 0
|
||||||
|
player.fear = OrderedDict()
|
||||||
|
players[player.order] = player
|
||||||
|
|
||||||
|
return players
|
||||||
|
|
||||||
|
|
||||||
|
def get_players_with_fear(game, current_phase=None, players=None):
|
||||||
|
if players is None:
|
||||||
|
players = load_players(game)
|
||||||
|
if current_phase is None:
|
||||||
|
current_phase = game.get_current_phase()
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
players[fear.player.order].total_fear +=\
|
||||||
|
fear.pure_fear + fear.towns_destroyed + 2*fear.cities_destroyed
|
||||||
|
|
||||||
|
return players
|
||||||
|
|
||||||
|
|
||||||
|
def game_status_object(game, current_phase=None, players_with_fear=None):
|
||||||
|
if current_phase is None:
|
||||||
|
current_phase = game.get_current_phase()
|
||||||
|
if players_with_fear is None:
|
||||||
|
players_with_fear = get_players_with_fear(game, current_phase)
|
||||||
|
|
||||||
|
players = OrderedDict()
|
||||||
|
for order, player in players_with_fear.items():
|
||||||
|
players[order] = {
|
||||||
|
'ready': player.ready,
|
||||||
|
'fear': player.fear,
|
||||||
|
'total_fear': player.total_fear
|
||||||
|
}
|
||||||
|
|
||||||
|
status_obj = {
|
||||||
|
'turn': game.game_turn,
|
||||||
|
'phase': game.get_current_phase_name(),
|
||||||
|
'phase_id': game.game_phase,
|
||||||
|
'available_fear_cards': game.num_available_fear_cards(),
|
||||||
|
'fear_to_next_card': game.get_fear_to_next_card(),
|
||||||
|
'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()),
|
||||||
|
}
|
||||||
|
h = hashlib.sha256()
|
||||||
|
h.update(json.dumps(status_obj).encode('utf-8'))
|
||||||
|
status_hash = h.hexdigest()
|
||||||
|
status_obj['hash'] = status_hash
|
||||||
|
|
||||||
|
return status_obj
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
@lookup_access_code
|
||||||
|
@require_POST
|
||||||
|
def update_game(request, game):
|
||||||
|
return handle_game_request(request, game, update=True)
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@lookup_access_code
|
@lookup_access_code
|
||||||
@require_http_methods(["HEAD", "GET", "POST"])
|
@require_http_methods(["HEAD", "GET", "POST"])
|
||||||
def game(request, game):
|
def game(request, game):
|
||||||
players = OrderedDict()
|
return handle_game_request(request, game, update=False)
|
||||||
for player in game.player_set.order_by('order').all():
|
|
||||||
player.total_fear = 0
|
|
||||||
|
def handle_game_request(request, game, update):
|
||||||
|
# TODO Should check if game is over and redirect to summary table view.
|
||||||
|
|
||||||
|
players = load_players(game)
|
||||||
|
for player in players.values():
|
||||||
player.changed_to_ready = False
|
player.changed_to_ready = False
|
||||||
player.fear = OrderedDict()
|
|
||||||
players[player.order] = player
|
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
fear_kind_dict = {
|
fear_kind_dict = {
|
||||||
|
@ -106,14 +181,17 @@ def game(request, game):
|
||||||
'towns': 'towns_destroyed',
|
'towns': 'towns_destroyed',
|
||||||
'cities': 'cities_destroyed',
|
'cities': 'cities_destroyed',
|
||||||
}
|
}
|
||||||
|
current_value = None
|
||||||
current_phase = game.get_current_phase()
|
current_phase = game.get_current_phase()
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
post_data = request.POST
|
||||||
|
|
||||||
correct_phase =\
|
correct_phase =\
|
||||||
current_phase.game_turn == int(request.POST['game_turn'])\
|
current_phase.game_turn == int(post_data['game_turn'])\
|
||||||
and current_phase.game_phase == int(request.POST['game_phase'])
|
and current_phase.game_phase == int(post_data['game_phase'])
|
||||||
advance_phase = 'advance' in request.POST
|
advance_phase = 'advance' in post_data
|
||||||
revert_phase = 'revert' in request.POST
|
revert_phase = 'revert' in post_data
|
||||||
for key, value in request.POST.items():
|
for key, value in post_data.items():
|
||||||
sections = key.split('-')
|
sections = key.split('-')
|
||||||
if key in ['csrfmiddlewaretoken',
|
if key in ['csrfmiddlewaretoken',
|
||||||
'update', 'advance', 'revert',
|
'update', 'advance', 'revert',
|
||||||
|
@ -121,7 +199,7 @@ def game(request, game):
|
||||||
or sections[-1] == 'orig':
|
or sections[-1] == 'orig':
|
||||||
pass
|
pass
|
||||||
elif sections[0] == 'player':
|
elif sections[0] == 'player':
|
||||||
orig_value = request.POST.get(key + '-orig', None)
|
orig_value = post_data.get(key + '-orig', None)
|
||||||
player_ord = int(sections[1])
|
player_ord = int(sections[1])
|
||||||
player = players[player_ord]
|
player = players[player_ord]
|
||||||
if sections[2] == 'visible':
|
if sections[2] == 'visible':
|
||||||
|
@ -151,7 +229,7 @@ def game(request, game):
|
||||||
f"from {orig} to {amount} " +
|
f"from {orig} to {amount} " +
|
||||||
"was not processed.")
|
"was not processed.")
|
||||||
elif not correct_phase or advance_phase:
|
elif not correct_phase or advance_phase:
|
||||||
old_phase = request.POST['phase_name']
|
old_phase = post_data['phase_name']
|
||||||
errors.append(
|
errors.append(
|
||||||
f"{player.name} " +
|
f"{player.name} " +
|
||||||
f"({player.get_spirit_name()})'s " +
|
f"({player.get_spirit_name()})'s " +
|
||||||
|
@ -198,14 +276,22 @@ def game(request, game):
|
||||||
for player in players.values():
|
for player in players.values():
|
||||||
player.ready = True
|
player.ready = True
|
||||||
|
|
||||||
for fear in current_phase.fear_set.order_by('effect').all():
|
if update:
|
||||||
players[fear.player.order].fear[fear.effect] = {
|
if errors:
|
||||||
'pure_fear': fear.pure_fear,
|
res = {
|
||||||
'towns': fear.towns_destroyed,
|
'success': False,
|
||||||
'cities': fear.cities_destroyed,
|
'errors': '\n'.join(errors)
|
||||||
}
|
}
|
||||||
players[fear.player.order].total_fear +=\
|
if current_value is not None:
|
||||||
fear.pure_fear + fear.towns_destroyed + 2*fear.cities_destroyed
|
res['value'] = current_value
|
||||||
|
else:
|
||||||
|
res = {'success': True}
|
||||||
|
return HttpResponse(json.dumps(res))
|
||||||
|
|
||||||
|
players = get_players_with_fear(game, current_phase, players)
|
||||||
|
status_obj = game_status_object(game, current_phase, players)
|
||||||
|
status_string = json.dumps(status_obj)
|
||||||
|
|
||||||
for player in players.values():
|
for player in players.values():
|
||||||
info = player.fear
|
info = player.fear
|
||||||
if not info:
|
if not info:
|
||||||
|
@ -217,17 +303,12 @@ def game(request, game):
|
||||||
'towns': 0,
|
'towns': 0,
|
||||||
'cities': 0,
|
'cities': 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'game.html', {
|
return render(request, 'game.html', {
|
||||||
'access_code': game.access_code,
|
'access_code': game.access_code,
|
||||||
'turn': game.game_turn,
|
'status': status_obj,
|
||||||
'phase': game.get_current_phase_name(),
|
'status_json': status_string,
|
||||||
'phase_id': game.game_phase,
|
|
||||||
'available_fear_cards': game.num_available_fear_cards(),
|
|
||||||
'fear_to_next_card': game.get_fear_to_next_card(),
|
|
||||||
'fear_this_phase': current_phase.fear_this_phase(),
|
|
||||||
'total_fear': game.get_current_total_fear(),
|
|
||||||
'players': players,
|
'players': players,
|
||||||
'all_ready': all(player.ready for player in players.values()),
|
|
||||||
'range': range(21),
|
'range': range(21),
|
||||||
'errors': errors,
|
'errors': errors,
|
||||||
})
|
})
|
||||||
|
@ -235,6 +316,10 @@ def game(request, game):
|
||||||
|
|
||||||
@lookup_access_code
|
@lookup_access_code
|
||||||
@require_safe
|
@require_safe
|
||||||
def status(request, game):
|
def status(request, game, hashcode=None):
|
||||||
# TODO status json
|
status_obj = game_status_object(game)
|
||||||
pass
|
if hashcode == status_obj['hash']:
|
||||||
|
return HttpResponse(status=HTTPStatus.NOT_MODIFIED)
|
||||||
|
else:
|
||||||
|
status_string = json.dumps(status_obj)
|
||||||
|
return HttpResponse(status_string)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user