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

* 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 #4.
pull/20/head
Daniel Perelman 7 years ago committed by GitHub
parent 6740b8e3fc
commit 79fb7d7b97

@ -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 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:
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,

@ -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;
}

@ -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 %}

@ -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();

@ -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 () {

@ -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>

@ -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;

@ -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">

@ -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>

@ -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>

@ -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>

@ -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>

@ -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'),

@ -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

Loading…
Cancel
Save