-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6a6feb3
commit 0ca2ac7
Showing
2 changed files
with
377 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
""" | ||
Implementation for the 2023 CTF. | ||
|
||
|
||
Scenario: | ||
In the whimsical world of the CTF (Capture The Flag), an extraordinary | ||
challenge awaits our protagonist, Ollie the Otter. Ollie, a brilliant otter | ||
with a passion for education, has embarked on an academic journey like no | ||
other. But there's a twist – Ollie's unique otter nature has led to some | ||
special conditions on his degree planning adventure. | ||
|
||
Ollie's task is to navigate through a degree planner system called | ||
"Circles," specially designed to accommodate his otter-specific | ||
requirements. In this intriguing scenario, you must help Ollie, the otter | ||
scholar, chart his academic path using Circles. | ||
|
||
When you are done, press the `Validate CTF` button on the term planner page | ||
to recieve your flags. | ||
|
||
# Stage 1. The Otter's Academic Odyssey: Charting Uncharted Waters | ||
Being the pioneer Otter in academia, Ollie faces some unique conditions | ||
that must be fulfilled for him to successfully navigate his degree. Let's | ||
tackle parts 0-3 to obtain the first flag. | ||
|
||
# Stage 2. The Kafka Quandary: Or How I Learned To Stop Worrying and Love The | ||
# Handbook | ||
Ollie encounters additional hurdles due to university policies, with | ||
challenges 4 through 7 directly related to these policy-driven obstacles. | ||
Join Ollie in overcoming these challenges and guiding him through this | ||
academic maze | ||
|
||
# Stage 3: (HARD) Numbers, Notions, and the Cult of Calculus: An O-Week Odyssey | ||
During Orientation Week (O-week), Ollie had a chance encounter with a group | ||
of math students. Little did he know that this encounter would lead to him | ||
being initiated into a number-centric cult. Over time, Ollie developed | ||
superstitions about certain numbers, and the ultimate challenges he faces | ||
revolve around aligning his degree plan with his newfound beliefs. With the | ||
last challenges, Ollie on his quest to complete his degree while also | ||
seeking the approval of the enigmatic number cult | ||
""" | ||
from typing import Callable, Optional | ||
|
||
from fastapi import APIRouter | ||
from server.routers.model import PlannerData | ||
|
||
router = APIRouter( | ||
prefix="/ctf", tags=["ctf"], responses={404: {"description": "Not found"}} | ||
) | ||
|
||
def degree(data: PlannerData) -> bool: | ||
return data.program == "3778" | ||
|
||
|
||
def all_courses(data: PlannerData) -> set[str]: | ||
return { | ||
course | ||
for year in data.plan | ||
for term in year | ||
for course in term | ||
} | ||
|
||
|
||
def get_code(course: str) -> int: | ||
return int(course[4:]) | ||
|
||
|
||
def get_faculty(course: str) -> str: | ||
return course[:4] | ||
|
||
|
||
def gen_eds(courses: set[str]) -> set[str]: | ||
return set( | ||
course | ||
for course in courses | ||
if not course.startswith("COMP") and not course.startswith("MATH") | ||
) | ||
|
||
|
||
def hard_requirements(data: PlannerData) -> bool: | ||
# NOTE: Can't check start year from this | ||
# Frontend should handle most of this anyways | ||
# including validity of the program | ||
return ( | ||
data.program == "3778" | ||
and "COMPA1" in data.specialisations | ||
and "MATHC2" in data.specialisations | ||
and len(data.plan) == 3 | ||
) | ||
|
||
|
||
def extended_courses(data: PlannerData) -> bool: | ||
extended_courses = { | ||
"COMP3821", | ||
"COMP3891", | ||
"COMP6841", | ||
"COMP6843" | ||
"COMP6845" | ||
} | ||
return len(extended_courses & all_courses(data)) >= 3 | ||
|
||
|
||
def summer_course(data: PlannerData) -> bool: | ||
return any( | ||
course.startswith("COMP") | ||
for year in data.plan | ||
for course in year[0] | ||
) | ||
|
||
|
||
|
||
def term_sums_even(data: PlannerData) -> bool: | ||
""" | ||
Check that the sum of the course codes in even terms is even | ||
""" | ||
is_even: Callable[[int], bool] = lambda x: x % 2 == 0 | ||
print("Checking even") | ||
for y, year in enumerate(data.plan): | ||
# Exclude summer term + odd terms | ||
for i, term in enumerate(year[2::2], 2): | ||
term_sum = sum(map(get_code, term.keys())) | ||
print(f"{y}T{i} sum: {term_sum}") | ||
if not is_even(term_sum): | ||
print("failed: ", term) | ||
return False | ||
|
||
return True | ||
|
||
# TODO | ||
def term_sums_odd(data: PlannerData) -> bool: | ||
""" | ||
Check that the sum of the course codes in odd terms is odd | ||
""" | ||
is_odd: Callable[[int], bool] = lambda x: x % 2 == 1 | ||
print("Checking odd") | ||
for year in data.plan[::2]: | ||
# Exclude summer term + even terms | ||
for term in year[1::2]: | ||
term_sum = sum(map(get_code, term.keys())) | ||
if not is_odd(term_sum): | ||
print("failed: ", term) | ||
return False | ||
return True | ||
|
||
def term_sums(data: PlannerData) -> bool: | ||
for year in data.plan: | ||
for i, term in enumerate(year[2::2], 1): | ||
if sum(map(get_code, term.keys())) % 2 != i % 2: | ||
return False | ||
|
||
return True | ||
|
||
|
||
def comp1511_marks(data: PlannerData) -> bool: | ||
""" | ||
Ollie must achieve a mark of 100 in COMP1511 to keep his scholarship | ||
""" | ||
for year in data.plan: | ||
for term in year: | ||
for course in term: | ||
_, marks = term[course] # type: ignore | ||
if course == "COMP1511": | ||
return marks == 100 | ||
|
||
return False | ||
|
||
|
||
def gen_ed_sum(data: PlannerData) -> bool: | ||
""" | ||
The sum of GENED course codes must not exceed 2200 | ||
""" | ||
return sum(map(get_code, gen_eds(all_courses(data)))) <= 2200 | ||
|
||
|
||
def gen_ed_faculty(data: PlannerData) -> bool: | ||
""" | ||
Gen-Eds must all be from different faculties | ||
""" | ||
gen_eds_facs = list(map(get_faculty, gen_eds(all_courses(data)))) | ||
return len(gen_eds_facs) == len(set(gen_eds_facs)) | ||
|
||
|
||
def same_code_diff_faculty(data: PlannerData) -> bool: | ||
""" | ||
Must take two courses with the same code but, from different faculties | ||
""" | ||
codes = list(map(get_code, all_courses(data))) | ||
# Can't have duplicate of a course since it's a set | ||
return len(codes) != len(set(codes)) | ||
|
||
|
||
def math_limit(data: PlannerData) -> bool: | ||
""" | ||
In your N-th year, you can only take N + 1 math courses | ||
""" | ||
for i, year in enumerate(data.plan, 1): | ||
num_math = len([ | ||
course | ||
for term in year | ||
for course in term | ||
if course.startswith("MATH") | ||
]) | ||
if num_math > i + 1: | ||
return False | ||
|
||
return True | ||
|
||
def six_threes_limit(data: PlannerData) -> bool: | ||
""" | ||
There can by at most 6 occurrences of the number 3 in the entire | ||
planner | ||
""" | ||
all_codes = "".join(str(get_code(course)) for course in all_courses(data)) | ||
return all_codes.count("3") <= 6 | ||
|
||
def comp1531_third_year(data: PlannerData) -> bool: | ||
""" | ||
COMP1531 must be taken in the third year | ||
""" | ||
third_year = data.plan[2] | ||
for term in third_year: | ||
for course in term: | ||
if course == "COMP1531": | ||
return True | ||
|
||
return False | ||
|
||
# (validator_func, message, Optional<flag>) | ||
requirements: list[tuple[Callable[[PlannerData], bool], str, Optional[str]]] = [ | ||
# Challenge 1 | ||
(hard_requirements, "Before you can submit, you must check that you are in a 3 year CS degree and have a math minor", None), | ||
(summer_course, "Ollie must take one summer COMP course.", None), | ||
(comp1511_marks, "To keep their scholarship, Ollie must achieve a mark of 100 in COMP1511.", None), | ||
(extended_courses, "Ollie must complete FOUR COMP courses with extended in the name that have not been discontinued.", "mVd3_1t_2_un1"), | ||
# Challenge 2 | ||
(comp1531_third_year, "Unable to find a partner earlier, Ollie must take COMP1531 in their third year.", None), | ||
(gen_ed_faculty, "The university has decided that General Education must be very general. As such, each Gen-Ed unit that Ollie takes must be from a different faculty.", None), | ||
(math_limit, "The university has become a big believer in spaced repetition and want to prevent students from cramming subjects for their minors. Now, in their N-th year, Ollie can only take N + 1 math courses.", None), | ||
(gen_ed_sum, "Course codes now reflect the difficulty of a course. To avoid extremely stressful terms, the sum of Olli's Gen-Ed course codes must not exceed 2200.", "i<3TryMesters"), | ||
# Challenge 3 | ||
(same_code_diff_faculty, "You must take two courses from different faculties that have the same course code.", None), | ||
(term_sums_even, "You must ensure that the sum of your course codes in even terms is even. Note that summer courses do not count towards this.", None), | ||
(term_sums_odd, "You must ensure that the sum of your course codes in odd terms is odd. Note that summer courses do not count towards this.", None), | ||
(six_threes_limit, "In all your course codes, there can be at most 6 occurrences of the number 3", "CU1Tur3d"), | ||
] | ||
|
||
@router.post("/validateCtf/") | ||
def validate_ctf(data : PlannerData): | ||
""" | ||
Validates the CTF | ||
""" | ||
passed: list[str] = [] | ||
flags: list[str] = [] | ||
for req_num, (fn, msg, flag) in enumerate(requirements): | ||
if not fn(data): | ||
return { | ||
"valid": False, | ||
"passed": passed, | ||
"failed": req_num, | ||
"flags": flags, | ||
"message": msg | ||
} | ||
passed.append(msg) | ||
if flag is not None: | ||
flags.append(flag) | ||
print("Ok: ", req_num) | ||
return { | ||
"valid": True, | ||
"failed": -1, | ||
"passed": passed, | ||
"flags": flags, | ||
"message": "Congratulations! You have passed all the requirements for the CTF." | ||
} |
Oops, something went wrong.