comparison src/ljpc.c @ 1:38c62fded078

initial builds (linux and lj.nds)
author paulo@localhost
date Fri, 13 Mar 2009 01:10:13 -0700
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:18f0b4d2549c
1 /* PC frontend for LOCKJAW, an implementation of the Soviet Mind Game
2
3 Copyright (C) 2006-2008 Damian Yerrick <tepples+lj@spamcop.net>
4
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.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
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.
22
23 */
24
25 #include "ljpc.h"
26 #include "ljreplay.h"
27 #include <jpgalleg.h>
28 #include <time.h>
29 #include "scenario.h"
30 #include "ljpath.h"
31
32 #if 1
33 #define LJ_VERSION "0.46a ("__DATE__")"
34 #else
35 #define LJ_VERSION "WIP ("__DATE__")"
36 #endif
37
38 #define USE_PICS_FOLDER 0
39 #define MAX_PLAYERS 2
40 #define LJ_TICK_RATE 3600 // frames per minute
41
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];
52
53 int withPics = -1;
54
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;
69
70
71
72
73 static void incCurTime(void) {
74 ++curTime;
75 } END_OF_FUNCTION(incCurTime);
76
77 static void amnesia(void) {
78 redrawWholeScreen = 1;
79 } END_OF_FUNCTION(amnesia);
80
81 static void requestPause(void) {
82 wantPause |= autoPause;
83 }
84
85
86 int getTime(void) {
87 return curTime;
88 }
89
90 void yieldCPU(void) {
91 rest(5);
92 }
93
94 void ljBeginDraw(LJView *v, int sync) {
95 vsync();
96 if (redrawWholeScreen) {
97 redrawWholeScreen = 0;
98 playRedrawScreen(v);
99 }
100
101 BITMAP *sc = v->plat->skin->fullback;
102
103 // Draw shape
104 blit(v->plat->skin->bg, sc, 0, 0, 0, 0, 48, 16);
105 if (v->control->replayDst) {
106
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) {
111
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 {
119
120 // square: stop
121 rectfill(sc, 2, 2, 14, 14, fgColor);
122 }
123 blit(sc, screen, 0, 0, 0, 0, 48, 16);
124 }
125
126 void ljEndDraw(LJView *v) {
127
128 }
129
130 static void startRecording(LJView *v) {
131 withPics = -1;
132
133 // close existing playback
134 if (v->control->replaySrc) {
135 replayClose(v->control->replaySrc);
136 v->control->replaySrc = NULL;
137 }
138
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];
145
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 }
153
154 static void startPlaying(LJView *v) {
155 withPics = -1;
156
157 // close existing recording
158 if (v->control->replayDst) {
159 replayClose(v->control->replayDst);
160 v->control->replayDst = NULL;
161 }
162
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];
169
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 }
181
182 int ljHandleConsoleButtons(LJView *v) {
183 int canceled = 0;
184
185 while (keypressed()) {
186 int scancode;
187 int codepoint = ureadkey(&scancode);
188
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 }
209
210 if (wantsClose) {
211 canceled = 1;
212 }
213
214 return canceled;
215 }
216
217
218 static void drawBlock(const LJPCView *const v, int x, int y, int blk) {
219
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);
225
226 if (v->skin->transparentPF && (blk == 0 || blk == 0x100)) {
227
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) {
234
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 {
240
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 }
246
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 }
255
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;
263
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;
269
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 }
288
289
290 #define SHADOW_BLOCK 0x00
291
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;
313
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;
322
323 // color: 0 for shadow, >0 for piece
324 BITMAP *connBlocks = (color != SHADOW_BLOCK)
325 ? v->plat->skin->connBlocks : NULL;
326
327 BITMAP *transTemp = color < 0
328 || (color == SHADOW_BLOCK && v->hideShadow < LJSHADOW_COLORED)
329 ? create_bitmap(w, h) : 0;
330
331 if (transTemp) {
332 set_trans_blender(0, 0, 0, v->hideShadow ? 128 : 64);
333 }
334
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;
339
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);
348
349 if (color > 0) {
350 blkValue = color | (blkValue & CONNECT_MASK);
351 } else if (color == 0 && v->hideShadow == LJSHADOW_COLORLESS) {
352 blkValue = 0;
353 }
354
355 if (connBlocks) {
356 const unsigned int srcX = ((blkValue >> 0) & 15) * srcW;
357 const unsigned int srcY = ((blkValue >> 4) & 15) * srcH;
358
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;
383
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 }
407
408 return rows;
409 }
410
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);
419
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 }
429
430 v->backDirty |= bits;
431 v->frontDirty |= bits;
432 }
433
434
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];
444
445 // Draw trails
446 if (v->showTrails) {
447
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 }
453
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 }
463
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 }
470
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;
480
481 BITMAP *sc = v->plat->skin->fullback;
482
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;
488
489 // Move the hold piece within the screen
490 if (holdY < 0) {
491 holdY = 0;
492 }
493
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
508
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];
519
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;
535
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;
541
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 }
569
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];
579
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 }
589
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;
599
600 if (withPics >= 0) {
601 if (v->field->nPieces != withPics) {
602 saveScreen(v->field->nPieces);
603 }
604 withPics = v->field->nPieces;
605 }
606
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 }
613
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);
621
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;
636
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 }
652
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 }
667
668 if (v->frontDirty & LJ_DIRTY_NEXT) {
669
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);
690
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);
700
701 }
702
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);
710
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;
716
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 }
731
732 v->frontDirty &= (~0) << LJ_PF_HT;
733 }
734
735 void saveScreen(int n) {
736 BITMAP *b = create_bitmap(SCREEN_W, SCREEN_H);
737 if (b) {
738 PALETTE pal;
739
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 }
752
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
758
759 static const char *const nonPresetNames[N_NONPRESETS] = {
760 "Full Custom", "Back"
761 };
762
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);
772
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;
778
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 }
794
795 int getPreset(int lastPreset) {
796 LJBits lastKeys = ~0;
797 redrawWholeScreen = 1;
798
799 clear_keybuf();
800 if (lastPreset < 0) {
801 lastPreset += nLoadedPresets + N_NONPRESETS;
802 }
803
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);
810
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 }
828
829 int preset = lastPreset;
830 LJBits keys = menuReadPad();
831 LJBits newKeys = keys & ~lastKeys;
832
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 }
841
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 }
857
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 }
879
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 }
890
891 // Wrap the nonpresets into the negative numbers.
892 if (lastPreset >= nLoadedPresets) {
893 lastPreset -= (nLoadedPresets + N_NONPRESETS);
894 }
895
896 return lastPreset;
897 }
898
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;
907
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 }
921
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 }
939
940 }
941 LJMusic_pause(v->skin->bgm, 0);
942 redrawWholeScreen = 1;
943 clear_keybuf();
944 return quit;
945 }
946
947
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;
952
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 }
960
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 }
972
973 #if 0
974 void startingAniWantsSkip(LJView *v) {
975 LJInput unusedIn;
976 addKeysToInput(&unusedIn, readPad(), v->field, v->control);
977 }
978 #endif
979
980 void playSampleForTetromino(int piece);
981
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 }
990
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;
998
999 clear_keybuf();
1000 v->backDirty = 0;
1001
1002 playRedrawScreen(v);
1003 blitField(v);
1004 textout_centre_ex(screen, aver32, "Ready",
1005 readyGoX, readyGoY, pfFgColor, -1);
1006
1007 ezPlaySample("ready_wav", 128);
1008 restPollingMusic(36, bgmReadyGo ? v->plat->skin->bgm : NULL);
1009 v->frontDirty = ~0;
1010
1011 if (!wantsClose) {
1012 blitField(v);
1013 textout_centre_ex(screen, aver32, "GO!",
1014 readyGoX, readyGoY, pfFgColor, -1);
1015 drawScore(v);
1016
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 }
1026
1027
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;
1032
1033 ezPlaySample("sectionup_wav", 0);
1034 if (!won) {
1035 ezPlaySample("gameover_wav", 256);
1036 } else {
1037 ezPlaySample("win_wav", 256);
1038 }
1039
1040 for (int t = ceiling + v->skin->blkH - 2; t >= 0; --t) {
1041
1042 // FIXME: vsync doesn't work on Vista
1043 vsync();
1044 for (int row = ceiling - 1; row >= 0; --row) {
1045 int ysub = t - row;
1046
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 }
1058
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 };
1066
1067 static BITMAP *buildTitleScreen(void) {
1068 BITMAP *back = create_system_bitmap(SCREEN_W, SCREEN_H);
1069 if (!back) {
1070 return NULL;
1071 }
1072
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 }
1083
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 }
1094
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 }
1110
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;
1115
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 }
1122
1123 textout_centre_ex(back, aver32, "Arrows: change; Enter: choose",
1124 SCREEN_W / 2, 440,
1125 0, -1);
1126
1127 textout_centre_ex(back, aver32, "LOCKJAW: The Reference "LJ_VERSION,
1128 SCREEN_W / 2, SCREEN_H - 40,
1129 0, -1);
1130
1131 return back;
1132 }
1133
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 };
1143
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) {
1158
1159 // don't even draw if the player is trying to close the window
1160 if (wantsClose) {
1161 return 0;
1162 }
1163
1164 BITMAP *back = buildTitleScreen();
1165 LJBits lastKeys = ~0;
1166 int redraw = 1;
1167 int choice = 1;
1168
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 }
1176
1177 redrawWholeScreen = 1;
1178
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 }
1201
1202 while (keypressed()) {
1203 int scancode;
1204 ureadkey(&scancode);
1205 if (scancode == KEY_PRTSCR) {
1206 saveScreen(-1);
1207 }
1208 }
1209
1210 LJBits keys = menuReadPad();
1211 LJBits newKeys = keys & ~lastKeys;
1212
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 }
1238
1239 lastKeys = keys;
1240
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 }
1252
1253
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 }
1261
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 }
1270
1271
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 }
1287
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 }
1297
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;
1303
1304 for (int i = 0; i < MAX_PLAYERS; ++i) {
1305 int x = v->plat->skin->baseX
1306 + blkW * v[i].field->leftWall;
1307
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 }
1320
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 }
1332
1333 int openWindow(int windowed)
1334 {
1335 int depth = desktop_color_depth();
1336 int card = windowed ? GFX_AUTODETECT_WINDOWED : GFX_AUTODETECT_FULLSCREEN;
1337
1338 /* Reference implementation for Allegro is not compatible with
1339 indexed color. */
1340 if (depth < 15) {
1341 depth = 16;
1342 }
1343
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 }
1350
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 }
1359
1360 return -1;
1361 }
1362
1363 BITMAP *loadConnections(const char *filename, int blkW, int blkH) {
1364 BITMAP *src = load_bitmap(filename, NULL);
1365
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 }
1407
1408 void closeWindow(void) {
1409 set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
1410 }
1411
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 }
1426
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 }
1463
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 }
1480
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 }
1492
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;
1500
1501 // Verify and skip #
1502 if (*in != '#') {
1503 return -1;
1504 }
1505 ++in;
1506
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 }
1516
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 };
1526
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) {
1532
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];
1536
1537 key[0] = 0;
1538 var[0] = 0;
1539 val[0] = 0;
1540 loadSkinDefaults(s);
1541
1542 if (!fp) return 0;
1543 while(1) {
1544 int rval;
1545
1546 if(!fgets (input_buf, sizeof(input_buf), fp))
1547 break;
1548 rval = parse_ini_line(input_buf, key, var, val);
1549
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 }
1667
1668 }
1669 fclose(fp);
1670 ljpathSetSkinFolder(skinName);
1671 return 0;
1672 }
1673
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 }
1682
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;
1687
1688 rectfill(screen, 0, 592, 799, 599, 0);
1689 loadSkinFile(v->plat->skin, skinName);
1690
1691 destroyBackBuf(v->plat);
1692 if (!createBackBuf(v)) {
1693 allegro_message("Could not create back buffer.\n");
1694 return 1;
1695 }
1696
1697 drawProgressSegment(0, 20);
1698
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) {
1707
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);
1711
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);
1740
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);
1756
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);
1770
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 }
1782
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 }
1792
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 }
1803
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();
1813
1814 LJBits lastKeys = ~0;
1815 LJBits keys, newKeys = 0;
1816
1817 do {
1818 keys = menuReadPad();
1819 newKeys = keys & ~lastKeys;
1820 lastKeys = keys;
1821 rest(30);
1822 } while (!(newKeys & (VKEY_ROTL | VKEY_ROTR)));
1823 }
1824
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 }
1835
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;
1840
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 }
1849
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 };
1887
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 };
1896
1897 // as of 0.46, we're starting to make it a bit more 2-player-clean
1898 int nPlayers = 1;
1899
1900 allegro_init();
1901 ljpathInit(argc > 0 ? argv[0] : ".");
1902 install_timer();
1903 initOptions(prefs.number);
1904 loadOptions(&prefs);
1905 loadSkinFile(&skin, skinName);
1906
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 }
1918
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));
1933
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 }
1948
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 }
1955
1956 LOCK_FUNCTION(amnesia);
1957 LOCK_VARIABLE(redrawWholeScreen);
1958
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);
1965
1966 install_keyboard();
1967 initKeys();
1968
1969 for (int i = 0; i < MAX_PLAYERS; ++i) {
1970 p[i].seed = time(NULL) + 123456789*i;
1971 }
1972
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 }
1991
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 {
1999
2000 }
2001
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 }
2010
2011 srand(time(NULL));
2012
2013 // Wait for copyright notice to be displayed "conspicuously"
2014 if (curTime < 180) {
2015 rest(3100 - curTime * 16);
2016 }
2017
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
2053
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;
2064
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 }
2074
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
2083
2084 LJView *const players[2] = {&mainView[0], &mainView[1]};
2085
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;
2100
2101 case TITLE_SKIN:
2102 pickSkin();
2103
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 }
2114
2115 // reload the skin
2116 if (loadSkin(&mainView[0], skinName) < 0) {
2117 mainCleanup(platView);
2118 return EXIT_FAILURE;
2119 };
2120
2121 // save options
2122 saveOptions(&prefs);
2123 break;
2124
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);
2135
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;
2146
2147 case TITLE_KEYS:
2148 configureKeys();
2149 break;
2150
2151 }
2152 }
2153
2154 mainCleanup(platView);
2155 return 0;
2156 } END_OF_MAIN();