diff src/ljpc.c @ 0:c84446dfb3f5

initial add
author paulo@localhost
date Fri, 13 Mar 2009 00:39:12 -0700
parents
children
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/ljpc.c	Fri Mar 13 00:39:12 2009 -0700
     1.3 @@ -0,0 +1,2156 @@
     1.4 +/* PC frontend for LOCKJAW, an implementation of the Soviet Mind Game
     1.5 +
     1.6 +Copyright (C) 2006-2008 Damian Yerrick <tepples+lj@spamcop.net>
     1.7 +
     1.8 +This work is free software; you can redistribute it and/or modify
     1.9 +it under the terms of the GNU General Public License as published by
    1.10 +the Free Software Foundation; either version 2 of the License, or
    1.11 +(at your option) any later version.
    1.12 +
    1.13 +This program is distributed in the hope that it will be useful,
    1.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
    1.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1.16 +GNU General Public License for more details.
    1.17 +
    1.18 +You should have received a copy of the GNU General Public License
    1.19 +along with this program; if not, write to the Free Software
    1.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    1.21 +
    1.22 +Original game concept and design by Alexey Pajitnov.
    1.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
    1.24 +or The Tetris Company LLC.
    1.25 +
    1.26 +*/
    1.27 +
    1.28 +#include "ljpc.h"
    1.29 +#include "ljreplay.h"
    1.30 +#include <jpgalleg.h>
    1.31 +#include <time.h>
    1.32 +#include "scenario.h"
    1.33 +#include "ljpath.h"
    1.34 +
    1.35 +#if 1
    1.36 +  #define LJ_VERSION "0.46a ("__DATE__")"
    1.37 +#else
    1.38 +  #define LJ_VERSION "WIP ("__DATE__")"
    1.39 +#endif
    1.40 +
    1.41 +#define USE_PICS_FOLDER 0
    1.42 +#define MAX_PLAYERS 2
    1.43 +#define LJ_TICK_RATE 3600  // frames per minute
    1.44 +
    1.45 +int bgColor, fgColor = 0, hiliteColor, pfBgColor = 0, pfFgColor;
    1.46 +static volatile int curTime = 0;
    1.47 +volatile char redrawWholeScreen = 1;
    1.48 +static volatile int wantPause = 0;
    1.49 +const DATAFILE *dat = NULL, *sound_dat = NULL;
    1.50 +const FONT *aver32 = NULL, *aver16 = NULL;
    1.51 +char withSound = 0;
    1.52 +char autoPause;
    1.53 +int refreshRate = 0;
    1.54 +char demoFilename[512];
    1.55 +
    1.56 +int withPics = -1;
    1.57 +
    1.58 +char skinName[PATH_MAX] = "default.skin";
    1.59 +char ljblocksSRSName[PATH_MAX];
    1.60 +char ljblocksSegaName[PATH_MAX];
    1.61 +char ljconnSRSName[PATH_MAX];
    1.62 +char ljconnSegaName[PATH_MAX];
    1.63 +char ljbgName[PATH_MAX];
    1.64 +char menubgName[PATH_MAX];
    1.65 +char bgmName[PATH_MAX];
    1.66 +char bgmRhythmName[PATH_MAX];
    1.67 +unsigned long int bgmLoopPoint = 0;
    1.68 +int bgmReadyGo = 0;
    1.69 +int bgmVolume = 128;
    1.70 +int skinW = 800;
    1.71 +int skinH = 600;
    1.72 +
    1.73 +
    1.74 +
    1.75 +
    1.76 +static void incCurTime(void) {
    1.77 +  ++curTime;
    1.78 +} END_OF_FUNCTION(incCurTime);
    1.79 +
    1.80 +static void amnesia(void) {
    1.81 +  redrawWholeScreen = 1;
    1.82 +} END_OF_FUNCTION(amnesia);
    1.83 +
    1.84 +static void requestPause(void) {
    1.85 +  wantPause |= autoPause;
    1.86 +}
    1.87 +
    1.88 +
    1.89 +int getTime(void) {
    1.90 +  return curTime;
    1.91 +}
    1.92 +
    1.93 +void yieldCPU(void) {
    1.94 +  rest(5);  
    1.95 +}
    1.96 +
    1.97 +void ljBeginDraw(LJView *v, int sync) {
    1.98 +  vsync();
    1.99 +  if (redrawWholeScreen) {
   1.100 +    redrawWholeScreen = 0;
   1.101 +    playRedrawScreen(v);
   1.102 +  }
   1.103 +
   1.104 +  BITMAP *sc = v->plat->skin->fullback;
   1.105 +
   1.106 +  // Draw shape
   1.107 +  blit(v->plat->skin->bg, sc, 0, 0, 0, 0, 48, 16);
   1.108 +  if (v->control->replayDst) {
   1.109 +
   1.110 +    // circle: record
   1.111 +    circlefill(sc, 8, 8, 7, fgColor);
   1.112 +    textout_ex(sc, aver16, "Rec", 18, 2, fgColor, -1);
   1.113 +  } else if (v->control->replaySrc) {
   1.114 +
   1.115 +    // triangle: play
   1.116 +    for (int wid = 0; wid < 7; ++wid) {
   1.117 +      hline(sc, 2, 2 + wid, wid * 2 + 3, fgColor);
   1.118 +      hline(sc, 2, 14 - wid, wid * 2 + 3, fgColor);
   1.119 +    }
   1.120 +    textout_ex(sc, aver16, "Play", 18, 2, fgColor, -1);
   1.121 +  } else {
   1.122 +
   1.123 +    // square: stop
   1.124 +    rectfill(sc, 2, 2, 14, 14, fgColor);
   1.125 +  }
   1.126 +  blit(sc, screen, 0, 0, 0, 0, 48, 16);
   1.127 +}
   1.128 +
   1.129 +void ljEndDraw(LJView *v) {
   1.130 +
   1.131 +}
   1.132 +
   1.133 +static void startRecording(LJView *v) {
   1.134 +  withPics = -1;
   1.135 +
   1.136 +  // close existing playback
   1.137 +  if (v->control->replaySrc) {
   1.138 +    replayClose(v->control->replaySrc);
   1.139 +    v->control->replaySrc = NULL;
   1.140 +  }
   1.141 +      
   1.142 +  // toggle recording
   1.143 +  if (v->control->replayDst) {
   1.144 +    replayClose(v->control->replayDst);
   1.145 +    v->control->replayDst = NULL;
   1.146 +  } else {
   1.147 +    char path[PATH_MAX];
   1.148 +
   1.149 +    ljpathFind_w(path, "demo.ljm");
   1.150 +    v->control->replayDst = newReplay(path, v->field);
   1.151 +#if USE_PICS_FOLDER
   1.152 +    withPics = 0;
   1.153 +#endif
   1.154 +  }
   1.155 +}
   1.156 +
   1.157 +static void startPlaying(LJView *v) {
   1.158 +  withPics = -1;
   1.159 +
   1.160 +  // close existing recording
   1.161 +  if (v->control->replayDst) {
   1.162 +    replayClose(v->control->replayDst);
   1.163 +    v->control->replayDst = NULL;
   1.164 +  }
   1.165 +      
   1.166 +  // toggle playback
   1.167 +  if (v->control->replaySrc) {
   1.168 +    replayClose(v->control->replaySrc);
   1.169 +    v->control->replaySrc = NULL;
   1.170 +  } else {
   1.171 +    char path[PATH_MAX];
   1.172 +
   1.173 +    if (ljpathFind_r(path, "demo.ljm")) {
   1.174 +      v->control->replaySrc = openReplay(path, v->field);
   1.175 +    }
   1.176 +    v->nLockTimes = 0;
   1.177 +#if USE_PICS_FOLDER
   1.178 +    withPics = 0;
   1.179 +#endif
   1.180 +  }
   1.181 +  v->backDirty = ~0;
   1.182 +  v->field->reloaded = 1;
   1.183 +}
   1.184 +
   1.185 +int ljHandleConsoleButtons(LJView *v) {
   1.186 +  int canceled = 0;
   1.187 +  
   1.188 +  while (keypressed()) {
   1.189 +    int scancode;
   1.190 +    int codepoint = ureadkey(&scancode);
   1.191 +
   1.192 +    if (scancode == KEY_ESC) {
   1.193 +      wantPause = 1;
   1.194 +    } else if (scancode == KEY_PRTSCR) {
   1.195 +      saveScreen(-1);
   1.196 +      save_bitmap("ljbackbuf.bmp", v->plat->skin->fullback, NULL);
   1.197 +    } else if (codepoint == '[') {
   1.198 +      v->plat->wantRecord = 1;
   1.199 +    } else if (codepoint == ']') {
   1.200 +      v->plat->wantRecord = 0;
   1.201 +      startPlaying(v);
   1.202 +    }
   1.203 +  }
   1.204 +  if (v->plat->wantRecord) {
   1.205 +    v->plat->wantRecord = 0;
   1.206 +    startRecording(v);
   1.207 +  }
   1.208 +  if (wantPause) {
   1.209 +    canceled = pauseGame(v->plat);
   1.210 +    wantPause = 0;
   1.211 +  }
   1.212 +  
   1.213 +  if (wantsClose) {
   1.214 +    canceled = 1;
   1.215 +  }
   1.216 +  
   1.217 +  return canceled;
   1.218 +}
   1.219 +
   1.220 +
   1.221 +static void drawBlock(const LJPCView *const v, int x, int y, int blk) {
   1.222 +
   1.223 +  if (x >= 0 && x < LJ_PF_WID && y >= 0 && y < LJ_PF_VIS_HT) {
   1.224 +    const int w = v->skin->blkW;
   1.225 +    const int h = v->skin->blkH;
   1.226 +    const int dstX = w * x;
   1.227 +    const int dstY = h * (LJ_PF_VIS_HT - 1 - y);
   1.228 +
   1.229 +    if (v->skin->transparentPF && (blk == 0 || blk == 0x100)) {
   1.230 +
   1.231 +      // Copy background
   1.232 +      const unsigned int srcX = dstX + v->baseX;
   1.233 +      const unsigned int srcY = dstY + v->skin->baseY
   1.234 +                                - LJ_PF_VIS_HT * h - v->skin->pfElev;
   1.235 +      blit(v->skin->bg, v->back, srcX, srcY, dstX, dstY, w, h);
   1.236 +    } else if (blk && v->skin->connBlocks) {
   1.237 +
   1.238 +      // Copy connected block
   1.239 +      const unsigned int srcX = ((blk >> 0) & 15) * w;
   1.240 +      const unsigned int srcY = ((blk >> 4) & 15) * h;
   1.241 +      blit(v->skin->connBlocks, v->back, srcX, srcY, dstX, dstY, w, h);
   1.242 +    } else {
   1.243 +
   1.244 +      // Copy lone block
   1.245 +      const unsigned int srcX = ((blk >> 4) & 7) * w;
   1.246 +      const unsigned int srcY = ((blk >> 7) & 1) * h;
   1.247 +      blit(v->skin->blocks, v->back, srcX, srcY, dstX, dstY, w, h);
   1.248 +    }
   1.249 +    
   1.250 +    // 0x100: hotline
   1.251 +    if (blk & 0x100) {
   1.252 +      hline(v->back, dstX, dstY + h / 2 - 1, dstX + w - 1, pfFgColor);
   1.253 +      hline(v->back, dstX, dstY + h / 2    , dstX + w - 1, pfFgColor);
   1.254 +      hline(v->back, dstX, dstY + h / 2 + 1, dstX + w - 1, pfBgColor);
   1.255 +    }
   1.256 +  }
   1.257 +}
   1.258 +
   1.259 +/**
   1.260 + * Draws multiple rows of the playfield
   1.261 + * @param p     the playfield
   1.262 + * @param rows  the rows to be updated (0x00001 on bottom, 0x80000 on top)
   1.263 + */
   1.264 +void updField(const LJView *const v, LJBits rows) {
   1.265 +  const LJField *const p = v->field;
   1.266 +
   1.267 +  acquire_bitmap(v->plat->back);
   1.268 +  for (int y = 0;
   1.269 +       y < LJ_PF_VIS_HT && rows != 0;
   1.270 +       ++y, rows >>= 1) {
   1.271 +    int blankTile = 0;
   1.272 +    
   1.273 +    // hotline: draw 0x100 as the background
   1.274 +    if (hotlineRows[y] && v->field->scoreStyle == LJSCORE_HOTLINE) {
   1.275 +      blankTile = 0x100;
   1.276 +    }
   1.277 +    // during line clear delay, draw bars to show which lines were cleared
   1.278 +    if (p->state == LJS_LINES_FALLING && p->stateTime > 0
   1.279 +        && ((1 << y) & p->tempRows)) {
   1.280 +      blankTile = 0x100;
   1.281 +    }
   1.282 +    if (rows & 1) {
   1.283 +      for (int x = p->leftWall; x < p->rightWall; x++) {
   1.284 +        int b = v->hidePF ? 0 : p->b[y][x];
   1.285 +        drawBlock(v->plat, x, y, b ? b : blankTile);
   1.286 +      }
   1.287 +    }
   1.288 +  }
   1.289 +  release_bitmap(v->plat->back);
   1.290 +}
   1.291 +
   1.292 +
   1.293 +#define SHADOW_BLOCK 0x00
   1.294 +
   1.295 +/**
   1.296 + * Draws a tetromino whose lower left corner of the bounding box is at (x, y)
   1.297 + * @param b the bitmap to draw to
   1.298 + * @param piece the piece to be drawn
   1.299 + * @param x distance from to left side of 4x4 box
   1.300 + * @param y distance from top of bitmap to bottom of 4x4 box
   1.301 + * @param the rotation state (0: U; 1: R; 2: D; 3: L; 4: Initial position)
   1.302 + * @param w width of each block
   1.303 + * @param h height of each block
   1.304 + * @param color Drawing style
   1.305 + * color == 0: draw shadow
   1.306 + * color == 0x10 through 0x70: draw in that color
   1.307 + * color == 0x80: draw as garbage
   1.308 + * color == -255 through -1: draw with 255 through 1 percent lighting
   1.309 + */
   1.310 +LJBits drawPiece(LJView *const v, BITMAP *const b,
   1.311 +                 int piece, int x, int y, int theta,
   1.312 +                 int color, int w, int h) {
   1.313 +  // Don't try to draw the -1 that's the sentinel for no hold piece
   1.314 +  if (piece < 0)
   1.315 +    return 0;
   1.316 +
   1.317 +  LJBits rows = 0;
   1.318 +  LJBlkSpec blocks[4];
   1.319 +  const int srcW = v->plat->skin->blkW;
   1.320 +  const int srcH = v->plat->skin->blkH;
   1.321 +  BITMAP *singleBlocks = v->plat->skin->blocks;
   1.322 +  int singleSeries = (color == 0
   1.323 +                      && singleBlocks->h >= 6 * srcH)
   1.324 +                     ? 4 : 2;
   1.325 +
   1.326 +  // color: 0 for shadow, >0 for piece
   1.327 +  BITMAP *connBlocks = (color != SHADOW_BLOCK)
   1.328 +                       ? v->plat->skin->connBlocks : NULL;
   1.329 +
   1.330 +  BITMAP *transTemp = color < 0
   1.331 +                      || (color == SHADOW_BLOCK && v->hideShadow < LJSHADOW_COLORED)
   1.332 +                      ? create_bitmap(w, h) : 0;
   1.333 +
   1.334 +  if (transTemp) {
   1.335 +    set_trans_blender(0, 0, 0, v->hideShadow ? 128 : 64);
   1.336 +  }
   1.337 +
   1.338 +  expandPieceToBlocks(blocks, v->field, piece, 0, 0, theta);
   1.339 +  acquire_bitmap(b);
   1.340 +  for (int blk = 0; blk < 4; ++blk) {
   1.341 +    int blkValue = blocks[blk].conn;
   1.342 +    
   1.343 +    // If blkValue == 0 then the block is not part of the piece,
   1.344 +    // such as if it's a domino or tromino or (once pentominoes
   1.345 +    // are added) a tetromino.
   1.346 +    if (blkValue) {
   1.347 +      int blkX = blocks[blk].x;
   1.348 +      int blkY = blocks[blk].y;
   1.349 +      const int dstX = x + w * blkX;
   1.350 +      const int dstY = y + h * (-1 - blkY);
   1.351 +
   1.352 +      if (color > 0) {
   1.353 +        blkValue = color | (blkValue & CONNECT_MASK);
   1.354 +      } else if (color == 0 && v->hideShadow == LJSHADOW_COLORLESS) {
   1.355 +        blkValue = 0;
   1.356 +      }
   1.357 +    
   1.358 +      if (connBlocks) {
   1.359 +        const unsigned int srcX = ((blkValue >> 0) & 15) * srcW;
   1.360 +        const unsigned int srcY = ((blkValue >> 4) & 15) * srcH;
   1.361 +
   1.362 +        if (transTemp) {
   1.363 +          stretch_blit(connBlocks, transTemp,
   1.364 +                       srcX, srcY, srcW, srcH,
   1.365 +                       0, 0, w, h);
   1.366 +          if (color < 0) {
   1.367 +            draw_lit_sprite(b, transTemp, dstX, dstY, color + 256);
   1.368 +          } else {
   1.369 +            draw_trans_sprite(b, transTemp, dstX, dstY);
   1.370 +          }
   1.371 +        } else if (srcW != w || srcH != h) {
   1.372 +          stretch_blit(connBlocks, b,
   1.373 +                       srcX, srcY, srcW, srcH,
   1.374 +                       dstX, dstY, w, h);
   1.375 +          if (dstY > 600) {
   1.376 +            allegro_message("dstY = %d\n", dstY);
   1.377 +          }
   1.378 +        } else {
   1.379 +          blit(connBlocks, b,
   1.380 +               srcX, srcY,
   1.381 +               dstX, dstY, w, h);
   1.382 +        }
   1.383 +      } else {
   1.384 +        const unsigned int srcX = ((blkValue >> 4) & 7) * srcW;
   1.385 +        const unsigned int srcY = (((blkValue >> 7) & 1) + singleSeries) * srcH;
   1.386 +
   1.387 +        if (transTemp) {
   1.388 +          stretch_blit(singleBlocks, transTemp,
   1.389 +                       srcX, srcY, srcW, srcH,
   1.390 +                       0, 0, w, h);
   1.391 +          if (color < 0) {
   1.392 +            draw_lit_sprite(b, transTemp, dstX, dstY, color + 256);
   1.393 +          } else {
   1.394 +            draw_trans_sprite(b, transTemp, dstX, dstY);
   1.395 +          }
   1.396 +        } else {
   1.397 +          stretch_blit(singleBlocks, b,
   1.398 +                       srcX, srcY, srcW, srcH,
   1.399 +                       dstX, dstY, w, h);
   1.400 +        }
   1.401 +      }
   1.402 +      rows |= 1 << blkY;
   1.403 +    }
   1.404 +  }
   1.405 +  release_bitmap(b);
   1.406 +  if (transTemp) {
   1.407 +    solid_mode();
   1.408 +    destroy_bitmap(transTemp);
   1.409 +  }
   1.410 +
   1.411 +  return rows;
   1.412 +}
   1.413 +
   1.414 +void drawPieceInPF(LJView *v, int dstX, LJFixed dstY, int theta, int color) {
   1.415 +  LJBits bits = 0;
   1.416 +  BITMAP *b = v->plat->back;
   1.417 +  const LJField *const p = v->field;
   1.418 +  int y = ljfixfloor(dstY);
   1.419 +  const int w = v->plat->skin->blkW;
   1.420 +  const int h = v->plat->skin->blkH;
   1.421 +  int drawnY = fixmul(h, dstY);
   1.422 +
   1.423 +  bits = drawPiece(v, b, p->curPiece[0],
   1.424 +                   w * dstX, h * LJ_PF_VIS_HT - drawnY,
   1.425 +                   theta,
   1.426 +                   color, w, h);
   1.427 +  bits = (y >= 0) ? bits << y : bits >> -y;
   1.428 +  bits &= (1 << LJ_PF_VIS_HT) - 1;
   1.429 +  if (dstY & 0xFFFF) {
   1.430 +    bits |= bits << 1;
   1.431 +  }
   1.432 +
   1.433 +  v->backDirty |= bits;
   1.434 +  v->frontDirty |= bits;
   1.435 +}
   1.436 +
   1.437 +
   1.438 +void drawFallingPiece(LJView *v) {
   1.439 +  const LJField *const p = v->field;
   1.440 +  int piece = p->curPiece[0];
   1.441 +  int y = v->smoothGravity
   1.442 +          ? p->y
   1.443 +          : ljitofix(ljfixfloor(p->y));
   1.444 +  const int color = (p->state == LJS_LANDED)
   1.445 +                    ? -128 - ((p->stateTime + 1) * 128 / (p->speed.lockDelay + 1))
   1.446 +                    : pieceColors[piece];
   1.447 +
   1.448 +  // Draw trails
   1.449 +  if (v->showTrails) {
   1.450 +
   1.451 +    // trail going up
   1.452 +    while (v->trailY - y < ljitofix(-1)) {
   1.453 +      v->trailY += ljitofix(1);
   1.454 +      drawPieceInPF(v, p->x, v->trailY, p->theta, color);
   1.455 +    }
   1.456 +
   1.457 +    // trail going down
   1.458 +    while (v->trailY - y > ljitofix(1)) {
   1.459 +      v->trailY -= ljitofix(1);
   1.460 +      drawPieceInPF(v, p->x, v->trailY, p->theta, color);
   1.461 +    }
   1.462 +  }
   1.463 +  drawPieceInPF(v, p->x, y, p->theta, color);
   1.464 +  v->trailY = y;
   1.465 +}
   1.466 +
   1.467 +void drawShadow(LJView *v) {
   1.468 +  const LJField *const p = v->field;
   1.469 +  int y = ljitofix(p->hardDropY);
   1.470 +  const int color = SHADOW_BLOCK;
   1.471 +  drawPieceInPF(v, p->x, y, p->theta, color);
   1.472 +}
   1.473 +
   1.474 +void drawNextPieces(LJView *v) {
   1.475 +  int baseX = v->plat->baseX;
   1.476 +  int baseY = v->plat->skin->baseY - v->plat->skin->pfElev;
   1.477 +  int blkW = v->plat->skin->blkW;
   1.478 +  int blkH = v->plat->skin->blkH;
   1.479 +  int holdPieceColor = v->field->alreadyHeld
   1.480 +                       ? 0x80
   1.481 +                       : pieceColors[v->field->holdPiece];
   1.482 +  int ceil = v->field->ceiling;
   1.483 +  
   1.484 +  BITMAP *sc = v->plat->skin->fullback;
   1.485 +
   1.486 +  if (v->frontDirty & LJ_DIRTY_NEXT) {
   1.487 +    int holdW = blkW * 2 / 3;
   1.488 +    int holdH = blkH * 2 / 3;
   1.489 +    int holdX = baseX + blkW;
   1.490 +    int holdY = baseY - (ceil - 1) * blkH - 4 * holdH;
   1.491 +    
   1.492 +    // Move the hold piece within the screen
   1.493 +    if (holdY < 0) {
   1.494 +      holdY = 0;
   1.495 +    }
   1.496 +  
   1.497 +    // Draw hold piece
   1.498 +    blit(v->plat->skin->bg, sc,
   1.499 +         holdX, holdY,
   1.500 +         holdX, holdY,
   1.501 +         holdW * 4, holdH * 2);
   1.502 +    drawPiece(v, sc,
   1.503 +              v->field->holdPiece, holdX, holdY + 4 * holdH, 4,
   1.504 +              holdPieceColor, holdW, holdH);
   1.505 +    blit(sc, screen,
   1.506 +         holdX, holdY,
   1.507 +         holdX, holdY,
   1.508 +         holdW * 4, holdH * 2);
   1.509 +  }
   1.510 +  // Draw next pieces
   1.511 +
   1.512 +  switch (v->plat->skin->nextPos) {
   1.513 +  case LJNEXT_RIGHT:
   1.514 +  case LJNEXT_RIGHT_TAPER:
   1.515 +    if (v->frontDirty & LJ_DIRTY_NEXT) {
   1.516 +      int y = baseY - ceil * blkH;
   1.517 +      int x = baseX + LJ_PF_WID * blkW;
   1.518 +      int w = blkW;
   1.519 +      int h = blkH;
   1.520 +      for (int i = 1; i <= v->nextPieces; ++i) {
   1.521 +        int piece = v->field->curPiece[i];
   1.522 +
   1.523 +        blit(v->plat->skin->bg, sc, x, y, x, y, w * 4, h * 2);
   1.524 +        if (!v->hideNext) {
   1.525 +          drawPiece(v, sc,
   1.526 +                    piece, x, y + 4 * h, 4,
   1.527 +                    pieceColors[piece], w, h);
   1.528 +        }
   1.529 +        blit(sc, screen, x, y, x, y, w * 4, h * 2);
   1.530 +        y += 8 + h * 2;
   1.531 +        if (v->plat->skin->nextPos == LJNEXT_RIGHT_TAPER) {
   1.532 +          --h;
   1.533 +          --w;
   1.534 +        }
   1.535 +      }
   1.536 +    }
   1.537 +    break;
   1.538 +    
   1.539 +  case LJNEXT_TOP:
   1.540 +    if (v->frontDirty & LJ_DIRTY_NEXT) {
   1.541 +      int y = baseY - (ceil + 2) * blkH - 8;
   1.542 +      int x = baseX + 4 * blkW;
   1.543 +      int blitX = x, blitY = y;
   1.544 +
   1.545 +      blit(v->plat->skin->bg, sc, x, y, x, y, blkW * 8, blkH * 2);
   1.546 +      if (!v->hideNext) {
   1.547 +        if (v->nextPieces >= 1) {
   1.548 +          int piece = v->field->curPiece[1];
   1.549 +          drawPiece(v, sc,
   1.550 +                    piece, x, y + 4 * blkH, 4,
   1.551 +                    pieceColors[piece], blkW, blkH);
   1.552 +        }
   1.553 +        if (v->nextPieces >= 2) {
   1.554 +          int piece = v->field->curPiece[2];
   1.555 +          x += 4 * blkW;
   1.556 +          drawPiece(v, sc,
   1.557 +                    piece, x, y + 3 * blkH, 4,
   1.558 +                    pieceColors[piece], blkW/ 2, blkH / 2);
   1.559 +        }
   1.560 +        if (v->nextPieces >= 3) {
   1.561 +          int piece = v->field->curPiece[3];
   1.562 +          x += 2 * blkW;
   1.563 +          drawPiece(v, sc,
   1.564 +                    piece, x, y + 3 * blkH, 4,
   1.565 +                    pieceColors[piece], blkW / 2, blkH / 2);
   1.566 +        }
   1.567 +      }
   1.568 +      blit(sc, screen, blitX, blitY, blitX, blitY, blkW * 8, blkH * 2);
   1.569 +    }
   1.570 +    break;
   1.571 +  }
   1.572 +
   1.573 +  if (v->plat->nextAbove && !v->hideNext) {
   1.574 +    int row = (v->field->hardDropY + 4);
   1.575 +    int x = (1 + v->field->x) * blkW;
   1.576 +    for (int i = 1;
   1.577 +         i <= v->plat->nextAbove
   1.578 +         && row <= v->field->ceiling - 2;
   1.579 +         ++i) {
   1.580 +      int y = (LJ_PF_VIS_HT - row) * blkH;
   1.581 +      int piece = v->field->curPiece[i];
   1.582 +
   1.583 +      drawPiece(v, v->plat->back,
   1.584 +                piece, x, y, 4,
   1.585 +                pieceColors[piece], blkW / 2, blkH / 2);
   1.586 +      v->backDirty |= 3 << row;
   1.587 +      row += 2;
   1.588 +    }
   1.589 +  }
   1.590 +  v->frontDirty &= ~LJ_DIRTY_NEXT;
   1.591 +}
   1.592 +
   1.593 +void drawScore(LJView *v) {
   1.594 +  int gameTime = v->field->gameTime;
   1.595 +  int seconds = gameTime / 60;
   1.596 +  int minutes = seconds / 60;
   1.597 +  int baseX = v->plat->baseX;
   1.598 +  int tpm = -1;
   1.599 +  int spawnLeft = v->plat->skin->blkW * LJ_SPAWN_X + baseX;
   1.600 +  int pfRight = v->plat->skin->blkW * LJ_PF_WID + baseX;
   1.601 +  BITMAP *sc = v->plat->skin->fullback;
   1.602 +
   1.603 +  if (withPics >= 0) {
   1.604 +    if (v->field->nPieces != withPics) {
   1.605 +      saveScreen(v->field->nPieces);
   1.606 +    }
   1.607 +    withPics = v->field->nPieces;
   1.608 +  }
   1.609 +
   1.610 +  if (v->nLockTimes >= 2 ) {
   1.611 +    int time = v->lockTime[0] - v->lockTime[v->nLockTimes - 1];
   1.612 +    if (time > 0) {
   1.613 +      tpm = 3600 * (v->nLockTimes - 1) / time;
   1.614 +    }
   1.615 +  }
   1.616 +
   1.617 +  if (v->frontDirty & LJ_DIRTY_SCORE) {
   1.618 +    switch (v->plat->skin->nextPos) {
   1.619 +    case LJNEXT_TOP:
   1.620 +      blit(v->plat->skin->bg, sc,
   1.621 +           pfRight, 72,
   1.622 +           pfRight, 72,
   1.623 +           112, 136);
   1.624 +      
   1.625 +      textout_ex(sc, aver32, "Lines:", pfRight, 72, fgColor, -1);
   1.626 +      textprintf_right_ex(sc, aver32, pfRight + 104, 102, fgColor, -1,
   1.627 +                          "%d", v->field->lines);
   1.628 +      textout_ex(sc, aver32, "Score:", pfRight, 142, fgColor, -1);
   1.629 +      textprintf_right_ex(sc, aver32, pfRight + 104, 172, fgColor, -1,
   1.630 +                          "%d", v->field->score);
   1.631 +      textout_ex(sc, aver32, "Level:", pfRight, 212, fgColor, -1);
   1.632 +      textprintf_right_ex(sc, aver32, pfRight + 104, 242, fgColor, -1,
   1.633 +                          "%d", v->field->speedState.level);
   1.634 +      blit(sc, screen,
   1.635 +           pfRight, 72,
   1.636 +           pfRight, 72,
   1.637 +           112, 136);
   1.638 +      break;
   1.639 +
   1.640 +    default:
   1.641 +      blit(v->plat->skin->bg, sc, spawnLeft, 12, spawnLeft, 12, 288, 30);
   1.642 +      blit(v->plat->skin->bg, sc,
   1.643 +           spawnLeft, 42, spawnLeft, 42,
   1.644 +           pfRight - spawnLeft, 30);
   1.645 +      textprintf_right_ex(sc, aver32, spawnLeft + 288, 12, fgColor, -1,
   1.646 +                          "Lv. %d", v->field->speedState.level);
   1.647 +      textprintf_ex(sc, aver32, spawnLeft, 12, fgColor, -1,
   1.648 +                    "Lines: %d", v->field->lines);
   1.649 +      textprintf_ex(sc, aver32, spawnLeft, 42, fgColor, -1,
   1.650 +                    "Score: %d", v->field->score);
   1.651 +      blit(sc, screen, spawnLeft, 12, spawnLeft, 12, 288, 60);
   1.652 +      break;
   1.653 +    }
   1.654 +  }
   1.655 +  
   1.656 +  /* If speed is defined, and there is room to draw it, draw it. */
   1.657 +  if (tpm > 0 && v->nextPieces <= 3) {
   1.658 +    blit(v->plat->skin->bg, sc,
   1.659 +         pfRight, 282,
   1.660 +         pfRight, 282,
   1.661 +         104, 60);
   1.662 +    textout_ex(sc, aver32, "Speed:", pfRight, 282, fgColor, -1);
   1.663 +    textprintf_right_ex(sc, aver32, pfRight + 104, 312, fgColor, -1,
   1.664 +                        "%d", tpm);
   1.665 +    blit(sc, screen,
   1.666 +         pfRight, 282,
   1.667 +         pfRight, 282,
   1.668 +         104, 60);
   1.669 +  }
   1.670 +  
   1.671 +  if (v->frontDirty & LJ_DIRTY_NEXT) {
   1.672 +
   1.673 +    // Erase gimmick
   1.674 +    blit(v->plat->skin->bg, sc,
   1.675 +         baseX, v->plat->skin->baseY + 8,
   1.676 +         baseX, v->plat->skin->baseY + 8,
   1.677 +         v->plat->skin->blkW * LJ_PF_WID, 30);
   1.678 +    // Draw gimmick
   1.679 +    if (v->field->gimmick >= 0 && v->field->gimmick < LJGM_N_GIMMICKS) {
   1.680 +      const char *gimmickName = ljGetFourCCName(gimmickNames[v->field->gimmick]);
   1.681 +      textout_centre_ex(sc, aver32,
   1.682 +                        gimmickName ? gimmickName : "Bad gimmick ID",
   1.683 +                        baseX + (LJ_PF_WID / 2) * v->plat->skin->blkW,
   1.684 +                        v->plat->skin->baseY + 8,
   1.685 +                        fgColor, -1);
   1.686 +    blit(sc, screen,
   1.687 +         baseX, v->plat->skin->baseY + 8,
   1.688 +         baseX, v->plat->skin->baseY + 8,
   1.689 +         v->plat->skin->blkW * LJ_PF_WID, 30);
   1.690 +    }
   1.691 +  }
   1.692 +  drawNextPieces(v);
   1.693 +
   1.694 +  blit(v->plat->skin->bg, sc, pfRight, 42, pfRight, 42, 96, 30);
   1.695 +#if 0
   1.696 +  // Use this for DEBUG inspection into a variable
   1.697 +  textprintf_right_ex(sc, aver16, pfRight + 96, 8, fgColor, -1,
   1.698 +                      "%d", v->field->bpmCounter);
   1.699 +#endif
   1.700 +  textprintf_right_ex(sc, aver32, pfRight + 96, 42, fgColor, -1,
   1.701 +                      "%d:%02d", minutes, seconds - 60 * minutes);
   1.702 +  blit(sc, screen, pfRight, 42, pfRight, 42, 96, 30);
   1.703 +
   1.704 +}
   1.705 +
   1.706 +void blitField(LJView *v) {
   1.707 +  int blkH = v->plat->skin->blkH;
   1.708 +  int rowY = v->plat->skin->baseY
   1.709 +             - v->plat->skin->pfElev
   1.710 +             - blkH * v->field->ceiling;
   1.711 +  int x = v->plat->skin->blkW * v->field->leftWall;
   1.712 +  int w = v->plat->skin->blkW * (v->field->rightWall - v->field->leftWall);
   1.713 +
   1.714 +  // Copy each dirty row
   1.715 +  for (int y = v->field->ceiling - 1; y >= 0; --y) {
   1.716 +    if (v->frontDirty & (1 << y)) {
   1.717 +      int top = (LJ_PF_VIS_HT - y - 1) * blkH;
   1.718 +      int ht = 0;
   1.719 +      
   1.720 +      // Find the height of the contiguous rows to blit
   1.721 +      do {
   1.722 +        ht += blkH;
   1.723 +        --y;
   1.724 +      } while ((y >= 0)
   1.725 +               && (v->frontDirty & (1 << y)));
   1.726 +      blit(v->plat->back, screen,
   1.727 +           x, top,
   1.728 +           x + v->plat->baseX, rowY,
   1.729 +           w, ht);
   1.730 +      rowY += ht;
   1.731 +    }
   1.732 +    rowY += blkH;
   1.733 +  }
   1.734 +
   1.735 +  v->frontDirty &= (~0) << LJ_PF_HT;
   1.736 +}
   1.737 +
   1.738 +void saveScreen(int n) {
   1.739 +  BITMAP *b = create_bitmap(SCREEN_W, SCREEN_H);
   1.740 +  if (b) {
   1.741 +    PALETTE pal;
   1.742 +
   1.743 +    get_palette(pal);
   1.744 +    blit(screen, b, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
   1.745 +    if (n < 0) {
   1.746 +      save_bitmap("ljsnap.bmp", b, pal);
   1.747 +    } else {
   1.748 +      char filename[64];
   1.749 +      sprintf(filename, "pics/lj%05d.bmp", n);
   1.750 +      save_bitmap(filename, b, pal);
   1.751 +    }
   1.752 +    destroy_bitmap(b);
   1.753 +  }
   1.754 +}
   1.755 +
   1.756 +#define PRESETS_PER_COL 6
   1.757 +#define N_NONPRESETS 2
   1.758 +#define PRESETS_TOP 140
   1.759 +#define PRESET_COL_WIDTH 250
   1.760 +#define PRESET_ROW_HT 40
   1.761 +
   1.762 +static const char *const nonPresetNames[N_NONPRESETS] = {
   1.763 +  "Full Custom", "Back"
   1.764 +};
   1.765 +
   1.766 +static void getPresetDrawRow(unsigned int preset, int hilite) {
   1.767 +  unsigned int ht = text_height(aver32);
   1.768 +  const char *txt = preset < nLoadedPresets
   1.769 +                    ? loadedPresets[preset].name
   1.770 +                    : nonPresetNames[preset - nLoadedPresets];
   1.771 +  if (!txt) {
   1.772 +    txt = "Bad";
   1.773 +  }
   1.774 +  unsigned int wid = text_length(aver32, txt);
   1.775 +
   1.776 +  int buttonCol = preset / PRESETS_PER_COL;
   1.777 +  int buttonX = 20 + buttonCol * PRESET_COL_WIDTH;
   1.778 +  int buttonY = PRESETS_TOP
   1.779 +                + PRESET_ROW_HT * (preset - buttonCol * PRESETS_PER_COL);
   1.780 +  unsigned int buttonWidth = wid + 16;
   1.781 +
   1.782 +  rectfill(screen,
   1.783 +           buttonX, buttonY,
   1.784 +           buttonX + buttonWidth - 1, buttonY + PRESET_ROW_HT - 1,
   1.785 +           hilite ? hiliteColor : bgColor);
   1.786 +  if (hilite) {
   1.787 +    rect(screen,
   1.788 +         buttonX, buttonY,
   1.789 +         buttonX + buttonWidth - 1, buttonY + PRESET_ROW_HT - 1,
   1.790 +         fgColor);
   1.791 +  }
   1.792 +  textout_ex(screen, aver32, txt,
   1.793 +             8 + buttonX,
   1.794 +             20 + buttonY - ht / 2,
   1.795 +             fgColor, -1);
   1.796 +}
   1.797 +
   1.798 +int getPreset(int lastPreset) {
   1.799 +  LJBits lastKeys = ~0;
   1.800 +  redrawWholeScreen = 1;
   1.801 +
   1.802 +  clear_keybuf();
   1.803 +  if (lastPreset < 0) {
   1.804 +    lastPreset += nLoadedPresets + N_NONPRESETS;
   1.805 +  }
   1.806 +
   1.807 +  for(int done = 0; done == 0; ) {
   1.808 +    if (redrawWholeScreen) {
   1.809 +      redrawWholeScreen = 0;
   1.810 +      clear_to_color(screen, bgColor);
   1.811 +      textout_ex(screen, aver32, "LOCKJAW > Play", 16, 32, fgColor, -1);
   1.812 +      textout_ex(screen, aver32, "Select a scenario:", 16, 90, fgColor, -1);
   1.813 +      
   1.814 +      for (int preset = 0;
   1.815 +            preset < nLoadedPresets + N_NONPRESETS;
   1.816 +            ++preset) {
   1.817 +        getPresetDrawRow(preset, preset == lastPreset);
   1.818 +      }
   1.819 +      textout_ex(screen, aver16, "Arrows: move; Rotate Right: start",
   1.820 +                 16, PRESETS_TOP + 40 * (PRESETS_PER_COL + 1), fgColor, -1);
   1.821 +      textout_ex(screen, aver16, "Coming soon: an editor for these!",
   1.822 +                 16, PRESETS_TOP + 40 * (PRESETS_PER_COL + 1) + 20, fgColor, -1);
   1.823 +    }
   1.824 +    while (keypressed()) {
   1.825 +      int scancode;
   1.826 +      ureadkey(&scancode);
   1.827 +      if (scancode == KEY_PRTSCR) {
   1.828 +        saveScreen(-1);
   1.829 +      }
   1.830 +    }
   1.831 +
   1.832 +    int preset = lastPreset;    
   1.833 +    LJBits keys = menuReadPad();
   1.834 +    LJBits newKeys = keys & ~lastKeys;
   1.835 +
   1.836 +    if ((newKeys & VKEY_ROTL) || wantsClose) {
   1.837 +      preset = nLoadedPresets + N_NONPRESETS - 1;
   1.838 +      ezPlaySample("rotate_wav", 128);
   1.839 +    }
   1.840 +    if ((newKeys & VKEY_ROTR) || wantsClose) {
   1.841 +      done = 1;
   1.842 +      ezPlaySample("line_wav", 128);
   1.843 +    }
   1.844 +
   1.845 +    if (newKeys & VKEY_UP) {
   1.846 +      if (preset <= 0) {
   1.847 +        preset = nLoadedPresets + N_NONPRESETS - 1;
   1.848 +      } else {
   1.849 +        --preset;
   1.850 +      }
   1.851 +      ezPlaySample("shift_wav", 128);
   1.852 +    }
   1.853 +    if (newKeys & VKEY_DOWN) {
   1.854 +      ++preset;
   1.855 +      if (preset >= nLoadedPresets + N_NONPRESETS) {
   1.856 +        preset = 0;
   1.857 +      }
   1.858 +      ezPlaySample("shift_wav", 128);
   1.859 +    }
   1.860 +
   1.861 +    if (newKeys & VKEY_LEFT) {
   1.862 +      if (preset < PRESETS_PER_COL) {
   1.863 +        preset += (((nLoadedPresets + N_NONPRESETS)
   1.864 +                     / PRESETS_PER_COL)
   1.865 +                    )
   1.866 +                   * PRESETS_PER_COL;
   1.867 +        if (preset >= nLoadedPresets + N_NONPRESETS) {
   1.868 +          preset -= PRESETS_PER_COL;
   1.869 +        }
   1.870 +      } else {
   1.871 +        preset -= PRESETS_PER_COL;
   1.872 +      }
   1.873 +      ezPlaySample("shift_wav", 128);
   1.874 +    }
   1.875 +    if (newKeys & VKEY_RIGHT) {
   1.876 +      preset += PRESETS_PER_COL;
   1.877 +      if (preset >= nLoadedPresets + N_NONPRESETS) {
   1.878 +        preset %= PRESETS_PER_COL;
   1.879 +      }
   1.880 +      ezPlaySample("shift_wav", 128);
   1.881 +    }
   1.882 +
   1.883 +    if (preset != lastPreset) {
   1.884 +      vsync();
   1.885 +      getPresetDrawRow(lastPreset, 0);
   1.886 +      getPresetDrawRow(preset, 1);
   1.887 +      lastPreset = preset;
   1.888 +    } else {
   1.889 +      rest(30);
   1.890 +    }
   1.891 +    lastKeys = keys;
   1.892 +  }
   1.893 +
   1.894 +  // Wrap the nonpresets into the negative numbers.
   1.895 +  if (lastPreset >= nLoadedPresets) {
   1.896 +    lastPreset -= (nLoadedPresets + N_NONPRESETS);
   1.897 +  }
   1.898 +
   1.899 +  return lastPreset;
   1.900 +}
   1.901 +
   1.902 +/**
   1.903 + * Pauses the game, returning nonzero if the player wants to quit.
   1.904 + */
   1.905 +int pauseGame(LJPCView *v) {
   1.906 +  int escCount = 0;
   1.907 +  int quit = 0;
   1.908 +  int escHold = curTime;
   1.909 +  redrawWholeScreen = 1;
   1.910 +  
   1.911 +  LJMusic_pause(v->skin->bgm, 1);
   1.912 +  while (escCount < 2 && !quit) {
   1.913 +    LJMusic_poll(v->skin->bgm);
   1.914 +    if (redrawWholeScreen) {
   1.915 +      redrawWholeScreen = 0;
   1.916 +      clear_to_color(screen, pfBgColor);
   1.917 +      textout_centre_ex(screen, aver32, "LOCKJAW: GAME PAUSED",
   1.918 +                        SCREEN_W / 2, 200, pfFgColor, -1);
   1.919 +      textout_centre_ex(screen, aver32, "Press Esc to continue",
   1.920 +                        SCREEN_W / 2, 250, pfFgColor, -1);
   1.921 +      textout_centre_ex(screen, aver32, "or hold Esc to quit",
   1.922 +                        SCREEN_W / 2, 300, pfFgColor, -1);
   1.923 +    }
   1.924 +    
   1.925 +    if (key[KEY_ESC]) {
   1.926 +      if (escHold == 0) {
   1.927 +        escHold = curTime;
   1.928 +      }
   1.929 +      if (curTime - escHold >= 60) {
   1.930 +        quit = 1;
   1.931 +      }
   1.932 +    } else {
   1.933 +      if (escHold) {
   1.934 +        ++escCount;
   1.935 +      }
   1.936 +      escHold = 0;
   1.937 +    }
   1.938 +    rest(30);
   1.939 +    if (wantsClose) {
   1.940 +      quit = 1;
   1.941 +    }
   1.942 +    
   1.943 +  }
   1.944 +  LJMusic_pause(v->skin->bgm, 0);
   1.945 +  redrawWholeScreen = 1;
   1.946 +  clear_keybuf();
   1.947 +  return quit;
   1.948 +}
   1.949 +
   1.950 +
   1.951 +void pcInit(LJView *v, struct LJPrefs *prefs) {
   1.952 +  v->plat->b2bcd1 = 0;
   1.953 +  v->plat->b2bcd2 = 0;
   1.954 +  v->plat->baseX = v->plat->skin->baseX;
   1.955 +  
   1.956 +  // If the player has chosen to use more next pieces than the
   1.957 +  // next piece position can handle, set the number of
   1.958 +  // next pieces for correctness of debrief().
   1.959 +  if (v->nextPieces > 3 && v->plat->skin->nextPos == LJNEXT_TOP) {
   1.960 +    v->nextPieces = 3;
   1.961 +  }
   1.962 +}
   1.963 +
   1.964 +/**
   1.965 + * Redraws everything on the screen.
   1.966 + * Called when needs redraw.
   1.967 + */
   1.968 +void playRedrawScreen(LJView *v) {
   1.969 +  blit(v->plat->skin->bg, screen,
   1.970 +       0, 0,
   1.971 +       0, 0,
   1.972 +       SCREEN_W, SCREEN_H);
   1.973 +  v->frontDirty = ~0;
   1.974 +}
   1.975 +
   1.976 +#if 0
   1.977 +void startingAniWantsSkip(LJView *v) {
   1.978 +  LJInput unusedIn;
   1.979 +  addKeysToInput(&unusedIn, readPad(), v->field, v->control);
   1.980 +}
   1.981 +#endif
   1.982 +
   1.983 +void playSampleForTetromino(int piece);
   1.984 +
   1.985 +void restPollingMusic(int nFrames, LJMusic *bgm) {
   1.986 +  nFrames += curTime;
   1.987 +  while (curTime - nFrames < 0) {
   1.988 +    if (bgm) {
   1.989 +      LJMusic_poll(bgm);
   1.990 +    }
   1.991 +  }
   1.992 +}
   1.993 +
   1.994 +extern int bgmReadyGo;
   1.995 +void startingAnimation(LJView *v) {
   1.996 +  int readyGoX = v->plat->baseX + v->plat->skin->blkW * LJ_PF_WID / 2;
   1.997 +  int readyGoY = v->plat->skin->baseY
   1.998 +                 - v->plat->skin->pfElev
   1.999 +                 - v->plat->skin->blkH
  1.1000 +                   * v->field->ceiling * 3 / 5;
  1.1001 +
  1.1002 +  clear_keybuf();
  1.1003 +  v->backDirty = 0;
  1.1004 +  
  1.1005 +  playRedrawScreen(v);
  1.1006 +  blitField(v);
  1.1007 +  textout_centre_ex(screen, aver32, "Ready",
  1.1008 +                    readyGoX, readyGoY, pfFgColor, -1);
  1.1009 +  
  1.1010 +  ezPlaySample("ready_wav", 128);
  1.1011 +  restPollingMusic(36, bgmReadyGo ? v->plat->skin->bgm : NULL);
  1.1012 +  v->frontDirty = ~0;
  1.1013 +  
  1.1014 +  if (!wantsClose) {
  1.1015 +    blitField(v);
  1.1016 +    textout_centre_ex(screen, aver32, "GO!",
  1.1017 +                    readyGoX, readyGoY, pfFgColor, -1);
  1.1018 +    drawScore(v);
  1.1019 +
  1.1020 +    ezPlaySample("go_wav", 128);
  1.1021 +    v->frontDirty = ~0;
  1.1022 +    restPollingMusic(12, bgmReadyGo ? v->plat->skin->bgm : NULL);
  1.1023 +  }
  1.1024 +  if (!wantsClose) {
  1.1025 +    playSampleForTetromino(v->field->curPiece[1]);
  1.1026 +    restPollingMusic(24, bgmReadyGo ? v->plat->skin->bgm : NULL);
  1.1027 +  }
  1.1028 +}
  1.1029 +
  1.1030 +
  1.1031 +static void gameOverAnimation(const LJPCView *const v, const LJField *p, int won) {
  1.1032 +  int ceiling = p->ceiling;
  1.1033 +  int left = v->baseX + p->leftWall * v->skin->blkW;
  1.1034 +  int right = v->baseX + p->rightWall * v->skin->blkW - 1;
  1.1035 +
  1.1036 +  ezPlaySample("sectionup_wav", 0);
  1.1037 +  if (!won) {
  1.1038 +    ezPlaySample("gameover_wav", 256);
  1.1039 +  } else {
  1.1040 +    ezPlaySample("win_wav", 256);
  1.1041 +  }
  1.1042 +
  1.1043 +  for (int t = ceiling + v->skin->blkH - 2; t >= 0; --t) {
  1.1044 +
  1.1045 +    // FIXME: vsync doesn't work on Vista
  1.1046 +    vsync();
  1.1047 +    for (int row = ceiling - 1; row >= 0; --row) {
  1.1048 +      int ysub = t - row;
  1.1049 +
  1.1050 +      if (ysub >= 0 && ysub < v->skin->blkH) {
  1.1051 +        int y = v->skin->baseY - v->skin->pfElev
  1.1052 +                - row * v->skin->blkH - ysub - 1;
  1.1053 +        hline(screen, left, y, right, pfBgColor);
  1.1054 +      }
  1.1055 +    }
  1.1056 +    if (wantsClose) {
  1.1057 +      t = 0;
  1.1058 +    }
  1.1059 +  }
  1.1060 +}
  1.1061 +
  1.1062 +#define MENU_COPR_NOTICE_LINES 4
  1.1063 +const char *const menuCoprNotice[MENU_COPR_NOTICE_LINES] = {
  1.1064 +  "Copr. 2006-2008 Damian Yerrick",
  1.1065 +  "Not sponsored or endorsed by The Tetris Company.",
  1.1066 +  "LOCKJAW comes with ABSOLUTELY NO WARRANTY.  This is free software, and you are",
  1.1067 +  "welcome to redistribute it under certain conditions as described in GPL.txt."
  1.1068 +};
  1.1069 +
  1.1070 +static BITMAP *buildTitleScreen(void) {
  1.1071 +  BITMAP *back = create_system_bitmap(SCREEN_W, SCREEN_H);
  1.1072 +  if (!back) {
  1.1073 +    return NULL;
  1.1074 +  }
  1.1075 +
  1.1076 +  // Gradient from (0, 0, 0) to (0, 0, 153)
  1.1077 +  for (int y = 0; y < 192; ++y) {
  1.1078 +    for (int x = -((y * 13) & 0x1F);
  1.1079 +          x < SCREEN_W;
  1.1080 +          x += 32) {
  1.1081 +      int colValue = y + ((rand() & 0x7000) >> 12);
  1.1082 +      int c = makecol(0, 0, 153 * colValue / 192);
  1.1083 +      hline(back, x, y, x + 31, c);
  1.1084 +    }
  1.1085 +  }
  1.1086 +  
  1.1087 +  // Gradient from (102, 51, 0) to (204, 102, 0)
  1.1088 +  for (int y = 192; y < 384; ++y) {
  1.1089 +    for (int x = -((y * 13) & 0x1F);
  1.1090 +          x < SCREEN_W;
  1.1091 +          x += 32) {
  1.1092 +      int colValue = y + ((rand() & 0x7800) >> 11);
  1.1093 +      int c = makecol(102 * colValue / 192, 51 * colValue / 192, 0);
  1.1094 +      hline(back, x, y, x + 31, c);
  1.1095 +    }
  1.1096 +  }
  1.1097 +  
  1.1098 +  // Gradient from (204, 102, 0) to (255, 128, 0)
  1.1099 +  for (int y = 384; y < SCREEN_H; ++y) {
  1.1100 +    for (int x = -((y * 13) & 0x1F);
  1.1101 +          x < SCREEN_W;
  1.1102 +          x += 32) {
  1.1103 +      int colValue = y - 400 + ((rand() & 0x7C00) >> 10);
  1.1104 +      if (colValue > 600 - 384) {
  1.1105 +        colValue = 600 - 384;
  1.1106 +      }
  1.1107 +      int c = makecol(204 + 50 * colValue / (600 - 384),
  1.1108 +                      102 + 25 * colValue / (600 - 384),
  1.1109 +                      0);
  1.1110 +      hline(back, x, y, x + 31, c);
  1.1111 +    }
  1.1112 +  }
  1.1113 +
  1.1114 +  DATAFILE *obj = find_datafile_object(dat, "arttitle_bmp");
  1.1115 +  BITMAP *logo = obj ? obj->dat : NULL;
  1.1116 +  obj = find_datafile_object(dat, "arttitle_pal");
  1.1117 +  const RGB *pal = obj ? obj->dat : NULL;
  1.1118 +  
  1.1119 +  if (logo && pal) {
  1.1120 +    set_palette(pal);
  1.1121 +    draw_sprite(back, logo,
  1.1122 +                (SCREEN_W - logo->w) / 2, (384 - logo->h) / 2);
  1.1123 +    //unselect_palette();
  1.1124 +  }
  1.1125 +
  1.1126 +  textout_centre_ex(back, aver32, "Arrows: change; Enter: choose",
  1.1127 +                    SCREEN_W / 2, 440,
  1.1128 +                    0, -1);
  1.1129 +                    
  1.1130 +  textout_centre_ex(back, aver32, "LOCKJAW: The Reference "LJ_VERSION,
  1.1131 +                    SCREEN_W / 2, SCREEN_H - 40,
  1.1132 +                    0, -1);
  1.1133 +
  1.1134 +  return back;
  1.1135 +}
  1.1136 +
  1.1137 +enum {
  1.1138 +  TITLE_EXIT = 0,
  1.1139 +  TITLE_PLAY,
  1.1140 +  TITLE_REPLAY,
  1.1141 +  TITLE_OPTIONS,
  1.1142 +  TITLE_SKIN,
  1.1143 +  TITLE_KEYS,
  1.1144 +  N_TITLE_ACTIONS
  1.1145 +};
  1.1146 +
  1.1147 +static const char *titleActions[N_TITLE_ACTIONS] = {
  1.1148 +  [TITLE_EXIT] = "Exit",
  1.1149 +  [TITLE_PLAY] = "Play",
  1.1150 +  [TITLE_REPLAY] = "Replay",
  1.1151 +  [TITLE_SKIN] = "Skin...",
  1.1152 +  [TITLE_OPTIONS] = "Options...",
  1.1153 +  [TITLE_KEYS] = "Game Keys..."
  1.1154 +};
  1.1155 +/*
  1.1156 +  0: Exit
  1.1157 +  1: Play
  1.1158 +  2
  1.1159 +*/
  1.1160 +int title(void) {
  1.1161 +
  1.1162 +  // don't even draw if the player is trying to close the window
  1.1163 +  if (wantsClose) {
  1.1164 +    return 0;
  1.1165 +  }
  1.1166 +
  1.1167 +  BITMAP *back = buildTitleScreen();
  1.1168 +  LJBits lastKeys = ~0;
  1.1169 +  int redraw = 1;
  1.1170 +  int choice = 1;
  1.1171 +
  1.1172 +  if (!back) {
  1.1173 +    alert("Not enough memory to display the title screen.",
  1.1174 +          "(If you don't even have RAM for a title screen,",
  1.1175 +          "then what do you have RAM for?)",
  1.1176 +          "Exit", 0, 13, 0);
  1.1177 +    return 0;
  1.1178 +  }
  1.1179 +  
  1.1180 +  redrawWholeScreen = 1;
  1.1181 +
  1.1182 +  for(int done = 0; done == 0; ) {
  1.1183 +    if (redrawWholeScreen) {
  1.1184 +      redrawWholeScreen = 0;
  1.1185 +      blit(back, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
  1.1186 +      redraw = 1;
  1.1187 +    }
  1.1188 +    if (redraw) {
  1.1189 +      int dehilite = makecol(221, 153, 85);
  1.1190 +      int white = makecol(255, 255, 255);
  1.1191 +      redraw = 0;
  1.1192 +      vsync();
  1.1193 +      blit(back, screen, 0, 400, 0, 400, SCREEN_W, 30);
  1.1194 +      textout_centre_ex(screen, aver32,
  1.1195 +                        titleActions[choice],
  1.1196 +                        SCREEN_W / 2, 400, white, -1);
  1.1197 +      textout_centre_ex(screen, aver32,
  1.1198 +                        titleActions[choice == 0 ? N_TITLE_ACTIONS - 1 : choice - 1],
  1.1199 +                        SCREEN_W / 2 - 160, 400, dehilite, -1);
  1.1200 +      textout_centre_ex(screen, aver32,
  1.1201 +                        titleActions[choice == N_TITLE_ACTIONS - 1 ? 0 : choice + 1],
  1.1202 +                        SCREEN_W / 2 + 160, 400, dehilite, -1);
  1.1203 +    }
  1.1204 +
  1.1205 +    while (keypressed()) {
  1.1206 +      int scancode;
  1.1207 +      ureadkey(&scancode);
  1.1208 +      if (scancode == KEY_PRTSCR) {
  1.1209 +        saveScreen(-1);
  1.1210 +      }
  1.1211 +    }
  1.1212 +
  1.1213 +    LJBits keys = menuReadPad();
  1.1214 +    LJBits newKeys = keys & ~lastKeys;
  1.1215 +
  1.1216 +    if (newKeys & VKEY_LEFT) {
  1.1217 +      --choice;
  1.1218 +      redraw = 1;
  1.1219 +      ezPlaySample("shift_wav", 128);
  1.1220 +    }
  1.1221 +    if (newKeys & VKEY_RIGHT) {
  1.1222 +      ++choice;
  1.1223 +      redraw = 1;
  1.1224 +      ezPlaySample("shift_wav", 128);
  1.1225 +    }
  1.1226 +    if (newKeys & VKEY_ROTL) {
  1.1227 +      choice = 0;
  1.1228 +      redraw = 1;
  1.1229 +      ezPlaySample("rotate_wav", 128);
  1.1230 +    }
  1.1231 +    if (newKeys & VKEY_ROTR) {
  1.1232 +      done = 1;
  1.1233 +      ezPlaySample("line_wav", 128);
  1.1234 +    }
  1.1235 +    if (choice < 0) {
  1.1236 +      choice += N_TITLE_ACTIONS;
  1.1237 +    }
  1.1238 +    if (choice >= N_TITLE_ACTIONS) {
  1.1239 +      choice -= N_TITLE_ACTIONS;
  1.1240 +    }
  1.1241 +
  1.1242 +    lastKeys = keys;
  1.1243 +    
  1.1244 +    if (!redraw) {
  1.1245 +      rest(30);
  1.1246 +    }
  1.1247 +    if (wantsClose) {
  1.1248 +      done = 1;
  1.1249 +      choice = 0;
  1.1250 +    }
  1.1251 +  }
  1.1252 +  destroy_bitmap(back);
  1.1253 +  return choice;
  1.1254 +}
  1.1255 +
  1.1256 +
  1.1257 +void setupWindow(void) {
  1.1258 +  set_window_title("LOCKJAW");
  1.1259 +  bgColor = makecol(255, 255, 255);
  1.1260 +  pfFgColor = bgColor;
  1.1261 +  hiliteColor = makecol(255, 255, 204);
  1.1262 +  refreshRate = get_refresh_rate();
  1.1263 +}
  1.1264 +
  1.1265 +void drawCoprNotice(void) {
  1.1266 +  clear_to_color(screen, pfBgColor);
  1.1267 +  for (int i = 0; i < MENU_COPR_NOTICE_LINES; ++i) {
  1.1268 +    textout_ex(screen, font, menuCoprNotice[i],
  1.1269 +               16, 580 + 12 * (i - MENU_COPR_NOTICE_LINES),
  1.1270 +               pfFgColor, -1);
  1.1271 +  }
  1.1272 +}
  1.1273 +
  1.1274 +
  1.1275 +/**
  1.1276 + * Destroys the back buffers for both playfields.
  1.1277 + */
  1.1278 +static void destroyBackBuf(LJPCView *plat) {
  1.1279 +  for (int i = 0; i < MAX_PLAYERS; ++i) {
  1.1280 +    if (plat->back) {
  1.1281 +      destroy_bitmap(plat->back);
  1.1282 +      plat->back = NULL;
  1.1283 +    }
  1.1284 +  }
  1.1285 +  if (plat->skin->fullback) {
  1.1286 +    destroy_bitmap(plat->skin->fullback);
  1.1287 +    plat->skin->fullback = NULL;
  1.1288 +  }
  1.1289 +}
  1.1290 +
  1.1291 +/**
  1.1292 + * Creates the back buffers for both playfields.
  1.1293 + */
  1.1294 +static int createBackBuf(LJView *v) {
  1.1295 +  v->plat->skin->fullback = create_system_bitmap(SCREEN_W, SCREEN_H);
  1.1296 +  if(!v->plat->skin->fullback) {
  1.1297 +    allegro_message("Could not create back buffer.\n");
  1.1298 +    return 0;
  1.1299 +  }
  1.1300 +
  1.1301 +  int blkW = v->plat->skin->blkW;
  1.1302 +  int blkH = v->plat->skin->blkH;
  1.1303 +  int y = v->plat->skin->baseY
  1.1304 +          - v->plat->skin->pfElev
  1.1305 +          - blkH * v->field->ceiling;
  1.1306 +
  1.1307 +  for (int i = 0; i < MAX_PLAYERS; ++i) {
  1.1308 +    int x = v->plat->skin->baseX
  1.1309 +            + blkW * v[i].field->leftWall;
  1.1310 +
  1.1311 +    v[i].plat->back = create_sub_bitmap(v->plat->skin->fullback,
  1.1312 +                                        x + SCREEN_W / 2 * i,
  1.1313 +                                        y,
  1.1314 +                                        v->plat->skin->blkW * LJ_PF_WID,
  1.1315 +                                        v->plat->skin->blkH * LJ_PF_VIS_HT);
  1.1316 +    if (!v[i].plat->back) {
  1.1317 +      destroyBackBuf(v->plat);
  1.1318 +      return 0;
  1.1319 +    }
  1.1320 +  }
  1.1321 +  return 1;
  1.1322 +}
  1.1323 +
  1.1324 +/**
  1.1325 + * Destroys all system bitmaps that a given view owns.
  1.1326 + * Useful before changing screen mode.
  1.1327 + */
  1.1328 +void destroySystemBitmaps(LJPCView *plat) {
  1.1329 +  destroyBackBuf(plat);
  1.1330 +  if (plat->skin->connBlocks) {
  1.1331 +    destroy_bitmap(plat->skin->connBlocks);
  1.1332 +    plat->skin->connBlocks = NULL;
  1.1333 +  }
  1.1334 +}
  1.1335 +
  1.1336 +int openWindow(int windowed)
  1.1337 +{
  1.1338 +  int depth = desktop_color_depth();
  1.1339 +  int card = windowed ? GFX_AUTODETECT_WINDOWED : GFX_AUTODETECT_FULLSCREEN;
  1.1340 +
  1.1341 +  /* Reference implementation for Allegro is not compatible with
  1.1342 +       indexed color. */
  1.1343 +  if (depth < 15) {
  1.1344 +    depth = 16;
  1.1345 +  }
  1.1346 +
  1.1347 +  // Full screen procedure
  1.1348 +  set_color_depth(depth);
  1.1349 +  if (set_gfx_mode(card, skinW, skinH, 0, 0) == 0) {
  1.1350 +    setupWindow();
  1.1351 +    return 0;
  1.1352 +  }
  1.1353 +    
  1.1354 +  // Windows can't tell 16 bit from 15 bit. If desktop color depth is reported as 16, try 15 too.
  1.1355 +  if (depth == 16) {
  1.1356 +    set_color_depth(15);
  1.1357 +    if (set_gfx_mode(card, skinW, skinH, 0, 0) == 0) {
  1.1358 +      setupWindow();
  1.1359 +      return 0;
  1.1360 +    }
  1.1361 +  }
  1.1362 +
  1.1363 +  return -1;
  1.1364 +}
  1.1365 +
  1.1366 +BITMAP *loadConnections(const char *filename, int blkW, int blkH) {
  1.1367 +  BITMAP *src = load_bitmap(filename, NULL);
  1.1368 +  
  1.1369 +  if (!src) {
  1.1370 +    return NULL;
  1.1371 +  }
  1.1372 +  BITMAP *dst = create_system_bitmap(blkW*16, blkH*16);
  1.1373 +  if (!dst) {
  1.1374 +    destroy_bitmap(src);
  1.1375 +    return NULL;
  1.1376 +  }
  1.1377 +  acquire_bitmap(dst);
  1.1378 +  for (unsigned int col = 0; col < 16; ++col) {
  1.1379 +    unsigned int srcXBase = (col & 0x03) * blkW * 2;
  1.1380 +    unsigned int srcYBase = (col >> 2) * blkH * 2;
  1.1381 +    unsigned int dstYBase = col * blkH;
  1.1382 +    for (unsigned int conn = 0; conn < 16; ++conn) {
  1.1383 +      unsigned int dstXBase = conn * blkW;
  1.1384 +      unsigned int topSegY = (conn & CONNECT_U) ? blkH : 0;
  1.1385 +      unsigned int botSegY = (conn & CONNECT_D) ? blkH/2 : 3*blkH/2;
  1.1386 +      unsigned int leftSegX = (conn & CONNECT_L) ? blkW : 0;
  1.1387 +      unsigned int rightSegX = (conn & CONNECT_R) ? blkW/2 : 3*blkW/2;
  1.1388 +      blit(src, dst,
  1.1389 +           srcXBase + leftSegX, srcYBase + topSegY,
  1.1390 +           dstXBase + 0, dstYBase + 0,
  1.1391 +           blkW/2, blkH/2);
  1.1392 +      blit(src, dst,
  1.1393 +           srcXBase + rightSegX, srcYBase + topSegY,
  1.1394 +           dstXBase + blkW/2, dstYBase + 0,
  1.1395 +           blkW/2, blkH/2);
  1.1396 +      blit(src, dst,
  1.1397 +           srcXBase + leftSegX, srcYBase + botSegY,
  1.1398 +           dstXBase + 0, dstYBase + blkH/2,
  1.1399 +           blkW/2, blkH/2);
  1.1400 +      blit(src, dst,
  1.1401 +           srcXBase + rightSegX, srcYBase + botSegY,
  1.1402 +           dstXBase + blkW/2, dstYBase + blkH/2,
  1.1403 +           blkW/2, blkH/2);
  1.1404 +    }
  1.1405 +  }
  1.1406 +  release_bitmap(dst);
  1.1407 +  destroy_bitmap(src);
  1.1408 +  return dst;
  1.1409 +}
  1.1410 +
  1.1411 +void closeWindow(void) {
  1.1412 +  set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
  1.1413 +}
  1.1414 +
  1.1415 +static void mainCleanup(LJPCView *v) {
  1.1416 +  destroyBackBuf(v);
  1.1417 +  if (v->skin->blocks) {
  1.1418 +    destroy_bitmap(v->skin->blocks);
  1.1419 +  }
  1.1420 +  if (v->skin->connBlocks) {
  1.1421 +    destroy_bitmap(v->skin->connBlocks);
  1.1422 +  }
  1.1423 +  LJMusic_delete(v->skin->bgm);
  1.1424 +  if (withSound) {
  1.1425 +    remove_sound();
  1.1426 +  }
  1.1427 +  closeWindow();
  1.1428 +}
  1.1429 +
  1.1430 +/**
  1.1431 + * Resets all skin settings to their initial values
  1.1432 + * so that skins can override them.
  1.1433 + */
  1.1434 +void loadSkinDefaults(LJPCSkin *s) {
  1.1435 +  ustrzcpy(ljblocksSRSName, sizeof(ljblocksSRSName) - 1,
  1.1436 +           "ljblocks.bmp");
  1.1437 +  ustrzcpy(ljblocksSegaName, sizeof(ljblocksSegaName) - 1,
  1.1438 +           "ljblocks-sega.bmp");
  1.1439 +  ustrzcpy(ljconnSRSName, sizeof(ljconnSRSName) - 1,
  1.1440 +           "ljconn.bmp");
  1.1441 +  ustrzcpy(ljconnSegaName, sizeof(ljconnSegaName) - 1,
  1.1442 +           "ljconn-sega.bmp");
  1.1443 +  ustrzcpy(ljbgName, sizeof(ljbgName) - 1,
  1.1444 +           "ljbg.jpg");
  1.1445 +  ustrzcpy(menubgName, sizeof(ljbgName) - 1,
  1.1446 +           "menubg.jpg");
  1.1447 +  ustrzcpy(bgmName, sizeof(bgmName) - 1,
  1.1448 +           "bgm.s3m");
  1.1449 +  ustrzcpy(bgmRhythmName, sizeof(bgmRhythmName) - 1,
  1.1450 +           "bgm-rhythm.s3m");
  1.1451 +  bgmLoopPoint = 0;
  1.1452 +  bgmReadyGo = 0;
  1.1453 +  bgmVolume = 128;
  1.1454 +  bgColor = makecol(255, 255, 255);
  1.1455 +  fgColor = makecol(0, 0, 0);
  1.1456 +  hiliteColor = makecol(255, 255, 204);
  1.1457 +  pfBgColor = makecol(0, 0, 0);
  1.1458 +  pfFgColor = makecol(255, 255, 255);
  1.1459 +  s->blkW = 24;
  1.1460 +  s->blkH = 24;
  1.1461 +  s->transparentPF = 0;
  1.1462 +  s->shiftScale    = 0;
  1.1463 +  s->baseX       = 0;
  1.1464 +  s->nextPos     = 0;
  1.1465 +}
  1.1466 +
  1.1467 +/**
  1.1468 + * Converts a single hexadecimal digit to its value.
  1.1469 + * @param in USASCII/Unicode value of hex digit character
  1.1470 + * @return value
  1.1471 +*/
  1.1472 +int hexDigitValue(int in) {
  1.1473 +  if (in >= '0' && in <= '9') {
  1.1474 +    return in - '0';
  1.1475 +  } else if (in >= 'A' && in <= 'F') {
  1.1476 +    return in - 'A' + 10;
  1.1477 +  } else if (in >= 'a' && in <= 'f') {
  1.1478 +    return in - 'a' + 10;
  1.1479 +  } else {
  1.1480 +    return -1;
  1.1481 +  }
  1.1482 +}
  1.1483 +
  1.1484 +int translateComponent(const char **in, size_t nDigits) {
  1.1485 +  const char *here = *in;
  1.1486 +  int hi = hexDigitValue(*here++);
  1.1487 +  int lo = nDigits > 3 ? hexDigitValue(*here++) : hi;
  1.1488 +  *in = here;
  1.1489 +  if (hi >= 0 && lo >= 0) {
  1.1490 +    return hi * 16 + lo;
  1.1491 +  } else {
  1.1492 +    return -1;
  1.1493 +  }
  1.1494 +}
  1.1495 +
  1.1496 +/**
  1.1497 + * Interprets a hexadecimal color specifier.
  1.1498 + * @param in hexadecimal color specifier, in #ABC or #AABBCC format
  1.1499 + * @return Allegro device-dependent color, in makecol() format
  1.1500 + */
  1.1501 +int translateColor(const char *in) {
  1.1502 +  size_t nDigits = 0;
  1.1503 +
  1.1504 +  // Verify and skip #
  1.1505 +  if (*in != '#') {
  1.1506 +    return -1;
  1.1507 +  }
  1.1508 +  ++in;
  1.1509 +
  1.1510 +  // Determine whether we have a 3-digit color or a 6-digit color
  1.1511 +  for (const char *here = in;
  1.1512 +       hexDigitValue(*here) >= 0;
  1.1513 +       ++here) {
  1.1514 +    ++nDigits;
  1.1515 +  }
  1.1516 +  if (nDigits != 3 && nDigits != 6) {
  1.1517 +    return -1;
  1.1518 +  }
  1.1519 +  
  1.1520 +  int red = translateComponent(&in, nDigits);
  1.1521 +  int green = translateComponent(&in, nDigits);
  1.1522 +  int blue = translateComponent(&in, nDigits);
  1.1523 +  if (red >= 0 && green >= 0 && blue >= 0) {
  1.1524 +    return makecol(red, green, blue);
  1.1525 +  } else {
  1.1526 +    return -1;
  1.1527 +  }
  1.1528 +};
  1.1529 +
  1.1530 +/**
  1.1531 + * Loads skin parameters from the file.
  1.1532 + * @param skinName Name of skin ini file
  1.1533 + */
  1.1534 +int loadSkinFile(LJPCSkin *s, const char *skinName) {
  1.1535 +
  1.1536 +  // Don't use ljfopen here because lj.ini specifies the absolute skin file
  1.1537 +  FILE *fp = fopen(skinName, "rt");
  1.1538 +  char key[1024], var[1024], val[1024], input_buf[1024];
  1.1539 +
  1.1540 +  key[0] = 0;
  1.1541 +  var[0] = 0;
  1.1542 +  val[0] = 0;
  1.1543 +  loadSkinDefaults(s);
  1.1544 +
  1.1545 +  if (!fp) return 0;
  1.1546 +  while(1) {
  1.1547 +    int rval;
  1.1548 +
  1.1549 +    if(!fgets (input_buf, sizeof(input_buf), fp))
  1.1550 +      break;
  1.1551 +    rval = parse_ini_line(input_buf, key, var, val);
  1.1552 +
  1.1553 +    if(!ustrcmp ("ljblocksSRS", var)) {
  1.1554 +      ustrzcpy(ljblocksSRSName, sizeof(ljblocksSRSName) - 1, val);
  1.1555 +    }
  1.1556 +    else if(!ustrcmp ("ljblocksSega", var)) {
  1.1557 +      ustrzcpy(ljblocksSegaName, sizeof(ljblocksSegaName) - 1, val);
  1.1558 +    }
  1.1559 +    else if(!ustrcmp ("ljconnSRS", var)) {
  1.1560 +      ustrzcpy(ljconnSRSName, sizeof(ljconnSRSName) - 1, val);
  1.1561 +    }
  1.1562 +    else if(!ustrcmp ("ljconnSega", var)) {
  1.1563 +      ustrzcpy(ljconnSegaName, sizeof(ljconnSegaName) - 1, val);
  1.1564 +    }
  1.1565 +    else if(!ustrcmp ("bgm", var)) {
  1.1566 +      ustrzcpy(bgmName, sizeof(bgmName) - 1, val);
  1.1567 +    }
  1.1568 +    else if(!ustrcmp ("bgmRhythm", var)) {
  1.1569 +      ustrzcpy(bgmRhythmName, sizeof(bgmRhythmName) - 1, val);
  1.1570 +    }
  1.1571 +    else if(!ustrcmp ("ljbg", var)) {
  1.1572 +      ustrzcpy(ljbgName, sizeof(ljbgName) - 1, val);
  1.1573 +    }
  1.1574 +    else if(!ustrcmp ("bgmLoopPoint", var)) {
  1.1575 +      unsigned long int c = strtoul(val, NULL, 0);
  1.1576 +      if (c >= 0) {
  1.1577 +        bgmLoopPoint = c;
  1.1578 +      }
  1.1579 +    }
  1.1580 +    else if(!ustrcmp ("bgmVolume", var)) {
  1.1581 +      unsigned long int c = strtol(val, NULL, 0);
  1.1582 +      if (c >= 0) {
  1.1583 +        bgmVolume = c;
  1.1584 +      }
  1.1585 +    }
  1.1586 +    else if(!ustrcmp ("bgmReadyGo", var)) {
  1.1587 +      unsigned long int c = strtoul(val, NULL, 0);
  1.1588 +      if (c >= 0) {
  1.1589 +        bgmReadyGo = c;
  1.1590 +      }
  1.1591 +    }
  1.1592 +    else if(!ustrcmp ("pfbgcolor", var)) {
  1.1593 +      int c = translateColor(val);
  1.1594 +      if (c >= 0) {
  1.1595 +        pfBgColor = c;
  1.1596 +      }
  1.1597 +    }
  1.1598 +    else if(!ustrcmp ("pfcolor", var)) {
  1.1599 +      int c = translateColor(val);
  1.1600 +      if (c >= 0) {
  1.1601 +        pfFgColor = c;
  1.1602 +      }
  1.1603 +    }
  1.1604 +    else if(!ustrcmp ("bgcolor", var)) {
  1.1605 +      int c = translateColor(val);
  1.1606 +      if (c >= 0) {
  1.1607 +        bgColor = c;
  1.1608 +      }
  1.1609 +    }
  1.1610 +    else if(!ustrcmp ("color", var)) {
  1.1611 +      int c = translateColor(val);
  1.1612 +      if (c >= 0) {
  1.1613 +        fgColor = c;
  1.1614 +      }
  1.1615 +    }
  1.1616 +    else if(!ustrcmp ("hilitecolor", var)) {
  1.1617 +      int c = translateColor(val);
  1.1618 +      if (c >= 0) {
  1.1619 +        hiliteColor = c;
  1.1620 +      }
  1.1621 +    }
  1.1622 +    else if(!ustrcmp ("blkW", var)) {
  1.1623 +      int c = strtol(val, NULL, 0);
  1.1624 +      if (c >= 0) {
  1.1625 +        s->blkW = c;
  1.1626 +      }
  1.1627 +    }
  1.1628 +    else if(!ustrcmp ("blkH", var)) {
  1.1629 +      int c = strtol(val, NULL, 0);
  1.1630 +      if (c >= 0) {
  1.1631 +        s->blkH = c;
  1.1632 +      }
  1.1633 +    }
  1.1634 +    else if(!ustrcmp ("transparentPF", var)) {
  1.1635 +      int c = atoi(val);
  1.1636 +      if (c >= 0) {
  1.1637 +        s->transparentPF = c;
  1.1638 +      }
  1.1639 +    }
  1.1640 +    else if(!ustrcmp ("shiftScale", var)) {
  1.1641 +      int c = atoi(val);
  1.1642 +      if (c >= 0) {
  1.1643 +        s->shiftScale = c;
  1.1644 +      }
  1.1645 +    }
  1.1646 +    else if(!ustrcmp ("baseX", var)) {
  1.1647 +      int c = atoi(val);
  1.1648 +      if (c >= 0) {
  1.1649 +        s->baseX = c;
  1.1650 +      }
  1.1651 +    }
  1.1652 +    else if(!ustrcmp ("nextPos", var)) {
  1.1653 +      int c = atoi(val);
  1.1654 +      if (c >= 0) {
  1.1655 +        s->nextPos = c;
  1.1656 +      }
  1.1657 +    }
  1.1658 +    else if(!ustrcmp ("wndW", var)) {
  1.1659 +      unsigned long int c = strtol(val, NULL, 0);
  1.1660 +      if (c >= 0) {
  1.1661 +        skinW = c;
  1.1662 +      }
  1.1663 +    }
  1.1664 +    else if(!ustrcmp ("wndH", var)) {
  1.1665 +      unsigned long int c = strtoul(val, NULL, 0);
  1.1666 +      if (c >= 0) {
  1.1667 +        skinH = c;
  1.1668 +      }
  1.1669 +    }
  1.1670 +
  1.1671 +  }
  1.1672 +  fclose(fp);
  1.1673 +  ljpathSetSkinFolder(skinName);
  1.1674 +  return 0;
  1.1675 +}
  1.1676 +
  1.1677 +static void drawProgressSegment(int min, int max) {
  1.1678 +  min = min * SCREEN_W / 100;
  1.1679 +  max = max * SCREEN_W / 100;
  1.1680 +  int blue = makecol(0, 0, 255);
  1.1681 +  rectfill(screen, min, SCREEN_H - 8, max - 1, SCREEN_H - 5, blue);
  1.1682 +  int orange = makecol(255, 128, 0);
  1.1683 +  rectfill(screen, min, SCREEN_H - 4, max - 1, SCREEN_H - 1, orange);
  1.1684 +}
  1.1685 +
  1.1686 +static int loadSkin(LJView *v, const char *skinName) {
  1.1687 +  BITMAP *bmp;
  1.1688 +  const LJRotSystem *rs = rotSystems[v->field->rotationSystem];
  1.1689 +  int colorScheme = rs->colorScheme;
  1.1690 +  
  1.1691 +  rectfill(screen, 0, 592, 799, 599, 0);
  1.1692 +  loadSkinFile(v->plat->skin, skinName);
  1.1693 +
  1.1694 +  destroyBackBuf(v->plat);
  1.1695 +  if (!createBackBuf(v)) {
  1.1696 +    allegro_message("Could not create back buffer.\n");
  1.1697 +    return 1;
  1.1698 +  }
  1.1699 +
  1.1700 +  drawProgressSegment(0, 20);
  1.1701 +  
  1.1702 +  // Load background image
  1.1703 +  char path[PATH_MAX];
  1.1704 +  bmp = ljpathFind_r(path, ljbgName)
  1.1705 +        ? load_bitmap(path, NULL) : NULL;
  1.1706 +  if (v->plat->skin->bg) {
  1.1707 +    destroy_bitmap(v->plat->skin->bg);
  1.1708 +  }
  1.1709 +  if (bmp) {
  1.1710 +    
  1.1711 +    // If the image size doesn't match the window size, resize it
  1.1712 +    if (bmp->w != SCREEN_W || bmp->h != SCREEN_H) {
  1.1713 +      BITMAP *resized = create_bitmap(SCREEN_W, SCREEN_H);
  1.1714 +
  1.1715 +      if (resized) {
  1.1716 +        stretch_blit(bmp, resized, 0, 0, bmp->w, bmp->h,
  1.1717 +                     0, 0, SCREEN_W, SCREEN_H);
  1.1718 +        destroy_bitmap(bmp);
  1.1719 +      }
  1.1720 +      if (bmp) {
  1.1721 +        bmp = resized;
  1.1722 +      } else {
  1.1723 +        allegro_message("Background image \"%s\" resize failed.\n",
  1.1724 +                        ljbgName);
  1.1725 +      }
  1.1726 +    }
  1.1727 +  }
  1.1728 +  if(!bmp) {
  1.1729 +    bmp = create_bitmap(SCREEN_W, SCREEN_H);
  1.1730 +    if (bmp) {
  1.1731 +      allegro_message("Background image \"%s\" not found.\n"
  1.1732 +                      "Using plain background instead.\n",
  1.1733 +                      ljbgName);
  1.1734 +      clear_to_color(bmp, bgColor);
  1.1735 +    } else {
  1.1736 +      allegro_message("Background image \"%s\" not found.\n",
  1.1737 +                      ljbgName);
  1.1738 +      return 0;
  1.1739 +    }
  1.1740 +  }
  1.1741 +  v->plat->skin->bg = bmp;
  1.1742 +  drawProgressSegment(20, 40);
  1.1743 +  
  1.1744 +  // load block images  
  1.1745 +  if (v->plat->skin->blocks) {
  1.1746 +    destroy_bitmap(v->plat->skin->blocks);
  1.1747 +  }
  1.1748 +  bmp = ljpathFind_r(path, colorScheme
  1.1749 +                           ? ljblocksSegaName
  1.1750 +                           : ljblocksSRSName)
  1.1751 +        ? load_bitmap(path, NULL) : NULL;
  1.1752 +  v->plat->skin->blocks = bmp;
  1.1753 +  if(!v->plat->skin->blocks) {
  1.1754 +    allegro_message("Background image \"%s\" not found.\n",
  1.1755 +                    ljbgName);
  1.1756 +    return 0;
  1.1757 +  }
  1.1758 +  drawProgressSegment(40, 60);
  1.1759 +
  1.1760 +  // load connected block images  
  1.1761 +  if (v->plat->skin->connBlocks) {
  1.1762 +    destroy_bitmap(v->plat->skin->connBlocks);
  1.1763 +  }
  1.1764 +  bmp = ljpathFind_r(path, colorScheme
  1.1765 +                           ? ljconnSegaName
  1.1766 +                           : ljconnSRSName)
  1.1767 +        ? loadConnections(path,
  1.1768 +                          v->plat->skin->blkW,
  1.1769 +                          v->plat->skin->blkH)
  1.1770 +        : NULL;
  1.1771 +  v->plat->skin->connBlocks = bmp;
  1.1772 +  drawProgressSegment(60, 80);
  1.1773 +
  1.1774 +  // load music
  1.1775 +  int isRhythm = (v->field->speedState.curve == LJSPD_RHYTHM);
  1.1776 +  if (ljpathFind_r(path, isRhythm ? bgmRhythmName : bgmName)) {
  1.1777 +    LJMusic_load(v->plat->skin->bgm, path);
  1.1778 +  }
  1.1779 +  if (!isRhythm) {
  1.1780 +    LJMusic_setLoop(v->plat->skin->bgm, bgmLoopPoint);
  1.1781 +  }
  1.1782 +  drawProgressSegment(80, 100);
  1.1783 +  return 1;
  1.1784 +}
  1.1785 +
  1.1786 +void drop_mouse(void) {
  1.1787 +  while (mouse_y < SCREEN_H - 25) {
  1.1788 +    position_mouse(mouse_x, mouse_y + 20);
  1.1789 +    rest(10);
  1.1790 +  }
  1.1791 +  ezPlaySample("land_wav", 192);
  1.1792 +  position_mouse(mouse_x, SCREEN_H - 5);
  1.1793 +  ezPlaySample("lock_wav", 192);
  1.1794 +}
  1.1795 +
  1.1796 +int pickReplay(void) {
  1.1797 +  FONT *oldFont = font;
  1.1798 +  font = (FONT *)aver16;
  1.1799 +  install_mouse();
  1.1800 +  int got = file_select_ex("Choose a demo:", demoFilename, "ljm", sizeof(demoFilename), 600, 400);
  1.1801 +  drop_mouse();
  1.1802 +  remove_mouse();
  1.1803 +  font = oldFont;
  1.1804 +  return got ? 0 : -1;
  1.1805 +}
  1.1806 +
  1.1807 +void badReplay(void) {
  1.1808 +  acquire_screen();
  1.1809 +  clear_to_color(screen, bgColor);
  1.1810 +  textout_ex(screen, aver32, "The demo", 100, 100, fgColor, -1);
  1.1811 +  textout_ex(screen, aver16, demoFilename, 100, 130, fgColor, -1);
  1.1812 +  textout_ex(screen, aver32, "could not be played because it was", 100, 150, fgColor, -1);
  1.1813 +  textout_ex(screen, aver32, "recorded with a different version", 100, 180, fgColor, -1);
  1.1814 +  textout_ex(screen, aver32, "of LOCKJAW software.", 100, 210, fgColor, -1);
  1.1815 +  release_screen();
  1.1816 +
  1.1817 +  LJBits lastKeys = ~0;
  1.1818 +  LJBits keys, newKeys = 0;
  1.1819 +  
  1.1820 +  do {
  1.1821 +    keys = menuReadPad();
  1.1822 +    newKeys = keys & ~lastKeys;
  1.1823 +    lastKeys = keys;
  1.1824 +    rest(30);
  1.1825 +  } while (!(newKeys & (VKEY_ROTL | VKEY_ROTR)));
  1.1826 +}
  1.1827 +
  1.1828 +int pickSkin(void) {
  1.1829 +  FONT *oldFont = font;
  1.1830 +  font = (FONT *)aver16;
  1.1831 +  install_mouse();
  1.1832 +  int got = file_select_ex("Choose a skin:", skinName, "skin", sizeof(skinName), 600, 400);
  1.1833 +  drop_mouse();
  1.1834 +  remove_mouse();
  1.1835 +  font = oldFont;
  1.1836 +  return got ? 0 : -1;
  1.1837 +}
  1.1838 +
  1.1839 +void calcElev(LJView *v) {
  1.1840 +  int blkH = v->plat->skin->blkH;
  1.1841 +  int ceiling = v->field->ceiling;
  1.1842 +  int elev = (LJ_PF_VIS_HT - ceiling) * blkH;
  1.1843 +
  1.1844 +  if (elev > 480 - ceiling * blkH) {
  1.1845 +    elev = 480 - ceiling * blkH;
  1.1846 +  }
  1.1847 +  if (elev < 0) {
  1.1848 +    elev = 0;
  1.1849 +  }
  1.1850 +  v->plat->skin->pfElev = elev;
  1.1851 +}
  1.1852 +
  1.1853 +int main(const int argc, const char *const *argv) {
  1.1854 +  const char *cmdLineDemo = NULL;
  1.1855 +  int lastPreset = -2;  // start out with full custom
  1.1856 +  LJPCSkin skin = {
  1.1857 +    .baseY = 552,
  1.1858 +    .blkW = 24,
  1.1859 +    .blkH = 24
  1.1860 +  };
  1.1861 +  LJField p[MAX_PLAYERS];
  1.1862 +  LJControl control[2] = {
  1.1863 +    {
  1.1864 +      .replaySrc = 0,
  1.1865 +      .replayDst = 0
  1.1866 +    }
  1.1867 +  };
  1.1868 +  LJPCView platView[MAX_PLAYERS] = {
  1.1869 +    {
  1.1870 +      .skin = &skin
  1.1871 +    },
  1.1872 +    {
  1.1873 +      .skin = &skin
  1.1874 +    }
  1.1875 +  };
  1.1876 +  LJView mainView[MAX_PLAYERS] = {
  1.1877 +    {
  1.1878 +      .field = &p[0],
  1.1879 +      .control = &control[0],
  1.1880 +      .plat = &platView[0],
  1.1881 +      .backDirty = ~0
  1.1882 +    },
  1.1883 +    {
  1.1884 +      .field = &p[1],
  1.1885 +      .control = &control[1],
  1.1886 +      .plat = &platView[1],
  1.1887 +      .backDirty = ~0,
  1.1888 +    }
  1.1889 +  };
  1.1890 +  
  1.1891 +  struct LJPrefs prefs = {
  1.1892 +    .number = {
  1.1893 +      [OPTIONS_TRAILS] = 1,
  1.1894 +      [OPTIONS_AUTO_PAUSE] = 1,
  1.1895 +      [OPTIONS_AUTO_RECORD] = 0,
  1.1896 +      [OPTIONS_WINDOWED] = 1
  1.1897 +    }
  1.1898 +  };
  1.1899 +
  1.1900 +  // as of 0.46, we're starting to make it a bit more 2-player-clean
  1.1901 +  int nPlayers = 1;  
  1.1902 +
  1.1903 +  allegro_init();
  1.1904 +  ljpathInit(argc > 0 ? argv[0] : ".");
  1.1905 +  install_timer();
  1.1906 +  initOptions(prefs.number);
  1.1907 +  loadOptions(&prefs);
  1.1908 +  loadSkinFile(&skin, skinName);
  1.1909 +
  1.1910 +  if (argc > 1) {
  1.1911 +    if (argv[1][0] == '-') {
  1.1912 +      if (!ustrcmp("--help", argv[1])
  1.1913 +          || !ustrcmp("-h", argv[1])) {
  1.1914 +        allegro_message("Usage: lj [DEMOFILE]\n");
  1.1915 +        return 0;
  1.1916 +      }
  1.1917 +    } else {
  1.1918 +      cmdLineDemo = argv[1];
  1.1919 +    }
  1.1920 +  }
  1.1921 +
  1.1922 +  if (openWindow(prefs.number[OPTIONS_WINDOWED]) != 0) {
  1.1923 +    allegro_message("LOCKJAW fatal error: Could not open an %dx%d pixel %s.\n"
  1.1924 +                    "Trying %s next time.\n",
  1.1925 +                    skinW, skinH,
  1.1926 +                    prefs.number[OPTIONS_WINDOWED] ? "window": "screen mode",
  1.1927 +                    prefs.number[OPTIONS_WINDOWED] ? "the full screen": "a window");
  1.1928 +    prefs.number[OPTIONS_WINDOWED] = !prefs.number[OPTIONS_WINDOWED];
  1.1929 +    saveOptions(&prefs);
  1.1930 +    return EXIT_FAILURE;
  1.1931 +  }
  1.1932 +  drawCoprNotice();
  1.1933 +  LOCK_FUNCTION(incCurTime);
  1.1934 +  LOCK_VARIABLE(curTime);
  1.1935 +  install_int_ex(incCurTime, BPM_TO_TIMER(LJ_TICK_RATE));
  1.1936 +  
  1.1937 +  jpgalleg_init();
  1.1938 +  set_color_conversion(COLORCONV_NONE);
  1.1939 +  {
  1.1940 +    char path[PATH_MAX];
  1.1941 +    if (ljpathFind_r(path, "lj.dat")) {
  1.1942 +      dat = load_datafile(path);
  1.1943 +    }
  1.1944 +  }
  1.1945 +  set_color_conversion(COLORCONV_TOTAL);
  1.1946 +  if(!dat) {
  1.1947 +    closeWindow();
  1.1948 +    allegro_message("LOCKJAW fatal error: Could not load datafile lj.dat\n");
  1.1949 +    return 1;
  1.1950 +  }
  1.1951 +
  1.1952 +  {
  1.1953 +    const DATAFILE *aver16dat = find_datafile_object(dat, "Aver16_bmp");
  1.1954 +    aver16 = aver16dat ? aver16dat->dat : font;
  1.1955 +    const DATAFILE *aver32dat = find_datafile_object(dat, "Aver32_bmp");
  1.1956 +    aver32 = aver32dat ? aver32dat->dat : aver16;
  1.1957 +  }
  1.1958 +
  1.1959 +  LOCK_FUNCTION(amnesia);
  1.1960 +  LOCK_VARIABLE(redrawWholeScreen);
  1.1961 +  
  1.1962 +  // If we can be notified on switching out, take this notification. 
  1.1963 +  if (set_display_switch_mode(SWITCH_BACKGROUND) >= 0
  1.1964 +      || set_display_switch_mode(SWITCH_BACKAMNESIA) >= 0) {
  1.1965 +    set_display_switch_callback(SWITCH_OUT, requestPause);
  1.1966 +  }
  1.1967 +  set_display_switch_callback(SWITCH_IN, amnesia);
  1.1968 +
  1.1969 +  install_keyboard();
  1.1970 +  initKeys();
  1.1971 +
  1.1972 +  for (int i = 0; i < MAX_PLAYERS; ++i) {
  1.1973 +    p[i].seed = time(NULL) + 123456789*i;
  1.1974 +  }
  1.1975 +
  1.1976 +  reserve_voices(8, 0);
  1.1977 +  set_volume_per_voice(0);
  1.1978 +#ifdef ALLEGRO_WINDOWS
  1.1979 +  // Under Windows, use the Allegro mixer because on my machine
  1.1980 +  // and probably others, the built-in mixer will replace the very
  1.1981 +  // beginning of one sound with the end of the last sound played
  1.1982 +  // on that voice.
  1.1983 +  withSound = !install_sound(DIGI_DIRECTAMX(0), MIDI_NONE, NULL);
  1.1984 +#else
  1.1985 +  withSound = !install_sound(DIGI_AUTODETECT, MIDI_NONE, NULL);
  1.1986 +#endif
  1.1987 +  skin.bgm = LJMusic_new();
  1.1988 +  {
  1.1989 +    char path[PATH_MAX];
  1.1990 +    if (ljpathFind_r(path, "sound.dat")) {
  1.1991 +      sound_dat = load_datafile(path);
  1.1992 +    }
  1.1993 +  }
  1.1994 +
  1.1995 +  for (int i = 0; i < MAX_PLAYERS; ++i) {
  1.1996 +    unpackOptions(&mainView[i], &prefs);
  1.1997 +  }
  1.1998 +  if (loadSkin(&mainView[0], skinName) < 0) {
  1.1999 +    mainCleanup(platView);
  1.2000 +    return EXIT_FAILURE;
  1.2001 +  } else {
  1.2002 +  
  1.2003 +  }
  1.2004 +
  1.2005 +  if(!skin.blocks) {
  1.2006 +    mainCleanup(platView);
  1.2007 +    allegro_message("Blocks image \"%s\" not found.\n",
  1.2008 +                    p[0].rotationSystem
  1.2009 +                    ? ljblocksSegaName
  1.2010 +                    : ljblocksSRSName);
  1.2011 +    return 1;
  1.2012 +  }
  1.2013 +
  1.2014 +  srand(time(NULL));
  1.2015 +
  1.2016 +  // Wait for copyright notice to be displayed "conspicuously"
  1.2017 +  if (curTime < 180) {
  1.2018 +    rest(3100 - curTime * 16);
  1.2019 +  }
  1.2020 +
  1.2021 +  for (int action = cmdLineDemo ? TITLE_REPLAY : title();
  1.2022 +        action > TITLE_EXIT && !wantsClose;
  1.2023 +        action = title()) {
  1.2024 +    switch (action) {
  1.2025 +    case TITLE_PLAY:
  1.2026 +      for (int preset = getPreset(lastPreset);
  1.2027 +           preset != -1 && !wantsClose;
  1.2028 +           preset = getPreset(lastPreset))
  1.2029 +      {
  1.2030 +        lastPreset = preset;
  1.2031 +        for (int i = 0; i < MAX_PLAYERS; ++i) {
  1.2032 +          unpackOptions(&mainView[i], &prefs);
  1.2033 +        }
  1.2034 +        if (preset >= 0) {
  1.2035 +          presetStart();
  1.2036 +          presetAdd(preset);
  1.2037 +          for (int i = 0; i < MAX_PLAYERS; ++i) {
  1.2038 +            unpackOptions(&mainView[i], &prefs);
  1.2039 +            presetFinish(&mainView[i]);
  1.2040 +          }
  1.2041 +        }
  1.2042 +      // reload the skin
  1.2043 +      if (loadSkin(&mainView[0], skinName) < 0) {
  1.2044 +        mainCleanup(platView);
  1.2045 +        return EXIT_FAILURE;
  1.2046 +      };
  1.2047 +        for (int i = 0; i < nPlayers; ++i) {
  1.2048 +          calcElev(&mainView[0]);
  1.2049 +          pcInit(&mainView[0], &prefs);
  1.2050 +        }
  1.2051 +        wantPause = 0;
  1.2052 +        platView[0].wantRecord = prefs.number[OPTIONS_AUTO_RECORD];
  1.2053 +        LJMusic_start(skin.bgm,
  1.2054 +                      4096,  // mix buffer size
  1.2055 +                      bgmVolume);  // volume scale
  1.2056 +
  1.2057 +        LJView *const players[MAX_PLAYERS] = {&mainView[0], &mainView[1]};
  1.2058 +        play(players, nPlayers);
  1.2059 +        LJMusic_stop(skin.bgm);
  1.2060 +        if (!wantsClose) {
  1.2061 +          gameOverAnimation(&platView[0], &p[0],
  1.2062 +                            control[0].countdown <= 0);
  1.2063 +          debrief(&mainView[0]);
  1.2064 +        }
  1.2065 +      }
  1.2066 +      break;
  1.2067 +
  1.2068 +    case TITLE_REPLAY:
  1.2069 +      {
  1.2070 +        if (cmdLineDemo) {
  1.2071 +          ustrzcpy(demoFilename, sizeof(demoFilename) - 1,
  1.2072 +                   cmdLineDemo);
  1.2073 +          cmdLineDemo = NULL;
  1.2074 +        } else if (pickReplay() < 0) {
  1.2075 +          break;
  1.2076 +        }
  1.2077 +
  1.2078 +        unpackOptions(&mainView[0], &prefs);
  1.2079 +        calcElev(&mainView[0]);
  1.2080 +        pcInit(&mainView[0], &prefs);
  1.2081 +        wantPause = 0;
  1.2082 +        platView[0].wantRecord = 0;
  1.2083 +        LJMusic_start(skin.bgm,
  1.2084 +                      4096,  // mix buffer size
  1.2085 +                      bgmVolume);  // volume scale
  1.2086 +
  1.2087 +        LJView *const players[2] = {&mainView[0], &mainView[1]};
  1.2088 +
  1.2089 +        p->gimmick = -1;  // gimmick must be < 0 to activate replay
  1.2090 +        play(players, 1);
  1.2091 +        LJMusic_stop(skin.bgm);
  1.2092 +        if (p[0].gimmick < 0) {
  1.2093 +          badReplay();
  1.2094 +        } else {
  1.2095 +          if (!wantsClose) {
  1.2096 +            gameOverAnimation(&platView[0], &p[0],
  1.2097 +                              control[0].countdown <= 0);
  1.2098 +            debrief(&mainView[0]);
  1.2099 +          }
  1.2100 +        }
  1.2101 +      }
  1.2102 +      break;
  1.2103 +
  1.2104 +    case TITLE_SKIN:
  1.2105 +      pickSkin();
  1.2106 +
  1.2107 +      // if resolution changed, reopen the window
  1.2108 +      {
  1.2109 +        int oldW = skinW;
  1.2110 +        int oldH = skinH;
  1.2111 +        loadSkinFile(&skin, skinName);
  1.2112 +        if (skinH != oldH || skinW != oldW) {
  1.2113 +          destroySystemBitmaps(&platView[0]);
  1.2114 +          openWindow(prefs.number[OPTIONS_WINDOWED]);
  1.2115 +        }
  1.2116 +      }
  1.2117 +
  1.2118 +      // reload the skin
  1.2119 +      if (loadSkin(&mainView[0], skinName) < 0) {
  1.2120 +        mainCleanup(platView);
  1.2121 +        return EXIT_FAILURE;
  1.2122 +      };
  1.2123 +      
  1.2124 +      // save options
  1.2125 +      saveOptions(&prefs);
  1.2126 +      break;
  1.2127 +
  1.2128 +    case TITLE_OPTIONS:
  1.2129 +      {
  1.2130 +        int oldWindowed = prefs.number[OPTIONS_WINDOWED];
  1.2131 +        options(&mainView[0], prefs.number);
  1.2132 +        if (oldWindowed != prefs.number[OPTIONS_WINDOWED]) {
  1.2133 +          destroySystemBitmaps(&platView[0]);
  1.2134 +          openWindow(prefs.number[OPTIONS_WINDOWED]);
  1.2135 +        }
  1.2136 +      }
  1.2137 +      saveOptions(&prefs);
  1.2138 +
  1.2139 +      // reload the skin if the player changed the rotation system
  1.2140 +      unpackOptions(&mainView[0], &prefs);
  1.2141 +      if (wantsClose) {
  1.2142 +        break;
  1.2143 +      }
  1.2144 +      if (loadSkin(&mainView[0], skinName) < 0) {
  1.2145 +        mainCleanup(platView);
  1.2146 +        return EXIT_FAILURE;
  1.2147 +      };
  1.2148 +      break;
  1.2149 +
  1.2150 +    case TITLE_KEYS:
  1.2151 +      configureKeys();
  1.2152 +      break;
  1.2153 +    
  1.2154 +    }
  1.2155 +  }
  1.2156 +  
  1.2157 +  mainCleanup(platView);
  1.2158 +  return 0;
  1.2159 +} END_OF_MAIN();