1 /*
2  * Kuklomenos
3  * Copyright (C) 2008-2009 Martin Bays <mbays@sdf.lonestar.org>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/.
17  */
18 
19 #include <config.h>
20 
21 #ifdef HAVE_LIBCURL
22 #define HIGH_SCORE_REPORTING 1
23 #endif
24 
25 #include <cstdlib>
26 #include <cstdio>
27 #include <ctime>
28 #include <stack>
29 #include <SDL/SDL.h>
30 #include <SDL_gfxPrimitivesDirty.h>
31 #include <string>
32 #include <sstream>
33 
34 #include "state.h"
35 #include "geom.h"
36 #include "clock.h"
37 #include "overlay.h"
38 #include "data.h"
39 #include "settings.h"
40 #include "conffile.h"
41 #include "random.h"
42 #include "ai.h"
43 #include "menu.h"
44 #include "keybindings.h"
45 #include "sound.h"
46 #include "background.h"
47 
48 #ifdef HIGH_SCORE_REPORTING
49 # include "highScore.h"
50 #endif
51 
52 #ifndef __APPLE__
53 # include "config.h"
54 #endif
55 
56 
57 SDL_Surface* screen = NULL;
58 std::stack<Menu*> menuStack;
59 
60 enum EventsReturn
61 {
62     ER_NONE,
63     ER_MISC,
64     ER_QUIT,
65     ER_SURRENDER,
66     ER_RESTART,
67     ER_SCREENSHOT,
68     ER_NEWBACKGROUND,
69     ER_NOTIMETAKEN,
70     ER_MENU
71 };
72 
setVideoMode()73 bool setVideoMode()
74 {
75     SDL_Surface* ret = SDL_SetVideoMode(settings.width, settings.height,
76 	    settings.bpp, settings.videoFlags);
77 
78     if (!ret)
79     {
80 	settings.width = screen->w;
81 	settings.height = screen->h;
82 	settings.bpp = screen->format->BitsPerPixel;
83 	return false;
84     }
85 
86     screen = ret;
87     screenGeom = ScreenGeom(settings.width, settings.height);
88 
89     setBackground(screen);
90     setDirty(screen, background);
91 
92     return true;
93 }
94 
handleCommand(command c,GameState * gameState,GameClock & gameClock)95 EventsReturn handleCommand(command c, GameState* gameState, GameClock& gameClock)
96 {
97     switch (c)
98     {
99 	case C_QUIT:
100 	    if (menuStack.empty())
101 		return ER_QUIT;
102 	    else
103 		while (!menuStack.empty())
104 		    menuStack.pop();
105 	    break;
106 	case C_PAUSE:
107 	    gameClock.paused = !gameClock.paused;
108 	    break;
109 	case C_MENU:
110 	    if (menuStack.empty())
111 		menuStack.push(&topMenu);
112 	    else
113 		menuStack.pop();
114 	    break;
115 	case C_STARTGAME:
116 	    if (!menuStack.empty())
117 		menuStack.pop();
118 	    else if (gameState->end || gameState->ai)
119 		return ER_RESTART;
120 	    break;
121 #ifdef SOUND
122 	case C_SOUND:
123 	    settings.sound = !settings.sound;
124 	    break;
125 #endif
126 	case C_ZOOM:
127 	    settings.zoomEnabled = !settings.zoomEnabled;
128 	    break;
129 	case C_ROTATE:
130 	    settings.rotatingView = !settings.rotatingView;
131 	    break;
132 	case C_INCAA:
133 	    settings.useAA =
134 		(settings.useAA == AA_NO) ? AA_YES :
135 		AA_FORCE;
136 	    break;
137 	case C_DECAA:
138 	    settings.useAA =
139 		(settings.useAA == AA_FORCE) ? AA_YES :
140 		AA_NO;
141 	    break;
142 	case C_GRID:
143 	    settings.showGrid = !settings.showGrid;
144 	    break;
145 	case C_DECFPS:
146 	    settings.fps = std::max(1, settings.fps-1);
147 	    break;
148 	case C_INCFPS:
149 	    settings.fps = std::min(100, settings.fps+1);
150 	    break;
151 	case C_DECRATE:
152 	    if (settings.debug || gameState->end || gameState->ai)
153 	    {
154 		gameClock.rate = 4*gameClock.rate/5;
155 		const int diff =
156 		    gameClock.rate - rateOfSpeed(gameState->speed);
157 		if (abs(diff) < 100)
158 		    gameClock.rate = rateOfSpeed(gameState->speed);
159 	    }
160 	    break;
161 	case C_RESETRATE:
162 	    gameClock.rate = rateOfSpeed(gameState->speed);
163 	    break;
164 	case C_INCRATE:
165 	    if (settings.debug || gameState->end || gameState->ai)
166 	    {
167 		gameClock.rate = std::max(5*gameClock.rate/4,
168 			gameClock.rate+1);
169 		const int diff =
170 		    gameClock.rate - rateOfSpeed(gameState->speed);
171 		if (abs(diff) < 100)
172 		    gameClock.rate = rateOfSpeed(gameState->speed);
173 	    }
174 	    break;
175 	case C_INVULN:
176 	    if (settings.debug)
177 		settings.invuln = !settings.invuln;
178 	    break;
179 	case C_WIN:
180 	    if (settings.debug)
181 		gameState->end = END_WIN;
182 	    break;
183 
184 #ifdef HIGH_SCORE_REPORTING
185 	case C_REPORTHS:
186 	    if (gameState->end || gameClock.paused)
187 	    {
188 		reportHighScore(screen, gameState->speed);
189 	    }
190 	    return ER_NOTIMETAKEN;
191 #endif
192 	case C_SCREENSHOT:
193 	    return ER_SCREENSHOT;
194 
195 	default:
196 
197 	    if (!menuStack.empty())
198 	    {
199 		int dir =
200 		    (c == C_M_LEFT || c == C_LEFT) ? 0 :
201 		    (c == C_M_UP || c == C_DEZOOM) ? 1 :
202 		    (c == C_M_RIGHT || c == C_RIGHT) ? 2 :
203 		    (c == C_M_DOWN || c == C_DEAIM) ? 3 :
204 		    -1;
205 
206 		if (dir < 0)
207 		    break;
208 
209 		Menu* submenu = menuStack.top()->menus[dir];
210 		MenuLeaf* leaf = menuStack.top()->leaves[dir];
211 
212 		if (submenu)
213 		{
214 		    menuStack.push(submenu);
215 		}
216 		else if (leaf)
217 		{
218 		    LeafReturn lr = leaf->act();
219 		    switch (lr)
220 		    {
221 			case LR_QUIT:
222 			    return ER_QUIT;
223 			case LR_SURRENDER:
224 			    while (!menuStack.empty())
225 				menuStack.pop();
226 			    return ER_SURRENDER;
227 			case LR_NEWSPEED:
228 			    if (gameState->ai)
229 			    {
230 				// update speed on the fly, so the player
231 				// can see what they're getting themself
232 				// into...
233 				gameClock.rate = rateOfSpeed(settings.speed);
234 			    }
235 			    break;
236 			case LR_SETRES:
237 			    setVideoMode();
238 			    break;
239 			case LR_EXITMENU:
240 			    menuStack.pop();
241 			    break;
242 			case LR_SAVESETTINGS:
243 			    config.importSettings(settings);
244 			    config.write();
245 			    menuStack.pop();
246 			    break;
247 			case LR_NEWBACKGROUND:
248 			    return ER_NEWBACKGROUND;
249 			    break;
250 			default: ;
251 		    }
252 		}
253 	    }
254     }
255     return ER_MISC;
256 }
257 
process_events(GameState * gameState,GameClock & gameClock)258 EventsReturn process_events(GameState* gameState, GameClock& gameClock)
259 {
260     SDL_Event event;
261     while (SDL_PollEvent(&event))
262     {
263 	if (event.type == SDL_QUIT)
264 	    return ER_QUIT;
265 	else if (event.type == SDL_VIDEORESIZE)
266 	{
267 	    settings.width = event.resize.w;
268 	    settings.height = event.resize.h;
269 	    setVideoMode();
270 	    return ER_MISC;
271 	}
272 	else if (event.type == SDL_KEYDOWN)
273 	{
274 	    Key key(event.key.keysym);
275 
276 	    if (settings.commandToBind != C_NONE)
277 	    {
278 		if (key.sym >= SDLK_NUMLOCK && key.sym <= SDLK_COMPOSE)
279 		    // modifier - ignore
280 		    return ER_NONE;
281 
282 		settings.keybindings[settings.commandToBind] = key;
283 
284 		settings.commandToBind = C_NONE;
285 		return ER_MISC;
286 	    }
287 
288 	    for (command c = C_FIRST; c <= C_LASTDEBUG; c = command(c+1))
289 		if (settings.keybindings.get(c) == key)
290 		    return handleCommand(c, gameState, gameClock);
291 	}
292     }
293     return ER_NONE;
294 }
295 
drawInfo(SDL_Surface * surface,GameState * gameState,GameClock & gameClock,float observedFPS)296 void drawInfo(SDL_Surface* surface, GameState* gameState,
297 	GameClock& gameClock, float observedFPS)
298 {
299     int shownFPS = int(round(observedFPS));
300     char fpsStr[5+10+9];
301     snprintf(fpsStr, 5+10+9, "fps: %d/%d%s", shownFPS,
302 	    settings.fps, gameClock.paused ? " [Paused]" : "");
303 
304     char ratingStr[8+20+7+3];
305     snprintf(ratingStr, 8+20+7+3, "rating: %.1f %s (%s)",
306 	    gameState->rating, ratingString((int)(gameState->rating)),
307 	    speedStringShort(gameState->speed));
308 
309     char rateStr[6+5+10];
310     snprintf(rateStr, 6+5+10, "speed: %d.%d%s", gameClock.rate/1000,
311 	    (gameClock.rate%1000)/100, settings.debug ? " [*DEBUG*]" : "");
312 
313     // if not enough room, try short version; if still too long don't display:
314     if ((int)strlen(fpsStr) > screenGeom.infoMaxLength)
315 	snprintf(fpsStr, 5+10+9, "F: %d%s", shownFPS,
316 		gameClock.paused ? " [P]" : "" );
317     if ((int)strlen(fpsStr) > screenGeom.infoMaxLength)
318 	*fpsStr = '\0';
319 
320     if ((int)strlen(ratingStr) > screenGeom.infoMaxLength)
321 	snprintf(ratingStr, 8+20+7+5, "R: %.1f (%s)", gameState->rating,
322 		speedStringShort(gameState->speed));
323     if ((int)strlen(ratingStr) > screenGeom.infoMaxLength)
324 	*ratingStr = '\0';
325 
326     if ((int)strlen(rateStr) > screenGeom.infoMaxLength)
327 	snprintf(rateStr, 6+5+9, "S: %d.%d%s", gameClock.rate/1000,
328 		(gameClock.rate%1000)/100, settings.debug ? " [D]" : "");
329     if ((int)strlen(rateStr) > screenGeom.infoMaxLength)
330 	*rateStr = '\0';
331 
332     gfxPrimitivesSetFont(fontSmall,7,13);
333 
334     int line = 0;
335     if (settings.showFPS)
336 	stringColor(surface,
337 		screenGeom.info.x, screenGeom.info.y+15*line++,
338 		fpsStr, 0xffffffff);
339 
340 
341     if (screenGeom.infoMaxLines > line)
342     {
343 	stringColor(surface,
344 		screenGeom.info.x, screenGeom.info.y+15*line++,
345 		ratingStr, 0xffffffff);
346     }
347 
348     if ( (settings.debug || gameClock.rate != rateOfSpeed(gameState->speed))
349 	    && screenGeom.infoMaxLines > line)
350 	stringColor(surface,
351 		screenGeom.info.x, screenGeom.info.y+15*line++,
352 		rateStr, 0xffffffff);
353 }
354 
drawSplash(SDL_Surface * surface)355 void drawSplash(SDL_Surface* surface)
356 {
357     int x = screenGeom.centre.x - 19*10/2;
358     int y = screenGeom.centre.y - screenGeom.rad/3;
359 
360     // fade over time, and dim if menu up
361     const Uint32 titleColour = 0xff0000ff -
362 	std::min(0xa0, (int)(SDL_GetTicks()/500) +
363 		(menuStack.empty() ? 0 : 0x50));
364     const Uint32 instructColour = 0xffffffa0 -
365 	std::min(0x60, (int)(SDL_GetTicks()/500) +
366 		(menuStack.empty() ? 0 : 0x40));
367 
368     gfxPrimitivesSetFont(fontBig, 10, 20);
369 
370     if (x > 0)
371 	stringColor(surface, x, y, "K U K L O M E N O S", titleColour);
372     else
373     {
374 	// Not enough room for that
375 	x = screenGeom.centre.x - 10*10/2;
376 	stringColor(surface, x, y, "KUKLOMENOS", titleColour);
377     }
378 
379     static Overlay splashInstructOverlay(0.35);
380     static bool setText = false;
381     if (!setText)
382     {
383 	splashInstructOverlay.push_back(
384 		string("  ") + settings.keybindings[C_STARTGAME].getString() +
385 		string(" to start  "));
386 	splashInstructOverlay.push_back(
387 		string("  ") + settings.keybindings[C_MENU].getString() +
388 		string(" for menu  "));
389 	setText = true;
390     }
391 
392     splashInstructOverlay.colour = instructColour;
393 
394     splashInstructOverlay.draw(surface);
395 }
396 
397 
initialize_system()398 void initialize_system()
399 {
400     /* Initialize SDL */
401     if ( SDL_Init(SDL_INIT_EVERYTHING) < 0 ) {
402 	fprintf(stderr,
403 		"Couldn't initialize SDL: %s\n", SDL_GetError());
404 	exit(1);
405     }
406     atexit(SDL_Quit);			/* Clean up on exit */
407 
408     SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
409 
410     // set random seed
411     srand(time(NULL));
412 }
413 
initialize_video()414 void initialize_video()
415 {
416     /* Initialize the display */
417     if (settings.width != 0 && settings.height != 0)
418     {
419 	// try the mode we've been given
420 	screen = SDL_SetVideoMode(settings.width, settings.height,
421 		settings.bpp, settings.videoFlags);
422 	if ( screen == NULL ) {
423 	    fprintf(stderr, "Couldn't set %dx%dx%d video mode: %s\n",
424 		    settings.width, settings.height, settings.bpp,
425 		    SDL_GetError());
426 	}
427     }
428 
429     if (screen == NULL)
430     {
431 	// try a mode SDL tells us shold work:
432 	SDL_Rect** modes = SDL_ListModes(NULL, settings.videoFlags);
433 	if (modes == NULL)
434 	{
435 	    fprintf(stderr,
436 		    "SDL reports no modes available\n");
437 	    exit(1);
438 	}
439 	if (modes == (SDL_Rect**)(-1))
440 	{
441 	    // "All modes available"
442 	    settings.width = 1024;
443 	    settings.height = 768;
444 	}
445 	else
446 	{
447 	    // use the first (i.e. biggest) mode which is no bigger than
448 	    // 1024x768
449 	    SDL_Rect* bestMode;
450 	    do
451 	    {
452 		bestMode = *modes;
453 		if (bestMode->w <= 1024 && bestMode->h <= 768)
454 		    break;
455 	    } while (++modes);
456 	    settings.width = bestMode->w;
457 	    settings.height = bestMode->h;
458 	}
459 
460 	screen = SDL_SetVideoMode(settings.width, settings.height,
461 		settings.bpp, settings.videoFlags);
462 	if ( screen == NULL ) {
463 	    fprintf(stderr, "Couldn't set %dx%dx%d video mode: %s\n",
464 		    settings.width, settings.height, settings.bpp,
465 		    SDL_GetError());
466 
467 	    // one last try... let SDL use any bpp:
468 	    screen = SDL_SetVideoMode(settings.width, settings.height,
469 		    settings.bpp, settings.videoFlags | SDL_ANYFORMAT);
470 	    if ( screen == NULL )
471 	    {
472 		fprintf(stderr, "Couldn't set %dx%d video mode: %s\n",
473 			settings.width, settings.height, SDL_GetError());
474 		// give up
475 		exit(1);
476 	    }
477 	}
478     }
479 
480     /* Show some info */
481     printf("Set %dx%dx%d mode\n",
482 	    screen->w, screen->h, screen->format->BitsPerPixel);
483     printf("Video surface located in %s memory.\n",
484 	    (screen->flags&SDL_HWSURFACE) ? "video" : "system");
485 
486     SDL_WM_SetCaption( "Kuklomenos", NULL );
487     SDL_ShowCursor(SDL_DISABLE);
488 
489     if ( !initFont() )
490     {
491 	fprintf(stderr, "Failed to load font data file\n");
492 	exit(1);
493     }
494 
495     screenGeom = ScreenGeom(settings.width, settings.height);
496 
497     setBackground(screen);
498     setDirty(screen, background);
499 }
500 
haveInput()501 bool haveInput()
502 {
503     for (command c = C_FIRST; c <= C_LASTACTION; c = command(c+1))
504 	if (settings.keybindings.get(c).isPressed())
505 	    return true;
506     return false;
507 }
508 
run_game()509 void run_game()
510 {
511     if (settings.requestedRating == 0 &&
512 	    (!settings.debug || settings.wizard))
513 	config.shouldUpdateRating = true;
514 
515     // set up a game for the AI to play...
516     GameState* gameState = new GameState(settings.speed);
517     gameState->ai = new BasicAI(gameState);
518     GameClock gameClock(rateOfSpeed(settings.speed));
519 
520     // main loop
521     Uint32 lastStateUpdate = 0, beforeDelay = 0;
522     Uint32 ticksBefore, ticksAfter;
523     Uint32 loopTicks = SDL_GetTicks();
524     Uint32 AIEndTick = 0;
525     bool splash = true;
526     int timeTillNextFrame = 0;
527     int delayTime, actualDelayed, updateTime;
528     float avFrameTime = 1000/settings.fps;
529     int fpsRegulatorTenthTicks = 0;
530     const int avFrames = 10; // number of frames to average over
531     EventsReturn eventsReturn = ER_NONE;
532     bool wantScreenshot = false;
533     bool wantVideo = false;
534     int videoFrame = 1;
535     bool quit = false;
536     bool ended = false;
537     bool forceFrame = false;
538 
539     Overlay victoryOverlay(-0.2);
540     Overlay infoOverlay(0.2, 0xffffffff);
541 
542     const int MIN_INPUT_STEP = 30;
543     const int MIN_GAME_STEP = 30;
544 
545     while ( !quit ) {
546 	forceFrame = false;
547 	while (timeTillNextFrame > 0)
548 	{
549 	    delayTime = std::min(timeTillNextFrame, MIN_INPUT_STEP);
550 	    beforeDelay = SDL_GetTicks();
551 	    SDL_Delay(delayTime);
552 	    actualDelayed = SDL_GetTicks() - beforeDelay;
553 	    timeTillNextFrame -= actualDelayed;
554 
555 	    eventsReturn = process_events(gameState, gameClock);
556 
557 	    switch (eventsReturn)
558 	    {
559 		case ER_RESTART:
560 		    {
561 			GameState* newGameState =
562 			    new GameState(settings.speed);
563 			delete gameState;
564 			gameState = newGameState;
565 			gameClock = GameClock(rateOfSpeed(settings.speed));
566 		    }
567 		    ended = false;
568 		    victoryOverlay.clear();
569 		    infoOverlay.clear();
570 		    splash = false;
571 		    drawBackground(screen);
572 		    lastStateUpdate = SDL_GetTicks();
573 		    break;
574 		case ER_QUIT:
575 		    quit = true;
576 		    break;
577 		case ER_SURRENDER:
578 		    if (!ended)
579 			gameState->end = END_DEAD;
580 		    break;
581 		case ER_SCREENSHOT:
582 		    wantScreenshot = true;
583 		    break;
584 		case ER_NEWBACKGROUND:
585 		    if (!background)
586 		    {
587 			setBackground(screen);
588 			setDirty(screen, background);
589 		    }
590 		    else
591 			drawBackground(screen);
592 		    forceFrame = true;
593 		    break;
594 		case ER_NOTIMETAKEN:
595 		    // pretend the time spent in process_events() didn't
596 		    // actually happen:
597 		    lastStateUpdate = SDL_GetTicks();
598 		    forceFrame = true;
599 		    break;
600 		case ER_MISC:
601 		    forceFrame = true;
602 		    break;
603 		default: ;
604 	    }
605 
606 	    updateTime = std::max(1,
607 		    gameClock.scale(SDL_GetTicks() - lastStateUpdate));
608 	    lastStateUpdate = SDL_GetTicks();
609 	    if ( !gameClock.paused &&
610 		    ( (!settings.stopMotion || haveInput()) &&
611 		      menuStack.empty() ) || ended || gameState->ai )
612 	    {
613 		while (updateTime > 0)
614 		{
615 		    const int stepTime =
616 			std::min( MIN_GAME_STEP, updateTime );
617 		    gameState->update(stepTime, !menuStack.empty());
618 		    gameClock.updatePreScaled(stepTime);
619 		    updateTime -= stepTime;
620 		}
621 	    }
622 	}
623 
624 	ticksBefore = SDL_GetTicks();
625 	if (!gameClock.paused || forceFrame)
626 	{
627 	    gameState->draw(screen);
628 	    drawInfo(screen, gameState, gameClock, 1000.0/avFrameTime);
629 	    victoryOverlay.draw(screen, menuStack.empty() ? 0xff : 0xa0);
630 	    infoOverlay.draw(screen, menuStack.empty() ? 0xff : 0xa0);
631 	    if (!menuStack.empty())
632 		drawMenu(screen, *menuStack.top());
633 	    if (splash)
634 		drawSplash(screen);
635 	    SDL_Flip(screen);
636 
637 	    if (wantScreenshot)
638 	    {
639 		if (SDL_SaveBMP(screen, "screenshot.bmp") != 0)
640 		    fprintf(stderr, "Screenshot failed.\n");
641 		wantScreenshot = false;
642 	    }
643 	    if (wantVideo)
644 	    {
645 		char fname[16];
646 		snprintf(fname, 16, "frame%.5d.bmp", videoFrame);
647 		SDL_SaveBMP(screen, fname);
648 		videoFrame++;
649 	    }
650 
651 	    // blank over what we've drawn:
652 	    blankDirty();
653 	}
654 	ticksAfter = SDL_GetTicks();
655 
656 	const int renderingTicks = ticksAfter - ticksBefore;
657 
658 	// Add in a manual tweak to the delay, to ensure we approximate the
659 	// requested fps (where possible):
660 	if (10000/avFrameTime < settings.fps * 10 &&
661 		1000/settings.fps - renderingTicks + fpsRegulatorTenthTicks/10
662 		> 1)
663 	    fpsRegulatorTenthTicks -= 1;
664 	else if (10000/avFrameTime > settings.fps * 10)
665 	    fpsRegulatorTenthTicks += 1;
666 
667 	timeTillNextFrame =
668 	    std::max(1, (1000/settings.fps) - renderingTicks +
669 		    fpsRegulatorTenthTicks/10 + (rani(10) <
670 			fpsRegulatorTenthTicks%10));
671 
672 	if (!ended && gameState->end && !gameState->ai)
673 	{
674 	    // Game over, man
675 	    ended = true;
676 	    switch (gameState->end)
677 	    {
678 		case END_DEAD:
679 		case END_EXTRACTED:
680 		    victoryOverlay.push_back("Failure.");
681 		    victoryOverlay.colour = 0xff4444ff;
682 		    break;
683 		case END_WIN:
684 		    victoryOverlay.push_back("Victory!");
685 		    victoryOverlay.colour = 0x88ff00ff;
686 		    break;
687 		default: ;
688 	    }
689 
690 	    // give a hint if appropriate
691 	    const char* hint = gameState->getHint();
692 	    if (hint)
693 		infoOverlay.push_back(hint);
694 
695 	    if (config.shouldUpdateRating)
696 	    {
697 		int old = (int)(gameState->rating);
698 
699 		if (gameState->end == END_DEAD ||
700 			gameState->end == END_EXTRACTED)
701 		{
702 		    gameState->rating -= 0.1;
703 		    if (gameState->rating < 1)
704 			gameState->rating = 1.0;
705 
706 		}
707 		else if (gameState->end == END_WIN)
708 		{
709 		    gameState->rating += 0.4;
710 		    if (gameState->rating < 5)
711 			// extra boost, so clawing your way back up from the
712 			// depths you sink to while first learning the game
713 			// isn't too arduous:
714 			gameState->rating += 0.2*(5 - (int)gameState->rating);
715 		}
716 
717 		// deal with float inaccuracy - sometimes, 0.6+0.4 is just
718 		// less that 1.0...
719 		if ((gameState->rating - (int)(gameState->rating)) > 0.95)
720 		    gameState->rating = (int)(gameState->rating) + 1;
721 
722 		if ((int)(gameState->rating) != old)
723 		{
724 		    ostringstream s;
725 		    s << "New rating: \"" <<
726 			ratingString( (int)(gameState->rating) ) << "\".";
727 		    infoOverlay.push_back(s.str());
728 		}
729 		if (config.shouldUpdateRating)
730 		{
731 		    // update ratings. Ratings are always kept ordered: lowest
732 		    // speed highest rating down to highest speed lowest
733 		    // rating.
734 		    bool newHigh = false;
735 		    for (int speed=0; speed<3; speed++)
736 			if ( (speed <= gameState->speed &&
737 				    config.rating[speed] < gameState->rating)
738 				|| (speed >= gameState->speed &&
739 				    config.rating[speed] > gameState->rating) )
740 			{
741 			    config.rating[speed] = gameState->rating;
742 			    if (config.highestRating[speed] < gameState->rating)
743 			    {
744 				newHigh = true;
745 				config.highestRating[speed] = gameState->rating;
746 			    }
747 			}
748 		    if (newHigh)
749 		    {
750 			infoOverlay.push_back("New high score!");
751 #ifdef HIGH_SCORE_REPORTING
752 			infoOverlay.push_back(settings.keybindings[C_REPORTHS].getString()
753 				+ string(" to report to server"));
754 #endif
755 		    }
756 		}
757 	    }
758 	}
759 
760 	else if (gameState->end && gameState->ai)
761 	{
762 	    if (!ended)
763 	    {
764 		ended = true;
765 		AIEndTick = SDL_GetTicks();
766 	    }
767 	    else if (SDL_GetTicks() - AIEndTick > 5000)
768 	    {
769 		GameState* newGameState =
770 		    new GameState(settings.speed);
771 		delete gameState;
772 		gameState = newGameState;
773 		gameState->ai = new BasicAI(gameState);
774 		gameClock = GameClock(rateOfSpeed(settings.speed));
775 		drawBackground(screen);
776 		ended = false;
777 	    }
778 	}
779 
780 
781 	const int loopTime = SDL_GetTicks() - loopTicks;
782 	avFrameTime += (loopTime-avFrameTime)/avFrames;
783 	loopTicks = SDL_GetTicks();
784     }
785 
786     if (!ended && gameState->extracted > 0)
787     {
788 	// game is forfeit - reduce rating
789 	gameState->rating -= 0.1;
790 	if (gameState->rating < 1)
791 	    gameState->rating = 1.0;
792     }
793 
794     config.write();
795 
796     delete gameState->ai;
797     delete gameState;
798 
799     SDL_Quit();
800 }
801 
802 #ifndef __APPLE__
main(int argc,char ** argv)803 int main(int argc, char** argv)
804 {
805     load_settings(argc, argv);
806     initialize_system();
807     initialize_video();
808     run_game();
809 
810     return 0;
811 }
812 #endif /* __APPLE__ */
813