diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index a9921da..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21b6bb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# ===== Build ===== +cmake-build-*/ +build/ +out/ + +# ===== IDE ===== +.idea/ +*.iml + +# ===== OS ===== +.DS_Store + +# ===== Compiled ===== +*.o +*.obj +*.a +*.so +*.dylib +*.exe + +# ===== Logs ===== +*.log diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index d843f34..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 9dae3d4..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - - - ] -} \ No newline at end of file diff --git a/src/gamecube.cpp b/src/gamecube.cpp index 274e6d5..0e03c65 100644 --- a/src/gamecube.cpp +++ b/src/gamecube.cpp @@ -1,6 +1,7 @@ +#include "raylib.h" #include "gamecube.h" -gamecube::gamecube(const Vec3 &pos, Color col) +gamecube::gamecube(const Vec3& pos, Color col) : position(pos), color(col) {} void gamecube::Update(float flipSpeed) @@ -27,8 +28,8 @@ void gamecube::Update(float flipSpeed) } } -void gamecube::FlipForward() { flippingForward = true; } -void gamecube::FlipBackward() { flippingBackward = true; } +void gamecube::FlipForward() { flippingForward = true; } +void gamecube::FlipBackward() { flippingBackward = true; } bool gamecube::IsFlipped() const { return flipped; } bool gamecube::IsMatched() const { return matched; } @@ -38,29 +39,28 @@ void gamecube::Draw() const { rlPushMatrix(); - // Matrizen für Rotation und Translation erzeugen - auto matrix_a = Matrix3D::gameMatrix::translate({ position.x, position.y, position.z}); + auto matrix_a = Matrix3D::gameMatrix::translate( + { position.x, position.y, position.z } + ); auto matrix_b = Matrix3D::gameMatrix::rot3D(rotation, 'y'); - - // Matrizen multiplizieren (Translation * Rotation) auto model = Matrix3D::gameMatrix::matmul(matrix_a, matrix_b); - // transform for raylib matrix float f[16]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) f[j * 4 + i] = model[i][j]; + rlMultMatrixf(f); if (rotation < 90.0f) - DrawCube({0,0,0}, 1,1,1, GRAY); + DrawCube({ 0, 0, 0 }, 1, 1, 1, GRAY); else - DrawCube({0,0,0}, 1,1,1, color); + DrawCube({ 0, 0, 0 }, 1, 1, 1, color); - DrawCubeWires({0,0,0}, 1,1,1, BLACK); + DrawCubeWires({ 0, 0, 0 }, 1, 1, 1, BLACK); rlPopMatrix(); } -Vec3 gamecube::GetPosition() const { return position; } -float gamecube::GetRotationY() const { return rotation; } \ No newline at end of file +Vec3 gamecube::GetPosition() const { return position; } +float gamecube::GetRotationY() const { return rotation; } diff --git a/src/gamematrix.cpp b/src/gamematrix.cpp index 3a20980..e8eb7a8 100644 --- a/src/gamematrix.cpp +++ b/src/gamematrix.cpp @@ -37,9 +37,9 @@ namespace Matrix3D { Mat4 result = identity(); - result[0][3] = pos[0]; // x - result[1][3] = pos[1]; // y - result[2][3] = pos[2]; // z + result[0][3] = pos[0]; + result[1][3] = pos[1]; + result[2][3] = pos[2]; return result; } @@ -56,16 +56,16 @@ namespace Matrix3D switch (axis) { case 'x': - result[1][1] = c; result[1][2] = -s; - result[2][1] = s; result[2][2] = c; + result[1][1] = c; result[1][2] = -s; + result[2][1] = s; result[2][2] = c; break; case 'y': - result[0][0] = c; result[0][2] = s; + result[0][0] = c; result[0][2] = s; result[2][0] = -s; result[2][2] = c; break; case 'z': - result[0][0] = c; result[0][1] = -s; - result[1][0] = s; result[1][1] = c; + result[0][0] = c; result[0][1] = -s; + result[1][0] = s; result[1][1] = c; break; default: break; @@ -80,7 +80,7 @@ namespace Matrix3D Vec3 operator*(const Mat4& m, const Vec3& v) { - Vec4 v_hom = {v[0], v[1], v[2], 1.0}; + Vec4 v_hom = { v[0], v[1], v[2], 1.0 }; Vec4 res_hom = {}; for (int i = 0; i < 4; ++i) @@ -91,7 +91,6 @@ namespace Matrix3D } } - return {res_hom[0], res_hom[1], res_hom[2]}; + return { res_hom[0], res_hom[1], res_hom[2] }; } - -} \ No newline at end of file +} diff --git a/src/main.cpp b/src/main.cpp index dc271d2..b8a62a4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,202 +1,228 @@ #include "gamecube.h" #include #include +#include +#include "raylib.h" +enum GameScreen { MENU = 0, GAMEPLAY }; -enum class GameState +static void SetupGame(std::vector& cubes, int pairs) { - Idle, // kein Würfel offen, Eingabe erlaubt - OneFlipped, // ein Würfel offen - CheckingMatch, // zwei Würfel vollständig aufgeklappt, Vergleich läuft - LockInput // Würfel drehen gerade – Eingabe kurz blockiert -}; + cubes.clear(); + Color colors[] = { RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, WHITE, SKYBLUE }; + std::vector positions; + + const int count = pairs * 2; + int cols = 3; + int rows = 2; + + if (count > 6) { + cols = 4; + rows = 3; + } + + int index = 0; + for (int r = 0; r < rows && index < count; ++r) { + for (int c = 0; c < cols && index < count; ++c) { + positions.push_back({ (float)c * 2.0f - (cols - 1), 0.0f, (float)r * 2.0f - (rows - 1) }); + index++; + } + } + + std::vector colorPool; + colorPool.reserve(count); + + for (int i = 0; i < pairs; i++) { + colorPool.push_back(colors[i]); + colorPool.push_back(colors[i]); + } + + for (int i = (int)colorPool.size() - 1; i > 0; --i) { + int j = rand() % (i + 1); + std::swap(colorPool[i], colorPool[j]); + } + + for (int i = 0; i < count; i++) { + cubes.emplace_back(positions[i], colorPool[i]); + } +} -// ----------------------------------------------------------- -// 3D Memory Game – Hauptprogramm -// ----------------------------------------------------------- int main() { - // Zufall initialisieren - srand(time(NULL)); + srand((unsigned)time(NULL)); + double startTime = 0.0; double endTime = 0.0; bool timerStarted = false; + GameScreen currentScreen = MENU; + int selectedPairs = 3; + + std::vector cubes; + gamecube* first = nullptr; + gamecube* second = nullptr; + float flipSpeed = 5.0f; + bool gameWon = false; - // Fenster und Kamera InitWindow(800, 600, "3D Memory Game with Matrix3D Library"); SetTargetFPS(60); Camera3D camera{}; - camera.position = {6.0f, 6.0f, 6.0f}; - camera.target = {0.0f, 0.0f, 0.0f}; - camera.up = {0.0f, 1.0f, 0.0f}; - camera.fovy = 45.0f; + camera.position = { 6.0f, 6.0f, 6.0f }; + camera.target = { 0.0f, 0.0f, 0.0f }; + camera.up = { 0.0f, 1.0f, 0.0f }; + camera.fovy = 45.0f; camera.projection = CAMERA_PERSPECTIVE; - // Nur 3 Farben für 3 Paare - Color colors[] = { RED, GREEN, BLUE }; - - // 6 Karten-Positionen im 3x2 Raster - std::vector positions = {{-2, 0, -2}, {0, 0, -2}, {2, 0, -2},{-2, 0, 0}, {0, 0, 0}, {2, 0, 0}}; - - // Farben doppelt in einen Pool legen und mischen - std::vector colorPool; - for (int i = 0; i < 3; i++) - { - colorPool.push_back(colors[i]); - colorPool.push_back(colors[i]); - } - - // Fisher-Yates Shuffle mit rand() - for (int i = colorPool.size() - 1; i > 0; --i) - { - int j = rand() % (i + 1); // Zufallsindex von 0 bis i - std::swap(colorPool[i], colorPool[j]); - } - - // Karten/Würfel erstellen - std::vector cubes; - for (int i = 0; i < 6; i++) - cubes.emplace_back(positions[i], colorPool[i]); - - gamecube* first = nullptr; - gamecube* second = nullptr; - float flipSpeed = 5.0f; // Drehgeschwindigkeit - bool gameWon = false; - - GameState state = GameState::Idle; // Start Zustand - - // ----------------------------------------------------------- - // Hauptspielschleife - // ----------------------------------------------------------- while (!WindowShouldClose()) { - if (!timerStarted) + switch (currentScreen) { - startTime = GetTime(); - timerStarted = true; - } - // Klick-Erkennung - if (!gameWon - && state != GameState::LockInput - && state != GameState::CheckingMatch - && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - Vector2 mouse = GetMousePosition(); - - for (auto &c : cubes) + case MENU: { - if (!c.IsFlipped() && !c.IsMatched()) + if (IsKeyPressed(KEY_THREE)) { + selectedPairs = 3; + SetupGame(cubes, selectedPairs); + gameWon = false; + first = second = nullptr; + currentScreen = GAMEPLAY; + timerStarted = false; + } + else if (IsKeyPressed(KEY_SIX)) { + selectedPairs = 6; + SetupGame(cubes, selectedPairs); + gameWon = false; + first = second = nullptr; + currentScreen = GAMEPLAY; + timerStarted = false; + } + break; + } + + case GAMEPLAY: + { + if (!timerStarted) { + startTime = GetTime(); + timerStarted = true; + } + + if (!gameWon && IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && (second == nullptr)) { - Vector2 screenPos = GetWorldToScreen({c.GetPosition().x, c.GetPosition().y, c.GetPosition().z}, camera); + Vector2 mouse = GetMousePosition(); + Ray ray = GetMouseRay(mouse, camera); - if (fabs(mouse.x - screenPos.x) < 40 && fabs(mouse.y - screenPos.y) < 40) + gamecube* hitCube = nullptr; + float bestDist = 1e9f; + + for (auto& c : cubes) { - c.FlipForward(); + if (c.IsMatched() || c.IsFlipped()) continue; - // ZUSTANDSUMSCHALTLOGIK - if (state == GameState::Idle) - { - // 1. Click - c.FlipForward(); - first = &c; - state = GameState::OneFlipped; - break; // wichtig!!! Wir verlassen die Schleife :) + auto p = c.GetPosition(); + + BoundingBox box; + box.min = { p.x - 0.75f, p.y - 0.75f, p.z - 0.75f }; + box.max = { p.x + 0.75f, p.y + 0.75f, p.z + 0.75f }; + + RayCollision col = GetRayCollisionBox(ray, box); + if (col.hit && col.distance < bestDist) { + bestDist = col.distance; + hitCube = &c; } - else if (state == GameState::OneFlipped) - { - // 2. Click - // Lass uns überprüfen, ob es NICHT derselbe Würfel ist. - if (&c != first) - { - c.FlipForward(); - second = &c; - state = GameState::LockInput; - break; // wichtig!!! Wir verlassen die Schleife :) - } + } + + if (hitCube) + { + if (!first) { + first = hitCube; + first->FlipForward(); + } + else if (hitCube != first) { + second = hitCube; + second->FlipForward(); } } } + + for (auto& c : cubes) { + c.Update(flipSpeed); + } + + if (first && second && first->IsFlipped() && second->IsFlipped()) + { + Color col1 = first->GetColor(); + Color col2 = second->GetColor(); + + if (col1.r == col2.r && col1.g == col2.g && col1.b == col2.b) { + first->SetMatched(true); + second->SetMatched(true); + } + else { + first->FlipBackward(); + second->FlipBackward(); + } + + first = nullptr; + second = nullptr; + } + + if (!gameWon) + { + gameWon = std::all_of( + cubes.begin(), + cubes.end(), + [](const gamecube& c) { return c.IsMatched(); } + ); + + if (gameWon) { + endTime = GetTime() - startTime; + } + } + break; } } - // Animation aller Würfel - bool animationBusy = false; - - for (auto &c : cubes) - { - c.Update(flipSpeed); - } - - if (state == GameState::LockInput) - { - if (first && first->IsFlipped() && second && second->IsFlipped()) - { - state = GameState::CheckingMatch; - } - } - - // Matching-Logik - if (state == GameState::CheckingMatch && first && second) - { - if (first->GetColor().r == second->GetColor().r && - first->GetColor().g == second->GetColor().g && - first->GetColor().b == second->GetColor().b) - { - first->SetMatched(true); - second->SetMatched(true); - } - else - { - first->FlipBackward(); - second->FlipBackward(); - } - - first = second = nullptr; - state = GameState::Idle; - } - - // Gewinnprüfung - if (!gameWon) - { - gameWon = std::all_of(cubes.begin(), cubes.end(), [](const gamecube &c){ return c.IsMatched(); }); - if (gameWon) - { - endTime = GetTime() - startTime; - } - } - // ----------------------------------------------------------- - // Zeichnen - // ----------------------------------------------------------- BeginDrawing(); ClearBackground(RAYWHITE); - BeginMode3D(camera); - for (auto &c : cubes) - c.Draw(); - - EndMode3D(); - - if (gameWon) + switch (currentScreen) { - DrawText("Congrats! You found all pairs!", 150, 260, 30, DARKBLUE); + case MENU: + { + DrawText("3D MEMORY SPIEL", + GetScreenWidth() / 2 - MeasureText("3D MEMORY SPIEL", 50) / 2, + 80, 50, DARKGRAY); + DrawText("Waehlen Sie die Spielgroesse:", 50, 200, 25, BLACK); + DrawText("Druecken Sie [3] fuer 3 Paare (6 Wuerfel)", 50, 240, 20, BLUE); + DrawText("Druecken Sie [6] fuer 6 Paare (12 Wuerfel)", 50, 280, 20, BLUE); + break; + } - char buffer[64]; - sprintf(buffer, "Cleared in %.2f seconds", endTime); - DrawText(buffer, 150, 300, 28, DARKGREEN); - } - else - { - DrawText("Flip 2 cubes - find matching pairs!", 10, 10, 20, DARKGRAY); - char liveBuf[64]; - sprintf(liveBuf, "Time: %.2f", GetTime() - startTime); - DrawText(liveBuf, 10, 40, 20, DARKGRAY); + case GAMEPLAY: + { + BeginMode3D(camera); + for (auto& c : cubes) c.Draw(); + EndMode3D(); + + if (gameWon) { + DrawText("Congrats! You found all pairs!", 150, 260, 30, DARKBLUE); + char buffer[64]; + sprintf(buffer, "Cleared in %.2f seconds", endTime); + DrawText(buffer, 150, 300, 28, DARKGREEN); + } else { + DrawText("Flip 2 cubes - find matching pairs!", 10, 10, 20, DARKGRAY); + char liveBuf[64]; + sprintf(liveBuf, "Time: %.2f", GetTime() - startTime); + DrawText(liveBuf, 10, 40, 20, DARKGRAY); + } + break; + } } + EndDrawing(); } CloseWindow(); return 0; -} \ No newline at end of file +}