Skip to content

Commit

Permalink
Make a configurable quorum required for each game. Populate the game …
Browse files Browse the repository at this point in the history
…networks in sequence. Fix tests.
  • Loading branch information
alecpm committed Nov 9, 2024
1 parent 56f91bf commit 3e553c4
Show file tree
Hide file tree
Showing 10 changed files with 53 additions and 14 deletions.
20 changes: 15 additions & 5 deletions dlgr/griduniverse/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"num_rounds": int,
"num_games": int,
"quorum": int,
"game_quorum": int,
"time_per_round": float,
"instruct": bool,
"columns": int,
Expand Down Expand Up @@ -172,13 +173,12 @@ class Gridworld(object):

def __init__(self, **kwargs):
# If Singleton is already initialized, do nothing
if hasattr(self, "num_players"):
return

self.log_event = kwargs.get("log_event", lambda x: None)

# Players
self.num_players = kwargs.get("max_participants", 3)
# Minimum quorum for game defaults to max nubmer of players per game
self.quorum = kwargs.get("game_quorum", self.num_players)

# Rounds
self.num_rounds = kwargs.get("num_rounds", 1)
Expand Down Expand Up @@ -497,7 +497,7 @@ def build_labyrinth(self):

def _start_if_ready(self):
# Don't start unless we have a least one player
if self.players and not self.game_started:
if len(self.players) >= self.quorum and not self.game_started:
self.start_timestamp = time.time()

@property
Expand Down Expand Up @@ -1465,6 +1465,10 @@ def send_state_thread(self):
count += 1

player_count = len(self.grid.players)
# Don't start sending state until all players are on the board
if not self.grid.game_started:
continue

if not last_player_count or player_count != last_player_count:
update_walls = True
update_items = True
Expand Down Expand Up @@ -1636,7 +1640,8 @@ def configure(self):
self.num_participants = self.config.get("max_participants", 3)
self.instruct = self.config.get("instruct", True)
self.total_participants = self.num_participants * self.experiment_repeats
self.quorum = self.config.get("quorum", self.total_participants)
# Quorum defaults to the max number of players for a single game
self.quorum = self.config.get("quorum", self.num_participants)
self.initial_recruitment_size = self.config.get(
"num_recruits", self.total_participants
)
Expand Down Expand Up @@ -1735,6 +1740,11 @@ def create_network(self):
class_ = getattr(dallinger.networks, self.network_factory)
return class_(max_size=self.num_participants + 1)

def choose_network(self, networks, participant):
"""Start filling the first game/network first"""
networks.sort(key=lambda n: n.id)
return networks[0]

def create_node(self, participant, network):
try:
return dallinger.models.Node(network=network, participant=participant)
Expand Down
6 changes: 5 additions & 1 deletion dlgr/griduniverse/static/scripts/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,10 @@
};
}

function onNewRound(msg) {
return displayLeaderboards(msg);
}

$(function () {
var player_id = dallinger.getUrlParameter("participant_id");
isSpectator = _.isUndefined(player_id);
Expand All @@ -1297,7 +1301,7 @@
donation_processed: onDonationProcessed,
color_changed: onColorChanged,
state: onGameStateChange,
new_round: displayLeaderboards,
new_round: onNewRound,
stop: gameOverHandler(player_id),
wall_built: addWall,
move_rejection: onMoveRejected,
Expand Down
6 changes: 5 additions & 1 deletion dlgr/griduniverse/static/scripts/dist/bundle.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dlgr/griduniverse/static/scripts/dist/bundle.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dlgr/griduniverse/static/scripts/dist/difi.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dlgr/griduniverse/static/scripts/dist/questionnaire.js.map

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion dlgr/griduniverse/static/styles/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,9 @@ img.team {
font-size: 1.5rem;
position: absolute;
top: 170px;
left: 70px;
width: 421px;
padding: 0 20px;
text-align: center;
}

.grid-loading span {
Expand Down
2 changes: 1 addition & 1 deletion dlgr/griduniverse/templates/grid.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ <h2>Game over.</h2>
{% if experiment.config.num_rounds > 1 %}in round <span id="round">?</span>{% endif %}
</p>
</div>
<p class="grid-loading">Loading Game World <span>.</span><span>.</span><span>.</span></p>
<p class="grid-loading">Waiting for players to join <span>.</span><span>.</span><span>.</span></p>

<div id="grid" data-identicon-salt="{{ app_id }}">
<!-- Grid will be inserted here. -->
Expand Down
6 changes: 6 additions & 0 deletions test/test_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ def test_loop_publishes_stop_event(self, loop_game_3x):

def test_send_state_thread(self, loop_game_3x):
game = loop_game_3x
# The game state thread only starts when it has enough players
game.grid.players = {
"1": Player(id="1", score=0.0),
"2": Player(id="1", score=0.0),
"3": Player(id="3", score=0.0),
}
game.send_state_thread()

# State thread will loop 4 times before the loop is broken,
Expand Down
17 changes: 15 additions & 2 deletions test/test_gridworld.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,28 @@ def test_check_round_not_started(self, gridworld):
assert gridworld.remaining_round_time == 0
assert gridworld.game_over is False

def test_check_round_starts_with_players(self, gridworld):
# Adding players starts the game
def test_check_round_doesnt_start_with_too_few_players(self, gridworld):
# Adding enough players starts the game
gridworld.players = {1: mock.Mock()}
gridworld._start_if_ready()
gridworld.check_round_completion()
assert gridworld.round == 0
assert gridworld.start_timestamp is None
assert gridworld.remaining_round_time == 0
assert gridworld.game_over is False

def test_check_round_starts_with_player_quorum(self, gridworld):
# Adding enough players starts the game
gridworld.quorum = 2
gridworld.players = {1: mock.Mock(), 2: mock.Mock()}
gridworld._start_if_ready()
gridworld.check_round_completion()
assert gridworld.start_timestamp is not None
assert round(gridworld.remaining_round_time) == round(gridworld.time_per_round)
assert gridworld.round == 0

def test_check_round_change(self, gridworld):
gridworld.quorum = 1
gridworld.players = {1: mock.Mock()}
gridworld.num_rounds = 2
gridworld._start_if_ready()
Expand All @@ -139,6 +151,7 @@ def test_check_round_change(self, gridworld):
assert gridworld.players[1].motion_timestamp == 0

def test_game_end(self, gridworld):
gridworld.quorum = 1
gridworld.players = {1: mock.Mock()}
gridworld.num_rounds = 1
gridworld._start_if_ready()
Expand Down

0 comments on commit 3e553c4

Please sign in to comment.