Skip to content

Commit

Permalink
fix: Custom levels always have max score of 10 (#1422)
Browse files Browse the repository at this point in the history
  • Loading branch information
faucomte97 authored Feb 21, 2023
1 parent 234c87a commit d9a8c68
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 41 deletions.
30 changes: 15 additions & 15 deletions Pipfile.lock

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

6 changes: 2 additions & 4 deletions game/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ def __init__(self, *args, **kwargs):
)
# Each tuple in choices has two elements, id and name of each level
# First element is the actual value set on the model
# Second element is the string displayed on the drop down menu
episodes_choices = (
(episode.id, episode.name) for episode in Episode.objects.all()
)
# Second element is the string displayed on the dropdown menu
episodes_choices = ((episode.id, episode.name) for episode in Episode.objects.all())
self.fields["episodes"] = forms.MultipleChoiceField(
choices=itertools.chain(episodes_choices),
widget=forms.CheckboxSelectMultiple(),
Expand Down
4 changes: 2 additions & 2 deletions game/templates/game/scoreboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ <h4 class="text-center" id="scoreboardTitle">Scoreboard</h4>
{% endif %}
{% endfor %}
{% if user|is_logged_in_as_teacher %}
{% if student.percentage_complete > 75 %}
{% if student.success_rate > 75 %}
<td class="scoreboard--top-performer">
{% elif student.percentage_complete < 33 %}
{% elif student.success_rate < 33 %}
<td class="scoreboard--started">
{% else %}
<td class="scoreboard--medium-performer">
Expand Down
73 changes: 59 additions & 14 deletions game/tests/test_scoreboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,41 @@

from game.models import Attempt, Level, Episode
from game.tests.utils.level import create_save_level
from game.views.scoreboard import StudentRow, scoreboard_data, Headers, StudentInTrouble, shared_level_to_name
from game.views.scoreboard import (
StudentRow,
scoreboard_data,
Headers,
StudentInTrouble,
shared_level_to_name,
shared_levels_data,
SharedHeaders,
)
from game.views.scoreboard_csv import scoreboard_csv, Headers as CSVHeaders, SharedHeaders as CSVSharedHeaders


class ScoreboardTestCase(TestCase):
def test_teacher_multiple_students_multiple_levels(self):
episode_ids = [1]
# Setup official levels data
episode_ids = [1, 2]
episode1 = Episode.objects.get(id=1)
level_ids = [f"{x}" for x in range(1, len(episode1.levels) + 1)]
episode2 = Episode.objects.get(id=2)
level_ids = [f"{x}" for x in range(1, len(episode1.levels) + len(episode2.levels) + 1)]
level1 = Level.objects.get(name="1")
level2 = Level.objects.get(name="2")
level13 = Level.objects.get(name="13")

clas, student, student2 = set_up_data()

create_attempt(student, level1, 20)
create_attempt(student2, level1, 2)
create_attempt(student2, level2, 16)
create_attempt(student2, level13, 16)

all_levels = [level1, level2]
# Setup custom levels data
shared_level = create_save_level(student, "custom_level1", shared_with=[student2.new_user])

create_attempt(student2, shared_level, 10)

all_levels = [level1, level13]
all_shared_levels = [shared_level]

attempts_per_student = {
student: Attempt.objects.filter(level__in=all_levels, student=student, is_best_attempt=True).select_related(
Expand All @@ -42,8 +58,19 @@ def test_teacher_multiple_students_multiple_levels(self):
).select_related("level"),
}

shared_attempts_per_student = {
student2: Attempt.objects.filter(
level__in=all_shared_levels, student=student2, is_best_attempt=True
).select_related("level"),
}

# Generate results
student_data, headers, level_headers, levels_sorted = scoreboard_data(episode_ids, attempts_per_student)
shared_headers, shared_level_headers, shared_student_data = shared_levels_data(
student.new_user, all_shared_levels, shared_attempts_per_student
)

# Check data for official levels matches
assert headers == Headers
assert level_headers == [f"L{id}" for id in level_ids]

Expand All @@ -54,7 +81,7 @@ def test_teacher_multiple_students_multiple_levels(self):
assert student_row.name == student.user.user.first_name
assert student_row.total_score == 20
assert student_row.total_time == timedelta(0)
assert student_row.level_scores[1]["score"] == 20
assert student_row.level_scores[all_levels[0].id]["score"] == 20
assert student_row.completed == 1
assert student_row.success_rate == 100.0

Expand All @@ -63,13 +90,28 @@ def test_teacher_multiple_students_multiple_levels(self):
assert student_row.name == student2.user.user.first_name
assert student_row.total_score == 18
assert student_row.total_time == timedelta(0)
assert student_row.level_scores[1]["score"] == 2
assert student_row.level_scores[2]["score"] == 16
assert student_row.level_scores[all_levels[0].id]["score"] == 2
assert student_row.level_scores[all_levels[1].id]["score"] == 16
assert student_row.completed == 1
assert (
student_row.success_rate == 45.0
) ## the scores, (2 + 16 = 18), divided by the total possible, (2 * 20 = 40), 18/40 = 45%

# Check data for custom levels matches
assert shared_headers == SharedHeaders
assert shared_level_headers == [f"{shared_level.name} ({shared_level.owner})"]

assert len(shared_student_data) == 1

student_row = shared_student_data[0]
assert student_row.class_field.name == clas.name
assert student_row.name == student2.user.user.first_name
assert student_row.total_score == 10
assert student_row.total_time == timedelta(0)
assert student_row.level_scores[shared_level.id]["score"] == 10
assert student_row.completed == 1
assert student_row.success_rate == 100.0

def test_scoreboard_loads(self):
email, password = signup_teacher_directly()
create_organisation_directly(email)
Expand Down Expand Up @@ -211,9 +253,12 @@ def test_scoreboard_csv(self):
response = scoreboard_csv(student_rows, levels, improvement_data, shared_levels_headers, shared_level_rows)

# Gather the data from the CSV
actual_scoreboard_header, actual_scoreboard_rows, actual_shared_levels_header, actual_shared_levels_rows = self.actual_data(
response.content.decode("utf-8"), len(students)
)
(
actual_scoreboard_header,
actual_scoreboard_rows,
actual_shared_levels_header,
actual_shared_levels_rows,
) = self.actual_data(response.content.decode("utf-8"), len(students))

# Check the headers and the number or rows match expectations
assert actual_scoreboard_header == self.expected_scoreboard_header(levels)
Expand Down Expand Up @@ -281,7 +326,7 @@ def student_row(self, class_name=None):
total_score=total_score,
level_scores=level_scores,
completed=2,
percentage_complete=45,
success_rate=45,
)

return row, student
Expand Down Expand Up @@ -312,7 +357,7 @@ def shared_student_row(self, student, shared_levels):
total_score=0,
level_scores=level_scores,
completed=0,
percentage_complete=0,
success_rate=0,
)

return row
Expand Down
21 changes: 15 additions & 6 deletions game/views/scoreboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,21 @@ def student_row(levels_sorted, student, best_attempts):

if attempt:
max_score = 0
if not attempt.level.disable_route_score:
max_score += 10
if not attempt.level.disable_algorithm_score:
max_score += 10
if int(attempt.level.name) < 13:
max_score = 20

# Max score logic as follows:
# - All custom levels have a max score of 10
# - All official levels have a max score of 20 if both route score and algorithm scores are enabled
# except for levels 1-12 which have a max score of 20 even though the algorithm score is disabled
if attempt.level.episode is None:
max_score = 10
else:
if not attempt.level.disable_route_score:
max_score += 10
if not attempt.level.disable_algorithm_score:
max_score += 10
if int(attempt.level.name) < 13:
max_score = 20

num_all += 1
if attempt.score:
if attempt.score / max_score >= threshold:
Expand Down

0 comments on commit d9a8c68

Please sign in to comment.