You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
564 lines
21 KiB
564 lines
21 KiB
|
|
#include <stdio.h> // printf, snprintf
|
|
#include <stdint.h> // uint16_t
|
|
#include <stdlib.h> // srand, rand
|
|
#include <time.h> // time
|
|
#include <math.h> // cos, sin
|
|
|
|
#include "glad.h"
|
|
#include "glfw3.h"
|
|
|
|
|
|
/*
|
|
you can compile this (windows only, debug version, assuming you have cl.exe available) using:
|
|
|
|
cl.exe glad.c main.cpp /nologo /W3 /MTd /Od /Zi /RTC1 /fsanitize=address /link /out:game.exe /incremental:no glfw3.lib
|
|
|
|
the .exe will be around 1.5mb in this case.
|
|
|
|
|
|
or, in release version with a smaller .exe size and better perf (181kb with clang-cl, dang just barely missed 128kb total size!), no debugging symbols etc.
|
|
|
|
clang-cl.exe glad.c main.cpp /nologo /W3 /O2 /GS /clang:-fno-asynchronous-unwind-tables /link /out:game.exe /fixed /incremental:no /opt:icf /opt:ref libvcruntime.lib glfw3.lib
|
|
|
|
OR
|
|
|
|
cl.exe glad.c main.cpp /nologo /W3 /O2 /GS /link /out:game.exe /fixed /incremental:no /opt:icf /opt:ref libvcruntime.lib glfw3.lib
|
|
|
|
|
|
the first 10-20 frames in debug mode are prone to run below 60fps on my machine, which I unfortunately wasn't able to fix in 16 hours
|
|
- my computer is fairly weak though, it's about 10 years old and has no external GPU. You may fare better :). Subsequent frames perfome
|
|
many times better.
|
|
|
|
|
|
- [Y] - snake-like 0 player game
|
|
- [Y] - need to support on the order of 8k+ snakes concurrently (we're going to choose a max of 8192)
|
|
- [Y] - if the head of a snake touches the tail of another, the snake who's head touched the tail will merge up with the 'eaten' snake.
|
|
- [Y] - all snakes start with one segment
|
|
- [Y] - when there is only one snake left, the game must restart
|
|
- [Y?] - game data under 128kb - achieved if this means game state, not quite if it means .exe size (we're about ~1mb .exe size after trying to minimize size)
|
|
- [Y] - 60fps minimum, or locked
|
|
- [Y] - static memory allocation, avoid heap
|
|
- [Y] - minimal or zero usage of libraries (except glfw and opengl)
|
|
*/
|
|
|
|
static GLFWwindow *Window;
|
|
|
|
inline bool checkError__(int line) {
|
|
GLenum errorCode;
|
|
while ((errorCode = glGetError()) != GL_NO_ERROR) {
|
|
switch (errorCode) {
|
|
case GL_INVALID_ENUM:
|
|
printf("%d, INVALID_ENUM", line);
|
|
break;
|
|
case GL_INVALID_VALUE:
|
|
printf("%d, INVALID_VALUE", line);
|
|
break;
|
|
case GL_INVALID_OPERATION:
|
|
printf("%d, INVALID_OPERATION", line);
|
|
break;
|
|
case GL_OUT_OF_MEMORY:
|
|
printf("%d, OUT_OF_MEMORY", line);
|
|
break;
|
|
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
|
printf("%d, INVALID_FRAMEBUFFER_OPERATION", line);
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#define checkError() checkError__(__LINE__)
|
|
|
|
|
|
static const float PI = 3.14159f;
|
|
|
|
//--------------------------------------------------------------------------------
|
|
// game-data
|
|
// between QUADRANTS and pointBuffer, the only real data structures in use,
|
|
// you have 116.736kb of game state, assuming 8192 snakes.
|
|
|
|
int windowWidth;
|
|
int windowHeight;
|
|
|
|
struct Snake {
|
|
// coordinates are normalized unsigned integers.
|
|
// |numSegments| is just a count, where 0 indicates a dead snake.
|
|
uint16_t numSegments, headX, headY, theta;
|
|
};
|
|
static constexpr float SNAKE_SEGMENT_RADIUS = 8; // glPointSize
|
|
static constexpr unsigned int NUM_SNAKES = 8192; // must be greater than 1
|
|
|
|
struct Rectangle {
|
|
uint16_t left, bottom, right, top;
|
|
};
|
|
static constexpr uint32_t NUM_QUADRANTS = 64;
|
|
static constexpr unsigned int SNAKES_PER_QUADRANT = NUM_SNAKES/NUM_QUADRANTS;
|
|
struct Quadrant {
|
|
Snake snakes[SNAKES_PER_QUADRANT];
|
|
Rectangle rect;
|
|
};
|
|
static Quadrant QUADRANTS[NUM_QUADRANTS];
|
|
|
|
struct Point {
|
|
uint16_t x, y;
|
|
uint16_t weight;
|
|
};
|
|
static Point pointBuffer[NUM_SNAKES]; // each snake starts as one segment which is represented as a point, so you can have at most NUM_SNAKES points.
|
|
|
|
uint32_t numDead = 0;
|
|
uint32_t longest = 0;
|
|
uint32_t numPoints = 0;
|
|
|
|
double msPerFrame = 0.0;
|
|
double deltaTime = 0.0;
|
|
double lastFrameCountTime = 0.0;
|
|
double lastFrameStartTime = 0.0;
|
|
bool doFancyRestart = false;
|
|
|
|
long long int numFramesInLastSecond = 0;
|
|
long long int frameCounter = 0;
|
|
//--------------------------------------------------------------------------------
|
|
|
|
static inline uint16_t normalizeUint(uint16_t input, uint32_t oldMax) {
|
|
return input * USHRT_MAX / oldMax;
|
|
}
|
|
|
|
static inline float invlerp(float a, float b, float v) {
|
|
return (v - a) / (b - a);
|
|
}
|
|
static inline float lerp(float a, float b, float t) {
|
|
return a + t * (b - a);
|
|
}
|
|
|
|
static inline float snakeThetaToRadians(uint16_t a) {
|
|
return ((float)a/(float)USHRT_MAX)*2.f*PI;
|
|
}
|
|
static inline uint16_t radiansToSnakeTheta(float r) {
|
|
return (uint16_t) ((r + (0.f*PI)) / (2.f*PI) * (float)USHRT_MAX);
|
|
}
|
|
|
|
static inline void splitRectangleIntoQuadrants(Rectangle input, Rectangle quadrants[4]) {
|
|
auto centerX = (input.left + input.right) / 2;
|
|
auto centerY = (input.top + input.bottom) / 2;
|
|
|
|
// top-left quadrant, proceeding clockwise
|
|
quadrants[0].left = input.left;
|
|
quadrants[0].right = centerX;
|
|
quadrants[0].top = input.top;
|
|
quadrants[0].bottom = centerY;
|
|
|
|
quadrants[1].left = centerX;
|
|
quadrants[1].right = input.right;
|
|
quadrants[1].top = input.top;
|
|
quadrants[1].bottom = centerY;
|
|
|
|
quadrants[2].left = centerX;
|
|
quadrants[2].right = input.right;
|
|
quadrants[2].top = centerY;
|
|
quadrants[2].bottom = input.bottom;
|
|
|
|
quadrants[3].left = input.left;
|
|
quadrants[3].right = centerX;
|
|
quadrants[3].top = centerY;
|
|
quadrants[3].bottom = input.bottom;
|
|
}
|
|
|
|
static inline uint16_t randInRange(unsigned int min, unsigned int max) {
|
|
const unsigned int range = max - min + 1;
|
|
return (uint16_t) (min + (unsigned int)((double)rand() / (RAND_MAX + 1.0) * range));
|
|
}
|
|
|
|
static inline void calculateSegment(Snake* snake, uint16_t segmentNumber, Point* outPoint) {
|
|
const float weight = (float)segmentNumber / (float)snake->numSegments;
|
|
const float radians = snakeThetaToRadians(snake->theta);
|
|
const float dx = cos(radians);
|
|
const float dy = sin(radians);
|
|
|
|
outPoint->x = snake->headX + dx * segmentNumber * SNAKE_SEGMENT_RADIUS;
|
|
outPoint->y = snake->headY + dy * segmentNumber * SNAKE_SEGMENT_RADIUS;
|
|
outPoint->weight = (uint16_t) (weight * USHRT_MAX);
|
|
}
|
|
|
|
static inline void initializeQuadrants(int windowWidth, int windowHeight) {
|
|
Rectangle windowQuad = { 0, 0, normalizeUint(windowWidth, windowWidth), normalizeUint(windowHeight, windowHeight) };
|
|
Rectangle topLevelQuads[4];
|
|
splitRectangleIntoQuadrants(windowQuad, topLevelQuads);
|
|
|
|
// there's probably a better way to iteratively construct a quadtree...
|
|
for (int i = 0; i < 4; i++) {
|
|
Rectangle iquads[4];
|
|
splitRectangleIntoQuadrants(topLevelQuads[i], iquads);
|
|
|
|
for (int j = 0; j < 4; j++) {
|
|
Rectangle jquads[4];
|
|
splitRectangleIntoQuadrants(iquads[j], jquads);
|
|
|
|
for (int k = 0; k < 4; k++) {
|
|
QUADRANTS[i*16+j*4+k].rect = jquads[k];
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < NUM_QUADRANTS; i++) {
|
|
Quadrant* q= &QUADRANTS[i];
|
|
for (int j = 0; j < SNAKES_PER_QUADRANT; j++) {
|
|
Snake* snake = &q->snakes[j];
|
|
snake->numSegments = 1;
|
|
snake->headX = randInRange(q->rect.left, q->rect.right);
|
|
snake->headY = randInRange(q->rect.bottom, q->rect.top);
|
|
//printf("Snake #%d, quadrant %d - head (x/y): %u/%u, numSegments: %u\n", j+1, i, snake->headX, snake->headY, snake->numSegments);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline unsigned int computeQuadrant(int x, int y, int left, int top, int bottom, int right) {
|
|
if (x < left || y < bottom || x > right || y > top) {
|
|
printf("outside rect!\n");
|
|
return -1;
|
|
|
|
} else if (x < right/2 && y < top/2) {
|
|
return 0;
|
|
|
|
} else if (x >= right/2 && y < top/2) {
|
|
return 1;
|
|
|
|
} else if (x < right/2 && y >= top/2) {
|
|
return 2;
|
|
|
|
} else {
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
static inline bool findNearestSnakeInQuad(
|
|
Quadrant* quad,
|
|
Snake* snake,
|
|
int searchingSnakeIndex,
|
|
float& nearestD,
|
|
uint16_t& nearestX,
|
|
uint16_t& nearestY,
|
|
bool* outHadLiveSnake
|
|
) {
|
|
for (int j = 0; j < SNAKES_PER_QUADRANT; j++) {
|
|
if (searchingSnakeIndex == j) continue;
|
|
|
|
Snake* other = &quad->snakes[j];
|
|
if (other->numSegments == 0) continue; // dead snake, can't bite it
|
|
|
|
// if we have at least one snake that has a segment, that means this quad has at least one other live snake.
|
|
if (outHadLiveSnake) *outHadLiveSnake = true;
|
|
|
|
// find tail coordinates for this snake
|
|
Point tail;
|
|
calculateSegment(other, other->numSegments-1, &tail);
|
|
|
|
// compute distance, check if it's closer than previously recorded
|
|
float dx = ((float)snake->headX - (float)tail.x);
|
|
float dy = ((float)snake->headY - (float)tail.y);
|
|
float distance = sqrt(dx*dx + dy*dy);
|
|
if (distance < nearestD) {
|
|
nearestD = distance;
|
|
nearestX = tail.x;
|
|
nearestY = tail.y;
|
|
|
|
if (distance < SNAKE_SEGMENT_RADIUS*3) {
|
|
// if we're close enough, we can call it a bite and early-out.
|
|
snake->numSegments += other->numSegments;
|
|
other->numSegments = 0;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline bool updateQuadrant(unsigned int quadIndex) {
|
|
Quadrant* quad = &QUADRANTS[quadIndex];
|
|
|
|
for (int i = 0; i < SNAKES_PER_QUADRANT; i++) {
|
|
// update each snake in the quad. there are only really a few things to do:
|
|
// - find nearest neighbour to snake
|
|
// - check if bite
|
|
// - move and rotate snake
|
|
bool foundAtLeastOneOtherLiveSnakeInQuad = false;
|
|
Snake* snake = &quad->snakes[i];
|
|
|
|
uint16_t numSegments = snake->numSegments;
|
|
if (numSegments == 0) continue;
|
|
|
|
if (numSegments > longest) {
|
|
longest = numSegments; // just for fun.
|
|
}
|
|
|
|
// we now linear search for nearest tail to bite, early-outing on dead snakes,
|
|
// and very-close snakes (we technically do not always garuntee finding the 'nearest')
|
|
const uint16_t headX = snake->headX;
|
|
const uint16_t headY = snake->headY;
|
|
float nearestD = 999999.f; // arbitrary large-ish number
|
|
uint16_t nearestX = 0;
|
|
uint16_t nearestY = 0;
|
|
if (findNearestSnakeInQuad(quad, snake, i, nearestD, nearestX, nearestY, &foundAtLeastOneOtherLiveSnakeInQuad)) {
|
|
numDead++;
|
|
goto nextSnake; // we killed another snake, we can move on to the next snake.
|
|
};
|
|
|
|
if (!foundAtLeastOneOtherLiveSnakeInQuad) {
|
|
// we found 0 snakes in this quad (besides ourselves)
|
|
// this circumstance can only arise when there are a small number of snakes left
|
|
// , less than 2k at least, so we can just lazily iterate the rest of the quadrants.
|
|
for (int j = 0; j < NUM_QUADRANTS; j++) {
|
|
if (quadIndex == j) continue;
|
|
|
|
if (findNearestSnakeInQuad(&QUADRANTS[j], snake, -1, nearestD, nearestX, nearestY, &foundAtLeastOneOtherLiveSnakeInQuad)) {
|
|
numDead++;
|
|
goto nextSnake; // we killed another snake, we can move on to the next snake, after appending our draw data
|
|
}
|
|
}
|
|
}
|
|
if (!foundAtLeastOneOtherLiveSnakeInQuad) {
|
|
// we must be the only snake left!
|
|
return true;
|
|
}
|
|
|
|
// do the movement and rotation for this snake.
|
|
const float angle = atan2f((float)headY - (float)nearestY, (float)headX - (float)nearestX);
|
|
// it probably makes more sense for the smaller guys to be faster, but the simulation gets quite slow towards the end of it, so I do the opposite.
|
|
const float dx = -cos(angle) * 500.f*log(0.25f*snake->numSegments + 1) * deltaTime;
|
|
const float dy = -sin(angle) * 500.f*log(0.25f*snake->numSegments + 1) * deltaTime;
|
|
snake->headX += dx;
|
|
snake->headY += dy;
|
|
#if 1
|
|
// move the snake's angle towards angle |angle|, with some fixed radial speed, smoothly
|
|
const float snakeAngle = snakeThetaToRadians(snake->theta);
|
|
const float angleDelta = angle - snakeAngle;
|
|
//snake->theta = radiansToSnakeTheta(snakeAngle + ((2.f*PI*angleDelta)*0.0015f/(2.f*PI));
|
|
//snake->theta = radiansToSnakeTheta(fmin(snakeAngle + (2.f*PI*angleDelta)*(deltaTime/snake->numSegments)/(2.f*PI), angle));
|
|
snake->theta = radiansToSnakeTheta(snakeAngle + (angleDelta+2.f*PI)*deltaTime*100.f/snake->numSegments);
|
|
#else
|
|
snake->theta = radiansToSnakeTheta(angle);
|
|
#endif
|
|
// at this point, we know everything we need to know about this snake to populate the draw data for it.
|
|
// we have the head position, an angle, and a count of the number of segments, so we use this to generate
|
|
// points from head --> tail.
|
|
for (uint16_t p = 0; p < snake->numSegments; p++) {
|
|
Point* point = &pointBuffer[numPoints++];
|
|
calculateSegment(snake, p, point);
|
|
}
|
|
nextSnake: continue;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int main(void) {
|
|
glfwInit();
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
|
glfwWindowHint(GLFW_VISIBLE, false); // hide window initially - while we set up stuff. show after.
|
|
glfwWindowHint(GLFW_RESIZABLE, false); // don't allow the user to re-size the window
|
|
glfwWindowHint(GLFW_CENTER_CURSOR, true); // center the cursor in the window when opening a fullscreen window
|
|
glfwWindowHint(GLFW_FOCUS_ON_SHOW, true); // focus the window when glfwShowWindow is called
|
|
|
|
#ifdef __APPLE__
|
|
// removes all deprecated opengl functionality from older opengl versions, required on MacOSX
|
|
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
|
#endif
|
|
|
|
GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor();
|
|
const GLFWvidmode* primaryMonitorMode = glfwGetVideoMode(primaryMonitor);
|
|
float initialWidth = 1138.0f;
|
|
float initialHeight = 640.0f;
|
|
// scale up the dimensions of the initial size of the window until it exceeds the size of the screen, then go with the one just before that
|
|
while (1) {
|
|
if ((initialHeight * 1.2f > primaryMonitorMode->height) || (initialWidth * 1.2f) > primaryMonitorMode->width) {
|
|
break;
|
|
}
|
|
initialHeight *= 1.2f;
|
|
initialWidth *= 1.2f;
|
|
}
|
|
windowWidth = (int) initialWidth;
|
|
windowHeight = (int) initialHeight;
|
|
|
|
Window = glfwCreateWindow(windowWidth, windowHeight, "flOw MMo", nullptr, nullptr);
|
|
if (Window == nullptr) {
|
|
printf("%s", "Failed to initialize GLFWwindow\n");
|
|
return 1;
|
|
}
|
|
|
|
glfwMakeContextCurrent(Window);
|
|
|
|
//--------------------------------------------------------------------------------
|
|
// initialize glad, resolve OpenGL function pointers
|
|
if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
|
|
printf("%s", "Failed to initialize GLAD\n");
|
|
return 1;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------
|
|
// compile and link our shader.
|
|
// my apologies if this fails compilation on your machine.
|
|
// My main computer's OpenGL driver has been known to allow compilations where others fail it.
|
|
int success;
|
|
char infoLog[512];
|
|
// vertex...
|
|
const char* vertexShaderCode = R"(
|
|
#version 330 core
|
|
layout (location = 0) in vec2 aPos;
|
|
layout (location = 1) in float aWeight;
|
|
|
|
flat out float vWeight;
|
|
|
|
void main() {
|
|
vWeight = aWeight;
|
|
gl_Position = vec4(aPos*2.0-1.0, 0.0, 1.0);
|
|
gl_PointSize = 10 + (1.0f - aWeight)*10;
|
|
}
|
|
)";
|
|
unsigned int vertexShaderId = glCreateShader(GL_VERTEX_SHADER);
|
|
glShaderSource(vertexShaderId, 1, &vertexShaderCode, nullptr);
|
|
glCompileShader(vertexShaderId);
|
|
glGetShaderiv(vertexShaderId, GL_COMPILE_STATUS, &success);
|
|
if (!success) {
|
|
glGetShaderInfoLog(vertexShaderId, 512, nullptr, infoLog);
|
|
printf("Failure compiling vertex shader!\n%s\nSource: %s\n", infoLog, vertexShaderCode);
|
|
return 1;
|
|
}
|
|
|
|
// fragment...
|
|
const char* fragmentShaderCode = R"(
|
|
#version 330 core
|
|
|
|
flat in float vWeight;
|
|
|
|
out vec4 FragColor;
|
|
|
|
void main() {
|
|
FragColor = vec4(vWeight + 0.5, 0.5f * vWeight, 0.2f / (vWeight+0.01), 1.0f);
|
|
}
|
|
)";
|
|
unsigned int fragmentShaderId = glCreateShader(GL_FRAGMENT_SHADER);
|
|
glShaderSource(fragmentShaderId, 1, &fragmentShaderCode, nullptr);
|
|
glCompileShader(fragmentShaderId);
|
|
glGetShaderiv(fragmentShaderId, GL_COMPILE_STATUS, &success);
|
|
if (!success) {
|
|
glGetShaderInfoLog(fragmentShaderId, 512, nullptr, infoLog);
|
|
printf("Failure compiling fragment shader!\n%s\nSource: %s\n", infoLog, fragmentShaderCode);
|
|
return 1;
|
|
}
|
|
|
|
unsigned int shaderId = glCreateProgram();
|
|
glAttachShader(shaderId, vertexShaderId);
|
|
glAttachShader(shaderId, fragmentShaderId);
|
|
glLinkProgram(shaderId);
|
|
glGetProgramiv(shaderId, GL_LINK_STATUS, &success);
|
|
if (!success) {
|
|
glGetProgramInfoLog(shaderId, 512, nullptr, infoLog);
|
|
printf("Failure linking shader!\n%s\n", infoLog);
|
|
return 1;
|
|
}
|
|
checkError();
|
|
|
|
// use it, we only have one shader
|
|
glUseProgram(shaderId);
|
|
checkError();
|
|
|
|
//--------------------------------------------------------------------------------
|
|
srand(time(nullptr));
|
|
for (int i = 0; i < rand(); i++) rand(); // discard first few calls, randomly. 'rand()' has poor entropy
|
|
|
|
// initialize the game state. set up all the initial snake positions, etc.
|
|
initializeQuadrants(windowWidth, windowHeight);
|
|
|
|
//--------------------------------------------------------------------------------
|
|
// setup the VAO/VBO
|
|
unsigned int VAO, VBO;
|
|
glGenVertexArrays(1, &VAO);
|
|
glBindVertexArray(VAO);
|
|
glGenBuffers(1, &VBO);
|
|
glBindBuffer(GL_ARRAY_BUFFER, VBO);
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, 2, GL_UNSIGNED_SHORT, GL_TRUE, sizeof(Point), (void*)offsetof(Point, x));
|
|
glEnableVertexAttribArray(1);
|
|
glVertexAttribPointer(1, 1, GL_UNSIGNED_SHORT, GL_TRUE, sizeof(Point), (void*)offsetof(Point, weight));
|
|
|
|
//--------------------------------------------------------------------------------
|
|
// set global opengl state
|
|
glClearColor(0.25f, 0.52f, 0.54f, 1.0f);
|
|
glEnable(GL_PROGRAM_POINT_SIZE);
|
|
glEnable(GL_DEPTH_TEST);
|
|
glDepthFunc(GL_LESS);
|
|
glEnable(GL_CULL_FACE);
|
|
|
|
glfwShowWindow(Window); // make window visible before entering main loop
|
|
glfwRequestWindowAttention(Window);
|
|
|
|
while (!glfwWindowShouldClose(Window)) {
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
// calculate timing metrics, delta time, etc.
|
|
double currentTime = glfwGetTime();
|
|
frameCounter++;
|
|
if (currentTime - lastFrameCountTime >= 1.0) {
|
|
msPerFrame = 1000.0/frameCounter;
|
|
numFramesInLastSecond = frameCounter;
|
|
frameCounter = 0;
|
|
lastFrameCountTime += 1.0;
|
|
|
|
static char windowTitle[256];
|
|
snprintf(windowTitle, 256, "flOw MMo - %.2f ms per frame, %lld fps, delta %.2f, num snakes: %u, num alive: %u, dead: %u, longest boi: %u", msPerFrame, numFramesInLastSecond, deltaTime, NUM_SNAKES, NUM_SNAKES-numDead, numDead, longest);
|
|
glfwSetWindowTitle(Window, windowTitle); // <-- this call is very slow :(
|
|
}
|
|
deltaTime = currentTime - lastFrameStartTime;
|
|
lastFrameStartTime = currentTime;
|
|
|
|
// set ourselves to close if you press escape
|
|
if (glfwGetKey(Window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
|
|
glfwSetWindowShouldClose(Window, true);
|
|
continue;
|
|
}
|
|
|
|
static double lastUpdateTime = 0.0f;
|
|
if ((currentTime - lastUpdateTime) < 0.0167f) {
|
|
continue; // we already updated in the last 16ms, skip to next frame
|
|
}
|
|
|
|
if (doFancyRestart) {
|
|
initializeQuadrants(windowWidth, windowHeight);
|
|
doFancyRestart = false;
|
|
|
|
} else {
|
|
// update game state:
|
|
bool gameOver = false;
|
|
for (int q = 0; q < NUM_QUADRANTS; q++) {
|
|
gameOver = updateQuadrant(q);
|
|
if (gameOver) break;
|
|
}
|
|
if (gameOver) {
|
|
printf("game over!\n");
|
|
doFancyRestart = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// upload draw data and render
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(Point)*NUM_SNAKES, nullptr, GL_DYNAMIC_DRAW);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(Point)*NUM_SNAKES, pointBuffer, GL_DYNAMIC_DRAW);
|
|
glDrawArrays(GL_POINTS, 0, numPoints);
|
|
numPoints = 0;
|
|
checkError();
|
|
|
|
glfwSwapBuffers(Window);
|
|
glfwPollEvents(); // performs better on windows at the end of frame vs. start
|
|
}
|
|
|
|
glDeleteShader(vertexShaderId);
|
|
glDeleteShader(fragmentShaderId);
|
|
glDeleteProgram(shaderId);
|
|
|
|
glfwTerminate();
|
|
|
|
return 0;
|
|
}
|
|
|