From 2146ec5f34e020e4b86e604fdf4859fc55d8f199 Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Sun, 13 Dec 2020 14:07:18 -0500 Subject: fixed NaN bug; show total time --- README.md | 35 ++++++++++++++++-- make.bat | 2 +- math.cpp | 5 ++- platforms.cpp | 5 +++ setup.cpp | 1 + sim.cpp | 117 +++++++++++++++++++++++++++++++++++++--------------------- sim.hpp | 4 ++ time.cpp | 26 +++++++++++++ 8 files changed, 148 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index f974233..9bfac79 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,23 @@ -To install box2d on Linux/OS X: +# Boxcatapult2D + +Computer-generated catapults. + +Somewhat inspired by [boxcar2d](https://boxcar2d.com). + +## Editor controls +@TODO + +# Compiling it yourself +You will need SDL2 and Box2D. + +## Linux/OS X/etc. +First, install SDL2. On Debian/Ubuntu, you can do this with +```bash +sudo apt install libsdl2-dev +``` + +You need the latest version of Box2D. The versions in Debian stable/testing aren't new enough. +You can install it with: ```bash git clone https://github.com/erincatto/box2d/ cd box2d @@ -7,7 +26,16 @@ cd build cmake -DBOX2D_BUILD_TESTBED=False .. sudo make -j8 install ``` -On Windows (you need `vcvarsall.bat` and `git` in your PATH): + +Now, just run `make release`, and you will get the executable `boxcatapult2d`. + +## Windows +First, you will need MSVC and `vcvarsall.bat` in your PATH. +Then, download SDL2 (Visual C++ 32/64-bit): https://www.libsdl.org/download-2.0.php +Copy the contents of the folder `SDL2-something\lib\x64` into the same directory as Boxcatapult2D, +and copy the folder `SDL2-something\include` there too, renaming it to `SDL2`. + +Next, install `Box2D` (you will need `git` in your PATH): ```bash vcvarsall x64 git clone https://github.com/erincatto/box2d/ @@ -19,4 +47,5 @@ cmake .. cmake --build . --config Release copy bin\Release\box2d.lib ..\.. ``` -You will also need SDL2. + +Now, you should be able to run `make.bat release` and you will get `boxcatapult2d.exe`. diff --git a/make.bat b/make.bat index 74c3851..8a07eb7 100644 --- a/make.bat +++ b/make.bat @@ -5,7 +5,7 @@ if _%VCVARS% == _ ( ) if not exist obj mkdir obj -SET CFLAGS=/nologo /W3 /D_CRT_SECURE_NO_WARNINGS /I SDL2/include /I box2d SDL2/lib/x64/SDL2main.lib SDL2/lib/x64/SDL2.lib opengl32.lib box2d.lib /MD +SET CFLAGS=/nologo /W4 /wd4505 /wd4706 /D_CRT_SECURE_NO_WARNINGS /I SDL2/include /I box2d SDL2/lib/x64/SDL2main.lib SDL2/lib/x64/SDL2.lib opengl32.lib box2d.lib /MD rc /nologo boxcatapult2d.rc if _%1 == _ ( cl main.cpp /DDEBUG /DEBUG /Zi %CFLAGS% /Fo:obj/urbs /Fe:boxcatapult2d boxcatapult2d.res diff --git a/math.cpp b/math.cpp index 568c306..c17f57e 100644 --- a/math.cpp +++ b/math.cpp @@ -84,7 +84,10 @@ static float randf(void) { static float rand_gauss(void) { // https://en.wikipedia.org/wiki/Normal_distribution#Generating_values_from_normal_distribution - float U = randf(), V = randf(); + float U, V; + do { + U = randf(), V = randf(); + } while (U == 0 || V == 0); return sqrtf(-2 * logf(U)) * cosf(TAUf * V); } diff --git a/platforms.cpp b/platforms.cpp index 4cfa707..7bf9968 100644 --- a/platforms.cpp +++ b/platforms.cpp @@ -57,6 +57,11 @@ static Rect platform_bounding_box(Platform const *platform) { float y1 = platform_lowest_coordinate(platform, true); float x2 = platform_highest_coordinate(platform, false); float y2 = platform_highest_coordinate(platform, true); + bool any_nan = (isnan(x1) || isnan(y1) || isnan(x2) || isnan(y2)); // this used to be a problem, but it should be fixed now. this is here just in case. + assert(!any_nan); + if (any_nan) { + return rect4(-1000, -1000, 1000, 1000); + } return rect4(x1, y1, x2, y2); } diff --git a/setup.cpp b/setup.cpp index 28c12bc..313b862 100644 --- a/setup.cpp +++ b/setup.cpp @@ -168,6 +168,7 @@ static float setup_score(State *state, Setup *setup) { simulate_time(state, 0.1f); } setup->score = ball->pos.x - starting_line; + setup->total_time = state->total_time; return setup->score; } diff --git a/sim.cpp b/sim.cpp index 962fbb6..6636ac9 100644 --- a/sim.cpp +++ b/sim.cpp @@ -60,7 +60,9 @@ static void simulate_time(State *state, float dt) { state->stuck_time += time_step; state->total_time += time_step; b2Vec2 ball_pos = ball->body->GetPosition(); - + + assert(!(isnan(ball_pos.x) || isnan(ball_pos.y))); // there used to be a problem with NaN but it should be fixed now + bool reached_bottom = ball_pos.y - ball->radius < state->bottom_y; // ball reached bottom line float max_stuck_time = 10; bool stuck = state->stuck_time > max_stuck_time; // ball hasn't gotten any further in a while. it's over @@ -175,26 +177,13 @@ static void start_evolution(State *state) { } // make sure you call start_evolution before you call this function for the first time -static void simulate_generation(State *state) { - for (size_t i = 0; i < GENERATION_SIZE; ++i) { - // create new generation from TOP_KEPT - Setup *setup = &state->setups[i + TOP_KEPT]; - *setup = state->setups[rand() % TOP_KEPT]; // select one of the top setups to mutate from - ++setup->mutations; - switch (i / 20) { - case 0: setup_mutate(state, setup, 0.05f); break; // 5% mutation rate group - case 1: setup_mutate(state, setup, 0.10f); break; // 10% mutation rate group - case 2: setup_mutate(state, setup, 0.20f); break; // 20% mutation rate group - case 3: setup_mutate(state, setup, 0.30f); break; // 30% mutation rate group - case 4: // completely random group - memset(setup, 0, sizeof *setup); - setup_random(state, setup); - break; - } - setup_score(state, setup); - } - +static void start_generation(State *state) { + state->scoring_next = 0; + state->evolving = true; +} +static void finish_generation(State *state) { + setups_sort(state); for (size_t i = 0; i < TOP_KEPT; ++i) { Setup *setup = &state->setups[i]; char filename[64] = {0}; @@ -206,10 +195,35 @@ static void simulate_generation(State *state) { setup_write_to_file(setup, filename); } - setups_sort(state); ++state->generation; } +// returns true if this is the last one in the generation +static bool score_one(State *state) { + u32 i = state->scoring_next++; + // create new generation from TOP_KEPT + Setup *setup = &state->setups[i + TOP_KEPT]; + *setup = state->setups[rand() % TOP_KEPT]; // select one of the top setups to mutate from + ++setup->mutations; + switch (i / 20) { + case 0: setup_mutate(state, setup, 0.05f); break; // 5% mutation rate group + case 1: setup_mutate(state, setup, 0.10f); break; // 10% mutation rate group + case 2: setup_mutate(state, setup, 0.20f); break; // 20% mutation rate group + case 3: setup_mutate(state, setup, 0.30f); break; // 30% mutation rate group + case 4: // completely random group + memset(setup, 0, sizeof *setup); + setup_random(state, setup); + break; + } + setup_score(state, setup); + if (state->scoring_next >= GENERATION_SIZE) { + finish_generation(state); + state->scoring_next = 0; + return true; + } + return false; +} + #ifdef __cplusplus extern "C" #endif @@ -613,33 +627,47 @@ void sim_frame(Frame *frame) { } else if (state->evolve_menu) { // handle input if (!state->evolving && keys_pressed[KEY_SPACE]) { - simulate_generation(state); + start_generation(state); + state->run_one_generation = true; } - bool just_started = false; if (keys_pressed[KEY_P]) { - state->evolving = !state->evolving; - if (state->evolving) just_started = true; + if (state->evolving) { + state->evolving = false; + } else { + start_generation(state); + state->run_one_generation = false; + } } char text[128] = {}; // show generation - snprintf(text, sizeof text - 1, "Generation %llu (%s)", (ullong)state->generation, state->evolving ? "evolving" : "stopped"); + snprintf(text, sizeof text - 1, "Generation %llu", (ullong)state->generation); v2 size = text_get_size(state, font, text); - v2 pos = V2(0, 0.98f); + v2 pos = V2(-size.x * 0.5f, 0.98f); pos.y -= size.y * 1.5f; - pos.x -= size.x * 0.5f; if (state->evolving) glColor3f(0.5f,1,0.5f); else glColor3f(1,1,1); text_render(state, font, text, pos); + if (state->evolving) { + snprintf(text, sizeof text - 1, "(running %u/%u)", + (uint)state->scoring_next, (uint)GENERATION_SIZE); + } else { + snprintf(text, sizeof text - 1, "(stopped)"); + } + size = text_get_size(state, font, text); + pos.y -= size.y * 1.5f; + pos.x = -size.x * 0.5f; + text_render(state, font, text, pos); + pos.y -= 0.1f; for (int i = 0; i < 9; ++i) { Setup *setup = &state->setups[i]; - snprintf(text, sizeof text - 1, "%d. %.2f m (mutated %llu times)", - i+1, setup->score, (ullong)setup->mutations); + snprintf(text, sizeof text - 1, "%d. %.2fm in %.1fs (mutated %llu times)", + i+1, setup->score, setup->total_time, (ullong)setup->mutations); size = text_get_size(state, font, text); pos.x = -size.x * 0.5f; pos.y -= size.y * 1.5f; @@ -663,12 +691,14 @@ void sim_frame(Frame *frame) { } gl_color1f(0.8f); - snprintf(text, sizeof text - 1, "Press space to run a single generation."); - size = text_get_size(state, font, text); - pos.x = -size.x * 0.5f; pos.y -= size.y * 1.5f; - text_render(state, font, text, pos); + if (!state->evolving) { + snprintf(text, sizeof text - 1, "Press space to run a single generation."); + size = text_get_size(state, font, text); + pos.x = -size.x * 0.5f; pos.y -= size.y * 1.5f; + text_render(state, font, text, pos); + } - snprintf(text, sizeof text - 1, "Press P to %s running generations.", state->evolving ? "stop" : "start"); + snprintf(text, sizeof text - 1, "Press P to %s running generations automatically.", state->evolving ? "stop" : "start"); size = text_get_size(state, font, text); pos.x = -size.x * 0.5f; pos.y -= size.y * 1.5f; text_render(state, font, text, pos); @@ -678,11 +708,6 @@ void sim_frame(Frame *frame) { pos.x = -size.x * 0.5f; pos.y -= size.y * 1.5f; text_render(state, font, text, pos); - snprintf(text, sizeof text - 1, "(It may take several seconds to simulate each generation)"); - size = text_get_size(state, font, text); - pos.x = -size.x * 0.5f; pos.y -= size.y * 1.5f; - text_render(state, font, text, pos); - for (int i = 0; i < 9; ++i) { if (keys_pressed[KEY_1 + i]) { setup_use(state, &state->setups[i]); @@ -693,8 +718,16 @@ void sim_frame(Frame *frame) { } if (state->evolving) { - if (!just_started) // take one frame to update; change (stopped) to (evolving), and change "Press P to start..." to "Press P to stop..." - simulate_generation(state); // one generation per frame + // score some setups! + struct timespec start_time = time_get(); + do { + bool new_generation = score_one(state); + if (new_generation) { + if (state->run_one_generation) { + state->evolving = false; + } + } + } while (timespec_sub(time_get(), start_time) < 0.02f); // after 20ms, stop. } } else { diff --git a/sim.hpp b/sim.hpp index 12e4f5e..f84d011 100644 --- a/sim.hpp +++ b/sim.hpp @@ -167,6 +167,7 @@ typedef struct { #define MAX_PLATFORMS 32 typedef struct { float score; // distance this setup can throw the ball + float total_time; // time it took to finish u64 mutations; u32 nplatforms; Platform platforms[MAX_PLATFORMS]; @@ -203,6 +204,9 @@ typedef struct { bool simulating; // are we simulating the world's physics? bool evolve_menu; // is the evolve menu shown? bool evolving; // are we simulating generations? + bool run_one_generation; // only run one generation, then stop. + + u32 scoring_next; // which of this generation's setups we are scoring next u64 generation; // which generation we are on diff --git a/time.cpp b/time.cpp index fb007ba..3335d66 100644 --- a/time.cpp +++ b/time.cpp @@ -17,6 +17,32 @@ static struct timespec time_last_modified(char const *filename) { #endif } +// get the current time. this should only really be used for +// time intervals, since there's no significance to the absolute number. +static struct timespec time_get(void) { + struct timespec ts = {}; +#if _WIN32 + FILETIME ft; + ULARGE_INTEGER bigint; + ULONGLONG t; + GetSystemTimeAsFileTime(&ft); + bigint.LowPart = ft.dwLowDateTime; + bigint.HighPart = ft.dwHighDateTime; + t = bigint.QuadPart; + ts.tv_sec = t / 10000000; + ts.tv_nsec = (t % 10000000) * 100; +#else + clock_gettime(CLOCK_MONOTONIC, &ts); +#endif + return ts; +} + +// subtract timespecs. the return value is seconds. +static double timespec_sub(struct timespec a, struct timespec b) { + return (double)(a.tv_sec - b.tv_sec) + + 0.000000001 * (double)(a.tv_nsec - b.tv_nsec); +} + static int timespec_cmp(struct timespec a, struct timespec b) { if (a.tv_sec > b.tv_sec) return 1; if (a.tv_sec < b.tv_sec) return -1; -- cgit v1.2.3