1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
|
/* play.c: Top-level game-playing functions.
*
* Copyright (C) 2001-2006 by Brian Raiter, under the GNU General Public
* License. No warranty. See COPYING for details.
*/
#include <stdlib.h>
#include <string.h>
#include "defs.h"
#include "err.h"
#include "state.h"
#include "encoding.h"
#include "oshw.h"
#include "res.h"
#include "logic.h"
#include "random.h"
#include "solution.h"
#include "play.h"
/* The current state of the current game.
*/
static gamestate state;
/* The current logic module.
*/
static gamelogic *logic = NULL;
/* TRUE if the user has requested pedantic mode game play.
*/
static int pedanticmode = FALSE;
/* How much mud to make the timer suck (i.e., the slowdown factor).
*/
static int mudsucking = 1;
/* Turn on the pedantry.
*/
void setpedanticmode(void)
{
pedanticmode = TRUE;
}
/* Set the slowdown factor.
*/
int setmudsuckingfactor(int mud)
{
if (mud < 1)
return FALSE;
mudsucking = mud;
return TRUE;
}
/* Configure the game logic, and some of the OS/hardware layer, as
* required for the given ruleset. Do nothing if the requested ruleset
* is already the current ruleset.
*/
static int setrulesetbehavior(int ruleset, int withgui)
{
if (logic) {
if (ruleset == logic->ruleset)
return TRUE;
(*logic->shutdown)(logic);
logic = NULL;
free(state.localstateinfo);
state.localstateinfo = NULL;
}
if (ruleset == Ruleset_None)
return TRUE;
switch (ruleset) {
case Ruleset_Lynx:
logic = lynxlogicstartup();
if (!logic)
return FALSE;
setkeyboardarrowsrepeat(TRUE);
settimersecond(1000 * mudsucking);
break;
case Ruleset_MS:
logic = mslogicstartup();
if (!logic)
return FALSE;
setkeyboardarrowsrepeat(FALSE);
settimersecond(1100 * mudsucking);
break;
default:
errmsg(NULL, "unknown ruleset requested (ruleset=%d)", ruleset);
return FALSE;
}
if (withgui) {
if (!loadgameresources(ruleset) || !creategamedisplay()) {
die("unable to proceed due to previous errors.");
return FALSE;
}
}
state.localstateinfo = calloc(logic->localstateinfosize, 1);
logic->state = &state;
return TRUE;
}
/* Initialize the current state to the starting position of the
* given level.
*/
int initgamestate(gamesetup *game, int ruleset, int withgui)
{
if (!setrulesetbehavior(ruleset, withgui))
die("unable to initialize the system for the requested ruleset");
memset(state.map, 0, sizeof state.map);
state.game = game;
state.ruleset = ruleset;
state.replay = -1;
state.currenttime = -1;
state.timeoffset = 0;
state.currentinput = NIL;
state.lastmove = NIL;
state.initrndslidedir = NIL;
state.stepping = -1;
state.soundeffects = 0;
state.timelimit = game->time * TICKS_PER_SECOND;
state.statusflags = 0;
if (pedanticmode)
state.statusflags |= SF_PEDANTIC;
initmovelist(&state.moves);
resetprng(&state.mainprng);
if (!expandleveldata(&state))
return FALSE;
return (*logic->initgame)(logic);
}
/* Change the current state to run from the recorded solution.
*/
int prepareplayback(void)
{
solutioninfo solution;
if (!state.game->solutionsize)
return FALSE;
solution.moves.list = NULL;
solution.moves.allocated = 0;
if (!expandsolution(&solution, state.game) || !solution.moves.count)
return FALSE;
destroymovelist(&state.moves);
state.moves = solution.moves;
restartprng(&state.mainprng, solution.rndseed);
state.initrndslidedir = solution.rndslidedir;
state.stepping = solution.stepping;
state.replay = 0;
return TRUE;
}
/* Return the amount of time passed in the current game, in seconds.
*/
int secondsplayed(void)
{
return (state.currenttime + state.timeoffset) / TICKS_PER_SECOND;
}
/* Change the system behavior according to the given gameplay mode.
*/
void setgameplaymode(int mode)
{
switch (mode) {
case BeginInput:
setkeyboardinputmode(TRUE);
break;
case EndInput:
setkeyboardinputmode(FALSE);
break;
case BeginPlay:
setkeyboardrepeat(FALSE);
settimer(+1);
break;
case EndPlay:
setkeyboardrepeat(TRUE);
settimer(-1);
break;
case BeginVerify:
settimer(+1);
break;
case EndVerify:
settimer(-1);
break;
case SuspendPlayShuttered:
if (state.ruleset == Ruleset_MS)
state.statusflags |= SF_SHUTTERED;
case SuspendPlay:
setkeyboardrepeat(TRUE);
settimer(0);
setsoundeffects(0);
break;
case ResumePlay:
setkeyboardrepeat(FALSE);
settimer(+1);
setsoundeffects(+1);
state.statusflags &= ~SF_SHUTTERED;
break;
}
}
/* Alter the stepping. If display is true, the new stepping value is
* reported to the user.
*/
int setstepping(int stepping, int display)
{
char msg[32], *p;
state.stepping = stepping;
if (display) {
p = msg;
p += sprintf(p, "%s-step", state.stepping & 4 ? "odd" : "even");
if (state.stepping & 3)
p += sprintf(p, " +%d", state.stepping & 3);
setdisplaymsg(msg, 500, 500);
}
return TRUE;
}
/* Alter the stepping by a delta. Force the stepping to be appropriate
* to the current ruleset.
*/
int changestepping(int delta, int display)
{
int n;
if (state.stepping < 0)
state.stepping = 0;
n = (state.stepping + delta) % 8;
if (state.ruleset == Ruleset_MS)
n &= ~3;
if (state.stepping != n)
return setstepping(n, display);
return TRUE;
}
/* Rotate the initial random slide direction. Note that the stored
* value for initrndslidedir is actually to the left of the first
* direction that will actually be used, so the displayed message
* needs to reflect that.
*/
int rotaterndslidedir(int display)
{
char msg[32];
char const *dirname;
if (state.ruleset == Ruleset_MS)
return FALSE;
state.initrndslidedir = right(state.initrndslidedir);
if (display) {
switch (right(state.initrndslidedir)) {
case NORTH: dirname = "north"; break;
case WEST: dirname = "west"; break;
case SOUTH: dirname = "south"; break;
case EAST: dirname = "east"; break;
default: dirname = "(nil)"; break;
}
sprintf(msg, "random slide: %s", dirname);
setdisplaymsg(msg, 500, 500);
}
return TRUE;
}
/* Advance the game one tick and update the game state. cmd is the
* current keyboard command supplied by the user. The return value is
* positive if the game was completed successfully, negative if the
* game ended unsuccessfully, and zero otherwise.
*/
int doturn(int cmd)
{
action act;
int n;
state.soundeffects &= ~((1 << SND_ONESHOT_COUNT) - 1);
state.currenttime = gettickcount();
if (state.currenttime >= MAXIMUM_TICK_COUNT) {
errmsg(NULL, "timer reached its maximum of %d.%d hours; quitting now",
MAXIMUM_TICK_COUNT / (TICKS_PER_SECOND * 3600),
(MAXIMUM_TICK_COUNT / (TICKS_PER_SECOND * 360)) % 10);
return -1;
}
if (state.replay < 0) {
if (cmd != CmdPreserve)
state.currentinput = cmd;
} else {
if (state.replay < state.moves.count) {
if (state.currenttime > state.moves.list[state.replay].when)
warn("Replay: Got ahead of saved solution: %d > %d!",
state.currenttime, state.moves.list[state.replay].when);
if (state.currenttime == state.moves.list[state.replay].when) {
state.currentinput = state.moves.list[state.replay].dir;
++state.replay;
}
} else {
n = state.currenttime + state.timeoffset - 1;
if (n > state.game->besttime)
return -1;
}
}
n = (*logic->advancegame)(logic);
if (state.replay < 0 && state.lastmove) {
act.when = state.currenttime;
act.dir = state.lastmove;
addtomovelist(&state.moves, act);
state.lastmove = NIL;
}
if (n)
return n;
return 0;
}
/* Update the display to show the current game state (including sound
* effects, if any). If showframe is FALSE, then nothing is actually
* displayed.
*/
int drawscreen(int showframe)
{
int currenttime;
int timeleft, besttime;
playsoundeffects(state.soundeffects);
if (!showframe)
return TRUE;
currenttime = state.currenttime + state.timeoffset;
if (hassolution(state.game))
besttime = (state.game->time ? state.game->time : 999)
- state.game->besttime / TICKS_PER_SECOND;
else
besttime = TIME_NIL;
timeleft = TIME_NIL;
if (state.game->time) {
timeleft = state.game->time - currenttime / TICKS_PER_SECOND;
if (timeleft <= 0) {
timeleft = 0;
setdisplaymsg("Out of time", 2, 2);
}
}
return displaygame(&state, timeleft, besttime);
}
/* Stop game play and clean up.
*/
int quitgamestate(void)
{
state.soundeffects = 0;
setsoundeffects(-1);
return TRUE;
}
/* Clean up after game play is over.
*/
int endgamestate(void)
{
setsoundeffects(-1);
return (*logic->endgame)(logic);
}
/* Close up shop.
*/
void shutdowngamestate(void)
{
setrulesetbehavior(Ruleset_None, FALSE);
destroymovelist(&state.moves);
}
/* Initialize the current game state to a small level used for display
* at the completion of a series.
*/
void setenddisplay(void)
{
state.replay = -1;
state.timelimit = 0;
state.currenttime = -1;
state.timeoffset = 0;
state.chipsneeded = 0;
state.currentinput = NIL;
state.statusflags = 0;
state.soundeffects = 0;
getenddisplaysetup(&state);
(*logic->initgame)(logic);
}
/*
* Solution handling functions.
*/
/* Return TRUE if a solution exists for the given level.
*/
int hassolution(gamesetup const *game)
{
return game->besttime != TIME_NIL;
}
/* Compare the most recent solution for the current game with the
* user's best solution (if any). If this solution beats what's there,
* or if the current solution has been marked as replaceable, then
* replace it. TRUE is returned if the solution was replaced.
*/
int replacesolution(void)
{
solutioninfo solution;
int currenttime;
if (state.statusflags & SF_NOSAVING)
return FALSE;
currenttime = state.currenttime + state.timeoffset;
if (hassolution(state.game) && !(state.game->sgflags & SGF_REPLACEABLE)
&& currenttime >= state.game->besttime)
return FALSE;
state.game->besttime = currenttime;
state.game->sgflags &= ~SGF_REPLACEABLE;
solution.moves = state.moves;
solution.rndseed = getinitialseed(&state.mainprng);
solution.flags = 0;
solution.rndslidedir = state.initrndslidedir;
solution.stepping = state.stepping;
if (!contractsolution(&solution, state.game))
return FALSE;
return TRUE;
}
/* Delete the user's best solution for the current game. FALSE is
* returned if no solution was present.
*/
int deletesolution(void)
{
if (!hassolution(state.game))
return FALSE;
state.game->besttime = TIME_NIL;
state.game->sgflags &= ~SGF_REPLACEABLE;
free(state.game->solutiondata);
state.game->solutionsize = 0;
state.game->solutiondata = NULL;
return TRUE;
}
/* Double-checks the timing for a solution that has just been played
* back. If the timing is off, and the cause of the discrepancy can be
* reasonably ascertained to be benign, the timing will be corrected
* and TRUE is returned.
*/
int checksolution(void)
{
int currenttime;
if (!hassolution(state.game))
return FALSE;
currenttime = state.currenttime + state.timeoffset;
if (currenttime == state.game->besttime)
return FALSE;
warn("saved game has solution time of %d ticks, but replay took %d ticks",
state.game->besttime, currenttime);
if (state.game->besttime == state.currenttime) {
warn("difference matches clock offset; fixing.");
state.game->besttime = currenttime;
return TRUE;
} else if (currenttime - state.game->besttime == 1) {
warn("difference matches pre-0.10.1 error; fixing.");
state.game->besttime = currenttime;
return TRUE;
}
warn("reason for difference unknown.");
state.game->besttime = currenttime;
return FALSE;
}
|