From 7b0ab1ca3bdb3e0d3a95fe4e60058c825c845f6b Mon Sep 17 00:00:00 2001 From: Bahnschrift Date: Fri, 15 Sep 2023 22:27:40 +1000 Subject: [PATCH 01/20] Most of the functionality for CTF --- backend/server/routers/ctf.py | 102 ++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 backend/server/routers/ctf.py diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py new file mode 100644 index 000000000..901357570 --- /dev/null +++ b/backend/server/routers/ctf.py @@ -0,0 +1,102 @@ +from pydantic import BaseModel + +from fastapi import APIRouter, HTTPException +from server.routers.model import (ValidCoursesState, PlannerData, + ValidPlannerData, ProgramTime) + +router = APIRouter( + prefix="/planner", tags=["planner"], responses={404: {"description": "Not found"}} +) + +# Requirements +# - Complete your degree plan (kinda hard to check from json) +# - Must take every single comp course with extended in the name (done) +# - Must take a summer COMP course (done) +# - For odd terms, the sum of your course codes must be odd. (done) +# In even terms, it must be even. +# Summer terms are excluded from this. +# - Must get full marks in COMP1511 (can't do with exported json) +# - Gen-Eds can't sum to more than 2200. (done) +# - Gen-Eds must be from different faculties. (done) +# - Must take two courses from different faculties that have the same course code. (done) +# - Must not have any warnings - including marks! (can't do with exported json) +# - May take at most N + 1 math courses in the Nth year + +# Hard Requirements +# - Cannot take any discontinued courses (hard to check) +# - 3 yr CS Degree - 3778 (can't check) +# - Must pick a math minor (can't check) +# - Start at 2024 (done) + +class ExportedData(BaseModel): + startYear: str + numYears: int + isSummerEnabled: bool + years: list[dict[str, list[str]]] + version: int # unused + + +def all_courses(data: ExportedData) -> set[str]: + return set(course for year in data.years for term in year.values() for course in term) + + +def code(course: str) -> int: + return int(course[4:]) + + +def 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: ExportedData) -> bool: + return data.startYear == "2024" and data.numYears == 3 + + +def extended_courses(data: ExportedData) -> bool: + return {"COMP3821", "COMP3891", "COMP6841", "COMP6843"} - all_courses(data) == set() + + +def summer_course(data: ExportedData) -> bool: + return data.isSummerEnabled and any(course.startswith("COMP") for year in data.years for course in year["T0"]) + + +def term_sums(data: ExportedData) -> bool: + for year in data.years: + for term, courses in year.items(): + if term != "T0": + if sum(map(code, courses)) % 2 != (term in {"T1", "T3"}): + return False + + return True + + +def gen_ed_sum(data: ExportedData) -> bool: + return sum(map(code, gen_eds(all_courses(data)))) <= 2200 + + +def gen_ed_faculty(data: ExportedData) -> bool: + gen_eds_facs = list(map(faculty, gen_eds(all_courses(data)))) + return len(gen_eds_facs) == len(set(gen_eds_facs)) + + +def same_code_diff_faculty(data: ExportedData) -> bool: + codes = list(map(code, all_courses(data))) + return len(codes) == len(set(codes)) # Can't have duplicate of a course since it's a set + + +def math_limit(data: ExportedData) -> bool: + for i, year in enumerate(data.years, 1): + num_math = len([course for term in year.values() for course in term if course.startswith("MATH")]) + if num_math > i + 1: + return False + + return True + + +@router.post("/validateCtf/") +def validate_ctf(data: ExportedData): + pass From 0c75678f2337287c2e886e7dae23ed971eac38cd Mon Sep 17 00:00:00 2001 From: Bahnschrift Date: Tue, 19 Sep 2023 18:21:12 +1000 Subject: [PATCH 02/20] Almost done with ctf --- backend/server/routers/ctf.py | 129 ++++++++++++------ backend/server/server.py | 3 +- .../OptionsHeader/OptionsHeader.tsx | 6 + .../ValidateCtfButton/ValidateCtfButton.tsx | 20 +++ .../TermPlanner/ValidateCtfButton/index.ts | 3 + 5 files changed, 120 insertions(+), 41 deletions(-) create mode 100644 frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx create mode 100644 frontend/src/pages/TermPlanner/ValidateCtfButton/index.ts diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index 901357570..429472363 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -1,43 +1,59 @@ from pydantic import BaseModel from fastapi import APIRouter, HTTPException -from server.routers.model import (ValidCoursesState, PlannerData, - ValidPlannerData, ProgramTime) +from server.routers.model import ( + ValidCoursesState, + PlannerData, + ValidPlannerData, + ProgramTime, +) router = APIRouter( - prefix="/planner", tags=["planner"], responses={404: {"description": "Not found"}} + prefix="/ctf", tags=["ctf"], responses={404: {"description": "Not found"}} ) # Requirements -# - Complete your degree plan (kinda hard to check from json) +# - Complete your degree plan (I think this is in the frontend?) # - Must take every single comp course with extended in the name (done) # - Must take a summer COMP course (done) # - For odd terms, the sum of your course codes must be odd. (done) # In even terms, it must be even. # Summer terms are excluded from this. -# - Must get full marks in COMP1511 (can't do with exported json) +# - Must get full marks in COMP1511 (done) # - Gen-Eds can't sum to more than 2200. (done) # - Gen-Eds must be from different faculties. (done) # - Must take two courses from different faculties that have the same course code. (done) -# - Must not have any warnings - including marks! (can't do with exported json) -# - May take at most N + 1 math courses in the Nth year +# - Must not have any warnings - including marks! (should probably do on frontend) +# - May take at most N + 1 math courses in the Nth year (done) # Hard Requirements # - Cannot take any discontinued courses (hard to check) -# - 3 yr CS Degree - 3778 (can't check) -# - Must pick a math minor (can't check) -# - Start at 2024 (done) +# - 3 yr CS Degree - 3778 +# - Must pick a math minor +# - Start at 2024 (can't check) + + +def degree(data: PlannerData) -> bool: + return data.program == "3778" + + +# Note: Didn't make this one of the requirements so not included +def major(data: PlannerData) -> bool: + return "COMPA1" in data.specialisations + -class ExportedData(BaseModel): - startYear: str - numYears: int - isSummerEnabled: bool - years: list[dict[str, list[str]]] - version: int # unused +def math_minor(data: PlannerData) -> bool: + return "MATHC2" in data.specialisations -def all_courses(data: ExportedData) -> set[str]: - return set(course for year in data.years for term in year.values() for course in term) +def all_courses(data: PlannerData) -> set[str]: + courses = set() + for year in data.plan: + for term in year: + for course in term: + courses.add(course) + + return courses def code(course: str) -> int: @@ -49,54 +65,87 @@ def faculty(course: str) -> str: 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")) + return set( + course + for course in courses + if not course.startswith("COMP") and not course.startswith("MATH") + ) -def hard_requirements(data: ExportedData) -> bool: - return data.startYear == "2024" and data.numYears == 3 +def hard_requirements(data: PlannerData) -> bool: + return ( + data.program == "3778" + and "COMPA1" in data.specialisations + and "MATHC2" in data.specialisations + and len(data.plan) == 3 + # NOTE: Can't check start year from this + ) -def extended_courses(data: ExportedData) -> bool: +def extended_courses(data: PlannerData) -> bool: return {"COMP3821", "COMP3891", "COMP6841", "COMP6843"} - all_courses(data) == set() -def summer_course(data: ExportedData) -> bool: - return data.isSummerEnabled and any(course.startswith("COMP") for year in data.years for course in year["T0"]) +def summer_course(data: PlannerData) -> bool: + return any(course.startswith("COMP") for year in data.plan for course in year[0]) -def term_sums(data: ExportedData) -> bool: - for year in data.years: - for term, courses in year.items(): - if term != "T0": - if sum(map(code, courses)) % 2 != (term in {"T1", "T3"}): - return False +def term_sums(data: PlannerData) -> bool: + for year in data.plan: + for i, term in enumerate(year[1:], 1): # Exclude summer term + if sum(map(code, term.keys())) % 2 != i % 2: + return False return True -def gen_ed_sum(data: ExportedData) -> bool: +def COMP1511_marks(data: PlannerData) -> bool: + 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: return sum(map(code, gen_eds(all_courses(data)))) <= 2200 -def gen_ed_faculty(data: ExportedData) -> bool: +def gen_ed_faculty(data: PlannerData) -> bool: gen_eds_facs = list(map(faculty, gen_eds(all_courses(data)))) return len(gen_eds_facs) == len(set(gen_eds_facs)) -def same_code_diff_faculty(data: ExportedData) -> bool: +def same_code_diff_faculty(data: PlannerData) -> bool: codes = list(map(code, all_courses(data))) - return len(codes) == len(set(codes)) # Can't have duplicate of a course since it's a set + return len(codes) != len( + set(codes) + ) # Can't have duplicate of a course since it's a set -def math_limit(data: ExportedData) -> bool: - for i, year in enumerate(data.years, 1): - num_math = len([course for term in year.values() for course in term if course.startswith("MATH")]) +def math_limit(data: PlannerData) -> bool: + 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 @router.post("/validateCtf/") -def validate_ctf(data: ExportedData): - pass +def validate_ctf(data: PlannerData): + print(data) + print(f"{hard_requirements(data) = }") + print(f"{extended_courses(data) = }") + print(f"{summer_course(data) = }") + print(f"{term_sums(data) = }") + print(f"{COMP1511_marks(data) = }") + print(f"{gen_ed_sum(data) = }") + print(f"{gen_ed_faculty(data) = }") + print(f"{same_code_diff_faculty(data) = }") + print(f"{math_limit(data) = }") diff --git a/backend/server/server.py b/backend/server/server.py index 96d80ccf5..626688053 100644 --- a/backend/server/server.py +++ b/backend/server/server.py @@ -6,7 +6,7 @@ from fastapi.middleware.cors import CORSMiddleware from data.config import LIVE_YEAR -from server.routers import courses, planner, programs, specialisations, followups +from server.routers import courses, planner, programs, specialisations, followups, ctf app = FastAPI() @@ -45,6 +45,7 @@ app.include_router(programs.router) app.include_router(specialisations.router) app.include_router(followups.router) +app.include_router(ctf.router) @app.get("/") diff --git a/frontend/src/pages/TermPlanner/OptionsHeader/OptionsHeader.tsx b/frontend/src/pages/TermPlanner/OptionsHeader/OptionsHeader.tsx index a2b682227..5365a2589 100644 --- a/frontend/src/pages/TermPlanner/OptionsHeader/OptionsHeader.tsx +++ b/frontend/src/pages/TermPlanner/OptionsHeader/OptionsHeader.tsx @@ -20,6 +20,7 @@ import HelpMenu from '../HelpMenu/HelpMenu'; import ImportPlannerMenu from '../ImportPlannerMenu'; import SettingsMenu from '../SettingsMenu'; import { isPlannerEmpty } from '../utils'; +import ValidateCtfButton from '../ValidateCtfButton/ValidateCtfButton'; import S from './styles'; // Used for tippy stylings import 'tippy.js/dist/tippy.css'; @@ -94,6 +95,11 @@ const OptionsHeader = ({ plannerRef }: Props) => { + +
+ +
+
{!isPlannerEmpty(years) && ( diff --git a/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx b/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx new file mode 100644 index 000000000..b8a742ae4 --- /dev/null +++ b/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import axios from 'axios'; +import prepareCoursesForValidationPayload from 'utils/prepareCoursesForValidationPayload'; +import { RootState } from 'config/store'; +import CS from '../common/styles'; + +const ValidateCtfButton = () => { + const planner = useSelector((state: RootState) => state.planner); + const degree = useSelector((state: RootState) => state.degree); + + const validateCtf = () => { + // TODO: Call this async and disaplay output + axios.post('/ctf/validateCtf/', prepareCoursesForValidationPayload(planner, degree, false)); + }; + + return Validate CTF; +}; + +export default ValidateCtfButton; diff --git a/frontend/src/pages/TermPlanner/ValidateCtfButton/index.ts b/frontend/src/pages/TermPlanner/ValidateCtfButton/index.ts new file mode 100644 index 000000000..413e16ef4 --- /dev/null +++ b/frontend/src/pages/TermPlanner/ValidateCtfButton/index.ts @@ -0,0 +1,3 @@ +import ValidateCtfButton from './ValidateCtfButton'; + +export default ValidateCtfButton; From 48d13ae2124237fe3571a399a5ded0b97e1a39c7 Mon Sep 17 00:00:00 2001 From: Bahnschrift Date: Tue, 19 Sep 2023 18:23:59 +1000 Subject: [PATCH 03/20] Forgot to remove these --- backend/server/routers/ctf.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index 429472363..139b29a21 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -37,15 +37,6 @@ def degree(data: PlannerData) -> bool: return data.program == "3778" -# Note: Didn't make this one of the requirements so not included -def major(data: PlannerData) -> bool: - return "COMPA1" in data.specialisations - - -def math_minor(data: PlannerData) -> bool: - return "MATHC2" in data.specialisations - - def all_courses(data: PlannerData) -> set[str]: courses = set() for year in data.plan: From 27f79905d62e15b791683df03b30ca7bf57ddadf Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Wed, 20 Sep 2023 01:03:04 +1000 Subject: [PATCH 04/20] in the arena --- backend/server/routers/ctf.py | 121 +++++++++++++++++++++++++++------- 1 file changed, 99 insertions(+), 22 deletions(-) diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index 139b29a21..b739a4a55 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -1,3 +1,4 @@ +from typing import Callable from pydantic import BaseModel from fastapi import APIRouter, HTTPException @@ -11,6 +12,27 @@ router = APIRouter( prefix="/ctf", tags=["ctf"], responses={404: {"description": "Not found"}} ) +""" +# Hard Requirements for valid submission +- The Degree Plan Must be Valid and have 100% Progression +- Cannot take any discontinued courses +- 3 yr CS Degree - 3778 +- Start at 2024 +- Must pick a math minor +- There must be no warnings in the degree plan. This includes warnings for marks. + +# Requirements +1. Must take a summer COMP course +2. Must take every single comp course with extended in the name +3. For odd terms, the sum of your course codes must be odd. Summer terms are excluded from this. +4. In even terms, it must be even. Summer terms are excluded from this. +5. Must get full marks in COMP1511 +6. The course codes of Gen-Eds can't sum to more than 2200. +7. Gen-Eds must be from different faculties. +8. Must take two courses from different faculties that have the same course code. +10. In your `N`-th year, you can only take `N + 1` math courses +11. There must not be more than `6` occurrences of the number 3 in the entire degree. +""" # Requirements # - Complete your degree plan (I think this is in the frontend?) @@ -38,13 +60,12 @@ def degree(data: PlannerData) -> bool: def all_courses(data: PlannerData) -> set[str]: - courses = set() - for year in data.plan: - for term in year: - for course in term: - courses.add(course) - - return courses + return { + course + for year in data.plan + for term in year + for course in term + } def code(course: str) -> int: @@ -64,23 +85,50 @@ def gen_eds(courses: set[str]) -> set[str]: 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 - # NOTE: Can't check start year from this ) def extended_courses(data: PlannerData) -> bool: - return {"COMP3821", "COMP3891", "COMP6841", "COMP6843"} - all_courses(data) == set() + return { + "COMP3821", + "COMP3891", + "COMP6841", + "COMP6843" + }.issubset(all_courses(data)) def summer_course(data: PlannerData) -> bool: - return any(course.startswith("COMP") for year in data.plan for course in year[0]) + return any( + course.startswith("COMP") + for year in data.plan + for course in year[0] + ) + + +def term_sums_even(data: PlannerData) -> bool: + for year in data.plan: + for i, term in enumerate(year[1:], 1): # Exclude summer term + if sum(map(code, term.keys())) % 2 != i % 2: + return False + return True +# TODO +def term_sums_odd(data: PlannerData) -> bool: + for year in data.plan: + for i, term in enumerate(year[1:], 1): # Exclude summer term + if sum(map(code, term.keys())) % 2 != i % 2: + return False + return True + def term_sums(data: PlannerData) -> bool: for year in data.plan: for i, term in enumerate(year[1:], 1): # Exclude summer term @@ -90,7 +138,7 @@ def term_sums(data: PlannerData) -> bool: return True -def COMP1511_marks(data: PlannerData) -> bool: +def comp1511_marks(data: PlannerData) -> bool: for year in data.plan: for term in year: for course in term: @@ -127,16 +175,45 @@ def math_limit(data: PlannerData) -> bool: return True +""" +1. Must take a summer COMP course +2. Must take every single comp course with extended in the name +3. For odd terms, the sum of your course codes must be odd. Summer terms are excluded from this. +4. In even terms, it must be even. Summer terms are excluded from this. +5. Must get full marks in COMP1511 +6. The course codes of Gen-Eds can't sum to more than 2200. +7. Gen-Eds must be from different faculties. +8. Must take two courses from different faculties that have the same course code. +9. +10. In your `N`-th year, you can only take `N + 1` math courses +11. There must not be more than `6` occurrences of the number 3 in the entire degree. + +""" +requirements: dict[int, Callable[[PlannerData], bool]] = { + 0: hard_requirements, + 1: summer_course, + 2: extended_courses, + 3: term_sums_even, # check + 4: term_sums_odd, # check + 5: comp1511_marks, + 6: gen_ed_sum, + 7: gen_ed_faculty, + 8: same_code_diff_faculty, + 9: lambda _: True, + 10: math_limit, + 11: lambda _: True, +} @router.post("/validateCtf/") -def validate_ctf(data: PlannerData): - print(data) - print(f"{hard_requirements(data) = }") - print(f"{extended_courses(data) = }") - print(f"{summer_course(data) = }") - print(f"{term_sums(data) = }") - print(f"{COMP1511_marks(data) = }") - print(f"{gen_ed_sum(data) = }") - print(f"{gen_ed_faculty(data) = }") - print(f"{same_code_diff_faculty(data) = }") - print(f"{math_limit(data) = }") +def validate_ctf(data : PlannerData): + for req_num, fn in requirements.items(): + if not fn(data): + return {"valid": False, "req_num": req_num} + return {"valid": True, "req_num": -1} + +@router.post("/test") +def test_do_validate(data: PlannerData): + return { + "valid": True, + "req_num": -1, + } From 2994427f33f5446ac20d8e3793b749ba1f1efe89 Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Wed, 20 Sep 2023 02:23:11 +1000 Subject: [PATCH 05/20] fix split of even / odd terms --- backend/server/routers/ctf.py | 38 +++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index b739a4a55..353062fc3 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -68,7 +68,7 @@ def all_courses(data: PlannerData) -> set[str]: } -def code(course: str) -> int: +def get_code(course: str) -> int: return int(course[4:]) @@ -113,26 +113,39 @@ def summer_course(data: PlannerData) -> bool: ) + def term_sums_even(data: PlannerData) -> bool: - for year in data.plan: - for i, term in enumerate(year[1:], 1): # Exclude summer term - if sum(map(code, term.keys())) % 2 != i % 2: + 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: - for year in data.plan: - for i, term in enumerate(year[1:], 1): # Exclude summer term - if sum(map(code, term.keys())) % 2 != i % 2: + is_odd: Callable[[int], bool] = lambda x: x % 2 == 1 + print("Checking odd") + for y, year in enumerate(data.plan[::2]): + # Exclude summer term + even terms + for i, term in enumerate(year[1::2], 2): + term_sum = sum(map(get_code, term.keys())) + print(f"{y}T{i} sum: {sum(map(get_code, term.keys()))}") + if not is_odd(sum(map(get_code, term.keys()))): + print("failed: ", term) return False return True def term_sums(data: PlannerData) -> bool: for year in data.plan: - for i, term in enumerate(year[1:], 1): # Exclude summer term - if sum(map(code, term.keys())) % 2 != i % 2: + for i, term in enumerate(year[2::2], 1): + if sum(map(get_code, term.keys())) % 2 != i % 2: return False return True @@ -150,7 +163,7 @@ def comp1511_marks(data: PlannerData) -> bool: def gen_ed_sum(data: PlannerData) -> bool: - return sum(map(code, gen_eds(all_courses(data)))) <= 2200 + return sum(map(get_code, gen_eds(all_courses(data)))) <= 2200 def gen_ed_faculty(data: PlannerData) -> bool: @@ -159,7 +172,7 @@ def gen_ed_faculty(data: PlannerData) -> bool: def same_code_diff_faculty(data: PlannerData) -> bool: - codes = list(map(code, all_courses(data))) + codes = list(map(get_code, all_courses(data))) return len(codes) != len( set(codes) ) # Can't have duplicate of a course since it's a set @@ -206,9 +219,12 @@ def math_limit(data: PlannerData) -> bool: @router.post("/validateCtf/") def validate_ctf(data : PlannerData): + print("\n"*3, "HERE================") for req_num, fn in requirements.items(): if not fn(data): + print("Not ok: ", req_num) return {"valid": False, "req_num": req_num} + print("Ok: ", req_num) return {"valid": True, "req_num": -1} @router.post("/test") From 4c2be4115a97b9b3990b52f9b61e1579736a6560 Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Wed, 20 Sep 2023 02:31:45 +1000 Subject: [PATCH 06/20] add the sixes limit --- backend/server/routers/ctf.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index 353062fc3..2d5447007 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -72,7 +72,7 @@ def get_code(course: str) -> int: return int(course[4:]) -def faculty(course: str) -> str: +def get_faculty(course: str) -> str: return course[:4] @@ -167,15 +167,16 @@ def gen_ed_sum(data: PlannerData) -> bool: def gen_ed_faculty(data: PlannerData) -> bool: - gen_eds_facs = list(map(faculty, gen_eds(all_courses(data)))) + 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: 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) - ) # Can't have duplicate of a course since it's a set + ) def math_limit(data: PlannerData) -> bool: @@ -188,6 +189,10 @@ def math_limit(data: PlannerData) -> bool: return True +def six_threes_limit(data: PlannerData) -> bool: + all_codes = "".join(str(get_code(course)) for course in all_courses(data)) + return all_codes.count("3") <= 6 + """ 1. Must take a summer COMP course 2. Must take every single comp course with extended in the name @@ -206,15 +211,15 @@ def math_limit(data: PlannerData) -> bool: 0: hard_requirements, 1: summer_course, 2: extended_courses, - 3: term_sums_even, # check - 4: term_sums_odd, # check + 3: term_sums_even, + 4: term_sums_odd, 5: comp1511_marks, 6: gen_ed_sum, 7: gen_ed_faculty, 8: same_code_diff_faculty, 9: lambda _: True, 10: math_limit, - 11: lambda _: True, + 11: six_threes_limit, } @router.post("/validateCtf/") From f2ac98e2e6f81f3cbd8efd59c5886d1fcfe2d938 Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Wed, 20 Sep 2023 02:49:21 +1000 Subject: [PATCH 07/20] comp1531 year3 --- backend/server/routers/ctf.py | 73 +++++++++++++---------------------- 1 file changed, 27 insertions(+), 46 deletions(-) diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index 2d5447007..f97c9192b 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -1,4 +1,4 @@ -from typing import Callable +from typing import Callable, Tuple from pydantic import BaseModel from fastapi import APIRouter, HTTPException @@ -34,27 +34,6 @@ 11. There must not be more than `6` occurrences of the number 3 in the entire degree. """ -# Requirements -# - Complete your degree plan (I think this is in the frontend?) -# - Must take every single comp course with extended in the name (done) -# - Must take a summer COMP course (done) -# - For odd terms, the sum of your course codes must be odd. (done) -# In even terms, it must be even. -# Summer terms are excluded from this. -# - Must get full marks in COMP1511 (done) -# - Gen-Eds can't sum to more than 2200. (done) -# - Gen-Eds must be from different faculties. (done) -# - Must take two courses from different faculties that have the same course code. (done) -# - Must not have any warnings - including marks! (should probably do on frontend) -# - May take at most N + 1 math courses in the Nth year (done) - -# Hard Requirements -# - Cannot take any discontinued courses (hard to check) -# - 3 yr CS Degree - 3778 -# - Must pick a math minor -# - Start at 2024 (can't check) - - def degree(data: PlannerData) -> bool: return data.program == "3778" @@ -193,6 +172,15 @@ def six_threes_limit(data: PlannerData) -> bool: 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: + third_year = data.plan[2] + for term in third_year: + for course in term: + if course == "COMP1531": + return True + + return False + """ 1. Must take a summer COMP course 2. Must take every single comp course with extended in the name @@ -202,39 +190,32 @@ def six_threes_limit(data: PlannerData) -> bool: 6. The course codes of Gen-Eds can't sum to more than 2200. 7. Gen-Eds must be from different faculties. 8. Must take two courses from different faculties that have the same course code. -9. +9. COMP1531 must be taken in your third year. 10. In your `N`-th year, you can only take `N + 1` math courses 11. There must not be more than `6` occurrences of the number 3 in the entire degree. - """ -requirements: dict[int, Callable[[PlannerData], bool]] = { - 0: hard_requirements, - 1: summer_course, - 2: extended_courses, - 3: term_sums_even, - 4: term_sums_odd, - 5: comp1511_marks, - 6: gen_ed_sum, - 7: gen_ed_faculty, - 8: same_code_diff_faculty, - 9: lambda _: True, - 10: math_limit, - 11: six_threes_limit, +requirements: dict[int, Tuple[Callable[[PlannerData], bool], str]] = { + 0: (hard_requirements, "You have not passed the hard requirement to get your submission validated."), + 1: (summer_course, "You must complete at least one course in the summer term."), + 2: (extended_courses, "You must complete ALL COMP courses with extended in the name that have not been discontinued. Hint: there are 4."), + 3: (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."), + 4: (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."), + 5: (comp1511_marks, "You must achieve a mark of 100 in COMP1511."), + 6: (gen_ed_sum, "The sum of your Gen-Ed course codes must not exceed 2200."), + 7: (gen_ed_faculty, "Each Gen-Ed unit that you take must be from a different faculty"), + 8: (same_code_diff_faculty, "You must take two courses from different faculties that have the same course code."), + 9: (comp1531_third_year, "COMP1531 must be taken in your third year"), + 10: (math_limit, "In your N-th year, you can only take N + 1 math courses."), + 11: (six_threes_limit, "In all your course codes, there can be at most 6 occurrences of the number 3"), } @router.post("/validateCtf/") def validate_ctf(data : PlannerData): print("\n"*3, "HERE================") - for req_num, fn in requirements.items(): + for req_num, (fn, msg) in requirements.items(): if not fn(data): print("Not ok: ", req_num) - return {"valid": False, "req_num": req_num} + return {"valid": False, "req_num": req_num, "message": msg} print("Ok: ", req_num) - return {"valid": True, "req_num": -1} + return {"valid": True, "req_num": -1, "message": ""} -@router.post("/test") -def test_do_validate(data: PlannerData): - return { - "valid": True, - "req_num": -1, - } From dc5059281ca08fe0343ccdc6c0ccc338928f7661 Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Wed, 20 Sep 2023 03:07:25 +1000 Subject: [PATCH 08/20] micheal is bikesheddking my tuples (litrly 1984 --- backend/server/routers/ctf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index f97c9192b..39f195254 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -1,4 +1,4 @@ -from typing import Callable, Tuple +from typing import Callable from pydantic import BaseModel from fastapi import APIRouter, HTTPException @@ -194,7 +194,7 @@ def comp1531_third_year(data: PlannerData) -> bool: 10. In your `N`-th year, you can only take `N + 1` math courses 11. There must not be more than `6` occurrences of the number 3 in the entire degree. """ -requirements: dict[int, Tuple[Callable[[PlannerData], bool], str]] = { +requirements: dict[int, tuple[Callable[[PlannerData], bool], str]] = { 0: (hard_requirements, "You have not passed the hard requirement to get your submission validated."), 1: (summer_course, "You must complete at least one course in the summer term."), 2: (extended_courses, "You must complete ALL COMP courses with extended in the name that have not been discontinued. Hint: there are 4."), From bb470889f0d4b9803cadae3f0572053dd5d1918a Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Thu, 21 Sep 2023 15:04:25 +1000 Subject: [PATCH 09/20] add modal style --- .../ValidateCtfButton/ValidateCtfButton.tsx | 20 +++++++++++++- .../TermPlanner/ValidateCtfButton/styles.ts | 26 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 frontend/src/pages/TermPlanner/ValidateCtfButton/styles.ts diff --git a/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx b/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx index b8a742ae4..e2a27504e 100644 --- a/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx +++ b/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx @@ -17,4 +17,22 @@ const ValidateCtfButton = () => { return Validate CTF; }; -export default ValidateCtfButton; +const Modal = styled(antdModal)` + .ant-modal-footer { + border: 0px; + } + .ant-modal-header { + border-color: ${({ theme }) => theme.editMark.borderColorHeader}; + } + .ant-btn-default { + background-color: ${({ theme }) => theme.editMark.backgroundColor}; + border-color: ${({ theme }) => theme.editMark.borderColor}; + color: ${({ theme }) => theme.editMark.color}; + &:hover { + background-color: ${({ theme }) => theme.editMark.backgroundColorHover}; + border-color: ${({ theme }) => theme.purplePrimary}; + } + } +`; + +export default { EditMarkWrapper, LetterGradeWrapper, Input, Modal }; diff --git a/frontend/src/pages/TermPlanner/ValidateCtfButton/styles.ts b/frontend/src/pages/TermPlanner/ValidateCtfButton/styles.ts new file mode 100644 index 000000000..9df8461c3 --- /dev/null +++ b/frontend/src/pages/TermPlanner/ValidateCtfButton/styles.ts @@ -0,0 +1,26 @@ +import { Input as antdInput, Modal as antdModal } from 'antd'; +import styled from 'styled-components'; + +// NOTE: Very hacky way to override modal styling +// THIS is also just duplicate code of the +// EditMarksModal styling. +// A better programmer might refactor this +const Modal = styled(antdModal)` + .ant-modal-footer { + border: 0px; + } + .ant-modal-header { + border-color: ${({ theme }) => theme.editMark.borderColorHeader}; + } + .ant-btn-default { + background-color: ${({ theme }) => theme.editMark.backgroundColor}; + border-color: ${({ theme }) => theme.editMark.borderColor}; + color: ${({ theme }) => theme.editMark.color}; + &:hover { + background-color: ${({ theme }) => theme.editMark.backgroundColorHover}; + border-color: ${({ theme }) => theme.purplePrimary}; + } + } +`; + +export default { Modal }; From fb5d5af8e41d8dc341064f1b6f0ad652ff79819d Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Thu, 21 Sep 2023 15:11:19 +1000 Subject: [PATCH 10/20] add the MODAL initial --- .../ValidateCtfButton/ValidateCtfButton.tsx | 35 ++++++++----------- .../TermPlanner/ValidateCtfButton/styles.ts | 2 +- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx b/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx index e2a27504e..ccc80bf6b 100644 --- a/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx +++ b/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx @@ -1,38 +1,33 @@ +/* eslint-disable */ import React from 'react'; import { useSelector } from 'react-redux'; import axios from 'axios'; import prepareCoursesForValidationPayload from 'utils/prepareCoursesForValidationPayload'; import { RootState } from 'config/store'; import CS from '../common/styles'; +import S from './styles'; const ValidateCtfButton = () => { const planner = useSelector((state: RootState) => state.planner); const degree = useSelector((state: RootState) => state.degree); + const [open, setOpen] = React.useState(false); const validateCtf = () => { // TODO: Call this async and disaplay output + setOpen(true); axios.post('/ctf/validateCtf/', prepareCoursesForValidationPayload(planner, degree, false)); }; - return Validate CTF; + return (<> + Validate CTF + setOpen(false)} + onCancel={() => setOpen(false)} + width="400px" + /> + ); }; -const Modal = styled(antdModal)` - .ant-modal-footer { - border: 0px; - } - .ant-modal-header { - border-color: ${({ theme }) => theme.editMark.borderColorHeader}; - } - .ant-btn-default { - background-color: ${({ theme }) => theme.editMark.backgroundColor}; - border-color: ${({ theme }) => theme.editMark.borderColor}; - color: ${({ theme }) => theme.editMark.color}; - &:hover { - background-color: ${({ theme }) => theme.editMark.backgroundColorHover}; - border-color: ${({ theme }) => theme.purplePrimary}; - } - } -`; - -export default { EditMarkWrapper, LetterGradeWrapper, Input, Modal }; +export default ValidateCtfButton; diff --git a/frontend/src/pages/TermPlanner/ValidateCtfButton/styles.ts b/frontend/src/pages/TermPlanner/ValidateCtfButton/styles.ts index 9df8461c3..2f300e114 100644 --- a/frontend/src/pages/TermPlanner/ValidateCtfButton/styles.ts +++ b/frontend/src/pages/TermPlanner/ValidateCtfButton/styles.ts @@ -1,4 +1,4 @@ -import { Input as antdInput, Modal as antdModal } from 'antd'; +import { Modal as antdModal } from 'antd'; import styled from 'styled-components'; // NOTE: Very hacky way to override modal styling From aa8428afbc832946f3b836328f7866687de04251 Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Thu, 21 Sep 2023 21:24:00 +1000 Subject: [PATCH 11/20] fk --- backend/server/routers/ctf.py | 13 +++++----- .../ValidateCtfButton/ValidateCtfButton.tsx | 25 ++++++++++++++++--- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index 39f195254..8a8e490ff 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -69,9 +69,9 @@ def hard_requirements(data: PlannerData) -> bool: # including validity of the program return ( data.program == "3778" - and "COMPA1" in data.specialisations - and "MATHC2" in data.specialisations - and len(data.plan) == 3 + # and "COMPA1" in data.specialisations + # and "MATHC2" in data.specialisations + # and len(data.plan) == 3 ) @@ -211,11 +211,10 @@ def comp1531_third_year(data: PlannerData) -> bool: @router.post("/validateCtf/") def validate_ctf(data : PlannerData): - print("\n"*3, "HERE================") + passed = [] for req_num, (fn, msg) in requirements.items(): if not fn(data): - print("Not ok: ", req_num) - return {"valid": False, "req_num": req_num, "message": msg} + return {"valid": False, "failed": req_num, "message": msg} print("Ok: ", req_num) - return {"valid": True, "req_num": -1, "message": ""} + return {"valid": True, "failed": -1, "message": ""} diff --git a/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx b/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx index ccc80bf6b..1334fa391 100644 --- a/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx +++ b/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx @@ -7,15 +7,30 @@ import { RootState } from 'config/store'; import CS from '../common/styles'; import S from './styles'; +type CtfResult = { + valid: boolean, + failed: number, + message: string, +} + +const loadingResult: CtfResult = { + valid: false, + failed: 0, + message: 'Loading...', +}; + const ValidateCtfButton = () => { const planner = useSelector((state: RootState) => state.planner); const degree = useSelector((state: RootState) => state.degree); const [open, setOpen] = React.useState(false); + const [result, setResult] = React.useState(loadingResult); - const validateCtf = () => { + const validateCtf = async () => { // TODO: Call this async and disaplay output setOpen(true); - axios.post('/ctf/validateCtf/', prepareCoursesForValidationPayload(planner, degree, false)); + const res = await axios.post('/ctf/validateCtf/', prepareCoursesForValidationPayload(planner, degree, false)); + setResult(res.data); + console.log(res.data); }; return (<> @@ -26,7 +41,11 @@ const ValidateCtfButton = () => { onOk={() => setOpen(false)} onCancel={() => setOpen(false)} width="400px" - /> + > + valid: {result.valid ? 'true' : 'false'}
+ failed: {result.failed}
+ message: {result.message}
+
); }; From e6ce4f4f19d7524ad81125c56a6e91b4c9a43254 Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Sat, 23 Sep 2023 01:31:54 +1000 Subject: [PATCH 12/20] actually fix modal but fr this time - just need to change messages --- backend/server/routers/ctf.py | 51 ++++++++---- .../ValidateCtfButton/ValidateCtfButton.tsx | 83 ++++++++++++++----- 2 files changed, 96 insertions(+), 38 deletions(-) diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index 8a8e490ff..b65c5a33a 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -194,27 +194,42 @@ def comp1531_third_year(data: PlannerData) -> bool: 10. In your `N`-th year, you can only take `N + 1` math courses 11. There must not be more than `6` occurrences of the number 3 in the entire degree. """ -requirements: dict[int, tuple[Callable[[PlannerData], bool], str]] = { - 0: (hard_requirements, "You have not passed the hard requirement to get your submission validated."), - 1: (summer_course, "You must complete at least one course in the summer term."), - 2: (extended_courses, "You must complete ALL COMP courses with extended in the name that have not been discontinued. Hint: there are 4."), - 3: (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."), - 4: (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."), - 5: (comp1511_marks, "You must achieve a mark of 100 in COMP1511."), - 6: (gen_ed_sum, "The sum of your Gen-Ed course codes must not exceed 2200."), - 7: (gen_ed_faculty, "Each Gen-Ed unit that you take must be from a different faculty"), - 8: (same_code_diff_faculty, "You must take two courses from different faculties that have the same course code."), - 9: (comp1531_third_year, "COMP1531 must be taken in your third year"), - 10: (math_limit, "In your N-th year, you can only take N + 1 math courses."), - 11: (six_threes_limit, "In all your course codes, there can be at most 6 occurrences of the number 3"), -} +requirements: list[tuple[Callable[[PlannerData], bool], str]] = [ + # Section 0 + (hard_requirements, "You have not passed the hard requirement to get your submission validated."), + # Section 1 - Some requirements + (summer_course, "You must complete at least one course in the summer term."), + (comp1511_marks, "You must achieve a mark of 100 in COMP1511."), + # Section 2 - + (extended_courses, "You must complete ALL COMP courses with extended in the name that have not been discontinued. Hint: there are 4."), + (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."), + (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."), + (gen_ed_sum, "The sum of your Gen-Ed course codes must not exceed 2200."), + # Section 3 - wtf? + (gen_ed_faculty, "Each Gen-Ed unit that you take must be from a different faculty"), + (same_code_diff_faculty, "You must take two courses from different faculties that have the same course code."), + (comp1531_third_year, "COMP1531 must be taken in your third year"), + (math_limit, "In your N-th year, you can only take N + 1 math courses."), + (six_threes_limit, "In all your course codes, there can be at most 6 occurrences of the number 3"), +] @router.post("/validateCtf/") def validate_ctf(data : PlannerData): - passed = [] - for req_num, (fn, msg) in requirements.items(): + passed: list[str] = [] + for req_num, (fn, msg) in enumerate(requirements): if not fn(data): - return {"valid": False, "failed": req_num, "message": msg} + return { + "valid": False, + "passed": passed, + "failed": req_num, + "message": msg + } + passed.append(msg) print("Ok: ", req_num) - return {"valid": True, "failed": -1, "message": ""} + return { + "valid": True, + "failed": -1, + "passed": passed, + "message": "Congratulations! You have passed all the requirements for the CTF." + } diff --git a/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx b/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx index 1334fa391..8d9e032d4 100644 --- a/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx +++ b/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx @@ -6,19 +6,37 @@ import prepareCoursesForValidationPayload from 'utils/prepareCoursesForValidatio import { RootState } from 'config/store'; import CS from '../common/styles'; import S from './styles'; +import { Typography } from 'antd'; +import styled from 'styled-components'; type CtfResult = { - valid: boolean, - failed: number, - message: string, -} + valid: boolean; + failed: number; + passed: Array; + message: string; +}; + +const { Text, Title } = Typography; + +const TextBlock = styled(Text)` + display: block; + padding: 1em; + color: ${({ theme }) => theme.graph.tabTextColor}; +`; const loadingResult: CtfResult = { valid: false, failed: 0, - message: 'Loading...', + passed: [], + message: 'Loading...' }; +const ModalTitle = styled(Title)` + margin: 0 !important; + color: ${({ theme }) => theme.text} !important; +`; + + const ValidateCtfButton = () => { const planner = useSelector((state: RootState) => state.planner); const degree = useSelector((state: RootState) => state.degree); @@ -28,25 +46,50 @@ const ValidateCtfButton = () => { const validateCtf = async () => { // TODO: Call this async and disaplay output setOpen(true); - const res = await axios.post('/ctf/validateCtf/', prepareCoursesForValidationPayload(planner, degree, false)); + const res = await axios.post( + '/ctf/validateCtf/', + prepareCoursesForValidationPayload(planner, degree, false) + ); setResult(res.data); console.log(res.data); }; - return (<> - Validate CTF - setOpen(false)} - onCancel={() => setOpen(false)} - width="400px" - > - valid: {result.valid ? 'true' : 'false'}
- failed: {result.failed}
- message: {result.message}
-
- ); + return ( + <> + Validate CTF + setOpen(false)} + onCancel={() => setOpen(false)} + width="60%" + > + Passed Challenges + + { +
    + {result.passed.map((challenge, index) => ( + <> +
  1. + {challenge} +
  2. + + ))} +
+ } +
+ + { + result.valid ? ( + {result.message} + ) : ( + Next: {result.message} + ) + } + +
+ + ); }; export default ValidateCtfButton; From 4df4da9ada79330e4e54790f3a9f34a49eb6c312 Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Sun, 24 Sep 2023 02:05:47 +1000 Subject: [PATCH 13/20] re-write the prompts --- backend/server/routers/ctf.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index b65c5a33a..288203952 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -195,21 +195,22 @@ def comp1531_third_year(data: PlannerData) -> bool: 11. There must not be more than `6` occurrences of the number 3 in the entire degree. """ requirements: list[tuple[Callable[[PlannerData], bool], str]] = [ - # Section 0 - (hard_requirements, "You have not passed the hard requirement to get your submission validated."), - # Section 1 - Some requirements - (summer_course, "You must complete at least one course in the summer term."), - (comp1511_marks, "You must achieve a mark of 100 in COMP1511."), - # Section 2 - - (extended_courses, "You must complete ALL COMP courses with extended in the name that have not been discontinued. Hint: there are 4."), + # The following are conditions placed on Ollie's enrolment by the university and + # the providers of their scholarship. + (hard_requirements, "Before you can submit, you must check that you are in a 3 year CS degree and have a math minor"), + (summer_course, "Ollie must take one summer COMP course."), + (comp1511_marks, "To keep their scholarship, Ollie must achieve a mark of 100 in COMP1511."), + (extended_courses, "Ollie must complete ALL COMP courses with extended in the name that have not been discontinued. Hint: there are 4."), + # The following reflect changes made to University Policy after Ollie's enrolment. + (comp1531_third_year, "Unable to find a partner earlier, Ollie must take COMP1531 in their third year."), + (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."), + (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."), + (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."), + # Ollie has joined the number theory club and has developed superstitions about + # certain numbers + (same_code_diff_faculty, "You must take two courses from different faculties that have the same course code."), (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."), (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."), - (gen_ed_sum, "The sum of your Gen-Ed course codes must not exceed 2200."), - # Section 3 - wtf? - (gen_ed_faculty, "Each Gen-Ed unit that you take must be from a different faculty"), - (same_code_diff_faculty, "You must take two courses from different faculties that have the same course code."), - (comp1531_third_year, "COMP1531 must be taken in your third year"), - (math_limit, "In your N-th year, you can only take N + 1 math courses."), (six_threes_limit, "In all your course codes, there can be at most 6 occurrences of the number 3"), ] From 49b58371a45bba1ee0370c6814eb30a6227bb152 Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Tue, 26 Sep 2023 01:26:10 +1000 Subject: [PATCH 14/20] finish CTF (added flags) --- backend/server/routers/ctf.py | 53 +++++++++++-------- .../ValidateCtfButton/ValidateCtfButton.tsx | 4 +- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index 288203952..c0f9bd4a9 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -1,4 +1,4 @@ -from typing import Callable +from typing import Callable, Optional from pydantic import BaseModel from fastapi import APIRouter, HTTPException @@ -32,6 +32,10 @@ 8. Must take two courses from different faculties that have the same course code. 10. In your `N`-th year, you can only take `N + 1` math courses 11. There must not be more than `6` occurrences of the number 3 in the entire degree. + +CTF Challenge: + +Description: """ def degree(data: PlannerData) -> bool: @@ -76,12 +80,13 @@ def hard_requirements(data: PlannerData) -> bool: def extended_courses(data: PlannerData) -> bool: - return { + return len({ "COMP3821", "COMP3891", "COMP6841", "COMP6843" - }.issubset(all_courses(data)) + "COMP6845" + }.intersection(all_courses(data))) >= 4 def summer_course(data: PlannerData) -> bool: @@ -194,43 +199,47 @@ def comp1531_third_year(data: PlannerData) -> bool: 10. In your `N`-th year, you can only take `N + 1` math courses 11. There must not be more than `6` occurrences of the number 3 in the entire degree. """ -requirements: list[tuple[Callable[[PlannerData], bool], str]] = [ - # The following are conditions placed on Ollie's enrolment by the university and - # the providers of their scholarship. - (hard_requirements, "Before you can submit, you must check that you are in a 3 year CS degree and have a math minor"), - (summer_course, "Ollie must take one summer COMP course."), - (comp1511_marks, "To keep their scholarship, Ollie must achieve a mark of 100 in COMP1511."), - (extended_courses, "Ollie must complete ALL COMP courses with extended in the name that have not been discontinued. Hint: there are 4."), - # The following reflect changes made to University Policy after Ollie's enrolment. - (comp1531_third_year, "Unable to find a partner earlier, Ollie must take COMP1531 in their third year."), - (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."), - (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."), - (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."), - # Ollie has joined the number theory club and has developed superstitions about - # certain numbers - (same_code_diff_faculty, "You must take two courses from different faculties that have the same course code."), - (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."), - (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."), - (six_threes_limit, "In all your course codes, there can be at most 6 occurrences of the number 3"), +# (validtor_func, message, Optional) +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): passed: list[str] = [] - for req_num, (fn, msg) in enumerate(requirements): + 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." } diff --git a/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx b/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx index 8d9e032d4..28057109e 100644 --- a/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx +++ b/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx @@ -14,6 +14,7 @@ type CtfResult = { failed: number; passed: Array; message: string; + flags: Array; }; const { Text, Title } = Typography; @@ -28,7 +29,8 @@ const loadingResult: CtfResult = { valid: false, failed: 0, passed: [], - message: 'Loading...' + message: 'Loading...', + flags: [], }; const ModalTitle = styled(Title)` From 67a89de5f31b390f6eb385cfcb0dce9a536e6dcc Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Tue, 26 Sep 2023 01:35:53 +1000 Subject: [PATCH 15/20] fix extension --- backend/server/routers/ctf.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index c0f9bd4a9..ee9a1c052 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -73,20 +73,21 @@ def hard_requirements(data: PlannerData) -> bool: # including validity of the program return ( data.program == "3778" - # and "COMPA1" in data.specialisations - # and "MATHC2" in data.specialisations - # and len(data.plan) == 3 + and "COMPA1" in data.specialisations + and "MATHC2" in data.specialisations + and len(data.plan) == 3 ) def extended_courses(data: PlannerData) -> bool: - return len({ + extended_courses = { "COMP3821", "COMP3891", "COMP6841", "COMP6843" "COMP6845" - }.intersection(all_courses(data))) >= 4 + } + return len(extended_courses & all_courses(data)) >= 3 def summer_course(data: PlannerData) -> bool: From 6a6feb303cf7da96f9e3e34d060eb339d5e1865e Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Tue, 26 Sep 2023 01:41:02 +1000 Subject: [PATCH 16/20] add flags --- .../ValidateCtfButton/ValidateCtfButton.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx b/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx index 28057109e..c2ccc9a77 100644 --- a/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx +++ b/frontend/src/pages/TermPlanner/ValidateCtfButton/ValidateCtfButton.tsx @@ -80,6 +80,18 @@ const ValidateCtfButton = () => { } + Unlocked Flags + +
    + {(result.flags.length > 0) && result.flags.map((flag, index) => ( + <> +
  1. + {flag} +
  2. + + ))} +
+
{ result.valid ? ( From 0ca2ac7e0b6bca4d301edc7be876e3bd446466a0 Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Tue, 26 Sep 2023 01:52:47 +1000 Subject: [PATCH 17/20] pylint --- "\\" | 272 ++++++++++++++++++++++++++++++++++ backend/server/routers/ctf.py | 177 +++++++++++++--------- 2 files changed, 377 insertions(+), 72 deletions(-) create mode 100644 "\\" diff --git "a/\\" "b/\\" new file mode 100644 index 000000000..54798e320 --- /dev/null +++ "b/\\" @@ -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) +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." + } diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index ee9a1c052..b4feae1d8 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -1,48 +1,56 @@ +""" +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 pydantic import BaseModel - -from fastapi import APIRouter, HTTPException -from server.routers.model import ( - ValidCoursesState, - PlannerData, - ValidPlannerData, - ProgramTime, -) + +from fastapi import APIRouter +from server.routers.model import PlannerData router = APIRouter( prefix="/ctf", tags=["ctf"], responses={404: {"description": "Not found"}} ) -""" -# Hard Requirements for valid submission -- The Degree Plan Must be Valid and have 100% Progression -- Cannot take any discontinued courses -- 3 yr CS Degree - 3778 -- Start at 2024 -- Must pick a math minor -- There must be no warnings in the degree plan. This includes warnings for marks. - -# Requirements -1. Must take a summer COMP course -2. Must take every single comp course with extended in the name -3. For odd terms, the sum of your course codes must be odd. Summer terms are excluded from this. -4. In even terms, it must be even. Summer terms are excluded from this. -5. Must get full marks in COMP1511 -6. The course codes of Gen-Eds can't sum to more than 2200. -7. Gen-Eds must be from different faculties. -8. Must take two courses from different faculties that have the same course code. -10. In your `N`-th year, you can only take `N + 1` math courses -11. There must not be more than `6` occurrences of the number 3 in the entire degree. - -CTF Challenge: - -Description: -""" - -def degree(data: PlannerData) -> bool: - return data.program == "3778" - def all_courses(data: PlannerData) -> set[str]: + """ + Returns all courses from a planner + """ return { course for year in data.plan @@ -52,14 +60,25 @@ def all_courses(data: PlannerData) -> set[str]: 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 @@ -80,6 +99,9 @@ def hard_requirements(data: PlannerData) -> bool: def extended_courses(data: PlannerData) -> bool: + """ + Must take atleast 3 courses with extended in the name + """ extended_courses = { "COMP3821", "COMP3891", @@ -91,6 +113,9 @@ def extended_courses(data: PlannerData) -> bool: def summer_course(data: PlannerData) -> bool: + """ + Must take atleast one summer course + """ return any( course.startswith("COMP") for year in data.plan @@ -100,6 +125,9 @@ def summer_course(data: PlannerData) -> bool: 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): @@ -115,28 +143,24 @@ def term_sums_even(data: PlannerData) -> bool: # 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 y, year in enumerate(data.plan[::2]): + for year in data.plan[::2]: # Exclude summer term + even terms - for i, term in enumerate(year[1::2], 2): + for term in year[1::2]: term_sum = sum(map(get_code, term.keys())) - print(f"{y}T{i} sum: {sum(map(get_code, term.keys()))}") - if not is_odd(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: @@ -148,37 +172,57 @@ def comp1511_marks(data: PlannerData) -> bool: 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) - ) + 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")] - ) + 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: @@ -187,20 +231,7 @@ def comp1531_third_year(data: PlannerData) -> bool: return False -""" -1. Must take a summer COMP course -2. Must take every single comp course with extended in the name -3. For odd terms, the sum of your course codes must be odd. Summer terms are excluded from this. -4. In even terms, it must be even. Summer terms are excluded from this. -5. Must get full marks in COMP1511 -6. The course codes of Gen-Eds can't sum to more than 2200. -7. Gen-Eds must be from different faculties. -8. Must take two courses from different faculties that have the same course code. -9. COMP1531 must be taken in your third year. -10. In your `N`-th year, you can only take `N + 1` math courses -11. There must not be more than `6` occurrences of the number 3 in the entire degree. -""" -# (validtor_func, message, Optional) +# (validator_func, message, Optional) 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), @@ -221,6 +252,9 @@ def comp1531_third_year(data: PlannerData) -> bool: @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): @@ -243,4 +277,3 @@ def validate_ctf(data : PlannerData): "flags": flags, "message": "Congratulations! You have passed all the requirements for the CTF." } - From fbb8391cb97fbaf1db134a5b03f484c1638aeb85 Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Tue, 26 Sep 2023 01:53:11 +1000 Subject: [PATCH 18/20] fuck --- "\\" | 272 ----------------------------------------------------------- 1 file changed, 272 deletions(-) delete mode 100644 "\\" diff --git "a/\\" "b/\\" deleted file mode 100644 index 54798e320..000000000 --- "a/\\" +++ /dev/null @@ -1,272 +0,0 @@ -""" -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) -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." - } From 21fb69aec108fb4a471429c10325f003aec009c1 Mon Sep 17 00:00:00 2001 From: imagine-hussain Date: Tue, 26 Sep 2023 02:07:16 +1000 Subject: [PATCH 19/20] add levelup to flag --- backend/server/routers/ctf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index b4feae1d8..44cfa0518 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -237,17 +237,17 @@ def comp1531_third_year(data: PlannerData) -> bool: (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"), + (extended_courses, "Ollie must complete FOUR 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.", "i<3TryMesters"), + (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", "CU1Tur3d"), + (six_threes_limit, "In all your course codes, there can be at most 6 occurrences of the number 3", "levelup{CU1Tur3d}"), ] @router.post("/validateCtf/") From 2e08d2bfb615ff4c4b7722423445e09c3c9e778a Mon Sep 17 00:00:00 2001 From: Leonardo Fan Date: Tue, 26 Sep 2023 02:51:27 +1000 Subject: [PATCH 20/20] edit description and extended to >= three courses --- backend/server/routers/ctf.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/server/routers/ctf.py b/backend/server/routers/ctf.py index 44cfa0518..378103f9f 100644 --- a/backend/server/routers/ctf.py +++ b/backend/server/routers/ctf.py @@ -14,6 +14,10 @@ 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. @@ -237,7 +241,7 @@ def comp1531_third_year(data: PlannerData) -> bool: (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.", "levelup{mVd3_1t_2_un1}"), + (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),