summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md35
-rw-r--r--make.bat2
-rw-r--r--math.cpp5
-rw-r--r--platforms.cpp5
-rw-r--r--setup.cpp1
-rw-r--r--sim.cpp117
-rw-r--r--sim.hpp4
-rw-r--r--time.cpp26
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;