-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ctf #1081
ctf #1081
Changes from all commits
7b0ab1c
0c75678
48d13ae
27f7990
2994427
4c2be41
f2ac98e
dc50592
bb47088
fb5d5af
aa8428a
e6ce4f4
4df4da9
49b5837
67a89de
6a6feb3
0ca2ac7
fbb8391
21fb69a
2e08d2b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,283 @@ | ||
""" | ||
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. | ||
|
||
Ollie is planning his 3 year Computer Science (3778) degree starting in 2024 | ||
and wants to take a Computer Science major and Mathematics minor. Help him set | ||
up his degree in the degree wizard! | ||
|
||
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 all_courses(data: PlannerData) -> set[str]: | ||
""" | ||
Returns all courses from a planner | ||
""" | ||
return { | ||
course | ||
for year in data.plan | ||
for term in year | ||
for course in term | ||
} | ||
|
||
|
||
def get_code(course: str) -> int: | ||
""" | ||
Returns the code of a courseCode | ||
EG: COMP1511 -> 1511 | ||
""" | ||
return int(course[4:]) | ||
|
||
|
||
def get_faculty(course: str) -> str: | ||
""" | ||
Returns the faculty of a courseCode | ||
EG: COMP1511 -> COMP | ||
""" | ||
return course[:4] | ||
|
||
|
||
def gen_eds(courses: set[str]) -> set[str]: | ||
""" | ||
Returns all gen eds from a set of courses | ||
""" | ||
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: | ||
""" | ||
Must take atleast 3 courses with extended in the name | ||
""" | ||
extended_courses = { | ||
"COMP3821", | ||
"COMP3891", | ||
"COMP6841", | ||
"COMP6843" | ||
"COMP6845" | ||
} | ||
return len(extended_courses & all_courses(data)) >= 3 | ||
|
||
|
||
def summer_course(data: PlannerData) -> bool: | ||
""" | ||
Must take atleast one summer course | ||
""" | ||
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 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❓ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. debugging stuff |
||
if course == "COMP1511": | ||
return marks == 100 | ||
|
||
return False | ||
Comment on lines
+168
to
+175
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thoughts on turning this into a generator comprehension with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. true, this can be triv-solved with return any(
course
for term in year
for course, mark in term
if course == "COMP1511" and mark == 100
) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. true don't even need next, but what about simplifying this even further and doing return any(
course == "COMP1511" and mark == 100
for term in year
for course, mark in term
) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. THIS is incredibel +++EV!!!!!! |
||
|
||
|
||
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") | ||
]) | ||
Comment on lines
+207
to
+212
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thoughts on using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actly not bad idea |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ummmmmmmmm There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wrong |
||
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]]] = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thoughts on extracting the validator_func type to make this more readable? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. its already readable, skill diff |
||
# 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 at least THREE COMP courses with extended in the name that have not been discontinued.", "levelup{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.", "levelup{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", "levelup{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." | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓