diff --git a/Source/Game.hpp b/Source/Game.hpp index 8c51445..b60b884 100644 --- a/Source/Game.hpp +++ b/Source/Game.hpp @@ -182,6 +182,7 @@ static __forceinline void swap_gl_buffers(void) extern SDL_Window* sdlwindow; SDL_GL_SwapWindow(sdlwindow); +#ifndef __EMSCRIPTEN__ // in the browser the animation frame sets the pace // try to limit this to 60fps, even if vsync fails. Uint32 now; static Uint32 frameticks = 0; @@ -189,6 +190,7 @@ static __forceinline void swap_gl_buffers(void) while ((now = SDL_GetTicks()) < endticks) { /* spin. */ } frameticks = now; +#endif } enum maptypes diff --git a/Source/Graphic/Text.cpp b/Source/Graphic/Text.cpp index b7ec4e5..488e0f1 100644 --- a/Source/Graphic/Text.cpp +++ b/Source/Graphic/Text.cpp @@ -29,53 +29,11 @@ void Text::LoadFontTexture(const std::string& fileName) LOG(std::string("Loading font texture...") + fileName); FontTexture.load(fileName, false); - if (base) { - glDeleteLists(base, 512); - base = 0; - } } -void Text::BuildFont() // Build Our Font Display List +void Text::BuildFont() // Display lists do not exist in the browser's OpenGL: the characters are drawn directly { - float cx; // Holds Our X Character Coord - float cy; // Holds Our Y Character Coord - int loop; - LOGFUNC; - - if (base) { - glDeleteLists(base, 512); - base = 0; - } - - base = glGenLists(512); // Creating 256 Display Lists - FontTexture.bind(); - for (loop = 0; loop < 512; loop++) { // Loop Through All 256 Lists - if (loop < 256) { - cx = float(loop % 16) / 16.0f; // X Position Of Current Character - cy = float(loop / 16) / 16.0f; // Y Position Of Current Character - } else { - cx = float((loop - 256) % 16) / 16.0f; // X Position Of Current Character - cy = float((loop - 256) / 16) / 16.0f; // Y Position Of Current Character - } - glNewList(base + loop, GL_COMPILE); // Start Building A List - glBegin(GL_QUADS); // Use A Quad For Each Character - glTexCoord2f(cx, 1 - cy - 0.0625f + .001); // Texture Coord (Bottom Left) - glVertex2i(0, 0); // Vertex Coord (Bottom Left) - glTexCoord2f(cx + 0.0625f, 1 - cy - 0.0625f + .001); // Texture Coord (Bottom Right) - glVertex2i(16, 0); // Vertex Coord (Bottom Right) - glTexCoord2f(cx + 0.0625f, 1 - cy - .001); // Texture Coord (Top Right) - glVertex2i(16, 16); // Vertex Coord (Top Right) - glTexCoord2f(cx, 1 - cy - +.001); // Texture Coord (Top Left) - glVertex2i(0, 16); // Vertex Coord (Top Left) - glEnd(); // Done Building Our Quad (Character) - if (loop < 256) { - glTranslated(10, 0, 0); // Move To The Right Of The Character - } else { - glTranslated(8, 0, 0); // Move To The Right Of The Character - } - glEndList(); // Done Building The Display List - } // Loop Until All 256 Are Built } void Text::_glPrint(float x, float y, const std::string& string, int set, float size, float width, float height, int start, int end, int offset) // Where The Printing Happens @@ -98,8 +56,28 @@ void Text::_glPrint(float x, float y, const std::string& string, int set, float glLoadIdentity(); glTranslated(x, y, 0); glScalef(size, size, 1); - glListBase(base - 32 + (128 * set) + offset); // Choose The Font Set (0 or 1) - glCallLists(end - start, GL_BYTE, &string[start]); // Write The Text To The Screen + // Write The Text To The Screen: one textured quad per character (set 0 or 1, plain or outlined) + glBegin(GL_QUADS); + float px = 0; + for (int i = start; i < end; i++) { + const int loop = (signed char)string[i] - 32 + (128 * set) + offset; + if (loop < 0 || loop >= 512) { + continue; + } + const int l = loop < 256 ? loop : loop - 256; + const float cx = float(l % 16) / 16.0f; + const float cy = float(l / 16) / 16.0f; + glTexCoord2f(cx, 1 - cy - 0.0625f + .001); + glVertex2i(px, 0); + glTexCoord2f(cx + 0.0625f, 1 - cy - 0.0625f + .001); + glVertex2i(px + 16, 0); + glTexCoord2f(cx + 0.0625f, 1 - cy - .001); + glVertex2i(px + 16, 16); + glTexCoord2f(cx, 1 - cy - +.001); + glVertex2i(px, 16); + px += loop < 256 ? 10 : 8; + } + glEnd(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); @@ -144,8 +122,4 @@ Text::Text() Text::~Text() { - if (base) { - glDeleteLists(base, 512); - base = 0; - } } diff --git a/Source/Graphic/gamegl.hpp b/Source/Graphic/gamegl.hpp index b824a43..6e66004 100644 --- a/Source/Graphic/gamegl.hpp +++ b/Source/Graphic/gamegl.hpp @@ -48,6 +48,10 @@ along with Lugaru. If not, see . #include #endif +#ifdef __EMSCRIPTEN__ +#include "Graphic/lugaru-gl.h" +#endif + using namespace std; /* !!! FIXME: until we replace logger better. --ryan. */ diff --git a/Source/Graphic/lugaru-gl.h b/Source/Graphic/lugaru-gl.h new file mode 100644 index 0000000..377a7f6 --- /dev/null +++ b/Source/Graphic/lugaru-gl.h @@ -0,0 +1,283 @@ +/* + * lugaru-gl.h: what the WebAssembly build of Lugaru (fredygames.com) needs on top of the OpenGL + * emulation of Emscripten (-sLEGACY_GL_EMULATION), which maps fixed-function OpenGL 1.x onto WebGL. + * + * - glBegin/glEnd: the emulation stores the attributes of every vertex in the order the calls come and + * expects all vertices to carry the same ones, while desktop OpenGL keeps a current color, texture + * coordinate and normal. The vertices are collected here with the current attributes and handed over + * complete at glEnd. A color set inside glBegin/glEnd stays current afterwards, as in OpenGL. + * - glInterleavedArrays, for the two formats the game uses, as separate array pointers. + * - Textures: WebGL accepts neither GL_CLAMP, nor GL_GENERATE_MIPMAP, nor numeric internal formats + * different from the format; glTexImage2D is wrapped to translate them (mipmaps are generated after + * the upload when the texture asked for them). + * - Calls with no WebGL equivalent (glPixelTransfer, glDrawBuffer, glReadBuffer, the texture LOD bias + * and GL_COMBINE texture environments, glHint) are dropped. + * + * Included at the end of gamegl.hpp, only when building with Emscripten. + * + * Copyright (C) 2026 FredyGames. Part of the Lugaru build for fredygames.com; distributed with Lugaru + * under the GNU General Public License, version 2 or (at your option) any later version. + */ +#ifndef LUGARU_GL_H +#define LUGARU_GL_H + +#include +#include +#include +#include + +/*===========================================================================*/ +// Immediate mode: + +struct lg_vertex { + GLfloat color[4], tc[2], normal[3], pos[3]; +}; + +static GLfloat lg_color[4] = { 1, 1, 1, 1 }; +static GLfloat lg_tc[2] = { 0, 0 }; +static GLfloat lg_normal[3] = { 0, 0, 1 }; +static GLenum lg_mode; +static int lg_in_begin = 0, lg_used_color = 0, lg_used_tc = 0, lg_used_normal = 0; +static lg_vertex* lg_vertices = NULL; +static int lg_count = 0, lg_capacity = 0; + +static void lg_glBegin(GLenum mode) +{ + lg_mode = mode; + lg_in_begin = 1; + lg_used_color = lg_used_tc = lg_used_normal = 0; + lg_count = 0; +} + +static void lg_glColor4f(GLfloat r, GLfloat g, GLfloat b, GLfloat a) +{ + lg_color[0] = r; + lg_color[1] = g; + lg_color[2] = b; + lg_color[3] = a; + if (lg_in_begin) { + lg_used_color = 1; + } else { + glColor4f(r, g, b, a); + } +} + +static void lg_glColor3f(GLfloat r, GLfloat g, GLfloat b) +{ + lg_glColor4f(r, g, b, 1.0f); +} + +static void lg_glTexCoord2f(GLfloat s, GLfloat t) +{ + lg_tc[0] = s; + lg_tc[1] = t; + if (lg_in_begin) { + lg_used_tc = 1; + } +} + +static void lg_glNormal3f(GLfloat x, GLfloat y, GLfloat z) +{ + lg_normal[0] = x; + lg_normal[1] = y; + lg_normal[2] = z; + if (lg_in_begin) { + lg_used_normal = 1; + } else { + glNormal3f(x, y, z); + } +} + +static void lg_glVertex3f(GLfloat x, GLfloat y, GLfloat z) +{ + if (!lg_in_begin) { + return; + } + if (lg_count == lg_capacity) { + lg_capacity = lg_capacity ? lg_capacity * 2 : 256; + lg_vertices = (lg_vertex*)realloc(lg_vertices, lg_capacity * sizeof(lg_vertex)); + } + lg_vertex* v = &lg_vertices[lg_count++]; + memcpy(v->color, lg_color, sizeof(lg_color)); + memcpy(v->tc, lg_tc, sizeof(lg_tc)); + memcpy(v->normal, lg_normal, sizeof(lg_normal)); + v->pos[0] = x; + v->pos[1] = y; + v->pos[2] = z; +} + +static void lg_glVertex2i(GLint x, GLint y) +{ + lg_glVertex3f((GLfloat)x, (GLfloat)y, 0.0f); +} + +static void lg_glEnd(void) +{ + if (!lg_in_begin) { + return; + } + lg_in_begin = 0; + if (lg_count == 0) { + return; + } + glBegin(lg_mode); + for (int i = 0; i < lg_count; ++i) { + const lg_vertex* v = &lg_vertices[i]; + if (lg_used_color) { + glColor4f(v->color[0], v->color[1], v->color[2], v->color[3]); + } + if (lg_used_tc) { + glTexCoord2f(v->tc[0], v->tc[1]); + } + if (lg_used_normal) { + glNormal3f(v->normal[0], v->normal[1], v->normal[2]); + } + glVertex3f(v->pos[0], v->pos[1], v->pos[2]); + } + glEnd(); + if (lg_used_color) { + glColor4f(lg_color[0], lg_color[1], lg_color[2], lg_color[3]); + } +} + +/*===========================================================================*/ +// Vertex arrays: + +// Only GL_T2F_C3F_V3F and GL_T2F_N3F_V3F, the two formats the game uses. +static void lg_glInterleavedArrays(GLenum format, GLsizei stride, const void* pointer) +{ + const GLfloat* p = (const GLfloat*)pointer; + glTexCoordPointer(2, GL_FLOAT, stride, p); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + if (format == GL_T2F_C3F_V3F) { + glColorPointer(3, GL_FLOAT, stride, p + 2); + glEnableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glVertexPointer(3, GL_FLOAT, stride, p + 5); + } else { + glNormalPointer(GL_FLOAT, stride, p + 2); + glEnableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glVertexPointer(3, GL_FLOAT, stride, p + 5); + } + glEnableClientState(GL_VERTEX_ARRAY); +} + +/*===========================================================================*/ +// Textures: + +#ifndef GL_CLAMP +#define GL_CLAMP 0x2900 +#endif +#ifndef GL_GENERATE_MIPMAP +#define GL_GENERATE_MIPMAP 0x8191 +#endif + +// WebGL 1 samples a texture whose sides are not powers of two as black unless it has no mipmaps and is +// clamped, so those textures (the 40x40 snowflake, for one) are forced to GL_LINEAR and GL_CLAMP_TO_EDGE. +#define LG_TEXTURES 4096 +static unsigned char lg_wants_mipmaps[LG_TEXTURES]; +static unsigned char lg_npot[LG_TEXTURES]; +static GLuint lg_bound = 0; + +static void lg_glBindTexture(GLenum target, GLuint texture) +{ + glBindTexture(target, texture); + if (target == GL_TEXTURE_2D) { + lg_bound = texture; + } +} + +static void lg_glTexParameteri(GLenum target, GLenum pname, GLint param) +{ + if (pname == GL_GENERATE_MIPMAP) { + if (lg_bound < LG_TEXTURES) { + lg_wants_mipmaps[lg_bound] = param ? 1 : 0; + } + return; + } + if ((pname == GL_TEXTURE_WRAP_S || pname == GL_TEXTURE_WRAP_T) && param == GL_CLAMP) { + param = GL_CLAMP_TO_EDGE; + } + if (target == GL_TEXTURE_2D && lg_bound < LG_TEXTURES && lg_npot[lg_bound]) { + if (pname == GL_TEXTURE_MIN_FILTER) { + param = GL_LINEAR; + } else if (pname == GL_TEXTURE_WRAP_S || pname == GL_TEXTURE_WRAP_T) { + param = GL_CLAMP_TO_EDGE; + } + } + glTexParameteri(target, pname, param); +} + +static void lg_glTexParameterf(GLenum target, GLenum pname, GLfloat param) +{ + lg_glTexParameteri(target, pname, (GLint)param); +} + +static void lg_glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, + GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels) +{ + // WebGL wants the internal format equal to the format + glTexImage2D(target, level, (GLint)format, width, height, border, format, type, pixels); + if (target == GL_TEXTURE_2D && level == 0 && lg_bound < LG_TEXTURES) { + lg_npot[lg_bound] = ((width & (width - 1)) || (height & (height - 1))) ? 1 : 0; + if (lg_npot[lg_bound]) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } else if (lg_wants_mipmaps[lg_bound] && pixels != NULL) { + glGenerateMipmap(GL_TEXTURE_2D); + } + } +} + +// The emulation implements only the modes of the fixed pipeline that WebGL shaders can reproduce +static void lg_glTexEnvi(GLenum target, GLenum pname, GLint param) +{ + if (target != GL_TEXTURE_ENV || pname != GL_TEXTURE_ENV_MODE) { + return; + } + if (param != GL_MODULATE && param != GL_REPLACE && param != GL_DECAL && param != GL_BLEND) { + param = GL_MODULATE; + } + glTexEnvi(target, pname, param); +} + +static void lg_glTexEnvf(GLenum target, GLenum pname, GLfloat param) +{ + lg_glTexEnvi(target, pname, (GLint)param); +} + +static void lg_glTexEnvfv(GLenum, GLenum, const GLfloat*) { } +static void lg_glPixelTransferi(GLenum, GLint) { } +static void lg_glDrawBuffer(GLenum) { } +static void lg_glReadBuffer(GLenum) { } +static void lg_glHint(GLenum, GLenum) { } +static void lg_glTranslated(GLdouble x, GLdouble y, GLdouble z) +{ + glTranslatef((GLfloat)x, (GLfloat)y, (GLfloat)z); +} + +#define glBegin lg_glBegin +#define glEnd lg_glEnd +#define glColor4f lg_glColor4f +#define glColor3f lg_glColor3f +#define glTexCoord2f lg_glTexCoord2f +#define glNormal3f lg_glNormal3f +#define glVertex3f lg_glVertex3f +#define glVertex2i lg_glVertex2i +#define glInterleavedArrays lg_glInterleavedArrays +#define glBindTexture lg_glBindTexture +#define glTexParameteri lg_glTexParameteri +#define glTexParameterf lg_glTexParameterf +#define glTexImage2D lg_glTexImage2D +#define glTexEnvi lg_glTexEnvi +#define glTexEnvf lg_glTexEnvf +#define glTexEnvfv lg_glTexEnvfv +#define glPixelTransferi lg_glPixelTransferi +#define glDrawBuffer lg_glDrawBuffer +#define glReadBuffer lg_glReadBuffer +#define glHint lg_glHint +#define glTranslated lg_glTranslated + +#endif diff --git a/Source/Menu/Menu.cpp b/Source/Menu/Menu.cpp index 253b764..b41354e 100644 --- a/Source/Menu/Menu.cpp +++ b/Source/Menu/Menu.cpp @@ -381,12 +381,20 @@ void Menu::Load() addImage(0, Mainmenuitems[0], 150, 480 - 128, 256, 128); addButtonImage(1, Mainmenuitems[mainmenu == 1 ? 1 : 5], 18, 480 - 152 - 32, 128, 32); addButtonImage(2, Mainmenuitems[2], 18, 480 - 228 - 32, 112, 32); +#ifdef __EMSCRIPTEN__ // nothing to quit to in a browser tab; "End game" stays + if (mainmenu != 1) { +#endif addButtonImage(3, Mainmenuitems[mainmenu == 1 ? 3 : 6], 18, 480 - 306 - 32, mainmenu == 1 ? 68 : 132, 32); +#ifdef __EMSCRIPTEN__ + } +#endif addLabel(-1, VERSION_NUMBER + VERSION_SUFFIX, 640 - 100, 10); break; case 3: +#ifndef __EMSCRIPTEN__ // the canvas has a fixed size and the page has the fullscreen button addButton(0, "", 10 + 20, 440); addButton(14, "", 10 + 400, 440); +#endif addButton(1, "", 10 + 60, 405); addButton(2, "", 10 + 70, 370); addButton(4, "", 10, 335); @@ -397,7 +405,9 @@ void Menu::Load() addButton(11, "", 10 + 60, 160); addButton(13, "", 30, 125); addButton(7, "-Configure Controls-", 10 + 15, 90); +#ifndef __EMSCRIPTEN__ // anaglyph and side by side 3D modes need more than the browser's OpenGL offers addButton(12, "-Configure Stereo -", 10 + 15, 55); +#endif addButton(8, "Back", 10, 10); updateSettingsMenu(); break; @@ -951,23 +961,26 @@ void Menu::Tick() oldmainmenu = mainmenu; } -int setKeySelected_thread(void*) +// Called by the main loop for each event while the game waits for the key to assign (it was a thread +// blocked in SDL_WaitEvent, which the browser has no room for). Returns true once the key is chosen. +bool menuCapturingKey = false; + +bool Menu::keySelectedEvent(const SDL_Event& evenement) { using namespace Game; int scancode = -1; - SDL_Event evenement; - while (scancode == -1) { - SDL_WaitEvent(&evenement); - switch (evenement.type) { - case SDL_KEYDOWN: - scancode = evenement.key.keysym.scancode; - break; - case SDL_MOUSEBUTTONDOWN: - scancode = SDL_NUM_SCANCODES + evenement.button.button; - break; - default: - break; - } + switch (evenement.type) { + case SDL_KEYDOWN: + scancode = evenement.key.keysym.scancode; + break; + case SDL_MOUSEBUTTONDOWN: + scancode = SDL_NUM_SCANCODES + evenement.button.button; + break; + default: + break; + } + if (scancode == -1) { + return false; } if (scancode != SDL_SCANCODE_ESCAPE) { fireSound(); @@ -1008,18 +1021,13 @@ int setKeySelected_thread(void*) } keyselect = -1; waiting = false; + menuCapturingKey = false; Menu::Load(); - return 0; + return true; } void Menu::setKeySelected() { waiting = true; - printf("launch thread\n"); - SDL_Thread* thread = SDL_CreateThread(setKeySelected_thread, NULL, NULL); - if (thread == NULL) { - fprintf(stderr, "Unable to create thread: %s\n", SDL_GetError()); - waiting = false; - return; - } + menuCapturingKey = true; } diff --git a/Source/Menu/Menu.hpp b/Source/Menu/Menu.hpp index a3ce2fe..3f121a5 100644 --- a/Source/Menu/Menu.hpp +++ b/Source/Menu/Menu.hpp @@ -23,6 +23,8 @@ along with Lugaru. If not, see . #include "Game.hpp" +extern bool menuCapturingKey; + struct MenuItem { enum MenuItemType @@ -73,6 +75,7 @@ public: static void updateStereoConfigMenu(); static void updateControlsMenu(); static void setKeySelected(); + static bool keySelectedEvent(const SDL_Event& e); static void startChallengeLevel(int selected); private: diff --git a/Source/main.cpp b/Source/main.cpp index 4e94243..3d176c3 100644 --- a/Source/main.cpp +++ b/Source/main.cpp @@ -34,6 +34,10 @@ along with Lugaru. If not, see . #include #include #include +#ifdef __EMSCRIPTEN__ +#include +#include +#endif using namespace Game; @@ -196,13 +200,26 @@ bool SetUp() SaveSettings(); } +#ifdef __EMSCRIPTEN__ + // The canvas has one fixed size, which the page scales to the window; no resolutions, no fullscreen + // of our own (the page has the button). + newscreenwidth = kContextWidth = 1024; + newscreenheight = kContextHeight = 768; + fullscreen = 0; +#endif + if (SDL_GL_LoadLibrary(NULL) == -1) { fprintf(stderr, "SDL_GL_LoadLibrary() failed: %s\n", SDL_GetError()); SDL_Quit(); return false; } +#ifdef __EMSCRIPTEN__ + resolutions.insert(pair(kContextWidth, kContextHeight)); + for (int displayIdx = 0; displayIdx < 0; ++displayIdx) { +#else for (int displayIdx = 0; displayIdx < SDL_GetNumVideoDisplays(); ++displayIdx) { +#endif for (int i = 0; i < SDL_GetNumDisplayModes(displayIdx); ++i) { SDL_DisplayMode mode; if (SDL_GetDisplayMode(displayIdx, i, &mode) == -1) { @@ -322,6 +339,17 @@ static void DoMouse() { if (mainmenu || ((abs(deltah) < 10 * realmultiplier * 1000) && (abs(deltav) < 10 * realmultiplier * 1000))) { +#ifdef __EMSCRIPTEN__ + if (mainmenu) { + // The page scales the canvas to the window: in the menus the cursor must follow the mouse + // on the screen, so the motion (in screen pixels) is converted to canvas pixels. + double cssWidth = 0, cssHeight = 0; + if (emscripten_get_element_css_size("#canvas", &cssWidth, &cssHeight) == 0 && cssWidth > 0) { + deltah *= kContextWidth / cssWidth; + deltav *= kContextHeight / cssHeight; + } + } +#endif deltah *= usermousesensitivity; deltav *= usermousesensitivity; mousecoordh += deltah; @@ -611,6 +639,41 @@ const option::Descriptor usage[] = option::Option commandLineOptions[commandLineOptionsNumber]; option::Option* commandLineOptionsBuffer; +#ifdef __EMSCRIPTEN__ +// One iteration of the game loop, run by the browser for each animation frame (the original loop +// blocked in SDL_WaitEvent when the window lost the focus, which the browser has no room for; here +// the game keeps its frame and the page decides when to call it). +static void browserFrame() +{ + try { + deltah = 0; + deltav = 0; + SDL_Event e; + bool done = false; + if (!waiting || menuCapturingKey) { + while (SDL_PollEvent(&e)) { + if (!sdlEventProc(e)) { + done = true; + break; + } + if (menuCapturingKey && Menu::keySelectedEvent(e)) { + break; + } + } + } + if (!done && !tryquit) { + DoUpdate(); + } + if (done || tryquit) { + emscripten_cancel_main_loop(); + } + } catch (const std::exception& error) { + LOG(std::string("Caught exception: ") + error.what()); + emscripten_cancel_main_loop(); + } +} +#endif + int main(int argc, char** argv) { argc -= (argc > 0); @@ -680,6 +743,22 @@ int main(int argc, char** argv) devtools = true; } +#ifdef __EMSCRIPTEN__ + srand(time(nullptr)); + + if (commandLineOptions[CMD].count() > 0) { + devtools = true; + Menu::startChallengeLevel(1); + for (option::Option* opt = commandLineOptions[CMD]; opt; opt = opt->next()) { + if (opt->arg && (strlen(opt->arg) > 0)) { + cmd_dispatch(opt->arg); + } + } + } + + // The browser calls one frame at a time; main never returns + emscripten_set_main_loop(browserFrame, 0, 1); +#else bool gameDone = false; bool gameFocused = true; @@ -728,6 +807,7 @@ int main(int argc, char** argv) } } +#endif deleteGame(); }