// LICENSING: WTFPL 授权
// for macOS
//#include <gtk/gtk.h>
#include <GL/freeglut.h>
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#include <gst/gst.h>
#define ACCEL 0.2
#define DECEL 0.93
#define SNAKE_HEAD_SPEED 0.17
#define EAT_RANGE 0.7
#define FOOD_RANGE 36
#define CAMERA_HEIGHT 45
#define CAMERA_DISTANCE 17
#define SIZE_UP 4
#define SIZE_START 6
typedef struct
{
float x, y, z;
} Vector3;
struct SnakeSegment
{
Vector3 position;
Vector3 rotation;
struct SnakeSegment *next;
};
struct Snake
{
struct SnakeSegment *head;
};
struct Snake snake;
Vector3 camera_position, camera_rotation, camera_focus;
Vector3 food_position, food_rotation;
time_t t;
bool move_left = FALSE;
int snake_length = 1;
float snake_turn_velocity;
float x_velocity, y_velocity, z_velocity;
void renderCube(GLfloat diffuse[], float x, float y, float z, float rx, float ry, float rz, float size)
{
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
glPushMatrix();
glTranslatef(x, y, z);
glRotatef(rx, 1, 0, 0);
glRotatef(ry, 0, 1, 0);
glRotatef(rz, 0, 0, 1);
glutSolidCube(size);
glPopMatrix();
}
void display()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(
camera_position.x, camera_position.y, camera_position.z,
camera_focus.x, camera_focus.y, camera_focus.z,
0, 1, 0);
GLfloat red_diffuse[] = {1, 0, 0, 1};
GLfloat green_diffuse[] = {0, 1, 0, 1};
GLfloat grey_diffuse[] = {0.4f, 0.4f, 0.4f};
GLfloat dark_grey_diffuse[] = {0.3f, 0.3f, 0.3f};
food_rotation.x += 0.8;
food_rotation.y += 0.8;
food_rotation.z += 0.8;
renderCube(red_diffuse, food_position.x, food_position.y, food_position.z, food_rotation.x, food_rotation.y, food_rotation.z, 0.7f);
struct SnakeSegment *current = snake.head;
while (current != NULL)
{
renderCube(green_diffuse, current->position.x, current->position.y, current->position.z, current->rotation.x, current->rotation.y, current->rotation.z, 1);
current = current->next;
}
renderCube(grey_diffuse, 0, -21, 0, 0, 0, 0, 40);
renderCube(dark_grey_diffuse, -40, -19, 0, 0, 0, 0, 40);
renderCube(dark_grey_diffuse, 40, -19, 0, 0, 0, 0, 40);
renderCube(dark_grey_diffuse, 0, -19, 40, 0, 0, 0, 40);
renderCube(dark_grey_diffuse, 0, -19, -40, 0, 0, 0, 40);
renderCube(dark_grey_diffuse, -40, -19, -40, 0, 0, 0, 40);
renderCube(dark_grey_diffuse, 40, -19, 40, 0, 0, 0, 40);
renderCube(dark_grey_diffuse, -40, -19, 40, 0, 0, 0, 40);
renderCube(dark_grey_diffuse, 40, -19, -40, 0, 0, 0, 40);
glutSwapBuffers();
}
void reshape(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, (double)width / (double)height, 1.0, 1000);
}
void addSegment(struct Snake *snake)
{
struct SnakeSegment *new_segment = (struct SnakeSegment *)malloc(sizeof(struct SnakeSegment));
if (new_segment == NULL)
{
fprintf(stderr, "\n-!!! Memory allocation failed for snake head. !!!-\n");
exit(1);
}
struct SnakeSegment *current = snake->head;
while (current->next != NULL)
{
current = current->next;
}
current->next = new_segment;
new_segment->next = NULL;
}
bool foodEaten()
{
if (fabs(snake.head->position.x - food_position.x) < EAT_RANGE && fabs(snake.head->position.z - food_position.z) < EAT_RANGE)
{
return true;
}
return false;
}
void generateFood()
{
food_position.x = ((rand() % FOOD_RANGE * 1000) / 1000) - FOOD_RANGE / 2;
food_position.z = ((rand() % FOOD_RANGE * 1000) / 1000) - FOOD_RANGE / 2;
food_rotation.x = ((rand() % 360 * 1000) / 1000);
food_rotation.y = ((rand() % 360 * 1000) / 1000);
food_rotation.z = ((rand() % 360 * 1000) / 1000);
}
Vector3 calculateDirection(Vector3 point_1, Vector3 point_2)
{
Vector3 vector;
vector.x = point_2.x - point_1.x;
vector.y = point_2.y - point_1.y;
vector.z = point_2.z - point_1.z;
double magnitude = sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
Vector3 direction;
direction.x = vector.x / magnitude;
direction.y = vector.y / magnitude;
direction.z = vector.z / magnitude;
return direction;
}
bool Colliding(Vector3 position)
{
if (position.x < -20 || position.x >= 20 || position.z < -20 || position.z >= 20)
{
return true;
}
return false;
}
void updateSnakeSegments()
{
struct SnakeSegment *current = snake.head;
Vector3 previous_position = snake.head->position;
Vector3 previous_rotation = snake.head->rotation;
while (current != NULL)
{
// Store the current position and rotation
Vector3 current_position = current->position;
Vector3 current_rotation = current->rotation;
// Update the position and rotation with the previous values
current->position = previous_position;
current->rotation = previous_rotation;
// Update the previous position and rotation variables
previous_position = current_position;
previous_rotation = current_rotation;
// Move to the next segment
current = current->next;
}
// Update the position and rotation of the snake's head
// snake.head->position.x += SNAKE_HEAD_SPEED * sin(snake.head->rotation.y * M_PI / 180.0f);
// snake.head->position.z += SNAKE_HEAD_SPEED * cos(snake.head->rotation.y * M_PI / 180.0f);
}
void timer()
{
if (foodEaten())
{
for (int i = 0; i < SIZE_UP; i++)
{
addSegment(&snake);
updateSnakeSegments();
}
snake_length++;
generateFood();
}
if (Colliding(snake.head->position))
{
exit(1037);
}
if (move_left)
{
snake_turn_velocity += ACCEL;
}
else if (!move_left)
{
snake_turn_velocity -= ACCEL;
}
snake_turn_velocity *= DECEL;
snake.head->rotation.y += snake_turn_velocity;
updateSnakeSegments();
snake.head->position.x += SNAKE_HEAD_SPEED * sin(snake.head->rotation.y * M_PI / 180.0f);
snake.head->position.z += SNAKE_HEAD_SPEED * cos(snake.head->rotation.y * M_PI / 180.0f);
camera_position.x = snake.head->position.x;
camera_position.y = snake.head->position.y + CAMERA_HEIGHT;
camera_position.z = snake.head->position.z;
camera_rotation = calculateDirection(camera_focus, camera_position);
camera_position.z += CAMERA_DISTANCE * cos(camera_rotation.y * M_PI / 180.0f);
camera_focus.x += ((snake.head->position.x + food_position.x * 0.7) / 2 - camera_focus.x) / 10;
camera_focus.y += ((snake.head->position.y + food_position.y * 0.7) / 2 - camera_focus.y) / 10;
camera_focus.z += ((snake.head->position.z + food_position.z * 0.7) / 2 - camera_focus.z) / 10;
glutPostRedisplay();
glutTimerFunc(16, timer, 0);
}
void keyboard(unsigned char key, int x, int y)
{
printf("yeehaw %d %d\n", x, y);
switch (key)
{
case 'a':
move_left = TRUE;
break;
case 'd':
move_left = FALSE;
break;
default:
break;
}
}
void init(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
glutInitWindowSize(1920, 1080);
glutCreateWindow("OpenGL 3D Snake Game");
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHT1);
GLfloat ambient_light[] = {0.9f, 0.9f, 0.9f, 1.0f};
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient_light);
GLfloat light_direction[] = {5.0f, 15.0f, 0.0f, 32768.0f};
glLightfv(GL_LIGHT0, GL_POSITION, light_direction);
snake.head = (struct SnakeSegment *)malloc(sizeof(struct SnakeSegment));
snake.head->position.x = 0;
snake.head->position.y = 0;
snake.head->position.z = 0;
snake.head->rotation.x = 0;
snake.head->rotation.y = 0;
snake.head->rotation.z = 0;
snake.head->next = NULL;
for (int i = 0; i < SIZE_START; i++)
{
addSegment(&snake);
updateSnakeSegments();
}
srand((unsigned)time(&t));
generateFood();
}
int main(int argc, char **argv)
{
init(argc, argv);
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutTimerFunc(0, timer, 0);
glutMainLoop();
return 0;
}