GIF
main.c
#include <stdio.h>
#include "level.h"
static void flush_stdin(void);
int main()
{
int level;
puts("Enter a negative integer to exit.");
for (;;)
{
printf("Enter level: ");
if (scanf("%d", &level) != 1)
{
puts("Please try again.");
flush_stdin();
}
else if (level < 0)
{
puts("Goodbye!");
break;
}
else
{
run_level(level);
flush_stdin();
}
}
return 0;
}
static void flush_stdin(void)
{
while (getchar() != '\n');
}
level.h
#ifndef LEVEL_H
#define LEVEL_H
void run_level(int level);
#endif
level.c
#include "level.h"
#include <stdio.h>
#include "level_t.h"
#include "level_renderer.h"
#include "bass.h"
static float last_frame_time = 0;
static float elapsed_time = 0;
static char *game_over_string = NULL;
static bool game_over(void);
static bool hero_reached_destination(void);
static void resolve_collisions(void);
static bool hero_is_outside(void);
static void do_empty(void);
static void do_full(void);
static void do_south(void);
static void do_north(void);
static void do_west(void);
static void do_east(void);
static void do_south_west_corner(void);
static void do_south_east_corner(void);
static void do_north_west_corner(void);
static void do_north_east_corner(void);
static void do_south_west(void);
static void do_south_east(void);
static void do_north_west(void);
static void do_north_east(void);
static void do_north_west_diag(void);
static void do_north_east_diag(void);
static void bounce_south(void);
static void bounce_north(void);
static void bounce_west(void);
static void bounce_east(void);
static float get_south_penetration(void);
static float get_north_penetration(void);
static float get_west_penetration(void);
static float get_east_penetration(void);
static void react_to_input(void);
static void push_west(void);
static void push_east(void);
static void apply_velocity(void);
void run_level(int level)
{
if (!init_level_t(level)) return;
init_renderer();
init_bass(lvl.bass_frequency, lvl.bass_peak_volume, lvl.bass_duration);
reset_timer();
while (!game_over())
{
render_level();
react_to_input();
apply_velocity();
resolve_collisions();
}
exit_bass();
exit_renderer();
exit_level_t();
}
static bool game_over(void)
{
if (window_should_close()) game_over_string = "Window closed.";
if (hero_reached_destination()) game_over_string = "You won!";
if (game_over_string != NULL)
{
printf("%s Time: %fs.\n", game_over_string, get_time());
game_over_string = NULL;
elapsed_time = last_frame_time = 0;
return true;
}
return false;
}
static bool hero_reached_destination(void)
{
return lvl.hero_pos.x > lvl.dest_pos.x
&& lvl.hero_pos.y > lvl.dest_pos.y
&& lvl.hero_pos.x + 1 < lvl.dest_pos.x + lvl.dest_sz.x
&& lvl.hero_pos.y + 1 < lvl.dest_pos.y + lvl.dest_sz.y;
}
static void resolve_collisions(void)
{
if (hero_is_outside())
{
lvl.hero_vel.x = lvl.hero_vel.y = 0;
game_over_string = "You ventured outside the level bounds.";
return;
}
else
{
elapsed_time = get_time() - last_frame_time;
lvl.hero_vel.x += lvl.grav_vel.x * elapsed_time;
lvl.hero_vel.y += lvl.grav_vel.y * elapsed_time;
last_frame_time = get_time();
}
int south_west = (int) lvl.hero_pos.y * lvl.bmp.w + (int) lvl.hero_pos.x;
int south_east = south_west + 1;
int north_west = south_west + lvl.bmp.w;
int north_east = north_west + 1;
if ( lvl.bmp.i[south_west] || lvl.bmp.i[south_east]
|| lvl.bmp.i[north_west] || lvl.bmp.i[north_east]) drop_bass();
if (lvl.bmp.i[south_west] == 0)
if (lvl.bmp.i[south_east] == 0)
if (lvl.bmp.i[north_west] == 0)
if (lvl.bmp.i[north_east] == 0)
do_empty();
else
do_north_east();
else
if (lvl.bmp.i[north_east] == 0)
do_north_west();
else
do_north();
else
if (lvl.bmp.i[north_west] == 0)
if (lvl.bmp.i[north_east] == 0)
do_south_east();
else
do_east();
else
if (lvl.bmp.i[north_east] == 0)
do_north_west_diag();
else
do_north_east_corner();
else
if (lvl.bmp.i[south_east] == 0)
if (lvl.bmp.i[north_west] == 0)
if (lvl.bmp.i[north_east] == 0)
do_south_west();
else
do_north_east_diag();
else
if (lvl.bmp.i[north_east] == 0)
do_west();
else
do_north_west_corner();
else
if (lvl.bmp.i[north_west] == 0)
if (lvl.bmp.i[north_east] == 0)
do_south();
else
do_south_east_corner();
else
if (lvl.bmp.i[north_east] == 0)
do_south_west_corner();
else
do_full();
}
static bool hero_is_outside(void)
{
return lvl.hero_pos.x < 0 || lvl.hero_pos.y < 0 ||
lvl.hero_pos.x >= lvl.bmp.w - 1 || lvl.hero_pos.y >= lvl.bmp.h - 1;
}
static void do_empty(void) {}
static void do_full(void)
{
lvl.hero_vel.x = lvl.hero_vel.y = 0;
game_over_string = "You were crushed.";
}
static void do_south(void)
{
bounce_north();
}
static void do_north(void)
{
bounce_south();
}
static void do_west(void)
{
bounce_east();
}
static void do_east(void)
{
bounce_west();
}
static void do_south_west_corner(void)
{
do_south();
do_west();
}
static void do_south_east_corner(void)
{
do_south();
do_east();
}
static void do_north_west_corner(void)
{
do_north();
do_west();
}
static void do_north_east_corner(void)
{
do_north();
do_east();
}
static void do_south_west(void)
{
if (get_south_penetration() < get_west_penetration()) bounce_north();
else bounce_east();
}
static void do_south_east(void)
{
if (get_south_penetration() < get_east_penetration()) bounce_north();
else bounce_west();
}
static void do_north_west(void)
{
if (get_north_penetration() < get_west_penetration()) bounce_south();
else bounce_east();
}
static void do_north_east(void)
{
if (get_north_penetration() < get_east_penetration()) bounce_south();
else bounce_west();
}
static void do_north_west_diag(void)
{
if (get_south_penetration() <= 0.5) do_south_west_corner();
else do_north_east_corner();
}
static void do_north_east_diag(void)
{
if (get_south_penetration() <= 0.5) do_south_east_corner();
else do_north_west_corner();
}
static void bounce_south(void)
{
lvl.hero_pos.y = (int) lvl.hero_pos.y;
lvl.hero_vel.y = -lvl.bounce_vel.y;
}
static void bounce_north(void)
{
lvl.hero_pos.y = (int) lvl.hero_pos.y + 1;
lvl.hero_vel.y = lvl.bounce_vel.y;
}
static void bounce_west(void)
{
lvl.hero_pos.x = (int) lvl.hero_pos.x ;
lvl.hero_vel.x = -lvl.bounce_vel.x;
}
static void bounce_east(void)
{
lvl.hero_pos.x = (int) lvl.hero_pos.x + 1;
lvl.hero_vel.x = lvl.bounce_vel.x;
}
static float get_south_penetration(void)
{
return ((int) lvl.hero_pos.y + 1) - lvl.hero_pos.y;
}
static float get_north_penetration(void)
{
return lvl.hero_pos.y - (int) lvl.hero_pos.y;
}
static float get_west_penetration(void)
{
return ((int) lvl.hero_pos.x + 1) - lvl.hero_pos.x;
}
static float get_east_penetration(void)
{
return lvl.hero_pos.x - (int) lvl.hero_pos.x;
}
static void react_to_input(void)
{
if (key_pressed(lvl.key_west)) push_west();
if (key_pressed(lvl.key_east)) push_east();
if (key_pressed(lvl.key_exit)) game_over_string = "Aborted by user.";
}
static void push_west(void)
{
lvl.hero_vel.x += lvl.key_west_vel.x;
lvl.hero_vel.y += lvl.key_east_vel.y;
}
static void push_east(void)
{
lvl.hero_vel.x += lvl.key_east_vel.x;
lvl.hero_vel.y += lvl.key_east_vel.y;
}
static void apply_velocity(void)
{
if (lvl.hero_vel.x > lvl.term_vel.x) lvl.hero_vel.x = lvl.term_vel.x;
if (lvl.hero_vel.x <-lvl.term_vel.x) lvl.hero_vel.x =-lvl.term_vel.x;
if (lvl.hero_vel.y > lvl.term_vel.y) lvl.hero_vel.y = lvl.term_vel.y;
if (lvl.hero_vel.y <-lvl.term_vel.y) lvl.hero_vel.y =-lvl.term_vel.y;
lvl.hero_pos.x += lvl.hero_vel.x * elapsed_time;
lvl.hero_pos.y += lvl.hero_vel.y * elapsed_time;
}
level_t.h
#ifndef LEVEL_T_H
#define LEVEL_T_H
#include <stdbool.h>
typedef struct colour_t
{
float r, g, b;
} colour_t;
typedef struct vector_t
{
float x, y;
} vector_t;
typedef struct intmap_t
{
int w, h, *i;
} intmap_t;
typedef struct level_t
{
int win_w, win_h, win_fscr, key_west, key_east, key_exit;
float bass_frequency, bass_peak_volume, bass_duration;
colour_t bg_clr, fg_clr, hero_clr, dest_clr;
vector_t hero_pos, hero_vel, dest_pos, dest_sz;
vector_t grav_vel, term_vel, bounce_vel, key_west_vel, key_east_vel;
intmap_t bmp; // Bit map
} level_t;
extern level_t lvl;
bool init_level_t(int level);
void exit_level_t(void);
#endif
level_t.c
#include "level_t.h"
#include <stdlib.h>
#include <stdio.h>
#define LEVEL_ADDR_HEAD "levels/level"
#define LEVEL_ADDR_TAIL ".txt"
level_t lvl;
static char *mk_addr(int level)
{
static char buffer[100];
sprintf(buffer, LEVEL_ADDR_HEAD "%d" LEVEL_ADDR_TAIL, level);
return buffer;
}
bool init_level_t(int level)
{
FILE *f = fopen(mk_addr(level), "r");
if (f == NULL)
{
puts("Level does not exist.");
return false;
}
fscanf(f, "%d%d%d%d%d%d%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f"
"%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%d%d",
&lvl.win_w, &lvl.win_h, &lvl.win_fscr,
&lvl.key_west, &lvl.key_east, &lvl.key_exit,
&lvl.bass_frequency, &lvl.bass_peak_volume, &lvl.bass_duration,
&lvl.bg_clr.r, &lvl.bg_clr.g, &lvl.bg_clr.b,
&lvl.fg_clr.r, &lvl.fg_clr.g, &lvl.fg_clr.b,
&lvl.hero_clr.r, &lvl.hero_clr.g, &lvl.hero_clr.b,
&lvl.dest_clr.r, &lvl.dest_clr.g, &lvl.dest_clr.b,
&lvl.hero_pos.x, &lvl.hero_pos.y, &lvl.hero_vel.x, &lvl.hero_vel.y,
&lvl.dest_pos.x, &lvl.dest_pos.y, &lvl.dest_sz.x, &lvl.dest_sz.y,
&lvl.grav_vel.x, &lvl.grav_vel.y, &lvl.term_vel.x, &lvl.term_vel.y,
&lvl.bounce_vel.x, &lvl.bounce_vel.y,
&lvl.key_west_vel.x, &lvl.key_west_vel.y,
&lvl.key_east_vel.x, &lvl.key_east_vel.y,
&lvl.bmp.w, &lvl.bmp.h);
lvl.bmp.i = malloc(lvl.bmp.w * lvl.bmp.h * sizeof(*lvl.bmp.i));
for (int y = lvl.bmp.h - 1; y >= 0; --y)
{
for (int x = 0; x < lvl.bmp.w; ++x)
{
fscanf(f, "%d", &lvl.bmp.i[y * lvl.bmp.w + x]);
}
}
fclose(f);
return true;
}
void exit_level_t(void)
{
free(lvl.bmp.i);
}
level_renderer.h
#ifndef LEVEL_RENDERER_H
#define LEVEL_RENDERER_H
#include <stdbool.h>
void init_renderer(void);
void exit_renderer(void);
void render_level(void);
bool window_should_close(void);
void reset_timer(void);
float get_time(void);
bool key_pressed(int key);
#endif
level_renderer.c
#include "level_renderer.h"
#include "level_t.h"
#include "gutil.h"
#include <string.h>
#include <stdlib.h>
#define WIN_TITLE "Bounce"
#define INDEX_T GLubyte
#define INDEX_MACRO GL_UNSIGNED_BYTE
const char *VERT =
"#version 330 core\n"
"layout (location = 0) in vec2 pos;\n"
"void main(void) { gl_Position = vec4(pos, 0, 1); }\n";
const char *FRAG =
"#version 330 core\n"
"out vec4 colour;\n"
"uniform float r, g, b;\n"
"void main(void) { colour = vec4(r, g, b, 1); }\n";
typedef enum PRG_VARS
{
VAR_R,
VAR_G,
VAR_B,
N_VARS
} PRG_VARS;
typedef struct object_t
{
int n_indices, n_points;
GLuint indices, points;
} object_t;
typedef struct renderer_t
{
GLFWwindow *win;
GLuint vao, prg;
GLint locs[N_VARS];
object_t fg, hero, dest;
} renderer_t;
typedef struct rect_t
{
int x, y, w, h;
} rect_t;
typedef struct point_t
{
int x, y;
} point_t;
static renderer_t ren;
static void init_fg(void);
static rect_t expand_rect(int x, int y);
static bool is_row(rect_t rect);
static void delete_rect(rect_t rect, intmap_t dmp);
static void attach_rect(rect_t rect, intmap_t imp, INDEX_T**is, vector_t**pts);
static void attach_point(point_t p, intmap_t imp, vector_t **points);
static void attach_index(point_t p, intmap_t imp, INDEX_T **indices);
static void mk_vbos(object_t *obj, INDEX_T *is, vector_t *pts, GLenum usage);
static void init_hero(void);
static void init_dest(void);
static void exit_obj(object_t *obj);
static void draw_obj(object_t obj, GLenum mode, colour_t rgb);
static void adjust_hero_position(void);
void init_renderer(void)
{
const char *SRCS[] = {VERT, FRAG};
const GLenum TYPES[] = {GL_VERTEX_SHADER, GL_FRAGMENT_SHADER};
const char *NAMES[] = {"r", "g", "b"};
init_glfw();
ren.win = mk_win(lvl.win_w, lvl.win_h, WIN_TITLE, false, lvl.win_fscr);
ren.vao = mk_vao();
glEnableVertexAttribArray(0);
ren.prg = mk_prg(2, SRCS, TYPES, N_VARS, NAMES, ren.locs);
glClearColor(lvl.bg_clr.r, lvl.bg_clr.g, lvl.bg_clr.b, 1.0f);
init_fg();
init_hero();
init_dest();
}
static void init_fg(void)
{
size_t sz = lvl.bmp.w * lvl.bmp.h * sizeof(*lvl.bmp.i); // for deletion map
int imp_w = lvl.bmp.w + 1, imp_h = lvl.bmp.h + 1; // for indices map
intmap_t dmp = {lvl.bmp.w, lvl.bmp.h, memcpy(malloc(sz), lvl.bmp.i, sz)};
intmap_t imp = {imp_w, imp_h, calloc(imp_w * imp_h, sizeof(*imp.i))};
INDEX_T *indices = NULL;
vector_t *points = NULL;
for (int y = 0; y < lvl.bmp.h; ++y)
for (int x = 0; x < lvl.bmp.w; ++x)
if (dmp.i[y * lvl.bmp.w + x] == 1)
{
rect_t rect = expand_rect(x, y);
delete_rect(rect, dmp);
attach_rect(rect, imp, &indices, &points);
}
mk_vbos(&ren.fg, indices, points, GL_STATIC_DRAW);
free(indices);
free(points);
free(dmp.i);
free(imp.i);
}
static rect_t expand_rect(int x, int y)
{
rect_t rect = {x, y, 1, 1};
while (x + rect.w < lvl.bmp.w&&lvl.bmp.i[y*lvl.bmp.w+x+rect.w]==1)++rect.w;
while (y + rect.h < lvl.bmp.h && is_row(rect)) ++rect.h;
return rect;
}
static bool is_row(rect_t rect)
{
int base = (rect.y + rect.h) * lvl.bmp.w + rect.x;
for (int i = base; i < base + rect.w; ++i)if(lvl.bmp.i[i]!=1) return false;
return true;
}
static void delete_rect(rect_t rect, intmap_t dmp)
{
for (int y = rect.y; y < rect.y + rect.h; ++y)
for (int x = rect.x; x < rect.x + rect.w; ++x)
dmp.i[y * dmp.w + x] = 0;
}
static void attach_rect(rect_t rect, intmap_t imp, INDEX_T**is, vector_t**pts)
{
point_t p1 = {rect.x, rect.y};
point_t p2 = {rect.x + rect.w, rect.y};
point_t p3 = {rect.x + rect.w, rect.y + rect.h};
point_t p4 = {rect.x, rect.y + rect.h};
point_t points[] = {p1, p2, p3, p4};
point_t indices[] = {p1, p2, p3, p3, p4, p1};
for (int i = 0; i < 4; ++i) attach_point(points[i], imp, pts);
for (int i = 0; i < 6; ++i) attach_index(indices[i],imp, is);
}
static void attach_point(point_t p, intmap_t imp, vector_t **points)
{
if (imp.i[p.y * imp.w + p.x] == 0)
{
*points = realloc(*points, ++ren.fg.n_points * sizeof(**points));
(*points)[ren.fg.n_points - 1] = (vector_t) {p.x, p.y};
imp.i[p.y * imp.w + p.x] = ren.fg.n_points;
}
}
static void attach_index(point_t p, intmap_t imp, INDEX_T **indices)
{
*indices = realloc(*indices, ++ren.fg.n_indices * sizeof(**indices));
(*indices)[ren.fg.n_indices - 1] = imp.i[p.y * imp.w + p.x] - 1;
}
static void init_hero(void)
{
INDEX_T indices[] = {0, 1, 2, 2, 3, 0};
vector_t points[]={{0,0}, {1,0}, {1,1}, {0,1}};
ren.hero.n_indices = 6;
ren.hero.n_points = 4;
mk_vbos(&ren.hero, indices, points, GL_DYNAMIC_DRAW);
}
static void init_dest(void)
{
INDEX_T indices[] = {0, 1, 1, 2, 2, 3, 3, 0};
vector_t points[] = {
{lvl.dest_pos.x, lvl.dest_pos.y},
{lvl.dest_pos.x + lvl.dest_sz.x, lvl.dest_pos.y},
{lvl.dest_pos.x + lvl.dest_sz.x, lvl.dest_pos.y + lvl.dest_sz.y},
{lvl.dest_pos.x, lvl.dest_pos.y + lvl.dest_sz.y}};
ren.dest.n_indices = 8;
ren.dest.n_points = 4;
mk_vbos(&ren.dest, indices, points, GL_STATIC_DRAW);
}
static void mk_vbos(object_t *obj, INDEX_T *is, vector_t *pts, GLenum usage)
{
for (int i = 0; i < obj->n_points; ++i)
{
pts[i].x = (pts[i].x / lvl.bmp.w) * 2 - 1;
pts[i].y = (pts[i].y / lvl.bmp.h) * 2 - 1;
}
size_t sz_i = obj->n_indices * sizeof(*is);
size_t sz_p = obj->n_points * sizeof(*pts);
obj->indices = mk_vbo(GL_ELEMENT_ARRAY_BUFFER,sz_i,is,GL_STATIC_DRAW);
obj->points = mk_vbo(GL_ARRAY_BUFFER, sz_p, pts, usage);
}
void exit_renderer(void)
{
exit_obj(&ren.dest);
exit_obj(&ren.hero);
exit_obj(&ren.fg);
ren.prg = rm_prg(ren.prg);
glDisableVertexAttribArray(0);
ren.vao = rm_vao(ren.vao);
ren.win = rm_win(ren.win);
exit_glfw();
}
static void exit_obj(object_t *obj)
{
obj->n_indices = obj->n_points = 0;
obj->indices = rm_vbo(obj->indices);
obj->points = rm_vbo(obj->points);
}
void render_level(void)
{
glClear(GL_COLOR_BUFFER_BIT);
draw_obj(ren.fg, GL_TRIANGLES, lvl.fg_clr);
draw_obj(ren.dest, GL_LINES, lvl.dest_clr);
adjust_hero_position();
draw_obj(ren.hero,GL_TRIANGLES, lvl.hero_clr);
glfwSwapBuffers(ren.win);
}
static void draw_obj(object_t obj, GLenum mode, colour_t rgb)
{
glUniform1f(ren.locs[0], rgb.r);
glUniform1f(ren.locs[1], rgb.g);
glUniform1f(ren.locs[2], rgb.b);
glBindBuffer(GL_ARRAY_BUFFER, obj.points);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, obj.indices);
glDrawElements(mode, obj.n_indices, INDEX_MACRO, 0);
}
static void adjust_hero_position(void)
{
vector_t points[] = {{lvl.hero_pos.x, lvl.hero_pos.y},
{lvl.hero_pos.x + 1, lvl.hero_pos.y},
{lvl.hero_pos.x + 1, lvl.hero_pos.y + 1},
{lvl.hero_pos.x, lvl.hero_pos.y + 1}};
for (int i = 0; i < 4; ++i)
{
points[i].x = (points[i].x / lvl.bmp.w) * 2 - 1;
points[i].y = (points[i].y / lvl.bmp.h) * 2 - 1;
}
glBindBuffer(GL_ARRAY_BUFFER, ren.hero.points);
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_DYNAMIC_DRAW);
}
bool window_should_close(void)
{
glfwPollEvents();
return glfwWindowShouldClose(ren.win);
}
void reset_timer(void)
{
glfwSetTime(0);
}
float get_time(void)
{
return glfwGetTime();
}
bool key_pressed(int key)
{
return glfwGetKey(ren.win, key);
}
gutil.h
#ifndef GUTIL_H
#define GUTIL_H
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <stdbool.h>
#define GUTIL_CONTEXT_VERSION_MAJOR 3
#define GUTIL_CONTEXT_VERSION_MINOR 3
#define GUTIL_OPENGL_FORWARD_COMPAT GL_TRUE
#define GUTIL_OPENGL_PROFILE GLFW_OPENGL_CORE_PROFILE
#define GUTIL_GLEW_EXPERIMENTAL GL_TRUE
void init_glfw(void);
void exit_glfw(void);
GLFWwindow *mk_win(int w, int h, const char *title, bool rsz, bool fscr);
GLFWwindow *rm_win(GLFWwindow *win);
GLuint mk_vao(void);
GLuint rm_vao(GLuint vao);
GLuint mk_prg(int n_shds, const char **srcs, const GLenum *types,
int n_vars, const char **names, GLint *locs);
GLuint rm_prg(GLuint prg);
GLuint mk_vbo(GLenum type, size_t sz, void *data, GLenum usage);
GLuint rm_vbo(GLuint vbo);
#endif // GUTIL_H
gutil.c
#include "gutil.h"
#include <stdlib.h>
static GLuint mk_shd(const char *src, GLenum type);
static GLuint rm_shd(GLuint shd);
void init_glfw(void)
{
glfwInit();
}
void exit_glfw(void)
{
glfwTerminate();
}
GLFWwindow *mk_win(int w, int h, const char *title, bool rsz, bool fscr)
{
glfwWindowHint(GLFW_RESIZABLE, rsz);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GUTIL_CONTEXT_VERSION_MAJOR);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, GUTIL_CONTEXT_VERSION_MINOR);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GUTIL_OPENGL_FORWARD_COMPAT);
glfwWindowHint(GLFW_OPENGL_PROFILE, GUTIL_OPENGL_PROFILE);
glewExperimental = GUTIL_GLEW_EXPERIMENTAL;
GLFWmonitor *mon = fscr ? glfwGetPrimaryMonitor() : NULL;
GLFWwindow *win = glfwCreateWindow(w, h, title, mon, NULL);
glfwMakeContextCurrent(win);
glewInit();
return win;
}
GLFWwindow *rm_win(GLFWwindow *win)
{
glfwDestroyWindow(win);
return NULL;
}
GLuint mk_vao(void)
{
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
return vao;
}
GLuint rm_vao(GLuint vao)
{
glDeleteVertexArrays(1, &vao);
return 0;
}
GLuint mk_prg(int n_shds, const char **srcs, const GLenum *types,
int n_vars, const char **names, GLint *locs)
{
GLuint prg = glCreateProgram();
GLuint *shds = malloc(n_shds * sizeof(*shds));
for (int i = 0; i < n_shds; ++i) shds[i] = mk_shd(srcs[i], types[i]);
for (int i = 0; i < n_shds; ++i) glAttachShader(prg, shds[i]);
glLinkProgram(prg);
for (int i = 0; i < n_shds; ++i) glDetachShader(prg, shds[i]);
glValidateProgram(prg);
glUseProgram(prg);
for (int i = 0; i < n_vars; ++i)locs[i]=glGetUniformLocation(prg,names[i]);
for (int i = 0; i < n_shds; ++i) shds[i] = rm_shd(shds[i]);
free(shds);
return prg;
}
GLuint rm_prg(GLuint prg)
{
glDeleteProgram(prg);
return 0;
}
static GLuint mk_shd(const char *src, GLenum type)
{
GLuint shd = glCreateShader(type);
glShaderSource(shd, 1, &src, NULL);
glCompileShader(shd);
return shd;
}
static GLuint rm_shd(GLuint shd)
{
glDeleteShader(shd);
return 0;
}
GLuint mk_vbo(GLenum type, size_t sz, void *data, GLenum usage)
{
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(type, vbo);
glBufferData(type, sz, data, usage);
return vbo;
}
GLuint rm_vbo(GLuint vbo)
{
glDeleteBuffers(1, &vbo);
return 0;
}
bass.h
#ifndef BASS_H
#define BASS_H
#define BASS_SAMPLE_RATE 44100
#define BASS_BUFFER_SIZE 256
void init_bass(float frequency, float volume, float duration);
void exit_bass(void);
void drop_bass(void);
#endif // BASS_H
bass.c
#include "bass.h"
#include <portaudio/portaudio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
PaStream *stream = NULL;
float *table = NULL;
bool playing = false;
int n_buffers = 0;
int buffer_index = 0;
static void init_pa(void);
static void init_table(float frequency, float volume, float duration);
static int cb(const void *in, void *out, unsigned long fpb,
const PaStreamCallbackTimeInfo *time_info,
PaStreamCallbackFlags status, void *p);
void init_bass(float frequency, float volume, float duration)
{
init_pa();
init_table(frequency, volume, duration);
Pa_OpenDefaultStream(&stream, 0, 2, paFloat32,
BASS_SAMPLE_RATE, BASS_BUFFER_SIZE, cb, NULL);
Pa_StartStream(stream);
}
static void init_pa(void)
{
FILE *orig_stderr; // ALSA puts garbage. I don't know to portably shut it.
orig_stderr = stderr;
stderr = tmpfile();
Pa_Initialize();
fflush(stderr);
fclose(stderr);
stderr = orig_stderr;
}
static void init_table(float frequency, float volume, float duration)
{
int actual_size = duration * BASS_SAMPLE_RATE;
n_buffers = actual_size / BASS_BUFFER_SIZE + 1;
int n_samples = n_buffers * BASS_BUFFER_SIZE;
table = malloc(n_samples * sizeof(*table));
float radians = (M_PI * 2 * frequency * duration) / actual_size;
for (int i = 0; i < actual_size; ++i)
{
float one_to_zero = 1 - (float) i / actual_size;
table[i] = sin(i * radians) * volume * one_to_zero;
}
for (int i = actual_size; i < n_samples; ++i) table[i] = 0;
}
void exit_bass(void)
{
Pa_StopStream(stream);
Pa_CloseStream(stream);
Pa_Terminate();
free(table);
table = NULL;
playing = false;
}
void drop_bass(void)
{
playing = true;
buffer_index = 0;
}
static int cb(const void *in, void *out, unsigned long fpb,
const PaStreamCallbackTimeInfo *time_info,
PaStreamCallbackFlags status, void *p)
{
(void) in;
(void) fpb;
(void) time_info;
(void) status;
(void) p;
float *output = out;
if (playing)
{
int base_index = buffer_index * BASS_BUFFER_SIZE;
for (int i = 0; i < BASS_BUFFER_SIZE; ++i)
{
output[i * 2 + 0] = table[base_index + i];
output[i * 2 + 1] = table[base_index + i];
}
++buffer_index;
if (buffer_index >= n_buffers) playing = false;
}
else for (int i = 0; i < BASS_BUFFER_SIZE * 2; ++i) output[i] = 0;
return paContinue;
}
Makefile (first time, I usually build it with Code::Blocks IDE)
install:
gcc -c *.c
gcc *.o -lm -lGL -lGLEW -lglfw -lportaudio -o bounce
rm -f *.o
uninstall:
rm -f bounce
1 Answer 1
I'd like to make some suggestions about the Makefile.
Make knows about .c and .o files
It even has an implicit rule to compile any .c
files for which the corresponding .o
file is needed. So a common thing to do is make a variable of all the objects that need to be in the final build line. You can list them individually or use make's special powers (I used the GNU manual, so some of these may be GNU-specific.)
SOURCES= $(notdir $(wildcard ./*.c))
OBJECTS= $(patsubst %.c,%.o,$(SOURCES))
Then you can use the variable in a dependencies line or a command. Probably both.
all:bounce
bounce:$(OBJECTS)
$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS)
The special variable $@
expands to the target of the production, here bounce
. And the special variable $^
expands to the dependencies from the production, which usually are on the line directly above, hence the caret as an up-arrow.
make
is for making, make install
is for installing
Notice I used all
as the master (first) target which triggers all others. I believe this is a common thing to do. This way, make all
== make
.
Installing is usually considered a distinct thing from compiling. When you install a program, you make a home directory for it in some appropriate place in the file-system; and copy the program to its installed location; and copy any other related files to their appropriate places (libraries, .pc files (which declare what other external libraries are needed), manpages, fonts?, other data files).
So I would suggest you not use the word install
at all as a target unless it's doing something permanent to make the program accessible. And adding an uninstall
target is a very worthwhile companion. (Which you did, but again not implementing the expected behavior of removing the program from some installed location.)
The rest of the program is very impressive!
-
\$\begingroup\$ Thanks! I'll make a much better makefile for the next game of mine. \$\endgroup\$Daniel Sunday– Daniel Sunday2016年06月04日 06:06:34 +00:00Commented Jun 4, 2016 at 6:06
Makefile
is pretty fragile. I actually made a pull request that makes it "better". I don't really feel like posting an answer on it though because I'm not comfortable at all writing Makefiles, and although everything appears to work, there is a good chance I'm doing one bad thing for every good thing I do. Also, it's the only thing I've looked at so far. \$\endgroup\$