From d2e40afae5c700e4c4bd13b973af5013bb6eda0f Mon Sep 17 00:00:00 2001 From: Sean Boettger Date: Wed, 11 Feb 2026 06:53:04 +1100 Subject: [PATCH 1/2] feat: clang-query concept usage checker script + ruleset --- .../concept_usage/check_concept_usage.py | 188 ++++++++++++++++++ app/models/concept_usage/rules.json | 182 +++++++++++++++++ 2 files changed, 370 insertions(+) create mode 100644 app/models/concept_usage/check_concept_usage.py create mode 100644 app/models/concept_usage/rules.json diff --git a/app/models/concept_usage/check_concept_usage.py b/app/models/concept_usage/check_concept_usage.py new file mode 100644 index 0000000000..bf5a0b1ea7 --- /dev/null +++ b/app/models/concept_usage/check_concept_usage.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 + +import json +import subprocess +import sys +from dataclasses import dataclass +from typing import Dict, List, Set +import re +import traceback +import os.path + +# ==================== Constants ====================== +RULES_PATH = "rules.json" + +# Return codes +SUCCESS = 0 +CODE_HAD_ISSUES = 32 +CODE_CHECKING_FAILED = 64 + +# Removes all output except "matched" messages +# No user code should be visible in the output +CLANG_QUERY_PREAMBLE = """\ +set traversal IgnoreUnlessSpelledInSource +disable output diag +disable output print +disable output detailed-ast +disable output dump +""" + +# ==================== Clang Query Utils ====================== + +# Run clang-query and return the result + +# Note: it deliberately ignores any stderr output, +# since we can't guarantee student's code will parse perfectly +# on our system (they could have their own include file, etc). +# Clang does a best effort parse in these cases, seems acceptable. + +# Syntax errors for the `match` queries are handled later +# when the result is sanity checked, by comparing +# the count of successful match results with the expected number + +# clang-query return code isn't useful either from what I can tell +def clang_query(source_file: str, query: str) -> str: + full_query = CLANG_QUERY_PREAMBLE + query + "\n" + + result = subprocess.run( + ["clang-query", source_file, "--"], + input=full_query, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + ) + + return result.stdout + +# Extract "0 matches, 1 match, etc" +def clang_query_extract_matches(output: List[str]) -> List[int]: + pattern = re.compile(r'(\d+)\s+match(?:es)?') + extracted_numbers = [] + + for line in output: + match = pattern.search(line) + if match: + number = int(match.group(1)) + extracted_numbers.append(number) + + return (extracted_numbers) + +# ==================== Rule Loading ====================== + +@dataclass +class MatchQuerySet: + queries: List[str] + id: str + description: str + +def load_rules(path: str) -> Dict: + with open(path, "r") as f: + return json.load(f) + +# Recursively include all the MatchQuerySets for the chosen ruleset (e.g chapter) +def resolve_rules( + rules: Dict, + ruleset: str +) -> List[MatchQuerySet]: + chapter = rules[ruleset] + matches: List[MatchQuerySet] = [] + + for include in chapter.get("includes", []): + matches.extend(resolve_rules(rules, include)) + + for rule in chapter.get("rules", []): + matches.append(MatchQuerySet(**rule)) + + return matches + +# ==================== Main Logic ====================== + +def check_usage(source_file: str, match_query_sets: List[MatchQuerySet]) -> None: + # Run every match query in one go + # We'll then split it up afterwards + + # Start by joining them all up + combined_query = "\n".join(["\n".join([f"match {y}" for y in x.queries]) for x in match_query_sets]) + + # Get the results from clang-query + output = clang_query(source_file, combined_query) + # Find the number of matches for each query + match_counts = clang_query_extract_matches(output.split("\n")) + # There should be the same number as there were match statements + expected_match_length = sum([len(x.queries) for x in match_query_sets]) + + if len(match_counts) != expected_match_length: + # If not, something is wrong, abort checking + print(f"Error: Mismatch between expected result count and clang-query output.", file=sys.stderr) + print(f"Expected {expected_match_length} match counts, got {len(match_counts)}.", file=sys.stderr) + print("There is likely a syntax error in the queries - see output below.", file=sys.stderr) + print(f"\n==== clang-query Input ====\n{combined_query}\n============================", file=sys.stderr) + print(f"\n==== clang-query Output ====\n{output}\n============================", file=sys.stderr) + sys.exit(CODE_CHECKING_FAILED) + + # Now we can step through and pair up each match count with its associated MatchQuerySet + idx = 0 + matched = [] + for matchQuerySet in match_query_sets: + total_matches = sum(match_counts[idx : idx + len(matchQuerySet.queries)]) + idx += len(matchQuerySet.queries) + + if total_matches > 0: + matched.append(matchQuerySet) + + return matched + +# ==================== Output Formatting ====================== + +def format_output_ids(matched: List[MatchQuerySet]) -> str: + return ", ".join([x.id for x in matched]) + +def format_output_descriptions(matched: List[MatchQuerySet]) -> str: + return "\n".join([x.description for x in matched]) + +# =========== Main Real (extracted so it can be tested...) =========== + +def main_inner(source_file: str, ruleset: str, output_style: str, rules_path: str) -> str: + rules = load_rules(rules_path) + match_query_sets = resolve_rules(rules, ruleset) + + matches = check_usage(source_file, match_query_sets) + + if output_style == "id": + return format_output_ids(matches) + else: + return format_output_descriptions(matches) + +# ==================== Command Line Usage ====================== + +def main() -> None: + try: + if len(sys.argv) != 4: + print(f"Usage: {sys.argv[0]} ") + sys.exit(CODE_CHECKING_FAILED) + + source_file = sys.argv[1] + ruleset = sys.argv[2] + output_style = sys.argv[3] + + # check inputs + assert os.path.isfile(RULES_PATH), f"Rule set file doesn't exist {RULES_PATH}" + assert os.path.isfile(source_file), f"Input source file doesn't exist: {source_file}" + assert output_style=="id" or output_style == "desc", f"Invalid output-style {output_style}" + + output = main_inner(source_file, ruleset, output_style, RULES_PATH) + + # Print the actual result on stdout + print(output) + + if len(output) > 0: + sys.exit(CODE_HAD_ISSUES) + + except Exception as e: + print("Error:", traceback.format_exc(), file=sys.stderr) + sys.exit(CODE_CHECKING_FAILED) + + sys.exit(SUCCESS) + +if __name__ == "__main__": + main() diff --git a/app/models/concept_usage/rules.json b/app/models/concept_usage/rules.json new file mode 100644 index 0000000000..01a4992198 --- /dev/null +++ b/app/models/concept_usage/rules.json @@ -0,0 +1,182 @@ +{ + "formatting_checks": { + "includes": [], + "rules": [ + { + "queries": ["varDecl(hasType(isConstQualified()), unless(matchesName(\"\\.*::[A-Z0-9_\\]+$\")), isExpansionInMainFile())"], + "id": "FormattingConstant", + "description": "Double check how you've formatted your constants." + }, + { + "queries": ["varDecl(unless(hasType(isConstQualified())), unless(matchesName(\".*::[a-z0-9_]+$\")), isExpansionInMainFile(), unless(parmVarDecl()))", "fieldDecl(unless(hasType(isConstQualified())), unless(matchesName(\".*::[a-z0-9_]+$\")), isExpansionInMainFile())"], + "id": "FormattingVariable", + "description": "Double check how you've formatted your variables." + } + ] + }, + + "always_checks": { + "includes": [], + "rules": [ + { + "queries": ["varDecl(hasGlobalStorage(), isExpansionInMainFile(), unless(hasType(isConstQualified())))"], + "id": "GlobalVariable", + "description": "Make sure you aren't using any global variables." + }, + { + "queries": ["declRefExpr(to(functionDecl(hasName(\"printf\"))), isExpansionInMainFile())", "declRefExpr(to(functionDecl(hasName(\"scanf\"))), isExpansionInMainFile())"], + "id": "Printf", + "description": "Make sure to use the terminal read/write functions we're using." + }, + { + "queries": ["declRefExpr(to(varDecl(hasName(\"cin\"))), isExpansionInMainFile())", "declRefExpr(to(varDecl(hasName(\"cout\"))), isExpansionInMainFile())"], + "id": "CppIO", + "description": "Make sure to use the terminal read/write functions we're using." + }, + { + "queries": ["labelStmt(isExpansionInMainFile())", "gotoStmt(isExpansionInMainFile())"], + "id": "Goto", + "description": "Make sure to use structured control flow statements rather than jumping to labels." + } + ] + }, + + "base_checks": { + "includes": ["formatting_checks", "always_checks"], + "rules": [] + }, + + "chapter_11_memory_deep_dive": { + "includes": ["base_checks"], + "rules": [] + }, + + "chapter_10_pointers_and_lists": { + "includes": ["chapter_11_memory_deep_dive"], + "rules": [ + { + "queries": ["declRefExpr(to(functionDecl(hasName(\"malloc\"))))", "declRefExpr(to(functionDecl(hasName(\"free\"))))"], + "id": "Malloc", + "description": "Try using C++ style memory allocation instead." + }, + { + "queries": ["cxxNewExpr(isExpansionInMainFile(), isArray())"], + "id": "CppNewArray", + "description": "Sorry, we'll cover dynamic arrays soon!" + } + ] + }, + + "chapter_09_generics_and_operators": { + "includes": ["chapter_10_pointers_and_lists"], + "rules": [ + { + "queries": ["varDecl(hasType(qualType(hasUnqualifiedDesugaredType(pointerType(unless(pointee(hasDeclaration(isExpansionInFileMatching(\"splashkit/.*\")))))))), isExpansionInMainFile())"], + "id": "Pointer", + "description": "Still a bit early for pointers." + }, + { + "queries": ["cxxThisExpr(isExpansionInMainFile(), unless(anything()))"], + "id": "This - NOTE: unless(isImplicit()) doesn't work on my machine, so for now this is nullified with anything()", + "description": "No need to use `this` yet." + }, + { + "queries": ["cxxNewExpr(isExpansionInMainFile())"], + "id": "CppNew", + "description": "For now make sure to keep things on the stack." + } + ] + }, + + "chapter_08_member_functions": { + "includes": ["chapter_09_generics_and_operators"], + "rules": [ + { + "queries": ["templateTypeParmDecl(isExpansionInMainFile())"], + "id": "Template", + "description": "For now just use concrete types - no need to generalize your code too much." + }, + { + "queries": ["functionDecl(matchesName(\"operator\"), isExpansionInMainFile())", "cxxMethodDecl(matchesName(\"operator\"), isExpansionInMainFile())"], + "id": "OperatorOverload", + "description": "No need to overload operators yet." + } + ] + }, + + "chapter_07_handling_multiples": { + "includes": ["chapter_08_member_functions"], + "rules": [ + { + "queries": ["cxxMethodDecl(isExpansionInMainFile())", "cxxConstructorDecl(isExpansionInMainFile())"], + "id": "Method", + "description": "Make sure your structs are just data for now." + } + ] + }, + + "chapter_06_structuring_data": { + "includes": ["chapter_07_handling_multiples"], + "rules": [ + { + "queries": ["varDecl(hasType(arrayType()), isExpansionInMainFile())", "fieldDecl(hasType(arrayType()), isExpansionInMainFile())", "declRefExpr(to(functionDecl(hasName(\"operator[]\"))), isExpansionInMainFile())", "cxxNewExpr(isExpansionInMainFile(), isArray())"], + "id": "Array", + "description": "For now, keep your variables holding only one piece of data at a time." + } + ] + }, + + "chapter_05_structuring_code": { + "includes": ["chapter_06_structuring_data"], + "rules": [ + { + "queries": ["recordDecl(isExpansionInMainFile())"], + "id": "Struct", + "description": "Keep the code simple for now - just pass around variables seperately, don't group them." + }, + { + "queries": ["enumDecl(isExpansionInMainFile())"], + "id": "Enum", + "description": "Rather than an enum, feel free to just use a series of integer constants for now." + } + ] + }, + + "chapter_04_control_flow": { + "includes": ["chapter_05_structuring_code"], + "rules": [ + { + "queries": ["functionDecl(isExpansionInMainFile(), unless(hasName(\"main\")), unless(cxxMethodDecl()))"], + "id": "Function", + "description": "Check that all your code is in the one function for now." + }, + { + "queries": ["cxxThrowExpr(isExpansionInMainFile())"], + "id": "Exception", + "description": "Simplify your error handling - you can achieve this in other ways." + } + ] + }, + + "chapter_03_data": { + "includes": ["chapter_04_control_flow"], + "rules": [ + { + "queries": ["ifStmt(isExpansionInMainFile())", "whileStmt(isExpansionInMainFile())", "doStmt(isExpansionInMainFile())", "forStmt(isExpansionInMainFile())", "breakStmt(isExpansionInMainFile())", "continueStmt(isExpansionInMainFile())"], + "id": "ControlFlow", + "description": "We'll get to flow control soon! For now, keep things simple and linear." + } + ] + }, + + "chapter_02_sequence": { + "includes": ["chapter_03_data"], + "rules": [ + { + "queries": ["varDecl(isExpansionInMainFile())"], + "id": "Variable", + "description": "No need for variables yet, we'll get to those next." + } + ] + } +} From 0aec2fdae2293903d6839914b710790667e44e5c Mon Sep 17 00:00:00 2001 From: Sean Boettger Date: Wed, 11 Feb 2026 06:53:55 +1100 Subject: [PATCH 2/2] chore: add concept usage checker test script & files --- test/models/concept_usage_test.py | 81 +++++++++++++++++ .../chapter_02_sequence.cpp | 9 ++ .../chapter_03_data.cpp | 43 ++++++++++ .../chapter_04_control_flow.cpp | 86 +++++++++++++++++++ .../chapter_05_structuring_code.cpp | 74 ++++++++++++++++ .../chapter_06_structuring_data.cpp | 48 +++++++++++ .../chapter_07_handling_multiples.cpp | 56 ++++++++++++ .../chapter_08_member_functions.cpp | 16 ++++ .../chapter_09_generics_and_operators.cpp | 22 +++++ .../chapter_10_pointers_and_lists.cpp | 41 +++++++++ .../chapter_11_memory_deep_dive.cpp | 45 ++++++++++ .../global_const_unformatted.cpp | 9 ++ .../missing_header.cpp | 10 +++ .../concept_usage_test_files/new_and_goto.cpp | 10 +++ .../concept_usage_test_files/printf.cpp | 8 ++ test_files/concept_usage_test_files/stdin.cpp | 9 ++ ...cturingdata_method_variable_formatting.cpp | 55 ++++++++++++ 17 files changed, 622 insertions(+) create mode 100644 test/models/concept_usage_test.py create mode 100644 test_files/concept_usage_test_files/chapter_02_sequence.cpp create mode 100644 test_files/concept_usage_test_files/chapter_03_data.cpp create mode 100644 test_files/concept_usage_test_files/chapter_04_control_flow.cpp create mode 100644 test_files/concept_usage_test_files/chapter_05_structuring_code.cpp create mode 100644 test_files/concept_usage_test_files/chapter_06_structuring_data.cpp create mode 100644 test_files/concept_usage_test_files/chapter_07_handling_multiples.cpp create mode 100644 test_files/concept_usage_test_files/chapter_08_member_functions.cpp create mode 100644 test_files/concept_usage_test_files/chapter_09_generics_and_operators.cpp create mode 100644 test_files/concept_usage_test_files/chapter_10_pointers_and_lists.cpp create mode 100644 test_files/concept_usage_test_files/chapter_11_memory_deep_dive.cpp create mode 100644 test_files/concept_usage_test_files/global_const_unformatted.cpp create mode 100644 test_files/concept_usage_test_files/missing_header.cpp create mode 100644 test_files/concept_usage_test_files/new_and_goto.cpp create mode 100644 test_files/concept_usage_test_files/printf.cpp create mode 100644 test_files/concept_usage_test_files/stdin.cpp create mode 100644 test_files/concept_usage_test_files/structuringdata_method_variable_formatting.cpp diff --git a/test/models/concept_usage_test.py b/test/models/concept_usage_test.py new file mode 100644 index 0000000000..cc7500fa52 --- /dev/null +++ b/test/models/concept_usage_test.py @@ -0,0 +1,81 @@ +from importlib import util +import sys +from pathlib import Path + +root_path = Path(__file__).resolve().parent.parent.parent +main_path = root_path / "app" / "models" / "concept_usage" +rules_path = main_path / "rules.json" +tests_path = root_path / "test_files" / "concept_usage_test_files" + +sys.path.append(str(main_path)) +import check_concept_usage + +# [, , ] +tests = [ + # Tests by chapter + # Test using the ruleset for the associated chapter (no issues), + # a chapter in the future (no issues), and in the past (should have issues) + ["chapter_02_sequence.cpp", "chapter_02_sequence", ""], + ["chapter_02_sequence.cpp", "chapter_10_pointers_and_lists", ""], + + ["chapter_03_data.cpp", "chapter_03_data", ""], + ["chapter_03_data.cpp", "chapter_10_pointers_and_lists", ""], + ["chapter_03_data.cpp", "chapter_02_sequence", "Variable"], + + ["chapter_04_control_flow.cpp", "chapter_04_control_flow", ""], + ["chapter_04_control_flow.cpp", "chapter_10_pointers_and_lists", ""], + ["chapter_04_control_flow.cpp", "chapter_03_data", "ControlFlow"], + ["chapter_04_control_flow.cpp", "chapter_02_sequence", "ControlFlow, Variable"], + + ["chapter_05_structuring_code.cpp", "chapter_05_structuring_code", ""], + ["chapter_05_structuring_code.cpp", "chapter_10_pointers_and_lists", ""], + ["chapter_05_structuring_code.cpp", "chapter_02_sequence", "Function, ControlFlow, Variable"], + + ["chapter_06_structuring_data.cpp", "chapter_06_structuring_data", ""], + ["chapter_06_structuring_data.cpp", "chapter_10_pointers_and_lists", ""], + ["chapter_06_structuring_data.cpp", "chapter_02_sequence", "Struct, Variable"], + ["chapter_06_structuring_data.cpp", "chapter_05_structuring_code", "Struct"], + + ["chapter_07_handling_multiples.cpp", "chapter_07_handling_multiples", ""], + ["chapter_07_handling_multiples.cpp", "chapter_10_pointers_and_lists", ""], + ["chapter_07_handling_multiples.cpp", "chapter_02_sequence", "Array, Struct, Enum, Function, ControlFlow, Variable"], + ["chapter_07_handling_multiples.cpp", "chapter_05_structuring_code", "Array, Struct, Enum"], + + ["chapter_08_member_functions.cpp", "chapter_08_member_functions", ""], + ["chapter_08_member_functions.cpp", "chapter_07_handling_multiples", "Method"], + ["chapter_08_member_functions.cpp", "chapter_02_sequence", "Method, Array, Struct, Variable"], + + ["chapter_09_generics_and_operators.cpp", "chapter_09_generics_and_operators", ""], + ["chapter_09_generics_and_operators.cpp", "chapter_08_member_functions", "Template, OperatorOverload"], + ["chapter_09_generics_and_operators.cpp", "chapter_02_sequence", "Template, OperatorOverload, Method, Array, Struct, Variable"], + + ["chapter_10_pointers_and_lists.cpp", "chapter_10_pointers_and_lists", ""], + ["chapter_10_pointers_and_lists.cpp", "chapter_02_sequence", "Pointer, CppNew, Array, Function, ControlFlow, Variable"], + ["chapter_10_pointers_and_lists.cpp", "chapter_05_structuring_code", "Pointer, CppNew, Array"], + + ["chapter_11_memory_deep_dive.cpp", "chapter_11_memory_deep_dive", ""], + ["chapter_11_memory_deep_dive.cpp", "chapter_02_sequence", "CppNewArray, Pointer, CppNew, Template, Method, Array, Struct, Enum, Function, Exception, ControlFlow, Variable"], + ["chapter_11_memory_deep_dive.cpp", "chapter_05_structuring_code", "CppNewArray, Pointer, CppNew, Template, Method, Array, Struct, Enum"], + + # Misc Checks + ["stdin.cpp", "base_checks", "CppIO"], + ["printf.cpp", "base_checks", "Printf"], + ["global_const_unformatted.cpp", "base_checks", "FormattingConstant, FormattingVariable, GlobalVariable"], + ["structuringdata_method_variable_formatting.cpp", "chapter_06_structuring_data", "FormattingVariable, Method"], + ["new_and_goto.cpp", "chapter_06_structuring_data", "Goto, Pointer, CppNew"], + ["missing_header.cpp", "chapter_03_data", "Struct"], + ["missing_header.cpp", "chapter_06_structuring_data", ""], +] + +print("============= Running Tests =============") +print("[" + (" " * len(tests)) + "]\r[", end="", flush=True) +for i, test in enumerate(tests): + result = check_concept_usage.main_inner(tests_path / test[0], test[1], "id", rules_path) + + resultSet = set([x.strip() for x in result.split(",")]) + expectedSet = set([x.strip() for x in test[2].split(",")]) + + assert resultSet == expectedSet, f"\n\nFailed on test {i}, for file {test[0]}, ruleset {test[1]}. \nExpected result: {test[2]}\nRecieved : {result}" + + print("=", end="", flush=True) +print("\r============= Completed Tests =============") diff --git a/test_files/concept_usage_test_files/chapter_02_sequence.cpp b/test_files/concept_usage_test_files/chapter_02_sequence.cpp new file mode 100644 index 0000000000..728a4f7005 --- /dev/null +++ b/test_files/concept_usage_test_files/chapter_02_sequence.cpp @@ -0,0 +1,9 @@ +#include "splashkit.h" + +int main() +{ + write_line("Hello!"); + write_line("World?"); + + return 0; +} diff --git a/test_files/concept_usage_test_files/chapter_03_data.cpp b/test_files/concept_usage_test_files/chapter_03_data.cpp new file mode 100644 index 0000000000..6e716c0176 --- /dev/null +++ b/test_files/concept_usage_test_files/chapter_03_data.cpp @@ -0,0 +1,43 @@ +#include "splashkit.h" + +int main() +{ + string name; + string favourite_tv_show; + string favourite_meal; + + write_line("Welcome to the very basic bot experience!"); + write_line("Please answer using single words :)"); + delay(200); + write_line("----------------------------------------"); + delay(200); + write_line(); + + delay(200); + write_line("Bot: Hello! What's your name?"); + delay(200); + write("> "); + name = read_line(); + write_line("Bot: Hi " + name + ", nice to meet you!"); + + delay(200); + write_line("Bot: What's your favourite TV show?"); + delay(200); + write("> "); + favourite_tv_show = read_line(); + write_line("Bot: " + to_uppercase(favourite_tv_show) + "!? Umm... I don't like " + favourite_tv_show + " that much sorry."); + + delay(200); + write_line("What about food? Favourite meal?"); + delay(200); + write("> "); + favourite_meal = read_line(); + write_line("Bot: " + to_lowercase(favourite_meal) + " is a better choice than " + to_lowercase(favourite_tv_show) + " at least..."); + + delay(600); + write_line("Anyway bye!"); + delay(200); + write_line("\nThe bot has left the chat."); + + return 0; +} diff --git a/test_files/concept_usage_test_files/chapter_04_control_flow.cpp b/test_files/concept_usage_test_files/chapter_04_control_flow.cpp new file mode 100644 index 0000000000..dc86661d99 --- /dev/null +++ b/test_files/concept_usage_test_files/chapter_04_control_flow.cpp @@ -0,0 +1,86 @@ +#include "splashkit.h" + +const string GAME_TIMER = "GameTimer"; + +const int SCREEN_WIDTH = 800; +const int SCREEN_HEIGHT = 600; +const int SPIDER_RADIUS = 25; +const int SPIDER_SPEED = 3; + +const int FLY_RADIUS = 10; + +int main() +{ + // Set the spider in the center of the screen + int spider_x = SCREEN_WIDTH / 2; + int spider_y = SCREEN_HEIGHT / 2; + + // Create the fly + int fly_x = rnd(SCREEN_WIDTH), fly_y = rnd(SCREEN_HEIGHT); + bool fly_appeared = false; + long appear_at_time = 1000 + rnd(2000); + long escape_at_time = 0; + + open_window("Fly Catch", SCREEN_WIDTH, SCREEN_HEIGHT); + + create_timer(GAME_TIMER); + start_timer(GAME_TIMER); + + // The event loop + while (!quit_requested()) + { + // Handle Input + if (key_down(RIGHT_KEY) && spider_x + SPIDER_RADIUS < SCREEN_WIDTH) + { + spider_x += SPIDER_SPEED; + } + if (key_down(LEFT_KEY) && spider_x - SPIDER_RADIUS > 0) + { + spider_x -= SPIDER_SPEED; + } + + // Update the Game + // Check if the fly should appear + if (!fly_appeared && timer_ticks(GAME_TIMER) > appear_at_time) + { + // Make the fly appear + fly_appeared = true; + + // Give it a new random position + fly_x = rnd(SCREEN_WIDTH); + fly_y = rnd(SCREEN_HEIGHT); + + // Set its escape time + escape_at_time = timer_ticks(GAME_TIMER) + 2000 + rnd(5000); + } + else if (fly_appeared && timer_ticks(GAME_TIMER) > escape_at_time) + { + fly_appeared = false; + appear_at_time = timer_ticks(GAME_TIMER) + 1000 + rnd(2000); + } + + // Test if the spider and fly are touching + if (circles_intersect(spider_x, spider_y, SPIDER_RADIUS, fly_x, fly_y, FLY_RADIUS)) + { + fly_appeared = false; + appear_at_time = timer_ticks(GAME_TIMER) + 1000 + rnd(2000); + } + + // Draw the game + clear_screen(COLOR_WHITE); + // Draw the spider + fill_circle(COLOR_BLACK, spider_x, spider_y, SPIDER_RADIUS); + + if (fly_appeared) + { + // Draw the fly + fill_circle(COLOR_DARK_GREEN, fly_x, fly_y, FLY_RADIUS); + } + + // Show it to the user + refresh_screen(60); + + // Get any new user interactions + process_events(); + } +} diff --git a/test_files/concept_usage_test_files/chapter_05_structuring_code.cpp b/test_files/concept_usage_test_files/chapter_05_structuring_code.cpp new file mode 100644 index 0000000000..0a1f0b060a --- /dev/null +++ b/test_files/concept_usage_test_files/chapter_05_structuring_code.cpp @@ -0,0 +1,74 @@ +#include "splashkit.h" + +int read_integer(string prompt) +{ + write(prompt); + string line = read_line(); + while (!is_integer(line)) + { + write_line("Please enter a whole number."); + write(prompt); + line = read_line(); + } + return stoi(line); +} + +void give_change(int change_value) +{ + const int NUM_COIN_TYPES = 0; + + const int TWO_DOLLARS = 200; + + int to_give; + + write("Change: "); + + int coin_value; + string coin_text; + + for (int i = 0; i < NUM_COIN_TYPES; i++) + { + switch (i) + { + case 0: + coin_value = TWO_DOLLARS; + coin_text = "$2, "; + break; + default: + coin_value = 0; + coin_text = "ERROR"; + break; + } + + // Give Change + to_give = change_value / coin_value; + change_value = change_value - to_give * coin_value; + write(to_string(to_give) + " x " + coin_text); + } + + write_line(); +} + +int main() +{ + string again = ""; // used to check if the user want to run again + string line; + + do + { + int cost_of_item = read_integer("Cost of item in cents: "); + int amount_paid = read_integer("Payment in cents: "); + + if (amount_paid >= cost_of_item) + { + give_change(amount_paid - cost_of_item); + } + else + { + write_line("Insufficient payment"); + } + + write("Run again: "); + again = read_line(); + } while (again != "n" && again != "N"); +} diff --git a/test_files/concept_usage_test_files/chapter_06_structuring_data.cpp b/test_files/concept_usage_test_files/chapter_06_structuring_data.cpp new file mode 100644 index 0000000000..188ef23e4a --- /dev/null +++ b/test_files/concept_usage_test_files/chapter_06_structuring_data.cpp @@ -0,0 +1,48 @@ +#include "splashkit.h" + +struct user_info +{ + string name; + string favourite_tv_show; + string favourite_meal; +}; + +int main() +{ + user_info user; + + write_line("Welcome to the very basic bot experience!"); + write_line("Please answer using single words :)"); + delay(200); + write_line("----------------------------------------"); + delay(200); + write_line(); + + delay(200); + write_line("Bot: Hello! What's your name?"); + delay(200); + write("> "); + user.name = read_line(); + write_line("Bot: Hi " + user.name + ", nice to meet you!"); + + delay(200); + write_line("Bot: What's your favourite TV show?"); + delay(200); + write("> "); + user.favourite_tv_show = read_line(); + write_line("Bot: " + to_uppercase(user.favourite_tv_show) + "!? Umm... I don't like " + favourite_tv_show + " that much sorry."); + + delay(200); + write_line("What about food? Favourite meal?"); + delay(200); + write("> "); + user.favourite_meal = read_line(); + write_line("Bot: " + to_lowercase(user.favourite_meal) + " is a better choice than " + to_lowercase(user.favourite_tv_show) + " at least..."); + + delay(600); + write_line("Anyway bye!"); + delay(200); + write_line("\nThe bot has left the chat."); + + return 0; +} diff --git a/test_files/concept_usage_test_files/chapter_07_handling_multiples.cpp b/test_files/concept_usage_test_files/chapter_07_handling_multiples.cpp new file mode 100644 index 0000000000..7beaa6c25a --- /dev/null +++ b/test_files/concept_usage_test_files/chapter_07_handling_multiples.cpp @@ -0,0 +1,56 @@ +#include "splashkit.h" + +const int MAX_MAP_ROWS = 20; +const int MAX_MAP_COLS = 20; + +const int TILE_WIDTH = 60; +const int TILE_HEIGHT = 60; + +enum explorer_state_kind +{ + PLAY_STATE, +}; + +enum tile_kind +{ + WATER_TILE, + GRASS_TILE, +}; + +struct tile_data +{ + tile_kind kind; +}; + +struct map_data +{ + tile_data tiles[MAX_MAP_COLS][MAX_MAP_ROWS]; +}; + +struct explorer_data +{ + map_data map; + point_2d camera_position; +}; + +void init_map(map_data &map) +{ + for(int c = 0; c < MAX_MAP_COLS; c++) + { + } +} + +void random_map(map_data &map) +{ + for(int c = 0; c < MAX_MAP_COLS; c++) + { + } +} + +int main() +{ + open_window("Map Explorer", 800, 600); + + explorer_data data; + init_explorer_data(data); +} diff --git a/test_files/concept_usage_test_files/chapter_08_member_functions.cpp b/test_files/concept_usage_test_files/chapter_08_member_functions.cpp new file mode 100644 index 0000000000..6afde6c73a --- /dev/null +++ b/test_files/concept_usage_test_files/chapter_08_member_functions.cpp @@ -0,0 +1,16 @@ +struct array +{ + int inner[2]; + + void do_something() + { + + } +}; + + +int main() +{ + array test; + test.do_something(); +} diff --git a/test_files/concept_usage_test_files/chapter_09_generics_and_operators.cpp b/test_files/concept_usage_test_files/chapter_09_generics_and_operators.cpp new file mode 100644 index 0000000000..74445c6e0a --- /dev/null +++ b/test_files/concept_usage_test_files/chapter_09_generics_and_operators.cpp @@ -0,0 +1,22 @@ +template +struct array +{ + T inner[2]; + + void do_something() + { + + } + + int& operator [](int i) + { + return inner[i]; + } +}; + + +int main() +{ + array test; + test.do_something(); +} diff --git a/test_files/concept_usage_test_files/chapter_10_pointers_and_lists.cpp b/test_files/concept_usage_test_files/chapter_10_pointers_and_lists.cpp new file mode 100644 index 0000000000..779732b6f5 --- /dev/null +++ b/test_files/concept_usage_test_files/chapter_10_pointers_and_lists.cpp @@ -0,0 +1,41 @@ +#include "splashkit.h" + +void simple_print(string message) +{ + write_line("Message: " + message); +} +void title_print(string message) +{ + write_line("====" + to_uppercase(message) + "===="); +} +void strange_print(string message) +{ + string lowercase = to_lowercase(message); + + write("~"); + for(int i = 0; i < lowercase.size(); i ++) + { + + write(lowercase[i]); + write("~"); + } + + write_line(); +} + +void print_messages(void (*func)(string)) +{ + func("Hello!"); + func("Another message!"); + func("Function pointers are fun!"); + func("Hopefully!"); +} + +int main() +{ + print_messages(simple_print); + print_messages(title_print); + print_messages(strange_print); + int* x = new int(); + delete x; +} diff --git a/test_files/concept_usage_test_files/chapter_11_memory_deep_dive.cpp b/test_files/concept_usage_test_files/chapter_11_memory_deep_dive.cpp new file mode 100644 index 0000000000..405e359e49 --- /dev/null +++ b/test_files/concept_usage_test_files/chapter_11_memory_deep_dive.cpp @@ -0,0 +1,45 @@ +#include + +enum an_enum { + +}; + +template +struct ptr +{ + ptr* p; + T* data; + ptr() + { + p = nullptr; + data = new T[50]; + } + ~ptr() + { + if (data) + delete [] data; + } +}; + +template +void procedure(ptr* p) +{ + ptr* q = p; + while(q) + { + ptr* o = q->p; + delete q; + q = o; + } +} + +int main() +{ + ptr* p = new ptr(); + + p->p = new ptr(); + + procedure(p); + + throw std::string("An exception!"); +} diff --git a/test_files/concept_usage_test_files/global_const_unformatted.cpp b/test_files/concept_usage_test_files/global_const_unformatted.cpp new file mode 100644 index 0000000000..eed8f5c415 --- /dev/null +++ b/test_files/concept_usage_test_files/global_const_unformatted.cpp @@ -0,0 +1,9 @@ +const int hello_from_outside_a_function = 123; +int a_global_variable = -999; + +int main() +{ + bool BadDlYForMatted = true; + + return 0; +} diff --git a/test_files/concept_usage_test_files/missing_header.cpp b/test_files/concept_usage_test_files/missing_header.cpp new file mode 100644 index 0000000000..c3c06cb310 --- /dev/null +++ b/test_files/concept_usage_test_files/missing_header.cpp @@ -0,0 +1,10 @@ +#include "my_utils.h" + +int main() +{ + a_class name; + + int value = read_integer("Test!"); + + write_line("Hello!"); +} diff --git a/test_files/concept_usage_test_files/new_and_goto.cpp b/test_files/concept_usage_test_files/new_and_goto.cpp new file mode 100644 index 0000000000..0270a0ce22 --- /dev/null +++ b/test_files/concept_usage_test_files/new_and_goto.cpp @@ -0,0 +1,10 @@ +int main() +{ + int* a = new int(); + + jump_forever: + + goto jump_forever; + + return 0; +} diff --git a/test_files/concept_usage_test_files/printf.cpp b/test_files/concept_usage_test_files/printf.cpp new file mode 100644 index 0000000000..b88766e718 --- /dev/null +++ b/test_files/concept_usage_test_files/printf.cpp @@ -0,0 +1,8 @@ +#include + +int main() +{ + printf("Used a printf!(>﹏<)"); + + return 0; +} diff --git a/test_files/concept_usage_test_files/stdin.cpp b/test_files/concept_usage_test_files/stdin.cpp new file mode 100644 index 0000000000..a303e4948c --- /dev/null +++ b/test_files/concept_usage_test_files/stdin.cpp @@ -0,0 +1,9 @@ +#include "splashkit.h" +#include + +int main() +{ + std::cout << "Streams are fun :)" << std::endl; + + return 0; +} diff --git a/test_files/concept_usage_test_files/structuringdata_method_variable_formatting.cpp b/test_files/concept_usage_test_files/structuringdata_method_variable_formatting.cpp new file mode 100644 index 0000000000..7bcd20d5cf --- /dev/null +++ b/test_files/concept_usage_test_files/structuringdata_method_variable_formatting.cpp @@ -0,0 +1,55 @@ +#include "splashkit.h" + +struct user_info +{ + string Name; + string favourite_tv_show; + string favouriteMeal; + + user_info() + { + Name = "N/A"; + favourite_tv_show = "N/A"; + favouriteMeal = "N/A"; + } +}; + +int main() +{ + user_info user; + + write_line("Welcome to the very basic bot experience!"); + write_line("Please answer using single words :)"); + delay(200); + write_line("----------------------------------------"); + delay(200); + write_line(); + + delay(200); + write_line("Bot: Hello! What's your name?"); + delay(200); + write("> "); + user.Name = read_line(); + write_line("Bot: Hi " + user.Name + ", nice to meet you!"); + + delay(200); + write_line("Bot: What's your favourite TV show?"); + delay(200); + write("> "); + user.favourite_tv_show = read_line(); + write_line("Bot: " + to_uppercase(user.favourite_tv_show) + "!? Umm... I don't like " + user.favourite_tv_show + " that much sorry."); + + delay(200); + write_line("What about food? Favourite meal?"); + delay(200); + write("> "); + user.favouriteMeal = read_line(); + write_line("Bot: " + to_lowercase(user.favouriteMeal) + " is a better choice than " + to_lowercase(user.favourite_tv_show) + " at least..."); + + delay(600); + write_line("Anyway bye!"); + delay(200); + write_line("\nThe bot has left the chat."); + + return 0; +}