diff --git a/.vscode/settings.json b/.vscode/settings.json index b5601daaa..c71a2653d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,62 @@ { "files.associations": { "*.mdx": "markdown" - } + }, + "C_Cpp_Runner.cCompilerPath": "gcc", + "C_Cpp_Runner.cppCompilerPath": "g++", + "C_Cpp_Runner.debuggerPath": "gdb", + "C_Cpp_Runner.cStandard": "", + "C_Cpp_Runner.cppStandard": "", + "C_Cpp_Runner.msvcBatchPath": "C:/Program Files/Microsoft Visual Studio/VR_NR/Community/VC/Auxiliary/Build/vcvarsall.bat", + "C_Cpp_Runner.useMsvc": false, + "C_Cpp_Runner.warnings": [ + "-Wall", + "-Wextra", + "-Wpedantic", + "-Wshadow", + "-Wformat=2", + "-Wcast-align", + "-Wconversion", + "-Wsign-conversion", + "-Wnull-dereference" + ], + "C_Cpp_Runner.msvcWarnings": [ + "/W4", + "/permissive-", + "/w14242", + "/w14287", + "/w14296", + "/w14311", + "/w14826", + "/w44062", + "/w44242", + "/w14905", + "/w14906", + "/w14263", + "/w44265", + "/w14928" + ], + "C_Cpp_Runner.enableWarnings": true, + "C_Cpp_Runner.warningsAsError": false, + "C_Cpp_Runner.compilerArgs": [], + "C_Cpp_Runner.linkerArgs": [], + "C_Cpp_Runner.includePaths": [], + "C_Cpp_Runner.includeSearch": [ + "*", + "**/*" + ], + "C_Cpp_Runner.excludeSearch": [ + "**/build", + "**/build/**", + "**/.*", + "**/.*/**", + "**/.vscode", + "**/.vscode/**" + ], + "C_Cpp_Runner.useAddressSanitizer": false, + "C_Cpp_Runner.useUndefinedSanitizer": false, + "C_Cpp_Runner.useLeakSanitizer": false, + "C_Cpp_Runner.showCompilationTime": false, + "C_Cpp_Runner.useLinkTimeOptimization": false, + "C_Cpp_Runner.msvcSecureNoWarnings": false } \ No newline at end of file diff --git a/astro.config.mjs b/astro.config.mjs index ba2997f09..ef9d1833d 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -11,13 +11,14 @@ import starlightDocSearch from '@astrojs/starlight-docsearch'; import remarkHeadingID from 'remark-heading-id'; import { loadEnv } from "vite"; -const { DOCSEARCH_API_ID } = loadEnv(process.env.DOCSEARCH_API_ID, process.cwd(), ""); -const { DOCSEARCH_API_SEARCH_KEY } = loadEnv(process.env.DOCSEARCH_API_SEARCH_KEY, process.cwd(), ""); -const { DOCSEARCH_INDEX_NAME } = loadEnv(process.env.DOCSEARCH_INDEX_NAME, process.cwd(), ""); +const env = loadEnv(process.env.NODE_ENV ?? "development", process.cwd(), ""); +const DOCSEARCH_API_ID = process.env.DOCSEARCH_API_ID || env.DOCSEARCH_API_ID; +const DOCSEARCH_API_SEARCH_KEY = process.env.DOCSEARCH_API_SEARCH_KEY || env.DOCSEARCH_API_SEARCH_KEY; +const DOCSEARCH_INDEX_NAME = process.env.DOCSEARCH_INDEX_NAME || env.DOCSEARCH_INDEX_NAME; +const hasDocSearchConfig = Boolean(DOCSEARCH_API_ID && DOCSEARCH_API_SEARCH_KEY && DOCSEARCH_INDEX_NAME); -if (!DOCSEARCH_API_ID || !DOCSEARCH_API_SEARCH_KEY || !DOCSEARCH_INDEX_NAME) { - console.error("Algolia DocSearch enviroment variables are invalid. Please check configuration!"); - process.exit(1); +if (!hasDocSearchConfig) { + console.warn("Algolia DocSearch environment variables are missing. Continuing build without DocSearch."); } // https://astro.build/config @@ -36,11 +37,15 @@ export default defineConfig({ starlightLinksValidator({ errorOnRelativeLinks: true, }), - starlightDocSearch({ - appId: DOCSEARCH_API_ID, - apiKey: DOCSEARCH_API_SEARCH_KEY, - indexName: DOCSEARCH_INDEX_NAME, - }), + ...(hasDocSearchConfig + ? [ + starlightDocSearch({ + appId: DOCSEARCH_API_ID, + apiKey: DOCSEARCH_API_SEARCH_KEY, + indexName: DOCSEARCH_INDEX_NAME, + }), + ] + : []), ], expressiveCode: { // theme: ["github-dark", "github-light"], diff --git a/public/usage-examples/physics/celestial_mechanics-1-example-oop.cs b/public/usage-examples/physics/celestial_mechanics-1-example-oop.cs new file mode 100644 index 000000000..254eb8553 --- /dev/null +++ b/public/usage-examples/physics/celestial_mechanics-1-example-oop.cs @@ -0,0 +1,97 @@ +using SplashKitSDK; +using System.Collections.Generic; + +namespace CelestialMechanics +{ + public class Program + { + // Constants for physics + private const double G = 10.0; // Scaled Gravitational Constant for visual appeal + private const int WINDOW_WIDTH = 800; + private const int WINDOW_HEIGHT = 600; + + public static void Main() + { + SplashKit.OpenWindow("Celestial Mechanics", WINDOW_WIDTH, WINDOW_HEIGHT); + + // Load a simple circle bitmap for planets + Bitmap planetBmp = SplashKit.CreateBitmap("planet", 20, 20); + SplashKit.ClearBitmap(planetBmp, SplashKit.ColorTransparent()); + SplashKit.FillCircleOnBitmap(planetBmp, SplashKit.ColorWhite(), 10, 10, 10); + + // Create 3 planetary Sprites with varying masses + // Sun-like mass + Sprite sun = SplashKit.CreateSprite(planetBmp); + SplashKit.SpriteSetX(sun, WINDOW_WIDTH / 2 - 10); + SplashKit.SpriteSetY(sun, WINDOW_HEIGHT / 2 - 10); + SplashKit.SpriteSetMass(sun, 1000.0); + + // Planet-like mass + Sprite planet = SplashKit.CreateSprite(planetBmp); + SplashKit.SpriteSetX(planet, WINDOW_WIDTH / 2 + 150); + SplashKit.SpriteSetY(planet, WINDOW_HEIGHT / 2 - 10); + SplashKit.SpriteSetMass(planet, 10.0); + SplashKit.SpriteSetVelocity(planet, SplashKit.VectorTo(0, 8)); // Vertical tangent velocity + + // Moon-like mass + Sprite moon = SplashKit.CreateSprite(planetBmp); + SplashKit.SpriteSetX(moon, WINDOW_WIDTH / 2 + 170); + SplashKit.SpriteSetY(moon, WINDOW_HEIGHT / 2 - 10); + SplashKit.SpriteSetMass(moon, 1.0); + SplashKit.SpriteSetVelocity(moon, SplashKit.VectorTo(0, 10)); + + List bodies = new List { sun, planet, moon }; + + while (!SplashKit.QuitRequested()) + { + SplashKit.ProcessEvents(); + + // N-Body Gravity Calculation + for (int i = 0; i < bodies.Count; i++) + { + for (int j = 0; j < bodies.Count; j++) + { + if (i == j) continue; + + // Position difference + Point2D p1 = SplashKit.SpritePosition(bodies[i]); + Point2D p2 = SplashKit.SpritePosition(bodies[j]); + + // direction = p2 - p1 + Vector2D direction = SplashKit.VectorTo(p2.X - p1.X, p2.Y - p1.Y); + double distance = SplashKit.VectorMagnitude(direction); + + // Prevent division by zero and extreme forces when overlapping + if (distance < 5.0) distance = 5.0; + + // F = G * (m1 * m2) / r^2 + double forceMagnitude = (G * SplashKit.SpriteMass(bodies[i]) * SplashKit.SpriteMass(bodies[j])) / (distance * distance); + + // Apply force to body i towards body j (Acceleration = F/m) + Vector2D forceVector = SplashKit.VectorMultiply(SplashKit.UnitVector(direction), forceMagnitude); + Vector2D acceleration = SplashKit.VectorMultiply(forceVector, 1.0 / SplashKit.SpriteMass(bodies[i])); + SplashKit.SpriteSetVelocity(bodies[i], SplashKit.VectorAdd(SplashKit.SpriteVelocity(bodies[i]), acceleration)); + } + } + + // Update movement + foreach (Sprite body in bodies) + { + SplashKit.UpdateSprite(body); + } + + // Rendering + SplashKit.ClearScreen(SplashKit.ColorBlack()); + + foreach (Sprite body in bodies) + { + SplashKit.DrawSprite(body); + } + + SplashKit.RefreshScreen(60); + } + + SplashKit.CloseAllWindows(); + } + } +} diff --git a/public/usage-examples/physics/celestial_mechanics-1-example-top-level.cs b/public/usage-examples/physics/celestial_mechanics-1-example-top-level.cs new file mode 100644 index 000000000..277f9054e --- /dev/null +++ b/public/usage-examples/physics/celestial_mechanics-1-example-top-level.cs @@ -0,0 +1,88 @@ +using SplashKitSDK; +using static SplashKitSDK.SplashKit; + +// Constants for physics +const double G = 10.0; // Scaled Gravitational Constant for visual appeal +const int WINDOW_WIDTH = 800; +const int WINDOW_HEIGHT = 600; + +OpenWindow("Celestial Mechanics", WINDOW_WIDTH, WINDOW_HEIGHT); + +// Load a simple circle bitmap for planets +Bitmap planetBmp = CreateBitmap("planet", 20, 20); +ClearBitmap(planetBmp, ColorTransparent()); +FillCircleOnBitmap(planetBmp, ColorWhite(), 10, 10, 10); + +// Create 3 planetary Sprites with varying masses +// Sun-like mass +Sprite sun = CreateSprite(planetBmp); +SpriteSetX(sun, WINDOW_WIDTH / 2 - 10); +SpriteSetY(sun, WINDOW_HEIGHT / 2 - 10); +SpriteSetMass(sun, 1000.0); + +// Planet-like mass +Sprite planet = CreateSprite(planetBmp); +SpriteSetX(planet, WINDOW_WIDTH / 2 + 150); +SpriteSetY(planet, WINDOW_HEIGHT / 2 - 10); +SpriteSetMass(planet, 10.0); +SpriteSetVelocity(planet, VectorTo(0, 8)); // Vertical tangent velocity + +// Moon-like mass +Sprite moon = CreateSprite(planetBmp); +SpriteSetX(moon, WINDOW_WIDTH / 2 + 170); +SpriteSetY(moon, WINDOW_HEIGHT / 2 - 10); +SpriteSetMass(moon, 1.0); +SpriteSetVelocity(moon, VectorTo(0, 10)); + +Sprite[] bodies = { sun, planet, moon }; + +while (!QuitRequested()) +{ + ProcessEvents(); + + // N-Body Gravity Calculation + for (int i = 0; i < bodies.Length; i++) + { + for (int j = 0; j < bodies.Length; j++) + { + if (i == j) continue; + + // Position difference + Point2D p1 = SpritePosition(bodies[i]); + Point2D p2 = SpritePosition(bodies[j]); + + // direction = p2 - p1 + Vector2D direction = VectorTo(p2.X - p1.X, p2.Y - p1.Y); + double distance = VectorMagnitude(direction); + + // Prevent division by zero and extreme forces when overlapping + if (distance < 5.0) distance = 5.0; + + // F = G * (m1 * m2) / r^2 + double forceMagnitude = (G * SpriteMass(bodies[i]) * SpriteMass(bodies[j])) / (distance * distance); + + // Apply force to body i towards body j (Acceleration = F/m) + Vector2D forceVector = VectorMultiply(UnitVector(direction), forceMagnitude); + Vector2D acceleration = VectorMultiply(forceVector, 1.0 / SpriteMass(bodies[i])); + SpriteSetVelocity(bodies[i], VectorAdd(SpriteVelocity(bodies[i]), acceleration)); + } + } + + // Update movement + foreach (Sprite body in bodies) + { + UpdateSprite(body); + } + + // Rendering + ClearScreen(ColorBlack()); + + foreach (Sprite body in bodies) + { + DrawSprite(body); + } + + RefreshScreen(60); +} + +CloseAllWindows(); diff --git a/public/usage-examples/physics/celestial_mechanics-1-example.cpp b/public/usage-examples/physics/celestial_mechanics-1-example.cpp new file mode 100644 index 000000000..d7ad42e9a --- /dev/null +++ b/public/usage-examples/physics/celestial_mechanics-1-example.cpp @@ -0,0 +1,94 @@ +#include "splashkit.h" +#include + +// Constants for physics +const double G = 10.0; // Scaled Gravitational Constant for visual appeal +const int WINDOW_WIDTH = 800; +const int WINDOW_HEIGHT = 600; + +int main() +{ + open_window("Celestial Mechanics", WINDOW_WIDTH, WINDOW_HEIGHT); + + // Load a simple circle bitmap for planets + bitmap planet_bmp = create_bitmap("planet", 20, 20); + clear_bitmap(planet_bmp, COLOR_TRANSPARENT); + fill_circle_on_bitmap(planet_bmp, COLOR_WHITE, 10, 10, 10); + + // Create 3 planetary Sprites with varying masses + // Sun-like mass + sprite sun = create_sprite(planet_bmp); + sprite_set_x(sun, WINDOW_WIDTH / 2 - 10); + sprite_set_y(sun, WINDOW_HEIGHT / 2 - 10); + sprite_set_mass(sun, 1000.0); + + // Planet-like mass + sprite planet = create_sprite(planet_bmp); + sprite_set_x(planet, WINDOW_WIDTH / 2 + 150); + sprite_set_y(planet, WINDOW_HEIGHT / 2 - 10); + sprite_set_mass(planet, 10.0); + sprite_set_velocity(planet, vector_to(0, 8)); // Vertical tangent velocity + + // Moon-like mass + sprite moon = create_sprite(planet_bmp); + sprite_set_x(moon, WINDOW_WIDTH / 2 + 170); + sprite_set_y(moon, WINDOW_HEIGHT / 2 - 10); + sprite_set_mass(moon, 1.0); + sprite_set_velocity(moon, vector_to(0, 10)); + + std::vector bodies; + bodies.push_back(sun); + bodies.push_back(planet); + bodies.push_back(moon); + + while (!quit_requested()) + { + process_events(); + + // N-Body Gravity Calculation + for (int i = 0; i < bodies.size(); i++) + { + for (int j = 0; j < bodies.size(); j++) + { + if (i == j) continue; + + // Position difference + point_2d p1 = sprite_position(bodies[i]); + point_2d p2 = sprite_position(bodies[j]); + + vector_2d direction = vector_to(p2.x - p1.x, p2.y - p1.y); + double distance = vector_magnitude(direction); + + // Prevent division by zero and extreme forces when overlapping + if (distance < 5.0) distance = 5.0; + + // F = G * (m1 * m2) / r^2 + double force_magnitude = (G * sprite_mass(bodies[i]) * sprite_mass(bodies[j])) / (distance * distance); + + // Apply force to body i towards body j (F = m*a -> a = F/m) + vector_2d force_vector = vector_multiply(unit_vector(direction), force_magnitude); + vector_2d acceleration = vector_multiply(force_vector, 1.0 / sprite_mass(bodies[i])); + sprite_set_velocity(bodies[i], vector_add(sprite_velocity(bodies[i]), acceleration)); + } + } + + // Update movement + for (unsigned int i = 0; i < bodies.size(); i++) + { + update_sprite(bodies[i]); + } + + // Rendering + clear_screen(COLOR_BLACK); + + for (unsigned int i = 0; i < bodies.size(); i++) + { + draw_sprite(bodies[i]); + } + + refresh_screen(60); + } + + close_all_windows(); + return 0; +} diff --git a/public/usage-examples/physics/celestial_mechanics-1-example.png b/public/usage-examples/physics/celestial_mechanics-1-example.png new file mode 100644 index 000000000..c50644c45 Binary files /dev/null and b/public/usage-examples/physics/celestial_mechanics-1-example.png differ diff --git a/public/usage-examples/physics/celestial_mechanics-1-example.py b/public/usage-examples/physics/celestial_mechanics-1-example.py new file mode 100644 index 000000000..0ffc48787 --- /dev/null +++ b/public/usage-examples/physics/celestial_mechanics-1-example.py @@ -0,0 +1,83 @@ +from splashkit import * + +# Constants for physics +G = 10.0 # Scaled Gravitational Constant for visual appeal +WINDOW_WIDTH = 800 +WINDOW_HEIGHT = 600 + +def main(): + open_window("Celestial Mechanics", WINDOW_WIDTH, WINDOW_HEIGHT) + + # Load a simple circle bitmap for planets + planet_bmp = create_bitmap("planet", 20, 20) + clear_bitmap(planet_bmp, color_transparent()) + fill_circle_on_bitmap(planet_bmp, color_white(), 10, 10, 10) + + # Create 3 planetary Sprites with varying masses + # Sun-like mass + sun = create_sprite(planet_bmp) + sprite_set_x(sun, WINDOW_WIDTH / 2 - 10) + sprite_set_y(sun, WINDOW_HEIGHT / 2 - 10) + sprite_set_mass(sun, 1000.0) + + # Planet-like mass + planet = create_sprite(planet_bmp) + sprite_set_x(planet, WINDOW_WIDTH / 2 + 150) + sprite_set_y(planet, WINDOW_HEIGHT / 2 - 10) + sprite_set_mass(planet, 10.0) + sprite_set_velocity(planet, vector_to(0, 8)) # Vertical tangent velocity + + # Moon-like mass + moon = create_sprite(planet_bmp) + sprite_set_x(moon, WINDOW_WIDTH / 2 + 170) + sprite_set_y(moon, WINDOW_HEIGHT / 2 - 10) + sprite_set_mass(moon, 1.0) + sprite_set_velocity(moon, vector_to(0, 10)) + + bodies = [sun, planet, moon] + + while not quit_requested(): + process_events() + + # N-Body Gravity Calculation + for i in range(len(bodies)): + for j in range(len(bodies)): + if i == j: + continue + + # Position difference + p1 = sprite_position(bodies[i]) + p2 = sprite_position(bodies[j]) + + # direction = p2 - p1 + direction = vector_to(p2.x - p1.x, p2.y - p1.y) + distance = vector_magnitude(direction) + + # Prevent division by zero and extreme forces when overlapping + if distance < 5.0: + distance = 5.0 + + # F = G * (m1 * m2) / r^2 + force_magnitude = (G * sprite_mass(bodies[i]) * sprite_mass(bodies[j])) / (distance * distance) + + # Apply force to body i towards body j (a = F/m) + force_vector = vector_multiply(unit_vector(direction), force_magnitude) + acceleration = vector_multiply(force_vector, 1.0 / sprite_mass(bodies[i])) + sprite_set_velocity(bodies[i], vector_add(sprite_velocity(bodies[i]), acceleration)) + + # Update movement + for body in bodies: + update_sprite(body) + + # Rendering + clear_screen(color_black()) + + for body in bodies: + draw_sprite(body) + + refresh_screen(60) + + close_all_windows() + +if __name__ == "__main__": + main() diff --git a/public/usage-examples/physics/celestial_mechanics-1-example.txt b/public/usage-examples/physics/celestial_mechanics-1-example.txt new file mode 100644 index 000000000..fa3ea7bda --- /dev/null +++ b/public/usage-examples/physics/celestial_mechanics-1-example.txt @@ -0,0 +1 @@ +Celestial Mechanics Integration Example \ No newline at end of file diff --git a/scripts/json-files/usage-example-references.json b/scripts/json-files/usage-example-references.json index c41688ec6..8454a7f46 100644 --- a/scripts/json-files/usage-example-references.json +++ b/scripts/json-files/usage-example-references.json @@ -38,6 +38,31 @@ ] } ], + "color": [ + { + "funcKey": "saturation_of", + "title": "Draws a rectangle in a random colour. The colour's saturation value is displayed on screen", + "url": "/api/color/#saturation-of", + "functions": [ + "open_window", + "random_rgb_color", + "round", + "rectangle_from", + "clear_screen", + "color_white", + "fill_rectangle_record", + "draw_text_no_font_no_size", + "color_black", + "red_of", + "green_of", + "blue_of", + "alpha_of", + "refresh_screen", + "delay", + "close_all_windows" + ] + } + ], "geometry": [ { "funcKey": "center_point", @@ -161,6 +186,25 @@ "close_all_windows" ] }, + { + "funcKey": "lines_intersect", + "title": "Simple Line Intersect Check", + "url": "/api/geometry/#lines-intersect", + "functions": [ + "open_window", + "Point2D", + "line_from_point_to_point", + "draw_line_record", + "color_red", + "draw_text_no_font_no_size", + "color_black", + "color_blue", + "color_green", + "refresh_screen", + "delay", + "close_all_windows" + ] + }, { "funcKey": "line_intersects_rect", "title": "Avoid the Rectangle", @@ -256,25 +300,6 @@ "close_all_windows" ] }, - { - "funcKey": "lines_intersect", - "title": "Simple Line Intersect Check", - "url": "/api/geometry/#lines-intersect", - "functions": [ - "open_window", - "Point2D", - "line_from_point_to_point", - "draw_line_record", - "color_red", - "draw_text_no_font_no_size", - "color_black", - "color_blue", - "color_green", - "refresh_screen", - "delay", - "close_all_windows" - ] - }, { "funcKey": "point_at", "title": "Flower Grid", @@ -524,6 +549,32 @@ "close_window" ] }, + { + "funcKey": "rectangle_around_circle", + "title": "A perpetually moving circle which increases and decreases in size, surrounded by a rectangle shape", + "url": "/api/geometry/#rectangle-around-circle", + "functions": [ + "open_window", + "create_timer", + "start_timer", + "quit_requested", + "point_at", + "cosine", + "sine", + "circle_at", + "timer_ticks", + "reset_timer", + "process_events", + "clear_screen_to_white", + "draw_rectangle_record", + "color_black", + "rectangle_around_circle", + "fill_circle_record", + "color_red", + "refresh_screen", + "close_all_windows" + ] + }, { "funcKey": "same_point", "title": "Point 2D Guessing Game", @@ -545,7 +596,7 @@ "graphics": [ { "funcKey": "bitmap_bounding_circle", - "title": "Draw bitmaps surrounded by circles\r", + "title": "Draw bitmaps surrounded by circles", "url": "/api/graphics/#bitmap-bounding-circle", "functions": [ "open_window", @@ -561,6 +612,24 @@ "close_all_windows" ] }, + { + "funcKey": "bitmap_center", + "title": "Draw a bitmap with a red dot at its center", + "url": "/api/graphics/#bitmap-center", + "functions": [ + "open_window", + "load_bitmap", + "clear_screen", + "color_white", + "draw_bitmap", + "fill_circle_record", + "color_red", + "circle_at", + "refresh_screen", + "delay", + "close_all_windows" + ] + }, { "funcKey": "clear_screen", "title": "Background Color", @@ -597,6 +666,26 @@ "SplashKit" ] }, + { + "funcKey": "draw_circle", + "title": "Circle Showcase", + "url": "/api/graphics/#draw-circle", + "functions": [ + "open_window", + "clear_screen", + "color_white", + "fill_circle", + "color_red", + "color_blue", + "color_green", + "color_orange", + "color_purple", + "random_rgb_color", + "refresh_screen", + "delay", + "close_all_windows" + ] + }, { "funcKey": "draw_circle_on_bitmap", "title": "Creating a Red Planet", @@ -1189,6 +1278,25 @@ "close_all_windows" ] }, + { + "funcKey": "get_font_style", + "title": "The program automatically cycles through font styles for a given font (also displaying example text), with the numerical value of each style being shown on screen", + "url": "/api/graphics/#get-font-style", + "functions": [ + "open_window", + "font_named", + "quit_requested", + "process_events", + "set_font_style", + "clear_screen_to_white", + "draw_text_no_font_no_size", + "color_black", + "draw_text", + "refresh_screen", + "delay", + "close_all_windows" + ] + }, { "funcKey": "has_font", "title": "Checking for Font using Variable", @@ -1301,6 +1409,43 @@ } ], "physics": [ + { + "funcKey": "sprite_set_velocity", + "title": "Celestial Mechanics Integration Example", + "url": "/api/sprites/#sprite-set-velocity", + "functions": [ + "main", + "open_window", + "create_bitmap", + "clear_bitmap", + "color_transparent", + "fill_circle_on_bitmap", + "color_white", + "create_sprite", + "sprite_set_x", + "sprite_set_y", + "sprite_set_mass", + "sprite_set_velocity", + "vector_to", + "quit_requested", + "process_events", + "len", + "sprite_position", + "vector_magnitude", + "sprite_mass", + "j", + "vector_multiply", + "unit_vector", + "vector_add", + "sprite_velocity", + "update_sprite", + "clear_screen", + "color_black", + "draw_sprite", + "refresh_screen", + "close_all_windows" + ] + }, { "funcKey": "sprite_bitmap_collision", "title": "Does SplashKit have bugs?", @@ -1790,5 +1935,30 @@ "write_line" ] } + ], + "windows": [ + { + "funcKey": "close_window", + "title": "Starts a countdown to close the window at the push of a button.", + "url": "/api/windows/#close-window", + "functions": [ + "open_window", + "create_timer", + "quit_requested", + "process_events", + "clear_window", + "color_white", + "button_at_position", + "rectangle_from", + "start_timer", + "draw_text_font_as_string", + "color_black", + "timer_ticks", + "reset_timer", + "draw_interface", + "refresh_window", + "close_all_windows" + ] + } ] } \ No newline at end of file diff --git a/scripts/usage-example-scraping.cjs b/scripts/usage-example-scraping.cjs index 0ec5376cf..b8ab54381 100644 --- a/scripts/usage-example-scraping.cjs +++ b/scripts/usage-example-scraping.cjs @@ -8,6 +8,45 @@ const path = require('path'); // Handle and transform file paths const srcDirectory = "./public/usage-examples"; //directory to be scraped const outputDirectory = "./scripts/json-files/usage-example-references.json" //directory where "Usage Example" functions will be savedc +const apiJsonPath = "./scripts/json-files/api.json"; + +const functionKeyOverrides = { + celestial_mechanics: "sprite_set_velocity", + rectangle_around: "rectangle_around_circle" +}; + +function buildApiFunctionIndex(filePath) { + const apiData = JSON.parse(fs.readFileSync(filePath, "utf8")); + const functionIndex = new Map(); + + Object.entries(apiData).forEach(([categoryKey, categoryValue]) => { + if (categoryKey === "types" || !categoryValue || !Array.isArray(categoryValue.functions)) return; + + categoryValue.functions.forEach((func) => { + if (func && typeof func.unique_global_name === "string") { + functionIndex.set(func.unique_global_name, categoryKey); + } + }); + }); + + return functionIndex; +} + +function resolvePrimaryFunctionKey(rawFuncKey, calledFunctions, apiFunctionIndex) { + const candidateKeys = []; + + if (functionKeyOverrides[rawFuncKey]) { + candidateKeys.push(functionKeyOverrides[rawFuncKey]); + } + + candidateKeys.push(rawFuncKey, ...calledFunctions); + + for (const key of candidateKeys) { + if (apiFunctionIndex.has(key)) return key; + } + + return rawFuncKey; +} // ------------------------------------------------------------------------------ // Scraping all of the folders in usage example and retrieving the functions and title @@ -15,6 +54,7 @@ const outputDirectory = "./scripts/json-files/usage-example-references.json" //d function getAvailableExamplesFunctionUsage(dir) { const result = {}; const fileNameRegex = /^([a-zA-Z_][a-zA-Z0-9_]*)-/; + const apiFunctionIndex = buildApiFunctionIndex(apiJsonPath); const ignoreKey = new Set(["if", "else", "elif", "while", "for", "range", "int", "str", "match"]); @@ -36,38 +76,50 @@ function getAvailableExamplesFunctionUsage(dir) { pythonFiles.forEach(pyFile => { const fileName = path.join(folderPath, pyFile); const fileName2 = fileName.replace('.py', '.txt') - const pythonFile = fs.readFileSync(fileName); + const pythonFile = fs.readFileSync(fileName, "utf8"); const textFile = fs.readFileSync(fileName2, "utf8"); - const title = textFile.split("\n")[0]; + const title = textFile.split(/\r?\n/)[0].trim(); const pyFileMatch = fileNameRegex.exec(pyFile); try { const folderKey = folder.toLowerCase(); const funcKey = pyFileMatch[1].toLowerCase(); + const calledFunctions = []; + + let match; + while ((match = functionCallRegex.exec(pythonFile)) !== null) { + const funcName = match[1]; + if (!calledFunctions.includes(funcName) && !ignoreKey.has(funcName) && funcKey != funcName) { + calledFunctions.push(funcName) + } + } + + const resolvedFuncKey = resolvePrimaryFunctionKey(funcKey, calledFunctions, apiFunctionIndex); + const apiCategory = apiFunctionIndex.get(resolvedFuncKey); if (!result[folderKey]) { result[folderKey] = [] } - let funcEntry = result[folderKey].find(entry => entry.funcKey === funcKey); + let funcEntry = result[folderKey].find(entry => entry.funcKey === resolvedFuncKey); if (!funcEntry) { funcEntry = { - funcKey: funcKey, + funcKey: resolvedFuncKey, title: title, - url: `/api/${folderKey}/#${funcKey.replaceAll("_", "-")}`, + url: apiCategory + ? `/api/${apiCategory}/#${resolvedFuncKey.replaceAll("_", "-")}` + : `/usage-examples/${folderKey}`, functions: [] }; result[folderKey].push(funcEntry); } - let match; - while ((match = functionCallRegex.exec(pythonFile)) !== null) { - const funcName = match[1]; - if (!funcEntry.functions.includes(funcName) && !ignoreKey.has(funcName) && funcKey != funcName) { + calledFunctions.forEach((funcName) => { + if (!funcEntry.functions.includes(funcName)) { funcEntry.functions.push(funcName) } - } + }); } catch (error) { console.error(`Error parsing JSON in file: ${pythonFiles}`);