From 72668404c9f81ad52798b370ab0907009e15effa Mon Sep 17 00:00:00 2001 From: Robert Lech Date: Tue, 11 Nov 2025 12:23:02 -0500 Subject: [PATCH 1/7] refactor: extracts input logic --- CMakeLists.txt | 6 +- main.cpp | 246 +------------------------------------- src/input/InputRouter.cpp | 227 +++++++++++++++++++++++++++++++++++ src/input/InputRouter.h | 10 ++ 4 files changed, 244 insertions(+), 245 deletions(-) create mode 100644 src/input/InputRouter.cpp create mode 100644 src/input/InputRouter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fec323..cbc5dd4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,11 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Generate compile_commands.json for tooli include_directories(src) -set(SOURCE_FILES main.cpp src/game/GameSession.cpp) # Add main.cpp file of project root directory as source file +set(SOURCE_FILES + main.cpp + src/game/GameSession.cpp + src/input/InputRouter.cpp +) # Add main.cpp file of project root directory as source file add_executable(main ${SOURCE_FILES}) # Add executable target with source files listed in SOURCE_FILES variable find_package(OpenGL REQUIRED) diff --git a/main.cpp b/main.cpp index 89e898b..202318e 100644 --- a/main.cpp +++ b/main.cpp @@ -19,10 +19,9 @@ // Project includes #include "game/GameSession.h" +#include "input/InputRouter.h" constexpr GLenum GL_CLAMP_TO_EDGE_VALUE = 0x812F; -const float ZOOM_SCALE = 0.01F; - #if defined(__cpp_lib_math_constants) constexpr float PI = std::numbers::pi_v; // Pi constant for trigonometric helpers #else @@ -35,12 +34,6 @@ constexpr float PI = 3.14159265358979323846F; // NOLINT(modernize-use-std-number // declaration, forward void display(); void display(GameSession &session); -void keyboard(unsigned char, int, int); -void keyboard(GameSession &session, unsigned char, int, int); -void mouse(int button, int state, int x, int y); -void mouse(GameSession &session, int button, int state, int x, int y); -void motion(int x, int y); -void motion(GameSession &session, int x, int y); void init(GameSession &session); void idle(); void idle(GameSession &session); @@ -91,249 +84,14 @@ auto main(int argc, char **argv) -> int { init(session); // register rendering mainloop - glutKeyboardFunc(keyboard); + input::register_callbacks(); glutDisplayFunc(display); glutIdleFunc(idle); - - glutMouseFunc(mouse); // Call mouse whenever mouse button pressed - glutMotionFunc(motion); // Call motion whenever mouse moves while button - // pressed let's rock ... glutMainLoop(); return 0; } -void keyboard(unsigned char key, [[maybe_unused]] int x, [[maybe_unused]] int y) { - auto &session = current_session(); - keyboard(session, key, x, y); -} - -void keyboard(GameSession &session, unsigned char key, [[maybe_unused]] int x, - [[maybe_unused]] int y) { - /* - - 1 : Toggle specularity - 2 : Toggle lighting - 3 : Toggle wireframe - 4 : Toggle light animation - 5 : Toggle depth testing - 6 : Toggle culling (try disabling both depth testing and culling) - 7 : Toggle between smooth and flat shading - 8 : Toggle texture - - o,O : Toggle board rotation along the y-axis - p,P : Toggle board rotation along the x-axis - r,R : Activate "reset board option" - y,Y : Confirm "reset board option" - n,N : Cancel "reset board option" - v,V : Set color to white (This makes testing a bit faster) - b,B : Set color to black (This makes testing a bit faster) - - wasd : Move a piece around the board - enter : Place a piece on the board - - ON RIGHT CLICK AND HOLD MOUSE: - slide left : move camera left - slide right : move camera right - slide up : zoom camera in - slide down : zoom camera out - */ - - switch (key) { - case '1': - // Enable/disable wireframe mode - session.wireframe = !session.wireframe; - - if (session.wireframe) { - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - } else { - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - } - - break; - - case '3': - session.material = !session.material; - - break; - - case '5': - session.depth_test = !session.depth_test; - - if (session.depth_test) { - glEnable(GL_CULL_FACE); - glEnable(GL_DEPTH_TEST); - } else { - glDisable(GL_DEPTH_TEST); - } - - break; - - case '6': - session.cull_face = !session.cull_face; - - if (session.cull_face) { - glEnable(GL_CULL_FACE); - } else { - glDisable(GL_CULL_FACE); - } - - break; - - case '7': - session.smooth_shading = !session.smooth_shading; - - if (session.smooth_shading) { - glShadeModel(GL_SMOOTH); - } else { - glShadeModel(GL_FLAT); - } - - break; - - case '8': - session.enable_texture = !session.enable_texture; - - if (session.enable_texture) { - glEnable(GL_TEXTURE_2D); - } else { - glDisable(GL_TEXTURE_2D); - } - break; - - case 'o': - case 'O': - std::cout << "PRESSED O\n"; - session.pause_board_rotation_y = !session.pause_board_rotation_y; - - break; - - case 'p': - case 'P': - std::cout << "PRESSED P\n"; - session.pause_board_rotation_x = !session.pause_board_rotation_x; - - break; - - case 'a': - case 'A': - if (session.place_x > -BOARD_CENTER) { - session.place_x -= 1; - } - - break; - - case 'w': - case 'W': - if (session.place_y < BOARD_CENTER) { - session.place_y += 1; - } - - break; - case 's': - case 'S': - if (session.place_y > -BOARD_CENTER) { - session.place_y -= 1; - } - - break; - case 'd': - case 'D': - if (session.place_x < BOARD_CENTER) { - session.place_x += 1; - } - - break; - - case '\r': - if (session.board_status[session.place_x + BOARD_CENTER] - [session.place_y + BOARD_CENTER] != 0) { - std::cout << "YOU CAN'T PLACE A PIECE HERE BECAUSE THERE ALREADY IS A " - "PIECE HERE!!!\n"; - } else { - std::cout << "ENTER KEY PRESSED!!!\n"; - make_move(session, session.place_x + BOARD_CENTER, - session.place_y + BOARD_CENTER, session.stone_color); - if (session.stone_color == 1) { - session.stone_color = 2; - } else { - session.stone_color = 1; - } - } - break; - case 'r': - case 'R': - std::cout << "You pressed 'r', the restart button. Press 'y' to confirm " - "restart. Press 'n' to cancel.\n"; - session.restart_option = 1; - break; - case 'n': - case 'N': - if (session.restart_option) { - std::cout << "You pressed 'n'. Restart option cancelled.\n"; - session.restart_option = 0; - } - break; - case 'y': - case 'Y': - if (session.restart_option) { - std::cout << "You pressed 'y'. The game has been restarted.\n"; - init_board(session); - session.stone_color = 1; - } - break; - - case 'b': - case 'B': - session.stone_color = 2; - break; - case 'v': - case 'V': - session.stone_color = 1; - break; - } -} - -// Handles mouse motion events while a button is pressed -void motion(int x, int y) { - auto &session = current_session(); - motion(session, x, y); -} - -void motion(GameSession &session, int x, int y) { - // If the RMB is pressed and dragged then zoom in / out - if (session.update_cam_z_pos) { - // Update camera position while the mouse is dragged - session.camera.z += (y - session.last_y) * ZOOM_SCALE; - session.camera.x += (x - session.last_x) * ZOOM_SCALE; - session.last_x = x; - session.last_y = y; - - // Redraw the scene from updated camera position - glutSetWindow(session.window_id); - glutPostRedisplay(); - } -} - -// Handles mouse button pressed / released events -void mouse(int button, int state, int x, int y) { - auto &session = current_session(); - mouse(session, button, state, x, y); -} - -void mouse(GameSession &session, int button, int state, int x, int y) { - // If the RMB is pressed and dragged then zoom in / out - if (button == GLUT_RIGHT_BUTTON) { - if (state == GLUT_DOWN) { - session.last_x = x; - session.last_y = y; - session.update_cam_z_pos = true; - } else { - session.update_cam_z_pos = false; - } - } -} - void idle() { auto &session = current_session(); idle(session); diff --git a/src/input/InputRouter.cpp b/src/input/InputRouter.cpp new file mode 100644 index 0000000..fd1246f --- /dev/null +++ b/src/input/InputRouter.cpp @@ -0,0 +1,227 @@ +#include "input/InputRouter.h" + +#include +#include + +#include + +#include "game/GameSession.h" + +void init_board(GameSession &session); +auto make_move(GameSession &session, int x, int y, int piece) -> int; + +namespace { + +constexpr float ZOOM_SCALE = 0.01F; + +void keyboard(GameSession &session, unsigned char key, [[maybe_unused]] int x, + [[maybe_unused]] int y); +void mouse(GameSession &session, int button, int state, int x, int y); +void motion(GameSession &session, int x, int y); + +void keyboard_callback(unsigned char key, int x, int y) { + auto &session = current_session(); + keyboard(session, key, x, y); +} + +void mouse_callback(int button, int state, int x, int y) { + auto &session = current_session(); + mouse(session, button, state, x, y); +} + +void motion_callback(int x, int y) { + auto &session = current_session(); + motion(session, x, y); +} + +void keyboard(GameSession &session, unsigned char key, [[maybe_unused]] int x, + [[maybe_unused]] int y) { + switch (key) { + case '1': + session.wireframe = !session.wireframe; + + if (session.wireframe) { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } else { + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + + break; + + case '3': + session.material = !session.material; + + break; + + case '5': + session.depth_test = !session.depth_test; + + if (session.depth_test) { + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + } else { + glDisable(GL_DEPTH_TEST); + } + + break; + + case '6': + session.cull_face = !session.cull_face; + + if (session.cull_face) { + glEnable(GL_CULL_FACE); + } else { + glDisable(GL_CULL_FACE); + } + + break; + + case '7': + session.smooth_shading = !session.smooth_shading; + + if (session.smooth_shading) { + glShadeModel(GL_SMOOTH); + } else { + glShadeModel(GL_FLAT); + } + + break; + + case '8': + session.enable_texture = !session.enable_texture; + + if (session.enable_texture) { + glEnable(GL_TEXTURE_2D); + } else { + glDisable(GL_TEXTURE_2D); + } + break; + + case 'o': + case 'O': + std::cout << "PRESSED O\n"; + session.pause_board_rotation_y = !session.pause_board_rotation_y; + + break; + + case 'p': + case 'P': + std::cout << "PRESSED P\n"; + session.pause_board_rotation_x = !session.pause_board_rotation_x; + + break; + + case 'a': + case 'A': + if (session.place_x > -BOARD_CENTER) { + session.place_x -= 1; + } + + break; + + case 'w': + case 'W': + if (session.place_y < BOARD_CENTER) { + session.place_y += 1; + } + + break; + case 's': + case 'S': + if (session.place_y > -BOARD_CENTER) { + session.place_y -= 1; + } + + break; + case 'd': + case 'D': + if (session.place_x < BOARD_CENTER) { + session.place_x += 1; + } + + break; + + case '\r': + if (session.board_status[session.place_x + BOARD_CENTER] + [session.place_y + BOARD_CENTER] != 0) { + std::cout << "YOU CAN'T PLACE A PIECE HERE BECAUSE THERE ALREADY IS A " + << "PIECE HERE!!!\n"; + } else { + std::cout << "ENTER KEY PRESSED!!!\n"; + make_move(session, session.place_x + BOARD_CENTER, + session.place_y + BOARD_CENTER, session.stone_color); + if (session.stone_color == 1) { + session.stone_color = 2; + } else { + session.stone_color = 1; + } + } + break; + case 'r': + case 'R': + std::cout << "You pressed 'r', the restart button. Press 'y' to confirm " + "restart. Press 'n' to cancel.\n"; + session.restart_option = 1; + break; + case 'n': + case 'N': + if (session.restart_option) { + std::cout << "You pressed 'n'. Restart option cancelled.\n"; + session.restart_option = 0; + } + break; + case 'y': + case 'Y': + if (session.restart_option) { + std::cout << "You pressed 'y'. The game has been restarted.\n"; + init_board(session); + session.stone_color = 1; + } + break; + + case 'b': + case 'B': + session.stone_color = 2; + break; + case 'v': + case 'V': + session.stone_color = 1; + break; + } +} + +void mouse(GameSession &session, int button, int state, int x, int y) { + if (button == GLUT_RIGHT_BUTTON) { + if (state == GLUT_DOWN) { + session.last_x = x; + session.last_y = y; + session.update_cam_z_pos = true; + } else { + session.update_cam_z_pos = false; + } + } +} + +void motion(GameSession &session, int x, int y) { + if (session.update_cam_z_pos) { + session.camera.z += (y - session.last_y) * ZOOM_SCALE; + session.camera.x += (x - session.last_x) * ZOOM_SCALE; + session.last_x = x; + session.last_y = y; + + glutSetWindow(session.window_id); + glutPostRedisplay(); + } +} + +} // namespace + +namespace input { + +void register_callbacks() { + glutKeyboardFunc(keyboard_callback); + glutMouseFunc(mouse_callback); + glutMotionFunc(motion_callback); +} + +} // namespace input diff --git a/src/input/InputRouter.h b/src/input/InputRouter.h new file mode 100644 index 0000000..aad7c8f --- /dev/null +++ b/src/input/InputRouter.h @@ -0,0 +1,10 @@ +#ifndef INPUT_INPUTROUTER_H +#define INPUT_INPUTROUTER_H + +namespace input { + +void register_callbacks(); + +} // namespace input + +#endif // INPUT_INPUTROUTER_H From 34bac69cd2ca10fa8782141aba4e2ee10933253d Mon Sep 17 00:00:00 2001 From: Robert Lech Date: Tue, 11 Nov 2025 13:20:46 -0500 Subject: [PATCH 2/7] refactor: extracts graphics logic --- CMakeLists.txt | 1 + main.cpp | 508 +------------------------------------- src/graphics/Renderer.cpp | 434 ++++++++++++++++++++++++++++++++ src/graphics/Renderer.h | 13 + 4 files changed, 452 insertions(+), 504 deletions(-) create mode 100644 src/graphics/Renderer.cpp create mode 100644 src/graphics/Renderer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cbc5dd4..4aa2033 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ include_directories(src) set(SOURCE_FILES main.cpp src/game/GameSession.cpp + src/graphics/Renderer.cpp src/input/InputRouter.cpp ) # Add main.cpp file of project root directory as source file add_executable(main ${SOURCE_FILES}) # Add executable target with source files listed in SOURCE_FILES variable diff --git a/main.cpp b/main.cpp index 202318e..da1b26f 100644 --- a/main.cpp +++ b/main.cpp @@ -1,16 +1,11 @@ // Standard libraries -#include -#include #include // For std::cout #include -#include -#include // For usleep #include // includes, graphics #include #include -#include // DevIL includes #define ILUT_USE_OPENGL #include @@ -19,29 +14,13 @@ // Project includes #include "game/GameSession.h" +#include "graphics/Renderer.h" #include "input/InputRouter.h" -constexpr GLenum GL_CLAMP_TO_EDGE_VALUE = 0x812F; -#if defined(__cpp_lib_math_constants) -constexpr float PI = std::numbers::pi_v; // Pi constant for trigonometric helpers -#else -constexpr float PI = 3.14159265358979323846F; // NOLINT(modernize-use-std-numbers) -#endif - // float rm_array[1083]; // Holds the objects that will be removed. // int p = 0; // Always points to the last index of rm_array. // declaration, forward -void display(); -void display(GameSession &session); -void init(GameSession &session); -void idle(); -void idle(GameSession &session); -void set_images(GameSession &session); -void draw_sphere(int color); -void draw_unit_cube(GameSession &session, int color); -void apply_transformations(float indx, float indy, float z); -void apply_transformation_general(float indx, float indy, float z); // Behind the Scenes void init_board(GameSession &session); @@ -50,7 +29,6 @@ auto make_move(GameSession &session, int x, int y, int piece) -> int; auto check_liberties(GameSession &session, int x, int y, int originx, int originy, int piece) -> int; void remove_block(GameSession &session, int x, int y, int piece); -void jump_off(GameSession &session, int x0, int y0, int color); //! Program entry point auto main(int argc, char **argv) -> int { @@ -81,402 +59,17 @@ auto main(int argc, char **argv) -> int { glGenTextures(2, session.textures.data()); // initialize OpenGL - init(session); + graphics::initialize(session); + init_board(session); // register rendering mainloop + graphics::register_callbacks(); input::register_callbacks(); - glutDisplayFunc(display); - glutIdleFunc(idle); glutMainLoop(); return 0; } -void idle() { - auto &session = current_session(); - idle(session); -} - -void idle(GameSession &session) { - const float board_rotation_step = 0.5f; - const float angle_limit = 360.0f; - if (!session.pause_board_rotation_y) { - session.angle_y += board_rotation_step; - if (session.angle_y > angle_limit) { - session.angle_y -= angle_limit; - } - } - - if (!session.pause_board_rotation_x) { - session.angle_x += board_rotation_step; - if (session.angle_x > angle_limit) { - session.angle_x -= angle_limit; - } - } - - /* - if (!session.pause_lighting) { - session.translate_light += board_rotation_step; - - if (session.translate_light > 360) { - session.translate_light -= 360; - } - } - */ - - glutPostRedisplay(); -} - -void lighting_func(const GameSession &session) { - // Specify light position - const std::array light_position{0.0F, 0.0F, -0.5F, 1.0F}; - glLightfv(GL_LIGHT0, GL_POSITION, light_position.data()); - - // Specify diffuse component. Diffuse component is white. - // GLfloat lightdiffuse[4] = {0.0, 0.0, 0.0, 1.0}; - const std::array light_strength{2.0F, 2.0F, 2.0F, 2.0F}; - glLightfv(GL_LIGHT0, GL_DIFFUSE, light_strength.data()); - - // Specify specular component. Specular component is green. - // TODO: Decide on desired specular component -- IF NEEDED - // GLfloat lightspecular[4] = {0.0, 0.0, 0.0, 1.0}; - // GLfloat lightspecular[4] = { 0.0, 2.0, 0.0, 1.0 }; - glLightfv(GL_LIGHT0, GL_SPECULAR, light_strength.data()); - - GLfloat spread = 90.0F - 90.0F * session.animation_time; - glLightfv(GL_LIGHT0, GL_SPOT_CUTOFF, &spread); - - glEnable(GL_LIGHT0); -} - -void material_func() { - // Add a diffuse component to our board. The diffuse reflection constant - // is white (white light source produces white reflection). - const std::array material_diffuse{0.5F, 0.5F, 1.0F, 1.0F}; - glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_diffuse.data()); - - // Add a specular component to our board. The specular reflection constant - // is white (white light source produces white reflection). - const std::array material_specular{1.0F, 1.0F, 1.0F, 1.0F}; - glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_specular.data()); - - // Defines the shinyness (the exponent to the phong model). - const std::array material_shininess{100.0F}; - glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, material_shininess.data()); -} - -void draw_sphere(int color) { - const std::array color_none{0.0F, 0.0F, 0.0F, 0.0F}; - - if (color == 0) { - const std::array color_red{1.0F, 0.0F, 0.0F, 0.0F}; - glColor4fv(color_red.data()); - glMaterialfv(GL_FRONT, GL_DIFFUSE, color_red.data()); - glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); - } - - if (color == 1) { - const std::array color_white{1.0F, 1.0F, 1.0F, 1.0F}; - glColor4fv(color_white.data()); - glMaterialfv(GL_FRONT, GL_DIFFUSE, color_white.data()); - glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); - } - - if (color == 2) { - const std::array color_black{0.0F, 0.0F, 0.0F, 0.0F}; - glColor4fv(color_black.data()); - glMaterialfv(GL_FRONT, GL_DIFFUSE, color_black.data()); - glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); - } - - if (color == 3) { - const std::array color_green{0.0F, 0.0F, 1.0F, 0.0F}; - glColor4fv(color_green.data()); - glMaterialfv(GL_FRONT, GL_DIFFUSE, color_green.data()); - glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); - } - - if (color == 4) { - const std::array color_blue{0.0F, 1.0F, 0.0F, 0.0F}; - glColor4fv(color_blue.data()); - glMaterialfv(GL_FRONT, GL_DIFFUSE, color_blue.data()); - glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); - } - - glBegin(GL_TRIANGLES); - GLUquadricObj *quadratic; - quadratic = gluNewQuadric(); // Create A Pointer To The Quadric Object - gluQuadricNormals(quadratic, GLU_SMOOTH); // Create Smooth Normals - gluQuadricTexture(quadratic, GL_TRUE); // Create Texture Coords - - gluSphere(quadratic, 1.0f, 32, 32); - glEnd(); -} - -void draw_unit_cube(GameSession &session, int color) { - if (color == 0) { - glBindTexture(GL_TEXTURE_2D, session.textures[0]); - } else if (color == 3) { - const std::array color_none{0.0F, 0.0F, 0.0F, 0.0F}; - const std::array color_green{0.0F, 0.0F, 1.0F, 0.0F}; - glColor4fv(color_green.data()); - glMaterialfv(GL_FRONT, GL_DIFFUSE, color_green.data()); - glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); - } else if (color == 4) { - const std::array color_none{0.0F, 0.0F, 0.0F, 0.0F}; - const std::array color_blue{0.0F, 1.0F, 0.0F, 0.0F}; - glColor4fv(color_blue.data()); - glMaterialfv(GL_FRONT, GL_DIFFUSE, color_blue.data()); - glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); - } - - glBegin(GL_QUADS); - - // Front Face - glNormal3f(0.0f, 0.0f, 2.0f); - glTexCoord2f(0.0f, 0.0f); - glVertex3f(-1.0f, -1.0f, 1.0f); - glTexCoord2f(1.0f, 0.0f); - glVertex3f(1.0f, -1.0f, 1.0f); - glTexCoord2f(1.0f, 1.0f); - glVertex3f(1.0f, 1.0f, 1.0f); - glTexCoord2f(0.0f, 1.0f); - glVertex3f(-1.0f, 1.0f, 1.0f); - // Back Face - glNormal3f(0.0f, 0.0f, -2.0f); - glTexCoord2f(1.0f, 0.0f); - glVertex3f(-1.0f, -1.0f, -1.0f); - glTexCoord2f(1.0f, 1.0f); - glVertex3f(-1.0f, 1.0f, -1.0f); - glTexCoord2f(0.0f, 1.0f); - glVertex3f(1.0f, 1.0f, -1.0f); - glTexCoord2f(0.0f, 0.0f); - glVertex3f(1.0f, -1.0f, -1.0f); - // Bottom Face - glNormal3f(0.0f, -2.0f, 0.0f); - glTexCoord2f(1.0f, 1.0f); - glVertex3f(-1.0f, -1.0f, -1.0f); - glTexCoord2f(0.0f, 1.0f); - glVertex3f(1.0f, -1.0f, -1.0f); - glTexCoord2f(0.0f, 0.0f); - glVertex3f(1.0f, -1.0f, 1.0f); - glTexCoord2f(1.0f, 0.0f); - glVertex3f(-1.0f, -1.0f, 1.0f); - // Right face - glNormal3f(2.0f, 0.0f, 0.0f); - glTexCoord2f(1.0f, 0.0f); - glVertex3f(1.0f, -1.0f, -1.0f); - glTexCoord2f(1.0f, 1.0f); - glVertex3f(1.0f, 1.0f, -1.0f); - glTexCoord2f(0.0f, 1.0f); - glVertex3f(1.0f, 1.0f, 1.0f); - glTexCoord2f(0.0f, 0.0f); - glVertex3f(1.0f, -1.0f, 1.0f); - // Left Face - glNormal3f(-2.0f, 0.0f, 0.0f); - glTexCoord2f(0.0f, 0.0f); - glVertex3f(-1.0f, -1.0f, -1.0f); - glTexCoord2f(1.0f, 0.0f); - glVertex3f(-1.0f, -1.0f, 1.0f); - glTexCoord2f(1.0f, 1.0f); - glVertex3f(-1.0f, 1.0f, 1.0f); - glTexCoord2f(0.0f, 1.0f); - glVertex3f(-1.0f, 1.0f, -1.0f); - - if (color == 0) { - glEnd(); - - glBindTexture(GL_TEXTURE_2D, session.textures[1]); - glBegin(GL_QUADS); - } - - // Top Face - glNormal3f(0.0f, 2.0f, 0.0f); - glTexCoord2f(0.0f, 1.0f); - glVertex3f(-1.0f, 1.0f, -1.0f); - glTexCoord2f(0.0f, 0.0f); - glVertex3f(-1.0f, 1.0f, 1.0f); - glTexCoord2f(1.0f, 0.0f); - glVertex3f(1.0f, 1.0f, 1.0f); - glTexCoord2f(1.0f, 1.0f); - glVertex3f(1.0f, 1.0f, -1.0f); - - glEnd(); - - usleep(DEFAULT_SLEEP_TIME); -} - -void display() { - auto &session = current_session(); - display(session); -} - -void display(GameSession &session) { - // clear screen to background color - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - // draw a white quad - glColor4f(1.0, 1.0, 1.0, 1.0); - - // Push lighting bit; we'll pop it later so that the lighting state - // isn't saved onto the next frame. We do this so that we can disable - // lighting more easily. - glPushAttrib(GL_LIGHTING_BIT); - - glLoadIdentity(); - - // Specify camera transformation - glTranslatef(static_cast(session.camera.x), - static_cast(session.camera.y), - static_cast(session.camera.z)); - - // Specify the lighting we'll be using for this app. Note that - // the lights can be transformed using the usual translate, rotate, - // and scale commands. - glPushMatrix(); - - //////////////////// MOVING LIGHT!!!!! //////////////////// - glTranslatef(2.0F * std::sin(session.translate_light * 2.0F * PI / 360.0F), 0.0F, - 0.0F); - - if (session.lighting) { - lighting_func(session); - } - - glPopMatrix(); - - glTranslatef(0.0, 0.0, -2.0); // Move objects to viewing area. - glRotatef(session.angle_x, 1.0, 0.0, 0.0); // To allow rotation on the x-axis - glRotatef(session.angle_y, 0.0, 1.0, 0.0); // To allow rotation on the y-axis - glPushMatrix(); - - glScalef(1.0, 0.05, 1.0); // Flatten the Cube. - // It should look like a board now. - - // Specify material for the board we'll be drawing. - if (session.material) { - material_func(); - } - - draw_unit_cube(session, 0); - - glPopMatrix(); - glPushMatrix(); - // Draw Beads - - glBindTexture(GL_TEXTURE_2D, 0); - - glScalef(0.0525, 0.0525, 0.0525); // Just the right size :) - glTranslatef(0.0, 1.0, 0.0); // The centre - - glPushMatrix(); - apply_transformations(static_cast(session.place_x), - static_cast(session.place_y), 0); - draw_sphere(0); - glPopMatrix(); - - for (int i = 0; i < 19; i++) { - for (int j = 0; j < 19; j++) { - if (session.board_status[i][j] != 0) { - glPushMatrix(); - apply_transformations(static_cast(i - 9), - static_cast(j - 9), 0); - draw_sphere(session.board_status[i][j]); - glPopMatrix(); - } - } - } - - // THE REMOVAL OF PIECE PORTION - if (!session.captured_groups.empty()) { - for (const auto &piece : session.captured_groups) { - jump_off(session, piece[0], piece[1], piece[2]); - } - session.animation_time += - (TIME_INCREMENT * static_cast(session.captured_groups.size())); - } - if (session.animation_time > 1.0F) { - session.animation_time = 0.0F; - session.captured_groups.clear(); - } - - glPopMatrix(); - glPopAttrib(); - - // immediately process commands - glFlush(); - - // immediately call display loop again - glutSwapBuffers(); -} - -void apply_transformations(float indx, float indy, [[maybe_unused]] float z) { - int i; - if (indx < 0) { - for (i = 0; i < std::abs(indx); i++) { - glTranslatef(-2.0, 0.0, 0.0); - } - } else { - for (i = 0; i < indx; i++) { - glTranslatef(2.0, 0.0, 0.0); - } - } - if (indy < 0) { - for (i = 0; i < std::abs(indy); i++) { - glTranslatef(0.0, 0.0, 2.0); - } - } else { - for (i = 0; i < indy; i++) { - glTranslatef(0.0, 0.0, -2.0); - } - } -} - -/* -We can probably get rid of the first method, -but I don't feel like doing that. -*/ - -void apply_transformation_general(float indx, float indy, float z) { - glTranslatef(2 * indx, 10 * indy, 2 * z); -} - -void set_images(GameSession &session) { - ///////////////////// LOAD WOODEN TEXTURE ///////////////////// - ilBindImage(session.image_ids[0]); - ilLoadImage("images/wooden.jpg"); - ilConvertImage(IL_RGB, IL_UNSIGNED_SHORT); - - glBindTexture(GL_TEXTURE_2D, session.textures[0]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, - static_cast(GL_CLAMP_TO_EDGE_VALUE)); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, - static_cast(GL_CLAMP_TO_EDGE_VALUE)); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, ilGetInteger(IL_IMAGE_WIDTH), - ilGetInteger(IL_IMAGE_HEIGHT), 0, GL_RGB, GL_UNSIGNED_SHORT, - ilGetData()); - - ///////////////////// LOAD CHECKERBOARD TEXTURE ///////////////// - ilBindImage(session.image_ids[1]); - ilLoadImage("images/checkerboard.png"); - ilConvertImage(IL_RGB, IL_UNSIGNED_SHORT); - - glBindTexture(GL_TEXTURE_2D, session.textures[1]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, - static_cast(GL_CLAMP_TO_EDGE_VALUE)); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, - static_cast(GL_CLAMP_TO_EDGE_VALUE)); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, ilGetInteger(IL_IMAGE_WIDTH), - ilGetInteger(IL_IMAGE_HEIGHT), 0, GL_RGB, GL_UNSIGNED_SHORT, - ilGetData()); - - glEnable(GL_NORMALIZE); -} - /////////////////////////////////////////////////////////////////////////////// /////////////////////////// BEHIND THE SCENES ///////////////////////////////// /////////////////////////////////////////////////////////////////////////////// @@ -520,67 +113,6 @@ auto check_liberties(GameSession &session, int x, int y, int originx, int origin return session.liberties_status[x][y]; } -void jump_off(GameSession &session, int x0, int z0, int color) { - // For now, let's just assume they're all jumping to point (-4,0,-4) - - int x1 = -4; - float xt = x0 * (1 - session.animation_time) + x1 * session.animation_time; - int y0 = 9; - float yt = - -y0 * std::pow(session.animation_time, 2.0F) + y0 * session.animation_time + y0; - int z1 = -4; - float zt = z0 * (1 - session.animation_time) + z1 * session.animation_time; - - glPushMatrix(); - apply_transformation_general((xt - 9), (yt - 9), -(zt - 9)); - draw_sphere(color); - - // Draw Thigh - glPushMatrix(); - if (session.animation_time < 0.1F) { - glRotatef(-80 + 80 * std::sin(session.animation_time * 10.0F * PI), 1.0, 0.0, - 1.0); - } else { - glRotatef(-80, 1.0, 0.0, 1.0); - } - glScalef(2.0 / 5.0, 1.0, 2.0 / 5.0); - glTranslatef(0.0, -1.0, 0.0); - draw_unit_cube(session, 3); - glScalef(5.0 / 2.0, 1.0, 5.0 / 2.0); - - // Draw Shin - glPushMatrix(); - - glTranslatef(0, -0.8, 0); - if (session.animation_time < 0.1F) { - glRotatef(90 - 90 * std::sin(session.animation_time * 10.0F * PI), 1.0, 0.0, - 1.0); - } else { - glRotatef(90, 1.0, 0.0, 1.0); - } - glScalef(1.0 / 5.0, 1.0, 1.0 / 5.0); - glTranslatef(0, -1.0, 0); - draw_unit_cube(session, 4); - glScalef(5.0, 1.0, 5.0); - - // Draw Ankle - glPushMatrix(); - glTranslatef(0, -1.0, 0); - if (session.animation_time < 0.1F) { - glRotatef(-100 + 100 * std::sin(session.animation_time * 10.0F * PI), 1.0, 0.0, - 1.0); - } else { - glRotatef(-100, 1.0, 0.0, 1.0); - } - glScalef(1.0 / 2.0, 2.0 / 3.0, 1.0 / 2.0); - glTranslatef(0, -1.0, 0); - draw_sphere(0); - glPopMatrix(); - glPopMatrix(); - glPopMatrix(); - glPopMatrix(); -} - void remove_block(GameSession &session, int x, int y, int piece) { if (session.board_status[x][y] == piece) { session.board_status[x][y] = 0; @@ -654,35 +186,3 @@ auto make_move(GameSession &session, int x, int y, int piece) -> int { } return 1; } - -void init(GameSession &session) { - // select clearing color - glClearColor(0.0, 0.0, 0.0, 0.0); - - // initialize projection matrix - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - double fov_y = 45; - double aspect = 1.0; - double z_near = 0.1; - double z_far = 100; - - gluPerspective(fov_y, // Field of view - aspect, // Aspect ratio (width / height) - z_near, // Near plane - z_far); // Far plane - - // initialize modelview matrix - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glEnable(GL_LIGHTING); // enable lighting - glEnable(GL_CULL_FACE); // enable backface culling - glEnable(GL_DEPTH_TEST); // enable depth testing - // glEnable(GL_TEXTURE_2D); // enable textures - - set_images(session); - - init_board(session); // Create the "Board" -} diff --git a/src/graphics/Renderer.cpp b/src/graphics/Renderer.cpp new file mode 100644 index 0000000..a62e728 --- /dev/null +++ b/src/graphics/Renderer.cpp @@ -0,0 +1,434 @@ +#include "graphics/Renderer.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "game/GameSession.h" + +namespace { + +constexpr GLenum GL_CLAMP_TO_EDGE_VALUE = 0x812F; +#if defined(__cpp_lib_math_constants) +constexpr float PI = std::numbers::pi_v; +#else +constexpr float PI = 3.14159265358979323846F; // NOLINT(modernize-use-std-numbers) +#endif + +void lighting_func(const GameSession &session) { + const std::array light_position{0.0F, 0.0F, -0.5F, 1.0F}; + glLightfv(GL_LIGHT0, GL_POSITION, light_position.data()); + + const std::array light_strength{2.0F, 2.0F, 2.0F, 2.0F}; + glLightfv(GL_LIGHT0, GL_DIFFUSE, light_strength.data()); + glLightfv(GL_LIGHT0, GL_SPECULAR, light_strength.data()); + + GLfloat spread = 90.0F - 90.0F * session.animation_time; + glLightfv(GL_LIGHT0, GL_SPOT_CUTOFF, &spread); + + glEnable(GL_LIGHT0); +} + +void material_func() { + const std::array material_diffuse{0.5F, 0.5F, 1.0F, 1.0F}; + glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_diffuse.data()); + + const std::array material_specular{1.0F, 1.0F, 1.0F, 1.0F}; + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_specular.data()); + + const std::array material_shininess{100.0F}; + glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, material_shininess.data()); +} + +void draw_sphere(int color) { + const std::array color_none{0.0F, 0.0F, 0.0F, 0.0F}; + + if (color == 0) { + const std::array color_red{1.0F, 0.0F, 0.0F, 0.0F}; + glColor4fv(color_red.data()); + glMaterialfv(GL_FRONT, GL_DIFFUSE, color_red.data()); + glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); + } + + if (color == 1) { + const std::array color_white{1.0F, 1.0F, 1.0F, 1.0F}; + glColor4fv(color_white.data()); + glMaterialfv(GL_FRONT, GL_DIFFUSE, color_white.data()); + glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); + } + + if (color == 2) { + const std::array color_black{0.0F, 0.0F, 0.0F, 0.0F}; + glColor4fv(color_black.data()); + glMaterialfv(GL_FRONT, GL_DIFFUSE, color_black.data()); + glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); + } + + if (color == 3) { + const std::array color_green{0.0F, 0.0F, 1.0F, 0.0F}; + glColor4fv(color_green.data()); + glMaterialfv(GL_FRONT, GL_DIFFUSE, color_green.data()); + glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); + } + + if (color == 4) { + const std::array color_blue{0.0F, 1.0F, 0.0F, 0.0F}; + glColor4fv(color_blue.data()); + glMaterialfv(GL_FRONT, GL_DIFFUSE, color_blue.data()); + glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); + } + + glBegin(GL_TRIANGLES); + GLUquadricObj *quadratic = gluNewQuadric(); + gluQuadricNormals(quadratic, GLU_SMOOTH); + gluQuadricTexture(quadratic, GL_TRUE); + + gluSphere(quadratic, 1.0F, 32, 32); + glEnd(); +} + +void draw_unit_cube(GameSession &session, int color) { + if (color == 0) { + glBindTexture(GL_TEXTURE_2D, session.textures[0]); + } else if (color == 3) { + const std::array color_none{0.0F, 0.0F, 0.0F, 0.0F}; + const std::array color_green{0.0F, 0.0F, 1.0F, 0.0F}; + glColor4fv(color_green.data()); + glMaterialfv(GL_FRONT, GL_DIFFUSE, color_green.data()); + glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); + } else if (color == 4) { + const std::array color_none{0.0F, 0.0F, 0.0F, 0.0F}; + const std::array color_blue{0.0F, 1.0F, 0.0F, 0.0F}; + glColor4fv(color_blue.data()); + glMaterialfv(GL_FRONT, GL_DIFFUSE, color_blue.data()); + glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); + } + + glBegin(GL_QUADS); + + glNormal3f(0.0F, 0.0F, 2.0F); + glTexCoord2f(0.0F, 0.0F); + glVertex3f(-1.0F, -1.0F, 1.0F); + glTexCoord2f(1.0F, 0.0F); + glVertex3f(1.0F, -1.0F, 1.0F); + glTexCoord2f(1.0F, 1.0F); + glVertex3f(1.0F, 1.0F, 1.0F); + glTexCoord2f(0.0F, 1.0F); + glVertex3f(-1.0F, 1.0F, 1.0F); + + glNormal3f(0.0F, 0.0F, -2.0F); + glTexCoord2f(1.0F, 0.0F); + glVertex3f(-1.0F, -1.0F, -1.0F); + glTexCoord2f(1.0F, 1.0F); + glVertex3f(-1.0F, 1.0F, -1.0F); + glTexCoord2f(0.0F, 1.0F); + glVertex3f(1.0F, 1.0F, -1.0F); + glTexCoord2f(0.0F, 0.0F); + glVertex3f(1.0F, -1.0F, -1.0F); + + glNormal3f(0.0F, -2.0F, 0.0F); + glTexCoord2f(1.0F, 1.0F); + glVertex3f(-1.0F, -1.0F, -1.0F); + glTexCoord2f(0.0F, 1.0F); + glVertex3f(1.0F, -1.0F, -1.0F); + glTexCoord2f(0.0F, 0.0F); + glVertex3f(1.0F, -1.0F, 1.0F); + glTexCoord2f(1.0F, 0.0F); + glVertex3f(-1.0F, -1.0F, 1.0F); + + glNormal3f(2.0F, 0.0F, 0.0F); + glTexCoord2f(1.0F, 0.0F); + glVertex3f(1.0F, -1.0F, -1.0F); + glTexCoord2f(1.0F, 1.0F); + glVertex3f(1.0F, 1.0F, -1.0F); + glTexCoord2f(0.0F, 1.0F); + glVertex3f(1.0F, 1.0F, 1.0F); + glTexCoord2f(0.0F, 0.0F); + glVertex3f(1.0F, -1.0F, 1.0F); + + glNormal3f(-2.0F, 0.0F, 0.0F); + glTexCoord2f(0.0F, 0.0F); + glVertex3f(-1.0F, -1.0F, -1.0F); + glTexCoord2f(1.0F, 0.0F); + glVertex3f(-1.0F, -1.0F, 1.0F); + glTexCoord2f(1.0F, 1.0F); + glVertex3f(-1.0F, 1.0F, 1.0F); + glTexCoord2f(0.0F, 1.0F); + glVertex3f(-1.0F, 1.0F, -1.0F); + + if (color == 0) { + glEnd(); + + glBindTexture(GL_TEXTURE_2D, session.textures[1]); + glBegin(GL_QUADS); + } + + glNormal3f(0.0F, 2.0F, 0.0F); + glTexCoord2f(0.0F, 1.0F); + glVertex3f(-1.0F, 1.0F, -1.0F); + glTexCoord2f(0.0F, 0.0F); + glVertex3f(-1.0F, 1.0F, 1.0F); + glTexCoord2f(1.0F, 0.0F); + glVertex3f(1.0F, 1.0F, 1.0F); + glTexCoord2f(1.0F, 1.0F); + glVertex3f(1.0F, 1.0F, -1.0F); + + glEnd(); + + usleep(DEFAULT_SLEEP_TIME); +} + +void apply_transformations(float indx, float indy, [[maybe_unused]] float z) { + const int steps_x = static_cast(std::round(std::fabs(indx))); + const int steps_y = static_cast(std::round(std::fabs(indy))); + + const float step_x = indx < 0 ? -2.0F : 2.0F; + const float step_y = indy < 0 ? 2.0F : -2.0F; + + for (int i = 0; i < steps_x; i++) { + glTranslatef(step_x, 0.0F, 0.0F); + } + + for (int i = 0; i < steps_y; i++) { + glTranslatef(0.0F, 0.0F, step_y); + } +} + +void apply_transformation_general(float indx, float indy, float z) { + glTranslatef(2 * indx, 10 * indy, 2 * z); +} + +void set_images(GameSession &session) { + ilBindImage(session.image_ids[0]); + ilLoadImage("images/wooden.jpg"); + ilConvertImage(IL_RGB, IL_UNSIGNED_SHORT); + + glBindTexture(GL_TEXTURE_2D, session.textures[0]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, + static_cast(GL_CLAMP_TO_EDGE_VALUE)); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, + static_cast(GL_CLAMP_TO_EDGE_VALUE)); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, ilGetInteger(IL_IMAGE_WIDTH), + ilGetInteger(IL_IMAGE_HEIGHT), 0, GL_RGB, GL_UNSIGNED_SHORT, + ilGetData()); + + ilBindImage(session.image_ids[1]); + ilLoadImage("images/checkerboard.png"); + ilConvertImage(IL_RGB, IL_UNSIGNED_SHORT); + + glBindTexture(GL_TEXTURE_2D, session.textures[1]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, + static_cast(GL_CLAMP_TO_EDGE_VALUE)); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, + static_cast(GL_CLAMP_TO_EDGE_VALUE)); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, ilGetInteger(IL_IMAGE_WIDTH), + ilGetInteger(IL_IMAGE_HEIGHT), 0, GL_RGB, GL_UNSIGNED_SHORT, + ilGetData()); + + glEnable(GL_NORMALIZE); +} + +void jump_off(GameSession &session, int x0, int z0, int color) { + const float animation = session.animation_time; + const bool early_animation = animation < 0.1F; + + int x1 = -4; + const float xt = x0 * (1 - animation) + static_cast(x1) * animation; + int y0 = 9; + const auto y0_float = static_cast(y0); + const float yt = + -y0_float * std::pow(animation, 2.0F) + y0_float * animation + y0_float; + int z1 = -4; + const float zt = z0 * (1 - animation) + static_cast(z1) * animation; + + glPushMatrix(); + apply_transformation_general((xt - 9), (yt - 9), -(zt - 9)); + draw_sphere(color); + + glPushMatrix(); + const float thigh_angle = + early_animation ? -80 + 80 * std::sin(animation * 10.0F * PI) : -80; + glRotatef(thigh_angle, 1.0F, 0.0F, 1.0F); + glScalef(2.0F / 5.0F, 1.0F, 2.0F / 5.0F); + glTranslatef(0.0F, -1.0F, 0.0F); + draw_unit_cube(session, 3); + glScalef(5.0F / 2.0F, 1.0F, 5.0F / 2.0F); + + glPushMatrix(); + + glTranslatef(0.0F, -0.8F, 0.0F); + const float shin_angle = + early_animation ? 90 - 90 * std::sin(animation * 10.0F * PI) : 90; + glRotatef(shin_angle, 1.0F, 0.0F, 1.0F); + glScalef(1.0F / 5.0F, 1.0F, 1.0F / 5.0F); + glTranslatef(0.0F, -1.0F, 0.0F); + draw_unit_cube(session, 4); + glScalef(5.0F, 1.0F, 5.0F); + + glPushMatrix(); + glTranslatef(0.0F, -1.0F, 0.0F); + const float ankle_angle = + early_animation ? -100 + 100 * std::sin(animation * 10.0F * PI) : -100; + glRotatef(ankle_angle, 1.0F, 0.0F, 1.0F); + glScalef(1.0F / 2.0F, 2.0F / 3.0F, 1.0F / 2.0F); + glTranslatef(0.0F, -1.0F, 0.0F); + draw_sphere(0); + glPopMatrix(); + glPopMatrix(); + glPopMatrix(); + glPopMatrix(); +} + +void display(GameSession &session) { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + + glPushAttrib(GL_LIGHTING_BIT); + glLoadIdentity(); + + glTranslatef(static_cast(session.camera.x), + static_cast(session.camera.y), + static_cast(session.camera.z)); + + glPushMatrix(); + const float light_offset = std::sin(session.translate_light * 2.0F * PI / 360.0F); + glTranslatef(2.0F * light_offset, 0.0F, 0.0F); + + if (session.lighting) { + lighting_func(session); + } + + glPopMatrix(); + + glTranslatef(0.0F, 0.0F, -2.0F); + glRotatef(session.angle_x, 1.0F, 0.0F, 0.0F); + glRotatef(session.angle_y, 0.0F, 1.0F, 0.0F); + glPushMatrix(); + + glScalef(1.0F, 0.05F, 1.0F); + + if (session.material) { + material_func(); + } + + draw_unit_cube(session, 0); + + glPopMatrix(); + glPushMatrix(); + + glBindTexture(GL_TEXTURE_2D, 0); + + glScalef(0.0525F, 0.0525F, 0.0525F); + glTranslatef(0.0F, 1.0F, 0.0F); + + glPushMatrix(); + apply_transformations(static_cast(session.place_x), + static_cast(session.place_y), 0.0F); + draw_sphere(0); + glPopMatrix(); + + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + if (session.board_status[i][j] != 0) { + glPushMatrix(); + apply_transformations(static_cast(i - BOARD_CENTER), + static_cast(j - BOARD_CENTER), 0.0F); + draw_sphere(session.board_status[i][j]); + glPopMatrix(); + } + } + } + + if (!session.captured_groups.empty()) { + for (const auto &piece : session.captured_groups) { + jump_off(session, piece[0], piece[1], piece[2]); + } + session.animation_time += + (TIME_INCREMENT * static_cast(session.captured_groups.size())); + } + if (session.animation_time > 1.0F) { + session.animation_time = 0.0F; + session.captured_groups.clear(); + } + + glPopMatrix(); + glPopAttrib(); + + glFlush(); + glutSwapBuffers(); +} + +void idle(GameSession &session) { + const float board_rotation_step = 0.5F; + const float angle_limit = 360.0F; + if (!session.pause_board_rotation_y) { + session.angle_y += board_rotation_step; + if (session.angle_y > angle_limit) { + session.angle_y -= angle_limit; + } + } + + if (!session.pause_board_rotation_x) { + session.angle_x += board_rotation_step; + if (session.angle_x > angle_limit) { + session.angle_x -= angle_limit; + } + } + + glutPostRedisplay(); +} + +void display_callback() { + auto &session = current_session(); + display(session); +} + +void idle_callback() { + auto &session = current_session(); + idle(session); +} + +} // namespace + +namespace graphics { + +void initialize(GameSession &session) { + glClearColor(0.0F, 0.0F, 0.0F, 0.0F); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + constexpr double FOV_Y = 45.0; + constexpr double ASPECT = 1.0; + constexpr double Z_NEAR = 0.1; + constexpr double Z_FAR = 100.0; + + gluPerspective(FOV_Y, ASPECT, Z_NEAR, Z_FAR); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glEnable(GL_LIGHTING); + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + + set_images(session); +} + +void register_callbacks() { + glutDisplayFunc(display_callback); + glutIdleFunc(idle_callback); +} + +} // namespace graphics diff --git a/src/graphics/Renderer.h b/src/graphics/Renderer.h new file mode 100644 index 0000000..63c33c7 --- /dev/null +++ b/src/graphics/Renderer.h @@ -0,0 +1,13 @@ +#ifndef GRAPHICS_RENDERER_H +#define GRAPHICS_RENDERER_H + +class GameSession; + +namespace graphics { + +void initialize(GameSession &session); +void register_callbacks(); + +} + +#endif // GRAPHICS_RENDERER_H From 3b4b98f697586c985db8937bfa249797c91c0d77 Mon Sep 17 00:00:00 2001 From: Robert Lech Date: Tue, 11 Nov 2025 13:30:08 -0500 Subject: [PATCH 3/7] refactor: extracts rules logic --- CMakeLists.txt | 1 + main.cpp | 135 +------------------------------------- src/game/Rules.cpp | 120 +++++++++++++++++++++++++++++++++ src/game/Rules.h | 13 ++++ src/input/InputRouter.cpp | 10 ++- 5 files changed, 140 insertions(+), 139 deletions(-) create mode 100644 src/game/Rules.cpp create mode 100644 src/game/Rules.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4aa2033..72162db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ include_directories(src) set(SOURCE_FILES main.cpp src/game/GameSession.cpp + src/game/Rules.cpp src/graphics/Renderer.cpp src/input/InputRouter.cpp ) # Add main.cpp file of project root directory as source file diff --git a/main.cpp b/main.cpp index da1b26f..f4452d8 100644 --- a/main.cpp +++ b/main.cpp @@ -1,8 +1,3 @@ -// Standard libraries -#include // For std::cout -#include -#include - // includes, graphics #include #include @@ -14,22 +9,13 @@ // Project includes #include "game/GameSession.h" +#include "game/Rules.h" #include "graphics/Renderer.h" #include "input/InputRouter.h" // float rm_array[1083]; // Holds the objects that will be removed. // int p = 0; // Always points to the last index of rm_array. -// declaration, forward - -// Behind the Scenes -void init_board(GameSession &session); -void clear_liberties(GameSession &session); -auto make_move(GameSession &session, int x, int y, int piece) -> int; -auto check_liberties(GameSession &session, int x, int y, int originx, int originy, - int piece) -> int; -void remove_block(GameSession &session, int x, int y, int piece); - //! Program entry point auto main(int argc, char **argv) -> int { const int default_window_width = 768; @@ -60,7 +46,7 @@ auto main(int argc, char **argv) -> int { // initialize OpenGL graphics::initialize(session); - init_board(session); + rules::init_board(session); // register rendering mainloop graphics::register_callbacks(); @@ -69,120 +55,3 @@ auto main(int argc, char **argv) -> int { return 0; } - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////// BEHIND THE SCENES ///////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// - -void init_board(GameSession &session) { - for (auto &column : session.board_status) { - column.fill(0); - } -} - -void clear_liberties(GameSession &session) { - for (auto &column : session.liberties_status) { - column.fill(-1); - } -} - -auto check_liberties(GameSession &session, int x, int y, int originx, int originy, - int piece) -> int { - if (x < 0 || x > 18 || y < 0 || y > 18) { - return 0; - } - if (session.board_status[x][y] == 0) { - return 1; - } - if (session.board_status[x][y] != piece) { - return 0; - } - if (session.liberties_status[x][y] != -1) { - return session.liberties_status[x][y]; - } - int direcx = x - originx; - int direcy = y - originy; - if ((direcx >= 0 && check_liberties(session, x + 1, y, originx, originy, piece)) || - (direcx <= 0 && check_liberties(session, x - 1, y, originx, originy, piece)) || - (direcy >= 0 && check_liberties(session, x, y + 1, originx, originy, piece)) || - (direcy <= 0 && check_liberties(session, x, y - 1, originx, originy, piece))) { - session.liberties_status[x][y] = 1; - } else { - session.liberties_status[x][y] = 0; - } - return session.liberties_status[x][y]; -} - -void remove_block(GameSession &session, int x, int y, int piece) { - if (session.board_status[x][y] == piece) { - session.board_status[x][y] = 0; - std::cout << "Jump from "; - std::cout << x; - std::cout << y; - std::cout << '\n'; - - // rm_queue.push(x); - // rm_queue.push(y); - // rm_queue.push(piece); - - std::vector rm_piece; - rm_piece.push_back(x); - rm_piece.push_back(y); - rm_piece.push_back(piece); - session.captured_groups.push_back(rm_piece); - - remove_block(session, x - 1, y, piece); - remove_block(session, x + 1, y, piece); - remove_block(session, x, y - 1, piece); - remove_block(session, x, y + 1, piece); - } -} - -auto make_move(GameSession &session, int x, int y, int piece) -> int { - session.board_status[x][y] = piece; - clear_liberties(session); - int other; - if (piece == 1) { - other = 2; - } else { - other = 1; - } - int has_liberties = 0; - if (x > 0 && session.board_status[x - 1][y] != piece) { - if (piece == 0) { - has_liberties = 1; - } else if (!check_liberties(session, x - 1, y, x - 1, y, other)) { - has_liberties = 1; - remove_block(session, x - 1, y, other); - } - } - if (x < 18 && session.board_status[x + 1][y] != piece) { - if (piece == 0) { - has_liberties = 1; - } else if (!check_liberties(session, x + 1, y, x + 1, y, other)) { - has_liberties = 1; - remove_block(session, x + 1, y, other); - } - } - if (y > 0 && session.board_status[x][y - 1] != piece) { - if (piece == 0) { - has_liberties = 1; - } else if (!check_liberties(session, x, y - 1, x, y - 1, other)) { - has_liberties = 1; - remove_block(session, x, y - 1, other); - } - } - if (y < 18 && session.board_status[x][y + 1] != piece) { - if (piece == 0) { - has_liberties = 1; - } else if (!check_liberties(session, x, y + 1, x, y + 1, other)) { - has_liberties = 1; - remove_block(session, x, y + 1, other); - } - } - if (!has_liberties && !check_liberties(session, x, y, x, y, piece)) { - remove_block(session, x, y, piece); - return 0; - } - return 1; -} diff --git a/src/game/Rules.cpp b/src/game/Rules.cpp new file mode 100644 index 0000000..d411fbb --- /dev/null +++ b/src/game/Rules.cpp @@ -0,0 +1,120 @@ +#include "game/Rules.h" + +#include +#include +#include +#include + +#include "game/GameSession.h" + +namespace { + +void init_board_state(GameSession &session) { + for (auto &column : session.board_status) { + column.fill(0); + } +} + +void clear_liberties(GameSession &session) { + for (auto &column : session.liberties_status) { + column.fill(-1); + } +} + +void remove_block(GameSession &session, int x, int y, int piece) { + if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE) { + return; + } + if (session.board_status[x][y] != piece) { + return; + } + + session.board_status[x][y] = 0; + std::cout << "Jump from " << x << y << '\n'; + + std::vector rm_piece; + rm_piece.push_back(x); + rm_piece.push_back(y); + rm_piece.push_back(piece); + session.captured_groups.push_back(rm_piece); + + remove_block(session, x - 1, y, piece); + remove_block(session, x + 1, y, piece); + remove_block(session, x, y - 1, piece); + remove_block(session, x, y + 1, piece); +} + +auto check_liberties(GameSession &session, int x, int y, int originx, int originy, + int piece) -> int { + if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE) { + return 0; + } + if (session.board_status[x][y] == 0) { + return 1; + } + if (session.board_status[x][y] != piece) { + return 0; + } + if (session.liberties_status[x][y] != -1) { + return session.liberties_status[x][y]; + } + + const int direcx = x - originx; + const int direcy = y - originy; + if ((direcx >= 0 && check_liberties(session, x + 1, y, originx, originy, piece)) || + (direcx <= 0 && check_liberties(session, x - 1, y, originx, originy, piece)) || + (direcy >= 0 && check_liberties(session, x, y + 1, originx, originy, piece)) || + (direcy <= 0 && check_liberties(session, x, y - 1, originx, originy, piece))) { + session.liberties_status[x][y] = 1; + } else { + session.liberties_status[x][y] = 0; + } + return session.liberties_status[x][y]; +} + +} // namespace + +namespace rules { + +void init_board(GameSession &session) { init_board_state(session); } + +auto make_move(GameSession &session, int x, int y, int piece) -> int { + session.board_status[x][y] = piece; + clear_liberties(session); + + const int other = piece == 1 ? 2 : 1; + int has_liberties = 0; + + const std::array, 4> neighbors{{ + {x - 1, y}, + {x + 1, y}, + {x, y - 1}, + {x, y + 1}, + }}; + + for (const auto &[nx, ny] : neighbors) { + if (nx < 0 || nx >= BOARD_SIZE || ny < 0 || ny >= BOARD_SIZE) { + continue; + } + + if (session.board_status[nx][ny] == piece) { + continue; + } + + if (piece == 0) { + has_liberties = 1; + } else if (!check_liberties(session, nx, ny, nx, ny, other)) { + has_liberties = 1; + remove_block(session, nx, ny, other); + } + } + + if (!has_liberties && !check_liberties(session, x, y, x, y, piece)) { + remove_block(session, x, y, piece); + return 0; + } + + return 1; +} + +} // namespace rules diff --git a/src/game/Rules.h b/src/game/Rules.h new file mode 100644 index 0000000..91a2142 --- /dev/null +++ b/src/game/Rules.h @@ -0,0 +1,13 @@ +#ifndef GAME_RULES_H +#define GAME_RULES_H + +class GameSession; + +namespace rules { + +void init_board(GameSession &session); +auto make_move(GameSession &session, int x, int y, int piece) -> int; + +} // namespace rules + +#endif // GAME_RULES_H diff --git a/src/input/InputRouter.cpp b/src/input/InputRouter.cpp index fd1246f..8f078b6 100644 --- a/src/input/InputRouter.cpp +++ b/src/input/InputRouter.cpp @@ -6,9 +6,7 @@ #include #include "game/GameSession.h" - -void init_board(GameSession &session); -auto make_move(GameSession &session, int x, int y, int piece) -> int; +#include "game/Rules.h" namespace { @@ -148,8 +146,8 @@ void keyboard(GameSession &session, unsigned char key, [[maybe_unused]] int x, << "PIECE HERE!!!\n"; } else { std::cout << "ENTER KEY PRESSED!!!\n"; - make_move(session, session.place_x + BOARD_CENTER, - session.place_y + BOARD_CENTER, session.stone_color); + rules::make_move(session, session.place_x + BOARD_CENTER, + session.place_y + BOARD_CENTER, session.stone_color); if (session.stone_color == 1) { session.stone_color = 2; } else { @@ -174,7 +172,7 @@ void keyboard(GameSession &session, unsigned char key, [[maybe_unused]] int x, case 'Y': if (session.restart_option) { std::cout << "You pressed 'y'. The game has been restarted.\n"; - init_board(session); + rules::init_board(session); session.stone_color = 1; } break; From c52227df812d51d3f7b77ff7135f287084ae1d40 Mon Sep 17 00:00:00 2001 From: Robert Lech Date: Tue, 11 Nov 2025 13:49:33 -0500 Subject: [PATCH 4/7] refactor: extracts board logic --- CMakeLists.txt | 1 + src/game/Board.cpp | 37 ++++++++++++++++++++++++++++ src/game/Board.h | 38 ++++++++++++++++++++++++++++ src/game/GameSession.h | 12 ++++----- src/game/Rules.cpp | 52 ++++++++++++++++----------------------- src/graphics/Renderer.cpp | 14 ++++++----- src/input/InputRouter.cpp | 8 +++--- 7 files changed, 115 insertions(+), 47 deletions(-) create mode 100644 src/game/Board.cpp create mode 100644 src/game/Board.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 72162db..94feca3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ include_directories(src) set(SOURCE_FILES main.cpp src/game/GameSession.cpp + src/game/Board.cpp src/game/Rules.cpp src/graphics/Renderer.cpp src/input/InputRouter.cpp diff --git a/src/game/Board.cpp b/src/game/Board.cpp new file mode 100644 index 0000000..fff4f01 --- /dev/null +++ b/src/game/Board.cpp @@ -0,0 +1,37 @@ +#include "game/Board.h" + +#include + +void Board::clear() { + for (auto &column : stones_) { + column.fill(0); + } + clear_liberties(); + clear_captured_groups(); +} + +void Board::clear_liberties() { + for (auto &column : liberties_) { + column.fill(-1); + } +} + +auto Board::stones() -> Grid & { return stones_; } + +auto Board::stones() const -> const Grid & { return stones_; } + +auto Board::liberties() -> Grid & { return liberties_; } + +auto Board::liberties() const -> const Grid & { return liberties_; } + +auto Board::captured_groups() -> CaptureGroups & { return captured_groups_; } + +auto Board::captured_groups() const -> const CaptureGroups & { + return captured_groups_; +} + +void Board::add_captured_group(CaptureGroup group) { + captured_groups_.push_back(std::move(group)); +} + +void Board::clear_captured_groups() { captured_groups_.clear(); } diff --git a/src/game/Board.h b/src/game/Board.h new file mode 100644 index 0000000..0aabdec --- /dev/null +++ b/src/game/Board.h @@ -0,0 +1,38 @@ +#ifndef GAME_BOARD_H +#define GAME_BOARD_H + +#include +#include +#include + +class Board { + public: + static constexpr int SIZE = 19; + static constexpr int CENTER = SIZE / 2; + + using Grid = std::array, SIZE>; + using CaptureGroup = std::vector; + using CaptureGroups = std::list; + + void clear(); + void clear_liberties(); + + auto stones() -> Grid &; + auto stones() const -> const Grid &; + + auto liberties() -> Grid &; + auto liberties() const -> const Grid &; + + auto captured_groups() -> CaptureGroups &; + auto captured_groups() const -> const CaptureGroups &; + + void add_captured_group(CaptureGroup group); + void clear_captured_groups(); + + private: + Grid stones_{}; + Grid liberties_{}; + CaptureGroups captured_groups_; +}; + +#endif // GAME_BOARD_H diff --git a/src/game/GameSession.h b/src/game/GameSession.h index 78ceb4d..6c9f61d 100644 --- a/src/game/GameSession.h +++ b/src/game/GameSession.h @@ -2,14 +2,14 @@ #define GAME_SESSION_H #include -#include -#include #include #include -constexpr int BOARD_SIZE = 19; -constexpr int BOARD_CENTER = BOARD_SIZE / 2; +#include "game/Board.h" + +constexpr int BOARD_SIZE = Board::SIZE; +constexpr int BOARD_CENTER = Board::CENTER; // Camera initial position constexpr GLdouble INITIAL_CAM_X = 0.0; @@ -60,10 +60,8 @@ class GameSession { int stone_color = 1; float animation_time = 0.0F; - std::array, BOARD_SIZE> board_status{}; - std::array, BOARD_SIZE> liberties_status{}; int restart_option = 0; - std::list> captured_groups; + Board board; }; auto current_session() -> GameSession &; diff --git a/src/game/Rules.cpp b/src/game/Rules.cpp index d411fbb..b4900bd 100644 --- a/src/game/Rules.cpp +++ b/src/game/Rules.cpp @@ -3,40 +3,25 @@ #include #include #include -#include #include "game/GameSession.h" namespace { -void init_board_state(GameSession &session) { - for (auto &column : session.board_status) { - column.fill(0); - } -} - -void clear_liberties(GameSession &session) { - for (auto &column : session.liberties_status) { - column.fill(-1); - } -} - void remove_block(GameSession &session, int x, int y, int piece) { if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE) { return; } - if (session.board_status[x][y] != piece) { + auto &stones = session.board.stones(); + if (stones[x][y] != piece) { return; } - session.board_status[x][y] = 0; + stones[x][y] = 0; std::cout << "Jump from " << x << y << '\n'; - std::vector rm_piece; - rm_piece.push_back(x); - rm_piece.push_back(y); - rm_piece.push_back(piece); - session.captured_groups.push_back(rm_piece); + Board::CaptureGroup rm_piece{x, y, piece}; + session.board.add_captured_group(std::move(rm_piece)); remove_block(session, x - 1, y, piece); remove_block(session, x + 1, y, piece); @@ -49,14 +34,16 @@ auto check_liberties(GameSession &session, int x, int y, int originx, int origin if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE) { return 0; } - if (session.board_status[x][y] == 0) { + auto &stones = session.board.stones(); + if (stones[x][y] == 0) { return 1; } - if (session.board_status[x][y] != piece) { + if (stones[x][y] != piece) { return 0; } - if (session.liberties_status[x][y] != -1) { - return session.liberties_status[x][y]; + auto &liberties = session.board.liberties(); + if (liberties[x][y] != -1) { + return liberties[x][y]; } const int direcx = x - originx; @@ -65,22 +52,25 @@ auto check_liberties(GameSession &session, int x, int y, int originx, int origin (direcx <= 0 && check_liberties(session, x - 1, y, originx, originy, piece)) || (direcy >= 0 && check_liberties(session, x, y + 1, originx, originy, piece)) || (direcy <= 0 && check_liberties(session, x, y - 1, originx, originy, piece))) { - session.liberties_status[x][y] = 1; + liberties[x][y] = 1; } else { - session.liberties_status[x][y] = 0; + liberties[x][y] = 0; } - return session.liberties_status[x][y]; + return liberties[x][y]; } } // namespace namespace rules { -void init_board(GameSession &session) { init_board_state(session); } +void init_board(GameSession &session) { session.board.clear(); } auto make_move(GameSession &session, int x, int y, int piece) -> int { - session.board_status[x][y] = piece; - clear_liberties(session); + auto &board = session.board; + auto &stones = board.stones(); + + stones[x][y] = piece; + board.clear_liberties(); const int other = piece == 1 ? 2 : 1; int has_liberties = 0; @@ -97,7 +87,7 @@ auto make_move(GameSession &session, int x, int y, int piece) -> int { continue; } - if (session.board_status[nx][ny] == piece) { + if (stones[nx][ny] == piece) { continue; } diff --git a/src/graphics/Renderer.cpp b/src/graphics/Renderer.cpp index a62e728..a2d50ad 100644 --- a/src/graphics/Renderer.cpp +++ b/src/graphics/Renderer.cpp @@ -338,28 +338,30 @@ void display(GameSession &session) { draw_sphere(0); glPopMatrix(); + const auto &stones = session.board.stones(); for (int i = 0; i < BOARD_SIZE; i++) { for (int j = 0; j < BOARD_SIZE; j++) { - if (session.board_status[i][j] != 0) { + if (stones[i][j] != 0) { glPushMatrix(); apply_transformations(static_cast(i - BOARD_CENTER), static_cast(j - BOARD_CENTER), 0.0F); - draw_sphere(session.board_status[i][j]); + draw_sphere(stones[i][j]); glPopMatrix(); } } } - if (!session.captured_groups.empty()) { - for (const auto &piece : session.captured_groups) { + const auto &captured = session.board.captured_groups(); + if (!captured.empty()) { + for (const auto &piece : captured) { jump_off(session, piece[0], piece[1], piece[2]); } session.animation_time += - (TIME_INCREMENT * static_cast(session.captured_groups.size())); + (TIME_INCREMENT * static_cast(captured.size())); } if (session.animation_time > 1.0F) { session.animation_time = 0.0F; - session.captured_groups.clear(); + session.board.clear_captured_groups(); } glPopMatrix(); diff --git a/src/input/InputRouter.cpp b/src/input/InputRouter.cpp index 8f078b6..d498e50 100644 --- a/src/input/InputRouter.cpp +++ b/src/input/InputRouter.cpp @@ -139,9 +139,10 @@ void keyboard(GameSession &session, unsigned char key, [[maybe_unused]] int x, break; - case '\r': - if (session.board_status[session.place_x + BOARD_CENTER] - [session.place_y + BOARD_CENTER] != 0) { + case '\r': { + const auto &stones = session.board.stones(); + if (stones[session.place_x + BOARD_CENTER][session.place_y + BOARD_CENTER] != + 0) { std::cout << "YOU CAN'T PLACE A PIECE HERE BECAUSE THERE ALREADY IS A " << "PIECE HERE!!!\n"; } else { @@ -155,6 +156,7 @@ void keyboard(GameSession &session, unsigned char key, [[maybe_unused]] int x, } } break; + } case 'r': case 'R': std::cout << "You pressed 'r', the restart button. Press 'y' to confirm " From 67e4a2339dc036f7b0dd88dfb85e79ea00b2677b Mon Sep 17 00:00:00 2001 From: Robert Lech Date: Tue, 11 Nov 2025 14:02:28 -0500 Subject: [PATCH 5/7] refactor: extracts game state --- CMakeLists.txt | 1 + src/game/GameSession.h | 8 ++----- src/game/GameState.cpp | 39 +++++++++++++++++++++++++++++++ src/game/GameState.h | 34 +++++++++++++++++++++++++++ src/graphics/Renderer.cpp | 18 ++++++++------- src/input/InputRouter.cpp | 48 +++++++++++++++++++-------------------- 6 files changed, 110 insertions(+), 38 deletions(-) create mode 100644 src/game/GameState.cpp create mode 100644 src/game/GameState.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 94feca3..6d14f94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ include_directories(src) set(SOURCE_FILES main.cpp src/game/GameSession.cpp + src/game/GameState.cpp src/game/Board.cpp src/game/Rules.cpp src/graphics/Renderer.cpp diff --git a/src/game/GameSession.h b/src/game/GameSession.h index 6c9f61d..5f0b450 100644 --- a/src/game/GameSession.h +++ b/src/game/GameSession.h @@ -7,6 +7,7 @@ #include #include "game/Board.h" +#include "game/GameState.h" constexpr int BOARD_SIZE = Board::SIZE; constexpr int BOARD_CENTER = Board::CENTER; @@ -55,12 +56,7 @@ class GameSession { Camera camera{.x = INITIAL_CAM_X, .y = INITIAL_CAM_Y, .z = INITIAL_CAM_Z}; - int place_x = 0; - int place_y = 0; - int stone_color = 1; - float animation_time = 0.0F; - - int restart_option = 0; + GameState state; Board board; }; diff --git a/src/game/GameState.cpp b/src/game/GameState.cpp new file mode 100644 index 0000000..0540070 --- /dev/null +++ b/src/game/GameState.cpp @@ -0,0 +1,39 @@ +#include "game/GameState.h" + +void GameState::set_cursor(int x, int y) { + cursor_x_ = x; + cursor_y_ = y; +} + +void GameState::move_cursor(int dx, int dy) { + cursor_x_ += dx; + cursor_y_ += dy; +} + +void GameState::set_cursor_x(int x) { cursor_x_ = x; } + +void GameState::set_cursor_y(int y) { cursor_y_ = y; } + +auto GameState::cursor_x() const -> int { return cursor_x_; } + +auto GameState::cursor_y() const -> int { return cursor_y_; } + +void GameState::set_current_player(int color) { current_player_ = color; } + +auto GameState::current_player() const -> int { return current_player_; } + +void GameState::advance_turn() { current_player_ = current_player_ == 1 ? 2 : 1; } + +void GameState::request_restart() { restart_pending_ = true; } + +void GameState::cancel_restart() { restart_pending_ = false; } + +auto GameState::restart_pending() const -> bool { return restart_pending_; } + +void GameState::reset_animation() { animation_time_ = 0.0F; } + +void GameState::advance_animation(float delta) { animation_time_ += delta; } + +void GameState::set_animation_time(float time) { animation_time_ = time; } + +auto GameState::animation_time() const -> float { return animation_time_; } diff --git a/src/game/GameState.h b/src/game/GameState.h new file mode 100644 index 0000000..8b2a6ba --- /dev/null +++ b/src/game/GameState.h @@ -0,0 +1,34 @@ +#ifndef GAME_STATE_H +#define GAME_STATE_H + +class GameState { + public: + void set_cursor(int x, int y); + void move_cursor(int dx, int dy); + void set_cursor_x(int x); + void set_cursor_y(int y); + auto cursor_x() const -> int; + auto cursor_y() const -> int; + + void set_current_player(int color); + auto current_player() const -> int; + void advance_turn(); + + void request_restart(); + void cancel_restart(); + auto restart_pending() const -> bool; + + void reset_animation(); + void advance_animation(float delta); + void set_animation_time(float time); + auto animation_time() const -> float; + + private: + int cursor_x_ = 0; + int cursor_y_ = 0; + int current_player_ = 1; + bool restart_pending_ = false; + float animation_time_ = 0.0F; +}; + +#endif // GAME_STATE_H diff --git a/src/graphics/Renderer.cpp b/src/graphics/Renderer.cpp index a2d50ad..7e9de78 100644 --- a/src/graphics/Renderer.cpp +++ b/src/graphics/Renderer.cpp @@ -30,7 +30,7 @@ void lighting_func(const GameSession &session) { glLightfv(GL_LIGHT0, GL_DIFFUSE, light_strength.data()); glLightfv(GL_LIGHT0, GL_SPECULAR, light_strength.data()); - GLfloat spread = 90.0F - 90.0F * session.animation_time; + GLfloat spread = 90.0F - 90.0F * session.state.animation_time(); glLightfv(GL_LIGHT0, GL_SPOT_CUTOFF, &spread); glEnable(GL_LIGHT0); @@ -240,7 +240,7 @@ void set_images(GameSession &session) { } void jump_off(GameSession &session, int x0, int z0, int color) { - const float animation = session.animation_time; + const float animation = session.state.animation_time(); const bool early_animation = animation < 0.1F; int x1 = -4; @@ -333,8 +333,10 @@ void display(GameSession &session) { glTranslatef(0.0F, 1.0F, 0.0F); glPushMatrix(); - apply_transformations(static_cast(session.place_x), - static_cast(session.place_y), 0.0F); + const int cursor_x = session.state.cursor_x(); + const int cursor_y = session.state.cursor_y(); + apply_transformations(static_cast(cursor_x), static_cast(cursor_y), + 0.0F); draw_sphere(0); glPopMatrix(); @@ -356,11 +358,11 @@ void display(GameSession &session) { for (const auto &piece : captured) { jump_off(session, piece[0], piece[1], piece[2]); } - session.animation_time += - (TIME_INCREMENT * static_cast(captured.size())); + session.state.advance_animation(TIME_INCREMENT * + static_cast(captured.size())); } - if (session.animation_time > 1.0F) { - session.animation_time = 0.0F; + if (session.state.animation_time() > 1.0F) { + session.state.reset_animation(); session.board.clear_captured_groups(); } diff --git a/src/input/InputRouter.cpp b/src/input/InputRouter.cpp index d498e50..0b72db3 100644 --- a/src/input/InputRouter.cpp +++ b/src/input/InputRouter.cpp @@ -34,6 +34,7 @@ void motion_callback(int x, int y) { void keyboard(GameSession &session, unsigned char key, [[maybe_unused]] int x, [[maybe_unused]] int y) { + auto &state = session.state; switch (key) { case '1': session.wireframe = !session.wireframe; @@ -111,49 +112,46 @@ void keyboard(GameSession &session, unsigned char key, [[maybe_unused]] int x, case 'a': case 'A': - if (session.place_x > -BOARD_CENTER) { - session.place_x -= 1; + if (state.cursor_x() > -BOARD_CENTER) { + state.move_cursor(-1, 0); } break; case 'w': case 'W': - if (session.place_y < BOARD_CENTER) { - session.place_y += 1; + if (state.cursor_y() < BOARD_CENTER) { + state.move_cursor(0, 1); } break; case 's': case 'S': - if (session.place_y > -BOARD_CENTER) { - session.place_y -= 1; + if (state.cursor_y() > -BOARD_CENTER) { + state.move_cursor(0, -1); } break; case 'd': case 'D': - if (session.place_x < BOARD_CENTER) { - session.place_x += 1; + if (state.cursor_x() < BOARD_CENTER) { + state.move_cursor(1, 0); } break; case '\r': { const auto &stones = session.board.stones(); - if (stones[session.place_x + BOARD_CENTER][session.place_y + BOARD_CENTER] != - 0) { + const int cursor_x = state.cursor_x(); + const int cursor_y = state.cursor_y(); + if (stones[cursor_x + BOARD_CENTER][cursor_y + BOARD_CENTER] != 0) { std::cout << "YOU CAN'T PLACE A PIECE HERE BECAUSE THERE ALREADY IS A " << "PIECE HERE!!!\n"; } else { std::cout << "ENTER KEY PRESSED!!!\n"; - rules::make_move(session, session.place_x + BOARD_CENTER, - session.place_y + BOARD_CENTER, session.stone_color); - if (session.stone_color == 1) { - session.stone_color = 2; - } else { - session.stone_color = 1; - } + rules::make_move(session, cursor_x + BOARD_CENTER, cursor_y + BOARD_CENTER, + state.current_player()); + state.advance_turn(); } break; } @@ -161,31 +159,33 @@ void keyboard(GameSession &session, unsigned char key, [[maybe_unused]] int x, case 'R': std::cout << "You pressed 'r', the restart button. Press 'y' to confirm " "restart. Press 'n' to cancel.\n"; - session.restart_option = 1; + state.request_restart(); break; case 'n': case 'N': - if (session.restart_option) { + if (state.restart_pending()) { std::cout << "You pressed 'n'. Restart option cancelled.\n"; - session.restart_option = 0; + state.cancel_restart(); } break; case 'y': case 'Y': - if (session.restart_option) { + if (state.restart_pending()) { std::cout << "You pressed 'y'. The game has been restarted.\n"; rules::init_board(session); - session.stone_color = 1; + state.cancel_restart(); + state.set_current_player(1); + state.reset_animation(); } break; case 'b': case 'B': - session.stone_color = 2; + state.set_current_player(2); break; case 'v': case 'V': - session.stone_color = 1; + state.set_current_player(1); break; } } From a6815dc99a497053174ffdb71db6f59df719f173 Mon Sep 17 00:00:00 2001 From: Robert Lech Date: Tue, 11 Nov 2025 14:13:14 -0500 Subject: [PATCH 6/7] refactor: extracts lights and mesh from renderer --- CMakeLists.txt | 2 + src/graphics/Lights.cpp | 36 +++++++ src/graphics/Lights.h | 13 +++ src/graphics/Meshes.cpp | 172 ++++++++++++++++++++++++++++++ src/graphics/Meshes.h | 15 +++ src/graphics/Renderer.cpp | 216 +++----------------------------------- 6 files changed, 255 insertions(+), 199 deletions(-) create mode 100644 src/graphics/Lights.cpp create mode 100644 src/graphics/Lights.h create mode 100644 src/graphics/Meshes.cpp create mode 100644 src/graphics/Meshes.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d14f94..4bdd47f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,8 @@ set(SOURCE_FILES src/game/GameState.cpp src/game/Board.cpp src/game/Rules.cpp + src/graphics/Lights.cpp + src/graphics/Meshes.cpp src/graphics/Renderer.cpp src/input/InputRouter.cpp ) # Add main.cpp file of project root directory as source file diff --git a/src/graphics/Lights.cpp b/src/graphics/Lights.cpp new file mode 100644 index 0000000..4e0f5d6 --- /dev/null +++ b/src/graphics/Lights.cpp @@ -0,0 +1,36 @@ +#include "graphics/Lights.h" + +#include + +#include + +#include "game/GameSession.h" + +namespace graphics::lights { + +void apply(const GameSession &session) { + const std::array light_position{0.0F, 0.0F, -0.5F, 1.0F}; + glLightfv(GL_LIGHT0, GL_POSITION, light_position.data()); + + const std::array light_strength{2.0F, 2.0F, 2.0F, 2.0F}; + glLightfv(GL_LIGHT0, GL_DIFFUSE, light_strength.data()); + glLightfv(GL_LIGHT0, GL_SPECULAR, light_strength.data()); + + GLfloat spread = 90.0F - 90.0F * session.state.animation_time(); + glLightfv(GL_LIGHT0, GL_SPOT_CUTOFF, &spread); + + glEnable(GL_LIGHT0); +} + +void apply_material() { + const std::array material_diffuse{0.5F, 0.5F, 1.0F, 1.0F}; + glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_diffuse.data()); + + const std::array material_specular{1.0F, 1.0F, 1.0F, 1.0F}; + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_specular.data()); + + const std::array material_shininess{100.0F}; + glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, material_shininess.data()); +} + +} // namespace graphics::lights diff --git a/src/graphics/Lights.h b/src/graphics/Lights.h new file mode 100644 index 0000000..790fe17 --- /dev/null +++ b/src/graphics/Lights.h @@ -0,0 +1,13 @@ +#ifndef GRAPHICS_LIGHTS_H +#define GRAPHICS_LIGHTS_H + +class GameSession; + +namespace graphics::lights { + +void apply(const GameSession &session); +void apply_material(); + +} // namespace graphics::lights + +#endif // GRAPHICS_LIGHTS_H diff --git a/src/graphics/Meshes.cpp b/src/graphics/Meshes.cpp new file mode 100644 index 0000000..5d42871 --- /dev/null +++ b/src/graphics/Meshes.cpp @@ -0,0 +1,172 @@ +#include "graphics/Meshes.h" + +#include +#include +#include + +#include +#include + +#include "game/GameSession.h" + +namespace graphics::mesh { + +void draw_sphere(int color) { + const std::array color_none{0.0F, 0.0F, 0.0F, 0.0F}; + + if (color == 0) { + const std::array color_red{1.0F, 0.0F, 0.0F, 0.0F}; + glColor4fv(color_red.data()); + glMaterialfv(GL_FRONT, GL_DIFFUSE, color_red.data()); + glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); + } + + if (color == 1) { + const std::array color_white{1.0F, 1.0F, 1.0F, 1.0F}; + glColor4fv(color_white.data()); + glMaterialfv(GL_FRONT, GL_DIFFUSE, color_white.data()); + glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); + } + + if (color == 2) { + const std::array color_black{0.0F, 0.0F, 0.0F, 0.0F}; + glColor4fv(color_black.data()); + glMaterialfv(GL_FRONT, GL_DIFFUSE, color_black.data()); + glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); + } + + if (color == 3) { + const std::array color_green{0.0F, 0.0F, 1.0F, 0.0F}; + glColor4fv(color_green.data()); + glMaterialfv(GL_FRONT, GL_DIFFUSE, color_green.data()); + glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); + } + + if (color == 4) { + const std::array color_blue{0.0F, 1.0F, 0.0F, 0.0F}; + glColor4fv(color_blue.data()); + glMaterialfv(GL_FRONT, GL_DIFFUSE, color_blue.data()); + glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); + } + + glBegin(GL_TRIANGLES); + GLUquadricObj *quadratic = gluNewQuadric(); + gluQuadricNormals(quadratic, GLU_SMOOTH); + gluQuadricTexture(quadratic, GL_TRUE); + + gluSphere(quadratic, 1.0F, 32, 32); + glEnd(); +} + +void draw_unit_cube(GameSession &session, int color) { + if (color == 0) { + glBindTexture(GL_TEXTURE_2D, session.textures[0]); + } else if (color == 3) { + const std::array color_none{0.0F, 0.0F, 0.0F, 0.0F}; + const std::array color_green{0.0F, 0.0F, 1.0F, 0.0F}; + glColor4fv(color_green.data()); + glMaterialfv(GL_FRONT, GL_DIFFUSE, color_green.data()); + glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); + } else if (color == 4) { + const std::array color_none{0.0F, 0.0F, 0.0F, 0.0F}; + const std::array color_blue{0.0F, 1.0F, 0.0F, 0.0F}; + glColor4fv(color_blue.data()); + glMaterialfv(GL_FRONT, GL_DIFFUSE, color_blue.data()); + glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); + } + + glBegin(GL_QUADS); + + glNormal3f(0.0F, 0.0F, 2.0F); + glTexCoord2f(0.0F, 0.0F); + glVertex3f(-1.0F, -1.0F, 1.0F); + glTexCoord2f(1.0F, 0.0F); + glVertex3f(1.0F, -1.0F, 1.0F); + glTexCoord2f(1.0F, 1.0F); + glVertex3f(1.0F, 1.0F, 1.0F); + glTexCoord2f(0.0F, 1.0F); + glVertex3f(-1.0F, 1.0F, 1.0F); + + glNormal3f(0.0F, 0.0F, -2.0F); + glTexCoord2f(1.0F, 0.0F); + glVertex3f(-1.0F, -1.0F, -1.0F); + glTexCoord2f(1.0F, 1.0F); + glVertex3f(-1.0F, 1.0F, -1.0F); + glTexCoord2f(0.0F, 1.0F); + glVertex3f(1.0F, 1.0F, -1.0F); + glTexCoord2f(0.0F, 0.0F); + glVertex3f(1.0F, -1.0F, -1.0F); + + glNormal3f(0.0F, -2.0F, 0.0F); + glTexCoord2f(1.0F, 1.0F); + glVertex3f(-1.0F, -1.0F, -1.0F); + glTexCoord2f(0.0F, 1.0F); + glVertex3f(1.0F, -1.0F, -1.0F); + glTexCoord2f(0.0F, 0.0F); + glVertex3f(1.0F, -1.0F, 1.0F); + glTexCoord2f(1.0F, 0.0F); + glVertex3f(-1.0F, -1.0F, 1.0F); + + glNormal3f(2.0F, 0.0F, 0.0F); + glTexCoord2f(1.0F, 0.0F); + glVertex3f(1.0F, -1.0F, -1.0F); + glTexCoord2f(1.0F, 1.0F); + glVertex3f(1.0F, 1.0F, -1.0F); + glTexCoord2f(0.0F, 1.0F); + glVertex3f(1.0F, 1.0F, 1.0F); + glTexCoord2f(0.0F, 0.0F); + glVertex3f(1.0F, -1.0F, 1.0F); + + glNormal3f(-2.0F, 0.0F, 0.0F); + glTexCoord2f(0.0F, 0.0F); + glVertex3f(-1.0F, -1.0F, -1.0F); + glTexCoord2f(1.0F, 0.0F); + glVertex3f(-1.0F, -1.0F, 1.0F); + glTexCoord2f(1.0F, 1.0F); + glVertex3f(-1.0F, 1.0F, 1.0F); + glTexCoord2f(0.0F, 1.0F); + glVertex3f(-1.0F, 1.0F, -1.0F); + + if (color == 0) { + glEnd(); + + glBindTexture(GL_TEXTURE_2D, session.textures[1]); + glBegin(GL_QUADS); + } + + glNormal3f(0.0F, 2.0F, 0.0F); + glTexCoord2f(0.0F, 1.0F); + glVertex3f(-1.0F, 1.0F, -1.0F); + glTexCoord2f(0.0F, 0.0F); + glVertex3f(-1.0F, 1.0F, 1.0F); + glTexCoord2f(1.0F, 0.0F); + glVertex3f(1.0F, 1.0F, 1.0F); + glTexCoord2f(1.0F, 1.0F); + glVertex3f(1.0F, 1.0F, -1.0F); + + glEnd(); + + usleep(DEFAULT_SLEEP_TIME); +} + +void apply_transformations(float indx, float indy, [[maybe_unused]] float z) { + const int steps_x = static_cast(std::round(std::fabs(indx))); + const int steps_y = static_cast(std::round(std::fabs(indy))); + + const float step_x = indx < 0 ? -2.0F : 2.0F; + const float step_y = indy < 0 ? 2.0F : -2.0F; + + for (int i = 0; i < steps_x; i++) { + glTranslatef(step_x, 0.0F, 0.0F); + } + + for (int i = 0; i < steps_y; i++) { + glTranslatef(0.0F, 0.0F, step_y); + } +} + +void apply_transformation_general(float indx, float indy, float z) { + glTranslatef(2 * indx, 10 * indy, 2 * z); +} + +} // namespace graphics::mesh diff --git a/src/graphics/Meshes.h b/src/graphics/Meshes.h new file mode 100644 index 0000000..77eb707 --- /dev/null +++ b/src/graphics/Meshes.h @@ -0,0 +1,15 @@ +#ifndef GRAPHICS_MESHES_H +#define GRAPHICS_MESHES_H + +class GameSession; + +namespace graphics::mesh { + +void draw_sphere(int color); +void draw_unit_cube(GameSession &session, int color); +void apply_transformations(float indx, float indy, float z = 0.0F); +void apply_transformation_general(float indx, float indy, float z); + +} // namespace graphics::mesh + +#endif // GRAPHICS_MESHES_H diff --git a/src/graphics/Renderer.cpp b/src/graphics/Renderer.cpp index 7e9de78..aad99ca 100644 --- a/src/graphics/Renderer.cpp +++ b/src/graphics/Renderer.cpp @@ -1,9 +1,7 @@ #include "graphics/Renderer.h" -#include #include #include -#include #include #include @@ -12,6 +10,8 @@ #include #include "game/GameSession.h" +#include "graphics/Lights.h" +#include "graphics/Meshes.h" namespace { @@ -22,189 +22,6 @@ constexpr float PI = std::numbers::pi_v; constexpr float PI = 3.14159265358979323846F; // NOLINT(modernize-use-std-numbers) #endif -void lighting_func(const GameSession &session) { - const std::array light_position{0.0F, 0.0F, -0.5F, 1.0F}; - glLightfv(GL_LIGHT0, GL_POSITION, light_position.data()); - - const std::array light_strength{2.0F, 2.0F, 2.0F, 2.0F}; - glLightfv(GL_LIGHT0, GL_DIFFUSE, light_strength.data()); - glLightfv(GL_LIGHT0, GL_SPECULAR, light_strength.data()); - - GLfloat spread = 90.0F - 90.0F * session.state.animation_time(); - glLightfv(GL_LIGHT0, GL_SPOT_CUTOFF, &spread); - - glEnable(GL_LIGHT0); -} - -void material_func() { - const std::array material_diffuse{0.5F, 0.5F, 1.0F, 1.0F}; - glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_diffuse.data()); - - const std::array material_specular{1.0F, 1.0F, 1.0F, 1.0F}; - glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_specular.data()); - - const std::array material_shininess{100.0F}; - glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, material_shininess.data()); -} - -void draw_sphere(int color) { - const std::array color_none{0.0F, 0.0F, 0.0F, 0.0F}; - - if (color == 0) { - const std::array color_red{1.0F, 0.0F, 0.0F, 0.0F}; - glColor4fv(color_red.data()); - glMaterialfv(GL_FRONT, GL_DIFFUSE, color_red.data()); - glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); - } - - if (color == 1) { - const std::array color_white{1.0F, 1.0F, 1.0F, 1.0F}; - glColor4fv(color_white.data()); - glMaterialfv(GL_FRONT, GL_DIFFUSE, color_white.data()); - glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); - } - - if (color == 2) { - const std::array color_black{0.0F, 0.0F, 0.0F, 0.0F}; - glColor4fv(color_black.data()); - glMaterialfv(GL_FRONT, GL_DIFFUSE, color_black.data()); - glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); - } - - if (color == 3) { - const std::array color_green{0.0F, 0.0F, 1.0F, 0.0F}; - glColor4fv(color_green.data()); - glMaterialfv(GL_FRONT, GL_DIFFUSE, color_green.data()); - glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); - } - - if (color == 4) { - const std::array color_blue{0.0F, 1.0F, 0.0F, 0.0F}; - glColor4fv(color_blue.data()); - glMaterialfv(GL_FRONT, GL_DIFFUSE, color_blue.data()); - glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); - } - - glBegin(GL_TRIANGLES); - GLUquadricObj *quadratic = gluNewQuadric(); - gluQuadricNormals(quadratic, GLU_SMOOTH); - gluQuadricTexture(quadratic, GL_TRUE); - - gluSphere(quadratic, 1.0F, 32, 32); - glEnd(); -} - -void draw_unit_cube(GameSession &session, int color) { - if (color == 0) { - glBindTexture(GL_TEXTURE_2D, session.textures[0]); - } else if (color == 3) { - const std::array color_none{0.0F, 0.0F, 0.0F, 0.0F}; - const std::array color_green{0.0F, 0.0F, 1.0F, 0.0F}; - glColor4fv(color_green.data()); - glMaterialfv(GL_FRONT, GL_DIFFUSE, color_green.data()); - glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); - } else if (color == 4) { - const std::array color_none{0.0F, 0.0F, 0.0F, 0.0F}; - const std::array color_blue{0.0F, 1.0F, 0.0F, 0.0F}; - glColor4fv(color_blue.data()); - glMaterialfv(GL_FRONT, GL_DIFFUSE, color_blue.data()); - glMaterialfv(GL_FRONT, GL_SPECULAR, color_none.data()); - } - - glBegin(GL_QUADS); - - glNormal3f(0.0F, 0.0F, 2.0F); - glTexCoord2f(0.0F, 0.0F); - glVertex3f(-1.0F, -1.0F, 1.0F); - glTexCoord2f(1.0F, 0.0F); - glVertex3f(1.0F, -1.0F, 1.0F); - glTexCoord2f(1.0F, 1.0F); - glVertex3f(1.0F, 1.0F, 1.0F); - glTexCoord2f(0.0F, 1.0F); - glVertex3f(-1.0F, 1.0F, 1.0F); - - glNormal3f(0.0F, 0.0F, -2.0F); - glTexCoord2f(1.0F, 0.0F); - glVertex3f(-1.0F, -1.0F, -1.0F); - glTexCoord2f(1.0F, 1.0F); - glVertex3f(-1.0F, 1.0F, -1.0F); - glTexCoord2f(0.0F, 1.0F); - glVertex3f(1.0F, 1.0F, -1.0F); - glTexCoord2f(0.0F, 0.0F); - glVertex3f(1.0F, -1.0F, -1.0F); - - glNormal3f(0.0F, -2.0F, 0.0F); - glTexCoord2f(1.0F, 1.0F); - glVertex3f(-1.0F, -1.0F, -1.0F); - glTexCoord2f(0.0F, 1.0F); - glVertex3f(1.0F, -1.0F, -1.0F); - glTexCoord2f(0.0F, 0.0F); - glVertex3f(1.0F, -1.0F, 1.0F); - glTexCoord2f(1.0F, 0.0F); - glVertex3f(-1.0F, -1.0F, 1.0F); - - glNormal3f(2.0F, 0.0F, 0.0F); - glTexCoord2f(1.0F, 0.0F); - glVertex3f(1.0F, -1.0F, -1.0F); - glTexCoord2f(1.0F, 1.0F); - glVertex3f(1.0F, 1.0F, -1.0F); - glTexCoord2f(0.0F, 1.0F); - glVertex3f(1.0F, 1.0F, 1.0F); - glTexCoord2f(0.0F, 0.0F); - glVertex3f(1.0F, -1.0F, 1.0F); - - glNormal3f(-2.0F, 0.0F, 0.0F); - glTexCoord2f(0.0F, 0.0F); - glVertex3f(-1.0F, -1.0F, -1.0F); - glTexCoord2f(1.0F, 0.0F); - glVertex3f(-1.0F, -1.0F, 1.0F); - glTexCoord2f(1.0F, 1.0F); - glVertex3f(-1.0F, 1.0F, 1.0F); - glTexCoord2f(0.0F, 1.0F); - glVertex3f(-1.0F, 1.0F, -1.0F); - - if (color == 0) { - glEnd(); - - glBindTexture(GL_TEXTURE_2D, session.textures[1]); - glBegin(GL_QUADS); - } - - glNormal3f(0.0F, 2.0F, 0.0F); - glTexCoord2f(0.0F, 1.0F); - glVertex3f(-1.0F, 1.0F, -1.0F); - glTexCoord2f(0.0F, 0.0F); - glVertex3f(-1.0F, 1.0F, 1.0F); - glTexCoord2f(1.0F, 0.0F); - glVertex3f(1.0F, 1.0F, 1.0F); - glTexCoord2f(1.0F, 1.0F); - glVertex3f(1.0F, 1.0F, -1.0F); - - glEnd(); - - usleep(DEFAULT_SLEEP_TIME); -} - -void apply_transformations(float indx, float indy, [[maybe_unused]] float z) { - const int steps_x = static_cast(std::round(std::fabs(indx))); - const int steps_y = static_cast(std::round(std::fabs(indy))); - - const float step_x = indx < 0 ? -2.0F : 2.0F; - const float step_y = indy < 0 ? 2.0F : -2.0F; - - for (int i = 0; i < steps_x; i++) { - glTranslatef(step_x, 0.0F, 0.0F); - } - - for (int i = 0; i < steps_y; i++) { - glTranslatef(0.0F, 0.0F, step_y); - } -} - -void apply_transformation_general(float indx, float indy, float z) { - glTranslatef(2 * indx, 10 * indy, 2 * z); -} - void set_images(GameSession &session) { ilBindImage(session.image_ids[0]); ilLoadImage("images/wooden.jpg"); @@ -253,8 +70,8 @@ void jump_off(GameSession &session, int x0, int z0, int color) { const float zt = z0 * (1 - animation) + static_cast(z1) * animation; glPushMatrix(); - apply_transformation_general((xt - 9), (yt - 9), -(zt - 9)); - draw_sphere(color); + graphics::mesh::apply_transformation_general((xt - 9), (yt - 9), -(zt - 9)); + graphics::mesh::draw_sphere(color); glPushMatrix(); const float thigh_angle = @@ -262,7 +79,7 @@ void jump_off(GameSession &session, int x0, int z0, int color) { glRotatef(thigh_angle, 1.0F, 0.0F, 1.0F); glScalef(2.0F / 5.0F, 1.0F, 2.0F / 5.0F); glTranslatef(0.0F, -1.0F, 0.0F); - draw_unit_cube(session, 3); + graphics::mesh::draw_unit_cube(session, 3); glScalef(5.0F / 2.0F, 1.0F, 5.0F / 2.0F); glPushMatrix(); @@ -273,7 +90,7 @@ void jump_off(GameSession &session, int x0, int z0, int color) { glRotatef(shin_angle, 1.0F, 0.0F, 1.0F); glScalef(1.0F / 5.0F, 1.0F, 1.0F / 5.0F); glTranslatef(0.0F, -1.0F, 0.0F); - draw_unit_cube(session, 4); + graphics::mesh::draw_unit_cube(session, 4); glScalef(5.0F, 1.0F, 5.0F); glPushMatrix(); @@ -283,7 +100,7 @@ void jump_off(GameSession &session, int x0, int z0, int color) { glRotatef(ankle_angle, 1.0F, 0.0F, 1.0F); glScalef(1.0F / 2.0F, 2.0F / 3.0F, 1.0F / 2.0F); glTranslatef(0.0F, -1.0F, 0.0F); - draw_sphere(0); + graphics::mesh::draw_sphere(0); glPopMatrix(); glPopMatrix(); glPopMatrix(); @@ -306,7 +123,7 @@ void display(GameSession &session) { glTranslatef(2.0F * light_offset, 0.0F, 0.0F); if (session.lighting) { - lighting_func(session); + graphics::lights::apply(session); } glPopMatrix(); @@ -319,10 +136,10 @@ void display(GameSession &session) { glScalef(1.0F, 0.05F, 1.0F); if (session.material) { - material_func(); + graphics::lights::apply_material(); } - draw_unit_cube(session, 0); + graphics::mesh::draw_unit_cube(session, 0); glPopMatrix(); glPushMatrix(); @@ -335,9 +152,9 @@ void display(GameSession &session) { glPushMatrix(); const int cursor_x = session.state.cursor_x(); const int cursor_y = session.state.cursor_y(); - apply_transformations(static_cast(cursor_x), static_cast(cursor_y), - 0.0F); - draw_sphere(0); + graphics::mesh::apply_transformations(static_cast(cursor_x), + static_cast(cursor_y), 0.0F); + graphics::mesh::draw_sphere(0); glPopMatrix(); const auto &stones = session.board.stones(); @@ -345,9 +162,10 @@ void display(GameSession &session) { for (int j = 0; j < BOARD_SIZE; j++) { if (stones[i][j] != 0) { glPushMatrix(); - apply_transformations(static_cast(i - BOARD_CENTER), - static_cast(j - BOARD_CENTER), 0.0F); - draw_sphere(stones[i][j]); + graphics::mesh::apply_transformations( + static_cast(i - BOARD_CENTER), + static_cast(j - BOARD_CENTER), 0.0F); + graphics::mesh::draw_sphere(stones[i][j]); glPopMatrix(); } } From 9aeefeaec5aee953a30767d529ebab31dbb6fc9b Mon Sep 17 00:00:00 2001 From: Robert Lech Date: Tue, 11 Nov 2025 14:49:12 -0500 Subject: [PATCH 7/7] refactor: formalizes stone logic --- src/game/Board.cpp | 46 +++++++++++++-- src/game/Board.h | 59 ++++++++++++++++--- src/game/GameSession.h | 3 + src/game/Rules.cpp | 117 +++++++++++++++++++++++--------------- src/game/Rules.h | 4 +- src/graphics/Renderer.cpp | 36 +++++++----- src/input/InputRouter.cpp | 15 +++-- 7 files changed, 201 insertions(+), 79 deletions(-) diff --git a/src/game/Board.cpp b/src/game/Board.cpp index fff4f01..1e4b06d 100644 --- a/src/game/Board.cpp +++ b/src/game/Board.cpp @@ -1,10 +1,16 @@ #include "game/Board.h" +#include #include +auto Board::is_on_board(Point point) -> bool { + const auto [ix, iy] = to_index(point); + return ix >= 0 && ix < SIZE && iy >= 0 && iy < SIZE; +} + void Board::clear() { for (auto &column : stones_) { - column.fill(0); + column.fill(Stone::Empty); } clear_liberties(); clear_captured_groups(); @@ -16,13 +22,37 @@ void Board::clear_liberties() { } } -auto Board::stones() -> Grid & { return stones_; } +auto Board::stone_at(Point point) const -> Stone { + assert(is_on_board(point)); + const auto [ix, iy] = to_index(point); + return stones_[ix][iy]; +} -auto Board::stones() const -> const Grid & { return stones_; } +void Board::place_stone(Point point, Stone stone) { + assert(is_on_board(point)); + const auto [ix, iy] = to_index(point); + stones_[ix][iy] = stone; +} -auto Board::liberties() -> Grid & { return liberties_; } +void Board::remove_stone(Point point) { place_stone(point, Stone::Empty); } -auto Board::liberties() const -> const Grid & { return liberties_; } +auto Board::is_empty(Point point) const -> bool { + return stone_at(point) == Stone::Empty; +} + +auto Board::liberty(Point point) const -> int { + assert(is_on_board(point)); + const auto [ix, iy] = to_index(point); + return liberties_[ix][iy]; +} + +auto Board::mutable_liberty(Point point) -> int & { + assert(is_on_board(point)); + const auto [ix, iy] = to_index(point); + return liberties_[ix][iy]; +} + +void Board::set_liberty(Point point, int value) { mutable_liberty(point) = value; } auto Board::captured_groups() -> CaptureGroups & { return captured_groups_; } @@ -35,3 +65,9 @@ void Board::add_captured_group(CaptureGroup group) { } void Board::clear_captured_groups() { captured_groups_.clear(); } + +auto Board::to_index(Point point) -> std::pair { + return {point.x + CENTER, point.y + CENTER}; +} + +auto Board::index_to_point(int x, int y) -> Point { return {x - CENTER, y - CENTER}; } diff --git a/src/game/Board.h b/src/game/Board.h index 0aabdec..91d8985 100644 --- a/src/game/Board.h +++ b/src/game/Board.h @@ -2,26 +2,60 @@ #define GAME_BOARD_H #include +#include +#include #include +#include #include +enum class Stone : std::uint8_t { + Empty = 0, + White = 1, + Black = 2, +}; + +struct Point { + int x = 0; + int y = 0; + + constexpr Point() = default; + constexpr Point(int x_coord, int y_coord) : x(x_coord), y(y_coord) {} +}; + +struct CapturedStone { + Point location{}; + Stone color = Stone::Empty; + + constexpr CapturedStone() = default; + constexpr CapturedStone(Point point, Stone stone_color) + : location(point), color(stone_color) {} +}; + +inline auto is_empty(Stone stone) -> bool { return stone == Stone::Empty; } + class Board { public: static constexpr int SIZE = 19; static constexpr int CENTER = SIZE / 2; - using Grid = std::array, SIZE>; - using CaptureGroup = std::vector; + using Grid = std::array, SIZE>; + using LibertyGrid = std::array, SIZE>; + using CaptureGroup = std::vector; using CaptureGroups = std::list; + static auto is_on_board(Point point) -> bool; + void clear(); void clear_liberties(); - auto stones() -> Grid &; - auto stones() const -> const Grid &; + auto stone_at(Point point) const -> Stone; + void place_stone(Point point, Stone stone); + void remove_stone(Point point); + auto is_empty(Point point) const -> bool; - auto liberties() -> Grid &; - auto liberties() const -> const Grid &; + auto liberty(Point point) const -> int; + auto mutable_liberty(Point point) -> int &; + void set_liberty(Point point, int value); auto captured_groups() -> CaptureGroups &; auto captured_groups() const -> const CaptureGroups &; @@ -29,9 +63,20 @@ class Board { void add_captured_group(CaptureGroup group); void clear_captured_groups(); + template void for_each_stone(Func &&fn) const { + for (int column = 0; column < SIZE; ++column) { + for (int row = 0; row < SIZE; ++row) { + fn(index_to_point(column, row), stones_[column][row]); + } + } + } + private: + static auto to_index(Point point) -> std::pair; + static auto index_to_point(int x, int y) -> Point; + Grid stones_{}; - Grid liberties_{}; + LibertyGrid liberties_{}; CaptureGroups captured_groups_; }; diff --git a/src/game/GameSession.h b/src/game/GameSession.h index 5f0b450..446d770 100644 --- a/src/game/GameSession.h +++ b/src/game/GameSession.h @@ -9,6 +9,9 @@ #include "game/Board.h" #include "game/GameState.h" +struct Point; +enum class Stone : std::uint8_t; + constexpr int BOARD_SIZE = Board::SIZE; constexpr int BOARD_CENTER = Board::CENTER; diff --git a/src/game/Rules.cpp b/src/game/Rules.cpp index b4900bd..405c7da 100644 --- a/src/game/Rules.cpp +++ b/src/game/Rules.cpp @@ -4,59 +4,74 @@ #include #include +#include "game/Board.h" #include "game/GameSession.h" namespace { -void remove_block(GameSession &session, int x, int y, int piece) { - if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE) { +void collect_block(GameSession &session, Point point, Stone target_color, + Board::CaptureGroup &removed_group) { + if (!Board::is_on_board(point)) { return; } - auto &stones = session.board.stones(); - if (stones[x][y] != piece) { + + if (session.board.stone_at(point) != target_color) { return; } - stones[x][y] = 0; - std::cout << "Jump from " << x << y << '\n'; + session.board.remove_stone(point); + std::cout << "Jump from " << point.x << point.y << '\n'; + + removed_group.emplace_back(point, target_color); - Board::CaptureGroup rm_piece{x, y, piece}; - session.board.add_captured_group(std::move(rm_piece)); + collect_block(session, Point{point.x - 1, point.y}, target_color, removed_group); + collect_block(session, Point{point.x + 1, point.y}, target_color, removed_group); + collect_block(session, Point{point.x, point.y - 1}, target_color, removed_group); + collect_block(session, Point{point.x, point.y + 1}, target_color, removed_group); +} - remove_block(session, x - 1, y, piece); - remove_block(session, x + 1, y, piece); - remove_block(session, x, y - 1, piece); - remove_block(session, x, y + 1, piece); +void remove_block(GameSession &session, Point point, Stone target_color) { + Board::CaptureGroup removed{}; + collect_block(session, point, target_color, removed); + if (!removed.empty()) { + session.board.add_captured_group(std::move(removed)); + } } -auto check_liberties(GameSession &session, int x, int y, int originx, int originy, - int piece) -> int { - if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE) { +auto check_liberties(GameSession &session, Point point, Point origin, + Stone target_color) -> int { + if (!Board::is_on_board(point)) { return 0; } - auto &stones = session.board.stones(); - if (stones[x][y] == 0) { + + const auto stone = session.board.stone_at(point); + if (is_empty(stone)) { return 1; } - if (stones[x][y] != piece) { + if (stone != target_color) { return 0; } - auto &liberties = session.board.liberties(); - if (liberties[x][y] != -1) { - return liberties[x][y]; + + auto &liberty_value = session.board.mutable_liberty(point); + if (liberty_value != -1) { + return liberty_value; } - const int direcx = x - originx; - const int direcy = y - originy; - if ((direcx >= 0 && check_liberties(session, x + 1, y, originx, originy, piece)) || - (direcx <= 0 && check_liberties(session, x - 1, y, originx, originy, piece)) || - (direcy >= 0 && check_liberties(session, x, y + 1, originx, originy, piece)) || - (direcy <= 0 && check_liberties(session, x, y - 1, originx, originy, piece))) { - liberties[x][y] = 1; + const int direcx = point.x - origin.x; + const int direcy = point.y - origin.y; + if ((direcx >= 0 && + check_liberties(session, Point{point.x + 1, point.y}, origin, target_color)) || + (direcx <= 0 && + check_liberties(session, Point{point.x - 1, point.y}, origin, target_color)) || + (direcy >= 0 && + check_liberties(session, Point{point.x, point.y + 1}, origin, target_color)) || + (direcy <= 0 && + check_liberties(session, Point{point.x, point.y - 1}, origin, target_color))) { + liberty_value = 1; } else { - liberties[x][y] = 0; + liberty_value = 0; } - return liberties[x][y]; + return liberty_value; } } // namespace @@ -65,42 +80,50 @@ namespace rules { void init_board(GameSession &session) { session.board.clear(); } -auto make_move(GameSession &session, int x, int y, int piece) -> int { +auto make_move(GameSession &session, Point move_point, Stone stone_color) -> int { auto &board = session.board; - auto &stones = board.stones(); - stones[x][y] = piece; + if (!Board::is_on_board(move_point)) { + return 0; + } + + if (!board.is_empty(move_point)) { + return 0; + } + + board.place_stone(move_point, stone_color); board.clear_liberties(); - const int other = piece == 1 ? 2 : 1; + const Stone opponent = stone_color == Stone::Black ? Stone::White : Stone::Black; int has_liberties = 0; - const std::array, 4> neighbors{{ - {x - 1, y}, - {x + 1, y}, - {x, y - 1}, - {x, y + 1}, + const std::array neighbors{{ + Point{move_point.x - 1, move_point.y}, + Point{move_point.x + 1, move_point.y}, + Point{move_point.x, move_point.y - 1}, + Point{move_point.x, move_point.y + 1}, }}; - for (const auto &[nx, ny] : neighbors) { - if (nx < 0 || nx >= BOARD_SIZE || ny < 0 || ny >= BOARD_SIZE) { + for (const auto &neighbor : neighbors) { + if (!Board::is_on_board(neighbor)) { continue; } - if (stones[nx][ny] == piece) { + if (board.stone_at(neighbor) == stone_color) { continue; } - if (piece == 0) { + if (stone_color == Stone::Empty) { has_liberties = 1; - } else if (!check_liberties(session, nx, ny, nx, ny, other)) { + } else if (!check_liberties(session, neighbor, neighbor, opponent)) { has_liberties = 1; - remove_block(session, nx, ny, other); + remove_block(session, neighbor, opponent); } } - if (!has_liberties && !check_liberties(session, x, y, x, y, piece)) { - remove_block(session, x, y, piece); + if (!has_liberties && + !check_liberties(session, move_point, move_point, stone_color)) { + remove_block(session, move_point, stone_color); return 0; } diff --git a/src/game/Rules.h b/src/game/Rules.h index 91a2142..aaa5ffb 100644 --- a/src/game/Rules.h +++ b/src/game/Rules.h @@ -2,11 +2,13 @@ #define GAME_RULES_H class GameSession; +struct Point; +enum class Stone : unsigned char; namespace rules { void init_board(GameSession &session); -auto make_move(GameSession &session, int x, int y, int piece) -> int; +auto make_move(GameSession &session, Point point, Stone stone) -> int; } // namespace rules diff --git a/src/graphics/Renderer.cpp b/src/graphics/Renderer.cpp index aad99ca..2a38599 100644 --- a/src/graphics/Renderer.cpp +++ b/src/graphics/Renderer.cpp @@ -1,6 +1,7 @@ #include "graphics/Renderer.h" #include +#include #include #include @@ -157,27 +158,32 @@ void display(GameSession &session) { graphics::mesh::draw_sphere(0); glPopMatrix(); - const auto &stones = session.board.stones(); - for (int i = 0; i < BOARD_SIZE; i++) { - for (int j = 0; j < BOARD_SIZE; j++) { - if (stones[i][j] != 0) { - glPushMatrix(); - graphics::mesh::apply_transformations( - static_cast(i - BOARD_CENTER), - static_cast(j - BOARD_CENTER), 0.0F); - graphics::mesh::draw_sphere(stones[i][j]); - glPopMatrix(); - } + session.board.for_each_stone([&](Point point, Stone stone) { + if (stone == Stone::Empty) { + return; } - } + + glPushMatrix(); + graphics::mesh::apply_transformations(static_cast(point.x), + static_cast(point.y), 0.0F); + graphics::mesh::draw_sphere(static_cast(stone)); + glPopMatrix(); + }); const auto &captured = session.board.captured_groups(); if (!captured.empty()) { - for (const auto &piece : captured) { - jump_off(session, piece[0], piece[1], piece[2]); + std::size_t captured_count = 0; + for (const auto &group : captured) { + for (const auto &captured_stone : group) { + const int board_x = captured_stone.location.x + BOARD_CENTER; + const int board_y = captured_stone.location.y + BOARD_CENTER; + jump_off(session, board_x, board_y, + static_cast(captured_stone.color)); + ++captured_count; + } } session.state.advance_animation(TIME_INCREMENT * - static_cast(captured.size())); + static_cast(captured_count)); } if (session.state.animation_time() > 1.0F) { session.state.reset_animation(); diff --git a/src/input/InputRouter.cpp b/src/input/InputRouter.cpp index 0b72db3..e23709e 100644 --- a/src/input/InputRouter.cpp +++ b/src/input/InputRouter.cpp @@ -5,6 +5,7 @@ #include +#include "game/Board.h" #include "game/GameSession.h" #include "game/Rules.h" @@ -141,16 +142,22 @@ void keyboard(GameSession &session, unsigned char key, [[maybe_unused]] int x, break; case '\r': { - const auto &stones = session.board.stones(); const int cursor_x = state.cursor_x(); const int cursor_y = state.cursor_y(); - if (stones[cursor_x + BOARD_CENTER][cursor_y + BOARD_CENTER] != 0) { + const Point cursor_point{cursor_x, cursor_y}; + + if (!Board::is_on_board(cursor_point)) { + std::cout << "Cursor is out of bounds; cannot place a stone.\n"; + break; + } + + if (!session.board.is_empty(cursor_point)) { std::cout << "YOU CAN'T PLACE A PIECE HERE BECAUSE THERE ALREADY IS A " << "PIECE HERE!!!\n"; } else { std::cout << "ENTER KEY PRESSED!!!\n"; - rules::make_move(session, cursor_x + BOARD_CENTER, cursor_y + BOARD_CENTER, - state.current_player()); + const auto current_stone = static_cast(state.current_player()); + rules::make_move(session, cursor_point, current_stone); state.advance_turn(); } break;