add basic input handling for klotski board/graph + populate graph based on klotski moves

This commit is contained in:
2026-02-17 15:12:32 +01:00
parent 9d0afffb57
commit 8d5a6a827c
6 changed files with 255 additions and 56 deletions

View File

@ -10,8 +10,10 @@ auto Renderer::Rotate(const Vector3 &a, const float cos_angle,
a.x * sin_angle + a.z * cos_angle);
};
auto Renderer::Translate(const Vector3 &a, const float distance) -> Vector3 {
return Vector3(a.x, a.y, a.z + distance);
auto Renderer::Translate(const Vector3 &a, const float distance,
const float horizontal, const float vertical)
-> Vector3 {
return Vector3(a.x + horizontal, a.y + vertical, a.z + distance);
};
auto Renderer::Project(const Vector3 &a) -> Vector2 {
@ -24,27 +26,72 @@ auto Renderer::Map(const Vector2 &a) -> Vector2 {
auto Renderer::Transform(Edge2Set &edges, Vertex2Set &vertices,
const MassSpringSystem &mass_springs,
const float angle, const float distance) -> void {
const float angle, const float distance,
const float horizontal, const float vertical) -> void {
const float cos_angle = cos(angle);
const float sin_angle = sin(angle);
edges.clear();
for (const auto &spring : mass_springs.springs) {
Vector2 a = Map(Project(Translate(
Rotate(spring.massA.position, cos_angle, sin_angle), distance)));
Vector2 b = Map(Project(Translate(
Rotate(spring.massB.position, cos_angle, sin_angle), distance)));
const Mass &massA = spring.massA;
const Mass &massB = spring.massB;
// Stuff behind the camera
if (massA.position.z + distance <= 0.1) {
continue;
}
if (massB.position.z + distance <= 0.1) {
continue;
}
Vector2 a =
Map(Project(Translate(Rotate(massA.position, cos_angle, sin_angle),
distance, horizontal, vertical)));
Vector2 b =
Map(Project(Translate(Rotate(massB.position, cos_angle, sin_angle),
distance, horizontal, vertical)));
// Stuff outside the viewport
if (!CheckCollisionPointRec(
a, Rectangle(-1.0 * width * CULLING_TOLERANCE,
-1.0 * height * CULLING_TOLERANCE,
width + width * CULLING_TOLERANCE * 2.0,
height + height * CULLING_TOLERANCE * 2.0))) {
continue;
}
if (!CheckCollisionPointRec(
b, Rectangle(-1.0 * width * CULLING_TOLERANCE,
-1.0 * height * CULLING_TOLERANCE,
width + width * CULLING_TOLERANCE * 2.0,
height + height * CULLING_TOLERANCE * 2.0))) {
continue;
}
edges.emplace_back(a, b);
}
// This is duplicated work, but easy to read
vertices.clear();
for (const auto &mass : mass_springs.masses) {
Vector3 a =
Translate(Rotate(mass.position, cos_angle, sin_angle), distance);
for (const auto &[state, mass] : mass_springs.masses) {
// Stuff behind the camera
if (mass.position.z + distance <= 0.1) {
continue;
}
Vector3 a = Translate(Rotate(mass.position, cos_angle, sin_angle), distance,
horizontal, vertical);
Vector2 b = Map(Project(a));
// Stuff outside the viewport
if (!CheckCollisionPointRec(
b, Rectangle(-1.0 * width * CULLING_TOLERANCE,
-1.0 * height * CULLING_TOLERANCE,
width + width * CULLING_TOLERANCE * 2.0,
height + height * CULLING_TOLERANCE * 2.0))) {
continue;
}
vertices.emplace_back(b.x, b.y, a.z);
}
}
@ -53,20 +100,26 @@ auto Renderer::DrawMassSprings(const Edge2Set &edges,
const Vertex2Set &vertices) -> void {
BeginTextureMode(render_target);
ClearBackground(RAYWHITE);
// Draw springs
for (const auto &[a, b] : edges) {
DrawLine(a.x, a.y, b.x, b.y, EDGE_COLOR);
}
// Draw masses
for (const auto &a : vertices) {
// Increase the perspective perception by squaring the z-coordinate
const float size = VERTEX_SIZE / (a.z * a.z);
const float size = Clamp(VERTEX_SIZE / (a.z * a.z), 0.1, 100.0);
DrawRectangle(a.x - size / 2.0, a.y - size / 2.0, size, size, VERTEX_COLOR);
}
DrawLine(0, 0, 0, height, BLACK);
EndTextureMode();
}
auto Renderer::DrawKlotski(State &state) -> void {
auto Renderer::DrawKlotski(State &state, int hov_x, int hov_y, int sel_x,
int sel_y) -> void {
BeginTextureMode(klotski_target);
ClearBackground(RAYWHITE);
@ -106,9 +159,16 @@ auto Renderer::DrawKlotski(State &state) -> void {
// Draw Blocks
for (Block block : state) {
Color c = EDGE_COLOR;
Color c = BLOCK_COLOR;
if (block.Covers(sel_x, sel_y)) {
c = HL_BLOCK_COLOR;
}
if (block.target) {
c = RED;
if (block.Covers(sel_x, sel_y)) {
c = HL_TARGET_BLOCK_COLOR;
} else {
c = TARGET_BLOCK_COLOR;
}
}
DrawRectangle(x_offset + BOARD_PADDING + block.x * BLOCK_PADDING * 2 +
BLOCK_PADDING + block.x * block_size,
@ -119,6 +179,19 @@ auto Renderer::DrawKlotski(State &state) -> void {
block.height * block_size + block.height * 2 * BLOCK_PADDING -
2 * BLOCK_PADDING,
c);
if (block.Covers(hov_x, hov_y)) {
DrawRectangleLinesEx(
Rectangle(x_offset + BOARD_PADDING + block.x * BLOCK_PADDING * 2 +
BLOCK_PADDING + block.x * block_size,
y_offset + BOARD_PADDING + block.y * BLOCK_PADDING * 2 +
BLOCK_PADDING + block.y * block_size,
block.width * block_size + block.width * 2 * BLOCK_PADDING -
2 * BLOCK_PADDING,
block.height * block_size +
block.height * 2 * BLOCK_PADDING - 2 * BLOCK_PADDING),
2.0, BLACK);
}
}
DrawLine(width - 1, 0, width - 1, height, BLACK);