Browse Source

Implemented setting up a game up until actually playing.

pull/11/head
Daniel Perelman 5 years ago
parent
commit
fc492aa18d
16 changed files with 609 additions and 30 deletions
  1. +2
    -0
      LICENSE
  2. +1
    -0
      avalon/avalon/settings.py
  3. +75
    -0
      avalon/avalon_game/forms.py
  4. +54
    -0
      avalon/avalon_game/helpers.py
  5. +125
    -5
      avalon/avalon_game/models.py
  6. +1
    -2
      avalon/avalon_game/static/css/styles.css
  7. +4
    -0
      avalon/avalon_game/static/js/jquery-2.1.4.min.js
  8. +6
    -1
      avalon/avalon_game/templates/base.html
  9. +9
    -0
      avalon/avalon_game/templates/game.html
  10. +57
    -0
      avalon/avalon_game/templates/in_game.html
  11. +17
    -0
      avalon/avalon_game/templates/join_game.html
  12. +31
    -0
      avalon/avalon_game/templates/lobby.html
  13. +16
    -0
      avalon/avalon_game/templates/new_game.html
  14. +20
    -0
      avalon/avalon_game/templates/role_phase.html
  15. +4
    -2
      avalon/avalon_game/urls.py
  16. +187
    -20
      avalon/avalon_game/views.py

+ 2
- 0
LICENSE View File

@@ -1,3 +1,5 @@
jQuery is licensed according to https://jquery.org/license/

The HTML and CSS are based on https://github.com/athtran/avalon
All modifications to them and all other files in this repository are licensed
under the below license.


+ 1
- 0
avalon/avalon/settings.py View File

@@ -79,6 +79,7 @@ DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'ATOMIC_REQUESTS': True,
}
}



+ 75
- 0
avalon/avalon_game/forms.py View File

@@ -0,0 +1,75 @@
from django import forms

from .models import Game, Player

class NewGameForm(forms.Form):
name = forms.CharField(label='Name', max_length=80)

class JoinGameForm(forms.Form):
game = forms.CharField(label='Access code',
max_length=Game.ACCESS_CODE_LENGTH)
player = forms.CharField(label='Name', max_length=80)

def clean_game(self):
data = self.cleaned_data['game']

try:
return Game.objects.get(access_code=data)
except Game.DoesNotExist:
raise forms.ValidationError("Invalid access code.")

def clean(self):
cleaned_data = super(JoinGameForm, self).clean()

game = cleaned_data.get("game")
name = cleaned_data.get("player")

if game is None or name is None:
return

try:
player = Player.objects.get(game=game, name=name)
if player.is_expired():
player.change_secret_id();
player.save()
cleaned_data["player"] = player
else:
self.add_error('player', "Please choose a different name; there is already a player using that name.")
self.add_error('player', "Please try again in a few seconds if you are trying to rejoin.")
except Player.DoesNotExist:
if game.game_phase == Game.GAME_PHASE_LOBBY:
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.")

class StartGameForm(forms.Form):
display_history = forms.BooleanField(required=False, initial=True,
label="show history table")
merlin = forms.BooleanField(required=False, initial=True, label="Merlin")
percival = forms.BooleanField(required=False, initial=True,
label="Percival")
assassin = forms.BooleanField(required=False, initial=True,
label="Assassin")
morgana = forms.BooleanField(required=False, initial=True, label="Morgana")
mordred = forms.BooleanField(required=False, initial=False,
label="Mordred")
oberon = forms.BooleanField(required=False, initial=False, label="Oberon")

def clean(self):
cleaned_data = super(StartGameForm, self).clean()

merlin = cleaned_data.get("merlin")
percival = cleaned_data.get("percival")
assassin = cleaned_data.get("assassin")
morgana = cleaned_data.get("morgana")
mordred = cleaned_data.get("mordred")

if assassin and not merlin:
self.add_error('assassin', "The assassin requires Merlin to be in the game.")
if percival and not merlin:
self.add_error('percival', "Percival requires Merlin to be in the game.")
if morgana and (not merlin or not percival):
self.add_error('morgana', "Morgana requies Merlin and Percival to be in the game.")
if mordred and not merlin:
self.add_error('mordred', "Mordred requires Merlin to be in the game.")

+ 54
- 0
avalon/avalon_game/helpers.py View File

@@ -0,0 +1,54 @@
def mission_size(num_players, round_num):
if num_players == 5:
if round_num == 1:
return (2, 1)
elif round_num == 2:
return (3, 1)
elif round_num == 3:
return (2, 1)
elif round_num == 4:
return (3, 1)
elif round_num == 5:
return (3, 1)
elif num_players == 6:
if round_num == 1:
return (2, 1)
elif round_num == 2:
return (3, 1)
elif round_num == 3:
return (4, 1)
elif round_num == 4:
return (3, 1)
elif round_num == 5:
return (4, 1)
elif num_players == 7:
if round_num == 1:
return (2, 1)
elif round_num == 2:
return (3, 1)
elif round_num == 3:
return (3, 1)
elif round_num == 4:
return (4, 2)
elif round_num == 5:
return (4, 1)
elif num_players > 7 and num_players < 11:
if round_num == 1:
return (3, 1)
elif round_num == 2:
return (4, 1)
elif round_num == 3:
return (5, 1)
elif round_num == 4:
return (5, 2)
elif round_num == 5:
return (5, 1)
else:
raise ValueError("Invalid # of players %d or round number %d" %\
(num_players, round_num))

def mission_size_string(mission_size):
if mission_size[1] == 2:
return "%d*" % mission_size[0]
else:
return "%d" % mission_size[0]

+ 125
- 5
avalon/avalon_game/models.py View File

@@ -1,22 +1,47 @@
from __future__ import unicode_literals

from datetime import datetime, timedelta
import random
import string

from django.db import models
from django.utils import timezone

def generate_code(length):
return "".join([random.choice(string.ascii_lowercase)
for i in xrange(length)])

class Game(models.Model):
access_key = models.CharField(db_index=True, unique=True, max_length=6)
ACCESS_CODE_LENGTH = 6
access_code = models.CharField(db_index=True, unique=True,
max_length=ACCESS_CODE_LENGTH)
GAME_PHASE_LOBBY = 0
GAME_PHASE_ROLE = 1
GAME_PHASE_PICK = 2
GAME_PHASE_VOTING = 3
GAME_PHASE_VOTE = 3
GAME_PHASE_MISSION = 4
GAME_PHASE_ASSASSIN = 5
GAME_PHASE_END = 6
game_phase = models.IntegerField(default=GAME_PHASE_LOBBY)
player_assassinated = models.ForeignKey('Player', related_name='+')
display_history = models.NullBooleanField()
player_assassinated = models.ForeignKey('Player', null=True, default=None,
related_name='+')

# from http://stackoverflow.com/a/11821832
def save(self, *args, **kwargs):
# object is being created, thus no primary key field yet
if not self.pk:
# Make sure access_code is unique before using it.
access_code = generate_code(Game.ACCESS_CODE_LENGTH)
while Game.objects.filter(access_code=access_code).exists():
access_code = generate_code(Game.ACCESS_CODE_LENGTH)
self.access_code = access_code
super(Game, self).save(*args, **kwargs)

class Player(models.Model):
game = models.ForeignKey(Game, on_delete=models.CASCADE, db_index=True)
secret_id = models.CharField(db_index=True, max_length=8)
SECRET_ID_LENGTH = 8
secret_id = models.CharField(db_index=True, max_length=SECRET_ID_LENGTH)
name = models.CharField(max_length=80)
ROLE_SPY = -1
ROLE_ASSASSIN = -2
@@ -26,17 +51,112 @@ class Player(models.Model):
ROLE_GOOD = 1
ROLE_MERLIN = 2
ROLE_PERCIVAL = 3
role = models.IntegerField()
role = models.IntegerField(null=True, default=None)
order = models.IntegerField(null=True, default=None)
ready = models.BooleanField(default=False)
last_accessed = models.DateTimeField()
# names are unique in a game
unique_together = (("game", "name"), ("game", "secret_id"))

def is_expired(self):
return timezone.now() - self.last_accessed > timedelta(seconds=10)

def change_secret_id(self):
# Make sure secret_id is unique before using it.
secret_id = generate_code(Player.SECRET_ID_LENGTH)
while Player.objects.filter(game=self.game,
secret_id=secret_id).exists():
secret_id = generate_code(Player.SECRET_ID_LENGTH)
self.secret_id = secret_id

# from http://stackoverflow.com/a/11821832
def save(self, *args, **kwargs):
# object is being created, thus no primary key field yet
if not self.pk:
self.change_secret_id()
self.last_accessed = timezone.now()
super(Player, self).save(*args, **kwargs)

def is_spy(self):
if self.role is None:
return None
elif self.role < 0:
return True
else:
return False

def team(self):
if self.is_spy() is None:
return None
elif self.is_spy():
return 'spy'
else:
return 'resistance'

def role_string(self):
if self.role is None:
return None
elif self.role == Player.ROLE_SPY:
return "Minion of Mordred"
elif self.role == Player.ROLE_ASSASSIN:
return "Assassin"
elif self.role == Player.ROLE_MORGANA:
return "Morgana"
elif self.role == Player.ROLE_MORDRED:
return "Mordred"
elif self.role == Player.ROLE_OBERON:
return "Oberon"
elif self.role == Player.ROLE_GOOD:
return "Loyal servant of Arthur"
elif self.role == Player.ROLE_MERLIN:
return "Merlin"
elif self.role == Player.ROLE_PERCIVAL:
return "Percival"

def is_oberon(self):
return self.role == Player.ROLE_OBERON

def is_merlin(self):
return self.role == Player.ROLE_MERLIN

def is_percival(self):
return self.role == Player.ROLE_PERCIVAL

def is_assassin(self):
return self.role == Player.ROLE_ASSASSIN

def is_morgana(self):
return self.role == Player.ROLE_MORGANA

def is_mordred(self):
return self.role == Player.ROLE_MORDRED

def sees_as_spy(self, other):
if other.is_spy():
if self.is_merlin() and not other.is_mordred():
return True
elif self.is_spy() and\
not self.is_oberon() and not other.is_oberon():
return True
return False

def appears_as_merlin(self):
return self.is_merlin() or self.is_morgana()

class GameRound(models.Model):
game = models.ForeignKey(Game, on_delete=models.CASCADE, db_index=True)
round_num = models.IntegerField()
unique_together = (("game", "round_num"),)
mission_passed = models.NullBooleanField()

def winner_string(self):
if self.mission_passed is None:
return ''
elif self.mission_passed:
return 'resistance'
else:
return 'spy'

class MissionAction(models.Model):
game_round = models.ForeignKey(GameRound, on_delete=models.CASCADE, db_index=True)
player = models.ForeignKey(Player, on_delete=models.CASCADE)


+ 1
- 2
avalon/avalon_game/static/css/styles.css View File

@@ -218,7 +218,7 @@ li.voter {
.game-options li {
text-align: left;
margin: 0;
padding: 10px;
padding: 0;
border-bottom: 0;
}

@@ -231,7 +231,6 @@ label {
display: inline;
}

.role-options,
#role-info {
display: none;
}


+ 4
- 0
avalon/avalon_game/static/js/jquery-2.1.4.min.js
File diff suppressed because it is too large
View File


+ 6
- 1
avalon/avalon_game/templates/base.html View File

@@ -5,7 +5,8 @@
<link rel="stylesheet" href="{% static 'css/normalize.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/skeleton.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/styles.css' %}" type="text/css">
<title>Resistance</title>
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
<title>Resistance</title>
</head>

<body>
@@ -17,11 +18,15 @@
{% endblock %}
{% block score %}
<div class="score">
{% for round_num, round in round_scores.items %}
<div class="score-box {{ round.winner }}"><p>{{ round.mission_size }}</p></div>
{% empty %}
<div class="score-box"><p></p></div>
<div class="score-box"><p></p></div>
<div class="score-box"><p></p></div>
<div class="score-box"><p></p></div>
<div class="score-box"><p></p></div>
{% endfor %}
</div>
{% endblock %}
<br>


+ 9
- 0
avalon/avalon_game/templates/game.html View File

@@ -0,0 +1,9 @@
{% extends "base.html" %}

{% block content %}
{% block game_header %}{% endblock %}
{% block game_content %}{% endblock %}
<div class="button-container">
<a href="{% url 'game' access_code=access_code player_secret=player_secret %}" class="button">Refresh</a>
</div>
{% endblock %}

+ 57
- 0
avalon/avalon_game/templates/in_game.html View File

@@ -0,0 +1,57 @@
{% extends "game.html" %}

{% block game_header %}
<div class="role-info">
<hr class="small-hr">
<div id="role-info">
<p>Team: <b class="{{ player.team }}">{{ player.team|title }}</b> | Role: {{ player.role_string }}</p>

{% if player.is_spy and not player.is_oberon %}
<p>Other spies:
{% for spy in visible_spies %}
<b class="spy">{{ spy.name }} </b>
{% endfor %}
</p>
{% endif %}

{% if player.is_merlin %}
<p>The spies {% if game_has_mordred %}(except Mordred){% endif %} are:
{% for spy in visible_spies %}
<b class="spy">{{ spy.name }} </b>
{% endfor %}
</p>
{% endif %}

{% if player.is_percival %}
<p>Merlin is {{ possible_merlins }}.</p>
{% endif %}

{% if player.is_assassin %}
<p>Try to see if you can spot who the Merlin is. You'll have a chance to identify him at the end of the game to win it.</p>
{% endif %}

{% if player.is_morgana %}
<p>Percival sees you as Merlin.</p>
{% endif %}

{% if player.is_mordred %}
<p>Merlin does not know you are a spy.</p>
{% endif %}
</div>
<p id="role-info-hidden">
Tap to view role info
</p>
</div>

<script>
$(document).ready(function () {
$('.role-info').click(function () {
$('#role-info').toggle();
$('#role-info-hidden').toggle();
})
})
</script>
{% endblock %}

{% block history %}
{% endblock %}

+ 17
- 0
avalon/avalon_game/templates/join_game.html View File

@@ -0,0 +1,17 @@
{% extends "base.html" %}

{% block content %}
<h2>Join Game</h2>

<form class="join-game-form" action="{% url 'enter_code' %}" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>

<div class="button-container">
<input type="submit" class="button button-join" value="Join"></input>
<a href="{% url 'index' %}" class="button button-main-menu">Back</a>
</div>
</form>
{% endblock %}

+ 31
- 0
avalon/avalon_game/templates/lobby.html View File

@@ -0,0 +1,31 @@
{% extends "game.html" %}

{% block game_content %}
<div class="lobby">
<h2>Lobby</h2>
<form method="post">
{% csrf_token %}
<div class="lobby-info">
<div class="access-code">
<p>Access Code: {{ access_code }}</p>
</div>
<div class="game-options">
<p class="options-header">Options</p>
<div class="role-options">
{{ form.as_ul }}
</div>
</div>
</div>
<ul>
{% for player in players %}
<li>{{ 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="{% url 'leave' access_code=access_code player_secret=player_secret %}" class="button-leave">Leave</button>
</div>
</form>
</div>
{% endblock %}

+ 16
- 0
avalon/avalon_game/templates/new_game.html View File

@@ -0,0 +1,16 @@
{% extends "base.html" %}

{% block content %}
<h2>New Game</h2>

<form class="new-game-form" action="#" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<div class="button-container">
<input type="submit" class="button button-create" value="Create"></input>
<a href="{% url 'index' %}" class="button button-main-menu">Back</a>
</div>
</form>
{% endblock %}

+ 20
- 0
avalon/avalon_game/templates/role_phase.html View File

@@ -0,0 +1,20 @@
{% extends "in_game.html" %}

{% block game_content %}
<div class="role-phase">
<h2>Role Phase</h2>

<p><b>Ready up!</b></p>
<ul>
{% for p in players %}
<li class="{% if p.ready %}ready{% endif %}">{{ p.name }}</li>
{% endfor %}
</ul>
<form method="post" action="{% url 'ready' access_code=access_code player_secret=player_secret %}">
{% csrf_token %}
<div class="button-container">
<button type="submit">Ready</button>
</div>
</form>
</div>
{% endblock %}

+ 4
- 2
avalon/avalon_game/urls.py View File

@@ -7,13 +7,15 @@ urlpatterns = [
url(r'^join/$', views.enter_code, name='enter_code'),
url(r'^new/$', views.new_game, name='new_game'),
url(r'^(?P<access_code>[a-z]{6})/', include([
url(r'$', views.join_game, name='join_game'),
url(r'^$', views.join_game, name='join_game'),
url(r'(?P<player_secret>[a-z]{8})/', include([
url(r'^$', views.game, name='game'),
url(r'^start/$', views.start, name='start'),
url(r'^leave/$', views.leave, name='leave'),
url(r'^ready/$', views.ready, name='ready'),
url(r'^vote/(?P<round_num>[1-5])/(?P<vote_num>[1-5])/(?P<vote>(approve|reject))/$', views.vote, name='vote'),
url(r'^choose/(?P<round_num>[1-5])/(?P<vote_num>[1-5])/(?P<who>[0-9]+)/$', views.choose, name='choose'),
url(r'^remove/(?P<round_num>[1-5])/(?P<vote_num>[1-5])/(?P<who>[0-9]+)/$', views.remove, name='remove'),
url(r'^remove/(?P<round_num>[1-5])/(?P<vote_num>[1-5])/(?P<who>[0-9]+)/$', views.unchoose, name='unchoose'),
url(r'^mission/(?P<round_num>[1-5])/(?P<mission_action>(success|fail))/$', views.mission, name='mission'),
url(r'^assassinate/(?P<target>[0-9]+)/$', views.choose, name='choose'),
])),


+ 187
- 20
avalon/avalon_game/views.py View File

@@ -1,10 +1,15 @@
import math
import random

from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.http import require_safe,\
require_POST,\
require_http_methods

from .models import Game, Player
from .forms import NewGameForm, JoinGameForm, StartGameForm
from .helpers import mission_size, mission_size_string
from .models import Game, GameRound, Player

# helpers to interpret arguments
def lookup_access_code(func):
@@ -30,53 +35,215 @@ def index(request):

@require_http_methods(["HEAD", "GET", "POST"])
def enter_code(request):
if request.method == 'GET':
pass
elif request.method == 'POST':
pass
if request.method == 'POST':
form = JoinGameForm(request.POST)
if form.is_valid():
game = form.cleaned_data.get('game')
player = form.cleaned_data.get('player')
return redirect('game',
access_code=game.access_code,
player_secret=player.secret_id)
else:
form = JoinGameForm()

return render(request, 'join_game.html', {'form': form})

@require_http_methods(["HEAD", "GET", "POST"])
def new_game(request):
if request.method == 'GET':
pass
elif request.method == 'POST':
pass
if request.method == 'POST':
form = NewGameForm(request.POST)
if form.is_valid():
game = Game.objects.create()
name = form.cleaned_data.get('name')
player = Player.objects.create(game=game, name=name)
return redirect('game',
access_code=game.access_code,
player_secret=player.secret_id)
else:
form = NewGameForm()

return render(request, 'new_game.html', {'form': form})

@lookup_access_code
@require_safe
def join_game(request, game):
pass
form = JoinGameForm(initial={'game': game.access_code})
return render(request, 'join_game.html', {'access_code': game.access_code,
'form': form})

def game_base_context(game, player):
players = Player.objects.filter(game=game).order_by('order')
num_players = players.count()

context = {}

context['access_code'] = game.access_code
context['player_secret'] = player.secret_id
context['players'] = players
context['player'] = player

try:
round_scores = {}
for round_num in range(1, 6):
round_scores[round_num] = {'mission_size': mission_size_string(mission_size(num_players=num_players, round_num=round_num)), 'winner': ''}
for game_round in GameRound.objects.filter(game=game):
round_scores[game_round.round_num]['winner'] = game_round.winner_string()
context['round_scores'] = round_scores
except ValueError:
pass

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

return context

@lookup_player_secret
@lookup_access_code
@lookup_player_secret
@require_safe
def game(request, game, player):
pass
player.save() # update last_accessed

context = game_base_context(game, player)

if game.game_phase == Game.GAME_PHASE_LOBBY:
context['form'] = StartGameForm()
return render(request, 'lobby.html', context)
elif game.game_phase == Game.GAME_PHASE_ROLE:
return render(request, 'role_phase.html', context)
elif game.game_phase == Game.GAME_PHASE_PICK:
pass
elif game.game_phase == Game.GAME_PHASE_VOTE:
pass
elif game.game_phase == Game.GAME_PHASE_MISSION:
pass
elif game.game_phase == Game.GAME_PHASE_ASSASSIN:
pass
elif game.game_phase == Game.GAME_PHASE_END:
pass
else:
pass

return render(request, 'in_game.html', context)

@lookup_access_code
@lookup_player_secret
@require_POST
def leave(request, game, player):
player.delete()
num_players = Player.objects.filter(game=game).count()
if num_players == 0:
game.delete()
return redirect('index')

@lookup_access_code
def ready(request, game, player):
pass
@lookup_player_secret
@require_POST
def start(request, game, player):
player.save() # update last_accessed

if game.game_phase != Game.GAME_PHASE_LOBBY:
return redirect('game', access_code=game.access_code,
player_secret=player.secret_id)

players = Player.objects.filter(game=game)
num_players = players.count()

form = StartGameForm(request.POST)
if num_players < 5:
form.add_error(None, "You must have at least 5 players to play!")
elif num_players > 10:
form.add_error(None, "You can't have more than 10 players to play!")

if form.is_valid():
num_spies = int(math.ceil(num_players / 3.0))
spy_roles = []
if form.cleaned_data.get('assassin'):
spy_roles.append(Player.ROLE_ASSASSIN)
if form.cleaned_data.get('morgana'):
spy_roles.append(Player.ROLE_MORGANA)
if form.cleaned_data.get('mordred'):
spy_roles.append(Player.ROLE_MORDRED)
if form.cleaned_data.get('oberon'):
spy_roles.append(Player.ROLE_OBERON)
if len(spy_roles) > num_spies:
form.add_error(None, "There will only be %d spies. Select no more than that many special roles for spies." % num_spies)
else:
game.display_history = form.cleaned_data.get('display_history')
game.game_phase = Game.GAME_PHASE_ROLE

resistance_roles = []
if form.cleaned_data.get('merlin'):
resistance_roles.append(Player.ROLE_MERLIN)
if form.cleaned_data.get('percival'):
resistance_roles.append(Player.ROLE_PERCIVAL)

num_resistance = num_players - num_spies
roles = spy_roles + resistance_roles +\
[Player.ROLE_SPY]*(num_spies - len(spy_roles)) +\
[Player.ROLE_GOOD]*(num_resistance - len(resistance_roles))
assert len(roles) == num_players

play_order = range(num_players)
random.shuffle(play_order)
random.shuffle(roles)

for p, role, order in zip(players, roles, play_order):
p.role = role
p.order = order
p.save()

game.save()
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
@lookup_player_secret
@require_POST
def ready(request, game, player):
if game.game_phase == Game.GAME_PHASE_ROLE:
player.ready = True
player.save()

if not Player.objects.filter(game=game, ready=False):
game.game_phase = Game.GAME_PHASE_PICK
game.save()
GameRound.objects.create(game=game, round_num=1)

return redirect('game', access_code=game.access_code,
player_secret=player.secret_id)

@lookup_access_code
@lookup_player_secret
def choose(request, game, player, round_num, vote_num, who):
pass

@lookup_player_secret
@lookup_access_code
def remove(request, game, player, round_num, vote_num, who):
@lookup_player_secret
def unchoose(request, game, player, round_num, vote_num, who):
pass

@lookup_player_secret
@lookup_access_code
@lookup_player_secret
def vote(request, game, player, round_num, vote_num, vote):
pass

@lookup_player_secret
@lookup_access_code
@lookup_player_secret
def mission(request, game, player, round_num, mission_action):
pass

@lookup_player_secret
@lookup_access_code
@lookup_player_secret
def assassinate(request, game, player, target):
pass

Loading…
Cancel
Save