diff --git a/.vscode/launch.json b/.vscode/launch.json index d5ba7db..768e9f5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/program", - "args": [], + "args": ["examples/maze3.txt"], "stopAtEntry": false, "cwd": "${fileDirname}", "environment": [], @@ -30,4 +30,4 @@ "preLaunchTask": "build debug" } ] -} \ No newline at end of file +} diff --git a/examples/maze1.txt b/examples/maze1.txt new file mode 100644 index 0000000..b263fb2 --- /dev/null +++ b/examples/maze1.txt @@ -0,0 +1,5 @@ +XXXXXXX +X X +X X X +X X +XXXXXXX diff --git a/examples/maze2.txt b/examples/maze2.txt new file mode 100644 index 0000000..59ff0ca --- /dev/null +++ b/examples/maze2.txt @@ -0,0 +1,9 @@ +XXXXXXXXXXXXXX +X X +XXXXXXXXXXXX X +X X +XXXXX XXXXXXXX +X X +XXXXXXXXXXX X +X X +XXXXXXXXXXXXXX diff --git a/examples/maze3.txt b/examples/maze3.txt new file mode 100644 index 0000000..7dc2ec4 --- /dev/null +++ b/examples/maze3.txt @@ -0,0 +1,7 @@ +XXXXXXX +X X +X X X +X XXX X +X X X +X X +XXXXXXX diff --git a/src/drawable.h b/src/drawable.h deleted file mode 100644 index 9e9afe1..0000000 --- a/src/drawable.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -class Drawable { -public: - virtual void draw() = 0; -}; diff --git a/src/glut_events.h b/src/glut_events.h new file mode 100644 index 0000000..b29131c --- /dev/null +++ b/src/glut_events.h @@ -0,0 +1,16 @@ +#pragma once + +struct GlutEvents { + virtual void display() {} + virtual void overlayDisplay() {} + virtual void reshape(int width, int height) {} + virtual void keyboard(unsigned char key, int x, int y) {} + virtual void keyboardUp(unsigned char key, int x, int y) {} + virtual void mouse(int button, int state, int x, int y) {} + virtual void motion(int x, int y) {} + virtual void passiveMotion(int x, int y) {} + virtual void visibility(int state) {} + virtual void entry(int state) {} + virtual void special(int key, int x, int y) {} + virtual void idle() {} +}; diff --git a/src/main.cpp b/src/main.cpp index ad92e6a..14c5266 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,243 +1,158 @@ -#include -#include +#include +#include +#include #include -#include #include #include #include -#include "pixel_wise.h" -#include "drawable.h" +#include "glut_events.h" #include "utils.hpp" +#include "no_maze.h" +#include "maze.h" -GLfloat deltaTime; - -bool gameRunning = false; - -bool mouseDown = false; -int leftScore = 0; -int rightScore = 0; - -int leftMove = 0; -int rightMove = 0; +std::vector> maze; +std::unique_ptr events; +void display(void) { + if (events) { + events->display(); + } +} +void overlayDisplay(void) { + if (events) { + events->overlayDisplay(); + } +} +void reshape(int width, int height) { + if (events) { + events->reshape(width, height); + } +} +void keyDown(unsigned char key, int x, int y) { + if (events) { + events->keyboard(key, x, y); + } +} +void keyUp(unsigned char key, int x, int y) { + if (events) { + events->keyboardUp(key, x, y); + } +} void mouse(int button, int state, int x, int y) { - if (state == GLUT_DOWN) { - gameRunning = true; + if (events) { + events->mouse(button, state, x, y); } } - -class BoardMiddle : public Drawable { -public: - virtual void draw() { - glBegin(GL_POLYGON); - glColor3ub(0xBD, 0xBD, 0xBD); - glVertex2fv(PW::nm(-2, -1).data()); - glVertex2fv(PW::nm(2, -1).data()); - glVertex2fv(PW::nm(2, 1).data()); - glVertex2fv(PW::nm(-2, 1).data()); - glEnd(); - } -} bm; - -class Ball : public Drawable { -public: - static constexpr GLfloat defaultVeloX = 0.5; - - GLfloat radius; - GLfloat x, y; - GLfloat veloX, veloY; - - Ball(GLfloat x, GLfloat y) : x(x), y(y), radius(0.025), veloX(defaultVeloX) { - +void motion(int x, int y) { + if (events) { + events->motion(x, y); } - - virtual void draw() { - glBegin(GL_POLYGON); - glColor3ub(0xFF, 0xEE, 0x58); - for (float i = 0; i < 360; i += 0.5) { - const auto rad = Utils::toRad(i); - glVertex2f(x + radius * cos(rad), y + radius * sin(rad)); - } - glEnd(); +} +void passiveMotion(int x, int y) { + if (events) { + events->passiveMotion(x, y); } - - void update() { - x += veloX * deltaTime; - y += veloY * deltaTime; +} +void visibility(int state) { + if (events) { + events->visibility(state); } -} ball {0, 0}; - -class Text : public Drawable { - std::function strObtainer; -public: - std::function x, y; - void *font; - Text(std::function strObtainer, GLfloat x, GLfloat y) : strObtainer(strObtainer), x([=](){return x;}), y([=](){return y;}), font(GLUT_BITMAP_HELVETICA_18) {} - Text(std::function strObtainer, std::function x, std::function y) : strObtainer(strObtainer), x(x), y(y), font(GLUT_BITMAP_HELVETICA_18) {} - Text(std::function strObtainer, std::array pos) : strObtainer(strObtainer), x([=](){return pos[0];}), y([=](){return pos[1];}), font(GLUT_BITMAP_HELVETICA_18) {} - Text(std::function strObtainer, std::function()> pos) : strObtainer(strObtainer), x([=](){return pos()[0];}), y([=](){return pos()[1];}), font(GLUT_BITMAP_HELVETICA_18) {} - - virtual void draw() { - glRasterPos2f(x(), y()); - for (const auto& c: strObtainer()) { - glutBitmapCharacter(font, c); - } +} +void entry(int state) { + if (events) { + events->entry(state); + } +} +void special(int key, int x, int y) { + if (events) { + events->special(key, x, y); } -} leftScoreText{[](){ return std::to_string(leftScore); }, [](){return PW::tm(-100, 20);}}, - rightScoreText{[](){ return std::to_string(rightScore); }, [](){return PW::tm(100, 20);}}; - -class Paddle : public Drawable { -public: - GLfloat height, width; - GLfloat x, y; - - Paddle(GLfloat x, GLfloat y) : x(x), y(y), height(0.4), width(0.02) {} - - virtual void draw() { - glBegin(GL_POLYGON); - glVertex2f(x, y); - glVertex2f(x + width, y); - glVertex2f(x + width, y + height); - glVertex2f(x, y + height); - glEnd(); - } -} leftPaddle{-0.9, -0.2}, rightPaddle{0.9 - 0.02, -0.2}; - -void display(void) { - glClear(GL_COLOR_BUFFER_BIT); - - bm.draw(); - ball.draw(); - - glColor3ub(0x66, 0xBB, 0x6A); - leftScoreText.draw(); - leftPaddle.draw(); - - glColor3ub(0x42, 0xA5, 0xF5); - rightScoreText.draw(); - rightPaddle.draw(); - - glFlush(); } - -int gameTime; - -const GLfloat paddleMoveSpeed = 0.8; - void idle() { - int newTime = glutGet(GLUT_ELAPSED_TIME); - deltaTime = (newTime - gameTime) / 1000.0; - gameTime = newTime; + if (events) { + events->idle(); + } +} - if (gameRunning) { - ball.update(); +void load_maze(const std::string& path) { + std::ifstream f{path}; + std::string line; - // Detect ball collission - if (leftPaddle.x <= (ball.x + ball.radius) && (ball.x - ball.radius) <= (leftPaddle.x + leftPaddle.width)) { - // Ball inside left-right area of left paddle - if (leftPaddle.y <= ball.y && ball.y <= leftPaddle.y + leftPaddle.height) { - // Ball inside up-down area of left paddle - ball.veloX = ball.defaultVeloX; - ball.veloY = Utils::nummap(ball.y, leftPaddle.y, leftPaddle.y + leftPaddle.height, (GLfloat)-1, (GLfloat)1); - } + while(true) { + std::getline(f, line); + if (!f) { + break; } - else if (rightPaddle.x <= (ball.x + ball.radius) && (ball.x - ball.radius) <= (rightPaddle.x + rightPaddle.width)) { - // Ball inside left-right area of right paddle - if (rightPaddle.y <= ball.y && ball.y <= rightPaddle.y + rightPaddle.height) { - // Ball inside up-down area of right paddle - ball.veloX = -ball.defaultVeloX; - ball.veloY = Utils::nummap(ball.y, rightPaddle.y, rightPaddle.y + rightPaddle.height, (GLfloat)-1, (GLfloat)1); + + std::vector row; + for (const auto& c: line) { + switch(c) { + case ' ': + row.push_back(0); + break; + case 'X': + row.push_back(1); + break; + default: + row.push_back(-1); } } - else if (ball.x + ball.radius < -1) { - // Ball exited board on the left side - rightScore += 1; - ball.x = ball.y = 0; - ball.veloY = 0; - gameRunning = false; - } - else if (ball.x - ball.radius > 1) { - // Ball exited board on the right side - leftScore += 1; - ball.x = ball.y = 0; - ball.veloY = 0; - gameRunning = false; - } - else if (ball.y - ball.radius < -1) { - // Ball touched bottom side - ball.veloY *= -1; - } - else if (ball.y + ball.radius > 1) { - // Ball touched top side - ball.veloY *= -1; + if (row.size() > 0) { + maze.push_back(row); } } +} + +int main(int argc, char** argv) +{ + std::string maze_file; + if (argc < 2) { + events = std::make_unique(); + } + else { + maze_file = std::string{argv[1]}; + load_maze(maze_file); + events = std::make_unique(maze); + } - // Move paddle - leftPaddle.y += leftMove * paddleMoveSpeed * deltaTime; - rightPaddle.y += rightMove * paddleMoveSpeed * deltaTime; + glutInit(&argc, argv); - // Clamp paddle to stay on screen - if (leftPaddle.y < -1) leftPaddle.y = -1; - if (rightPaddle.y < -1) rightPaddle.y = -1; - if (leftPaddle.y > 1 - leftPaddle.height) leftPaddle.y = 1 - leftPaddle.height; - if (rightPaddle.y > 1 - rightPaddle.height) rightPaddle.y = 1 - rightPaddle.height; + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH); + glClearDepth(1.0f); + glDepthFunc(GL_LEQUAL); - glutPostRedisplay(); -} -void keyDown(unsigned char key, int x, int y) { - switch(key) { - case 'w': - case 'W': - leftMove = 1; - break; - case 's': - case 'S': - leftMove = -1; - break; - case 'p': - case 'P': - rightMove = 1; - break; - case ';': - case ':': - rightMove = -1; - break; + glutInitWindowSize(640, 480); + glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH) - 640) * 0.5, (glutGet(GLUT_SCREEN_HEIGHT) - 480) * 0.5); + if (maze_file.size() > 0) { + glutCreateWindow((std::string{"Maze - "} + maze_file).c_str()); + } + else { + glutCreateWindow("Maze"); } -} -void keyUp(unsigned char key, int x, int y) { - switch(key) { - case 'w': - case 'W': - case 's': - case 'S': - leftMove = 0; - break; - case 'p': - case 'P': - case ';': - case ':': - rightMove = 0; - break; + if (maze_file.size() > 0) { + glEnable(GL_DEPTH_TEST); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(60, 1, 0.1, 100); } -} -int main(int argc, char** argv) -{ - glutInit(&argc, argv); - glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); - glutInitWindowSize(600, 600); - glutInitWindowPosition(glutGet(GLUT_SCREEN_WIDTH) * 0.6, (glutGet(GLUT_SCREEN_HEIGHT) - 600) * 0.5); - glutCreateWindow("Pong"); + glutDisplayFunc(display); + glutOverlayDisplayFunc(overlayDisplay); + glutReshapeFunc(reshape); glutSetKeyRepeat(GLUT_KEY_REPEAT_OFF); glutKeyboardFunc(keyDown); glutKeyboardUpFunc(keyUp); - glutDisplayFunc(display); glutMouseFunc(mouse); + glutMotionFunc(motion); + glutPassiveMotionFunc(passiveMotion); + glutVisibilityFunc(visibility); + glutEntryFunc(entry); + glutSpecialFunc(special); glutIdleFunc(idle); glutMainLoop(); return 0; diff --git a/src/maze.cpp b/src/maze.cpp new file mode 100644 index 0000000..89126a5 --- /dev/null +++ b/src/maze.cpp @@ -0,0 +1,153 @@ +#include "maze.h" + +#include + +#include +#include +#include + +#include "utils.hpp" + +constexpr double PI = 3.14159265358979; + +MazeScreen::MazeScreen(const std::vector>& maze): + maze(maze), + angleX(0), + mouseCapture(false), + posX(1), + posZ(1) {} + +void MazeScreen::display() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + //configures the camera with the position_x and position_z variables + gluLookAt( + posX, + 0, + posZ, + posX + cos((angleX * PI) / 180) * 3, + 0, + posZ - sin((angleX * PI) / 180) * 3, + 0, + 1, + 0 + ); + + for (int i = 0; i < maze.size(); i++) { + for (int j = 0; j < maze[i].size(); j++) { + switch (maze[i][j]) { + case -1: + glPushMatrix(); + glColor3f(1, 0, 0); + glTranslatef(i, 0, j); + glutWireCube(1); + glPopMatrix(); + break; + case 1: + glPushMatrix(); + // Scale green by X + auto green = Utils::clamp(Utils::nummap(abs(i - posX), 0, 3, 0, 1), 0, 1); + // Scale blue by Z + auto blue = Utils::clamp(Utils::nummap(abs(j - posZ), 0, 3, 0, 1), 0, 1); + glColor3f(1, green, blue); + glTranslatef(i, 0, j); + if (solid) { + glutSolidCube(1); + } + else { + glutWireCube(1); + } + glPopMatrix(); + break; + } + } + } + + glFlush(); + glutSwapBuffers(); +} + +void MazeScreen::keyboard(unsigned char key, int x, int y) { + if (key == '\e') { + mouseCapture = false; + glutSetCursor(GLUT_CURSOR_INHERIT); + } + else if (key == ' ') { + solid = !solid; + } + else if (key == 'w') { + forceX += 1; + } + else if (key == 's') { + forceX -= 1; + } + else if (key == 'a') { + forceZ -= 1; + } + else if (key == 'd') { + forceZ += 1; + } + glutPostRedisplay(); +} + +void MazeScreen::keyboardUp(unsigned char key, int x, int y) { + if (key == 'w') { + forceX -= 1; + } + else if (key == 's') { + forceX += 1; + } + else if (key == 'a') { + forceZ += 1; + } + else if (key == 'd') { + forceZ -= 1; + } +} + +int gameTime; + +void MazeScreen::idle() { + int newTime = glutGet(GLUT_ELAPSED_TIME); + auto deltaTime = (newTime - gameTime) / 1000.0; + gameTime = newTime; + + posX += forceX * deltaTime * cos(angleX * PI / 180) + forceZ * deltaTime * sin(angleX * PI / 180); + posZ += forceZ * deltaTime * cos(angleX * PI / 180) - forceX * deltaTime * sin(angleX * PI / 180); + + glutPostRedisplay(); +} + +void MazeScreen::passiveMotion(int x, int y) { + if (mouseCapture) { + auto centerX = glutGet(GLUT_WINDOW_WIDTH) / 2; + auto centerY = glutGet(GLUT_WINDOW_HEIGHT) / 2; + + auto diffX = x - centerX; + auto diffY = x - centerY; + + // only left-right movement + angleX -= diffX; + + if (x != centerX || y != centerY) { + glutWarpPointer(centerX, centerY); + } + glutPostRedisplay(); + } +} + +void MazeScreen::mouse(int button, int state, int x, int y) { + if (button == GLUT_LEFT_BUTTON) { + auto centerX = glutGet(GLUT_WINDOW_WIDTH) / 2; + auto centerY = glutGet(GLUT_WINDOW_HEIGHT) / 2; + glutWarpPointer(centerX, centerY); + mouseCapture = true; + glutSetCursor(GLUT_CURSOR_NONE); + } + else if (button == GLUT_RIGHT_BUTTON) { + mouseCapture = false; + glutSetCursor(GLUT_CURSOR_INHERIT); + } +} diff --git a/src/maze.h b/src/maze.h new file mode 100644 index 0000000..e41da57 --- /dev/null +++ b/src/maze.h @@ -0,0 +1,27 @@ +#pragma once + +#include "glut_events.h" + +#include + +class MazeScreen : public GlutEvents { +private: + std::vector> maze; + int angleX; + bool mouseCapture; + float posX; + float posZ; + bool solid; + float forceX; + float forceZ; + +public: + MazeScreen(const std::vector> &maze); + + virtual void display(); + virtual void keyboard(unsigned char key, int x, int y); + virtual void keyboardUp(unsigned char key, int x, int y); + virtual void idle(); + virtual void passiveMotion(int x, int y); + virtual void mouse(int button, int state, int x, int y); +}; diff --git a/src/no_maze.cpp b/src/no_maze.cpp new file mode 100644 index 0000000..2e1f367 --- /dev/null +++ b/src/no_maze.cpp @@ -0,0 +1,22 @@ +#include "no_maze.h" + +#include + +#include +#include +#include + +#include "pixel_wise.h" + +void NoMazeScreen::display() { + glClear(GL_COLOR_BUFFER_BIT); + + glColor3ub(0xFF, 0x00, 0x00); + glRasterPos2fv(PW::tm(-100, 20).data()); + for (const auto& c : std::string{"No Maze Loaded"}) { + glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, c); + } + + glFlush(); + glutSwapBuffers(); +} diff --git a/src/no_maze.h b/src/no_maze.h new file mode 100644 index 0000000..310ee7a --- /dev/null +++ b/src/no_maze.h @@ -0,0 +1,7 @@ +#pragma once + +#include "glut_events.h" + +struct NoMazeScreen : public GlutEvents { + virtual void display(); +}; diff --git a/src/utils.hpp b/src/utils.hpp index d28a929..a0adac5 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -9,6 +9,19 @@ namespace Utils { return (input - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; } + template + T clamp(T input, T min, T max) { + if (input < min) { + return min; + } + else if (input > max) { + return max; + } + else { + return input; + } + } + template constexpr T toRad(T deg) { return deg * PI / 180;