Mercurial > hg > index.fcgi > lj > lj046
view src/ljpc.c @ 3:17286938e22a
change DS alt. rotate key to rotate twice
author | paulo@localhost |
---|---|
date | Wed, 08 Apr 2009 21:50:13 -0700 |
parents | |
children |
line source
1 /* PC frontend for LOCKJAW, an implementation of the Soviet Mind Game
3 Copyright (C) 2006-2008 Damian Yerrick <tepples+lj@spamcop.net>
5 This work is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 Original game concept and design by Alexey Pajitnov.
20 The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
21 or The Tetris Company LLC.
23 */
25 #include "ljpc.h"
26 #include "ljreplay.h"
27 #include <jpgalleg.h>
28 #include <time.h>
29 #include "scenario.h"
30 #include "ljpath.h"
32 #if 1
33 #define LJ_VERSION "0.46a ("__DATE__")"
34 #else
35 #define LJ_VERSION "WIP ("__DATE__")"
36 #endif
38 #define USE_PICS_FOLDER 0
39 #define MAX_PLAYERS 2
40 #define LJ_TICK_RATE 3600 // frames per minute
42 int bgColor, fgColor = 0, hiliteColor, pfBgColor = 0, pfFgColor;
43 static volatile int curTime = 0;
44 volatile char redrawWholeScreen = 1;
45 static volatile int wantPause = 0;
46 const DATAFILE *dat = NULL, *sound_dat = NULL;
47 const FONT *aver32 = NULL, *aver16 = NULL;
48 char withSound = 0;
49 char autoPause;
50 int refreshRate = 0;
51 char demoFilename[512];
53 int withPics = -1;
55 char skinName[PATH_MAX] = "default.skin";
56 char ljblocksSRSName[PATH_MAX];
57 char ljblocksSegaName[PATH_MAX];
58 char ljconnSRSName[PATH_MAX];
59 char ljconnSegaName[PATH_MAX];
60 char ljbgName[PATH_MAX];
61 char menubgName[PATH_MAX];
62 char bgmName[PATH_MAX];
63 char bgmRhythmName[PATH_MAX];
64 unsigned long int bgmLoopPoint = 0;
65 int bgmReadyGo = 0;
66 int bgmVolume = 128;
67 int skinW = 800;
68 int skinH = 600;
73 static void incCurTime(void) {
74 ++curTime;
75 } END_OF_FUNCTION(incCurTime);
77 static void amnesia(void) {
78 redrawWholeScreen = 1;
79 } END_OF_FUNCTION(amnesia);
81 static void requestPause(void) {
82 wantPause |= autoPause;
83 }
86 int getTime(void) {
87 return curTime;
88 }
90 void yieldCPU(void) {
91 rest(5);
92 }
94 void ljBeginDraw(LJView *v, int sync) {
95 vsync();
96 if (redrawWholeScreen) {
97 redrawWholeScreen = 0;
98 playRedrawScreen(v);
99 }
101 BITMAP *sc = v->plat->skin->fullback;
103 // Draw shape
104 blit(v->plat->skin->bg, sc, 0, 0, 0, 0, 48, 16);
105 if (v->control->replayDst) {
107 // circle: record
108 circlefill(sc, 8, 8, 7, fgColor);
109 textout_ex(sc, aver16, "Rec", 18, 2, fgColor, -1);
110 } else if (v->control->replaySrc) {
112 // triangle: play
113 for (int wid = 0; wid < 7; ++wid) {
114 hline(sc, 2, 2 + wid, wid * 2 + 3, fgColor);
115 hline(sc, 2, 14 - wid, wid * 2 + 3, fgColor);
116 }
117 textout_ex(sc, aver16, "Play", 18, 2, fgColor, -1);
118 } else {
120 // square: stop
121 rectfill(sc, 2, 2, 14, 14, fgColor);
122 }
123 blit(sc, screen, 0, 0, 0, 0, 48, 16);
124 }
126 void ljEndDraw(LJView *v) {
128 }
130 static void startRecording(LJView *v) {
131 withPics = -1;
133 // close existing playback
134 if (v->control->replaySrc) {
135 replayClose(v->control->replaySrc);
136 v->control->replaySrc = NULL;
137 }
139 // toggle recording
140 if (v->control->replayDst) {
141 replayClose(v->control->replayDst);
142 v->control->replayDst = NULL;
143 } else {
144 char path[PATH_MAX];
146 ljpathFind_w(path, "demo.ljm");
147 v->control->replayDst = newReplay(path, v->field);
148 #if USE_PICS_FOLDER
149 withPics = 0;
150 #endif
151 }
152 }
154 static void startPlaying(LJView *v) {
155 withPics = -1;
157 // close existing recording
158 if (v->control->replayDst) {
159 replayClose(v->control->replayDst);
160 v->control->replayDst = NULL;
161 }
163 // toggle playback
164 if (v->control->replaySrc) {
165 replayClose(v->control->replaySrc);
166 v->control->replaySrc = NULL;
167 } else {
168 char path[PATH_MAX];
170 if (ljpathFind_r(path, "demo.ljm")) {
171 v->control->replaySrc = openReplay(path, v->field);
172 }
173 v->nLockTimes = 0;
174 #if USE_PICS_FOLDER
175 withPics = 0;
176 #endif
177 }
178 v->backDirty = ~0;
179 v->field->reloaded = 1;
180 }
182 int ljHandleConsoleButtons(LJView *v) {
183 int canceled = 0;
185 while (keypressed()) {
186 int scancode;
187 int codepoint = ureadkey(&scancode);
189 if (scancode == KEY_ESC) {
190 wantPause = 1;
191 } else if (scancode == KEY_PRTSCR) {
192 saveScreen(-1);
193 save_bitmap("ljbackbuf.bmp", v->plat->skin->fullback, NULL);
194 } else if (codepoint == '[') {
195 v->plat->wantRecord = 1;
196 } else if (codepoint == ']') {
197 v->plat->wantRecord = 0;
198 startPlaying(v);
199 }
200 }
201 if (v->plat->wantRecord) {
202 v->plat->wantRecord = 0;
203 startRecording(v);
204 }
205 if (wantPause) {
206 canceled = pauseGame(v->plat);
207 wantPause = 0;
208 }
210 if (wantsClose) {
211 canceled = 1;
212 }
214 return canceled;
215 }
218 static void drawBlock(const LJPCView *const v, int x, int y, int blk) {
220 if (x >= 0 && x < LJ_PF_WID && y >= 0 && y < LJ_PF_VIS_HT) {
221 const int w = v->skin->blkW;
222 const int h = v->skin->blkH;
223 const int dstX = w * x;
224 const int dstY = h * (LJ_PF_VIS_HT - 1 - y);
226 if (v->skin->transparentPF && (blk == 0 || blk == 0x100)) {
228 // Copy background
229 const unsigned int srcX = dstX + v->baseX;
230 const unsigned int srcY = dstY + v->skin->baseY
231 - LJ_PF_VIS_HT * h - v->skin->pfElev;
232 blit(v->skin->bg, v->back, srcX, srcY, dstX, dstY, w, h);
233 } else if (blk && v->skin->connBlocks) {
235 // Copy connected block
236 const unsigned int srcX = ((blk >> 0) & 15) * w;
237 const unsigned int srcY = ((blk >> 4) & 15) * h;
238 blit(v->skin->connBlocks, v->back, srcX, srcY, dstX, dstY, w, h);
239 } else {
241 // Copy lone block
242 const unsigned int srcX = ((blk >> 4) & 7) * w;
243 const unsigned int srcY = ((blk >> 7) & 1) * h;
244 blit(v->skin->blocks, v->back, srcX, srcY, dstX, dstY, w, h);
245 }
247 // 0x100: hotline
248 if (blk & 0x100) {
249 hline(v->back, dstX, dstY + h / 2 - 1, dstX + w - 1, pfFgColor);
250 hline(v->back, dstX, dstY + h / 2 , dstX + w - 1, pfFgColor);
251 hline(v->back, dstX, dstY + h / 2 + 1, dstX + w - 1, pfBgColor);
252 }
253 }
254 }
256 /**
257 * Draws multiple rows of the playfield
258 * @param p the playfield
259 * @param rows the rows to be updated (0x00001 on bottom, 0x80000 on top)
260 */
261 void updField(const LJView *const v, LJBits rows) {
262 const LJField *const p = v->field;
264 acquire_bitmap(v->plat->back);
265 for (int y = 0;
266 y < LJ_PF_VIS_HT && rows != 0;
267 ++y, rows >>= 1) {
268 int blankTile = 0;
270 // hotline: draw 0x100 as the background
271 if (hotlineRows[y] && v->field->scoreStyle == LJSCORE_HOTLINE) {
272 blankTile = 0x100;
273 }
274 // during line clear delay, draw bars to show which lines were cleared
275 if (p->state == LJS_LINES_FALLING && p->stateTime > 0
276 && ((1 << y) & p->tempRows)) {
277 blankTile = 0x100;
278 }
279 if (rows & 1) {
280 for (int x = p->leftWall; x < p->rightWall; x++) {
281 int b = v->hidePF ? 0 : p->b[y][x];
282 drawBlock(v->plat, x, y, b ? b : blankTile);
283 }
284 }
285 }
286 release_bitmap(v->plat->back);
287 }
290 #define SHADOW_BLOCK 0x00
292 /**
293 * Draws a tetromino whose lower left corner of the bounding box is at (x, y)
294 * @param b the bitmap to draw to
295 * @param piece the piece to be drawn
296 * @param x distance from to left side of 4x4 box
297 * @param y distance from top of bitmap to bottom of 4x4 box
298 * @param the rotation state (0: U; 1: R; 2: D; 3: L; 4: Initial position)
299 * @param w width of each block
300 * @param h height of each block
301 * @param color Drawing style
302 * color == 0: draw shadow
303 * color == 0x10 through 0x70: draw in that color
304 * color == 0x80: draw as garbage
305 * color == -255 through -1: draw with 255 through 1 percent lighting
306 */
307 LJBits drawPiece(LJView *const v, BITMAP *const b,
308 int piece, int x, int y, int theta,
309 int color, int w, int h) {
310 // Don't try to draw the -1 that's the sentinel for no hold piece
311 if (piece < 0)
312 return 0;
314 LJBits rows = 0;
315 LJBlkSpec blocks[4];
316 const int srcW = v->plat->skin->blkW;
317 const int srcH = v->plat->skin->blkH;
318 BITMAP *singleBlocks = v->plat->skin->blocks;
319 int singleSeries = (color == 0
320 && singleBlocks->h >= 6 * srcH)
321 ? 4 : 2;
323 // color: 0 for shadow, >0 for piece
324 BITMAP *connBlocks = (color != SHADOW_BLOCK)
325 ? v->plat->skin->connBlocks : NULL;
327 BITMAP *transTemp = color < 0
328 || (color == SHADOW_BLOCK && v->hideShadow < LJSHADOW_COLORED)
329 ? create_bitmap(w, h) : 0;
331 if (transTemp) {
332 set_trans_blender(0, 0, 0, v->hideShadow ? 128 : 64);
333 }
335 expandPieceToBlocks(blocks, v->field, piece, 0, 0, theta);
336 acquire_bitmap(b);
337 for (int blk = 0; blk < 4; ++blk) {
338 int blkValue = blocks[blk].conn;
340 // If blkValue == 0 then the block is not part of the piece,
341 // such as if it's a domino or tromino or (once pentominoes
342 // are added) a tetromino.
343 if (blkValue) {
344 int blkX = blocks[blk].x;
345 int blkY = blocks[blk].y;
346 const int dstX = x + w * blkX;
347 const int dstY = y + h * (-1 - blkY);
349 if (color > 0) {
350 blkValue = color | (blkValue & CONNECT_MASK);
351 } else if (color == 0 && v->hideShadow == LJSHADOW_COLORLESS) {
352 blkValue = 0;
353 }
355 if (connBlocks) {
356 const unsigned int srcX = ((blkValue >> 0) & 15) * srcW;
357 const unsigned int srcY = ((blkValue >> 4) & 15) * srcH;
359 if (transTemp) {
360 stretch_blit(connBlocks, transTemp,
361 srcX, srcY, srcW, srcH,
362 0, 0, w, h);
363 if (color < 0) {
364 draw_lit_sprite(b, transTemp, dstX, dstY, color + 256);
365 } else {
366 draw_trans_sprite(b, transTemp, dstX, dstY);
367 }
368 } else if (srcW != w || srcH != h) {
369 stretch_blit(connBlocks, b,
370 srcX, srcY, srcW, srcH,
371 dstX, dstY, w, h);
372 if (dstY > 600) {
373 allegro_message("dstY = %d\n", dstY);
374 }
375 } else {
376 blit(connBlocks, b,
377 srcX, srcY,
378 dstX, dstY, w, h);
379 }
380 } else {
381 const unsigned int srcX = ((blkValue >> 4) & 7) * srcW;
382 const unsigned int srcY = (((blkValue >> 7) & 1) + singleSeries) * srcH;
384 if (transTemp) {
385 stretch_blit(singleBlocks, transTemp,
386 srcX, srcY, srcW, srcH,
387 0, 0, w, h);
388 if (color < 0) {
389 draw_lit_sprite(b, transTemp, dstX, dstY, color + 256);
390 } else {
391 draw_trans_sprite(b, transTemp, dstX, dstY);
392 }
393 } else {
394 stretch_blit(singleBlocks, b,
395 srcX, srcY, srcW, srcH,
396 dstX, dstY, w, h);
397 }
398 }
399 rows |= 1 << blkY;
400 }
401 }
402 release_bitmap(b);
403 if (transTemp) {
404 solid_mode();
405 destroy_bitmap(transTemp);
406 }
408 return rows;
409 }
411 void drawPieceInPF(LJView *v, int dstX, LJFixed dstY, int theta, int color) {
412 LJBits bits = 0;
413 BITMAP *b = v->plat->back;
414 const LJField *const p = v->field;
415 int y = ljfixfloor(dstY);
416 const int w = v->plat->skin->blkW;
417 const int h = v->plat->skin->blkH;
418 int drawnY = fixmul(h, dstY);
420 bits = drawPiece(v, b, p->curPiece[0],
421 w * dstX, h * LJ_PF_VIS_HT - drawnY,
422 theta,
423 color, w, h);
424 bits = (y >= 0) ? bits << y : bits >> -y;
425 bits &= (1 << LJ_PF_VIS_HT) - 1;
426 if (dstY & 0xFFFF) {
427 bits |= bits << 1;
428 }
430 v->backDirty |= bits;
431 v->frontDirty |= bits;
432 }
435 void drawFallingPiece(LJView *v) {
436 const LJField *const p = v->field;
437 int piece = p->curPiece[0];
438 int y = v->smoothGravity
439 ? p->y
440 : ljitofix(ljfixfloor(p->y));
441 const int color = (p->state == LJS_LANDED)
442 ? -128 - ((p->stateTime + 1) * 128 / (p->speed.lockDelay + 1))
443 : pieceColors[piece];
445 // Draw trails
446 if (v->showTrails) {
448 // trail going up
449 while (v->trailY - y < ljitofix(-1)) {
450 v->trailY += ljitofix(1);
451 drawPieceInPF(v, p->x, v->trailY, p->theta, color);
452 }
454 // trail going down
455 while (v->trailY - y > ljitofix(1)) {
456 v->trailY -= ljitofix(1);
457 drawPieceInPF(v, p->x, v->trailY, p->theta, color);
458 }
459 }
460 drawPieceInPF(v, p->x, y, p->theta, color);
461 v->trailY = y;
462 }
464 void drawShadow(LJView *v) {
465 const LJField *const p = v->field;
466 int y = ljitofix(p->hardDropY);
467 const int color = SHADOW_BLOCK;
468 drawPieceInPF(v, p->x, y, p->theta, color);
469 }
471 void drawNextPieces(LJView *v) {
472 int baseX = v->plat->baseX;
473 int baseY = v->plat->skin->baseY - v->plat->skin->pfElev;
474 int blkW = v->plat->skin->blkW;
475 int blkH = v->plat->skin->blkH;
476 int holdPieceColor = v->field->alreadyHeld
477 ? 0x80
478 : pieceColors[v->field->holdPiece];
479 int ceil = v->field->ceiling;
481 BITMAP *sc = v->plat->skin->fullback;
483 if (v->frontDirty & LJ_DIRTY_NEXT) {
484 int holdW = blkW * 2 / 3;
485 int holdH = blkH * 2 / 3;
486 int holdX = baseX + blkW;
487 int holdY = baseY - (ceil - 1) * blkH - 4 * holdH;
489 // Move the hold piece within the screen
490 if (holdY < 0) {
491 holdY = 0;
492 }
494 // Draw hold piece
495 blit(v->plat->skin->bg, sc,
496 holdX, holdY,
497 holdX, holdY,
498 holdW * 4, holdH * 2);
499 drawPiece(v, sc,
500 v->field->holdPiece, holdX, holdY + 4 * holdH, 4,
501 holdPieceColor, holdW, holdH);
502 blit(sc, screen,
503 holdX, holdY,
504 holdX, holdY,
505 holdW * 4, holdH * 2);
506 }
507 // Draw next pieces
509 switch (v->plat->skin->nextPos) {
510 case LJNEXT_RIGHT:
511 case LJNEXT_RIGHT_TAPER:
512 if (v->frontDirty & LJ_DIRTY_NEXT) {
513 int y = baseY - ceil * blkH;
514 int x = baseX + LJ_PF_WID * blkW;
515 int w = blkW;
516 int h = blkH;
517 for (int i = 1; i <= v->nextPieces; ++i) {
518 int piece = v->field->curPiece[i];
520 blit(v->plat->skin->bg, sc, x, y, x, y, w * 4, h * 2);
521 if (!v->hideNext) {
522 drawPiece(v, sc,
523 piece, x, y + 4 * h, 4,
524 pieceColors[piece], w, h);
525 }
526 blit(sc, screen, x, y, x, y, w * 4, h * 2);
527 y += 8 + h * 2;
528 if (v->plat->skin->nextPos == LJNEXT_RIGHT_TAPER) {
529 --h;
530 --w;
531 }
532 }
533 }
534 break;
536 case LJNEXT_TOP:
537 if (v->frontDirty & LJ_DIRTY_NEXT) {
538 int y = baseY - (ceil + 2) * blkH - 8;
539 int x = baseX + 4 * blkW;
540 int blitX = x, blitY = y;
542 blit(v->plat->skin->bg, sc, x, y, x, y, blkW * 8, blkH * 2);
543 if (!v->hideNext) {
544 if (v->nextPieces >= 1) {
545 int piece = v->field->curPiece[1];
546 drawPiece(v, sc,
547 piece, x, y + 4 * blkH, 4,
548 pieceColors[piece], blkW, blkH);
549 }
550 if (v->nextPieces >= 2) {
551 int piece = v->field->curPiece[2];
552 x += 4 * blkW;
553 drawPiece(v, sc,
554 piece, x, y + 3 * blkH, 4,
555 pieceColors[piece], blkW/ 2, blkH / 2);
556 }
557 if (v->nextPieces >= 3) {
558 int piece = v->field->curPiece[3];
559 x += 2 * blkW;
560 drawPiece(v, sc,
561 piece, x, y + 3 * blkH, 4,
562 pieceColors[piece], blkW / 2, blkH / 2);
563 }
564 }
565 blit(sc, screen, blitX, blitY, blitX, blitY, blkW * 8, blkH * 2);
566 }
567 break;
568 }
570 if (v->plat->nextAbove && !v->hideNext) {
571 int row = (v->field->hardDropY + 4);
572 int x = (1 + v->field->x) * blkW;
573 for (int i = 1;
574 i <= v->plat->nextAbove
575 && row <= v->field->ceiling - 2;
576 ++i) {
577 int y = (LJ_PF_VIS_HT - row) * blkH;
578 int piece = v->field->curPiece[i];
580 drawPiece(v, v->plat->back,
581 piece, x, y, 4,
582 pieceColors[piece], blkW / 2, blkH / 2);
583 v->backDirty |= 3 << row;
584 row += 2;
585 }
586 }
587 v->frontDirty &= ~LJ_DIRTY_NEXT;
588 }
590 void drawScore(LJView *v) {
591 int gameTime = v->field->gameTime;
592 int seconds = gameTime / 60;
593 int minutes = seconds / 60;
594 int baseX = v->plat->baseX;
595 int tpm = -1;
596 int spawnLeft = v->plat->skin->blkW * LJ_SPAWN_X + baseX;
597 int pfRight = v->plat->skin->blkW * LJ_PF_WID + baseX;
598 BITMAP *sc = v->plat->skin->fullback;
600 if (withPics >= 0) {
601 if (v->field->nPieces != withPics) {
602 saveScreen(v->field->nPieces);
603 }
604 withPics = v->field->nPieces;
605 }
607 if (v->nLockTimes >= 2 ) {
608 int time = v->lockTime[0] - v->lockTime[v->nLockTimes - 1];
609 if (time > 0) {
610 tpm = 3600 * (v->nLockTimes - 1) / time;
611 }
612 }
614 if (v->frontDirty & LJ_DIRTY_SCORE) {
615 switch (v->plat->skin->nextPos) {
616 case LJNEXT_TOP:
617 blit(v->plat->skin->bg, sc,
618 pfRight, 72,
619 pfRight, 72,
620 112, 136);
622 textout_ex(sc, aver32, "Lines:", pfRight, 72, fgColor, -1);
623 textprintf_right_ex(sc, aver32, pfRight + 104, 102, fgColor, -1,
624 "%d", v->field->lines);
625 textout_ex(sc, aver32, "Score:", pfRight, 142, fgColor, -1);
626 textprintf_right_ex(sc, aver32, pfRight + 104, 172, fgColor, -1,
627 "%d", v->field->score);
628 textout_ex(sc, aver32, "Level:", pfRight, 212, fgColor, -1);
629 textprintf_right_ex(sc, aver32, pfRight + 104, 242, fgColor, -1,
630 "%d", v->field->speedState.level);
631 blit(sc, screen,
632 pfRight, 72,
633 pfRight, 72,
634 112, 136);
635 break;
637 default:
638 blit(v->plat->skin->bg, sc, spawnLeft, 12, spawnLeft, 12, 288, 30);
639 blit(v->plat->skin->bg, sc,
640 spawnLeft, 42, spawnLeft, 42,
641 pfRight - spawnLeft, 30);
642 textprintf_right_ex(sc, aver32, spawnLeft + 288, 12, fgColor, -1,
643 "Lv. %d", v->field->speedState.level);
644 textprintf_ex(sc, aver32, spawnLeft, 12, fgColor, -1,
645 "Lines: %d", v->field->lines);
646 textprintf_ex(sc, aver32, spawnLeft, 42, fgColor, -1,
647 "Score: %d", v->field->score);
648 blit(sc, screen, spawnLeft, 12, spawnLeft, 12, 288, 60);
649 break;
650 }
651 }
653 /* If speed is defined, and there is room to draw it, draw it. */
654 if (tpm > 0 && v->nextPieces <= 3) {
655 blit(v->plat->skin->bg, sc,
656 pfRight, 282,
657 pfRight, 282,
658 104, 60);
659 textout_ex(sc, aver32, "Speed:", pfRight, 282, fgColor, -1);
660 textprintf_right_ex(sc, aver32, pfRight + 104, 312, fgColor, -1,
661 "%d", tpm);
662 blit(sc, screen,
663 pfRight, 282,
664 pfRight, 282,
665 104, 60);
666 }
668 if (v->frontDirty & LJ_DIRTY_NEXT) {
670 // Erase gimmick
671 blit(v->plat->skin->bg, sc,
672 baseX, v->plat->skin->baseY + 8,
673 baseX, v->plat->skin->baseY + 8,
674 v->plat->skin->blkW * LJ_PF_WID, 30);
675 // Draw gimmick
676 if (v->field->gimmick >= 0 && v->field->gimmick < LJGM_N_GIMMICKS) {
677 const char *gimmickName = ljGetFourCCName(gimmickNames[v->field->gimmick]);
678 textout_centre_ex(sc, aver32,
679 gimmickName ? gimmickName : "Bad gimmick ID",
680 baseX + (LJ_PF_WID / 2) * v->plat->skin->blkW,
681 v->plat->skin->baseY + 8,
682 fgColor, -1);
683 blit(sc, screen,
684 baseX, v->plat->skin->baseY + 8,
685 baseX, v->plat->skin->baseY + 8,
686 v->plat->skin->blkW * LJ_PF_WID, 30);
687 }
688 }
689 drawNextPieces(v);
691 blit(v->plat->skin->bg, sc, pfRight, 42, pfRight, 42, 96, 30);
692 #if 0
693 // Use this for DEBUG inspection into a variable
694 textprintf_right_ex(sc, aver16, pfRight + 96, 8, fgColor, -1,
695 "%d", v->field->bpmCounter);
696 #endif
697 textprintf_right_ex(sc, aver32, pfRight + 96, 42, fgColor, -1,
698 "%d:%02d", minutes, seconds - 60 * minutes);
699 blit(sc, screen, pfRight, 42, pfRight, 42, 96, 30);
701 }
703 void blitField(LJView *v) {
704 int blkH = v->plat->skin->blkH;
705 int rowY = v->plat->skin->baseY
706 - v->plat->skin->pfElev
707 - blkH * v->field->ceiling;
708 int x = v->plat->skin->blkW * v->field->leftWall;
709 int w = v->plat->skin->blkW * (v->field->rightWall - v->field->leftWall);
711 // Copy each dirty row
712 for (int y = v->field->ceiling - 1; y >= 0; --y) {
713 if (v->frontDirty & (1 << y)) {
714 int top = (LJ_PF_VIS_HT - y - 1) * blkH;
715 int ht = 0;
717 // Find the height of the contiguous rows to blit
718 do {
719 ht += blkH;
720 --y;
721 } while ((y >= 0)
722 && (v->frontDirty & (1 << y)));
723 blit(v->plat->back, screen,
724 x, top,
725 x + v->plat->baseX, rowY,
726 w, ht);
727 rowY += ht;
728 }
729 rowY += blkH;
730 }
732 v->frontDirty &= (~0) << LJ_PF_HT;
733 }
735 void saveScreen(int n) {
736 BITMAP *b = create_bitmap(SCREEN_W, SCREEN_H);
737 if (b) {
738 PALETTE pal;
740 get_palette(pal);
741 blit(screen, b, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
742 if (n < 0) {
743 save_bitmap("ljsnap.bmp", b, pal);
744 } else {
745 char filename[64];
746 sprintf(filename, "pics/lj%05d.bmp", n);
747 save_bitmap(filename, b, pal);
748 }
749 destroy_bitmap(b);
750 }
751 }
753 #define PRESETS_PER_COL 6
754 #define N_NONPRESETS 2
755 #define PRESETS_TOP 140
756 #define PRESET_COL_WIDTH 250
757 #define PRESET_ROW_HT 40
759 static const char *const nonPresetNames[N_NONPRESETS] = {
760 "Full Custom", "Back"
761 };
763 static void getPresetDrawRow(unsigned int preset, int hilite) {
764 unsigned int ht = text_height(aver32);
765 const char *txt = preset < nLoadedPresets
766 ? loadedPresets[preset].name
767 : nonPresetNames[preset - nLoadedPresets];
768 if (!txt) {
769 txt = "Bad";
770 }
771 unsigned int wid = text_length(aver32, txt);
773 int buttonCol = preset / PRESETS_PER_COL;
774 int buttonX = 20 + buttonCol * PRESET_COL_WIDTH;
775 int buttonY = PRESETS_TOP
776 + PRESET_ROW_HT * (preset - buttonCol * PRESETS_PER_COL);
777 unsigned int buttonWidth = wid + 16;
779 rectfill(screen,
780 buttonX, buttonY,
781 buttonX + buttonWidth - 1, buttonY + PRESET_ROW_HT - 1,
782 hilite ? hiliteColor : bgColor);
783 if (hilite) {
784 rect(screen,
785 buttonX, buttonY,
786 buttonX + buttonWidth - 1, buttonY + PRESET_ROW_HT - 1,
787 fgColor);
788 }
789 textout_ex(screen, aver32, txt,
790 8 + buttonX,
791 20 + buttonY - ht / 2,
792 fgColor, -1);
793 }
795 int getPreset(int lastPreset) {
796 LJBits lastKeys = ~0;
797 redrawWholeScreen = 1;
799 clear_keybuf();
800 if (lastPreset < 0) {
801 lastPreset += nLoadedPresets + N_NONPRESETS;
802 }
804 for(int done = 0; done == 0; ) {
805 if (redrawWholeScreen) {
806 redrawWholeScreen = 0;
807 clear_to_color(screen, bgColor);
808 textout_ex(screen, aver32, "LOCKJAW > Play", 16, 32, fgColor, -1);
809 textout_ex(screen, aver32, "Select a scenario:", 16, 90, fgColor, -1);
811 for (int preset = 0;
812 preset < nLoadedPresets + N_NONPRESETS;
813 ++preset) {
814 getPresetDrawRow(preset, preset == lastPreset);
815 }
816 textout_ex(screen, aver16, "Arrows: move; Rotate Right: start",
817 16, PRESETS_TOP + 40 * (PRESETS_PER_COL + 1), fgColor, -1);
818 textout_ex(screen, aver16, "Coming soon: an editor for these!",
819 16, PRESETS_TOP + 40 * (PRESETS_PER_COL + 1) + 20, fgColor, -1);
820 }
821 while (keypressed()) {
822 int scancode;
823 ureadkey(&scancode);
824 if (scancode == KEY_PRTSCR) {
825 saveScreen(-1);
826 }
827 }
829 int preset = lastPreset;
830 LJBits keys = menuReadPad();
831 LJBits newKeys = keys & ~lastKeys;
833 if ((newKeys & VKEY_ROTL) || wantsClose) {
834 preset = nLoadedPresets + N_NONPRESETS - 1;
835 ezPlaySample("rotate_wav", 128);
836 }
837 if ((newKeys & VKEY_ROTR) || wantsClose) {
838 done = 1;
839 ezPlaySample("line_wav", 128);
840 }
842 if (newKeys & VKEY_UP) {
843 if (preset <= 0) {
844 preset = nLoadedPresets + N_NONPRESETS - 1;
845 } else {
846 --preset;
847 }
848 ezPlaySample("shift_wav", 128);
849 }
850 if (newKeys & VKEY_DOWN) {
851 ++preset;
852 if (preset >= nLoadedPresets + N_NONPRESETS) {
853 preset = 0;
854 }
855 ezPlaySample("shift_wav", 128);
856 }
858 if (newKeys & VKEY_LEFT) {
859 if (preset < PRESETS_PER_COL) {
860 preset += (((nLoadedPresets + N_NONPRESETS)
861 / PRESETS_PER_COL)
862 )
863 * PRESETS_PER_COL;
864 if (preset >= nLoadedPresets + N_NONPRESETS) {
865 preset -= PRESETS_PER_COL;
866 }
867 } else {
868 preset -= PRESETS_PER_COL;
869 }
870 ezPlaySample("shift_wav", 128);
871 }
872 if (newKeys & VKEY_RIGHT) {
873 preset += PRESETS_PER_COL;
874 if (preset >= nLoadedPresets + N_NONPRESETS) {
875 preset %= PRESETS_PER_COL;
876 }
877 ezPlaySample("shift_wav", 128);
878 }
880 if (preset != lastPreset) {
881 vsync();
882 getPresetDrawRow(lastPreset, 0);
883 getPresetDrawRow(preset, 1);
884 lastPreset = preset;
885 } else {
886 rest(30);
887 }
888 lastKeys = keys;
889 }
891 // Wrap the nonpresets into the negative numbers.
892 if (lastPreset >= nLoadedPresets) {
893 lastPreset -= (nLoadedPresets + N_NONPRESETS);
894 }
896 return lastPreset;
897 }
899 /**
900 * Pauses the game, returning nonzero if the player wants to quit.
901 */
902 int pauseGame(LJPCView *v) {
903 int escCount = 0;
904 int quit = 0;
905 int escHold = curTime;
906 redrawWholeScreen = 1;
908 LJMusic_pause(v->skin->bgm, 1);
909 while (escCount < 2 && !quit) {
910 LJMusic_poll(v->skin->bgm);
911 if (redrawWholeScreen) {
912 redrawWholeScreen = 0;
913 clear_to_color(screen, pfBgColor);
914 textout_centre_ex(screen, aver32, "LOCKJAW: GAME PAUSED",
915 SCREEN_W / 2, 200, pfFgColor, -1);
916 textout_centre_ex(screen, aver32, "Press Esc to continue",
917 SCREEN_W / 2, 250, pfFgColor, -1);
918 textout_centre_ex(screen, aver32, "or hold Esc to quit",
919 SCREEN_W / 2, 300, pfFgColor, -1);
920 }
922 if (key[KEY_ESC]) {
923 if (escHold == 0) {
924 escHold = curTime;
925 }
926 if (curTime - escHold >= 60) {
927 quit = 1;
928 }
929 } else {
930 if (escHold) {
931 ++escCount;
932 }
933 escHold = 0;
934 }
935 rest(30);
936 if (wantsClose) {
937 quit = 1;
938 }
940 }
941 LJMusic_pause(v->skin->bgm, 0);
942 redrawWholeScreen = 1;
943 clear_keybuf();
944 return quit;
945 }
948 void pcInit(LJView *v, struct LJPrefs *prefs) {
949 v->plat->b2bcd1 = 0;
950 v->plat->b2bcd2 = 0;
951 v->plat->baseX = v->plat->skin->baseX;
953 // If the player has chosen to use more next pieces than the
954 // next piece position can handle, set the number of
955 // next pieces for correctness of debrief().
956 if (v->nextPieces > 3 && v->plat->skin->nextPos == LJNEXT_TOP) {
957 v->nextPieces = 3;
958 }
959 }
961 /**
962 * Redraws everything on the screen.
963 * Called when needs redraw.
964 */
965 void playRedrawScreen(LJView *v) {
966 blit(v->plat->skin->bg, screen,
967 0, 0,
968 0, 0,
969 SCREEN_W, SCREEN_H);
970 v->frontDirty = ~0;
971 }
973 #if 0
974 void startingAniWantsSkip(LJView *v) {
975 LJInput unusedIn;
976 addKeysToInput(&unusedIn, readPad(), v->field, v->control);
977 }
978 #endif
980 void playSampleForTetromino(int piece);
982 void restPollingMusic(int nFrames, LJMusic *bgm) {
983 nFrames += curTime;
984 while (curTime - nFrames < 0) {
985 if (bgm) {
986 LJMusic_poll(bgm);
987 }
988 }
989 }
991 extern int bgmReadyGo;
992 void startingAnimation(LJView *v) {
993 int readyGoX = v->plat->baseX + v->plat->skin->blkW * LJ_PF_WID / 2;
994 int readyGoY = v->plat->skin->baseY
995 - v->plat->skin->pfElev
996 - v->plat->skin->blkH
997 * v->field->ceiling * 3 / 5;
999 clear_keybuf();
1000 v->backDirty = 0;
1002 playRedrawScreen(v);
1003 blitField(v);
1004 textout_centre_ex(screen, aver32, "Ready",
1005 readyGoX, readyGoY, pfFgColor, -1);
1007 ezPlaySample("ready_wav", 128);
1008 restPollingMusic(36, bgmReadyGo ? v->plat->skin->bgm : NULL);
1009 v->frontDirty = ~0;
1011 if (!wantsClose) {
1012 blitField(v);
1013 textout_centre_ex(screen, aver32, "GO!",
1014 readyGoX, readyGoY, pfFgColor, -1);
1015 drawScore(v);
1017 ezPlaySample("go_wav", 128);
1018 v->frontDirty = ~0;
1019 restPollingMusic(12, bgmReadyGo ? v->plat->skin->bgm : NULL);
1020 }
1021 if (!wantsClose) {
1022 playSampleForTetromino(v->field->curPiece[1]);
1023 restPollingMusic(24, bgmReadyGo ? v->plat->skin->bgm : NULL);
1024 }
1025 }
1028 static void gameOverAnimation(const LJPCView *const v, const LJField *p, int won) {
1029 int ceiling = p->ceiling;
1030 int left = v->baseX + p->leftWall * v->skin->blkW;
1031 int right = v->baseX + p->rightWall * v->skin->blkW - 1;
1033 ezPlaySample("sectionup_wav", 0);
1034 if (!won) {
1035 ezPlaySample("gameover_wav", 256);
1036 } else {
1037 ezPlaySample("win_wav", 256);
1038 }
1040 for (int t = ceiling + v->skin->blkH - 2; t >= 0; --t) {
1042 // FIXME: vsync doesn't work on Vista
1043 vsync();
1044 for (int row = ceiling - 1; row >= 0; --row) {
1045 int ysub = t - row;
1047 if (ysub >= 0 && ysub < v->skin->blkH) {
1048 int y = v->skin->baseY - v->skin->pfElev
1049 - row * v->skin->blkH - ysub - 1;
1050 hline(screen, left, y, right, pfBgColor);
1051 }
1052 }
1053 if (wantsClose) {
1054 t = 0;
1055 }
1056 }
1057 }
1059 #define MENU_COPR_NOTICE_LINES 4
1060 const char *const menuCoprNotice[MENU_COPR_NOTICE_LINES] = {
1061 "Copr. 2006-2008 Damian Yerrick",
1062 "Not sponsored or endorsed by The Tetris Company.",
1063 "LOCKJAW comes with ABSOLUTELY NO WARRANTY. This is free software, and you are",
1064 "welcome to redistribute it under certain conditions as described in GPL.txt."
1065 };
1067 static BITMAP *buildTitleScreen(void) {
1068 BITMAP *back = create_system_bitmap(SCREEN_W, SCREEN_H);
1069 if (!back) {
1070 return NULL;
1071 }
1073 // Gradient from (0, 0, 0) to (0, 0, 153)
1074 for (int y = 0; y < 192; ++y) {
1075 for (int x = -((y * 13) & 0x1F);
1076 x < SCREEN_W;
1077 x += 32) {
1078 int colValue = y + ((rand() & 0x7000) >> 12);
1079 int c = makecol(0, 0, 153 * colValue / 192);
1080 hline(back, x, y, x + 31, c);
1081 }
1082 }
1084 // Gradient from (102, 51, 0) to (204, 102, 0)
1085 for (int y = 192; y < 384; ++y) {
1086 for (int x = -((y * 13) & 0x1F);
1087 x < SCREEN_W;
1088 x += 32) {
1089 int colValue = y + ((rand() & 0x7800) >> 11);
1090 int c = makecol(102 * colValue / 192, 51 * colValue / 192, 0);
1091 hline(back, x, y, x + 31, c);
1092 }
1093 }
1095 // Gradient from (204, 102, 0) to (255, 128, 0)
1096 for (int y = 384; y < SCREEN_H; ++y) {
1097 for (int x = -((y * 13) & 0x1F);
1098 x < SCREEN_W;
1099 x += 32) {
1100 int colValue = y - 400 + ((rand() & 0x7C00) >> 10);
1101 if (colValue > 600 - 384) {
1102 colValue = 600 - 384;
1103 }
1104 int c = makecol(204 + 50 * colValue / (600 - 384),
1105 102 + 25 * colValue / (600 - 384),
1106 0);
1107 hline(back, x, y, x + 31, c);
1108 }
1109 }
1111 DATAFILE *obj = find_datafile_object(dat, "arttitle_bmp");
1112 BITMAP *logo = obj ? obj->dat : NULL;
1113 obj = find_datafile_object(dat, "arttitle_pal");
1114 const RGB *pal = obj ? obj->dat : NULL;
1116 if (logo && pal) {
1117 set_palette(pal);
1118 draw_sprite(back, logo,
1119 (SCREEN_W - logo->w) / 2, (384 - logo->h) / 2);
1120 //unselect_palette();
1121 }
1123 textout_centre_ex(back, aver32, "Arrows: change; Enter: choose",
1124 SCREEN_W / 2, 440,
1125 0, -1);
1127 textout_centre_ex(back, aver32, "LOCKJAW: The Reference "LJ_VERSION,
1128 SCREEN_W / 2, SCREEN_H - 40,
1129 0, -1);
1131 return back;
1132 }
1134 enum {
1135 TITLE_EXIT = 0,
1136 TITLE_PLAY,
1137 TITLE_REPLAY,
1138 TITLE_OPTIONS,
1139 TITLE_SKIN,
1140 TITLE_KEYS,
1141 N_TITLE_ACTIONS
1142 };
1144 static const char *titleActions[N_TITLE_ACTIONS] = {
1145 [TITLE_EXIT] = "Exit",
1146 [TITLE_PLAY] = "Play",
1147 [TITLE_REPLAY] = "Replay",
1148 [TITLE_SKIN] = "Skin...",
1149 [TITLE_OPTIONS] = "Options...",
1150 [TITLE_KEYS] = "Game Keys..."
1151 };
1152 /*
1153 0: Exit
1154 1: Play
1155 2
1156 */
1157 int title(void) {
1159 // don't even draw if the player is trying to close the window
1160 if (wantsClose) {
1161 return 0;
1162 }
1164 BITMAP *back = buildTitleScreen();
1165 LJBits lastKeys = ~0;
1166 int redraw = 1;
1167 int choice = 1;
1169 if (!back) {
1170 alert("Not enough memory to display the title screen.",
1171 "(If you don't even have RAM for a title screen,",
1172 "then what do you have RAM for?)",
1173 "Exit", 0, 13, 0);
1174 return 0;
1175 }
1177 redrawWholeScreen = 1;
1179 for(int done = 0; done == 0; ) {
1180 if (redrawWholeScreen) {
1181 redrawWholeScreen = 0;
1182 blit(back, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
1183 redraw = 1;
1184 }
1185 if (redraw) {
1186 int dehilite = makecol(221, 153, 85);
1187 int white = makecol(255, 255, 255);
1188 redraw = 0;
1189 vsync();
1190 blit(back, screen, 0, 400, 0, 400, SCREEN_W, 30);
1191 textout_centre_ex(screen, aver32,
1192 titleActions[choice],
1193 SCREEN_W / 2, 400, white, -1);
1194 textout_centre_ex(screen, aver32,
1195 titleActions[choice == 0 ? N_TITLE_ACTIONS - 1 : choice - 1],
1196 SCREEN_W / 2 - 160, 400, dehilite, -1);
1197 textout_centre_ex(screen, aver32,
1198 titleActions[choice == N_TITLE_ACTIONS - 1 ? 0 : choice + 1],
1199 SCREEN_W / 2 + 160, 400, dehilite, -1);
1200 }
1202 while (keypressed()) {
1203 int scancode;
1204 ureadkey(&scancode);
1205 if (scancode == KEY_PRTSCR) {
1206 saveScreen(-1);
1207 }
1208 }
1210 LJBits keys = menuReadPad();
1211 LJBits newKeys = keys & ~lastKeys;
1213 if (newKeys & VKEY_LEFT) {
1214 --choice;
1215 redraw = 1;
1216 ezPlaySample("shift_wav", 128);
1217 }
1218 if (newKeys & VKEY_RIGHT) {
1219 ++choice;
1220 redraw = 1;
1221 ezPlaySample("shift_wav", 128);
1222 }
1223 if (newKeys & VKEY_ROTL) {
1224 choice = 0;
1225 redraw = 1;
1226 ezPlaySample("rotate_wav", 128);
1227 }
1228 if (newKeys & VKEY_ROTR) {
1229 done = 1;
1230 ezPlaySample("line_wav", 128);
1231 }
1232 if (choice < 0) {
1233 choice += N_TITLE_ACTIONS;
1234 }
1235 if (choice >= N_TITLE_ACTIONS) {
1236 choice -= N_TITLE_ACTIONS;
1237 }
1239 lastKeys = keys;
1241 if (!redraw) {
1242 rest(30);
1243 }
1244 if (wantsClose) {
1245 done = 1;
1246 choice = 0;
1247 }
1248 }
1249 destroy_bitmap(back);
1250 return choice;
1251 }
1254 void setupWindow(void) {
1255 set_window_title("LOCKJAW");
1256 bgColor = makecol(255, 255, 255);
1257 pfFgColor = bgColor;
1258 hiliteColor = makecol(255, 255, 204);
1259 refreshRate = get_refresh_rate();
1260 }
1262 void drawCoprNotice(void) {
1263 clear_to_color(screen, pfBgColor);
1264 for (int i = 0; i < MENU_COPR_NOTICE_LINES; ++i) {
1265 textout_ex(screen, font, menuCoprNotice[i],
1266 16, 580 + 12 * (i - MENU_COPR_NOTICE_LINES),
1267 pfFgColor, -1);
1268 }
1269 }
1272 /**
1273 * Destroys the back buffers for both playfields.
1274 */
1275 static void destroyBackBuf(LJPCView *plat) {
1276 for (int i = 0; i < MAX_PLAYERS; ++i) {
1277 if (plat->back) {
1278 destroy_bitmap(plat->back);
1279 plat->back = NULL;
1280 }
1281 }
1282 if (plat->skin->fullback) {
1283 destroy_bitmap(plat->skin->fullback);
1284 plat->skin->fullback = NULL;
1285 }
1286 }
1288 /**
1289 * Creates the back buffers for both playfields.
1290 */
1291 static int createBackBuf(LJView *v) {
1292 v->plat->skin->fullback = create_system_bitmap(SCREEN_W, SCREEN_H);
1293 if(!v->plat->skin->fullback) {
1294 allegro_message("Could not create back buffer.\n");
1295 return 0;
1296 }
1298 int blkW = v->plat->skin->blkW;
1299 int blkH = v->plat->skin->blkH;
1300 int y = v->plat->skin->baseY
1301 - v->plat->skin->pfElev
1302 - blkH * v->field->ceiling;
1304 for (int i = 0; i < MAX_PLAYERS; ++i) {
1305 int x = v->plat->skin->baseX
1306 + blkW * v[i].field->leftWall;
1308 v[i].plat->back = create_sub_bitmap(v->plat->skin->fullback,
1309 x + SCREEN_W / 2 * i,
1310 y,
1311 v->plat->skin->blkW * LJ_PF_WID,
1312 v->plat->skin->blkH * LJ_PF_VIS_HT);
1313 if (!v[i].plat->back) {
1314 destroyBackBuf(v->plat);
1315 return 0;
1316 }
1317 }
1318 return 1;
1319 }
1321 /**
1322 * Destroys all system bitmaps that a given view owns.
1323 * Useful before changing screen mode.
1324 */
1325 void destroySystemBitmaps(LJPCView *plat) {
1326 destroyBackBuf(plat);
1327 if (plat->skin->connBlocks) {
1328 destroy_bitmap(plat->skin->connBlocks);
1329 plat->skin->connBlocks = NULL;
1330 }
1331 }
1333 int openWindow(int windowed)
1334 {
1335 int depth = desktop_color_depth();
1336 int card = windowed ? GFX_AUTODETECT_WINDOWED : GFX_AUTODETECT_FULLSCREEN;
1338 /* Reference implementation for Allegro is not compatible with
1339 indexed color. */
1340 if (depth < 15) {
1341 depth = 16;
1342 }
1344 // Full screen procedure
1345 set_color_depth(depth);
1346 if (set_gfx_mode(card, skinW, skinH, 0, 0) == 0) {
1347 setupWindow();
1348 return 0;
1349 }
1351 // Windows can't tell 16 bit from 15 bit. If desktop color depth is reported as 16, try 15 too.
1352 if (depth == 16) {
1353 set_color_depth(15);
1354 if (set_gfx_mode(card, skinW, skinH, 0, 0) == 0) {
1355 setupWindow();
1356 return 0;
1357 }
1358 }
1360 return -1;
1361 }
1363 BITMAP *loadConnections(const char *filename, int blkW, int blkH) {
1364 BITMAP *src = load_bitmap(filename, NULL);
1366 if (!src) {
1367 return NULL;
1368 }
1369 BITMAP *dst = create_system_bitmap(blkW*16, blkH*16);
1370 if (!dst) {
1371 destroy_bitmap(src);
1372 return NULL;
1373 }
1374 acquire_bitmap(dst);
1375 for (unsigned int col = 0; col < 16; ++col) {
1376 unsigned int srcXBase = (col & 0x03) * blkW * 2;
1377 unsigned int srcYBase = (col >> 2) * blkH * 2;
1378 unsigned int dstYBase = col * blkH;
1379 for (unsigned int conn = 0; conn < 16; ++conn) {
1380 unsigned int dstXBase = conn * blkW;
1381 unsigned int topSegY = (conn & CONNECT_U) ? blkH : 0;
1382 unsigned int botSegY = (conn & CONNECT_D) ? blkH/2 : 3*blkH/2;
1383 unsigned int leftSegX = (conn & CONNECT_L) ? blkW : 0;
1384 unsigned int rightSegX = (conn & CONNECT_R) ? blkW/2 : 3*blkW/2;
1385 blit(src, dst,
1386 srcXBase + leftSegX, srcYBase + topSegY,
1387 dstXBase + 0, dstYBase + 0,
1388 blkW/2, blkH/2);
1389 blit(src, dst,
1390 srcXBase + rightSegX, srcYBase + topSegY,
1391 dstXBase + blkW/2, dstYBase + 0,
1392 blkW/2, blkH/2);
1393 blit(src, dst,
1394 srcXBase + leftSegX, srcYBase + botSegY,
1395 dstXBase + 0, dstYBase + blkH/2,
1396 blkW/2, blkH/2);
1397 blit(src, dst,
1398 srcXBase + rightSegX, srcYBase + botSegY,
1399 dstXBase + blkW/2, dstYBase + blkH/2,
1400 blkW/2, blkH/2);
1401 }
1402 }
1403 release_bitmap(dst);
1404 destroy_bitmap(src);
1405 return dst;
1406 }
1408 void closeWindow(void) {
1409 set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
1410 }
1412 static void mainCleanup(LJPCView *v) {
1413 destroyBackBuf(v);
1414 if (v->skin->blocks) {
1415 destroy_bitmap(v->skin->blocks);
1416 }
1417 if (v->skin->connBlocks) {
1418 destroy_bitmap(v->skin->connBlocks);
1419 }
1420 LJMusic_delete(v->skin->bgm);
1421 if (withSound) {
1422 remove_sound();
1423 }
1424 closeWindow();
1425 }
1427 /**
1428 * Resets all skin settings to their initial values
1429 * so that skins can override them.
1430 */
1431 void loadSkinDefaults(LJPCSkin *s) {
1432 ustrzcpy(ljblocksSRSName, sizeof(ljblocksSRSName) - 1,
1433 "ljblocks.bmp");
1434 ustrzcpy(ljblocksSegaName, sizeof(ljblocksSegaName) - 1,
1435 "ljblocks-sega.bmp");
1436 ustrzcpy(ljconnSRSName, sizeof(ljconnSRSName) - 1,
1437 "ljconn.bmp");
1438 ustrzcpy(ljconnSegaName, sizeof(ljconnSegaName) - 1,
1439 "ljconn-sega.bmp");
1440 ustrzcpy(ljbgName, sizeof(ljbgName) - 1,
1441 "ljbg.jpg");
1442 ustrzcpy(menubgName, sizeof(ljbgName) - 1,
1443 "menubg.jpg");
1444 ustrzcpy(bgmName, sizeof(bgmName) - 1,
1445 "bgm.s3m");
1446 ustrzcpy(bgmRhythmName, sizeof(bgmRhythmName) - 1,
1447 "bgm-rhythm.s3m");
1448 bgmLoopPoint = 0;
1449 bgmReadyGo = 0;
1450 bgmVolume = 128;
1451 bgColor = makecol(255, 255, 255);
1452 fgColor = makecol(0, 0, 0);
1453 hiliteColor = makecol(255, 255, 204);
1454 pfBgColor = makecol(0, 0, 0);
1455 pfFgColor = makecol(255, 255, 255);
1456 s->blkW = 24;
1457 s->blkH = 24;
1458 s->transparentPF = 0;
1459 s->shiftScale = 0;
1460 s->baseX = 0;
1461 s->nextPos = 0;
1462 }
1464 /**
1465 * Converts a single hexadecimal digit to its value.
1466 * @param in USASCII/Unicode value of hex digit character
1467 * @return value
1468 */
1469 int hexDigitValue(int in) {
1470 if (in >= '0' && in <= '9') {
1471 return in - '0';
1472 } else if (in >= 'A' && in <= 'F') {
1473 return in - 'A' + 10;
1474 } else if (in >= 'a' && in <= 'f') {
1475 return in - 'a' + 10;
1476 } else {
1477 return -1;
1478 }
1479 }
1481 int translateComponent(const char **in, size_t nDigits) {
1482 const char *here = *in;
1483 int hi = hexDigitValue(*here++);
1484 int lo = nDigits > 3 ? hexDigitValue(*here++) : hi;
1485 *in = here;
1486 if (hi >= 0 && lo >= 0) {
1487 return hi * 16 + lo;
1488 } else {
1489 return -1;
1490 }
1491 }
1493 /**
1494 * Interprets a hexadecimal color specifier.
1495 * @param in hexadecimal color specifier, in #ABC or #AABBCC format
1496 * @return Allegro device-dependent color, in makecol() format
1497 */
1498 int translateColor(const char *in) {
1499 size_t nDigits = 0;
1501 // Verify and skip #
1502 if (*in != '#') {
1503 return -1;
1504 }
1505 ++in;
1507 // Determine whether we have a 3-digit color or a 6-digit color
1508 for (const char *here = in;
1509 hexDigitValue(*here) >= 0;
1510 ++here) {
1511 ++nDigits;
1512 }
1513 if (nDigits != 3 && nDigits != 6) {
1514 return -1;
1515 }
1517 int red = translateComponent(&in, nDigits);
1518 int green = translateComponent(&in, nDigits);
1519 int blue = translateComponent(&in, nDigits);
1520 if (red >= 0 && green >= 0 && blue >= 0) {
1521 return makecol(red, green, blue);
1522 } else {
1523 return -1;
1524 }
1525 };
1527 /**
1528 * Loads skin parameters from the file.
1529 * @param skinName Name of skin ini file
1530 */
1531 int loadSkinFile(LJPCSkin *s, const char *skinName) {
1533 // Don't use ljfopen here because lj.ini specifies the absolute skin file
1534 FILE *fp = fopen(skinName, "rt");
1535 char key[1024], var[1024], val[1024], input_buf[1024];
1537 key[0] = 0;
1538 var[0] = 0;
1539 val[0] = 0;
1540 loadSkinDefaults(s);
1542 if (!fp) return 0;
1543 while(1) {
1544 int rval;
1546 if(!fgets (input_buf, sizeof(input_buf), fp))
1547 break;
1548 rval = parse_ini_line(input_buf, key, var, val);
1550 if(!ustrcmp ("ljblocksSRS", var)) {
1551 ustrzcpy(ljblocksSRSName, sizeof(ljblocksSRSName) - 1, val);
1552 }
1553 else if(!ustrcmp ("ljblocksSega", var)) {
1554 ustrzcpy(ljblocksSegaName, sizeof(ljblocksSegaName) - 1, val);
1555 }
1556 else if(!ustrcmp ("ljconnSRS", var)) {
1557 ustrzcpy(ljconnSRSName, sizeof(ljconnSRSName) - 1, val);
1558 }
1559 else if(!ustrcmp ("ljconnSega", var)) {
1560 ustrzcpy(ljconnSegaName, sizeof(ljconnSegaName) - 1, val);
1561 }
1562 else if(!ustrcmp ("bgm", var)) {
1563 ustrzcpy(bgmName, sizeof(bgmName) - 1, val);
1564 }
1565 else if(!ustrcmp ("bgmRhythm", var)) {
1566 ustrzcpy(bgmRhythmName, sizeof(bgmRhythmName) - 1, val);
1567 }
1568 else if(!ustrcmp ("ljbg", var)) {
1569 ustrzcpy(ljbgName, sizeof(ljbgName) - 1, val);
1570 }
1571 else if(!ustrcmp ("bgmLoopPoint", var)) {
1572 unsigned long int c = strtoul(val, NULL, 0);
1573 if (c >= 0) {
1574 bgmLoopPoint = c;
1575 }
1576 }
1577 else if(!ustrcmp ("bgmVolume", var)) {
1578 unsigned long int c = strtol(val, NULL, 0);
1579 if (c >= 0) {
1580 bgmVolume = c;
1581 }
1582 }
1583 else if(!ustrcmp ("bgmReadyGo", var)) {
1584 unsigned long int c = strtoul(val, NULL, 0);
1585 if (c >= 0) {
1586 bgmReadyGo = c;
1587 }
1588 }
1589 else if(!ustrcmp ("pfbgcolor", var)) {
1590 int c = translateColor(val);
1591 if (c >= 0) {
1592 pfBgColor = c;
1593 }
1594 }
1595 else if(!ustrcmp ("pfcolor", var)) {
1596 int c = translateColor(val);
1597 if (c >= 0) {
1598 pfFgColor = c;
1599 }
1600 }
1601 else if(!ustrcmp ("bgcolor", var)) {
1602 int c = translateColor(val);
1603 if (c >= 0) {
1604 bgColor = c;
1605 }
1606 }
1607 else if(!ustrcmp ("color", var)) {
1608 int c = translateColor(val);
1609 if (c >= 0) {
1610 fgColor = c;
1611 }
1612 }
1613 else if(!ustrcmp ("hilitecolor", var)) {
1614 int c = translateColor(val);
1615 if (c >= 0) {
1616 hiliteColor = c;
1617 }
1618 }
1619 else if(!ustrcmp ("blkW", var)) {
1620 int c = strtol(val, NULL, 0);
1621 if (c >= 0) {
1622 s->blkW = c;
1623 }
1624 }
1625 else if(!ustrcmp ("blkH", var)) {
1626 int c = strtol(val, NULL, 0);
1627 if (c >= 0) {
1628 s->blkH = c;
1629 }
1630 }
1631 else if(!ustrcmp ("transparentPF", var)) {
1632 int c = atoi(val);
1633 if (c >= 0) {
1634 s->transparentPF = c;
1635 }
1636 }
1637 else if(!ustrcmp ("shiftScale", var)) {
1638 int c = atoi(val);
1639 if (c >= 0) {
1640 s->shiftScale = c;
1641 }
1642 }
1643 else if(!ustrcmp ("baseX", var)) {
1644 int c = atoi(val);
1645 if (c >= 0) {
1646 s->baseX = c;
1647 }
1648 }
1649 else if(!ustrcmp ("nextPos", var)) {
1650 int c = atoi(val);
1651 if (c >= 0) {
1652 s->nextPos = c;
1653 }
1654 }
1655 else if(!ustrcmp ("wndW", var)) {
1656 unsigned long int c = strtol(val, NULL, 0);
1657 if (c >= 0) {
1658 skinW = c;
1659 }
1660 }
1661 else if(!ustrcmp ("wndH", var)) {
1662 unsigned long int c = strtoul(val, NULL, 0);
1663 if (c >= 0) {
1664 skinH = c;
1665 }
1666 }
1668 }
1669 fclose(fp);
1670 ljpathSetSkinFolder(skinName);
1671 return 0;
1672 }
1674 static void drawProgressSegment(int min, int max) {
1675 min = min * SCREEN_W / 100;
1676 max = max * SCREEN_W / 100;
1677 int blue = makecol(0, 0, 255);
1678 rectfill(screen, min, SCREEN_H - 8, max - 1, SCREEN_H - 5, blue);
1679 int orange = makecol(255, 128, 0);
1680 rectfill(screen, min, SCREEN_H - 4, max - 1, SCREEN_H - 1, orange);
1681 }
1683 static int loadSkin(LJView *v, const char *skinName) {
1684 BITMAP *bmp;
1685 const LJRotSystem *rs = rotSystems[v->field->rotationSystem];
1686 int colorScheme = rs->colorScheme;
1688 rectfill(screen, 0, 592, 799, 599, 0);
1689 loadSkinFile(v->plat->skin, skinName);
1691 destroyBackBuf(v->plat);
1692 if (!createBackBuf(v)) {
1693 allegro_message("Could not create back buffer.\n");
1694 return 1;
1695 }
1697 drawProgressSegment(0, 20);
1699 // Load background image
1700 char path[PATH_MAX];
1701 bmp = ljpathFind_r(path, ljbgName)
1702 ? load_bitmap(path, NULL) : NULL;
1703 if (v->plat->skin->bg) {
1704 destroy_bitmap(v->plat->skin->bg);
1705 }
1706 if (bmp) {
1708 // If the image size doesn't match the window size, resize it
1709 if (bmp->w != SCREEN_W || bmp->h != SCREEN_H) {
1710 BITMAP *resized = create_bitmap(SCREEN_W, SCREEN_H);
1712 if (resized) {
1713 stretch_blit(bmp, resized, 0, 0, bmp->w, bmp->h,
1714 0, 0, SCREEN_W, SCREEN_H);
1715 destroy_bitmap(bmp);
1716 }
1717 if (bmp) {
1718 bmp = resized;
1719 } else {
1720 allegro_message("Background image \"%s\" resize failed.\n",
1721 ljbgName);
1722 }
1723 }
1724 }
1725 if(!bmp) {
1726 bmp = create_bitmap(SCREEN_W, SCREEN_H);
1727 if (bmp) {
1728 allegro_message("Background image \"%s\" not found.\n"
1729 "Using plain background instead.\n",
1730 ljbgName);
1731 clear_to_color(bmp, bgColor);
1732 } else {
1733 allegro_message("Background image \"%s\" not found.\n",
1734 ljbgName);
1735 return 0;
1736 }
1737 }
1738 v->plat->skin->bg = bmp;
1739 drawProgressSegment(20, 40);
1741 // load block images
1742 if (v->plat->skin->blocks) {
1743 destroy_bitmap(v->plat->skin->blocks);
1744 }
1745 bmp = ljpathFind_r(path, colorScheme
1746 ? ljblocksSegaName
1747 : ljblocksSRSName)
1748 ? load_bitmap(path, NULL) : NULL;
1749 v->plat->skin->blocks = bmp;
1750 if(!v->plat->skin->blocks) {
1751 allegro_message("Background image \"%s\" not found.\n",
1752 ljbgName);
1753 return 0;
1754 }
1755 drawProgressSegment(40, 60);
1757 // load connected block images
1758 if (v->plat->skin->connBlocks) {
1759 destroy_bitmap(v->plat->skin->connBlocks);
1760 }
1761 bmp = ljpathFind_r(path, colorScheme
1762 ? ljconnSegaName
1763 : ljconnSRSName)
1764 ? loadConnections(path,
1765 v->plat->skin->blkW,
1766 v->plat->skin->blkH)
1767 : NULL;
1768 v->plat->skin->connBlocks = bmp;
1769 drawProgressSegment(60, 80);
1771 // load music
1772 int isRhythm = (v->field->speedState.curve == LJSPD_RHYTHM);
1773 if (ljpathFind_r(path, isRhythm ? bgmRhythmName : bgmName)) {
1774 LJMusic_load(v->plat->skin->bgm, path);
1775 }
1776 if (!isRhythm) {
1777 LJMusic_setLoop(v->plat->skin->bgm, bgmLoopPoint);
1778 }
1779 drawProgressSegment(80, 100);
1780 return 1;
1781 }
1783 void drop_mouse(void) {
1784 while (mouse_y < SCREEN_H - 25) {
1785 position_mouse(mouse_x, mouse_y + 20);
1786 rest(10);
1787 }
1788 ezPlaySample("land_wav", 192);
1789 position_mouse(mouse_x, SCREEN_H - 5);
1790 ezPlaySample("lock_wav", 192);
1791 }
1793 int pickReplay(void) {
1794 FONT *oldFont = font;
1795 font = (FONT *)aver16;
1796 install_mouse();
1797 int got = file_select_ex("Choose a demo:", demoFilename, "ljm", sizeof(demoFilename), 600, 400);
1798 drop_mouse();
1799 remove_mouse();
1800 font = oldFont;
1801 return got ? 0 : -1;
1802 }
1804 void badReplay(void) {
1805 acquire_screen();
1806 clear_to_color(screen, bgColor);
1807 textout_ex(screen, aver32, "The demo", 100, 100, fgColor, -1);
1808 textout_ex(screen, aver16, demoFilename, 100, 130, fgColor, -1);
1809 textout_ex(screen, aver32, "could not be played because it was", 100, 150, fgColor, -1);
1810 textout_ex(screen, aver32, "recorded with a different version", 100, 180, fgColor, -1);
1811 textout_ex(screen, aver32, "of LOCKJAW software.", 100, 210, fgColor, -1);
1812 release_screen();
1814 LJBits lastKeys = ~0;
1815 LJBits keys, newKeys = 0;
1817 do {
1818 keys = menuReadPad();
1819 newKeys = keys & ~lastKeys;
1820 lastKeys = keys;
1821 rest(30);
1822 } while (!(newKeys & (VKEY_ROTL | VKEY_ROTR)));
1823 }
1825 int pickSkin(void) {
1826 FONT *oldFont = font;
1827 font = (FONT *)aver16;
1828 install_mouse();
1829 int got = file_select_ex("Choose a skin:", skinName, "skin", sizeof(skinName), 600, 400);
1830 drop_mouse();
1831 remove_mouse();
1832 font = oldFont;
1833 return got ? 0 : -1;
1834 }
1836 void calcElev(LJView *v) {
1837 int blkH = v->plat->skin->blkH;
1838 int ceiling = v->field->ceiling;
1839 int elev = (LJ_PF_VIS_HT - ceiling) * blkH;
1841 if (elev > 480 - ceiling * blkH) {
1842 elev = 480 - ceiling * blkH;
1843 }
1844 if (elev < 0) {
1845 elev = 0;
1846 }
1847 v->plat->skin->pfElev = elev;
1848 }
1850 int main(const int argc, const char *const *argv) {
1851 const char *cmdLineDemo = NULL;
1852 int lastPreset = -2; // start out with full custom
1853 LJPCSkin skin = {
1854 .baseY = 552,
1855 .blkW = 24,
1856 .blkH = 24
1857 };
1858 LJField p[MAX_PLAYERS];
1859 LJControl control[2] = {
1860 {
1861 .replaySrc = 0,
1862 .replayDst = 0
1863 }
1864 };
1865 LJPCView platView[MAX_PLAYERS] = {
1866 {
1867 .skin = &skin
1868 },
1869 {
1870 .skin = &skin
1871 }
1872 };
1873 LJView mainView[MAX_PLAYERS] = {
1874 {
1875 .field = &p[0],
1876 .control = &control[0],
1877 .plat = &platView[0],
1878 .backDirty = ~0
1879 },
1880 {
1881 .field = &p[1],
1882 .control = &control[1],
1883 .plat = &platView[1],
1884 .backDirty = ~0,
1885 }
1886 };
1888 struct LJPrefs prefs = {
1889 .number = {
1890 [OPTIONS_TRAILS] = 1,
1891 [OPTIONS_AUTO_PAUSE] = 1,
1892 [OPTIONS_AUTO_RECORD] = 0,
1893 [OPTIONS_WINDOWED] = 1
1894 }
1895 };
1897 // as of 0.46, we're starting to make it a bit more 2-player-clean
1898 int nPlayers = 1;
1900 allegro_init();
1901 ljpathInit(argc > 0 ? argv[0] : ".");
1902 install_timer();
1903 initOptions(prefs.number);
1904 loadOptions(&prefs);
1905 loadSkinFile(&skin, skinName);
1907 if (argc > 1) {
1908 if (argv[1][0] == '-') {
1909 if (!ustrcmp("--help", argv[1])
1910 || !ustrcmp("-h", argv[1])) {
1911 allegro_message("Usage: lj [DEMOFILE]\n");
1912 return 0;
1913 }
1914 } else {
1915 cmdLineDemo = argv[1];
1916 }
1917 }
1919 if (openWindow(prefs.number[OPTIONS_WINDOWED]) != 0) {
1920 allegro_message("LOCKJAW fatal error: Could not open an %dx%d pixel %s.\n"
1921 "Trying %s next time.\n",
1922 skinW, skinH,
1923 prefs.number[OPTIONS_WINDOWED] ? "window": "screen mode",
1924 prefs.number[OPTIONS_WINDOWED] ? "the full screen": "a window");
1925 prefs.number[OPTIONS_WINDOWED] = !prefs.number[OPTIONS_WINDOWED];
1926 saveOptions(&prefs);
1927 return EXIT_FAILURE;
1928 }
1929 drawCoprNotice();
1930 LOCK_FUNCTION(incCurTime);
1931 LOCK_VARIABLE(curTime);
1932 install_int_ex(incCurTime, BPM_TO_TIMER(LJ_TICK_RATE));
1934 jpgalleg_init();
1935 set_color_conversion(COLORCONV_NONE);
1936 {
1937 char path[PATH_MAX];
1938 if (ljpathFind_r(path, "lj.dat")) {
1939 dat = load_datafile(path);
1940 }
1941 }
1942 set_color_conversion(COLORCONV_TOTAL);
1943 if(!dat) {
1944 closeWindow();
1945 allegro_message("LOCKJAW fatal error: Could not load datafile lj.dat\n");
1946 return 1;
1947 }
1949 {
1950 const DATAFILE *aver16dat = find_datafile_object(dat, "Aver16_bmp");
1951 aver16 = aver16dat ? aver16dat->dat : font;
1952 const DATAFILE *aver32dat = find_datafile_object(dat, "Aver32_bmp");
1953 aver32 = aver32dat ? aver32dat->dat : aver16;
1954 }
1956 LOCK_FUNCTION(amnesia);
1957 LOCK_VARIABLE(redrawWholeScreen);
1959 // If we can be notified on switching out, take this notification.
1960 if (set_display_switch_mode(SWITCH_BACKGROUND) >= 0
1961 || set_display_switch_mode(SWITCH_BACKAMNESIA) >= 0) {
1962 set_display_switch_callback(SWITCH_OUT, requestPause);
1963 }
1964 set_display_switch_callback(SWITCH_IN, amnesia);
1966 install_keyboard();
1967 initKeys();
1969 for (int i = 0; i < MAX_PLAYERS; ++i) {
1970 p[i].seed = time(NULL) + 123456789*i;
1971 }
1973 reserve_voices(8, 0);
1974 set_volume_per_voice(0);
1975 #ifdef ALLEGRO_WINDOWS
1976 // Under Windows, use the Allegro mixer because on my machine
1977 // and probably others, the built-in mixer will replace the very
1978 // beginning of one sound with the end of the last sound played
1979 // on that voice.
1980 withSound = !install_sound(DIGI_DIRECTAMX(0), MIDI_NONE, NULL);
1981 #else
1982 withSound = !install_sound(DIGI_AUTODETECT, MIDI_NONE, NULL);
1983 #endif
1984 skin.bgm = LJMusic_new();
1985 {
1986 char path[PATH_MAX];
1987 if (ljpathFind_r(path, "sound.dat")) {
1988 sound_dat = load_datafile(path);
1989 }
1990 }
1992 for (int i = 0; i < MAX_PLAYERS; ++i) {
1993 unpackOptions(&mainView[i], &prefs);
1994 }
1995 if (loadSkin(&mainView[0], skinName) < 0) {
1996 mainCleanup(platView);
1997 return EXIT_FAILURE;
1998 } else {
2000 }
2002 if(!skin.blocks) {
2003 mainCleanup(platView);
2004 allegro_message("Blocks image \"%s\" not found.\n",
2005 p[0].rotationSystem
2006 ? ljblocksSegaName
2007 : ljblocksSRSName);
2008 return 1;
2009 }
2011 srand(time(NULL));
2013 // Wait for copyright notice to be displayed "conspicuously"
2014 if (curTime < 180) {
2015 rest(3100 - curTime * 16);
2016 }
2018 for (int action = cmdLineDemo ? TITLE_REPLAY : title();
2019 action > TITLE_EXIT && !wantsClose;
2020 action = title()) {
2021 switch (action) {
2022 case TITLE_PLAY:
2023 for (int preset = getPreset(lastPreset);
2024 preset != -1 && !wantsClose;
2025 preset = getPreset(lastPreset))
2026 {
2027 lastPreset = preset;
2028 for (int i = 0; i < MAX_PLAYERS; ++i) {
2029 unpackOptions(&mainView[i], &prefs);
2030 }
2031 if (preset >= 0) {
2032 presetStart();
2033 presetAdd(preset);
2034 for (int i = 0; i < MAX_PLAYERS; ++i) {
2035 unpackOptions(&mainView[i], &prefs);
2036 presetFinish(&mainView[i]);
2037 }
2038 }
2039 // reload the skin
2040 if (loadSkin(&mainView[0], skinName) < 0) {
2041 mainCleanup(platView);
2042 return EXIT_FAILURE;
2043 };
2044 for (int i = 0; i < nPlayers; ++i) {
2045 calcElev(&mainView[0]);
2046 pcInit(&mainView[0], &prefs);
2047 }
2048 wantPause = 0;
2049 platView[0].wantRecord = prefs.number[OPTIONS_AUTO_RECORD];
2050 LJMusic_start(skin.bgm,
2051 4096, // mix buffer size
2052 bgmVolume); // volume scale
2054 LJView *const players[MAX_PLAYERS] = {&mainView[0], &mainView[1]};
2055 play(players, nPlayers);
2056 LJMusic_stop(skin.bgm);
2057 if (!wantsClose) {
2058 gameOverAnimation(&platView[0], &p[0],
2059 control[0].countdown <= 0);
2060 debrief(&mainView[0]);
2061 }
2062 }
2063 break;
2065 case TITLE_REPLAY:
2066 {
2067 if (cmdLineDemo) {
2068 ustrzcpy(demoFilename, sizeof(demoFilename) - 1,
2069 cmdLineDemo);
2070 cmdLineDemo = NULL;
2071 } else if (pickReplay() < 0) {
2072 break;
2073 }
2075 unpackOptions(&mainView[0], &prefs);
2076 calcElev(&mainView[0]);
2077 pcInit(&mainView[0], &prefs);
2078 wantPause = 0;
2079 platView[0].wantRecord = 0;
2080 LJMusic_start(skin.bgm,
2081 4096, // mix buffer size
2082 bgmVolume); // volume scale
2084 LJView *const players[2] = {&mainView[0], &mainView[1]};
2086 p->gimmick = -1; // gimmick must be < 0 to activate replay
2087 play(players, 1);
2088 LJMusic_stop(skin.bgm);
2089 if (p[0].gimmick < 0) {
2090 badReplay();
2091 } else {
2092 if (!wantsClose) {
2093 gameOverAnimation(&platView[0], &p[0],
2094 control[0].countdown <= 0);
2095 debrief(&mainView[0]);
2096 }
2097 }
2098 }
2099 break;
2101 case TITLE_SKIN:
2102 pickSkin();
2104 // if resolution changed, reopen the window
2105 {
2106 int oldW = skinW;
2107 int oldH = skinH;
2108 loadSkinFile(&skin, skinName);
2109 if (skinH != oldH || skinW != oldW) {
2110 destroySystemBitmaps(&platView[0]);
2111 openWindow(prefs.number[OPTIONS_WINDOWED]);
2112 }
2113 }
2115 // reload the skin
2116 if (loadSkin(&mainView[0], skinName) < 0) {
2117 mainCleanup(platView);
2118 return EXIT_FAILURE;
2119 };
2121 // save options
2122 saveOptions(&prefs);
2123 break;
2125 case TITLE_OPTIONS:
2126 {
2127 int oldWindowed = prefs.number[OPTIONS_WINDOWED];
2128 options(&mainView[0], prefs.number);
2129 if (oldWindowed != prefs.number[OPTIONS_WINDOWED]) {
2130 destroySystemBitmaps(&platView[0]);
2131 openWindow(prefs.number[OPTIONS_WINDOWED]);
2132 }
2133 }
2134 saveOptions(&prefs);
2136 // reload the skin if the player changed the rotation system
2137 unpackOptions(&mainView[0], &prefs);
2138 if (wantsClose) {
2139 break;
2140 }
2141 if (loadSkin(&mainView[0], skinName) < 0) {
2142 mainCleanup(platView);
2143 return EXIT_FAILURE;
2144 };
2145 break;
2147 case TITLE_KEYS:
2148 configureKeys();
2149 break;
2151 }
2152 }
2154 mainCleanup(platView);
2155 return 0;
2156 } END_OF_MAIN();