Implemented observer view for a computer visible to all players. ()

* Implemented observer view for a computer visible to all players.
* Updated New Game and Join Game pages/forms so observe button requires name to be blank and joining as a player requires name to be non-blank.
* Highlight more clearly what player is on a device to make the observer more clearly separate.

Closes .
This commit is contained in:
Daniel Perelman 2017-05-07 14:51:29 -07:00 committed by GitHub
parent 6740b8e3fc
commit 79fb7d7b97
14 changed files with 155 additions and 30 deletions

View File

@ -3,12 +3,26 @@ from django import forms
from .models import Game, Player
class NewGameForm(forms.Form):
name = forms.CharField(label='Name', max_length=80)
name = forms.CharField(label='Name', max_length=80, required=False)
def clean(self):
cleaned_data = super(NewGameForm, self).clean()
name = cleaned_data.get("name")
observer = 'observe' in self.data
if observer and (name is None or len(name) > 0):
self.add_error('name', "Only fill in 'Name' field if you will be playing (not observing) using this device.")
elif not observer and len(name) == 0:
self.add_error('name', "Player name must be non-empty (did you mean to click 'Create as observer'?).")
if name is None or observer:
cleaned_data["name"] = None
class JoinGameForm(forms.Form):
game = forms.CharField(label='Access code',
max_length=Game.ACCESS_CODE_LENGTH)
player = forms.CharField(label='Name', max_length=80)
player = forms.CharField(label='Name', max_length=80, required=False)
def clean_game(self):
data = self.cleaned_data['game']
@ -23,8 +37,17 @@ class JoinGameForm(forms.Form):
game = cleaned_data.get("game")
name = cleaned_data.get("player")
observer = 'observe' in self.data
cleaned_data["observer"] = observer
if game is None or name is None:
if observer and (name is None or len(name) > 0):
self.add_error('player', "Leave 'Name' field blank if observing or use 'Join' button to join as a player.")
elif not observer and len(name) == 0:
self.add_error('player', "Player name must be non-empty (did you mean to click 'Observe'?).")
return
if game is None or name is None or observer:
cleaned_data["player"] = None
return
try:
@ -41,7 +64,7 @@ class JoinGameForm(forms.Form):
player = Player.objects.create(game=game, name=name)
cleaned_data["player"] = player
else:
self.add_error('player', "That game has already started. If you want to rejoin, please enter your name exactly as you did before.")
self.add_error('player', "That game has already started. If you want to rejoin, please enter your name exactly as you did before or select \"Observe\" if you just want to display the game status.")
class StartGameForm(forms.Form):
display_history = forms.BooleanField(required=False, initial=True,

View File

@ -47,6 +47,15 @@ h1 {
margin-right: 1em;
}
.access-code .header {
font-weight: bold;
margin-left: 1em;
}
.access-code .header::after {
font-weight: bold;
content: ": ";
}
li {
position: relative;
list-style: none;
@ -54,6 +63,10 @@ li {
border-bottom: #eee solid 1px;
}
li.this-player {
font-weight: bold;
}
.button-container {
display: flex;
}

View File

@ -13,7 +13,10 @@
<div class="container">
{% block accesscode %}
<div class="access-code">
<p>Access Code: {{ access_code|default:"(not in game)" }}</p>
<p>
<span class="header">Access Code</span> {{ access_code|default:"(not in game)" }}
<span class="header">Name</span> {% if is_observer %}(observer){% else %}{{ player.name|default:"(not in game)" }}{% endif %}
</p>
</div>
{% endblock %}
{% block score %}

View File

@ -5,7 +5,7 @@
{% block game_content %}{% endblock %}
{% block game_refresh %}
<div class="button-container">
<a href="{% url 'game' access_code=access_code player_secret=player_secret %}" class="button" id="button-refresh">Refresh</a>
<a href="{% if is_observer %}{% url 'observe' access_code=access_code %}{% else %}{% url 'game' access_code=access_code player_secret=player_secret %}{% endif %}" class="button" id="button-refresh">Refresh</a>
</div>
<script>
var statusObj = JSON.parse("{{ status|escapejs }}");
@ -15,7 +15,7 @@
{% endblock %}
}
setInterval(function() {
$.get("{% url 'status' access_code=access_code player_secret=player_secret %}", function(data, textStatus, jqXHR) {
$.get("{% if is_observer %}{% url 'observe_status' access_code=access_code %}{% else %}{% url 'status' access_code=access_code player_secret=player_secret %}{% endif %}", function(data, textStatus, jqXHR) {
if(JSON.stringify(data) != JSON.stringify(statusObj)) {
if(!handleNewStatus(statusObj, data)) {
document.getElementById("button-refresh").click();

View File

@ -3,6 +3,7 @@
{% block game_header %}
<div class="role-info">
<hr class="small-hr">
{% if not is_observer %}
<div id="role-info">
<p>Team: <b class="{{ player.team }}">{{ player.team|title }}</b> | Role: {{ player.role_string }}</p>
@ -44,6 +45,7 @@
Tap to view role info
</p>
</div>
{% endif %}
<script>
$(document).ready(function () {

View File

@ -11,6 +11,9 @@
<div class="button-container">
<input type="submit" class="button button-join" value="Join"></input>
<input type="submit" class="button button-observe" name="observe" value="Observe"></input>
</div>
<div class="button-container">
<a href="{% url 'index' %}" class="button button-main-menu">Back</a>
</div>
</form>

View File

@ -18,14 +18,18 @@
</div>
</div>
<ul id="players-in-lobby">
{% for player in players %}
<li>{{ player.name }}</li>
{% for listed_player in players %}
<li{% if player == listed_player %} class="this-player"{% endif %}>{{ listed_player.name }}</li>
{% endfor %}
</ul>
<div class="button-container">
<button type="submit" formaction="{% url 'start' access_code=access_code player_secret=player_secret %}" class="button-start">Start</button>
<button type="submit" formaction="{% if is_observer %}{% url 'observe_start' access_code=access_code %}{% else %}{% url 'start' access_code=access_code player_secret=player_secret %}{% endif %}" class="button-start">Start</button>
{% if is_observer %}
<a href="{% url 'index' %}" class="button button-leave">Leave</a>
{% else %}
<button type="submit" formaction="{% url 'leave' access_code=access_code player_secret=player_secret %}" class="button-leave">Leave</button>
{% endif %}
</div>
</form>
</div>
@ -40,6 +44,9 @@
newStatus.players.forEach(function(name) {
var el = document.createElement('li');
el.innerText = name;
if(name == '{{ player.name }}') {
el.className = 'this-player';
}
lobby_list.appendChild(el);
});
statusObj.players = newStatus.players;

View File

@ -6,6 +6,12 @@
<p>
The vote has passed. You have been chosen to go on the mission.
The following players are on the mission:
<ul id="chosen-for-mission">
{% for chosen_player in chosen %}
<li>{{ chosen_player.name }}</li>
{% endfor %}
</ul>
</p>
<form method="post">

View File

@ -5,7 +5,16 @@
<h2>Mission Phase</h2>
<p>
The vote has passed. You have not been selected. Please wait.
The vote has passed.
{% if not is_observer %}
You have not been selected. Please wait.
{% endif %}
The following players are on the mission:
<ul id="chosen-for-mission">
{% for chosen_player in chosen %}
<li>{{ chosen_player.name }}</li>
{% endfor %}
</ul>
</p>
</div>

View File

@ -9,7 +9,10 @@
{{ form.as_table }}
</table>
<div class="button-container">
<input type="submit" class="button button-create" value="Create"></input>
<input type="submit" class="button button-create" value="Create as player"></input>
<input type="submit" class="button button-observe" name="observe" value="Create as observer"></input>
</div>
<div class="button-container">
<a href="{% url 'index' %}" class="button button-main-menu">Back</a>
</div>
</form>

View File

@ -9,7 +9,7 @@
<p><b>Ready up!</b></p>
<ul>
{% for p in players %}
<li id="player-{{ p.order }}" class="{% if p.ready %}ready{% endif %}">
<li id="player-{{ p.order }}" class="{% if p.ready %}ready{% endif %}{% if p == player %} this-player{% endif %}">
{{ p.name }}
</li>
{% endfor %}
@ -17,8 +17,10 @@
<form method="post">
{% csrf_token %}
<div class="button-container">
{% if not is_observer %}
<button type="submit" formaction="{% url 'ready' access_code=access_code player_secret=player_secret %}">Ready</button>
<button type="submit" formaction="{% url 'cancel_game' access_code=access_code player_secret=player_secret %}">Return to Lobby</button>
{% endif %}
<button type="submit" formaction="{% if is_observer %}{% url 'observe_cancel_game' access_code=access_code %}{% else %}{% url 'cancel_game' access_code=access_code player_secret=player_secret %}{% endif %}">Return to Lobby</button>
</div>
</form>
</div>

View File

@ -14,6 +14,7 @@
<p><b>Waiting for <span id="missing-votes">{% if missing_votes_count == 1 %}1 person{% else %}{{ missing_votes_count }} people{% endif %}</span> to vote.</b></p>
{% if not is_observer %}
<form method="post">
{% csrf_token %}
<div class="button-container">
@ -31,6 +32,7 @@
<button type="submit" formaction="{% url 'retract_team' access_code=access_code player_secret=player_secret round_num=round_num vote_num=vote_num %}" class="button-cancel">Change team</button>
</div>
{% endif %}
{% endif %}
{% if player_vote %}
<p>You are voting: {{ player_vote }}</p>

View File

@ -9,6 +9,12 @@ urlpatterns = [
url(r'^(?P<access_code>[a-zA-Z]{6})/', include([
url(r'^$', views.join_game, name='join_game'),
url(r'^qr/$', views.qr_code, name='qr_code'),
url(r'^observe/', include([
url(r'^$', views.observe, name='observe'),
url(r'^status/$', views.observe_status, name='observe_status'),
url(r'^start/$', views.observe_start, name='observe_start'),
url(r'^cancel_game/$', views.observe_cancel_game, name='observe_cancel_game'),
])),
url(r'(?P<player_secret>[a-z]{8})/', include([
url(r'^$', views.game, name='game'),
url(r'^status/$', views.status, name='status'),

View File

@ -52,6 +52,8 @@ def enter_code(request):
if form.is_valid():
game = form.cleaned_data.get('game')
player = form.cleaned_data.get('player')
if player is None:
return redirect('observe', access_code=game.access_code)
return redirect('game',
access_code=game.access_code,
player_secret=player.secret_id)
@ -67,6 +69,8 @@ def new_game(request):
if form.is_valid():
game = Game.objects.create()
name = form.cleaned_data.get('name')
if name is None:
return redirect('observe', access_code=game.access_code)
player = Player.objects.create(game=game, name=name)
return redirect('game',
access_code=game.access_code,
@ -93,6 +97,10 @@ def qr_code(request, game):
img.save(output, "PNG")
return HttpResponse(output.getvalue(), content_type='image/png')
@lookup_access_code
@require_safe
def observe(request, game):
return _game(request, game, None)
def game_status_string(game, player):
game_status_object = {}
@ -132,6 +140,12 @@ def game_status_string(game, player):
return json.dumps(game_status_object)
@lookup_access_code
@require_safe
@transaction.non_atomic_requests
def observe_status(request, game):
return HttpResponse(game_status_string(game, None))
@lookup_access_code
@lookup_player_secret
@require_safe
@ -149,9 +163,11 @@ def game_base_context(game, player):
context['status'] = game_status_string(game, player)
context['access_code'] = game.access_code
context['player_secret'] = player.secret_id
context['is_observer'] = player is None
if player is not None:
context['player_secret'] = player.secret_id
context['player'] = player
context['players'] = players
context['player'] = player
context['num_players'] = num_players
context['game_rounds'] = game.gameround_set.all().order_by('round_num')
@ -171,12 +187,15 @@ def game_base_context(game, player):
if game.game_phase != Game.GAME_PHASE_LOBBY:
context['game_has_mordred'] = players.filter(role=Player.ROLE_MORDRED)\
.exists()
context['visible_spies'] = [p for p in players
if player.sees_as_spy(p)]
if player.is_percival():
possible_merlins = " or ".join([p.name for p in players
if p.appears_as_merlin()])
context['possible_merlins'] = possible_merlins
if player is None:
context['visible_spies'] = []
else:
context['visible_spies'] = [p for p in players
if player.sees_as_spy(p)]
if player.is_percival():
possible_merlins = " or ".join([p.name for p in players
if p.appears_as_merlin()])
context['possible_merlins'] = possible_merlins
return context
@ -193,6 +212,9 @@ def deterministic_random_boolean(seed):
def game(request, game, player):
player.save() # update last_accessed
return _game(request, game, player)
def _game(request, game, player):
context = game_base_context(game, player)
if game.game_phase == Game.GAME_PHASE_LOBBY:
@ -232,9 +254,10 @@ def game(request, game, player):
player_votes = vote_round.playervote_set.count()
num_players = context['num_players']
context['missing_votes_count'] = num_players - player_votes
seed = "%s-%s-%d-%d" % (game.access_code, player.secret_id,
round_num, vote_num)
context['swap_buttons'] = deterministic_random_boolean(seed)
if player is not None:
seed = "%s-%s-%d-%d" % (game.access_code, player.secret_id,
round_num, vote_num)
context['swap_buttons'] = deterministic_random_boolean(seed)
return render(request, 'vote.html', context)
elif game.game_phase == Game.GAME_PHASE_MISSION:
vote_round = VoteRound.objects.get_current_vote_round(game=game)
@ -261,7 +284,7 @@ def game(request, game, player):
else:
return render(request, 'mission_wait.html', context)
elif game.game_phase == Game.GAME_PHASE_ASSASSIN:
if player.is_assassin():
if player is not None and player.is_assassin():
context['targets'] = [p for p in context['players']
if p != player and not player.sees_as_spy(p)]
return render(request, 'assassinate.html', context)
@ -287,15 +310,26 @@ def leave(request, game, player):
game.delete()
return redirect('index')
@lookup_access_code
@require_POST
def observe_start(request, game):
return _start(request, game, None)
@lookup_access_code
@lookup_player_secret
@require_POST
def start(request, game, player):
player.save() # update last_accessed
return _start(request, game, player)
def _start(request, game, player):
if game.game_phase != Game.GAME_PHASE_LOBBY:
return redirect('game', access_code=game.access_code,
player_secret=player.secret_id)
if player is None:
return redirect('observe', access_code=game.access_code)
else:
return redirect('game', access_code=game.access_code,
player_secret=player.secret_id)
players = game.player_set.all()
num_players = players.count()
@ -346,13 +380,25 @@ def start(request, game, player):
p.save()
game.save()
return redirect('game', access_code=game.access_code,
player_secret=player.secret_id)
if player is None:
return redirect('observe', access_code=game.access_code)
else:
return redirect('game', access_code=game.access_code,
player_secret=player.secret_id)
context = game_base_context(game, player)
context['form'] = form
return render(request, 'lobby.html', context)
@lookup_access_code
@require_POST
def observe_cancel_game(request, game):
if game.game_phase == Game.GAME_PHASE_ROLE:
game.game_phase = Game.GAME_PHASE_LOBBY
game.save()
return redirect('observe', access_code=game.access_code)
@lookup_access_code
@lookup_player_secret
@require_POST