changeset 0:c84446dfb3f5

initial add
author paulo@localhost
date Fri, 13 Mar 2009 00:39:12 -0700
parents
children 38c62fded078
files BUGS.txt CHANGES.txt COPYING.txt GPL.txt README.html TODO.txt binzip.in docs/Compiling_on_Windows.txt docs/appicon.ico docs/appicon.xcf docs/dsicon.bmp docs/favicon.ico docs/ijlo-stz.png docs/ljhtml.css docs/ljlogo192.png docs/ljsnap018.png docs/titlebarbg.png dsmakefile gbamakefile makefile mkzip.bat obj/ds/index.txt obj/gba/index.txt obj/win32/index.txt src/debrief.c src/dsarm7.c src/dsdebrief.c src/dsjoy.c src/dssleep.c src/dssound7.c src/explode.c src/font.bmp src/fontdraw.c src/fontdraw.h src/fontdraw.iwram.c src/fontdraw_engine.c src/gba_asm.s src/gbablk.chr src/gbaisr.iwram.c src/gbamenus.c src/gbamenus.h src/gbanotefreq.c src/gbaopt.c src/gbasound.c src/gimmicks.c src/lj.c src/lj.h src/ljcontrol.h src/ljds.c src/ljds.h src/ljgba.c src/ljgba.h src/ljgbads.inc src/ljlocale.c src/ljlocale.h src/ljmusic.c src/ljmusic.h src/ljpath.c src/ljpath.h src/ljpc.c src/ljpc.h src/ljplay.c src/ljplay.h src/ljreplay.c src/ljreplay.h src/ljtypes.h src/ljvorbis.c src/ljvorbis.h src/macro.c src/old_pc_options.c src/options.c src/options.h src/pcdebrief.c src/pcjoy.c src/pcjoy.h src/pcsound.c src/pin8gba_sound.h src/random.c src/scenario.c src/scenario.h src/speed.c src/talkback.h src/text.chr src/winicon.rc src/wktables.c tools/fontconv.c tools/mktables.c zip.in
diffstat 88 files changed, 18929 insertions(+), 0 deletions(-) [+]
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/BUGS.txt	Fri Mar 13 00:39:12 2009 -0700
     1.3 @@ -0,0 +1,906 @@
     1.4 +bug 0001: Dual Marathon gimmick
     1.5 +
     1.6 +The keys control both a platform game (in the top window) and a
     1.7 +tetromino stacking game (in the bottom window).  If you die on
     1.8 +either window, you lose.
     1.9 +
    1.10 +
    1.11 +bug 0002: Customizable top out behavior
    1.12 +Parity: Tetris (NES), freepuzzlearena
    1.13 +
    1.14 +A rule can dictate that "top out" occurs when the following happens:
    1.15 +* Overlap or lock out: Entry overlapping blocks, or lock entirely
    1.16 +  above ceiling (current behavior)
    1.17 +* Partial lock out: Lock with any block above ceiling
    1.18 +* Garbage out: Entry with any block above ceiling
    1.19 +* Loose block out: Lock overlapping blocks; block can be shifted
    1.20 +  out of the way in some cases (like Tetris for NES)
    1.21 +
    1.22 +
    1.23 +bug 0003: Mode 7 style blitter for PC
    1.24 +Platform: PC
    1.25 +Parity: TOD
    1.26 +
    1.27 +Extend blitField(LJView *) to scale and rotate the field as it's
    1.28 +being copied to the screen.
    1.29 +
    1.30 +
    1.31 +bug 0004: Mode 7 style blitter for GBA and DS
    1.32 +Platform: GBA
    1.33 +
    1.34 +Extend blitField(LJView *) to calculate scaling and rotation factors
    1.35 +for each scanline, and extend other functions to start DMA.
    1.36 +
    1.37 +
    1.38 +bug 0005: Port Lockjaw: The Overdose (TOD)
    1.39 +Requires: 0002, 0003, 0004
    1.40 +Parity: TOD
    1.41 +
    1.42 +TOD used Carbon Engine.  I want to migrate away from Carbon Engine.
    1.43 +
    1.44 +
    1.45 +bug 0006: Ridin' Spinners
    1.46 +Requires: 0003, 0004
    1.47 +SRS and Infinity are turned on.  If you abuse lock delay, or you make
    1.48 +a "spin triple" twist, the screen begins to spin, and the music
    1.49 +changes to a cover of "Ridin' Spinners" by Three 6 Mafia.
    1.50 +
    1.51 +
    1.52 +bug 0007: Improve Items gimmick
    1.53 +Requires: 0008
    1.54 +Votes: Caithness
    1.55 +Parity: Tetris DS
    1.56 +
    1.57 +Give only one item at a time in the early stages.
    1.58 +
    1.59 +
    1.60 +bug 0008: Prerotated next queue
    1.61 +Parity: Tetris DS
    1.62 +
    1.63 +Store the orientation of each piece in the next queue.
    1.64 +
    1.65 +
    1.66 +bug 0009: TDS 4-player worldwide simulation
    1.67 +Parity: Tetris DS
    1.68 +
    1.69 +3/4 of the time you start it, it will play annoying music for
    1.70 +two minutes, give an 86420 error, and quit. The rest of the
    1.71 +time, it will play annoying music for between 15 seconds and
    1.72 +two minutes and then switch to Items gimmick.
    1.73 +
    1.74 +
    1.75 +bug 0010: Colored blocks
    1.76 +Parity: freepuzzlearena
    1.77 +
    1.78 +In active, hold, and next, have each block store its color.
    1.79 +
    1.80 +
    1.81 +bug 0011: H&R Blocks scoring method
    1.82 +Parity: freepuzzlearena
    1.83 +
    1.84 +First spotted in freepuzzlearena Tetanus.  Each piece is either
    1.85 +entirely dark or entirely light.  A line of all dark or all light
    1.86 +scores as 2 lines.  Score for n lines with one piece is n*(n+1)*50.
    1.87 +
    1.88 +
    1.89 +bug 0012: Two physical keys per vkey
    1.90 +Platform: PC
    1.91 +
    1.92 +Allow the player to set more than one key that operates a given,
    1.93 +game function, like StepMania does.
    1.94 +
    1.95 +
    1.96 +bug 0013: Two-player mode
    1.97 +Parity: freepuzzlearena
    1.98 +
    1.99 +Allow two players to play at once, and allow garbage to be sent back
   1.100 +and forth.
   1.101 +
   1.102 +
   1.103 +bug 0014: Store and display assertion text
   1.104 +
   1.105 +Lockjaw Engine has ljassert().  If an assertion fails, have some way
   1.106 +for front-ends to retrieve the text.
   1.107 +
   1.108 +
   1.109 +bug 0015: Rotation system should be array of structs
   1.110 +
   1.111 +A single struct should describe all aspects of a rotation system.
   1.112 +The descriptions in wktables.c should be an array of structs, not
   1.113 +struct of arrays.  While you're at it, extend the kick tables
   1.114 +from 5 to 8 positions.
   1.115 +
   1.116 +
   1.117 +bug 0016: Rotation system editor
   1.118 +Platform: PC
   1.119 +Requires: 0015
   1.120 +Votes: Needle, DIGITAL, (RA) Red Star, jujube, fnord
   1.121 +
   1.122 +Before developing Lockjaw Engine, I described Wall Kick Explorer, an
   1.123 +editor for rotation systems.  Make this.
   1.124 +* initial shift and orientation of each piece
   1.125 +* kick tables for +90deg and +270deg
   1.126 +
   1.127 +<tepples>
   1.128 +0.44 inverts rotation systems into a format that a loader might be
   1.129 +able to produce.
   1.130 +
   1.131 +
   1.132 +bug 0017: Adjustable window size
   1.133 +Votes: Bloodstar
   1.134 +Resolution: Fixed in 0.42
   1.135 +
   1.136 +The skin should control the screen resolution or the size of the
   1.137 +window.  This would make camstudio recording on older PCs feasible.
   1.138 +
   1.139 +
   1.140 +bug 0018: Store keypress count in .ljm
   1.141 +
   1.142 +Replays should store the number of keypresses, so that Baboo! result
   1.143 +screens are correct.
   1.144 +
   1.145 +
   1.146 +bug 0019: Start recording .ljm only during ARE
   1.147 +
   1.148 +It would probably simplify the code to start a replay only during
   1.149 +entry delay.  This would also allow playback to become slightly
   1.150 +more resilient to game engine changes, as the viewer would at least
   1.151 +be able to view block placements.
   1.152 +
   1.153 +
   1.154 +bug 0020: Option for fractional row kick
   1.155 +Parity: Tetris DX
   1.156 +
   1.157 +Right now, a floor kick will move a piece by the minimal amount
   1.158 +needed to place a block in the correct row.  This means downward
   1.159 +kicks move to the top of the row, and upward kicks move to the bottom
   1.160 +and immediately start lock delay.  Other games don't do this.
   1.161 +To simulate Tetris DX properly, we need a new option:
   1.162 +
   1.163 +Fractional floor kick
   1.164 +  Unchanged
   1.165 +> Minimal move (current behavior)
   1.166 +  Move to top (Tetris DX behavior)
   1.167 +
   1.168 +
   1.169 +bug 0021: Double buffer entire screen
   1.170 +Platform: PC
   1.171 +Votes: 4matsy
   1.172 +
   1.173 +On some video cards, the score flashes.  Double buffering the entire
   1.174 +screen, not just the well, would fix this.
   1.175 +
   1.176 +
   1.177 +bug 0022: Automatic exit on Vista
   1.178 +Platform: PC
   1.179 +Resolution: Fixed in 0.42
   1.180 +
   1.181 +One machine running Windows Vista had Esc perform an immediate exit
   1.182 +rather than a pause > exit.
   1.183 +
   1.184 +<tepples>
   1.185 +Might it have been caused by vsync() returning immediately?
   1.186 +If so, 0.42 fixes that.
   1.187 +
   1.188 +
   1.189 +bug 0023: Draw internal preview without clipping to well edge
   1.190 +Requires: 0020
   1.191 +Votes: 4matsy
   1.192 +
   1.193 +Internal preview should extend outside the well if the piece is close
   1.194 +enough to the wall.
   1.195 +
   1.196 +
   1.197 +bug 0024: Improve garbage options
   1.198 +Votes: Bloodstar, Lardarse
   1.199 +Parity: Tetris (NES)
   1.200 +
   1.201 +Add option for garbage density, especially for B-type
   1.202 +
   1.203 +Garbage density
   1.204 +  Half
   1.205 +  Two holes
   1.206 +> One hole
   1.207 +
   1.208 +Add option for garbage randomness (1-100)
   1.209 +
   1.210 +
   1.211 +bug 0025: Internet play
   1.212 +Requires: 0013
   1.213 +Votes: Bloodstar
   1.214 +Parity: Tetris DS
   1.215 +
   1.216 +Allow sending garbage over the Internet.
   1.217 +
   1.218 +
   1.219 +bug 0026: FreeType font support
   1.220 +Platform: PC
   1.221 +Votes: Bloodstar
   1.222 +
   1.223 +Use TrueType fonts instead of bitmapped fonts.
   1.224 +
   1.225 +
   1.226 +bug 0027: AVI export
   1.227 +Platform: PC
   1.228 +Votes: Bloodstar
   1.229 +Parity: Tetris (NES) in emulation
   1.230 +
   1.231 +FCE Ultra and Nestopia can convert a replay to an AVI.
   1.232 +Lockjaw should too.
   1.233 +
   1.234 +
   1.235 +bug 0028: Game_Music_Emu music support
   1.236 +Platform: PC
   1.237 +Votes: Bloodstar
   1.238 +Parity: Tetris (NES) in emulation
   1.239 +
   1.240 +I'd like to play nsf/sid/spc music without having to
   1.241 +convert it to .ogg first.
   1.242 +
   1.243 +
   1.244 +bug 0029: Height-sensitive music
   1.245 +Platform: PC
   1.246 +Votes: Bloodstar
   1.247 +Parity: Tetris (NES), Tetris (GB)
   1.248 +
   1.249 +Tetris DX changes the music when the stack surpasses a certain
   1.250 +height.  Lockjaw should too.
   1.251 +
   1.252 +
   1.253 +bug 0030: Music on GBA
   1.254 +Platform: GBA
   1.255 +Votes: Bloodstar
   1.256 +Parity: TOD
   1.257 +
   1.258 +GBA has sound effects.  I'd like some sort of music too.  Pimpmobile?
   1.259 +
   1.260 +
   1.261 +bug 0031: Debrief on handhelds
   1.262 +Platform: GBA, DS
   1.263 +Votes: Bloodstar, zzo38computer
   1.264 +Resolution: Fixed in 0.43
   1.265 +
   1.266 +PC has a result screen.  I want this on GBA and DS
   1.267 +
   1.268 +<tepples>
   1.269 +Fixed in 0.42 for DS.  To get this onto the GBA, we'd have to
   1.270 +eliminate three lines of text from the result screen.
   1.271 +
   1.272 +<tepples>
   1.273 +kesiev gave me a few ideas of how I could slim it down.  Fixing.
   1.274 +
   1.275 +
   1.276 +bug 0032: Baboo! should force infinite lock delay
   1.277 +Votes: caffeine
   1.278 +
   1.279 +right now, Baboo! is defined by use of 0g.  it should be
   1.280 +infinite lock delay instead.
   1.281 +
   1.282 +<tepples>
   1.283 +Why?  It would add an extra keystroke to Zangi-moves.
   1.284 +
   1.285 +
   1.286 +bug 0033: Move all skin-related files to a folder
   1.287 +Platform: PC
   1.288 +Votes: caffeine
   1.289 +Resolution: Fixed in 0.43
   1.290 +
   1.291 +Skin-related files should be kept together in the folder "skin".
   1.292 +
   1.293 +
   1.294 +bug 0034: Configure console buttons
   1.295 +Platform: PC
   1.296 +Votes: caffeine, 4matsy
   1.297 +Parity: StepMania
   1.298 +
   1.299 +Escape to pause, [ to record, ] to play should be configurable.
   1.300 +
   1.301 +<tepples>
   1.302 +4matsy said that MenuUp, MenuDown, MenuLeft, MenuRight,
   1.303 +MenuStart, and MenuBack also being configurable might help.
   1.304 +
   1.305 +
   1.306 +bug 0035: skip ready go and game over animations
   1.307 +Votes: caffeine
   1.308 +
   1.309 +I like to kill and restart games when I make one little mistake,
   1.310 +and startingAnimation and gameOverAnimation slow me down.
   1.311 +I want to skip them, even though each is less than 2 seconds.
   1.312 +
   1.313 +
   1.314 +bug 0036: Skip to mistakes
   1.315 +Platform: PC
   1.316 +Votes: caffeine
   1.317 +
   1.318 +While watching a replay, I want to skip to a piece that increases
   1.319 +the number of holes or whose active time is greater than three
   1.320 +times the average.
   1.321 +
   1.322 +
   1.323 +bug 0037: Hide active times in results
   1.324 +Votes: caffeine
   1.325 +
   1.326 +The Quadra-style "active time" statistics confuse me.  I want to
   1.327 +see stats based on wall time only.
   1.328 +
   1.329 +
   1.330 +bug 0038: Result time unit
   1.331 +Votes: caffeine
   1.332 +
   1.333 +I'd like to control whether statistics are expressed per minute
   1.334 +or per second.
   1.335 +
   1.336 +<tepples>
   1.337 +Why?  DDR is measured in beats per minute, and gasoline engine speed
   1.338 +is measured in revolutions per minute.
   1.339 +
   1.340 +
   1.341 +bug 0039: More garbage options
   1.342 +Votes: Caithness
   1.343 +
   1.344 +I want more realistic garbage.
   1.345 +
   1.346 +<tepples>
   1.347 +In what way?
   1.348 +
   1.349 +
   1.350 +bug 0040: Custom speed curves
   1.351 +Votes: Caithness, caffeine, Ezzelin, zzo38computer, DIGITAL, mushroom
   1.352 +
   1.353 +Let the user modify speed curves: for example, user should be
   1.354 +able to make rhythm speed up slower
   1.355 +
   1.356 +<tepples>
   1.357 +0.45 introduces a new speed curve format
   1.358 +
   1.359 +
   1.360 +bug 0041: Saved rule sets
   1.361 +Votes: Needle, kotetsu213, jujube, DIGITAL, Rich Nagel, Deets
   1.362 +Votes: Kukuunen
   1.363 +Parity: Tetris Worlds, Heboris
   1.364 +
   1.365 +Let the user create sets of options that override the user's
   1.366 +current options, and replace the gimmick selection screen
   1.367 +with preset selection.
   1.368 +
   1.369 +<tepples>
   1.370 +0.42 adds scenario support for the PC.  A scenario editor and
   1.371 +scenario support for handhelds will have to wait.
   1.372 +
   1.373 +
   1.374 +bug 0042: Release Lockjaw XLII
   1.375 +Requires: 0041
   1.376 +
   1.377 +Lockjaw 0.42 won't be released until noticeable progress is made
   1.378 +on these issues.
   1.379 +
   1.380 +<tepples>
   1.381 +As of Monday, January 28, 2008, scenario support is in, so Lockjaw
   1.382 +will be released the morning before the Super Bowl.
   1.383 +
   1.384 +
   1.385 +bug 0043: More rotation systems
   1.386 +Requires: 0016
   1.387 +Votes: herc
   1.388 +
   1.389 +Once the rotation system editor is in place, create these:
   1.390 +* Flat-up SRS
   1.391 +
   1.392 +
   1.393 +bug 0044: Vanish zone
   1.394 +Platform: PC
   1.395 +Votes: DIGITAL
   1.396 +
   1.397 +The skin should allow showing the vanish zone.
   1.398 +
   1.399 +
   1.400 +bug 0045: Handheld skin support
   1.401 +Platform: GBA
   1.402 +Votes: Dood77, matt_hatter83
   1.403 +Parity: TOD 
   1.404 +
   1.405 +GBA and DS should have skins too.
   1.406 +
   1.407 +
   1.408 +bug 0046: Pussy 20G
   1.409 +Votes: herc, DIGITAL
   1.410 +
   1.411 +Option for moving a piece up 1 cell when shifting it,
   1.412 +counting it as a floor kick.
   1.413 +
   1.414 +
   1.415 +bug 0047: Pentominoes
   1.416 +Votes: Dood77, jujube, colour_thief, Lardarse
   1.417 +Parity: Bombliss
   1.418 +
   1.419 +Add the 5-block pieces.
   1.420 +
   1.421 +
   1.422 +bug 0048: Bastet randomizer
   1.423 +Votes: zzo38computer
   1.424 +Parity: Abandoned Blocks
   1.425 +
   1.426 +In bastet, AI plays each possible next piece in each
   1.427 +possible position and gives one of the worst 3.
   1.428 +
   1.429 +
   1.430 +bug 0049: lj-contrib as skins
   1.431 +Platform: PC
   1.432 +Votes: Rich Nagel
   1.433 +
   1.434 +lj-contrib was written before .skin files were done.
   1.435 +
   1.436 +
   1.437 +bug 0050: Next above shadow (faint)
   1.438 +Platform: PC
   1.439 +Votes: cdsboy
   1.440 +
   1.441 +Draw next above shadow, but draw it semitransparent.
   1.442 +
   1.443 +
   1.444 +bug 0051: Separate keys for initial actions
   1.445 +Votes: DIGITAL
   1.446 +
   1.447 +I like initial actions, but not ARE.  Can I get separate
   1.448 +keys to perform initial actions on the next piece before
   1.449 +this one locks down?
   1.450 +
   1.451 +
   1.452 +bug 0052: Speed graph in debrief
   1.453 +Platform: PC
   1.454 +Votes: Cubicz
   1.455 +Parity: StepMania
   1.456 +
   1.457 +Screen 1 is results.  Screen 2 is settings.
   1.458 +Why not put a speed graph on screen 3?
   1.459 +
   1.460 +
   1.461 +bug 0053: High score table
   1.462 +Requires: 0041
   1.463 +Votes: Cubicz, Ezzelin, Rich Nagel, Kuukunen
   1.464 +
   1.465 +For each ruleset, keep the highest scores achieved on that
   1.466 +ruleset.
   1.467 +
   1.468 +
   1.469 +bug 0054: Disable ljconn
   1.470 +Platform: PC
   1.471 +Votes: kotetsu213, Needle
   1.472 +Parity: Tetris The Grand Master, The New Tetris
   1.473 +
   1.474 +The skin should be able to turn off ljconn for falling pieces
   1.475 +(as opposed to blocks in the stack).  That would improve
   1.476 +accuracy of TNT and TGM simulation.
   1.477 +
   1.478 +
   1.479 +bug 0055: Skin control for sound effects
   1.480 +Platform: PC
   1.481 +Votes: Cubicz, Needle
   1.482 +Parity: Lumines
   1.483 +
   1.484 +Allow skins to specify a .dat file or set of .wav files used
   1.485 +for sound effects.
   1.486 +
   1.487 +
   1.488 +bug 0056: Bleep gimmick
   1.489 +Votes: Lardarse
   1.490 +
   1.491 +Player must clear row 1, 2, 1, 3, 1, 4, 1, 5, 1, ..., 20, 1.
   1.492 +
   1.493 +
   1.494 +bug 0057: Bravo
   1.495 +Votes: Lardarse
   1.496 +Parity: Luminesweeper
   1.497 +
   1.498 +Several other falling block games display "BRAVO!" or another similar
   1.499 +message when there are no blocks left in the well after a line clear,
   1.500 +and award lots of bonus points.  Detect and score for this situation.
   1.501 +
   1.502 +
   1.503 +bug 0058: Vs. Elite garbage
   1.504 +Votes: PetitPrince
   1.505 +
   1.506 +you get 7 garbages after random(1,7) seconds, then random(3,5)
   1.507 +garbage every random (5,20) seconds
   1.508 +
   1.509 +
   1.510 +bug 0059: Wall kick control for IRS
   1.511 +Votes: jagorochi
   1.512 +Parity: TGM
   1.513 +
   1.514 +In TGM, initial rotation does not perform kicks.
   1.515 +Change the IRS option:
   1.516 +
   1.517 +Initial rotation 
   1.518 +  Off: do not IRS (current behavior)
   1.519 +  On, no kick: if collision, fail
   1.520 +> On: if collision, try wall kick (current behavior)
   1.521 +
   1.522 +<tepples>
   1.523 +0.43 disallows IRS kick entirely, changing the current behavior
   1.524 +from "On" to "On, no kick", by adding a new argument to
   1.525 +doRotateLeft and doRotateRight.  The change to this option (search
   1.526 +for !isFirstFrame) could be used to implement this option.
   1.527 +
   1.528 +
   1.529 +bug 0060: Menu background
   1.530 +Platform: PC
   1.531 +Votes: lvankeulen, Kuukunen
   1.532 +Parity: Tetris (NES)
   1.533 +
   1.534 +The skin should specify a background image to be drawn behind
   1.535 +the menu text.
   1.536 +
   1.537 +
   1.538 +bug 0061: Frame stepping
   1.539 +Platform: PC
   1.540 +Votes: jagorochi
   1.541 +Parity: Tetris (NES) in emulation
   1.542 +
   1.543 +To check the event loop for off-by-one errors that affect
   1.544 +accuracy against other games, it would be useful to be able
   1.545 +to step the game engine forward by one frame at a time.
   1.546 +
   1.547 +
   1.548 +bug 0062: Replay stepping by piece
   1.549 +Platform: PC
   1.550 +Votes: Lardarse
   1.551 +
   1.552 +Pause demo playback (with well still visible) with hold button;
   1.553 +advance to next lock with rotate button
   1.554 +
   1.555 +
   1.556 +bug 0063: Option for goal
   1.557 +Votes: Lardarse, caffeine, Rosti LFC, fnord, M.Bison
   1.558 +
   1.559 +Separate goal and countdown code out of gimmicks into an option,
   1.560 +controlled by LJField::goalType.  Goals include none, lines,
   1.561 +level, score, time, and keys, which activate comparisons against
   1.562 +goalCount.  This would obsolete many gimmicks.
   1.563 +
   1.564 +
   1.565 +bug 0064: Fill in missing sound effects on handhelds
   1.566 +Platform: GBA, DS
   1.567 +Votes: Lardarse
   1.568 +
   1.569 +These sounds are missing:
   1.570 +GBA: Ready, go, countdown
   1.571 +DS: Ready, go, lose, distinct countdown
   1.572 +
   1.573 +<tepples>
   1.574 +0.42 adds "lose" on DS
   1.575 +
   1.576 +
   1.577 +bug 0065: Bombliss game mode
   1.578 +Requires: 0010
   1.579 +Votes: Joshua
   1.580 +Parity: Bombliss
   1.581 +
   1.582 +When game mode is set to Bombliss, line clears no longer remove
   1.583 +lines from the field.  Instead, one block of each piece is
   1.584 +colored differently and explodes, removing blocks, when in a
   1.585 +line clear.
   1.586 +
   1.587 +
   1.588 +bug 0066: Dr. Mario clone
   1.589 +Requires: 0010
   1.590 +Votes: Lardarse
   1.591 +Parity: freepuzzlearena
   1.592 +
   1.593 +You made Vitamins. Can you make domino rotation, color matching,
   1.594 +and floating garbage with fast DAS, IRS, and 20G?
   1.595 +
   1.596 +
   1.597 +bug 0067: Puyo clone
   1.598 +Requires: 0010
   1.599 +Votes: Lardarse
   1.600 +Parity: freepuzzlearena
   1.601 +
   1.602 +You made a puyo clone in FPA. Can you port it to LJ?
   1.603 +
   1.604 +
   1.605 +bug 0068: Preload hold piece
   1.606 +Votes: Lardarse
   1.607 +
   1.608 +Option to load a specific piece (such as I or T) into the hold box
   1.609 +at game start.
   1.610 +
   1.611 +
   1.612 +bug 0069: Bag with good start
   1.613 +Votes: Rosti LFC
   1.614 +Parity: Tetris The Grand Master ACE
   1.615 +
   1.616 +Randomizer should always give a "preferred piece" (e.g. I, J, L, T)
   1.617 +first, like history does.
   1.618 +
   1.619 +
   1.620 +bug 0070: Small skins shouldn't hide the score
   1.621 +Votes: Rich Nagel
   1.622 +Platform: PC
   1.623 +
   1.624 +Make wiki.skin (and other small-blkW skins) not hide the score
   1.625 +
   1.626 +
   1.627 +bug 0071: Preview at left
   1.628 +Votes: DIGITAL
   1.629 +Platform: PC
   1.630 +
   1.631 +Skin should specify that the vertical preview ("Next at right")
   1.632 +can be moved to the left side of the playfield.
   1.633 +
   1.634 +
   1.635 +bug 0072: Volume for sfx
   1.636 +Votes: DIGITAL
   1.637 +Platform: PC
   1.638 +
   1.639 +Skin should specify volume for sound effects, as it does for music.
   1.640 +
   1.641 +
   1.642 +bug 0073: Make PC use GBA options
   1.643 +Votes: jujube
   1.644 +Platform: PC
   1.645 +Resolution: Fixed in 0.43
   1.646 +
   1.647 +Move non-GBA/DS specific code from gbaopt.c to options.c,
   1.648 +then take out everything in old_pc_options.c that isn't
   1.649 +load, save, or LJPCView related
   1.650 +
   1.651 +<tepples>
   1.652 +0.42 takes some steps in this direction.
   1.653 +
   1.654 +
   1.655 +bug 0074: Macro editor
   1.656 +Votes: deepdorp
   1.657 +
   1.658 +Make the actions performed by the macro vkeys editable.
   1.659 +
   1.660 +
   1.661 +bug 0075: Pattern randomizer
   1.662 +Votes: Rich Nagel
   1.663 +
   1.664 +Allow the player to define a piece sequence, such as the Sega
   1.665 +power-on pattern, and deal that.  It could replace iCheat(tm).
   1.666 +
   1.667 +
   1.668 +bug 0076: Skin should vary per section
   1.669 +Votes: Needle, Rich Nagel, DIGITAL
   1.670 +Parity: freepuzzlearena, Luminesweeper
   1.671 +
   1.672 +For speed curves that have sections, it should be possible for a
   1.673 +skin to specify a background image and music for each section.
   1.674 +
   1.675 +<Rich Nagel>
   1.676 +Option to load all skin files before the game starts, so as not to
   1.677 +make an unpredictable ARE at the start of a section.
   1.678 +
   1.679 +
   1.680 +bug 0077: Define conditions that trigger initial drop
   1.681 +Votes: Needle
   1.682 +Parity: Tetris The Absolute The Grand Master 2 PLUS
   1.683 +
   1.684 +In TAP, initial hard drop and initial soft drop don't get turned on
   1.685 +in Master 900+ or in Death 0+.  Find an appropriate rule for whether
   1.686 +to do initial hard drop, and document it.
   1.687 +
   1.688 +
   1.689 +bug 0078: Set random seed
   1.690 +Votes: Rich Nagel, colour_thief, Lardarse
   1.691 +Parity: FreeCell
   1.692 +
   1.693 +For competition purposes, it should be possible to seed the
   1.694 +randomizer to a constant value.  Note that this differs from the
   1.695 +"power on pattern" proposal of bug 0075.
   1.696 +
   1.697 +
   1.698 +bug 0079: Customizable speedometer sample window
   1.699 +Votes: matt_hatter83
   1.700 +
   1.701 +The speedometer uses a sliding window of 10 pieces.  Can one make it
   1.702 +shorter in options?
   1.703 +
   1.704 +
   1.705 +bug 0080: Portrait monitor support
   1.706 +Votes: PetitPrince
   1.707 +
   1.708 +Skin setting to rotate the entire playfield by 90 degrees.
   1.709 +
   1.710 +
   1.711 +bug 0081: DTET rotation system
   1.712 +Requires: 0015
   1.713 +Votes: cdsboy, caffeine, fnord
   1.714 +Parity: DTET
   1.715 +
   1.716 +Implement the rotation system of DTET, as described on the wiki.
   1.717 +This includes a third set of wall kick tables for each piece in each
   1.718 +rotation system for +180 (in addition to existing +90 and +270).
   1.719 +
   1.720 +
   1.721 +bug 0082: Paged options on PC
   1.722 +Votes: Cubicz
   1.723 +Resolution: Fixed in 0.43
   1.724 +
   1.725 +Break the PC options menu into pages like it is on the handhelds.
   1.726 +
   1.727 +
   1.728 +bug 0083: Undo
   1.729 +Votes: Kuukunen
   1.730 +
   1.731 +Press a button to take back one drop.  Useful for training.
   1.732 +
   1.733 +
   1.734 +bug 0084: Manual entry delay
   1.735 +Votes: Kuukunen
   1.736 +
   1.737 +Pause after each piece with game displayed.  Press a button to
   1.738 +resume.  Useful for training.
   1.739 +
   1.740 +
   1.741 +bug 0085: Forward progress lock delay reset
   1.742 +Votes: zaphod77
   1.743 +
   1.744 +Keep track of the lowest space that this piece has fallen to.
   1.745 +A floor kick should never reset lock delay, and only a net
   1.746 +downward movement below where it was when the lock delay was
   1.747 +last reset should reset lock delay.
   1.748 +
   1.749 +
   1.750 +bug 0086: Carbon Engine style lock delay reset
   1.751 +Parity: TOD
   1.752 +
   1.753 +Carbon Engine implemented lock delay as follows:  When a piece
   1.754 +rotates or shifts from landed to falling, recharge one row's
   1.755 +worth of lock delay for each row that the piece moves down.
   1.756 +
   1.757 +To implement this in Lockjaw Engine, move the piece down by
   1.758 +(lockDelay - stateTime).  If the piece is less than
   1.759 +(lockDelay - stateTime) * gravity above the shadow, only add
   1.760 +(y - shadowY) / gravity to stateTime.
   1.761 +
   1.762 +
   1.763 +bug 0087: Blackjack randomizer framework
   1.764 +Votes: Lardarse
   1.765 +
   1.766 +Lardarse and tepples described a language for describing randomizers
   1.767 +on tetrisconcept.com's wiki.  It is called Blackjack.
   1.768 +
   1.769 +
   1.770 +bug 0088: Option for piece color
   1.771 +Votes: Lardarse
   1.772 +Requires: 0010
   1.773 +
   1.774 +Once we have colors per block, add an option for piece colors,
   1.775 +which might replace the ljconnSRS vs. ljconnSega divide.
   1.776 +Skins would always be in Guideline order.
   1.777 +
   1.778 +> Set by rotation system (current behavior)
   1.779 +  SRS
   1.780 +  Sega
   1.781 +  Dualism (formerly H&R)
   1.782 +  Shuffle (randomize at game start)
   1.783 +  Super Shuffle (randomize every piece)
   1.784 +
   1.785 +
   1.786 +bug 0089: Long debrief lines are clipped
   1.787 +Requires: 0031
   1.788 +Votes: Lardarse, kesiev
   1.789 +Resolution: Fixed in 0.43
   1.790 +
   1.791 +The DS doesn't have a small font, so any debrief line longer than
   1.792 +about 48 characters will get clipped.  But in order to make this
   1.793 +line shorter, I need to move things around anyway.
   1.794 +
   1.795 +
   1.796 +bug 0089: Merge preliminary GNU/Linux support
   1.797 +Requires: 0091
   1.798 +Votes: kesiev
   1.799 +Resolution: Fixed in 0.43
   1.800 +
   1.801 +kesiev wrote a patch to let Lockjaw work with separate read-only
   1.802 +and read-write folders, as is often the case in GNU/Linux.
   1.803 +Merge these changes.
   1.804 +
   1.805 +<tepples>
   1.806 +kesiev's patch adds a function called 'turnslash' that turns '\' into
   1.807 +'/' in all path names.  In fact, I can use this on all platforms:
   1.808 +Mac and Linux use '/' as the native path separator, and the Windows
   1.809 +API accepts it even if some legacy command-line programs do not.
   1.810 +
   1.811 +LJVorbis_close: I must have missed that line in the ov_clear
   1.812 +documentation.
   1.813 +
   1.814 +
   1.815 +bug 0090: Process IRS before checking for block out
   1.816 +Votes: Kitaru
   1.817 +Parity: TGM series
   1.818 +Resolution: Fixed in 0.43
   1.819 +
   1.820 +TGM allows the player to IRS out of a block out situation.  So don't check for block out on the last frame of NEW_PIECE. Instead, check at
   1.821 +the end of the first frame (LJSND_SPAWN|LJSND_HOLD) of FALLING_PIECE.
   1.822 +
   1.823 +
   1.824 +bug 0091: Read-only vs. read-write directories
   1.825 +Votes: Ezzelin, kesiev
   1.826 +Resolution: Fixed in 0.43
   1.827 +
   1.828 +Lockjaw for PC currently runs as a "portable application".  But it
   1.829 +should support being installed in the same way as any other program
   1.830 +for Windows or Linux.  If an empty file called "installed" is in the
   1.831 +same folder as argv[0], switch to installed mode.
   1.832 +
   1.833 +Installed mode ignores the working directory when loading .dat,
   1.834 +.skin, .ini, etc.  Instead, it loads from the save data folder
   1.835 +($HOME/.lockjaw on Linux; %APPDATA%/Pin Eight/Lockjaw on Windows)
   1.836 +and then from the folder that contains argv[0].
   1.837 +
   1.838 +The program would usually be installed to /opt/Lockjaw under Linux
   1.839 +or %ProgramFiles%/Lockjaw under Windows.
   1.840 +
   1.841 +
   1.842 +bug 0092: Option to limit IRS to 90 degrees
   1.843 +Votes: jujube
   1.844 +Parity: TGM series
   1.845 +
   1.846 +Rotation limit
   1.847 +  90 degrees
   1.848 +> Unlimited (current behavior)
   1.849 +
   1.850 +And make Master and Death use 90 degrees.
   1.851 +
   1.852 +
   1.853 +bug 0093: Option to disable instant shifting
   1.854 +Parity: TGM series
   1.855 +
   1.856 +Add an option only to scenarios that limits shifting to 1G,
   1.857 +disallowing cheating with far left, far right, DAS=Instant.
   1.858 +
   1.859 +
   1.860 +bug 0094: World reverse
   1.861 +Votes: jujube
   1.862 +Parity: Ti
   1.863 +
   1.864 +Option in Game Keys to reverse rotation directions when playing
   1.865 +rotation systems that start the T flat-down (SRS, TOD M4).
   1.866 +
   1.867 +
   1.868 +bug 0095: Move mouse pointer out of the way
   1.869 +Votes: jujube
   1.870 +Resolution: Fixed in 0.43
   1.871 +
   1.872 +Some players navigate Lockjaw for PC entirely with the keyboard.
   1.873 +For these people, the mouse gets in the way after it has been used
   1.874 +in Replay or Skin (or, soon, Options and Game Keys).  Drop it to
   1.875 +the bottom of the window when the player goes to a screen that
   1.876 +doesn't use the mouse.
   1.877 +
   1.878 +
   1.879 +bug 0096: Allow options to "underride" skin settings
   1.880 +Votes: Rich Nagel
   1.881 +
   1.882 +Allow the user to change shift sound scale, next piece position,
   1.883 +playfield position, as it was in 0.41.  The game would use the
   1.884 +skin setting, or the user setting only if the skin does not specify.
   1.885 +
   1.886 +
   1.887 +bug 0097: Package as Windows installer
   1.888 +Requires: 0091
   1.889 +
   1.890 +Package the Windows executable as an installer that automatically
   1.891 +puts itself in %ProgramFiles%/Lockjaw.
   1.892 +
   1.893 +
   1.894 +bug 0098: Reactive speed curve and scoring
   1.895 +URL: http://www.jenovachen.com/flowingames/flowtheory.htm
   1.896 +Votes: herc
   1.897 +
   1.898 +Start with zero speed.  Track the player's speed using the existing
   1.899 +speedometer code.  Then once the player has dropped a few pieces,
   1.900 +start scaling up the delays and such so that entry + (20/gravity) +
   1.901 +lock delay equals the average time that the player took to place 
   1.902 +a piece.  Then multiply all line clear scores by the current speed.
   1.903 +
   1.904 +
   1.905 +bug 0100: Import old bugs
   1.906 +Parity: bugzilla.mozilla.org
   1.907 +
   1.908 +Bring some order to the old 'todo.txt' enhancement list.
   1.909 +
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/CHANGES.txt	Fri Mar 13 00:39:12 2009 -0700
     2.3 @@ -0,0 +1,1067 @@
     2.4 +0.46a (2008-11-09)
     2.5 +  * Preview at top is copied correctly from the back buffer
     2.6 +    (0.45a regression, reported by rednefed).
     2.7 +
     2.8 +_____________________________________________________________________
     2.9 +0.46 (2008-11-08)
    2.10 +  * Tracks number of 4x4 squares that the player forms
    2.11 +    (TOD parity; requested by Ghett0).
    2.12 +  * Added T-Party randomizer (requested by Lardarse).
    2.13 +  * Default lock delay for Zero set to 40 frames, even if it is
    2.14 +    played first (0.45 regression, reported by rck).
    2.15 +  * "[TC]" removed from scenario names (requested by caffeine).
    2.16 +  * PC: Uses fourCCs for option values in lj.ini.
    2.17 +  * PC: Uses double buffer for entire background (bug 0021).
    2.18 +  * PC: Can disable sound effects by deleting or renaming sound.dat
    2.19 +    (requested by moxie101).
    2.20 +  * PC: More of the code is 2-player-clean.
    2.21 +
    2.22 +_____________________________________________________________________
    2.23 +0.45 (2008-04-20)
    2.24 +  * New unified speed curve system: less code, more tables, for
    2.25 +    future loading from a file (bug 0040).
    2.26 +  * All speed curves except Rhythm and Rhythm Zero have been ported
    2.27 +    to the new system.  Notably, Master has been rewritten based on
    2.28 +    the tables from the wiki, and Exponential uses sections.  The
    2.29 +    lock delay speeds up as brutally after 600 pieces as always.
    2.30 +  * Added kludge in the basic makefile that allows building on *BSD
    2.31 +    and GNU/Linux: 'make linux' (patch by kesiev).
    2.32 +  * ljpath: Fixed a couple misspellings in the non-Windows code path
    2.33 +    (reported by kesiev).
    2.34 +  * ljpath: Supports libfat when running on Nintendo DS.
    2.35 +  * A few descriptions of options have been shortened to fit into
    2.36 +    the new GBA/DS look (reported by Bloodstar; patch by caffeine).
    2.37 +  * Writes result and options text to standard output as well as to
    2.38 +    lj-scores.txt. This allows you to do 'lj | postprocessor' even
    2.39 +    if you can't do 'tail -f lj-scores.txt' (requested by kesiev).
    2.40 +  * DS: Writes result and options to /data/lockjaw/lj-scores.txt.
    2.41 +
    2.42 +_____________________________________________________________________
    2.43 +0.44 (2008-02-20)
    2.44 +  * Computes the preview and hold offset of all pieces (was
    2.45 +    wkNextMove in <= 0.43) from the entry position of the I
    2.46 +    tetromino (was wkSpawnMove in <= 0.43).
    2.47 +  * Kick tables have been inverted to a single struct per rotation
    2.48 +    system, for future loading from a file (bug 0016).
    2.49 +  * SKIP_IF system for handling J/L/T exceptions in Arika is replaced
    2.50 +    with IF_NOT_CENTER: wall kicks are allowed if the free-space
    2.51 +    orientation overlaps any blocks other than in the center column.
    2.52 +  * Options DAS doesn't repeat too fast, even on those systems where
    2.53 +    vsync() is a no-op (0.43 regression, reported by caffeine).
    2.54 +    This has the same cause as 0022: Windows Vista ignores vsync().
    2.55 +  * Options DAS doesn't continue across page boundaries.
    2.56 +  * GBA/DS: Options has new look.
    2.57 +  * DS: Reads the touch screen and the X and Y buttons.  Right now,
    2.58 +    touch isn't mapped to anything, but X and Y are Far Right and
    2.59 +    Far Left.
    2.60 +
    2.61 +_____________________________________________________________________
    2.62 +0.43a (2008-02-11)
    2.63 +  * Arika modified to use SKIP_IF3 after (not before) the basic
    2.64 +    position.  This restores ability to rotate J, L, and T with
    2.65 +    kicks turned off, such as in IRS (reported by Lion).
    2.66 +  * PC: Uses shfolder.dll so that installability works even on
    2.67 +    Windows 98 (0.43 regression, reported by Rich Nagel).
    2.68 +  * PC: Key bindings to J, M, and Z are correctly saved (0.43
    2.69 +    regression, reported by DIGITAL).
    2.70 +  * PC: In Game Keys, the names of Up and Down key bindings are
    2.71 +    clearer (requested by 4matsy).
    2.72 +
    2.73 +_____________________________________________________________________
    2.74 +0.43 (2008-02-10)
    2.75 +  * Player can initial-rotate out of block out (bug 0090).
    2.76 +  * Initial rotation doesn't use wall kick (bug 0059).
    2.77 +  * Names for values in Drop scoring and Shadow fixed
    2.78 +    (0.42 regression, reported by Lardarse).
    2.79 +  * Number of line clears in DS debrief should be cut off at the
    2.80 +    right side less often (bug 0089; fix based on a patch by kesiev).
    2.81 +  * Fixed line clear gravity in cascade preset (reported by 4matsy).
    2.82 +  * Fixed Fibonacci scoring with more than 8 lines per piece
    2.83 +    (reported by 4matsy).
    2.84 +  * Debrief is more compact, allowing GBA to use debrief (bug 0031;
    2.85 +    fix based on a patch by kesiev).
    2.86 +  * Debrief: Zigzag secret grade requires the hole in the top row to
    2.87 +    be covered (reported by Kitaru).
    2.88 +  * PC: If installed.txt is in the same folder as the program,
    2.89 +    ignores the current directory and stores all writable files
    2.90 +    to the user's application data folder (bug 0091).
    2.91 +  * PC: When loading skins, searches for files in the folder with
    2.92 +    the .skin file instead of the current directory (bug 0033).
    2.93 +  * PC: Replay sets up playback gimmick (0.42 regression,
    2.94 +    reported by DIGITAL).
    2.95 +  * PC: LJVorbis doesn't double-fclose the .ogg file
    2.96 +    (reported by kesiev).
    2.97 +  * PC: Mouse drops to bottom after skin or replay selection
    2.98 +    (requested by jujube).
    2.99 +  * PC: Options is separated into pages, like on GBA/DS (bug 0082).
   2.100 +    This frees up room for larger type (requested by jujube).
   2.101 +
   2.102 +_____________________________________________________________________
   2.103 +0.42 (2008-02-03) aka LOCKJAW XLII
   2.104 +  * Docs: Most of TODO.txt has been converted to a numbered list
   2.105 +    of bugs (bug 0100).
   2.106 +  * An off-by-1/65536 error in enter below ceiling was fixed.
   2.107 +    This means it doesn't give one extra row of room to slide on
   2.108 +    at the top of the well (reported by Caithness and Kukuunen).
   2.109 +  * In case of a block out, draws the offending piece in the well
   2.110 +    before entering game over animation.
   2.111 +  * Extra 5-frame cascade delay removed.
   2.112 +  * With Lockdown set to Step reset, lock delay resets on the
   2.113 +    first floor kick.
   2.114 +  * Tengen rotation system tries kicking one space to the left
   2.115 +    (requested by zaphod77).
   2.116 +  * Options' names and values are looked up from fourCCs for future
   2.117 +    localizability.
   2.118 +  * Options: Names of speeds in fractions of 1G are computed.
   2.119 +  * Options skips unpacking options with a value of 255.
   2.120 +    This allows multiple pref structures to be unpacked on top
   2.121 +    of each other, and each overrides the ones before it.
   2.122 +  * Debrief displays options on a second page.
   2.123 +  * Debrief correctly displays soft drop speed as a fraction (0.41
   2.124 +    regression, reported by Lardarse).
   2.125 +  * Debrief: PC specific parts moved to separate source code file.
   2.126 +  * Debrief uses a lower-level function to format ISO 8601 date
   2.127 +    and time so as not to bring in the floating-point library's
   2.128 +    space overhead on platforms without an FPU.
   2.129 +  * Debrief plays a sound when turning the page.
   2.130 +  * PC: Delay for holding Esc to quit no longer depends on
   2.131 +    display refresh rate.  Instead, it uses the global 60 Hz
   2.132 +    timer (reported by Sara).
   2.133 +  * PC: Draws speedometer even when preview is at right,
   2.134 +    as long as there is room, that is, 3 or fewer next pieces
   2.135 +    (requested by Cubicz).
   2.136 +  * PC: Gimmick choice moved to Options to match GBA/DS.
   2.137 +  * PC: Select scenario before starting game (bug 0041).
   2.138 +  * PC options uses standardized GBA names (OPTIONS_*) instead of
   2.139 +    PC names (LJPREFS_*).
   2.140 +  * PC options Shift sound scale, Side, and Next position moved to
   2.141 +    skin.
   2.142 +  * PC options sets initial options using initOptions() from GBA/DS.
   2.143 +    Incidentally, this restores the initial randomizer to bag
   2.144 +    (0.41 regression and may make similar regressions less likely.
   2.145 +  * PC options uses unpackOptions() from GBA/DS.
   2.146 +  * PC options sound is also routed through the cross-platform
   2.147 +    sound manager.
   2.148 +  * PC: lj.ini uses fourCCs as names for easier saving and loading.
   2.149 +  * PC: Switching between windowed and full screen in Options
   2.150 +    no longer needs the user to restart the program.
   2.151 +  * PC: LJVorbis doesn't crash if polled while the music is stopped.
   2.152 +    This can happen when cross-platform code plays sound effects
   2.153 +    during debrief.
   2.154 +  * PC: Skin controls window size, or full screen resolution
   2.155 +    (bug 0017).
   2.156 +  * PC/DS: Displays a description of some option values in addition
   2.157 +    to descriptions of options themselves.
   2.158 +  * GBA/DS: Options disabled on the PC are disabled here.
   2.159 +  * GBA: Menus are faster now that part of the text engine has been
   2.160 +    moved to fast RAM.
   2.161 +  * DS: Plays sound for game over (bug 0064).
   2.162 +  * DS: Options displayed on touch screen for future touch operation.
   2.163 +  * DS: Displays debrief after game ends (bug 0031).
   2.164 +
   2.165 +_____________________________________________________________________
   2.166 +0.41 (2007-12-18)
   2.167 +  * Piece set and randomizer are decoupled (requested by Lardarse
   2.168 +    and Dood77).
   2.169 +  * New piece set: iCheat(tm) deals only I tetrominoes, like an
   2.170 +    infamous cheat code for some other game (requested by DIGITAL).
   2.171 +  * New garbage style "Preload zigzag", which preloads a zigzag
   2.172 +    pattern of holes.
   2.173 +  * At debrief time, if at least two rows of the zigzag pattern
   2.174 +    or at least four rows of a rectum (1-block-wide opening) are
   2.175 +    formed, shows a secret grade based on the number of pattern
   2.176 +    rows instead of the number of blocks left in the playfield.
   2.177 +  * Debrief options section sorted by position in options menu.
   2.178 +  * Debrief allows for multiple pages of reports.
   2.179 +
   2.180 +_____________________________________________________________________
   2.181 +0.40a (2007-11-30)
   2.182 +  * Does not attempt to create a ghost piece before the first
   2.183 +    falling piece.  This may fix a crash on some systems if the
   2.184 +    first game played during a session has deep drop turned on
   2.185 +    (reported by Rich Nagel on Windows 98 Second Edition).
   2.186 +  * Deep drop ghost piece code stops checking at the ceiling.
   2.187 +
   2.188 +_____________________________________________________________________
   2.189 +0.40 (2007-11-27)
   2.190 +  * Split out speed curves and randomizers to separate files called
   2.191 +    speed.c and random.c.
   2.192 +  * Adjusted master delays to be closer to TAP; 600-999 no longer
   2.193 +    overlap Death 000-399.
   2.194 +  * Displays short description of each option (requested by AXYPB).
   2.195 +  * Drill gimmick is back, with the new goal of clearing the bottom
   2.196 +    row (requested by DIGITAL).
   2.197 +  * Begin to add code for explosive line clear gimmick; not yet
   2.198 +    integrated (requested by Joshua).
   2.199 +  * Debrief shows number of floor kicks.
   2.200 +  * Bottom blocks is called Deep drop to match recent Multiblocks.
   2.201 +  * PC: Close box works in Options and Game Keys.
   2.202 +  * PC: Does not redraw title screen if close box is clicked.
   2.203 +  * PC: If skin sets transparentPF=1, hotlines and line clear
   2.204 +    animations are drawn transparent (requested by Rich Nagel).
   2.205 +  * GBA/DS: Can quit game from pause screen (requested by AXYPB).
   2.206 +  * GBA/DS: Copyright notice and options screen drawn with
   2.207 +    proportional font for readability.
   2.208 +  * GBA/DS: Menu uses the same sound manager that the game play
   2.209 +    uses, not the platform-specific sound manager.
   2.210 +  * GBA: Sound for end of game (requested by Lardarse).
   2.211 +  * DS: Closing the lid pauses the game and turns off the screens
   2.212 +    (requested by AXYPB).
   2.213 +  * DS: Unavailable hold piece is really grayed out.
   2.214 +  * DS: Real music and sound effects engine (requested by
   2.215 +    bob_fossil).
   2.216 +
   2.217 +_____________________________________________________________________
   2.218 +0.39 (2007-10-24)
   2.219 +  * Does not double line clear delay (unknown regression).
   2.220 +  * Source code includes new files options.h and old_pc_options.c
   2.221 +    which were mistakenly left out of 0.38 (reported by tr3).
   2.222 +  * Draws "hotline" symbol on lines being cleared (requested by
   2.223 +    Rosti LFC and kiwibonga).
   2.224 +  * Debrief no longer counts a zero line T-spin as a home run
   2.225 +    (0.28 regression; reported by Lardarse).
   2.226 +  * New randomizer 7+1-piece Bag adds one randomly selected
   2.227 +    tetromino to each bag (requested by colour_thief).
   2.228 +  * Refactored speed curve state information for pseudo-OO
   2.229 +    refactoring.
   2.230 +  * New option: Bottom blocks allows the falling piece to fall past
   2.231 +    blocks into an appropriately shaped hole (requested by mar).
   2.232 +  * Added bottom blocks and well size to debrief.
   2.233 +  * New gluing options Sticky and Sticky by color.
   2.234 +  * PC: Skin specifies the loop point and whether the music starts
   2.235 +    before or after the "Ready, Go" animation.
   2.236 +  * PC: Skin specifies music volume (requested by DIGITAL).
   2.237 +  * PC: Fixed off-by-one in sideways delay (reported by Lardarse).
   2.238 +  * PC: Fields with blkH > 24, especially blkH == 48 and Well height
   2.239 +    10, are more likely to fit on screen (reported by Needle).
   2.240 +
   2.241 +_____________________________________________________________________
   2.242 +0.38 (2007-09-11)
   2.243 +  * Plays a .ljm specified at the command line (requested by
   2.244 +    Rich Nagel).
   2.245 +  * More refactoring for multiplayer.
   2.246 +  * Added wall kick opcode SKIP_IF3, similar to SKIP_IF except it
   2.247 +    also checks the two cells above the specified cell.
   2.248 +  * L, J, and T in Arika use SKIP_IF3 to check the entire center
   2.249 +    column when rotating to vertical (requested by edo).
   2.250 +  * Moves new tetrominoes to be between the walls (fixing Tengen in
   2.251 +    width 4) and, if "Enter above playfield" is off, down far enough
   2.252 +    so that they're within the playfield (requested by Caithness).
   2.253 +  * New rotation system: Climbing, based on Tetris DX.  (No, you
   2.254 +    can't wall-climb yet because the sub-block behavior differs.)
   2.255 +  * Option "4x4 squares" is called "Gluing".  Other gluing options
   2.256 +    are planned.
   2.257 +  * In Square gluing, formation of a square adds a delay
   2.258 +    equal to line clear delay.
   2.259 +  * PC: Refactored to share some of the GBA/DS options logic.
   2.260 +  * PC: Moved most sound effect code into pcsound.c.
   2.261 +  * PC: Sound for 4x4 square formation (cribbed from TOD).
   2.262 +  * PC: Reloads the default skin's configuration before loading 
   2.263 +    the skin, so that skins always inherit from the default skin
   2.264 +    and not the last chosen skin (requested by Rich Nagel).
   2.265 +  * GBA: Uses duration as priority for square wave sound effects,
   2.266 +    which should prevent the section up sound from getting
   2.267 +    interrupted as often.
   2.268 +  * GBA/DS: Draws real-time speedometer.
   2.269 +
   2.270 +_____________________________________________________________________
   2.271 +0.37 (2007-07-09)
   2.272 +  * Requires "Up" and "Alt firm drop" to be pressed again when
   2.273 +    lock delay is turned off or less than DAS delay
   2.274 +    (requested by reivilo).
   2.275 +  * Keypress count is reset to 0 even in "Play .ljm" (reported by
   2.276 +    jujube).
   2.277 +  * Playing a broken .ljm from the gimmick menu gives an informative
   2.278 +    error message instead of crashing (0.32a regression; reported by
   2.279 +    Rosti LFC).
   2.280 +  * Rhythm section-up logic waits for 64 beats at L bpm, not L+1 bpm.
   2.281 +  * Fixed a potential buffer overflow in shuffleColumns (the "banana"
   2.282 +    item), brought on by the expansion of the playfield to 12 columns
   2.283 +    (0.33 regression).
   2.284 +  * Better comments in source code about wall kick entry macros
   2.285 +    WK(x, y), WKX(wk), WKY(wk) in lj.h (requested by Lardarse).
   2.286 +  * PC: Reinstate Bach music while investing a trademark claim.
   2.287 +  * PC: Separate music for Rhythm speed curves.
   2.288 +  * PC: Does not use stretch blitter when drawing the blocks of
   2.289 +    sprite pieces at full size (e.g. falling piece).
   2.290 +  * PC: blitField() blits adjacent rows in one call, which is
   2.291 +    faster on slow machines (requested by Matthew).
   2.292 +  * PC: Size of hold piece respects blkW and blkH.
   2.293 +  * README describes build instructions for GBA and DS.
   2.294 +  * For user convenience, the Allegro DLL is included with the
   2.295 +    executable, and the source code is in a separate archive.
   2.296 +
   2.297 +_____________________________________________________________________
   2.298 +0.36 (2007-05-21)
   2.299 +  * Allows DAS delay up to 400 ms (requested by Rich Nagel).
   2.300 +  * Option for entry within or above ceiling (requested by DIGITAL
   2.301 +    and Cubicz).
   2.302 +  * Option for hold piece behavior (requested by Lardarse).
   2.303 +  * PC: User can change individual game keys, like in StepMania
   2.304 +    (requested by DIGITAL and Rosti LFC).
   2.305 +  * PC: Minor changes to included background music.
   2.306 +  * PC: Option for playing a scale during sideways motion
   2.307 +    (requested by herc).
   2.308 +  * PC: Selecting a skin saves options (reported by Rich Nagel).
   2.309 +  * PC: When loading saved options, treats 16 ms DAS delay as valid
   2.310 +    (reported by jujube).
   2.311 +  * PC: Skin setting for a transparent background (requested by
   2.312 +    cdsboy and Bloodstar).
   2.313 +  * PC: Demo recording uses "Rec" and "Play" in addition to icons.
   2.314 +  * PC: Reallocates back buffer after each skin switch, avoiding
   2.315 +    problems related to changes in blkW and blkH (reported by
   2.316 +    Rich Nagel).
   2.317 +  * GBA/DS: Fixed line clear options (0.34 regression).
   2.318 +  * GBA/DS: Press Start after lose, rather than timing out to options
   2.319 +    (requested by Webby).
   2.320 +  * "Default" means not to pay back a loan.  Manual uses "initial"
   2.321 +    or "preset" instead (requested by Bruce Tognazzini).
   2.322 +
   2.323 +_____________________________________________________________________
   2.324 +0.35 (2007-04-25)
   2.325 +  * New speed curve Game Boy Heart.
   2.326 +  * New speed curve Death 300+, which starts three sections into
   2.327 +    Death just like cgwg's cheat for TAP (requested by Amnesia).
   2.328 +  * New randomizer 10-piece Memoryless.
   2.329 +  * First garbage line takes into account well width (reported by
   2.330 +    Lardarse).
   2.331 +  * Displays section number for NES, Game Boy, and Game Boy Heart
   2.332 +    speed curves (requested by Rich Nagel).
   2.333 +  * Debrief displays Quadra-style pieces per active minute
   2.334 +    (requested by Cubicz).
   2.335 +  * Debrief calls pieces "pieces" instead of "tetrominoes" in
   2.336 +    10-piece randomizers because I2, I3, L3 are not tetrominoes.
   2.337 +  * PC: Gimmick and level are displayed in skin foreground color
   2.338 +    (missed in 0.30; reported by Rich Nagel).
   2.339 +  * PC: Skin file name suffix is now .skin not .ini, so that skin.ini
   2.340 +    cannot be confused with lj.ini.  The default skin is called
   2.341 +    default.skin not skin.ini.
   2.342 +  * PC: Title screen has option to select skin (requested by
   2.343 +    cdsboy).
   2.344 +  * PC: Block size is no longer hard-coded at 24x24 pixels.
   2.345 +    The new .skin commands blkW= and blkH= control their size
   2.346 +    (requested by Bloodstar).
   2.347 +  * PC: Source code comes with new installation guide written by
   2.348 +    Lardarse in "docs/Compiling_on_Windows.txt".
   2.349 +
   2.350 +_____________________________________________________________________
   2.351 +0.34a (2007-03-27)
   2.352 +  * PC: Fixed hidden level in Next at right mode (0.33 regression).
   2.353 +  * DS: Fixed hidden sprites due to bug in devkitARM R20 libnds
   2.354 +    (0.34 regression).
   2.355 +  * DS: New touch screen code should eliminate erroneous low notes
   2.356 +    played upon pen-up.
   2.357 +
   2.358 +_____________________________________________________________________
   2.359 +0.34 (2007-03-23)
   2.360 +  * Fixed Move to Back piece set (0.14 regression: dang!).
   2.361 +  * Separate options for number of previewed pieces above shadow and
   2.362 +    outside field (requested by kiwibonga, caffeine, and Cubicz).
   2.363 +  * Option to disable IRS (requested by DIGITAL).
   2.364 +  * Garbage gimmicks (vs., drill 40, hr derby) replaced with new
   2.365 +    garbage option (requested by Cubicz).
   2.366 +  * In garbage style "Vs.", player can choose difficulty 1 through 4
   2.367 +    (requested by Caithness).
   2.368 +  * Option for line clear delay, independent of entry delay
   2.369 +    (requested by kiwibonga).
   2.370 +  * Garbage style Drill no longer automatically tops out on
   2.371 +    short playfields.
   2.372 +  * Garbage style Vs. generates garbage with SZSZ randomizer.
   2.373 +  * Frequency of Vs. w/Items bananas is independent of randomizer.
   2.374 +  * Fixed self-clearing garbage (0.33 regression).
   2.375 +  * GBA/DS: Fixed pause button hiding left and right walls
   2.376 +    (0.33 regression).
   2.377 +  * GBA/DS: R button also works as hold piece (requested by
   2.378 +    PetitPrince).
   2.379 +
   2.380 +_____________________________________________________________________
   2.381 +0.33 (2007-03-08)
   2.382 +  * New speed curve Rhythm Zero is identical to Rhythm except it
   2.383 +    operates at 0G instead of 20G (requested by Cubicz and caffeine).
   2.384 +  * Speed curves that do not use level reset level to 0.
   2.385 +    Therefore, playing a game of Death then a game of Exponential
   2.386 +    on GBA/DS no longer shows the level that the player finished
   2.387 +    Death on as the level during Exponential.
   2.388 +  * Maximum well width increased to 12 (requested by Zed0).
   2.389 +  * Hold piece does not reset floor kicks, lock delay time in entry
   2.390 +    reset, or Rhythm's placement timer.
   2.391 +  * GBA/DS: More shared code moved to file "ljgbads.inc".
   2.392 +  * PC: Stops end-of-section sound when game is over
   2.393 +    (requested by Bloodstar).
   2.394 +
   2.395 +_____________________________________________________________________
   2.396 +0.32a (2007-02-25)
   2.397 +  * PC: Fixed demo recording (0.32 regression; reported by Rosti LFC)
   2.398 +  * PC: Option to record all games from the start
   2.399 +    (requested by cdsboy, DIGITAL, Cubicz, and colour_thief).
   2.400 +  * PC: Added Play .ljm gimmick:
   2.401 +    Allegro file selector pops up, and the player can choose the
   2.402 +    filename of a demo to play back (requested by Lardarse).
   2.403 +
   2.404 +_____________________________________________________________________
   2.405 +0.32 (2007-02-13)
   2.406 +  * Changed a CPU yield behavior that was affecting battery
   2.407 +    efficiency on the GBA and DS front ends (reported by Mighty Max).
   2.408 +  * Removed Low Rider gimmick in favor of option for well height
   2.409 +    (requested by Matthew).
   2.410 +  * Option for well width, like Shimizu's Tetris Semipro-68k.
   2.411 +    Combine this with a low well height to simulate BIG mode of
   2.412 +    TGM and Heboris (requested by Matthew).
   2.413 +  * Options for entry delay and sideways delay have "max" added to
   2.414 +    their description to clarify things (requested by kotetsu213 and
   2.415 +    Ezzelin).
   2.416 +  * All speed options given in Hz or G are given in both.
   2.417 +  * PC: Next above shadow works in all playfield positions (reported
   2.418 +    by caffeine).
   2.419 +  * GBA/DS: Added option for soft drop speed (requested by Ezzelin).
   2.420 +  * GBA/DS: Some shared code moved to file "ljgbads.inc".
   2.421 +  * GBA: Unavailable hold piece is grayed out.
   2.422 +  * GBA/DS: Lock delay = no lock works (reported by Ezzelin).
   2.423 +  * GBA/DS: Fixed frames/ms display of delays (reported by Ezzelin).
   2.424 +
   2.425 +_____________________________________________________________________
   2.426 +0.31 (2007-02-03)
   2.427 +  * Draws the score and next pieces before blitting the playfield
   2.428 +    in case a front-end draws the score or next pieces inside the
   2.429 +    playfield.
   2.430 +  * In Baboo!, speed curve Zero sets the level to the number of
   2.431 +    keypresses so far.
   2.432 +  * PC, GBA: Plays sound effect for end of section in speed curves
   2.433 +    that use sections (Master, Death, NES, Game Boy).
   2.434 +  * PC: Option to draw next pieces inside the playfield, above the
   2.435 +    shadow (requested by caffeine, cdsboy, and Cubicz).
   2.436 +  * GBA, DS: Blocks within a piece are drawn connected once they lock
   2.437 +    (TOD parity, requested by Lardarse and Bloodstar).
   2.438 +  * GBA, DS: Baboo! no longer double-counts console buttons passed
   2.439 +    through the joypad reading code.
   2.440 +  * README: "color=image" clone-and-hack error fixed (reported by
   2.441 +    Bloodstar).
   2.442 +  * README: Explains black rectangle in upper left corner as an
   2.443 +    icon for stop (requested by herc).
   2.444 +  * README: Explains controls on GBA and DS.
   2.445 +
   2.446 +_____________________________________________________________________
   2.447 +0.30 (2007-02-01)
   2.448 +  * Fixed ARE display in debrief, which could cause a crash in some
   2.449 +    situations (0.26 regression?).
   2.450 +  * Garbage no longer self-clears in Cascade gravity (reported by
   2.451 +    Bloodstar).
   2.452 +  * In high gravity, land a T with one block over an overhang and
   2.453 +    rotate it once so that it falls and clears a line.  This is no
   2.454 +    longer counted as a T-spin.
   2.455 +  * During a replay, counts keypresses by the replay player.
   2.456 +  * PC: Scales undersized background images to the size of the window
   2.457 +    (requested by Bloodstar).
   2.458 +  * PC: If a piece enters during a skipped frame, a second upward
   2.459 +    trail is no longer drawn (reported by Bloodstar and caffeine).
   2.460 +  * PC: "Next at top" layout draws speedometer (requested by
   2.461 +    matt_hatter83).
   2.462 +  * PC: Trails option is saved properly (reported by caffeine).
   2.463 +  * PC: User can customize the game's text and background colors
   2.464 +    using skin.ini (requested by Bloodstar).
   2.465 +  * Added rudimentary port to Nintendo DS for people with MAX Media
   2.466 +    Dock, M3 Pro, SuperCard Rumble, or SLOT-1 cards, which can run
   2.467 +    DS homebrew but not GBA homebrew.
   2.468 +  * Moscow Nights removed from lj-contrib due to research into the
   2.469 +    copyright term extensions enacted by Russian Federation during
   2.470 +    the 1990s.
   2.471 +
   2.472 +_____________________________________________________________________
   2.473 +0.29 (2007-01-17)
   2.474 +  * TDS scoring recognizes chains properly (reported by Lardarse).
   2.475 +  * TDS scoring section factor stops increasing after 190 lines.
   2.476 +  * Added NES scoring method.
   2.477 +  * Added scoring for soft and hard drops to options and debrief.
   2.478 +  * Cascade no longer deletes the bottom row when lines high on the
   2.479 +    playfield are cleared (reported by Lardarse).
   2.480 +  * PC: Draws trails when a tetromino goes up or down rapidly.
   2.481 +    (Option to turn them off.)
   2.482 +  * PC: Handles dirty rectangles for next pieces and score
   2.483 +    separately.
   2.484 +  * Scoring section of README describes all scoring methods.
   2.485 +
   2.486 +_____________________________________________________________________
   2.487 +0.28 (2007-01-04)
   2.488 +  * Added Cascade gravity, as seen in Quadra, Tetris Worlds Cascade,
   2.489 +    and Tetris DS Touch.
   2.490 +  * Master/Death: Level starts at -1, so that the first piece is
   2.491 +    played at level 0 (requested by Lardarse).
   2.492 +  * Changed T-spin detection to save whether or not a rotation
   2.493 +    involved a kick (0: move; 1: rotate; 2: rotate with kick) so
   2.494 +    that scoring methods such as TDS can score T-spins with and
   2.495 +    without kicks differently.
   2.496 +  * Added TDS scoring method (line clears only).
   2.497 +  * New, more general, possibly easier to understand reshuffle code
   2.498 +    in bag randomizer (requested by Lardarse)
   2.499 +  * GBA: Gold and silver square colors added (requested by Lardarse).
   2.500 +  * PC: Game over in low ceiling no longer fades the area outside the
   2.501 +    playfield (reported by Lardarse).
   2.502 +  * PC: Close box interrupts starting and game over animations.
   2.503 +  * README warns that 4x4 squares mode needs ljconn.
   2.504 +
   2.505 +_____________________________________________________________________
   2.506 +0.27 (2006-12-20)
   2.507 +  * GBA version includes a valid header (requested by Ezzelin).
   2.508 +  * Fixed incorrect spawn orientations for Game Boy rotation system
   2.509 +    (0.25? regression, reported by Lardarse).
   2.510 +  * Fixed incorrect win/loss sound early in 180 seconds (0.25
   2.511 +    regression).
   2.512 +  * New randomizer 10-piece Bag, including the domino and both
   2.513 +    trominoes.
   2.514 +  * GBA version now reports entry and lock delays in both frames
   2.515 +    and milliseconds.
   2.516 +  * Possibly fixed off-by-one in Game Boy speed curve section
   2.517 +    computation, which caused an incorrect slowdown in 210-219
   2.518 +    section (reported by Ezzelin).
   2.519 +
   2.520 +_____________________________________________________________________
   2.521 +0.26 (2006-12-15)
   2.522 +  * Option to allow game to proceed in the background (requested by
   2.523 +    Ezzelin).  However, Windows doesn't appear to pass joystick
   2.524 +    presses to the game running in the background.  FCE Ultra has
   2.525 +    what appears to be exactly the same problem.
   2.526 +  * Added Game Boy and NES speed curves.
   2.527 +  * Options and Game Keys are accessed through title screen, not
   2.528 +    gimmick selection.
   2.529 +  * Option to hide playfield without changing the skin.
   2.530 +  * Rearranged rules section of options to correspond more closely to
   2.531 +    the sequence of operations for each tetromino.
   2.532 +  * Soft or hard drop set to "lock on release" no longer produces
   2.533 +    double locks at 20G.
   2.534 +  * Disable initial hard drop when set to "lock" and entry delay is
   2.535 +    greater than 0 but less than DAS delay.
   2.536 +  * Checks for 4x4 squares top to bottom per comparison with The New
   2.537 +    Tetris (N64).
   2.538 +  * Finer grained selections in entry delay (requested by
   2.539 +    matt_hatter83) and lock delay (requested by caffeine).
   2.540 +    They now are at 50ms increments at the low end.
   2.541 +  * Expanded TGM speed curve to 12 sections.  One comparatively slow
   2.542 +    section at 20G was added to Master before Death-equivalent starts
   2.543 +    (now Death 0 is equivalent to Master 600, not 500), and one
   2.544 +    faster section was added to the end of Death based on info posted
   2.545 +    to wiki by colour_thief.
   2.546 +  * Added H.R. Derby gimmick:
   2.547 +    Like Marathon, but every line you clear other than with a
   2.548 +    home run or a T-spin gives you garbage.
   2.549 +  * Ready Go animation is 1.2 seconds, not 2.0 seconds.
   2.550 +  * Preliminary support for Game Boy Advance, with experimental
   2.551 +    paged options replacing scrolling options.
   2.552 +
   2.553 +_____________________________________________________________________
   2.554 +0.25 (2006-11-28)
   2.555 +  * Fixed History 6 Rolls deciding between the first piece algorithm
   2.556 +    and the subsequent pieces algorithm (reported by Lardarse).
   2.557 +  * Fixed a signedness issue that broke 180 seconds gimmick in the
   2.558 +    case that a tetromino was kept active between 3:00 and 3:01 (0.24
   2.559 +    regression; reported by caffeine).
   2.560 +  * Allows ending the game with a piece in mid-air (0.24 regression).
   2.561 +  * Removed Arika and renamed Arika+TI to Arika.  For the old
   2.562 +    Arika behavior, set Options:Floor kicks to 0.
   2.563 +  * Options screen allows DAS, allowing future versions to make more
   2.564 +    options and more values for each option available in a less
   2.565 +    cumbersome way.
   2.566 +
   2.567 +_____________________________________________________________________
   2.568 +0.24 (2006-11-16)
   2.569 +  * Added Score style to options and debrief.
   2.570 +  * Choice of LJ or TNT64 scoring is based on chosen score style, not
   2.571 +    whether 4x4 squares are turned on.
   2.572 +  * Added Hotline scoring method, where only lines cleared on
   2.573 +    specific rows count toward scoring.  When enabled, draws white
   2.574 +    lines through empty spaces in these rows.
   2.575 +  * New soft drop and hard drop lock setting: Lock on release.  It's
   2.576 +    a spring-loaded system:  When you press the button, it doesn't
   2.577 +    lock, but when you let go, it locks (requested by colour_thief).
   2.578 +  * Option to override lock delay time (requested by caffeine).
   2.579 +  * Option to limit upward kicks (requested by Needle). This may
   2.580 +    obviate the difference between Arika and Arika+TI.
   2.581 +  * Sped up line clear in Master 300-499 to make less of a jarring
   2.582 +    transition to "death" style timings.
   2.583 +  * LJM loading fails even more gracefully on wrong format version,
   2.584 +    properly treating an LJM of the wrong version as not existing.
   2.585 +  * Fixed bug in 0.23's application switch pausing where switching
   2.586 +    away while already paused would require two Esc presses to
   2.587 +    continue (reported by caffeine).
   2.588 +  * Does not stop play until after the line clear animation finishes.
   2.589 +    This allows the game to properly update the single, double,
   2.590 +    triple, etc. counts for the last line that completes the goal
   2.591 +    (0.19 scoring regression?; reported by caffeine).
   2.592 +  * Refactored duplicated option loading code.
   2.593 +  * Options: Ditched parallel struct and array in favor of a
   2.594 +    single list of named indices.
   2.595 +  * Corrected Moscow Nights and Kalinka in lj-contrib for greater
   2.596 +    compatibility with obscure s3m players that can't handle odd
   2.597 +    numbers of channels (requested by Lardarse).
   2.598 +
   2.599 +_____________________________________________________________________
   2.600 +0.23 (2006-11-04)
   2.601 +  * Fixed S and Z in Sega 1988.
   2.602 +  * DUMB has been wrapped in a library called LJMusic and can be
   2.603 +    turned off at compile time (edit ljmusic.c and makefile) if you
   2.604 +    don't want to install DUMB.
   2.605 +  * Added support for Xiph.org's OggVorbis codec to LJMusic
   2.606 +    (requested by Needle).
   2.607 +  * Option to ignore sideways movement on first frame (like TGM
   2.608 +    series) (requested by Needle, seconded by caffeine).
   2.609 +  * Properly pauses the game when the player switches away from the
   2.610 +    window (requested by Lardarse).
   2.611 +  * Fails gracefully (does nothing) instead of crashing when loading
   2.612 +    replay files of a different format version.
   2.613 +  * Moved as much as possible below setting display mode so that
   2.614 +    people porting LJ to other platforms can toss up working alert()
   2.615 +    boxes earlier (requested by cdsboy).
   2.616 +
   2.617 +_____________________________________________________________________
   2.618 +0.22 (2006-10-23)
   2.619 +  * Fixed color system change after reloading (0.20 regression?,
   2.620 +    reported by lardarse).  Should always unpack options just before
   2.621 +    loading skin.
   2.622 +  * All numbers in saved states are stored big-endian, making movies
   2.623 +    compatible between 0.22 for Windows and Intel Mac and 0.22 for
   2.624 +    PowerPC Mac (requested by cdsboy).
   2.625 +  * Sega rotation systems renamed to Arika for less confusion with
   2.626 +    the rotation systems in Sega's Tetris games from 1988 and 1998
   2.627 +    (requested by Needle).
   2.628 +  * Added Sega 1988 rotation system (like Arika without wall kicks).
   2.629 +  * Preliminary Vorbis playback code is included in the source code
   2.630 +    distribution but has not been activated in the program.
   2.631 +    (The keys to compiling OggVorbis on MinGW+MSYS are 1. set the
   2.632 +    prefix to match the MINGDIR you used to build Allegro, and
   2.633 +    2. disable creation of the shared library.)
   2.634 +
   2.635 +_____________________________________________________________________
   2.636 +0.21 (2006-10-21)
   2.637 +  * Added new T-spin detection rule "3-corner T no kick", identical
   2.638 +    to 3-corner T except that a wallkick is not counted as a rotation
   2.639 +    (requested by kotetsu213).
   2.640 +  * Options that don't apply because of how another option is set
   2.641 +    are grayed out with an explanation (requested by Needle).
   2.642 +  * Debrief tells number of blocks left in the playfield and whether
   2.643 +    saved state was used (requested by caffeine).
   2.644 +  * Debrief tells what level the player stopped on.
   2.645 +  * Added support for saving input stream ("demo" or "movie") to file
   2.646 +    "demo.ljm".  Press [ to start/stop recording and ] to start/stop
   2.647 +    playback (requested by caffeine).
   2.648 +  * Pieces are actually random again (0.20 regression).
   2.649 +  * New lockdown mode: Entry reset.  Instead of resetting, the lock
   2.650 +    timer pauses while the piece is falling (requested by caffeine).
   2.651 +  * No +20 for banking more than 5 beats (requested by caffeine).
   2.652 +  * 250 ms entry delay option (requested by matt_hatter83).
   2.653 +  * Entry and sideways delays in Master and Death use the shorter of
   2.654 +    the speed curve's delay and the user's delay.
   2.655 +  * Added soft drop speed to options.
   2.656 +  * Added settings 8.6 Hz through 6.7 Hz to Sideways speed.
   2.657 +
   2.658 +_____________________________________________________________________
   2.659 +0.20 (2006-10-15)
   2.660 +  * Fixed color system change after options (0.19 regression).
   2.661 +  * Restored ability to hold first piece (0.17 regression?).
   2.662 +  * Requires down to be re-pressed only when entry delay is less
   2.663 +    than sideways delay.
   2.664 +  * In modes with ARE, allow initial hard drop even on first piece
   2.665 +    (requested by caffeine).
   2.666 +  * Visual refresh in Game Keys.
   2.667 +  * Game Keys ignores keypresses for 1/4 second after each key is
   2.668 +    set, which should fix problems with GameCube to USB adapters
   2.669 +    (requested by Caithness).
   2.670 +  * All platforms use a single randomizer, whose state is associated
   2.671 +    with the playfield.
   2.672 +  * ARE is a binary option again, as several speed curves ignored the
   2.673 +    old version's difference between constant and decreasing.
   2.674 +  * Moved version display to title screen.
   2.675 +  * Sound effect for win differs from that for game over.
   2.676 +  * Sound effect for rotating a piece on its first active frame.
   2.677 +  * Begun to add assertions, causing game over if they fail.
   2.678 +  * Added support for saving the state of the playfield.
   2.679 +    Press [ to save or ] to load.  Movies might be next.
   2.680 +
   2.681 +_____________________________________________________________________
   2.682 +0.19 (2006-09-28):
   2.683 +  * Use of SRS or Sega colors is determined by a flag set for each
   2.684 +    rotation system.  The rule is that rotation systems that use
   2.685 +    bounding box rotation should use SRS colors.
   2.686 +  * Added TOD M4 rotation system (face up entry + bounding box
   2.687 +    rotation + roughly TGM style compensation).
   2.688 +  * List items in Options flicker much less.
   2.689 +  * Beginning of an actual title screen.
   2.690 +  * Removed Rhythm gimmick in favor of speed curve option.
   2.691 +  * Added Master and Death speed curves (requested by Needle).
   2.692 +    Death hasn't been tested thoroughly because the developer
   2.693 +    sucks at death.
   2.694 +  * Blits only those playfield rows that have changed, making
   2.695 +    animation smoother on old, cheap, or mobile video cards
   2.696 +    (requested by cosmonaut).
   2.697 +  * Rhythm speed curve no longer has what feels like a big elbow
   2.698 +    (requested by caffeine).  Specifically it gives 20 points
   2.699 +    instead of banking more than 5 pieces' worth of time.
   2.700 +  * Doesn't draw the state after the tetromino spawns but before
   2.701 +    initial rotation has taken effect (requested by Needle).
   2.702 +  * Added sound effect for hold piece (requested by Needle).
   2.703 +  * Land and lock sound much better (requested by Needle).
   2.704 +  * Shuffle columns (seen only in Vs. w/ Items) correctly disconnects
   2.705 +    blocks horizontally.
   2.706 +  * In Square mode, T-spins cause avalanches.
   2.707 +  * In Square mode, lines containing a piece of a 4x4 block are worth
   2.708 +    more, and homers are worth less.
   2.709 +  * Fixed ignoring diagonal presses (0.17 regression).
   2.710 +  * Created a new struct "LJPrefs" to hold preferences set by the
   2.711 +    player in the Options menu separately from the parameters
   2.712 +    that the game actually uses. This allows gimmicks to override
   2.713 +    preferences more cleanly.
   2.714 +  * Added support for future scoring methods where the score per line
   2.715 +    at a given point in the chain is based on a formula.
   2.716 +  * Created a union that combines the new struct with the array that
   2.717 +    the Options menu edits to improve maintainability of Options.
   2.718 +
   2.719 +_____________________________________________________________________
   2.720 +0.18 (2006-09-16):
   2.721 +  * Fixed Classic lock reset (unknown regression).
   2.722 +  * Fixed misbehavior when hold is pressed after a piece
   2.723 +    lands but before it locks (0.17 regression).
   2.724 +  * Shadow is drawn using the piece's color, at 25% opacity,
   2.725 +    from rows 4 and 5 of ljblocks.
   2.726 +  * Fixed shadow option saving (0.17a regression).
   2.727 +  * Added option to hide shadow color or change shadow opacity.
   2.728 +  * Split Sega rotation system into one with and one without
   2.729 +    TGM3's upward kicks.
   2.730 +  * Added NES and Game Boy rotation systems.
   2.731 +  * README: Illustration of game play has an image map.  The reader
   2.732 +    can select (hover over) a region of the screenshot, and the
   2.733 +    browser will show the title of the region as a tooltip.
   2.734 +  * BPM is now called speed level in preparation for other
   2.735 +    speed curves (including TGM and TA Death).
   2.736 +
   2.737 +_____________________________________________________________________
   2.738 +0.17 (2006-09-13):
   2.739 +  * Option for sticky gravity per color, as seen in The Next Tetris.
   2.740 +  * "Ready, Go" is centered, even in low ceiling gimmicks.
   2.741 +  * Added TGM3's upward kick to Sega T rotations.
   2.742 +  * Added Tengen rotation system.
   2.743 +  * Removed TGM gimmick in favor of entry delay option.
   2.744 +  * Initial hold works at any time.  The key can be pressed and
   2.745 +    released during entry delay or even during line clear delay.
   2.746 +  * Initial actions do not inflate keypress count
   2.747 +    (helpful in Baboo! with entry delay).
   2.748 +  * Draws hold piece in garbage colors when it is not available.
   2.749 +  * Options scroll.
   2.750 +  * Option to draw next pieces to right in constant size or above.
   2.751 +  * Better looking scroll bar in options.
   2.752 +  * Debrief: In naive gravity, reports T-spin singles, doubles,
   2.753 +    and triples separately from non-T-spins.  In other line
   2.754 +    clear gravity modes, "home run" is now called "quad".
   2.755 +  * Fixed a buffer overflow bug in sticky: fillCLoop() no longer
   2.756 +    treats the right side of one row and the left side of the
   2.757 +    row above it as one region.
   2.758 +  * Clarified manual as to the purpose of the Vs. gimmicks.
   2.759 +  * Separated platform specific stuff into a separate struct.
   2.760 +  * Refactored scoring into a separate function for future movement
   2.761 +    to gimmick and option control.
   2.762 +  * Debrief formats the report into a string and then writes it
   2.763 +    to a file and the screen all at once.
   2.764 +  * Refactored play() to move everything in the game loop that is
   2.765 +    not platform specific out of ljpc.c into new file ljplay.c.
   2.766 +  * Wall kick table is no longer flipped, making it easier to add
   2.767 +    new rotation systems.
   2.768 +  * Unified counterclockwise and clockwise rotation code.
   2.769 +
   2.770 +_____________________________________________________________________
   2.771 +0.16 (2006-09-03):
   2.772 +  * Fixed instant sideways speed (0.15 regression).
   2.773 +  * In countdown modes, debrief screen displays whether clear was
   2.774 +    successful.
   2.775 +  * 6-piece Bag no longer makes Vs. ridiculously easy.
   2.776 +  * Counts score and garbage separately for future shift to
   2.777 +    gimmick-controlled scoring.
   2.778 +  * Displays score instead of garbage on the play screen.
   2.779 +  * Counts singles, doubles, triples, and homers, and displays them
   2.780 +    in debrief.
   2.781 +  * Option to hide falling piece in addition to shadow.
   2.782 +  * Can load mod, xm, or it music in addition to s3m.
   2.783 +  * Moved skin description from lj.ini to skin.ini, and the name of
   2.784 +    this skin description file name can be changed with the Skin=
   2.785 +    command in lj.ini.
   2.786 +  * Closes lj.ini when reading it.
   2.787 +  * All vkey->action game key handling code moved to macro.c for
   2.788 +    future refactoring.
   2.789 +  * Build process uses automatic generation of C files' dependencies
   2.790 +    for future refactoring of header files.
   2.791 +  * Added two versions of NES-style blocks to lj-contrib:
   2.792 +    one by tepples and one by deepdorp.
   2.793 +
   2.794 +_____________________________________________________________________
   2.795 +0.15 (2006-08-27):
   2.796 +  * Options for DAS delay, number of next pieces, and window vs.
   2.797 +    full screen.
   2.798 +  * Options scrolling doesn't cause the dialog's title and
   2.799 +    instructions to flicker.
   2.800 +  * Small font's 5 glyph is the same width as other digits.
   2.801 +  * Fixed O shape table.
   2.802 +  * Macros (vkey->action mappings past the first 8) are in a
   2.803 +    lookup table in the new file macro.c.  This paves the way
   2.804 +    for a future macro editor.
   2.805 +  * Added macros Alt. Firm Drop and Alt. Hold.
   2.806 +  * Game Keys displays names for all keys, not just printable keys.
   2.807 +  * In Sega rotation mode, reads block images from ljblocks-sega.bmp
   2.808 +    and ljconn-sega.bmp if available.
   2.809 +  * If ljbg.jpg is not available, uses a plain white backdrop.
   2.810 +  * Reads file names for block images, background image, and music
   2.811 +    from lj.ini, which the player can edit with a text editor.
   2.812 +  * New "6-piece Bag" randomizer is one stick short of a bundle.
   2.813 +  * Added an arrangement of the minuet from JS Bach's French Suite
   2.814 +    in B minor (BWV 814) to lj-contrib.
   2.815 +
   2.816 +_____________________________________________________________________
   2.817 +0.14 (2006-08-18):
   2.818 +  * pause() renamed to pauseGame() to fix namespace collision on
   2.819 +    platforms with unistd.h (Linux, BSD, Mac).
   2.820 +  * Gimmick settings for initial gravity, ARE, and ceiling height
   2.821 +    are initialized from a lookup table.
   2.822 +  * History 6 Rolls randomizer uses correct initial history (ZSZS)
   2.823 +    and correct selection for the first piece (I, J, L, or T).
   2.824 +  * Blocks are drawn connected within each tetromino if ljconn.bmp
   2.825 +    is present.
   2.826 +  * Corrected connection table for O tetromino.
   2.827 +  * Rhythm's BPM counter increases after every 64 beats, not every
   2.828 +    64 tetrominoes.
   2.829 +  * Added Low Vs. gimmick:
   2.830 +    Like Vs. CPU with a low ceiling.
   2.831 +
   2.832 +_____________________________________________________________________
   2.833 +0.13 (2006-08-15):
   2.834 +  * Hard drop lock option "Zangi" renamed to "Slide".
   2.835 +  * Added soft drop lock option.
   2.836 +  * Ignores diagonal presses differently: instead of pretending the
   2.837 +    keys are up, it pretends that they haven't changed from the last
   2.838 +    frame.  UP, UP+RIGHT, UP no longer makes a double hard drop.
   2.839 +  * Esc pauses the game and music instead of immediately quitting.
   2.840 +  * Added contributions from cdsboy to lj-contrib.
   2.841 +
   2.842 +_____________________________________________________________________
   2.843 +0.12 (2006-08-13):
   2.844 +  * Creates lj.ini if it doesn't exist, instead of crashing on
   2.845 +    startup.
   2.846 +  * Added half-size Aver16 font.
   2.847 +  * Zero gravity (as in Baboo!) + Zangi hard drop = land and lock,
   2.848 +    instead of just sit there
   2.849 +  * Option to use classic (Game Boy style) or step-reset (TGM style)
   2.850 +    lockdown instead of move-reset (SRS style) lockdown.
   2.851 +  * Contains connection tables for all pieces.  Not used yet,
   2.852 +    but will be used for Cascade (where gravity is based on shapes
   2.853 +    of pieces), Square (which forms big squares only out of whole
   2.854 +    tetrominoes), and Bombliss (which doesn't place a bomb in a block
   2.855 +    that has blocks on opposite sides of it within the piece, such as
   2.856 +    the middle of an I, J, L, or T).
   2.857 +  * Pieces can carry bomb blocks within them.  Not used yet,
   2.858 +    but will be used for Bombliss.
   2.859 +  * Option for sticky gravity.  This will also be used for Bombliss.
   2.860 +  * Options rearranged: game on top, control in middle,
   2.861 +    view on bottom.
   2.862 +  * Options menu uses a smaller font and scrolls if necessary (but
   2.863 +    it's not necessary just yet).
   2.864 +  * Zero isn't wider than other digits in the large or small font.
   2.865 +  * Split debrief screen into debrief.c.
   2.866 +  * Debrief displays lockdown and line clear gravity type.
   2.867 +  * List of options in debrief is written with smaller font.
   2.868 +  * Config screen does a better job of ignoring simultaneous presses
   2.869 +    on USB joystick adapters that map the Control Pad to both axes
   2.870 +    and buttons (such as EMS USB2).
   2.871 +  * Main menu and options always respond to the arrow keys (in
   2.872 +    addition to the keys set in Game Keys), allowing players to
   2.873 +    reset Game Keys even when the keys have become corrupted.
   2.874 +
   2.875 +_____________________________________________________________________
   2.876 +0.11 (2006-08-01):
   2.877 +  * Added Instant, 20 Hz, 15 Hz, and 12 Hz sideways speeds.
   2.878 +  * Added randomizer History 6 Rolls, very similar to Move to Back
   2.879 +    but sometimes repeating recent tetrominoes.
   2.880 +  * Allows separate wall kick tables for clockwise and anticlockwise
   2.881 +    rotation.
   2.882 +  * New "theta=default" in piece->block expansion reflects the
   2.883 +    rotation system's initial orientations for new pieces, next
   2.884 +    pieces, and the hold piece.
   2.885 +  * Option to use Sega rotations instead of SRS rotations.
   2.886 +  * Option for ignoring diagonal presses.
   2.887 +  * Option for locking or not when using hard drop (Up).
   2.888 +  * Option to turn off smooth gravity animation.
   2.889 +  * Option to use TNT style T-spin detection instead of TDS style.
   2.890 +  * Debrief displays rotation system and T-spin detection.
   2.891 +  * Saves options to lj.ini.
   2.892 +  * Split options screen and wall kick table into separate source
   2.893 +    code files.
   2.894 +  * Plays music in stereo.
   2.895 +  * Site distributes contributed graphics and music files to
   2.896 +    replace the defaults.
   2.897 +
   2.898 +_____________________________________________________________________
   2.899 +0.10 (2006-07-27):
   2.900 +  * Set default full screen color depth to 16-bit which may be more
   2.901 +    compatible than 15-bit.
   2.902 +  * Press Print Screen to save the current screen as ljsnap.bmp.
   2.903 +  * Plays next piece sound whenever the next column moves up.
   2.904 +    0.08-0.09 didn't do so for the first hold in a game.
   2.905 +  * No longer tries rotating right when rotating left fails.
   2.906 +  * Handles DirectDraw amnesia (screen corruption in Alt+Tab)
   2.907 +    correctly in menu, game play, and debrief screens.
   2.908 +  * Asset and video buffer cleanup consolidated into one function.
   2.909 +  * Gravity speeds up 50% faster.
   2.910 +  * Added options menu for selecting sideways movement speed and
   2.911 +    randomizer.
   2.912 +  * Added more randomizers: 14-piece bag, move-to-back (TGM style),
   2.913 +    memoryless (classic), and SZSZ (used in the well-known proof
   2.914 +    that memoryless cannot be played indefinitely).
   2.915 +  * Debrief screen displays date and time of report generation as
   2.916 +    well as randomizer and sideways speed, and logs what it displays
   2.917 +    to lj-scores.txt.
   2.918 +  * Moved all gimmick-specific code from lj.c to new file gimmicks.c.
   2.919 +  * Built against Allegro 4.2.0.  (Users of previous versions will
   2.920 +    need the new DLL.)
   2.921 +  * Countdown based gimmicks (40 lines, 180 seconds, Baboo!,
   2.922 +    TGM World, Drill 40) use the same countdown variable for
   2.923 +    end state detection that they had used for countdown sound.
   2.924 +  * Support for tracked music using DUMB.
   2.925 +  * Manual is HTML.
   2.926 +
   2.927 +_____________________________________________________________________
   2.928 +0.09 (2006-07-16):
   2.929 +  * Reverted sideways kicks of I tetromino rotating from horizontal
   2.930 +    to vertical based on testing I-I interactions in Tetris DS.
   2.931 +  * Key pretending is more consistent.
   2.932 +  * TGM World stops at 290 lines, more like Tetris The Grand Master.
   2.933 +  * In gimmicks with ARE (currently TGM World), allows initial
   2.934 +    hard drop.
   2.935 +  * Licensed as free software under GNU General Public License.
   2.936 +  * Fixed incomplete first bag (0.08 regression).
   2.937 +  * Has its own icon instead of the wall kick editor's icon.
   2.938 +  * No double initial rotation when using initial hold in TGM World.
   2.939 +  * Input actions are a 32-bit record for future recording.
   2.940 +  * Hidden next piece is a separate variable, which next piece
   2.941 +    sound also respects, fixing Items.
   2.942 +
   2.943 +_____________________________________________________________________
   2.944 +0.08 (2006-07-08):
   2.945 +  * Plays sounds for the next piece like TGM.
   2.946 +  * No longer allows 800x552 window now that 0.08 displays the
   2.947 +    gimmick name.
   2.948 +  * Supports spawn delay and line delay.
   2.949 +  * Supports initial hold and rotation.
   2.950 +  * Supports variable garbage randomness.
   2.951 +  * Begun to move gimmick code out of core.
   2.952 +  * Added TGM World gimmick:
   2.953 +    Standard S.M.G., except there's a half-second delay before each
   2.954 +    tetromino, and a half-second delay after each tetromino that
   2.955 +    forms at least one line. So if you want fast play, make more
   2.956 +    lines at once.
   2.957 +  * Added Drill 40 gimmick: 
   2.958 +    Like 40 lines, but the screen starts with 18 rows of garbage.
   2.959 +    Be prepared to rely on SRS infinite spin for the first few
   2.960 +    lines until your skill improves.
   2.961 +
   2.962 +_____________________________________________________________________
   2.963 +0.07 (2006-06-30):
   2.964 +  * Added Baboo gimmick:
   2.965 +    Standard S.M.G., except there is no gravity, and the game
   2.966 +    ends after 300 keypresses.
   2.967 +  * Cleaned up the font a bit.
   2.968 +  * Corrected wall kicks for I tetromino to match an updated
   2.969 +    description provided by nicholas.  Specifically, I tetromino
   2.970 +    prefers kick-down to kick-up.
   2.971 +  * Removed sound-not-found debug message.
   2.972 +  * Plays "ready, go!" like TGM.
   2.973 +  * Bigger font for lines and time.
   2.974 +
   2.975 +_____________________________________________________________________
   2.976 +0.06 (2006-06-25):
   2.977 +  * Gimmicks are sorted into columns.
   2.978 +  * Debrief responds to controller mapping.
   2.979 +  * Debrief shows keypresses per tetromino and garbage per minute.
   2.980 +  * Loads/saves vkey configuration from/to disk instead of
   2.981 +    prompting the user every time the program starts.
   2.982 +  * Build system switched to GNU Make.
   2.983 +  * Terminal window ("DOS box") hidden.
   2.984 +  * Responds to window system's close button.
   2.985 +  * Counts down 5-4-3-2-1 in line or time limited gimmicks.
   2.986 +  * Icon in the top left corner of the window is customized.
   2.987 +  * Displays ljbg.jpg in the window's blank space.
   2.988 +  * Displays blocks from ljblocks.bmp.
   2.989 +  * Releases the CPU for 5ms per frame when possible, so that
   2.990 +    background tasks such as a music player can keep up.
   2.991 +  * Increased sound effects' volume so that music players do not
   2.992 +    completely overpower them.
   2.993 +  * Added macros for rotate twice, move far left, and move far right.
   2.994 +  * Corrected fix to alternation between falling and landed states
   2.995 +    that occurred especially when sliding a tetromino to the right
   2.996 +    under high gravity.
   2.997 +  * Added Items gimmick:
   2.998 +    Standard S.M.G., except after the first 7 pieces you get
   2.999 +    random starting orientations, no rotation, and hidden next
  2.1000 +    pieces, and the speed goes to 1G.  Every time you are given an
  2.1001 +    I piece, either you get 2 lines of garbage or the columns of
  2.1002 +    blocks in the well are shuffled.
  2.1003 +  * Fixed scoring tables in README.txt.
  2.1004 +
  2.1005 +_____________________________________________________________________
  2.1006 +0.05 (2006-06-23):
  2.1007 +  * Included readme.txt.
  2.1008 +  * Debrief report shows more decimal places in time and PPM.
  2.1009 +  * Game over plays a simple animation and sound.
  2.1010 +  * Playfield can be resized (with a recompile).
  2.1011 +  * Removed hardcoded key binding help text.
  2.1012 +  * Window is bigger in preparation for wallpaper.
  2.1013 +  * Display mode is 15/16/24/32-bit in preparation for wallpaper.
  2.1014 +  * Added Low Rider gimmick:
  2.1015 +    Standard S.M.G. with an 8-block-tall visible playfield.
  2.1016 +
  2.1017 +_____________________________________________________________________
  2.1018 +0.04 (2006-06-22):
  2.1019 +  * Uses an OS-native bitmap rather than an Allegro bitmap,
  2.1020 +    which may allow faster drawing with some video cards.
  2.1021 +  * Allows play from a keyboard or a joystick.
  2.1022 +  * Presents a key configuration screen when the program starts.
  2.1023 +  * A 500ms delay after the game ends. Drama will come soon to this
  2.1024 +    space.
  2.1025 +
  2.1026 +_____________________________________________________________________
  2.1027 +0.03 (2006-06-21):
  2.1028 +  * Debriefing responds only to Esc, Enter, and keypad Enter.
  2.1029 +  * Compensates for refresh rates other than 60 Hz.
  2.1030 +  * Change log included with program.
  2.1031 +  * Plays sound effects for shift, rotate, land, lock, line, b2b.
  2.1032 +  * Bigger text using the "Aver" font.
  2.1033 +  * Lock delay in Marathon decreases gradually after gravity
  2.1034 +    surpasses 20G.
  2.1035 +  * Reads keys through a bitfield, allowing for custom key->vkey
  2.1036 +    bindings to be implemented in the future.
  2.1037 +  * (Internal) Does not alternate between "falling" and "landed"
  2.1038 +    states when resetting lock delay on slide.
  2.1039 +
  2.1040 +_____________________________________________________________________
  2.1041 +0.02 (2006-06-19):
  2.1042 +  * Speed level progresses 1/3 as fast.
  2.1043 +  * Added menu for selecting a gimmick (game mode) to play.
  2.1044 +  * Added Marathon gimmick:
  2.1045 +    Like Vs. but no garbage.
  2.1046 +  * Added 40 lines gimmick:
  2.1047 +    Like Marathon but ends after 40 lines.
  2.1048 +  * Added 3 minutes gimmick (like Marathon but ends after 10800
  2.1049 +    frames).
  2.1050 +  * Added Rhythm gimmick:
  2.1051 +    20G, and if you fall below the minimum PPM, it automatically
  2.1052 +    locks the tetromino, and the minimum PPM increases by 10 every
  2.1053 +    64 tetrominoes.
  2.1054 +  * Debriefing computes PPM and score.
  2.1055 +  * Debriefing returns to gimmick selection instead of exiting
  2.1056 +    the program.
  2.1057 +
  2.1058 +_____________________________________________________________________
  2.1059 +0.01 (2006-06-14):
  2.1060 +  * initial release
  2.1061 +  * full SRS implemented
  2.1062 +  * 7-piece bag randomizer
  2.1063 +  * 1G DAS
  2.1064 +  * smooth falling animation
  2.1065 +  * playfield size: 10w x 24h; pieces start above row 20
  2.1066 +  * 8 next pieces and 1 hold piece
  2.1067 +  * Vs. gimmick:
  2.1068 +    Standard S.M.G., except before every I tetromino after the first,
  2.1069 +    you get 4 garbage lines.
  2.1070 +
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/COPYING.txt	Fri Mar 13 00:39:12 2009 -0700
     3.3 @@ -0,0 +1,52 @@
     3.4 +Copying conditions for LOCKJAW:
     3.5 +
     3.6 +Copyright 2007 Damian Yerrick <tepples+lj@spamcop.net>
     3.7 +
     3.8 +This program is free software; you can redistribute it and/or
     3.9 +modify it under the terms of the GNU General Public License
    3.10 +as published by the Free Software Foundation; either version 2
    3.11 +of the License, or (at your option) any later version.
    3.12 +
    3.13 +This program is distributed in the hope that it will be useful,
    3.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
    3.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    3.16 +GNU General Public License for more details.
    3.17 +
    3.18 +You should have received a copy of the GNU General Public License
    3.19 +(GPL.txt) along with this program.  If not, see
    3.20 +<http://www.gnu.org/licenses/>.
    3.21 +
    3.22 +_____________________________________________________________________
    3.23 +Copying conditions for LJVorbis:
    3.24 +
    3.25 +Copyright 2006 Damian Yerrick
    3.26 +Copyright 2002-2004 Xiph.org Foundation
    3.27 +
    3.28 +Redistribution and use in source and binary forms, with or without
    3.29 +modification, are permitted provided that the following conditions
    3.30 +are met:
    3.31 +
    3.32 +- Redistributions of source code must retain the above copyright
    3.33 +notice, this list of conditions and the following disclaimer.
    3.34 +
    3.35 +- Redistributions in binary form must reproduce the above copyright
    3.36 +notice, this list of conditions and the following disclaimer in the
    3.37 +documentation and/or other materials provided with the distribution.
    3.38 +
    3.39 +- Neither the name of the Xiph.org Foundation nor the names of its
    3.40 +contributors may be used to endorse or promote products derived from
    3.41 +this software without specific prior written permission.
    3.42 +
    3.43 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    3.44 +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    3.45 +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    3.46 +A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION
    3.47 +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    3.48 +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    3.49 +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    3.50 +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    3.51 +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    3.52 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    3.53 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    3.54 +
    3.55 +_____________________________________________________________________
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/GPL.txt	Fri Mar 13 00:39:12 2009 -0700
     4.3 @@ -0,0 +1,342 @@
     4.4 +		    GNU GENERAL PUBLIC LICENSE
     4.5 +		       Version 2, June 1991
     4.6 +
     4.7 + Copyright (C) 1989, 1991 Free Software Foundation, Inc.
     4.8 +                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
     4.9 + Everyone is permitted to copy and distribute verbatim copies
    4.10 + of this license document, but changing it is not allowed.
    4.11 +
    4.12 +			    Preamble
    4.13 +
    4.14 +  The licenses for most software are designed to take away your
    4.15 +freedom to share and change it.  By contrast, the GNU General Public
    4.16 +License is intended to guarantee your freedom to share and change free
    4.17 +software--to make sure the software is free for all its users.  This
    4.18 +General Public License applies to most of the Free Software
    4.19 +Foundation's software and to any other program whose authors commit to
    4.20 +using it.  (Some other Free Software Foundation software is covered by
    4.21 +the GNU Library General Public License instead.)  You can apply it to
    4.22 +your programs, too.
    4.23 +
    4.24 +  When we speak of free software, we are referring to freedom, not
    4.25 +price.  Our General Public Licenses are designed to make sure that you
    4.26 +have the freedom to distribute copies of free software (and charge for
    4.27 +this service if you wish), that you receive source code or can get it
    4.28 +if you want it, that you can change the software or use pieces of it
    4.29 +in new free programs; and that you know you can do these things.
    4.30 +
    4.31 +  To protect your rights, we need to make restrictions that forbid
    4.32 +anyone to deny you these rights or to ask you to surrender the rights.
    4.33 +These restrictions translate to certain responsibilities for you if you
    4.34 +distribute copies of the software, or if you modify it.
    4.35 +
    4.36 +  For example, if you distribute copies of such a program, whether
    4.37 +gratis or for a fee, you must give the recipients all the rights that
    4.38 +you have.  You must make sure that they, too, receive or can get the
    4.39 +source code.  And you must show them these terms so they know their
    4.40 +rights.
    4.41 +
    4.42 +  We protect your rights with two steps: (1) copyright the software, and
    4.43 +(2) offer you this license which gives you legal permission to copy,
    4.44 +distribute and/or modify the software.
    4.45 +
    4.46 +  Also, for each author's protection and ours, we want to make certain
    4.47 +that everyone understands that there is no warranty for this free
    4.48 +software.  If the software is modified by someone else and passed on, we
    4.49 +want its recipients to know that what they have is not the original, so
    4.50 +that any problems introduced by others will not reflect on the original
    4.51 +authors' reputations.
    4.52 +
    4.53 +  Finally, any free program is threatened constantly by software
    4.54 +patents.  We wish to avoid the danger that redistributors of a free
    4.55 +program will individually obtain patent licenses, in effect making the
    4.56 +program proprietary.  To prevent this, we have made it clear that any
    4.57 +patent must be licensed for everyone's free use or not licensed at all.
    4.58 +
    4.59 +  The precise terms and conditions for copying, distribution and
    4.60 +modification follow.
    4.61 +
    4.62 +		    GNU GENERAL PUBLIC LICENSE
    4.63 +   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
    4.64 +
    4.65 +  0. This License applies to any program or other work which contains
    4.66 +a notice placed by the copyright holder saying it may be distributed
    4.67 +under the terms of this General Public License.  The "Program", below,
    4.68 +refers to any such program or work, and a "work based on the Program"
    4.69 +means either the Program or any derivative work under copyright law:
    4.70 +that is to say, a work containing the Program or a portion of it,
    4.71 +either verbatim or with modifications and/or translated into another
    4.72 +language.  (Hereinafter, translation is included without limitation in
    4.73 +the term "modification".)  Each licensee is addressed as "you".
    4.74 +
    4.75 +Activities other than copying, distribution and modification are not
    4.76 +covered by this License; they are outside its scope.  The act of
    4.77 +running the Program is not restricted, and the output from the Program
    4.78 +is covered only if its contents constitute a work based on the
    4.79 +Program (independent of having been made by running the Program).
    4.80 +Whether that is true depends on what the Program does.
    4.81 +
    4.82 +  1. You may copy and distribute verbatim copies of the Program's
    4.83 +source code as you receive it, in any medium, provided that you
    4.84 +conspicuously and appropriately publish on each copy an appropriate
    4.85 +copyright notice and disclaimer of warranty; keep intact all the
    4.86 +notices that refer to this License and to the absence of any warranty;
    4.87 +and give any other recipients of the Program a copy of this License
    4.88 +along with the Program.
    4.89 +
    4.90 +You may charge a fee for the physical act of transferring a copy, and
    4.91 +you may at your option offer warranty protection in exchange for a fee.
    4.92 +
    4.93 +  2. You may modify your copy or copies of the Program or any portion
    4.94 +of it, thus forming a work based on the Program, and copy and
    4.95 +distribute such modifications or work under the terms of Section 1
    4.96 +above, provided that you also meet all of these conditions:
    4.97 +
    4.98 +    a) You must cause the modified files to carry prominent notices
    4.99 +    stating that you changed the files and the date of any change.
   4.100 +
   4.101 +    b) You must cause any work that you distribute or publish, that in
   4.102 +    whole or in part contains or is derived from the Program or any
   4.103 +    part thereof, to be licensed as a whole at no charge to all third
   4.104 +    parties under the terms of this License.
   4.105 +
   4.106 +    c) If the modified program normally reads commands interactively
   4.107 +    when run, you must cause it, when started running for such
   4.108 +    interactive use in the most ordinary way, to print or display an
   4.109 +    announcement including an appropriate copyright notice and a
   4.110 +    notice that there is no warranty (or else, saying that you provide
   4.111 +    a warranty) and that users may redistribute the program under
   4.112 +    these conditions, and telling the user how to view a copy of this
   4.113 +    License.  (Exception: if the Program itself is interactive but
   4.114 +    does not normally print such an announcement, your work based on
   4.115 +    the Program is not required to print an announcement.)
   4.116 +
   4.117 +These requirements apply to the modified work as a whole.  If
   4.118 +identifiable sections of that work are not derived from the Program,
   4.119 +and can be reasonably considered independent and separate works in
   4.120 +themselves, then this License, and its terms, do not apply to those
   4.121 +sections when you distribute them as separate works.  But when you
   4.122 +distribute the same sections as part of a whole which is a work based
   4.123 +on the Program, the distribution of the whole must be on the terms of
   4.124 +this License, whose permissions for other licensees extend to the
   4.125 +entire whole, and thus to each and every part regardless of who wrote it.
   4.126 +
   4.127 +Thus, it is not the intent of this section to claim rights or contest
   4.128 +your rights to work written entirely by you; rather, the intent is to
   4.129 +exercise the right to control the distribution of derivative or
   4.130 +collective works based on the Program.
   4.131 +
   4.132 +In addition, mere aggregation of another work not based on the Program
   4.133 +with the Program (or with a work based on the Program) on a volume of
   4.134 +a storage or distribution medium does not bring the other work under
   4.135 +the scope of this License.
   4.136 +
   4.137 +  3. You may copy and distribute the Program (or a work based on it,
   4.138 +under Section 2) in object code or executable form under the terms of
   4.139 +Sections 1 and 2 above provided that you also do one of the following:
   4.140 +
   4.141 +    a) Accompany it with the complete corresponding machine-readable
   4.142 +    source code, which must be distributed under the terms of Sections
   4.143 +    1 and 2 above on a medium customarily used for software interchange; or,
   4.144 +
   4.145 +    b) Accompany it with a written offer, valid for at least three
   4.146 +    years, to give any third party, for a charge no more than your
   4.147 +    cost of physically performing source distribution, a complete
   4.148 +    machine-readable copy of the corresponding source code, to be
   4.149 +    distributed under the terms of Sections 1 and 2 above on a medium
   4.150 +    customarily used for software interchange; or,
   4.151 +
   4.152 +    c) Accompany it with the information you received as to the offer
   4.153 +    to distribute corresponding source code.  (This alternative is
   4.154 +    allowed only for noncommercial distribution and only if you
   4.155 +    received the program in object code or executable form with such
   4.156 +    an offer, in accord with Subsection b above.)
   4.157 +
   4.158 +The source code for a work means the preferred form of the work for
   4.159 +making modifications to it.  For an executable work, complete source
   4.160 +code means all the source code for all modules it contains, plus any
   4.161 +associated interface definition files, plus the scripts used to
   4.162 +control compilation and installation of the executable.  However, as a
   4.163 +special exception, the source code distributed need not include
   4.164 +anything that is normally distributed (in either source or binary
   4.165 +form) with the major components (compiler, kernel, and so on) of the
   4.166 +operating system on which the executable runs, unless that component
   4.167 +itself accompanies the executable.
   4.168 +
   4.169 +If distribution of executable or object code is made by offering
   4.170 +access to copy from a designated place, then offering equivalent
   4.171 +access to copy the source code from the same place counts as
   4.172 +distribution of the source code, even though third parties are not
   4.173 +compelled to copy the source along with the object code.
   4.174 +
   4.175 +  4. You may not copy, modify, sublicense, or distribute the Program
   4.176 +except as expressly provided under this License.  Any attempt
   4.177 +otherwise to copy, modify, sublicense or distribute the Program is
   4.178 +void, and will automatically terminate your rights under this License.
   4.179 +However, parties who have received copies, or rights, from you under
   4.180 +this License will not have their licenses terminated so long as such
   4.181 +parties remain in full compliance.
   4.182 +
   4.183 +  5. You are not required to accept this License, since you have not
   4.184 +signed it.  However, nothing else grants you permission to modify or
   4.185 +distribute the Program or its derivative works.  These actions are
   4.186 +prohibited by law if you do not accept this License.  Therefore, by
   4.187 +modifying or distributing the Program (or any work based on the
   4.188 +Program), you indicate your acceptance of this License to do so, and
   4.189 +all its terms and conditions for copying, distributing or modifying
   4.190 +the Program or works based on it.
   4.191 +
   4.192 +  6. Each time you redistribute the Program (or any work based on the
   4.193 +Program), the recipient automatically receives a license from the
   4.194 +original licensor to copy, distribute or modify the Program subject to
   4.195 +these terms and conditions.  You may not impose any further
   4.196 +restrictions on the recipients' exercise of the rights granted herein.
   4.197 +You are not responsible for enforcing compliance by third parties to
   4.198 +this License.
   4.199 +
   4.200 +  7. If, as a consequence of a court judgment or allegation of patent
   4.201 +infringement or for any other reason (not limited to patent issues),
   4.202 +conditions are imposed on you (whether by court order, agreement or
   4.203 +otherwise) that contradict the conditions of this License, they do not
   4.204 +excuse you from the conditions of this License.  If you cannot
   4.205 +distribute so as to satisfy simultaneously your obligations under this
   4.206 +License and any other pertinent obligations, then as a consequence you
   4.207 +may not distribute the Program at all.  For example, if a patent
   4.208 +license would not permit royalty-free redistribution of the Program by
   4.209 +all those who receive copies directly or indirectly through you, then
   4.210 +the only way you could satisfy both it and this License would be to
   4.211 +refrain entirely from distribution of the Program.
   4.212 +
   4.213 +If any portion of this section is held invalid or unenforceable under
   4.214 +any particular circumstance, the balance of the section is intended to
   4.215 +apply and the section as a whole is intended to apply in other
   4.216 +circumstances.
   4.217 +
   4.218 +It is not the purpose of this section to induce you to infringe any
   4.219 +patents or other property right claims or to contest validity of any
   4.220 +such claims; this section has the sole purpose of protecting the
   4.221 +integrity of the free software distribution system, which is
   4.222 +implemented by public license practices.  Many people have made
   4.223 +generous contributions to the wide range of software distributed
   4.224 +through that system in reliance on consistent application of that
   4.225 +system; it is up to the author/donor to decide if he or she is willing
   4.226 +to distribute software through any other system and a licensee cannot
   4.227 +impose that choice.
   4.228 +
   4.229 +This section is intended to make thoroughly clear what is believed to
   4.230 +be a consequence of the rest of this License.
   4.231 +
   4.232 +  8. If the distribution and/or use of the Program is restricted in
   4.233 +certain countries either by patents or by copyrighted interfaces, the
   4.234 +original copyright holder who places the Program under this License
   4.235 +may add an explicit geographical distribution limitation excluding
   4.236 +those countries, so that distribution is permitted only in or among
   4.237 +countries not thus excluded.  In such case, this License incorporates
   4.238 +the limitation as if written in the body of this License.
   4.239 +
   4.240 +  9. The Free Software Foundation may publish revised and/or new versions
   4.241 +of the General Public License from time to time.  Such new versions will
   4.242 +be similar in spirit to the present version, but may differ in detail to
   4.243 +address new problems or concerns.
   4.244 +
   4.245 +Each version is given a distinguishing version number.  If the Program
   4.246 +specifies a version number of this License which applies to it and "any
   4.247 +later version", you have the option of following the terms and conditions
   4.248 +either of that version or of any later version published by the Free
   4.249 +Software Foundation.  If the Program does not specify a version number of
   4.250 +this License, you may choose any version ever published by the Free Software
   4.251 +Foundation.
   4.252 +
   4.253 +  10. If you wish to incorporate parts of the Program into other free
   4.254 +programs whose distribution conditions are different, write to the author
   4.255 +to ask for permission.  For software which is copyrighted by the Free
   4.256 +Software Foundation, write to the Free Software Foundation; we sometimes
   4.257 +make exceptions for this.  Our decision will be guided by the two goals
   4.258 +of preserving the free status of all derivatives of our free software and
   4.259 +of promoting the sharing and reuse of software generally.
   4.260 +
   4.261 +			    NO WARRANTY
   4.262 +
   4.263 +  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
   4.264 +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
   4.265 +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
   4.266 +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
   4.267 +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
   4.268 +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
   4.269 +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
   4.270 +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
   4.271 +REPAIR OR CORRECTION.
   4.272 +
   4.273 +  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
   4.274 +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
   4.275 +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
   4.276 +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
   4.277 +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
   4.278 +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
   4.279 +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
   4.280 +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
   4.281 +POSSIBILITY OF SUCH DAMAGES.
   4.282 +
   4.283 +		     END OF TERMS AND CONDITIONS
   4.284 +
   4.285 +	    How to Apply These Terms to Your New Programs
   4.286 +
   4.287 +  If you develop a new program, and you want it to be of the greatest
   4.288 +possible use to the public, the best way to achieve this is to make it
   4.289 +free software which everyone can redistribute and change under these terms.
   4.290 +
   4.291 +  To do so, attach the following notices to the program.  It is safest
   4.292 +to attach them to the start of each source file to most effectively
   4.293 +convey the exclusion of warranty; and each file should have at least
   4.294 +the "copyright" line and a pointer to where the full notice is found.
   4.295 +
   4.296 +    <one line to give the program's name and a brief idea of what it does.>
   4.297 +    Copyright (C) <year>  <name of author>
   4.298 +
   4.299 +    This program is free software; you can redistribute it and/or modify
   4.300 +    it under the terms of the GNU General Public License as published by
   4.301 +    the Free Software Foundation; either version 2 of the License, or
   4.302 +    (at your option) any later version.
   4.303 +
   4.304 +    This program is distributed in the hope that it will be useful,
   4.305 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
   4.306 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   4.307 +    GNU General Public License for more details.
   4.308 +
   4.309 +    You should have received a copy of the GNU General Public License
   4.310 +    along with this program; if not, write to the Free Software
   4.311 +    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   4.312 +
   4.313 +
   4.314 +Also add information on how to contact you by electronic and paper mail.
   4.315 +
   4.316 +If the program is interactive, make it output a short notice like this
   4.317 +when it starts in an interactive mode:
   4.318 +
   4.319 +    Gnomovision version 69, Copyright (C) year name of author
   4.320 +    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
   4.321 +    This is free software, and you are welcome to redistribute it
   4.322 +    under certain conditions; type `show c' for details.
   4.323 +
   4.324 +The hypothetical commands `show w' and `show c' should show the appropriate
   4.325 +parts of the General Public License.  Of course, the commands you use may
   4.326 +be called something other than `show w' and `show c'; they could even be
   4.327 +mouse-clicks or menu items--whatever suits your program.
   4.328 +
   4.329 +You should also get your employer (if you work as a programmer) or your
   4.330 +school, if any, to sign a "copyright disclaimer" for the program, if
   4.331 +necessary.  Here is a sample; alter the names:
   4.332 +
   4.333 +  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
   4.334 +  `Gnomovision' (which makes passes at compilers) written by James Hacker.
   4.335 +
   4.336 +  <signature of Ty Coon>, 1 April 1989
   4.337 +  Ty Coon, President of Vice
   4.338 +
   4.339 +This General Public License does not permit incorporating your program into
   4.340 +proprietary programs.  If your program is a subroutine library, you may
   4.341 +consider it more useful to permit linking proprietary applications with the
   4.342 +library.  If this is what you want to do, use the GNU Library General
   4.343 +Public License instead of this License.
   4.344 +
   4.345 +
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/README.html	Fri Mar 13 00:39:12 2009 -0700
     5.3 @@ -0,0 +1,546 @@
     5.4 +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
     5.5 +<html lang="en">
     5.6 +<head>
     5.7 +<title>LOCKJAW - About</title>
     5.8 +<link rel="stylesheet" type="text/css" href="docs/ljhtml.css">
     5.9 +<link rel="icon" href="docs/appicon.ico" type="image/x-icon">
    5.10 +<link rel="shortcut icon" href="docs/appicon.ico" type="image/x-icon"> 
    5.11 +<meta http-equiv="Content-type" content="text/html;charset=iso-8859-1">
    5.12 +</head><body>
    5.13 +<h1><img src="docs/ljlogo192.png" alt="LOCKJAW Tetromino Game:"> About</h1>
    5.14 +<ul id="linkbar">
    5.15 +<li><a href="http://www.pineight.com/lj/">Home</a></li>
    5.16 +<li>About</li>
    5.17 +<li><a href="http://www.pineight.com/lj/dl">Download</a></li>
    5.18 +</ul>
    5.19 +<div id="content">
    5.20 +<p>
    5.21 +<a href="#installing">Installing</a> | <a href="#skin">Skinning</a> | <a href="#controls">Controls</a> | <a href="#scoring">Scoring</a> | <a href="#scenario">Scenario</a> | <a href="#options">Options</a> | <a href="#discussion">Discussion</a> | <a href="#legal">Legal</a>
    5.22 +</p><p>
    5.23 +LOCKJAW is a <a href="http://www.gnu.org/philosophy/free-sw.html">free software</a> implementation of the so-called Soviet Mind Game, a highly popular computer puzzle game that involves guiding tetrominoes into neat stacks in a well.  This game was designed in the mid-1980s by Russian game designer Alexey Pajitnov and was first implemented in a software product called <a href="http://en.wikipedia.org/wiki/Tetris">TETRIS</a>&reg;.<a href="#trademarks">*</a> Other products implementing the Soviet Mind Game include
    5.24 +<a href="http://quadra.sourceforge.net/">Quadra</a>,
    5.25 +<a href="http://abrick.sourceforge.net/">Abandoned Bricks</a>,
    5.26 +<a href="http://ksirtet.sourceforge.net/">KSirtet</a>,
    5.27 +<a href="http://fbg.sourceforge.net/">F.B.G.</a>,
    5.28 +<a href="http://www.neave.com/games/nblox/">N-Blox</a>, and
    5.29 +<a href="http://www.tetrisconcept.com/forum/viewtopic.php?t=54">Heboris</a>. While originally developed to parody the behavior of a few notorious implementations of the Soviet Mind Game, LOCKJAW is now a platform for research into the properties of the game, into the effects of rule variations, and into the capabilities of the human mind to react.
    5.30 +</p><p>
    5.31 +<a href="http://en.wikipedia.org/wiki/Tetromino">Tetrominoes</a> are geometric shapes made of four connected square blocks. There are seven distinct tetrominoes, shaped roughly like letters of the Latin alphabet:
    5.32 +</p><p>
    5.33 +<img src="docs/ijlo-stz.png" alt="(Illustration of the seven tetrominoes)"><br>
    5.34 +Tetrominoes. Top row: I, J, L, O. Bottom row: S, T, Z.
    5.35 +</p><p style="size: 80%">
    5.36 +Some products spell the term as "tetramino", "tetrimino", or "tetrad".
    5.37 +</p>
    5.38 +<div class="screenshot">
    5.39 +<img src="docs/ljsnap033.png" alt="(Game screenshot)" width="400" height="300" usemap="#ljsnap018_map">
    5.40 +<p>
    5.41 +LOCKJAW gameplay screen. Hover over objects to see their names.
    5.42 +</p>
    5.43 +<map name="ljsnap018_map">
    5.44 +<area shape="rect" coords="12,12,48,36" alt="Hold piece" title="Hold piece" style="border: 1px solid white">
    5.45 +<area shape="rect" coords="48,0,192,36" alt="Status" title="Status">
    5.46 +<area shape="rect" coords="96,48,132,108" alt="Falling piece" title="Falling piece">
    5.47 +<area shape="rect" coords="96,168,132,216" alt="Shadow" title="Shadow">
    5.48 +<area shape="rect" coords="12,36,132,276" alt="Well" title="Well">
    5.49 +<area shape="rect" coords="36,276,108,300" alt="Game mode" title="Game mode">
    5.50 +<area shape="rect" coords="144,36,192,252" alt="Next pieces" title="Next pieces">
    5.51 +<area shape="rect" coords="216,60,360,240" alt="Gus" title="Gus">
    5.52 +</map>
    5.53 +</div>
    5.54 +
    5.55 +<p>
    5.56 +The well is 10 blocks wide by 20 rows high. (There are four out-of-bounds rows above the top of the visible portion for a total of 24 rows.) The player can rotate and shift the tetrominoes as they fall in order to pack them tightly into the well.  If a tetromino lands on the floor or other blocks and remains motionless for half a second, it locks into place, and the next tetromino begins to fall. The next few tetrominoes to fall are displayed in a queue to the right of the playfield.  At any time, the player can swap the falling tetromino with the one in the hold box above the playfield, but a tetromino that has been swapped out cannot be immediately swapped back in. The gray blocks below the falling tetromino are the shadow, which shows where the tetromino will land.
    5.57 +</p><p>
    5.58 +A "line", or a complete row of blocks across the well, will disappear, and everything above it moves down by one row.  But if the well fills so high that a tetromino is placed entirely out of bounds or in a position such that the next tetromino does not have room to enter, the player "tops out" and the game is over.  So the goal is to stay alive by making more lines.
    5.59 +</p>
    5.60 +
    5.61 +<h2><a name="installing">Installing</a></h2>
    5.62 +<p>
    5.63 +The executable included with the official distribution is designed for Microsoft Windows systems. Unzip it into a folder on your hard disk or USB memory card. It ordinarily writes preferences and logs to the current directory, which is most often the folder containing <code>lj.exe</code>. (If you do not want to allow users to write to the program's folder, place an empty file called <code>installed.ini</code> in the same folder as <code>lj.exe</code>. This will make Game Keys and Options write settings to the user's application data folder, commonly <code>C:\Documents and Settings\Gus\Application Data\Pin Eight\Lockjaw</code>.)
    5.64 +</p><p>
    5.65 +The files <code>lj.exe</code>, <code>lj.dat</code>, <code>ljblocks.bmp</code>, and <code>alleg42.dll</code> are required to play.  (If you get an error about <code>alleg42.dll</code>, see the instructions on the LOCKJAW download page.) To start the game, run <code>lj.exe</code> in Windows Explorer.
    5.66 +</p><p>
    5.67 +The program should work on any PC running Microsoft Windows 98 or newer operating system with an 800x600 pixel display at 16-bit or greater color depth and DirectX 7 software installed.
    5.68 +Its source code is portable to any platform that supports the <a href="http://alleg.sourceforge.net/">Allegro library</a>, but it is tested only on Microsoft Windows because the author has access only to machines that run Windows. 
    5.69 +Occasionally, people manage to build and run it on other operating systems such as GNU/Linux or Mac OS X; if you are willing to maintain a port, <a href="http://www.pineight.com/contact/">get in touch with the author</a>.
    5.70 +</p><p>
    5.71 +For the handheld version, you only need the file <code>lj.gba</code> or <code>lj.nds</code>. Due to limitations in the DS homebrew operating system, the DS version always acts installed, writing user files to the folder <code>/data/lockjaw</code> on the memory card.
    5.72 +</p><p>
    5.73 +
    5.74 +<h3>Installing from source</h3>
    5.75 +<p>
    5.76 +To recompile the program, such as if you are testing a patch or porting it to another system:
    5.77 +</p><ol>
    5.78 +<li>Install GNU Coreutils and GNU Make. These are packaged as MSYS from <a href="http://www.mingw.org/MinGWiki/index.php/GettingStarted">MinGW.org</a></li>
    5.79 +<li>Install a GCC toolchain. This is packaged as MinGW from <a href="http://www.mingw.org/MinGWiki/index.php/GettingStarted">MinGW.org</a></li>
    5.80 +<li>Install Allegro 4.2.1 from <a href="http://alleg.sourceforge.net/">SourceForge.net</a></li>
    5.81 +<li>Install JPGalleg 2.5 from <a href="http://www.ecplusplus.com/index.php?page=projects&amp;pid=1">Enhanced Creations++</a></li>
    5.82 +<li>Install DUMB 0.9.3 from <a href="http://dumb.sourceforge.net/">SourceForge.net</a><br >
    5.83 +(Editor's note: DUMB has a dumb license, but section 6 allows relicensing under GNU GPL. Update: It appears <a href="http://packages.debian.org/changelogs/pool/main/libd/libdumb/libdumb_0.9.3-5/libaldmb1.copyright">Debian got the author to relicense it under a straight zlib style license</a>.)</li>
    5.84 +<li>Open a command prompt and <kbd>cd</kbd> to the folder containing the file <code>makefile</code></li>
    5.85 +<li>Type <code>make</code></li>
    5.86 +</ol><p>
    5.87 +Lardarse has written a detailed guide of how to recompile the program on Microsoft Windows OS. This guide is included with the source code inside the "docs" folder.
    5.88 +</p><p>
    5.89 +To recompile the program for Game Boy Advance or Nintendo DS:
    5.90 +</p><ol>
    5.91 +<li>Install devkitARM, libgba, libnds, and MSYS using devkitPro Updater at <a href="http://www.devkitpro.org/">devkitPro.org</a>.</li>
    5.92 +<li>Install a GCC toolchain, such as MinGW from <a href="http://www.mingw.org/MinGWiki/index.php/GettingStarted">MinGW.org</a></li>
    5.93 +<li>Open a command prompt and <kbd>cd</kbd> to the folder containing the file <code>gbamakefile</code> or <code>dsmakefile</code></li>
    5.94 +<li>Type <code>make -f gbamakefile</code> or <code>make -f dsmakefile</code></li>
    5.95 +</ol><p>
    5.96 +To recompile all three ports, type <code>make all</code>
    5.97 +</p><p>
    5.98 +TIP: On GNU/Linux, Windows, and several other platforms, GNU Make can compile multiple files in parallel. This can speed up a large rebuild by allowing GCC to compile one file while reading another from disk. Add <code>-j2</code> to the end of a Make command line to compile two files at once, or if you have a dual-core CPU, add <code>-j3</code> to have GCC run one core, the other core, and the disk at the same time.
    5.99 +</p>
   5.100 +
   5.101 +<h2><a name="skin">Skinning</a></h2>
   5.102 +<p>
   5.103 +You can customize the appearance of LOCKJAW Tetromino Game for PC by using Notepad or any other text editor to create a skin description that lists the images that shall be used. This skin description should be placed in a text file whose name ends in <code>.skin</code>. (You can create and edit <code>.ini</code> and <code>.skin</code> files using Notepad, Notepad++, vi, Emacs, or any other plain text editor.) LOCKJAW recognizes the following commands in a skin description:
   5.104 +</p><dl>
   5.105 +<dt><code>ljblocksSRS=<i>image</i></code></dt>
   5.106 +<dd>A grid of block images, used in bounding-box rotation systems (SRS and TOD M4). The image's size size should be 8 columns by 4 rows (usually 192x96 pixels) or 8 columns by 6 rows (usually 192x144 pixels). Rows 1 and 2 are for blocks in the well, and rows 3 and 4 are for the falling piece. If rows 5 and 6 are present, the shadow will use those; otherwise, it will use rows 3 and 4. If <tt>blkW</tt> or <tt>blkH</tt> is present, the program uses that size instead of 24x24.</dd>
   5.107 +<dt><code>ljblocksSega=<i>image</i></code></dt>
   5.108 +<dd>Same as <code>ljblocksSRS</code>, but used in other rotation systems.</dd>
   5.109 +<dt><code>ljconnSRS=<i>image</i></code></dt>
   5.110 +<dd>An image 8 rows and 8 columns in size (usually 192x192 pixels), containing an O tetromino of each color. The skin loader cuts this up to form tetrominoes with the blocks drawn connected, used for blocks in the well and the falling piece (not the shadow or empty areas of the well) in bounding-box rotation systems. If this file is not present, <code>ljblocksSRS</code> will be used instead.</dd>
   5.111 +<dt><code>ljconnSega=<i>image</i></code>
   5.112 +<dd>Same as <code>ljconnSRS</code>, but used in other rotation systems. If this file is not present, <code>ljblocksSega</code> will be used instead.</dd>
   5.113 +<dt><code>color=<i>#RRGGBB</i></code></dt>
   5.114 +<dd>A 3- or 6-digit hexadecimal color (e.g. <code>#ABC</code> or <code>#D0FFE3</code>) for text in the menus and during the game. If not present, the game will use black.</dd>
   5.115 +<dt><code>bgcolor=<i>#RRGGBB</i></code></dt>
   5.116 +<dd>A color for the menus' background. If not present, the game will use white.</dd>
   5.117 +<dt><code>hilitecolor=<i>#RRGGBB</i></code></dt>
   5.118 +<dd>A color for the background of highlighted text. If not present, the game will use pale yellow (#FFC).</dd>
   5.119 +<dt><code>pfcolor=<i>#RRGGBB</i></code></dt>
   5.120 +<dd>A color for text in front of the playfield. If not present, the game will use white.</dd>
   5.121 +<dt><code>pfbgcolor=<i>#RRGGBB</i></code></dt>
   5.122 +<dd>An "average" color for the playfield, to be displayed behind the pause screen and during the game over animation. If not present, the game will use black.</dd>
   5.123 +<dt><code>ljbg=<i>#RRGGBB</i></code></dt>
   5.124 +<dd>An 800x600 pixel image to be displayed behind the game. If this file is not 800x600 pixels, the image will be resized (sloppily) after being loaded. If this file is not present, the game will use a plain backdrop of the same color as <code>bgcolor</code>.</dd>
   5.125 +<dt><code>blkW=<i>length</i></code></dt>
   5.126 +<dd>The width in pixels of each block in the <tt>ljblocks</tt> and <tt>ljconn</tt>, if it is not 24 pixels.</dd>
   5.127 +<dt><code>blkH=<i>length</i></code></dt>
   5.128 +<dd>The height in pixels of each block in the <tt>ljblocks</tt> and <tt>ljconn</tt>, if it is not 24 pixels. This is allowed to differ from <code>blkW</code></dd>
   5.129 +<dt><code>transparentPF=<i>boolean</i></code></dt>
   5.130 +<dd>If this is set to the value 0, tile 0 of <code>ljblocksSRS</code> covers up the background within the playfield. If this is set to nonzero, the playfield background shows through.</dd>
   5.131 +<dt><code>bgm=<i>music file</i></code></dt>
   5.132 +<dd>A music file in Vorbis format (<code>.ogg</code>) or tracker format (<code>.mod</code>, <code>.s3m</code>, <code>.xm</code>, or <code>.it</code>) to be played during the game. You can create Vorbis format files by extracting audio from your CDs to <code>.wav</code> format using <a href="http://sourceforge.net/projects/cdexos/">CDex software</a> and converting them with <a href="http://www.rarewares.org/ogg.html">OggDropXPd software</a>. You can download tracker format files from <a href="http://www.modarchive.com/">The Mod Archive</a> or create them yourself using the <a href="http://lpchip.com/modplug/viewtopic.php?t=18">OpenMPT music editor</a>, the continuation of MODPlug Tracker. If this file is not present, the game will not play music.</dd>
   5.133 +<dt><code>bgmLoopPoint=<i>sampleNumber</i></code></dt>
   5.134 +<dd>For music in Vorbis format, sets the sample at which playback restarts once the music file ends. For example, if your .ogg file is 44100 Hz, 441000 represents rewinding to 10 seconds after the start. Has no effect with tracker format music, which specifies its own loop point, or Rhythm speed curve, whose music isn't supposed to loop anyway.</dd>
   5.135 +<dt><code>bgmRhythm=<i>music file</i></code></dt>
   5.136 +<dd>Like <code>bgm</code>, but used in the Rhythm speed curve. Music tempo should have 64 beats of 60 BPM, 64 beats of 70 BPM, 64 beats of 80 BPM, etc.</dd>
   5.137 +<dt><code>bgmReadyGo=<i>Boolean</i></code></dt>
   5.138 +<dd>If this is set to the value 0, the background music starts after the "Ready Go" sequence.  within the playfield. this is set to nonzero, the background music plays during the "Ready Go" sequence.</dd>
   5.139 +<dt><code>bgmVolume=<i>volume</i></code></dt>
   5.140 +<dd>Sets the volume of the music to balance it against the sound effects, where 256 is full volume. If not specified, uses 128; 1 <abbr title="decibel">dB</abbr> louder than this would be 144.</dd>
   5.141 +<dt><code>shiftScale=<i>Boolean</i></code></dt>
   5.142 +<dd>When set to 1, moving the falling piece sideways produces sound at a different pitch based on how far the piece is from the left side. (PC version only)</dd>
   5.143 +<dt>baseX=<i>distance</i></dt>
   5.144 +<dd>This moves all gameplay graphics to the left (0), middle (200), or right (400) of the screen.</dd>
   5.145 +<dt>nextPos=<i>style number</i></dt>
   5.146 +<dd>Controls where the next tetrominoes are displayed. Up to eight can fit to the right of the well, or up to 3 can fit above the well. A value of 0 places the pieces on the right; 2 places them on the top.</dd>
   5.147 +<dt><code>wndW=<i>length</i></code></dt>
   5.148 +<dd>Sets the width of the game window in pixels.</dd>
   5.149 +<dt><code>wndH=<i>length</i></code></dt>
   5.150 +<dd>Sets the height of the game window in pixels.</dd>
   5.151 +</dl><p>
   5.152 +You can create more than one skin description file and then switch among them by using the "Skin..." command at the main menu, which produces the following command in <code>lj.ini</code>:
   5.153 +</p><dl>
   5.154 +<dt><code>Skin=<i>skin description file</i></code></dt>
   5.155 +<dd>A skin description file. If the skin is not present, the game uses preset file names (<code>ljblocks.bmp</code>, <code>ljblocks-sega.bmp</code>, <code>ljconn.bmp</code>, <code>ljconn-sega.bmp</code>, <code>ljbg.jpg</code>, and <code>bgm.s3m</code>).</dd>
   5.156 +</dl><p>
   5.157 +Images can be in Windows bitmap (<code>.bmp</code>), PC-Paintbrush (<code>.pcx</code>), Truevision TGA (<code>.tga</code>), or JFIF/JPEG (<code>.jpg</code>) format. All color depths should be supported. Paths are interpreted relative to the folder containing the <code>.skin</code> file, that is, specifying <code>ljblocksSega=Gradient Blocks.bmp</code> will try to pull <code>Gradient Blocks.bmp</code> from the skin's folder. If you want to create a folder structure inside your skin package, it is best to use forward slashes (<code>'/'</code>) instead of backslashes (<code>'\'</code>) so that users of Mac OS X and GNU/Linux will be able to use your skin.
   5.158 +</p><p>
   5.159 +Example image and sound files are located in <code>lj-contrib.zip</code>, available from the <a href="http://www.pineight.com/lj/dl#contrib">Skins section of the download page</a>. You can also customize the sound effects by using <a href="http://www.allegro.cc/depot/Grabber/">Allegro Grabber software</a> to edit <code>lj.dat</code>.
   5.160 +</p>
   5.161 +
   5.162 +<h2><a name="controls">Controls</a></h2>
   5.163 +<h3>PC</h3>
   5.164 +<p>
   5.165 +The controls in LOCKJAW Tetromino Game for PC are initially set as follows:
   5.166 +</p>
   5.167 +<dl>
   5.168 +<dt>&larr; Shift left</dt><dd>Left arrow key</dd>
   5.169 +<dt>&rarr; Shift right</dt><dd>Right arrow key</dd>
   5.170 +<dt>&darr; Soft drop</dt><dd>Down arrow key</dd>
   5.171 +<dt><u>&darr;</u> Hard drop</dt><dd>Up arrow key</dd>
   5.172 +<dt>&#8624; Rotate left</dt><dd>Z, C</dd>
   5.173 +<dt>&#8625; Rotate right</dt><dd>X</dd>
   5.174 +<dt>&#8630; Rotate twice</dt><dd>W</dd>
   5.175 +<dt>&#8598; Hold piece</dt><dd>S, D</dd>
   5.176 +<dt>&#8676; Shift far left</dt><dd>Q</dd>
   5.177 +<dt>&#8677; Shift far right</dt><dd>E</dd>
   5.178 +<dt><u>&darr;</u> Firm drop</dt><dd>Enter</dd>
   5.179 +</dl><p>
   5.180 +Controls are configurable to the keyboard or any compatible joystick. (The key labeled "Item" is not used in single-player, and there is no multiplayer yet.)  From the main menu, choose "Game Keys..." and then press the keys in order as prompted. If you don't want to bind a key or button to a given function, press a key that you won't use. The key bindings are saved to the file <code>lj-keys.043</code>; if they become unusable, you can delete this file to reset them to the initial settings.
   5.181 +</p><p>
   5.182 +Some controls during game play are hard-coded to keyboard keys:
   5.183 +</p><ul>
   5.184 +<li>Esc pauses and resumes the game. Holding Esc for one second stops the game and goes to a result screen.</li>
   5.185 +<li>[ (left bracket) starts and stops demo recording, and ] (right bracket) starts and stops demo playback. An icon for stop, record, or play appears in the upper left corner of the window. The demo is saved to a file called <code>demo.ljm</code>. <strong>Caution:</strong> Demos recorded on one version of LOCKJAW may not play correctly on another version. Recording another demo will overwrite the last demo, so make sure to rename the demo when you record a good one.</li>
   5.186 +</ul><p>
   5.187 +In the menus, Esc acts as Rotate Left, and Enter acts as Rotate Right. In all screens, Print Screen (F13) saves a copy of the display to the file <code>ljsnap.bmp</code>.
   5.188 +</p>
   5.189 +
   5.190 +<h3>GBA and DS</h3>
   5.191 +<p>
   5.192 +Controls in the Game Boy Advance and Nintendo DS versions are hardcoded as follows:
   5.193 +</p><dl>
   5.194 +<dt>&larr; Shift left</dt><dd>Left on Control Pad</dd>
   5.195 +<dt>&rarr; Shift right</dt><dd>Right on Control Pad</dd>
   5.196 +<dt>&darr; Soft drop</dt><dd>Down on Control Pad</dd>
   5.197 +<dt><u>&darr;</u> Hard drop</dt><dd>Up on Control Pad</dd>
   5.198 +<dt>&#8624; Rotate left</dt><dd>B Button</dd>
   5.199 +<dt>&#8625; Rotate right</dt><dd>A Button</dd>
   5.200 +<dt>&#8598; Hold piece</dt><dd>L Button</dd>
   5.201 +</dl><p>
   5.202 +In the options menu, the B and A Buttons change screens, and the Start Button starts the game. Pressing A on the last screen of options also starts the game. Start pauses and resumes the game.
   5.203 +</p>
   5.204 +
   5.205 +<h3>Movement features</h3>
   5.206 +<p>
   5.207 +Unlike some other <abbr title="Soviet Mind Game">S.M.G.</abbr> implementations, LOCKJAW features Initial Actions. Holding a rotate button while a tetromino enters the playfield will cause the action to be performed the moment the tetromino enters. This is important for fast play. In addition, the hold key works at any time; if a piece is not falling yet, it will swap the hold piece with the next piece.
   5.208 +</p><p>
   5.209 +LOCKJAW also implements the so-called <a href="http://www.tetrisconcept.com/wiki/index.php?title=SRS">Super Rotation System (SRS)</a>, which allows a tetromino to rotate around obstacles for more mobility across the pile. Some players abuse SRS by <a href="http://infinitespin.ytmnd.com/">rotating a piece in place repeatedly</a>, but this will result in poorer scores in timed gimmicks. To replace this behavior with the "Sega style" behavior used in Arika's <i>Tetris The Grand Master 3: Terror-Instinct</i>, change the <a href="#options">Options described below</a>.
   5.210 +</p>
   5.211 +
   5.212 +<h2><a name="scoring">Scoring</a></h2>
   5.213 +<p>
   5.214 +LOCKJAW Tetromino Game allows the player to choose among several methods of calculating the score for a line clear.
   5.215 +The terms "single", "double", "triple", and "home run" refer to clearing 1, 2, 3, or 4 lines with one tetromino.
   5.216 +(A "home run" is called a "tetris" in some other games.)
   5.217 +"T-spin" means rotating a T piece as it lands to fit into a tight space.
   5.218 +</p><p>
   5.219 +In LJ and TDS, making a bonus line clear when your last line clear was also bonus ("back-to-back homer" or "back-to-back T-spin") will produce extra points. Making a T-spin that does not clear a line has no effect on bonus state.
   5.220 +</p>
   5.221 +
   5.222 +<h3>LJ</h3>
   5.223 +<p>
   5.224 +As you clear lines, you also produce garbage that depends on the number of lines that you clear with a single tetromino.  In single player mode, you earn 100 points per line cleared and 200 points per line of garbage, and in multiplayer mode (which is not yet implemented), the garbage will push up the blocks in another player's well.
   5.225 +</p>
   5.226 +<table summary="first row: column headings">
   5.227 +<caption>LJ scoring method</caption>
   5.228 +<thead>
   5.229 +<tr><th> </th><th>Lines</th><th>Garbage</th><th>Score</th><th>Bonus</th></tr>
   5.230 +</thead><tbody>
   5.231 +<tr><td>1 line </td><td>1</td><td>0</td><td>100</td><td>No</td></tr>
   5.232 +<tr><td>2 lines</td><td>2</td><td>1</td><td>400</td><td>No</td></tr>
   5.233 +<tr><td>3 lines</td><td>3</td><td>2</td><td>700</td><td>No</td></tr>
   5.234 +<tr><td>4 lines</td><td>4</td><td>4</td><td>1200</td><td>Yes</td></tr>
   5.235 +<tr><td>1 line T-spin </td><td>1</td><td>2</td><td>500</td><td>Yes</td></tr>
   5.236 +<tr><td>2 lines T-spin</td><td>2</td><td>4</td><td>1000</td><td>Yes</td></tr>
   5.237 +<tr><td>3 lines T-spin</td><td>3</td><td>6</td><td>1500</td><td>Yes</td></tr>
   5.238 +<tr><td>Back-to-back bonus</td><td> </td><td>1</td><td>200</td><td>Yes</td></tr>
   5.239 +</tbody>
   5.240 +</table><p>
   5.241 +LJ has a variant called "<a href="http://en.wikipedia.org/wiki/Nerf_%28computer_gaming%29" title="nerf, v. To weaken or remove (an element of play) as a method of rebalancing a game.">nerfed</a> spin" where T-spins produce half the garbage and only 300 points per line.
   5.242 +</p>
   5.243 +
   5.244 +<h3>Fibonacci</h3>
   5.245 +<p>
   5.246 +This extension of the scoring curve seen in <i>The New Tetris</i> is based on the <a href="http://en.wikipedia.org/wiki/Fibonacci_number" title="a sequence of integers where each element is the sum of the two preceding elements">Fibonacci sequence</a>.
   5.247 +</p>
   5.248 +<table summary="first row: column headings">
   5.249 +<caption>TNT scoring method</caption>
   5.250 +<thead>
   5.251 +<tr><th>Lines</th><th>Score</th></tr>
   5.252 +</thead><tbody>
   5.253 +<tr><td>1</td><td>100</td></tr>
   5.254 +<tr><td>2</td><td>200</td></tr>
   5.255 +<tr><td>3</td><td>300</td></tr>
   5.256 +<tr><td>4</td><td>500</td></tr>
   5.257 +<tr><td>5</td><td>800</td></tr>
   5.258 +<tr><td>6</td><td>1300</td></tr>
   5.259 +<tr><td>7</td><td>2100</td></tr>
   5.260 +<tr><td>8</td><td>3400</td></tr>
   5.261 +<tr><td>9</td><td>5500</td></tr>
   5.262 +<tr><td>10</td><td>8900</td></tr>
   5.263 +<tr><td>11</td><td>14400</td></tr>
   5.264 +<tr><td>12</td><td>23300</td></tr>
   5.265 +<tr><td>13</td><td>37700</td></tr>
   5.266 +</tbody>
   5.267 +</table><p>
   5.268 +Further lines score 20,000 points per line.
   5.269 +</p>
   5.270 +
   5.271 +<h3>Hotline</h3>
   5.272 +<p>
   5.273 +Only lines cleared on specific rows are worth points. There is no back-to-back bonus nor T-spin bonus.
   5.274 +</p>
   5.275 +<table summary="first row: column headings">
   5.276 +<caption>Hotline scoring method</caption>
   5.277 +<thead>
   5.278 +<tr><th>Row</th><th>Score</th></tr>
   5.279 +</thead><tbody>
   5.280 +<tr><td>5</td><td>100</td></tr>
   5.281 +<tr><td>10</td><td>200</td></tr>
   5.282 +<tr><td>14</td><td>300</td></tr>
   5.283 +<tr><td>17</td><td>400</td></tr>
   5.284 +<tr><td>19</td><td>500</td></tr>
   5.285 +<tr><td>20</td><td>600</td></tr>
   5.286 +</tbody>
   5.287 +</table>
   5.288 +
   5.289 +<h3>TDS</h3>
   5.290 +<p>
   5.291 +Each line clear score is multiplied by a section number, computed by dividing the total number of lines cleared before this line by 10 and adding 1. Thus, lines cleared later in the game when gravity is faster are worth more than lines cleared earlier. In TDS, the section number stops increasing after 190 lines.
   5.292 +</p>
   5.293 +<table summary="first row: column headings">
   5.294 +<caption>TDS scoring method</caption>
   5.295 +<thead>
   5.296 +<tr><th> </th><th>Lines</th><th>Score</th><th>Bonus</th></tr>
   5.297 +</thead><tbody>
   5.298 +<tr><td>1 line </td><td>1</td><td>100 * section</td><td>No</td></tr>
   5.299 +<tr><td>2 lines</td><td>2</td><td>300 * section</td><td>No</td></tr>
   5.300 +<tr><td>3 lines</td><td>3</td><td>500 * section</td><td>No</td></tr>
   5.301 +<tr><td>4 lines</td><td>4</td><td>800 * section</td><td>Yes</td></tr>
   5.302 +<tr><td>0 lines T-spin (wall kick)   </td><td>0</td><td>100 * section</td><td> </td></tr>
   5.303 +<tr><td>0 lines T-spin (no wall kick)</td><td>0</td><td>400 * section</td><td> </td></tr>
   5.304 +<tr><td>1 line T-spin (wall kick)    </td><td>1</td><td>200 * section</td><td>Yes</td></tr>
   5.305 +<tr><td>1 line T-spin (no wall kick) </td><td>1</td><td>800 * section</td><td>Yes</td></tr>
   5.306 +<tr><td>2 lines T-spin               </td><td>2</td><td>1200 * section</td><td>Yes</td></tr>
   5.307 +<tr><td>3 lines T-spin               </td><td>3</td><td>1600 * section</td><td>Yes</td></tr>
   5.308 +<tr><td>Back-to-back bonus</td><td> </td><td>50% more</td><td>Yes</td></tr>
   5.309 +</tbody></table>
   5.310 +
   5.311 +<h3>NES</h3>
   5.312 +<p>
   5.313 +Each line clear is multiplied by the section number, as in TDS. The section number does not stop increasing. There is no back-to-back bonus nor T-spin bonus.
   5.314 +</p>
   5.315 +<table summary="first row: column headings">
   5.316 +<caption>NES scoring method</caption>
   5.317 +<thead>
   5.318 +<tr><th>Lines</th><th>Score</th></tr>
   5.319 +</thead><tbody>
   5.320 +<tr><td>1</td><td>40 * section</td></tr>
   5.321 +<tr><td>2</td><td>100 * section</td></tr>
   5.322 +<tr><td>3</td><td>300 * section</td></tr>
   5.323 +<tr><td>4</td><td>1200 * section</td></tr>
   5.324 +</tbody></table>
   5.325 +
   5.326 +<h3>Drop scoring</h3>
   5.327 +<p>
   5.328 +Some games award extra points every time the tetromino lands if the player used soft drop or hard drop. LOCKJAW can simulate these.
   5.329 +</p><dl>
   5.330 +<dt>None</dt><dd>Award no points.</dd>
   5.331 +<dt>Continuous</dt><dd>Award 1 point per row for hard drops. Award 1 point per row between when a soft drop starts when the piece lands. A soft drop that is started, then stopped, then started again, will award points only for the last soft drop.</dd>
   5.332 +<dt>Drop</dt><dd>Award 1 point per row for hard drops and for soft drops, whether or not they are interrupted.</dd>
   5.333 +<dt>Soft x1 Hard x2</dt><dd>Award 1 point per row for soft drops and 2 points per row for hard drops.</dd>
   5.334 +</dl>
   5.335 +
   5.336 +<h3>lj-scores.txt</h3>
   5.337 +<p>
   5.338 +On the PC and DS version, each game's results are written to standard output and <code>lj-scores.txt</code> once it ends. This way, the user can copy the results to an online forum, or the user can <code>lj | something else</code> or <code>tail -f lj-scores.txt | something else</code> in order to have an external program analyze each game as it finishes.
   5.339 +</p>
   5.340 +
   5.341 +<h2><a name="scenario">Scenarios</a></h2>
   5.342 +<p>
   5.343 +In the PC version, scenarios are predefined sets of rules for the game. Most scenarios leave several rules unspecified so that you can further customize them in Options. On the Play menu, you can choose from over ten preset scenarios, or you can design your own rules in Options and then activate them with Custom Game.
   5.344 +</p><p>
   5.345 +Future versions of LOCKJAW Tetromino Game will allow the user to edit scenarios and to use scenarios on handhelds.
   5.346 +</p>
   5.347 +
   5.348 +<h2><a name="options">Options</a></h2>
   5.349 +<p>
   5.350 +In Tetris, rules change you. But in LOCKJAW, <strong>you</strong> change the rules:
   5.351 +</p>
   5.352 +
   5.353 +<h3>Definitions</h3>
   5.354 +<dl>
   5.355 +<dt>Frame</dt>
   5.356 +<dd>All video games are turn-based. Real-time games make each turn take a fraction of a second, where each turn lasts only one frame of animation. LOCKJAW always uses 60 frames per second, regardless of the refresh rate of the attached monitor.</dd>
   5.357 +<dt>G</dt>
   5.358 +<dd>1G is a velocity of 1 cell per frame, or 60 cells per second. "20G" means that tetrominoes fall through the entire height of the well in one frame.</dd>
   5.359 +<dt>Hertz (Hz)</dt>
   5.360 +<dd>1 Hz is a rate of 1 event per second.</dd>
   5.361 +</dl>
   5.362 +
   5.363 +<h3><a name="gimmicks">Gimmicks</a></h3>
   5.364 +<dl>
   5.365 +<dt>Marathon</dt>
   5.366 +<dd>The game gets faster and faster as each tetromino enters the well. Play until you top out. Some people play for lines; others play for points.</dd>
   5.367 +<dt>40 lines</dt>
   5.368 +<dd>Play until you clear 40 lines, or until you top out, whatever comes first. The author's record is 1:00.70.</dd>
   5.369 +<dt>180 seconds</dt>
   5.370 +<dd>Play for three minutes, or until you top out, whatever comes first.</dd>
   5.371 +<dt>Vs. w/Items</dt>
   5.372 +<dd>This mode is a joke. After the first 7 pieces you get random starting orientations, no rotation, and hidden next pieces, and the speed goes to 1G. Every time you're about to get an I tetromino, either you get 2 lines of garbage or the columns of blocks in the well are shuffled. It is debatable whether this mode is even playable. This mode exists primarily as a criticism of a similar mode in the game <i>Tetris DS</i>.</dd>
   5.373 +<dt>Baboo!</dt>
   5.374 +<dd>Standard S.M.G. with zero gravity, ending after 300 keypresses. Some players recommend practicing Baboo!, claiming that the way to fast play in other gimmicks involves using as few keystrokes as possible to place each tetromino.</dd>
   5.375 +</dl>
   5.376 +
   5.377 +<h3>Well rules</h3>
   5.378 +<dl>
   5.379 +<dt>Well width</dt>
   5.380 +<dd>Standard S.M.G. uses 10 columns; it can be set anywhere between 4 and 12.</dd>
   5.381 +<dt>Well height</dt>
   5.382 +<dd>Standard S.M.G. uses 20 rows; it can be decreased to 8.</dd>
   5.383 +<dt>Enter above ceiling</dt>
   5.384 +<dd>Normally, tetrominoes enter the well above the ceiling. But when this is turned off, tetrominoes enter inside the well.</dd>
   5.385 +<dt>Speed curve</dt>
   5.386 +<dd>This controls how fast tetrominoes fall:
   5.387 +<ul>
   5.388 +<li>Exponential is the familiar behavior where the game speeds up gradually as pieces enter the well. For the first 600 pieces, it speeds up every 30 pieces, doubling every 60. Then, the gravity hits 20G, and lock delay begins to decrease in harmonic progression: 2/(3 + 9/256 * (<i>n</i> - 609)) seconds.</li>
   5.389 +<li>In Zero, tetrominoes do not fall on their own. It's pointless for setting endurance records but useful for practicing tricky stacking methods.</li>
   5.390 +<li>Rhythm starts at 0G or 20G; if your play speed drops below the stated level, tetrominoes will lock on their own. This level starts at 60 tetrominoes per minute and increases by 10 every 64 beats.</li>
   5.391 +<li>Master and Death are similar to Exponential and Rhythm but resemble some Japanese S.M.G. implementations: every line cleared advances the level by 1, and except at levels 99, 199, 299, etc., so does every tetromino dropped. (Death is a bit faster than starting Master at level 600, where blocks fall at 20G. Death 300+ is what it sounds like: starting Death at level 300.)</li>
   5.392 +<li>NES and Game Boy speed curves approximate the behavior of the classic 8-bit games published by Nintendo. Game Boy Heart is like Game Boy but starting 100 lines in.</li>
   5.393 +</ul></dd>
   5.394 +<dt>Max entry delay</dt>
   5.395 +<dd>When set greater than 0, there is a brief delay before each tetromino enters the playfield, and another delay when one or more lines are cleared.
   5.396 +(So if you want fast play, make more lines at once.)
   5.397 +During entry delay, sometimes called "ARE", the player can hold the next piece, rotate the next piece (if turned on), and charge up the autorepeat of sideways movement.
   5.398 +This delay is considered a "maximum", as it decreases over the course of the game in Master and Death speed curves.
   5.399 +Caution: When entry delay is on, hard drop also functions as an Initial Action.</dd>
   5.400 +<dt>Piece set</dt>
   5.401 +<dd>This selects which pieces are used:
   5.402 +<ul>
   5.403 +<li>"All pieces" deals all tetrominoes, plus trominoes of 3 blocks (I3, L3) and the domino of 2 blocks (I2). In history randomizer, it starts with I, J, L, T, or L3.</li>
   5.404 +<li>"Tetrominoes" includes the seven pieces with four blocks (I, J, L, O, S, T, and Z). In history randomizer, it starts with I, J, L, or T.</li>
   5.405 +<li>"No sticks" is one stick short of a bundle because it generates all tetrominoes other than I, which is sometimes called a "stick" or "bar". It's useful for practicing "push" and T-spins. In history randomizer, it starts with J, L, or T.</li>
   5.406 +<li>"SZSZ" deals only S and Z tetrominoes. History randomizers will deal an alternating sequence (S, Z, S, Z). It was used in a <a href="http://www2.math.uic.edu/~burgiel/Tetris/explanation.html">proof that S.M.G. with the Memoryless randomizer cannot be played forever</a>.</li>
   5.407 +<li>"iCheat&trade;" generates <em>only</em> I tetrominoes, simulating cheat modes on some other games.</li>
   5.408 +<li>"T-Party" generates only T tetrominoes.</li>
   5.409 +</ul></dd>
   5.410 +<dt>Randomizer</dt>
   5.411 +<dd>This selects the function used to shuffle the pieces:
   5.412 +<ul>
   5.413 +<li>"Memoryless" has a more or less equal probability of choosing each piece at any given time, as seen in most <abbr title="Soviet Mind Game">S.M.G.</abbr> implementations (including Tetris brand games from before 2001).</li>
   5.414 +<li>"Bag" treats the pieces as sets and deals a randomized sequence of all different pieces, as if drawing them from a bag, before the next set. Advanced players like to "count cards", keeping track of the "seams" between bags of seven tetrominoes. This randomizer has a weakness: it can easily be <a href="http://www.tetrisconcept.com/wiki/index.php?title=Playing_forever">played forever</a>.</li>
   5.415 +<li>"Bag + 1" is like Bag but tosses a duplicate piece into each bag.</li>
   5.416 +<li>"Double bag" adds an extra copy of each piece to the bag, as a compromise between the even distribution of Bag and unpredictability of Memoryless.</li>
   5.417 +<li>"Strict history" will not deal a piece that was recently dealt. For tetrominoes, it will not deal one of the previous four. This way the player never gets a clump of all the same piece.</li>
   5.418 +<li>"History 6 rolls" is similar to strict history but sometimes (about 3.5%) generates repeats of recent pieces.</li>
   5.419 +</ul></dd>
   5.420 +</dl>
   5.421 +
   5.422 +<h3>Movement rules</h3>
   5.423 +<dl>
   5.424 +<dt>Hold piece</dt>
   5.425 +<dd>Controls the behavior of the hold piece. Empty leaves the hold box empty at the start of the game; the first time the player uses hold piece, the falling piece goes to the hold box, and the next piece enters instead of the hold piece. Random fills the hold piece with a random piece at the start of the game. Hold to next holds to the next piece box instead of the hold box. Off disables hold entirely.</dd>
   5.426 +<dt>Rotation system</dt>
   5.427 +<dd>In different rotation systems, tetrominoes may enter the field at different positions, and they behave differently when rotated against the walls and the other blocks. This option can be set to SRS (easiest), TOD M4 (like SRS in free space but less forgiving in wall kicks), Arika (like Sega with basic wall kicks), Sega or NES or Game Boy (no kicks), or Tengen (strict, with even stranger twists). Apart from one set of graphics for SRS and TOD M4 and one set for other systems, this option does not affect the colors of the tetrominoes; use the <code>.skin</code> commands to select replacement images that contain other colors. It also does not affect lockdown behavior.</dd>
   5.428 +<dt>Floor kicks</dt>
   5.429 +<dd>SRS and Arika rotation systems move a tetromino upward when a rotation would strike the floor or blocks under the tetromino. As this makes some tetrominoes easy to spin in place, this can detract from the immediacy of the game. This option limits the number of times each tetromino can be kicked upward.</dd>
   5.430 +<dt>Lockdown</dt>
   5.431 +<dd>Controls the interpretation of the delay between when each tetromino lands and when it locks. Classic disables the delay, as was the case in the oldest S.M.G. implementations; this makes slide moves more difficult. Entry reset allows only a constant amount of this delay per tetromino; instead of resetting, the lock timer pauses while the tetromino is falling. With Step reset, the delay resets every time the tetromino moves downward. With Move reset, the delay resets every time the tetromino moves at all. (Outside of Zero and Exponential speed curves, Classic behaves as Step reset.)</dd>
   5.432 +<dt>Lock delay</dt>
   5.433 +<dd>Controls the length of this delay. It can be set to a constant amount of time, or it can be controlled by the speed curve.</dd>
   5.434 +<dt>Deep drop</dt>
   5.435 +<dd>When this is on, a piece can fall past blocks in the playfield to fill appropriately shaped holes. This is potentially more controversial than the "T-spin triple" of SRS.</dd>
   5.436 +</dl>
   5.437 +
   5.438 +<h3>Line clear rules</h3>
   5.439 +<dl>
   5.440 +<dt>Line clear delay</dt>
   5.441 +<dd>Controls the length of the delay after a line clear. It can be set to a constant amount of time, or it can be controlled by the speed curve.</dd>
   5.442 +<dt>Clear gravity</dt>
   5.443 +<dd>Controls what happens to blocks after a line clear.
   5.444 +Naive means that blocks move down by exactly the number of cleared lines below them, potentially causing floating blocks.
   5.445 +In Sticky, tetrominoes stick together when they lock, and contiguous groups fall together.
   5.446 +It's possible for a group to fall past the cleared lines, causing chain reactions.
   5.447 +Sticky by color is similar to Sticky except only blocks of a single color stick together.
   5.448 +Cascade treats each piece as a separate entity unless Gluing is turned on.</dd>
   5.449 +<dt>Gluing</dt>
   5.450 +<dd>When turned on, this controls how pieces glue themselves together after they land:
   5.451 +<ul>
   5.452 +<li>In Square, when the player makes a 4x4 block square out of four complete tetrominoes, it will become a large square of solid gold or silver. Pieces that have been broken by a line clear cannot make squares and are thus drawn as garbage. A line containing a row of a silver square is worth 500 bonus points; a line containing a row of a gold square is worth 1000. <strong>Caution:</strong> This mode is displayed incorrectly if the current <a href="#skin">skin</a> lacks the appropriate <code>ljconn</code> file.</li>
   5.453 +<li>Sticky and Sticky by color act much like the corresponding Clear gravity settings. If a skin using connected blocks has a bright border around each piece, this can be used to draw a white border around each mass of pieces. Garbage always acts Sticky by color.</li>
   5.454 +</ul>
   5.455 +</dd>
   5.456 +<dt>Scoring</dt>
   5.457 +<dd>Choose a scoring method for line clears, as described <a href="#scoring">above</a>.</dd>
   5.458 +<dt>Drop scoring</dt>
   5.459 +<dd>Choose a scoring method for drops, as described <a href="#scoring">above</a>.</dd>
   5.460 +<dt>T-spin detection</dt>
   5.461 +<dd>Controls the definition of a T-spin used by the scoring method. When turned off, T-spins are not rewarded. When set to Immobile, a line is a T-spin line if the tetromino that completes it could not have moved left, right, or up. When set to 3-corner T, a line is a T-spin line if all of the following are true:
   5.462 +<ul>
   5.463 +<li>The line is completed with a T tetromino.</li>
   5.464 +<li>At least 3 of the 4 boxes diagonal to the center of the tetromino are either the wall or occupied with a block.</li>
   5.465 +<li>The tetromino has not shifted sideways or down by one or more spaces since it was last rotated.</li>
   5.466 +</ul>
   5.467 +3-corner T no kick adds an additional restriction: The rotation itself did not move the tetromino.</dd>
   5.468 +<dt>Garbage</dt>
   5.469 +<dd>Pushes up the lines in your playfield. In Level 1 through 4, a simulated computer opponent occasionally sends you this many lines of garbage. In Home Run Derby, every line you clear other than with a home run or a T-spin gives you garbage. In Drill and Preload Zigzag, the well is full of garbage except for the top few rows. Be prepared to rely on infinite spin for the first few lines until your skill improves.</dd>
   5.470 +</dl>
   5.471 +
   5.472 +<h3>Control options</h3>
   5.473 +<dl>
   5.474 +<dt>Max sideways delay</dt>
   5.475 +<dd>This controls the time the left or right key has to be held down before sideways movement begins. This can be set in 16.7 <abbr title="millisecond">ms</abbr> increments from 16.7 ms to 300 ms.
   5.476 +This delay is considered a "maximum", as it decreases over the course of the game in Master and Death speed curves.</dd>
   5.477 +<dt>Sideways speed</dt>
   5.478 +<dd>This controls how fast the tetromino moves sideways. It can be set at Instant (tetromino reaches side instantly as soon as sideways delay expires) or 10 to 60 moves per second.</dd>
   5.479 +<dt>Initial sideways motion</dt>
   5.480 +<dd>When this is turned on, the tetromino can move sideways in the first frame that it appears. This affects mobility at very high speeds (e.g. Death or Rhythm).</dd>
   5.481 +<dt>Initial rotation</dt>
   5.482 +<dd>When this is turned on, the player can rotate a piece before it enters by holding down a rotate button.</dd>
   5.483 +<dt>Allow diagonal motion</dt>
   5.484 +<dd>When this is turned off, the Left, Right, Up, and Down keys will be ignored if Left or Right is pressed at the same time as Up or Down. Some players who use joysticks or gamepads claim that such "4-way" logic reduces misdrops. Keyboard players should leave it turned on, as it is necessary for "Lock on release" to work properly.</dd>
   5.485 +<dt>Soft drop speed</dt>
   5.486 +<dd>Controls how fast the tetromino moves downward in a soft drop. Can be set to 1G, 1/2G, or 1/3G.</dd>
   5.487 +<dt>Soft drop</dt>
   5.488 +<dd>Controls how tetrominoes behave when they land after being soft dropped with the Down key. "Lock" means that they will lock instantly. "Slide" allows the player to place a tetromino under an overhang in one smooth motion (move down, move sideways, then press Up to lock). "Lock on release" is similar to "Slide" but locks when the player lets go of Down.</dd>
   5.489 +<dt>Hard drop</dt>
   5.490 +<dd>Controls how tetrominoes behave when they land after being hard dropped with the Up key. "Lock" means that hard-dropped tetrominoes will lock instantly.  "Slide" allows the player to place a tetromino under an overhang in one smooth motion (move down, move sideways, then press Down to lock). (The "Firm drop" key acts as a hard drop set to "Slide".) "Lock on release" is similar to "Slide" but locks when the player lets go of Up.</dd>
   5.491 +</dl>
   5.492 +
   5.493 +<h3>Display options</h3>
   5.494 +<p>
   5.495 +The details may vary between the PC version and the GBA and DS versions:
   5.496 +</p><dl>
   5.497 +<dt>Shadow</dt>
   5.498 +<dd>Controls how the falling tetromino and its shadow are displayed. It can show the falling tetromino along with a transparent colored shadow, opaque colored shadow, or colorless shadow. It's also possible to hide the shadow entirely or (for experts only) hide both the shadow and the falling tetromino.</dd>
   5.499 +<dt>Hide blocks in well</dt>
   5.500 +<dd>When turned on, hides the blocks that have locked down in the well. Useful for practicing for "invisible challenge" in another game.</dd>
   5.501 +<dt>Next pieces</dt>
   5.502 +<dd>Controls the number of previewed pieces displayed next to the well.</dd>
   5.503 +<dt>Smooth gravity</dt>
   5.504 +<dd>When this is turned off, tetrominoes fall in units of one block. When turned on, they fall pixel by pixel, and only the bottom edge of each block in a tetromino is tested for collision against the blocks in the well.</dd>
   5.505 +<dt>Next above shadow</dt>
   5.506 +<dd>Controls the number of previewed pieces displayed above the shadow. (PC version only)</dd>
   5.507 +<dt>Drop trails</dt>
   5.508 +<dd>When this is turned on, hard drops and fast piece gravity cause a piece to make a trail as it falls. It makes players feel faster at the game. (PC version only)</dd>
   5.509 +<dt>Pause on task switch</dt>
   5.510 +<dd>When turned on, automatically pauses the game when the player switches to another window. (PC version only; DS version always pauses on lid close)</dd>
   5.511 +<dt>Record all games</dt>
   5.512 +<dd>Automatically records each game to <tt>demo.ljm</tt>. Remember to rename your record-setting replays. (PC version only)</dd>
   5.513 +<dt>Display</dt>
   5.514 +<dd>Choose full screen or windowed mode. If windowed mode does not work, try excluding LOCKJAW from your window decoration theming program. (PC version only; GBA and DS always run in full screen)</dd>
   5.515 +</dl><p>
   5.516 +The PC version saves these options to <code>lj.ini</code> every time you exit the options screen. Other options not shown are also saved to <code>lj.ini</code> and can be changed with a text editor.
   5.517 +</p>
   5.518 +
   5.519 +<h2><a name="discussion">Discussion</a></h2>
   5.520 +<p>
   5.521 +If you want to discuss the game, you can do so at <a href="http://www.tetrisconcept.com/forum/viewtopic.php?t=100">TetrisConcept.com forum</a>.
   5.522 +</p><p>
   5.523 +If you want a Tetris shot, ask your doctor.
   5.524 +</p>
   5.525 +
   5.526 +<h2><a name="legal">Legal</a></h2>
   5.527 +<div style="border: 1px solid black; padding: 1em 2em">
   5.528 +<p>
   5.529 +Copyright 2006&ndash;2008 Damian Yerrick &lt;<a href="http://www.pineight.com/contact/">tepp&#108;es+lockjaw (at) spam&#99;op (full stop) net</a>&gt;. This manual is under the following license:
   5.530 +</p><p>
   5.531 +This work is provided 'as-is', <span style="font-variant: small-caps">without any express or implied warranty.</span>  In no event will the authors be held liable for any damages arising from the use of this work.
   5.532 +</p><p>
   5.533 +Permission is granted to anyone to use this work for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
   5.534 +</p><ol>
   5.535 +<li>The origin of this work must not be misrepresented; you must not claim that you wrote the original work. If you use this work in a product, an acknowledgment in the product documentation would be appreciated but is not required.</li>
   5.536 +<li>Altered source versions must be plainly marked as such, and must not be misrepresented as being the original work.</li>
   5.537 +<li>This notice may not be removed or altered from any source distribution.</li>
   5.538 +</ol><p>
   5.539 +The term "source" refers to the preferred form of a work for making changes to it.
   5.540 +</p>
   5.541 +</div>
   5.542 +<p>
   5.543 +The LOCKJAW software described by this manual is distributed under the <a href="http://www.gnu.org/copyleft/gpl.html">GNU General Public License, version 2 or later</a>, <span style="font-variant: small-caps">with absolutely no warranty.</span> See <code>COPYING.txt</code> for details.
   5.544 +</p><p>
   5.545 +<a name="trademarks">*</a> Tetris is a trademark of The Tetris Company.  The Software is not sponsored or endorsed by Alexey Pajitnov, Tetris Holding, Apple, Cloudmakers, Free Software Foundation, Microsoft, or Nintendo.
   5.546 +</p>
   5.547 +</div>
   5.548 +</body>
   5.549 +</html>
   5.550 \ No newline at end of file
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/TODO.txt	Fri Mar 13 00:39:12 2009 -0700
     6.3 @@ -0,0 +1,13 @@
     6.4 +As of LOCKJAW 0.42, TODO.txt is being migrated to a bug list format;
     6.5 +see BUGS.txt.  Not yet migrated:
     6.6 +
     6.7 +cdsboy wants:
     6.8 +  * DTET rush mode (please describe this in detail)
     6.9 +  * DTET lives (please describe this in detail)
    6.10 +  * Editable game keys presets (please describe the UI)
    6.11 +
    6.12 +DIGITAL wants:
    6.13 +  * Ability to switch tetromino colors in game (huh?)
    6.14 +  
    6.15 +Zed0 wants:
    6.16 +  * Tetrinet client
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/binzip.in	Fri Mar 13 00:39:12 2009 -0700
     7.3 @@ -0,0 +1,25 @@
     7.4 +alleg42.dll
     7.5 +lj.exe
     7.6 +lj.dat
     7.7 +COPYING.txt
     7.8 +GPL.txt
     7.9 +README.html
    7.10 +CHANGES.txt
    7.11 +TODO.txt
    7.12 +BUGS.txt
    7.13 +ljbg.jpg
    7.14 +ljblocks.bmp
    7.15 +ljconn.bmp
    7.16 +ljblocks-sega.bmp
    7.17 +ljconn-sega.bmp
    7.18 +bgm.s3m
    7.19 +bgm-rhythm.s3m
    7.20 +sound.dat
    7.21 +lj.gba
    7.22 +lj.nds
    7.23 +docs/ljlogo192.png
    7.24 +docs/titlebarbg.png
    7.25 +docs/ljsnap033.png
    7.26 +docs/ijlo-stz.png
    7.27 +docs/appicon.ico
    7.28 +docs/ljhtml.css
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/docs/Compiling_on_Windows.txt	Fri Mar 13 00:39:12 2009 -0700
     8.3 @@ -0,0 +1,103 @@
     8.4 +______________________________________________________________________
     8.5 +
     8.6 +Guide to compiling LOCKJAW on Microsoft Windows
     8.7 +by Lardarse and tepples
     8.8 +
     8.9 +You will need the following files:
    8.10 +
    8.11 +* Dev-C++ 5.0 Beta
    8.12 +  http://prdownloads.sourceforge.net/dev-cpp/devcpp-4.9.9.2_setup.exe
    8.13 +* devkitPro Updater
    8.14 +  http://www.devkitpro.org/
    8.15 +* Allegro 4.2.0
    8.16 +  http://www.pineight.com/lj/all420.zip
    8.17 +* Minimal DirectX 7 SDK for MinGW
    8.18 +  http://alleg.sourceforge.net/files/dx70_mgw.zip
    8.19 +* libogg 1.1.3
    8.20 +  http://www.pineight.com/lj/libogg-1.1.3.zip
    8.21 +* libvorbis 1.1.2
    8.22 +  http://www.pineight.com/lj/libvorbis-1.1.2.zip
    8.23 +* DUMB 0.9.3
    8.24 +  http://www.pineight.com/lj/dumb-0.9.3.zip
    8.25 +* JPGalleg 2.5
    8.26 +  http://www.pineight.com/lj/jpgalleg-2.5.tar.gz
    8.27 +
    8.28 +______________________________________________________________________
    8.29 +Install MinGW
    8.30 +
    8.31 +Put all of those files into a folder on your desktop. Run the Dev-C++
    8.32 +installer, installing into C:\Dev-Cpp, and using the default settings,
    8.33 +unless you have a reason for not wanting to associate C source file
    8.34 +types with Dev-C++.
    8.35 +
    8.36 +______________________________________________________________________
    8.37 +Install MSYS and devkitARM
    8.38 +
    8.39 +Run the devkitPro Updater. It will ask you if you want to get a newer
    8.40 +version.  Answer yes to this, and then install everything that it asks
    8.41 +to, except devkitPSP and devkitPPC.  MSYS should be installed into
    8.42 +C:\devkitPro\msys, and just press Enter to all of the questions that
    8.43 +it asks when installing it.  The other parts will install without user
    8.44 +intervention, unless the downloading is interrupted.
    8.45 +
    8.46 +______________________________________________________________________
    8.47 +Make MinGW and MSYS available
    8.48 +
    8.49 +Right-click My Computer and go to Properties -> Advanced ->
    8.50 +Environment Variables.  Add MINGDIR = C:\Dev-Cpp and make sure
    8.51 +that Path starts with C:\Dev-Cpp\Bin;c:\devkitPro\msys\bin;
    8.52 +
    8.53 +Extract dx70_mgw.zip into C:\Dev-Cpp, over-writing files as
    8.54 +necessary.  Extract the other 5 files that were downloaded earlier
    8.55 +into C:\, where each one will be in its own folder.  Now open a
    8.56 +command prompt (Start -> Run -> cmd) and enter the following commands:
    8.57 +
    8.58 +______________________________________________________________________
    8.59 +Make and install required libraries
    8.60 +
    8.61 +cd \
    8.62 +cd allegro
    8.63 +fix.bat mingw32
    8.64 +make
    8.65 +make install
    8.66 +cd \
    8.67 +cd libogg-1.1.3
    8.68 +sh configure --prefix=C:\Dev-Cpp --disable-shared
    8.69 +make
    8.70 +make install
    8.71 +cd \
    8.72 +cd libvorbis-1.1.2
    8.73 +sh configure --prefix=C:\Dev-Cpp --disable-shared
    8.74 +make
    8.75 +make install
    8.76 +cd \
    8.77 +cd dumb-0.9.3
    8.78 +make
    8.79 +
    8.80 +This command will ask you 2 questions.  Answer M to the first
    8.81 +and Y to the second.
    8.82 +
    8.83 +make install
    8.84 +cd \
    8.85 +cd jpgalleg-2.5
    8.86 +fix.bat mingw32
    8.87 +make
    8.88 +make install
    8.89 +
    8.90 +At this point you can now close this window.
    8.91 +
    8.92 +Now open another command window and navigate to the folder where you
    8.93 +have Lockjaw installed.  You may prefer to use a different folder
    8.94 +than your usual playing folder.  If you are going to be compiling
    8.95 +a lot, and you are using Windows XP, then you may want to install
    8.96 +the Open Command Window Here powertoy:
    8.97 +http://www.microsoft.com/windowsxp/downloads/powertoys/xppowertoys.mspx
    8.98 +
    8.99 +Once you are in the folder, just type in make, and within a few
   8.100 +seconds, it should be compiled.  The lj.exe will be roughly twice the
   8.101 +size of that in the official distribution; you can fix this with UPX.
   8.102 +http://upx.sourceforge.net/
   8.103 +
   8.104 +Now to try to do something intersting with it...
   8.105 +
   8.106 +Now grab yourself a drink. You deserve it...
     9.1 Binary file docs/appicon.ico has changed
    10.1 Binary file docs/appicon.xcf has changed
    11.1 Binary file docs/dsicon.bmp has changed
    12.1 Binary file docs/favicon.ico has changed
    13.1 Binary file docs/ijlo-stz.png has changed
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/docs/ljhtml.css	Fri Mar 13 00:39:12 2009 -0700
    14.3 @@ -0,0 +1,47 @@
    14.4 +body {
    14.5 +  margin: 0 0 1em 0; font-family: Bitstream Vera Sans, sans-serif
    14.6 +}
    14.7 +h1 {
    14.8 +  height: 192px; margin: 0; padding: 0 1em; background: url(titlebarbg.png); color: #FFF
    14.9 +}
   14.10 +h1 img {
   14.11 +  vertical-align: middle;
   14.12 +}
   14.13 +#content {
   14.14 +  margin: 0; padding: 1em 2em;
   14.15 +  color: #000; background: #FFF
   14.16 +}
   14.17 +#linkbar {
   14.18 +  position: absolute; top: 1em; left: 480px; color: #fff; margin: 0; font-size: 120%;
   14.19 +  padding-left: 1em
   14.20 +}
   14.21 +#linkbar a:link { color: #99f }
   14.22 +#linkbar a:visited { color: #f69 }
   14.23 +#linkbar a:hover { color: #ccf }
   14.24 +#linkbar a:active { color: #fff }
   14.25 +
   14.26 +.precis  {
   14.27 +  margin: 2em 2em 2em 2em;
   14.28 +  border: 1px solid #c00;
   14.29 +  background: #ffc;
   14.30 +  padding: 1em 2em 1em 2em;
   14.31 +  font-size: 120%;
   14.32 +}
   14.33 +
   14.34 +.precis-ad  {
   14.35 +  margin: 2em 160px 2em 2em;
   14.36 +  border: 1px solid #c00;
   14.37 +  background: #ffc;
   14.38 +  padding: 1em 2em 1em 2em;
   14.39 +  font-size: 120%;
   14.40 +}
   14.41 +
   14.42 +.screenshot {
   14.43 +  float: right;
   14.44 +  margin: 0 0 1em 2em;
   14.45 +  font-size: 80%;
   14.46 +}
   14.47 +
   14.48 +img.screenshot, .screenshot img {
   14.49 +  border: 0px none;
   14.50 +}
    15.1 Binary file docs/ljlogo192.png has changed
    16.1 Binary file docs/ljsnap018.png has changed
    17.1 Binary file docs/titlebarbg.png has changed
    18.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.2 +++ b/dsmakefile	Fri Mar 13 00:39:12 2009 -0700
    18.3 @@ -0,0 +1,136 @@
    18.4 +# Makefile for Nintendo DS version of LOCKJAW
    18.5 +#
    18.6 +# Copr. 2006-2007 Damian Yerrick
    18.7 +# 
    18.8 +# This work is free software; you can redistribute it and/or modify
    18.9 +# it under the terms of the GNU General Public License as published by
   18.10 +# the Free Software Foundation; either version 2 of the License, or
   18.11 +# (at your option) any later version.
   18.12 +# 
   18.13 +# This program is distributed in the hope that it will be useful,
   18.14 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
   18.15 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18.16 +# GNU General Public License for more details.
   18.17 +# 
   18.18 +# You should have received a copy of the GNU General Public License
   18.19 +# along with this program; if not, write to the Free Software
   18.20 +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   18.21 +
   18.22 +srcdir := src
   18.23 +objdir := obj/ds
   18.24 +EXE := $(objdir)/ljds.elf
   18.25 +EXE7 := $(objdir)/ljds7.elf
   18.26 +ASSETSFILE := $(objdir)/assets.s
   18.27 +ASSETS := $(srcdir)/text.chr $(srcdir)/gbablk.chr $(objdir)/vwfont.bin
   18.28 +ARMGCC=arm-eabi-gcc
   18.29 +ARMOBJ=arm-eabi-objcopy
   18.30 +CFLAGS7=-std=gnu99 -Wall -O2 -mcpu=arm7tdmi -mtune=arm7tdmi -fomit-frame-pointer -ffast-math -mthumb-interwork
   18.31 +CFLAGS9=-std=gnu99 -Wall -O2 -mcpu=arm9tdmi -mtune=arm9tdmi -fomit-frame-pointer -ffast-math -mthumb-interwork -DDEBRIEF_SHORT -DHAS_FOPEN
   18.32 +LDLIBS9=-lfat -lnds9
   18.33 +NDSLIB_INCLUDE=$(DEVKITPRO)/libnds/include
   18.34 +NDSLIB_LIB=$(DEVKITPRO)/libnds/lib
   18.35 +DEPOBJS := \
   18.36 +$(objdir)/ljds.o $(objdir)/ljplay.o $(objdir)/lj.o \
   18.37 +$(objdir)/gimmicks.o $(objdir)/wktables.o $(objdir)/macro.o \
   18.38 +$(objdir)/gbaopt.o $(objdir)/speed.o $(objdir)/options.o \
   18.39 +$(objdir)/fontdraw.o $(objdir)/dssleep.o $(objdir)/random.o \
   18.40 +$(objdir)/ljlocale.o $(objdir)/debrief.o $(objdir)/dsdebrief.o \
   18.41 +$(objdir)/gbamenus.o $(objdir)/ljpath.o $(objdir)/dsjoy.o
   18.42 +OBJS := $(DEPOBJS) $(ASSETSFILE)
   18.43 +EMU := start
   18.44 +GAMEICON := docs/dsicon.bmp
   18.45 +
   18.46 +CC := gcc
   18.47 +CFLAGS := 
   18.48 +
   18.49 +# The wintermute-approved way to ensure devkitARM is in the PATH
   18.50 +ifeq ($(strip $(DEVKITPRO)),)
   18.51 +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>devkitPro)
   18.52 +endif
   18.53 +ifeq ($(strip $(DEVKITARM)),)
   18.54 +$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM)
   18.55 +endif
   18.56 +export PATH	:=	$(DEVKITARM)/bin:$(PATH)
   18.57 +
   18.58 +.PHONY: run clean cf
   18.59 +
   18.60 +run: lj.nds
   18.61 +	$(EMU) $<
   18.62 +
   18.63 +lj.nds: $(objdir)/ljds.bin $(objdir)/ljds7.bin $(GAMEICON)
   18.64 +	ndstool -c $@ -9 $(objdir)/ljds.bin -7 $(objdir)/ljds7.bin -b $(GAMEICON) "LOCKJAW DS;The Soviet Mind Game"
   18.65 +
   18.66 +%.nds.gba: %.nds
   18.67 +	dsbuild $< -o $@
   18.68 +
   18.69 +%.bin: %.elf
   18.70 +	$(ARMOBJ) -O binary $< $@
   18.71 +
   18.72 +$(EXE): $(OBJS)
   18.73 +	$(ARMGCC) -g -mthumb-interwork -mno-fpu -specs=ds_arm9.specs $^ -L$(NDSLIB_LIB) $(LDLIBS9) -o $@
   18.74 +
   18.75 +$(objdir)/ljds7.elf: $(objdir)/dsarm7.o $(objdir)/lookup_tables7.o $(objdir)/dssound7.o
   18.76 +	$(ARMGCC) -g -mthumb-interwork -mno-fpu -specs=ds_arm7.specs $^ -L$(NDSLIB_LIB) -lnds7 -o $@
   18.77 +
   18.78 +$(objdir)/%7.o: $(srcdir)/%7.c
   18.79 +	$(ARMGCC) $(CFLAGS7) -I$(NDSLIB_INCLUDE) -MMD -DARM7 -c $< -o $@
   18.80 +	@cp $(objdir)/$*.d $(objdir)/$*.P; \
   18.81 +	sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
   18.82 +	-e '/^$$/ d' -e 's/$$/ :/' < $(objdir)/$*.d >> $(objdir)/$*.P; \
   18.83 +	rm -f $(objdir)/$*.d
   18.84 +
   18.85 +$(objdir)/%.o: $(srcdir)/%.c $(srcdir)/ljds.h
   18.86 +	$(ARMGCC) $(CFLAGS9) -I$(NDSLIB_INCLUDE) -MMD -DARM9 -c $< -o $@
   18.87 +	@cp $(objdir)/$*.d $(objdir)/$*.P; \
   18.88 +	sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
   18.89 +	-e '/^$$/ d' -e 's/$$/ :/' < $(objdir)/$*.d >> $(objdir)/$*.P; \
   18.90 +	rm -f $(objdir)/$*.d
   18.91 +
   18.92 +%.o: %.s
   18.93 +	$(ARMGCC) $(CFLAGS9) -I$(NDSLIB_INCLUDE) -MMD -DARM9 -c $< -o $@
   18.94 +	@cp $(objdir)/$*.d $(objdir)/$*.P; \
   18.95 +	sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
   18.96 +	-e '/^$$/ d' -e 's/$$/ :/' < $(objdir)/$*.d >> $(objdir)/$*.P; \
   18.97 +	rm -f $(objdir)/$*.d
   18.98 +
   18.99 +$(ASSETSFILE): $(ASSETS)
  18.100 +	bin2s $^ > $@
  18.101 +
  18.102 +# Handle header dependencies
  18.103 +
  18.104 +-include $(DEPOBJS:%.o=%.P)
  18.105 +# The master copy of the variable width font library is in another folder
  18.106 +
  18.107 +src/fontdraw%: /e/games/ac/double/rac/src/fontdraw%
  18.108 +	cp $< $@
  18.109 +
  18.110 +#ejector is at http://jimprice.com/jim-soft.shtml#eject
  18.111 +#and is not necessary for building the program
  18.112 +cf: lj.nds
  18.113 +	cp lj.nds /h/
  18.114 +	ejector H:
  18.115 +
  18.116 +# PC side rules
  18.117 +
  18.118 +tools/fontconv.exe: tools/fontconv.o
  18.119 +	gcc -Wall -s $^ -lalleg -o $@
  18.120 +
  18.121 +tools/mktables.exe: tools/mktables.o
  18.122 +	gcc -Wall -s $^ -o $@
  18.123 +
  18.124 +tools/%.o: tools/%.c
  18.125 +	gcc -Wall -O2 -std=gnu99 -c $< -o $@
  18.126 +
  18.127 +$(objdir)/vwfont.bin: tools/fontconv.exe $(srcdir)/font.bmp
  18.128 +	$^ $@
  18.129 +
  18.130 +$(objdir)/lookup_tables7.s: tools/mktables.exe
  18.131 +	$<
  18.132 +
  18.133 +# Cleanup rules
  18.134 +
  18.135 +clean:
  18.136 +	-rm $(objdir)/*.s
  18.137 +	-rm $(objdir)/*.o
  18.138 +	-rm $(objdir)/*.P
  18.139 +
    19.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.2 +++ b/gbamakefile	Fri Mar 13 00:39:12 2009 -0700
    19.3 @@ -0,0 +1,135 @@
    19.4 +# Makefile for Game Boy Advance version of LOCKJAW
    19.5 +#
    19.6 +# Copr. 2006-2007 Damian Yerrick
    19.7 +# 
    19.8 +# This work is free software; you can redistribute it and/or modify
    19.9 +# it under the terms of the GNU General Public License as published by
   19.10 +# the Free Software Foundation; either version 2 of the License, or
   19.11 +# (at your option) any later version.
   19.12 +# 
   19.13 +# This program is distributed in the hope that it will be useful,
   19.14 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
   19.15 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   19.16 +# GNU General Public License for more details.
   19.17 +# 
   19.18 +# You should have received a copy of the GNU General Public License
   19.19 +# along with this program; if not, write to the Free Software
   19.20 +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   19.21 +
   19.22 +
   19.23 +srcdir := src
   19.24 +objdir := obj/gba
   19.25 +EXE := $(objdir)/lj.elf
   19.26 +ASSETSFILE := $(objdir)/assets.s
   19.27 +INCGBA := -I$(DEVKITPRO)/libgba/include
   19.28 +LIBGBA := -L$(DEVKITPRO)/libgba/lib -lgba
   19.29 +THUMB_CFLAGS := -mthumb -Wall -O2 -std=gnu99 -mcpu=arm7tdmi \
   19.30 +-DFONTDRAW_SPLIT_COMPILE -DNO_DATETIME -DDEBRIEF_SHORT
   19.31 +ARM_CFLAGS := -marm -Wall -O2 -std=gnu99 -mcpu=arm7tdmi \
   19.32 +-DFONTDRAW_SPLIT_COMPILE
   19.33 +CC := arm-eabi-gcc -mthumb-interwork
   19.34 +LD := arm-eabi-gcc -mthumb-interwork
   19.35 +LDFLAGS := -mthumb -Wall -specs=gba_mb.specs
   19.36 +
   19.37 +MUSICOBJS := $(objdir)/gbasound.o $(objdir)/gbanotefreq.o
   19.38 +MUSICLIBS := 
   19.39 +
   19.40 +# The wintermute-approved way to ensure devkitARM is in the PATH
   19.41 +ifeq ($(strip $(DEVKITPRO)),)
   19.42 +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>devkitPro)
   19.43 +endif
   19.44 +ifeq ($(strip $(DEVKITARM)),)
   19.45 +$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM)
   19.46 +endif
   19.47 +export PATH	:=	$(DEVKITARM)/bin:$(PATH)
   19.48 +
   19.49 +# Objects
   19.50 +
   19.51 +LDLIBS := $(LIBGBA)
   19.52 +DEPOBJS := \
   19.53 +$(objdir)/ljgba.o $(objdir)/gbaopt.o $(objdir)/gbaisr.iwram.o \
   19.54 +$(objdir)/ljplay.o $(objdir)/lj.o $(objdir)/speed.o $(objdir)/gimmicks.o \
   19.55 +$(objdir)/options.o $(objdir)/wktables.o $(objdir)/fontdraw.o \
   19.56 +$(objdir)/fontdraw.iwram.o $(objdir)/macro.o $(objdir)/random.o \
   19.57 +$(objdir)/gba_asm.o $(objdir)/ljlocale.o $(objdir)/debrief.o \
   19.58 +$(objdir)/dsdebrief.o $(objdir)/gbamenus.o $(objdir)/dsjoy.o \
   19.59 +$(MUSICOBJS)
   19.60 +ASSETS := $(srcdir)/text.chr $(srcdir)/gbablk.chr $(objdir)/vwfont.bin
   19.61 +#deleted: ljpc pcjoy options debrief ljreplay ljmusic
   19.62 +
   19.63 +OTHEROBJS := $(ASSETSFILE)
   19.64 +
   19.65 +run: lj.gba
   19.66 +	start $<
   19.67 +
   19.68 +#ejector is at http://jimprice.com/jim-soft.shtml#eject
   19.69 +#and is not necessary for building the program
   19.70 +cf: lj.gba
   19.71 +	cp lj.gba /h/gba/
   19.72 +	ejector H:
   19.73 +
   19.74 +$(EXE): $(DEPOBJS) $(OTHEROBJS)
   19.75 +	$(LD) $(LDFLAGS) $^ $(MUSICLIBS) $(LDLIBS) -o $@
   19.76 +
   19.77 +$(ASSETSFILE): $(ASSETS)
   19.78 +	bin2s $^ > $@
   19.79 +
   19.80 +# Compilation rules
   19.81 +
   19.82 +%.gba: $(objdir)/%.elf
   19.83 +	arm-eabi-objcopy -O binary $< $@
   19.84 +	gbafix -tLOCKJAW $@
   19.85 +
   19.86 +$(objdir)/%.iwram.o: $(srcdir)/%.iwram.c
   19.87 +	$(CC) $(ARM_CFLAGS) -MMD -c -o $@ $< $(INCGBA)
   19.88 +	@cp $(objdir)/$*.iwram.d $(objdir)/$*.iwram.P; \
   19.89 +	sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
   19.90 +	-e '/^$$/ d' -e 's/$$/ :/' < $(objdir)/$*.iwram.d >> $(objdir)/$*.iwram.P; \
   19.91 +	rm -f $(objdir)/$*.iwram.d
   19.92 +
   19.93 +$(objdir)/%.o: $(srcdir)/%.c
   19.94 +	$(CC) $(THUMB_CFLAGS) -MMD -c -o $@ $< $(INCGBA)
   19.95 +	@cp $(objdir)/$*.d $(objdir)/$*.P; \
   19.96 +	sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
   19.97 +	-e '/^$$/ d' -e 's/$$/ :/' < $(objdir)/$*.d >> $(objdir)/$*.P; \
   19.98 +	rm -f $(objdir)/$*.d
   19.99 +
  19.100 +$(objdir)/%.o: $(srcdir)/%.s
  19.101 +	$(CC) -MMD -c -o $@ $< $(INCGBA)
  19.102 +	@cp $(objdir)/$*.d $(objdir)/$*.P; \
  19.103 +	sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
  19.104 +	-e '/^$$/ d' -e 's/$$/ :/' < $(objdir)/$*.d >> $(objdir)/$*.P; \
  19.105 +	rm -f $(objdir)/$*.d
  19.106 +    
  19.107 +$(objdir)/%.o: $(objdir)/%.s
  19.108 +	$(CC) -c -o $@ $<
  19.109 +
  19.110 +# The master copy of the variable width font library is in another folder
  19.111 +
  19.112 +src/fontdraw%: /e/games/ac/double/rac/src/fontdraw%
  19.113 +	cp $< $@
  19.114 +
  19.115 +src/font.bmp: /e/games/ac/double/rac/src/font.bmp
  19.116 +	cp $< $@
  19.117 +
  19.118 +$(objdir)/vwfont.bin: tools/fontconv.exe $(srcdir)/font.bmp
  19.119 +	$^ $@
  19.120 +
  19.121 +tools/fontconv.exe: tools/fontconv.o
  19.122 +	gcc -Wall -s $^ -lalleg -o $@
  19.123 +
  19.124 +tools/%.o: tools/%.c
  19.125 +	gcc -Wall -O2 -std=gnu99 -c $< -o $@
  19.126 +
  19.127 +# Header dependencies
  19.128 +
  19.129 +-include $(DEPOBJS:%.o=%.P)
  19.130 +
  19.131 +
  19.132 +# Cleanup rules
  19.133 +.PHONY: clean run cf
  19.134 +
  19.135 +clean:
  19.136 +	-rm $(objdir)/*.s
  19.137 +	-rm $(objdir)/*.o
  19.138 +	-rm $(objdir)/*.P
    20.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.2 +++ b/makefile	Fri Mar 13 00:39:12 2009 -0700
    20.3 @@ -0,0 +1,108 @@
    20.4 +# Makefile for Allegro version of LOCKJAW
    20.5 +# under Windows and Linux
    20.6 +#
    20.7 +# Copr. 2006-2007 Damian Yerrick
    20.8 +# 
    20.9 +# This work is free software; you can redistribute it and/or modify
   20.10 +# it under the terms of the GNU General Public License as published by
   20.11 +# the Free Software Foundation; either version 2 of the License, or
   20.12 +# (at your option) any later version.
   20.13 +# 
   20.14 +# This program is distributed in the hope that it will be useful,
   20.15 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
   20.16 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   20.17 +# GNU General Public License for more details.
   20.18 +# 
   20.19 +# You should have received a copy of the GNU General Public License
   20.20 +# along with this program; if not, write to the Free Software
   20.21 +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   20.22 +
   20.23 +
   20.24 +EXE := lj.exe
   20.25 +CFLAGS := -Wall -O2 -std=gnu99 -DWITH_REPLAY=1 -DHAS_FPU
   20.26 +CC := gcc
   20.27 +LD := gcc
   20.28 +LDFLAGS := -Wall -s -mwindows
   20.29 +srcdir := src
   20.30 +objdir := obj/win32
   20.31 +
   20.32 +# Set this to the drive letter of your CF or SD card adapter
   20.33 +# so that 'make cf' works
   20.34 +# for info about Ejector by Jim Price, see the GBA makefile
   20.35 +CFDRIVE := h
   20.36 +
   20.37 +MUSICOBJS := $(objdir)/ljvorbis.o 
   20.38 +MUSICLIBS := -laldmb -ldumb -lvorbisfile -lvorbis -logg
   20.39 +
   20.40 +LDLIBS := -lshfolder -ljpgal -lalleg
   20.41 +LINUXLDLIBS := -ljpgal `allegro-config --libs`
   20.42 +DEPOBJS := $(objdir)/ljpc.o $(objdir)/lj.o $(objdir)/ljplay.o \
   20.43 +$(objdir)/pcjoy.o $(objdir)/gimmicks.o $(objdir)/random.o \
   20.44 +$(objdir)/wktables.o $(objdir)/speed.o $(objdir)/options.o \
   20.45 +$(objdir)/debrief.o $(objdir)/macro.o $(objdir)/ljreplay.o \
   20.46 +$(objdir)/ljmusic.o $(objdir)/old_pc_options.o $(objdir)/pcsound.o \
   20.47 +$(objdir)/pcdebrief.o $(objdir)/ljlocale.o $(objdir)/scenario.o \
   20.48 +$(objdir)/ljpath.o \
   20.49 +$(MUSICOBJS)
   20.50 +
   20.51 +OTHEROBJS := $(objdir)/winicon.o
   20.52 +
   20.53 +.PHONY: pc clean lj.gba lj.nds cf all run
   20.54 +
   20.55 +# Scripts to coordinate building the PC, GBA, and DS versions
   20.56 +
   20.57 +pc: $(EXE)
   20.58 +
   20.59 +run: $(EXE)
   20.60 +	./lj
   20.61 +
   20.62 +all: $(EXE) lj.gba lj.nds
   20.63 +
   20.64 +cf: /$(CFDRIVE)/gba/lj.gba /$(CFDRIVE)/lj.nds
   20.65 +	#ejector from http://www.jimprice.com/jim-soft.shtml#eject
   20.66 +	ejector $(CFDRIVE):
   20.67 +
   20.68 +/$(CFDRIVE)/gba/lj.gba: lj.gba
   20.69 +	cp $< $@
   20.70 +
   20.71 +/$(CFDRIVE)/lj.nds: lj.nds
   20.72 +	cp $< $@
   20.73 +
   20.74 +lj.gba:
   20.75 +	+make -f gbamakefile $@
   20.76 +
   20.77 +lj.nds:
   20.78 +	+make -f dsmakefile $@
   20.79 +
   20.80 +# Content of executable
   20.81 +
   20.82 +$(EXE): $(DEPOBJS) $(OTHEROBJS)
   20.83 +	$(LD) $(LDFLAGS) $^ $(MUSICLIBS) $(LDLIBS) -o $@
   20.84 +
   20.85 +# kesiev's makefile for GNU/Linux
   20.86 +
   20.87 +linux: $(DEPOBJS)
   20.88 +	$(LD) $(LDFLAGS) $^ $(MUSICLIBS) $(LINUXLDLIBS) -o lj
   20.89 +
   20.90 +# Compilation rules
   20.91 +
   20.92 +$(objdir)/%.o: $(srcdir)/%.c
   20.93 +	$(CC) $(CFLAGS) -MMD -c -o $@ $<
   20.94 +	@cp $(objdir)/$*.d $(objdir)/$*.P; \
   20.95 +	sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
   20.96 +	-e '/^$$/ d' -e 's/$$/ :/' < $(objdir)/$*.d >> $(objdir)/$*.P; \
   20.97 +	rm -f $(objdir)/$*.d
   20.98 +
   20.99 +$(objdir)/%.o: src/%.rc docs/favicon.ico
  20.100 +	windres -i $< -o $@
  20.101 +
  20.102 +# Header dependencies
  20.103 +
  20.104 +-include $(DEPOBJS:%.o=%.P)
  20.105 +
  20.106 +
  20.107 +# Cleanup rules
  20.108 +
  20.109 +clean:
  20.110 +	-rm $(objdir)/*.o
  20.111 +	-rm $(objdir)/*.P
    21.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.2 +++ b/mkzip.bat	Fri Mar 13 00:39:12 2009 -0700
    21.3 @@ -0,0 +1,16 @@
    21.4 +@echo off
    21.5 +echo Compressing
    21.6 +upx -9 lj.exe
    21.7 +
    21.8 +echo Sending zip to local copy of Pin Eight
    21.9 +zip -9 -u lj-src.zip -@ < zip.in
   21.10 +
   21.11 +zip -9 -u lj.zip -@ < binzip.in
   21.12 +
   21.13 +copy lj.zip E:\p8\lj\
   21.14 +copy lj-src.zip E:\p8\lj\
   21.15 +copy lj-contrib.zip E:\p8\lj\
   21.16 +copy README.html E:\p8\lj\
   21.17 +copy docs\ljhtml.css E:\p8\lj\docs\
   21.18 +start E:\p8\lj
   21.19 +echo Now FTP this to the server.
   21.20 \ No newline at end of file
    22.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    22.2 +++ b/obj/ds/index.txt	Fri Mar 13 00:39:12 2009 -0700
    22.3 @@ -0,0 +1,1 @@
    22.4 +Windows object files go here. 
    23.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    23.2 +++ b/obj/gba/index.txt	Fri Mar 13 00:39:12 2009 -0700
    23.3 @@ -0,0 +1,1 @@
    23.4 +Windows object files go here. 
    24.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    24.2 +++ b/obj/win32/index.txt	Fri Mar 13 00:39:12 2009 -0700
    24.3 @@ -0,0 +1,1 @@
    24.4 +Windows object files go here. 
    25.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    25.2 +++ b/src/debrief.c	Fri Mar 13 00:39:12 2009 -0700
    25.3 @@ -0,0 +1,603 @@
    25.4 +/* PC frontend for LOCKJAW, an implementation of the Soviet Mind Game
    25.5 +
    25.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    25.7 +
    25.8 +This work is free software; you can redistribute it and/or modify
    25.9 +it under the terms of the GNU General Public License as published by
   25.10 +the Free Software Foundation; either version 2 of the License, or
   25.11 +(at your option) any later version.
   25.12 +
   25.13 +This program is distributed in the hope that it will be useful,
   25.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   25.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   25.16 +GNU General Public License for more details.
   25.17 +
   25.18 +You should have received a copy of the GNU General Public License
   25.19 +along with this program; if not, write to the Free Software
   25.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   25.21 +
   25.22 +Original game concept and design by Alexey Pajitnov.
   25.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   25.24 +or The Tetris Company LLC.
   25.25 +
   25.26 +*/
   25.27 +#include "ljplay.h"
   25.28 +#include "options.h"
   25.29 +#include <stdio.h>
   25.30 +#ifndef NO_DATETIME
   25.31 +#include <time.h>
   25.32 +#endif
   25.33 +
   25.34 +#define USING_DEBRIEF_PAGE 1
   25.35 +#define MIN_RECTUM_ROWS 4
   25.36 +#define MIN_ZIGZAG_ROWS 2
   25.37 +
   25.38 +// On newlib, use a variant of sprintf that doesn't handle floating
   25.39 +// point formats. On other C libraries, use standard sprintf.
   25.40 +#ifdef HAS_FPU
   25.41 +#define siprintf sprintf
   25.42 +#ifndef HAS_FOPEN
   25.43 +#define HAS_FOPEN
   25.44 +#endif
   25.45 +#define DEBRIEF_TO_STDOUT
   25.46 +#endif
   25.47 +
   25.48 +#ifdef HAS_FOPEN
   25.49 +#include "ljpath.h"  // for ljfopen
   25.50 +#endif
   25.51 +
   25.52 +const char *const debriefBoolNames[2] = {
   25.53 +  "Off", "On"
   25.54 +};
   25.55 +
   25.56 +typedef struct TimeResultMSF {
   25.57 +  unsigned int pieces_100m;
   25.58 +  unsigned int garbage_100m;
   25.59 +  unsigned int minutes;
   25.60 +  unsigned char seconds;
   25.61 +  unsigned char frames;
   25.62 +} TimeResultMSF;
   25.63 +
   25.64 +void frames2msf(unsigned int gameTime,
   25.65 +                unsigned int nPieces,
   25.66 +                unsigned int outGarbage,
   25.67 +                TimeResultMSF *out) {
   25.68 +  unsigned int gameSeconds = gameTime / 60U;
   25.69 +  unsigned int gameMinutes = gameSeconds / 60U;
   25.70 +  out->pieces_100m = gameTime
   25.71 +                     ? 360000ULL * nPieces / gameTime
   25.72 +                     : 0;
   25.73 +  out->garbage_100m = gameTime
   25.74 +                      ? 360000ULL * outGarbage / gameTime
   25.75 +                      : 0;
   25.76 +  out->minutes = gameMinutes;
   25.77 +  out->seconds = gameSeconds - gameMinutes * 60U;
   25.78 +  out->frames = gameTime - gameSeconds * 60U;
   25.79 +}
   25.80 +
   25.81 +static unsigned int countBlocksLeft(const LJField *p) {
   25.82 +  unsigned int n = 0;
   25.83 +
   25.84 +  for (int y = 0; y < LJ_PF_HT; ++y) {
   25.85 +    for (int x = 0; x < LJ_PF_WID; ++x) {
   25.86 +      if (p->b[y][x]) {
   25.87 +        ++n;
   25.88 +      }
   25.89 +    }
   25.90 +  }
   25.91 +  return n;
   25.92 +}
   25.93 +
   25.94 +/**
   25.95 + * Calculates the number of rows starting at the bottom that
   25.96 + * conform to a zigzag pattern.
   25.97 + * @param p the playfield to test
   25.98 + * @return the number of rows successfully completed
   25.99 + */
  25.100 +unsigned int calcZigzagGrade(const LJField *p) {
  25.101 +  unsigned int hole = p->leftWall;
  25.102 +  int delta = 1;
  25.103 +
  25.104 +  for (size_t y = 0; y < p->ceiling; ++y) {
  25.105 +
  25.106 +    // invariant:
  25.107 +    // at this point, y equals the number of rows known to conform
  25.108 +    for (size_t x = p->leftWall; x < p->rightWall; ++x) {
  25.109 +      unsigned int blk = p->b[y][x];
  25.110 +
  25.111 +      // A block should be in all cells of the row
  25.112 +      // except for the hole cell, which should be empty.
  25.113 +      if (x == hole) {
  25.114 +
  25.115 +        // if there's a block where a hole should be, this row
  25.116 +        // doesn't conform, but all the rows below do conform
  25.117 +        if (blk) {
  25.118 +          return y;
  25.119 +        }
  25.120 +      } else {
  25.121 +
  25.122 +        // if there's a hole where a block should be, this row
  25.123 +        // doesn't conform, but all the rows below do conform
  25.124 +        if (!blk) {
  25.125 +          return y;
  25.126 +        }
  25.127 +      }
  25.128 +    }
  25.129 +
  25.130 +    // if this hole isn't covered up on the next row,
  25.131 +    // this row doesn't conform either
  25.132 +    // changed in 0.43 after a clarification from Kitaru
  25.133 +    if (!p->b[y + 1][hole]) {
  25.134 +      return y;
  25.135 +    }
  25.136 +
  25.137 +    // by now we know that the row conforms,
  25.138 +    // so move the hole for the next row
  25.139 +    if (hole == p->rightWall - 1) {
  25.140 +      delta = -1;
  25.141 +    } else if (hole == p->leftWall) {
  25.142 +      delta = 1;
  25.143 +    }
  25.144 +    hole += delta;
  25.145 +
  25.146 +  }
  25.147 +  return p->ceiling;
  25.148 +}
  25.149 +
  25.150 +
  25.151 +/**
  25.152 + * Calculates the number of rows in a playfield that were prepared
  25.153 + * for an I tetromino.
  25.154 + * Rule for this pattern:
  25.155 + * 1. Only one hole on the bottom row.
  25.156 + * 2. This column must be unoccupied below the ceiling.
  25.157 + * 3. Conforming rows must be full except for the hole.
  25.158 + *
  25.159 + * Easy test procedure for this pattern:
  25.160 + * Level 4 garbage
  25.161 + * @return the number of rows that conform to constraint 3, or 0 if the
  25.162 + * field does not conform to constraint 1 or 2.
  25.163 + */
  25.164 +unsigned int calcRectumGrade(const LJField *p) {
  25.165 +  unsigned int hole = LJ_PF_WID;
  25.166 +
  25.167 +  // search for the first hole on the bottom row
  25.168 +  for (unsigned int x = p->leftWall;
  25.169 +       x < p->rightWall;
  25.170 +       ++x) {
  25.171 +    if (!p->b[0][x]) {
  25.172 +      hole = x;
  25.173 +      break;
  25.174 +    }
  25.175 +  }
  25.176 +
  25.177 +  // If there is no hole in the bottom row, then 0 rows conform.
  25.178 +  // This shouldn't happen in standard smg because the line would be
  25.179 +  // cleared, but eventually LJ will support games other than SMG,
  25.180 +  // and checking the array bounds is O(1), so why not?
  25.181 +  if (hole >= p->rightWall) {
  25.182 +    return 0;
  25.183 +  }
  25.184 +
  25.185 +  // make sure that the row is clear through the whole visible
  25.186 +  // portion of the playfield
  25.187 +  for (unsigned int y = 0; y < p->ceiling; ++y) {
  25.188 +
  25.189 +    // If this column isn't empty, the whole field doesn't conform.
  25.190 +    if (p->b[y][hole]) {
  25.191 +      return 0;
  25.192 +    }
  25.193 +  }
  25.194 +
  25.195 +  for (unsigned int y = 0; y < p->ceiling; ++y) {
  25.196 +
  25.197 +    // At this point, the bottom y rows conform.
  25.198 +    for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
  25.199 +
  25.200 +      // Disregarding the hole column, if there's an empty cell
  25.201 +      // in this row, then this row does not conform.
  25.202 +      if (x != hole && !p->b[y][x]) {
  25.203 +        return y;
  25.204 +      }
  25.205 +    }
  25.206 +  }
  25.207 +  return p->ceiling;
  25.208 +}
  25.209 +
  25.210 +/**
  25.211 + * Converts a rank number (0-19) to a text rank.
  25.212 + * 0-9: 10-1 kyu; 10-99: 1-90 dan; 100+: grandmaster
  25.213 + * @return number of characters written
  25.214 + */
  25.215 +static size_t printSecretGrade(char *dst, int rank) {
  25.216 +  if (rank >= 100) {
  25.217 +    return siprintf(dst, "GM");
  25.218 +  } else if (rank >= 10) {
  25.219 +    return siprintf(dst, "S%d", rank - 9);
  25.220 +  } else {
  25.221 +    return siprintf(dst, "%d", 10 - rank);
  25.222 +  }
  25.223 +}
  25.224 +
  25.225 +static size_t printFracG(char *dst, unsigned int value) {
  25.226 +  if (value == 0) {
  25.227 +    return siprintf(dst, "Instant");
  25.228 +  } else if (value == 1) {
  25.229 +    return siprintf(dst, "1G");
  25.230 +  } else {
  25.231 +    return siprintf(dst, "1/%dG", value);
  25.232 +  }
  25.233 +}
  25.234 +
  25.235 +#include <string.h>  // remove once fourcc transition is complete
  25.236 +static size_t printFourCC(char *dst, FourCC f) {
  25.237 +  const char *data = ljGetFourCCName(f);
  25.238 +  if (data) {
  25.239 +    int pos = 0;
  25.240 +    for(pos = 0; *data; ++pos) {
  25.241 +      dst[pos] = *data++;
  25.242 +    }
  25.243 +    return pos;
  25.244 +  } else {
  25.245 +    int pos = 0;
  25.246 +    for(pos = 0; f.c[pos] && pos < 4; ++pos) {
  25.247 +      dst[pos] = f.c[pos];
  25.248 +    }
  25.249 +    return pos;
  25.250 +  }
  25.251 +}
  25.252 +
  25.253 +size_t buildOptionsReportPage(char *dst, const LJView *v) {
  25.254 +  size_t pos = 0;
  25.255 +  
  25.256 +
  25.257 +#if USING_DEBRIEF_PAGE
  25.258 +  const LJField *p = v->field;
  25.259 +
  25.260 +  pos += siprintf(dst + pos,
  25.261 +                  "Options\n\n"
  25.262 +                  "Well: %dx%d%s, Enter: %s\n"
  25.263 +                  "Speed: ",
  25.264 +                  p->rightWall - p->leftWall,
  25.265 +                  p->ceiling,
  25.266 +                  v->hidePF ? " invisible" : "",
  25.267 +                  p->enterAbove ? "Above" : "Below");
  25.268 +  pos += printFourCC(dst + pos, 
  25.269 +             optionsSpeedCurveNames[(size_t)p->speedState.curve]);
  25.270 +  pos += siprintf(dst + pos,
  25.271 +                  ", ARE: %d ms\n"
  25.272 +                  "Randomizer: ",
  25.273 +                  v->field->areStyle * 50 / 3);
  25.274 +  pos += printFourCC(dst + pos, 
  25.275 +                     optionsRandNames[(size_t)p->randomizer]);
  25.276 +  pos += siprintf(dst + pos, " of ");
  25.277 +  pos += printFourCC(dst + pos, 
  25.278 +                     optionsPieceSetNames[(size_t)p->pieceSet]);
  25.279 +  pos += siprintf(dst + pos, "\nHold: ");
  25.280 +  pos += printFourCC(dst + pos, 
  25.281 +                     optionsHoldStyleNames[(size_t)p->holdStyle]);
  25.282 +  pos += siprintf(dst + pos, ", Rotation: ");
  25.283 +  pos += printFourCC(dst + pos, 
  25.284 +                     optionsRotNames[(size_t)p->rotationSystem]);
  25.285 +  if (p->maxUpwardKicks < 20) {
  25.286 +    pos += siprintf(dst + pos,
  25.287 +                    " %d FK",
  25.288 +                    (int)p->maxUpwardKicks);
  25.289 +  }
  25.290 +  if (v->control->initialRotate) {
  25.291 +    pos += siprintf(dst + pos, "+initial");
  25.292 +  }
  25.293 +
  25.294 +  pos += siprintf(dst + pos,
  25.295 +                  "\nLock: ");
  25.296 +  if (p->setLockDelay >= 128) {
  25.297 +    pos += siprintf(dst + pos, "Never");
  25.298 +  } else {
  25.299 +    if (p->setLockDelay > 0) {
  25.300 +      pos += siprintf(dst + pos,
  25.301 +                       "%d ms ",
  25.302 +                       p->setLockDelay * 50 / 3);
  25.303 +    }
  25.304 +    pos += printFourCC(dst + pos, 
  25.305 +                       optionsLockdownNames[(size_t)p->lockReset]);
  25.306 +  }
  25.307 +  pos += siprintf(dst + pos,
  25.308 +                   ", Deep: %s\n",
  25.309 +                   debriefBoolNames[p->bottomBlocks]);
  25.310 +
  25.311 +  pos += siprintf(dst + pos,
  25.312 +                  "Line clear: %d ms ",
  25.313 +                  p->speed.lineDelay * 50 / 3);
  25.314 +  pos += printFourCC(dst + pos,
  25.315 +                     optionsGravNames[(size_t)p->clearGravity]);
  25.316 +  pos += siprintf(dst + pos,
  25.317 +                  ", Gluing: ");
  25.318 +  pos += printFourCC(dst + pos,
  25.319 +                     optionsGluingNames[(size_t)p->gluing]);
  25.320 +  pos += siprintf(dst + pos,
  25.321 +                  "\nDrop score: ");
  25.322 +  pos += printFourCC(dst + pos,
  25.323 +                optionsDropScoringNames[(size_t)p->dropScoreStyle]);
  25.324 +  pos += siprintf(dst + pos,
  25.325 +                  ", T-spin: ");
  25.326 +  pos += printFourCC(dst + pos,
  25.327 +                     optionsTspinNames[(size_t)p->tSpinAlgo]);
  25.328 +  pos += siprintf(dst + pos,
  25.329 +                  "\nGarbage: ");
  25.330 +  pos += printFourCC(dst + pos,
  25.331 +                     optionsGarbageNames[(size_t)p->garbageStyle]);
  25.332 +  pos += siprintf(dst + pos,
  25.333 +                   ", DAS: %d ms ",
  25.334 +                   v->control->dasDelay * 50 / 3);
  25.335 +  pos += printFracG(dst + pos, v->control->dasSpeed);
  25.336 +  pos += siprintf(dst + pos,
  25.337 +                   "\nSoft drop: ");
  25.338 +  pos += printFracG(dst + pos, v->control->softDropSpeed + 1);
  25.339 +  dst[pos++] = ' ';
  25.340 +  pos += printFourCC(dst + pos,
  25.341 +                     optionsZangiNames[v->control->softDropLock]);
  25.342 +  pos += siprintf(dst + pos,
  25.343 +                  ", Hard drop: ");
  25.344 +  pos += printFourCC(dst + pos,
  25.345 +                     optionsZangiNames[v->control->hardDropLock]);
  25.346 +  pos += siprintf(dst + pos, "\nShadow: ");
  25.347 +  pos += printFourCC(dst + pos,
  25.348 +                     optionsShadowNames[v->hideShadow]);
  25.349 +  pos += siprintf(dst + pos,
  25.350 +                  ", Next: %d\n",
  25.351 +                  v->nextPieces);
  25.352 +#else
  25.353 +  pos += siprintf(dst, "\n\nDebrief disabled.\n");
  25.354 +#endif
  25.355 +
  25.356 +  dst[pos] = 0;
  25.357 +  return pos;
  25.358 +}
  25.359 +
  25.360 +#ifdef DEBRIEF_SHORT
  25.361 +static const char *const naiveGravity[8] = {
  25.362 +  "1L", "2L", "3L", "4L",
  25.363 +  "T1", "T2", "T3"
  25.364 +};
  25.365 +static const char *const cascadeGravity[8] = {
  25.366 +  "1L", "2L", "3L", "4L",
  25.367 +  "5L", "6L", "7L", "8L+"
  25.368 +};
  25.369 +#else
  25.370 +static const char *const naiveGravity[8] = {
  25.371 +  "single", "double", "triple", "home run",
  25.372 +  "T single", "T double", "T triple"
  25.373 +};
  25.374 +static const char *const cascadeGravity[8] = {
  25.375 +  "single", "double", "triple", "quad",
  25.376 +  "5L", "6L", "7L", "8L+"
  25.377 +};
  25.378 +#endif
  25.379 +
  25.380 +static size_t printLineCounts(char *dst, const LJField *p) {
  25.381 +  size_t pos = 0;
  25.382 +  const char *const *names = (p->clearGravity == LJGRAV_NAIVE)
  25.383 +                             ? naiveGravity : cascadeGravity;
  25.384 +  dst[pos++] = '(';
  25.385 +  for (int i = 0; i < 8; ++i) {
  25.386 +    if (names[i]) {
  25.387 +      pos += siprintf(dst + pos,
  25.388 +                      "%s%s: %u",
  25.389 +                      i ? "; " : "",
  25.390 +                      names[i],
  25.391 +                      p->nLineClears[i]);
  25.392 +    }
  25.393 +  }
  25.394 +  dst[pos++] = ')';
  25.395 +  dst[pos++] = '\n';
  25.396 +  return pos;
  25.397 +}
  25.398 +
  25.399 +
  25.400 +size_t buildDebriefPage(char *dst, const LJView *v) {
  25.401 +  size_t pos = 0;
  25.402 +
  25.403 +#if USING_DEBRIEF_PAGE
  25.404 +#ifndef NO_DATETIME
  25.405 +  const time_t finishTimeUNIX = time(NULL);
  25.406 +  const struct tm *finishTime = localtime(&finishTimeUNIX);
  25.407 +  char finishTimeStr[64];
  25.408 +  
  25.409 +  /* I would have used
  25.410 +  strftime(finishTimeStr, sizeof(finishTimeStr), "%Y-%m-%d at %H:%M", finishTime);
  25.411 +  but it brings in the floating-point library on GBA/DS. */
  25.412 +  siprintf(finishTimeStr,
  25.413 +           "%04d-%02d-%02d at %02d:%02d",
  25.414 +           finishTime->tm_year + 1900,
  25.415 +           finishTime->tm_mon + 1, finishTime->tm_mday,
  25.416 +           finishTime->tm_hour, finishTime->tm_min);
  25.417 +#endif
  25.418 +
  25.419 +  const LJField *p = v->field;
  25.420 +  unsigned long int keys_100m = p->nPieces
  25.421 +                                ? 100U * v->control->presses / p->nPieces
  25.422 +                                : 666;
  25.423 +  TimeResultMSF gameMSF, activeMSF;
  25.424 +  const char *wordPieces = (p->nPieces != 1) ? "tetrominoes" : "tetromino";
  25.425 +  if (p->pieceSet == LJRAND_234BLK) {
  25.426 +    wordPieces = (p->nPieces != 1) ? "pieces" : "piece";
  25.427 +  }
  25.428 +  
  25.429 +  /* Secret grades */
  25.430 +  unsigned int nBlocksLeft = countBlocksLeft(p);
  25.431 +  unsigned int nZigzagRows = calcZigzagGrade(p);
  25.432 +  unsigned int nRectumRows = calcRectumGrade(p);
  25.433 +
  25.434 +  pos += siprintf(dst + pos,
  25.435 +                   "Result:\n\n%s ",
  25.436 +                   v->control->countdown <= 0 ? "Cleared" : "Stopped");
  25.437 +  pos += printFourCC(dst + pos, gimmickNames[p->gimmick]);
  25.438 +  pos += siprintf(dst + pos,
  25.439 +                  " at level %d\n",
  25.440 +                  p->speedState.level);
  25.441 +#if !defined(NO_DATETIME) || defined(WITH_REPLAY)
  25.442 +#ifndef NO_DATETIME
  25.443 +  pos += siprintf(dst + pos, "on %s", finishTimeStr);
  25.444 +#endif
  25.445 +#ifdef WITH_REPLAY
  25.446 +  if (p->reloaded) {
  25.447 +    pos += siprintf(dst + pos, " from saved state");
  25.448 +  }
  25.449 +#endif
  25.450 +  dst[pos++] = '\n';
  25.451 +#endif
  25.452 +
  25.453 +  frames2msf(p->gameTime, p->nPieces, p->outGarbage, &gameMSF);
  25.454 +  frames2msf(p->activeTime, p->nPieces, p->outGarbage, &activeMSF);
  25.455 +
  25.456 +  pos += siprintf(dst + pos,
  25.457 +                   "Played %d %s in %u:%02u.%02u (%u.%02u/min)\n",
  25.458 +                   p->nPieces,
  25.459 +                   wordPieces,
  25.460 +                   gameMSF.minutes,
  25.461 +                   (unsigned int)gameMSF.seconds,
  25.462 +                   gameMSF.frames * 5U / 3U,
  25.463 +                   (unsigned int) (gameMSF.pieces_100m / 100),
  25.464 +                   (unsigned int) (gameMSF.pieces_100m % 100));
  25.465 +  pos += siprintf(dst + pos,
  25.466 +                   "(active time only: %u:%02u.%02u, %u.%02u/min)\n",
  25.467 +                   activeMSF.minutes,
  25.468 +                   (unsigned int)activeMSF.seconds,
  25.469 +                   activeMSF.frames * 5U / 3U,
  25.470 +                   (unsigned int) (activeMSF.pieces_100m / 100),
  25.471 +                   (unsigned int) (activeMSF.pieces_100m % 100));
  25.472 +  pos += siprintf(dst + pos,
  25.473 +                   "Pressed %u keys (%u.%02u/piece)\n\n",
  25.474 +                   v->control->presses,
  25.475 +                   (unsigned int) (keys_100m / 100),
  25.476 +                   (unsigned int) (keys_100m % 100));
  25.477 +
  25.478 +  pos += siprintf(dst + pos,
  25.479 +                  "Made %u lines",
  25.480 +                  p->lines);
  25.481 +  if (p->gluing == LJGLUING_SQUARE) {
  25.482 +    pos += siprintf(dst + pos,
  25.483 +                    " and %u pure + %u squares",
  25.484 +                    p->monosquares, p->multisquares);
  25.485 +  }
  25.486 +  dst[pos++] = '\n';
  25.487 +  pos += printLineCounts(dst + pos, p);
  25.488 +  pos += siprintf(dst + pos,
  25.489 +                   "Sent %u garbage (%u.%02u per minute)\n",
  25.490 +                   p->outGarbage,
  25.491 +                   (unsigned int) (gameMSF.garbage_100m / 100),
  25.492 +                   (unsigned int) (gameMSF.garbage_100m % 100));
  25.493 +  if (nZigzagRows >= MIN_ZIGZAG_ROWS) {
  25.494 +    pos += siprintf(dst + pos,
  25.495 +                    "Made %u rows of > for grade ",
  25.496 +                    nZigzagRows);
  25.497 +    pos += printSecretGrade(dst + pos,
  25.498 +                            nZigzagRows >= 19 ? 100 : nZigzagRows);
  25.499 +  } else if (nRectumRows >= MIN_RECTUM_ROWS) {
  25.500 +    pos += siprintf(dst + pos,
  25.501 +                    "Made %u-row rectum for grade ",
  25.502 +                    nRectumRows);
  25.503 +    pos += printSecretGrade(dst + pos,
  25.504 +                            nRectumRows >= 20 ? 100 : nRectumRows - 1);
  25.505 +  } else if (nBlocksLeft >= 110 && nBlocksLeft <= 111) {
  25.506 +    pos += siprintf(dst + pos,
  25.507 +                    "Secret grade: Eleventy%s",
  25.508 +                    (nBlocksLeft & 1) ? "-one" : "");
  25.509 +  } else {
  25.510 +    pos += siprintf(dst + pos,
  25.511 +                    "Left %d blocks behind",
  25.512 +                    nBlocksLeft);
  25.513 +  }
  25.514 +  dst[pos++] = '\n';
  25.515 +  dst[pos++] = '\n';
  25.516 +  pos += printFourCC(dst + pos, optionsScoringNames[p->scoreStyle]);
  25.517 +
  25.518 +  pos += siprintf(dst + pos,
  25.519 +                   " score: %d\n",
  25.520 +                   p->score);
  25.521 +  
  25.522 +      /*
  25.523 +      tod stats not in lj
  25.524 +
  25.525 +        points/line, 40 lines score, silver squares, gold squares
  25.526 +        
  25.527 +        */
  25.528 +
  25.529 +#else
  25.530 +  pos += siprintf(dst, "\n\nDebrief disabled. Score: %u\n", v->field->score);
  25.531 +#endif
  25.532 +  return pos;
  25.533 +}
  25.534 +
  25.535 +void debriefDrawPage(const char *page, size_t pageNumber);
  25.536 +LJBits debriefHandleKeys(void);
  25.537 +extern volatile char redrawWholeScreen;
  25.538 +
  25.539 +
  25.540 +/**
  25.541 + * Reports the player's performance.
  25.542 + */
  25.543 +void debrief(LJView *v) {
  25.544 +  char pageData[2000];  // current length is under 1000 chars
  25.545 +  char *page[2] = {pageData, "Page coming soon!"};
  25.546 +  int curPage = 0;
  25.547 +  
  25.548 +  {
  25.549 +    size_t pos;
  25.550 +
  25.551 +    pos = buildDebriefPage(page[0], v);
  25.552 +    page[1] = page[0] + (++pos);
  25.553 +    pos += buildOptionsReportPage(page[1], v);
  25.554 +  }
  25.555 +#ifdef HAS_FOPEN
  25.556 +  FILE *logFile = ljfopen("lj-scores.txt", "at");
  25.557 +  if (logFile) {
  25.558 +    fputs("\n\n\n", logFile);
  25.559 +    fputs(page[0], logFile);
  25.560 +    fputs(page[1], logFile);
  25.561 +#ifdef DEBRIEF_TO_STDOUT
  25.562 +    fputs(page[0], stdout);
  25.563 +    fputs(page[1], stdout);
  25.564 +#endif
  25.565 +    fclose(logFile);
  25.566 +  }
  25.567 +#endif
  25.568 +
  25.569 +  LJBits lastKeys = ~0;
  25.570 +  redrawWholeScreen = 1;
  25.571 +  debriefHandleKeys();  // call once to clear key buffer
  25.572 +
  25.573 +  for (;;) {
  25.574 +    LJBits sounds = 0;
  25.575 +
  25.576 +    if (redrawWholeScreen) {
  25.577 +      redrawWholeScreen = 0;
  25.578 +      debriefDrawPage(page[curPage], curPage);
  25.579 +    }
  25.580 +    LJBits keys = debriefHandleKeys();
  25.581 +    LJBits newKeys = keys & ~lastKeys;
  25.582 +
  25.583 +    if (newKeys & VKEY_LEFT) {
  25.584 +      if (curPage > 0) {
  25.585 +        curPage -= 1;
  25.586 +        redrawWholeScreen = 1;
  25.587 +        sounds |= LJSND_HOLD;
  25.588 +      }
  25.589 +    }
  25.590 +    
  25.591 +    if (newKeys & VKEY_RIGHT) {
  25.592 +      if (curPage + 1 < sizeof(page) / sizeof(page[0])) {
  25.593 +        curPage += 1;
  25.594 +        redrawWholeScreen = 1;
  25.595 +        sounds |= LJSND_HOLD;
  25.596 +      }
  25.597 +    }
  25.598 +    
  25.599 +    if (newKeys & (VKEY_ROTR | VKEY_ROTL)) {
  25.600 +      break;
  25.601 +    } else {
  25.602 +      lastKeys = keys;
  25.603 +    }
  25.604 +    playSoundEffects(v, sounds, 100);
  25.605 +  }
  25.606 +}
    26.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    26.2 +++ b/src/dsarm7.c	Fri Mar 13 00:39:12 2009 -0700
    26.3 @@ -0,0 +1,200 @@
    26.4 +/*---------------------------------------------------------------------------------
    26.5 +
    26.6 +	LOCKJAW Tetromino Game: ARM7 core
    26.7 +
    26.8 +	Copyright (C) 2005-2007
    26.9 +		Damian Yerrick (tepples)
   26.10 +
   26.11 +	based on
   26.12 +	default ARM7 core
   26.13 +
   26.14 +	Copyright (C) 2005-2007
   26.15 +		Michael Noland (joat)
   26.16 +		Jason Rogers (dovoto)
   26.17 +		Dave Murphy (WinterMute)
   26.18 +
   26.19 +	This software is provided 'as-is', without any express or implied
   26.20 +	warranty.  In no event will the authors be held liable for any
   26.21 +	damages arising from the use of this software.
   26.22 +
   26.23 +	Permission is granted to anyone to use this software for any
   26.24 +	purpose, including commercial applications, and to alter it and
   26.25 +	redistribute it freely, subject to the following restrictions:
   26.26 +
   26.27 +	1.	The origin of this software must not be misrepresented; you
   26.28 +		must not claim that you wrote the original software. If you use
   26.29 +		this software in a product, an acknowledgment in the product
   26.30 +		documentation would be appreciated but is not required.
   26.31 +	2.	Altered source versions must be plainly marked as such, and
   26.32 +		must not be misrepresented as being the original software.
   26.33 +	3.	This notice may not be removed or altered from any source
   26.34 +		distribution.
   26.35 +
   26.36 +---------------------------------------------------------------------------------*/
   26.37 +#include <stdlib.h>
   26.38 +#include <nds.h>
   26.39 +#include "talkback.h"  // custom talkback
   26.40 +
   26.41 +void ljSoundEffects(unsigned long int sounds,
   26.42 +                           int countdown);
   26.43 +void ljMusic(void);
   26.44 +extern int order;
   26.45 +extern char ticksLeft;
   26.46 +extern char row;
   26.47 +extern char musicPaused;
   26.48 +
   26.49 +
   26.50 +
   26.51 +
   26.52 +void powerOffDS(void)
   26.53 +{
   26.54 +  writePowerManagement(PM_CONTROL_REG, 1 << 6);//6 DS power (0: on, 1: shut down!)
   26.55 +}
   26.56 +
   26.57 +void doSleep(void) {
   26.58 +
   26.59 +  // Check if the lid has been closed.
   26.60 +  if(IPC->buttons & BIT(7)) {
   26.61 +    // Save the current interrupt sate.
   26.62 +    u32 ie_save = REG_IE;
   26.63 +    // Turn the speaker down.
   26.64 +    swiChangeSoundBias(0,0x400);
   26.65 +    // Save current power state.
   26.66 +    int power = readPowerManagement(PM_CONTROL_REG);
   26.67 +    // Set sleep LED.
   26.68 +    writePowerManagement(PM_CONTROL_REG, PM_LED_CONTROL(1));
   26.69 +    // Register for the lid interrupt.
   26.70 +    REG_IE = IRQ_LID;
   26.71 +   
   26.72 +    // Power down till we get our interrupt.
   26.73 +    swiSleep(); //waits for PM (lid open) interrupt
   26.74 +   
   26.75 +    REG_IF = ~0;
   26.76 +    // Restore the interrupt state.
   26.77 +    REG_IE = ie_save;
   26.78 +    // Restore power state.
   26.79 +    writePowerManagement(PM_CONTROL_REG, power);
   26.80 +   
   26.81 +    // Turn the speaker up.
   26.82 +    swiChangeSoundBias(1,0x400);
   26.83 +    IPC->buttons &= ~BIT(7);
   26.84 +  }
   26.85 +}
   26.86 +
   26.87 +void VblankHandler(void) {
   26.88 +
   26.89 +  doSleep();
   26.90 +
   26.91 +  volatile P8A7Talkback *tb = (P8A7Talkback *)IPC->soundData;
   26.92 +  if (tb) {
   26.93 +      int cmd = tb->cmd;
   26.94 +      tb->cmd = 0;
   26.95 +
   26.96 +      switch (cmd) {
   26.97 +      case TALKBACK_POWER_OFF:
   26.98 +        powerOFF(POWER_SOUND);
   26.99 +        powerOffDS();
  26.100 +        swiWaitForVBlank();
  26.101 +        break;
  26.102 +      case TALKBACK_PLAY_MUSIC:
  26.103 +        musicPaused = 0;
  26.104 +        break;
  26.105 +      case TALKBACK_STOP_MUSIC:
  26.106 +        musicPaused = 1;
  26.107 +        order = 0;
  26.108 +        row = 0;
  26.109 +        ticksLeft = 1;
  26.110 +        break;
  26.111 +      case TALKBACK_PAUSE_MUSIC:
  26.112 +        musicPaused = 1;
  26.113 +      }
  26.114 +      unsigned long int fx = tb->sounds;
  26.115 +      tb->sounds = 0;
  26.116 +      
  26.117 +      ljSoundEffects(fx, tb->countdown);
  26.118 +  }
  26.119 +
  26.120 +  ljMusic();
  26.121 +
  26.122 +}
  26.123 +
  26.124 +
  26.125 +
  26.126 +// All below this is wintermute's and may need to be replaced
  26.127 +// with new versions of libnds
  26.128 +
  26.129 +
  26.130 +touchPosition first,tempPos;
  26.131 +
  26.132 +//---------------------------------------------------------------------------------
  26.133 +void VcountHandler() {
  26.134 +//---------------------------------------------------------------------------------
  26.135 +	static int lastbut = -1;
  26.136 +	
  26.137 +	uint16 but=0, x=0, y=0, xpx=0, ypx=0, z1=0, z2=0;
  26.138 +
  26.139 +	but = REG_KEYXY;
  26.140 +
  26.141 +	if (!( (but ^ lastbut) & (1<<6))) {
  26.142 + 
  26.143 +		tempPos = touchReadXY();
  26.144 +
  26.145 +		if ( tempPos.x == 0 || tempPos.y == 0 ) {
  26.146 +			but |= (1 <<6);
  26.147 +			lastbut = but;
  26.148 +		} else {
  26.149 +			x = tempPos.x;
  26.150 +			y = tempPos.y;
  26.151 +			xpx = tempPos.px;
  26.152 +			ypx = tempPos.py;
  26.153 +			z1 = tempPos.z1;
  26.154 +			z2 = tempPos.z2;
  26.155 +		}
  26.156 +		
  26.157 +	} else {
  26.158 +		lastbut = but;
  26.159 +		but |= (1 <<6);
  26.160 +	}
  26.161 +
  26.162 +	IPC->touchX			= x;
  26.163 +	IPC->touchY			= y;
  26.164 +	IPC->touchXpx		= xpx;
  26.165 +	IPC->touchYpx		= ypx;
  26.166 +	IPC->touchZ1		= z1;
  26.167 +	IPC->touchZ2		= z2;
  26.168 +	IPC->buttons		= but;
  26.169 +
  26.170 +}
  26.171 +
  26.172 +
  26.173 +
  26.174 +//---------------------------------------------------------------------------------
  26.175 +int main(int argc, char ** argv) {
  26.176 +//---------------------------------------------------------------------------------
  26.177 +
  26.178 +    // set up custom ipc struct
  26.179 +    IPC->soundData = 0;
  26.180 +
  26.181 +	// read User Settings from firmware
  26.182 +	readUserSettings();
  26.183 +
  26.184 +	//enable sound
  26.185 +	powerON(POWER_SOUND);
  26.186 +	writePowerManagement(PM_CONTROL_REG, ( readPowerManagement(PM_CONTROL_REG) & ~PM_SOUND_MUTE & ~(1 << 6) ) | PM_SOUND_AMP );
  26.187 +	SOUND_CR = SOUND_ENABLE | SOUND_VOL(0x7F);
  26.188 +
  26.189 +	irqInit();
  26.190 +
  26.191 +	// Start the RTC tracking IRQ
  26.192 +	initClockIRQ();
  26.193 +
  26.194 +	SetYtrigger(80);
  26.195 +	irqSet(IRQ_VCOUNT, VcountHandler);
  26.196 +	irqSet(IRQ_VBLANK, VblankHandler);
  26.197 +
  26.198 +
  26.199 +	irqEnable( IRQ_VBLANK | IRQ_VCOUNT);
  26.200 +
  26.201 +	// Keep the ARM7 mostly idle
  26.202 +	while (1) swiWaitForVBlank();
  26.203 +}
    27.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    27.2 +++ b/src/dsdebrief.c	Fri Mar 13 00:39:12 2009 -0700
    27.3 @@ -0,0 +1,77 @@
    27.4 +/*
    27.5 +GBA/DS debrief code for LOCKJAW Tetromino Game
    27.6 +
    27.7 +Copr. 2008 Damian Yerrick
    27.8 +also GPL
    27.9 +
   27.10 +*/
   27.11 +#include <stdio.h>
   27.12 +#ifdef ARM9
   27.13 +#include "ljds.h"
   27.14 +#define vwfOptions vwfTouch
   27.15 +#else
   27.16 +#include "ljgba.h"
   27.17 +#define vwfOptions vwfTop
   27.18 +#endif
   27.19 +#include "fontdraw.h"
   27.20 +
   27.21 +void debriefDrawPage(const char *page, size_t pageNumber) {
   27.22 +  int y = 0;
   27.23 +  char line[256];
   27.24 +  int linePos = 0;
   27.25 +  int done = 0;
   27.26 +  int scrW = vwfOptions.width * 8;
   27.27 +  int scrH = vwfOptions.height * 8;
   27.28 +
   27.29 +#ifdef ARM9
   27.30 +  BG_PALETTE_SUB[0] = RGB5(31,31,31);
   27.31 +  BG_PALETTE_SUB[1] = RGB5(21,21,21);
   27.32 +  BG_PALETTE_SUB[2] = RGB5(0, 0, 0);
   27.33 +  BG_PALETTE_SUB[3] = RGB5(0, 0, 0);
   27.34 +  videoSetModeSub(MODE_0_2D
   27.35 +                 | DISPLAY_BG0_ACTIVE);
   27.36 +#else
   27.37 +  BG_PALETTE[0] = RGB5(31,31,31);
   27.38 +  BG_PALETTE[1] = RGB5(21,21,21);
   27.39 +  BG_PALETTE[2] = RGB5(0, 0, 0);
   27.40 +  BG_PALETTE[3] = RGB5(0, 0, 0);
   27.41 +  REG_DISPCNT = 0 | BG0_ON;
   27.42 +#endif
   27.43 +  vwfWinInit(&vwfOptions);
   27.44 +  vwfPuts(&vwfOptions, "GAME OVER", (scrW - 64) / 2, y);
   27.45 +  siprintf(line, "Page %u", (unsigned int)pageNumber + 1);
   27.46 +  vwfPuts(&vwfOptions, line, scrW - 40, y);
   27.47 +  vwfPuts(&vwfOptions, "Left/Right: change; Rotate: close",
   27.48 +          4, scrH - 12);
   27.49 +
   27.50 +  while (!done && y <= scrH - 24) {
   27.51 +    int c = *page++;
   27.52 +
   27.53 +    // Break at newline and at end of text
   27.54 +    if (c == '\n' || c == 0) {
   27.55 +
   27.56 +      // Terminate the line of text and print it
   27.57 +      line[linePos] = 0;
   27.58 +      vwfPuts(&vwfOptions, line, 4, y);
   27.59 +
   27.60 +      y += linePos ? 12 : 6;  // Line feed
   27.61 +      linePos = 0;  // Carriage return
   27.62 +    } else {
   27.63 +      if (linePos + 2 < sizeof(line)) {
   27.64 +        line[linePos++] = c;
   27.65 +      }
   27.66 +    }
   27.67 +    if (c == 0) {
   27.68 +      done = 1;
   27.69 +    }
   27.70 +  }
   27.71 +}
   27.72 +
   27.73 +LJBits debriefHandleKeys(void) {
   27.74 +  int j = readPad(0);
   27.75 +  if (j & (KEY_START << 16)) {
   27.76 +    j |= VKEY_ROTR;
   27.77 +  }
   27.78 +  vsync();
   27.79 +  return j;
   27.80 +}
    28.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    28.2 +++ b/src/dsjoy.c	Fri Mar 13 00:39:12 2009 -0700
    28.3 @@ -0,0 +1,48 @@
    28.4 +#include "ljcontrol.h"
    28.5 +#ifdef ARM9
    28.6 +#include <nds.h>
    28.7 +#else
    28.8 +#include <gba.h>
    28.9 +#endif
   28.10 +
   28.11 +LJBits readHWKeys(void);
   28.12 +
   28.13 +LJBits readPad(unsigned int player) {
   28.14 +  LJBits hwKeys = readHWKeys();
   28.15 +  LJBits out = 0;
   28.16 +  
   28.17 +  if (hwKeys & KEY_UP) {
   28.18 +    out |= VKEY_UP;
   28.19 +  }
   28.20 +  if (hwKeys & KEY_DOWN) {
   28.21 +    out |= VKEY_DOWN;
   28.22 +  }
   28.23 +  if (hwKeys & KEY_LEFT) {
   28.24 +    out |= VKEY_LEFT;
   28.25 +  }
   28.26 +  if (hwKeys & KEY_RIGHT) {
   28.27 +    out |= VKEY_RIGHT;
   28.28 +  }
   28.29 +  if (hwKeys & KEY_B) {
   28.30 +    out |= VKEY_ROTL;
   28.31 +  }
   28.32 +  if (hwKeys & KEY_A) {
   28.33 +    out |= VKEY_ROTR;
   28.34 +  }
   28.35 +  if (hwKeys & (KEY_L | KEY_R)) {
   28.36 +    out |= VKEY_HOLD;
   28.37 +  }
   28.38 +
   28.39 +#ifdef ARM9
   28.40 +  if (hwKeys & KEY_X) {
   28.41 +    out |= VKEY_MACRO(3);
   28.42 +  }
   28.43 +  if (hwKeys & KEY_Y) {
   28.44 +    out |= VKEY_MACRO(2);
   28.45 +  }
   28.46 +#endif
   28.47 +
   28.48 +  // on the GBA and DS, we need to add the console buttons in
   28.49 +  out |= hwKeys << 16;
   28.50 +  return out;
   28.51 +}
    29.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    29.2 +++ b/src/dssleep.c	Fri Mar 13 00:39:12 2009 -0700
    29.3 @@ -0,0 +1,35 @@
    29.4 +#include <nds.h>
    29.5 +
    29.6 +/* 
    29.7 +by Mighty Max and melw,
    29.8 +after http://forum.gbadev.org/viewtopic.php?t=12011
    29.9 +*/
   29.10 +
   29.11 +int needLidSleep(void) {
   29.12 +  // when reading keys
   29.13 +  if (IPC->buttons & 0x0080) {
   29.14 +    /* hinge is closed */
   29.15 +    
   29.16 +    /* Step 1: Turn off all interrupts but vblank for waking up */
   29.17 +    unsigned long oldIE = REG_IE;
   29.18 +    REG_IE = IRQ_VBLANK;
   29.19 +
   29.20 +    /* Step 2: Power off most of the system */
   29.21 +    powerOFF(POWER_LCD);
   29.22 +
   29.23 +    /* Step 3: Wait one vblank at a time until the ARM7 tells us
   29.24 +       that the lid is no longer closed */
   29.25 +    while (IPC->buttons & 0x0080) {
   29.26 +      swiWaitForVBlank();
   29.27 +    }
   29.28 +
   29.29 +    /* Step 4: Wait a bit more (necessary?) */
   29.30 +    swiWaitForVBlank();
   29.31 +
   29.32 +    /* Step 5: Restore old machine state */
   29.33 +    powerON(POWER_LCD);
   29.34 +    REG_IE = oldIE;
   29.35 +    return 1;
   29.36 +  }
   29.37 +  return 0;
   29.38 +}
    30.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    30.2 +++ b/src/dssound7.c	Fri Mar 13 00:39:12 2009 -0700
    30.3 @@ -0,0 +1,429 @@
    30.4 +/*---------------------------------------------------------------------------------
    30.5 +
    30.6 +	LOCKJAW Tetromino Game: ARM7 sound code
    30.7 +
    30.8 +	based on
    30.9 +	default ARM7 core
   30.10 +
   30.11 +	Copyright (C) 2007
   30.12 +		Damian Yerrick (tepples)
   30.13 +
   30.14 +	This software is provided 'as-is', without any express or implied
   30.15 +	warranty.  In no event will the authors be held liable for any
   30.16 +	damages arising from the use of this software.
   30.17 +
   30.18 +	Permission is granted to anyone to use this software for any
   30.19 +	purpose, including commercial applications, and to alter it and
   30.20 +	redistribute it freely, subject to the following restrictions:
   30.21 +
   30.22 +	1.	The origin of this software must not be misrepresented; you
   30.23 +		must not claim that you wrote the original software. If you use
   30.24 +		this software in a product, an acknowledgment in the product
   30.25 +		documentation would be appreciated but is not required.
   30.26 +	2.	Altered source versions must be plainly marked as such, and
   30.27 +		must not be misrepresented as being the original software.
   30.28 +	3.	This notice may not be removed or altered from any source
   30.29 +		distribution.
   30.30 +
   30.31 +---------------------------------------------------------------------------------*/
   30.32 +#include <nds.h>
   30.33 +#include <sys/types.h>
   30.34 +
   30.35 +extern const unsigned short midi2psgFreq[120];
   30.36 +
   30.37 +#define C(octave)  (octave * 12 +  0)
   30.38 +#define CS(octave) (octave * 12 +  1)
   30.39 +#define D(octave)  (octave * 12 +  2)
   30.40 +#define DS(octave) (octave * 12 +  3)
   30.41 +#define E(octave)  (octave * 12 +  4)
   30.42 +#define F(octave)  (octave * 12 +  5)
   30.43 +#define FS(octave) (octave * 12 +  6)
   30.44 +#define G(octave)  (octave * 12 +  7)
   30.45 +#define GS(octave) (octave * 12 +  8)
   30.46 +#define A(octave)  (octave * 12 +  9)
   30.47 +#define AS(octave) (octave * 12 + 10)
   30.48 +#define B(octave)  (octave * 12 + 11)
   30.49 +
   30.50 +
   30.51 +#define N_MUSIC_CHANNELS 2
   30.52 +
   30.53 +static unsigned short chNote[N_MUSIC_CHANNELS];
   30.54 +static unsigned char chVol[N_MUSIC_CHANNELS];
   30.55 +
   30.56 +
   30.57 +static const unsigned char startVol[N_MUSIC_CHANNELS] = {32, 30};
   30.58 +static const unsigned char fade[N_MUSIC_CHANNELS] = {0, 1};
   30.59 +static const unsigned char chDuty[N_MUSIC_CHANNELS] = {0, 3};
   30.60 +
   30.61 +static const signed char bassLine[64] = {
   30.62 +  A(2), 0, A(3), 0, -1, 0, A(2), 0,
   30.63 +  A(3), 0, -1, 0, 0, 0, 0, 0,
   30.64 +  0, 0, 0, 0, 0, 0, 0, 0,
   30.65 +  C(3), 0, C(4), 0, G(2), 0, G(3), 0,
   30.66 +  A(2), 0, A(3), 0, -1, 0, A(2), 0,
   30.67 +  A(3), 0, -1, 0, 0, 0, 0, 0,
   30.68 +  0, 0, 0, 0, 0, 0, 0, 0,
   30.69 +  C(3), 0, C(4), 0, G(2), 0, G(3), 0,
   30.70 +};
   30.71 +
   30.72 +static const signed char mel1[64] = {
   30.73 +  E(6), 0, 0, 0, B(5), 0, C(6), 0,
   30.74 +  D(6), 0, 0, 0, C(6), 0, B(5), 0,
   30.75 +  A(5), 0, 0, 0, A(5), 0, C(6), 0,
   30.76 +  E(6), 0, 0, 0, D(6), 0, C(6), 0,
   30.77 +  B(5), 0, 0, 0, 0, 0, C(6), 0,
   30.78 +  D(6), 0, 0, 0, E(6), 0, 0, 0,
   30.79 +  C(6), 0, 0, 0, A(5), 0, 0, 0,
   30.80 +  A(5)
   30.81 +};
   30.82 +
   30.83 +static const signed char mel2[64] = {
   30.84 +  0, 0, D(6), 0, 0, 0, F(6), 0,
   30.85 +  A(6), 0, 0, 0, G(6), 0, F(6), 0,
   30.86 +  E(6), 0, 0, 0, 0, 0, C(6), 0,
   30.87 +  E(6), 0, 0, 0, D(6), 0, C(6), 0,
   30.88 +  B(5), 0, 0, 0, B(5), 0, C(6), 0,
   30.89 +  D(6), 0, 0, 0, E(6), 0, 0, 0,
   30.90 +  C(6), 0, 0, 0, A(5), 0, 0, 0,
   30.91 +  A(5)
   30.92 +};
   30.93 +
   30.94 +static const signed char *const scripts[4][N_MUSIC_CHANNELS] = {
   30.95 +  { bassLine, mel1 },
   30.96 +  { bassLine, mel1 },
   30.97 +  { bassLine, mel2 },
   30.98 +  { bassLine, mel2 }
   30.99 +};
  30.100 +
  30.101 +int order = 0;
  30.102 +char ticksLeft = 1;
  30.103 +char row = 0;
  30.104 +char musicPaused = 1;
  30.105 +
  30.106 +/*
  30.107 + * Plays a repeating drum track and bass track.
  30.108 + */
  30.109 +void ljMusic(void) {
  30.110 +  if (musicPaused) {
  30.111 +    for (int ch = 0; ch < N_MUSIC_CHANNELS; ++ch) {
  30.112 +      chVol[ch] = 0;
  30.113 +    }
  30.114 +  } else if (--ticksLeft <= 0) {
  30.115 +    ticksLeft += 6;
  30.116 +    for (int ch = 0; ch < N_MUSIC_CHANNELS; ++ch) {
  30.117 +      const signed char *script = scripts[order][ch];
  30.118 +      int note = script ? script[(int)row] : 0;
  30.119 +      if (note < 0) {
  30.120 +        chVol[ch] = 0;
  30.121 +      } else if (note > 0) {
  30.122 +        chVol[ch] = startVol[ch];
  30.123 +        chNote[ch] = midi2psgFreq[note];
  30.124 +      }
  30.125 +    }
  30.126 +    ++row;
  30.127 +    if (row >= 64) {
  30.128 +      row = 0;
  30.129 +      ++order;
  30.130 +      if (order == 4) {
  30.131 +        order = 0;
  30.132 +      }
  30.133 +    }
  30.134 +  }
  30.135 +  
  30.136 +  
  30.137 +  // Play notes
  30.138 +  for (int ch = 0; ch < N_MUSIC_CHANNELS; ++ch) {
  30.139 +    SCHANNEL_CR(8 + ch) = SCHANNEL_ENABLE | SOUND_FORMAT_PSG | SOUND_PAN(64)
  30.140 +                   | (chDuty[ch] << 24) | chVol[ch];
  30.141 +    SCHANNEL_TIMER(8 + ch) = chNote[ch];
  30.142 +    
  30.143 +    // update volumes
  30.144 +    if (chVol[ch] > fade[ch]) {
  30.145 +      chVol[ch] -= fade[ch];
  30.146 +    } else {
  30.147 +      chVol[ch] = 0;
  30.148 +    }
  30.149 +  }
  30.150 +}
  30.151 +
  30.152 +#define FX(note, volume, duty) ((note) | ((volume) << 7) | ((duty) << 14))
  30.153 +
  30.154 +static const u16 rotateEffect[] = {
  30.155 +  0, 6,
  30.156 +  FX(A(5), 32, 1), FX(A(5), 8, 1),
  30.157 +  FX(D(6), 32, 1), FX(D(6), 8, 1),
  30.158 +  FX(G(6), 32, 1), FX(G(6), 8, 1)
  30.159 +};
  30.160 +
  30.161 +static const u16 moveEffect[] = {
  30.162 +  0, 2,
  30.163 +  FX(A(6), 32, 1), FX(A(6), 8, 1)
  30.164 +};
  30.165 +
  30.166 +static const u16 landEffect[] = {
  30.167 +  0, 8,
  30.168 +  FX(C(4), 64, 3), FX(GS(3), 56, 3),
  30.169 +  FX(F(3), 48, 3), FX(D(3), 40, 3),
  30.170 +  FX(C(3), 32, 3), FX(B(2), 24, 3),
  30.171 +  FX(C(3), 16, 3), FX(B(2), 8, 3),
  30.172 +};
  30.173 +
  30.174 +static const u16 lockEffect[] = {
  30.175 +  1, 2,
  30.176 +  FX(C(8), 16, 1), FX(C(8), 4, 1)
  30.177 +};
  30.178 +
  30.179 +static const u16 lineEffect[] = {
  30.180 +  0, 15,
  30.181 +  FX(C(6), 64, 2), FX(DS(6), 59, 2), FX(F(6), 54, 2),
  30.182 +  FX(C(6), 49, 2), FX(D(6), 44, 2), FX(F(6), 39, 2),
  30.183 +  FX(C(6), 34, 3), FX(DS(6), 29, 3), FX(F(6), 24, 3),
  30.184 +  FX(C(6), 20, 3), FX(D(6), 16, 3), FX(F(6), 12, 3),
  30.185 +  FX(C(6), 9, 3), FX(DS(6), 6, 3), FX(F(6), 3, 3)
  30.186 +};
  30.187 +
  30.188 +static const u16 homerEffect[] = {
  30.189 +  0, 25,
  30.190 +  FX(C(6), 64, 2), FX(DS(6), 59, 2), FX(F(6), 54, 2),
  30.191 +  FX(C(6), 49, 2), FX(D(6), 44, 2), FX(F(6), 39, 2),
  30.192 +  FX(C(6), 34, 3), FX(DS(6), 29, 3), FX(F(6), 24, 3),
  30.193 +  FX(C(6), 20, 3),
  30.194 +  FX(DS(6), 64, 2), FX(FS(6), 59, 2), FX(GS(6), 54, 2),
  30.195 +  FX(DS(6), 49, 2), FX(F(6), 44, 2), FX(GS(6), 39, 2),
  30.196 +  FX(DS(6), 34, 3), FX(FS(6), 29, 3), FX(GS(6), 24, 3),
  30.197 +  FX(DS(6), 20, 3), FX(F(6), 16, 3), FX(GS(6), 12, 3),
  30.198 +  FX(DS(6), 9, 3), FX(F(6), 6, 3), FX(GS(6), 3, 3)
  30.199 +};
  30.200 +
  30.201 +static const u16 streakEffect[] = {
  30.202 +  0, 35,
  30.203 +  FX(C(6), 64, 2), FX(DS(6), 59, 2), FX(F(6), 54, 2),
  30.204 +  FX(C(6), 49, 2), FX(D(6), 44, 2), FX(F(6), 39, 2),
  30.205 +  FX(C(6), 34, 3), FX(DS(6), 29, 3), FX(F(6), 24, 3),
  30.206 +  FX(C(6), 20, 3),
  30.207 +  FX(DS(6), 64, 2), FX(FS(6), 59, 2), FX(GS(6), 54, 2),
  30.208 +  FX(DS(6), 49, 2), FX(F(6), 44, 2), FX(GS(6), 39, 2),
  30.209 +  FX(DS(6), 34, 3), FX(FS(6), 29, 3), FX(GS(6), 24, 3),
  30.210 +  FX(DS(6), 20, 3),
  30.211 +  FX(FS(6), 64, 2), FX(A(6), 59, 2), FX(B(6), 54, 2),
  30.212 +  FX(FS(6), 49, 2), FX(GS(6), 44, 2), FX(B(6), 39, 2),
  30.213 +  FX(FS(6), 34, 3), FX(A(6), 29, 3), FX(B(6), 24, 3),
  30.214 +  FX(FS(6), 20, 3), FX(GS(6), 16, 3), FX(B(6), 12, 3),
  30.215 +  FX(FS(6), 9, 3), FX(GS(6), 6, 3), FX(B(6), 3, 3)
  30.216 +};
  30.217 +
  30.218 +static const u16 holdEffect[] = {
  30.219 +  1, 10,
  30.220 +  FX(E(5), 20, 0), FX(G(5), 25, 0),
  30.221 +  FX(B(5), 30, 0), FX(B(5), 30, 0),
  30.222 +  FX(G(5), 28, 0), FX(E(5), 24, 0),
  30.223 +  FX(D(5), 20, 0), FX(C(5), 15, 0),
  30.224 +  FX(C(5), 10, 0), FX(C(5), 5, 0)
  30.225 +};
  30.226 +
  30.227 +
  30.228 +static const u16 irsEffect[] = {
  30.229 +  0, 12,
  30.230 +  FX(CS(7), 40, 3), FX(CS(7), 40, 3),
  30.231 +  FX(FS(7), 40, 3), FX(FS(7), 40, 3),
  30.232 +  FX(B(7), 40, 3), FX(B(7), 35, 3),
  30.233 +  FX(B(7), 30, 3), FX(B(7), 25, 3),
  30.234 +  FX(B(7), 20, 3), FX(B(7), 15, 3),
  30.235 +  FX(B(7), 10, 3), FX(B(7), 5, 3),
  30.236 +};
  30.237 +
  30.238 +static const u16 countEffect[] = {
  30.239 +  0, 14,
  30.240 +  FX(D(5), 64, 0), FX(D(5), 64, 0),
  30.241 +  FX(CS(5), 64, 0), FX(CS(5), 64, 0),
  30.242 +  FX(C(5), 64, 1), FX(C(5), 64, 1),
  30.243 +  FX(B(4), 64, 1), FX(AS(4), 64, 1),
  30.244 +  FX(A(4), 64, 2), FX(GS(4), 64, 2),
  30.245 +  FX(GS(4), 64, 3), FX(G(4), 48, 3),
  30.246 +  FX(G(4), 32, 3), FX(G(4), 16, 3),
  30.247 +};
  30.248 +
  30.249 +static const u16 winEffect1[] = {
  30.250 +  0, 54,
  30.251 +  FX(C(6), 32, 2), FX(C(6), 48, 1), FX(C(6), 48, 1), FX(C(6), 48, 1),
  30.252 +  FX(C(6), 32, 2), 0, 0, 0, 0,
  30.253 +  FX(AS(5), 32, 2), FX(AS(5), 48, 1), FX(AS(5), 48, 1), FX(AS(5), 48, 1),
  30.254 +  FX(AS(5), 32, 2), 0, 0, 0, 0,
  30.255 +  FX(C(6), 32, 2), FX(C(6), 48, 1), FX(C(6), 48, 1), FX(C(6), 48, 1),
  30.256 +  FX(D(6), 32, 2), 0, 0, 0, 0,
  30.257 +
  30.258 +  FX(D(6), 32, 2), FX(D(6), 48, 1), FX(D(6), 48, 1), FX(D(6), 48, 1),
  30.259 +  FX(D(6), 47, 1), FX(D(6), 46, 1), FX(D(6), 45, 1), FX(D(6), 44, 1),
  30.260 +  FX(D(6), 43, 1), FX(D(6), 42, 1), FX(D(6), 41, 1), FX(D(6), 40, 1),
  30.261 +  FX(D(6), 39, 1), FX(D(6), 38, 1), FX(D(6), 37, 1), FX(D(6), 36, 1),
  30.262 +  FX(D(6), 35, 1), FX(D(6), 34, 1), FX(D(6), 33, 1), FX(D(6), 32, 1),
  30.263 +  FX(D(6), 31, 1), FX(D(6), 30, 1), FX(D(6), 29, 1), FX(D(6), 28, 1),
  30.264 +  FX(D(6), 27, 1), FX(D(6), 26, 1), FX(D(6), 25, 1), FX(D(6), 24, 1),
  30.265 +  FX(D(6), 18, 2), FX(D(6), 12, 2), FX(D(6), 6, 2)
  30.266 +};
  30.267 +
  30.268 +static const u16 winEffect2[] = {
  30.269 +  0, 54,
  30.270 +  FX(E(6), 32, 2), FX(E(6), 48, 1), FX(E(6), 48, 1), FX(E(6), 48, 1),
  30.271 +  FX(E(6), 32, 2), 0, 0, 0, 0,
  30.272 +  FX(D(6), 32, 2), FX(D(6), 48, 1), FX(D(6), 48, 1), FX(D(6), 48, 1),
  30.273 +  FX(D(6), 32, 2), 0, 0, 0, 0,
  30.274 +  FX(E(6), 32, 2), FX(E(6), 48, 1), FX(E(6), 48, 1), FX(E(6), 48, 1),
  30.275 +  FX(E(6), 32, 2), 0, 0, 0, 0,
  30.276 +
  30.277 +  FX(FS(6), 32, 2), FX(FS(6), 48, 1), FX(FS(6), 48, 1), FX(FS(6), 48, 1),
  30.278 +  FX(FS(6), 47, 1), FX(FS(6), 46, 1), FX(FS(6), 45, 1), FX(FS(6), 44, 1),
  30.279 +  FX(FS(6), 43, 1), FX(FS(6), 42, 1), FX(FS(6), 41, 1), FX(FS(6), 40, 1),
  30.280 +  FX(FS(6), 39, 1), FX(FS(6), 38, 1), FX(FS(6), 37, 1), FX(FS(6), 36, 1),
  30.281 +  FX(FS(6), 35, 1), FX(FS(6), 34, 1), FX(FS(6), 33, 1), FX(FS(6), 32, 1),
  30.282 +  FX(FS(6), 31, 1), FX(FS(6), 30, 1), FX(FS(6), 29, 1), FX(FS(6), 28, 1),
  30.283 +  FX(FS(6), 27, 1), FX(FS(6), 26, 1), FX(FS(6), 25, 1), FX(FS(6), 24, 1),
  30.284 +  FX(FS(6), 18, 2), FX(FS(6), 12, 2), FX(FS(6), 6, 2)
  30.285 +};
  30.286 +
  30.287 +static const u16 dieEffect1[] = {
  30.288 +  0, 60,
  30.289 +  FX(E(3), 60, 2), FX(E(3), 59, 2), FX(E(3), 58, 2), FX(E(3), 57, 2),
  30.290 +  FX(E(3), 56, 2), FX(E(3), 55, 2), FX(E(3), 54, 2), FX(E(3), 53, 2),
  30.291 +  FX(E(3), 52, 2), FX(E(3), 51, 2), FX(E(3), 50, 2), FX(E(3), 49, 2),
  30.292 +  FX(E(3), 48, 2), FX(E(3), 47, 2), FX(E(3), 46, 2), FX(E(3), 45, 2),
  30.293 +  FX(E(3), 44, 2), FX(E(3), 43, 2), FX(E(3), 42, 2), FX(E(3), 41, 2),
  30.294 +  FX(E(3), 40, 2), FX(E(3), 39, 2), FX(E(3), 38, 2), FX(E(3), 37, 2),
  30.295 +  FX(E(3), 36, 2), FX(E(3), 35, 2), FX(E(3), 34, 2), FX(E(3), 33, 2),
  30.296 +  FX(E(3), 32, 2), FX(E(3), 31, 2), FX(E(3), 30, 2), FX(E(3), 29, 2),
  30.297 +  FX(E(3), 28, 2), FX(E(3), 27, 2), FX(E(3), 26, 2), FX(E(3), 25, 2),
  30.298 +  FX(E(3), 24, 2), FX(E(3), 23, 2), FX(E(3), 22, 2), FX(E(3), 21, 2),
  30.299 +  FX(E(3), 20, 2), FX(E(3), 19, 2), FX(E(3), 18, 2), FX(E(3), 17, 2),
  30.300 +  FX(E(3), 16, 2), FX(E(3), 15, 2), FX(E(3), 14, 2), FX(E(3), 13, 2),
  30.301 +  FX(E(3), 12, 2), FX(E(3), 11, 2), FX(E(3), 10, 2), FX(E(3),  9, 2),
  30.302 +  FX(E(3),  8, 2), FX(E(3),  7, 2), FX(E(3),  6, 2), FX(E(3),  5, 2),
  30.303 +  FX(E(3),  4, 2), FX(E(3),  3, 2), FX(E(3),  2, 2), FX(E(3),  1, 2)
  30.304 +};
  30.305 +
  30.306 +static const u16 dieEffect2[] = {
  30.307 +  1, 60,
  30.308 +  FX(E(4), 60, 2), FX(B(3), 59, 2), FX(E(3), 58, 2), FX(E(3), 57, 2),
  30.309 +  FX(E(3), 56, 2), FX(E(3), 55, 2), FX(E(3), 54, 2), FX(E(3), 53, 2),
  30.310 +  FX(E(3), 52, 2), FX(E(3), 51, 2), FX(E(3), 50, 2), FX(E(3), 49, 2),
  30.311 +  FX(E(3), 48, 2), FX(E(3), 47, 2), FX(E(3), 46, 2), FX(E(3), 45, 2),
  30.312 +  FX(E(3), 44, 2), FX(E(3), 43, 2), FX(E(3), 42, 2), FX(E(3), 41, 2),
  30.313 +  FX(E(3), 40, 2), FX(E(3), 39, 2), FX(E(3), 38, 2), FX(E(3), 37, 2),
  30.314 +  FX(E(3), 36, 2), FX(E(3), 35, 2), FX(E(3), 34, 2), FX(E(3), 33, 2),
  30.315 +  FX(E(3), 32, 2), FX(E(3), 31, 2), FX(E(3), 30, 2), FX(E(3), 29, 2),
  30.316 +  FX(E(3), 28, 2), FX(E(3), 27, 2), FX(E(3), 26, 2), FX(E(3), 25, 2),
  30.317 +  FX(E(3), 24, 2), FX(E(3), 23, 2), FX(E(3), 22, 2), FX(E(3), 21, 2),
  30.318 +  FX(E(3), 20, 2), FX(E(3), 19, 2), FX(E(3), 18, 2), FX(E(3), 17, 2),
  30.319 +  FX(E(3), 16, 2), FX(E(3), 15, 2), FX(E(3), 14, 2), FX(E(3), 13, 2),
  30.320 +  FX(E(3), 12, 2), FX(E(3), 11, 2), FX(E(3), 10, 2), FX(E(3),  9, 2),
  30.321 +  FX(E(3),  8, 2), FX(E(3),  7, 2), FX(E(3),  6, 2), FX(E(3),  5, 2),
  30.322 +  FX(E(3),  4, 2), FX(E(3),  3, 2), FX(E(3),  2, 2), FX(E(3),  1, 2)
  30.323 +};
  30.324 +
  30.325 +static const u16 *effects[32] = {
  30.326 +  rotateEffect,
  30.327 +  moveEffect,
  30.328 +  NULL,  // drop: not used
  30.329 +  landEffect,
  30.330 +  lockEffect,
  30.331 +  lineEffect,
  30.332 +  homerEffect,
  30.333 +  streakEffect,
  30.334 +  NULL,  // spawn
  30.335 +  holdEffect,  // hold
  30.336 +  irsEffect,  // irs
  30.337 +  NULL,  // 4x4 square
  30.338 +  NULL, NULL, NULL, NULL,  // unused
  30.339 +  dieEffect1, dieEffect2
  30.340 +};
  30.341 +
  30.342 +
  30.343 +
  30.344 +static const u16 *fxPtr[5];
  30.345 +static u8 fxLen[5];
  30.346 +static s8 lastCD;
  30.347 +
  30.348 +
  30.349 +
  30.350 +static void startEffect(const u16 *s) {
  30.351 +
  30.352 +  // skip effects that lack an actual effect
  30.353 +  if (!s) {
  30.354 +    return;
  30.355 +  }
  30.356 +  
  30.357 +  unsigned int type = s[0];
  30.358 +  unsigned int len = s[1];
  30.359 +  unsigned int min = 0;
  30.360 +  unsigned int max = 2;
  30.361 +  
  30.362 +  if (type > 0) {
  30.363 +    min = 3;
  30.364 +    max = 4;
  30.365 +  }
  30.366 +
  30.367 +  // prepare to kill the channel with the shortest time remaining
  30.368 +  unsigned int shortestLen = fxLen[min];
  30.369 +  unsigned int shortestCh = min;
  30.370 +  for (int ch = min + 1; ch <= max; ++ch) {
  30.371 +    if (fxLen[ch] < shortestLen) {
  30.372 +      shortestLen = fxLen[ch];
  30.373 +      shortestCh = ch;
  30.374 +    }
  30.375 +  }
  30.376 +  
  30.377 +  // if the channel is suitable, kill it
  30.378 +  if (shortestLen < len) {
  30.379 +    fxPtr[shortestCh] = s + 2;
  30.380 +    fxLen[shortestCh] = len;
  30.381 +  }
  30.382 +}
  30.383 +
  30.384 +static void pollEffects(void) {
  30.385 +  for (int ch = 0; ch < 5; ++ch) {
  30.386 +    unsigned int note = 36;
  30.387 +    unsigned int vol = 0;
  30.388 +    unsigned int duty = 0;
  30.389 +    if (fxLen[ch] > 0) {
  30.390 +      unsigned int data = *fxPtr[ch]++;
  30.391 +      duty = (data >> 14) & 0x03;
  30.392 +      vol = (data >> 7) & 0x7F;
  30.393 +      note = data & 0x7F;
  30.394 +      --fxLen[ch];
  30.395 +    }
  30.396 +    SCHANNEL_CR(11 + ch) = SCHANNEL_ENABLE | SOUND_FORMAT_PSG | SOUND_PAN(64)
  30.397 +                   | (duty << 24) | vol;
  30.398 +    SCHANNEL_TIMER(11 + ch) = midi2psgFreq[note];
  30.399 +  }
  30.400 +}
  30.401 +
  30.402 +void ljSoundEffects(unsigned long int sounds,
  30.403 +                           int countdown) {
  30.404 +
  30.405 +  // cancel out overlapping line clear sounds
  30.406 +  if (sounds & 0x0080) {
  30.407 +    sounds &= ~0x0060;
  30.408 +  } else if (sounds & 0x0040) {
  30.409 +    sounds &= ~0x0020;
  30.410 +  }
  30.411 +  
  30.412 +  for (int y = 0; sounds; ++y, sounds >>= 1) {
  30.413 +    if (sounds & 1) {
  30.414 +      startEffect(effects[y]);
  30.415 +    }
  30.416 +  }
  30.417 +  if (countdown < 0) {
  30.418 +    countdown = 0;
  30.419 +  } else if (countdown > 6) {
  30.420 +    countdown = 6;
  30.421 +  }
  30.422 +  if (countdown < lastCD) {
  30.423 +    if (countdown > 0) {
  30.424 +      startEffect(countEffect);
  30.425 +    } else {
  30.426 +      startEffect(winEffect1);
  30.427 +      startEffect(winEffect2);
  30.428 +    }
  30.429 +  }
  30.430 +  lastCD = countdown;
  30.431 +  pollEffects();
  30.432 +}
    31.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    31.2 +++ b/src/explode.c	Fri Mar 13 00:39:12 2009 -0700
    31.3 @@ -0,0 +1,142 @@
    31.4 +/*
    31.5 +Bombl*ss extension for LOCKJAW Tetromino Game
    31.6 +Copr. 2007 Damian Yerrick
    31.7 +
    31.8 +*/
    31.9 +
   31.10 +
   31.11 +/*
   31.12 +
   31.13 +During falling, the auxiliary playfield p->c[][] contains a map of
   31.14 +individual regions used by the falling code.  But during explosion,
   31.15 +it serves a different purpose: counting the number of frames before
   31.16 +a given block is erased.
   31.17 +
   31.18 +*/
   31.19 +
   31.20 +const char explodeWidth[nLines] = {
   31.21 +  4, // megabomb
   31.22 +  3, 3, 3, 3, 4, 5, 5, 6, 6, 7  // 1-10 lines
   31.23 +};
   31.24 +const char explodeHeight[nLines] = {
   31.25 +  3, // megabomb
   31.26 +  0, 1, 2, 3, 4, 5, 5, 6, 6, 7  // 1-10 lines
   31.27 +};
   31.28 +
   31.29 +
   31.30 +/**
   31.31 + * Sets the number of frames before a block is erased based on
   31.32 + * the explosion.
   31.33 + * @param p the playfield
   31.34 + * @param x the column where the bomb is centered
   31.35 + * @param y the row where the bomb is centered
   31.36 + * @param bombColor the color of the bomb
   31.37 + * @param nLines the number of lines cleared, which determines the explosion size
   31.38 + */
   31.39 +void explodeAt(LJField *p, int x, int y, int bombColor, int nLines) {
   31.40 +  int boom = nLines;
   31.41 +
   31.42 +  if ((bombColor & COLOR_MASK) == BOMBCOLOR_SQUARE) {
   31.43 +    boom = 4;
   31.44 +  } else if (boom > 10) {
   31.45 +    boom = 10;
   31.46 +  }
   31.47 +
   31.48 +  int boomLeft = x - explodeWidth[boom];
   31.49 +  if (boomLeft < p->leftWall) {
   31.50 +    boomLeft = p->leftWall;
   31.51 +  }
   31.52 +  int boomRight = x + explodeWidth[boom];
   31.53 +  if (boomRight > p->rightWall - 1) {
   31.54 +    boomLeft = p->rightWall - 1;
   31.55 +  }
   31.56 +  int boomBottom = y - explodeHeight[boom];
   31.57 +  if (boomLeft < 0) {
   31.58 +    boomLeft = 0;
   31.59 +  }
   31.60 +  int boomTop = y + explodeHeight[boom];
   31.61 +  if (boomRight > LJ_PF_HT - 1) {
   31.62 +    boomLeft = LJ_PF_HT - 1;
   31.63 +  }
   31.64 +
   31.65 +  for (by = boomBottom; bx < boomTop; ++bx) {
   31.66 +    int dy = y - by;
   31.67 +    int dy2 = dy * dy;
   31.68 +    for (bx = boomLeft; bx < boomRight; ++bx) {
   31.69 +      int dx = x - bx;
   31.70 +      int dx2 = dx * dx;
   31.71 +      unsigned int time = (dx2 + dy2) / EXPLODE_SPEED + 2;
   31.72 +      if (p->c[y][x] > time) {
   31.73 +        p->c[y][x] = time;
   31.74 +      }
   31.75 +    }
   31.76 +  }
   31.77 +}
   31.78 +
   31.79 +static int blockIsBomb(unsigned int blk) {
   31.80 +  return (blk & COLOR_MASK) == BOMBCOLOR_NORMAL
   31.81 +          || (blk & COLOR_MASK) == BOMBCOLOR_SQUARE;
   31.82 +}
   31.83 +
   31.84 +/**
   31.85 + * Scans the exploding areas, erases blocks, and sets up bombs to explode.
   31.86 + * @param p the playfield
   31.87 + * @return the rows that were changed
   31.88 + */
   31.89 +LJBits explodeFrame(LJField *p) {
   31.90 +  LJBits changedRows = 0;
   31.91 +  LJBits changeAny = 0;
   31.92 +  
   31.93 +  int nLines = countRows(p->tempRows);
   31.94 +
   31.95 +  // first pass: destruction
   31.96 +  for (int y = 0; y < LJ_PF_HT; ++y) {
   31.97 +    for (int x = p->leftWall; x < p->rightWall; ++x) {
   31.98 +      if (p->c[y][x] == 1) {
   31.99 +        unsigned int blk = p->b[y][x];
  31.100 +        
  31.101 +        if (blk) {
  31.102 +          changedRows |= 1 << y;
  31.103 +          p->b[y][x] = 0;
  31.104 +          if (blockIsBomb(blk)) {
  31.105 +            explodeAt(p, x, y, blk, nLines);
  31.106 +          }
  31.107 +        }
  31.108 +      }
  31.109 +    }
  31.110 +  }
  31.111 +
  31.112 +  // second pass: countdown timer per block
  31.113 +  for (int y = 0; y < LJ_PF_HT; ++y) {
  31.114 +    for (int x = p->leftWall; x < p->rightWall; ++x) {
  31.115 +      if (p->c[y][x] > 0) {
  31.116 +        --p->c[y][x] > 0;
  31.117 +        changeAny = 1;
  31.118 +      }
  31.119 +    }
  31.120 +  }
  31.121 +
  31.122 +  return changeAny ? (1 << (LJ_PF_HT - 1)) : changedRows;
  31.123 +}
  31.124 +
  31.125 +/**
  31.126 + * Scans the cleared lines for bombs and marks c[] to trigger them.
  31.127 + * @param p the playfield, where tempRows holds the cleared lines
  31.128 + * @return the rows where bombs were triggered
  31.129 + */
  31.130 +LJBits triggerBombs(LJField *p) {
  31.131 +  LJBits bombRows = 0;
  31.132 +
  31.133 +  for (int y = 0; y < LJ_PF_HT; ++y) {
  31.134 +    if (p->tempRows & (1 << y)) {
  31.135 +      for (int x = p->leftWall; x < p->rightWall; ++x) {
  31.136 +        bool isBomb = blockIsBomb(p->b[y][x]);
  31.137 +        p->c[y][x] = isBomb;
  31.138 +        if (isBomb) {
  31.139 +          bombRows |= 1 << y;
  31.140 +        }
  31.141 +      }
  31.142 +    }
  31.143 +  }
  31.144 +  return bombRows;
  31.145 +}
    32.1 Binary file src/font.bmp has changed
    33.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    33.2 +++ b/src/fontdraw.c	Fri Mar 13 00:39:12 2009 -0700
    33.3 @@ -0,0 +1,372 @@
    33.4 +/*
    33.5 +Variable width font drawing library for DS (and GBA)
    33.6 +
    33.7 +Copyright 2007-2008 Damian Yerrick <pinoandchester@pineight.com>
    33.8 +
    33.9 +This work is provided 'as-is', without any express or implied
   33.10 +warranty.  In no event will the authors be held liable for any
   33.11 +damages arising from the use of this work.
   33.12 +
   33.13 +Permission is granted to anyone to use this work for any purpose,
   33.14 +including commercial applications, and to alter it and redistribute
   33.15 +it freely, subject to the following restrictions:
   33.16 +
   33.17 +1. The origin of this work must not be misrepresented; you must
   33.18 +   not claim that you wrote the original work. If you use
   33.19 +   this work in a product, an acknowledgment in the product
   33.20 +   documentation would be appreciated but is not required.
   33.21 +2. Altered source versions must be plainly marked as such, and must
   33.22 +   not be misrepresented as being the original work.
   33.23 +3. This notice may not be removed or altered from any source
   33.24 +   distribution.
   33.25 +
   33.26 +"Source" is the preferred form of a work for making changes to it.
   33.27 +
   33.28 +*/
   33.29 +
   33.30 +#include <string.h>
   33.31 +#include <stdio.h>
   33.32 +#include "fontdraw.h"
   33.33 +
   33.34 +
   33.35 +extern const unsigned char vwfont_bin[];
   33.36 +
   33.37 +typedef struct GlyphRec {
   33.38 +  unsigned short dataOffset;
   33.39 +  unsigned char glyphWidth;
   33.40 +  unsigned char reserved;
   33.41 +} GlyphRec;
   33.42 +
   33.43 +#ifndef FONTDRAW_SPLIT_COMPILE
   33.44 +#include "fontdraw_engine.c"
   33.45 +#else
   33.46 +__attribute__((long_call))
   33.47 +unsigned int fontdraw_putchar(u32 *dst, unsigned int colStride, int wid, int x, int glyph);
   33.48 +__attribute__((long_call))
   33.49 +void vwfRectfillColumn(u32 *dst, unsigned int colStride,
   33.50 +                       unsigned int l, unsigned int t,
   33.51 +                       unsigned int r, unsigned int b,
   33.52 +                       unsigned int c);
   33.53 +#endif
   33.54 +
   33.55 +
   33.56 +unsigned int fontdraw_charWidth(int glyph) {
   33.57 +  glyph &= 0xFF;
   33.58 +  if (glyph < vwfont_bin[1]) {
   33.59 +    return 0;
   33.60 +  }
   33.61 +  glyph -= vwfont_bin[1];
   33.62 +  if (vwfont_bin[2] != 0 && glyph >= vwfont_bin[2]) {
   33.63 +    return 0;
   33.64 +  }
   33.65 +  const GlyphRec *glyphRec = 
   33.66 +    ((const GlyphRec *)(vwfont_bin + vwfont_bin[0])) + glyph;
   33.67 +  return glyphRec->glyphWidth;
   33.68 +}
   33.69 +
   33.70 +unsigned int fontdraw_strWidth(const char *s) {
   33.71 +  unsigned int width = 0;
   33.72 +  for (; *s; ++s) {
   33.73 +    width += fontdraw_charWidth(*s);
   33.74 +  }
   33.75 +  return width;
   33.76 +}
   33.77 +
   33.78 +size_t fontdraw_cutStr(const char *s, int targetWidth) {
   33.79 +  size_t len;
   33.80 +  for (len = 0; s[len]; ++len) {
   33.81 +    int charWidth = fontdraw_charWidth(s[len]);
   33.82 +    if (charWidth > targetWidth) {
   33.83 +      return len;
   33.84 +    }
   33.85 +    targetWidth -= charWidth;
   33.86 +  }
   33.87 +  return len;
   33.88 +}
   33.89 +
   33.90 +static unsigned int fontdraw_putline(u32 *dst, unsigned int colStride, int wid, int x, const char *src) {
   33.91 +  unsigned int startX = x;
   33.92 +  for (int c = *src; c != 0 && wid > 0; c = *++src) {
   33.93 +    int chWidth = fontdraw_putchar(dst, colStride, wid, x, c & 0xFF);
   33.94 +    x += chWidth;
   33.95 +    wid -= chWidth;
   33.96 +  }
   33.97 +  return x - startX;
   33.98 +}
   33.99 +
  33.100 +#ifdef ARM9
  33.101 +const VWFWindow vwfTop = {
  33.102 +  .left = 0, .top = 0, .width = 32, .height = 24,
  33.103 +  .chrBase = (u32 *)BG_TILE_RAM(0),
  33.104 +  .map = 31,
  33.105 +  .core = 0,
  33.106 +  .mapTileBase = 0
  33.107 +};
  33.108 +
  33.109 +const VWFWindow vwfTouch = {
  33.110 +  .left = 0, .top = 0, .width = 32, .height = 24,
  33.111 +  .chrBase = (u32 *)BG_TILE_RAM_SUB(0),
  33.112 +  .map = 31,
  33.113 +  .core = 1,
  33.114 +  .mapTileBase = 0
  33.115 +};
  33.116 +#else
  33.117 +const VWFWindow vwfTop = {
  33.118 +  .left = 0, .top = 0, .width = 30, .height = 20,
  33.119 +  .chrBase = (u32 *)PATRAM4(0, 0),
  33.120 +  .map = 31,
  33.121 +  .core = 0,
  33.122 +  .mapTileBase = 0
  33.123 +};
  33.124 +#endif
  33.125 +
  33.126 +void vwfRectfill(const VWFWindow *v, int l, int t, int r, int b, int c)
  33.127 +{
  33.128 +  u32 *dst = v->chrBase;
  33.129 +  c &= 0x0000000F;
  33.130 +  c |= c << 4;
  33.131 +  c |= c << 8;
  33.132 +  c |= c << 16;
  33.133 +  
  33.134 +  unsigned int x = l;
  33.135 +  unsigned int stride = v->height * 8;
  33.136 +  u32 *tile = dst + stride * (l >> 3);
  33.137 +  
  33.138 +  if (t < 0) {
  33.139 +    t = 0;
  33.140 +  }
  33.141 +  if (b > v->height * 8) {
  33.142 +    b = v->height * 8;
  33.143 +  }
  33.144 +
  33.145 +  for(x = l; x < r; x = (x + 8) & -8) {
  33.146 +    vwfRectfillColumn(tile, stride, x & 7, t,
  33.147 +                      (r & -8) > (x & -8) ? 8 : (r & 7),
  33.148 +                      b, c);
  33.149 +    tile += stride;
  33.150 +  }
  33.151 +}
  33.152 +
  33.153 +void vwfHline(const VWFWindow *v, int l, int t, int r, int c) {
  33.154 +  if (r < l) {
  33.155 +    int temp = l;
  33.156 +    l = r;
  33.157 +    r = temp;
  33.158 +  }
  33.159 +  vwfRectfill(v, l, t, r, t + 1, c);
  33.160 +}
  33.161 +
  33.162 +void vwfVline(const VWFWindow *v, int l, int t, int b, int c) {
  33.163 +  if (b < t) {
  33.164 +    int temp = t;
  33.165 +    t = b;
  33.166 +    b = temp;
  33.167 +  }
  33.168 +  vwfRectfill(v, l, t, l + 1, b, c);
  33.169 +}
  33.170 +
  33.171 +void vwfRect(const VWFWindow *v, int l, int t, int r, int b, int c) {
  33.172 +  vwfVline(v, l, t, b, c);
  33.173 +  vwfVline(v, r - 1, t, b, c);
  33.174 +  vwfHline(v, l, t, r, c);
  33.175 +  vwfHline(v, l, b - 1, r, c);
  33.176 +}
  33.177 +
  33.178 +void vwfWinClear(const VWFWindow *w) {
  33.179 +  size_t nBytes = 32 * w->width * w->height;
  33.180 +  memset(w->chrBase, 0, nBytes);
  33.181 +}
  33.182 +
  33.183 +static inline NAMETABLE *vwfGetMapBase(int core, int map) {
  33.184 +#if ARM9
  33.185 +  NAMETABLE *dst = core ? &(MAP_SUB[map]) : &(MAP[map]);
  33.186 +#else
  33.187 +  NAMETABLE *dst = &(MAP[map]);
  33.188 +#endif
  33.189 +
  33.190 +  return dst;
  33.191 +}
  33.192 +
  33.193 +void vwfPutMap(const VWFWindow *w,
  33.194 +               int l, int t, int r, int b,
  33.195 +               unsigned int orMask) {
  33.196 +
  33.197 +  if (r > (int)w->width) {
  33.198 +    r = (int)w->width;
  33.199 +  }
  33.200 +  if (l < 0) {
  33.201 +    l = 0;
  33.202 +  }
  33.203 +  if (r <= l) {
  33.204 +    return;
  33.205 +  }
  33.206 +
  33.207 +  if (b > (int)w->height) {
  33.208 +    b = (int)w->height;
  33.209 +  }
  33.210 +  if (t < 0) {
  33.211 +    t = 0;
  33.212 +  }
  33.213 +  if (b <= t) {
  33.214 +    return;
  33.215 +  }
  33.216 +  
  33.217 +  NAMETABLE *dst = vwfGetMapBase(w->core, w->map);
  33.218 +
  33.219 +  int mapTile = (w->mapTileBase + w->height * l + t) | orMask;
  33.220 +  for (int x = w->left + l; x < w->left + r; ++x) {
  33.221 +    for (int y = w->top + t; y < w->top + b; ++y) {
  33.222 +      (*dst)[y][x] = mapTile++;
  33.223 +    }
  33.224 +    mapTile += w->height + t - b;
  33.225 +  }
  33.226 +}
  33.227 +
  33.228 +void vwfWinInit(const VWFWindow *w) {
  33.229 +  vwfWinClear(w);
  33.230 +  vwfPutMap(w, 0, 0, w->width, w->height, 0x0000);
  33.231 +}
  33.232 +
  33.233 +#if 0
  33.234 +void vwfWinInit(const VWFWindow *w) {
  33.235 +#if ARM9
  33.236 +  NAMETABLE *dst = w->core ? &(MAP_SUB[w->map]) : &(MAP[w->map]);
  33.237 +#else
  33.238 +  NAMETABLE *dst = &(MAP[w->map]);
  33.239 +#endif
  33.240 +
  33.241 +  int mapTile = w->mapTileBase;
  33.242 +  for (int x = w->left; x < w->left + w->width; ++x) {
  33.243 +    for (int y = w->top; y < w->top + w->height; ++y) {
  33.244 +      (*dst)[y][x] = mapTile++;
  33.245 +    }
  33.246 +  }
  33.247 +  vwfWinClear(w);
  33.248 +}
  33.249 +#endif
  33.250 +
  33.251 +unsigned int vwfPuts(const VWFWindow *w,
  33.252 +             const char *str,
  33.253 +             int x, int y) {
  33.254 +  return fontdraw_putline(w->chrBase + y, 8 * w->height,
  33.255 +                           w->width * 8 - x, x, str);
  33.256 +}
  33.257 +
  33.258 +unsigned int vwfPutc(const VWFWindow *w,
  33.259 +             int c,
  33.260 +             int x, int y) {
  33.261 +  int width = 8 * w->width - x;
  33.262 +  if (width >= fontdraw_charWidth(c)) {
  33.263 +    return fontdraw_putchar(w->chrBase + y, 8 * w->height,
  33.264 +                             w->width * 8 - x, x, c & 0xFF);
  33.265 +  } else {
  33.266 +    return 0;
  33.267 +  }
  33.268 +}
  33.269 +
  33.270 +// old api emulation
  33.271 +
  33.272 +#if 0
  33.273 +void fontdraw_demo_putline(int x, int y, const char *str) {
  33.274 +  const VWFWindow *w = &vwfTop;
  33.275 +#if ARM9
  33.276 +  if (y >= 288) {
  33.277 +    w = &vwfTouch;
  33.278 +    y -= 288;
  33.279 +  }
  33.280 +#endif
  33.281 +  vwfPuts(w, str, x, y);
  33.282 +}
  33.283 +#endif
  33.284 +
  33.285 +void fontdraw_setupVRAM(int sub) {
  33.286 +#if ARM9
  33.287 +  const VWFWindow *w = sub ? &vwfTouch : &vwfTop;
  33.288 +#else
  33.289 +  const VWFWindow *w = &vwfTop;
  33.290 +#endif
  33.291 +
  33.292 +#if ARM9
  33.293 +  if (sub == 0) {
  33.294 +    BGCTRL_SUB[0] = BG_MAP_BASE(31) | BG_TILE_BASE(0);
  33.295 +    BG_OFFSET_SUB[0].x = 0;
  33.296 +    BG_OFFSET_SUB[0].y = 0;
  33.297 +  } else
  33.298 +#endif
  33.299 +  {
  33.300 +    BGCTRL[0] = BG_MAP_BASE(31) | BG_TILE_BASE(0);
  33.301 +    BG_OFFSET[0].x = 0;
  33.302 +    BG_OFFSET[0].y = 0;
  33.303 +  }
  33.304 +  
  33.305 +  vwfWinInit(w);
  33.306 +}
  33.307 +
  33.308 +void fontdraw_cls(int sub) {
  33.309 +#if ARM9
  33.310 +  vwfWinClear(sub ? &vwfTouch : &vwfTop);
  33.311 +#else
  33.312 +  vwfWinClear(&vwfTop);
  33.313 +#endif
  33.314 +}
  33.315 +
  33.316 +void vwfBlitAligned(const VWFWindow *src, const VWFWindow *dst,
  33.317 +                    int srcX, int srcY, int dstX, int dstY,
  33.318 +                    int w, int h) {
  33.319 +
  33.320 +  /* Clip in X */
  33.321 +  if (srcX < 0) {
  33.322 +    dstX -= srcX;
  33.323 +    w -= srcX;
  33.324 +    srcX = 0;
  33.325 +  }
  33.326 +  if (dstX < 0) {
  33.327 +    srcX -= dstX;
  33.328 +    w -= dstX;
  33.329 +    dstX = 0;
  33.330 +  }
  33.331 +  if (srcX + w > (int)src->width) {
  33.332 +    w = src->width - srcX;
  33.333 +  }
  33.334 +  if (dstX + w > (int)dst->width) {
  33.335 +    w = dst->width - dstX;
  33.336 +  }
  33.337 +  if (w <= 0) {
  33.338 +    return;
  33.339 +  }
  33.340 +
  33.341 +  /* Clip in Y */
  33.342 +  if (srcY < 0) {
  33.343 +    dstY -= srcY;
  33.344 +    h -= srcY;
  33.345 +    srcY = 0;
  33.346 +  }
  33.347 +  if (dstY < 0) {
  33.348 +    srcY -= dstY;
  33.349 +    h -= dstY;
  33.350 +    dstY = 0;
  33.351 +  }
  33.352 +  if (srcY + h > src->height * 8) {
  33.353 +    h = src->height * 8 - srcY;
  33.354 +  }
  33.355 +  if (dstX + w > dst->height * 8) {
  33.356 +    h = dst->height * 8 - dstY;
  33.357 +  }
  33.358 +  if (h < 0) {
  33.359 +    return;
  33.360 +  }
  33.361 +
  33.362 +  {
  33.363 +    const u32 *srcCol = src->chrBase + srcX * src->height * 8 + srcY;
  33.364 +    u32 *dstCol = dst->chrBase + dstX * dst->height * 8 + dstY;
  33.365 +    
  33.366 +    for (; w > 0; --w) {
  33.367 +      for (unsigned int y = 0; y < h; ++y) {
  33.368 +        dstCol[y] = srcCol[y];
  33.369 +      }
  33.370 +      srcCol += src->height * 8;
  33.371 +      dstCol += dst->height * 8;
  33.372 +    }
  33.373 +  }
  33.374 +  
  33.375 +}
    34.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    34.2 +++ b/src/fontdraw.h	Fri Mar 13 00:39:12 2009 -0700
    34.3 @@ -0,0 +1,127 @@
    34.4 +/*
    34.5 +Variable width font drawing library for DS (and GBA)
    34.6 +
    34.7 +Copyright 2007 Damian Yerrick <pinoandchester@pineight.com>
    34.8 +
    34.9 +This work is provided 'as-is', without any express or implied
   34.10 +warranty.  In no event will the authors be held liable for any
   34.11 +damages arising from the use of this work.
   34.12 +
   34.13 +Permission is granted to anyone to use this work for any purpose,
   34.14 +including commercial applications, and to alter it and redistribute
   34.15 +it freely, subject to the following restrictions:
   34.16 +
   34.17 +1. The origin of this work must not be misrepresented; you must
   34.18 +   not claim that you wrote the original work. If you use
   34.19 +   this work in a product, an acknowledgment in the product
   34.20 +   documentation would be appreciated but is not required.
   34.21 +2. Altered source versions must be plainly marked as such, and must
   34.22 +   not be misrepresented as being the original work.
   34.23 +3. This notice may not be removed or altered from any source
   34.24 +   distribution.
   34.25 +
   34.26 +"Source" is the preferred form of a work for making changes to it.
   34.27 +
   34.28 +*/
   34.29 +
   34.30 +#ifndef FONTDRAW_H
   34.31 +#define FONTDRAW_H
   34.32 +#include <sys/types.h>
   34.33 +
   34.34 +#ifdef ARM9
   34.35 +// DS specific macros
   34.36 +#include <nds.h>
   34.37 +#ifndef BG_OFFSET_SUB
   34.38 +#define BG_OFFSET_SUB ((bg_scroll *)(0x04001010))
   34.39 +#endif
   34.40 +
   34.41 +// macros from libgba that didn't make it to libnds
   34.42 +#ifndef MAP
   34.43 +typedef u16 NAMETABLE[32][32];
   34.44 +#define MAP ((NAMETABLE *)BG_MAP_RAM(0))
   34.45 +#define MAP_SUB ((NAMETABLE *)BG_MAP_RAM_SUB(0))
   34.46 +#endif
   34.47 +
   34.48 +#else
   34.49 +// GBA specific macros
   34.50 +#include <gba_video.h>
   34.51 +
   34.52 +#endif
   34.53 +
   34.54 +unsigned int fontdraw_charWidth(int glyph);
   34.55 +unsigned int fontdraw_strWidth(const char *s);
   34.56 +
   34.57 +/**
   34.58 + * Returns the number of characters in s that fit within targetWidth pixels.
   34.59 + */
   34.60 +size_t fontdraw_cutStr(const char *s, int targetWidth);
   34.61 +
   34.62 +void fontdraw_setupVRAM(int sub);
   34.63 +
   34.64 +// New API
   34.65 +
   34.66 +typedef struct VWFWindow {
   34.67 +  u8 left;    // in 8 pixel units on nametable
   34.68 +  u8 top;     // in 8 pixel units on nametable
   34.69 +  u8 width;   // in 8 pixel units on nametable
   34.70 +  u8 height;  // in 8 pixel units on nametable
   34.71 +  u32 *chrBase;
   34.72 +  u8 map;  // in 2 KiB units on VRAM
   34.73 +  u8 core;    // 0: main; 1: sub
   34.74 +  u16 mapTileBase;
   34.75 +} VWFWindow;
   34.76 +
   34.77 +void vwfWinInit(const VWFWindow *vwf);
   34.78 +void vwfWinClear(const VWFWindow *vwf);
   34.79 +/**
   34.80 + * Sets up a portion of a window.
   34.81 + * @param vwf the window
   34.82 + * @param l distance in tiles from the left side of the window to the
   34.83 + * left side of the area to be updated
   34.84 + * @param t distance in tiles from the top of the window to the
   34.85 + * top of the area to be updated
   34.86 + * @param r distance in tiles from the left side of the window to the
   34.87 + * right side of the area to be updated
   34.88 + * @param b distance in tiles from the top of the window to the
   34.89 + * bottom of the area to be updated
   34.90 + * @param orMask the data to be OR'd with each map space, typically
   34.91 + * containing a palette number in bits 12 to 15
   34.92 + */
   34.93 +void vwfPutMap(const VWFWindow *vwf,
   34.94 +               int l, int t, int r, int b,
   34.95 +               unsigned int orMask);
   34.96 +
   34.97 +unsigned int vwfPutc(const VWFWindow *w,
   34.98 +             int c,
   34.99 +             int x, int y);
  34.100 +unsigned int vwfPuts(const VWFWindow *src,
  34.101 +                     const char *str,
  34.102 +                     int x, int y);
  34.103 +void vwfRectfill(const VWFWindow *v,
  34.104 +                 int l, int t, int r, int b,
  34.105 +                 int c);
  34.106 +void vwfHline(const VWFWindow *v, int l, int t, int r, int c);
  34.107 +void vwfVline(const VWFWindow *v, int l, int t, int b, int c);
  34.108 +void vwfRect(const VWFWindow *v, int l, int t, int r, int b, int c);
  34.109 +
  34.110 +/**
  34.111 + * Replaces a rectangle of pixels in dst with pixels from src.
  34.112 + * @param src the bitmap to copy from
  34.113 + * @param dst the bitmap to copy to
  34.114 + * @param srcX the left side of the part of src to copy,
  34.115 + * in 8-pixel units
  34.116 + * @param srcY the top of the part of src to copy,
  34.117 + * in pixels
  34.118 + * @param dstX the left side of the part of dst to be replaced,
  34.119 + * in 8-pixel units
  34.120 + * @param dstY the top of the part of src dst to be replaced,
  34.121 + * in pixels
  34.122 + */
  34.123 +void vwfBlitAligned(const VWFWindow *src, const VWFWindow *dst,
  34.124 +                    int srcX, int srcY, int dstX, int dstY,
  34.125 +                    int w, int h);
  34.126 +
  34.127 +extern const VWFWindow vwfTop, vwfTouch;
  34.128 +
  34.129 +
  34.130 +#endif
    35.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    35.2 +++ b/src/fontdraw.iwram.c	Fri Mar 13 00:39:12 2009 -0700
    35.3 @@ -0,0 +1,1 @@
    35.4 +#include "fontdraw_engine.c" 
    36.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    36.2 +++ b/src/fontdraw_engine.c	Fri Mar 13 00:39:12 2009 -0700
    36.3 @@ -0,0 +1,135 @@
    36.4 +/*
    36.5 +Variable width font drawing library for DS (and GBA)
    36.6 +
    36.7 +Copyright 2007-2008 Damian Yerrick <pinoandchester@pineight.com>
    36.8 +
    36.9 +This work is provided 'as-is', without any express or implied
   36.10 +warranty.  In no event will the authors be held liable for any
   36.11 +damages arising from the use of this work.
   36.12 +
   36.13 +Permission is granted to anyone to use this work for any purpose,
   36.14 +including commercial applications, and to alter it and redistribute
   36.15 +it freely, subject to the following restrictions:
   36.16 +
   36.17 +1. The origin of this work must not be misrepresented; you must
   36.18 +   not claim that you wrote the original work. If you use
   36.19 +   this work in a product, an acknowledgment in the product
   36.20 +   documentation would be appreciated but is not required.
   36.21 +2. Altered source versions must be plainly marked as such, and must
   36.22 +   not be misrepresented as being the original work.
   36.23 +3. This notice may not be removed or altered from any source
   36.24 +   distribution.
   36.25 +
   36.26 +"Source" is the preferred form of a work for making changes to it.
   36.27 +
   36.28 +*/
   36.29 +/*
   36.30 +
   36.31 +Set FONTDRAW_SPLIT_COMPILE when compiling the time-sensitve parts
   36.32 +into a separate file to be placed in "fast" memory.
   36.33 +
   36.34 +*/
   36.35 +
   36.36 +
   36.37 +#ifdef FONTDRAW_SPLIT_COMPILE
   36.38 +typedef unsigned int u32;
   36.39 +extern const unsigned char vwfont_bin[];
   36.40 +
   36.41 +typedef struct GlyphRec {
   36.42 +  unsigned short dataOffset;
   36.43 +  unsigned char glyphWidth;
   36.44 +  unsigned char reserved;
   36.45 +} GlyphRec;
   36.46 +#endif
   36.47 +
   36.48 +unsigned int fontdraw_putchar(u32 *dst, unsigned int colStride, int wid, int x, int glyph) {
   36.49 +  glyph &= 0xFF;
   36.50 +  if (glyph < vwfont_bin[1]) {
   36.51 +    return 0;
   36.52 +  }
   36.53 +  glyph -= vwfont_bin[1];
   36.54 +  if (vwfont_bin[2] != 0 && glyph >= vwfont_bin[2]) {
   36.55 +    return 0;
   36.56 +  }
   36.57 +  const GlyphRec *glyphRec = 
   36.58 +    ((const GlyphRec *)(vwfont_bin + vwfont_bin[0])) + glyph;
   36.59 +  const unsigned char *data = vwfont_bin + glyphRec->dataOffset;
   36.60 +  unsigned int dataShift = 2;
   36.61 +  unsigned int dataBits = *data++;
   36.62 +  unsigned int pixelCode = dataBits & 0x03;
   36.63 +
   36.64 +  // Convert x to tile column address and bit address within tile
   36.65 +  dst += colStride * (x >> 3);
   36.66 +  x = (x & 0x07) << 2;
   36.67 +  
   36.68 +  if (dataShift >= 8) {
   36.69 +    dataShift = 2;
   36.70 +    dataBits = *data++;
   36.71 +  }
   36.72 +
   36.73 +  for (unsigned int height = vwfont_bin[3];
   36.74 +       height > 0;
   36.75 +       --height, ++dst) {
   36.76 +    int eol = 0;
   36.77 +    int xLine = x;
   36.78 +    int widLeft = wid;
   36.79 +    u32 *dstLine = dst;
   36.80 +    u32 dstBits = *dstLine;
   36.81 +    while (!eol) {
   36.82 +
   36.83 +      // Process a pixel instruction
   36.84 +      if (pixelCode == 0) {
   36.85 +        eol = 1;
   36.86 +      } else if (widLeft > 0) {
   36.87 +
   36.88 +        // Change pixel and move to next pixel
   36.89 +        if (pixelCode > 1) {
   36.90 +          //dstBits &= ~(0x0F << xLine);
   36.91 +          dstBits |= (pixelCode - 1) << xLine;
   36.92 +        }
   36.93 +        xLine += 4;
   36.94 +        --widLeft;
   36.95 +      }
   36.96 +
   36.97 +      // If finished with this tile, write back changed bits
   36.98 +      if (xLine >= 32) {
   36.99 +        xLine = 0;
  36.100 +        *dstLine = dstBits;
  36.101 +        dstLine += colStride;
  36.102 +        dstBits = *dstLine;
  36.103 +      }
  36.104 +      
  36.105 +      // Decode next pixel instruction
  36.106 +      if (dataShift >= 8) {
  36.107 +        dataShift = 0;
  36.108 +        dataBits = *data++;
  36.109 +      }
  36.110 +
  36.111 +      // Decode a byte into pixel instructions
  36.112 +      pixelCode = (dataBits >> dataShift) & 0x03;
  36.113 +      dataShift += 2;
  36.114 +    }
  36.115 +
  36.116 +    // Write back changed bits
  36.117 +    if (xLine > 0) {
  36.118 +      *dstLine = dstBits;
  36.119 +    }
  36.120 +  }
  36.121 +  return glyphRec->glyphWidth;
  36.122 +}
  36.123 +
  36.124 +void vwfRectfillColumn(u32 *dst, unsigned int colStride,
  36.125 +                       unsigned int l, unsigned int t,
  36.126 +                       unsigned int r, unsigned int b,
  36.127 +                       unsigned int c)
  36.128 +{
  36.129 +  u32 mask = 0xffffffffU << (4 * l);
  36.130 +  mask &= 0xffffffffU >> (4 * (8 - r));
  36.131 +  c &= mask;
  36.132 +  mask = ~mask;
  36.133 +
  36.134 +  for (; t < b; t++) {
  36.135 +    dst[t] = (dst[t] & mask) | c;
  36.136 +  }
  36.137 +}
  36.138 +
    37.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    37.2 +++ b/src/gba_asm.s	Fri Mar 13 00:39:12 2009 -0700
    37.3 @@ -0,0 +1,73 @@
    37.4 +@ fixed fastfmul(fixed x, fixed y)
    37.5 +@ Multiply two 16.16 fixed-point numbers.
    37.6 +
    37.7 +.ARM
    37.8 +.ALIGN
    37.9 +.GLOBL  fastfmul
   37.10 +
   37.11 +fastfmul:
   37.12 +  smull r1,r2,r0,r1
   37.13 +  mov r0,r2,LSL#16
   37.14 +  orr r0,r0,r1,LSR#16
   37.15 +  bx lr
   37.16 +
   37.17 +
   37.18 +@ int dv(int num, int den)
   37.19 +@ Divide two signed integers.
   37.20 +
   37.21 +.THUMB
   37.22 +.THUMB_FUNC
   37.23 +.ALIGN
   37.24 +.GLOBL  dv
   37.25 +
   37.26 +dv:
   37.27 +  cmp r1, #0
   37.28 +  beq 0f
   37.29 +  swi 6
   37.30 +  bx lr
   37.31 +0:
   37.32 +  ldr r0, =0x7fffffff
   37.33 +  bx lr
   37.34 +
   37.35 +
   37.36 +@ int fracmul(signed int x, signed int frac)
   37.37 +@ Multiply by a 0.32 fractional number between -0.5 and 0.5.
   37.38 +@ Used for fast division by a constant.
   37.39 +
   37.40 +.ARM
   37.41 +.ALIGN
   37.42 +.GLOBL  fracmul
   37.43 +
   37.44 +fracmul:
   37.45 +  smull r1,r2,r0,r1
   37.46 +  mov r0, r2
   37.47 +  bx lr
   37.48 +
   37.49 +
   37.50 +@ int fracumul(unsigned int x, unsigned int frac)
   37.51 +@ Multiply by a 0.32 fractional number between 0 and 1.
   37.52 +@ Used for fast division by a constant.
   37.53 +
   37.54 +.ARM
   37.55 +.ALIGN
   37.56 +.GLOBL  fracumul
   37.57 +
   37.58 +fracumul:
   37.59 +  umull r1,r2,r0,r1
   37.60 +  mov r0, r2
   37.61 +  bx lr
   37.62 +
   37.63 +
   37.64 +@ void gblz_unpack(const void *src, void *dst)
   37.65 +@ Unpack GB LZSS format data.
   37.66 +
   37.67 +.THUMB
   37.68 +.THUMB_FUNC
   37.69 +.ALIGN
   37.70 +.GLOBL  _gblz_unpack
   37.71 +
   37.72 +_gblz_unpack:
   37.73 +  swi 0x11  @ LZ77UnCompWRAM
   37.74 +  bx lr
   37.75 +
   37.76 +
    38.1 Binary file src/gbablk.chr has changed
    39.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    39.2 +++ b/src/gbaisr.iwram.c	Fri Mar 13 00:39:12 2009 -0700
    39.3 @@ -0,0 +1,15 @@
    39.4 +#include "ljgba.h"
    39.5 +#define BIOS_IF (*(volatile unsigned short *)0x03FFFFF8)
    39.6 +
    39.7 +volatile int curTime = 0;
    39.8 +
    39.9 +void isr(void) {
   39.10 +  unsigned int interrupts = REG_IF;
   39.11 +
   39.12 +  if (interrupts & IRQ_VBLANK) {
   39.13 +    ++curTime;
   39.14 +  }
   39.15 +
   39.16 +  BIOS_IF |= interrupts;
   39.17 +  REG_IF = interrupts;
   39.18 +}
    40.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    40.2 +++ b/src/gbamenus.c	Fri Mar 13 00:39:12 2009 -0700
    40.3 @@ -0,0 +1,458 @@
    40.4 +/*
    40.5 +
    40.6 +BGCTRL layout
    40.7 +Layer 0: Current menu
    40.8 +Layer 1: Menu that is fading away
    40.9 +Layer 2: Gradient background
   40.10 +
   40.11 +VRAM layout
   40.12 +(1 map == 64 tiles)
   40.13 +Tiles 0.000-2FF: layer 0 bitmap
   40.14 +Tiles 0.300-3FF: unused
   40.15 +Tiles 2.000-2FF: layer 1 bitmap
   40.16 +Tiles 2.300-33F: unused
   40.17 +Tiles 2.340-36F: map 29 (layer 2)
   40.18 +Tiles 2.370-37F: layer 2 gradient tiles
   40.19 +Tiles 2.380-3FF: layer 0 map
   40.20 +Tiles 2.3C0-3FF: layer 1 map
   40.21 +
   40.22 +
   40.23 +*/
   40.24 +
   40.25 +#include <stdint.h>
   40.26 +#include <string.h>
   40.27 +#include "fontdraw.h"
   40.28 +typedef u32 TileSliver;
   40.29 +
   40.30 +#ifdef ARM9
   40.31 +#include <nds.h>
   40.32 +#define MENU_GFX_CORE 1
   40.33 +#define MENU_GFX_VRAM(bank, tile) (TileSliver *)(BG_TILE_RAM_SUB(bank) + 32 * tile)
   40.34 +#define MENU_GFX_MAP ((NAMETABLE *)0x06200000)
   40.35 +#define MENU_GFX_BGCTRL BGCTRL_SUB
   40.36 +#define MENU_GFX_OFFSET ((bg_scroll *)(0x04001010))
   40.37 +#define HIDDEN_ROWS 0
   40.38 +#define HIDDEN_COLS 0
   40.39 +#define BG0_ON DISPLAY_BG0_ACTIVE
   40.40 +#define BG1_ON DISPLAY_BG1_ACTIVE
   40.41 +#define BG2_ON DISPLAY_BG2_ACTIVE
   40.42 +#define videoSetModeMenu(x) videoSetModeSub(x)
   40.43 +#define MENU_GFX_PALETTE BG_PALETTE_SUB
   40.44 +#define USING_TOUCH 1
   40.45 +#else
   40.46 +#include <gba.h>
   40.47 +#define MENU_GFX_CORE 0
   40.48 +#define MENU_GFX_VRAM(bank, tile) (TileSliver *)PATRAM4(bank, tile)
   40.49 +#define MENU_GFX_MAP MAP
   40.50 +#define MENU_GFX_BGCTRL BGCTRL
   40.51 +#define MENU_GFX_OFFSET BG_OFFSET
   40.52 +#define HIDDEN_ROWS 2
   40.53 +#define HIDDEN_COLS 1
   40.54 +#define videoSetModeMenu(x) (REG_DISPCNT = x)
   40.55 +#define MODE_0_2D 0
   40.56 +#define MENU_GFX_PALETTE BG_PALETTE
   40.57 +#define USING_TOUCH 0
   40.58 +#endif
   40.59 +
   40.60 +static const VWFWindow vwfLayer0 = {
   40.61 +  .left = 0, .top = 0, .width = 32, .height = 24,
   40.62 +  .chrBase = MENU_GFX_VRAM(0, 0),
   40.63 +  .map = 30,
   40.64 +  .core = MENU_GFX_CORE,
   40.65 +  .mapTileBase = 0
   40.66 +};
   40.67 +
   40.68 +static const VWFWindow vwfLayer1 = {
   40.69 +  .left = 0, .top = 0, .width = 32, .height = 24,
   40.70 +  .chrBase = MENU_GFX_VRAM(2, 0),
   40.71 +  .map = 31,
   40.72 +  .core = MENU_GFX_CORE,
   40.73 +  .mapTileBase = 0
   40.74 +};
   40.75 +
   40.76 +
   40.77 +void vsync(void);
   40.78 +
   40.79 +static const TileSliver gradientBackgroundDelta[8] = {
   40.80 +  0x00000000,
   40.81 +  0x10101010,
   40.82 +  0x00000000,
   40.83 +  0x01010101,
   40.84 +  0x10101010,
   40.85 +  0x01010101,
   40.86 +  0x11111111,
   40.87 +  0x10101010
   40.88 +};
   40.89 +
   40.90 +/*  Makes 12 gradient background tiles starting at dst.
   40.91 +*/
   40.92 +
   40.93 +static void makeGradientBackgroundTiles(TileSliver *dst) {
   40.94 +  for (TileSliver tile = 0x11111111;
   40.95 +       tile < 0xDDDDDDDD;
   40.96 +       tile += 0x11111111) {
   40.97 +    for (unsigned int y = 0; y < 8; ++y) {
   40.98 +      *dst++ = tile + gradientBackgroundDelta[y];
   40.99 +    }
  40.100 +  }
  40.101 +}
  40.102 +
  40.103 +static void makeLayer2(void) {
  40.104 +  makeGradientBackgroundTiles(MENU_GFX_VRAM(2, 0x300));
  40.105 +  for (int y = 0; y < 12; ++y) {
  40.106 +    for (int x = 0; x < 32; ++x) {
  40.107 +      MENU_GFX_MAP[29][y][x] = 0xE300 + y;
  40.108 +    }
  40.109 +  }
  40.110 +  for (int y = 0; y < 12; ++y) {
  40.111 +    for (int x = 0; x < 32; ++x) {
  40.112 +      MENU_GFX_MAP[29][y + 12][x] = 0xF300 + y;
  40.113 +    }
  40.114 +  }
  40.115 +  MENU_GFX_BGCTRL[2] = BG_TILE_BASE(2) | BG_MAP_BASE(29);
  40.116 +  MENU_GFX_OFFSET[2].x = HIDDEN_COLS * 8;
  40.117 +  MENU_GFX_OFFSET[2].y = HIDDEN_ROWS * 8;
  40.118 +}
  40.119 +
  40.120 +
  40.121 +
  40.122 +/*
  40.123 +Button palette:
  40.124 +0 transparent
  40.125 +1 White (button top)
  40.126 +2 Medium gray (button side)
  40.127 +3 Darkest gray (button bottom)
  40.128 +4 Light gray (button background)
  40.129 +5 Dark gray (button text aa, lower corner)
  40.130 +6 Black (button text)
  40.131 +7 Black
  40.132 +
  40.133 +This comes in both ordinary and highlighted versions.  There are two copies of the highlighted text.
  40.134 +
  40.135 +*/
  40.136 +
  40.137 +static const TileSliver buttonSideTiles[16] = {
  40.138 +  0x11111114,
  40.139 +  0x11111142,
  40.140 +  0x11111422,
  40.141 +  0x44444222,
  40.142 +  0x44444222,
  40.143 +  0x44444222,
  40.144 +  0x44444222,
  40.145 +  0x44444222,
  40.146 +  0x44444222,
  40.147 +  0x44444222,
  40.148 +  0x44444222,
  40.149 +  0x44444222,
  40.150 +  0x44444222,
  40.151 +  0x33333522,
  40.152 +  0x33333352,
  40.153 +  0x33333335
  40.154 +};
  40.155 +
  40.156 +static void loadButtonSideTiles(void) {
  40.157 +  for (int bank = 0; bank <= 2; bank += 2) {
  40.158 +    TileSliver *dst = MENU_GFX_VRAM(bank, 0x30C);
  40.159 +    memcpy(dst, buttonSideTiles, 32);
  40.160 +    memcpy(dst + 8, buttonSideTiles + 4, 32);
  40.161 +    memcpy(dst + 16, buttonSideTiles + 8, 32);
  40.162 +  }
  40.163 +}
  40.164 +
  40.165 +static const u8 buttonIntensity[8] =
  40.166 +  {28, 31, 23, 15, 28, 19, 0, 0};
  40.167 +
  40.168 +/**
  40.169 + * Loads the unhighlighted (gray) button palette.
  40.170 + */
  40.171 +static void loadButtonPalette(void) {
  40.172 +  for (int i = 0; i < 8; ++i) {
  40.173 +    unsigned int intensity = buttonIntensity[i];
  40.174 +
  40.175 +    MENU_GFX_PALETTE[192 + i] = intensity * RGB5(1, 1, 1);
  40.176 +  }
  40.177 +}
  40.178 +
  40.179 +void ljmenu_setHilitePalette(int phase) {
  40.180 +
  40.181 +  // generate triangle wave
  40.182 +  phase = (phase & 0x3F) ^ 0x20;
  40.183 +  if (phase & 0x20) {
  40.184 +    phase ^= 0x3F;
  40.185 +  }
  40.186 +
  40.187 +  for (int i = 0; i < 8; ++i) {
  40.188 +    int intensity = buttonIntensity[i];
  40.189 +    int intensity34 = (3 * intensity) >> 2;
  40.190 +    int rg = intensity34 + (phase >> 2) + 4;
  40.191 +    int b = rg >> 1;
  40.192 +    if (rg > 31) {
  40.193 +      rg = 31;
  40.194 +    }
  40.195 +    unsigned int c = RGB5(1, 1, 0) * rg + RGB5(0, 0, 1) * b;
  40.196 +
  40.197 +    MENU_GFX_PALETTE[200 + i] = c;
  40.198 +    MENU_GFX_PALETTE[208 + i] = c;
  40.199 +  }
  40.200 +}
  40.201 +
  40.202 +#define TILE_HFLIP 0x0400
  40.203 +
  40.204 +void ljmenu_hiliteButton(int l, int t, int r, int b, int hilite) {
  40.205 +  hilite = hilite ? 0xD000 : 0xC000;
  40.206 +
  40.207 +  /* Draw sides of button */
  40.208 +  MENU_GFX_MAP[30][t][l] = 0x30C | hilite;
  40.209 +  MENU_GFX_MAP[30][t][r - 1] = 0x30C | TILE_HFLIP | hilite;
  40.210 +  for (int y = t + 1; y < b - 1; ++y) {
  40.211 +    MENU_GFX_MAP[30][y][l] = 0x30D | hilite;
  40.212 +    MENU_GFX_MAP[30][y][r - 1] = 0x30D | TILE_HFLIP | hilite;
  40.213 +  }
  40.214 +  MENU_GFX_MAP[30][b - 1][l] = 0x30E | hilite;
  40.215 +  MENU_GFX_MAP[30][b - 1][r - 1] = 0x30E | TILE_HFLIP | hilite;
  40.216 +  vwfPutMap(&vwfLayer0, l + 1, t, r - 1, b, hilite);
  40.217 +}
  40.218 +
  40.219 +void ljmenu_drawButton(int l, int t, int r, int b, const char *text) {
  40.220 +  vwfRectfill(&vwfLayer0,
  40.221 +              l * 8 + 8, t * 8, r * 8 - 8, t * 8 + 3, 1);
  40.222 +  vwfRectfill(&vwfLayer0,
  40.223 +              l * 8 + 8, t * 8 + 3, r * 8 - 8, b * 8 - 3, 4);
  40.224 +  vwfRectfill(&vwfLayer0,
  40.225 +              l * 8 + 8, b * 8 - 3, r * 8 - 8, b * 8, 3);
  40.226 +
  40.227 +  int w = fontdraw_strWidth(text);
  40.228 +  int x = l * 8 + (r - l) * 4 - w / 2;
  40.229 +  int y = t * 8 + (b - t) * 4 - 12 / 2;
  40.230 +
  40.231 +  vwfPuts(&vwfLayer0, text, x, y);
  40.232 +  ljmenu_hiliteButton(l, t, r, b, 0);
  40.233 +}
  40.234 +
  40.235 +static void makePalettes(void) {
  40.236 +  // make background layer palettes
  40.237 +  for (unsigned int i = 0; i <= 12; ++i) {
  40.238 +    int blue = 2 * i;
  40.239 +    MENU_GFX_PALETTE[225 + i]= RGB5(0, 0, blue);
  40.240 +  }
  40.241 +  for (unsigned int i = 0; i <= 12; ++i) {
  40.242 +    int red = ((i + 9) * 3) >> 1;
  40.243 +    MENU_GFX_PALETTE[241 + i]= RGB5(red, red / 2, 0);
  40.244 +  }
  40.245 +  
  40.246 +  MENU_GFX_PALETTE[1] = RGB5(21,21,23);
  40.247 +  MENU_GFX_PALETTE[2] = RGB5(31,31,31);
  40.248 +  MENU_GFX_PALETTE[3] = RGB5(31,31,31);
  40.249 +  loadButtonPalette();
  40.250 +}
  40.251 +
  40.252 +void ljmenu_cls(void) {
  40.253 +  vwfWinInit(&vwfLayer0);
  40.254 +  vwfPutMap(&vwfLayer0, 2, 4, 30, 22, 0xC000);
  40.255 +}
  40.256 +
  40.257 +void ljmenu_init(void) {
  40.258 +  makeLayer2();
  40.259 +  loadButtonSideTiles();
  40.260 +  makePalettes();
  40.261 +  ljmenu_cls();
  40.262 +  MENU_GFX_BGCTRL[0] = BG_TILE_BASE(0) | BG_MAP_BASE(30);
  40.263 +  MENU_GFX_OFFSET[0].x = HIDDEN_COLS * 8;
  40.264 +  MENU_GFX_OFFSET[0].y = HIDDEN_ROWS * 8;
  40.265 +  MENU_GFX_BGCTRL[1] = BG_TILE_BASE(2) | BG_MAP_BASE(31);
  40.266 +  MENU_GFX_OFFSET[1].x = HIDDEN_COLS * 8;
  40.267 +  MENU_GFX_OFFSET[1].y = HIDDEN_ROWS * 8;
  40.268 +  videoSetModeMenu(MODE_0_2D | BG0_ON | BG1_ON | BG2_ON);
  40.269 +}
  40.270 +
  40.271 +void ljmenu_freeze(void) {
  40.272 +
  40.273 +  // Copy background
  40.274 +  memcpy(vwfLayer0.chrBase, vwfLayer1.chrBase, 240*160/2);
  40.275 +
  40.276 +  // Copy map
  40.277 +  memcpy(MENU_GFX_MAP[30], MENU_GFX_MAP[31], sizeof(NAMETABLE));
  40.278 +}
  40.279 +
  40.280 +void ljmenu_setTitle(const char *topLeft, const char *topRight) {
  40.281 +  vwfRectfill(&vwfLayer0, 16, 16, 240, 16 + 12, 0);
  40.282 +  vwfPuts(&vwfLayer0, topLeft, 16, 16);
  40.283 +  if (topRight) {
  40.284 +    int x = 240 - fontdraw_strWidth(topRight);
  40.285 +    vwfPuts(&vwfLayer0, topRight, x, 16);
  40.286 +  }
  40.287 +}
  40.288 +
  40.289 +static unsigned short tabsX;
  40.290 +static unsigned short tabsPadding;
  40.291 +#define TAB_TOP 32
  40.292 +#define TAB_BOTTOM 44
  40.293 +#define TAB_LEFT 16
  40.294 +#define TAB_RIGHT 240
  40.295 +
  40.296 +void ljmenu_beginTabs(unsigned int padding) {
  40.297 +  tabsX = TAB_LEFT;
  40.298 +  tabsPadding = padding;
  40.299 +}
  40.300 +
  40.301 +void ljmenu_addTab(const char *text, int hilite) {
  40.302 +  unsigned int bgColor = hilite ? 12 : 4;
  40.303 +  unsigned int w = text ? fontdraw_strWidth(text) : 0;
  40.304 +  unsigned int left = tabsX;
  40.305 +  unsigned int right = left + 2 * tabsPadding + w;
  40.306 +  
  40.307 +  if (right > TAB_RIGHT) {
  40.308 +    return;
  40.309 +  }
  40.310 +  vwfRectfill(&vwfLayer0,
  40.311 +              left, TAB_TOP, right, TAB_BOTTOM,
  40.312 +              bgColor);
  40.313 +  if (hilite) {
  40.314 +    vwfHline(&vwfLayer0,
  40.315 +             left, TAB_TOP, right,
  40.316 +             bgColor + 1);
  40.317 +    vwfVline(&vwfLayer0,
  40.318 +             left, TAB_TOP, TAB_BOTTOM,
  40.319 +             bgColor + 1);
  40.320 +    vwfVline(&vwfLayer0,
  40.321 +             right - 1, TAB_TOP, TAB_BOTTOM,
  40.322 +             bgColor + 1);
  40.323 +  } else {
  40.324 +    vwfHline(&vwfLayer0,
  40.325 +             left, TAB_BOTTOM - 1, right,
  40.326 +             bgColor + 1);
  40.327 +  }
  40.328 +  if (text) {
  40.329 +    vwfPuts(&vwfLayer0, text, left + tabsPadding, TAB_TOP);
  40.330 +  }
  40.331 +  tabsX = right;
  40.332 +}
  40.333 +
  40.334 +void ljmenu_endTabs(void) {
  40.335 +  vwfRectfill(&vwfLayer0,
  40.336 +              tabsX, TAB_TOP, TAB_RIGHT, TAB_BOTTOM - 1,
  40.337 +              4);
  40.338 +  vwfHline(&vwfLayer0,
  40.339 +           tabsX, TAB_BOTTOM - 1, TAB_RIGHT,
  40.340 +           5);
  40.341 +}
  40.342 +
  40.343 +#define PROPPANEL_TOP TAB_BOTTOM
  40.344 +#define PROPPANEL_HT 12
  40.345 +#define PROPPANEL_ROWS 7
  40.346 +#define PROPPANEL_BOTTOM (PROPPANEL_TOP + PROPPANEL_HT * PROPPANEL_ROWS)
  40.347 +
  40.348 +void ljmenu_propPanelClear(unsigned int nRows) {
  40.349 +  if (nRows > PROPPANEL_ROWS) {
  40.350 +    nRows = PROPPANEL_ROWS;
  40.351 +  }
  40.352 +  int y = PROPPANEL_TOP + PROPPANEL_HT * nRows;
  40.353 +  vwfRectfill(&vwfLayer0,
  40.354 +              TAB_LEFT, PROPPANEL_TOP, TAB_RIGHT, y,
  40.355 +              4);
  40.356 +  vwfRectfill(&vwfLayer0,
  40.357 +              TAB_LEFT, y, TAB_RIGHT, PROPPANEL_BOTTOM,
  40.358 +              0);
  40.359 +}
  40.360 +
  40.361 +void ljmenu_propPanelDrawRow(const char *name, const char *value,
  40.362 +                             unsigned int y, unsigned int hilite) {
  40.363 +  if (y > PROPPANEL_ROWS) {
  40.364 +    return;
  40.365 +  }
  40.366 +  y = PROPPANEL_TOP + PROPPANEL_HT * y;
  40.367 +  unsigned int bgColor = hilite ? 12 : 4;
  40.368 +  
  40.369 +  vwfRectfill(&vwfLayer0,
  40.370 +              TAB_LEFT, y, TAB_RIGHT, y + PROPPANEL_HT,
  40.371 +              bgColor);
  40.372 +  if (hilite) {
  40.373 +    vwfRect(&vwfLayer0,
  40.374 +            TAB_LEFT, y, TAB_RIGHT, y + PROPPANEL_HT,
  40.375 +            bgColor + 1);
  40.376 +  }
  40.377 +  if (name) {
  40.378 +    vwfPuts(&vwfLayer0, name, TAB_LEFT + 8, y);
  40.379 +  }
  40.380 +  if (value) {
  40.381 +    int x = TAB_RIGHT - 8 - fontdraw_strWidth(value);
  40.382 +    vwfPuts(&vwfLayer0, value, x, y);
  40.383 +  }
  40.384 +}
  40.385 +
  40.386 +static void roundrect(const VWFWindow *w,
  40.387 +                      int l, int t, int r, int b, int c) {
  40.388 +  vwfRectfill(w, l, t + 2, r, b - 2, c);
  40.389 +  vwfHline(w, l + 2, t, r - 2, c);
  40.390 +  vwfHline(w, l + 1, t + 1, r - 1, c);
  40.391 +  vwfHline(w, l + 1, b - 2, r - 1, c);
  40.392 +  vwfHline(w, l + 2, b - 1, r - 2, c);
  40.393 +}
  40.394 +
  40.395 +static void ljmenu_balloon(const char *text1, const char *text2,
  40.396 +                           int l, int t, int r) {
  40.397 +  int b = t + (text2 ? 24 : 12);
  40.398 +  roundrect(&vwfLayer0, l, t, r, b, 4);
  40.399 +  vwfPuts(&vwfLayer0, text1, l + 4, t);
  40.400 +  if (text2) {
  40.401 +    vwfPuts(&vwfLayer0, text2, l + 4, t + 12);
  40.402 +  }
  40.403 +}
  40.404 +
  40.405 +void ljmenu_propPanelDrawDesc(const char *text1, const char *text2) {
  40.406 +  ljmenu_balloon(text1, text2, TAB_LEFT, PROPPANEL_BOTTOM + 4, TAB_RIGHT);
  40.407 +}
  40.408 +
  40.409 +void ljmenu_propPanelDrawHelp(const char *text1, const char *text2) {
  40.410 +  int y = text2 ? 176 - 24 : 176 - 12;
  40.411 +  ljmenu_balloon(text1, text2, TAB_LEFT, y, TAB_RIGHT);
  40.412 +}
  40.413 +
  40.414 +#if 0
  40.415 +static void ljmenu_pressA(void) {
  40.416 +  int phase = 0;
  40.417 +  while (!(REG_KEYINPUT & KEY_A)) {
  40.418 +    ++phase;
  40.419 +    vsync();
  40.420 +    ljmenu_setHilitePalette(phase);
  40.421 +  }
  40.422 +  while (REG_KEYINPUT & KEY_A) {
  40.423 +    ++phase;
  40.424 +    vsync();
  40.425 +    ljmenu_setHilitePalette(phase);
  40.426 +  }
  40.427 +}
  40.428 +
  40.429 +void ljmenuTest(void) {
  40.430 +  ljmenu_init();
  40.431 +  ljmenu_cls();
  40.432 +  ljmenu_setTitle("LOCKJAW 0.43", "© 2008 Damian Yerrick");
  40.433 +  ljmenu_drawButton(22, 18, 22 + 8, 18 + 3, "OK");
  40.434 +  ljmenu_hiliteButton(22, 18, 22 + 8, 18 + 3, 1);
  40.435 +  ljmenu_drawButton(13, 18, 13 + 8, 18 + 3, "Cancel");
  40.436 +  ljmenu_drawButton(2, 8, 30, 11,
  40.437 +                    "coming soon: the new look of LOCKJAW");
  40.438 +  ljmenu_pressA();
  40.439 +  
  40.440 +  ljmenu_cls();
  40.441 +  ljmenu_setTitle("LOCKJAW 0.43", "© 2008 Damian Yerrick");
  40.442 +  ljmenu_beginTabs(4);
  40.443 +  ljmenu_addTab("Game", 1);
  40.444 +  ljmenu_addTab("Well", 0);
  40.445 +  ljmenu_addTab("Move", 0);
  40.446 +  ljmenu_addTab("Line", 0);
  40.447 +  ljmenu_addTab("Ctrl", 0);
  40.448 +  ljmenu_addTab("Drop", 0);
  40.449 +  ljmenu_addTab("Disp", 0);
  40.450 +  ljmenu_endTabs();
  40.451 +  ljmenu_propPanelClear(7);
  40.452 +  ljmenu_propPanelDrawRow("Gimmick", "Marathon", 0, 1);
  40.453 +  ljmenu_propPanelDrawRow("Mr. Gimmick", "Halo", 1, 0);
  40.454 +  ljmenu_propPanelDrawDesc("Goal or other game mode",
  40.455 +                           "Play until you <Ganon>DIE.</Ganon>");
  40.456 +  ljmenu_propPanelDrawHelp("u d: move; l r: change; L R: page; Start: OK",
  40.457 +                           NULL);
  40.458 +  ljmenu_pressA();
  40.459 +}
  40.460 +
  40.461 +#endif
    41.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    41.2 +++ b/src/gbamenus.h	Fri Mar 13 00:39:12 2009 -0700
    41.3 @@ -0,0 +1,26 @@
    41.4 +#ifndef GBAMENUS_H
    41.5 +#define GBAMENUS_H
    41.6 +
    41.7 +void ljmenu_init(void);
    41.8 +void ljmenu_cls(void);
    41.9 +void ljmenu_setTitle(const char *topLeft, const char *topRight);
   41.10 +void ljmenu_freeze(void);
   41.11 +
   41.12 +void ljmenu_drawButton(int l, int t, int r, int b, const char *text);
   41.13 +void ljmenu_hiliteButton(int l, int t, int r, int b, int hilite);
   41.14 +void ljmenu_setHilitePalette(int phase);
   41.15 +
   41.16 +void ljmenu_beginTabs(unsigned int padding);
   41.17 +void ljmenu_addTab(const char *text, int hilite);
   41.18 +void ljmenu_endTabs(void);
   41.19 +
   41.20 +void ljmenu_propPanelClear(unsigned int nRows);
   41.21 +void ljmenu_propPanelDrawRow(const char *name, const char *value,
   41.22 +                             unsigned int y, unsigned int hilite);
   41.23 +void ljmenu_propPanelDrawDesc(const char *text1, const char *text2);
   41.24 +void ljmenu_propPanelDrawHelp(const char *text1, const char *text2);
   41.25 +
   41.26 +
   41.27 +void ljmenuTest(void);
   41.28 +
   41.29 +#endif
    42.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    42.2 +++ b/src/gbanotefreq.c	Fri Mar 13 00:39:12 2009 -0700
    42.3 @@ -0,0 +1,25 @@
    42.4 +/* notefreq.h generated by mkpowers.c
    42.5 +   lookup table for note frequencies */
    42.6 +
    42.7 +/* DMG Sound period values
    42.8 +   frequency = 131072 Hz/(2048 + n) */
    42.9 +const unsigned short note_freqs[64] = {
   42.10 +   44,  156,  262,  362,  457,  546,  630,  710,  785,  856,  923,  986,
   42.11 + 1046, 1102, 1155, 1205, 1252, 1297, 1339, 1379, 1416, 1452, 1485, 1517,
   42.12 + 1547, 1575, 1601, 1626, 1650, 1672, 1693, 1713, 1732, 1750, 1766, 1782,
   42.13 + 1797, 1811, 1824, 1837, 1849, 1860, 1870, 1880, 1890, 1899, 1907, 1915,
   42.14 + 1922, 1929, 1936, 1942, 1948, 1954, 1959, 1964, 1969, 1973, 1977, 1981,
   42.15 + 1985, 1988, 1992, 1995
   42.16 +};
   42.17 +#if 0
   42.18 +/* DMG Sound period values
   42.19 +   frequency = base_freq * n / 4096 */
   42.20 +const unsigned short sampled_freqs[64] = {
   42.21 + 1024, 1084, 1149, 1217, 1290, 1366, 1448, 1534, 1625, 1722, 1824, 1933,
   42.22 + 2048, 2169, 2298, 2435, 2580, 2733, 2896, 3068, 3250, 3444, 3649, 3866,
   42.23 + 4096, 4339, 4597, 4870, 5160, 5467, 5792, 6137, 6501, 6888, 7298, 7732,
   42.24 + 8192, 8679, 9195, 9741,10321,10935,11585,12274,13003,13777,14596,15464,
   42.25 +16384,17358,18390,19483,20642,21870,23170,24548,26007,27554,29192,30928,
   42.26 +32768,34716,36780,38967
   42.27 +};
   42.28 +#endif
   42.29 \ No newline at end of file
    43.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    43.2 +++ b/src/gbaopt.c	Fri Mar 13 00:39:12 2009 -0700
    43.3 @@ -0,0 +1,123 @@
    43.4 +/*
    43.5 +
    43.6 +options for gba port of lockjaw
    43.7 +
    43.8 +*/
    43.9 +
   43.10 +#include <stdio.h>
   43.11 +#include <string.h>
   43.12 +#include "options.h"
   43.13 +#include "ljlocale.h"
   43.14 +#include "ljplay.h"
   43.15 +#include "gbamenus.h"
   43.16 +
   43.17 +#ifdef ARM9
   43.18 +#include "ljds.h"
   43.19 +#else
   43.20 +#include "ljgba.h"
   43.21 +#endif
   43.22 +
   43.23 +#define optionsMenu commonOptionsMenu
   43.24 +
   43.25 +unsigned char customPrefs[OPTIONS_MENU_LEN];
   43.26 +
   43.27 +void unpackOptions(LJView *v, const unsigned char *prefs) {
   43.28 +  unpackCommonOptions(v, prefs);
   43.29 +}
   43.30 +
   43.31 +static const char optionsHelpText[] =
   43.32 +"\020\021 page  \026\027 move  \025\024 change  Start:play";
   43.33 +
   43.34 +void optionsWinInit(void) {
   43.35 +  ljmenu_init();
   43.36 +  ljmenu_cls();
   43.37 +  ljmenu_propPanelDrawHelp(optionsHelpText,
   43.38 +                           NULL);  
   43.39 +}
   43.40 +
   43.41 +static void optionsClearRow(int y, int hilite) {
   43.42 +  ljmenu_propPanelDrawRow("", "", y, hilite);
   43.43 +}
   43.44 +
   43.45 +static char hiliteBlinkPhase = 1;
   43.46 +
   43.47 +void optionsDrawRow(const unsigned char *prefs,
   43.48 +                    int y, int line, int value, int hilite) {
   43.49 +  char txt[OPTIONS_VALUE_LEN];
   43.50 +  const char *nameText;
   43.51 +  const char *valueDesc = NULL;
   43.52 +  char altNameText[8];
   43.53 +  const char *valueOverride = isDisabledOption(prefs, line);
   43.54 +
   43.55 +  {
   43.56 +    nameText = ljGetFourCCName(optionsMenu[line].name);
   43.57 +    if (!nameText) {
   43.58 +      strncpy(altNameText, optionsMenu[line].name.c, 4);
   43.59 +      altNameText[4] = 0;
   43.60 +      nameText = altNameText;
   43.61 +    }
   43.62 +  }
   43.63 +
   43.64 +  // Format value
   43.65 +  if (valueOverride) {
   43.66 +    strcpy(txt, "overridden");
   43.67 +    valueDesc = valueOverride;
   43.68 +  } else {
   43.69 +    valueDesc = getOptionsValueStr(txt, line, value);
   43.70 +  }
   43.71 +  
   43.72 +  ljmenu_propPanelDrawRow(nameText, txt, y, hilite);
   43.73 +
   43.74 +  if (hilite & 1) {
   43.75 +    hiliteBlinkPhase = 0;
   43.76 +    const char *descText = ljGetFourCCDesc(optionsMenu[line].name);
   43.77 +    ljmenu_propPanelDrawDesc(descText ? descText : "",
   43.78 +                             valueDesc ? valueDesc : "");
   43.79 +  }
   43.80 +}
   43.81 +
   43.82 +static const char *const optionsPageShortNames[7] = {
   43.83 +  "Game", "Well", "Move", "Line", "Ctrl", "Drop", "Disp"
   43.84 +};
   43.85 +
   43.86 +void optionsDrawPage(int page, const unsigned char *prefs) {
   43.87 +  int nPages = 0;
   43.88 +  for (; optionsPages[nPages].name; ++nPages) { }
   43.89 +
   43.90 +  // draw page title
   43.91 +  ljmenu_setTitle("LOCKJAW > Options", optionsPages[page].name);
   43.92 +
   43.93 +  // draw all tabs
   43.94 +  ljmenu_beginTabs(4);
   43.95 +  for (int p = 0; p < 7; ++p) {
   43.96 +    ljmenu_addTab(optionsPageShortNames[p], p == page);
   43.97 +  }
   43.98 +  ljmenu_endTabs();
   43.99 +
  43.100 +  for (int i = optionsPages[page].start;
  43.101 +       i < optionsPages[page + 1].start; ++i) {
  43.102 +    optionsDrawRow(prefs, i - optionsPages[page].start,
  43.103 +                   i, prefs[i], 0);
  43.104 +  }
  43.105 +  for (int y = optionsPages[page + 1].start - optionsPages[page].start;
  43.106 +       y < 7; ++y) {
  43.107 +    optionsClearRow(y, 0);
  43.108 +  }
  43.109 +}
  43.110 +
  43.111 +void optionsIdle(void) {
  43.112 +  vsync();
  43.113 +  ljmenu_setHilitePalette(++hiliteBlinkPhase);
  43.114 +}
  43.115 +
  43.116 +#if 0
  43.117 +typedef u16 hicolor_t;
  43.118 +
  43.119 +__attribute__((aligned(4))) 
  43.120 +const hicolor_t gbaOptionsPalette[8] =
  43.121 +{
  43.122 +  RGB5(31,31,31), RGB5(20,20,20), RGB5( 0, 0, 0), RGB5( 0, 0, 0),
  43.123 +  RGB5(31,31,23), RGB5(20,20,15), RGB5( 0, 0, 0), RGB5( 0, 0, 0)
  43.124 +};
  43.125 +#endif
  43.126 +
    44.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    44.2 +++ b/src/gbasound.c	Fri Mar 13 00:39:12 2009 -0700
    44.3 @@ -0,0 +1,224 @@
    44.4 +#include <string.h>
    44.5 +#include "ljgba.h"
    44.6 +#include "pin8gba_sound.h"
    44.7 +extern const unsigned short note_freqs[];
    44.8 +
    44.9 +static const u16 moveEffect[] = {
   44.10 +  0, 3,
   44.11 +  0x516D, 0x0000, 0x0100
   44.12 +};
   44.13 +
   44.14 +static const u16 rotateEffect[] = {
   44.15 +  1, 7,
   44.16 +  0x5161, 0x0000, 0x5166, 0x0000, 0x516b, 0x0000, 0x0100
   44.17 +};
   44.18 +
   44.19 +static const u16 landEffect[] = {
   44.20 +  1, 6,
   44.21 +  0x8188, 0x7185, 0x6182, 0x5181, 0x3180, 0x0000
   44.22 +};
   44.23 +
   44.24 +static const u16 lockEffect[] = {
   44.25 +  3, 1,
   44.26 +  0x2138
   44.27 +};
   44.28 +
   44.29 +static const u16 lineEffect[] = {
   44.30 +  0, 12,
   44.31 +  0xB1A4, 0xA1A6, 0x91A9, 0x81A4, 0x71A7, 0x61A9,
   44.32 +  0x61A4, 0x51A6, 0x41A9, 0x31A4, 0x21A7, 0x11A9
   44.33 +};
   44.34 +
   44.35 +static const u16 holdEffect[] = {
   44.36 +  3, 8,
   44.37 +  0x3030, 0x0000, 0x4032, 0x0000, 0x3130, 0x0000, 0x202c, 0x2128
   44.38 +};
   44.39 +
   44.40 +static const u16 irsRotateEffect[] = {
   44.41 +  1, 12,
   44.42 +  0x5161, 0x51B1, 0x5166, 0x51B6, 0x516B, 0x51BB, 0x0000, 0x0000,
   44.43 +  0x0000, 0x0000, 0x0000, 0x0100
   44.44 +};
   44.45 +
   44.46 +static const u16 homerEffect[] = {
   44.47 +  0, 22,
   44.48 +  0xB1A4, 0xA1A6, 0x91A9, 0x81A4, 0x71A7, 0x61A9,
   44.49 +  0x61A4, 0x51A6, 0x41A9, 0x31A4,
   44.50 +  0xB1A7, 0xA1A9, 0x91AC, 0x81A7, 0x71AA, 0x61AC,
   44.51 +  0x51A7, 0x41A9, 0x31AC, 0x21A7, 0x11AA, 0x01AC
   44.52 +};
   44.53 +
   44.54 +static const u16 streakEffect[] = {
   44.55 +  0, 30,
   44.56 +  0xB1A4, 0xA1A6, 0x91A9, 0x81A4, 0x71A7, 0x61A9,
   44.57 +  0x61A4, 0x51A6, 0x41A9, 0x31A4,
   44.58 +  0xB1A7, 0xA1A9, 0x91AC, 0x81A7, 0x71AA, 0x61AC,
   44.59 +  0x51A7, 0x41A9, 0x31AC, 0x21A7,
   44.60 +  0xB1AA, 0xA1AC, 0x91AF, 0x71AA, 0x61AD, 0x51AF,
   44.61 +  0x31AA, 0x21AC, 0x11AF, 0x01AA
   44.62 +};
   44.63 +
   44.64 +static const u16 sectionEffect[] = {
   44.65 +  1, 30,
   44.66 +  0xB1B0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
   44.67 +  0xB1B4, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
   44.68 +  0xB1B7, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
   44.69 +  0xB1BC, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
   44.70 +  0xB1B0, 0x0000
   44.71 +};
   44.72 +
   44.73 +static const u16 gameOverEffect1[] = {
   44.74 +  1, 40,
   44.75 +  0xF504, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
   44.76 +  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
   44.77 +  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
   44.78 +  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
   44.79 +  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
   44.80 +};
   44.81 +
   44.82 +static const u16 gameOverEffect2[] = {
   44.83 +  3, 40,
   44.84 +  0xC12C, 0xA725, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
   44.85 +  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
   44.86 +  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
   44.87 +  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
   44.88 +  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
   44.89 +};
   44.90 +
   44.91 +static const u16 winEffect1[] = {
   44.92 +  1, 30,
   44.93 +  0xA564, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000,
   44.94 +  0xA562, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000,
   44.95 +  0xA564, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000,
   44.96 +  0xA566, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
   44.97 +  0x0000, 0x0000
   44.98 +};
   44.99 +
  44.100 +static const u16 winEffect2[] = {
  44.101 +  0, 30,
  44.102 +  0xA568, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000,
  44.103 +  0xA566, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000,
  44.104 +  0xA568, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000,
  44.105 +  0xA56A, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
  44.106 +  0x0000, 0x0000
  44.107 +};
  44.108 +
  44.109 +
  44.110 +static const u16 *const effects[] = {
  44.111 +  moveEffect, rotateEffect, landEffect, lockEffect, lineEffect,
  44.112 +  holdEffect, irsRotateEffect, homerEffect, streakEffect, sectionEffect,
  44.113 +  gameOverEffect1, gameOverEffect2, winEffect1, winEffect2
  44.114 +};
  44.115 +
  44.116 +const unsigned short tri_vol_table[5] = {
  44.117 +  0,
  44.118 +  TRILENVOL_25,
  44.119 +  TRILENVOL_50,
  44.120 +  TRILENVOL_75,
  44.121 +  TRILENVOL_100
  44.122 +};
  44.123 +
  44.124 +void play_note(unsigned int ch, unsigned int note, unsigned int instrument) {
  44.125 +  switch (ch) {
  44.126 +  case 0:
  44.127 +    SQR1CTRL = instrument;
  44.128 +    SQR1FREQ = note_freqs[note] | FREQ_RESET;
  44.129 +    break;
  44.130 +  case 1:
  44.131 +    SQR2CTRL = instrument;
  44.132 +    SQR2FREQ = note_freqs[note] | FREQ_RESET;
  44.133 +    break;
  44.134 +  case 2:
  44.135 +    {
  44.136 +      int vol = instrument >> 13;
  44.137 +      TRILENVOL = tri_vol_table[(vol + 1) / 2];
  44.138 +      TRIFREQ = note_freqs[note] | FREQ_RESET;
  44.139 +    }
  44.140 +    break;
  44.141 +  case 3:
  44.142 +    NOISECTRL = instrument;
  44.143 +    {
  44.144 +      int octave = (note & 0x3E) << 2;
  44.145 +      int pitch = note & 0x03;
  44.146 +      NOISEFREQ = (octave | pitch) ^ (0xF7 | FREQ_RESET);
  44.147 +    }
  44.148 +    break;
  44.149 +  }
  44.150 +}
  44.151 +
  44.152 +void gba_poll_sound(struct LJPCView *v) {
  44.153 +  for (int i = 0; i < 4; ++i) {
  44.154 +    if (v->sndLeft[i]) {
  44.155 +      unsigned int data = *v->sndData[i];
  44.156 +      if (data) {
  44.157 +        play_note(i, data & 0x003F, data & 0xFFC0);
  44.158 +      }
  44.159 +      ++v->sndData[i];
  44.160 +      --v->sndLeft[i];
  44.161 +    }
  44.162 +  }
  44.163 +}
  44.164 +
  44.165 +void gba_play_sound(struct LJPCView *v, int effect) {
  44.166 +  const u16 *data = effects[effect];
  44.167 +  int ch = data[0];
  44.168 +  int len = data[1];
  44.169 +  
  44.170 +  // square waves can be played on either channel 0 or 1;
  44.171 +  // switch if this channel is occupied and the other is free
  44.172 +  if (ch < 2
  44.173 +      && v->sndLeft[ch] > v->sndLeft[1 - ch]) {
  44.174 +    ch = 1 - ch;
  44.175 +  }
  44.176 +  v->sndData[ch] = data + 2;
  44.177 +  v->sndLeft[ch] = len;
  44.178 +}
  44.179 +
  44.180 +/**
  44.181 + * Sets the sound bias to mid and resolution to 8-bit.
  44.182 + * Setting bias is needed because some launchers (such as pogoshell)
  44.183 + * set it to a state that mutes the tone generators.
  44.184 + */
  44.185 +static void set_bias(void)
  44.186 +{
  44.187 +  asm volatile("mov r2, #2; lsl r2, #8; swi 0x19" ::: "r0", "r1", "r2", "r3");
  44.188 +  SETSNDRES(1);
  44.189 +}
  44.190 +
  44.191 +__attribute__((aligned(4))) 
  44.192 +static const unsigned char triangleWave[16] = {
  44.193 +  0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
  44.194 +  0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10
  44.195 +};
  44.196 +unsigned char lastWaveBank = 0;
  44.197 +
  44.198 +void tri_set_waveform(const void *waveform) {
  44.199 +  memcpy((void *)TRIWAVERAM, waveform, 16);
  44.200 +  lastWaveBank = !lastWaveBank;
  44.201 +  TRICTRL = TRICTRL_2X32 | TRICTRL_BANK(lastWaveBank) | TRICTRL_ENABLE;
  44.202 +}
  44.203 +
  44.204 +void install_sound(struct LJPCView *v) {
  44.205 +  SNDSTAT = SNDSTAT_ENABLE;
  44.206 +  DMGSNDCTRL = DMGSNDCTRL_LVOL(7) | DMGSNDCTRL_RVOL(7)
  44.207 +             | DMGSNDCTRL_LSQR1 | DMGSNDCTRL_RSQR1
  44.208 +             | DMGSNDCTRL_LSQR2 | DMGSNDCTRL_RSQR2
  44.209 +             | DMGSNDCTRL_LTRI | DMGSNDCTRL_RTRI
  44.210 +             | DMGSNDCTRL_LNOISE | DMGSNDCTRL_RNOISE;
  44.211 +  DSOUNDCTRL = DSOUNDCTRL_DMG100;
  44.212 +  set_bias();
  44.213 +  SQR1SWEEP = SQR1SWEEP_OFF;
  44.214 +
  44.215 +#if 0
  44.216 +  TRICTRL = TRICTRL_2X32 | TRICTRL_BANK(0) | TRICTRL_ENABLE;
  44.217 +  tri_set_waveform(triangleWave);
  44.218 +  TRILENVOL = 0;
  44.219 +  TRIFREQ = 1024 | FREQ_RESET;
  44.220 +#endif
  44.221 +
  44.222 +  v->sndLeft[0] = 0;
  44.223 +  v->sndLeft[1] = 0;
  44.224 +  v->sndLeft[2] = 0;
  44.225 +  v->sndLeft[3] = 0;
  44.226 +}
  44.227 +
    45.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    45.2 +++ b/src/gimmicks.c	Fri Mar 13 00:39:12 2009 -0700
    45.3 @@ -0,0 +1,295 @@
    45.4 +/* Gimmick code for LOCKJAW, an implementation of the Soviet Mind Game
    45.5 +
    45.6 +Copyright (C) 2006-2007 Damian Yerrick <tepples+lj@spamcop.net>
    45.7 +
    45.8 +This work is free software; you can redistribute it and/or modify
    45.9 +it under the terms of the GNU General Public License as published by
   45.10 +the Free Software Foundation; either version 2 of the License, or
   45.11 +(at your option) any later version.
   45.12 +
   45.13 +This program is distributed in the hope that it will be useful,
   45.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   45.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   45.16 +GNU General Public License for more details.
   45.17 +
   45.18 +You should have received a copy of the GNU General Public License
   45.19 +along with this program; if not, write to the Free Software
   45.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   45.21 +
   45.22 +Original game concept and design by Alexey Pajitnov.
   45.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   45.24 +or The Tetris Company LLC.
   45.25 +
   45.26 +*/
   45.27 +
   45.28 +#include "lj.h"
   45.29 +#include "ljcontrol.h"
   45.30 +
   45.31 +void initSpeed(LJField *p);  // in speed.c
   45.32 +void setSpeed(LJField *p, LJControl *c);  // in speed.c
   45.33 +int updLevelAfterPiece(LJField *p);  // in speed.c
   45.34 +int updLevelAfterLines(LJField *p, size_t nLines);  // in speed.c
   45.35 +
   45.36 +void initGimmicks(LJField *p) {
   45.37 +  initSpeed(p);
   45.38 +  p->speed.entryDelay = 0;  /* new pieces will increase this */
   45.39 +  p->speed.lineDelay = 0;
   45.40 +  if (p->garbageStyle == LJGARBAGE_DRILL
   45.41 +      || p->gimmick == LJGM_DRILL) {
   45.42 +    p->garbageRandomness = 255;
   45.43 +    p->garbage = p->ceiling - 2;
   45.44 +  } else if (p->garbageStyle == LJGARBAGE_ZIGZAG) {
   45.45 +    setupZigzagField(p, p->ceiling * 3 / 4);
   45.46 +  }
   45.47 +
   45.48 +}
   45.49 +
   45.50 +LJBits gimmicks(LJField *p, LJControl *c) {
   45.51 +  LJBits changed = 0;
   45.52 +
   45.53 +  // In rhythm, lock the tetromino if the player
   45.54 +  // isn't keeping up with the rate
   45.55 +  if (p->speedState.curve == LJSPD_RHYTHM
   45.56 +      || p->speedState.curve == LJSPD_RHYTHMZERO) {
   45.57 +    p->bpmCounter += p->speedState.level;
   45.58 +
   45.59 +    if (p->bpmCounter >= 0) {
   45.60 +      if (p->state == LJS_LANDED) {
   45.61 +        p->stateTime = 0;
   45.62 +      } else if (p->state == LJS_FALLING) {
   45.63 +        p->speed.gravity = ljitofix(LJ_PF_HT);
   45.64 +      }
   45.65 +    }
   45.66 +
   45.67 +    // In rhythm, increase BPM periodically
   45.68 +    p->speedupCounter += p->speedState.level;
   45.69 +    if(p->speedupCounter >= 60 * 60 * 64) {
   45.70 +      p->speedupCounter -= 60 * 60 * 64;
   45.71 +      p->speedState.level += 10;
   45.72 +      p->sounds |= LJSND_SECTIONUP;
   45.73 +    }
   45.74 +  }
   45.75 +
   45.76 +  // For each piece, set the entry and line delays.
   45.77 +  // Don't set it twice when spawning to replace the
   45.78 +  // first held piece (both LJSND_SPAWN and LJSND_HOLD
   45.79 +  // on the same piece).
   45.80 +  if ((p->sounds & (LJSND_SPAWN | LJSND_HOLD))
   45.81 +      == LJSND_SPAWN) {
   45.82 +    setSpeed(p, c);
   45.83 +  }
   45.84 +
   45.85 +  if (p->sounds & LJSND_LOCK) {
   45.86 +    if (p->gimmick == LJGM_ITEMS) {
   45.87 +      if (p->nPieces >= 7) {
   45.88 +        p->canRotate = 0;
   45.89 +        p->speed.gravity = ljitofix(1);
   45.90 +      }
   45.91 +    }
   45.92 +    
   45.93 +    // Garbage in simulated multiplayer
   45.94 +    int simGarbage = p->nPieces >= 7
   45.95 +                     && p->garbageStyle >= LJGARBAGE_1
   45.96 +                     && p->garbageStyle <= LJGARBAGE_4
   45.97 +                     && (p->curPiece[1] == LJP_I
   45.98 +                         || ((p->pieceSet == LJRAND_SZ
   45.99 +                              || p->pieceSet == LJRAND_JLOSTZ)
  45.100 +                             && p->nPieces % 7 == 3));
  45.101 +    if (simGarbage) {
  45.102 +      p->garbage += p->garbageStyle;
  45.103 +    }
  45.104 +
  45.105 +    // Banana attack in "Vs. with Items" gimmick    
  45.106 +    if (p->gimmick == LJGM_ITEMS
  45.107 +        && (ljRand(p) & 0xF00) == 0xF00) {
  45.108 +      shuffleColumns(p);
  45.109 +      changed |= (1 << LJ_PF_HT) - 1;
  45.110 +    }
  45.111 +  }
  45.112 +  return changed;
  45.113 +}
  45.114 +
  45.115 +
  45.116 +
  45.117 +const char hotlineRows[LJ_PF_HT] =
  45.118 +{ 
  45.119 +  0, 0, 0, 0, 1,
  45.120 +  0, 0, 0, 0, 2,
  45.121 +  0, 0, 0, 3, 0,
  45.122 +  0, 4, 0, 5, 6,
  45.123 +  0, 0, 0, 0
  45.124 +};
  45.125 +
  45.126 +static const char garbageScoreTable[] = { 0, 0, 1, 2, 4 };
  45.127 +static const char tSpinGarbageScoreTable[] = { 0, 2, 4, 6, 6 };
  45.128 +static const unsigned char squareScoreTable[] =
  45.129 + {1, 1, 1, 2, 3, 5, 8, 13,
  45.130 +  21, 34, 55, 89, 144, 200};
  45.131 +
  45.132 +static const char tdsScoreTable[] = {0, 1, 3, 5, 8};
  45.133 +static const char tdsTSScoreTable[] = {4, 8, 12, 16};
  45.134 +static const char nesScoreTable[] = {0, 4, 10, 30, 120};
  45.135 +
  45.136 +
  45.137 +/**
  45.138 + * Computes the score and outgoing garbage for lines
  45.139 + * and adds them to the player's total.
  45.140 + * @param p The playfield
  45.141 + * @param lines Bit array where 1 means a line clear on this row.
  45.142 + */
  45.143 +void addLinesScore(LJField *p, LJBits lines) {
  45.144 +  const int nLines = countOnes(lines);
  45.145 +  int oldLines = p->nLinesThisPiece;
  45.146 +  int tdsSection = p->lines / 10 + 1;
  45.147 +
  45.148 +  p->lines += nLines;
  45.149 +  if (updLevelAfterLines(p, nLines)) {
  45.150 +    p->sounds |= LJSND_SECTIONUP;
  45.151 +  }
  45.152 +
  45.153 +  switch (p->scoreStyle) {
  45.154 +  
  45.155 +  case LJSCORE_TNT64:
  45.156 +    if (nLines < 1) {
  45.157 +      return;
  45.158 +    }
  45.159 +    for (int i = nLines; i > 0; --i) {
  45.160 +      if (oldLines > sizeof(squareScoreTable) - 1) {
  45.161 +        oldLines = sizeof(squareScoreTable) - 1;
  45.162 +      }
  45.163 +      p->score += 100 * squareScoreTable[oldLines++];
  45.164 +    } break;
  45.165 +    
  45.166 +  case LJSCORE_NES:
  45.167 +    {
  45.168 +      int garbageLevel = (nLines > 4) ? 4 : nLines;
  45.169 +      int value = nesScoreTable[garbageLevel];
  45.170 +
  45.171 +      p->score += 10 * tdsSection * value;
  45.172 +      if (nLines >= 4) {
  45.173 +        p->sounds |= LJSND_SETB2B;
  45.174 +      }
  45.175 +    } break;
  45.176 +
  45.177 +  case LJSCORE_TDS:
  45.178 +    {
  45.179 +      // Garbage based scoring system.
  45.180 +      int garbageLevel = (nLines > 4) ? 4 : nLines;
  45.181 +      int chain, value;
  45.182 +      
  45.183 +      if (p->isSpin) {
  45.184 +        chain = (nLines >= 1);
  45.185 +        value = tdsTSScoreTable[garbageLevel];
  45.186 +        
  45.187 +        // TDS gives fewer points for a T-spin single
  45.188 +        // that involves a wall kick.
  45.189 +        if (p->isSpin == 2 && nLines < 2) {
  45.190 +          value >>= 2;
  45.191 +        }
  45.192 +      } else {
  45.193 +        chain = (nLines >= 4);
  45.194 +        value = tdsScoreTable[garbageLevel];
  45.195 +      }
  45.196 +      if (chain && p->chain && nLines >= 1) {  // b2b
  45.197 +        value = value * 3 / 2;
  45.198 +      }
  45.199 +
  45.200 +      if (tdsSection > 20) {
  45.201 +        tdsSection = 20;
  45.202 +      }
  45.203 +      p->score += 100 * tdsSection * value;
  45.204 +      if (nLines >= 1) {
  45.205 +        if (chain) {
  45.206 +          p->sounds |= LJSND_SETB2B;
  45.207 +          if (p->chain) {
  45.208 +            p->sounds |= LJSND_B2B;
  45.209 +          }
  45.210 +        }
  45.211 +        p->chain = chain;
  45.212 +      }
  45.213 +    } break;
  45.214 +
  45.215 +  case LJSCORE_LJ:
  45.216 +  case LJSCORE_LJ_NERF:
  45.217 +    if (nLines >= 1) {
  45.218 +      // Garbage based scoring system.
  45.219 +      int garbageLevel = (nLines > 4) ? 4 : nLines;
  45.220 +      unsigned int chain, garbage;
  45.221 +
  45.222 +      if (p->isSpin) {
  45.223 +        chain = (nLines >= 1);
  45.224 +        garbage = tSpinGarbageScoreTable[garbageLevel];
  45.225 +        if (p->scoreStyle == LJSCORE_LJ_NERF) {
  45.226 +          garbage /= 2;
  45.227 +        }
  45.228 +        garbage += (chain && p->chain);
  45.229 +      } else {
  45.230 +        chain = (nLines >= 4);
  45.231 +        garbage = garbageScoreTable[garbageLevel]
  45.232 +                      + (chain && p->chain);
  45.233 +      }
  45.234 +      p->score += 100 * nLines + 200 * garbage;
  45.235 +      p->outGarbage += garbage;
  45.236 +      if (chain) {
  45.237 +        p->sounds |= LJSND_SETB2B;
  45.238 +        if (p->chain) {
  45.239 +          p->sounds |= LJSND_B2B;
  45.240 +        }
  45.241 +      }
  45.242 +      p->chain = chain;
  45.243 +    } break;
  45.244 +  
  45.245 +  case LJSCORE_HOTLINE:
  45.246 +    for (int y = 0; y < 24; ++y) {
  45.247 +      if (lines & (1 << y)) {
  45.248 +        p->score += 100 * hotlineRows[y];
  45.249 +      }
  45.250 +    } break;
  45.251 +
  45.252 +  }
  45.253 +
  45.254 +  p->nLinesThisPiece += nLines;
  45.255 +}
  45.256 +
  45.257 +/**
  45.258 + * Puts a zigzag pattern of garbage into the playfield.
  45.259 + * Useful as a test suite for calcZigzagGrade() in debrief.c.
  45.260 + * @param p the playfield to set up
  45.261 + * @param height the number of rows to set up
  45.262 + */
  45.263 +void setupZigzagField(LJField *p, size_t height) {
  45.264 +  unsigned int hole = p->leftWall;
  45.265 +  int delta = 1;
  45.266 +
  45.267 +  if (height > p->ceiling) {
  45.268 +    height = p->ceiling;
  45.269 +  }
  45.270 +
  45.271 +  // set up example pattern
  45.272 +  for (size_t y = 0; y < height; ++y) {
  45.273 +    for (size_t x = p->leftWall; x < p->rightWall; ++x) {
  45.274 +
  45.275 +      // A block should be in all cells of the row
  45.276 +      // except for the hole cell, which should be empty.
  45.277 +      if (x == hole) {
  45.278 +        p->b[y][x] = 0;
  45.279 +      } else {
  45.280 +        p->b[y][x] = 0x80;
  45.281 +      }
  45.282 +    }
  45.283 +
  45.284 +    if (hole == p->rightWall - 1) {
  45.285 +      delta = -1;
  45.286 +    } else if (hole == p->leftWall) {
  45.287 +      delta = 1;
  45.288 +    }
  45.289 +    hole += delta;
  45.290 +  }
  45.291 +
  45.292 +  // clear rows above pattern
  45.293 +  for (size_t y = height; y < LJ_PF_HT; ++y) {
  45.294 +    for (size_t x = p->leftWall; x < p->rightWall; ++x) {
  45.295 +      p->b[y][x] = 0;
  45.296 +    }
  45.297 +  }
  45.298 +}
    46.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    46.2 +++ b/src/lj.c	Fri Mar 13 00:39:12 2009 -0700
    46.3 @@ -0,0 +1,1516 @@
    46.4 +/* Engine of LOCKJAW, an implementation of the Soviet Mind Game
    46.5 +
    46.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    46.7 +
    46.8 +This work is free software; you can redistribute it and/or modify
    46.9 +it under the terms of the GNU General Public License as published by
   46.10 +the Free Software Foundation; either version 2 of the License, or
   46.11 +(at your option) any later version.
   46.12 +
   46.13 +This program is distributed in the hope that it will be useful,
   46.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   46.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   46.16 +GNU General Public License for more details.
   46.17 +
   46.18 +You should have received a copy of the GNU General Public License
   46.19 +along with this program; if not, write to the Free Software
   46.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   46.21 +
   46.22 +Original game concept and design by Alexey Pajitnov.
   46.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   46.24 +or The Tetris Company LLC.
   46.25 +
   46.26 +
   46.27 +*/
   46.28 +
   46.29 +#define LJ_INTERNAL
   46.30 +#include "lj.h"
   46.31 +
   46.32 +unsigned int ljRand(LJField *p) {
   46.33 +  p->seed = p->seed * 2147001325 + 715136305;
   46.34 +  return p->seed >> 17;
   46.35 +}
   46.36 +
   46.37 +static inline void ljAssert(LJField *p, int shouldBeTrue, const char *reason) {
   46.38 +  if (!shouldBeTrue) {
   46.39 +    p->state = LJS_GAMEOVER;
   46.40 +  }
   46.41 +}
   46.42 +
   46.43 +static const char xShapes[N_PIECE_SHAPES][4][4] = {
   46.44 +  { {0,1,2,3}, {2,2,2,2}, {3,2,1,0}, {1,1,1,1} }, // I
   46.45 +  { {0,1,2,0}, {1,1,1,2}, {2,1,0,2}, {1,1,1,0} }, // J
   46.46 +  { {0,1,2,2}, {1,1,1,2}, {2,1,0,0}, {1,1,1,0} }, // L
   46.47 +  { {1,1,2,2}, {1,2,2,1}, {2,2,1,1}, {2,1,1,2} }, // O
   46.48 +  { {0,1,1,2}, {1,1,2,2}, {2,1,1,0}, {1,1,0,0} }, // S
   46.49 +  { {0,1,2,1}, {1,1,1,2}, {2,1,0,1}, {1,1,1,0} }, // T
   46.50 +  { {0,1,1,2}, {2,2,1,1}, {2,1,1,0}, {0,0,1,1} }, // Z
   46.51 +  { {0,1,2,3}, {2,2,2,2}, {3,2,1,0}, {1,1,1,1} }, // I2
   46.52 +  { {0,1,2,0}, {1,1,1,2}, {2,1,0,2}, {1,1,1,0} }, // I3
   46.53 +  { {1,1,2,2}, {1,2,2,1}, {2,2,1,1}, {2,1,1,2} }, // L3
   46.54 +};
   46.55 +
   46.56 +static const char yShapes[N_PIECE_SHAPES][4][4] = {
   46.57 +  { {2,2,2,2}, {3,2,1,0}, {1,1,1,1}, {0,1,2,3} }, // I
   46.58 +  { {2,2,2,3}, {3,2,1,3}, {2,2,2,1}, {1,2,3,1} }, // J
   46.59 +  { {2,2,2,3}, {3,2,1,1}, {2,2,2,1}, {1,2,3,3} }, // L
   46.60 +  { {2,3,3,2}, {3,3,2,2}, {3,2,2,3}, {2,2,3,3} }, // O
   46.61 +  { {2,2,3,3}, {3,2,2,1}, {2,2,1,1}, {1,2,2,3} }, // S
   46.62 +  { {2,2,2,3}, {3,2,1,2}, {2,2,2,1}, {1,2,3,2} }, // T
   46.63 +  { {3,3,2,2}, {3,2,2,1}, {1,1,2,2}, {1,2,2,3} }, // Z
   46.64 +  { {2,2,2,2}, {3,2,1,0}, {1,1,1,1}, {0,1,2,3} }, // I2
   46.65 +  { {2,2,2,3}, {3,2,1,3}, {2,2,2,1}, {1,2,3,1} }, // I3
   46.66 +  { {2,3,3,2}, {3,3,2,2}, {3,2,2,3}, {2,2,3,3} }, // L3
   46.67 +};
   46.68 +
   46.69 +const char pieceColors[N_PIECE_SHAPES] = {
   46.70 +  0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,
   46.71 +  0x40, 0x10, 0x20
   46.72 +};
   46.73 +
   46.74 +static const signed char connShapes[N_PIECE_SHAPES][4] = {
   46.75 +  {CONNECT_R,   CONNECT_LR,  CONNECT_LR,  CONNECT_L},  // I
   46.76 +  {CONNECT_UR,  CONNECT_LR,  CONNECT_L,   CONNECT_D},  // J
   46.77 +  {CONNECT_R,   CONNECT_LR,  CONNECT_UL,  CONNECT_D},  // L
   46.78 +  {CONNECT_UR,  CONNECT_DR,  CONNECT_DL,  CONNECT_UL}, // O
   46.79 +  {CONNECT_R,   CONNECT_UL,  CONNECT_DR,  CONNECT_L},  // S
   46.80 +  {CONNECT_R,   CONNECT_ULR, CONNECT_L,   CONNECT_D},  // T
   46.81 +  {CONNECT_R,   CONNECT_DL,  CONNECT_UR,  CONNECT_L},  // Z
   46.82 +  {-1,          CONNECT_R,   CONNECT_L,   -1},         // I2
   46.83 +  {CONNECT_R,   CONNECT_LR,  CONNECT_L,   -1},         // I3
   46.84 +  {CONNECT_UR,  CONNECT_D,   -1,          CONNECT_L},  // L3
   46.85 +};
   46.86 +
   46.87 +static inline int pieceToFieldBlock(int piece, int conn) {
   46.88 +  return conn | pieceColors[piece];
   46.89 +}
   46.90 +
   46.91 +/**
   46.92 + * @theta rotation state: 0-3 normal, 4 for next piece
   46.93 + */
   46.94 +void expandPieceToBlocks(LJBlkSpec out[],
   46.95 +                         const LJField *p, int piece, int xBase, int yBase, int theta) {
   46.96 +  int shape = piece & LJP_MASK;
   46.97 +  
   46.98 +  if (theta >= 4) {
   46.99 +    const LJRotSystem *rs = rotSystems[p->rotationSystem];
  46.100 +    int kickData = rs->entryOffset[shape];
  46.101 +    xBase += WKX(kickData);
  46.102 +    yBase += WKY(kickData);
  46.103 +    kickData = rs->entryOffset[LJP_I];
  46.104 +    xBase -= WKX(kickData);
  46.105 +    yBase -= WKY(kickData);
  46.106 +    theta = rs->entryTheta[shape];
  46.107 +  }
  46.108 +  
  46.109 +  const char *xBl = xShapes[shape][theta];
  46.110 +  const char *yBl = yShapes[shape][theta];
  46.111 +
  46.112 +  for (int blk = 0; blk < 4; ++blk) {
  46.113 +    if (connShapes[shape][blk] == -1) {
  46.114 +      out[blk].conn = 0;
  46.115 +    } else {
  46.116 +      int conn = connShapes[shape][blk] << theta;
  46.117 +      int connRotated = (conn | (conn >> 4)) & CONNECT_MASK;
  46.118 +      int color = ((0x10 << blk) & piece) ? 8 : piece;
  46.119 +      out[blk].y = yBl[blk] + yBase;
  46.120 +      out[blk].x = xBl[blk] + xBase;
  46.121 +      out[blk].conn = pieceToFieldBlock(color, connRotated);
  46.122 +    }
  46.123 +  }
  46.124 +}
  46.125 +
  46.126 +int isOccupied(const LJField *p, int x, int y) {
  46.127 +  if (x < p->leftWall || x >= p->rightWall || y < 0)
  46.128 +    return 1;
  46.129 +  if (y > LJ_PF_HT)
  46.130 +    return 0;
  46.131 +  return p->b[y][x] > 1;
  46.132 +}
  46.133 +
  46.134 +int isCollision(const LJField *p, int x, int y, int theta) {
  46.135 +  LJBlkSpec blocks[4];
  46.136 +  int piece = p->curPiece[0];
  46.137 +
  46.138 +  expandPieceToBlocks(blocks, p, piece, x, y, theta);
  46.139 +
  46.140 +  for (int blk = 0; blk < 4; ++blk) {
  46.141 +    if (blocks[blk].conn
  46.142 +        && isOccupied(p, blocks[blk].x, blocks[blk].y))
  46.143 +      return 1;
  46.144 +  }
  46.145 +  return 0;
  46.146 +}
  46.147 +
  46.148 +/**
  46.149 + * Determines whether the piece that just landed was a T-spin.
  46.150 + * Must be called just BEFORE lockdown writes the blocks to the
  46.151 + * playfield; otherwise TNT will break.
  46.152 + */
  46.153 +static int isTspin(const LJField *p) {
  46.154 +  int blks = 0;
  46.155 +  int x = p->x;
  46.156 +  int y = p->hardDropY;
  46.157 +
  46.158 +  switch (p->tSpinAlgo) {
  46.159 +  case LJTS_TNT:
  46.160 +    if (!isCollision(p, x, y + 1, p->theta)
  46.161 +        || !isCollision(p, x - 1, y, p->theta)
  46.162 +        || !isCollision(p, x + 1, y, p->theta)) {
  46.163 +      return 0;
  46.164 +    }    
  46.165 +    return p->isSpin;
  46.166 +  case LJTS_TDS_NO_KICK:
  46.167 +  
  46.168 +    // If t-spin involved wall kick, don't count it
  46.169 +    if (p->isSpin == 2) {
  46.170 +      return 0;
  46.171 +    }
  46.172 +    
  46.173 +    // otherwise fall through
  46.174 +  case LJTS_TDS:
  46.175 +    // 1. T tetromino
  46.176 +    if ((p->curPiece[0] & LJP_MASK) != LJP_T) {
  46.177 +      return 0;
  46.178 +    }
  46.179 +
  46.180 +    // 2. Last move was spin
  46.181 +    if (!p->isSpin) {
  46.182 +      return 0;
  46.183 +    }
  46.184 +
  46.185 +    // 3. At least three cells around the rotation center are full
  46.186 +    if (isOccupied(p, x, y + 1)) {
  46.187 +      ++blks;
  46.188 +    }
  46.189 +    if (isOccupied(p, x, y + 3)) {
  46.190 +      ++blks;
  46.191 +    }
  46.192 +    if (isOccupied(p, x + 2, y + 1)) {
  46.193 +      ++blks;
  46.194 +    }
  46.195 +    if (isOccupied(p, x + 2, y + 3)) {
  46.196 +      ++blks;
  46.197 +    }
  46.198 +    if (blks < 3) {
  46.199 +      return 0;
  46.200 +    }
  46.201 +    
  46.202 +    // 3. Last move was spin
  46.203 +    return p->isSpin;
  46.204 +  default:
  46.205 +    return 0;
  46.206 +  }
  46.207 +}
  46.208 +
  46.209 +/**
  46.210 + * Calculates where the active piece in a playfield will fall
  46.211 + * if dropped, and writes it back to the playfield.
  46.212 + * This value is used for ghost piece, gravity, soft drop, and
  46.213 + * hard drop. Call this after the active piece has been spawned,
  46.214 + * moved, or rotated.
  46.215 + * @param p the playfield
  46.216 + */
  46.217 +static void updHardDropY(LJField *p) {
  46.218 +  int x = p->x;
  46.219 +  int y = ljfixfloor(p->y);
  46.220 +  int theta = p->theta;
  46.221 +
  46.222 +  if (p->bottomBlocks) {
  46.223 +    y = -2;
  46.224 +    while (y < (int)LJ_PF_HT
  46.225 +           && y < ljfixfloor(p->y)
  46.226 +           && isCollision(p, x, y, theta)) {
  46.227 +      ++y;
  46.228 +    }
  46.229 +  }
  46.230 +  else {
  46.231 +    while (!isCollision(p, x, y - 1, theta)) {
  46.232 +      --y;
  46.233 +    }
  46.234 +  }
  46.235 +  p->hardDropY = y;
  46.236 +}
  46.237 +
  46.238 +
  46.239 +/**
  46.240 + * Look for a TNT square in this position.
  46.241 + * @param x column of left side, such that 0 <= x <= playfield width - 4
  46.242 + * @param y row of bottom block, such that 0 <= y <= playfield height - 4
  46.243 + * @param isMulti nonzero for multisquares; 0 for monosquares
  46.244 + * @return nonzero if found; 0 if not found
  46.245 + */
  46.246 +static int isSquareAt(LJField *p, int x, int y, int isMulti)
  46.247 +{
  46.248 +  int firstColor = p->b[y][x] & COLOR_MASK;
  46.249 +
  46.250 +  // Check the frame to make sure it isn't connected to anything else
  46.251 +  for(int i = 0; i <= 3; i++)
  46.252 +  {
  46.253 +    /* don't allow squares within parts of squares */
  46.254 +    if((p->b[y + i][x] & COLOR_MASK) >= 0x80)
  46.255 +      return 0;
  46.256 +    /* the block doesn't connect on the left */
  46.257 +    if(p->b[y + i][x] & CONNECT_L)
  46.258 +      return 0;
  46.259 +    /* the block doesn't connect on the right */
  46.260 +    if(p->b[y + i][x + 3] & CONNECT_R)
  46.261 +      return 0;
  46.262 +    /* the block doesn't connect on the bottom */
  46.263 +    if(p->b[y][x + i] & CONNECT_D)
  46.264 +      return 0;
  46.265 +    /* the block doesn't connect on the top */
  46.266 +    if(p->b[y + 3][x + i] & CONNECT_U)
  46.267 +      return 0;
  46.268 +  }
  46.269 +
  46.270 +  for(int ySub = 0; ySub < 4; ++ySub)
  46.271 +  {
  46.272 +    for(int xSub = 0; xSub <= 3; ++xSub)
  46.273 +    {
  46.274 +      int blkHere = p->b[y + ySub][x + xSub];
  46.275 +
  46.276 +      /* the square contains no nonexistent blocks */
  46.277 +      if(!blkHere)
  46.278 +        return 0;
  46.279 +      /* the square contains no blocks of garbage or broken pieces */
  46.280 +      if((blkHere & COLOR_MASK) == 0x80)
  46.281 +        return 0;
  46.282 +      /* if looking for monosquares, disallow multisquares */
  46.283 +      if(isMulti == 0 && (blkHere & COLOR_MASK) != firstColor)
  46.284 +        return 0;
  46.285 +    }
  46.286 +  }
  46.287 +  return 1;
  46.288 +}
  46.289 +
  46.290 +/**
  46.291 + * Replaces the 4x4 blocks with a 4x4 square.
  46.292 + * @param x column of left side, such that 0 <= x <= playfield width - 4
  46.293 + * @param y row of bottom block, such that 0 <= y <= playfield height - 4
  46.294 + * @param isMulti nonzero for multisquares; 0 for monosquares
  46.295 + * @return the rows that were changed
  46.296 + */
  46.297 +static LJBits markSquare(LJField *p, int x, int y, int isMulti)
  46.298 +{
  46.299 +  int baseBlk = (isMulti ? 0xA0 : 0xB0)
  46.300 +                | CONNECT_MASK;
  46.301 +  for(int i = 0; i < 4; ++i)
  46.302 +  {
  46.303 +    int c;
  46.304 +
  46.305 +    if(i == 0)
  46.306 +      c = baseBlk & ~CONNECT_D;
  46.307 +    else if(i == 3)
  46.308 +      c = baseBlk & ~CONNECT_U;
  46.309 +    else
  46.310 +      c = baseBlk;
  46.311 +
  46.312 +    p->b[y + i][x + 0] = c & ~CONNECT_L;
  46.313 +    p->b[y + i][x + 1] = c;
  46.314 +    p->b[y + i][x + 2] = c;
  46.315 +    p->b[y + i][x + 3] = c & ~CONNECT_R;
  46.316 +  }
  46.317 +
  46.318 +  if (isMulti) {
  46.319 +    ++p->multisquares;
  46.320 +  } else {
  46.321 +    ++p->monosquares;
  46.322 +  }
  46.323 +
  46.324 +  return 0x0F << y;
  46.325 +}
  46.326 +
  46.327 +
  46.328 +/**
  46.329 + * Marks all 4x4 squares in the playfield.
  46.330 + * In the case that a single tetromino forms multiple overlapping
  46.331 + * squares, prefers gold over silver, high over low, left over right.
  46.332 + * @param isMulti nonzero for multisquares; 0 for monosquares
  46.333 + * @return the rows that were changed
  46.334 + */
  46.335 +static LJBits findSquares(LJField *p, int isMulti) {
  46.336 +  LJBits changed = 0;
  46.337 +
  46.338 +  for (int y = LJ_PF_HT - 4; y >= 0; --y) {
  46.339 +    for (int x = p->leftWall; x <= p->rightWall - 4; ++x) {
  46.340 +      int baseBlk = p->b[y][x];
  46.341 +
  46.342 +      // If this block is filled in and not connected on the left or right
  46.343 +      // then do stuff to it
  46.344 +      if (baseBlk
  46.345 +          && !(baseBlk & (CONNECT_D | CONNECT_L))
  46.346 +          && isSquareAt(p, x, y, isMulti)) {
  46.347 +        changed |= markSquare(p, x, y, isMulti);
  46.348 +        p->sounds |= LJSND_SQUARE;
  46.349 +      }
  46.350 +    }
  46.351 +  }
  46.352 +  return changed;
  46.353 +}
  46.354 +
  46.355 +static LJBits stickyGluing(LJField *p) {
  46.356 +  LJBits changed = 0;
  46.357 +  int byColor = (p->gluing == LJGLUING_STICKY_BY_COLOR);
  46.358 +
  46.359 +  for (int y = 0; y < LJ_PF_HT; ++y) {
  46.360 +    for (int x = p->leftWall; x < p->rightWall - 1; ++x) {
  46.361 +      int l = p->b[y][x];
  46.362 +      int r = p->b[y][x + 1];
  46.363 +      if (l && r
  46.364 +          && (!(l & CONNECT_R) || !(r & CONNECT_L))
  46.365 +          && (!byColor || !((l ^ r) & COLOR_MASK))) {
  46.366 +        p->b[y][x] = l | CONNECT_R;
  46.367 +        p->b[y][x + 1] = r | CONNECT_L;
  46.368 +        changed |= 1 << y;
  46.369 +      }
  46.370 +    }
  46.371 +  }
  46.372 +  
  46.373 +  for (int y = 0; y < LJ_PF_HT - 1; ++y) {
  46.374 +    for (int x = p->leftWall; x < p->rightWall; ++x) {
  46.375 +      int b = p->b[y][x];
  46.376 +      int t = p->b[y + 1][x];
  46.377 +      if (b && t
  46.378 +          && (!(b & CONNECT_U) || !(t & CONNECT_D))
  46.379 +          && (!byColor || !((b ^ t) & COLOR_MASK))) {
  46.380 +        p->b[y][x] = b | CONNECT_U;
  46.381 +        p->b[y + 1][x] = t | CONNECT_D;
  46.382 +        changed |= 3 << y;
  46.383 +      }
  46.384 +    }
  46.385 +  }
  46.386 +  
  46.387 +  return changed;
  46.388 +}
  46.389 +
  46.390 +/**
  46.391 + * Locks the current tetromino in the playfield.
  46.392 + * @param p the playfield
  46.393 + * @return  the rows in which the tetromino was placed
  46.394 + */
  46.395 +static LJBits lockPiece(LJField *p) {
  46.396 +  LJBits rows = 0;
  46.397 +  int xBase = p->x;
  46.398 +  int yBase = ljfixfloor(p->y);
  46.399 +  LJBlkSpec blocks[4];
  46.400 +  int piece = p->curPiece[0];
  46.401 +  expandPieceToBlocks(blocks, p, piece, xBase, yBase, p->theta);
  46.402 +  
  46.403 +  p->isSpin = isTspin(p);
  46.404 +
  46.405 +  for (int blk = 0; blk < 4; ++blk) {
  46.406 +    int blkY = blocks[blk].y;
  46.407 +    int blkX = blocks[blk].x;
  46.408 +    int blkValue = blocks[blk].conn;
  46.409 +
  46.410 +    if(blkValue && blkY >= 0 && blkY < LJ_PF_HT) {
  46.411 +      rows |= 1 << blkY;
  46.412 +      if (blkX >= p->leftWall && blkX < p->rightWall) {
  46.413 +        p->b[blkY][blkX] = blkValue;
  46.414 +      }
  46.415 +    }
  46.416 +  }
  46.417 +  p->sounds |= LJSND_LOCK;
  46.418 +  p->alreadyHeld = 0;
  46.419 +  p->nLinesThisPiece = 0;
  46.420 +
  46.421 +  return rows;
  46.422 +}
  46.423 +
  46.424 +void shuffleColumns(LJField *p) {
  46.425 +  unsigned int permu[LJ_PF_WID];
  46.426 +
  46.427 +  for (int x = p->leftWall; x < p->rightWall; ++x) {
  46.428 +    permu[x] = x;
  46.429 +  }
  46.430 +  for (int x = p->rightWall - 1; x > p->rightWall + 1; --x) {
  46.431 +    int r = ljRand(p) % x;
  46.432 +    int t = permu[x];
  46.433 +    permu[x] = permu[r];
  46.434 +    permu[r] = t;
  46.435 +  }
  46.436 +
  46.437 +  for (int y = 0; y < LJ_PF_HT; ++y) {
  46.438 +    unsigned int blk[LJ_PF_WID];
  46.439 +    
  46.440 +    // Copy blocks to temporary buffer, eliminating left and right connections
  46.441 +    for (int x = p->leftWall; x < p->rightWall; ++x) {
  46.442 +      blk[x] = p->b[y][permu[x]] & ~(CONNECT_L | CONNECT_R);
  46.443 +    }
  46.444 +    for (int x = p->leftWall; x < p->rightWall; ++x) {
  46.445 +      p->b[y][x] = blk[x];
  46.446 +    }
  46.447 +  }
  46.448 +}
  46.449 +
  46.450 +/**
  46.451 + * Rotates and shifts a new piece per the rotation system.
  46.452 + */
  46.453 +static void rotateNewPiece(LJField *p) {
  46.454 +  const LJRotSystem *rs = rotSystems[p->rotationSystem];
  46.455 +  int shape = p->curPiece[0] & LJP_MASK;
  46.456 +  int kickData = rs->entryOffset[shape];
  46.457 +  p->x += WKX(kickData);
  46.458 +  p->y += ljitofix(WKY(kickData));
  46.459 +  p->theta = rs->entryTheta[shape];
  46.460 +  
  46.461 +  /* Find extents of piece so that it doesn't enter already collided
  46.462 +     with the walls (otherwise, in Tengen rotation and well width 4,
  46.463 +     spawning I causes block out) */
  46.464 +
  46.465 +  LJBlkSpec blocks[4];
  46.466 +  int minX = LJ_PF_WID - 1, maxX = 0, maxY = 0;
  46.467 +
  46.468 +  expandPieceToBlocks(blocks, p, p->curPiece[0],
  46.469 +                      p->x, ljfixfloor(p->y), p->theta);
  46.470 +  for (int blk = 0; blk < 4; ++blk) {
  46.471 +    if (blocks[blk].conn) {
  46.472 +      if (maxY < blocks[blk].y) {
  46.473 +        maxY = blocks[blk].y;
  46.474 +      }
  46.475 +      if (maxX < blocks[blk].x) {
  46.476 +        maxX = blocks[blk].x;
  46.477 +      }
  46.478 +      if (minX > blocks[blk].x) {
  46.479 +        minX = blocks[blk].x;
  46.480 +      }
  46.481 +    }
  46.482 +  }
  46.483 +
  46.484 +  if (minX < p->leftWall) {
  46.485 +    p->x += minX - p->leftWall;
  46.486 +  } else if (maxX >= p->rightWall) {
  46.487 +    p->x -= (maxX + 1) - p->rightWall;
  46.488 +  }
  46.489 +  if (!p->enterAbove && maxY >= p->ceiling) {
  46.490 +    p->y -= ljitofix(maxY - p->ceiling) + 1;
  46.491 +  }
  46.492 +}
  46.493 +
  46.494 +
  46.495 +/**
  46.496 + * Spawns a tetromino onto the playfield.
  46.497 + * @param p    the playfield
  46.498 + * @param hold 0 for spawning from next; nonzero for swapping with hold
  46.499 + * @return     the rows that were changed by banana effect
  46.500 + */
  46.501 +static LJBits newPiece(LJField *p, int hold) {
  46.502 +  LJBits changed = 0;
  46.503 +  int initial = p->state != LJS_FALLING
  46.504 +                && p->state != LJS_LANDED;
  46.505 +  int ihs = initial && hold;
  46.506 +
  46.507 +  if (hold) {
  46.508 +    if (p->state == LJS_LANDED && p->lockReset == LJLOCK_SPAWN) {
  46.509 +      p->speed.lockDelay = p->stateTime;
  46.510 +    }
  46.511 +  } else {
  46.512 +    p->upwardKicks = 0;
  46.513 +  }
  46.514 +  p->x = (LJ_PF_WID - 4) / 2;
  46.515 +  p->dropDist = 0;
  46.516 +  if (!ihs) {
  46.517 +    p->state = LJS_FALLING;
  46.518 +    p->stateTime = 0;
  46.519 +  }
  46.520 +  p->isSpin = 0;
  46.521 +  p->y = ljitofix(p->ceiling - 2);
  46.522 +  
  46.523 +  /* Note: The gimmick sets the gravity speed after frame() finishes. */
  46.524 +
  46.525 +  if (hold) {
  46.526 +    int temp;
  46.527 +
  46.528 +    if (p->holdStyle != LJHOLD_TO_NEXT) {
  46.529 +      temp = p->holdPiece;
  46.530 +      p->holdPiece = p->curPiece[ihs];
  46.531 +    } else {
  46.532 +      temp = p->curPiece[ihs + 1];
  46.533 +      p->curPiece[ihs + 1] = p->curPiece[ihs];
  46.534 +    }
  46.535 +    p->curPiece[ihs] = temp;
  46.536 +    p->alreadyHeld = 1;
  46.537 +    p->sounds |= LJSND_HOLD;
  46.538 +
  46.539 +    // If a negative number was swapped into the current piece,
  46.540 +    // then there was nothing in the hold space (e.g. at the
  46.541 +    // beginning of a round).  In this case, we'll need to fall
  46.542 +    // through and generate a new piece.
  46.543 +    if (temp >= 0) {
  46.544 +      rotateNewPiece(p);
  46.545 +      return changed;
  46.546 +    }
  46.547 +  }
  46.548 +
  46.549 +  // Shift the next pieces down
  46.550 +  for (int i = 0; i < LJ_NEXT_PIECES; i++) {
  46.551 +    p->curPiece[i] = p->curPiece[i + 1];
  46.552 +  }
  46.553 +  p->sounds |= LJSND_SPAWN;
  46.554 +
  46.555 +  p->curPiece[LJ_NEXT_PIECES] = randomize(p);
  46.556 +  ++p->nPieces;
  46.557 +  if (!p->canRotate) {
  46.558 +    p->theta = (ljRand(p) >> 12) & 0x03;
  46.559 +  } else {
  46.560 +    rotateNewPiece(p);
  46.561 +  }
  46.562 +
  46.563 +  return changed;
  46.564 +}
  46.565 +
  46.566 +void newGame(LJField *p) {
  46.567 +
  46.568 +  // Clear playfield
  46.569 +  for (int y = 0; y < LJ_PF_HT; y++) {
  46.570 +    for(int x = 0; x < LJ_PF_WID; x++) {
  46.571 +      p->b[y][x] = 0;
  46.572 +    }
  46.573 +  }
  46.574 +  
  46.575 +  for (int y = 0; y < LJ_MAX_LINES_PER_PIECE; ++y) {
  46.576 +    p->nLineClears[y] = 0;
  46.577 +  }
  46.578 +
  46.579 +  if (p->holdStyle == LJHOLD_TNT) {
  46.580 +    initRandomize(p);
  46.581 +    p->holdPiece = randomize(p);
  46.582 +  } else {
  46.583 +    p->holdPiece = -1;  // sentinel for no piece in hold box
  46.584 +  }
  46.585 +
  46.586 +  // Generate pieces
  46.587 +  initRandomize(p);
  46.588 +  for(int i = 0; i < LJ_NEXT_PIECES; i++) {
  46.589 +    newPiece(p, 0);
  46.590 +  }
  46.591 +  p->clearedLines = 0;
  46.592 +  p->nPieces = 0;
  46.593 +  p->state = LJS_NEW_PIECE;
  46.594 +  p->stateTime = 1;
  46.595 +  p->garbage = 0;
  46.596 +  p->outGarbage = 0;
  46.597 +  p->score = 0;
  46.598 +  p->gameTime = 0;
  46.599 +  p->activeTime = 0;
  46.600 +  p->lines = 0;
  46.601 +  p->alreadyHeld = 0;
  46.602 +  p->chain = 0;
  46.603 +  p->theta = 0;
  46.604 +  p->nLinesThisPiece = 0;
  46.605 +  p->canRotate = 1;
  46.606 +  p->speed.entryDelay = 0;
  46.607 +  p->garbageRandomness = 64;
  46.608 +  p->reloaded = 0;
  46.609 +  p->monosquares = 0;
  46.610 +  p->multisquares = 0;
  46.611 +
  46.612 +  p->garbageX = ljRand(p) % (p->rightWall - p->leftWall)
  46.613 +                + p->leftWall;
  46.614 +}
  46.615 +
  46.616 +/**
  46.617 + * Handles scoring for hard and soft drops.
  46.618 + * @return 0 for no change or LJ_DIRTY_SCORE for change
  46.619 + */
  46.620 +LJBits scoreDropRows(LJField *p, LJFixed gravity, LJFixed newY) {
  46.621 +  LJBits changed = 0;
  46.622 +  if (gravity > 0) {
  46.623 +    int fallDist = ljfixfloor(p->y) - ljfixfloor(newY);
  46.624 +      
  46.625 +    p->dropDist += fallDist;
  46.626 +
  46.627 +    // Double scoring for hard drop
  46.628 +    if (p->dropScoreStyle == LJDROP_1S_2H
  46.629 +        && gravity >= ljitofix(p->ceiling)) {
  46.630 +      fallDist *= 2;
  46.631 +    }
  46.632 +    if (p->dropScoreStyle == LJDROP_1CELL
  46.633 +        || p->dropScoreStyle == LJDROP_1S_2H) {
  46.634 +      p->score += fallDist;
  46.635 +      changed |= LJ_DIRTY_SCORE;
  46.636 +    }
  46.637 +
  46.638 +    // Handle scoring for continuous drop    
  46.639 +    if (p->dropScoreStyle == LJDROP_NES
  46.640 +        && newY <= ljitofix(p->hardDropY)) {
  46.641 +      p->score += p->dropDist;
  46.642 +      changed |= LJ_DIRTY_SCORE;
  46.643 +      p->dropDist = 0;
  46.644 +    }
  46.645 +  } else {
  46.646 +    p->dropDist = 0;
  46.647 +  }
  46.648 +  return changed;
  46.649 +}
  46.650 +
  46.651 +/**
  46.652 + * Handles gravity.
  46.653 + * @param p         the playfield
  46.654 + * @param gravity   amount of additional gravity applied by the player
  46.655 + * @param otherKeys other LJI_* keys being pressed by the player
  46.656 + *                  (specifically LJI_LOCK)
  46.657 + */
  46.658 +static LJBits doPieceGravity(LJField *p, LJFixed gravity, LJBits otherKeys) {
  46.659 +  int changedRows = 0;
  46.660 +
  46.661 +  LJFixed newY = p->y - gravity - p->speed.gravity;
  46.662 +
  46.663 +  // Check for landed
  46.664 +  if (newY <= ljitofix(p->hardDropY)) {
  46.665 +    newY = ljitofix(p->hardDropY);
  46.666 +    
  46.667 +    // Downward movement does not result in a T-spin
  46.668 +    if (ljfixfloor(newY) < ljfixfloor(p->y)) {
  46.669 +      p->isSpin = 0;
  46.670 +    }
  46.671 +
  46.672 +    changedRows |= scoreDropRows(p, gravity, newY);
  46.673 +    p->y = newY;
  46.674 +
  46.675 +    if (p->state == LJS_FALLING) {
  46.676 +      p->state = LJS_LANDED;
  46.677 +      p->stateTime = p->speed.lockDelay;
  46.678 +      p->sounds |= LJSND_LAND;
  46.679 +    }
  46.680 +    if (p->stateTime > 0 && !(otherKeys & LJI_LOCK)) {
  46.681 +      // lock delay > 128 is a special case for don't lock at all
  46.682 +      if (p->setLockDelay < 128) {
  46.683 +        --p->stateTime;
  46.684 +      }
  46.685 +    } else {
  46.686 +      LJBits lockRows = lockPiece(p);
  46.687 +      p->state = LJS_LINES;
  46.688 +      p->stateTime = 0;
  46.689 +      changedRows |= lockRows | LJ_DIRTY_NEXT;
  46.690 +      
  46.691 +      // LOCK OUT rule: If a piece locks
  46.692 +      // completely above the ceiling, the game is over.
  46.693 +      if (!(lockRows & ((1 << p->ceiling) - 1))) {
  46.694 +        p->state = LJS_GAMEOVER;
  46.695 +      }
  46.696 +    }
  46.697 +  } else {
  46.698 +    changedRows |= scoreDropRows(p, gravity, newY);
  46.699 +    p->state = LJS_FALLING;
  46.700 +
  46.701 +    // Downward movement does not result in a T-spin
  46.702 +    if (ljfixfloor(newY) < ljfixfloor(p->y)) {
  46.703 +      p->isSpin = 0;
  46.704 +    }
  46.705 +  }
  46.706 +  p->y = newY;
  46.707 +  return changedRows;
  46.708 +}
  46.709 +
  46.710 +static void updateLockDelayOnMove(LJField *p, int isUpwardKick) {
  46.711 +  if (p->state == LJS_LANDED) {
  46.712 +
  46.713 +    // if tetromino can move down, go back to falling state;
  46.714 +    // otherwise, reset lock delay.
  46.715 +    if (!isCollision(p, p->x, ljfixfloor(p->y) - 1, p->theta)) {
  46.716 +      p->state = LJS_FALLING;
  46.717 +      if (p->lockReset == LJLOCK_SPAWN) {
  46.718 +        p->speed.lockDelay = p->stateTime;
  46.719 +      }
  46.720 +      p->stateTime = 0;
  46.721 +    } else {
  46.722 +      p->state = LJS_LANDED;
  46.723 +      if (p->lockReset == LJLOCK_MOVE
  46.724 +          || (p->lockReset == LJLOCK_STEP && isUpwardKick)) {
  46.725 +        p->stateTime = p->speed.lockDelay;
  46.726 +      }
  46.727 +    }
  46.728 +  }
  46.729 +}
  46.730 +
  46.731 +static int doRotate(LJField *p,
  46.732 +                    int newDir,
  46.733 +                    const WallKickTable *const pieceTable,
  46.734 +                    int withKicks) {
  46.735 +  int baseX = p->x;
  46.736 +  int baseY = ljfixfloor(p->y);
  46.737 +  
  46.738 +  withKicks = withKicks ? KICK_TABLE_LEN : 1;
  46.739 +  
  46.740 +  // allow specifying null tables for O
  46.741 +  if (!pieceTable) {
  46.742 +    if (!isCollision(p, baseX, baseY, newDir)) {
  46.743 +      p->theta = newDir;
  46.744 +      p->sounds |= LJSND_ROTATE;
  46.745 +      return 1;
  46.746 +    }
  46.747 +    return 0;
  46.748 +  }
  46.749 +  
  46.750 +  const unsigned char *const table = (*pieceTable)[newDir];
  46.751 +  int baseKickY = -1000;  // sentinel for uninitialized
  46.752 +
  46.753 +  for (int kick = 0; kick < withKicks; kick++) {
  46.754 +    unsigned int kickData = table[kick];
  46.755 +    if (kickData == WK_END) {
  46.756 +      break;
  46.757 +    } else if (kickData == ARIKA_IF_NOT_CENTER) {
  46.758 +    
  46.759 +      // Compute the free space position
  46.760 +      kickData = table[0];
  46.761 +      int kickX = WKX(kickData) + baseX;
  46.762 +      int kickY = WKY(kickData) + baseY;
  46.763 +      LJBlkSpec blocks[4];
  46.764 +      int allowed = 0;
  46.765 +      
  46.766 +      // If a block other than the center column of this position
  46.767 +      // is occupied, go to the next step (that is,
  46.768 +      // allow subsequent kicks)
  46.769 +      expandPieceToBlocks(blocks, p, p->curPiece[0],
  46.770 +                          kickX, kickY, newDir);
  46.771 +                          
  46.772 +      for (int blk = 0; blk < 4; ++blk) {
  46.773 +        if (blocks[blk].conn
  46.774 +            && blocks[blk].x != baseX + 1
  46.775 +            && isOccupied(p, blocks[blk].x, blocks[blk].y)) {
  46.776 +          allowed = 1;
  46.777 +          break;
  46.778 +        }
  46.779 +      }
  46.780 +
  46.781 +      // Otherwise, only blocks of the center column are occupied,
  46.782 +      // and these cannot kick the piece.
  46.783 +      if (!allowed) {
  46.784 +        return 0;
  46.785 +      }
  46.786 +    } else {
  46.787 +      int kickX = WKX(kickData) + baseX;
  46.788 +      int kickY = WKY(kickData) + baseY;
  46.789 +
  46.790 +      // If this is the first 
  46.791 +      if (baseKickY == -1000) {
  46.792 +        baseKickY = kickY;
  46.793 +      }
  46.794 +      if ((kickY <= baseKickY || p->upwardKicks < p->maxUpwardKicks)
  46.795 +          && !isCollision(p, kickX, kickY, newDir)) {
  46.796 +        p->theta = newDir;
  46.797 +        p->x = kickX;
  46.798 +        if (kickY > baseKickY) {
  46.799 +          p->y = ljitofix(kickY);
  46.800 +          
  46.801 +          // on the FIRST floor kick of a piece, reset lock delay
  46.802 +          if (p->upwardKicks == 0) {
  46.803 +            updateLockDelayOnMove(p, 1);
  46.804 +          }
  46.805 +          ++p->upwardKicks;
  46.806 +        } else if (kickY < baseKickY) {
  46.807 +          p->y = ljitofix(kickY) + 0xFFFF;
  46.808 +        } else {
  46.809 +          p->y = ljitofix(kickY) + (p->y & 0xFFFF);
  46.810 +        }
  46.811 +        p->sounds |= LJSND_ROTATE;
  46.812 +        return 1;
  46.813 +      }
  46.814 +    }
  46.815 +  }
  46.816 +  return 0;
  46.817 +}
  46.818 +
  46.819 +
  46.820 +/**
  46.821 + * Tries to rotate the current piece 90 degrees counterclockwise,
  46.822 + * using the counterclockwise wall kick tables.
  46.823 + * @param p  the playfield
  46.824 + * @param withKicks nonzero for wall kick, zero for none
  46.825 + */
  46.826 +static int doRotateLeft(LJField *p, int withKicks) {
  46.827 +  int newDir = (p->theta + 3) & 0x03;
  46.828 +  const LJRotSystem *rs = rotSystems[p->rotationSystem];
  46.829 +  signed int tableNo = rs->kicksL[p->curPiece[0] & LJP_MASK];
  46.830 +  const WallKickTable *pieceTable = tableNo >= 0
  46.831 +                                    ? &(rs->kickTables[tableNo])
  46.832 +                                    : NULL;
  46.833 +  return doRotate(p, newDir, pieceTable, withKicks);
  46.834 +}
  46.835 +
  46.836 +/**
  46.837 + * Tries to rotate the current piece 90 degrees clockwise,
  46.838 + * using the clockwise wall kick tables.
  46.839 + * @param p  the playfield
  46.840 + * @param withKicks nonzero for wall kick, zero for none
  46.841 + */
  46.842 +static int doRotateRight(LJField *p, int withKicks) {
  46.843 +  int newDir = (p->theta + 1) & 0x03;
  46.844 +  const LJRotSystem *rs = rotSystems[p->rotationSystem];
  46.845 +  signed int tableNo = rs->kicksR[p->curPiece[0] & LJP_MASK];
  46.846 +  const WallKickTable *pieceTable = tableNo >= 0
  46.847 +                                    ? &(rs->kickTables[tableNo])
  46.848 +                                    : NULL;
  46.849 +  return doRotate(p, newDir, pieceTable, withKicks);
  46.850 +}
  46.851 +
  46.852 +static LJBits checkLines(const LJField *p, LJBits checkRows) {
  46.853 +  LJBits foundLines = 0;
  46.854 +  for (int y = 0;
  46.855 +       y < LJ_PF_HT && checkRows != 0;
  46.856 +       ++y, checkRows >>= 1) {
  46.857 +    if (checkRows & 1) {
  46.858 +      const unsigned char *row = p->b[y];
  46.859 +      int found = 1;
  46.860 +
  46.861 +      for (int x = p->leftWall; x < p->rightWall && found; ++x) {
  46.862 +        found = found && (row[x] != 0);
  46.863 +      }
  46.864 +      if (found) {
  46.865 +        foundLines |= 1 << y;
  46.866 +      }
  46.867 +    }
  46.868 +  }
  46.869 +
  46.870 +  return foundLines;
  46.871 +}
  46.872 +
  46.873 +static void fillCLoop(LJField *p, int x, int y, unsigned int src, unsigned int dst)
  46.874 +{
  46.875 +  int fillL, fillR, i;
  46.876 +
  46.877 +  fillL = fillR = x;
  46.878 +  do {
  46.879 +    p->c[y][fillL] = dst;
  46.880 +    fillL--;
  46.881 +  } while ((fillL >= p->leftWall) && (p->c[y][fillL] == src));
  46.882 +  fillL++;
  46.883 +
  46.884 +  do {
  46.885 +    p->c[y][fillR] = dst;
  46.886 +    fillR++;
  46.887 +  } while ((fillR < p->rightWall) && (p->c[y][fillR] == src));
  46.888 +  fillR--;
  46.889 +
  46.890 +  for(i = fillL; i <= fillR; i++)
  46.891 +  {
  46.892 +    if(y > 0 && p->c[y - 1][i] == src) {
  46.893 +	  fillCLoop(p, i, y - 1, src, dst);
  46.894 +    }
  46.895 +    if(y < LJ_PF_HT - 1 && p->c[y + 1][i] == src) {
  46.896 +	  fillCLoop(p, i, y + 1, src, dst);
  46.897 +    }
  46.898 +  }
  46.899 +}
  46.900 +
  46.901 +
  46.902 +static void fillC(LJField *p, int x, int y, int dstC) {
  46.903 +  if (p->c[y][x] != dstC) {
  46.904 +    fillCLoop(p, x, y, p->c[y][x], dstC);
  46.905 +  }
  46.906 +}
  46.907 +
  46.908 +/**
  46.909 + * Locks the block regions that have landed.
  46.910 + * @param p the playfield
  46.911 + */
  46.912 +void lockLandedRegions(LJField *p) {
  46.913 +  // Look for regions that are on top of ground regions, where
  46.914 +  // "ground regions" are any block that is solid and whose region ID is 0.
  46.915 +  for (int landed = 1; landed != 0; ) {
  46.916 +    landed = 0;
  46.917 +    // If something hit the ground, erase its floating bit
  46.918 +    for (int y = 0; y < LJ_PF_HT; ++y) {
  46.919 +      for (int x = p->leftWall; x < p->rightWall; ++x) {
  46.920 +        // If there's a floating block here, and a not-floating block below,
  46.921 +        // erase this block group's floatiness
  46.922 +        if (p->c[y][x] &&
  46.923 +            (y == 0 || (!p->c[y - 1][x] && p->b[y - 1][x]))) {
  46.924 +          fillC(p, x, y, 0);
  46.925 +          p->sounds |= LJSND_LAND;
  46.926 +          landed = 1;
  46.927 +        }
  46.928 +      }
  46.929 +    }
  46.930 +  }
  46.931 +}
  46.932 +
  46.933 +/**
  46.934 + * Separates the playfield into regions that shall fall separately.
  46.935 + * @param p the playfield
  46.936 + * @param byColors Zero: Touching blocks form a region.
  46.937 + *               Nonzero: Touching blocks of a single color form a region.
  46.938 + */
  46.939 +static void stickyMark(LJField *p, int byColors) {
  46.940 +  for (unsigned int y = 0; y < LJ_PF_HT; ++y) {
  46.941 +    for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
  46.942 +      int blkHere = p->b[y][x] & COLOR_MASK;
  46.943 +
  46.944 +      if (!byColors) {
  46.945 +        blkHere = blkHere ? 0x10 : 0;
  46.946 +      }
  46.947 +      p->c[y][x] = blkHere;
  46.948 +    }
  46.949 +  }
  46.950 +  
  46.951 +  if (byColors) {
  46.952 +    lockLandedRegions(p);
  46.953 +  } else {
  46.954 +    // mark the bottom row as landed
  46.955 +    for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
  46.956 +      if (p->c[0][x]) {
  46.957 +        fillC(p, x, 0, 0);
  46.958 +      }
  46.959 +    }
  46.960 +  }
  46.961 +
  46.962 +  //p->stateTime = 5;
  46.963 +}
  46.964 +
  46.965 +
  46.966 +/**
  46.967 + * Sets the color of a piece to gray/garbage (0x80).
  46.968 + * @param x column of a block in the piece
  46.969 + * @param y row of a block in the piece
  46.970 + * @param rgn the region ID
  46.971 + */
  46.972 +static void cascadeMarkPiece(LJField *p, int x, int y, int rgn) {
  46.973 +  int blkHere = p->b[y][x];
  46.974 +  
  46.975 +  if (blkHere && !p->c[y][x]) {
  46.976 +    p->c[y][x] = rgn;
  46.977 +    if((blkHere & CONNECT_D) && y > 0)
  46.978 +      cascadeMarkPiece(p, x, y - 1, rgn);
  46.979 +    if((blkHere & CONNECT_U) && y < LJ_PF_HT - 1)
  46.980 +      cascadeMarkPiece(p, x, y + 1, rgn);
  46.981 +    if((blkHere & CONNECT_L) && x > p->leftWall)
  46.982 +      cascadeMarkPiece(p, x - 1, y, rgn);
  46.983 +    if((blkHere & CONNECT_R) && x < p->rightWall - 1 )
  46.984 +      cascadeMarkPiece(p, x + 1, y, rgn);
  46.985 +  }
  46.986 +}
  46.987 +
  46.988 +static void cascadeMark(LJField *p) {
  46.989 +  int rgn = 0;
  46.990 +
  46.991 +  for (unsigned int y = 0; y < LJ_PF_HT; ++y) {
  46.992 +    for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
  46.993 +      p->c[y][x] = 0;
  46.994 +    }
  46.995 +  }
  46.996 +  for (unsigned int y = 0; y < LJ_PF_HT; ++y) {
  46.997 +    for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
  46.998 +      cascadeMarkPiece(p, x, y, ++rgn);
  46.999 +    }
 46.1000 +  }
 46.1001 +  lockLandedRegions(p);
 46.1002 +  //p->stateTime = 5;
 46.1003 +}
 46.1004 +
 46.1005 +static void breakEverything(LJField *p) {
 46.1006 +  for (unsigned int y = 0; y < LJ_PF_HT; ++y) {
 46.1007 +    for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
 46.1008 +      if (p->b[y][x]) {
 46.1009 +        p->c[y][x] = x + 1;
 46.1010 +        p->b[y][x] = 0x80;
 46.1011 +      } else {
 46.1012 +        p->c[y][x] = 0;
 46.1013 +      }
 46.1014 +    }
 46.1015 +  }
 46.1016 +  
 46.1017 +  // fill bottom row
 46.1018 +  for (unsigned int x = x = p->leftWall; x < p->rightWall; ++x) {
 46.1019 +    if (p->c[0][x]) {
 46.1020 +      fillC(p, x, 0, 0);
 46.1021 +    }
 46.1022 +  }
 46.1023 +  p->stateTime = 5;
 46.1024 +}
 46.1025 +
 46.1026 +/**
 46.1027 + * Sets the color of a piece to gray/garbage (0x80).
 46.1028 + * @param x column of a block in the piece
 46.1029 + * @param y row of a block in the piece
 46.1030 + */
 46.1031 +static LJBits breakPiece(LJField *p, int x, int y) {
 46.1032 +  LJBits changed = 0;
 46.1033 +  int blkHere = p->b[y][x];
 46.1034 +  int colorHere = blkHere & COLOR_MASK;
 46.1035 +  int connHere = blkHere & CONNECT_MASK;
 46.1036 +  
 46.1037 +  if (colorHere != 0x80) {
 46.1038 +    p->b[y][x] = connHere | 0x80;
 46.1039 +    changed |= 1 << y;
 46.1040 +    if((blkHere & CONNECT_D) && y > 0)
 46.1041 +      changed |= breakPiece(p, x, y - 1);
 46.1042 +    if((blkHere & CONNECT_U) && y < LJ_PF_HT - 1)
 46.1043 +      changed |= breakPiece(p, x, y + 1);
 46.1044 +    if((blkHere & CONNECT_L) && x > p->leftWall)
 46.1045 +      changed |= breakPiece(p, x - 1, y);
 46.1046 +    if((blkHere & CONNECT_R) && x < p->rightWall - 1 )
 46.1047 +      changed |= breakPiece(p, x + 1, y);
 46.1048 +  }
 46.1049 +  return changed;
 46.1050 +}
 46.1051 +
 46.1052 +/**
 46.1053 + * Removes blocks in cleared lines from the playfield and marks
 46.1054 + * remaining blocks for gravity.
 46.1055 + * @param the lines to be cleared
 46.1056 + * @return the rows that were changed
 46.1057 + */
 46.1058 +static LJBits clearLines(LJField *p, LJBits foundLines) {
 46.1059 +  LJBits changed = foundLines;
 46.1060 +
 46.1061 +  p->clearedLines = foundLines;
 46.1062 +  if (foundLines != 0) {
 46.1063 +    p->sounds |= LJSND_LINE;
 46.1064 +  }
 46.1065 +  for (int y = 0;
 46.1066 +       y < LJ_PF_HT && foundLines != 0;
 46.1067 +       ++y, foundLines >>= 1) {
 46.1068 +    if (foundLines & 1) {
 46.1069 +
 46.1070 +      // In square mode, turn broken pieces (but not 4x4 squares)
 46.1071 +      // into garbage blocks
 46.1072 +      if (p->gluing == LJGLUING_SQUARE) {
 46.1073 +        for (int x = p->leftWall; x < p->rightWall; ++x) {
 46.1074 +          if (p->b[y][x] < 0x80) {
 46.1075 +            changed |= breakPiece(p, x, y);
 46.1076 +          } else if ((p->b[y][x] & (0xF0 | CONNECT_R)) == 0xA0) {
 46.1077 +            p->score += 500;
 46.1078 +            changed |= LJ_DIRTY_SCORE;
 46.1079 +          } else if ((p->b[y][x] & (0xF0 | CONNECT_R)) == 0xB0) {
 46.1080 +            p->score += 1000;
 46.1081 +            changed |= LJ_DIRTY_SCORE;
 46.1082 +          }
 46.1083 +        }
 46.1084 +      }
 46.1085 +
 46.1086 +      for (int x = p->leftWall; x < p->rightWall; ++x) {
 46.1087 +        p->b[y][x] = 0;
 46.1088 +      }
 46.1089 +
 46.1090 +      // break connections up and down (like Tengen Tetyais)
 46.1091 +      if (y > 0) {
 46.1092 +        for (int x = p->leftWall; x < p->rightWall; ++x) {
 46.1093 +          p->b[y - 1][x] &= ~CONNECT_U;
 46.1094 +        }
 46.1095 +        changed |= 1 << (y - 1);
 46.1096 +      }
 46.1097 +      if (y < LJ_PF_HT - 1) {
 46.1098 +        for (int x = p->leftWall; x < p->rightWall; ++x) {
 46.1099 +          p->b[y + 1][x] &= ~CONNECT_D;
 46.1100 +        }
 46.1101 +        changed |= 1 << (y + 1);
 46.1102 +      }
 46.1103 +    }
 46.1104 +  }
 46.1105 +  if (p->gluing == LJGLUING_SQUARE && p->isSpin) {
 46.1106 +    breakEverything(p);
 46.1107 +    changed |= (1 << LJ_PF_HT) - 1;
 46.1108 +  } else if (p->clearGravity == LJGRAV_STICKY) {
 46.1109 +    stickyMark(p, 0);
 46.1110 +  } else if (p->clearGravity == LJGRAV_STICKY_BY_COLOR) {
 46.1111 +    stickyMark(p, 1);
 46.1112 +  } else if (p->clearGravity == LJGRAV_CASCADE) {
 46.1113 +    cascadeMark(p);
 46.1114 +  } else {
 46.1115 +    p->stateTime = 0;
 46.1116 +  }
 46.1117 +  
 46.1118 +  return changed;
 46.1119 +}
 46.1120 +
 46.1121 +static unsigned int stickyFallLines(LJField *p) {
 46.1122 +  int minY = LJ_PF_HT;
 46.1123 +
 46.1124 +  // Move floating stuff down by one block
 46.1125 +  for (int y = 1; y < LJ_PF_HT; ++y) {
 46.1126 +    for (int x = p->leftWall; x < p->rightWall; ++x) {
 46.1127 +      int c = p->c[y][x];
 46.1128 +      if (c) {
 46.1129 +        p->c[y - 1][x] = c;
 46.1130 +        p->c[y][x] = 0;
 46.1131 +        p->b[y - 1][x] = p->b[y][x];
 46.1132 +        p->b[y][x] = 0;
 46.1133 +
 46.1134 +        if (minY > y) {
 46.1135 +          minY = y;
 46.1136 +        }
 46.1137 +      }
 46.1138 +    }
 46.1139 +  }
 46.1140 +
 46.1141 +  // If we're done, skip all the rest
 46.1142 +  if (minY >= LJ_PF_HT) {
 46.1143 +    return LJ_PF_HT;
 46.1144 +  }
 46.1145 +
 46.1146 +  lockLandedRegions(p);
 46.1147 +  return minY - 1;
 46.1148 +}
 46.1149 +
 46.1150 +
 46.1151 +unsigned int bfffo(LJBits rowBits) {
 46.1152 +  unsigned int lineRow = 0;
 46.1153 +
 46.1154 +  if (!rowBits) {
 46.1155 +    return 32;
 46.1156 +  }
 46.1157 +  if ((rowBits & 0xFFFF) == 0) {
 46.1158 +    rowBits >>= 16;
 46.1159 +    lineRow += 16;
 46.1160 +  }
 46.1161 +  if ((rowBits & 0xFF) == 0) {
 46.1162 +    rowBits >>= 8;
 46.1163 +    lineRow += 8;
 46.1164 +  }
 46.1165 +  if ((rowBits & 0xF) == 0) {
 46.1166 +    rowBits >>= 4;
 46.1167 +    lineRow += 4;
 46.1168 +  }
 46.1169 +  if ((rowBits & 0x3) == 0) {
 46.1170 +    rowBits >>= 2;
 46.1171 +    lineRow += 2;
 46.1172 +  }
 46.1173 +  if ((rowBits & 0x1) == 0) {
 46.1174 +    rowBits >>= 1;
 46.1175 +    lineRow += 1;
 46.1176 +  }
 46.1177 +  return lineRow;
 46.1178 +}
 46.1179 +
 46.1180 +static unsigned int fallLines(LJField *p) {
 46.1181 +  LJBits rowBits = p->tempRows;
 46.1182 +  unsigned int lineRow = 0;
 46.1183 +  
 46.1184 +  if (p->clearGravity != LJGRAV_NAIVE
 46.1185 +      || (p->gluing == LJGLUING_SQUARE && p->isSpin)) {
 46.1186 +    return stickyFallLines(p);
 46.1187 +  }
 46.1188 +
 46.1189 +  if (rowBits == 0) {
 46.1190 +    return LJ_PF_HT;
 46.1191 +  }
 46.1192 +
 46.1193 +  lineRow = bfffo(rowBits);
 46.1194 +  p->tempRows = (p->tempRows & (-2 << lineRow)) >> 1;
 46.1195 +  if (!(p->tempRows & (1 << lineRow))) {
 46.1196 +    p->sounds |= LJSND_LAND;
 46.1197 +  }
 46.1198 +
 46.1199 +  // Move stuff down by 1 row
 46.1200 +  for (int y = lineRow; y < LJ_PF_HT - 1; ++y) {
 46.1201 +    unsigned char *row0 = p->b[y];
 46.1202 +    const unsigned char *row1 = p->b[y + 1];
 46.1203 +    for (int x = p->leftWall; x < p->rightWall; ++x) {
 46.1204 +      row0[x] = row1[x];
 46.1205 +    }
 46.1206 +  }
 46.1207 +
 46.1208 +  // Clear top row
 46.1209 +  for (int x = p->leftWall; x < p->rightWall; ++x) {
 46.1210 +    p->b[LJ_PF_HT - 1][x] = 0;
 46.1211 +  }
 46.1212 +
 46.1213 +  return lineRow;
 46.1214 +}
 46.1215 +
 46.1216 +/**
 46.1217 + * Counts the bits in a bit vector that are true (1).
 46.1218 + * @param b a bit vector
 46.1219 + * @return the number of 1 bits
 46.1220 + */
 46.1221 +unsigned int countOnes(LJBits b) {
 46.1222 +  unsigned int ones = 0;
 46.1223 +
 46.1224 +  while(b) {
 46.1225 +    ++ones;
 46.1226 +    b &= b - 1;
 46.1227 +  }
 46.1228 +  return ones;
 46.1229 +}
 46.1230 +
 46.1231 +static unsigned int addGarbage(LJField *p) {
 46.1232 +  // Move stuff up by 1 row
 46.1233 +  for (int y = LJ_PF_HT - 2; y >= 0; --y) {
 46.1234 +    unsigned char *row1 = p->b[y + 1];
 46.1235 +    const unsigned char *row0 = p->b[y];
 46.1236 +    for (int x = p->leftWall; x < p->rightWall; ++x) {
 46.1237 +      row1[x] = row0[x];
 46.1238 +    }
 46.1239 +  }
 46.1240 +
 46.1241 +  // Garbage in bottom row
 46.1242 +  for (int x = p->leftWall; x < p->rightWall; ++x) {
 46.1243 +    p->b[0][x] = 0x80;
 46.1244 +  }
 46.1245 +
 46.1246 +  // Randomize location of garbage hole
 46.1247 +  int r = (ljRand(p) >> 7) & 0xFF;
 46.1248 +  int garbageX = (r <= p->garbageRandomness)
 46.1249 +                 ? (ljRand(p) % (p->rightWall - p->leftWall)) + p->leftWall
 46.1250 +                 : p->garbageX;
 46.1251 +  p->b[0][garbageX] = 0;
 46.1252 +  p->garbageX = garbageX;
 46.1253 +
 46.1254 +  // Horizontally connect the blocks that make up garbage in bottom row
 46.1255 +  for (int x = p->leftWall; x < p->rightWall - 1; ++x) {
 46.1256 +    if (p->b[0][x] && p->b[0][x + 1]) {
 46.1257 +      p->b[0][x] |= CONNECT_R;
 46.1258 +      p->b[0][x + 1] |= CONNECT_L;
 46.1259 +    }
 46.1260 +  }
 46.1261 +
 46.1262 +  // Vertically connect the blocks that make up garbage in bottom row
 46.1263 +  for (int x = p->leftWall; x < p->rightWall; ++x) {
 46.1264 +    if (p->b[0][x]
 46.1265 +        && ((p->b[1][x] & COLOR_MASK) == 0x80)) {
 46.1266 +      p->b[0][x] |= CONNECT_U;
 46.1267 +      p->b[1][x] |= CONNECT_D;
 46.1268 +    }
 46.1269 +  }
 46.1270 +
 46.1271 +  return (1 << LJ_PF_VIS_HT) - 1;
 46.1272 +}
 46.1273 +
 46.1274 +/**
 46.1275 + * Computes the score and outgoing garbage for lines
 46.1276 + * and adds them to the player's total.
 46.1277 + * @param p The playfield
 46.1278 + * @param lines Bit array where 1 means a line clear on this row.
 46.1279 + */
 46.1280 +void addLinesScore(LJField *p, LJBits lines);
 46.1281 +
 46.1282 +/**
 46.1283 + * Things to do just before launching a new piece.
 46.1284 + */
 46.1285 +void prepareForNewPiece(LJField *p) {
 46.1286 +  int nLines = p->nLinesThisPiece;
 46.1287 +
 46.1288 +  // Add to number of clears of each number of lines.
 46.1289 +  int idx;
 46.1290 +
 46.1291 +  if (p->clearGravity == LJGRAV_NAIVE) {
 46.1292 +    // In naive gravity, T-spin single, double, and triple counts
 46.1293 +    // are stored in 5-row, 6-row, and 7-row slots, and T-spins
 46.1294 +    // that clear 0 lines are not counted.
 46.1295 +    idx = (nLines > 4)
 46.1296 +          ? 4
 46.1297 +          : nLines;
 46.1298 +
 46.1299 +    if (nLines >= 1 && p->isSpin) {
 46.1300 +      idx += 4;
 46.1301 +    }
 46.1302 +  } else {
 46.1303 +    idx = (nLines > LJ_MAX_LINES_PER_PIECE)
 46.1304 +          ? LJ_MAX_LINES_PER_PIECE
 46.1305 +          : nLines;
 46.1306 +  }
 46.1307 +  
 46.1308 +  if (nLines < 4 && !p->isSpin && p->garbageStyle == LJGARBAGE_HRDERBY) {
 46.1309 +    p->garbage += nLines;
 46.1310 +  }
 46.1311 +
 46.1312 +  ljAssert(p,
 46.1313 +           idx <= LJ_MAX_LINES_PER_PIECE,
 46.1314 +           "Number of lines cleared with last piece out of bounds in prepareForNewPiece");
 46.1315 +  if (idx > 0) {
 46.1316 +    p->nLineClears[idx - 1] += 1;
 46.1317 +  }
 46.1318 +  
 46.1319 +  p->state = LJS_NEW_PIECE;
 46.1320 +  p->stateTime = p->speed.entryDelay;
 46.1321 +}
 46.1322 +
 46.1323 +LJBits frame(LJField *p, const LJInput *in) {
 46.1324 +  LJBits changedRows = 0;
 46.1325 +  LJBits tempRows;
 46.1326 +  int distance;
 46.1327 +  int moved = 0;
 46.1328 +  int isFirstFrame = (p->sounds & (LJSND_SPAWN | LJSND_HOLD))
 46.1329 +                     ? 1 : 0;
 46.1330 +
 46.1331 +  p->sounds = 0;
 46.1332 +
 46.1333 +  // Make hold work at ANY time.
 46.1334 +  if ((in->other & LJI_HOLD)
 46.1335 +      && p->holdStyle != LJHOLD_NONE
 46.1336 +      && !p->alreadyHeld) {
 46.1337 +    changedRows |= newPiece(p, 1) | LJ_DIRTY_NEXT;
 46.1338 +    updHardDropY(p);
 46.1339 +  }
 46.1340 +
 46.1341 +  switch(p->state) {
 46.1342 +  case LJS_NEW_PIECE:
 46.1343 +    if (p->garbage > 0) { 
 46.1344 +      changedRows |= addGarbage(p);
 46.1345 +      --p->garbage;
 46.1346 +      break;
 46.1347 +    }
 46.1348 +    
 46.1349 +    // ARE
 46.1350 +    if (p->stateTime > 0) {
 46.1351 +      --p->stateTime;
 46.1352 +    }
 46.1353 +    if (p->stateTime > 0) {
 46.1354 +      break;
 46.1355 +    }
 46.1356 +
 46.1357 +    changedRows |= newPiece(p, 0);
 46.1358 +    updHardDropY(p);
 46.1359 +    changedRows |= LJ_DIRTY_NEXT;
 46.1360 +    
 46.1361 +    /* If the piece spawns over blocks, this is a "block out" and a
 46.1362 +       loss under most rules.  But skip checking it now so that
 46.1363 +       the player can IRS out of block out. */
 46.1364 +
 46.1365 +    break;
 46.1366 +      
 46.1367 +  // the following executes for both falling and landed
 46.1368 +  case LJS_FALLING:
 46.1369 +  case LJS_LANDED:
 46.1370 +    ++p->activeTime;
 46.1371 +    if (p->canRotate) {
 46.1372 +      int oldX = p->x;
 46.1373 +      int oldY = ljfixfloor(p->y);
 46.1374 +      distance = in->rotation;
 46.1375 +      for(; distance < 0; ++distance) {
 46.1376 +
 46.1377 +        // 0.43: Do not apply wall kicks on the first frame (IRS)
 46.1378 +        if (doRotateLeft(p, !isFirstFrame)) {
 46.1379 +          moved = 1;
 46.1380 +
 46.1381 +          // isSpin == 1: twist in place
 46.1382 +          // isSpin == 2: twist with kick
 46.1383 +          // if (p->tSpinAlgo == LJTS_TDS_NO_KICK)
 46.1384 +          // then only isSpin == 1 is worth points.
 46.1385 +          if (p->x == oldX && ljfixfloor(p->y) == oldY) {
 46.1386 +            p->isSpin = 1;
 46.1387 +          } else {
 46.1388 +            p->isSpin = 2;
 46.1389 +          }
 46.1390 +        } else {
 46.1391 +          break;
 46.1392 +        }
 46.1393 +      }
 46.1394 +      for(; distance > 0; --distance) {
 46.1395 +        if (doRotateRight(p, !isFirstFrame)) {
 46.1396 +          moved = 1;
 46.1397 +          if (p->x == oldX && ljfixfloor(p->y) == oldY) {
 46.1398 +            p->isSpin = 1;
 46.1399 +          } else {
 46.1400 +            p->isSpin = 2;
 46.1401 +          }
 46.1402 +        } else {
 46.1403 +          break;
 46.1404 +        }
 46.1405 +      }
 46.1406 +    }
 46.1407 +    
 46.1408 +    /* If the piece spawns over blocks, this is a "block out" and a
 46.1409 +       loss under most rules.  Check it now, after rotation, so that
 46.1410 +       the player can IRS out of block out. */
 46.1411 +    if (isFirstFrame
 46.1412 +        && isCollision(p, p->x, ljfixfloor(p->y), p->theta)) {
 46.1413 +      changedRows |= lockPiece(p);
 46.1414 +      p->state = LJS_GAMEOVER;
 46.1415 +    }
 46.1416 +
 46.1417 +    distance = in->movement;
 46.1418 +    for(; distance < 0; ++distance) {
 46.1419 +      if (!isCollision(p, p->x - 1, ljfixfloor(p->y), p->theta)) {
 46.1420 +        --p->x;
 46.1421 +        p->sounds |= LJSND_SHIFT;
 46.1422 +        moved = 1;
 46.1423 +        p->isSpin = 0;
 46.1424 +      }
 46.1425 +    }
 46.1426 +    for(; distance > 0; --distance) {
 46.1427 +      if (!isCollision(p, p->x + 1, ljfixfloor(p->y), p->theta)) {
 46.1428 +        ++p->x;
 46.1429 +        p->sounds |= LJSND_SHIFT;
 46.1430 +        moved = 1;
 46.1431 +        p->isSpin = 0;
 46.1432 +      }
 46.1433 +    }
 46.1434 +    updHardDropY(p);
 46.1435 +    if (p->state != LJS_GAMEOVER) {
 46.1436 +      if (moved) {
 46.1437 +        updateLockDelayOnMove(p, 0);
 46.1438 +      }
 46.1439 +      tempRows = doPieceGravity(p, ljitofix(in->gravity) >> 3, in->other);
 46.1440 +      p->tempRows = tempRows;
 46.1441 +      changedRows |= tempRows;
 46.1442 +    }
 46.1443 +
 46.1444 +    // At this point, if the piece locked,
 46.1445 +    // p->tempRows holds the rows in which the piece landed.
 46.1446 +    break;
 46.1447 +
 46.1448 +  case LJS_LINES:
 46.1449 +    if (p->stateTime > 0) {
 46.1450 +      --p->stateTime;
 46.1451 +    }
 46.1452 +    if (p->stateTime > 0) {
 46.1453 +      break;
 46.1454 +    }
 46.1455 +    if (p->gluing == LJGLUING_SQUARE) {
 46.1456 +      LJBits gluedRows = findSquares(p, 0);
 46.1457 +      gluedRows |= findSquares(p, 1);
 46.1458 +      changedRows |= gluedRows;
 46.1459 +      if (gluedRows) {
 46.1460 +      
 46.1461 +        // When a 4x4 block square is formed, a delay
 46.1462 +        // equal to the line delay is added.
 46.1463 +        p->stateTime += p->speed.lineDelay;
 46.1464 +        break;
 46.1465 +      }
 46.1466 +    } else if (p->gluing == LJGLUING_STICKY
 46.1467 +               || p->gluing == LJGLUING_STICKY_BY_COLOR) {
 46.1468 +      changedRows |= stickyGluing(p);
 46.1469 +    }
 46.1470 +
 46.1471 +    // At this point, p->tempRows holds the rows in which
 46.1472 +    // a line could possibly have been made.
 46.1473 +    tempRows = p->tempRows;
 46.1474 +    tempRows = checkLines(p, tempRows);
 46.1475 +    p->tempRows = tempRows;
 46.1476 +    // At this point, p->tempRows holds the rows in which
 46.1477 +    // a line HAS been made.
 46.1478 +    addLinesScore(p, tempRows);
 46.1479 +    changedRows |= LJ_DIRTY_SCORE;
 46.1480 +
 46.1481 +    // At this point, p->tempRows holds the rows in which a line
 46.1482 +    // HAS been made.
 46.1483 +    p->clearedLines = tempRows;
 46.1484 +    if (!tempRows) {
 46.1485 +      prepareForNewPiece(p);
 46.1486 +      break;
 46.1487 +    }
 46.1488 +
 46.1489 +    changedRows |= clearLines(p, tempRows);
 46.1490 +
 46.1491 +    p->state = LJS_LINES_FALLING;
 46.1492 +    p->stateTime += p->speed.lineDelay;
 46.1493 +    break;
 46.1494 +
 46.1495 +  case LJS_LINES_FALLING:
 46.1496 +    if (p->stateTime > 0) {
 46.1497 +      --p->stateTime;
 46.1498 +    }
 46.1499 +    if (p->stateTime > 0) {
 46.1500 +      break;
 46.1501 +    }
 46.1502 +    moved = fallLines(p);
 46.1503 +    if (moved >= LJ_PF_HT) {
 46.1504 +      p->state = LJS_LINES;
 46.1505 +      p->tempRows = (1 << LJ_PF_HT) - 1;
 46.1506 +    }
 46.1507 +    changedRows |= (~0 << moved) & ((1 << LJ_PF_VIS_HT) - 1);
 46.1508 +    break;
 46.1509 +
 46.1510 +  default:
 46.1511 +    break;
 46.1512 +
 46.1513 +  }
 46.1514 +
 46.1515 +  ++p->gameTime;
 46.1516 +  return changedRows;
 46.1517 +}
 46.1518 +
 46.1519 +
    47.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    47.2 +++ b/src/lj.h	Fri Mar 13 00:39:12 2009 -0700
    47.3 @@ -0,0 +1,507 @@
    47.4 +/* Header file for the engine of LOCKJAW, an implementation of the Soviet Mind Game
    47.5 +
    47.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    47.7 +
    47.8 +This work is free software; you can redistribute it and/or modify
    47.9 +it under the terms of the GNU General Public License as published by
   47.10 +the Free Software Foundation; either version 2 of the License, or
   47.11 +(at your option) any later version.
   47.12 +
   47.13 +This program is distributed in the hope that it will be useful,
   47.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   47.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   47.16 +GNU General Public License for more details.
   47.17 +
   47.18 +You should have received a copy of the GNU General Public License
   47.19 +along with this program; if not, write to the Free Software
   47.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   47.21 +
   47.22 +Original game concept and design by Alexey Pajitnov.
   47.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   47.24 +or The Tetris Company LLC.
   47.25 +
   47.26 +*/
   47.27 +
   47.28 +#ifndef LJ_H
   47.29 +#define LJ_H
   47.30 +
   47.31 +#include <stdlib.h>
   47.32 +#include "ljtypes.h"
   47.33 +
   47.34 +enum {
   47.35 +  CONNECT_U    = 0x01,
   47.36 +  CONNECT_R    = 0x02,
   47.37 +  CONNECT_D    = 0x04,
   47.38 +  CONNECT_L    = 0x08,
   47.39 +  CONNECT_UD   = CONNECT_U | CONNECT_D,
   47.40 +  CONNECT_UL   = CONNECT_U | CONNECT_L,
   47.41 +  CONNECT_DL   = CONNECT_D | CONNECT_L,
   47.42 +  CONNECT_UR   = CONNECT_U | CONNECT_R,
   47.43 +  CONNECT_DR   = CONNECT_D | CONNECT_R,
   47.44 +  CONNECT_LR   = CONNECT_L | CONNECT_R,
   47.45 +  CONNECT_UDL  = CONNECT_UD | CONNECT_L,
   47.46 +  CONNECT_UDR  = CONNECT_UD | CONNECT_R,
   47.47 +  CONNECT_ULR  = CONNECT_UL | CONNECT_R,
   47.48 +  CONNECT_DLR  = CONNECT_DL | CONNECT_R,
   47.49 +  CONNECT_UDLR = CONNECT_UD | CONNECT_LR,
   47.50 +  CONNECT_MASK = CONNECT_UDLR,
   47.51 +  COLOR_MASK   = 0xF0
   47.52 +};
   47.53 +
   47.54 +typedef unsigned char LJBlock;
   47.55 +
   47.56 +typedef enum LJState {
   47.57 +  LJS_INACTIVE,
   47.58 +  LJS_NEW_PIECE,  /* delay is ARE */
   47.59 +  LJS_FALLING,  /* delay is fall delay; NOT IMPLEMENTED */
   47.60 +  LJS_LANDED,  /* delay is lock delay */
   47.61 +  LJS_LINES,  /* delay is line delay */
   47.62 +  LJS_LINES_FALLING, /* delay is fall delay per line; NOT IMPLEMENTED */
   47.63 +  LJS_GAMEOVER,  /* game over animation */
   47.64 +  LJS_EXPLODE  /* used for bombliss extension */
   47.65 +} LJState;
   47.66 +
   47.67 +// for other
   47.68 +enum {
   47.69 +  LJI_HOLD = 0x01,
   47.70 +  LJI_LOCK = 0x02
   47.71 +};
   47.72 +
   47.73 +// for dirty bits
   47.74 +enum {
   47.75 +  LJ_DIRTY_NEXT = 0x01000000,
   47.76 +  LJ_DIRTY_SCORE = 0x02000000
   47.77 +};
   47.78 +
   47.79 +// for gimmick (game mode)
   47.80 +enum {
   47.81 +  LJGM_ATYPE,          // marathon
   47.82 +  LJGM_BTYPE,          // 40 lines
   47.83 +  LJGM_ULTRA,          // 180 seconds
   47.84 +  LJGM_DRILL,          // clear bottom line
   47.85 +  LJGM_ITEMS,          // no rotation + no next + fast drop + garbage + banana
   47.86 +  LJGM_BABY,           // 300 keypresses
   47.87 +  LJGM_N_GIMMICKS
   47.88 +};
   47.89 +
   47.90 +enum {
   47.91 +  LJP_I = 0,
   47.92 +  LJP_J,
   47.93 +  LJP_L,
   47.94 +  LJP_O,
   47.95 +  LJP_S,
   47.96 +  LJP_T,
   47.97 +  LJP_Z,
   47.98 +  LJP_I2,
   47.99 +  LJP_I3,
  47.100 +  LJP_L3,
  47.101 +  LJP_GARBAGE = 7,
  47.102 +  LJP_MASK = 0x0F
  47.103 +};
  47.104 +
  47.105 +enum {
  47.106 +  LJSND_ROTATE    = 0x0001,
  47.107 +  LJSND_SHIFT     = 0x0002,
  47.108 +  LJSND_DROP      = 0x0004,
  47.109 +  LJSND_LAND      = 0x0008,
  47.110 +  LJSND_LOCK      = 0x0010,
  47.111 +  LJSND_LINE      = 0x0020,  // a line was scored
  47.112 +  LJSND_SETB2B    = 0x0040,  // this line is b2b worthy (tetris or t-spin)
  47.113 +  LJSND_B2B       = 0x0080,  // this line AND last line were b2b worthy
  47.114 +  LJSND_SPAWN     = 0x0100,  // next piece was moved up
  47.115 +  LJSND_HOLD      = 0x0200,  // hold piece was activated
  47.116 +  LJSND_IRS       = 0x0400,  // initial rotation: spawn last frame and rotation this frame
  47.117 +  LJSND_SQUARE    = 0x0800,  // formed 4x4 square
  47.118 +  LJSND_COUNTDOWN = 0x4000,  // countdown value changed
  47.119 +  LJSND_SECTIONUP = 0x8000,  // section increased
  47.120 +};
  47.121 +
  47.122 +enum {
  47.123 +  LJRAND_PURE,
  47.124 +  LJRAND_BAG,
  47.125 +  LJRAND_BAGPLUS1,
  47.126 +  LJRAND_2BAG,
  47.127 +  LJRAND_HIST_INF,
  47.128 +  LJRAND_HIST_6,
  47.129 +  LJRAND_N_RANDS
  47.130 +};
  47.131 +
  47.132 +enum {
  47.133 +  LJRAND_4BLK,
  47.134 +  LJRAND_JLOSTZ,
  47.135 +  LJRAND_SZ,
  47.136 +  LJRAND_I,
  47.137 +  LJRAND_234BLK,
  47.138 +  LJRAND_T,
  47.139 +  LJRAND_N_PIECE_SETS
  47.140 +};
  47.141 +
  47.142 +enum {
  47.143 +  LJTS_OFF  = 0,
  47.144 +  LJTS_TNT,
  47.145 +  LJTS_TDS,
  47.146 +  LJTS_TDS_NO_KICK,
  47.147 +  LJTS_N_ALGOS
  47.148 +};
  47.149 +
  47.150 +enum {
  47.151 +  LJSPD_ZERO = 0,
  47.152 +  LJSPD_RHYTHMZERO,
  47.153 +  LJSPD_EXP,
  47.154 +  LJSPD_RHYTHM,
  47.155 +  LJSPD_TGM,
  47.156 +  LJSPD_DEATH,
  47.157 +  LJSPD_DEATH300,
  47.158 +  LJSPD_NES,
  47.159 +  LJSPD_GB,
  47.160 +  LJSPD_GBHEART,
  47.161 +  LJSPD_N_CURVES
  47.162 +};
  47.163 +
  47.164 +enum {
  47.165 +  LJLOCK_NOW = 0,
  47.166 +  LJLOCK_SPAWN,
  47.167 +  LJLOCK_STEP,
  47.168 +  LJLOCK_MOVE,
  47.169 +  LJLOCK_N_STYLES
  47.170 +};
  47.171 +
  47.172 +enum {
  47.173 +  LJGRAV_NAIVE = 0,
  47.174 +  LJGRAV_STICKY,
  47.175 +  LJGRAV_STICKY_BY_COLOR,
  47.176 +  LJGRAV_CASCADE,
  47.177 +  LJGRAV_N_ALGOS
  47.178 +};
  47.179 +
  47.180 +enum {
  47.181 +  LJSCORE_LJ,
  47.182 +  LJSCORE_TNT64,
  47.183 +  LJSCORE_HOTLINE,
  47.184 +  LJSCORE_TDS,
  47.185 +  LJSCORE_NES,
  47.186 +  LJSCORE_LJ_NERF,
  47.187 +  LJSCORE_N_STYLES,
  47.188 +  LJSCORE_WORLDS,
  47.189 +  LJSCORE_TGM1,
  47.190 +  LJSCORE_IPOD
  47.191 +};
  47.192 +
  47.193 +enum {
  47.194 +  LJDROP_NOSCORE,
  47.195 +  LJDROP_NES,
  47.196 +  LJDROP_1CELL,
  47.197 +  LJDROP_1S_2H,
  47.198 +  LJDROP_N_STYLES
  47.199 +};
  47.200 +
  47.201 +enum {
  47.202 +  LJGARBAGE_NONE,
  47.203 +  LJGARBAGE_1,
  47.204 +  LJGARBAGE_2,
  47.205 +  LJGARBAGE_3,
  47.206 +  LJGARBAGE_4,
  47.207 +  LJGARBAGE_HRDERBY,
  47.208 +  LJGARBAGE_DRILL,
  47.209 +  LJGARBAGE_ZIGZAG,
  47.210 +  
  47.211 +  LJGARBAGE_N_STYLES
  47.212 +};
  47.213 +
  47.214 +enum {
  47.215 +  LJHOLD_NONE,
  47.216 +  LJHOLD_EMPTY,
  47.217 +  LJHOLD_TNT,
  47.218 +  LJHOLD_TO_NEXT,
  47.219 +  LJHOLD_N_STYLES
  47.220 +};
  47.221 +
  47.222 +enum {
  47.223 +  LJGLUING_NONE,
  47.224 +  LJGLUING_SQUARE,
  47.225 +  LJGLUING_STICKY,
  47.226 +  LJGLUING_STICKY_BY_COLOR,
  47.227 +  LJGLUING_N_STYLES
  47.228 +};
  47.229 +
  47.230 +// Guideline says 10 wide, but we want to support tetrinet mode
  47.231 +#define LJ_PF_WID ((size_t)12)
  47.232 +#define LJ_SPAWN_X ((LJ_PF_WID - 3) / 2)
  47.233 +#define LJ_PF_HT ((size_t)24)
  47.234 +#define LJ_PF_VIS_HT ((size_t)20)
  47.235 +#define LJ_NEXT_PIECES 8
  47.236 +#define LJ_MAX_LINES_PER_PIECE 8
  47.237 +
  47.238 +typedef struct LJBlkSpec
  47.239 +{
  47.240 +  signed char x, y, conn, reserved;
  47.241 +} LJBlkSpec;
  47.242 +
  47.243 +typedef struct LJInput {
  47.244 +  signed char rotation, movement;
  47.245 +  unsigned char gravity;  /* 5.3 format */
  47.246 +  unsigned char other;
  47.247 +} LJInput;
  47.248 +
  47.249 +typedef struct LJSpeedSetting {
  47.250 +  LJFixed gravity;
  47.251 +  unsigned char entryDelay, dasDelay, lockDelay, lineDelay;
  47.252 +} LJSpeedSetting;
  47.253 +
  47.254 +typedef struct SpeedStateBase {
  47.255 +  unsigned char curve, section;
  47.256 +  unsigned char pad[2];
  47.257 +  signed int level;
  47.258 +} SpeedStateBase;
  47.259 +
  47.260 +#define MAX_BAG_LEN 10
  47.261 +#define MAX_OMINO 4
  47.262 +
  47.263 +typedef struct LJField
  47.264 +{
  47.265 +  /* Game state */
  47.266 +  LJBlock b[LJ_PF_HT][LJ_PF_WID];
  47.267 +  LJBlock c[LJ_PF_HT][LJ_PF_WID];
  47.268 +  LJBits clearedLines;
  47.269 +  LJBits sounds;
  47.270 +  LJBits tempRows;
  47.271 +  unsigned char curPiece[1 + LJ_NEXT_PIECES];
  47.272 +  signed char permuPiece[2 * MAX_BAG_LEN], permuPhase;
  47.273 +  unsigned short nLineClears[LJ_MAX_LINES_PER_PIECE];  // [n-1] = # of n-line clears
  47.274 +  LJFixed y;
  47.275 +  LJState state;
  47.276 +  signed char stateTime;
  47.277 +  unsigned char theta;
  47.278 +  signed char x;
  47.279 +  signed char hardDropY;
  47.280 +  char alreadyHeld;
  47.281 +  char isSpin;
  47.282 +  char nLinesThisPiece;
  47.283 +  char canRotate;
  47.284 +  unsigned char upwardKicks;
  47.285 +  
  47.286 +  /* Persist from piece to piece */
  47.287 +  int score, lines;
  47.288 +  unsigned int gameTime;  // number of frames
  47.289 +  unsigned int activeTime;  // number of frames
  47.290 +  signed short holdPiece;
  47.291 +  char chain;
  47.292 +  signed char garbage;
  47.293 +  unsigned char dropDist;
  47.294 +  unsigned char garbageX;
  47.295 +  unsigned short nPieces, outGarbage;
  47.296 +  unsigned short monosquares, multisquares;
  47.297 +
  47.298 +  /* Determined by gimmick */
  47.299 +  signed char gimmick;
  47.300 +  signed int bpmCounter;
  47.301 +  unsigned int speedupCounter;
  47.302 +  unsigned int goalCount;
  47.303 +  unsigned int seed;
  47.304 +  unsigned char goalType;
  47.305 +
  47.306 +  LJSpeedSetting speed;
  47.307 +  SpeedStateBase speedState;
  47.308 +
  47.309 +  unsigned char lockReset;  // lockdown reset rule type
  47.310 +  unsigned char areStyle;
  47.311 +  unsigned char setLockDelay;  // Overridden lock delay (255 = never)
  47.312 +  unsigned char setLineDelay;  // Overridden line delay
  47.313 +  unsigned char garbageStyle;
  47.314 +  unsigned char ceiling;
  47.315 +  unsigned char enterAbove;
  47.316 +  unsigned char leftWall, rightWall;
  47.317 +  unsigned char pieceSet;
  47.318 +  unsigned char randomizer;
  47.319 +  unsigned char rotationSystem;
  47.320 +  unsigned char garbageRandomness;  // 64: change garbageX in 1/4 of rows; 255: change all
  47.321 +  unsigned char tSpinAlgo;  // 0: off, 1: TNT, 2: TDS
  47.322 +  unsigned char clearGravity;
  47.323 +  unsigned char gluing;  // 0: off; 1: square
  47.324 +  unsigned char scoreStyle;
  47.325 +  unsigned char dropScoreStyle;
  47.326 +  unsigned char maxUpwardKicks;
  47.327 +  unsigned char holdStyle;
  47.328 +  unsigned char bottomBlocks;
  47.329 +  unsigned char reloaded;  // 0: played through; 1: reloaded from saved state
  47.330 +} LJField;
  47.331 +
  47.332 +/**
  47.333 + * Names of the supported rotation systems (wktables.c).
  47.334 + */
  47.335 +enum {
  47.336 +  LJROT_SRS,
  47.337 +  LJROT_SEGA,
  47.338 +  LJROT_ARIKA,
  47.339 +  LJROT_ATARI,
  47.340 +  LJROT_NES,
  47.341 +  LJROT_GB,
  47.342 +  LJROT_TOD,
  47.343 +  LJROT_TDX,
  47.344 +  N_ROTATION_SYSTEMS
  47.345 +};
  47.346 +
  47.347 +#define N_PIECE_SHAPES 10
  47.348 +#define KICK_TABLE_LEN 5
  47.349 +extern const char pieceColors[N_PIECE_SHAPES];
  47.350 +typedef unsigned char WallKickTable[4][KICK_TABLE_LEN];
  47.351 +typedef struct LJRotSystem {
  47.352 +  /**
  47.353 +   * Color scheme for this rotation system (0: SRS; 1: Sega)
  47.354 +   * Use colorset 0 (SRS) if all free-space kicks are WK(0, 0).
  47.355 +   * Otherwise, your rotation system has what Eddie Rogers has called
  47.356 +   * a topknot, and you should use Sega colors.
  47.357 +   */
  47.358 +  unsigned char colorScheme;
  47.359 +  unsigned char reserved1[3];
  47.360 +  unsigned char entryOffset[N_PIECE_SHAPES];
  47.361 +  unsigned char entryTheta[N_PIECE_SHAPES];
  47.362 +  /* These control which kick table is used for each piece.
  47.363 +   * If negative, no kick table is present.
  47.364 +   */
  47.365 +  signed char kicksL[N_PIECE_SHAPES];
  47.366 +  signed char kicksR[N_PIECE_SHAPES];
  47.367 +  WallKickTable kickTables[];
  47.368 +} LJRotSystem;
  47.369 +
  47.370 +extern const LJRotSystem *const rotSystems[N_ROTATION_SYSTEMS];
  47.371 +
  47.372 +#ifdef LJ_INTERNAL
  47.373 +  // Used for defining wall kick tables:
  47.374 +  // WK(x, y) builds a 1-byte record with 2 coordinates, at 4 bits
  47.375 +  // per coordinate.
  47.376 +  // WKX() and WKY() retrieve the X or Y coordinate from this record.
  47.377 +  // The  ((stuff ^ 8) - 8) is sign extension magick
  47.378 +  #define WK(x, y) (((x) & 0x0F) | ((y) & 0x0F) << 4)
  47.379 +  #define WKX(wk) ((((wk) & 0x0F) ^ 8) - 8)
  47.380 +  #define WKY(wk) WKX((wk) >> 4)
  47.381 +  // If ARIKA_IF_NOT_CENTER is specified, check the free space
  47.382 +  // rotation for blocks in columns other than 1, and stop if
  47.383 +  // none of them are filled.  This obsoletes the old SKIP_IF.
  47.384 +  #define SKIP_IF 0x80
  47.385 +  #define SKIP_IF3 0x82
  47.386 +  
  47.387 +  #define ARIKA_IF_NOT_CENTER 0x83
  47.388 +  #define WK_END 0x8F
  47.389 +#if 0
  47.390 +  extern const WallKickTable *const wkTablesL[N_ROTATION_SYSTEMS][N_PIECE_SHAPES];
  47.391 +  extern const WallKickTable *const wkTablesR[N_ROTATION_SYSTEMS][N_PIECE_SHAPES];
  47.392 +#endif
  47.393 +#endif
  47.394 +
  47.395 +/**
  47.396 + * Expands a tetromino to the blocks that make it up.
  47.397 + * @param out   an array of length 4
  47.398 + * @param p the field into which the tetromino will be spawned
  47.399 + *        (used for default spawn orientation)
  47.400 + * @param piece a piece number
  47.401 + * @param xBase x coordinate of base position of the tetromino
  47.402 + * @param yBase y coordinate of base position of the tetromino
  47.403 + * @param theta the orientation of the tetromino
  47.404 + *        (0-3: use this; 4: use default spawn rotation)
  47.405 + */
  47.406 +void expandPieceToBlocks(LJBlkSpec out[],
  47.407 +                         const LJField *p,
  47.408 +                         int piece, int xBase, int yBase, int theta);
  47.409 +
  47.410 +/**
  47.411 + * Tests whether a particular block is occupied.
  47.412 + * @param p the playfield
  47.413 + * @param x the column
  47.414 + * @param y the row (0 = bottom)
  47.415 + * @return  zero iff the space is open
  47.416 + */
  47.417 +int isOccupied(const LJField *p, int x, int y);
  47.418 +
  47.419 +/**
  47.420 + * Tests whether a particular tetromino would overlap one or more
  47.421 + * blocks, a side wall, or the floor
  47.422 + * @param p the playfield
  47.423 + * @param x the column of the left side of the piece's bounding 4x4
  47.424 + * @param y the row of the bottom of the piece's bounding 4x4 (0 = bottom)
  47.425 + * @return  zero iff the space is open
  47.426 + */
  47.427 +int isCollision(const LJField *p, int x, int y, int theta);
  47.428 +
  47.429 +/**
  47.430 + * Blanks a playfield and prepares it for a new game.
  47.431 + * @param p    the playfield
  47.432 + */
  47.433 +void newGame(LJField *p);
  47.434 +
  47.435 +/**
  47.436 + * Runs one frame of S.M.G.
  47.437 + * @param p   the playfield
  47.438 + * @param in  the player's input
  47.439 + * @return the rows that were modified
  47.440 + */
  47.441 +LJBits frame(LJField *p, const LJInput *in);
  47.442 +
  47.443 +/**
  47.444 + * Applies gimmicks to a new game.
  47.445 + * @param p   the playfield
  47.446 + * @param in  the player's input
  47.447 + */
  47.448 +void initGimmicks(LJField *p);
  47.449 +
  47.450 +/**
  47.451 + * Runs gimmicks for one frame of S.M.G.
  47.452 + * @param p   the playfield
  47.453 + * @param c   the control settings
  47.454 + * @return the rows that were modified
  47.455 + */
  47.456 +struct LJControl;
  47.457 +LJBits gimmicks(LJField *p, struct LJControl *c);
  47.458 +
  47.459 +/**
  47.460 + * Sets up the randomizer.
  47.461 + * @return the number of this piece
  47.462 + */
  47.463 +void initRandomize(LJField *p);
  47.464 +
  47.465 +/**
  47.466 + * Chooses a pseudo-random piece.
  47.467 + * @param the field on which the piece will be generated
  47.468 + * @return the number of this piece
  47.469 + *   (0=i, 1=j, 2=l, 3=o, 4=s, 5=t, 6=z)
  47.470 + */
  47.471 +unsigned int randomize(LJField *p);
  47.472 +
  47.473 +/**
  47.474 + * Counts the number of 1 bits in a bitfield.
  47.475 + * @param p   the bitfield
  47.476 + * @return    the number of bits in p with a value of 1.
  47.477 + */
  47.478 +unsigned int countOnes(LJBits b);
  47.479 +
  47.480 +/**
  47.481 + * Shuffles the columns of blocks in the playfield.
  47.482 + * @param p   the playfield
  47.483 + */
  47.484 +void shuffleColumns(LJField *p);
  47.485 +
  47.486 +/**
  47.487 + * Random number generator.  Use this for all random events
  47.488 + * that affect the game state.
  47.489 + * @param field
  47.490 + * @return uniformly distributed number in 0 to 0x7FFF
  47.491 + */
  47.492 +unsigned int ljRand(LJField *p);
  47.493 +
  47.494 +/**
  47.495 + * List of lines used for hotline scoring.
  47.496 + * A line clear completed at row hotlineRows[i] is worth 100*(i + 1).
  47.497 + */
  47.498 +extern const char hotlineRows[LJ_PF_HT];
  47.499 +
  47.500 +/**
  47.501 + * Counts the number of trailing zero bits on an integer.
  47.502 + * For instance, 00000000 00000000 00000001 11111000
  47.503 + * has 3 trailing zeroes.
  47.504 + */
  47.505 +unsigned int bfffo(LJBits rowBits);
  47.506 +
  47.507 +void setupZigzagField(LJField *p, size_t height);
  47.508 +unsigned int calcZigzagGrade(const LJField *p);
  47.509 +
  47.510 +#endif
    48.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    48.2 +++ b/src/ljcontrol.h	Fri Mar 13 00:39:12 2009 -0700
    48.3 @@ -0,0 +1,109 @@
    48.4 +/* Control for LOCKJAW, an implementation of the Soviet Mind Game
    48.5 +
    48.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    48.7 +
    48.8 +This work is free software; you can redistribute it and/or modify
    48.9 +it under the terms of the GNU General Public License as published by
   48.10 +the Free Software Foundation; either version 2 of the License, or
   48.11 +(at your option) any later version.
   48.12 +
   48.13 +This program is distributed in the hope that it will be useful,
   48.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   48.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   48.16 +GNU General Public License for more details.
   48.17 +
   48.18 +You should have received a copy of the GNU General Public License
   48.19 +along with this program; if not, write to the Free Software
   48.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   48.21 +
   48.22 +Original game concept and design by Alexey Pajitnov.
   48.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   48.24 +or The Tetris Company LLC.
   48.25 +
   48.26 +*/
   48.27 +
   48.28 +#ifndef LJCONTROL_H
   48.29 +#define LJCONTROL_H
   48.30 +
   48.31 +#include "ljtypes.h"
   48.32 +
   48.33 +// The cross-platform parts of the view are stored here
   48.34 +typedef struct LJControl {
   48.35 +  LJBits lastKeys, repressKeys;
   48.36 +  unsigned int presses;
   48.37 +  unsigned char dasSpeed;
   48.38 +  unsigned char dasDelay;
   48.39 +  signed char dasCounter;
   48.40 +  unsigned char allowDiagonals;
   48.41 +  unsigned char softDropSpeed;
   48.42 +  unsigned char softDropLock;
   48.43 +  unsigned char hardDropLock;
   48.44 +  unsigned char initialDAS;
   48.45 +  unsigned char initialRotate;
   48.46 +  signed char countdown;
   48.47 +  struct LJReplay *replaySrc;
   48.48 +  struct LJReplay *replayDst;
   48.49 +} LJControl;
   48.50 +
   48.51 +struct LJField;
   48.52 +struct LJInput;
   48.53 +
   48.54 +#define N_SPEED_METER_PIECES 10
   48.55 +
   48.56 +typedef struct LJView {
   48.57 +  struct LJField *field;
   48.58 +  LJControl *control;
   48.59 +  LJBits backDirty;
   48.60 +  LJBits frontDirty;
   48.61 +  struct LJPCView *plat;
   48.62 +  unsigned char smoothGravity;
   48.63 +  unsigned char hideNext;
   48.64 +  unsigned char hideShadow;
   48.65 +  unsigned char nextPieces;
   48.66 +  unsigned char hidePF;
   48.67 +  unsigned char showTrails;
   48.68 +  unsigned char nLockTimes;
   48.69 +  unsigned int lockTime[N_SPEED_METER_PIECES];
   48.70 +  LJFixed trailY;
   48.71 +} LJView;
   48.72 +
   48.73 +enum {
   48.74 +  LJSHADOW_COLORED_25 = 0,
   48.75 +  LJSHADOW_COLORED_50,
   48.76 +  LJSHADOW_COLORED,
   48.77 +  LJSHADOW_COLORLESS,
   48.78 +  LJSHADOW_NONE,
   48.79 +  LJSHADOW_NO_FALLING,
   48.80 +  LJSHADOW_N_STYLES
   48.81 +};
   48.82 +
   48.83 +#define VKEY_UP        0x0001
   48.84 +#define VKEY_DOWN      0x0002
   48.85 +#define VKEY_LEFT      0x0004
   48.86 +#define VKEY_RIGHT     0x0008
   48.87 +#define VKEY_ROTL      0x0010
   48.88 +#define VKEY_ROTR      0x0020
   48.89 +#define VKEY_HOLD      0x0040
   48.90 +#define VKEY_ITEM      0x0080
   48.91 +#define VKEY_MACRO(x)  (0x100 << (x))
   48.92 +#define VKEY_MACROS    0xFF00
   48.93 +#define VKEY_START     0x00080000
   48.94 +
   48.95 +enum {
   48.96 +  LJZANGI_SLIDE,
   48.97 +  LJZANGI_LOCK,
   48.98 +  LJZANGI_LOCK_RELEASE,
   48.99 +  LJZANGI_N_STYLES
  48.100 +};
  48.101 +
  48.102 +/* VKEY_MACRO(0) to VKEY_MACRO(7)
  48.103 +
  48.104 +Event planners can restrict how many macros a player can use,
  48.105 +so that keyboardists don't have an unfair advantage over
  48.106 +gamepad users.
  48.107 +
  48.108 +*/
  48.109 +
  48.110 +void addKeysToInput(struct LJInput *dst, LJBits keys, const struct LJField *p, LJControl *c);
  48.111 +
  48.112 +#endif
    49.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    49.2 +++ b/src/ljds.c	Fri Mar 13 00:39:12 2009 -0700
    49.3 @@ -0,0 +1,334 @@
    49.4 +/* DS frontend for LOCKJAW, an implementation of the Soviet Mind Game
    49.5 +
    49.6 +Copyright (C) 2006-2007 Damian Yerrick <tepples+lj@spamcop.net>
    49.7 +
    49.8 +This work is free software; you can redistribute it and/or modify
    49.9 +it under the terms of the GNU General Public License as published by
   49.10 +the Free Software Foundation; either version 2 of the License, or
   49.11 +(at your option) any later version.
   49.12 +
   49.13 +This program is distributed in the hope that it will be useful,
   49.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   49.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   49.16 +GNU General Public License for more details.
   49.17 +
   49.18 +You should have received a copy of the GNU General Public License
   49.19 +along with this program; if not, write to the Free Software
   49.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   49.21 +
   49.22 +Original game concept and design by Alexey Pajitnov.
   49.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   49.24 +or The Tetris Company LLC.
   49.25 +
   49.26 +*/
   49.27 +
   49.28 +#include "ljplay.h"
   49.29 +#include "ljds.h"
   49.30 +#include "talkback.h"
   49.31 +#include <stdio.h>
   49.32 +#include <string.h>
   49.33 +#include "options.h"
   49.34 +#include "gbamenus.h"
   49.35 +#include "ljpath.h"
   49.36 +
   49.37 +#if 1
   49.38 +  #define LJ_VERSION "0.46 ("__DATE__")"
   49.39 +#else
   49.40 +  #define LJ_VERSION "WIP ("__DATE__")"
   49.41 +#endif
   49.42 +
   49.43 +#define SCREEN_W 32
   49.44 +#define SCREEN_H 24
   49.45 +#define DS_PFTOP 3
   49.46 +#define DS_PFLEFT 10
   49.47 +
   49.48 +/* My ghetto IPC system */
   49.49 +volatile P8A7Talkback tb_cached = {
   49.50 +  .cmd = 0,
   49.51 +  .sounds = 0
   49.52 +};
   49.53 +
   49.54 +#define tb (*(volatile P8A7Talkback *) \
   49.55 +             ((volatile char *)&tb_cached + 0x00400000))
   49.56 +
   49.57 +short mouse_x, mouse_y;
   49.58 +LJBits mouse_b;
   49.59 +
   49.60 +#include "ljgbads.inc"
   49.61 +
   49.62 +unsigned int nSprites = 0;
   49.63 +volatile int curTime;
   49.64 +
   49.65 +void gba_play_sound(struct LJPCView *v, int n) {
   49.66 +
   49.67 +}
   49.68 +
   49.69 +void gba_poll_sound(struct LJPCView *plat) {
   49.70 +  
   49.71 +}
   49.72 +
   49.73 +LJBits readHWKeys(void) {
   49.74 +  scanKeys();
   49.75 +  LJBits j = keysHeld();
   49.76 +  touchPosition xy = touchReadXY();
   49.77 +
   49.78 +  if (j & KEY_TOUCH) {
   49.79 +    mouse_x = xy.px;
   49.80 +    mouse_y = xy.py;
   49.81 +    mouse_b = 1;
   49.82 +    j &= ~(KEY_TOUCH_RIGHT | KEY_TOUCH_LEFT
   49.83 +           | KEY_TOUCH_UP | KEY_TOUCH_DOWN);
   49.84 +    if (xy.px < 96) {
   49.85 +      j |= KEY_TOUCH_LEFT;
   49.86 +    } else if (xy.px >= 160) {
   49.87 +      j |= KEY_TOUCH_RIGHT;
   49.88 +    }
   49.89 +    if (xy.py < 64) {
   49.90 +      j |= KEY_TOUCH_UP;
   49.91 +    } else if (xy.py >= 128) {
   49.92 +      j |= KEY_TOUCH_DOWN;
   49.93 +    }
   49.94 +  } else {
   49.95 +    mouse_b = 0;
   49.96 +  }
   49.97 +  return j;
   49.98 +}
   49.99 +
  49.100 +void finishSprites(void) {
  49.101 +  for (int i = nSprites - 1; i >= 0; --i) {
  49.102 +    MAINOAM[i].attribute[0] = 512;
  49.103 +  }
  49.104 +  nSprites = 128;
  49.105 +}
  49.106 +
  49.107 +void vsync(void) {
  49.108 +  swiWaitForVBlank();
  49.109 +  wantPause |= needLidSleep();
  49.110 +}
  49.111 +
  49.112 +void isr(void) 
  49.113 +{
  49.114 +  int interrupts = REG_IF;
  49.115 +
  49.116 +  VBLANK_INTR_WAIT_FLAGS |= interrupts;
  49.117 +  REG_IF = interrupts;
  49.118 +  ++curTime;
  49.119 +}
  49.120 +
  49.121 +#define KEY_X (IPC_X << 16)
  49.122 +#define KEY_Y (IPC_Y << 16)
  49.123 +#define KEY_PEN (IPC_PEN_DOWN << 16)
  49.124 +#define VRAM_MAIN ((uint16 *)0x06000000)
  49.125 +#define VRAM_SUB ((uint16 *)0x06200000)
  49.126 +
  49.127 +
  49.128 +
  49.129 +/**
  49.130 + * Tells the ARM7 to play these sound effects.
  49.131 + */
  49.132 +void playSoundEffects(LJView *v, LJBits sounds, int countdown) {
  49.133 +  tb.countdown = countdown;
  49.134 +  tb.sounds |= sounds;
  49.135 +}
  49.136 +
  49.137 +#define SHADOW_BLOCK 0x00
  49.138 +
  49.139 +/**
  49.140 + * Draws a tetromino whose lower left corner of the bounding box is at (x, y)
  49.141 + * @param b the bitmap to draw to
  49.142 + * @param piece the piece to be drawn
  49.143 + * @param x distance from to left side of 4x4 box
  49.144 + * @param y distance from top of bitmap to bottom of 4x4 box
  49.145 + * @param the rotation state (0: U; 1: R; 2: D; 3: L; 4: Initial position)
  49.146 + * @param w width of each block
  49.147 + * @param h height of each block
  49.148 + * @param color Drawing style
  49.149 + * color == 0: draw shadow
  49.150 + * color == 0x10 through 0x70: draw in that color
  49.151 + * color == 0x80: draw as garbage
  49.152 + * color == -255 through -1: draw with 255 through 1 percent lighting
  49.153 + */
  49.154 +LJBits drawPiece(LJView *const v, void *const b,
  49.155 +                 int piece, int x, int y, int theta,
  49.156 +                 int color, int w, int h) {
  49.157 +  // Don't try to draw the -1 that's the sentinel for no hold piece
  49.158 +  if (piece < 0)
  49.159 +    return 0;
  49.160 +
  49.161 +  LJBits rows = 0;
  49.162 +  LJBlkSpec blocks[4];
  49.163 +
  49.164 +  expandPieceToBlocks(blocks, v->field, piece, 0, 0, theta);
  49.165 +  
  49.166 +  for (int blk = 0; blk < 4; ++blk) {
  49.167 +    int blkValue = blocks[blk].conn;
  49.168 +    if (blkValue) {
  49.169 +      int blkX = blocks[blk].x;
  49.170 +      int blkY = blocks[blk].y;
  49.171 +      const int dstX = x + w * blkX;
  49.172 +      const int dstY = y + h * (-1 - blkY);
  49.173 +    
  49.174 +      if (color == 0x80) {
  49.175 +        blkValue = 0x8001;  // garbage hold
  49.176 +      } else if (color != 0) {
  49.177 +        blkValue = ((blkValue & 0xF0) << 8) | 1;
  49.178 +      } else if (color == 0) {
  49.179 +        if (v->hideShadow == LJSHADOW_COLORED) {
  49.180 +          blkValue = ((blkValue & 0xF0) << 8) | 2;
  49.181 +        } else {
  49.182 +          blkValue = 0x8002;
  49.183 +        }
  49.184 +      }
  49.185 +    
  49.186 +      if (dstY > -8 && dstY < 192) {
  49.187 +        --nSprites;
  49.188 +        MAINOAM[nSprites].attribute[0] = dstY & 0x00FF;
  49.189 +        MAINOAM[nSprites].attribute[1] = dstX & 0x01FF;
  49.190 +        MAINOAM[nSprites].attribute[2] = blkValue;
  49.191 +      }
  49.192 +
  49.193 +      rows |= 1 << blkY;
  49.194 +    }
  49.195 +  }
  49.196 +
  49.197 +  return rows;
  49.198 +}
  49.199 +
  49.200 +void openWindow(void) {
  49.201 +  videoSetMode(MODE_0_2D
  49.202 +               | DISPLAY_BG0_ACTIVE
  49.203 +               | DISPLAY_SPR_1D_LAYOUT 
  49.204 +               | DISPLAY_SPR_ACTIVE);
  49.205 +  videoSetModeSub(MODE_0_2D
  49.206 +                 | DISPLAY_BG0_ACTIVE);
  49.207 +  BGCTRL[0] = BG_16_COLOR | BG_TILE_BASE(0) | BG_MAP_BASE(31);
  49.208 +  BGCTRL_SUB[0] = BG_16_COLOR | BG_TILE_BASE(0) | BG_MAP_BASE(31);
  49.209 +  
  49.210 +  vramSetMainBanks(VRAM_A_MAIN_BG, VRAM_B_MAIN_SPRITE_0x06400000,
  49.211 +                   VRAM_C_SUB_BG, VRAM_D_SUB_SPRITE);
  49.212 +  /* load_font(); */
  49.213 +  // Load palette
  49.214 +  BG_PALETTE[0] = RGB5(31,31,31);
  49.215 +  BG_PALETTE[1] = RGB5( 0, 0,15);
  49.216 +  BG_PALETTE_SUB[0] = RGB5(0, 0, 0);
  49.217 +  setupPalette(srsColors);
  49.218 +
  49.219 +  // Set scrolling
  49.220 +  BG_OFFSET[0].x = 0;
  49.221 +  BG_OFFSET[0].y = 0;
  49.222 +  BG_OFFSET_SUB[0].x = 0;
  49.223 +  BG_OFFSET_SUB[0].y = 0;
  49.224 +
  49.225 +  SUB_BG2_XDX = 0x100;
  49.226 +  SUB_BG2_XDY = 0;
  49.227 +  SUB_BG2_YDX = 0;
  49.228 +  SUB_BG2_YDY = 0x100;
  49.229 +  SUB_BG2_CY = 0;
  49.230 +  SUB_BG2_CX = 0;
  49.231 +
  49.232 +  lcdMainOnTop();
  49.233 +}
  49.234 +
  49.235 +void install_sound(void) {
  49.236 +  IPC->soundData = (void *)&tb;
  49.237 +}
  49.238 +
  49.239 +#ifdef TRAP_SPRINTF
  49.240 +int sprintf (char *dst, const char *format, ...) {
  49.241 +  BG_PALETTE[0] = RGB5(31, 0, 0);
  49.242 +  strcpy(dst, "[NO FPU]");
  49.243 +  return 8;
  49.244 +}
  49.245 +#endif
  49.246 +
  49.247 +int main(void) {
  49.248 +  LJField p = {
  49.249 +    .leftWall = 1,
  49.250 +    .rightWall = 11,
  49.251 +    .ceiling = 20
  49.252 +  };
  49.253 +  LJControl control = {
  49.254 +    .dasSpeed = 1,
  49.255 +    .dasDelay = 10,
  49.256 +    .initialDAS = 1,
  49.257 +    .allowDiagonals = 0,
  49.258 +    .softDropSpeed = 0,
  49.259 +    .softDropLock = 0,
  49.260 +    .hardDropLock = 1
  49.261 +  };
  49.262 +  struct LJPCView platView;
  49.263 +  LJView mainView = {
  49.264 +    .field = &p,
  49.265 +    .control = &control,
  49.266 +    .smoothGravity = 1,
  49.267 +    .nextPieces = 3,
  49.268 +    .plat = &platView,
  49.269 +    .backDirty = ~0
  49.270 +  };
  49.271 +
  49.272 +  powerON(POWER_ALL_2D);
  49.273 +  initOptions(customPrefs);
  49.274 +  install_timer();
  49.275 +  install_sound();
  49.276 +  openWindow();
  49.277 +
  49.278 +  BG_PALETTE_SUB[0] = RGB5( 0, 0, 0);
  49.279 +  BG_PALETTE_SUB[1] = RGB5(10,20,10);
  49.280 +  BG_PALETTE_SUB[2] = RGB5(15,31,15);
  49.281 +  BG_PALETTE_SUB[3] = RGB5(15,31,15);
  49.282 +  vwfWinInit(&vwfTouch);
  49.283 +  {
  49.284 +    int x = vwfPuts(&vwfTouch, "finding memory card... ", 0, 0);
  49.285 +    if (ljpathInit("/data/lockjaw/lj.nds")) {
  49.286 +      vwfPuts(&vwfTouch, "success!", x, 0);
  49.287 +    } else {
  49.288 +      vwfPuts(&vwfTouch, "failed.", x, 0);
  49.289 +      vwfPuts(&vwfTouch, "To learn how to fix this, see", 0, 12);
  49.290 +      vwfPuts(&vwfTouch, "http://dldi.drunkencoders.com/", 0, 24);
  49.291 +    }
  49.292 +  }
  49.293 +  
  49.294 +  coprNotice();
  49.295 +
  49.296 +  load_font();
  49.297 +  drawFrame(&mainView);
  49.298 +
  49.299 +  while (1) {
  49.300 +    LJView *const players[1] = {&mainView};
  49.301 +
  49.302 +    for (int y = 0; y < LJ_PF_VIS_HT; ++y) {
  49.303 +      for (int x = 0; x < LJ_PF_WID; ++x) {
  49.304 +        p.b[y][x] = 0;
  49.305 +      }
  49.306 +    }
  49.307 +    updField(&mainView, ~0);
  49.308 +
  49.309 +    // turn on sub display only long enough for options
  49.310 +    videoSetModeSub(MODE_0_2D
  49.311 +                   | DISPLAY_BG0_ACTIVE);
  49.312 +    setupPalette(srsColors);
  49.313 +    options(&mainView, customPrefs);
  49.314 +    videoSetModeSub(MODE_0_2D);
  49.315 +    BGCTRL_SUB[0] = BG_16_COLOR | BG_TILE_BASE(0) | BG_MAP_BASE(31);
  49.316 +    BG_PALETTE_SUB[0] = RGB5(0, 0, 0);
  49.317 +    unpackCommonOptions(&mainView, customPrefs);
  49.318 +
  49.319 +    p.seed = curTime ^ (curTime << 16);
  49.320 +    play(players, 1);
  49.321 +
  49.322 +    tb.cmd = TALKBACK_STOP_MUSIC;
  49.323 +    BG_PALETTE[0] = (control.countdown > 0)
  49.324 +                    ? RGB5(31, 15, 15) 
  49.325 +                    : RGB5(15, 31, 15);
  49.326 +
  49.327 +    // play game over sound
  49.328 +    if (control.countdown > 0) {
  49.329 +      playSoundEffects(&mainView, 3 << 16, control.countdown);
  49.330 +    }
  49.331 +    for (int i = 0; i < 60; ++i) {
  49.332 +      vsync();
  49.333 +      //gba_poll_sound(&platView);
  49.334 +    }
  49.335 +    debrief(&mainView);
  49.336 +  }
  49.337 +}
    50.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    50.2 +++ b/src/ljds.h	Fri Mar 13 00:39:12 2009 -0700
    50.3 @@ -0,0 +1,77 @@
    50.4 +/* DS frontend for LOCKJAW, an implementation of the Soviet Mind Game
    50.5 +
    50.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    50.7 +
    50.8 +This work is free software; you can redistribute it and/or modify
    50.9 +it under the terms of the GNU General Public License as published by
   50.10 +the Free Software Foundation; either version 2 of the License, or
   50.11 +(at your option) any later version.
   50.12 +
   50.13 +This program is distributed in the hope that it will be useful,
   50.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   50.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   50.16 +GNU General Public License for more details.
   50.17 +
   50.18 +You should have received a copy of the GNU General Public License
   50.19 +along with this program; if not, write to the Free Software
   50.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   50.21 +
   50.22 +Original game concept and design by Alexey Pajitnov.
   50.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   50.24 +or The Tetris Company LLC.
   50.25 +
   50.26 +*/
   50.27 +
   50.28 +#ifndef LJDS_H
   50.29 +#define LJDS_H
   50.30 +
   50.31 +#include <nds.h>
   50.32 +#include <stdlib.h>
   50.33 +#include "ljcontrol.h"
   50.34 +
   50.35 +//#define SPR_VRAM(x) ((u32 *)(0x06420000 + 32 * x))
   50.36 +#define SPR_VRAM(x) ((u32 *)(0x06400000 + 32 * x))
   50.37 +#define MAINOAM ((SpriteEntry *)0x07000000)
   50.38 +
   50.39 +#define PATRAM4(x, tn) ((u32 *)(BG_TILE_RAM(0) | (((x) << 14) + ((tn) << 5)) ))
   50.40 +
   50.41 +#ifndef MAP
   50.42 +typedef u16 NAMETABLE[32][32];
   50.43 +
   50.44 +#define MAP ((NAMETABLE *)BG_MAP_RAM(0))
   50.45 +#define MAP_HFLIP      0x0400
   50.46 +#define MAP_VFLIP      0x0800
   50.47 +#define MAP_FLIP       0x0c00
   50.48 +#define MAP_PALETTE(x) ((x) << 12)
   50.49 +#endif
   50.50 +
   50.51 +struct LJPCView {
   50.52 +  const u16 *sndData[4];
   50.53 +  u8 sndLeft[4];
   50.54 +};
   50.55 +
   50.56 +#define KEY_TOUCH_RIGHT (KEY_RIGHT << 8)
   50.57 +#define KEY_TOUCH_LEFT  (KEY_LEFT << 8)
   50.58 +#define KEY_TOUCH_UP    (KEY_UP << 8)
   50.59 +#define KEY_TOUCH_DOWN  (KEY_DOWN << 8)
   50.60 +
   50.61 +extern short mouse_x, mouse_y;
   50.62 +extern LJBits mouse_b;
   50.63 +
   50.64 +void textout(const char *str, int x, int y, int c);
   50.65 +void isr(void);
   50.66 +void cls(void);
   50.67 +void vsync(void);
   50.68 +LJBits readPad(unsigned int player);
   50.69 +extern volatile int curTime;
   50.70 +
   50.71 +void install_sound(void);
   50.72 +void gba_poll_sound(struct LJPCView *v);
   50.73 +void gba_play_sound(struct LJPCView *v, int effect);
   50.74 +
   50.75 +extern unsigned char customPrefs[];
   50.76 +void options(LJView *view, unsigned char *prefs);
   50.77 +
   50.78 +int needLidSleep(void);
   50.79 +void debrief(const LJView *v);
   50.80 +#endif
    51.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    51.2 +++ b/src/ljgba.c	Fri Mar 13 00:39:12 2009 -0700
    51.3 @@ -0,0 +1,230 @@
    51.4 +/* GBA frontend for LOCKJAW, an implementation of the Soviet Mind Game
    51.5 +
    51.6 +Copyright (C) 2006-2007 Damian Yerrick <tepples+lj@spamcop.net>
    51.7 +
    51.8 +This work is free software; you can redistribute it and/or modify
    51.9 +it under the terms of the GNU General Public License as published by
   51.10 +the Free Software Foundation; either version 2 of the License, or
   51.11 +(at your option) any later version.
   51.12 +
   51.13 +This program is distributed in the hope that it will be useful,
   51.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   51.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   51.16 +GNU General Public License for more details.
   51.17 +
   51.18 +You should have received a copy of the GNU General Public License
   51.19 +along with this program; if not, write to the Free Software
   51.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   51.21 +
   51.22 +Original game concept and design by Alexey Pajitnov.
   51.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   51.24 +or The Tetris Company LLC.
   51.25 +
   51.26 +*/
   51.27 +
   51.28 +#include <stdio.h>
   51.29 +#include <string.h>
   51.30 +#include "ljgba.h"
   51.31 +#include "ljplay.h"
   51.32 +#include "options.h"
   51.33 +
   51.34 +#if 1
   51.35 +  #define LJ_VERSION "0.46 ("__DATE__")"
   51.36 +#else
   51.37 +  #define LJ_VERSION "WIP ("__DATE__")"
   51.38 +#endif
   51.39 +
   51.40 +#define SCREEN_W 30
   51.41 +#define SCREEN_H 20
   51.42 +#define DS_PFTOP 0
   51.43 +#define DS_PFLEFT 9
   51.44 +#include "ljgbads.inc"
   51.45 +
   51.46 +static unsigned int nSprites;
   51.47 +
   51.48 +LJBits readHWKeys(void) {
   51.49 +  return (~REG_KEYINPUT) & 0x03FF;
   51.50 +}
   51.51 +
   51.52 +void finishSprites() {
   51.53 +  for (int i = nSprites - 1; i >= 0; --i) {
   51.54 +    OAM[i].attr0 = 512;
   51.55 +  }
   51.56 +  nSprites = 128;
   51.57 +}
   51.58 +
   51.59 +void vsync(void) {
   51.60 +  VBlankIntrWait();
   51.61 +}
   51.62 +
   51.63 +void openWindow(void) {
   51.64 +  REG_DISPCNT = MODE_0 | BG0_ON | OBJ_ON;
   51.65 +  BGCTRL[0] = BG_TILE_BASE(0) | BG_MAP_BASE(31);
   51.66 +  BG_PALETTE[0] = RGB5(31, 31, 31);
   51.67 +  BG_PALETTE[1] = RGB5(0, 0, 0);
   51.68 +  SPRITE_PALETTE[0] = RGB5(31, 0, 31);
   51.69 +  SPRITE_PALETTE[1] = RGB5(0, 0, 0);
   51.70 +  setupPalette(srsColors);
   51.71 +}
   51.72 +
   51.73 +void playSoundEffects(LJView *v, LJBits sounds, int countdown) {
   51.74 +  if (sounds & LJSND_IRS) {
   51.75 +    gba_play_sound(v->plat, 6);
   51.76 +  } else if (sounds & LJSND_ROTATE) {
   51.77 +    gba_play_sound(v->plat, 1);
   51.78 +  }
   51.79 +  if (sounds & LJSND_SHIFT) {
   51.80 +    gba_play_sound(v->plat, 0);
   51.81 +  }
   51.82 +  if (sounds & LJSND_LAND) {
   51.83 +    gba_play_sound(v->plat, 2);
   51.84 +  }
   51.85 +  if (sounds & LJSND_LOCK) {
   51.86 +    gba_play_sound(v->plat, 3);
   51.87 +  }
   51.88 +  if (sounds & LJSND_B2B) {
   51.89 +    gba_play_sound(v->plat, 8);
   51.90 +  } else if (sounds & LJSND_SETB2B) {
   51.91 +    gba_play_sound(v->plat, 7);
   51.92 +  } else if (sounds & LJSND_LINE) {
   51.93 +    gba_play_sound(v->plat, 4);
   51.94 +  }
   51.95 +  if (sounds & LJSND_HOLD) {
   51.96 +    gba_play_sound(v->plat, 5);
   51.97 +  }
   51.98 +  if (sounds & LJSND_SECTIONUP) {
   51.99 +    gba_play_sound(v->plat, 9);
  51.100 +  }
  51.101 +  gba_poll_sound(v->plat);
  51.102 +}
  51.103 +
  51.104 +/**
  51.105 + * Draws a tetromino whose lower left corner of the bounding box is at (x, y)
  51.106 + * @param b the bitmap to draw to
  51.107 + * @param piece the piece to be drawn
  51.108 + * @param x distance from to left side of 4x4 box
  51.109 + * @param y distance from top of bitmap to bottom of 4x4 box
  51.110 + * @param the rotation state (0: U; 1: R; 2: D; 3: L; 4: Initial position)
  51.111 + * @param w width of each block
  51.112 + * @param h height of each block
  51.113 + * @param color Drawing style
  51.114 + * color == 0: draw shadow
  51.115 + * color == 0x10 through 0x70: draw in that color
  51.116 + * color == 0x80: draw as garbage
  51.117 + * color == -255 through -1: draw with 255 through 1 percent lighting
  51.118 + */
  51.119 +LJBits drawPiece(LJView *const v, void *const b,
  51.120 +                 int piece, int x, int y, int theta,
  51.121 +                 int color, int w, int h) {
  51.122 +  // Don't try to draw the -1 that's the sentinel for no hold piece
  51.123 +  if (piece < 0)
  51.124 +    return 0;
  51.125 +
  51.126 +  LJBits rows = 0;
  51.127 +  LJBlkSpec blocks[4];
  51.128 +
  51.129 +  expandPieceToBlocks(blocks, v->field, piece, 0, 0, theta);
  51.130 +  
  51.131 +  for (int blk = 0; blk < 4; ++blk) {
  51.132 +    int blkValue = blocks[blk].conn;
  51.133 +    if (blkValue) {
  51.134 +      int blkX = blocks[blk].x;
  51.135 +      int blkY = blocks[blk].y;
  51.136 +      const int dstX = x + w * blkX;
  51.137 +      const int dstY = y + h * (-1 - blkY);
  51.138 +    
  51.139 +      if (color == 0x80) {
  51.140 +        blkValue = 0x8001;  // garbage hold
  51.141 +      } else if (color != 0) {
  51.142 +        blkValue = ((blkValue & 0xF0) << 8) | 1;
  51.143 +      } else if (color == 0) {
  51.144 +        if (v->hideShadow == LJSHADOW_COLORED) {
  51.145 +          blkValue = ((blkValue & 0xF0) << 8) | 2;
  51.146 +        } else {
  51.147 +          blkValue = 0x0002;
  51.148 +        }
  51.149 +      }
  51.150 +    
  51.151 +      if (dstY > -8 && dstY < 160) {
  51.152 +        --nSprites;
  51.153 +        OAM[nSprites].attr0 = dstY & 0x00FF;
  51.154 +        OAM[nSprites].attr1 = dstX & 0x01FF;
  51.155 +        OAM[nSprites].attr2 = blkValue;
  51.156 +      }
  51.157 +
  51.158 +      rows |= 1 << blkY;
  51.159 +    }
  51.160 +  }
  51.161 +
  51.162 +  return rows;
  51.163 +}
  51.164 +
  51.165 +extern unsigned char prefs[OPTIONS_MENU_LEN];
  51.166 +
  51.167 +#include "gbamenus.h"
  51.168 +
  51.169 +int main(void) {
  51.170 +  LJField p = {
  51.171 +    .leftWall = 1,
  51.172 +    .rightWall = 11,
  51.173 +    .ceiling = 20
  51.174 +  };
  51.175 +  LJControl control = {
  51.176 +    .dasSpeed = 1,
  51.177 +    .dasDelay = 10,
  51.178 +    .initialDAS = 1,
  51.179 +    .allowDiagonals = 0,
  51.180 +    .softDropSpeed = 0,
  51.181 +    .softDropLock = 0,
  51.182 +    .hardDropLock = 1
  51.183 +  };
  51.184 +  struct LJPCView platView;
  51.185 +  LJView mainView = {
  51.186 +    .field = &p,
  51.187 +    .control = &control,
  51.188 +    .smoothGravity = 1,
  51.189 +    .plat = &platView,
  51.190 +    .backDirty = ~0
  51.191 +  };
  51.192 +
  51.193 +  videoSetMode(MODE_0_2D);
  51.194 +  BG_PALETTE[0] = RGB5(31, 0, 0);
  51.195 +  install_timer();
  51.196 +  openWindow();
  51.197 +  install_sound(&platView);
  51.198 +  initOptions(customPrefs);
  51.199 +  coprNotice();
  51.200 +  
  51.201 +  while (1) {
  51.202 +    LJView *const players[1] = {&mainView};
  51.203 +    REG_DISPCNT = MODE_0 | BG0_ON;
  51.204 +    BG_PALETTE[0] = RGB5(31, 31, 31);
  51.205 +    setupPalette(srsColors);
  51.206 +    options(&mainView, customPrefs);
  51.207 +    unpackCommonOptions(&mainView, customPrefs);
  51.208 +
  51.209 +    p.seed = curTime ^ (curTime << 16);
  51.210 +    play(players, 1);
  51.211 +    BG_PALETTE[0] = (control.countdown > 0)
  51.212 +                    ? RGB5(31, 15, 15) 
  51.213 +                    : RGB5(15, 31, 15);
  51.214 +    if (control.countdown > 0) {
  51.215 +      gba_play_sound(&platView, 10);
  51.216 +      gba_play_sound(&platView, 11);
  51.217 +    } else {
  51.218 +      gba_play_sound(&platView, 12);
  51.219 +      gba_play_sound(&platView, 13);
  51.220 +    }
  51.221 +    for (int i = 0; i < 60; ++i) {
  51.222 +      vsync();
  51.223 +      gba_poll_sound(&platView);
  51.224 +    }
  51.225 +#if 1
  51.226 +    debrief(&mainView);
  51.227 +#else
  51.228 +    textout(" Press  ", (SCREEN_W - 8) / 2, DS_PFTOP + 8, 0);
  51.229 +    textout("  Start ", (SCREEN_W - 8) / 2, DS_PFTOP + 9, 0);
  51.230 +    waitForStart();
  51.231 +#endif
  51.232 +  }
  51.233 +}
    52.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    52.2 +++ b/src/ljgba.h	Fri Mar 13 00:39:12 2009 -0700
    52.3 @@ -0,0 +1,51 @@
    52.4 +/* GBA frontend for LOCKJAW, an implementation of the Soviet Mind Game
    52.5 +
    52.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    52.7 +
    52.8 +This work is free software; you can redistribute it and/or modify
    52.9 +it under the terms of the GNU General Public License as published by
   52.10 +the Free Software Foundation; either version 2 of the License, or
   52.11 +(at your option) any later version.
   52.12 +
   52.13 +This program is distributed in the hope that it will be useful,
   52.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   52.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   52.16 +GNU General Public License for more details.
   52.17 +
   52.18 +You should have received a copy of the GNU General Public License
   52.19 +along with this program; if not, write to the Free Software
   52.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   52.21 +
   52.22 +Original game concept and design by Alexey Pajitnov.
   52.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   52.24 +or The Tetris Company LLC.
   52.25 +
   52.26 +*/
   52.27 +
   52.28 +#ifndef LJGBA_H
   52.29 +#define LJGBA_H
   52.30 +
   52.31 +#include <gba.h>
   52.32 +#include <stdlib.h>
   52.33 +#include "ljcontrol.h"
   52.34 +
   52.35 +struct LJPCView {
   52.36 +  const u16 *sndData[4];
   52.37 +  u8 sndLeft[4];
   52.38 +};
   52.39 +
   52.40 +void textout(const char *str, int x, int y, int c);
   52.41 +void isr(void);
   52.42 +void cls(void);
   52.43 +void vsync(void);
   52.44 +LJBits readPad(unsigned int player);
   52.45 +extern volatile int curTime;
   52.46 +
   52.47 +void install_sound(struct LJPCView *v);
   52.48 +void gba_poll_sound(struct LJPCView *v);
   52.49 +void gba_play_sound(struct LJPCView *v, int effect);
   52.50 +
   52.51 +extern unsigned char customPrefs[];
   52.52 +void options(LJView *view, unsigned char *prefs);
   52.53 +void debrief(const LJView *v);
   52.54 +#endif
    53.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    53.2 +++ b/src/ljgbads.inc	Fri Mar 13 00:39:12 2009 -0700
    53.3 @@ -0,0 +1,579 @@
    53.4 +/* DS/GBA frontend for LOCKJAW, an implementation of the Soviet Mind Game
    53.5 +
    53.6 +Copyright (C) 2006-2007 Damian Yerrick <tepples+lj@spamcop.net>
    53.7 +
    53.8 +This work is free software; you can redistribute it and/or modify
    53.9 +it under the terms of the GNU General Public License as published by
   53.10 +the Free Software Foundation; either version 2 of the License, or
   53.11 +(at your option) any later version.
   53.12 +
   53.13 +This program is distributed in the hope that it will be useful,
   53.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   53.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   53.16 +GNU General Public License for more details.
   53.17 +
   53.18 +You should have received a copy of the GNU General Public License
   53.19 +along with this program; if not, write to the Free Software
   53.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   53.21 +
   53.22 +Original game concept and design by Alexey Pajitnov.
   53.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   53.24 +or The Tetris Company LLC.
   53.25 +
   53.26 +*/
   53.27 +
   53.28 +#include "fontdraw.h"
   53.29 +
   53.30 +unsigned char wantPause;
   53.31 +volatile char redrawWholeScreen = 0;
   53.32 +
   53.33 +void finishSprites();
   53.34 +
   53.35 +void cls(void) {
   53.36 +  for (int y = 0; y < SCREEN_H; ++y) {
   53.37 +    for (int x = 0; x < SCREEN_W; ++x) {
   53.38 +      MAP[31][y][x] = ' ';
   53.39 +    }
   53.40 +  }
   53.41 +}
   53.42 +
   53.43 +void drawFrame(LJView *v) {
   53.44 +  int left = v->field->leftWall;
   53.45 +  int right = v->field->rightWall;  
   53.46 +  cls();
   53.47 +  if (DS_PFTOP > 0) {
   53.48 +    for (int x = left - 1; x < right + 1; ++x) {
   53.49 +      MAP[31][DS_PFTOP - 1][DS_PFLEFT + x] = 0x8005;
   53.50 +    }
   53.51 +  }
   53.52 +  if (DS_PFTOP + LJ_PF_VIS_HT < SCREEN_H) {
   53.53 +    for (int x = left - 1; x < right + 1; ++x) {
   53.54 +      MAP[31][DS_PFTOP + LJ_PF_VIS_HT][DS_PFLEFT + x] = 0x8004;
   53.55 +    }
   53.56 +  }
   53.57 +  for (int i = 0; i < LJ_PF_VIS_HT; ++i) {
   53.58 +    MAP[31][i + DS_PFTOP][DS_PFLEFT + left - 1] = 0x8003;
   53.59 +    MAP[31][i + DS_PFTOP][DS_PFLEFT + right] = 0x8003;
   53.60 +  }
   53.61 +  textout("Score", 1, 6 + DS_PFTOP, 0);
   53.62 +  textout("Lines", 1, 8 + DS_PFTOP, 0);
   53.63 +  textout("Speed", 1, 10 + DS_PFTOP, 0);
   53.64 +}
   53.65 +
   53.66 +static void drawBlock(struct LJPCView *unused, int x, int y, int b) {
   53.67 +  if (x >= 0 && x < LJ_PF_WID && y >= 0 && y < LJ_PF_VIS_HT) {
   53.68 +    int c;
   53.69 +    
   53.70 +    if (b == 0x100) {
   53.71 +      c = '-';
   53.72 +    } else if (b == 0x110) {
   53.73 +      c = 0x8005;  // ceiling tile
   53.74 +    } else if (b >= 0xA0 && b < 0xC0) {
   53.75 +      c = (b & 0xF0) << 8 | (b & 0x0F) | 0x100;
   53.76 +    } else if (b >= 0x10) {
   53.77 +      c = (b & 0xF0) << 8 | (b & 0x0F) | 0x10;
   53.78 +    } else {
   53.79 +      c = ' ';
   53.80 +    }
   53.81 +    MAP[31][DS_PFTOP + LJ_PF_VIS_HT - 1 - y][DS_PFLEFT + x] = c;
   53.82 +  }
   53.83 +}
   53.84 +
   53.85 +void updField(const LJView *const v, LJBits rows) {
   53.86 +  const LJField *const p = v->field;
   53.87 +
   53.88 +  for (int y = 0;
   53.89 +       y < LJ_PF_VIS_HT && rows != 0;
   53.90 +       ++y, rows >>= 1) {
   53.91 +    int blankTile = 0;
   53.92 +    
   53.93 +    if (y == p->ceiling) {
   53.94 +      blankTile = 0x110;
   53.95 +    } else if (hotlineRows[y] && v->field->scoreStyle == LJSCORE_HOTLINE) {
   53.96 +      blankTile = 0x100;
   53.97 +    }
   53.98 +    if (p->state == LJS_LINES_FALLING && p->stateTime > 0
   53.99 +        && ((1 << y) & p->tempRows)) {
  53.100 +      blankTile = 0x100;
  53.101 +    }
  53.102 +    if (rows & 1) {
  53.103 +      for (int x = p->leftWall; x < p->rightWall; x++) {
  53.104 +        int b = v->hidePF ? 0 : p->b[y][x];
  53.105 +        drawBlock(v->plat, x, y, b ? b : blankTile);
  53.106 +      }
  53.107 +    }
  53.108 +  }
  53.109 +}
  53.110 +
  53.111 +void blitField(LJView *v) {
  53.112 +
  53.113 +}
  53.114 +
  53.115 +int getTime() {
  53.116 +  return curTime;
  53.117 +}
  53.118 +
  53.119 +#if !defined(DISP_VBLANK_IRQ)
  53.120 +#define DISP_VBLANK_IRQ LCDC_VBL
  53.121 +#endif
  53.122 +#if !defined(IRQ_HANDLER)
  53.123 +#define IRQ_HANDLER INT_VECTOR
  53.124 +#endif
  53.125 +
  53.126 +void install_timer(void)
  53.127 +{
  53.128 +
  53.129 +  // Turn off interrupts before doing anything
  53.130 +  REG_IME = 0;
  53.131 +
  53.132 +  // Overwrite the ISR
  53.133 +  IRQ_HANDLER = isr;
  53.134 +
  53.135 +  // Hook up the interrupt destination
  53.136 +  REG_IE = IRQ_VBLANK;
  53.137 +
  53.138 +  // Acknowledge all pending interrupts
  53.139 +  REG_IF = ~0;
  53.140 +
  53.141 +  // Set up an interrupt source
  53.142 +  REG_DISPSTAT = DISP_VBLANK_IRQ;
  53.143 +
  53.144 +  // Turn interrupts back on
  53.145 +  REG_IME = 1;
  53.146 +}
  53.147 +
  53.148 +void yieldCPU(void) {
  53.149 +  // we're not multitasking so we don't need this
  53.150 +  // on the GBA and DS, vsync() does all the waiting we need
  53.151 +}
  53.152 +
  53.153 +static void upcvt_4bit(void *dst, const u8 *src, size_t len)
  53.154 +{
  53.155 +  u32 *out = dst;
  53.156 +
  53.157 +  for(; len > 0; len--)
  53.158 +  {
  53.159 +    u32 dst_bits = 0;
  53.160 +    u32 src_bits = *src++;
  53.161 +    u32 x;
  53.162 +
  53.163 +    for(x = 0; x < 8; x++)
  53.164 +    {
  53.165 +      dst_bits <<= 4;
  53.166 +      dst_bits |= src_bits & 1;
  53.167 +      src_bits >>= 1;
  53.168 +    }
  53.169 +    *out++ = dst_bits;
  53.170 +  }
  53.171 +}
  53.172 +
  53.173 +extern const unsigned char text_chr[];
  53.174 +extern const unsigned int text_chr_size;
  53.175 +extern const unsigned char gbablk_chr[];
  53.176 +extern const unsigned int gbablk_chr_size;
  53.177 +
  53.178 +static void loadOneConnection(void *in_dst, const void *in_src) {
  53.179 +  u16 *dst = in_dst;
  53.180 +  const u16 *src = in_src;
  53.181 +  for (unsigned int conn = 0; conn < 16; ++conn) {
  53.182 +    unsigned int topSegY = (conn & CONNECT_U) ? 32 : 0;
  53.183 +    unsigned int botSegY = (conn & CONNECT_D) ? 8 : 40;
  53.184 +    unsigned int leftSegX = (conn & CONNECT_L) ? 16 : 0;
  53.185 +    unsigned int rightSegX = (conn & CONNECT_R) ? 1 : 17;
  53.186 +    for (unsigned int i = 0; i < 8; i += 2) {
  53.187 +      *dst++ = src[leftSegX + topSegY + i];
  53.188 +      *dst++ = src[rightSegX + topSegY + i];
  53.189 +    }
  53.190 +    for (unsigned int i = 0; i < 8; i += 2) {
  53.191 +      *dst++ = src[leftSegX + botSegY + i];
  53.192 +      *dst++ = src[rightSegX + botSegY + i];
  53.193 +    }
  53.194 +  }
  53.195 +}
  53.196 +
  53.197 +static void loadConnections(void) {
  53.198 +  loadOneConnection(PATRAM4(0, 16), gbablk_chr + 8*32);
  53.199 +  loadOneConnection(PATRAM4(0, 256), gbablk_chr + 12*32);
  53.200 +}
  53.201 +
  53.202 +static void load_font(void) {
  53.203 +  upcvt_4bit(PATRAM4(0, 0), text_chr, text_chr_size);
  53.204 +  memcpy(PATRAM4(0, 0), gbablk_chr, 8*32);
  53.205 +  memcpy(SPR_VRAM(0), gbablk_chr, 8*32);
  53.206 +  loadConnections();
  53.207 +}
  53.208 +
  53.209 +void textout(const char *str, int x, int y, int c) {
  53.210 +  u16 *dst = &(MAP[31][y][x]);
  53.211 +  int spacesLeft = SCREEN_W - x;
  53.212 +
  53.213 +  c <<= 12;
  53.214 +  while (*str != 0 && spacesLeft > 0) {
  53.215 +    *dst++ = c | *(unsigned char *)str++;
  53.216 +    --spacesLeft;
  53.217 +  }
  53.218 +}
  53.219 +
  53.220 +static const u16 srsColors[12] = {
  53.221 +  RGB5(2, 2, 2),
  53.222 +  RGB5(0, 3, 3),
  53.223 +  RGB5(0, 0, 3),
  53.224 +  RGB5(3, 2, 0),
  53.225 +  RGB5(3, 3, 0),
  53.226 +  RGB5(0, 3, 0),
  53.227 +  RGB5(2, 0, 3),
  53.228 +  RGB5(3, 0, 0),
  53.229 +  RGB5(2, 2, 2),
  53.230 +  RGB5(3, 0, 0),
  53.231 +  RGB5(2, 2, 2),
  53.232 +  RGB5(3, 2, 1),
  53.233 +};
  53.234 +
  53.235 +static const u16 arsColors[12] = {
  53.236 +  RGB5(2, 2, 2),
  53.237 +  RGB5(3, 1, 0),
  53.238 +  RGB5(0, 0, 3),
  53.239 +  RGB5(3, 2, 0),
  53.240 +  RGB5(3, 3, 0),
  53.241 +  RGB5(2, 0, 3),
  53.242 +  RGB5(0, 3, 3),
  53.243 +  RGB5(0, 3, 0),
  53.244 +  RGB5(2, 2, 2),
  53.245 +  RGB5(3, 0, 0),
  53.246 +  RGB5(2, 2, 2),
  53.247 +  RGB5(3, 2, 1),
  53.248 +};
  53.249 +
  53.250 +void setupPalette(const u16 *colors) {
  53.251 +  for (int i = 1; i < 12; ++i) {
  53.252 +    int c = colors[i];
  53.253 +    
  53.254 +    BG_PALETTE[i * 16 + 1] = RGB5(22,22,22) + 3 * c;
  53.255 +    BG_PALETTE[i * 16 + 2] = RGB5(13,13,13) + 6 * c;
  53.256 +    BG_PALETTE[i * 16 + 3] = RGB5( 4, 4, 4) + 9 * c;
  53.257 +    BG_PALETTE[i * 16 + 4] = RGB5( 4, 4, 4) + 7 * c;
  53.258 +    BG_PALETTE[i * 16 + 5] = RGB5( 4, 4, 4) + 5 * c;
  53.259 +    BG_PALETTE[i * 16 + 6] = RGB5( 4, 4, 4) + 3 * c;
  53.260 +  }
  53.261 +  memcpy(SPRITE_PALETTE, BG_PALETTE, 12 * 32);
  53.262 +}
  53.263 +
  53.264 +// libnds style wrapper around libgba header
  53.265 +#ifndef DISPLAY_BG0_ACTIVE
  53.266 +#define DISPLAY_BG0_ACTIVE BG0_ON
  53.267 +#define DISPLAY_SPR_ACTIVE OBJ_ON
  53.268 +#define MODE_0_2D MODE_0
  53.269 +#define DISPLAY_SPR_1D_LAYOUT OBJ_1D_MAP
  53.270 +static inline void videoSetMode(int x) {
  53.271 +  REG_DISPCNT = x;
  53.272 +}
  53.273 +
  53.274 +#endif
  53.275 +
  53.276 +void waitForStart(void) {
  53.277 +  LJBits lastJ = ~0;
  53.278 +  LJBits jnew = 0;
  53.279 +
  53.280 +  do {
  53.281 +    LJBits j = ~REG_KEYINPUT;
  53.282 +    jnew = j & ~lastJ;
  53.283 +    lastJ = j;
  53.284 +    vsync();
  53.285 +  } while(!(jnew & (KEY_A | KEY_START)));
  53.286 +}
  53.287 +
  53.288 +
  53.289 +static const char *const coprNoticeLines[] = {
  53.290 +  "LOCKJAW: The Reference",
  53.291 +  "Version "LJ_VERSION,
  53.292 +  NULL,
  53.293 +  "© 2008 Damian Yerrick",
  53.294 +  "Not sponsored or endorsed by Nintendo",
  53.295 +  "or Tetris Holding.",
  53.296 +  "Comes with ABSOLUTELY NO WARRANTY.",
  53.297 +  "This is free software, and you are welcome",
  53.298 +  "to share it under the conditions described",
  53.299 +  "in GPL.txt."
  53.300 +};
  53.301 +
  53.302 +void coprNotice(void) {
  53.303 +  videoSetMode(MODE_0_2D);
  53.304 +  BGCTRL[0] = BG_TILE_BASE(0) | BG_MAP_BASE(31);
  53.305 +  BG_OFFSET[0].x = 0;
  53.306 +  BG_OFFSET[0].y = 0;
  53.307 +
  53.308 +  BG_PALETTE[0] = RGB5(31,31,31);
  53.309 +  BG_PALETTE[1] = RGB5(20,20,20);
  53.310 +  BG_PALETTE[2] = RGB5( 0, 0, 0);
  53.311 +  vwfWinInit(&vwfTop);
  53.312 +  for (int i = 0, y = 8;
  53.313 +       i < sizeof(coprNoticeLines) / sizeof(coprNoticeLines[0]);
  53.314 +       ++i) {
  53.315 +    if (coprNoticeLines[i]) {
  53.316 +      vwfPuts(&vwfTop, coprNoticeLines[i], 8,  y);
  53.317 +      y += 12;
  53.318 +    } else {
  53.319 +      y += 6;
  53.320 +    }
  53.321 +  }
  53.322 +  vwfPuts(&vwfTop, "Press Start", 8, SCREEN_H * 8 - 16);
  53.323 +  videoSetMode(MODE_0_2D | DISPLAY_BG0_ACTIVE);
  53.324 +  waitForStart();
  53.325 +}
  53.326 +
  53.327 +LJBits menuReadPad(void) {
  53.328 +  LJBits keys = readPad(0);
  53.329 +  if (keys & VKEY_START) {
  53.330 +    keys |= VKEY_ROTR;
  53.331 +  }
  53.332 +  return keys;
  53.333 +}
  53.334 +
  53.335 +void ljBeginDraw(LJView *v, int sync) {
  53.336 +  vsync();
  53.337 +  finishSprites();
  53.338 +}
  53.339 +
  53.340 +void ljEndDraw(LJView *v) {
  53.341 +
  53.342 +}
  53.343 +
  53.344 +/* Replay stubs */
  53.345 +void replayRecord(struct LJReplay *r, LJBits keys, const LJInput *in) {
  53.346 +
  53.347 +}
  53.348 +
  53.349 +void replayClose(struct LJReplay *r) {
  53.350 +
  53.351 +}
  53.352 +
  53.353 +int getReplayFrame(struct LJReplay *r, LJInput *d) {
  53.354 +  return 0;
  53.355 +}
  53.356 +
  53.357 +#define READY_GO_LINE 13
  53.358 +
  53.359 +void startingAnimation(LJView *v) {
  53.360 +  vsync();
  53.361 +  gba_poll_sound(v->plat);
  53.362 +  setupPalette(rotSystems[v->field->rotationSystem]->colorScheme ? arsColors : srsColors);
  53.363 +  videoSetMode(MODE_0_2D);
  53.364 +  cls();
  53.365 +  load_font();
  53.366 +  BG_PALETTE[0] = RGB5(31,31,31);
  53.367 +  BG_PALETTE[1] = RGB5( 0, 0, 0);
  53.368 +  drawFrame(v);
  53.369 +  finishSprites();
  53.370 +  vsync();
  53.371 +  gba_poll_sound(v->plat);
  53.372 +  videoSetMode(MODE_0_2D
  53.373 +               | DISPLAY_BG0_ACTIVE);
  53.374 +  BGCTRL[0] = BG_TILE_BASE(0) | BG_MAP_BASE(31);
  53.375 +  BG_OFFSET[0].x = 0;
  53.376 +  BG_OFFSET[0].y = 0;
  53.377 +
  53.378 +  textout("Ready",
  53.379 +          (LJ_PF_WID - 5) / 2 + DS_PFLEFT,
  53.380 +          DS_PFTOP + LJ_PF_VIS_HT - 1 - READY_GO_LINE,
  53.381 +          0);
  53.382 +  for (int i = 0; i < 30; ++i) {
  53.383 +    vsync();
  53.384 +    gba_poll_sound(v->plat);
  53.385 +  }
  53.386 +  v->backDirty = ~0;
  53.387 +  updField(v, ~0);
  53.388 +  videoSetMode(MODE_0_2D
  53.389 +               | DISPLAY_BG0_ACTIVE
  53.390 +               | DISPLAY_SPR_1D_LAYOUT 
  53.391 +               | DISPLAY_SPR_ACTIVE);
  53.392 +  drawScore(v);
  53.393 +  finishSprites();
  53.394 +  
  53.395 +  textout(" GO! ",
  53.396 +          (LJ_PF_WID - 5) / 2 + DS_PFLEFT,
  53.397 +          DS_PFTOP + LJ_PF_VIS_HT - 1 - READY_GO_LINE,
  53.398 +          0);
  53.399 +  for (int i = 0; i < 30; ++i) {
  53.400 +    vsync();
  53.401 +    gba_poll_sound(v->plat);
  53.402 +  }
  53.403 +  drawFrame(v);
  53.404 +  wantPause = 0;
  53.405 +  
  53.406 +#ifdef ARM9
  53.407 +  tb.cmd = TALKBACK_PLAY_MUSIC;
  53.408 +#endif
  53.409 +}
  53.410 +
  53.411 +int pauseGame(struct LJPCView *v) {
  53.412 +  LJBits lastKeys = ~0;
  53.413 +  int unpaused = 0;
  53.414 +  int canceled = 0;
  53.415 +  
  53.416 +  // hide playfield
  53.417 +  for (int y = DS_PFTOP; y < DS_PFTOP + LJ_PF_VIS_HT; ++y) {
  53.418 +    for (int x = DS_PFLEFT;
  53.419 +         x < DS_PFLEFT + LJ_PF_WID;
  53.420 +         ++x) {
  53.421 +      MAP[31][y][x] = ' ';
  53.422 +    }
  53.423 +  }
  53.424 +  textout("Game", 2 + DS_PFLEFT, 6 + DS_PFTOP, 0);
  53.425 +  textout("Paused", 2 + DS_PFLEFT, 7 + DS_PFTOP, 0);
  53.426 +  textout("Start:", 2 + DS_PFLEFT, 10 + DS_PFTOP, 0);
  53.427 +  textout("Resume", 2 + DS_PFLEFT, 11 + DS_PFTOP, 0);
  53.428 +  textout("Select:", 2 + DS_PFLEFT, 13 + DS_PFTOP, 0);
  53.429 +  textout("Exit", 2 + DS_PFLEFT, 14 + DS_PFTOP, 0);
  53.430 +
  53.431 +#ifdef ARM9
  53.432 +  tb.cmd = TALKBACK_PAUSE_MUSIC;
  53.433 +#endif
  53.434 +  while (!unpaused || (lastKeys & (KEY_SELECT | KEY_START))) {
  53.435 +    int keys = ~REG_KEYINPUT;
  53.436 +    if (keys & ~lastKeys & KEY_START) {
  53.437 +      unpaused = 1;
  53.438 +    }
  53.439 +    if (keys & ~lastKeys & KEY_SELECT) {
  53.440 +      unpaused = 1;
  53.441 +      canceled = 1;
  53.442 +    }
  53.443 +    finishSprites();
  53.444 +    vsync();
  53.445 +    gba_poll_sound(v);
  53.446 +    lastKeys = keys;
  53.447 +  }
  53.448 +#ifdef ARM9
  53.449 +  tb.cmd = TALKBACK_PLAY_MUSIC;
  53.450 +#endif
  53.451 +  return canceled;
  53.452 +}
  53.453 +
  53.454 +int ljHandleConsoleButtons(LJView *v) {
  53.455 +  LJBits keys = ~REG_KEYINPUT;
  53.456 +  int canceled = 0;
  53.457 +
  53.458 +  wantPause |= !!(keys & KEY_START);
  53.459 +  if (wantPause) {
  53.460 +    canceled = pauseGame(v->plat);
  53.461 +    wantPause = 0;
  53.462 +    drawFrame(v);
  53.463 +    v->backDirty = ~0;
  53.464 +  }
  53.465 +  return canceled;
  53.466 +}
  53.467 +
  53.468 +LJBits drawPiece(LJView *const v, void *const b,
  53.469 +                 int piece, int x, int y, int theta,
  53.470 +                 int color, int w, int h);
  53.471 +
  53.472 +void drawFallingPiece(LJView *v) {
  53.473 +  LJBits bits = 0;
  53.474 +  const LJField *const p = v->field;
  53.475 +  int piece = p->curPiece[0];
  53.476 +  int y = ljfixfloor(p->y);
  53.477 +  const int w = 8;
  53.478 +  const int h = 8;
  53.479 +  int drawnY = v->smoothGravity ? ljfixfloor(h * p->y) : h * y;
  53.480 +  const int color = (p->state == LJS_LANDED)
  53.481 +                    ? -128 - ((p->stateTime + 1) * 128 / (p->speed.lockDelay + 1))
  53.482 +                    : pieceColors[piece];
  53.483 +
  53.484 +  bits = drawPiece(v, NULL, piece,
  53.485 +                   w * (p->x + DS_PFLEFT),
  53.486 +                   h * (LJ_PF_VIS_HT + DS_PFTOP) - drawnY,
  53.487 +                   p->theta,
  53.488 +                   color, w, h);
  53.489 +  bits = (y >= 0) ? bits << y : bits >> -y;
  53.490 +  bits &= (1 << LJ_PF_VIS_HT) - 1;
  53.491 +
  53.492 +  v->backDirty |= bits | (bits << 1);
  53.493 +  v->frontDirty |= bits | (bits << 1);
  53.494 +}
  53.495 +
  53.496 +#define SHADOW_BLOCK 0x00
  53.497 +
  53.498 +void drawShadow(LJView *v) {
  53.499 +  LJBits bits = 0;
  53.500 +  const LJField *const p = v->field;
  53.501 +  int piece = p->curPiece[0];
  53.502 +  int y = p->hardDropY;
  53.503 +  const int w = 8;
  53.504 +  const int h = 8;
  53.505 +
  53.506 +  bits = drawPiece(v, NULL, piece,
  53.507 +                   w * (p->x + DS_PFLEFT),
  53.508 +                   h * (LJ_PF_VIS_HT + DS_PFTOP - y),
  53.509 +                   p->theta,
  53.510 +                   SHADOW_BLOCK, w, h);
  53.511 +  bits = (y >= 0) ? bits << y : bits >> -y;
  53.512 +  bits &= (1 << LJ_PF_VIS_HT) - 1;
  53.513 +
  53.514 +  v->backDirty |= bits;
  53.515 +  v->frontDirty |= bits;
  53.516 +}
  53.517 +
  53.518 +void drawNextPieces(LJView *v) {
  53.519 +  int holdPieceColor = v->field->alreadyHeld
  53.520 +                       ? 0x80
  53.521 +                       : pieceColors[v->field->holdPiece];
  53.522 +
  53.523 +  // Draw hold piece
  53.524 +  drawPiece(v, NULL,
  53.525 +            v->field->holdPiece,
  53.526 +            (DS_PFLEFT - 5) * 8, (DS_PFTOP + 5) * 8, 4,
  53.527 +            holdPieceColor, 8, 8);
  53.528 +
  53.529 +  // Draw next pieces
  53.530 +  int y = 32 + 8 * DS_PFTOP;
  53.531 +  int x = (DS_PFLEFT + LJ_PF_WID) * 8;
  53.532 +  for(int i = 1; i <= v->nextPieces; ++i) {
  53.533 +    int piece = v->field->curPiece[i];
  53.534 +
  53.535 +    if (!v->hideNext) {
  53.536 +      drawPiece(v, NULL,
  53.537 +                piece, x, y, 4,
  53.538 +                pieceColors[piece], 8, 8);
  53.539 +    }
  53.540 +    y += 20;
  53.541 +  }
  53.542 +  v->frontDirty &= (1 << LJ_PF_VIS_HT) - 1;
  53.543 +}
  53.544 +
  53.545 +void drawScore(LJView *v) {
  53.546 +  char txt[16];
  53.547 +  int tpm = -1;
  53.548 +  int lvl = v->field->speedState.level;
  53.549 +
  53.550 +  siprintf(txt, "%8u", v->field->score);
  53.551 +  textout(txt, 0, 7 + DS_PFTOP, 0);
  53.552 +  siprintf(txt, "%8u", v->field->lines);
  53.553 +  textout(txt, 0, 9 + DS_PFTOP, 0);
  53.554 +
  53.555 +  if (lvl > 0) {
  53.556 +    textout("Level:", 1, SCREEN_H - 3, 0);
  53.557 +    siprintf(txt, "%9u", lvl);
  53.558 +    textout(txt, 0, SCREEN_H - 2, 0);
  53.559 +  }
  53.560 +
  53.561 +  if (v->nLockTimes >= 2) {
  53.562 +    int time = v->lockTime[0] - v->lockTime[v->nLockTimes - 1];
  53.563 +    if (time > 0) {
  53.564 +      tpm = 3600 * (v->nLockTimes - 1) / time;
  53.565 +    }
  53.566 +  }
  53.567 +  if (tpm > 0) {
  53.568 +    siprintf(txt, "%8d", tpm);
  53.569 +    textout(txt, 0, 11 + DS_PFTOP, 0);
  53.570 +  } else {
  53.571 +    textout("     ---", 0, 11 + DS_PFTOP, 0);
  53.572 +  }
  53.573 +
  53.574 +  {
  53.575 +    int seconds = v->field->gameTime / 60;
  53.576 +    int minutes = seconds / 60;
  53.577 +    seconds -= minutes * 60;
  53.578 +    siprintf(txt, "%6d:%02d", minutes, seconds);
  53.579 +    textout(txt, 0, SCREEN_H - 1, 0);
  53.580 +  }
  53.581 +  drawNextPieces(v);
  53.582 +}
    54.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    54.2 +++ b/src/ljlocale.c	Fri Mar 13 00:39:12 2009 -0700
    54.3 @@ -0,0 +1,299 @@
    54.4 +#include "ljlocale.h"
    54.5 +#include <stdlib.h>
    54.6 +#include <string.h>
    54.7 +
    54.8 +
    54.9 +static const FourCCLocaleEntry *curLocale = englishLocale;
   54.10 +
   54.11 +void ljSetLocale(const FourCCLocaleEntry *locale) {
   54.12 +  if (locale) {
   54.13 +    curLocale = locale;
   54.14 +  }
   54.15 +}
   54.16 +
   54.17 +static const FourCCLocaleEntry *ljGetFourCCEntry(unsigned int i) {
   54.18 +  const FourCCLocaleEntry *base = curLocale;
   54.19 +
   54.20 +  for(; base->code.i != 0; ++base) {
   54.21 +    if (base->code.i == i) {
   54.22 +      return base;
   54.23 +    }
   54.24 +  }
   54.25 +  return NULL;
   54.26 +}
   54.27 +
   54.28 +const char *ljGetFourCCName(FourCC i) {
   54.29 +  const FourCCLocaleEntry *ent = ljGetFourCCEntry(i.i);
   54.30 +  return ent ? ent->name : NULL;
   54.31 +}
   54.32 +
   54.33 +const char *ljGetFourCCDesc(FourCC i) {
   54.34 +  const FourCCLocaleEntry *ent = ljGetFourCCEntry(i.i);
   54.35 +  return ent ? ent->desc : NULL;
   54.36 +}
   54.37 +
   54.38 +const FourCCLocaleEntry englishLocale[] = {
   54.39 +  /* Page names */
   54.40 +  /* omitted */
   54.41 +
   54.42 +  /* Option names */
   54.43 +  {{"gimm"}, "Gimmick",
   54.44 +             "Goal or other game mode" },
   54.45 +  {{"pfw"},  "Well width",
   54.46 +             "Amount of space to build in" },
   54.47 +  {{"pfh"},  "Well height",
   54.48 +             "Amount of space to build in" },
   54.49 +  {{"vzsp"}, "Enter above ceiling",
   54.50 +             "Pieces start below or above top of well" },
   54.51 +  {{"spdc"}, "Speed curve",
   54.52 +             "Controls speed of falling piece and delays" },
   54.53 +  {{"are"},  "Max entry delay",
   54.54 +             "Time from lockdown to next piece entry" },
   54.55 +  {{"piec"}, "Piece set",
   54.56 +             "Which pieces will be used" },
   54.57 +  {{"rand"}, "Randomizer",
   54.58 +             "How the pieces are shuffled" },
   54.59 +  {{"hold"}, "Hold piece",
   54.60 +             "Press Hold to save a piece for later" },
   54.61 +  {{"rots"}, "Rotation system",
   54.62 +             "How pieces turn and react to walls" },
   54.63 +  {{"upkl"}, "Floor kicks",
   54.64 +             "Times each piece can rotate upward" },
   54.65 +  {{"srk"},  "Sub-row kicks",
   54.66 +             "" },
   54.67 +  {{"lock"}, "Lockdown",
   54.68 +             "Start of time when a piece slides around" },
   54.69 +  {{"sldt"}, "Lock delay",
   54.70 +             "Amount of time a piece can slide around" },
   54.71 +  {{"deep"}, "Deep drop",
   54.72 +             "Hard drop past blocks in the well" },
   54.73 +  {{"clrd"}, "Line clear delay",
   54.74 +             "Time for cleared lines to disappear" },
   54.75 +  {{"grav"}, "Clear gravity",
   54.76 +             "How blocks above a cleared line fall" },
   54.77 +  {{"glue"}, "Gluing",
   54.78 +             "How blocks stick together" },
   54.79 +  {{"lsco"}, "Line scoring",
   54.80 +             "Points awarded for lines" },
   54.81 +  {{"dsco"}, "Drop scoring",
   54.82 +             "Points awarded for fast play (Up/Down)" },
   54.83 +  {{"tspn"}, "T-spin detection",
   54.84 +             "Detect turning a piece into a tight space" },
   54.85 +  {{"garb"}, "Garbage",
   54.86 +             "Blocks added to the bottom of the well" },
   54.87 +  {{"dasd"}, "Max sideways delay",
   54.88 +             "Hold left/right this long to move fast" },
   54.89 +  {{"dass"}, "Sideways speed",
   54.90 +             "How fast piece moves left/right" },
   54.91 +  {{"idas"}, "Initial sideways motion",
   54.92 +             "Allow shifting as a piece is coming out?" },
   54.93 +  {{"irs"},  "Initial rotation",
   54.94 +             "Hold rotate to turn each piece as it enters" },
   54.95 +  {{"8way"}, "Allow diagonal motion",
   54.96 +             "Turn off to prevent accidental hard drops" },
   54.97 +  {{"sfds"}, "Soft drop speed",
   54.98 +             "Hold down to drop the piece this fast" },
   54.99 +  {{"sfdl"}, "Soft drop",
  54.100 +             "Behavior when a piece lands with down" },
  54.101 +  {{"hrdl"}, "Hard drop",
  54.102 +             "Behavior when a piece lands with up" },
  54.103 +  {{"tls"},  "Shadow",
  54.104 +             "Shows where the piece will land" },
  54.105 +  {{"invs"}, "Hide blocks in well",
  54.106 +             "Invisible challenge tests your memory" },
  54.107 +  {{"next"}, "Next pieces",
  54.108 +             "Number of previewed pieces" },
  54.109 +  {{"srph"}, "Smooth gravity",
  54.110 +             "Turn off to make the piece fall row-by-row" },
  54.111 +  {{"inpv"}, "Next above shadow",
  54.112 +             "Number of previewed pieces inside the well" },
  54.113 +  {{"mblr"}, "Drop trails",
  54.114 +             "Makes hard drops look harder" },
  54.115 +  
  54.116 +  {{"lidp"}, "Pause on task switch",
  54.117 +             "Makes Alt+Tab or screensaver safe" },
  54.118 +  {{"rec"},  "Record all games",
  54.119 +             "Save each game's replay to demo.ljm" },
  54.120 +  {{"wndw"}, "Display mode",
  54.121 +             "Shut out distractions" },
  54.122 +
  54.123 +  /* Booleans */
  54.124 +  {{"Off"}, "Off" },
  54.125 +  {{"On"}, "On" },
  54.126 +  
  54.127 +  /* Gimmicks */
  54.128 +  {{"Mara"}, "Marathon",
  54.129 +             "Play until you top out."},
  54.130 +  {{"Line"}, "40 lines",
  54.131 +             "Game ends after you clear this many lines."},
  54.132 +  {{"Time"}, "180 seconds",
  54.133 +             "Game ends after this much time."},
  54.134 +  {{"DrlA"}, "Drill Attack",
  54.135 +             "Clear the bottom row of garbage to win."},
  54.136 +  {{"Item"}, "Vs. w/Items",
  54.137 +             "Three opponents send disabling attacks."},
  54.138 +  {{"Keys"}, "Baboo!",
  54.139 +             "What can you do with 300 keystrokes?"},
  54.140 +
  54.141 +  /* Garbage */
  54.142 +  {{"HRD"},  "Home Run Derby",
  54.143 +             "Clear 1, 2, or 3 lines and get garbage back"},
  54.144 +  {{"DrlG"}, "Drill",
  54.145 +             "Well is filled with random garbage"},
  54.146 +  {{"Zigz"}, "Zigzag",
  54.147 +             "Well is filled with zigzag garbage"},
  54.148 +
  54.149 +  /* Piece set */
  54.150 +  {{"AllP"}, "All pieces",
  54.151 +             "Pieces made of two, three, or four blocks"  },
  54.152 +  {{"IJLO"}, "Tetrominoes",
  54.153 +             "Pieces made of four blocks"},
  54.154 +  {{"JLOT"}, "Tetrominoes no I",
  54.155 +             "Four blocks, no straight pieces"},
  54.156 +  {{"SZSZ"}, "S and Z only" },
  54.157 +  {{"IIII"}, "iCheat(tm)",
  54.158 +             "All I (straight) tetrominoes" },
  54.159 +  {{"TTTT"}, "T-Party",
  54.160 +             "All T tetrominoes" },
  54.161 +  
  54.162 +  /* Randomizer */
  54.163 +  {{"Unif"}, "Memoryless",
  54.164 +             "Equal probability to deal any piece" },
  54.165 +  {{"Bag"},  "Bag",
  54.166 +             "Deals one of each piece before repeats"},
  54.167 +  {{"Bag+"}, "Bag + 1",
  54.168 +             "Adds one extra to each set of pieces"},
  54.169 +  {{"Bag2"}, "Double bag",
  54.170 +             "Deals two of each piece before repeats"},
  54.171 +  {{"Hist"}, "Strict history",
  54.172 +             "Does not deal a recent piece"},
  54.173 +  {{"His6"}, "History 6 rolls",
  54.174 +             "Rarely deals a recent piece"},
  54.175 +
  54.176 +  /* Speed curve */
  54.177 +  {{"Exp"},  "Exponential",
  54.178 +             "Game gradually gets faster"},
  54.179 +  {{"Rh20"}, "Rhythm 20G",
  54.180 +             "Slide pieces into place to the beat"},
  54.181 +  {{"Rhy0"}, "Rhythm 0G",
  54.182 +             "Drop pieces into place to the beat"},
  54.183 +  {{"TGM2"}, "Master" },
  54.184 +  {{"TAPD"}, "Death" },
  54.185 +  {{"TAD3"}, "Death 300+" },
  54.186 +  {{"NESc"}, "NES" },
  54.187 +  {{"GBc"},  "Game Boy" },
  54.188 +  {{"GBHL"}, "Game Boy Heart" },
  54.189 +
  54.190 +  /* Hold */
  54.191 +  {{"HldE"}, "On, empty",
  54.192 +             "First held piece brings out next"},
  54.193 +  {{"HldR"}, "On, random",
  54.194 +             "Random piece in hold box at start"},
  54.195 +  {{"HldN"}, "Hold to next",
  54.196 +             "Hold swaps falling and next (like Radica)"},
  54.197 +
  54.198 +  /* Rotation systems */
  54.199 +  {{"SRS"},  "SRS",
  54.200 +             "LOL T-spin triples"},
  54.201 +  {{"Sega"}, "Sega 1988" },
  54.202 +  {{"ARS"},  "Arika",
  54.203 +             "The rotation system of grandmasters"},
  54.204 +  {{"Tngn"}, "Tengen",
  54.205 +             "Unlicensed" },
  54.206 +  {{"NRSL"}, "Game Boy",
  54.207 +             "Left-handed system"},
  54.208 +  {{"NRSR"}, "NES",
  54.209 +             "Right-handed system"},
  54.210 +  {{"TOD4"}, "TOD M4",
  54.211 +             "Like SRS but less radical" },
  54.212 +  {{"TDX"},  "Climbing",
  54.213 +             "Spider-Man's favorite Tetris game is DX"},
  54.214 +
  54.215 +  /* Lockdown */
  54.216 +  {{"OLD"},  "Classic",
  54.217 +             "Lock when piece lands" },
  54.218 +  {{"EntR"}, "Entry reset",
  54.219 +             "Lock delay resets for each new piece" },
  54.220 +  {{"StpR"}, "Step reset",
  54.221 +             "Lock delay resets when piece moves down" },
  54.222 +  {{"MovR"}, "Move reset",
  54.223 +             "Lock delay resets when piece moves" },
  54.224 +
  54.225 +  /* Lock/line delay and floor kicks */
  54.226 +  {{"SBSC"}, "Set by speed curve" },
  54.227 +  {{"Inf"},  "Unlimited" },
  54.228 +
  54.229 +  /* Sub-row kick */
  54.230 +  {{"srkN"}, "Unchanged" },
  54.231 +  {{"srkM"}, "Kick minimal distance" },
  54.232 +  {{"srkU"}, "Always kick up" },
  54.233 +
  54.234 +  /* Gluing and gravity */
  54.235 +  {{"Naiv"}, "Naive",
  54.236 +             "Can result in floating blocks"},
  54.237 +  {{"Squ"},  "Square",
  54.238 +             "Form a 4x4 square from complete pieces" },
  54.239 +  {{"Stky"}, "Sticky",
  54.240 +             "Blocks stick together"},
  54.241 +  {{"byCo"}, "Sticky by color",
  54.242 +             "Blocks of each color stick together"},
  54.243 +  {{"Casc"}, "Cascade",
  54.244 +             "Each piece falls separately" },
  54.245 +
  54.246 +  /* Line scoring */
  54.247 +  {{"LJ"},   "LJ",
  54.248 +             "100, 400, 700, 1200, B2B=200, T=500*n" },
  54.249 +  {{"Fibo"}, "Fibonacci",
  54.250 +             "100, 200, 300, 500 (800, 1300, ...)" },
  54.251 +  {{"HotL"}, "Hotline",
  54.252 +             "100-600 for clearing lines on specific rows" },
  54.253 +  {{"TDSl"}, "Guideline",
  54.254 +             "Level * 100, 300, 500, 800, B2B=50%, T=?" },
  54.255 +  {{"NESl"}, "8-bit",
  54.256 +             "Level * 40, 100, 300, 1200" },
  54.257 +  {{"LJns"}, "LJ (nerfed spin)",
  54.258 +             "100, 400, 700, 1200, B2B=200, T=300*n" },
  54.259 +
  54.260 +  /* Drop scoring */
  54.261 +  {{"ConD"}, "Continuous drop",
  54.262 +             "1 point per row dropped without stopping"},
  54.263 +  {{"S1H1"}, "Drop",
  54.264 +             "1 point per row soft or hard dropped"},
  54.265 +  {{"S1H2"}, "Soft x1, Hard x2",
  54.266 +             "1 pt/row for soft drop, 2 for hard drop"},
  54.267 +
  54.268 +  /* T-spin */
  54.269 +  {{"Imob"}, "Immobile",
  54.270 +             "Rotate any piece so it can't move up"},
  54.271 +  {{"CrnT"}, "3-corner T",
  54.272 +             "Rotate T with 3 corners filled" },
  54.273 +  {{"WKT"},  "3-corner T no kick",
  54.274 +             "Rotate T with 3 corners filled, without kick" },
  54.275 +
  54.276 +  /* sideways speed, soft drop speed, zangi */
  54.277 +  
  54.278 +  /* Zangi */
  54.279 +  {{"Slid"}, "Slide",
  54.280 +             "The piece can be dropped and then moved"},
  54.281 +  {{"Lock"}, "Lock",
  54.282 +             "The piece locks immediately"},
  54.283 +  {{"LocU"}, "Lock on release",
  54.284 +             "Can be moved while drop key is pressed"},
  54.285 +
  54.286 +  /* Shadow */
  54.287 +  {{"tl25"}, "Fainter color" },
  54.288 +  {{"tl50"}, "Faint color" },
  54.289 +  {{"tlsC"}, "Color" },
  54.290 +  {{"tlsM"}, "Monochrome" },
  54.291 +  {{"invF"}, "Off, hide falling piece too"},
  54.292 +  
  54.293 +#ifdef HAS_FPU
  54.294 +  {{"FulS"}, "Full screen",
  54.295 +             "Try to run the game in the full screen."},
  54.296 +  {{"Wind"}, "Windowed",
  54.297 +             "Run the game in a window."},
  54.298 +#endif
  54.299 +
  54.300 +  /* list terminator */
  54.301 +  { {.i=0}, NULL }
  54.302 +};
    55.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    55.2 +++ b/src/ljlocale.h	Fri Mar 13 00:39:12 2009 -0700
    55.3 @@ -0,0 +1,33 @@
    55.4 +#ifndef LJLOCALE_H
    55.5 +#define LJLOCALE_H
    55.6 +#include <stdint.h>
    55.7 +
    55.8 +/*
    55.9 +A FourCC, or four-character code, is a short string that fits in a
   55.10 +32-bit word.
   55.11 +*/
   55.12 +typedef union FourCC {
   55.13 +  char c[4];
   55.14 +  uint32_t i;
   55.15 +} FourCC;
   55.16 +
   55.17 +/* When abusing union, test that the types that the compiler uses are
   55.18 +   the same types the developer's compiler uses. */
   55.19 +extern char sizeof_uint32_t_equals_4[(sizeof(uint32_t) == 4) ? 1 : -1];
   55.20 +
   55.21 +typedef struct FourCCLocaleEntry {
   55.22 +  FourCC code;
   55.23 +  const char *name;
   55.24 +  const char *desc;
   55.25 +} FourCCLocaleEntry;
   55.26 +
   55.27 +void ljSetLocale(const FourCCLocaleEntry *locale);
   55.28 +
   55.29 +#define _4CC(i) (ljGetFourCCName((FourCC){i}))
   55.30 +const char *ljGetFourCCName(FourCC i);
   55.31 +
   55.32 +#define _4CCDESC(i) (ljGetFourCCDesc((FourCC){i}))
   55.33 +const char *ljGetFourCCDesc(FourCC i);
   55.34 +
   55.35 +extern const FourCCLocaleEntry englishLocale[];
   55.36 +#endif
    56.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    56.2 +++ b/src/ljmusic.c	Fri Mar 13 00:39:12 2009 -0700
    56.3 @@ -0,0 +1,204 @@
    56.4 +/* FIXME: add license block */
    56.5 +#include <allegro.h>
    56.6 +#include "ljmusic.h"
    56.7 +
    56.8 +#if LJMUSIC_USING_DUMB
    56.9 +#include <aldumb.h>
   56.10 +static int dumb_inited;
   56.11 +#endif
   56.12 +#if LJMUSIC_USING_VORBIS
   56.13 +#include "ljvorbis.h"
   56.14 +#endif
   56.15 +
   56.16 +#define WARNINGS 0
   56.17 +
   56.18 +struct LJMusic {
   56.19 +  int paused;
   56.20 +#if LJMUSIC_USING_VORBIS
   56.21 +  LJVorbis *ogg;
   56.22 +#endif
   56.23 +#if LJMUSIC_USING_DUMB
   56.24 +  DUH *duh;
   56.25 +  AL_DUH_PLAYER *duhplayer;
   56.26 +#endif
   56.27 +};
   56.28 +
   56.29 +struct LJMusic *LJMusic_new(void) {
   56.30 +  struct LJMusic *m = malloc(sizeof(struct LJMusic));
   56.31 +  if (!m) {
   56.32 +    return NULL;
   56.33 +  }
   56.34 +#if LJMUSIC_USING_VORBIS
   56.35 +  m->ogg = NULL;
   56.36 +#endif
   56.37 +#if LJMUSIC_USING_DUMB
   56.38 +  m->duh = NULL;
   56.39 +  m->duhplayer = NULL;
   56.40 +#endif
   56.41 +  return m;
   56.42 +}
   56.43 +
   56.44 +void LJMusic_delete(struct LJMusic *m) {
   56.45 +  if (!m) {
   56.46 +    return;
   56.47 +  }
   56.48 +  LJMusic_unload(m);
   56.49 +  free(m);
   56.50 +}
   56.51 +
   56.52 +int LJMusic_load(struct LJMusic *m, const char *filename) {
   56.53 +  if (!m || !filename) {
   56.54 +    return -1;
   56.55 +  }
   56.56 +
   56.57 +  LJMusic_unload(m);
   56.58 +  const char *ext = get_extension(filename);
   56.59 +
   56.60 +#if LJMUSIC_USING_VORBIS
   56.61 +  if (!ustricmp(ext, "ogg")) {
   56.62 +    m->ogg = LJVorbis_open(filename);
   56.63 +    return m->ogg ? 1 : 0;
   56.64 +  }
   56.65 +#endif
   56.66 +
   56.67 +#if LJMUSIC_USING_DUMB
   56.68 +  if (!dumb_inited) {
   56.69 +    atexit(dumb_exit);
   56.70 +    dumb_register_stdfiles();
   56.71 +  }
   56.72 +  if (!ustricmp(ext, "it")) {
   56.73 +    m->duh = dumb_load_it_quick(filename);
   56.74 +    return m->duh ? 1 : 0;
   56.75 +  }
   56.76 +  if (!ustricmp(ext, "xm")) {
   56.77 +    m->duh = dumb_load_xm_quick(filename);
   56.78 +    return m->duh ? 1 : 0;
   56.79 +  }
   56.80 +  if (!ustricmp(ext, "s3m")) {
   56.81 +    m->duh = dumb_load_s3m_quick(filename);
   56.82 +    return m->duh ? 1 : 0;
   56.83 +  }
   56.84 +  if (!ustricmp(ext, "mod")) {
   56.85 +    m->duh = dumb_load_mod_quick(filename);
   56.86 +    return m->duh ? 1 : 0;
   56.87 +  }
   56.88 +#endif
   56.89 +
   56.90 +  return 0;
   56.91 +}
   56.92 +
   56.93 +void LJMusic_unload(struct LJMusic *m) {
   56.94 +  if (!m) {
   56.95 +    return;
   56.96 +  }
   56.97 +  LJMusic_stop(m);
   56.98 +
   56.99 +#if LJMUSIC_USING_DUMB
  56.100 +  if (m->duh) {
  56.101 +    unload_duh(m->duh);
  56.102 +    m->duh = NULL;
  56.103 +  }
  56.104 +#endif
  56.105 +
  56.106 +#if LJMUSIC_USING_VORBIS
  56.107 +  if (m->ogg) {
  56.108 +    LJVorbis_close(m->ogg);
  56.109 +    m->ogg = NULL;
  56.110 +  }
  56.111 +#endif
  56.112 +
  56.113 +}
  56.114 +
  56.115 +void LJMusic_setLoop(struct LJMusic *m, unsigned long int start) {
  56.116 +#if LJMUSIC_USING_VORBIS
  56.117 +  if (m->ogg) {
  56.118 +    LJVorbis_setLoop(m->ogg, start);
  56.119 +  }
  56.120 +#endif
  56.121 +}
  56.122 +
  56.123 +void LJMusic_start(struct LJMusic *m, int bufferSize, int vol) {
  56.124 +  if (!m) {
  56.125 +    return;
  56.126 +  }
  56.127 +
  56.128 +  LJMusic_stop(m);
  56.129 +
  56.130 +#if LJMUSIC_USING_DUMB
  56.131 +  if (m->duh) {
  56.132 +    m->duhplayer = al_start_duh(m->duh, 2, 0,
  56.133 +                                vol / 256.0,
  56.134 +                                bufferSize,
  56.135 +                                48000);
  56.136 +  }
  56.137 +#endif
  56.138 +#if LJMUSIC_USING_VORBIS
  56.139 +  if (m->ogg) {
  56.140 +    LJVorbis_start(m->ogg, bufferSize, vol, 128);
  56.141 +  }
  56.142 +#endif
  56.143 +  m->paused = 0;
  56.144 +}
  56.145 +
  56.146 +void LJMusic_stop(struct LJMusic *m) {
  56.147 +  if (!m) {
  56.148 +    return;
  56.149 +  }
  56.150 +
  56.151 +#if LJMUSIC_USING_DUMB
  56.152 +  if (m->duhplayer) {
  56.153 +    al_stop_duh(m->duhplayer);
  56.154 +    m->duhplayer = NULL;
  56.155 +  }
  56.156 +#endif
  56.157 +
  56.158 +#if LJMUSIC_USING_VORBIS
  56.159 +  LJVorbis_stop(m->ogg);
  56.160 +#endif
  56.161 +}
  56.162 +
  56.163 +void LJMusic_poll(struct LJMusic *m) {
  56.164 +  if (!m) {
  56.165 +    return;
  56.166 +  }
  56.167 +
  56.168 +#if LJMUSIC_USING_DUMB
  56.169 +  if (m->duhplayer) {
  56.170 +    al_poll_duh(m->duhplayer);
  56.171 +  }
  56.172 +#endif
  56.173 +
  56.174 +#if LJMUSIC_USING_VORBIS
  56.175 +  if (m->ogg) {
  56.176 +    LJVorbis_poll(m->ogg);
  56.177 +  }
  56.178 +#endif
  56.179 +}
  56.180 +
  56.181 +void LJMusic_pause(struct LJMusic *m, int value) {
  56.182 +  value = value ? 1 : 0;
  56.183 +  if (!m) {
  56.184 +
  56.185 +  }
  56.186 +  if (WARNINGS && (value == m->paused)) {
  56.187 +    alert("LJMusic_pause", "unchanging.", "", "OK", 0, 13, 0);
  56.188 +    return;
  56.189 +  }
  56.190 +#if LJMUSIC_USING_DUMB
  56.191 +  if (m->duhplayer) {
  56.192 +    if (value) {
  56.193 +      al_pause_duh(m->duhplayer);
  56.194 +    } else {
  56.195 +      al_resume_duh(m->duhplayer);
  56.196 +    }
  56.197 +  }
  56.198 +#endif
  56.199 +
  56.200 +#if LJMUSIC_USING_VORBIS
  56.201 +  if (m->ogg) {
  56.202 +    LJVorbis_pause(m->ogg, value);
  56.203 +  }
  56.204 +#endif
  56.205 +  m->paused = value;
  56.206 +}
  56.207 +
    57.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    57.2 +++ b/src/ljmusic.h	Fri Mar 13 00:39:12 2009 -0700
    57.3 @@ -0,0 +1,22 @@
    57.4 +/* FIXME: add license block */
    57.5 +#ifndef LJMUSIC_H
    57.6 +#define LJMUSIC_H
    57.7 +
    57.8 +#define LJMUSIC_USING_DUMB 1
    57.9 +#define LJMUSIC_USING_VORBIS 1
   57.10 +
   57.11 +typedef struct LJMusic LJMusic;
   57.12 +
   57.13 +struct LJMusic *LJMusic_new(void);
   57.14 +void LJMusic_delete(struct LJMusic *m);
   57.15 +int LJMusic_load(struct LJMusic *m, const char *filename);
   57.16 +void LJMusic_unload(struct LJMusic *m);
   57.17 +void LJMusic_start(struct LJMusic *m, int bufferSize, int vol);
   57.18 +void LJMusic_setLoop(struct LJMusic *m, unsigned long int start);
   57.19 +void LJMusic_stop(struct LJMusic *m);
   57.20 +void LJMusic_poll(struct LJMusic *m);
   57.21 +void LJMusic_pause(struct LJMusic *m, int value);
   57.22 +
   57.23 +
   57.24 +#endif
   57.25 +
    58.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    58.2 +++ b/src/ljpath.c	Fri Mar 13 00:39:12 2009 -0700
    58.3 @@ -0,0 +1,294 @@
    58.4 +/*
    58.5 +ljpath - functions to support an application that can be
    58.6 +installed to either a read-write folder ("portable" config)
    58.7 +or to a read-only folder ("installed" config)
    58.8 +
    58.9 +Copyright 2008 Damian Yerrick
   58.10 +
   58.11 +Insert zlib license here.
   58.12 +
   58.13 +*/
   58.14 +
   58.15 +
   58.16 +#include <stdlib.h>
   58.17 +#include <string.h>
   58.18 +#include "ljpath.h"
   58.19 +#include <limits.h>  // for PATH_MAX
   58.20 +#include <sys/stat.h>  // for mkdir()
   58.21 +#include <errno.h>
   58.22 +
   58.23 +#ifdef WIN32
   58.24 +#include <shlobj.h>
   58.25 +#include <direct.h>
   58.26 +#define USE_WINDOWS_APPDATA
   58.27 +#endif
   58.28 +
   58.29 +// on the DS
   58.30 +#ifdef ARM9
   58.31 +#include <fat.h>  // Chishm's libfat
   58.32 +static char hasFAT;
   58.33 +#include <unistd.h>  // for MAXPATHLEN
   58.34 +#else
   58.35 +#define hasFAT 1
   58.36 +#endif
   58.37 +
   58.38 +// http://forum.gbadev.org/viewtopic.php?p=147795#147795
   58.39 +#if !defined(PATH_MAX) && defined(MAXPATHLEN)
   58.40 +#define PATH_MAX MAXPATHLEN
   58.41 +#endif
   58.42 +
   58.43 +static char readWriteFolder[PATH_MAX];
   58.44 +static char skinFolder[PATH_MAX];
   58.45 +static char readOnlyFolder[PATH_MAX];
   58.46 +
   58.47 +
   58.48 +#ifdef WIN32
   58.49 +static int ljmkdir(const char *path) {
   58.50 +  //allegro_message("mkdir(\"%s\")\n", path);
   58.51 +  return _mkdir(path);
   58.52 +}
   58.53 +#else
   58.54 +static int ljmkdir(const char *path) {
   58.55 +  //allegro_message("mkdir(\"%s\")\n", path);
   58.56 +  return mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
   58.57 +}
   58.58 +#endif
   58.59 +
   58.60 +/**
   58.61 + * Wrapper around mkdir() that doesn't error if the
   58.62 + * directory already exists.
   58.63 + */
   58.64 +static int makeFolder(const char *path) {
   58.65 +  if (ljmkdir(path) < 0) {
   58.66 +    if (errno != EEXIST) {
   58.67 +      return -1;
   58.68 +    } 
   58.69 +  }
   58.70 +  return 0;
   58.71 +}
   58.72 +
   58.73 +
   58.74 +
   58.75 +/**
   58.76 + * Computes the name of the folder containing filename.
   58.77 + * @param dst destination to receive the folder name, of size PATH_MAX
   58.78 + * @param filename name of a file in this folder
   58.79 + */
   58.80 +static void ljpathSetFolder(char *dst, const char *filename) {
   58.81 +  const char *lastPathSep = NULL;
   58.82 +
   58.83 +  // I considered strrchr, but it would need two passes:
   58.84 +  // one for '/' and one for the drive-letter ':' under Windows.
   58.85 +  for (const char *here = filename; *here != 0; ++here) {
   58.86 +    if (*here == '/' || *here == '\\' || *here == ':') {
   58.87 +      lastPathSep = here;
   58.88 +    }
   58.89 +  }
   58.90 +  if (lastPathSep == NULL || lastPathSep - filename > PATH_MAX - 1) {
   58.91 +    strcpy(dst, ".");
   58.92 +  } else {
   58.93 +    memcpy(dst, filename, lastPathSep - filename);
   58.94 +    dst[lastPathSep - filename] = 0;
   58.95 +  }
   58.96 +}
   58.97 +
   58.98 +#if defined(USE_WINDOWS_APPDATA)
   58.99 +/**
  58.100 + * Retrieves the read/write path on a Windows system.
  58.101 + * SHGetFolderPath() is described in
  58.102 + * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/functions/shgetfolderpath.asp
  58.103 + */
  58.104 +static void ljpathSetRWFolder(void) {
  58.105 +  static const char suffix1[] = "/Pin Eight";
  58.106 +  static const char suffix2[] = "/lockjaw";
  58.107 +  char path[MAX_PATH + sizeof(suffix1) + sizeof(suffix2)] = "";
  58.108 +  
  58.109 +  int rVal = SHGetFolderPath(      
  58.110 +    NULL,  // owner window used with obscure dial-up functionality
  58.111 +    CSIDL_APPDATA,  // type of folder to retrieve
  58.112 +    NULL,  // user ID when impersonating another user
  58.113 +    0,  // get the current path, not the default path
  58.114 +    path
  58.115 +  );
  58.116 +  if (rVal) {
  58.117 +    return;
  58.118 +  }
  58.119 +
  58.120 +  strcat(path, suffix1);
  58.121 +  ljmkdir(path);
  58.122 +  strcat(path, suffix2);
  58.123 +  ljmkdir(path);
  58.124 +  if (strlen(path) >= PATH_MAX) {
  58.125 +    return;
  58.126 +  }
  58.127 +
  58.128 +  strncpy(readWriteFolder, path, PATH_MAX - 1);
  58.129 +  readWriteFolder[PATH_MAX - 1] = 0;
  58.130 +}
  58.131 +#else
  58.132 +/**
  58.133 + * Retrieves the read/write path on a UNIX system using the HOME
  58.134 + * environment variable.
  58.135 + */
  58.136 +static void ljpathSetRWFolder(void) {
  58.137 +  static const char suffix[] = "/.lockjaw";
  58.138 +  const char *value = getenv("HOME");
  58.139 +  if (!value) {
  58.140 +    return;
  58.141 +  }
  58.142 +  
  58.143 +  unsigned int len = strlen(value) + sizeof(suffix);
  58.144 +  if (len > PATH_MAX) {
  58.145 +    return;
  58.146 +  }
  58.147 +  
  58.148 +  strcpy(readWriteFolder, value);
  58.149 +  strcat(readWriteFolder, suffix);
  58.150 +  ljmkdir(readWriteFolder);
  58.151 +  //allegro_message("readWriteFolder is now \"%s\"\n", skinFolder);
  58.152 +}
  58.153 +#endif
  58.154 +
  58.155 +void ljpathSetSkinFolder(const char *filename) {
  58.156 +  //allegro_message("ljpathSetFolder(skinFolder, \"%s\")\n", filename);
  58.157 +  ljpathSetFolder(skinFolder, filename);
  58.158 +  //allegro_message("skinFolder is now \"%s\"\n", skinFolder);
  58.159 +}
  58.160 +
  58.161 +static int ljpathJoin(char *restrict dst,
  58.162 +                      const char *restrict folder,
  58.163 +                      const char *restrict filename) {
  58.164 +  size_t len = 0;
  58.165 +
  58.166 +  //allegro_message("ljpathJoin(\"%s\", \"%s\")\n", folder, filename);
  58.167 +
  58.168 +  // If the path is not absolute, copy the folder and the
  58.169 +  // path separator into the destination string.
  58.170 +  if (filename[0] != '\\' && filename[0] != '\\'
  58.171 +      && filename[1] != ':') {
  58.172 +    while (len < PATH_MAX - 1 && *folder != 0) {
  58.173 +      dst[len++] = *folder++;
  58.174 +    }
  58.175 +    /* The path separator '/' is standard under Linux and Mac OS X.
  58.176 +       It also works on MS-DOS and Windows API, notwithstanding 
  58.177 +       certain Microsoft command-line utilities. */
  58.178 +    dst[len++] = '/';
  58.179 +    while (len < PATH_MAX - 1 && *filename != 0) {
  58.180 +      dst[len++] = *filename++;
  58.181 +    }
  58.182 +  }
  58.183 +
  58.184 +  if (len < PATH_MAX - 1) {
  58.185 +    dst[len] = 0;
  58.186 +    //allegro_message("equals \"%s\"\n", dst);
  58.187 +    return 1;
  58.188 +  }
  58.189 +  //allegro_message("failed\n");
  58.190 +  return 0;
  58.191 +}
  58.192 +
  58.193 +/**
  58.194 + * Determines whether a file exists and is readable.
  58.195 + * @param path the path of a file
  58.196 + * @return nonzero iff readable
  58.197 + */
  58.198 +static int fexists(const char *path) {
  58.199 +  struct stat st;
  58.200 +  if (!hasFAT) {
  58.201 +    return 0;
  58.202 +  }
  58.203 +  int rval = stat(path, &st);
  58.204 +  //allegro_message("stat(\"%s\") returned %d\n", path, rval);
  58.205 +  return rval == 0;
  58.206 +}
  58.207 +
  58.208 +int ljpathInit(const char *argv0) {
  58.209 +#ifdef ARM9
  58.210 +  hasFAT = fatInitDefault();
  58.211 +  if (hasFAT) {
  58.212 +    if (makeFolder("/data") < 0 || makeFolder("/data/lockjaw") < 0) {
  58.213 +      hasFAT = 0;
  58.214 +    }
  58.215 +  }
  58.216 +  strcpy(readOnlyFolder, "/data/lockjaw");
  58.217 +  strcpy(readWriteFolder, "/data/lockjaw");
  58.218 +  strcpy(skinFolder, "/data/lockjaw");
  58.219 +  return hasFAT;
  58.220 +#else
  58.221 +  char path[PATH_MAX];
  58.222 +  int installed;
  58.223 +
  58.224 +  //allegro_message("argv[0] = \"%s\"", argv0);
  58.225 +  ljpathSetFolder(readOnlyFolder, argv0);
  58.226 +  strcpy(readWriteFolder, ".");
  58.227 +  strcpy(skinFolder, ".");
  58.228 +
  58.229 +  // determine whether "installed.ini" (an empty text file)
  58.230 +  // exists
  58.231 +  installed = ljpathJoin(path, readOnlyFolder, "installed.ini") >= 0 
  58.232 +              && fexists(path);
  58.233 +  if (installed) {
  58.234 +    ljpathSetRWFolder();
  58.235 +  }
  58.236 +  return installed;
  58.237 +#endif
  58.238 +}
  58.239 +
  58.240 +/**
  58.241 + * Finds a file name for reading.
  58.242 + * @param dst buffer for path, of size PATH_MAX
  58.243 + * @param src name of file
  58.244 + * @return 0 if not found, nonzero if found
  58.245 + */
  58.246 +int ljpathFind_r(char *restrict dst, const char *restrict filename) {
  58.247 +  if (!hasFAT) {
  58.248 +    return 0;
  58.249 +  }
  58.250 +  if (ljpathJoin(dst, readWriteFolder, filename) >= 0 
  58.251 +      && fexists(dst)) {
  58.252 +    return 1;
  58.253 +  }
  58.254 +  if (ljpathJoin(dst, skinFolder, filename) >= 0 
  58.255 +      && fexists(dst)) {
  58.256 +    return 1;
  58.257 +  }
  58.258 +  if (ljpathJoin(dst, readOnlyFolder, filename) >= 0 
  58.259 +      && fexists(dst)) {
  58.260 +    return 1;
  58.261 +  }
  58.262 +  return 0;
  58.263 +}
  58.264 +
  58.265 +int ljpathFind_w(char *restrict dst, const char *restrict filename) {
  58.266 +  return ljpathJoin(dst, readWriteFolder, filename);
  58.267 +}
  58.268 +
  58.269 +FILE *ljfopen(const char *restrict filename, const char *restrict mode) {
  58.270 +  char path[PATH_MAX];
  58.271 +  FILE *fp;
  58.272 +  
  58.273 +  if (!hasFAT) {
  58.274 +    return NULL;
  58.275 +  }
  58.276 +
  58.277 +  //allegro_message("ljfopen(\"%s\", \"%s\")\n", filename, mode);
  58.278 +  if (mode[0] == 'r') {
  58.279 +
  58.280 +    // For read-only files, search all three paths.
  58.281 +    if (ljpathFind_r(path, filename) >= 0 
  58.282 +        && (fp = fopen(path, mode)) != 0) {
  58.283 +      //allegro_message("fopen(\"%s\", \"%s\") for reading\n", path, mode);
  58.284 +      return fp;
  58.285 +    }
  58.286 +  } else {
  58.287 +
  58.288 +    // For read/write files, check only the read/write path.
  58.289 +    if (ljpathFind_w(path, filename) >= 0 
  58.290 +        && (fp = fopen(path, mode)) != 0) {
  58.291 +      //allegro_message("fopen(\"%s\", \"%s\") for writing\n", path, mode);
  58.292 +      return fp;
  58.293 +    }
  58.294 +  }
  58.295 +  return NULL;
  58.296 +}
  58.297 +
    59.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    59.2 +++ b/src/ljpath.h	Fri Mar 13 00:39:12 2009 -0700
    59.3 @@ -0,0 +1,74 @@
    59.4 +/*
    59.5 +ljpath - functions to support an application that can be
    59.6 +installed to either a read-write folder ("portable" config)
    59.7 +or to a read-only folder ("installed" config)
    59.8 +
    59.9 +Copyright 2008 Damian Yerrick
   59.10 +
   59.11 +Insert zlib license here.
   59.12 +
   59.13 +*/
   59.14 +
   59.15 +#ifndef LJPATH_H
   59.16 +#define LJPATH_H
   59.17 +
   59.18 +#ifdef __cplusplus
   59.19 +#include <cstdio>
   59.20 +using std::FILE;
   59.21 +extern "C"
   59.22 +{
   59.23 +#define DISTINCT
   59.24 +#else
   59.25 +#include <stdio.h>
   59.26 +#define DISTINCT restrict
   59.27 +#endif
   59.28 +
   59.29 +/**
   59.30 + * Sets up the paths used by ljfopen().
   59.31 + * Determines whether the program is marked as "installed", by
   59.32 + * the presence of a file called installed.ini in the folder
   59.33 + * containing the executable file.  If so, uses a folder in the
   59.34 + * user's home directory instead of the current directory for 
   59.35 + * writable files.
   59.36 + * @param argv0 the executable file's path
   59.37 + * @return nonzero for installed; zero for portable
   59.38 + */
   59.39 +int ljpathInit(const char *argv0);
   59.40 +
   59.41 +/**
   59.42 + * Sets the skin folder to the folder containing a file.  For instance,
   59.43 + * in a skinnable falling block game, this would be the folder holding
   59.44 + * the .skin file that describes the path to each graphic used for the
   59.45 + * game display.
   59.46 + * @param filename the name of the file
   59.47 + */
   59.48 +void ljpathSetSkinFolder(const char *filename);
   59.49 +
   59.50 +/**
   59.51 + * Searches for a file in read-write, skin, and read-only folders
   59.52 + * @param dst pointer to a PATH_MAX-byte buffer to hold the path
   59.53 + * @param filename the name of the file that will be searched for
   59.54 + * @return nonzero if the file was found; 0 if not found
   59.55 + */
   59.56 +int ljpathFind_r(char *DISTINCT dst, const char *DISTINCT filename);
   59.57 +
   59.58 +int ljpathFind_w(char *DISTINCT dst, const char *DISTINCT filename);
   59.59 +
   59.60 +/**
   59.61 + * Searches for a file and opens it.  After it is opened, the
   59.62 + * caller may use stdio.h operations on it and must close it.
   59.63 + * Files being read are searched for using ljpathFind_r; others are
   59.64 + * searched for using ljpathFind_r
   59.65 + * @param filename the name of the file that will be searched for
   59.66 + * @param mode the stdio mode (r, w, a, rb, wb, ab)
   59.67 + * @return a magic cookie suitable for passing to stdio.h if the file
   59.68 + * was opened; 0 if not opened
   59.69 + */
   59.70 +FILE *ljfopen(const char *DISTINCT filename, const char *DISTINCT mode);
   59.71 +
   59.72 +
   59.73 +#ifdef __cplusplus
   59.74 +}
   59.75 +#endif
   59.76 +
   59.77 +#endif
    60.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    60.2 +++ b/src/ljpc.c	Fri Mar 13 00:39:12 2009 -0700
    60.3 @@ -0,0 +1,2156 @@
    60.4 +/* PC frontend for LOCKJAW, an implementation of the Soviet Mind Game
    60.5 +
    60.6 +Copyright (C) 2006-2008 Damian Yerrick <tepples+lj@spamcop.net>
    60.7 +
    60.8 +This work is free software; you can redistribute it and/or modify
    60.9 +it under the terms of the GNU General Public License as published by
   60.10 +the Free Software Foundation; either version 2 of the License, or
   60.11 +(at your option) any later version.
   60.12 +
   60.13 +This program is distributed in the hope that it will be useful,
   60.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   60.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   60.16 +GNU General Public License for more details.
   60.17 +
   60.18 +You should have received a copy of the GNU General Public License
   60.19 +along with this program; if not, write to the Free Software
   60.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   60.21 +
   60.22 +Original game concept and design by Alexey Pajitnov.
   60.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   60.24 +or The Tetris Company LLC.
   60.25 +
   60.26 +*/
   60.27 +
   60.28 +#include "ljpc.h"
   60.29 +#include "ljreplay.h"
   60.30 +#include <jpgalleg.h>
   60.31 +#include <time.h>
   60.32 +#include "scenario.h"
   60.33 +#include "ljpath.h"
   60.34 +
   60.35 +#if 1
   60.36 +  #define LJ_VERSION "0.46a ("__DATE__")"
   60.37 +#else
   60.38 +  #define LJ_VERSION "WIP ("__DATE__")"
   60.39 +#endif
   60.40 +
   60.41 +#define USE_PICS_FOLDER 0
   60.42 +#define MAX_PLAYERS 2
   60.43 +#define LJ_TICK_RATE 3600  // frames per minute
   60.44 +
   60.45 +int bgColor, fgColor = 0, hiliteColor, pfBgColor = 0, pfFgColor;
   60.46 +static volatile int curTime = 0;
   60.47 +volatile char redrawWholeScreen = 1;
   60.48 +static volatile int wantPause = 0;
   60.49 +const DATAFILE *dat = NULL, *sound_dat = NULL;
   60.50 +const FONT *aver32 = NULL, *aver16 = NULL;
   60.51 +char withSound = 0;
   60.52 +char autoPause;
   60.53 +int refreshRate = 0;
   60.54 +char demoFilename[512];
   60.55 +
   60.56 +int withPics = -1;
   60.57 +
   60.58 +char skinName[PATH_MAX] = "default.skin";
   60.59 +char ljblocksSRSName[PATH_MAX];
   60.60 +char ljblocksSegaName[PATH_MAX];
   60.61 +char ljconnSRSName[PATH_MAX];
   60.62 +char ljconnSegaName[PATH_MAX];
   60.63 +char ljbgName[PATH_MAX];
   60.64 +char menubgName[PATH_MAX];
   60.65 +char bgmName[PATH_MAX];
   60.66 +char bgmRhythmName[PATH_MAX];
   60.67 +unsigned long int bgmLoopPoint = 0;
   60.68 +int bgmReadyGo = 0;
   60.69 +int bgmVolume = 128;
   60.70 +int skinW = 800;
   60.71 +int skinH = 600;
   60.72 +
   60.73 +
   60.74 +
   60.75 +
   60.76 +static void incCurTime(void) {
   60.77 +  ++curTime;
   60.78 +} END_OF_FUNCTION(incCurTime);
   60.79 +
   60.80 +static void amnesia(void) {
   60.81 +  redrawWholeScreen = 1;
   60.82 +} END_OF_FUNCTION(amnesia);
   60.83 +
   60.84 +static void requestPause(void) {
   60.85 +  wantPause |= autoPause;
   60.86 +}
   60.87 +
   60.88 +
   60.89 +int getTime(void) {
   60.90 +  return curTime;
   60.91 +}
   60.92 +
   60.93 +void yieldCPU(void) {
   60.94 +  rest(5);  
   60.95 +}
   60.96 +
   60.97 +void ljBeginDraw(LJView *v, int sync) {
   60.98 +  vsync();
   60.99 +  if (redrawWholeScreen) {
  60.100 +    redrawWholeScreen = 0;
  60.101 +    playRedrawScreen(v);
  60.102 +  }
  60.103 +
  60.104 +  BITMAP *sc = v->plat->skin->fullback;
  60.105 +
  60.106 +  // Draw shape
  60.107 +  blit(v->plat->skin->bg, sc, 0, 0, 0, 0, 48, 16);
  60.108 +  if (v->control->replayDst) {
  60.109 +
  60.110 +    // circle: record
  60.111 +    circlefill(sc, 8, 8, 7, fgColor);
  60.112 +    textout_ex(sc, aver16, "Rec", 18, 2, fgColor, -1);
  60.113 +  } else if (v->control->replaySrc) {
  60.114 +
  60.115 +    // triangle: play
  60.116 +    for (int wid = 0; wid < 7; ++wid) {
  60.117 +      hline(sc, 2, 2 + wid, wid * 2 + 3, fgColor);
  60.118 +      hline(sc, 2, 14 - wid, wid * 2 + 3, fgColor);
  60.119 +    }
  60.120 +    textout_ex(sc, aver16, "Play", 18, 2, fgColor, -1);
  60.121 +  } else {
  60.122 +
  60.123 +    // square: stop
  60.124 +    rectfill(sc, 2, 2, 14, 14, fgColor);
  60.125 +  }
  60.126 +  blit(sc, screen, 0, 0, 0, 0, 48, 16);
  60.127 +}
  60.128 +
  60.129 +void ljEndDraw(LJView *v) {
  60.130 +
  60.131 +}
  60.132 +
  60.133 +static void startRecording(LJView *v) {
  60.134 +  withPics = -1;
  60.135 +
  60.136 +  // close existing playback
  60.137 +  if (v->control->replaySrc) {
  60.138 +    replayClose(v->control->replaySrc);
  60.139 +    v->control->replaySrc = NULL;
  60.140 +  }
  60.141 +      
  60.142 +  // toggle recording
  60.143 +  if (v->control->replayDst) {
  60.144 +    replayClose(v->control->replayDst);
  60.145 +    v->control->replayDst = NULL;
  60.146 +  } else {
  60.147 +    char path[PATH_MAX];
  60.148 +
  60.149 +    ljpathFind_w(path, "demo.ljm");
  60.150 +    v->control->replayDst = newReplay(path, v->field);
  60.151 +#if USE_PICS_FOLDER
  60.152 +    withPics = 0;
  60.153 +#endif
  60.154 +  }
  60.155 +}
  60.156 +
  60.157 +static void startPlaying(LJView *v) {
  60.158 +  withPics = -1;
  60.159 +
  60.160 +  // close existing recording
  60.161 +  if (v->control->replayDst) {
  60.162 +    replayClose(v->control->replayDst);
  60.163 +    v->control->replayDst = NULL;
  60.164 +  }
  60.165 +      
  60.166 +  // toggle playback
  60.167 +  if (v->control->replaySrc) {
  60.168 +    replayClose(v->control->replaySrc);
  60.169 +    v->control->replaySrc = NULL;
  60.170 +  } else {
  60.171 +    char path[PATH_MAX];
  60.172 +
  60.173 +    if (ljpathFind_r(path, "demo.ljm")) {
  60.174 +      v->control->replaySrc = openReplay(path, v->field);
  60.175 +    }
  60.176 +    v->nLockTimes = 0;
  60.177 +#if USE_PICS_FOLDER
  60.178 +    withPics = 0;
  60.179 +#endif
  60.180 +  }
  60.181 +  v->backDirty = ~0;
  60.182 +  v->field->reloaded = 1;
  60.183 +}
  60.184 +
  60.185 +int ljHandleConsoleButtons(LJView *v) {
  60.186 +  int canceled = 0;
  60.187 +  
  60.188 +  while (keypressed()) {
  60.189 +    int scancode;
  60.190 +    int codepoint = ureadkey(&scancode);
  60.191 +
  60.192 +    if (scancode == KEY_ESC) {
  60.193 +      wantPause = 1;
  60.194 +    } else if (scancode == KEY_PRTSCR) {
  60.195 +      saveScreen(-1);
  60.196 +      save_bitmap("ljbackbuf.bmp", v->plat->skin->fullback, NULL);
  60.197 +    } else if (codepoint == '[') {
  60.198 +      v->plat->wantRecord = 1;
  60.199 +    } else if (codepoint == ']') {
  60.200 +      v->plat->wantRecord = 0;
  60.201 +      startPlaying(v);
  60.202 +    }
  60.203 +  }
  60.204 +  if (v->plat->wantRecord) {
  60.205 +    v->plat->wantRecord = 0;
  60.206 +    startRecording(v);
  60.207 +  }
  60.208 +  if (wantPause) {
  60.209 +    canceled = pauseGame(v->plat);
  60.210 +    wantPause = 0;
  60.211 +  }
  60.212 +  
  60.213 +  if (wantsClose) {
  60.214 +    canceled = 1;
  60.215 +  }
  60.216 +  
  60.217 +  return canceled;
  60.218 +}
  60.219 +
  60.220 +
  60.221 +static void drawBlock(const LJPCView *const v, int x, int y, int blk) {
  60.222 +
  60.223 +  if (x >= 0 && x < LJ_PF_WID && y >= 0 && y < LJ_PF_VIS_HT) {
  60.224 +    const int w = v->skin->blkW;
  60.225 +    const int h = v->skin->blkH;
  60.226 +    const int dstX = w * x;
  60.227 +    const int dstY = h * (LJ_PF_VIS_HT - 1 - y);
  60.228 +
  60.229 +    if (v->skin->transparentPF && (blk == 0 || blk == 0x100)) {
  60.230 +
  60.231 +      // Copy background
  60.232 +      const unsigned int srcX = dstX + v->baseX;
  60.233 +      const unsigned int srcY = dstY + v->skin->baseY
  60.234 +                                - LJ_PF_VIS_HT * h - v->skin->pfElev;
  60.235 +      blit(v->skin->bg, v->back, srcX, srcY, dstX, dstY, w, h);
  60.236 +    } else if (blk && v->skin->connBlocks) {
  60.237 +
  60.238 +      // Copy connected block
  60.239 +      const unsigned int srcX = ((blk >> 0) & 15) * w;
  60.240 +      const unsigned int srcY = ((blk >> 4) & 15) * h;
  60.241 +      blit(v->skin->connBlocks, v->back, srcX, srcY, dstX, dstY, w, h);
  60.242 +    } else {
  60.243 +
  60.244 +      // Copy lone block
  60.245 +      const unsigned int srcX = ((blk >> 4) & 7) * w;
  60.246 +      const unsigned int srcY = ((blk >> 7) & 1) * h;
  60.247 +      blit(v->skin->blocks, v->back, srcX, srcY, dstX, dstY, w, h);
  60.248 +    }
  60.249 +    
  60.250 +    // 0x100: hotline
  60.251 +    if (blk & 0x100) {
  60.252 +      hline(v->back, dstX, dstY + h / 2 - 1, dstX + w - 1, pfFgColor);
  60.253 +      hline(v->back, dstX, dstY + h / 2    , dstX + w - 1, pfFgColor);
  60.254 +      hline(v->back, dstX, dstY + h / 2 + 1, dstX + w - 1, pfBgColor);
  60.255 +    }
  60.256 +  }
  60.257 +}
  60.258 +
  60.259 +/**
  60.260 + * Draws multiple rows of the playfield
  60.261 + * @param p     the playfield
  60.262 + * @param rows  the rows to be updated (0x00001 on bottom, 0x80000 on top)
  60.263 + */
  60.264 +void updField(const LJView *const v, LJBits rows) {
  60.265 +  const LJField *const p = v->field;
  60.266 +
  60.267 +  acquire_bitmap(v->plat->back);
  60.268 +  for (int y = 0;
  60.269 +       y < LJ_PF_VIS_HT && rows != 0;
  60.270 +       ++y, rows >>= 1) {
  60.271 +    int blankTile = 0;
  60.272 +    
  60.273 +    // hotline: draw 0x100 as the background
  60.274 +    if (hotlineRows[y] && v->field->scoreStyle == LJSCORE_HOTLINE) {
  60.275 +      blankTile = 0x100;
  60.276 +    }
  60.277 +    // during line clear delay, draw bars to show which lines were cleared
  60.278 +    if (p->state == LJS_LINES_FALLING && p->stateTime > 0
  60.279 +        && ((1 << y) & p->tempRows)) {
  60.280 +      blankTile = 0x100;
  60.281 +    }
  60.282 +    if (rows & 1) {
  60.283 +      for (int x = p->leftWall; x < p->rightWall; x++) {
  60.284 +        int b = v->hidePF ? 0 : p->b[y][x];
  60.285 +        drawBlock(v->plat, x, y, b ? b : blankTile);
  60.286 +      }
  60.287 +    }
  60.288 +  }
  60.289 +  release_bitmap(v->plat->back);
  60.290 +}
  60.291 +
  60.292 +
  60.293 +#define SHADOW_BLOCK 0x00
  60.294 +
  60.295 +/**
  60.296 + * Draws a tetromino whose lower left corner of the bounding box is at (x, y)
  60.297 + * @param b the bitmap to draw to
  60.298 + * @param piece the piece to be drawn
  60.299 + * @param x distance from to left side of 4x4 box
  60.300 + * @param y distance from top of bitmap to bottom of 4x4 box
  60.301 + * @param the rotation state (0: U; 1: R; 2: D; 3: L; 4: Initial position)
  60.302 + * @param w width of each block
  60.303 + * @param h height of each block
  60.304 + * @param color Drawing style
  60.305 + * color == 0: draw shadow
  60.306 + * color == 0x10 through 0x70: draw in that color
  60.307 + * color == 0x80: draw as garbage
  60.308 + * color == -255 through -1: draw with 255 through 1 percent lighting
  60.309 + */
  60.310 +LJBits drawPiece(LJView *const v, BITMAP *const b,
  60.311 +                 int piece, int x, int y, int theta,
  60.312 +                 int color, int w, int h) {
  60.313 +  // Don't try to draw the -1 that's the sentinel for no hold piece
  60.314 +  if (piece < 0)
  60.315 +    return 0;
  60.316 +
  60.317 +  LJBits rows = 0;
  60.318 +  LJBlkSpec blocks[4];
  60.319 +  const int srcW = v->plat->skin->blkW;
  60.320 +  const int srcH = v->plat->skin->blkH;
  60.321 +  BITMAP *singleBlocks = v->plat->skin->blocks;
  60.322 +  int singleSeries = (color == 0
  60.323 +                      && singleBlocks->h >= 6 * srcH)
  60.324 +                     ? 4 : 2;
  60.325 +
  60.326 +  // color: 0 for shadow, >0 for piece
  60.327 +  BITMAP *connBlocks = (color != SHADOW_BLOCK)
  60.328 +                       ? v->plat->skin->connBlocks : NULL;
  60.329 +
  60.330 +  BITMAP *transTemp = color < 0
  60.331 +                      || (color == SHADOW_BLOCK && v->hideShadow < LJSHADOW_COLORED)
  60.332 +                      ? create_bitmap(w, h) : 0;
  60.333 +
  60.334 +  if (transTemp) {
  60.335 +    set_trans_blender(0, 0, 0, v->hideShadow ? 128 : 64);
  60.336 +  }
  60.337 +
  60.338 +  expandPieceToBlocks(blocks, v->field, piece, 0, 0, theta);
  60.339 +  acquire_bitmap(b);
  60.340 +  for (int blk = 0; blk < 4; ++blk) {
  60.341 +    int blkValue = blocks[blk].conn;
  60.342 +    
  60.343 +    // If blkValue == 0 then the block is not part of the piece,
  60.344 +    // such as if it's a domino or tromino or (once pentominoes
  60.345 +    // are added) a tetromino.
  60.346 +    if (blkValue) {
  60.347 +      int blkX = blocks[blk].x;
  60.348 +      int blkY = blocks[blk].y;
  60.349 +      const int dstX = x + w * blkX;
  60.350 +      const int dstY = y + h * (-1 - blkY);
  60.351 +
  60.352 +      if (color > 0) {
  60.353 +        blkValue = color | (blkValue & CONNECT_MASK);
  60.354 +      } else if (color == 0 && v->hideShadow == LJSHADOW_COLORLESS) {
  60.355 +        blkValue = 0;
  60.356 +      }
  60.357 +    
  60.358 +      if (connBlocks) {
  60.359 +        const unsigned int srcX = ((blkValue >> 0) & 15) * srcW;
  60.360 +        const unsigned int srcY = ((blkValue >> 4) & 15) * srcH;
  60.361 +
  60.362 +        if (transTemp) {
  60.363 +          stretch_blit(connBlocks, transTemp,
  60.364 +                       srcX, srcY, srcW, srcH,
  60.365 +                       0, 0, w, h);
  60.366 +          if (color < 0) {
  60.367 +            draw_lit_sprite(b, transTemp, dstX, dstY, color + 256);
  60.368 +          } else {
  60.369 +            draw_trans_sprite(b, transTemp, dstX, dstY);
  60.370 +          }
  60.371 +        } else if (srcW != w || srcH != h) {
  60.372 +          stretch_blit(connBlocks, b,
  60.373 +                       srcX, srcY, srcW, srcH,
  60.374 +                       dstX, dstY, w, h);
  60.375 +          if (dstY > 600) {
  60.376 +            allegro_message("dstY = %d\n", dstY);
  60.377 +          }
  60.378 +        } else {
  60.379 +          blit(connBlocks, b,
  60.380 +               srcX, srcY,
  60.381 +               dstX, dstY, w, h);
  60.382 +        }
  60.383 +      } else {
  60.384 +        const unsigned int srcX = ((blkValue >> 4) & 7) * srcW;
  60.385 +        const unsigned int srcY = (((blkValue >> 7) & 1) + singleSeries) * srcH;
  60.386 +
  60.387 +        if (transTemp) {
  60.388 +          stretch_blit(singleBlocks, transTemp,
  60.389 +                       srcX, srcY, srcW, srcH,
  60.390 +                       0, 0, w, h);
  60.391 +          if (color < 0) {
  60.392 +            draw_lit_sprite(b, transTemp, dstX, dstY, color + 256);
  60.393 +          } else {
  60.394 +            draw_trans_sprite(b, transTemp, dstX, dstY);
  60.395 +          }
  60.396 +        } else {
  60.397 +          stretch_blit(singleBlocks, b,
  60.398 +                       srcX, srcY, srcW, srcH,
  60.399 +                       dstX, dstY, w, h);
  60.400 +        }
  60.401 +      }
  60.402 +      rows |= 1 << blkY;
  60.403 +    }
  60.404 +  }
  60.405 +  release_bitmap(b);
  60.406 +  if (transTemp) {
  60.407 +    solid_mode();
  60.408 +    destroy_bitmap(transTemp);
  60.409 +  }
  60.410 +
  60.411 +  return rows;
  60.412 +}
  60.413 +
  60.414 +void drawPieceInPF(LJView *v, int dstX, LJFixed dstY, int theta, int color) {
  60.415 +  LJBits bits = 0;
  60.416 +  BITMAP *b = v->plat->back;
  60.417 +  const LJField *const p = v->field;
  60.418 +  int y = ljfixfloor(dstY);
  60.419 +  const int w = v->plat->skin->blkW;
  60.420 +  const int h = v->plat->skin->blkH;
  60.421 +  int drawnY = fixmul(h, dstY);
  60.422 +
  60.423 +  bits = drawPiece(v, b, p->curPiece[0],
  60.424 +                   w * dstX, h * LJ_PF_VIS_HT - drawnY,
  60.425 +                   theta,
  60.426 +                   color, w, h);
  60.427 +  bits = (y >= 0) ? bits << y : bits >> -y;
  60.428 +  bits &= (1 << LJ_PF_VIS_HT) - 1;
  60.429 +  if (dstY & 0xFFFF) {
  60.430 +    bits |= bits << 1;
  60.431 +  }
  60.432 +
  60.433 +  v->backDirty |= bits;
  60.434 +  v->frontDirty |= bits;
  60.435 +}
  60.436 +
  60.437 +
  60.438 +void drawFallingPiece(LJView *v) {
  60.439 +  const LJField *const p = v->field;
  60.440 +  int piece = p->curPiece[0];
  60.441 +  int y = v->smoothGravity
  60.442 +          ? p->y
  60.443 +          : ljitofix(ljfixfloor(p->y));
  60.444 +  const int color = (p->state == LJS_LANDED)
  60.445 +                    ? -128 - ((p->stateTime + 1) * 128 / (p->speed.lockDelay + 1))
  60.446 +                    : pieceColors[piece];
  60.447 +
  60.448 +  // Draw trails
  60.449 +  if (v->showTrails) {
  60.450 +
  60.451 +    // trail going up
  60.452 +    while (v->trailY - y < ljitofix(-1)) {
  60.453 +      v->trailY += ljitofix(1);
  60.454 +      drawPieceInPF(v, p->x, v->trailY, p->theta, color);
  60.455 +    }
  60.456 +
  60.457 +    // trail going down
  60.458 +    while (v->trailY - y > ljitofix(1)) {
  60.459 +      v->trailY -= ljitofix(1);
  60.460 +      drawPieceInPF(v, p->x, v->trailY, p->theta, color);
  60.461 +    }
  60.462 +  }
  60.463 +  drawPieceInPF(v, p->x, y, p->theta, color);
  60.464 +  v->trailY = y;
  60.465 +}
  60.466 +
  60.467 +void drawShadow(LJView *v) {
  60.468 +  const LJField *const p = v->field;
  60.469 +  int y = ljitofix(p->hardDropY);
  60.470 +  const int color = SHADOW_BLOCK;
  60.471 +  drawPieceInPF(v, p->x, y, p->theta, color);
  60.472 +}
  60.473 +
  60.474 +void drawNextPieces(LJView *v) {
  60.475 +  int baseX = v->plat->baseX;
  60.476 +  int baseY = v->plat->skin->baseY - v->plat->skin->pfElev;
  60.477 +  int blkW = v->plat->skin->blkW;
  60.478 +  int blkH = v->plat->skin->blkH;
  60.479 +  int holdPieceColor = v->field->alreadyHeld
  60.480 +                       ? 0x80
  60.481 +                       : pieceColors[v->field->holdPiece];
  60.482 +  int ceil = v->field->ceiling;
  60.483 +  
  60.484 +  BITMAP *sc = v->plat->skin->fullback;
  60.485 +
  60.486 +  if (v->frontDirty & LJ_DIRTY_NEXT) {
  60.487 +    int holdW = blkW * 2 / 3;
  60.488 +    int holdH = blkH * 2 / 3;
  60.489 +    int holdX = baseX + blkW;
  60.490 +    int holdY = baseY - (ceil - 1) * blkH - 4 * holdH;
  60.491 +    
  60.492 +    // Move the hold piece within the screen
  60.493 +    if (holdY < 0) {
  60.494 +      holdY = 0;
  60.495 +    }
  60.496 +  
  60.497 +    // Draw hold piece
  60.498 +    blit(v->plat->skin->bg, sc,
  60.499 +         holdX, holdY,
  60.500 +         holdX, holdY,
  60.501 +         holdW * 4, holdH * 2);
  60.502 +    drawPiece(v, sc,
  60.503 +              v->field->holdPiece, holdX, holdY + 4 * holdH, 4,
  60.504 +              holdPieceColor, holdW, holdH);
  60.505 +    blit(sc, screen,
  60.506 +         holdX, holdY,
  60.507 +         holdX, holdY,
  60.508 +         holdW * 4, holdH * 2);
  60.509 +  }
  60.510 +  // Draw next pieces
  60.511 +
  60.512 +  switch (v->plat->skin->nextPos) {
  60.513 +  case LJNEXT_RIGHT:
  60.514 +  case LJNEXT_RIGHT_TAPER:
  60.515 +    if (v->frontDirty & LJ_DIRTY_NEXT) {
  60.516 +      int y = baseY - ceil * blkH;
  60.517 +      int x = baseX + LJ_PF_WID * blkW;
  60.518 +      int w = blkW;
  60.519 +      int h = blkH;
  60.520 +      for (int i = 1; i <= v->nextPieces; ++i) {
  60.521 +        int piece = v->field->curPiece[i];
  60.522 +
  60.523 +        blit(v->plat->skin->bg, sc, x, y, x, y, w * 4, h * 2);
  60.524 +        if (!v->hideNext) {
  60.525 +          drawPiece(v, sc,
  60.526 +                    piece, x, y + 4 * h, 4,
  60.527 +                    pieceColors[piece], w, h);
  60.528 +        }
  60.529 +        blit(sc, screen, x, y, x, y, w * 4, h * 2);
  60.530 +        y += 8 + h * 2;
  60.531 +        if (v->plat->skin->nextPos == LJNEXT_RIGHT_TAPER) {
  60.532 +          --h;
  60.533 +          --w;
  60.534 +        }
  60.535 +      }
  60.536 +    }
  60.537 +    break;
  60.538 +    
  60.539 +  case LJNEXT_TOP:
  60.540 +    if (v->frontDirty & LJ_DIRTY_NEXT) {
  60.541 +      int y = baseY - (ceil + 2) * blkH - 8;
  60.542 +      int x = baseX + 4 * blkW;
  60.543 +      int blitX = x, blitY = y;
  60.544 +
  60.545 +      blit(v->plat->skin->bg, sc, x, y, x, y, blkW * 8, blkH * 2);
  60.546 +      if (!v->hideNext) {
  60.547 +        if (v->nextPieces >= 1) {
  60.548 +          int piece = v->field->curPiece[1];
  60.549 +          drawPiece(v, sc,
  60.550 +                    piece, x, y + 4 * blkH, 4,
  60.551 +                    pieceColors[piece], blkW, blkH);
  60.552 +        }
  60.553 +        if (v->nextPieces >= 2) {
  60.554 +          int piece = v->field->curPiece[2];
  60.555 +          x += 4 * blkW;
  60.556 +          drawPiece(v, sc,
  60.557 +                    piece, x, y + 3 * blkH, 4,
  60.558 +                    pieceColors[piece], blkW/ 2, blkH / 2);
  60.559 +        }
  60.560 +        if (v->nextPieces >= 3) {
  60.561 +          int piece = v->field->curPiece[3];
  60.562 +          x += 2 * blkW;
  60.563 +          drawPiece(v, sc,
  60.564 +                    piece, x, y + 3 * blkH, 4,
  60.565 +                    pieceColors[piece], blkW / 2, blkH / 2);
  60.566 +        }
  60.567 +      }
  60.568 +      blit(sc, screen, blitX, blitY, blitX, blitY, blkW * 8, blkH * 2);
  60.569 +    }
  60.570 +    break;
  60.571 +  }
  60.572 +
  60.573 +  if (v->plat->nextAbove && !v->hideNext) {
  60.574 +    int row = (v->field->hardDropY + 4);
  60.575 +    int x = (1 + v->field->x) * blkW;
  60.576 +    for (int i = 1;
  60.577 +         i <= v->plat->nextAbove
  60.578 +         && row <= v->field->ceiling - 2;
  60.579 +         ++i) {
  60.580 +      int y = (LJ_PF_VIS_HT - row) * blkH;
  60.581 +      int piece = v->field->curPiece[i];
  60.582 +
  60.583 +      drawPiece(v, v->plat->back,
  60.584 +                piece, x, y, 4,
  60.585 +                pieceColors[piece], blkW / 2, blkH / 2);
  60.586 +      v->backDirty |= 3 << row;
  60.587 +      row += 2;
  60.588 +    }
  60.589 +  }
  60.590 +  v->frontDirty &= ~LJ_DIRTY_NEXT;
  60.591 +}
  60.592 +
  60.593 +void drawScore(LJView *v) {
  60.594 +  int gameTime = v->field->gameTime;
  60.595 +  int seconds = gameTime / 60;
  60.596 +  int minutes = seconds / 60;
  60.597 +  int baseX = v->plat->baseX;
  60.598 +  int tpm = -1;
  60.599 +  int spawnLeft = v->plat->skin->blkW * LJ_SPAWN_X + baseX;
  60.600 +  int pfRight = v->plat->skin->blkW * LJ_PF_WID + baseX;
  60.601 +  BITMAP *sc = v->plat->skin->fullback;
  60.602 +
  60.603 +  if (withPics >= 0) {
  60.604 +    if (v->field->nPieces != withPics) {
  60.605 +      saveScreen(v->field->nPieces);
  60.606 +    }
  60.607 +    withPics = v->field->nPieces;
  60.608 +  }
  60.609 +
  60.610 +  if (v->nLockTimes >= 2 ) {
  60.611 +    int time = v->lockTime[0] - v->lockTime[v->nLockTimes - 1];
  60.612 +    if (time > 0) {
  60.613 +      tpm = 3600 * (v->nLockTimes - 1) / time;
  60.614 +    }
  60.615 +  }
  60.616 +
  60.617 +  if (v->frontDirty & LJ_DIRTY_SCORE) {
  60.618 +    switch (v->plat->skin->nextPos) {
  60.619 +    case LJNEXT_TOP:
  60.620 +      blit(v->plat->skin->bg, sc,
  60.621 +           pfRight, 72,
  60.622 +           pfRight, 72,
  60.623 +           112, 136);
  60.624 +      
  60.625 +      textout_ex(sc, aver32, "Lines:", pfRight, 72, fgColor, -1);
  60.626 +      textprintf_right_ex(sc, aver32, pfRight + 104, 102, fgColor, -1,
  60.627 +                          "%d", v->field->lines);
  60.628 +      textout_ex(sc, aver32, "Score:", pfRight, 142, fgColor, -1);
  60.629 +      textprintf_right_ex(sc, aver32, pfRight + 104, 172, fgColor, -1,
  60.630 +                          "%d", v->field->score);
  60.631 +      textout_ex(sc, aver32, "Level:", pfRight, 212, fgColor, -1);
  60.632 +      textprintf_right_ex(sc, aver32, pfRight + 104, 242, fgColor, -1,
  60.633 +                          "%d", v->field->speedState.level);
  60.634 +      blit(sc, screen,
  60.635 +           pfRight, 72,
  60.636 +           pfRight, 72,
  60.637 +           112, 136);
  60.638 +      break;
  60.639 +
  60.640 +    default:
  60.641 +      blit(v->plat->skin->bg, sc, spawnLeft, 12, spawnLeft, 12, 288, 30);
  60.642 +      blit(v->plat->skin->bg, sc,
  60.643 +           spawnLeft, 42, spawnLeft, 42,
  60.644 +           pfRight - spawnLeft, 30);
  60.645 +      textprintf_right_ex(sc, aver32, spawnLeft + 288, 12, fgColor, -1,
  60.646 +                          "Lv. %d", v->field->speedState.level);
  60.647 +      textprintf_ex(sc, aver32, spawnLeft, 12, fgColor, -1,
  60.648 +                    "Lines: %d", v->field->lines);
  60.649 +      textprintf_ex(sc, aver32, spawnLeft, 42, fgColor, -1,
  60.650 +                    "Score: %d", v->field->score);
  60.651 +      blit(sc, screen, spawnLeft, 12, spawnLeft, 12, 288, 60);
  60.652 +      break;
  60.653 +    }
  60.654 +  }
  60.655 +  
  60.656 +  /* If speed is defined, and there is room to draw it, draw it. */
  60.657 +  if (tpm > 0 && v->nextPieces <= 3) {
  60.658 +    blit(v->plat->skin->bg, sc,
  60.659 +         pfRight, 282,
  60.660 +         pfRight, 282,
  60.661 +         104, 60);
  60.662 +    textout_ex(sc, aver32, "Speed:", pfRight, 282, fgColor, -1);
  60.663 +    textprintf_right_ex(sc, aver32, pfRight + 104, 312, fgColor, -1,
  60.664 +                        "%d", tpm);
  60.665 +    blit(sc, screen,
  60.666 +         pfRight, 282,
  60.667 +         pfRight, 282,
  60.668 +         104, 60);
  60.669 +  }
  60.670 +  
  60.671 +  if (v->frontDirty & LJ_DIRTY_NEXT) {
  60.672 +
  60.673 +    // Erase gimmick
  60.674 +    blit(v->plat->skin->bg, sc,
  60.675 +         baseX, v->plat->skin->baseY + 8,
  60.676 +         baseX, v->plat->skin->baseY + 8,
  60.677 +         v->plat->skin->blkW * LJ_PF_WID, 30);
  60.678 +    // Draw gimmick
  60.679 +    if (v->field->gimmick >= 0 && v->field->gimmick < LJGM_N_GIMMICKS) {
  60.680 +      const char *gimmickName = ljGetFourCCName(gimmickNames[v->field->gimmick]);
  60.681 +      textout_centre_ex(sc, aver32,
  60.682 +                        gimmickName ? gimmickName : "Bad gimmick ID",
  60.683 +                        baseX + (LJ_PF_WID / 2) * v->plat->skin->blkW,
  60.684 +                        v->plat->skin->baseY + 8,
  60.685 +                        fgColor, -1);
  60.686 +    blit(sc, screen,
  60.687 +         baseX, v->plat->skin->baseY + 8,
  60.688 +         baseX, v->plat->skin->baseY + 8,
  60.689 +         v->plat->skin->blkW * LJ_PF_WID, 30);
  60.690 +    }
  60.691 +  }
  60.692 +  drawNextPieces(v);
  60.693 +
  60.694 +  blit(v->plat->skin->bg, sc, pfRight, 42, pfRight, 42, 96, 30);
  60.695 +#if 0
  60.696 +  // Use this for DEBUG inspection into a variable
  60.697 +  textprintf_right_ex(sc, aver16, pfRight + 96, 8, fgColor, -1,
  60.698 +                      "%d", v->field->bpmCounter);
  60.699 +#endif
  60.700 +  textprintf_right_ex(sc, aver32, pfRight + 96, 42, fgColor, -1,
  60.701 +                      "%d:%02d", minutes, seconds - 60 * minutes);
  60.702 +  blit(sc, screen, pfRight, 42, pfRight, 42, 96, 30);
  60.703 +
  60.704 +}
  60.705 +
  60.706 +void blitField(LJView *v) {
  60.707 +  int blkH = v->plat->skin->blkH;
  60.708 +  int rowY = v->plat->skin->baseY
  60.709 +             - v->plat->skin->pfElev
  60.710 +             - blkH * v->field->ceiling;
  60.711 +  int x = v->plat->skin->blkW * v->field->leftWall;
  60.712 +  int w = v->plat->skin->blkW * (v->field->rightWall - v->field->leftWall);
  60.713 +
  60.714 +  // Copy each dirty row
  60.715 +  for (int y = v->field->ceiling - 1; y >= 0; --y) {
  60.716 +    if (v->frontDirty & (1 << y)) {
  60.717 +      int top = (LJ_PF_VIS_HT - y - 1) * blkH;
  60.718 +      int ht = 0;
  60.719 +      
  60.720 +      // Find the height of the contiguous rows to blit
  60.721 +      do {
  60.722 +        ht += blkH;
  60.723 +        --y;
  60.724 +      } while ((y >= 0)
  60.725 +               && (v->frontDirty & (1 << y)));
  60.726 +      blit(v->plat->back, screen,
  60.727 +           x, top,
  60.728 +           x + v->plat->baseX, rowY,
  60.729 +           w, ht);
  60.730 +      rowY += ht;
  60.731 +    }
  60.732 +    rowY += blkH;
  60.733 +  }
  60.734 +
  60.735 +  v->frontDirty &= (~0) << LJ_PF_HT;
  60.736 +}
  60.737 +
  60.738 +void saveScreen(int n) {
  60.739 +  BITMAP *b = create_bitmap(SCREEN_W, SCREEN_H);
  60.740 +  if (b) {
  60.741 +    PALETTE pal;
  60.742 +
  60.743 +    get_palette(pal);
  60.744 +    blit(screen, b, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
  60.745 +    if (n < 0) {
  60.746 +      save_bitmap("ljsnap.bmp", b, pal);
  60.747 +    } else {
  60.748 +      char filename[64];
  60.749 +      sprintf(filename, "pics/lj%05d.bmp", n);
  60.750 +      save_bitmap(filename, b, pal);
  60.751 +    }
  60.752 +    destroy_bitmap(b);
  60.753 +  }
  60.754 +}
  60.755 +
  60.756 +#define PRESETS_PER_COL 6
  60.757 +#define N_NONPRESETS 2
  60.758 +#define PRESETS_TOP 140
  60.759 +#define PRESET_COL_WIDTH 250
  60.760 +#define PRESET_ROW_HT 40
  60.761 +
  60.762 +static const char *const nonPresetNames[N_NONPRESETS] = {
  60.763 +  "Full Custom", "Back"
  60.764 +};
  60.765 +
  60.766 +static void getPresetDrawRow(unsigned int preset, int hilite) {
  60.767 +  unsigned int ht = text_height(aver32);
  60.768 +  const char *txt = preset < nLoadedPresets
  60.769 +                    ? loadedPresets[preset].name
  60.770 +                    : nonPresetNames[preset - nLoadedPresets];
  60.771 +  if (!txt) {
  60.772 +    txt = "Bad";
  60.773 +  }
  60.774 +  unsigned int wid = text_length(aver32, txt);
  60.775 +
  60.776 +  int buttonCol = preset / PRESETS_PER_COL;
  60.777 +  int buttonX = 20 + buttonCol * PRESET_COL_WIDTH;
  60.778 +  int buttonY = PRESETS_TOP
  60.779 +                + PRESET_ROW_HT * (preset - buttonCol * PRESETS_PER_COL);
  60.780 +  unsigned int buttonWidth = wid + 16;
  60.781 +
  60.782 +  rectfill(screen,
  60.783 +           buttonX, buttonY,
  60.784 +           buttonX + buttonWidth - 1, buttonY + PRESET_ROW_HT - 1,
  60.785 +           hilite ? hiliteColor : bgColor);
  60.786 +  if (hilite) {
  60.787 +    rect(screen,
  60.788 +         buttonX, buttonY,
  60.789 +         buttonX + buttonWidth - 1, buttonY + PRESET_ROW_HT - 1,
  60.790 +         fgColor);
  60.791 +  }
  60.792 +  textout_ex(screen, aver32, txt,
  60.793 +             8 + buttonX,
  60.794 +             20 + buttonY - ht / 2,
  60.795 +             fgColor, -1);
  60.796 +}
  60.797 +
  60.798 +int getPreset(int lastPreset) {
  60.799 +  LJBits lastKeys = ~0;
  60.800 +  redrawWholeScreen = 1;
  60.801 +
  60.802 +  clear_keybuf();
  60.803 +  if (lastPreset < 0) {
  60.804 +    lastPreset += nLoadedPresets + N_NONPRESETS;
  60.805 +  }
  60.806 +
  60.807 +  for(int done = 0; done == 0; ) {
  60.808 +    if (redrawWholeScreen) {
  60.809 +      redrawWholeScreen = 0;
  60.810 +      clear_to_color(screen, bgColor);
  60.811 +      textout_ex(screen, aver32, "LOCKJAW > Play", 16, 32, fgColor, -1);
  60.812 +      textout_ex(screen, aver32, "Select a scenario:", 16, 90, fgColor, -1);
  60.813 +      
  60.814 +      for (int preset = 0;
  60.815 +            preset < nLoadedPresets + N_NONPRESETS;
  60.816 +            ++preset) {
  60.817 +        getPresetDrawRow(preset, preset == lastPreset);
  60.818 +      }
  60.819 +      textout_ex(screen, aver16, "Arrows: move; Rotate Right: start",
  60.820 +                 16, PRESETS_TOP + 40 * (PRESETS_PER_COL + 1), fgColor, -1);
  60.821 +      textout_ex(screen, aver16, "Coming soon: an editor for these!",
  60.822 +                 16, PRESETS_TOP + 40 * (PRESETS_PER_COL + 1) + 20, fgColor, -1);
  60.823 +    }
  60.824 +    while (keypressed()) {
  60.825 +      int scancode;
  60.826 +      ureadkey(&scancode);
  60.827 +      if (scancode == KEY_PRTSCR) {
  60.828 +        saveScreen(-1);
  60.829 +      }
  60.830 +    }
  60.831 +
  60.832 +    int preset = lastPreset;    
  60.833 +    LJBits keys = menuReadPad();
  60.834 +    LJBits newKeys = keys & ~lastKeys;
  60.835 +
  60.836 +    if ((newKeys & VKEY_ROTL) || wantsClose) {
  60.837 +      preset = nLoadedPresets + N_NONPRESETS - 1;
  60.838 +      ezPlaySample("rotate_wav", 128);
  60.839 +    }
  60.840 +    if ((newKeys & VKEY_ROTR) || wantsClose) {
  60.841 +      done = 1;
  60.842 +      ezPlaySample("line_wav", 128);
  60.843 +    }
  60.844 +
  60.845 +    if (newKeys & VKEY_UP) {
  60.846 +      if (preset <= 0) {
  60.847 +        preset = nLoadedPresets + N_NONPRESETS - 1;
  60.848 +      } else {
  60.849 +        --preset;
  60.850 +      }
  60.851 +      ezPlaySample("shift_wav", 128);
  60.852 +    }
  60.853 +    if (newKeys & VKEY_DOWN) {
  60.854 +      ++preset;
  60.855 +      if (preset >= nLoadedPresets + N_NONPRESETS) {
  60.856 +        preset = 0;
  60.857 +      }
  60.858 +      ezPlaySample("shift_wav", 128);
  60.859 +    }
  60.860 +
  60.861 +    if (newKeys & VKEY_LEFT) {
  60.862 +      if (preset < PRESETS_PER_COL) {
  60.863 +        preset += (((nLoadedPresets + N_NONPRESETS)
  60.864 +                     / PRESETS_PER_COL)
  60.865 +                    )
  60.866 +                   * PRESETS_PER_COL;
  60.867 +        if (preset >= nLoadedPresets + N_NONPRESETS) {
  60.868 +          preset -= PRESETS_PER_COL;
  60.869 +        }
  60.870 +      } else {
  60.871 +        preset -= PRESETS_PER_COL;
  60.872 +      }
  60.873 +      ezPlaySample("shift_wav", 128);
  60.874 +    }
  60.875 +    if (newKeys & VKEY_RIGHT) {
  60.876 +      preset += PRESETS_PER_COL;
  60.877 +      if (preset >= nLoadedPresets + N_NONPRESETS) {
  60.878 +        preset %= PRESETS_PER_COL;
  60.879 +      }
  60.880 +      ezPlaySample("shift_wav", 128);
  60.881 +    }
  60.882 +
  60.883 +    if (preset != lastPreset) {
  60.884 +      vsync();
  60.885 +      getPresetDrawRow(lastPreset, 0);
  60.886 +      getPresetDrawRow(preset, 1);
  60.887 +      lastPreset = preset;
  60.888 +    } else {
  60.889 +      rest(30);
  60.890 +    }
  60.891 +    lastKeys = keys;
  60.892 +  }
  60.893 +
  60.894 +  // Wrap the nonpresets into the negative numbers.
  60.895 +  if (lastPreset >= nLoadedPresets) {
  60.896 +    lastPreset -= (nLoadedPresets + N_NONPRESETS);
  60.897 +  }
  60.898 +
  60.899 +  return lastPreset;
  60.900 +}
  60.901 +
  60.902 +/**
  60.903 + * Pauses the game, returning nonzero if the player wants to quit.
  60.904 + */
  60.905 +int pauseGame(LJPCView *v) {
  60.906 +  int escCount = 0;
  60.907 +  int quit = 0;
  60.908 +  int escHold = curTime;
  60.909 +  redrawWholeScreen = 1;
  60.910 +  
  60.911 +  LJMusic_pause(v->skin->bgm, 1);
  60.912 +  while (escCount < 2 && !quit) {
  60.913 +    LJMusic_poll(v->skin->bgm);
  60.914 +    if (redrawWholeScreen) {
  60.915 +      redrawWholeScreen = 0;
  60.916 +      clear_to_color(screen, pfBgColor);
  60.917 +      textout_centre_ex(screen, aver32, "LOCKJAW: GAME PAUSED",
  60.918 +                        SCREEN_W / 2, 200, pfFgColor, -1);
  60.919 +      textout_centre_ex(screen, aver32, "Press Esc to continue",
  60.920 +                        SCREEN_W / 2, 250, pfFgColor, -1);
  60.921 +      textout_centre_ex(screen, aver32, "or hold Esc to quit",
  60.922 +                        SCREEN_W / 2, 300, pfFgColor, -1);
  60.923 +    }
  60.924 +    
  60.925 +    if (key[KEY_ESC]) {
  60.926 +      if (escHold == 0) {
  60.927 +        escHold = curTime;
  60.928 +      }
  60.929 +      if (curTime - escHold >= 60) {
  60.930 +        quit = 1;
  60.931 +      }
  60.932 +    } else {
  60.933 +      if (escHold) {
  60.934 +        ++escCount;
  60.935 +      }
  60.936 +      escHold = 0;
  60.937 +    }
  60.938 +    rest(30);
  60.939 +    if (wantsClose) {
  60.940 +      quit = 1;
  60.941 +    }
  60.942 +    
  60.943 +  }
  60.944 +  LJMusic_pause(v->skin->bgm, 0);
  60.945 +  redrawWholeScreen = 1;
  60.946 +  clear_keybuf();
  60.947 +  return quit;
  60.948 +}
  60.949 +
  60.950 +
  60.951 +void pcInit(LJView *v, struct LJPrefs *prefs) {
  60.952 +  v->plat->b2bcd1 = 0;
  60.953 +  v->plat->b2bcd2 = 0;
  60.954 +  v->plat->baseX = v->plat->skin->baseX;
  60.955 +  
  60.956 +  // If the player has chosen to use more next pieces than the
  60.957 +  // next piece position can handle, set the number of
  60.958 +  // next pieces for correctness of debrief().
  60.959 +  if (v->nextPieces > 3 && v->plat->skin->nextPos == LJNEXT_TOP) {
  60.960 +    v->nextPieces = 3;
  60.961 +  }
  60.962 +}
  60.963 +
  60.964 +/**
  60.965 + * Redraws everything on the screen.
  60.966 + * Called when needs redraw.
  60.967 + */
  60.968 +void playRedrawScreen(LJView *v) {
  60.969 +  blit(v->plat->skin->bg, screen,
  60.970 +       0, 0,
  60.971 +       0, 0,
  60.972 +       SCREEN_W, SCREEN_H);
  60.973 +  v->frontDirty = ~0;
  60.974 +}
  60.975 +
  60.976 +#if 0
  60.977 +void startingAniWantsSkip(LJView *v) {
  60.978 +  LJInput unusedIn;
  60.979 +  addKeysToInput(&unusedIn, readPad(), v->field, v->control);
  60.980 +}
  60.981 +#endif
  60.982 +
  60.983 +void playSampleForTetromino(int piece);
  60.984 +
  60.985 +void restPollingMusic(int nFrames, LJMusic *bgm) {
  60.986 +  nFrames += curTime;
  60.987 +  while (curTime - nFrames < 0) {
  60.988 +    if (bgm) {
  60.989 +      LJMusic_poll(bgm);
  60.990 +    }
  60.991 +  }
  60.992 +}
  60.993 +
  60.994 +extern int bgmReadyGo;
  60.995 +void startingAnimation(LJView *v) {
  60.996 +  int readyGoX = v->plat->baseX + v->plat->skin->blkW * LJ_PF_WID / 2;
  60.997 +  int readyGoY = v->plat->skin->baseY
  60.998 +                 - v->plat->skin->pfElev
  60.999 +                 - v->plat->skin->blkH
 60.1000 +                   * v->field->ceiling * 3 / 5;
 60.1001 +
 60.1002 +  clear_keybuf();
 60.1003 +  v->backDirty = 0;
 60.1004 +  
 60.1005 +  playRedrawScreen(v);
 60.1006 +  blitField(v);
 60.1007 +  textout_centre_ex(screen, aver32, "Ready",
 60.1008 +                    readyGoX, readyGoY, pfFgColor, -1);
 60.1009 +  
 60.1010 +  ezPlaySample("ready_wav", 128);
 60.1011 +  restPollingMusic(36, bgmReadyGo ? v->plat->skin->bgm : NULL);
 60.1012 +  v->frontDirty = ~0;
 60.1013 +  
 60.1014 +  if (!wantsClose) {
 60.1015 +    blitField(v);
 60.1016 +    textout_centre_ex(screen, aver32, "GO!",
 60.1017 +                    readyGoX, readyGoY, pfFgColor, -1);
 60.1018 +    drawScore(v);
 60.1019 +
 60.1020 +    ezPlaySample("go_wav", 128);
 60.1021 +    v->frontDirty = ~0;
 60.1022 +    restPollingMusic(12, bgmReadyGo ? v->plat->skin->bgm : NULL);
 60.1023 +  }
 60.1024 +  if (!wantsClose) {
 60.1025 +    playSampleForTetromino(v->field->curPiece[1]);
 60.1026 +    restPollingMusic(24, bgmReadyGo ? v->plat->skin->bgm : NULL);
 60.1027 +  }
 60.1028 +}
 60.1029 +
 60.1030 +
 60.1031 +static void gameOverAnimation(const LJPCView *const v, const LJField *p, int won) {
 60.1032 +  int ceiling = p->ceiling;
 60.1033 +  int left = v->baseX + p->leftWall * v->skin->blkW;
 60.1034 +  int right = v->baseX + p->rightWall * v->skin->blkW - 1;
 60.1035 +
 60.1036 +  ezPlaySample("sectionup_wav", 0);
 60.1037 +  if (!won) {
 60.1038 +    ezPlaySample("gameover_wav", 256);
 60.1039 +  } else {
 60.1040 +    ezPlaySample("win_wav", 256);
 60.1041 +  }
 60.1042 +
 60.1043 +  for (int t = ceiling + v->skin->blkH - 2; t >= 0; --t) {
 60.1044 +
 60.1045 +    // FIXME: vsync doesn't work on Vista
 60.1046 +    vsync();
 60.1047 +    for (int row = ceiling - 1; row >= 0; --row) {
 60.1048 +      int ysub = t - row;
 60.1049 +
 60.1050 +      if (ysub >= 0 && ysub < v->skin->blkH) {
 60.1051 +        int y = v->skin->baseY - v->skin->pfElev
 60.1052 +                - row * v->skin->blkH - ysub - 1;
 60.1053 +        hline(screen, left, y, right, pfBgColor);
 60.1054 +      }
 60.1055 +    }
 60.1056 +    if (wantsClose) {
 60.1057 +      t = 0;
 60.1058 +    }
 60.1059 +  }
 60.1060 +}
 60.1061 +
 60.1062 +#define MENU_COPR_NOTICE_LINES 4
 60.1063 +const char *const menuCoprNotice[MENU_COPR_NOTICE_LINES] = {
 60.1064 +  "Copr. 2006-2008 Damian Yerrick",
 60.1065 +  "Not sponsored or endorsed by The Tetris Company.",
 60.1066 +  "LOCKJAW comes with ABSOLUTELY NO WARRANTY.  This is free software, and you are",
 60.1067 +  "welcome to redistribute it under certain conditions as described in GPL.txt."
 60.1068 +};
 60.1069 +
 60.1070 +static BITMAP *buildTitleScreen(void) {
 60.1071 +  BITMAP *back = create_system_bitmap(SCREEN_W, SCREEN_H);
 60.1072 +  if (!back) {
 60.1073 +    return NULL;
 60.1074 +  }
 60.1075 +
 60.1076 +  // Gradient from (0, 0, 0) to (0, 0, 153)
 60.1077 +  for (int y = 0; y < 192; ++y) {
 60.1078 +    for (int x = -((y * 13) & 0x1F);
 60.1079 +          x < SCREEN_W;
 60.1080 +          x += 32) {
 60.1081 +      int colValue = y + ((rand() & 0x7000) >> 12);
 60.1082 +      int c = makecol(0, 0, 153 * colValue / 192);
 60.1083 +      hline(back, x, y, x + 31, c);
 60.1084 +    }
 60.1085 +  }
 60.1086 +  
 60.1087 +  // Gradient from (102, 51, 0) to (204, 102, 0)
 60.1088 +  for (int y = 192; y < 384; ++y) {
 60.1089 +    for (int x = -((y * 13) & 0x1F);
 60.1090 +          x < SCREEN_W;
 60.1091 +          x += 32) {
 60.1092 +      int colValue = y + ((rand() & 0x7800) >> 11);
 60.1093 +      int c = makecol(102 * colValue / 192, 51 * colValue / 192, 0);
 60.1094 +      hline(back, x, y, x + 31, c);
 60.1095 +    }
 60.1096 +  }
 60.1097 +  
 60.1098 +  // Gradient from (204, 102, 0) to (255, 128, 0)
 60.1099 +  for (int y = 384; y < SCREEN_H; ++y) {
 60.1100 +    for (int x = -((y * 13) & 0x1F);
 60.1101 +          x < SCREEN_W;
 60.1102 +          x += 32) {
 60.1103 +      int colValue = y - 400 + ((rand() & 0x7C00) >> 10);
 60.1104 +      if (colValue > 600 - 384) {
 60.1105 +        colValue = 600 - 384;
 60.1106 +      }
 60.1107 +      int c = makecol(204 + 50 * colValue / (600 - 384),
 60.1108 +                      102 + 25 * colValue / (600 - 384),
 60.1109 +                      0);
 60.1110 +      hline(back, x, y, x + 31, c);
 60.1111 +    }
 60.1112 +  }
 60.1113 +
 60.1114 +  DATAFILE *obj = find_datafile_object(dat, "arttitle_bmp");
 60.1115 +  BITMAP *logo = obj ? obj->dat : NULL;
 60.1116 +  obj = find_datafile_object(dat, "arttitle_pal");
 60.1117 +  const RGB *pal = obj ? obj->dat : NULL;
 60.1118 +  
 60.1119 +  if (logo && pal) {
 60.1120 +    set_palette(pal);
 60.1121 +    draw_sprite(back, logo,
 60.1122 +                (SCREEN_W - logo->w) / 2, (384 - logo->h) / 2);
 60.1123 +    //unselect_palette();
 60.1124 +  }
 60.1125 +
 60.1126 +  textout_centre_ex(back, aver32, "Arrows: change; Enter: choose",
 60.1127 +                    SCREEN_W / 2, 440,
 60.1128 +                    0, -1);
 60.1129 +                    
 60.1130 +  textout_centre_ex(back, aver32, "LOCKJAW: The Reference "LJ_VERSION,
 60.1131 +                    SCREEN_W / 2, SCREEN_H - 40,
 60.1132 +                    0, -1);
 60.1133 +
 60.1134 +  return back;
 60.1135 +}
 60.1136 +
 60.1137 +enum {
 60.1138 +  TITLE_EXIT = 0,
 60.1139 +  TITLE_PLAY,
 60.1140 +  TITLE_REPLAY,
 60.1141 +  TITLE_OPTIONS,
 60.1142 +  TITLE_SKIN,
 60.1143 +  TITLE_KEYS,
 60.1144 +  N_TITLE_ACTIONS
 60.1145 +};
 60.1146 +
 60.1147 +static const char *titleActions[N_TITLE_ACTIONS] = {
 60.1148 +  [TITLE_EXIT] = "Exit",
 60.1149 +  [TITLE_PLAY] = "Play",
 60.1150 +  [TITLE_REPLAY] = "Replay",
 60.1151 +  [TITLE_SKIN] = "Skin...",
 60.1152 +  [TITLE_OPTIONS] = "Options...",
 60.1153 +  [TITLE_KEYS] = "Game Keys..."
 60.1154 +};
 60.1155 +/*
 60.1156 +  0: Exit
 60.1157 +  1: Play
 60.1158 +  2
 60.1159 +*/
 60.1160 +int title(void) {
 60.1161 +
 60.1162 +  // don't even draw if the player is trying to close the window
 60.1163 +  if (wantsClose) {
 60.1164 +    return 0;
 60.1165 +  }
 60.1166 +
 60.1167 +  BITMAP *back = buildTitleScreen();
 60.1168 +  LJBits lastKeys = ~0;
 60.1169 +  int redraw = 1;
 60.1170 +  int choice = 1;
 60.1171 +
 60.1172 +  if (!back) {
 60.1173 +    alert("Not enough memory to display the title screen.",
 60.1174 +          "(If you don't even have RAM for a title screen,",
 60.1175 +          "then what do you have RAM for?)",
 60.1176 +          "Exit", 0, 13, 0);
 60.1177 +    return 0;
 60.1178 +  }
 60.1179 +  
 60.1180 +  redrawWholeScreen = 1;
 60.1181 +
 60.1182 +  for(int done = 0; done == 0; ) {
 60.1183 +    if (redrawWholeScreen) {
 60.1184 +      redrawWholeScreen = 0;
 60.1185 +      blit(back, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
 60.1186 +      redraw = 1;
 60.1187 +    }
 60.1188 +    if (redraw) {
 60.1189 +      int dehilite = makecol(221, 153, 85);
 60.1190 +      int white = makecol(255, 255, 255);
 60.1191 +      redraw = 0;
 60.1192 +      vsync();
 60.1193 +      blit(back, screen, 0, 400, 0, 400, SCREEN_W, 30);
 60.1194 +      textout_centre_ex(screen, aver32,
 60.1195 +                        titleActions[choice],
 60.1196 +                        SCREEN_W / 2, 400, white, -1);
 60.1197 +      textout_centre_ex(screen, aver32,
 60.1198 +                        titleActions[choice == 0 ? N_TITLE_ACTIONS - 1 : choice - 1],
 60.1199 +                        SCREEN_W / 2 - 160, 400, dehilite, -1);
 60.1200 +      textout_centre_ex(screen, aver32,
 60.1201 +                        titleActions[choice == N_TITLE_ACTIONS - 1 ? 0 : choice + 1],
 60.1202 +                        SCREEN_W / 2 + 160, 400, dehilite, -1);
 60.1203 +    }
 60.1204 +
 60.1205 +    while (keypressed()) {
 60.1206 +      int scancode;
 60.1207 +      ureadkey(&scancode);
 60.1208 +      if (scancode == KEY_PRTSCR) {
 60.1209 +        saveScreen(-1);
 60.1210 +      }
 60.1211 +    }
 60.1212 +
 60.1213 +    LJBits keys = menuReadPad();
 60.1214 +    LJBits newKeys = keys & ~lastKeys;
 60.1215 +
 60.1216 +    if (newKeys & VKEY_LEFT) {
 60.1217 +      --choice;
 60.1218 +      redraw = 1;
 60.1219 +      ezPlaySample("shift_wav", 128);
 60.1220 +    }
 60.1221 +    if (newKeys & VKEY_RIGHT) {
 60.1222 +      ++choice;
 60.1223 +      redraw = 1;
 60.1224 +      ezPlaySample("shift_wav", 128);
 60.1225 +    }
 60.1226 +    if (newKeys & VKEY_ROTL) {
 60.1227 +      choice = 0;
 60.1228 +      redraw = 1;
 60.1229 +      ezPlaySample("rotate_wav", 128);
 60.1230 +    }
 60.1231 +    if (newKeys & VKEY_ROTR) {
 60.1232 +      done = 1;
 60.1233 +      ezPlaySample("line_wav", 128);
 60.1234 +    }
 60.1235 +    if (choice < 0) {
 60.1236 +      choice += N_TITLE_ACTIONS;
 60.1237 +    }
 60.1238 +    if (choice >= N_TITLE_ACTIONS) {
 60.1239 +      choice -= N_TITLE_ACTIONS;
 60.1240 +    }
 60.1241 +
 60.1242 +    lastKeys = keys;
 60.1243 +    
 60.1244 +    if (!redraw) {
 60.1245 +      rest(30);
 60.1246 +    }
 60.1247 +    if (wantsClose) {
 60.1248 +      done = 1;
 60.1249 +      choice = 0;
 60.1250 +    }
 60.1251 +  }
 60.1252 +  destroy_bitmap(back);
 60.1253 +  return choice;
 60.1254 +}
 60.1255 +
 60.1256 +
 60.1257 +void setupWindow(void) {
 60.1258 +  set_window_title("LOCKJAW");
 60.1259 +  bgColor = makecol(255, 255, 255);
 60.1260 +  pfFgColor = bgColor;
 60.1261 +  hiliteColor = makecol(255, 255, 204);
 60.1262 +  refreshRate = get_refresh_rate();
 60.1263 +}
 60.1264 +
 60.1265 +void drawCoprNotice(void) {
 60.1266 +  clear_to_color(screen, pfBgColor);
 60.1267 +  for (int i = 0; i < MENU_COPR_NOTICE_LINES; ++i) {
 60.1268 +    textout_ex(screen, font, menuCoprNotice[i],
 60.1269 +               16, 580 + 12 * (i - MENU_COPR_NOTICE_LINES),
 60.1270 +               pfFgColor, -1);
 60.1271 +  }
 60.1272 +}
 60.1273 +
 60.1274 +
 60.1275 +/**
 60.1276 + * Destroys the back buffers for both playfields.
 60.1277 + */
 60.1278 +static void destroyBackBuf(LJPCView *plat) {
 60.1279 +  for (int i = 0; i < MAX_PLAYERS; ++i) {
 60.1280 +    if (plat->back) {
 60.1281 +      destroy_bitmap(plat->back);
 60.1282 +      plat->back = NULL;
 60.1283 +    }
 60.1284 +  }
 60.1285 +  if (plat->skin->fullback) {
 60.1286 +    destroy_bitmap(plat->skin->fullback);
 60.1287 +    plat->skin->fullback = NULL;
 60.1288 +  }
 60.1289 +}
 60.1290 +
 60.1291 +/**
 60.1292 + * Creates the back buffers for both playfields.
 60.1293 + */
 60.1294 +static int createBackBuf(LJView *v) {
 60.1295 +  v->plat->skin->fullback = create_system_bitmap(SCREEN_W, SCREEN_H);
 60.1296 +  if(!v->plat->skin->fullback) {
 60.1297 +    allegro_message("Could not create back buffer.\n");
 60.1298 +    return 0;
 60.1299 +  }
 60.1300 +
 60.1301 +  int blkW = v->plat->skin->blkW;
 60.1302 +  int blkH = v->plat->skin->blkH;
 60.1303 +  int y = v->plat->skin->baseY
 60.1304 +          - v->plat->skin->pfElev
 60.1305 +          - blkH * v->field->ceiling;
 60.1306 +
 60.1307 +  for (int i = 0; i < MAX_PLAYERS; ++i) {
 60.1308 +    int x = v->plat->skin->baseX
 60.1309 +            + blkW * v[i].field->leftWall;
 60.1310 +
 60.1311 +    v[i].plat->back = create_sub_bitmap(v->plat->skin->fullback,
 60.1312 +                                        x + SCREEN_W / 2 * i,
 60.1313 +                                        y,
 60.1314 +                                        v->plat->skin->blkW * LJ_PF_WID,
 60.1315 +                                        v->plat->skin->blkH * LJ_PF_VIS_HT);
 60.1316 +    if (!v[i].plat->back) {
 60.1317 +      destroyBackBuf(v->plat);
 60.1318 +      return 0;
 60.1319 +    }
 60.1320 +  }
 60.1321 +  return 1;
 60.1322 +}
 60.1323 +
 60.1324 +/**
 60.1325 + * Destroys all system bitmaps that a given view owns.
 60.1326 + * Useful before changing screen mode.
 60.1327 + */
 60.1328 +void destroySystemBitmaps(LJPCView *plat) {
 60.1329 +  destroyBackBuf(plat);
 60.1330 +  if (plat->skin->connBlocks) {
 60.1331 +    destroy_bitmap(plat->skin->connBlocks);
 60.1332 +    plat->skin->connBlocks = NULL;
 60.1333 +  }
 60.1334 +}
 60.1335 +
 60.1336 +int openWindow(int windowed)
 60.1337 +{
 60.1338 +  int depth = desktop_color_depth();
 60.1339 +  int card = windowed ? GFX_AUTODETECT_WINDOWED : GFX_AUTODETECT_FULLSCREEN;
 60.1340 +
 60.1341 +  /* Reference implementation for Allegro is not compatible with
 60.1342 +       indexed color. */
 60.1343 +  if (depth < 15) {
 60.1344 +    depth = 16;
 60.1345 +  }
 60.1346 +
 60.1347 +  // Full screen procedure
 60.1348 +  set_color_depth(depth);
 60.1349 +  if (set_gfx_mode(card, skinW, skinH, 0, 0) == 0) {
 60.1350 +    setupWindow();
 60.1351 +    return 0;
 60.1352 +  }
 60.1353 +    
 60.1354 +  // Windows can't tell 16 bit from 15 bit. If desktop color depth is reported as 16, try 15 too.
 60.1355 +  if (depth == 16) {
 60.1356 +    set_color_depth(15);
 60.1357 +    if (set_gfx_mode(card, skinW, skinH, 0, 0) == 0) {
 60.1358 +      setupWindow();
 60.1359 +      return 0;
 60.1360 +    }
 60.1361 +  }
 60.1362 +
 60.1363 +  return -1;
 60.1364 +}
 60.1365 +
 60.1366 +BITMAP *loadConnections(const char *filename, int blkW, int blkH) {
 60.1367 +  BITMAP *src = load_bitmap(filename, NULL);
 60.1368 +  
 60.1369 +  if (!src) {
 60.1370 +    return NULL;
 60.1371 +  }
 60.1372 +  BITMAP *dst = create_system_bitmap(blkW*16, blkH*16);
 60.1373 +  if (!dst) {
 60.1374 +    destroy_bitmap(src);
 60.1375 +    return NULL;
 60.1376 +  }
 60.1377 +  acquire_bitmap(dst);
 60.1378 +  for (unsigned int col = 0; col < 16; ++col) {
 60.1379 +    unsigned int srcXBase = (col & 0x03) * blkW * 2;
 60.1380 +    unsigned int srcYBase = (col >> 2) * blkH * 2;
 60.1381 +    unsigned int dstYBase = col * blkH;
 60.1382 +    for (unsigned int conn = 0; conn < 16; ++conn) {
 60.1383 +      unsigned int dstXBase = conn * blkW;
 60.1384 +      unsigned int topSegY = (conn & CONNECT_U) ? blkH : 0;
 60.1385 +      unsigned int botSegY = (conn & CONNECT_D) ? blkH/2 : 3*blkH/2;
 60.1386 +      unsigned int leftSegX = (conn & CONNECT_L) ? blkW : 0;
 60.1387 +      unsigned int rightSegX = (conn & CONNECT_R) ? blkW/2 : 3*blkW/2;
 60.1388 +      blit(src, dst,
 60.1389 +           srcXBase + leftSegX, srcYBase + topSegY,
 60.1390 +           dstXBase + 0, dstYBase + 0,
 60.1391 +           blkW/2, blkH/2);
 60.1392 +      blit(src, dst,
 60.1393 +           srcXBase + rightSegX, srcYBase + topSegY,
 60.1394 +           dstXBase + blkW/2, dstYBase + 0,
 60.1395 +           blkW/2, blkH/2);
 60.1396 +      blit(src, dst,
 60.1397 +           srcXBase + leftSegX, srcYBase + botSegY,
 60.1398 +           dstXBase + 0, dstYBase + blkH/2,
 60.1399 +           blkW/2, blkH/2);
 60.1400 +      blit(src, dst,
 60.1401 +           srcXBase + rightSegX, srcYBase + botSegY,
 60.1402 +           dstXBase + blkW/2, dstYBase + blkH/2,
 60.1403 +           blkW/2, blkH/2);
 60.1404 +    }
 60.1405 +  }
 60.1406 +  release_bitmap(dst);
 60.1407 +  destroy_bitmap(src);
 60.1408 +  return dst;
 60.1409 +}
 60.1410 +
 60.1411 +void closeWindow(void) {
 60.1412 +  set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
 60.1413 +}
 60.1414 +
 60.1415 +static void mainCleanup(LJPCView *v) {
 60.1416 +  destroyBackBuf(v);
 60.1417 +  if (v->skin->blocks) {
 60.1418 +    destroy_bitmap(v->skin->blocks);
 60.1419 +  }
 60.1420 +  if (v->skin->connBlocks) {
 60.1421 +    destroy_bitmap(v->skin->connBlocks);
 60.1422 +  }
 60.1423 +  LJMusic_delete(v->skin->bgm);
 60.1424 +  if (withSound) {
 60.1425 +    remove_sound();
 60.1426 +  }
 60.1427 +  closeWindow();
 60.1428 +}
 60.1429 +
 60.1430 +/**
 60.1431 + * Resets all skin settings to their initial values
 60.1432 + * so that skins can override them.
 60.1433 + */
 60.1434 +void loadSkinDefaults(LJPCSkin *s) {
 60.1435 +  ustrzcpy(ljblocksSRSName, sizeof(ljblocksSRSName) - 1,
 60.1436 +           "ljblocks.bmp");
 60.1437 +  ustrzcpy(ljblocksSegaName, sizeof(ljblocksSegaName) - 1,
 60.1438 +           "ljblocks-sega.bmp");
 60.1439 +  ustrzcpy(ljconnSRSName, sizeof(ljconnSRSName) - 1,
 60.1440 +           "ljconn.bmp");
 60.1441 +  ustrzcpy(ljconnSegaName, sizeof(ljconnSegaName) - 1,
 60.1442 +           "ljconn-sega.bmp");
 60.1443 +  ustrzcpy(ljbgName, sizeof(ljbgName) - 1,
 60.1444 +           "ljbg.jpg");
 60.1445 +  ustrzcpy(menubgName, sizeof(ljbgName) - 1,
 60.1446 +           "menubg.jpg");
 60.1447 +  ustrzcpy(bgmName, sizeof(bgmName) - 1,
 60.1448 +           "bgm.s3m");
 60.1449 +  ustrzcpy(bgmRhythmName, sizeof(bgmRhythmName) - 1,
 60.1450 +           "bgm-rhythm.s3m");
 60.1451 +  bgmLoopPoint = 0;
 60.1452 +  bgmReadyGo = 0;
 60.1453 +  bgmVolume = 128;
 60.1454 +  bgColor = makecol(255, 255, 255);
 60.1455 +  fgColor = makecol(0, 0, 0);
 60.1456 +  hiliteColor = makecol(255, 255, 204);
 60.1457 +  pfBgColor = makecol(0, 0, 0);
 60.1458 +  pfFgColor = makecol(255, 255, 255);
 60.1459 +  s->blkW = 24;
 60.1460 +  s->blkH = 24;
 60.1461 +  s->transparentPF = 0;
 60.1462 +  s->shiftScale    = 0;
 60.1463 +  s->baseX       = 0;
 60.1464 +  s->nextPos     = 0;
 60.1465 +}
 60.1466 +
 60.1467 +/**
 60.1468 + * Converts a single hexadecimal digit to its value.
 60.1469 + * @param in USASCII/Unicode value of hex digit character
 60.1470 + * @return value
 60.1471 +*/
 60.1472 +int hexDigitValue(int in) {
 60.1473 +  if (in >= '0' && in <= '9') {
 60.1474 +    return in - '0';
 60.1475 +  } else if (in >= 'A' && in <= 'F') {
 60.1476 +    return in - 'A' + 10;
 60.1477 +  } else if (in >= 'a' && in <= 'f') {
 60.1478 +    return in - 'a' + 10;
 60.1479 +  } else {
 60.1480 +    return -1;
 60.1481 +  }
 60.1482 +}
 60.1483 +
 60.1484 +int translateComponent(const char **in, size_t nDigits) {
 60.1485 +  const char *here = *in;
 60.1486 +  int hi = hexDigitValue(*here++);
 60.1487 +  int lo = nDigits > 3 ? hexDigitValue(*here++) : hi;
 60.1488 +  *in = here;
 60.1489 +  if (hi >= 0 && lo >= 0) {
 60.1490 +    return hi * 16 + lo;
 60.1491 +  } else {
 60.1492 +    return -1;
 60.1493 +  }
 60.1494 +}
 60.1495 +
 60.1496 +/**
 60.1497 + * Interprets a hexadecimal color specifier.
 60.1498 + * @param in hexadecimal color specifier, in #ABC or #AABBCC format
 60.1499 + * @return Allegro device-dependent color, in makecol() format
 60.1500 + */
 60.1501 +int translateColor(const char *in) {
 60.1502 +  size_t nDigits = 0;
 60.1503 +
 60.1504 +  // Verify and skip #
 60.1505 +  if (*in != '#') {
 60.1506 +    return -1;
 60.1507 +  }
 60.1508 +  ++in;
 60.1509 +
 60.1510 +  // Determine whether we have a 3-digit color or a 6-digit color
 60.1511 +  for (const char *here = in;
 60.1512 +       hexDigitValue(*here) >= 0;
 60.1513 +       ++here) {
 60.1514 +    ++nDigits;
 60.1515 +  }
 60.1516 +  if (nDigits != 3 && nDigits != 6) {
 60.1517 +    return -1;
 60.1518 +  }
 60.1519 +  
 60.1520 +  int red = translateComponent(&in, nDigits);
 60.1521 +  int green = translateComponent(&in, nDigits);
 60.1522 +  int blue = translateComponent(&in, nDigits);
 60.1523 +  if (red >= 0 && green >= 0 && blue >= 0) {
 60.1524 +    return makecol(red, green, blue);
 60.1525 +  } else {
 60.1526 +    return -1;
 60.1527 +  }
 60.1528 +};
 60.1529 +
 60.1530 +/**
 60.1531 + * Loads skin parameters from the file.
 60.1532 + * @param skinName Name of skin ini file
 60.1533 + */
 60.1534 +int loadSkinFile(LJPCSkin *s, const char *skinName) {
 60.1535 +
 60.1536 +  // Don't use ljfopen here because lj.ini specifies the absolute skin file
 60.1537 +  FILE *fp = fopen(skinName, "rt");
 60.1538 +  char key[1024], var[1024], val[1024], input_buf[1024];
 60.1539 +
 60.1540 +  key[0] = 0;
 60.1541 +  var[0] = 0;
 60.1542 +  val[0] = 0;
 60.1543 +  loadSkinDefaults(s);
 60.1544 +
 60.1545 +  if (!fp) return 0;
 60.1546 +  while(1) {
 60.1547 +    int rval;
 60.1548 +
 60.1549 +    if(!fgets (input_buf, sizeof(input_buf), fp))
 60.1550 +      break;
 60.1551 +    rval = parse_ini_line(input_buf, key, var, val);
 60.1552 +
 60.1553 +    if(!ustrcmp ("ljblocksSRS", var)) {
 60.1554 +      ustrzcpy(ljblocksSRSName, sizeof(ljblocksSRSName) - 1, val);
 60.1555 +    }
 60.1556 +    else if(!ustrcmp ("ljblocksSega", var)) {
 60.1557 +      ustrzcpy(ljblocksSegaName, sizeof(ljblocksSegaName) - 1, val);
 60.1558 +    }
 60.1559 +    else if(!ustrcmp ("ljconnSRS", var)) {
 60.1560 +      ustrzcpy(ljconnSRSName, sizeof(ljconnSRSName) - 1, val);
 60.1561 +    }
 60.1562 +    else if(!ustrcmp ("ljconnSega", var)) {
 60.1563 +      ustrzcpy(ljconnSegaName, sizeof(ljconnSegaName) - 1, val);
 60.1564 +    }
 60.1565 +    else if(!ustrcmp ("bgm", var)) {
 60.1566 +      ustrzcpy(bgmName, sizeof(bgmName) - 1, val);
 60.1567 +    }
 60.1568 +    else if(!ustrcmp ("bgmRhythm", var)) {
 60.1569 +      ustrzcpy(bgmRhythmName, sizeof(bgmRhythmName) - 1, val);
 60.1570 +    }
 60.1571 +    else if(!ustrcmp ("ljbg", var)) {
 60.1572 +      ustrzcpy(ljbgName, sizeof(ljbgName) - 1, val);
 60.1573 +    }
 60.1574 +    else if(!ustrcmp ("bgmLoopPoint", var)) {
 60.1575 +      unsigned long int c = strtoul(val, NULL, 0);
 60.1576 +      if (c >= 0) {
 60.1577 +        bgmLoopPoint = c;
 60.1578 +      }
 60.1579 +    }
 60.1580 +    else if(!ustrcmp ("bgmVolume", var)) {
 60.1581 +      unsigned long int c = strtol(val, NULL, 0);
 60.1582 +      if (c >= 0) {
 60.1583 +        bgmVolume = c;
 60.1584 +      }
 60.1585 +    }
 60.1586 +    else if(!ustrcmp ("bgmReadyGo", var)) {
 60.1587 +      unsigned long int c = strtoul(val, NULL, 0);
 60.1588 +      if (c >= 0) {
 60.1589 +        bgmReadyGo = c;
 60.1590 +      }
 60.1591 +    }
 60.1592 +    else if(!ustrcmp ("pfbgcolor", var)) {
 60.1593 +      int c = translateColor(val);
 60.1594 +      if (c >= 0) {
 60.1595 +        pfBgColor = c;
 60.1596 +      }
 60.1597 +    }
 60.1598 +    else if(!ustrcmp ("pfcolor", var)) {
 60.1599 +      int c = translateColor(val);
 60.1600 +      if (c >= 0) {
 60.1601 +        pfFgColor = c;
 60.1602 +      }
 60.1603 +    }
 60.1604 +    else if(!ustrcmp ("bgcolor", var)) {
 60.1605 +      int c = translateColor(val);
 60.1606 +      if (c >= 0) {
 60.1607 +        bgColor = c;
 60.1608 +      }
 60.1609 +    }
 60.1610 +    else if(!ustrcmp ("color", var)) {
 60.1611 +      int c = translateColor(val);
 60.1612 +      if (c >= 0) {
 60.1613 +        fgColor = c;
 60.1614 +      }
 60.1615 +    }
 60.1616 +    else if(!ustrcmp ("hilitecolor", var)) {
 60.1617 +      int c = translateColor(val);
 60.1618 +      if (c >= 0) {
 60.1619 +        hiliteColor = c;
 60.1620 +      }
 60.1621 +    }
 60.1622 +    else if(!ustrcmp ("blkW", var)) {
 60.1623 +      int c = strtol(val, NULL, 0);
 60.1624 +      if (c >= 0) {
 60.1625 +        s->blkW = c;
 60.1626 +      }
 60.1627 +    }
 60.1628 +    else if(!ustrcmp ("blkH", var)) {
 60.1629 +      int c = strtol(val, NULL, 0);
 60.1630 +      if (c >= 0) {
 60.1631 +        s->blkH = c;
 60.1632 +      }
 60.1633 +    }
 60.1634 +    else if(!ustrcmp ("transparentPF", var)) {
 60.1635 +      int c = atoi(val);
 60.1636 +      if (c >= 0) {
 60.1637 +        s->transparentPF = c;
 60.1638 +      }
 60.1639 +    }
 60.1640 +    else if(!ustrcmp ("shiftScale", var)) {
 60.1641 +      int c = atoi(val);
 60.1642 +      if (c >= 0) {
 60.1643 +        s->shiftScale = c;
 60.1644 +      }
 60.1645 +    }
 60.1646 +    else if(!ustrcmp ("baseX", var)) {
 60.1647 +      int c = atoi(val);
 60.1648 +      if (c >= 0) {
 60.1649 +        s->baseX = c;
 60.1650 +      }
 60.1651 +    }
 60.1652 +    else if(!ustrcmp ("nextPos", var)) {
 60.1653 +      int c = atoi(val);
 60.1654 +      if (c >= 0) {
 60.1655 +        s->nextPos = c;
 60.1656 +      }
 60.1657 +    }
 60.1658 +    else if(!ustrcmp ("wndW", var)) {
 60.1659 +      unsigned long int c = strtol(val, NULL, 0);
 60.1660 +      if (c >= 0) {
 60.1661 +        skinW = c;
 60.1662 +      }
 60.1663 +    }
 60.1664 +    else if(!ustrcmp ("wndH", var)) {
 60.1665 +      unsigned long int c = strtoul(val, NULL, 0);
 60.1666 +      if (c >= 0) {
 60.1667 +        skinH = c;
 60.1668 +      }
 60.1669 +    }
 60.1670 +
 60.1671 +  }
 60.1672 +  fclose(fp);
 60.1673 +  ljpathSetSkinFolder(skinName);
 60.1674 +  return 0;
 60.1675 +}
 60.1676 +
 60.1677 +static void drawProgressSegment(int min, int max) {
 60.1678 +  min = min * SCREEN_W / 100;
 60.1679 +  max = max * SCREEN_W / 100;
 60.1680 +  int blue = makecol(0, 0, 255);
 60.1681 +  rectfill(screen, min, SCREEN_H - 8, max - 1, SCREEN_H - 5, blue);
 60.1682 +  int orange = makecol(255, 128, 0);
 60.1683 +  rectfill(screen, min, SCREEN_H - 4, max - 1, SCREEN_H - 1, orange);
 60.1684 +}
 60.1685 +
 60.1686 +static int loadSkin(LJView *v, const char *skinName) {
 60.1687 +  BITMAP *bmp;
 60.1688 +  const LJRotSystem *rs = rotSystems[v->field->rotationSystem];
 60.1689 +  int colorScheme = rs->colorScheme;
 60.1690 +  
 60.1691 +  rectfill(screen, 0, 592, 799, 599, 0);
 60.1692 +  loadSkinFile(v->plat->skin, skinName);
 60.1693 +
 60.1694 +  destroyBackBuf(v->plat);
 60.1695 +  if (!createBackBuf(v)) {
 60.1696 +    allegro_message("Could not create back buffer.\n");
 60.1697 +    return 1;
 60.1698 +  }
 60.1699 +
 60.1700 +  drawProgressSegment(0, 20);
 60.1701 +  
 60.1702 +  // Load background image
 60.1703 +  char path[PATH_MAX];
 60.1704 +  bmp = ljpathFind_r(path, ljbgName)
 60.1705 +        ? load_bitmap(path, NULL) : NULL;
 60.1706 +  if (v->plat->skin->bg) {
 60.1707 +    destroy_bitmap(v->plat->skin->bg);
 60.1708 +  }
 60.1709 +  if (bmp) {
 60.1710 +    
 60.1711 +    // If the image size doesn't match the window size, resize it
 60.1712 +    if (bmp->w != SCREEN_W || bmp->h != SCREEN_H) {
 60.1713 +      BITMAP *resized = create_bitmap(SCREEN_W, SCREEN_H);
 60.1714 +
 60.1715 +      if (resized) {
 60.1716 +        stretch_blit(bmp, resized, 0, 0, bmp->w, bmp->h,
 60.1717 +                     0, 0, SCREEN_W, SCREEN_H);
 60.1718 +        destroy_bitmap(bmp);
 60.1719 +      }
 60.1720 +      if (bmp) {
 60.1721 +        bmp = resized;
 60.1722 +      } else {
 60.1723 +        allegro_message("Background image \"%s\" resize failed.\n",
 60.1724 +                        ljbgName);
 60.1725 +      }
 60.1726 +    }
 60.1727 +  }
 60.1728 +  if(!bmp) {
 60.1729 +    bmp = create_bitmap(SCREEN_W, SCREEN_H);
 60.1730 +    if (bmp) {
 60.1731 +      allegro_message("Background image \"%s\" not found.\n"
 60.1732 +                      "Using plain background instead.\n",
 60.1733 +                      ljbgName);
 60.1734 +      clear_to_color(bmp, bgColor);
 60.1735 +    } else {
 60.1736 +      allegro_message("Background image \"%s\" not found.\n",
 60.1737 +                      ljbgName);
 60.1738 +      return 0;
 60.1739 +    }
 60.1740 +  }
 60.1741 +  v->plat->skin->bg = bmp;
 60.1742 +  drawProgressSegment(20, 40);
 60.1743 +  
 60.1744 +  // load block images  
 60.1745 +  if (v->plat->skin->blocks) {
 60.1746 +    destroy_bitmap(v->plat->skin->blocks);
 60.1747 +  }
 60.1748 +  bmp = ljpathFind_r(path, colorScheme
 60.1749 +                           ? ljblocksSegaName
 60.1750 +                           : ljblocksSRSName)
 60.1751 +        ? load_bitmap(path, NULL) : NULL;
 60.1752 +  v->plat->skin->blocks = bmp;
 60.1753 +  if(!v->plat->skin->blocks) {
 60.1754 +    allegro_message("Background image \"%s\" not found.\n",
 60.1755 +                    ljbgName);
 60.1756 +    return 0;
 60.1757 +  }
 60.1758 +  drawProgressSegment(40, 60);
 60.1759 +
 60.1760 +  // load connected block images  
 60.1761 +  if (v->plat->skin->connBlocks) {
 60.1762 +    destroy_bitmap(v->plat->skin->connBlocks);
 60.1763 +  }
 60.1764 +  bmp = ljpathFind_r(path, colorScheme
 60.1765 +                           ? ljconnSegaName
 60.1766 +                           : ljconnSRSName)
 60.1767 +        ? loadConnections(path,
 60.1768 +                          v->plat->skin->blkW,
 60.1769 +                          v->plat->skin->blkH)
 60.1770 +        : NULL;
 60.1771 +  v->plat->skin->connBlocks = bmp;
 60.1772 +  drawProgressSegment(60, 80);
 60.1773 +
 60.1774 +  // load music
 60.1775 +  int isRhythm = (v->field->speedState.curve == LJSPD_RHYTHM);
 60.1776 +  if (ljpathFind_r(path, isRhythm ? bgmRhythmName : bgmName)) {
 60.1777 +    LJMusic_load(v->plat->skin->bgm, path);
 60.1778 +  }
 60.1779 +  if (!isRhythm) {
 60.1780 +    LJMusic_setLoop(v->plat->skin->bgm, bgmLoopPoint);
 60.1781 +  }
 60.1782 +  drawProgressSegment(80, 100);
 60.1783 +  return 1;
 60.1784 +}
 60.1785 +
 60.1786 +void drop_mouse(void) {
 60.1787 +  while (mouse_y < SCREEN_H - 25) {
 60.1788 +    position_mouse(mouse_x, mouse_y + 20);
 60.1789 +    rest(10);
 60.1790 +  }
 60.1791 +  ezPlaySample("land_wav", 192);
 60.1792 +  position_mouse(mouse_x, SCREEN_H - 5);
 60.1793 +  ezPlaySample("lock_wav", 192);
 60.1794 +}
 60.1795 +
 60.1796 +int pickReplay(void) {
 60.1797 +  FONT *oldFont = font;
 60.1798 +  font = (FONT *)aver16;
 60.1799 +  install_mouse();
 60.1800 +  int got = file_select_ex("Choose a demo:", demoFilename, "ljm", sizeof(demoFilename), 600, 400);
 60.1801 +  drop_mouse();
 60.1802 +  remove_mouse();
 60.1803 +  font = oldFont;
 60.1804 +  return got ? 0 : -1;
 60.1805 +}
 60.1806 +
 60.1807 +void badReplay(void) {
 60.1808 +  acquire_screen();
 60.1809 +  clear_to_color(screen, bgColor);
 60.1810 +  textout_ex(screen, aver32, "The demo", 100, 100, fgColor, -1);
 60.1811 +  textout_ex(screen, aver16, demoFilename, 100, 130, fgColor, -1);
 60.1812 +  textout_ex(screen, aver32, "could not be played because it was", 100, 150, fgColor, -1);
 60.1813 +  textout_ex(screen, aver32, "recorded with a different version", 100, 180, fgColor, -1);
 60.1814 +  textout_ex(screen, aver32, "of LOCKJAW software.", 100, 210, fgColor, -1);
 60.1815 +  release_screen();
 60.1816 +
 60.1817 +  LJBits lastKeys = ~0;
 60.1818 +  LJBits keys, newKeys = 0;
 60.1819 +  
 60.1820 +  do {
 60.1821 +    keys = menuReadPad();
 60.1822 +    newKeys = keys & ~lastKeys;
 60.1823 +    lastKeys = keys;
 60.1824 +    rest(30);
 60.1825 +  } while (!(newKeys & (VKEY_ROTL | VKEY_ROTR)));
 60.1826 +}
 60.1827 +
 60.1828 +int pickSkin(void) {
 60.1829 +  FONT *oldFont = font;
 60.1830 +  font = (FONT *)aver16;
 60.1831 +  install_mouse();
 60.1832 +  int got = file_select_ex("Choose a skin:", skinName, "skin", sizeof(skinName), 600, 400);
 60.1833 +  drop_mouse();
 60.1834 +  remove_mouse();
 60.1835 +  font = oldFont;
 60.1836 +  return got ? 0 : -1;
 60.1837 +}
 60.1838 +
 60.1839 +void calcElev(LJView *v) {
 60.1840 +  int blkH = v->plat->skin->blkH;
 60.1841 +  int ceiling = v->field->ceiling;
 60.1842 +  int elev = (LJ_PF_VIS_HT - ceiling) * blkH;
 60.1843 +
 60.1844 +  if (elev > 480 - ceiling * blkH) {
 60.1845 +    elev = 480 - ceiling * blkH;
 60.1846 +  }
 60.1847 +  if (elev < 0) {
 60.1848 +    elev = 0;
 60.1849 +  }
 60.1850 +  v->plat->skin->pfElev = elev;
 60.1851 +}
 60.1852 +
 60.1853 +int main(const int argc, const char *const *argv) {
 60.1854 +  const char *cmdLineDemo = NULL;
 60.1855 +  int lastPreset = -2;  // start out with full custom
 60.1856 +  LJPCSkin skin = {
 60.1857 +    .baseY = 552,
 60.1858 +    .blkW = 24,
 60.1859 +    .blkH = 24
 60.1860 +  };
 60.1861 +  LJField p[MAX_PLAYERS];
 60.1862 +  LJControl control[2] = {
 60.1863 +    {
 60.1864 +      .replaySrc = 0,
 60.1865 +      .replayDst = 0
 60.1866 +    }
 60.1867 +  };
 60.1868 +  LJPCView platView[MAX_PLAYERS] = {
 60.1869 +    {
 60.1870 +      .skin = &skin
 60.1871 +    },
 60.1872 +    {
 60.1873 +      .skin = &skin
 60.1874 +    }
 60.1875 +  };
 60.1876 +  LJView mainView[MAX_PLAYERS] = {
 60.1877 +    {
 60.1878 +      .field = &p[0],
 60.1879 +      .control = &control[0],
 60.1880 +      .plat = &platView[0],
 60.1881 +      .backDirty = ~0
 60.1882 +    },
 60.1883 +    {
 60.1884 +      .field = &p[1],
 60.1885 +      .control = &control[1],
 60.1886 +      .plat = &platView[1],
 60.1887 +      .backDirty = ~0,
 60.1888 +    }
 60.1889 +  };
 60.1890 +  
 60.1891 +  struct LJPrefs prefs = {
 60.1892 +    .number = {
 60.1893 +      [OPTIONS_TRAILS] = 1,
 60.1894 +      [OPTIONS_AUTO_PAUSE] = 1,
 60.1895 +      [OPTIONS_AUTO_RECORD] = 0,
 60.1896 +      [OPTIONS_WINDOWED] = 1
 60.1897 +    }
 60.1898 +  };
 60.1899 +
 60.1900 +  // as of 0.46, we're starting to make it a bit more 2-player-clean
 60.1901 +  int nPlayers = 1;  
 60.1902 +
 60.1903 +  allegro_init();
 60.1904 +  ljpathInit(argc > 0 ? argv[0] : ".");
 60.1905 +  install_timer();
 60.1906 +  initOptions(prefs.number);
 60.1907 +  loadOptions(&prefs);
 60.1908 +  loadSkinFile(&skin, skinName);
 60.1909 +
 60.1910 +  if (argc > 1) {
 60.1911 +    if (argv[1][0] == '-') {
 60.1912 +      if (!ustrcmp("--help", argv[1])
 60.1913 +          || !ustrcmp("-h", argv[1])) {
 60.1914 +        allegro_message("Usage: lj [DEMOFILE]\n");
 60.1915 +        return 0;
 60.1916 +      }
 60.1917 +    } else {
 60.1918 +      cmdLineDemo = argv[1];
 60.1919 +    }
 60.1920 +  }
 60.1921 +
 60.1922 +  if (openWindow(prefs.number[OPTIONS_WINDOWED]) != 0) {
 60.1923 +    allegro_message("LOCKJAW fatal error: Could not open an %dx%d pixel %s.\n"
 60.1924 +                    "Trying %s next time.\n",
 60.1925 +                    skinW, skinH,
 60.1926 +                    prefs.number[OPTIONS_WINDOWED] ? "window": "screen mode",
 60.1927 +                    prefs.number[OPTIONS_WINDOWED] ? "the full screen": "a window");
 60.1928 +    prefs.number[OPTIONS_WINDOWED] = !prefs.number[OPTIONS_WINDOWED];
 60.1929 +    saveOptions(&prefs);
 60.1930 +    return EXIT_FAILURE;
 60.1931 +  }
 60.1932 +  drawCoprNotice();
 60.1933 +  LOCK_FUNCTION(incCurTime);
 60.1934 +  LOCK_VARIABLE(curTime);
 60.1935 +  install_int_ex(incCurTime, BPM_TO_TIMER(LJ_TICK_RATE));
 60.1936 +  
 60.1937 +  jpgalleg_init();
 60.1938 +  set_color_conversion(COLORCONV_NONE);
 60.1939 +  {
 60.1940 +    char path[PATH_MAX];
 60.1941 +    if (ljpathFind_r(path, "lj.dat")) {
 60.1942 +      dat = load_datafile(path);
 60.1943 +    }
 60.1944 +  }
 60.1945 +  set_color_conversion(COLORCONV_TOTAL);
 60.1946 +  if(!dat) {
 60.1947 +    closeWindow();
 60.1948 +    allegro_message("LOCKJAW fatal error: Could not load datafile lj.dat\n");
 60.1949 +    return 1;
 60.1950 +  }
 60.1951 +
 60.1952 +  {
 60.1953 +    const DATAFILE *aver16dat = find_datafile_object(dat, "Aver16_bmp");
 60.1954 +    aver16 = aver16dat ? aver16dat->dat : font;
 60.1955 +    const DATAFILE *aver32dat = find_datafile_object(dat, "Aver32_bmp");
 60.1956 +    aver32 = aver32dat ? aver32dat->dat : aver16;
 60.1957 +  }
 60.1958 +
 60.1959 +  LOCK_FUNCTION(amnesia);
 60.1960 +  LOCK_VARIABLE(redrawWholeScreen);
 60.1961 +  
 60.1962 +  // If we can be notified on switching out, take this notification. 
 60.1963 +  if (set_display_switch_mode(SWITCH_BACKGROUND) >= 0
 60.1964 +      || set_display_switch_mode(SWITCH_BACKAMNESIA) >= 0) {
 60.1965 +    set_display_switch_callback(SWITCH_OUT, requestPause);
 60.1966 +  }
 60.1967 +  set_display_switch_callback(SWITCH_IN, amnesia);
 60.1968 +
 60.1969 +  install_keyboard();
 60.1970 +  initKeys();
 60.1971 +
 60.1972 +  for (int i = 0; i < MAX_PLAYERS; ++i) {
 60.1973 +    p[i].seed = time(NULL) + 123456789*i;
 60.1974 +  }
 60.1975 +
 60.1976 +  reserve_voices(8, 0);
 60.1977 +  set_volume_per_voice(0);
 60.1978 +#ifdef ALLEGRO_WINDOWS
 60.1979 +  // Under Windows, use the Allegro mixer because on my machine
 60.1980 +  // and probably others, the built-in mixer will replace the very
 60.1981 +  // beginning of one sound with the end of the last sound played
 60.1982 +  // on that voice.
 60.1983 +  withSound = !install_sound(DIGI_DIRECTAMX(0), MIDI_NONE, NULL);
 60.1984 +#else
 60.1985 +  withSound = !install_sound(DIGI_AUTODETECT, MIDI_NONE, NULL);
 60.1986 +#endif
 60.1987 +  skin.bgm = LJMusic_new();
 60.1988 +  {
 60.1989 +    char path[PATH_MAX];
 60.1990 +    if (ljpathFind_r(path, "sound.dat")) {
 60.1991 +      sound_dat = load_datafile(path);
 60.1992 +    }
 60.1993 +  }
 60.1994 +
 60.1995 +  for (int i = 0; i < MAX_PLAYERS; ++i) {
 60.1996 +    unpackOptions(&mainView[i], &prefs);
 60.1997 +  }
 60.1998 +  if (loadSkin(&mainView[0], skinName) < 0) {
 60.1999 +    mainCleanup(platView);
 60.2000 +    return EXIT_FAILURE;
 60.2001 +  } else {
 60.2002 +  
 60.2003 +  }
 60.2004 +
 60.2005 +  if(!skin.blocks) {
 60.2006 +    mainCleanup(platView);
 60.2007 +    allegro_message("Blocks image \"%s\" not found.\n",
 60.2008 +                    p[0].rotationSystem
 60.2009 +                    ? ljblocksSegaName
 60.2010 +                    : ljblocksSRSName);
 60.2011 +    return 1;
 60.2012 +  }
 60.2013 +
 60.2014 +  srand(time(NULL));
 60.2015 +
 60.2016 +  // Wait for copyright notice to be displayed "conspicuously"
 60.2017 +  if (curTime < 180) {
 60.2018 +    rest(3100 - curTime * 16);
 60.2019 +  }
 60.2020 +
 60.2021 +  for (int action = cmdLineDemo ? TITLE_REPLAY : title();
 60.2022 +        action > TITLE_EXIT && !wantsClose;
 60.2023 +        action = title()) {
 60.2024 +    switch (action) {
 60.2025 +    case TITLE_PLAY:
 60.2026 +      for (int preset = getPreset(lastPreset);
 60.2027 +           preset != -1 && !wantsClose;
 60.2028 +           preset = getPreset(lastPreset))
 60.2029 +      {
 60.2030 +        lastPreset = preset;
 60.2031 +        for (int i = 0; i < MAX_PLAYERS; ++i) {
 60.2032 +          unpackOptions(&mainView[i], &prefs);
 60.2033 +        }
 60.2034 +        if (preset >= 0) {
 60.2035 +          presetStart();
 60.2036 +          presetAdd(preset);
 60.2037 +          for (int i = 0; i < MAX_PLAYERS; ++i) {
 60.2038 +            unpackOptions(&mainView[i], &prefs);
 60.2039 +            presetFinish(&mainView[i]);
 60.2040 +          }
 60.2041 +        }
 60.2042 +      // reload the skin
 60.2043 +      if (loadSkin(&mainView[0], skinName) < 0) {
 60.2044 +        mainCleanup(platView);
 60.2045 +        return EXIT_FAILURE;
 60.2046 +      };
 60.2047 +        for (int i = 0; i < nPlayers; ++i) {
 60.2048 +          calcElev(&mainView[0]);
 60.2049 +          pcInit(&mainView[0], &prefs);
 60.2050 +        }
 60.2051 +        wantPause = 0;
 60.2052 +        platView[0].wantRecord = prefs.number[OPTIONS_AUTO_RECORD];
 60.2053 +        LJMusic_start(skin.bgm,
 60.2054 +                      4096,  // mix buffer size
 60.2055 +                      bgmVolume);  // volume scale
 60.2056 +
 60.2057 +        LJView *const players[MAX_PLAYERS] = {&mainView[0], &mainView[1]};
 60.2058 +        play(players, nPlayers);
 60.2059 +        LJMusic_stop(skin.bgm);
 60.2060 +        if (!wantsClose) {
 60.2061 +          gameOverAnimation(&platView[0], &p[0],
 60.2062 +                            control[0].countdown <= 0);
 60.2063 +          debrief(&mainView[0]);
 60.2064 +        }
 60.2065 +      }
 60.2066 +      break;
 60.2067 +
 60.2068 +    case TITLE_REPLAY:
 60.2069 +      {
 60.2070 +        if (cmdLineDemo) {
 60.2071 +          ustrzcpy(demoFilename, sizeof(demoFilename) - 1,
 60.2072 +                   cmdLineDemo);
 60.2073 +          cmdLineDemo = NULL;
 60.2074 +        } else if (pickReplay() < 0) {
 60.2075 +          break;
 60.2076 +        }
 60.2077 +
 60.2078 +        unpackOptions(&mainView[0], &prefs);
 60.2079 +        calcElev(&mainView[0]);
 60.2080 +        pcInit(&mainView[0], &prefs);
 60.2081 +        wantPause = 0;
 60.2082 +        platView[0].wantRecord = 0;
 60.2083 +        LJMusic_start(skin.bgm,
 60.2084 +                      4096,  // mix buffer size
 60.2085 +                      bgmVolume);  // volume scale
 60.2086 +
 60.2087 +        LJView *const players[2] = {&mainView[0], &mainView[1]};
 60.2088 +
 60.2089 +        p->gimmick = -1;  // gimmick must be < 0 to activate replay
 60.2090 +        play(players, 1);
 60.2091 +        LJMusic_stop(skin.bgm);
 60.2092 +        if (p[0].gimmick < 0) {
 60.2093 +          badReplay();
 60.2094 +        } else {
 60.2095 +          if (!wantsClose) {
 60.2096 +            gameOverAnimation(&platView[0], &p[0],
 60.2097 +                              control[0].countdown <= 0);
 60.2098 +            debrief(&mainView[0]);
 60.2099 +          }
 60.2100 +        }
 60.2101 +      }
 60.2102 +      break;
 60.2103 +
 60.2104 +    case TITLE_SKIN:
 60.2105 +      pickSkin();
 60.2106 +
 60.2107 +      // if resolution changed, reopen the window
 60.2108 +      {
 60.2109 +        int oldW = skinW;
 60.2110 +        int oldH = skinH;
 60.2111 +        loadSkinFile(&skin, skinName);
 60.2112 +        if (skinH != oldH || skinW != oldW) {
 60.2113 +          destroySystemBitmaps(&platView[0]);
 60.2114 +          openWindow(prefs.number[OPTIONS_WINDOWED]);
 60.2115 +        }
 60.2116 +      }
 60.2117 +
 60.2118 +      // reload the skin
 60.2119 +      if (loadSkin(&mainView[0], skinName) < 0) {
 60.2120 +        mainCleanup(platView);
 60.2121 +        return EXIT_FAILURE;
 60.2122 +      };
 60.2123 +      
 60.2124 +      // save options
 60.2125 +      saveOptions(&prefs);
 60.2126 +      break;
 60.2127 +
 60.2128 +    case TITLE_OPTIONS:
 60.2129 +      {
 60.2130 +        int oldWindowed = prefs.number[OPTIONS_WINDOWED];
 60.2131 +        options(&mainView[0], prefs.number);
 60.2132 +        if (oldWindowed != prefs.number[OPTIONS_WINDOWED]) {
 60.2133 +          destroySystemBitmaps(&platView[0]);
 60.2134 +          openWindow(prefs.number[OPTIONS_WINDOWED]);
 60.2135 +        }
 60.2136 +      }
 60.2137 +      saveOptions(&prefs);
 60.2138 +
 60.2139 +      // reload the skin if the player changed the rotation system
 60.2140 +      unpackOptions(&mainView[0], &prefs);
 60.2141 +      if (wantsClose) {
 60.2142 +        break;
 60.2143 +      }
 60.2144 +      if (loadSkin(&mainView[0], skinName) < 0) {
 60.2145 +        mainCleanup(platView);
 60.2146 +        return EXIT_FAILURE;
 60.2147 +      };
 60.2148 +      break;
 60.2149 +
 60.2150 +    case TITLE_KEYS:
 60.2151 +      configureKeys();
 60.2152 +      break;
 60.2153 +    
 60.2154 +    }
 60.2155 +  }
 60.2156 +  
 60.2157 +  mainCleanup(platView);
 60.2158 +  return 0;
 60.2159 +} END_OF_MAIN();
    61.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    61.2 +++ b/src/ljpc.h	Fri Mar 13 00:39:12 2009 -0700
    61.3 @@ -0,0 +1,119 @@
    61.4 +/* Header for PC frontend for LOCKJAW, an implementation of the Soviet Mind Game
    61.5 +
    61.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    61.7 +
    61.8 +This work is free software; you can redistribute it and/or modify
    61.9 +it under the terms of the GNU General Public License as published by
   61.10 +the Free Software Foundation; either version 2 of the License, or
   61.11 +(at your option) any later version.
   61.12 +
   61.13 +This program is distributed in the hope that it will be useful,
   61.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   61.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   61.16 +GNU General Public License for more details.
   61.17 +
   61.18 +You should have received a copy of the GNU General Public License
   61.19 +along with this program; if not, write to the Free Software
   61.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   61.21 +
   61.22 +Original game concept and design by Alexey Pajitnov.
   61.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   61.24 +or The Tetris Company LLC.
   61.25 +
   61.26 +*/
   61.27 +
   61.28 +#ifndef LJPC_H
   61.29 +#define LJPC_H
   61.30 +#include <allegro.h>
   61.31 +#include "ljmusic.h"
   61.32 +#include "ljplay.h"
   61.33 +#include "pcjoy.h"
   61.34 +#include "ljvorbis.h"
   61.35 +#include "options.h"
   61.36 +
   61.37 +typedef struct LJPCSkin {
   61.38 +  BITMAP *bg;
   61.39 +  BITMAP *blocks;
   61.40 +  BITMAP *connBlocks;
   61.41 +  BITMAP *fullback;
   61.42 +  unsigned short blkW, blkH;
   61.43 +  unsigned short baseX, baseY;
   61.44 +  unsigned short pfElev;
   61.45 +  unsigned char transparentPF;
   61.46 +  unsigned char nextPos;
   61.47 +  unsigned char shiftScale;
   61.48 +  LJMusic *bgm;
   61.49 +} LJPCSkin;
   61.50 +
   61.51 +typedef struct LJPCView
   61.52 +{
   61.53 +  BITMAP *back;
   61.54 +  LJPCSkin *skin;
   61.55 +  unsigned short baseX;
   61.56 +  unsigned char nextAbove;  // number of next pieces above shadow
   61.57 +  
   61.58 +  // Platform-dependent sound
   61.59 +  unsigned char b2bcd1, b2bcd2;
   61.60 +
   61.61 +  unsigned char wantRecord;
   61.62 +} LJPCView;
   61.63 +
   61.64 +extern char skinName[PATH_MAX];
   61.65 +extern char ljblocksSRSName[PATH_MAX];
   61.66 +extern char ljblocksSegaName[PATH_MAX];
   61.67 +extern char ljconnSRSName[PATH_MAX];
   61.68 +extern char ljconnSegaName[PATH_MAX];
   61.69 +extern char ljbgName[PATH_MAX];
   61.70 +extern char bgmName[PATH_MAX];
   61.71 +
   61.72 +
   61.73 +#define N_GIMMICKS LJGM_N_GIMMICKS
   61.74 +
   61.75 +
   61.76 +enum {
   61.77 +  LJNEXT_RIGHT = 0,
   61.78 +  LJNEXT_RIGHT_TAPER,
   61.79 +  LJNEXT_TOP,
   61.80 +  LJNEXT_N_STYLES
   61.81 +};
   61.82 +
   61.83 +/**
   61.84 + * The number of persistent preferences.  Must match the number of
   61.85 + * fields in struct LJPrefsNamed.
   61.86 + */
   61.87 +
   61.88 +/**
   61.89 + * Names of persistent preferences.
   61.90 + * Order must be exactly the same as in
   61.91 + * optionsMenu[] (options.c)
   61.92 + */
   61.93 +enum {
   61.94 +  OPTIONS_NEXT_ABOVE = OPTIONS_MENU_LEN,
   61.95 +  OPTIONS_TRAILS,
   61.96 +  OPTIONS_AUTO_PAUSE,
   61.97 +  OPTIONS_AUTO_RECORD,
   61.98 +  OPTIONS_WINDOWED,
   61.99 +  PC_OPTIONS_MENU_LEN
  61.100 +};
  61.101 +struct LJPrefs {
  61.102 +  unsigned char number[PC_OPTIONS_MENU_LEN];
  61.103 +};
  61.104 +
  61.105 +
  61.106 +/* set by display mode */
  61.107 +extern int pfBgColor, pfFgColor, bgColor, fgColor, hiliteColor;
  61.108 +extern const FONT *aver32, *aver16;
  61.109 +extern volatile char redrawWholeScreen;
  61.110 +extern char autoPause;
  61.111 +
  61.112 +void saveScreen(int n);
  61.113 +void ezPlaySample(const char *filename, int vol);
  61.114 +int parse_ini_line(const char *in, char *key, char *var, char *val);
  61.115 +int loadOptions(struct LJPrefs *prefs);
  61.116 +void saveOptions(const struct LJPrefs *prefs);
  61.117 +void options(LJView *v, unsigned char *prefs);
  61.118 +void unpackOptions(LJView *v, const struct LJPrefs *prefs);
  61.119 +void debrief(LJView *v);
  61.120 +
  61.121 +
  61.122 +#endif
    62.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    62.2 +++ b/src/ljplay.c	Fri Mar 13 00:39:12 2009 -0700
    62.3 @@ -0,0 +1,216 @@
    62.4 +/* Game loop frontend for LOCKJAW, an implementation of the Soviet Mind Game
    62.5 +
    62.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    62.7 +
    62.8 +This work is free software; you can redistribute it and/or modify
    62.9 +it under the terms of the GNU General Public License as published by
   62.10 +the Free Software Foundation; either version 2 of the License, or
   62.11 +(at your option) any later version.
   62.12 +
   62.13 +This program is distributed in the hope that it will be useful,
   62.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   62.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   62.16 +GNU General Public License for more details.
   62.17 +
   62.18 +You should have received a copy of the GNU General Public License
   62.19 +along with this program; if not, write to the Free Software
   62.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   62.21 +
   62.22 +Original game concept and design by Alexey Pajitnov.
   62.23 +The Software is not sponsored or endorsed by Alexey Pajitnov[player], Elorg,
   62.24 +or The Tetris Company LLC.
   62.25 +
   62.26 +*/
   62.27 +
   62.28 +#include "ljplay.h"
   62.29 +#ifndef WITH_REPLAY
   62.30 +#define WITH_REPLAY 0
   62.31 +#endif
   62.32 +
   62.33 +#if WITH_REPLAY
   62.34 +#include "ljreplay.h"
   62.35 +extern const char demoFilename[];
   62.36 +
   62.37 +#endif
   62.38 +
   62.39 +
   62.40 +void play(LJView *const v[], size_t nPlayers) {
   62.41 +  int canceled = 0;
   62.42 +
   62.43 +  for (unsigned int player = 0; player < nPlayers; ++player) {
   62.44 +    LJField *const p = v[player]->field;
   62.45 +    LJControl *const ctl = v[player]->control;
   62.46 +
   62.47 +    ctl->countdown = 6;
   62.48 +    ctl->presses = 0;
   62.49 +
   62.50 +    /* Load replay if needed */
   62.51 +#if WITH_REPLAY
   62.52 +    if (p->gimmick < 0) {
   62.53 +      ctl->replaySrc = openReplay(demoFilename, p);
   62.54 +      if (!ctl->replaySrc) {
   62.55 +        return;
   62.56 +      }
   62.57 +      v[player]->backDirty = ~0;
   62.58 +      playRedrawScreen(v[player]);
   62.59 +    } else {
   62.60 +      ctl->replaySrc = 0;
   62.61 +    }
   62.62 +#endif
   62.63 +  }
   62.64 +  if (!v[0]->control->replaySrc) {
   62.65 +    for (unsigned int player = 0; player < nPlayers; ++player) {
   62.66 +      LJField *const p = v[player]->field;
   62.67 +      newGame(p);
   62.68 +      initGimmicks(p);
   62.69 +      v[player]->nLockTimes = 0;
   62.70 +      v[player]->hideNext = 0;
   62.71 +      updField(v[player], ~0);
   62.72 +    }
   62.73 +    startingAnimation(v[0]);
   62.74 +    for (unsigned int player = 0; player < nPlayers; ++player) {
   62.75 +      v[player]->control->lastKeys = 0;
   62.76 +      v[player]->control->repressKeys = 0;
   62.77 +      blitField(v[player]);
   62.78 +    }
   62.79 +  }
   62.80 +
   62.81 +  int lastTime = getTime();
   62.82 +
   62.83 +  while(v[0]->field->state != LJS_GAMEOVER && !canceled) {
   62.84 +    if (getTime() == lastTime) {
   62.85 +      yieldCPU();
   62.86 +    }
   62.87 +    canceled |= ljHandleConsoleButtons(v[0]);
   62.88 +    while (getTime() - lastTime > 0) {
   62.89 +      for (unsigned int player = 0; player < nPlayers; ++player) {
   62.90 +        LJField *const p = v[player]->field;
   62.91 +        LJControl *const ctl = v[player]->control;
   62.92 +        LJInput in = {0, 0, 0, 0};
   62.93 +        int curTime = getTime();
   62.94 +      
   62.95 +        addKeysToInput(&in, readPad(player), p, ctl);
   62.96 +
   62.97 +        // when returning from pause, catch up the speed control
   62.98 +        if (curTime - lastTime > 10
   62.99 +            || curTime - lastTime < -10) {
  62.100 +          lastTime = curTime;
  62.101 +        }
  62.102 +
  62.103 +        {
  62.104 +          int irsAttempted = (p->sounds & (LJSND_SPAWN | LJSND_HOLD))
  62.105 +                             && in.rotation && ctl->initialRotate;
  62.106 +                           
  62.107 +          // The next line does ALL the game logic.
  62.108 +          LJBits updatedRows = frame(p, &in) | gimmicks(p, ctl);
  62.109 +        
  62.110 +          v[player]->backDirty |= updatedRows;
  62.111 +          if (irsAttempted && (p->sounds & LJSND_ROTATE)) {
  62.112 +            p->sounds |= LJSND_IRS;
  62.113 +          }
  62.114 +        }
  62.115 +
  62.116 +        // items is a partly view-based gimmick
  62.117 +        if (p->gimmick == LJGM_ITEMS && (p->sounds & LJSND_SPAWN)) {
  62.118 +          v[player]->hideNext = (v[player]->field->nPieces > 7);
  62.119 +          v[player]->frontDirty |= LJ_DIRTY_NEXT;
  62.120 +        }
  62.121 +
  62.122 +        // five, four, three, two, one
  62.123 +        int curCountdown = ctl->countdown;
  62.124 +        if (p->gimmick == LJGM_ULTRA && p->gameTime >= 10500) {
  62.125 +          curCountdown = (int)(10859 - p->gameTime) / 60;
  62.126 +        } else if (p->gimmick == LJGM_BTYPE) {
  62.127 +          curCountdown = 40 - p->lines;
  62.128 +        } else if (p->gimmick == LJGM_BABY) {
  62.129 +          curCountdown = (309 - v[player]->control->presses) / 10;
  62.130 +        } else if (p->gimmick == LJGM_DRILL && (p->sounds & LJSND_LINE)) {
  62.131 +          int line = bfffo(p->tempRows);
  62.132 +          if (line < curCountdown)
  62.133 +          curCountdown = line;
  62.134 +        }
  62.135 +      
  62.136 +        // we have to wait for the new piece to come out
  62.137 +        // so that the score is credited properly
  62.138 +        if (curCountdown <= 0
  62.139 +            && (p->state == LJS_NEW_PIECE
  62.140 +                || p->state == LJS_FALLING
  62.141 +                || p->state == LJS_LANDED)) {
  62.142 +          p->state = LJS_GAMEOVER;
  62.143 +        }
  62.144 +      
  62.145 +        playSoundEffects(v[player], p->sounds, curCountdown);
  62.146 +        ctl->countdown = curCountdown;
  62.147 +
  62.148 +        // Update speedometer
  62.149 +        if (p->sounds & LJSND_LOCK) {
  62.150 +          for (int i = N_SPEED_METER_PIECES - 2; i >= 0; --i) {
  62.151 +            v[player]->lockTime[i + 1] = v[player]->lockTime[i];
  62.152 +          }
  62.153 +          v[player]->lockTime[0] = p->gameTime;
  62.154 +          if (v[player]->nLockTimes < N_SPEED_METER_PIECES) {
  62.155 +            ++v[player]->nLockTimes;
  62.156 +          }
  62.157 +          v[player]->frontDirty = LJ_DIRTY_SCORE;
  62.158 +        }
  62.159 +
  62.160 +        // If the piece was just spawned, move the trail to the piece's
  62.161 +        // starting position and redraw next pieces.
  62.162 +        if (p->sounds & (LJSND_SPAWN | LJSND_HOLD)) {
  62.163 +          v[player]->trailY = p->y;
  62.164 +          v[player]->frontDirty |= LJ_DIRTY_NEXT;
  62.165 +        }
  62.166 +      }
  62.167 +
  62.168 +      ++lastTime;
  62.169 +    }
  62.170 +
  62.171 +    for (unsigned int player = 0; player < nPlayers; ++player) {
  62.172 +      LJField *const p = v[player]->field;
  62.173 +      if (p->state == LJS_GAMEOVER && v[player]->hidePF) {
  62.174 +        v[player]->hidePF = 0;
  62.175 +        v[player]->backDirty |= (1 << LJ_PF_VIS_HT) - 1;
  62.176 +      }
  62.177 +    
  62.178 +      updField(v[player], v[player]->backDirty);
  62.179 +      v[player]->frontDirty |= v[player]->backDirty;
  62.180 +      v[player]->backDirty = 0;
  62.181 +    
  62.182 +      // If piece is falling or landed, and it wasn't just spawned,
  62.183 +      // draw the piece and its shadow.
  62.184 +
  62.185 +      if (p->sounds & (LJSND_SPAWN | LJSND_HOLD)) {
  62.186 +        // piece was just spawned, so don't draw the piece
  62.187 +      }
  62.188 +      // Otherwise, if the piece is falling or landed, draw it.
  62.189 +      else if (p->state == LJS_FALLING || p->state == LJS_LANDED
  62.190 +                || p->sounds & LJSND_LOCK) {
  62.191 +        if (v[player]->hideShadow != LJSHADOW_NO_FALLING) {
  62.192 +          if (p->state == LJS_FALLING && v[player]->hideShadow < LJSHADOW_NONE) {
  62.193 +            drawShadow(v[player]);
  62.194 +          }
  62.195 +          drawFallingPiece(v[player]);
  62.196 +        }
  62.197 +      }
  62.198 +
  62.199 +      ljBeginDraw(v[player], getTime() - lastTime < 2);
  62.200 +      drawScore(v[player]);
  62.201 +      blitField(v[player]);
  62.202 +      ljEndDraw(v[player]);
  62.203 +    }
  62.204 +  }
  62.205 +  
  62.206 +#if WITH_REPLAY
  62.207 +  {
  62.208 +    int player = 0;
  62.209 +    if (v[player]->control->replaySrc) {
  62.210 +      replayClose(v[player]->control->replaySrc);
  62.211 +      v[player]->control->replaySrc = NULL;
  62.212 +    }
  62.213 +    if (v[player]->control->replayDst) {
  62.214 +      replayClose(v[player]->control->replayDst);
  62.215 +      v[player]->control->replayDst = NULL;
  62.216 +    }
  62.217 +  }
  62.218 +#endif
  62.219 +}
    63.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    63.2 +++ b/src/ljplay.h	Fri Mar 13 00:39:12 2009 -0700
    63.3 @@ -0,0 +1,57 @@
    63.4 +/* Platform hooks for LOCKJAW, an implementation of the Soviet Mind Game
    63.5 +
    63.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    63.7 +
    63.8 +This work is free software; you can redistribute it and/or modify
    63.9 +it under the terms of the GNU General Public License as published by
   63.10 +the Free Software Foundation; either version 2 of the License, or
   63.11 +(at your option) any later version.
   63.12 +
   63.13 +This program is distributed in the hope that it will be useful,
   63.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   63.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   63.16 +GNU General Public License for more details.
   63.17 +
   63.18 +You should have received a copy of the GNU General Public License
   63.19 +along with this program; if not, write to the Free Software
   63.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   63.21 +
   63.22 +Original game concept and design by Alexey Pajitnov.
   63.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   63.24 +or The Tetris Company LLC.
   63.25 +
   63.26 +*/
   63.27 +
   63.28 +#ifndef LJPLAY_H
   63.29 +#define LJPLAY_H
   63.30 +#include "lj.h"
   63.31 +#include "ljcontrol.h"
   63.32 +
   63.33 +/**
   63.34 + * Plays Lockjaw.
   63.35 + * @param v view
   63.36 + */
   63.37 +void play(LJView *const v[], size_t nPlayers);
   63.38 +
   63.39 +/*
   63.40 + * Platform-native code must implement the following callbacks,
   63.41 + * which will be described later:
   63.42 + */
   63.43 +LJBits readPad(unsigned int player);
   63.44 +void updField(const LJView *const v, LJBits rows);
   63.45 +void startingAnimation(LJView *v);
   63.46 +void blitField(LJView *v);
   63.47 +int pauseGame(struct LJPCView *v);
   63.48 +void playSoundEffects(LJView *v, LJBits sounds, int countdown);
   63.49 +void drawShadow(LJView *v);
   63.50 +void drawFallingPiece(LJView *v);
   63.51 +void drawScore(LJView *v);
   63.52 +int getTime(void);
   63.53 +void yieldCPU(void);
   63.54 +void ljBeginDraw(LJView *v, int sync);
   63.55 +void ljEndDraw(LJView *v);
   63.56 +int ljHandleConsoleButtons(LJView *v);
   63.57 +void playRedrawScreen(LJView *v);
   63.58 +
   63.59 +
   63.60 +#endif
    64.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    64.2 +++ b/src/ljreplay.c	Fri Mar 13 00:39:12 2009 -0700
    64.3 @@ -0,0 +1,335 @@
    64.4 +/* Replay functionality for LOCKJAW, an implementation of the Soviet Mind Game
    64.5 +
    64.6 +Copyright (C) 2006-2008 Damian Yerrick <tepples+lj@spamcop.net>
    64.7 +
    64.8 +This work is free software; you can redistribute it and/or modify
    64.9 +it under the terms of the GNU General Public License as published by
   64.10 +the Free Software Foundation; either version 2 of the License, or
   64.11 +(at your option) any later version.
   64.12 +
   64.13 +This program is distributed in the hope that it will be useful,
   64.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   64.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   64.16 +GNU General Public License for more details.
   64.17 +
   64.18 +You should have received a copy of the GNU General Public License
   64.19 +along with this program; if not, write to the Free Software
   64.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   64.21 +
   64.22 +Original game concept and design by Alexey Pajitnov.
   64.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   64.24 +or The Tetris Company LLC.
   64.25 +
   64.26 +*/
   64.27 +
   64.28 +#include "ljreplay.h"
   64.29 +#include <stdio.h>
   64.30 +
   64.31 +#define FORMAT_VERSION 0x20080525
   64.32 +#define LAST_FORMAT_VERSION 20080420
   64.33 +#define DEBUG_OFFSETS 0
   64.34 +
   64.35 +#if DEBUG_OFFSETS
   64.36 +#include <allegro.h>
   64.37 +#endif
   64.38 +
   64.39 +struct LJReplayFrame {
   64.40 +  char length;
   64.41 +  char reserved;
   64.42 +  unsigned short keys;
   64.43 +  LJInput x;
   64.44 +};
   64.45 +
   64.46 +struct LJReplay {
   64.47 +  FILE *file;
   64.48 +};
   64.49 +
   64.50 +static void fput32(unsigned int src, FILE *dst) {
   64.51 +  fputc(src >> 24, dst);
   64.52 +  fputc(src >> 16, dst);
   64.53 +  fputc(src >> 8, dst);
   64.54 +  fputc(src, dst);
   64.55 +}
   64.56 +
   64.57 +static unsigned int fget32(FILE *src) {
   64.58 +  int c0 = fgetc(src) & 0xFF;
   64.59 +  c0 = (c0 << 8) | (fgetc(src) & 0xFF);
   64.60 +  c0 = (c0 << 8) | (fgetc(src) & 0xFF);
   64.61 +  c0 = (c0 << 8) | (fgetc(src) & 0xFF);
   64.62 +  return c0;
   64.63 +}
   64.64 +static void fput16(unsigned int src, FILE *dst) {
   64.65 +  fputc(src >> 8, dst);
   64.66 +  fputc(src, dst);
   64.67 +}
   64.68 +
   64.69 +static unsigned int fget16(FILE *src) {
   64.70 +  int c0 = fgetc(src) & 0xFF;
   64.71 +  c0 = (c0 << 8) | (fgetc(src) & 0xFF);
   64.72 +  return c0;
   64.73 +}
   64.74 +
   64.75 +static void LJField_serialize(const LJField *p, FILE *fp) {
   64.76 +  fput32(FORMAT_VERSION, fp);
   64.77 +
   64.78 +  for (int y = 0; y < LJ_PF_HT; ++y) {
   64.79 +    for (int x = 0; x < LJ_PF_WID; ++x) {
   64.80 +      fputc(p->b[y][x], fp);
   64.81 +    }
   64.82 +  }
   64.83 +  for (int y = 0; y < LJ_PF_HT; ++y) {
   64.84 +    for (int x = 0; x < LJ_PF_WID; ++x) {
   64.85 +      fputc(p->c[y][x], fp);
   64.86 +    }
   64.87 +  }
   64.88 +  fput32(p->clearedLines, fp);
   64.89 +  fput32(p->sounds, fp);
   64.90 +  fput32(p->tempRows, fp);
   64.91 +  for (int y = 0; y < 1 + LJ_NEXT_PIECES; ++y) {
   64.92 +    fputc(p->curPiece[y], fp);
   64.93 +  }
   64.94 +  for (int y = 0; y < 2 * MAX_BAG_LEN; ++y) {
   64.95 +    fputc(p->permuPiece[y], fp);
   64.96 +  }
   64.97 +  fputc(p->permuPhase, fp);
   64.98 +  for (int y = 0; y < LJ_MAX_LINES_PER_PIECE; ++y) {
   64.99 +    fput16(p->nLineClears[y], fp);
  64.100 +  }
  64.101 +  fput32(p->y, fp);
  64.102 +  fputc(p->state, fp);
  64.103 +  fputc(p->stateTime, fp);
  64.104 +  fputc(p->theta, fp);
  64.105 +  fputc(p->x, fp);
  64.106 +  fputc(p->hardDropY, fp);
  64.107 +  fputc(p->alreadyHeld, fp);
  64.108 +  fputc(p->isSpin, fp);
  64.109 +  fputc(p->nLinesThisPiece, fp);
  64.110 +  fputc(p->canRotate, fp);
  64.111 +
  64.112 +#if DEBUG_OFFSETS
  64.113 +  allegro_message("before score, offset = %u\n", (unsigned int)ftell(fp));
  64.114 +#endif
  64.115 +  fput32(p->score, fp);
  64.116 +  fput32(p->lines, fp);
  64.117 +  fput32(p->gameTime, fp);
  64.118 +  fput32(p->activeTime, fp);
  64.119 +  fput16(p->holdPiece, fp);
  64.120 +  fputc(p->chain, fp);
  64.121 +  fputc(p->garbage, fp);
  64.122 +  fputc(p->garbageX, fp);
  64.123 +  fput16(p->nPieces, fp);
  64.124 +  fput16(p->outGarbage, fp);
  64.125 +  
  64.126 +  fputc(p->gimmick, fp);
  64.127 +  fput32(p->speedState.level, fp);
  64.128 +  fput32(p->bpmCounter, fp);
  64.129 +  fput32(p->speedupCounter, fp);
  64.130 +  fput32(p->goalCount, fp);
  64.131 +  fput32(p->seed, fp);
  64.132 +  fput32(p->speed.gravity, fp);
  64.133 +  fputc(p->speedState.curve, fp);
  64.134 +  fputc(p->goalType, fp);
  64.135 +  fputc(p->speed.entryDelay, fp);
  64.136 +  fputc(p->areStyle, fp);
  64.137 +  fputc(p->lockReset, fp);
  64.138 +  fputc(p->speed.lockDelay, fp);
  64.139 +  fputc(p->speed.lineDelay, fp);
  64.140 +  fputc(p->ceiling, fp);
  64.141 +  fputc(p->enterAbove, fp);
  64.142 +  fputc(p->leftWall, fp);
  64.143 +  fputc(p->rightWall, fp);
  64.144 +  fputc(p->pieceSet, fp);
  64.145 +  fputc(p->randomizer, fp);
  64.146 +  fputc(p->rotationSystem, fp);
  64.147 +  fputc(p->garbageRandomness, fp);
  64.148 +  fputc(p->tSpinAlgo, fp);
  64.149 +  fputc(p->clearGravity, fp);
  64.150 +  fputc(p->gluing, fp);
  64.151 +  fputc(p->scoreStyle, fp);
  64.152 +  fputc(p->setLockDelay, fp);
  64.153 +  fputc(p->upwardKicks, fp);
  64.154 +  fputc(p->maxUpwardKicks, fp);
  64.155 +  fputc(p->setLineDelay, fp);
  64.156 +  fputc(p->garbageStyle, fp);
  64.157 +  fputc(p->holdStyle, fp);
  64.158 +  fputc(p->bottomBlocks, fp);
  64.159 +
  64.160 +  fput16(p->multisquares, fp);
  64.161 +  fput16(p->monosquares, fp);
  64.162 +
  64.163 +#if DEBUG_OFFSETS
  64.164 +  allegro_message("final offset = %u\n", (unsigned int)ftell(fp));
  64.165 +#endif
  64.166 +}
  64.167 +
  64.168 +static int LJField_deserialize(LJField *p, FILE *fp) {
  64.169 +  int format = fget32(fp);
  64.170 +  if (format != FORMAT_VERSION
  64.171 +      && format != LAST_FORMAT_VERSION) {
  64.172 +    return -1;
  64.173 +  }
  64.174 +
  64.175 +  for (int y = 0; y < LJ_PF_HT; ++y) {
  64.176 +    for (int x = 0; x < LJ_PF_WID; ++x) {
  64.177 +      p->b[y][x] = fgetc(fp);
  64.178 +    }
  64.179 +  }
  64.180 +  for (int y = 0; y < LJ_PF_HT; ++y) {
  64.181 +    for (int x = 0; x < LJ_PF_WID; ++x) {
  64.182 +      p->c[y][x] = fgetc(fp);
  64.183 +    }
  64.184 +  }
  64.185 +  p->clearedLines = fget32(fp);
  64.186 +  p->sounds = fget32(fp);
  64.187 +  p->tempRows = fget32(fp);
  64.188 +  for (int y = 0; y < 1 + LJ_NEXT_PIECES; ++y) {
  64.189 +    p->curPiece[y] = fgetc(fp);
  64.190 +  }
  64.191 +  for (int y = 0; y < 2 * MAX_BAG_LEN; ++y) {
  64.192 +    p->permuPiece[y] = fgetc(fp);
  64.193 +  }
  64.194 +  p->permuPhase = fgetc(fp);
  64.195 +  for (int y = 0; y < LJ_MAX_LINES_PER_PIECE; ++y) {
  64.196 +    p->nLineClears[y] = fget16(fp);
  64.197 +  }
  64.198 +  p->y = fget32(fp);
  64.199 +  p->state = fgetc(fp);
  64.200 +  p->stateTime = fgetc(fp);
  64.201 +  p->theta = fgetc(fp);
  64.202 +  p->x = fgetc(fp);
  64.203 +  p->hardDropY = fgetc(fp);
  64.204 +  p->alreadyHeld = fgetc(fp);
  64.205 +  p->isSpin = fgetc(fp);
  64.206 +  p->nLinesThisPiece = fgetc(fp);
  64.207 +  p->canRotate = fgetc(fp);
  64.208 +
  64.209 +#if DEBUG_OFFSETS
  64.210 +  allegro_message("before score, offset = %u\n", (unsigned int)ftell(fp));
  64.211 +#endif
  64.212 +  p->score = fget32(fp);
  64.213 +  p->lines = fget32(fp);
  64.214 +  p->gameTime = fget32(fp);
  64.215 +  p->activeTime = fget32(fp);
  64.216 +  p->holdPiece = fget16(fp);
  64.217 +  p->chain = fgetc(fp);
  64.218 +  p->garbage = fgetc(fp);
  64.219 +  p->garbageX = fgetc(fp);
  64.220 +  p->nPieces = fget16(fp);
  64.221 +  p->outGarbage = fget16(fp);
  64.222 +  
  64.223 +  p->gimmick = fgetc(fp);
  64.224 +  p->speedState.level = fget32(fp);
  64.225 +  p->bpmCounter = fget32(fp);
  64.226 +  p->speedupCounter = fget32(fp);
  64.227 +  p->goalCount = fget32(fp);
  64.228 +  p->seed = fget32(fp);
  64.229 +  p->speed.gravity = fget32(fp);
  64.230 +  p->speedState.curve = fgetc(fp);
  64.231 +  p->goalType = fgetc(fp);
  64.232 +  p->speed.entryDelay = fgetc(fp);
  64.233 +  p->areStyle = fgetc(fp);
  64.234 +  p->lockReset = fgetc(fp);
  64.235 +  p->speed.lockDelay = fgetc(fp);
  64.236 +  p->speed.lineDelay = fgetc(fp);
  64.237 +  p->ceiling = fgetc(fp);
  64.238 +  p->enterAbove = fgetc(fp);
  64.239 +  p->leftWall = fgetc(fp);
  64.240 +  p->rightWall = fgetc(fp);
  64.241 +  p->pieceSet = fgetc(fp);
  64.242 +  p->randomizer = fgetc(fp);
  64.243 +  p->rotationSystem = fgetc(fp);
  64.244 +  p->garbageRandomness = fgetc(fp);
  64.245 +  p->tSpinAlgo = fgetc(fp);
  64.246 +  p->clearGravity = fgetc(fp);
  64.247 +  p->gluing = fgetc(fp);
  64.248 +  p->scoreStyle = fgetc(fp);
  64.249 +  p->setLockDelay = fgetc(fp);
  64.250 +  p->upwardKicks = fgetc(fp);
  64.251 +  p->maxUpwardKicks = fgetc(fp);
  64.252 +  p->setLineDelay = fgetc(fp);
  64.253 +  p->garbageStyle = fgetc(fp);
  64.254 +  p->holdStyle = fgetc(fp);
  64.255 +  p->bottomBlocks = fgetc(fp);
  64.256 +
  64.257 +  if (format == FORMAT_VERSION) {
  64.258 +    p->multisquares = fget16(fp);
  64.259 +    p->monosquares = fget16(fp);
  64.260 +  }
  64.261 +#if DEBUG_OFFSETS
  64.262 +  allegro_message("final offset = %u\n", (unsigned int)ftell(fp));
  64.263 +#endif
  64.264 +  return 0;
  64.265 +}
  64.266 +
  64.267 +
  64.268 +LJReplay *newReplay(const char *filename, LJField *p) {
  64.269 +  LJReplay *r = malloc(sizeof(struct LJReplay));
  64.270 +  
  64.271 +  if (!r) {
  64.272 +    return NULL;
  64.273 +  }
  64.274 +  r->file = fopen(filename, "wb");
  64.275 +  if (!r->file) {
  64.276 +    free(r);
  64.277 +    return NULL;
  64.278 +  }
  64.279 +  LJField_serialize(p, r->file);
  64.280 +  return r;
  64.281 +}
  64.282 +
  64.283 +void replayRecord(LJReplay *r, LJBits keys, const LJInput *in) {
  64.284 +  fputc(0, r->file);
  64.285 +  fputc(0, r->file);
  64.286 +  fputc(keys >> 8, r->file);
  64.287 +  fputc(keys, r->file);
  64.288 +  fputc(in->rotation, r->file);
  64.289 +  fputc(in->movement, r->file);
  64.290 +  fputc(in->gravity, r->file);
  64.291 +  fputc(in->other, r->file);
  64.292 +}
  64.293 +
  64.294 +LJReplay *openReplay(const char *filename, LJField *p) {
  64.295 +  LJReplay *r = malloc(sizeof(struct LJReplay));
  64.296 +  
  64.297 +  if (!r) {
  64.298 +    return NULL;
  64.299 +  }
  64.300 +  r->file = fopen(filename, "rb");
  64.301 +  if (!r->file) {
  64.302 +    free(r);
  64.303 +    return NULL;
  64.304 +  }
  64.305 +
  64.306 +  /* This deserialization is still NOT robust. */
  64.307 +  if (LJField_deserialize(p, r->file) < 0) {
  64.308 +    fclose(r->file);
  64.309 +    free(r);
  64.310 +    return 0;
  64.311 +  }
  64.312 +  return r;
  64.313 +}
  64.314 +
  64.315 +int getReplayFrame(LJReplay *r, LJInput *d) {
  64.316 +  fgetc(r->file);
  64.317 +  fgetc(r->file);
  64.318 +  int keys = fgetc(r->file);
  64.319 +  
  64.320 +  if (keys == EOF) {
  64.321 +    return LJREPLAY_EOF;
  64.322 +  }
  64.323 +  keys = (keys << 8 & 0xFF) | (fgetc(r->file) & 0xFF);
  64.324 +  d->rotation = fgetc(r->file);
  64.325 +  d->movement = fgetc(r->file);
  64.326 +  d->gravity = fgetc(r->file);
  64.327 +  d->other = fgetc(r->file);
  64.328 +  return keys;
  64.329 +}
  64.330 +
  64.331 +void replayClose(LJReplay *r) {
  64.332 +  if (r) {
  64.333 +    if (r->file) {
  64.334 +      fclose(r->file);
  64.335 +    }
  64.336 +    free(r);
  64.337 +  }
  64.338 +}
    65.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    65.2 +++ b/src/ljreplay.h	Fri Mar 13 00:39:12 2009 -0700
    65.3 @@ -0,0 +1,70 @@
    65.4 +/* Replay functionality for LOCKJAW, an implementation of the Soviet Mind Game
    65.5 +
    65.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    65.7 +
    65.8 +This work is free software; you can redistribute it and/or modify
    65.9 +it under the terms of the GNU General Public License as published by
   65.10 +the Free Software Foundation; either version 2 of the License, or
   65.11 +(at your option) any later version.
   65.12 +
   65.13 +This program is distributed in the hope that it will be useful,
   65.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   65.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   65.16 +GNU General Public License for more details.
   65.17 +
   65.18 +You should have received a copy of the GNU General Public License
   65.19 +along with this program; if not, write to the Free Software
   65.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   65.21 +
   65.22 +Original game concept and design by Alexey Pajitnov.
   65.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   65.24 +or The Tetris Company LLC.
   65.25 +
   65.26 +*/
   65.27 +#ifndef LJREPLAY_H
   65.28 +#define LJREPLAY_H
   65.29 +
   65.30 +#include "lj.h"
   65.31 +#include "ljcontrol.h"
   65.32 +
   65.33 +#define LJREPLAY_EOF (-1)
   65.34 +
   65.35 +typedef struct LJReplay LJReplay;
   65.36 +
   65.37 +/**
   65.38 + * Creates a new replay.
   65.39 + * @param filename The name of the file to which the replay is recorded.
   65.40 + * @param p The field that is observed.
   65.41 + * @return A pointer to the replay object,
   65.42 + * or NULL if allocation failed.
   65.43 + */
   65.44 +LJReplay *newReplay(const char *filename, LJField *p);
   65.45 +
   65.46 +/**
   65.47 + * Records a single frame of input in the replay.
   65.48 + * If spawn or hold sound is played, records the new piece.
   65.49 + * @param r The replay object.
   65.50 + */
   65.51 +void replayRecord(LJReplay *r, LJBits keys, const LJInput *in);
   65.52 +
   65.53 +/**
   65.54 + * Stops recording the replay and dumps it to the file.
   65.55 + */
   65.56 +void replayClose(LJReplay *r);
   65.57 +
   65.58 +/**
   65.59 + * Opens an existing replay.
   65.60 + * @param filename The name of the file to which the replay is recorded.
   65.61 + * @param p The field that is observed.
   65.62 + * @return A pointer to the replay object,
   65.63 + * or NULL if allocation failed.
   65.64 + */
   65.65 +LJReplay *openReplay(const char *filename, LJField *p);
   65.66 +
   65.67 +/**
   65.68 + * @param d The structure to be filled with input
   65.69 + * @return The keys pressed, or REPLAY_EOF.
   65.70 + */
   65.71 +int getReplayFrame(LJReplay *r, LJInput *d);
   65.72 +
   65.73 +#endif
    66.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    66.2 +++ b/src/ljtypes.h	Fri Mar 13 00:39:12 2009 -0700
    66.3 @@ -0,0 +1,61 @@
    66.4 +/* Basic data types for LOCKJAW, an implementation of the Soviet Mind Game
    66.5 +
    66.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    66.7 +
    66.8 +This work is free software; you can redistribute it and/or modify
    66.9 +it under the terms of the GNU General Public License as published by
   66.10 +the Free Software Foundation; either version 2 of the License, or
   66.11 +(at your option) any later version.
   66.12 +
   66.13 +This program is distributed in the hope that it will be useful,
   66.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   66.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   66.16 +GNU General Public License for more details.
   66.17 +
   66.18 +You should have received a copy of the GNU General Public License
   66.19 +along with this program; if not, write to the Free Software
   66.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   66.21 +
   66.22 +Original game concept and design by Alexey Pajitnov.
   66.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   66.24 +or The Tetris Company LLC.
   66.25 +
   66.26 +*/
   66.27 +
   66.28 +#ifndef LJTYPES_H
   66.29 +#define LJTYPES_H
   66.30 +
   66.31 +/**
   66.32 + * A 16.16 signed number. Used commonly for Y piece positions.
   66.33 + */
   66.34 +typedef signed int LJFixed;
   66.35 +
   66.36 +static inline signed int ljfixfloor(LJFixed f) __attribute__((const)); 
   66.37 +
   66.38 +static inline signed int ljfixfloor(LJFixed f) {
   66.39 +  return f >> 16;
   66.40 +}
   66.41 +
   66.42 +static inline LJFixed ljitofix(signed int f) __attribute__((const)); 
   66.43 +
   66.44 +static inline LJFixed ljitofix(signed int f) {
   66.45 +  return f << 16;
   66.46 +}
   66.47 +
   66.48 +/*
   66.49 + * In most cases, macros are deprecated in favor of static inline functions
   66.50 + * because the latter allow the compiler to perform more type checks.
   66.51 + * However, the C language forbids calling a function in an inline
   66.52 + * constructor, even if it is a static inline function with no side effects.
   66.53 + * For example, GCC gives the misleading error message
   66.54 + * "error: initializer element is not constant".
   66.55 + * Therefore, I have to provide a second implementation of ljitofix()
   66.56 + * as a macro for use in global variable initializers.
   66.57 + */
   66.58 +#define LJITOFIX(f) ((LJFixed)((f) << 16))
   66.59 +
   66.60 +
   66.61 +typedef unsigned int LJBits;
   66.62 +
   66.63 +#endif
   66.64 +
    67.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    67.2 +++ b/src/ljvorbis.c	Fri Mar 13 00:39:12 2009 -0700
    67.3 @@ -0,0 +1,206 @@
    67.4 +/*
    67.5 +
    67.6 +ljvorbis.c
    67.7 +Simple wrapper around vorbisfile for use with the Allegro library
    67.8 +copyright 2006 Damian Yerrick
    67.9 +based on vorbisfile example
   67.10 +copyright 1994-2004 Xiph.Org Foundation
   67.11 +licensed under a BSD style license set forth in COPYING-OGG.txt
   67.12 +
   67.13 +*/
   67.14 +
   67.15 +#define ALLEGRO_USE_CONSOLE
   67.16 +#include <stdio.h>
   67.17 +#include "ljvorbis.h"
   67.18 +
   67.19 +#define VORBIS_BYTEDEPTH 2  // number of bytes per sample; 2 means 16-bit
   67.20 +#define VORBIS_ENDIAN 0  // 0: x86/little; 2: ppc/big
   67.21 +#define VORBIS_SIGNED 0  // 0: unsigned; 1: signed; Allegro uses unsigned
   67.22 +
   67.23 +LJVorbis *LJVorbis_open(const char *filename) {
   67.24 +  LJVorbis *ogg = malloc(sizeof(LJVorbis));
   67.25 +  if (!ogg) {
   67.26 +    return NULL;
   67.27 +  }
   67.28 +  ogg->fp = fopen(filename, "rb");
   67.29 +  if (!ogg->fp) {
   67.30 +    free(ogg);
   67.31 +    return NULL;
   67.32 +  }
   67.33 +  if (ov_open(ogg->fp, &(ogg->vf), NULL, 0) < 0) {
   67.34 +    fclose(ogg->fp);
   67.35 +    free(ogg);
   67.36 +    return NULL;
   67.37 +  }
   67.38 +  vorbis_info *vi=ov_info(&(ogg->vf),-1);
   67.39 +  ogg->rate = vi->rate;
   67.40 +  ogg->channels = vi->channels;
   67.41 +  ogg->length = ov_pcm_total(&(ogg->vf),-1);
   67.42 +  ogg->loopPoint = 0;
   67.43 +  ogg->voice = NULL;
   67.44 +  ogg->paused = 0;
   67.45 +  return ogg;
   67.46 +}
   67.47 +
   67.48 +void LJVorbis_setLoop(LJVorbis *ogg, unsigned long int loopPoint) {
   67.49 +  if (ogg) {
   67.50 +    if (loopPoint < ogg->length) {
   67.51 +      ogg->loopPoint = loopPoint;
   67.52 +    } else {
   67.53 +      ogg->loopPoint = 0;
   67.54 +    }
   67.55 +  }
   67.56 +}
   67.57 +
   67.58 +int LJVorbis_start(LJVorbis *ogg, int bufferSize, int vol, int pan) {
   67.59 +  if (ogg) {
   67.60 +  
   67.61 +    // if restarting, stop first
   67.62 +    if (ogg->voice) {
   67.63 +      LJVorbis_stop(ogg);
   67.64 +    }
   67.65 +    ogg->voice = play_audio_stream(bufferSize,
   67.66 +                                   8 * VORBIS_BYTEDEPTH,
   67.67 +                                   ogg->channels > 1,
   67.68 +                                   ogg->rate,
   67.69 +                                   vol, pan);
   67.70 +    ogg->bufferSize = bufferSize;
   67.71 +    if (!ogg->voice) {
   67.72 +      return -1;
   67.73 +    }
   67.74 +    ov_pcm_seek(&(ogg->vf), 0);
   67.75 +    return 0;
   67.76 +  }
   67.77 +  return -1;
   67.78 +}
   67.79 +
   67.80 +void LJVorbis_stop(LJVorbis *ogg) {
   67.81 +  if (ogg && ogg->voice) {
   67.82 +    stop_audio_stream(ogg->voice);
   67.83 +    ogg->voice = NULL;
   67.84 +  }
   67.85 +}
   67.86 +
   67.87 +void LJVorbis_close(LJVorbis *ogg) {
   67.88 +  if (ogg) {
   67.89 +    LJVorbis_stop(ogg);
   67.90 +    ov_clear(&(ogg->vf));  // finalize decoder and close the file
   67.91 +    // thanks kesiev for reminding me that ov_clear closes the file itself
   67.92 +    free(ogg);
   67.93 +  }
   67.94 +}
   67.95 +
   67.96 +void LJVorbis_pause(LJVorbis *ogg, int value) {
   67.97 +  if (ogg && ogg->voice) {
   67.98 +    int hwVoice = ogg->voice->voice;
   67.99 +    voice_set_frequency(hwVoice, value ? 0 : ogg->rate);
  67.100 +    ogg->paused = value ? 1 : 0;
  67.101 +  }
  67.102 +}
  67.103 +
  67.104 +int LJVorbis_poll(LJVorbis *ogg) {
  67.105 +  if (!ogg || !ogg->voice) {
  67.106 +    return -1;
  67.107 +  }
  67.108 +  char *buf = get_audio_stream_buffer(ogg->voice);
  67.109 +  int eofReached = 0;
  67.110 +    
  67.111 +  if (buf) {
  67.112 +    // the number of bytes left in this buffer
  67.113 +    long int bytesLeft = ogg->bufferSize * VORBIS_BYTEDEPTH * ogg->channels;
  67.114 +
  67.115 +    while (bytesLeft > 0) {
  67.116 +      long ret=ov_read(&(ogg->vf),
  67.117 +                       buf,
  67.118 +                       bytesLeft,
  67.119 +                       VORBIS_ENDIAN,
  67.120 +                       VORBIS_BYTEDEPTH,
  67.121 +                       VORBIS_SIGNED,
  67.122 +                       &(ogg->bitstream));
  67.123 +      if (ret == 0) {
  67.124 +        // try to seek back to the beginning of the file
  67.125 +        int pcmErr = ov_pcm_seek(&(ogg->vf), ogg->loopPoint);
  67.126 +        if (pcmErr) {
  67.127 +          /* EOF */
  67.128 +          eofReached = 1;
  67.129 +          bytesLeft = 0;
  67.130 +        }
  67.131 +      } else if (ret < 0) {
  67.132 +        // Stream error. Just ignore it.
  67.133 +      } else {
  67.134 +        /* FIXME: handle sample rate changes, etc */
  67.135 +        // advance forward in the buffer
  67.136 +        buf += ret;
  67.137 +        bytesLeft -= ret;
  67.138 +      }
  67.139 +    }
  67.140 +    free_audio_stream_buffer(ogg->voice);
  67.141 +  }
  67.142 +  return eofReached;
  67.143 +}
  67.144 +
  67.145 +#ifdef LJVORBIS_DEMO
  67.146 +int main(){
  67.147 +  int eofReached = 0;
  67.148 +  LJVorbis *ogg = LJVorbis_open("AM-3P.ogg");
  67.149 +
  67.150 +  if (!ogg) {
  67.151 +    fprintf(stderr, "Could not open AM-3P.ogg.\n");
  67.152 +    exit(1);
  67.153 +  }
  67.154 +  LJVorbis_setLoop(ogg, 650772);
  67.155 +
  67.156 +  if (allegro_init() < 0
  67.157 +      || install_timer() < 0
  67.158 +      || set_gfx_mode(GFX_SAFE, 320, 200, 0, 0) < 0
  67.159 +      || install_keyboard() < 0
  67.160 +      || install_sound(DIGI_AUTODETECT, MIDI_NONE, NULL) < 0) {
  67.161 +    allegro_exit();
  67.162 +    LJVorbis_close(ogg);
  67.163 +    fprintf(stderr, "Could not start Allegro library: %s\n", allegro_error);
  67.164 +    exit(1);
  67.165 +  }
  67.166 +
  67.167 +  /* Throw the comments plus a few lines about the bitstream we're
  67.168 +     decoding */
  67.169 +  if (0)
  67.170 +  {
  67.171 +    char line1[80], line2[80];
  67.172 +    
  67.173 +    usprintf(line1,"%d channels, %u Hz", ogg->channels, ogg->rate);
  67.174 +    usprintf(line2,"length: %lu samples", ogg->length);
  67.175 +    alert(line1, line2, "ready?",
  67.176 +          "Play", 0, 13, 0);
  67.177 +  }
  67.178 +  
  67.179 +  if (LJVorbis_start(ogg, 1024, 192, 128) < 0) {
  67.180 +    LJVorbis_close(ogg);
  67.181 +    alert("Could not allocate voice",
  67.182 +          "for playing audio.",
  67.183 +          "",
  67.184 +          "OK", 0, 13, 0);
  67.185 +    exit(1);
  67.186 +  }
  67.187 +  
  67.188 +  while(!eofReached){
  67.189 +    eofReached = LJVorbis_poll(ogg);
  67.190 +    rest(16);
  67.191 +    
  67.192 +    if (keypressed()) {
  67.193 +      int scancode;
  67.194 +      ureadkey(&scancode);
  67.195 +      
  67.196 +      if (scancode == KEY_P) {
  67.197 +        LJVorbis_pause(ogg, !ogg->paused);
  67.198 +      } else if (scancode == KEY_ESC) {
  67.199 +        eofReached = 1;
  67.200 +      }
  67.201 +    }
  67.202 +  }
  67.203 +
  67.204 +  /* cleanup */
  67.205 +  LJVorbis_close(ogg);
  67.206 +  return(0);
  67.207 +}
  67.208 +END_OF_MAIN();
  67.209 +#endif
    68.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    68.2 +++ b/src/ljvorbis.h	Fri Mar 13 00:39:12 2009 -0700
    68.3 @@ -0,0 +1,76 @@
    68.4 +/*
    68.5 +
    68.6 +ljvorbis.h
    68.7 +Simple wrapper around vorbisfile for use with the Allegro library
    68.8 +copyright 2006 Damian Yerrick
    68.9 +based on vorbisfile example
   68.10 +copyright 1994-2004 Xiph.Org Foundation
   68.11 +licensed under a BSD style license set forth in COPYING-OGG.txt
   68.12 +
   68.13 +*/
   68.14 +
   68.15 +#ifndef LJVORBIS_H
   68.16 +#define LJVORBIS_H
   68.17 +
   68.18 +#include <vorbis/codec.h>
   68.19 +#include <vorbis/vorbisfile.h>
   68.20 +#include <allegro.h>
   68.21 +
   68.22 +typedef struct LJVorbis {
   68.23 +  FILE *fp;
   68.24 +  AUDIOSTREAM *voice;
   68.25 +  OggVorbis_File vf;
   68.26 +  int bitstream;
   68.27 +  unsigned int bufferSize;
   68.28 +  unsigned int rate;
   68.29 +  unsigned long int length;
   68.30 +  unsigned long int loopPoint;
   68.31 +  unsigned char channels;
   68.32 +  unsigned char paused;
   68.33 +} LJVorbis;
   68.34 +
   68.35 +/**
   68.36 + * Creates a new LJVorbis instance.
   68.37 + * @param filename the name of the .ogg file to open
   68.38 + * @return an LJVorbis pointer
   68.39 + */
   68.40 +LJVorbis *LJVorbis_open(const char *filename);
   68.41 +
   68.42 +/**
   68.43 + * Sets the loop point of an LJVorbis.  If it is past the end
   68.44 + * of the file, sets the loop point to the start of the file.
   68.45 + * @param loopPoint the sample number to seek back to
   68.46 + */
   68.47 +void LJVorbis_setLoop(LJVorbis *ogg, unsigned long int loopPoint);
   68.48 +
   68.49 +/**
   68.50 + * Starts or restarts an LJVorbis playing in a new Allegro voice.
   68.51 + * @param bufferSize the size of the Allegro audio buffer in samples
   68.52 + * @param vol the Allegro volume (0-255?)
   68.53 + * @param pan the Allegro pan value (0=left, 256=right)
   68.54 + */
   68.55 +int LJVorbis_start(LJVorbis *ogg, int bufferSize, int vol, int pan);
   68.56 +
   68.57 +/**
   68.58 + * Stops an LJVorbis and frees its Allegro voice.
   68.59 + */
   68.60 +void LJVorbis_stop(LJVorbis *ogg);
   68.61 +
   68.62 +/**
   68.63 + * Destroys an LJVorbis instance entirely.
   68.64 + */
   68.65 +void LJVorbis_close(LJVorbis *ogg);
   68.66 +
   68.67 +/**
   68.68 + * Pauses or resumes an LJVorbis.
   68.69 + * @param value 0 to pause, or nonzero to resume
   68.70 + */
   68.71 +void LJVorbis_pause(LJVorbis *ogg, int value);
   68.72 +
   68.73 +/**
   68.74 + * Processes an LJVorbis
   68.75 + * Must be called periodically, at least once every bufferSize samples.
   68.76 + */
   68.77 +int LJVorbis_poll(LJVorbis *ogg);
   68.78 +
   68.79 +#endif
    69.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    69.2 +++ b/src/macro.c	Fri Mar 13 00:39:12 2009 -0700
    69.3 @@ -0,0 +1,263 @@
    69.4 +/* Input handling for LOCKJAW Tetromino Game
    69.5 +
    69.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    69.7 +
    69.8 +This work is free software; you can redistribute it and/or modify
    69.9 +it under the terms of the GNU General Public License as published by
   69.10 +the Free Software Foundation; either version 2 of the License, or
   69.11 +(at your option) any later version.
   69.12 +
   69.13 +This program is distributed in the hope that it will be useful,
   69.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   69.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   69.16 +GNU General Public License for more details.
   69.17 +
   69.18 +You should have received a copy of the GNU General Public License
   69.19 +along with this program; if not, write to the Free Software
   69.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   69.21 +
   69.22 +Original game concept and design by Alexey Pajitnov.
   69.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   69.24 +or The Tetris Company LLC.
   69.25 +
   69.26 +*/
   69.27 +#include "lj.h"
   69.28 +#include "ljcontrol.h"
   69.29 +#include "ljreplay.h"
   69.30 +#include "pcjoy.h"
   69.31 +
   69.32 +// first is rotation (+1 = 90 deg clockwise)
   69.33 +// second is movement (+1 = right 1 block)
   69.34 +// third is gravity (+1 = down 1/8 block)
   69.35 +// fourth is extra actions (hold, lock)
   69.36 +LJInput macros[8] = {
   69.37 +  { -1,  0,    0, 0 },  // default: alt. rotate left
   69.38 +  { -2,  0,    0, 0 },  // default: rotate left twice
   69.39 +  {  0, -9,    0, 0 },  // default: far left
   69.40 +  {  0,  9,    0, 0 },  // default: far right
   69.41 +  {  0,  0, 20*8, 0 },  // default: firm drop
   69.42 +  {  0,  0,    0, LJI_HOLD },  // default: alternate hold
   69.43 +  {  0,  0,    0, 0 },
   69.44 +  {  0,  0,    0, 0 }  
   69.45 +};
   69.46 +
   69.47 +void addMacrosToInput(LJInput *dst, LJBits keys) {
   69.48 +  int rotation = dst->rotation;
   69.49 +  int movement = dst->movement;
   69.50 +  int gravity = dst->gravity;
   69.51 +  int other = dst->other;
   69.52 +  int macro;
   69.53 +
   69.54 +  keys >>= 8;
   69.55 +  for (macro = 0;
   69.56 +       macro < 8;
   69.57 +       keys >>= 1, ++macro) {
   69.58 +    if (keys & 1) {
   69.59 +      rotation += macros[macro].rotation;
   69.60 +      movement += macros[macro].movement;
   69.61 +      gravity += macros[macro].gravity;
   69.62 +      other |= macros[macro].other;
   69.63 +    }
   69.64 +  }
   69.65 +
   69.66 +  // Clip rotation to [-3, +3]
   69.67 +  rotation -= rotation / 4 * 4;
   69.68 +
   69.69 +  // Clip movement to playfield width
   69.70 +  if (movement < (int)-LJ_PF_WID) {
   69.71 +    movement = -LJ_PF_WID;
   69.72 +  } else if (movement > (int)LJ_PF_WID) {
   69.73 +    movement = LJ_PF_WID;
   69.74 +  }
   69.75 +
   69.76 +  // Clip gravity to playfield height
   69.77 +  if (gravity > LJ_PF_HT * 8) {
   69.78 +    gravity = LJ_PF_HT * 8;
   69.79 +  }
   69.80 +  
   69.81 +  dst->rotation = rotation;
   69.82 +  dst->movement = movement;
   69.83 +  dst->gravity = gravity;
   69.84 +  dst->other = other;
   69.85 +}
   69.86 +
   69.87 +static const LJFixed softDropSpeeds[3] = {
   69.88 +  LJITOFIX(1),
   69.89 +  LJITOFIX(1)/2,
   69.90 +  LJITOFIX(1)/3
   69.91 +};
   69.92 +
   69.93 +void addKeysToInput(LJInput *dst, LJBits keys, const LJField *p, LJControl *c) {
   69.94 +  int actualKeys = keys;
   69.95 +
   69.96 +  if (c->replaySrc) {
   69.97 +    keys = getReplayFrame(c->replaySrc, dst);
   69.98 +    if (keys == LJREPLAY_EOF) {
   69.99 +      keys = actualKeys;
  69.100 +      replayClose(c->replaySrc);
  69.101 +      c->replaySrc = NULL;
  69.102 +    }
  69.103 +  }
  69.104 +
  69.105 +  int lastFrameKeys = c->lastKeys;
  69.106 +
  69.107 +  // If diagonal presses are disabled, ignore any changes
  69.108 +  if (!c->allowDiagonals
  69.109 +       && (keys & (VKEY_UP | VKEY_DOWN))
  69.110 +       && (keys & (VKEY_LEFT | VKEY_RIGHT))) {
  69.111 +    keys &= ~(VKEY_UP | VKEY_DOWN | VKEY_LEFT | VKEY_RIGHT);
  69.112 +    keys |= lastFrameKeys
  69.113 +            & (VKEY_UP | VKEY_DOWN | VKEY_LEFT | VKEY_RIGHT);
  69.114 +  }
  69.115 +
  69.116 +  LJBits newKeys = keys & ~lastFrameKeys;
  69.117 +  c->lastKeys = keys;
  69.118 +
  69.119 +  // Count presses for Baboo!, excluding console buttons
  69.120 +  c->presses += countOnes(newKeys & 0x0000FFFF);
  69.121 +
  69.122 +  // Only once the side effect of counting presses for Baboo!
  69.123 +  // is complete can we break out of a replay.
  69.124 +  if (c->replaySrc) {
  69.125 +    return;
  69.126 +  }
  69.127 +
  69.128 +  LJBits releasedKeys = ~keys & lastFrameKeys;
  69.129 +  
  69.130 +  // At this point, c->lastKeys holds the keys actually held
  69.131 +  // by the player this frame, and lastFrameKeys holds the keys 
  69.132 +  // actually held by the player last frame.
  69.133 +  
  69.134 +  // Handle keys that must be re-pressed
  69.135 +  releasedKeys &= ~c->repressKeys;
  69.136 +  c->repressKeys &= keys;
  69.137 +  keys &= ~c->repressKeys;
  69.138 +
  69.139 +  // If locking in a mode without ARE, require
  69.140 +  // down to be re-pressed before next piece
  69.141 +  if (p->sounds & LJSND_LOCK
  69.142 +      && p->speed.entryDelay <= c->dasDelay) {
  69.143 +    c->repressKeys |= VKEY_DOWN;
  69.144 +    
  69.145 +    // Treat up the same way when hard drop lock is set to lock on release.
  69.146 +    if (c->hardDropLock != LJZANGI_SLIDE
  69.147 +        || p->lockReset == LJLOCK_NOW
  69.148 +        || p->speed.lockDelay <= c->dasDelay) {
  69.149 +      c->repressKeys |= VKEY_UP | VKEY_MACRO(4);
  69.150 +    }
  69.151 +  }
  69.152 +  
  69.153 +  // Initial Rotation System (IRS):
  69.154 +  // When a piece spawns from next or hold, and a rotation or macro
  69.155 +  // key is held, treat the key as if they had just been pressed.
  69.156 +  // Treat hard drop the same way when ARE is turned on.
  69.157 +  if ((p->sounds & (LJSND_SPAWN | LJSND_HOLD))
  69.158 +      && c->initialRotate) {
  69.159 +    newKeys |= c->lastKeys
  69.160 +               & (VKEY_ROTL | VKEY_ROTR | VKEY_MACROS);
  69.161 +    if (p->speed.entryDelay > 0) {
  69.162 +      newKeys |= c->lastKeys
  69.163 +                 & VKEY_UP;
  69.164 +    }
  69.165 +  }
  69.166 +
  69.167 +  // if we're pretending that keys are not pressed,
  69.168 +  // pretend consistently
  69.169 +  newKeys &= keys;
  69.170 +
  69.171 +  // TGM does not perform sideways movement on
  69.172 +  // the first frame after a piece is spawned.
  69.173 +  if (c->initialDAS == 0 &&
  69.174 +      (p->sounds & (LJSND_SPAWN | LJSND_HOLD))) {
  69.175 +    
  69.176 +  } else if (keys & VKEY_LEFT) {
  69.177 +        if (c->dasCounter > -(int)c->dasDelay) {
  69.178 +          if (c->dasCounter >= 0) {
  69.179 +            c->dasCounter = -1;
  69.180 +            dst->movement = -1;
  69.181 +          } else {
  69.182 +            c->dasCounter -= 1;
  69.183 +          }
  69.184 +        } else {
  69.185 +          int dasSpeed = c->dasSpeed;
  69.186 +          if (dasSpeed) {
  69.187 +            dst->movement = -1;
  69.188 +            c->dasCounter += dasSpeed - 1;
  69.189 +          } else {
  69.190 +            dst->movement = -(int)LJ_PF_WID;
  69.191 +          }
  69.192 +        }
  69.193 +  } else if (keys & VKEY_RIGHT) {
  69.194 +        if (c->dasCounter < c->dasDelay) {
  69.195 +          if (c->dasCounter <= 0) {
  69.196 +            c->dasCounter = 1;
  69.197 +            dst->movement = 1;
  69.198 +          } else {
  69.199 +            c->dasCounter += 1;
  69.200 +          }
  69.201 +        } else {
  69.202 +          int dasSpeed = c->dasSpeed;
  69.203 +          if (dasSpeed) {
  69.204 +            dst->movement = 1;
  69.205 +            c->dasCounter -= dasSpeed - 1;
  69.206 +          } else {
  69.207 +            dst->movement = (int)LJ_PF_WID;
  69.208 +          }
  69.209 +        }
  69.210 +  } else {
  69.211 +    c->dasCounter = 0;
  69.212 +  }
  69.213 +
  69.214 +  if(keys & VKEY_DOWN) {
  69.215 +    int g = softDropSpeeds[c->softDropSpeed];
  69.216 +    
  69.217 +    // dither speed to 1/8G units
  69.218 +    g += ljitofix(p->gameTime % 3) / 24;
  69.219 +    dst->gravity += g >> 13;
  69.220 +
  69.221 +    if ((newKeys & VKEY_DOWN)
  69.222 +        || c->softDropLock == LJZANGI_LOCK) {
  69.223 +      dst->other |= LJI_LOCK;
  69.224 +    }
  69.225 +  }
  69.226 +
  69.227 +  if (newKeys & VKEY_ROTL) {
  69.228 +    dst->rotation -= 1;
  69.229 +  } 
  69.230 +  if (newKeys & VKEY_ROTR) {
  69.231 +    dst->rotation += 1;
  69.232 +  }
  69.233 +  if (newKeys & VKEY_HOLD) {
  69.234 +    dst->other |= LJI_HOLD;
  69.235 +  }
  69.236 +  if (newKeys & VKEY_UP) {
  69.237 +    dst->gravity = LJ_PF_HT << 3;
  69.238 +    if (p->state == LJS_LANDED
  69.239 +        || c->hardDropLock == LJZANGI_LOCK) {
  69.240 +      dst->other |= LJI_LOCK;
  69.241 +    }
  69.242 +  }
  69.243 +  
  69.244 +  if (c->hardDropLock == LJZANGI_LOCK_RELEASE) {
  69.245 +    if (releasedKeys & VKEY_UP) {
  69.246 +      dst->other |= LJI_LOCK;
  69.247 +    }
  69.248 +  }
  69.249 +  if (c->softDropLock == LJZANGI_LOCK_RELEASE) {
  69.250 +    if (releasedKeys & VKEY_DOWN) {
  69.251 +      dst->other |= LJI_LOCK;
  69.252 +    }
  69.253 +  }
  69.254 +
  69.255 +  addMacrosToInput(dst, newKeys);
  69.256 +  
  69.257 +  // Baboo! ends with a hard drop
  69.258 +  if (p->gimmick == LJGM_BABY && c->presses >= 300) {
  69.259 +    dst->gravity = LJ_PF_HT << 3;
  69.260 +    dst->other |= LJI_LOCK;
  69.261 +  }
  69.262 +
  69.263 +  if (c->replayDst) {
  69.264 +    replayRecord(c->replayDst, actualKeys, dst);
  69.265 +  }
  69.266 +}
    70.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    70.2 +++ b/src/old_pc_options.c	Fri Mar 13 00:39:12 2009 -0700
    70.3 @@ -0,0 +1,436 @@
    70.4 +/* PC option screen for LOCKJAW, an implementation of the Soviet Mind Game
    70.5 +
    70.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    70.7 +
    70.8 +This work is free software; you can redistribute it and/or modify
    70.9 +it under the terms of the GNU General Public License as published by
   70.10 +the Free Software Foundation; either version 2 of the License, or
   70.11 +(at your option) any later version.
   70.12 +
   70.13 +This program is distributed in the hope that it will be useful,
   70.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   70.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   70.16 +GNU General Public License for more details.
   70.17 +
   70.18 +You should have received a copy of the GNU General Public License
   70.19 +along with this program; if not, write to the Free Software
   70.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   70.21 +
   70.22 +Original game concept and design by Alexey Pajitnov.
   70.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   70.24 +or The Tetris Company LLC.
   70.25 +
   70.26 +*/
   70.27 +
   70.28 +#include "ljpc.h"
   70.29 +#include <ctype.h>
   70.30 +#include "ljpath.h"
   70.31 +#include <string.h>
   70.32 +#include "options.h"
   70.33 +#include "ljlocale.h"
   70.34 +
   70.35 +#define OPTIONS_OFFSET 0
   70.36 +#define optionsMenu (commonOptionsMenu + OPTIONS_OFFSET)
   70.37 +
   70.38 +
   70.39 +
   70.40 +#if 0
   70.41 +static int getOptionValueByName(int row, const char *value) {
   70.42 +  for (int x = 0; x < optionsMenu[row].nValues; ++x) {
   70.43 +    if (!ustrcmp(value, optionsMenu[row].valueNames[x])) {
   70.44 +      return x + optionsMenu[row].minValue;
   70.45 +    }
   70.46 +  }
   70.47 +  return -1;
   70.48 +}
   70.49 +
   70.50 +static void setOptionValueByNumber(struct LJPrefs *prefs,
   70.51 +                                   int row, const char *value) {
   70.52 +  if (value[0] >= '0' && value[0] <= '9') {
   70.53 +    int num = atoi(value);
   70.54 +    if (num >= optionsMenu[row].minValue
   70.55 +        && num < optionsMenu[row].minValue + optionsMenu[row].nValues) {
   70.56 +      prefs->number[row] = num;
   70.57 +    }
   70.58 +  }
   70.59 +}
   70.60 +
   70.61 +static void setOptionValueByName(struct LJPrefs *prefs,
   70.62 +                                 int row, const char *value) {
   70.63 +  int num = getOptionValueByName(row, value);
   70.64 +
   70.65 +  if (num >= 0) {
   70.66 +    prefs->number[row] = num;
   70.67 +  }
   70.68 +}
   70.69 +
   70.70 +static void setOptionValueByBoolean(struct LJPrefs *prefs,
   70.71 +                                 int row, const char *value) {
   70.72 +  int num = atoi(value);
   70.73 +
   70.74 +  if (num >= 0) {
   70.75 +    prefs->number[row] = num ? 1 : 0;
   70.76 +  }
   70.77 +}
   70.78 +#endif
   70.79 +
   70.80 +/* parse_ini_line *******************
   70.81 +*/
   70.82 +int parse_ini_line(const char *in, char *key, char *var, char *val)
   70.83 +{
   70.84 +  int c;
   70.85 +  char *kstart = key;
   70.86 +
   70.87 +  /* Skip whitespace before key */
   70.88 +  while(*in && isspace(*in))
   70.89 +    in++;
   70.90 +
   70.91 +  /* Parse key */
   70.92 +  if(*in == '[')  /* if we have a new key, load it */
   70.93 +  {
   70.94 +    in++;
   70.95 +
   70.96 +    /* Skip whitespace before actual key */
   70.97 +    while(*in && isspace(*in))
   70.98 +      in++;
   70.99 +
  70.100 +    for(c = *in++;
  70.101 +        c != 0 && c != ']' && c != '\n' && c != '\r';
  70.102 +        c = *in++)
  70.103 +    {
  70.104 +      if(!isspace(c))
  70.105 +        *key++ = c;
  70.106 +    }
  70.107 +    *key = 0;
  70.108 +    /* Strip whitespace after key */
  70.109 +    do {
  70.110 +      *key-- = 0;
  70.111 +    } while(key >= kstart && isspace(*key));
  70.112 +  }
  70.113 +
  70.114 +  /* Skip whitespace before variable */
  70.115 +  while(*in && isspace(*in))
  70.116 +    in++;
  70.117 +  if(*in == 0)  /* if there is no variable, don't do anything */
  70.118 +    return 0;
  70.119 +
  70.120 +  for(c = *in++;
  70.121 +      c != 0 && c != '=' && c != '\n' && c != '\r';
  70.122 +      c = *in++)
  70.123 +  {
  70.124 +    if(!isspace(c))
  70.125 +    {
  70.126 +      *var++ = c;
  70.127 +    }
  70.128 +  }
  70.129 +  *var = 0;
  70.130 +
  70.131 +  /* Skip whitespace before value */
  70.132 +  while(*in && isspace(*in))
  70.133 +    in++;
  70.134 +
  70.135 +  /* Get value */
  70.136 +  kstart = val;
  70.137 +  for(c = *in++;
  70.138 +      c != 0 && c != '\n' && c != '\r';
  70.139 +      c = *in++)
  70.140 +  {
  70.141 +    *val++ = c;
  70.142 +  }
  70.143 +  /* Strip whitespace after value */
  70.144 +  do {
  70.145 +    *val-- = 0;
  70.146 +  } while(val >= kstart && isspace(*val));
  70.147 +
  70.148 +  return 0;
  70.149 +}
  70.150 +
  70.151 +static const char ljIniName[] = "lj.ini";
  70.152 +
  70.153 +/**
  70.154 + * Finds a FourCC in a list.
  70.155 + * @param needle the fourCC to search for
  70.156 + * @param haystack a list of fourCCs
  70.157 + * @param haystackLen the length of this list
  70.158 + * @return the index within haystack where needle was found
  70.159 + *         or -1 if not found
  70.160 + */
  70.161 +int findFourCC(const char *needle,
  70.162 +               const FourCC *haystack, size_t haystackLen) {
  70.163 +  for (size_t i = 0; i < haystackLen; ++i) {
  70.164 +    if (!strncmp(haystack[i].c, needle, 4)) {
  70.165 +      return i;
  70.166 +    }
  70.167 +  }
  70.168 +  return -1;
  70.169 +}
  70.170 +
  70.171 +void loadOption(struct LJPrefs *prefs,
  70.172 +                const char *name, const char *value) {
  70.173 +  for (int i = 0; i < PC_OPTIONS_MENU_LEN; ++i) {
  70.174 +    if (!strncmp(optionsMenu[i].name.c, name, 4)) {
  70.175 +      if (isdigit(value[0])) {
  70.176 +        unsigned long int n = strtoul(value, NULL, 10);
  70.177 +        if (n >= optionsMenu[i].minValue
  70.178 +            && n < optionsMenu[i].minValue + optionsMenu[i].nValues) {
  70.179 +          prefs->number[i] = n;
  70.180 +        } else {
  70.181 +          allegro_message("ignoring out of range %s=%lu\n",
  70.182 +                          name, n);
  70.183 +        }
  70.184 +      } else if (optionsMenu[i].valueNames) {
  70.185 +        int nValueNames = (optionsMenu[i].style == OPTSTYLE_FRAMES
  70.186 +                           ? 2
  70.187 +                           : optionsMenu[i].nValues);
  70.188 +        int foundValue = findFourCC(value, optionsMenu[i].valueNames, nValueNames);
  70.189 +        if (optionsMenu[i].style == OPTSTYLE_FRAMES
  70.190 +            && foundValue > 0) {
  70.191 +          foundValue = optionsMenu[i].nValues - 1;
  70.192 +        }
  70.193 +        prefs->number[i] = foundValue + optionsMenu[i].minValue;
  70.194 +      }
  70.195 +      return;
  70.196 +    }
  70.197 +  }
  70.198 +}
  70.199 +
  70.200 +int loadOptions(struct LJPrefs *prefs) {
  70.201 +  FILE *fp = ljfopen(ljIniName, "rt");
  70.202 +  char key[1024], var[1024], val[1024], input_buf[1024];
  70.203 +
  70.204 +  if (!fp) return 0;
  70.205 +
  70.206 +  key[0] = 0;
  70.207 +  var[0] = 0;
  70.208 +  val[0] = 0;
  70.209 +
  70.210 +  while(fgets (input_buf, sizeof(input_buf), fp)) {
  70.211 +    parse_ini_line(input_buf, key, var, val);
  70.212 +    
  70.213 +    if(!ustrcmp ("Skin", var)) {
  70.214 +      ustrzcpy(skinName, sizeof(skinName) - 1, val);
  70.215 +    } else {
  70.216 +      loadOption(prefs, var, val);
  70.217 +    }
  70.218 +  }
  70.219 +  fclose(fp);
  70.220 +
  70.221 +  if (prefs->number[OPTIONS_SIDEWAYS_DELAY] < prefs->number[OPTIONS_SIDEWAYS_SPEED]) {
  70.222 +    prefs->number[OPTIONS_SIDEWAYS_DELAY] = prefs->number[OPTIONS_SIDEWAYS_SPEED];
  70.223 +  }
  70.224 +
  70.225 +  return 0;
  70.226 +}
  70.227 +
  70.228 +void saveOption(int i, int n, FILE *out) {
  70.229 +  char name[8], value[8];
  70.230 +  int nameIdx = -1;
  70.231 +
  70.232 +  strncpy(name, optionsMenu[i].name.c, 4);
  70.233 +  name[4] = 0;
  70.234 +
  70.235 +  if (optionsMenu[i].valueNames) {
  70.236 +    if (optionsMenu[i].style == OPTSTYLE_FRAMES) {
  70.237 +      if (n == optionsMenu[i].minValue) {
  70.238 +        nameIdx = 0;
  70.239 +      } else if (n == optionsMenu[i].minValue + optionsMenu[i].nValues - 1) {
  70.240 +        nameIdx = 1;
  70.241 +      }
  70.242 +    } else {
  70.243 +      nameIdx = n - optionsMenu[i].minValue;
  70.244 +    }
  70.245 +    if (nameIdx >= 0
  70.246 +        && optionsMenu[i].valueNames[nameIdx].i == 0) {
  70.247 +      nameIdx = -1;
  70.248 +    }
  70.249 +  }
  70.250 +
  70.251 +  if (nameIdx >= 0) {
  70.252 +    strncpy(value, optionsMenu[i].valueNames[nameIdx].c, 4);
  70.253 +    value[4] = 0;
  70.254 +    fprintf(out, "%s=%s\n",
  70.255 +                 name, value);
  70.256 +  } else {
  70.257 +    fprintf(out, "%s=%d\n",
  70.258 +                 name, n);
  70.259 +  }
  70.260 +}
  70.261 +
  70.262 +void saveOptions(const struct LJPrefs *prefs) {
  70.263 +  FILE *out = ljfopen(ljIniName, "wt");
  70.264 +  
  70.265 +  if (out) {
  70.266 +    for (int i = 0; i < PC_OPTIONS_MENU_LEN; ++i) {
  70.267 +      saveOption(i, prefs->number[i], out);
  70.268 +    }
  70.269 +    fprintf(out, "Skin=%s\n",
  70.270 +                 skinName);
  70.271 +    fclose(out);
  70.272 +  }
  70.273 +}
  70.274 +
  70.275 +void unpackOptions(LJView *v, const struct LJPrefs *prefs) {
  70.276 +  unpackCommonOptions(v, prefs->number);
  70.277 +
  70.278 +  v->plat->nextAbove         = prefs->number[OPTIONS_NEXT_ABOVE];
  70.279 +  v->showTrails              = prefs->number[OPTIONS_TRAILS];
  70.280 +  autoPause                  = prefs->number[OPTIONS_AUTO_PAUSE];
  70.281 +}
  70.282 +
  70.283 +/********************************************************************
  70.284 +
  70.285 +Allegro based option drawing code
  70.286 +
  70.287 +********************************************************************/
  70.288 +
  70.289 +#define OPTIONS_TOP 100
  70.290 +#define OPTIONS_ROW_HT 40
  70.291 +#define OPTIONS_ROW_LEFT 80
  70.292 +#define OPTIONS_ROW_MID 400
  70.293 +#define OPTIONS_ROW_RIGHT 760
  70.294 +#define OPTIONS_MENU_VIS 7
  70.295 +#define OPTIONS_FONT aver32
  70.296 +
  70.297 +
  70.298 +/**
  70.299 + * @param y number of option to draw
  70.300 + * @param hilite bitfield: 1=focused, 0=not
  70.301 + */
  70.302 +void optionsDrawRow(const unsigned char *prefs,
  70.303 +                    int dstY, int line, int value, int hilite) {
  70.304 +  unsigned int ht = text_height(OPTIONS_FONT);
  70.305 +  int buttonY = OPTIONS_TOP + OPTIONS_ROW_HT * dstY;
  70.306 +  int rowBg = bgColor;
  70.307 +  const char *nameText;
  70.308 +  char altNameText[8];
  70.309 +  char valueText[OPTIONS_VALUE_LEN];
  70.310 +  const char *valueOverride = isDisabledOption(prefs, line);
  70.311 +  int textcolor = fgColor;
  70.312 +  const char *valueDesc = NULL;
  70.313 +
  70.314 +  {
  70.315 +    nameText = ljGetFourCCName(optionsMenu[line].name);
  70.316 +    if (!nameText) {
  70.317 +      strncpy(altNameText, optionsMenu[line].name.c, 4);
  70.318 +      altNameText[4] = 0;
  70.319 +      nameText = altNameText;
  70.320 +    }
  70.321 +  }
  70.322 +
  70.323 +  if (valueOverride) {
  70.324 +    hilite |= 2;
  70.325 +    textcolor = makecol(128, 128, 128);
  70.326 +  }
  70.327 +  
  70.328 +  if (hilite == 3) {
  70.329 +    rowBg = makecol(204, 204, 204);
  70.330 +  } else if (hilite == 1) {
  70.331 +    rowBg = hiliteColor;
  70.332 +  }
  70.333 +
  70.334 +  // If the value of this option is within range, format it as a string.
  70.335 +  if (value >= optionsMenu[line].minValue
  70.336 +      && value < optionsMenu[line].minValue + optionsMenu[line].nValues) {
  70.337 +    if (valueOverride) {
  70.338 +      ustrzcpy(valueText, sizeof(valueText), "overridden");
  70.339 +      valueDesc = valueOverride;
  70.340 +    } else {
  70.341 +      valueDesc = getOptionsValueStr(valueText, line + OPTIONS_OFFSET, value);
  70.342 +    }
  70.343 +  } else {
  70.344 +    valueText[0] = '\0';
  70.345 +  }
  70.346 +
  70.347 +  // draw current option
  70.348 +  acquire_screen();
  70.349 +  rectfill(screen,
  70.350 +           OPTIONS_ROW_LEFT, buttonY,
  70.351 +           OPTIONS_ROW_RIGHT - 1, buttonY + OPTIONS_ROW_HT - 1,
  70.352 +           rowBg);
  70.353 +  textout_ex(screen, OPTIONS_FONT, nameText,
  70.354 +             OPTIONS_ROW_LEFT + 8, buttonY + (OPTIONS_ROW_HT - ht) / 2,
  70.355 +             textcolor, rowBg);
  70.356 +  textout_ex(screen, OPTIONS_FONT,
  70.357 +             valueText,
  70.358 +             OPTIONS_ROW_MID, buttonY + (OPTIONS_ROW_HT - ht) / 2,
  70.359 +             textcolor, rowBg);
  70.360 +  
  70.361 +  // For an enabled selected item, draw the frame
  70.362 +  if (hilite == 1) {
  70.363 +    rect(screen,
  70.364 +         OPTIONS_ROW_LEFT, buttonY,
  70.365 +         OPTIONS_ROW_RIGHT - 1, buttonY + OPTIONS_ROW_HT - 1,
  70.366 +         fgColor);
  70.367 +  }
  70.368 +  
  70.369 +  // For a selected item, draw the help text
  70.370 +  if (hilite & 1) {
  70.371 +    buttonY = OPTIONS_TOP + OPTIONS_ROW_HT * OPTIONS_MENU_VIS;
  70.372 +    rectfill(screen,
  70.373 +             OPTIONS_ROW_LEFT, buttonY,
  70.374 +             OPTIONS_ROW_RIGHT - 1, buttonY + OPTIONS_ROW_HT * 5 / 2 - 1,
  70.375 +             bgColor);
  70.376 +
  70.377 +    const char *descText = ljGetFourCCDesc(optionsMenu[line].name);
  70.378 +    if (descText) {
  70.379 +      textout_ex(screen, OPTIONS_FONT, descText,
  70.380 +                 OPTIONS_ROW_LEFT,
  70.381 +                 buttonY + OPTIONS_ROW_HT - ht / 2,
  70.382 +                 textcolor, bgColor);
  70.383 +    }
  70.384 +    if (valueDesc) {
  70.385 +      textout_ex(screen, OPTIONS_FONT, valueDesc,
  70.386 +                 OPTIONS_ROW_LEFT,
  70.387 +                 buttonY + 2 * OPTIONS_ROW_HT - ht / 2,
  70.388 +                 textcolor, bgColor);
  70.389 +    }
  70.390 +  }
  70.391 +
  70.392 +  release_screen();
  70.393 +}
  70.394 +
  70.395 +void optionsClearRow(int dstY, int hilite) {
  70.396 +  int buttonY = OPTIONS_TOP + OPTIONS_ROW_HT * dstY;
  70.397 +  rectfill(screen,
  70.398 +           OPTIONS_ROW_LEFT, buttonY,
  70.399 +           OPTIONS_ROW_RIGHT - 1, buttonY + OPTIONS_ROW_HT * 5 / 2 - 1,
  70.400 +           bgColor);
  70.401 +}
  70.402 +
  70.403 +void optionsDrawPage(int page, const unsigned char *prefs) {
  70.404 +  int nPages = 0;
  70.405 +  for (; optionsPages[nPages].name; ++nPages) { }
  70.406 +
  70.407 +  vsync();
  70.408 +  acquire_screen();
  70.409 +  rectfill(screen, 320, 32, SCREEN_W - 1, 63, bgColor);
  70.410 +  textprintf_right_ex(screen, aver32,
  70.411 +                      SCREEN_W - 16, 32, fgColor, -1,
  70.412 +                      "(%d/%d) %s",
  70.413 +                      page + 1, nPages, optionsPages[page].name);
  70.414 +
  70.415 +  for (int i = optionsPages[page].start;
  70.416 +       i < optionsPages[page + 1].start; ++i) {
  70.417 +    optionsDrawRow(prefs, i - optionsPages[page].start,
  70.418 +                   i, prefs[i], 0);
  70.419 +  }
  70.420 +  for (int i = optionsPages[page + 1].start;
  70.421 +       i < optionsPages[page].start + OPTIONS_MENU_VIS; ++i) {
  70.422 +    optionsClearRow(i - optionsPages[page].start, 0);
  70.423 +  }
  70.424 +  release_screen();
  70.425 +}
  70.426 +
  70.427 +void optionsWinInit(void) {
  70.428 +  acquire_screen();
  70.429 +  clear_to_color(screen, bgColor);
  70.430 +  textout_ex(screen, aver32, "LOCKJAW > Options", 16, 32, fgColor, -1);
  70.431 +  textout_ex(screen, aver32, "Rotate: change page; Up/Down: select", 16, 522, fgColor, -1);
  70.432 +  textout_ex(screen, aver32, "Left/Right change; Enter: exit", 16, 552, fgColor, -1);
  70.433 +  release_screen();
  70.434 +
  70.435 +}
  70.436 +
  70.437 +void optionsIdle(void) {
  70.438 +  rest(10);
  70.439 +}
    71.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    71.2 +++ b/src/options.c	Fri Mar 13 00:39:12 2009 -0700
    71.3 @@ -0,0 +1,642 @@
    71.4 +/* options code for LOCKJAW, an implementation of the Soviet Mind Game
    71.5 +
    71.6 +Copyright (C) 2007-2008 Damian Yerrick <tepples+lj@spamcop.net>
    71.7 +
    71.8 +This work is free software; you can redistribute it and/or modify
    71.9 +it under the terms of the GNU General Public License as published by
   71.10 +the Free Software Foundation; either version 2 of the License, or
   71.11 +(at your option) any later version.
   71.12 +
   71.13 +This program is distributed in the hope that it will be useful,
   71.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   71.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   71.16 +GNU General Public License for more details.
   71.17 +
   71.18 +You should have received a copy of the GNU General Public License
   71.19 +along with this program; if not, write to the Free Software
   71.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   71.21 +
   71.22 +Original game concept and design by Alexey Pajitnov.
   71.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   71.24 +or The Tetris Company LLC.
   71.25 +
   71.26 +*/
   71.27 +
   71.28 +
   71.29 +#include <string.h>
   71.30 +#include <stdio.h>
   71.31 +#include "options.h"
   71.32 +#include "ljplay.h"
   71.33 +#include "ljlocale.h"
   71.34 +
   71.35 +#ifdef HAS_FPU
   71.36 +#define siprintf sprintf
   71.37 +#define MIN_SHADOW 0
   71.38 +#define N_SHADOWS LJSHADOW_N_STYLES
   71.39 +#define FACTORY_SHADOW 0
   71.40 +#include "ljpc.h"
   71.41 +#else
   71.42 +#define MIN_SHADOW LJSHADOW_COLORED
   71.43 +#define N_SHADOWS (LJSHADOW_N_STYLES - 2)
   71.44 +#define FACTORY_SHADOW MIN_SHADOW
   71.45 +#endif
   71.46 +
   71.47 +const FourCC optionsBoolNames[2] = {
   71.48 +  {"Off"}, {"On"}
   71.49 +};
   71.50 +
   71.51 +const FourCC gimmickNames[LJGM_N_GIMMICKS] = {
   71.52 +  {"Mara"}, {"Line"}, {"Time"}, {"DrlA"}, {"Item"}, {"Keys"}
   71.53 +};
   71.54 +
   71.55 +const FourCC optionsGarbageNames[LJGARBAGE_N_STYLES] = {
   71.56 +  {"Off"}, {"Lv.1"}, {"Lv.2"}, {"Lv.3"}, {"Lv.4"},
   71.57 +  {"HRD"}, {"DrlG"}, {"Zigz"}
   71.58 +};
   71.59 +
   71.60 +const FourCC optionsScoringNames[LJSCORE_N_STYLES] = {
   71.61 +  {"LJ"}, {"Fibo"}, {"HotL"}, {"TDSl"}, {"NESl"}, {"LJns"}
   71.62 +};
   71.63 +
   71.64 +const FourCC optionsKickLimitNames[N_KICK_LIMITS] = {
   71.65 +  {"Off"}, {"1"}, {"2"}, {"3"}, {"4"}, {"5"}, {"Inf"}
   71.66 +};
   71.67 +
   71.68 +const FourCC optionsSpeedCurveNames[] = {
   71.69 +  {"Zero"}, {"Rhy0"}, {"Exp"},  {"Rh20"},
   71.70 +  {"TGM2"}, {"TAPD"}, {"TAD3"},
   71.71 +  {"NESc"}, {"GBc"},  {"GBHL"}
   71.72 +};
   71.73 +
   71.74 +static const FourCC optionsWindowedNames[2] = {
   71.75 +  {"FulS"}, {"Wind"}
   71.76 +};
   71.77 +
   71.78 +const FourCC optionsShadowNames[LJSHADOW_N_STYLES] = {
   71.79 +  {"tl25"}, {"tl50"}, {"tlsC"}, {"tlsM"}, {"Off"}, {"invF"}
   71.80 +};
   71.81 +
   71.82 +const FourCC optionsLockDelayNames[2] = {
   71.83 +  {"SBSC"}, {"Inf"}
   71.84 +};
   71.85 +
   71.86 +const FourCC optionsSBSCNames[2] = {
   71.87 +  {"SBSC"}, {.i=0}
   71.88 +};
   71.89 +
   71.90 +const FourCC optionsTspinNames[] = {
   71.91 +  {"Off"}, {"Imob"}, {"CrnT"}, {"WKT"}
   71.92 +};
   71.93 +
   71.94 +const FourCC optionsLockdownNames[] = {
   71.95 +  {"OLD"}, {"EntR"}, {"StpR"}, {"MovR"}
   71.96 +};
   71.97 +
   71.98 +const FourCC optionsHoldStyleNames[] = {
   71.99 +  {"Off"}, {"HldE"}, {"HldR"}, {"HldN"}
  71.100 +};
  71.101 +
  71.102 +const FourCC optionsDropScoringNames[LJDROP_N_STYLES] = {
  71.103 +  {"None"}, {"ConD"}, {"S1H1"}, {"S1H2"}
  71.104 +};
  71.105 +
  71.106 +const FourCC optionsGravNames[] = {
  71.107 +  {"Naiv"}, {"Stky"}, {"byCo"}, {"Casc"}
  71.108 +};
  71.109 +
  71.110 +const FourCC optionsPieceSetNames[LJRAND_N_PIECE_SETS] = {
  71.111 +  {"IJLO"},
  71.112 +  {"JLOT"},
  71.113 +  {"SZSZ"},
  71.114 +  {"IIII"},
  71.115 +  {"AllP"},
  71.116 +  {"TTTT"}
  71.117 +};
  71.118 +
  71.119 +const FourCC optionsRandNames[LJRAND_N_RANDS] = {
  71.120 +  {"Unif"},
  71.121 +  {"Bag"},
  71.122 +  {"Bag+"},
  71.123 +  {"Bag2"},
  71.124 +  {"Hist"},
  71.125 +  {"His6"}
  71.126 +};
  71.127 +
  71.128 +const FourCC optionsLineDelayNames[] = {
  71.129 +  {"SBSC"}, {.i=0}
  71.130 +};
  71.131 +
  71.132 +const FourCC optionsZangiNames[] = {
  71.133 +  {"Slid"}, {"Lock"}, {"LocU"}
  71.134 +};
  71.135 +
  71.136 +const FourCC optionsGluingNames[] = {
  71.137 +  {"Off"}, {"Squ"}, {"Stky"}, {"byCo"}
  71.138 +};
  71.139 +
  71.140 +const FourCC optionsRotNames[N_ROTATION_SYSTEMS] = {
  71.141 +  {"SRS"},  {"Sega"}, {"ARS"},  {"Tngn"},
  71.142 +  {"NRSR"}, {"NRSL"}, {"TOD4"}, {"TDX"}
  71.143 +};
  71.144 +
  71.145 +
  71.146 +const OptionsLine commonOptionsMenu[] = {
  71.147 +  {{"gimm"}, gimmickNames,
  71.148 +    0, LJGM_N_GIMMICKS,       0 },
  71.149 +  {{"pfw"},  NULL,
  71.150 +    4, LJ_PF_WID - 4 + 1,     10 },
  71.151 +  {{"pfh"},  NULL,
  71.152 +    8, LJ_PF_VIS_HT - 8 + 1,  LJ_PF_VIS_HT },
  71.153 +  {{"vzsp"}, optionsBoolNames,
  71.154 +    0, 2,                     1 },
  71.155 +  {{"spdc"}, optionsSpeedCurveNames,
  71.156 +    0, LJSPD_N_CURVES,        LJSPD_EXP },
  71.157 +  {{"are"},  NULL,
  71.158 +    0, 121,                   0, OPTSTYLE_FRAMES },
  71.159 +  {{"piec"}, optionsPieceSetNames,
  71.160 +    0, LJRAND_N_PIECE_SETS,   LJRAND_4BLK },
  71.161 +  {{"rand"}, optionsRandNames,
  71.162 +    0, LJRAND_N_RANDS,        LJRAND_BAG },
  71.163 +
  71.164 +  {{"hold"}, optionsHoldStyleNames,
  71.165 +    0, LJHOLD_N_STYLES,       LJHOLD_EMPTY },
  71.166 +  {{"rots"}, optionsRotNames,
  71.167 +    0, N_ROTATION_SYSTEMS,    0 },
  71.168 +  {{"upkl"}, optionsKickLimitNames,
  71.169 +    0, N_KICK_LIMITS,         N_KICK_LIMITS - 1 },
  71.170 +  {{"lock"}, optionsLockdownNames,
  71.171 +    0, LJLOCK_N_STYLES,       LJLOCK_MOVE },
  71.172 +  {{"sldt"}, optionsLockDelayNames,
  71.173 +    0, 129,                   0, OPTSTYLE_FRAMES },
  71.174 +  {{"deep"}, optionsBoolNames,
  71.175 +    0, 2,                     0 },
  71.176 +
  71.177 +  {{"clrd"}, optionsSBSCNames,
  71.178 +    0, 121,                   0, OPTSTYLE_FRAMES },
  71.179 +  {{"grav"}, optionsGravNames,
  71.180 +    0, LJGRAV_N_ALGOS,        LJGRAV_NAIVE },
  71.181 +  {{"glue"}, optionsGluingNames,
  71.182 +    0, LJGLUING_N_STYLES,     LJGLUING_NONE },
  71.183 +  {{"lsco"}, optionsScoringNames,
  71.184 +    0, LJSCORE_N_STYLES },
  71.185 +  {{"dsco"}, optionsDropScoringNames,
  71.186 +    0, LJDROP_N_STYLES,       0 },
  71.187 +  {{"tspn"}, optionsTspinNames,
  71.188 +    0, LJTS_N_ALGOS,          LJTS_TDS },
  71.189 +  {{"garb"}, optionsGarbageNames,
  71.190 +    0, LJGARBAGE_N_STYLES,    0 },
  71.191 +
  71.192 +  {{"dasd"}, NULL,
  71.193 +    1, 120,                   10, OPTSTYLE_FRAMES },
  71.194 +  {{"dass"}, NULL,
  71.195 +    0, 10,                    1, OPTSTYLE_FRAC_G },
  71.196 +  {{"idas"}, optionsBoolNames,
  71.197 +    0, 2,                     1 },
  71.198 +  {{"irs"},  optionsBoolNames,
  71.199 +    0, 2,                     1 },
  71.200 +  {{"8way"}, optionsBoolNames,
  71.201 +    0, 2,                     0 },
  71.202 +
  71.203 +  {{"sfds"}, NULL,
  71.204 +    1, 3, 1, OPTSTYLE_FRAC_G },
  71.205 +  {{"sfdl"}, optionsZangiNames,
  71.206 +    0, LJZANGI_N_STYLES,      LJZANGI_SLIDE },
  71.207 +  {{"hrdl"}, optionsZangiNames,
  71.208 +    0, LJZANGI_N_STYLES,      LJZANGI_LOCK },
  71.209 +
  71.210 +  {{"tls"},  optionsShadowNames + MIN_SHADOW,
  71.211 +    MIN_SHADOW, N_SHADOWS,    FACTORY_SHADOW },
  71.212 +  {{"invs"}, optionsBoolNames,
  71.213 +    0, 2,                     0 },
  71.214 +  {{"next"}, NULL,
  71.215 +    0, LJ_NEXT_PIECES + 1,    6 },
  71.216 +  {{"srph"}, optionsBoolNames,
  71.217 +    0, 2,                     1 },
  71.218 +
  71.219 +#ifdef HAS_FPU
  71.220 +  {{"inpv"}, NULL,
  71.221 +    0, LJ_NEXT_PIECES + 1,    0 },
  71.222 +  {{"mblr"}, optionsBoolNames,
  71.223 +    0, 2,                     1 },
  71.224 +  {{"lidp"}, optionsBoolNames,
  71.225 +    0, 2,                     1 },
  71.226 +  {{"rec"},  optionsBoolNames,
  71.227 +    0, 2,                     0 },
  71.228 +  {{"wndw"}, optionsWindowedNames,
  71.229 +    0, 2,                     1 }
  71.230 +#endif
  71.231 +};
  71.232 +
  71.233 +const OptionsPage optionsPages[] = {
  71.234 +  {OPTIONS_GIMMICK, "Game"},
  71.235 +  {OPTIONS_WIDTH, "Rules: Well"},
  71.236 +  {OPTIONS_HOLD_PIECE, "Rules: Movement"},
  71.237 +  {OPTIONS_LINE_DELAY, "Rules: Line clear"},
  71.238 +  {OPTIONS_SIDEWAYS_DELAY, "Control: Movement"},
  71.239 +  {OPTIONS_SOFT_DROP_SPEED, "Control: Drop"},
  71.240 +  {OPTIONS_SHADOW, "Display"},
  71.241 +#ifdef HAS_FPU
  71.242 +  {OPTIONS_MENU_LEN, "PC"},
  71.243 +  {PC_OPTIONS_MENU_LEN, NULL}
  71.244 +#else
  71.245 +  {OPTIONS_MENU_LEN, NULL}
  71.246 +#endif
  71.247 +};
  71.248 +
  71.249 +void setOptionsValueToFourCC(char *dst, FourCC f) {
  71.250 +  const char *valueName = ljGetFourCCName(f);
  71.251 +  if (valueName) {
  71.252 +    strncpy(dst,
  71.253 +            valueName,
  71.254 +            OPTIONS_VALUE_LEN - 1);
  71.255 +    dst[OPTIONS_VALUE_LEN - 1] = 0;
  71.256 +  } else {
  71.257 +    strncpy(dst, f.c, 4);
  71.258 +    dst[4] = 0;
  71.259 +  }
  71.260 +}
  71.261 +
  71.262 +struct DisabledOption {
  71.263 +  unsigned char name;
  71.264 +  unsigned char value;
  71.265 +  unsigned char name2;
  71.266 +  char reason[45];
  71.267 +};
  71.268 +
  71.269 +/*
  71.270 +   Semantics:
  71.271 +   If option name is set to value,
  71.272 +   then gray out option name2 and draw its value as reason.
  71.273 +*/
  71.274 +#define N_DISABLED_OPTIONS 12
  71.275 +const struct DisabledOption disabledOptions[N_DISABLED_OPTIONS] = {
  71.276 +  {  OPTIONS_LOCKDOWN,     LJLOCK_NOW,
  71.277 +     OPTIONS_SOFT_DROP, "Lockdown is immediate" },
  71.278 +  {  OPTIONS_LOCKDOWN,     LJLOCK_NOW,
  71.279 +     OPTIONS_HARD_DROP, "Lockdown is immediate" },
  71.280 +  {  OPTIONS_LOCKDOWN,     LJLOCK_NOW,
  71.281 +     OPTIONS_LOCK_DELAY,     "Lockdown is immediate" },
  71.282 +  {  OPTIONS_LOCK_DELAY,     128,
  71.283 +     OPTIONS_LOCKDOWN,     "Lockdown is manual" },
  71.284 +  {  OPTIONS_SPEED_CURVE,    LJSPD_DEATH,
  71.285 +     OPTIONS_SOFT_DROP_SPEED,"Death: pieces land instantly" },
  71.286 +  {  OPTIONS_SPEED_CURVE,    LJSPD_RHYTHM,
  71.287 +     OPTIONS_SOFT_DROP_SPEED,"Rhythm: pieces land instantly" },
  71.288 +  {  OPTIONS_SPEED_CURVE,    LJSPD_DEATH,
  71.289 +     OPTIONS_SMOOTH_GRAVITY, "Death: pieces land instantly" },
  71.290 +  {  OPTIONS_SPEED_CURVE,    LJSPD_RHYTHM,
  71.291 +     OPTIONS_SMOOTH_GRAVITY, "Rhythm: pieces land instantly" },
  71.292 +  {  OPTIONS_SPEED_CURVE,    LJSPD_DEATH,
  71.293 +     OPTIONS_SOFT_DROP,      "Death: pieces land instantly" },
  71.294 +  {  OPTIONS_SPEED_CURVE,    LJSPD_RHYTHM,
  71.295 +     OPTIONS_SOFT_DROP,      "Rhythm: pieces land instantly" },
  71.296 +  {  OPTIONS_SPEED_CURVE,    LJSPD_DEATH,
  71.297 +     OPTIONS_HARD_DROP,      "Death: pieces land instantly" },
  71.298 +  {  OPTIONS_SPEED_CURVE,    LJSPD_RHYTHM,
  71.299 +     OPTIONS_HARD_DROP,      "Rhythm: pieces land instantly" },
  71.300 +};
  71.301 +
  71.302 +const char *isDisabledOption(const unsigned char *prefs, int y) {
  71.303 +  for (int i = 0; i < N_DISABLED_OPTIONS; ++i) {
  71.304 +    if (y == disabledOptions[i].name2) {
  71.305 +      int name = disabledOptions[i].name;
  71.306 +      int value = disabledOptions[i].value;
  71.307 +
  71.308 +      if (prefs[name] == value) {
  71.309 +        return disabledOptions[i].reason;
  71.310 +      }
  71.311 +    }
  71.312 +  }
  71.313 +  return NULL;
  71.314 +}
  71.315 +
  71.316 +
  71.317 +const char *getOptionsValueStr(char *dst, int line, int value) {
  71.318 +  const OptionsLine *l = &(commonOptionsMenu[line]);
  71.319 +  FourCC f = {.i = 0};
  71.320 +  const char *desc = NULL;
  71.321 +
  71.322 +  switch (l->style) {
  71.323 +  case OPTSTYLE_DEFAULT:
  71.324 +    if (l->valueNames) {
  71.325 +      f = l->valueNames[value - l->minValue];
  71.326 +    } else {
  71.327 +      siprintf(dst, "%d", value);
  71.328 +    }
  71.329 +    break;
  71.330 +    
  71.331 +  case OPTSTYLE_FRAMES:
  71.332 +    if (l->valueNames
  71.333 +        && value == l->minValue
  71.334 +        && l->valueNames[0].i) {
  71.335 +        
  71.336 +      // override first with name 0
  71.337 +      f = l->valueNames[0];
  71.338 +    } else if (l->valueNames
  71.339 +                && value == l->minValue
  71.340 +                            + l->nValues - 1
  71.341 +                && l->valueNames[1].i) {
  71.342 +
  71.343 +      // override second with name 1
  71.344 +      f = l->valueNames[1];
  71.345 +    } else {
  71.346 +      if (value >= 60) {
  71.347 +        int ds = value / 6;
  71.348 +        int s = ds / 10;
  71.349 +        ds -= s * 10;
  71.350 +        siprintf(dst, "%d/60 s (%d.%d s)", value, s, ds);
  71.351 +      } else {
  71.352 +        int ms = value * 50 / 3;
  71.353 +        siprintf(dst, "%d/60 s (%d ms)", value, ms);
  71.354 +      }
  71.355 +    } break;
  71.356 +
  71.357 +  case OPTSTYLE_FRAC_G:
  71.358 +    if (value > 6) {
  71.359 +      int dHz = 600 / value;
  71.360 +      int Hz = dHz / 10;
  71.361 +      dHz -= Hz * 10;
  71.362 +      siprintf(dst, "1/%dG (%d.%d Hz)", value, Hz, dHz);
  71.363 +    } else if (value > 0) {
  71.364 +      if (value > 1) {
  71.365 +        dst[0] = '1';
  71.366 +        dst[1] = '/';
  71.367 +        dst += 2;
  71.368 +      }
  71.369 +      siprintf(dst, "%dG (%d Hz)", value, 60 / value);
  71.370 +    } else {
  71.371 +      strcpy(dst, "Instant");
  71.372 +    }
  71.373 +    break;
  71.374 +
  71.375 +  default:
  71.376 +    strncpy(dst, "Unknown option style.", OPTIONS_VALUE_LEN - 1);
  71.377 +    dst[OPTIONS_VALUE_LEN - 1] = 0;
  71.378 +    break;
  71.379 +  }
  71.380 +  
  71.381 +  /* If we have a fourCC, use it. */
  71.382 +  if (f.i != 0) {
  71.383 +    setOptionsValueToFourCC(dst, f);
  71.384 +    desc = ljGetFourCCDesc(f);
  71.385 +  }
  71.386 +  return desc;
  71.387 +}
  71.388 +
  71.389 +void unpackCommonOptions(LJView *v, const unsigned char *prefs) {
  71.390 +  if (prefs[OPTIONS_GIMMICK] < 255)
  71.391 +    v->field->gimmick = prefs[OPTIONS_GIMMICK];
  71.392 +  if (prefs[OPTIONS_WIDTH] < 255) {
  71.393 +    int width = prefs[OPTIONS_WIDTH];
  71.394 +    v->field->leftWall = (LJ_PF_WID - width) / 2;
  71.395 +    v->field->rightWall = v->field->leftWall + width;
  71.396 +  }
  71.397 +  if (prefs[OPTIONS_HEIGHT] < 255)
  71.398 +    v->field->ceiling = prefs[OPTIONS_HEIGHT];
  71.399 +  if (prefs[OPTIONS_ENTER_ABOVE] < 255)
  71.400 +    v->field->enterAbove = prefs[OPTIONS_ENTER_ABOVE];
  71.401 +  if (prefs[OPTIONS_SPEED_CURVE] < 255)
  71.402 +    v->field->speedState.curve = prefs[OPTIONS_SPEED_CURVE];
  71.403 +  if (prefs[OPTIONS_ENTRY_DELAY] < 255)
  71.404 +    v->field->areStyle = prefs[OPTIONS_ENTRY_DELAY];
  71.405 +  if (prefs[OPTIONS_PIECE_SET] < 255)
  71.406 +    v->field->pieceSet = prefs[OPTIONS_PIECE_SET];
  71.407 +  if (prefs[OPTIONS_RANDOMIZER] < 255)
  71.408 +    v->field->randomizer = prefs[OPTIONS_RANDOMIZER];
  71.409 +
  71.410 +  if (prefs[OPTIONS_ROTATION_SYSTEM] < 255)
  71.411 +    v->field->rotationSystem = prefs[OPTIONS_ROTATION_SYSTEM];
  71.412 +  if (prefs[OPTIONS_FLOOR_KICKS] < 255)
  71.413 +    v->field->maxUpwardKicks = prefs[OPTIONS_FLOOR_KICKS] == N_KICK_LIMITS - 1
  71.414 +                               ? 128
  71.415 +                               : prefs[OPTIONS_FLOOR_KICKS];
  71.416 +  if (prefs[OPTIONS_HOLD_PIECE] < 255)
  71.417 +    v->field->holdStyle = prefs[OPTIONS_HOLD_PIECE];
  71.418 +  if (prefs[OPTIONS_LOCKDOWN] < 255)
  71.419 +    v->field->lockReset = prefs[OPTIONS_LOCKDOWN];
  71.420 +  if (prefs[OPTIONS_LOCK_DELAY] < 255)
  71.421 +    v->field->setLockDelay = prefs[OPTIONS_LOCK_DELAY];
  71.422 +  if (prefs[OPTIONS_BOTTOM_BLOCKS] < 255)
  71.423 +    v->field->bottomBlocks = prefs[OPTIONS_BOTTOM_BLOCKS];
  71.424 +
  71.425 +  if (prefs[OPTIONS_LINE_DELAY] < 255)
  71.426 +    v->field->setLineDelay = prefs[OPTIONS_LINE_DELAY];
  71.427 +  if (prefs[OPTIONS_T_SPIN] < 255)
  71.428 +    v->field->tSpinAlgo = prefs[OPTIONS_T_SPIN];
  71.429 +  if (prefs[OPTIONS_CLEAR_GRAVITY] < 255)
  71.430 +    v->field->clearGravity = prefs[OPTIONS_CLEAR_GRAVITY];
  71.431 +  if (prefs[OPTIONS_GLUING] < 255)
  71.432 +    v->field->gluing = prefs[OPTIONS_GLUING];
  71.433 +  if (prefs[OPTIONS_SCORING] < 255)
  71.434 +    v->field->scoreStyle = prefs[OPTIONS_SCORING];
  71.435 +  if (prefs[OPTIONS_DROP_SCORING] < 255)
  71.436 +    v->field->dropScoreStyle = prefs[OPTIONS_DROP_SCORING];
  71.437 +  if (prefs[OPTIONS_GARBAGE] < 255)
  71.438 +    v->field->garbageStyle = prefs[OPTIONS_GARBAGE];
  71.439 +
  71.440 +  if (prefs[OPTIONS_SIDEWAYS_DELAY] < 255)
  71.441 +    v->control->dasDelay = prefs[OPTIONS_SIDEWAYS_DELAY];
  71.442 +  if (prefs[OPTIONS_SIDEWAYS_SPEED] < 255)
  71.443 +    v->control->dasSpeed = prefs[OPTIONS_SIDEWAYS_SPEED];
  71.444 +  if (v->control->dasDelay < v->control->dasSpeed) {
  71.445 +    v->control->dasDelay = v->control->dasSpeed;
  71.446 +  }
  71.447 +  if (prefs[OPTIONS_INITIAL_SIDEWAYS] < 255)
  71.448 +    v->control->initialDAS = prefs[OPTIONS_INITIAL_SIDEWAYS];
  71.449 +  if (prefs[OPTIONS_IRS] < 255)
  71.450 +    v->control->initialRotate = prefs[OPTIONS_IRS];
  71.451 +  if (prefs[OPTIONS_DIAGONAL_MOTION] < 255)
  71.452 +    v->control->allowDiagonals = prefs[OPTIONS_DIAGONAL_MOTION];
  71.453 +  if (prefs[OPTIONS_SOFT_DROP_SPEED] < 255)
  71.454 +    v->control->softDropSpeed  = prefs[OPTIONS_SOFT_DROP_SPEED] - 1;
  71.455 +  if (prefs[OPTIONS_SOFT_DROP] < 255)
  71.456 +    v->control->softDropLock = prefs[OPTIONS_SOFT_DROP];
  71.457 +  if (prefs[OPTIONS_HARD_DROP] < 255)
  71.458 +    v->control->hardDropLock = prefs[OPTIONS_HARD_DROP];
  71.459 +
  71.460 +  if (prefs[OPTIONS_SHADOW] < 255)
  71.461 +    v->hideShadow = prefs[OPTIONS_SHADOW];
  71.462 +  if (prefs[OPTIONS_HIDE_PF] < 255)
  71.463 +    v->hidePF = prefs[OPTIONS_HIDE_PF];
  71.464 +  if (prefs[OPTIONS_NEXT_PIECES] < 255)
  71.465 +    v->nextPieces = prefs[OPTIONS_NEXT_PIECES];
  71.466 +  if (prefs[OPTIONS_SMOOTH_GRAVITY] < 255)
  71.467 +    v->smoothGravity = prefs[OPTIONS_SMOOTH_GRAVITY];
  71.468 +}
  71.469 +
  71.470 +void initOptions(unsigned char *prefs) {
  71.471 +  for (int i = 0; i < OPTIONS_MENU_LEN; ++i) {
  71.472 +    prefs[i] = commonOptionsMenu[i].startValue;
  71.473 +  }
  71.474 +}
  71.475 +
  71.476 +LJBits menuReadPad(void);
  71.477 +void vsync(void);
  71.478 +
  71.479 +void options(LJView *v, unsigned char *prefs) {
  71.480 +  int page = 0, y = 0;
  71.481 +  int redraw = 2;
  71.482 +  int done = 0;
  71.483 +  LJBits lastKeys = ~0;
  71.484 +  int dasDir = 0;
  71.485 +  int dasCounter = 0;
  71.486 +  const OptionsLine const *optionsMenu = commonOptionsMenu;
  71.487 +  int lastClock = getTime();
  71.488 +  int erase = -1;
  71.489 +  
  71.490 +  optionsWinInit();
  71.491 +    
  71.492 +  while (!done) {
  71.493 +    if (redraw) {
  71.494 +      if (redraw == 2) {
  71.495 +        redraw = 1;
  71.496 +        optionsDrawPage(page, prefs);
  71.497 +      }
  71.498 +      if (redraw == 1) {
  71.499 +        if (erase >= 0) {
  71.500 +          vsync();
  71.501 +          optionsDrawRow(prefs,
  71.502 +                         erase - optionsPages[page].start,
  71.503 +                         erase, prefs[erase], 0);
  71.504 +          erase = -1;
  71.505 +        }
  71.506 +        optionsDrawRow(prefs,
  71.507 +                       y - optionsPages[page].start,
  71.508 +                       y, prefs[y], 1);
  71.509 +        redraw = 0;
  71.510 +      }
  71.511 +    } else {
  71.512 +      optionsIdle();
  71.513 +    }
  71.514 +    
  71.515 +    LJBits keys = menuReadPad();
  71.516 +    LJBits sounds = 0;
  71.517 +    int lastY = y;
  71.518 +    LJBits newKeys = keys & ~lastKeys;
  71.519 +    LJBits dasKeys = 0;
  71.520 +
  71.521 +    if (getTime() != lastClock) {
  71.522 +      // Handle DAS within options (fixed at 250 ms 30 Hz)
  71.523 +      lastClock = getTime();
  71.524 +      if (keys & dasDir) {
  71.525 +        ++dasCounter;
  71.526 +        if (dasCounter >= 15) {
  71.527 +          dasCounter -= 2;
  71.528 +          dasKeys = dasDir;
  71.529 +        }
  71.530 +      } else {
  71.531 +        dasCounter = 0;
  71.532 +      }
  71.533 +    }
  71.534 +    
  71.535 +    if (newKeys & VKEY_UP
  71.536 +        || ((dasKeys & VKEY_UP)
  71.537 +            && y > optionsPages[page].start)) {
  71.538 +      dasDir = VKEY_UP;
  71.539 +      if (y <= 0) {
  71.540 +        while (optionsPages[page].name) {
  71.541 +          ++page;
  71.542 +        }
  71.543 +        y = optionsPages[page].start - 1;
  71.544 +      } else {
  71.545 +        --y;
  71.546 +      }
  71.547 +    }
  71.548 +    if (newKeys & VKEY_DOWN
  71.549 +        || ((dasKeys & VKEY_DOWN)
  71.550 +             && y < optionsPages[page + 1].start - 1)) {
  71.551 +      dasDir = VKEY_DOWN;
  71.552 +      ++y;
  71.553 +      if (y >= optionsPages[page + 1].start
  71.554 +          && !optionsPages[page + 1].name) {
  71.555 +        y = 0;
  71.556 +      }
  71.557 +    }
  71.558 +
  71.559 +    if (!isDisabledOption(prefs, y)) {    
  71.560 +      if ((newKeys | dasKeys) & VKEY_RIGHT) {
  71.561 +        int num = prefs[y] + 1;
  71.562 +
  71.563 +        if (num >= optionsMenu[y].minValue + optionsMenu[y].nValues) {
  71.564 +          prefs[y] = optionsMenu[y].minValue;
  71.565 +        } else {
  71.566 +          prefs[y] = num;
  71.567 +        }
  71.568 +
  71.569 +        sounds |= LJSND_ROTATE;
  71.570 +        // XXX: need to redraw the whole box (redraw = 2)
  71.571 +        // if options have become enabled or disabled
  71.572 +        redraw = 1;
  71.573 +        dasDir = VKEY_RIGHT;
  71.574 +      }
  71.575 +
  71.576 +      if ((newKeys | dasKeys) & VKEY_LEFT) {
  71.577 +        int num = prefs[y] - 1;
  71.578 +
  71.579 +        if (num < optionsMenu[y].minValue) {
  71.580 +          prefs[y] = optionsMenu[y].minValue + optionsMenu[y].nValues - 1;
  71.581 +        } else {
  71.582 +          prefs[y] = num;
  71.583 +        }
  71.584 +
  71.585 +        sounds |= LJSND_ROTATE;
  71.586 +        redraw = 1;
  71.587 +        dasDir = VKEY_LEFT;
  71.588 +      }
  71.589 +    }
  71.590 +
  71.591 +    // Rotate left: Go to the top of the previous page if it exists.
  71.592 +    if (newKeys & VKEY_ROTL) {
  71.593 +      if (page > 0) {
  71.594 +        y = optionsPages[page - 1].start;
  71.595 +      } else {
  71.596 +        y = 0;
  71.597 +      }
  71.598 +    }
  71.599 +
  71.600 +    // Rotate right: If on last page, finish;
  71.601 +    // otherwise, go to the top of the next page.
  71.602 +    if (newKeys & VKEY_ROTR) {
  71.603 +      if (!optionsPages[page + 1].name) {
  71.604 +        done = 1;
  71.605 +      } else {
  71.606 +        y = optionsPages[page + 1].start;
  71.607 +      }
  71.608 +    }
  71.609 +    
  71.610 +    // Start: finish
  71.611 +    if (newKeys & VKEY_START) {
  71.612 +      done = 1;
  71.613 +    }
  71.614 +
  71.615 +    if (lastY != y) {
  71.616 +      sounds |= LJSND_SHIFT;
  71.617 +
  71.618 +      // calculate which page the cursor has moved to
  71.619 +      int lastPage = page;
  71.620 +      while (y < optionsPages[page].start) {
  71.621 +        --page;
  71.622 +      }
  71.623 +      while (y >= optionsPages[page + 1].start) {
  71.624 +        ++page;
  71.625 +      }
  71.626 +      
  71.627 +      if (lastPage == page) {
  71.628 +        erase = lastY;
  71.629 +        if (redraw < 1) {
  71.630 +          redraw = 1;
  71.631 +        }
  71.632 +      } else {
  71.633 +        // turning the page
  71.634 +        sounds |= LJSND_HOLD;
  71.635 +        redraw = 2;  // redraw the whole screen
  71.636 +      }
  71.637 +    }
  71.638 +    lastKeys = keys;
  71.639 +    
  71.640 +    if (done) {
  71.641 +      sounds |= LJSND_LINE;
  71.642 +    }
  71.643 +    playSoundEffects(v, sounds, 100);
  71.644 +  }
  71.645 +}
    72.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    72.2 +++ b/src/options.h	Fri Mar 13 00:39:12 2009 -0700
    72.3 @@ -0,0 +1,152 @@
    72.4 +/* options code for LOCKJAW, an implementation of the Soviet Mind Game
    72.5 +
    72.6 +Copyright (C) 2007 Damian Yerrick <tepples+lj@spamcop.net>
    72.7 +
    72.8 +This work is free software; you can redistribute it and/or modify
    72.9 +it under the terms of the GNU General Public License as published by
   72.10 +the Free Software Foundation; either version 2 of the License, or
   72.11 +(at your option) any later version.
   72.12 +
   72.13 +This program is distributed in the hope that it will be useful,
   72.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   72.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   72.16 +GNU General Public License for more details.
   72.17 +
   72.18 +You should have received a copy of the GNU General Public License
   72.19 +along with this program; if not, write to the Free Software
   72.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   72.21 +
   72.22 +Original game concept and design by Alexey Pajitnov.
   72.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   72.24 +or The Tetris Company LLC.
   72.25 +
   72.26 +*/
   72.27 +
   72.28 +#ifndef OPTIONS_H
   72.29 +#define OPTIONS_H
   72.30 +
   72.31 +#include "lj.h"
   72.32 +#include "ljcontrol.h"
   72.33 +#include "ljlocale.h"
   72.34 +
   72.35 +typedef struct OptionsLine {
   72.36 +  FourCC name;
   72.37 +  const FourCC *valueNames;
   72.38 +  unsigned char minValue;
   72.39 +  unsigned char nValues;  
   72.40 +  unsigned char startValue;
   72.41 +  unsigned char style;
   72.42 +  // const char *desc;
   72.43 +} OptionsLine;
   72.44 +
   72.45 +/*
   72.46 + * OPTSTYLE_DEFAULT and no valueNames:
   72.47 + *   Render value as a number.
   72.48 + * OPTSTYLE_DEFAULT and valueNames:
   72.49 + *   Render value as valueNames[value - minValue]
   72.50 + * OPTSTYLE_FRAMES and no valueNames:
   72.51 + *   Render value as a number and as a number times 50/3.
   72.52 + * OPTSTYLE_FRAMES and no valueNames:
   72.53 + *   Similar, except override value == minValue with valueNames[0]
   72.54 + *   and value == minValue + nValues - 1 with valueNames[1].
   72.55 + */
   72.56 +enum {
   72.57 +  OPTSTYLE_DEFAULT,  // names, or numbers
   72.58 +  OPTSTYLE_FRAMES,
   72.59 +  OPTSTYLE_FRAC_G
   72.60 +};
   72.61 +
   72.62 +typedef struct OptionsPage {
   72.63 +  unsigned int start;
   72.64 +  const char *name;
   72.65 +} OptionsPage;
   72.66 +
   72.67 +#define N_KICK_LIMITS 7
   72.68 +extern const OptionsLine commonOptionsMenu[];
   72.69 +extern const OptionsPage optionsPages[];
   72.70 +extern const FourCC optionsRotNames[N_ROTATION_SYSTEMS];
   72.71 +extern const FourCC optionsLockdownNames[];
   72.72 +extern const FourCC gimmickNames[LJGM_N_GIMMICKS];
   72.73 +extern const FourCC optionsPieceSetNames[];
   72.74 +extern const FourCC optionsBoolNames[2];
   72.75 +extern const FourCC optionsZangiNames[];
   72.76 +extern const FourCC optionsGluingNames[];
   72.77 +extern const FourCC optionsRandNames[];
   72.78 +extern const FourCC optionsTspinNames[LJTS_N_ALGOS];
   72.79 +extern const FourCC optionsGravNames[];
   72.80 +extern const FourCC optionsSpeedCurveNames[];
   72.81 +extern const FourCC optionsGarbageNames[];
   72.82 +extern const FourCC optionsHoldStyleNames[];
   72.83 +extern const FourCC optionsScoringNames[LJSCORE_N_STYLES];
   72.84 +extern const FourCC optionsDropScoringNames[LJDROP_N_STYLES];
   72.85 +extern const FourCC gimmickNames[LJGM_N_GIMMICKS];
   72.86 +extern const FourCC optionsSideNames[];
   72.87 +extern const FourCC optionsNextStyleNames[];
   72.88 +extern const FourCC optionsShadowNames[];
   72.89 +extern const FourCC optionsDASDelayNames[];
   72.90 +
   72.91 +
   72.92 +enum {
   72.93 +  OPTIONS_GIMMICK,
   72.94 +
   72.95 +  OPTIONS_WIDTH,
   72.96 +  OPTIONS_HEIGHT,
   72.97 +  OPTIONS_ENTER_ABOVE,
   72.98 +  OPTIONS_SPEED_CURVE,
   72.99 +  OPTIONS_ENTRY_DELAY,
  72.100 +  OPTIONS_PIECE_SET,
  72.101 +  OPTIONS_RANDOMIZER,
  72.102 +
  72.103 +  OPTIONS_HOLD_PIECE,
  72.104 +  OPTIONS_ROTATION_SYSTEM,
  72.105 +  OPTIONS_FLOOR_KICKS,
  72.106 +  OPTIONS_LOCKDOWN,
  72.107 +  OPTIONS_LOCK_DELAY,
  72.108 +  OPTIONS_BOTTOM_BLOCKS,
  72.109 +
  72.110 +  OPTIONS_LINE_DELAY,
  72.111 +  OPTIONS_CLEAR_GRAVITY,
  72.112 +  OPTIONS_GLUING,
  72.113 +  OPTIONS_SCORING,
  72.114 +  OPTIONS_DROP_SCORING,
  72.115 +  OPTIONS_T_SPIN,
  72.116 +  OPTIONS_GARBAGE,
  72.117 +
  72.118 +  OPTIONS_SIDEWAYS_DELAY,
  72.119 +  OPTIONS_SIDEWAYS_SPEED,
  72.120 +  OPTIONS_INITIAL_SIDEWAYS,
  72.121 +  OPTIONS_IRS,
  72.122 +  OPTIONS_DIAGONAL_MOTION,
  72.123 +  OPTIONS_SOFT_DROP_SPEED,
  72.124 +  OPTIONS_SOFT_DROP,
  72.125 +  OPTIONS_HARD_DROP,
  72.126 +
  72.127 +  OPTIONS_SHADOW,
  72.128 +  OPTIONS_HIDE_PF,
  72.129 +  OPTIONS_NEXT_PIECES,
  72.130 +  OPTIONS_SMOOTH_GRAVITY,
  72.131 +
  72.132 +  OPTIONS_MENU_LEN
  72.133 +};
  72.134 +
  72.135 +/**
  72.136 + * Builds a string representing the value of an option.
  72.137 + * @param dst 32-byte buffer to hold this string
  72.138 + * @param line index into optionsMenu
  72.139 + * @param value the (numeric) value of the option
  72.140 + * @return the description of this value, or NULL if unknown
  72.141 + */
  72.142 +const char *getOptionsValueStr(char *dst, int line, int value);
  72.143 +#define OPTIONS_VALUE_LEN 33
  72.144 +
  72.145 +void unpackCommonOptions(LJView *v, const unsigned char *prefs);
  72.146 +void initOptions(unsigned char *prefs);
  72.147 +const char *isDisabledOption(const unsigned char *prefs, int y);
  72.148 +
  72.149 +void optionsWinInit(void);
  72.150 +void optionsDrawPage(int scroll, const unsigned char *prefs);
  72.151 +void optionsDrawRow(const unsigned char *prefs,
  72.152 +                    int dstY, int line, int value, int hilite);
  72.153 +void optionsIdle(void);
  72.154 +
  72.155 +#endif
    73.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    73.2 +++ b/src/pcdebrief.c	Fri Mar 13 00:39:12 2009 -0700
    73.3 @@ -0,0 +1,72 @@
    73.4 +#include "ljpc.h"
    73.5 +
    73.6 +void debriefDrawPage(const char *page, size_t pageNumber) {
    73.7 +  int y = 40;
    73.8 +  char line[256];
    73.9 +  int linePos = 0;
   73.10 +  int done = 0;
   73.11 +
   73.12 +  acquire_screen();
   73.13 +  clear_to_color(screen, bgColor);
   73.14 +  textout_centre_ex(screen, aver32, "GAME OVER",
   73.15 +                    SCREEN_W / 2, y, fgColor, -1);
   73.16 +  textprintf_right_ex(screen, aver32,
   73.17 +                      SCREEN_W - 40, y, fgColor, -1,
   73.18 +                      "Page %u", (unsigned int)pageNumber + 1);
   73.19 +  textout_centre_ex(screen, aver32,
   73.20 +                    "Left/Right: Change; Rotate: close",
   73.21 +                    SCREEN_W / 2, SCREEN_H - 40, fgColor, -1);
   73.22 +
   73.23 +  while (!done) {
   73.24 +    int c = *page++;
   73.25 +
   73.26 +    // Break at newline and at end of text
   73.27 +    if (c == '\n' || c == 0) {
   73.28 +
   73.29 +      // Draw blank and parenthetical lines in smaller font
   73.30 +      const FONT *f = (linePos == 0 || line[0] == '(')
   73.31 +                      ? aver16
   73.32 +                      : aver32;
   73.33 +      int lineH = text_height(f);
   73.34 +
   73.35 +      // Terminate the line of text and print it
   73.36 +      line[linePos] = 0;
   73.37 +      textout_ex(screen, f, line, 40, y, fgColor, -1);
   73.38 +
   73.39 +      linePos = 0;  // Carriage return
   73.40 +      y += lineH * 6 / 5;  // Line feed
   73.41 +    } else {
   73.42 +      if (linePos + 2 < sizeof(line)) {
   73.43 +        line[linePos++] = c;
   73.44 +      }
   73.45 +    }
   73.46 +    if (c == 0) {
   73.47 +      done = 1;
   73.48 +    }
   73.49 +  }
   73.50 +  release_screen();
   73.51 +}
   73.52 +
   73.53 +extern volatile char redrawWholeScreen;
   73.54 +
   73.55 +LJBits debriefHandleKeys(void) {
   73.56 +  int keys = menuReadPad();
   73.57 +
   73.58 +  while (keypressed()) {
   73.59 +    int scancode;
   73.60 +    ureadkey(&scancode);
   73.61 +
   73.62 +    if (scancode == KEY_PRTSCR) {
   73.63 +      saveScreen(-1);
   73.64 +    }
   73.65 +  }
   73.66 +
   73.67 +  if (wantsClose) {
   73.68 +    keys |= VKEY_ROTL;
   73.69 +  }
   73.70 +  
   73.71 +  if (!(keys & (VKEY_ROTL | VKEY_ROTR))) {
   73.72 +    rest(30);
   73.73 +  }
   73.74 +  return keys;
   73.75 +}
    74.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    74.2 +++ b/src/pcjoy.c	Fri Mar 13 00:39:12 2009 -0700
    74.3 @@ -0,0 +1,503 @@
    74.4 +/* PC joystick code
    74.5 +
    74.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    74.7 +
    74.8 +This work is free software; you can redistribute it and/or modify
    74.9 +it under the terms of the GNU General Public License as published by
   74.10 +the Free Software Foundation; either version 2 of the License, or
   74.11 +(at your option) any later version.
   74.12 +
   74.13 +This program is distributed in the hope that it will be useful,
   74.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   74.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   74.16 +GNU General Public License for more details.
   74.17 +
   74.18 +You should have received a copy of the GNU General Public License
   74.19 +along with this program; if not, write to the Free Software
   74.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   74.21 +
   74.22 +Original game concept and design by Alexey Pajitnov.
   74.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   74.24 +or The Tetris Company LLC.
   74.25 +
   74.26 +*/
   74.27 +
   74.28 +#include "pcjoy.h"
   74.29 +#include <allegro.h>
   74.30 +#include <string.h>
   74.31 +#include "ljpath.h"
   74.32 +
   74.33 +extern const FONT *aver32, *aver16;
   74.34 +extern int bgColor, fgColor, hiliteColor;
   74.35 +
   74.36 +static volatile int lastScancodePressed = -1;
   74.37 +static void (*oldKeyListener)(int scancode);
   74.38 +volatile int wantsClose = 0;
   74.39 +void ezPlaySample(const char *filename, int vol);
   74.40 +
   74.41 +static void ljpcKeyListener(int scancode) {
   74.42 +  if (!(scancode & 0x80)) {
   74.43 +    lastScancodePressed = scancode;
   74.44 +  }
   74.45 +  if (oldKeyListener) {
   74.46 +    oldKeyListener(scancode);
   74.47 +  }
   74.48 +} END_OF_FUNCTION(ljpcKeyListener);
   74.49 +
   74.50 +
   74.51 +/**
   74.52 + * Presses the escape key when the user clicks the close box.
   74.53 + */
   74.54 +static void setWantsClose() {
   74.55 +  wantsClose = 1;
   74.56 +}
   74.57 +
   74.58 +static int withJoystick = 0;
   74.59 +
   74.60 +#define N_VKEYS 14
   74.61 +#define N_PLAYERS 2
   74.62 +
   74.63 +static const char keysFileName[] = "lj-keys.043";
   74.64 +
   74.65 +static const struct pkeyMapping defaultKeymappings[N_PLAYERS][N_VKEYS] = {
   74.66 +  {
   74.67 +    // [0]: player 1
   74.68 +    {-1, -1, KEY_UP},
   74.69 +    {-1, -1, KEY_DOWN},
   74.70 +    {-1, -1, KEY_LEFT},
   74.71 +    {-1, -1, KEY_RIGHT},
   74.72 +    {-1, -1, KEY_Z},
   74.73 +    {-1, -1, KEY_X},
   74.74 +    {-1, -1, KEY_S},
   74.75 +    {-1, -1, KEY_SPACE},
   74.76 +    {-1, -1, KEY_C},
   74.77 +    {-1, -1, KEY_W},
   74.78 +    {-1, -1, KEY_Q},
   74.79 +    {-1, -1, KEY_E},
   74.80 +    {-1, -1, KEY_ENTER},
   74.81 +    {-1, -1, KEY_D}
   74.82 +  },
   74.83 +  {
   74.84 +    // [1]: player 2
   74.85 +    {-1, -1, KEY_UP},
   74.86 +    {-1, -1, KEY_DOWN},
   74.87 +    {-1, -1, KEY_LEFT},
   74.88 +    {-1, -1, KEY_RIGHT},
   74.89 +    {-1, -1, KEY_Z},
   74.90 +    {-1, -1, KEY_X},
   74.91 +    {-1, -1, KEY_S},
   74.92 +    {-1, -1, KEY_SPACE},
   74.93 +    {-1, -1, KEY_C},
   74.94 +    {-1, -1, KEY_W},
   74.95 +    {-1, -1, KEY_Q},
   74.96 +    {-1, -1, KEY_E},
   74.97 +    {-1, -1, KEY_ENTER},
   74.98 +    {-1, -1, KEY_D}
   74.99 +  }
  74.100 +};
  74.101 +
  74.102 +static struct pkeyMapping keymappings[N_PLAYERS][N_VKEYS];
  74.103 +
  74.104 +void getPkeyName(char *dst, int j, int s, int a) {
  74.105 +  if (j < 0) {
  74.106 +    if (a >= 0 && a < KEY_MAX) {
  74.107 +      usprintf(dst, "Key %d (%s)", a, scancode_to_name(a));
  74.108 +    } else {
  74.109 +      usprintf(dst, "Key %d (???"")", a);
  74.110 +    }
  74.111 +  } else if (s < 0) {
  74.112 +    usprintf(dst,
  74.113 +             "Joy %d button %s",
  74.114 +             j,
  74.115 +             joy[j].button[a].name);
  74.116 +  } else if (a < 0) {
  74.117 +    usprintf(dst,
  74.118 +             "Joy %d stick %s axis %s -",
  74.119 +             j,
  74.120 +             joy[j].stick[s].name,
  74.121 +             joy[j].stick[s].axis[~a].name);
  74.122 +  } else {
  74.123 +    usprintf(dst,
  74.124 +             "Joy %d stick %s axis %s +",
  74.125 +             j,
  74.126 +             joy[j].stick[s].name,
  74.127 +             joy[j].stick[s].axis[a].name);
  74.128 +  }
  74.129 +}
  74.130 +
  74.131 +static int getPkeyState(int j, int s, int a) {
  74.132 +  int k;
  74.133 +  if (j < 0) {
  74.134 +    k = key[a];
  74.135 +  } else if (s < 0) {
  74.136 +    k = joy[j].button[a].b;
  74.137 +  } else if (a < 0) {
  74.138 +    k = joy[j].stick[s].axis[~a].d1;
  74.139 +  } else {
  74.140 +    k = joy[j].stick[s].axis[a].d2;
  74.141 +  }
  74.142 +  return k;
  74.143 +}
  74.144 +
  74.145 +const char *const vkeyNames[] = {
  74.146 +  "Hard Drop (Up)",
  74.147 +  "Soft Drop (Down)",
  74.148 +  "Left",
  74.149 +  "Right",
  74.150 +  "Rotate Left",
  74.151 +  "Rotate Right",
  74.152 +  "Hold",
  74.153 +  "Item (unused)",
  74.154 +  "Alt. Rotate Left",
  74.155 +  "Rotate Left Twice",
  74.156 +  "Far Left",
  74.157 +  "Far Right",
  74.158 +  "Alt. Firm Drop",
  74.159 +  "Alt. Hold",
  74.160 +  "Macro G",
  74.161 +  "Macro H"
  74.162 +};
  74.163 +
  74.164 +static int getVkeyState(int player, int vkey) {
  74.165 +  int j = keymappings[player][vkey].joy;
  74.166 +  int s = keymappings[player][vkey].stick;
  74.167 +  int a = keymappings[player][vkey].axis;
  74.168 +
  74.169 +  return getPkeyState(j, s, a);
  74.170 +}
  74.171 +
  74.172 +LJBits readPad(unsigned int player) {
  74.173 +  int keys = 0;
  74.174 +  poll_joystick();
  74.175 +
  74.176 +  for (int i = 0;
  74.177 +       i < N_VKEYS;
  74.178 +       ++i) {
  74.179 +    if (getVkeyState(player, i)) {
  74.180 +      keys |= 1 << i;
  74.181 +    }
  74.182 +  }
  74.183 +  return keys;
  74.184 +}
  74.185 +
  74.186 +LJBits menuReadPad(void) {
  74.187 +  if (key[KEY_ENTER]) {
  74.188 +    return VKEY_ROTR | VKEY_START;
  74.189 +  } else if (key[KEY_ESC]) {
  74.190 +    return VKEY_ROTL;
  74.191 +  } else if (key[KEY_UP]) {
  74.192 +    return VKEY_UP;
  74.193 +  } else if (key[KEY_DOWN]) {
  74.194 +    return VKEY_DOWN;
  74.195 +  } else if (key[KEY_LEFT]) {
  74.196 +    return VKEY_LEFT;
  74.197 +  } else if (key[KEY_RIGHT]) {
  74.198 +    return VKEY_RIGHT;
  74.199 +  } else {
  74.200 +    return readPad(0) | readPad(1);
  74.201 +  }
  74.202 +}
  74.203 +
  74.204 +// These contain the states of ALL buttons on ALL
  74.205 +// joysticks 
  74.206 +static LJBits lastConfigButtons[8];
  74.207 +static LJBits lastConfigStickAxis[8];
  74.208 +
  74.209 +static int newButton(int *outJ, int *outS, int *outA) {
  74.210 +  poll_joystick();
  74.211 +  int found = 0;
  74.212 +
  74.213 +  if (lastScancodePressed >= 0) {
  74.214 +    *outJ = -1;
  74.215 +    *outS = -1;
  74.216 +    *outA = lastScancodePressed;
  74.217 +    lastScancodePressed = -1;
  74.218 +    return 1;
  74.219 +  }
  74.220 +
  74.221 +  for (int j = 0;
  74.222 +       j < num_joysticks;
  74.223 +       ++j) {
  74.224 +    LJBits cur = 0;
  74.225 +
  74.226 +    for (int b = 0; b < joy[j].num_buttons; ++b) {
  74.227 +      if (joy[j].button[b].b) {
  74.228 +        if (!(lastConfigButtons[j] & (1 << b)) && !found) {
  74.229 +          *outJ = j;
  74.230 +          *outS = -1;
  74.231 +          *outA = b;
  74.232 +          found = 1;
  74.233 +        }
  74.234 +        cur |= 1 << b;
  74.235 +      }
  74.236 +    }
  74.237 +    lastConfigButtons[j] = cur;
  74.238 +  }
  74.239 +  if (found) {
  74.240 +    return 1;
  74.241 +  }
  74.242 +
  74.243 +  for (int j = 0;
  74.244 +       j < num_joysticks;
  74.245 +       ++j) {
  74.246 +    LJBits cur = 0;
  74.247 +    LJBits mask = 1;
  74.248 +
  74.249 +    for (int s = 0; s < joy[j].num_sticks; ++s) {
  74.250 +      for (int a = 0; a < joy[j].stick[s].num_axis; ++a) {
  74.251 +        if (joy[j].stick[s].axis[a].d1) {
  74.252 +          if (!(lastConfigStickAxis[j] & mask) && !found) {
  74.253 +            *outJ = j;
  74.254 +            *outS = s;
  74.255 +            *outA = ~a;
  74.256 +            found = 1;
  74.257 +          }
  74.258 +          cur |= mask;
  74.259 +        }
  74.260 +        mask <<= 1;
  74.261 +        if (joy[j].stick[s].axis[a].d2) {
  74.262 +          if (!(lastConfigStickAxis[j] & mask) && !found) {
  74.263 +            *outJ = j;
  74.264 +            *outS = s;
  74.265 +            *outA = a;
  74.266 +            found = 1;
  74.267 +          }
  74.268 +          cur |= mask;
  74.269 +        }
  74.270 +        mask <<= 1;
  74.271 +      }
  74.272 +    }
  74.273 +    lastConfigStickAxis[j] = cur;
  74.274 +  }
  74.275 +  return found;
  74.276 +}
  74.277 +
  74.278 +static void clearNewButton(void) {
  74.279 +  int j, s, a;
  74.280 +  while (newButton(&j, &s, &a));
  74.281 +}
  74.282 +
  74.283 +void loadKeys(const char *filename) {
  74.284 +  FILE *fp = ljfopen(filename, "rb");
  74.285 +
  74.286 +  memcpy(keymappings, defaultKeymappings, sizeof(keymappings));
  74.287 +  if (fp) {
  74.288 +    for (unsigned int player = 0; player < 2; ++player) {
  74.289 +      for (unsigned int vkey = 0;
  74.290 +           vkey < N_VKEYS;
  74.291 +           ++vkey) {
  74.292 +        int j = fgetc(fp);
  74.293 +        int s = fgetc(fp);
  74.294 +        int a = fgetc(fp);
  74.295 +        
  74.296 +        if (a == EOF) {
  74.297 +          break;
  74.298 +        }
  74.299 +        keymappings[player][vkey].joy = j;
  74.300 +        keymappings[player][vkey].stick = s;
  74.301 +        keymappings[player][vkey].axis = a;
  74.302 +      }
  74.303 +    }
  74.304 +    fclose(fp);
  74.305 +  }
  74.306 +}
  74.307 +
  74.308 +void saveKeys(const char *filename) {
  74.309 +  FILE *fp = ljfopen(filename, "wb");
  74.310 +
  74.311 +  if (fp) {
  74.312 +    for (unsigned int player = 0; player < 2; ++player) {
  74.313 +      for (unsigned int vkey = 0;
  74.314 +           vkey < N_VKEYS && !feof(fp);
  74.315 +           ++vkey) {
  74.316 +        fputc(keymappings[player][vkey].joy, fp);
  74.317 +        fputc(keymappings[player][vkey].stick, fp);
  74.318 +        fputc(keymappings[player][vkey].axis, fp);
  74.319 +      }
  74.320 +    }
  74.321 +    fclose(fp);
  74.322 +  }
  74.323 +}
  74.324 +
  74.325 +#define VKEY_ROWHT 24
  74.326 +#define VKEY_TOP 120
  74.327 +
  74.328 +void drawVkeyRow(int vkey, int hilite) {
  74.329 +  char name[256];
  74.330 +  int y = VKEY_TOP + vkey * VKEY_ROWHT;
  74.331 +  
  74.332 +  rectfill(screen,
  74.333 +           16, y, 719, y + VKEY_ROWHT - 1,
  74.334 +           hilite ? hiliteColor : bgColor);
  74.335 +  textout_ex(screen, aver16, vkeyNames[vkey], 24, y + 4, fgColor, -1);
  74.336 +  for (int player = 0; player < N_PLAYERS; ++player) {
  74.337 +    if (player + 1 == hilite) {
  74.338 +      rect(screen,
  74.339 +           240 + 240 * player, y,
  74.340 +           479 + 240 * player, y + VKEY_ROWHT - 1,
  74.341 +           fgColor);
  74.342 +    }
  74.343 +    getPkeyName(name,
  74.344 +                keymappings[player][vkey].joy,
  74.345 +                keymappings[player][vkey].stick,
  74.346 +                keymappings[player][vkey].axis);
  74.347 +    textout_ex(screen, aver16, name, 240 + 240 * player, y + 4, fgColor, -1);
  74.348 +  }
  74.349 +}
  74.350 +
  74.351 +/**
  74.352 + * Waits for a key or button press. If any key but Esc is pressed,
  74.353 + * reassigns the vkey. Otherwise, does nothing.
  74.354 + * @param vkey the index of the vkey to reassign
  74.355 + * @return 0 if key not changed; nonzero if key was changed
  74.356 + */
  74.357 +static int changeKey(int player, int vkey) {
  74.358 +  int changed = 0;
  74.359 +  int phase = 0;
  74.360 +  
  74.361 +  clearNewButton();
  74.362 +  while (!changed) {
  74.363 +    int j, s, a;
  74.364 +
  74.365 +    if (phase == 5) {
  74.366 +      drawVkeyRow(vkey, 0);
  74.367 +    } else if (phase == 0) {
  74.368 +      drawVkeyRow(vkey, player + 1);
  74.369 +      phase = 15;
  74.370 +    }
  74.371 +    --phase;
  74.372 +
  74.373 +    if (keypressed()) {
  74.374 +      int scancode;
  74.375 +      ureadkey(&scancode);
  74.376 +      if (scancode == KEY_ESC) {
  74.377 +        changed = -1;
  74.378 +      }
  74.379 +    }
  74.380 +    if (vkey < N_VKEYS && newButton(&j, &s, &a)) {
  74.381 +      if (j >= 0 || s > 0 || a != KEY_ESC) {
  74.382 +        keymappings[player][vkey].joy = j;
  74.383 +        keymappings[player][vkey].stick = s;
  74.384 +        keymappings[player][vkey].axis = a;
  74.385 +        ezPlaySample("nextS_wav", 128);
  74.386 +        changed = 1;
  74.387 +      } else {
  74.388 +        changed = -1;
  74.389 +      }
  74.390 +    }
  74.391 +    if (wantsClose) {
  74.392 +      changed = -1;
  74.393 +    }
  74.394 +
  74.395 +    rest(30);
  74.396 +  }
  74.397 +  
  74.398 +  // Draw new vkey value
  74.399 +  drawVkeyRow(vkey, 0);
  74.400 +  return changed > 0;
  74.401 +}
  74.402 +
  74.403 +void configureKeys(void) {
  74.404 +  clear_to_color(screen, bgColor);
  74.405 +  textout_ex(screen, aver32, "LOCKJAW > Game Keys", 16, 32, fgColor, -1);
  74.406 +  textout_ex(screen, aver16,
  74.407 +             "Use arrow keys to select a key. To reassign the selected key, press Enter",
  74.408 +             40, 80, fgColor, -1);
  74.409 +  textout_ex(screen, aver16,
  74.410 +             "and then the key you want to use. When done, press Esc.",
  74.411 +             40, 96, fgColor, -1);
  74.412 +
  74.413 +  // Draw each vkey's name
  74.414 +  for (int vkey = 0; vkey < N_VKEYS; ++vkey) {
  74.415 +    drawVkeyRow(vkey, 0);
  74.416 +  }
  74.417 +
  74.418 +  clearNewButton();
  74.419 +  int vkey = 0;
  74.420 +  int player = 0;
  74.421 +  
  74.422 +  drawVkeyRow(vkey, player + 1);
  74.423 +
  74.424 +  while (vkey >= 0 && !wantsClose) {
  74.425 +    int scancode = 0;
  74.426 +
  74.427 +    rest(30);
  74.428 +    if (keypressed()) {
  74.429 +      ureadkey(&scancode);
  74.430 +    }
  74.431 +    
  74.432 +    switch (scancode) {
  74.433 +    
  74.434 +    case KEY_RIGHT:
  74.435 +      if (player < N_PLAYERS - 1) {
  74.436 +        ezPlaySample("shift_wav", 128);
  74.437 +        drawVkeyRow(vkey, 0);
  74.438 +        ++player;
  74.439 +        drawVkeyRow(vkey, player + 1);
  74.440 +      }
  74.441 +      break;
  74.442 +    
  74.443 +    case KEY_LEFT:
  74.444 +      if (player > 0) {
  74.445 +        ezPlaySample("shift_wav", 128);
  74.446 +        drawVkeyRow(vkey, 0);
  74.447 +        --player;
  74.448 +        drawVkeyRow(vkey, player + 1);
  74.449 +      }
  74.450 +      break;
  74.451 +      
  74.452 +    case KEY_UP:
  74.453 +      if (vkey > 0) {
  74.454 +        ezPlaySample("shift_wav", 128);
  74.455 +        drawVkeyRow(vkey, 0);
  74.456 +        --vkey;
  74.457 +        drawVkeyRow(vkey, player + 1);
  74.458 +      }
  74.459 +      break;
  74.460 +    case KEY_DOWN:
  74.461 +      if (vkey < N_VKEYS - 1) {
  74.462 +        ezPlaySample("shift_wav", 128);
  74.463 +        drawVkeyRow(vkey, 0);
  74.464 +        ++vkey;
  74.465 +        drawVkeyRow(vkey, player + 1);
  74.466 +      }
  74.467 +      break;
  74.468 +    case KEY_ENTER:
  74.469 +      ezPlaySample("rotate_wav", 96);
  74.470 +      while (vkey < N_VKEYS && changeKey(player, vkey)) {
  74.471 +        rest(150);
  74.472 +        ++vkey;
  74.473 +      }
  74.474 +      if (vkey >= N_VKEYS) {
  74.475 +        ezPlaySample("land_wav", 128);
  74.476 +        vkey = N_VKEYS - 1;
  74.477 +      } else {
  74.478 +        ezPlaySample("nextO_wav", 128);
  74.479 +      }
  74.480 +      drawVkeyRow(vkey, player + 1);
  74.481 +      break;
  74.482 +    case KEY_ESC:
  74.483 +      vkey = -1;
  74.484 +      break;
  74.485 +    }
  74.486 +  }
  74.487 +  saveKeys(keysFileName);
  74.488 +  ezPlaySample("line_wav", 128);
  74.489 +}
  74.490 +
  74.491 +
  74.492 +void initKeys(void) {
  74.493 +  LOCK_FUNCTION(ljpcKeyListener);
  74.494 +  LOCK_VARIABLE(lastScancodePressed);
  74.495 +  LOCK_VARIABLE(oldKeyListener);
  74.496 +  LOCK_FUNCTION(setWantsClose);
  74.497 +  LOCK_VARIABLE(wantsClose);
  74.498 +
  74.499 +  oldKeyListener = keyboard_lowlevel_callback;
  74.500 +  keyboard_lowlevel_callback = ljpcKeyListener;
  74.501 +  withJoystick = !install_joystick(JOY_TYPE_AUTODETECT);
  74.502 +  loadKeys(keysFileName);
  74.503 +  set_close_button_callback(setWantsClose);
  74.504 +}
  74.505 +
  74.506 +
    75.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    75.2 +++ b/src/pcjoy.h	Fri Mar 13 00:39:12 2009 -0700
    75.3 @@ -0,0 +1,70 @@
    75.4 +/* PC joystick code
    75.5 +
    75.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    75.7 +
    75.8 +This work is free software; you can redistribute it and/or modify
    75.9 +it under the terms of the GNU General Public License as published by
   75.10 +the Free Software Foundation; either version 2 of the License, or
   75.11 +(at your option) any later version.
   75.12 +
   75.13 +This program is distributed in the hope that it will be useful,
   75.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   75.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   75.16 +GNU General Public License for more details.
   75.17 +
   75.18 +You should have received a copy of the GNU General Public License
   75.19 +along with this program; if not, write to the Free Software
   75.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   75.21 +
   75.22 +Original game concept and design by Alexey Pajitnov.
   75.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   75.24 +or The Tetris Company LLC.
   75.25 +
   75.26 +*/
   75.27 +
   75.28 +#ifndef PCJOY_H
   75.29 +#define PCJOY_H
   75.30 +
   75.31 +#include "ljtypes.h"
   75.32 +#include "ljcontrol.h"
   75.33 +
   75.34 +
   75.35 +/* Physical key definitions
   75.36 +
   75.37 +m.joy < 0:
   75.38 +  key[m.axis]
   75.39 +m.joy >= 0, m.stick < 0:
   75.40 +  joy[m.joy].button[m.axis].b
   75.41 +m.joy >= 0, m.stick >= 0, m.axis < 0:
   75.42 +  joy[m.joy].stick[m.stick].axis[~m.axis].d1
   75.43 +m.joy >= 0, m.stick >= 0, m.axis >= 0:
   75.44 +  joy[m.joy].stick[m.stick].axis[m.axis].d2
   75.45 +*/
   75.46 +
   75.47 +struct pkeyMapping {
   75.48 +  signed char joy;
   75.49 +  signed char stick;
   75.50 +  signed char axis;
   75.51 +};
   75.52 +
   75.53 +extern volatile int wantsClose;
   75.54 +
   75.55 +void initKeys(void);
   75.56 +
   75.57 +/**
   75.58 + * Reads the physical keys to produce a set of pressed virtual keys.
   75.59 + */
   75.60 +LJBits readPad(unsigned int player);
   75.61 +
   75.62 +/**
   75.63 + * Reads the physical keys to produce a set of pressed virtual keys,
   75.64 + * hardcoding keyboard Up, Down, Left, Right, Esc, and Enter
   75.65 + * to the appropriate vkeys.
   75.66 + */
   75.67 +LJBits menuReadPad(void);
   75.68 +
   75.69 +void getPkeyName(char *dst, int j, int s, int a);
   75.70 +extern const char *const vkeyNames[];
   75.71 +void configureKeys(void);
   75.72 +
   75.73 +#endif
    76.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    76.2 +++ b/src/pcsound.c	Fri Mar 13 00:39:12 2009 -0700
    76.3 @@ -0,0 +1,108 @@
    76.4 +#include "ljpc.h"
    76.5 +extern const DATAFILE *sound_dat;
    76.6 +extern char withSound;
    76.7 +
    76.8 +static const unsigned short shiftPitches[16] = {
    76.9 +   630,  667,  707,  749,  794,  841,  891,  944,
   76.10 +  1000, 1059, 1122, 1189, 1260, 1335, 1424, 1498
   76.11 +};
   76.12 +
   76.13 +/**
   76.14 + * Plays a sample from datafile by name, with the specified volume
   76.15 + * and the specified rate scale factor, panned center, without loop.
   76.16 + * @param filename name of object in datafile
   76.17 + * @param vol scale factor for volume (0-255); if set to 0,
   76.18 + * stops all voices playing the sample
   76.19 + * @param pitch scale factor for playback rate (1000 = normal; 2000 = double speed)
   76.20 + */
   76.21 +static void playPitchSample(const char *filename, int vol, int pitch) {
   76.22 +  if (withSound && sound_dat) {
   76.23 +    const DATAFILE *entry = find_datafile_object(sound_dat, filename);
   76.24 +
   76.25 +    if (entry) {
   76.26 +      if (vol < 1) {
   76.27 +        stop_sample(entry->dat);
   76.28 +      } else {
   76.29 +        play_sample(entry->dat, vol, 128, pitch, 0);
   76.30 +      }
   76.31 +    }
   76.32 +  }
   76.33 +}
   76.34 +
   76.35 +void ezPlaySample(const char *filename, int vol) {
   76.36 +  playPitchSample(filename, vol, 1000);
   76.37 +}
   76.38 +
   76.39 +void playSampleForTetromino(int piece) {
   76.40 +  static const char tetrominoNames[] = "IJLOSTZ22V";
   76.41 +  
   76.42 +  piece &= LJP_MASK;
   76.43 +  if (piece >= 0 && piece < 10) {
   76.44 +    char filename[32];
   76.45 +    usprintf(filename, "next%c_wav", tetrominoNames[piece]);
   76.46 +    ezPlaySample(filename, 128);
   76.47 +  }
   76.48 +}
   76.49 +
   76.50 +void playSoundEffects(LJView *v, LJBits sounds, int countdown) {
   76.51 +  // Handle sound
   76.52 +  if ((sounds & LJSND_SPAWN)
   76.53 +       && !(v->hideNext)) {
   76.54 +    playSampleForTetromino(v->field->curPiece[1]);
   76.55 +  }
   76.56 +  if (sounds & LJSND_HOLD) {
   76.57 +    ezPlaySample("hold_wav", 128);
   76.58 +  }
   76.59 +  if (sounds & LJSND_ROTATE) {
   76.60 +    ezPlaySample("rotate_wav", 128);
   76.61 +  }
   76.62 +  if (sounds & LJSND_IRS) {
   76.63 +    ezPlaySample("irs_wav", 128);
   76.64 +  }
   76.65 +  if (sounds & LJSND_SQUARE) {
   76.66 +    ezPlaySample("square_wav", 128);
   76.67 +  }
   76.68 +  if (sounds & LJSND_SHIFT) {
   76.69 +    int x = v->plat->skin->shiftScale
   76.70 +            ? v->field->x + 8 - (LJ_PF_WID - 4) / 2
   76.71 +            : 8;
   76.72 +    int pitch = shiftPitches[x];
   76.73 +    playPitchSample("shift_wav", 128, pitch);
   76.74 +  }
   76.75 +  if (sounds & LJSND_LAND) {
   76.76 +    ezPlaySample("land_wav", 192);
   76.77 +  }
   76.78 +  if (sounds & LJSND_LOCK) {
   76.79 +    ezPlaySample("lock_wav", 192);
   76.80 +  }
   76.81 +  if (sounds & LJSND_LINE) {
   76.82 +    ezPlaySample("line_wav", 128);
   76.83 +  }
   76.84 +  if (sounds & LJSND_SECTIONUP) {
   76.85 +    ezPlaySample("sectionup_wav", 128);
   76.86 +  }
   76.87 +
   76.88 +  if (v->plat->b2bcd1 > 0) {
   76.89 +    if (--v->plat->b2bcd1 == 0) {
   76.90 +      playPitchSample("line_wav", 148, 1200);
   76.91 +    }
   76.92 +  }
   76.93 +  if (sounds & LJSND_SETB2B) {
   76.94 +    v->plat->b2bcd1 = 10;
   76.95 +  }
   76.96 +  if (v->plat->b2bcd2 > 0) {
   76.97 +    if (--v->plat->b2bcd2 == 0) {
   76.98 +      playPitchSample("line_wav", 168, 1440);
   76.99 +    }
  76.100 +  }
  76.101 +  if (sounds & LJSND_B2B) {
  76.102 +    v->plat->b2bcd2 = 20;
  76.103 +  }
  76.104 +  if (countdown < v->control->countdown) {
  76.105 +    char name[32];
  76.106 +    usprintf(name, "count_%d_wav", countdown);
  76.107 +    ezPlaySample(name, 128);
  76.108 +  }
  76.109 +  LJMusic_poll(v->plat->skin->bgm);
  76.110 +}
  76.111 +
    77.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    77.2 +++ b/src/pin8gba_sound.h	Fri Mar 13 00:39:12 2009 -0700
    77.3 @@ -0,0 +1,117 @@
    77.4 +/*
    77.5 + * pin8gba_sound.h
    77.6 + * Header file for GBA sound registers
    77.7 + */
    77.8 +
    77.9 +
   77.10 +/* Copyright 2001-2006 Damian Yerrick
   77.11 +
   77.12 +(insert zlib license here)
   77.13 +
   77.14 +*/
   77.15 +
   77.16 +
   77.17 +/* Why the funny register names?
   77.18 +
   77.19 +The register names do not match the names in the official GBA
   77.20 +documentation.  I choose the names that seem most logical to me.
   77.21 +In addition, I do slick macro tricks with the VRAM addresses for
   77.22 +maps and the like and with registers that control timers, DMA,
   77.23 +and backgrounds.  The gba.h that comes with wintermute's libgba
   77.24 +incorporates some but not all of these tricks.  In addition,
   77.25 +the names in gba.h for the sound registers aren't descriptive
   77.26 +at all.
   77.27 +
   77.28 +*/
   77.29 +
   77.30 +#ifndef PIN8GBA_SOUND_H
   77.31 +#ifdef __cplusplus
   77.32 +extern "C" {
   77.33 +#endif
   77.34 +#define PIN8GBA_SOUND_H
   77.35 +
   77.36 +
   77.37 +#define DMGSNDCTRL         (*(volatile u16 *)0x04000080)
   77.38 +#define DMGSNDCTRL_LVOL(x) (x)
   77.39 +#define DMGSNDCTRL_RVOL(x) ((x) << 4)
   77.40 +#define DMGSNDCTRL_LSQR1   0x0100
   77.41 +#define DMGSNDCTRL_LSQR2   0x0200
   77.42 +#define DMGSNDCTRL_LTRI    0x0400
   77.43 +#define DMGSNDCTRL_LNOISE  0x0800
   77.44 +#define DMGSNDCTRL_RSQR1   0x1000
   77.45 +#define DMGSNDCTRL_RSQR2   0x2000
   77.46 +#define DMGSNDCTRL_RTRI    0x4000
   77.47 +#define DMGSNDCTRL_RNOISE  0x8000
   77.48 +
   77.49 +#define DSOUNDCTRL           (*(volatile u16 *)0x04000082)
   77.50 +#define DSOUNDCTRL_DMG25     0x0000
   77.51 +#define DSOUNDCTRL_DMG50     0x0001
   77.52 +#define DSOUNDCTRL_DMG100    0x0002
   77.53 +#define DSOUNDCTRL_A50       0x0000
   77.54 +#define DSOUNDCTRL_A100      0x0004
   77.55 +#define DSOUNDCTRL_B50       0x0000
   77.56 +#define DSOUNDCTRL_B100      0x0008
   77.57 +#define DSOUNDCTRL_AR        0x0100
   77.58 +#define DSOUNDCTRL_AL        0x0200
   77.59 +#define DSOUNDCTRL_ATIMER(x) ((x) << 10)
   77.60 +#define DSOUNDCTRL_ARESET    0x0400
   77.61 +#define DSOUNDCTRL_BR        0x1000
   77.62 +#define DSOUNDCTRL_BL        0x2000
   77.63 +#define DSOUNDCTRL_BTIMER(x) ((x) << 14)
   77.64 +#define DSOUNDCTRL_BRESET    0x8000
   77.65 +
   77.66 +#define SNDSTAT        (*(volatile u16*)0x04000084)
   77.67 +#define SNDSTAT_SQR1   0x0001
   77.68 +#define SNDSTAT_SQR2   0x0002
   77.69 +#define SNDSTAT_TRI    0x0004
   77.70 +#define SNDSTAT_NOISE  0x0008
   77.71 +#define SNDSTAT_ENABLE 0x0080
   77.72 +
   77.73 +#define SNDBIAS      (*(volatile u16 *)0x04000088)
   77.74 +#define SETSNDRES(x) SNDBIAS = (SNDBIAS & 0x3fff) | (x << 14)
   77.75 +
   77.76 +#define DSOUND_FIFOA (*(volatile u32 *)0x040000a0)
   77.77 +#define DSOUND_FIFOB (*(volatile u32 *)0x040000a4)
   77.78 +
   77.79 +
   77.80 +#define SQR1SWEEP   (*(volatile u16 *)0x04000060)
   77.81 +#define SQR1SWEEP_OFF 0x0008
   77.82 +
   77.83 +#define SQR1CTRL    (*(volatile u16 *)0x04000062)
   77.84 +#define SQR2CTRL    (*(volatile u16 *)0x04000068)
   77.85 +#define NOISECTRL   (*(volatile u16 *)0x04000078)
   77.86 +#define SQR_DUTY(n) ((n) << 6)
   77.87 +#define SQR_VOL(n)  ((n) << 12)
   77.88 +
   77.89 +#define SQR1FREQ      (*(volatile u16 *)0x04000064)
   77.90 +#define SQR2FREQ      (*(volatile u16 *)0x0400006c)
   77.91 +#define TRIFREQ       (*(volatile u16 *)0x04000074)
   77.92 +#define FREQ_HOLD  0x0000
   77.93 +#define FREQ_TIMED 0x4000
   77.94 +#define FREQ_RESET 0x8000
   77.95 +
   77.96 +#define NOISEFREQ       (*(volatile u16 *)0x0400007c)
   77.97 +#define NOISEFREQ_127   0x0008
   77.98 +#define NOISEFREQ_OCT(x) ((x) << 4)
   77.99 +
  77.100 +#define TRICTRL         (*(volatile u16 *)0x04000070)
  77.101 +#define TRICTRL_2X32    0x0000
  77.102 +#define TRICTRL_1X64    0x0020
  77.103 +#define TRICTRL_BANK(x) ((x) << 6)
  77.104 +#define TRICTRL_ENABLE  0x0080
  77.105 +
  77.106 +#define TRILENVOL        (*(volatile u16 *)0x04000072)
  77.107 +#define TRILENVOL_LEN(x) (256 - (x))
  77.108 +#define TRILENVOL_MUTE   0x0000
  77.109 +#define TRILENVOL_25     0x6000
  77.110 +#define TRILENVOL_50     0x4000
  77.111 +#define TRILENVOL_75     0x8000
  77.112 +#define TRILENVOL_100    0x2000
  77.113 +
  77.114 +#define TRIWAVERAM ((volatile u32 *)0x04000090)
  77.115 +
  77.116 +
  77.117 +#ifdef __cplusplus
  77.118 +}
  77.119 +#endif
  77.120 +#endif
    78.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    78.2 +++ b/src/random.c	Fri Mar 13 00:39:12 2009 -0700
    78.3 @@ -0,0 +1,253 @@
    78.4 +/* Piece randomizers for LOCKJAW, an implementation of the Soviet Mind Game
    78.5 +
    78.6 +Copyright (C) 2006-2007 Damian Yerrick <tepples+lj@spamcop.net>
    78.7 +
    78.8 +This work is free software; you can redistribute it and/or modify
    78.9 +it under the terms of the GNU General Public License as published by
   78.10 +the Free Software Foundation; either version 2 of the License, or
   78.11 +(at your option) any later version.
   78.12 +
   78.13 +This program is distributed in the hope that it will be useful,
   78.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   78.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   78.16 +GNU General Public License for more details.
   78.17 +
   78.18 +You should have received a copy of the GNU General Public License
   78.19 +along with this program; if not, write to the Free Software
   78.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   78.21 +
   78.22 +Original game concept and design by Alexey Pajitnov.
   78.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   78.24 +or The Tetris Company LLC.
   78.25 +
   78.26 +*/
   78.27 +
   78.28 +#include "lj.h"
   78.29 +
   78.30 +// Order matters.  The history randomizers prefer to give out the
   78.31 +// front half early in the game.  You don't want to give an S or Z
   78.32 +// first, or you force a slide. You also don't want to give an O
   78.33 +// first, or you force a slide with OS or OZ.
   78.34 +
   78.35 +static const signed char piecesNoI[] = {
   78.36 +  1, 2, 5, // front half: J L T
   78.37 +  3, 4, 6  // back half: O S Z
   78.38 +};
   78.39 +
   78.40 +static const signed char piecesTetrominoes[] = {
   78.41 +  0, 1, 2, 5, // front half: I J L T
   78.42 +  3, 4, 6 // back half: O S Z
   78.43 +};
   78.44 +
   78.45 +static const signed char piecesWithSmall[] = {
   78.46 +  0, 1, 2, 5, 9,  // front half: I J L T L3
   78.47 +  3, 7, 8, 4, 6  // back half: O I2 I3 S Z
   78.48 +  // make sure that the S and Z are in the back half
   78.49 +};
   78.50 +
   78.51 +static const signed char piecesSZ[] = {
   78.52 +  4, 6
   78.53 +};
   78.54 +
   78.55 +static const signed char piecesI[] = {
   78.56 +  0
   78.57 +};
   78.58 +
   78.59 +static const signed char piecesT[] = {
   78.60 +  5
   78.61 +};
   78.62 +
   78.63 +static const signed char *const pieceSets[] = {
   78.64 +  piecesTetrominoes,
   78.65 +  piecesNoI,
   78.66 +  piecesSZ,
   78.67 +  piecesI,
   78.68 +  piecesWithSmall,
   78.69 +  piecesT
   78.70 +};
   78.71 +
   78.72 +static const unsigned char pieceSetSizes[] = {
   78.73 +  sizeof(piecesTetrominoes),
   78.74 +  sizeof(piecesNoI),
   78.75 +  sizeof(piecesSZ),
   78.76 +  sizeof(piecesI),
   78.77 +  sizeof(piecesWithSmall),
   78.78 +  sizeof(piecesT)
   78.79 +};
   78.80 +
   78.81 +static void bagInitRandomize(LJField *p) {
   78.82 +  p->permuPhase = 0;
   78.83 +}
   78.84 +
   78.85 +static unsigned int bagRandomize(LJField *p) {
   78.86 +  unsigned int setNo = p->pieceSet;
   78.87 +  unsigned int len = pieceSetSizes[setNo];
   78.88 +  const signed char *set = pieceSets[setNo];
   78.89 +
   78.90 +  int piece;
   78.91 +
   78.92 +  // If we're out of permutation pieces, make new ones by copying
   78.93 +  // from one of the rand#Bag arrays until we hit the -1 sentinel.
   78.94 +  if (p->permuPhase == 0) {
   78.95 +    int pos;
   78.96 +  
   78.97 +    for (pos = 0; pos < len; ++pos) {
   78.98 +      p->permuPiece[pos] = set[pos];
   78.99 +      p->permuPiece[pos + len] = set[pos];
  78.100 +    }
  78.101 +    
  78.102 +    // Double bag: Add one randomly chosen piece to the bag
  78.103 +    if (p->randomizer == LJRAND_2BAG) {
  78.104 +      pos *= 2;
  78.105 +    }
  78.106 +    // 7+1 Bag (TOJ style): Add one randomly chosen piece to the bag
  78.107 +    else if (p->randomizer == LJRAND_BAGPLUS1) {
  78.108 +      p->permuPiece[pos++] = set[ljRand(p) % len];
  78.109 +    }
  78.110 +    p->permuPhase = pos;
  78.111 +  }
  78.112 +
  78.113 +  // Choose a position in the remainder of the deck
  78.114 +  int r = (p->permuPhase > 1) ? ljRand(p) % p->permuPhase : 0;
  78.115 +  
  78.116 +  // Swap the top card with the card at this position
  78.117 +  piece = p->permuPiece[r];
  78.118 +  p->permuPiece[r] = p->permuPiece[--p->permuPhase];
  78.119 +  return piece;
  78.120 +}
  78.121 +
  78.122 +static void mtbInitRandomize(LJField *p) {
  78.123 +  unsigned int setNo = p->pieceSet;
  78.124 +  unsigned int len = pieceSetSizes[setNo];
  78.125 +  const signed char *set = pieceSets[setNo];
  78.126 +
  78.127 +  // At game start, front three are I, J, L
  78.128 +  // Back four (never dealt as first tetromino) are O, S, T, Z
  78.129 +  for (int i = 0; i < len; ++i) {
  78.130 +    p->permuPiece[i] = set[i];
  78.131 +  }
  78.132 +}
  78.133 +
  78.134 +static unsigned int mtbRandomize(LJField *p) {
  78.135 +  unsigned int setNo = p->pieceSet;
  78.136 +  unsigned int len = pieceSetSizes[setNo];
  78.137 +
  78.138 +  // Choose a piece from the three in front
  78.139 +  int r = ljRand(p) % (len / 2);
  78.140 +  int piece = p->permuPiece[r];
  78.141 +  
  78.142 +  // Move it to the back
  78.143 +  for (; r < len - 1; ++r) {
  78.144 +    p->permuPiece[r] = p->permuPiece[r + 1];
  78.145 +  }
  78.146 +  p->permuPiece[len - 1] = piece;
  78.147 +  return piece;
  78.148 +}
  78.149 +
  78.150 +static void tgmInitRandomize(LJField *p) {
  78.151 +  unsigned int setNo = p->pieceSet;
  78.152 +  unsigned int len = pieceSetSizes[setNo];
  78.153 +  
  78.154 +  p->permuPiece[0] = 0;
  78.155 +  for (int i = len / 2; i < len; ++i) {
  78.156 +    p->permuPiece[i] = i & 1 ? LJP_Z : LJP_S;
  78.157 +  }
  78.158 +}
  78.159 +
  78.160 +/* State of TGM randomizer:
  78.161 + * permuPiece[3..6]: history, [3] most recent
  78.162 + * permuPiece[2]: 0 for first piece (use equal probability I, J, L, T)
  78.163 + *   or 1 for subsequent pieces
  78.164 + */
  78.165 +static unsigned int tgmRandomize(LJField *p) {
  78.166 +  unsigned int setNo = p->pieceSet;
  78.167 +  unsigned int len = pieceSetSizes[setNo];
  78.168 +  const signed char *set = pieceSets[setNo];
  78.169 +
  78.170 +  int r = ljRand(p) ^ (ljRand(p) << 15);
  78.171 +  int piece;
  78.172 +  
  78.173 +  if (p->permuPiece[0]) {
  78.174 +
  78.175 +    // Roll up to 6 times for pieces
  78.176 +    for (int rolls = 6;
  78.177 +          rolls > 0;
  78.178 +          --rolls, r /= len) {
  78.179 +      piece = set[r % len];
  78.180 +      int found = 0;
  78.181 +
  78.182 +      // If the piece is not in the history, use it now
  78.183 +      for (int histoPos = len / 2;
  78.184 +           !found && histoPos < len;
  78.185 +           ++histoPos) {
  78.186 +        if (piece == p->permuPiece[histoPos]) {
  78.187 +          found = 1;
  78.188 +        }
  78.189 +      }
  78.190 +      if (!found) {
  78.191 +        break;
  78.192 +      }
  78.193 +    }
  78.194 +  } else {
  78.195 +    p->permuPiece[0] = 1;
  78.196 +    // Generate only pieces in the first half of the list
  78.197 +    piece = set[r % ((len + 1) / 2)];
  78.198 +    if (piece == LJP_O) {
  78.199 +      piece = LJP_T;
  78.200 +    }
  78.201 +  }
  78.202 +  
  78.203 +  // Move it to the back
  78.204 +  for (r = len / 2; r < len - 1; ++r) {
  78.205 +    p->permuPiece[r] = p->permuPiece[r + 1];
  78.206 +  }
  78.207 +  p->permuPiece[len - 1] = piece;
  78.208 +  return piece;
  78.209 +}
  78.210 +
  78.211 +void initRandomize(LJField *p) {
  78.212 +  unsigned int setNo = p->pieceSet;
  78.213 +  unsigned int len = pieceSetSizes[setNo];
  78.214 +  
  78.215 +  if (len < 2) {
  78.216 +    p->randomizer = LJRAND_PURE;
  78.217 +  }
  78.218 +
  78.219 +  switch (p->randomizer) {
  78.220 +  case LJRAND_PURE:
  78.221 +    break;
  78.222 +  case LJRAND_BAG:
  78.223 +  case LJRAND_BAGPLUS1:
  78.224 +  case LJRAND_2BAG:
  78.225 +    bagInitRandomize(p);
  78.226 +    break;
  78.227 +  case LJRAND_HIST_INF:
  78.228 +    mtbInitRandomize(p);
  78.229 +    break;
  78.230 +  case LJRAND_HIST_6:
  78.231 +    tgmInitRandomize(p);
  78.232 +    break;
  78.233 +  }
  78.234 +}
  78.235 +
  78.236 +unsigned int randomize(LJField *p) {
  78.237 +  switch (p->randomizer) {
  78.238 +
  78.239 +  case LJRAND_BAG:
  78.240 +  case LJRAND_BAGPLUS1:
  78.241 +  case LJRAND_2BAG:
  78.242 +    return bagRandomize(p);
  78.243 +  case LJRAND_HIST_INF:
  78.244 +    return mtbRandomize(p);
  78.245 +  case LJRAND_HIST_6:
  78.246 +    return tgmRandomize(p);
  78.247 +  case LJRAND_PURE:
  78.248 +  default:
  78.249 +    {
  78.250 +      unsigned int setNo = p->pieceSet;
  78.251 +      unsigned int len = pieceSetSizes[setNo];
  78.252 +      const signed char *set = pieceSets[setNo];
  78.253 +      return set[ljRand(p) % len];
  78.254 +    }
  78.255 +  }
  78.256 +}
    79.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    79.2 +++ b/src/scenario.c	Fri Mar 13 00:39:12 2009 -0700
    79.3 @@ -0,0 +1,339 @@
    79.4 +/* Scenario code for LOCKJAW, an implementation of the Soviet Mind Game
    79.5 +
    79.6 +Copyright (C) 2008 Damian Yerrick <tepples+lj@spamcop.net>
    79.7 +
    79.8 +This work is free software; you can redistribute it and/or modify
    79.9 +it under the terms of the GNU General Public License as published by
   79.10 +the Free Software Foundation; either version 2 of the License, or
   79.11 +(at your option) any later version.
   79.12 +
   79.13 +This program is distributed in the hope that it will be useful,
   79.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   79.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   79.16 +GNU General Public License for more details.
   79.17 +
   79.18 +You should have received a copy of the GNU General Public License
   79.19 +along with this program; if not, write to the Free Software
   79.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   79.21 +
   79.22 +Original game concept and design by Alexey Pajitnov.
   79.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   79.24 +or The Tetris Company LLC.
   79.25 +
   79.26 +*/
   79.27 +
   79.28 +#include "scenario.h"
   79.29 +#include "options.h"
   79.30 +#include <string.h>
   79.31 +
   79.32 +#define END_OF_PRESET {OPTIONS_MENU_LEN}
   79.33 +
   79.34 +const Preset predefPresets[] = {
   79.35 +  {"Guideline", {
   79.36 +    {OPTIONS_WIDTH, 10},
   79.37 +    {OPTIONS_HEIGHT, 20},
   79.38 +    {OPTIONS_ENTRY_DELAY, 0},
   79.39 +    {OPTIONS_PIECE_SET, LJRAND_4BLK},
   79.40 +    {OPTIONS_RANDOMIZER, LJRAND_BAG},
   79.41 +    {OPTIONS_HOLD_PIECE, LJHOLD_EMPTY},
   79.42 +    {OPTIONS_ROTATION_SYSTEM, LJROT_SRS},
   79.43 +    {OPTIONS_FLOOR_KICKS, 6},
   79.44 +    {OPTIONS_LOCKDOWN, LJLOCK_MOVE},
   79.45 +    {OPTIONS_LOCK_DELAY, 30},
   79.46 +    {OPTIONS_BOTTOM_BLOCKS, 0},
   79.47 +    {OPTIONS_LINE_DELAY, 30},
   79.48 +    {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE},
   79.49 +    {OPTIONS_GLUING, 0},
   79.50 +    {OPTIONS_SCORING, LJSCORE_TDS},
   79.51 +    {OPTIONS_DROP_SCORING, LJDROP_1S_2H},
   79.52 +    {OPTIONS_T_SPIN, LJTS_TDS},
   79.53 +    END_OF_PRESET
   79.54 +  }},
   79.55 +
   79.56 +  {"Classic", {
   79.57 +    {OPTIONS_GIMMICK, LJGM_ATYPE},
   79.58 +    {OPTIONS_WIDTH, 10},
   79.59 +    {OPTIONS_HEIGHT, 20},
   79.60 +    {OPTIONS_ENTER_ABOVE, 0},
   79.61 +    {OPTIONS_ENTRY_DELAY, 10},
   79.62 +    {OPTIONS_SPEED_CURVE, LJSPD_NES},
   79.63 +    {OPTIONS_PIECE_SET, LJRAND_4BLK},
   79.64 +    {OPTIONS_RANDOMIZER, LJRAND_PURE},
   79.65 +    {OPTIONS_HOLD_PIECE, LJHOLD_NONE},
   79.66 +    {OPTIONS_ROTATION_SYSTEM, LJROT_NES},
   79.67 +    {OPTIONS_FLOOR_KICKS, 0},
   79.68 +    {OPTIONS_LOCKDOWN, LJLOCK_NOW},
   79.69 +    {OPTIONS_LOCK_DELAY, 0},
   79.70 +    {OPTIONS_BOTTOM_BLOCKS, 0},
   79.71 +    {OPTIONS_LINE_DELAY, 30},
   79.72 +    {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE},
   79.73 +    {OPTIONS_GLUING, 0},
   79.74 +    {OPTIONS_SCORING, LJSCORE_NES},
   79.75 +    {OPTIONS_DROP_SCORING, LJDROP_NES},
   79.76 +    {OPTIONS_GARBAGE, LJGARBAGE_NONE},
   79.77 +    {OPTIONS_SIDEWAYS_DELAY, 15},
   79.78 +    {OPTIONS_SIDEWAYS_SPEED, 6},
   79.79 +    {OPTIONS_SOFT_DROP_SPEED, 2},
   79.80 +    {OPTIONS_INITIAL_SIDEWAYS, 1},
   79.81 +    {OPTIONS_IRS, 0},
   79.82 +    {OPTIONS_NEXT_PIECES, 1},
   79.83 +    END_OF_PRESET
   79.84 +  }},
   79.85 +
   79.86 +  /* http://www.tetrisconcept.com/forum/viewtopic.php?t=892 */
   79.87 +  {"Master", {
   79.88 +    {OPTIONS_GIMMICK, LJGM_ATYPE},
   79.89 +    {OPTIONS_WIDTH, 10},
   79.90 +    {OPTIONS_HEIGHT, 20},
   79.91 +    {OPTIONS_ENTER_ABOVE, 0},
   79.92 +    {OPTIONS_SPEED_CURVE, LJSPD_TGM},
   79.93 +    {OPTIONS_ENTRY_DELAY, 30},
   79.94 +    {OPTIONS_PIECE_SET, LJRAND_4BLK},
   79.95 +    {OPTIONS_RANDOMIZER, LJRAND_HIST_6},
   79.96 +    {OPTIONS_HOLD_PIECE, LJHOLD_NONE},
   79.97 +    {OPTIONS_ROTATION_SYSTEM, LJROT_ARIKA},
   79.98 +    {OPTIONS_FLOOR_KICKS, 0},
   79.99 +    {OPTIONS_LOCKDOWN, LJLOCK_STEP},
  79.100 +    {OPTIONS_LOCK_DELAY, 30},
  79.101 +    {OPTIONS_BOTTOM_BLOCKS, 0},
  79.102 +    {OPTIONS_LINE_DELAY, 0},
  79.103 +    {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE},
  79.104 +    {OPTIONS_GLUING, LJGLUING_STICKY},
  79.105 +    {OPTIONS_GARBAGE, LJGARBAGE_NONE},
  79.106 +    {OPTIONS_SIDEWAYS_DELAY, 16},
  79.107 +    {OPTIONS_SIDEWAYS_SPEED, 1},
  79.108 +    {OPTIONS_INITIAL_SIDEWAYS, 0},
  79.109 +    {OPTIONS_IRS, 1},
  79.110 +    {OPTIONS_SOFT_DROP_SPEED, 1},
  79.111 +    {OPTIONS_SOFT_DROP, LJZANGI_LOCK},
  79.112 +    {OPTIONS_HARD_DROP, LJZANGI_SLIDE},
  79.113 +    {OPTIONS_NEXT_PIECES, 1},
  79.114 +    END_OF_PRESET
  79.115 +  }},
  79.116 +
  79.117 +  /* http://www.tetrisconcept.com/forum/viewtopic.php?t=892 */
  79.118 +  {"T.A. Death", {
  79.119 +    {OPTIONS_GIMMICK, LJGM_ATYPE},
  79.120 +    {OPTIONS_SPEED_CURVE, LJSPD_DEATH},
  79.121 +    {OPTIONS_WIDTH, 10},
  79.122 +    {OPTIONS_HEIGHT, 20},
  79.123 +    {OPTIONS_ENTER_ABOVE, 0},
  79.124 +    {OPTIONS_ENTRY_DELAY, 30},
  79.125 +    {OPTIONS_PIECE_SET, LJRAND_4BLK},
  79.126 +    {OPTIONS_RANDOMIZER, LJRAND_HIST_6},
  79.127 +    {OPTIONS_HOLD_PIECE, LJHOLD_NONE},
  79.128 +    {OPTIONS_ROTATION_SYSTEM, LJROT_ARIKA},
  79.129 +    {OPTIONS_FLOOR_KICKS, 0},
  79.130 +    {OPTIONS_LOCKDOWN, LJLOCK_STEP},
  79.131 +    {OPTIONS_LOCK_DELAY, 30},
  79.132 +    {OPTIONS_BOTTOM_BLOCKS, 0},
  79.133 +    {OPTIONS_LINE_DELAY, 0},
  79.134 +    {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE},
  79.135 +    {OPTIONS_GLUING, LJGLUING_STICKY},
  79.136 +    {OPTIONS_GARBAGE, LJGARBAGE_NONE},
  79.137 +    {OPTIONS_SIDEWAYS_DELAY, 16},
  79.138 +    {OPTIONS_SIDEWAYS_SPEED, 1},
  79.139 +    {OPTIONS_INITIAL_SIDEWAYS, 0},
  79.140 +    {OPTIONS_IRS, 1},
  79.141 +    {OPTIONS_NEXT_PIECES, 1},
  79.142 +    END_OF_PRESET
  79.143 +  }},
  79.144 +
  79.145 +  {"Square", {
  79.146 +    {OPTIONS_GIMMICK, LJGM_ATYPE},
  79.147 +    {OPTIONS_WIDTH, 10},
  79.148 +    {OPTIONS_HEIGHT, 20},
  79.149 +    {OPTIONS_ENTER_ABOVE, 1},
  79.150 +    {OPTIONS_ENTRY_DELAY, 30},
  79.151 +    {OPTIONS_SPEED_CURVE, LJSPD_EXP},
  79.152 +    {OPTIONS_PIECE_SET, LJRAND_4BLK},
  79.153 +    {OPTIONS_RANDOMIZER, LJRAND_PURE},
  79.154 +    {OPTIONS_HOLD_PIECE, LJHOLD_TNT},
  79.155 +    {OPTIONS_ROTATION_SYSTEM, LJROT_SRS},
  79.156 +    {OPTIONS_FLOOR_KICKS, 6},
  79.157 +    {OPTIONS_LOCKDOWN, LJLOCK_STEP},
  79.158 +    {OPTIONS_LOCK_DELAY, 30},
  79.159 +    {OPTIONS_BOTTOM_BLOCKS, 0},
  79.160 +    {OPTIONS_LINE_DELAY, 40},
  79.161 +    {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE},
  79.162 +    {OPTIONS_GLUING, LJGLUING_SQUARE},
  79.163 +    {OPTIONS_SCORING, LJSCORE_TNT64},
  79.164 +    {OPTIONS_DROP_SCORING, LJDROP_NOSCORE},
  79.165 +    {OPTIONS_T_SPIN, LJTS_TNT},
  79.166 +    {OPTIONS_GARBAGE, LJGARBAGE_NONE},
  79.167 +    {OPTIONS_SIDEWAYS_DELAY, 11},
  79.168 +    {OPTIONS_SIDEWAYS_SPEED, 5},
  79.169 +    {OPTIONS_SOFT_DROP_SPEED, 2},
  79.170 +    {OPTIONS_SOFT_DROP, LJZANGI_SLIDE},
  79.171 +    {OPTIONS_HARD_DROP, LJZANGI_SLIDE},
  79.172 +    {OPTIONS_INITIAL_SIDEWAYS, 0},
  79.173 +    {OPTIONS_IRS, 0},
  79.174 +    {OPTIONS_NEXT_PIECES, 3},
  79.175 +    END_OF_PRESET
  79.176 +  }},
  79.177 +
  79.178 +  /* http://www.tetrisconcept.com/forum/viewtopic.php?t=549 */
  79.179 +  {"40 Lines", {
  79.180 +    {OPTIONS_GIMMICK, LJGM_BTYPE},
  79.181 +    {OPTIONS_WIDTH, 10},
  79.182 +    {OPTIONS_PIECE_SET, LJRAND_4BLK},
  79.183 +    {OPTIONS_BOTTOM_BLOCKS, 0},
  79.184 +    {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE},
  79.185 +    {OPTIONS_GARBAGE, 0},
  79.186 +    END_OF_PRESET
  79.187 +  }},
  79.188 +
  79.189 +  /* http://www.tetrisconcept.com/forum/viewtopic.php?t=549 */
  79.190 +  {"180 Seconds", {
  79.191 +    {OPTIONS_GIMMICK, LJGM_ULTRA},
  79.192 +    {OPTIONS_WIDTH, 10},
  79.193 +    {OPTIONS_PIECE_SET, LJRAND_4BLK},
  79.194 +    {OPTIONS_HOLD_PIECE, LJHOLD_EMPTY},
  79.195 +    {OPTIONS_BOTTOM_BLOCKS, 0},
  79.196 +    {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE},
  79.197 +    {OPTIONS_GLUING, 0},
  79.198 +    {OPTIONS_SCORING, LJSCORE_LJ},
  79.199 +    {OPTIONS_DROP_SCORING, 0},
  79.200 +    {OPTIONS_T_SPIN, 0},
  79.201 +    {OPTIONS_GARBAGE, 0},
  79.202 +    END_OF_PRESET
  79.203 +  }},
  79.204 +
  79.205 +  /* http://www.tetrisconcept.com/forum/viewtopic.php?t=892 */
  79.206 +  {"Death 300", {
  79.207 +    {OPTIONS_GIMMICK, LJGM_ULTRA},
  79.208 +    {OPTIONS_SPEED_CURVE, LJSPD_DEATH300},
  79.209 +    {OPTIONS_WIDTH, 10},
  79.210 +    {OPTIONS_HEIGHT, 20},
  79.211 +    {OPTIONS_ENTER_ABOVE, 0},
  79.212 +    {OPTIONS_ENTRY_DELAY, 30},
  79.213 +    {OPTIONS_PIECE_SET, LJRAND_4BLK},
  79.214 +    {OPTIONS_RANDOMIZER, LJRAND_HIST_6},
  79.215 +    {OPTIONS_HOLD_PIECE, LJHOLD_EMPTY},
  79.216 +    {OPTIONS_ROTATION_SYSTEM, LJROT_ARIKA},
  79.217 +    {OPTIONS_FLOOR_KICKS, 0},
  79.218 +    {OPTIONS_LOCKDOWN, LJLOCK_STEP},
  79.219 +    {OPTIONS_LOCK_DELAY, 30},
  79.220 +    {OPTIONS_BOTTOM_BLOCKS, 0},
  79.221 +    {OPTIONS_LINE_DELAY, 0},
  79.222 +    {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE},
  79.223 +    {OPTIONS_GLUING, LJGLUING_STICKY},
  79.224 +    {OPTIONS_GARBAGE, LJGARBAGE_NONE},
  79.225 +    {OPTIONS_SIDEWAYS_DELAY, 16},
  79.226 +    {OPTIONS_SIDEWAYS_SPEED, 1},
  79.227 +    {OPTIONS_INITIAL_SIDEWAYS, 0},
  79.228 +    {OPTIONS_IRS, 1},
  79.229 +    {OPTIONS_NEXT_PIECES, 1},
  79.230 +    END_OF_PRESET
  79.231 +  }},
  79.232 +
  79.233 +  {"M-Roll", {
  79.234 +    {OPTIONS_GIMMICK, LJGM_BTYPE},
  79.235 +    {OPTIONS_SPEED_CURVE, LJSPD_DEATH300},
  79.236 +    {OPTIONS_WIDTH, 10},
  79.237 +    {OPTIONS_HEIGHT, 20},
  79.238 +    {OPTIONS_ENTER_ABOVE, 0},
  79.239 +    {OPTIONS_ENTRY_DELAY, 30},
  79.240 +    {OPTIONS_PIECE_SET, LJRAND_4BLK},
  79.241 +    {OPTIONS_RANDOMIZER, LJRAND_HIST_6},
  79.242 +    {OPTIONS_HOLD_PIECE, LJHOLD_EMPTY},
  79.243 +    {OPTIONS_ROTATION_SYSTEM, LJROT_ARIKA},
  79.244 +    {OPTIONS_FLOOR_KICKS, 0},
  79.245 +    {OPTIONS_LOCKDOWN, LJLOCK_STEP},
  79.246 +    {OPTIONS_LOCK_DELAY, 30},
  79.247 +    {OPTIONS_BOTTOM_BLOCKS, 0},
  79.248 +    {OPTIONS_LINE_DELAY, 0},
  79.249 +    {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE},
  79.250 +    {OPTIONS_GLUING, LJGLUING_STICKY},
  79.251 +    {OPTIONS_GARBAGE, LJGARBAGE_NONE},
  79.252 +    {OPTIONS_SIDEWAYS_DELAY, 16},
  79.253 +    {OPTIONS_SIDEWAYS_SPEED, 1},
  79.254 +    {OPTIONS_INITIAL_SIDEWAYS, 0},
  79.255 +    {OPTIONS_IRS, 1},
  79.256 +    {OPTIONS_NEXT_PIECES, 1},
  79.257 +    {OPTIONS_HIDE_PF, 1},
  79.258 +    END_OF_PRESET
  79.259 +  }},
  79.260 +
  79.261 +  /* http://www.tetrisconcept.com/forum/viewtopic.php?t=549 */
  79.262 +  {"Cascade", {
  79.263 +    {OPTIONS_GIMMICK, LJGM_BTYPE},
  79.264 +    {OPTIONS_WIDTH, 10},
  79.265 +    {OPTIONS_PIECE_SET, LJRAND_4BLK},
  79.266 +    {OPTIONS_HOLD_PIECE, LJHOLD_EMPTY},
  79.267 +    {OPTIONS_CLEAR_GRAVITY, LJGRAV_CASCADE},
  79.268 +    {OPTIONS_GARBAGE, 0},
  79.269 +    END_OF_PRESET
  79.270 +  }},
  79.271 +
  79.272 +  /* http://www.tetrisconcept.com/forum/viewtopic.php?t=777 */
  79.273 +  {"Baboo!", {
  79.274 +    {OPTIONS_GIMMICK, LJGM_BABY},
  79.275 +    {OPTIONS_WIDTH, 10},
  79.276 +    {OPTIONS_PIECE_SET, LJRAND_4BLK},
  79.277 +    {OPTIONS_SPEED_CURVE, LJSPD_ZERO},
  79.278 +    {OPTIONS_BOTTOM_BLOCKS, 0},
  79.279 +    {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE},
  79.280 +    {OPTIONS_GLUING, 0},
  79.281 +    {OPTIONS_SCORING, LJSCORE_LJ},
  79.282 +    {OPTIONS_DROP_SCORING, 0},
  79.283 +    {OPTIONS_T_SPIN, LJTS_TDS},
  79.284 +    {OPTIONS_GARBAGE, 0}
  79.285 +  }},
  79.286 +
  79.287 +};
  79.288 +
  79.289 +const Preset *loadedPresets = predefPresets;
  79.290 +size_t nLoadedPresets = sizeof(predefPresets)/sizeof(predefPresets[0]);
  79.291 +
  79.292 +static unsigned char presetBuffer[OPTIONS_MENU_LEN];
  79.293 +
  79.294 +void presetStart(void) {
  79.295 +  memset(&presetBuffer, 255, sizeof(presetBuffer));
  79.296 +}
  79.297 +
  79.298 +void presetAdd(size_t which) {
  79.299 +  if (which >= nLoadedPresets) {
  79.300 +    return;
  79.301 +  }
  79.302 +  const PresetRule *r = loadedPresets[which].rules;
  79.303 +  for (size_t i = 0;
  79.304 +       i < PRESET_MAX_RULES
  79.305 +       && r[i].line < OPTIONS_MENU_LEN;
  79.306 +       ++i) {
  79.307 +    presetBuffer[r[i].line] = r[i].value;
  79.308 +  }
  79.309 +}
  79.310 +
  79.311 +void presetFinish(struct LJView *v) {
  79.312 +  unpackCommonOptions(v, presetBuffer);
  79.313 +}
  79.314 +
  79.315 +#if 0
  79.316 +void loadPresetsFromText(FILE *fp) {
  79.317 +  Preset *presets = NULL;
  79.318 +
  79.319 +  size_t capacity = 0, n = 0;
  79.320 +  
  79.321 +  {
  79.322 +    // allocate memory for this preset
  79.323 +    if (capacity <= n) {
  79.324 +      if (capacity < 32) {
  79.325 +        capacity += 16;
  79.326 +      } else {
  79.327 +        capacity += capacity / 2;
  79.328 +      }
  79.329 +      Preset *newPresets = realloc(presets,
  79.330 +                                   capacity * sizeof(preset));
  79.331 +      // if we can't allocate memory, screw it
  79.332 +      if (!newPresets) {
  79.333 +        break;
  79.334 +      }
  79.335 +    }
  79.336 +  }
  79.337 +  
  79.338 +  loadedPresets = presets;
  79.339 +  nLoadedPresets = n;
  79.340 +}
  79.341 +#endif
  79.342 +
    80.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    80.2 +++ b/src/scenario.h	Fri Mar 13 00:39:12 2009 -0700
    80.3 @@ -0,0 +1,60 @@
    80.4 +/* PC preset code for LOCKJAW, an implementation of the Soviet Mind Game
    80.5 +
    80.6 +Copyright (C) 2008 Damian Yerrick <tepples+lj@spamcop.net>
    80.7 +
    80.8 +This work is free software; you can redistribute it and/or modify
    80.9 +it under the terms of the GNU General Public License as published by
   80.10 +the Free Software Foundation; either version 2 of the License, or
   80.11 +(at your option) any later version.
   80.12 +
   80.13 +This program is distributed in the hope that it will be useful,
   80.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   80.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   80.16 +GNU General Public License for more details.
   80.17 +
   80.18 +You should have received a copy of the GNU General Public License
   80.19 +along with this program; if not, write to the Free Software
   80.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   80.21 +
   80.22 +Original game concept and design by Alexey Pajitnov.
   80.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   80.24 +or The Tetris Company LLC.
   80.25 +
   80.26 +*/
   80.27 +
   80.28 +#ifndef PCPRESET_H
   80.29 +#define PCPRESET_H
   80.30 +#include <sys/types.h>
   80.31 +#include "ljcontrol.h"
   80.32 +
   80.33 +typedef struct PresetRule {
   80.34 +  unsigned char line, value;
   80.35 +} PresetRule;
   80.36 +
   80.37 +#define PRESET_NAME_LEN 32
   80.38 +#define PRESET_MAX_RULES 32 
   80.39 +
   80.40 +typedef struct Preset {
   80.41 +  char name[PRESET_NAME_LEN];
   80.42 +  PresetRule rules[PRESET_MAX_RULES];
   80.43 +} Preset;
   80.44 +
   80.45 +/**
   80.46 + * Resets all options in the preset buffer to inherit.
   80.47 + */
   80.48 +void presetStart(void);
   80.49 +
   80.50 +/**
   80.51 + * Adds a preset to the preset buffer.
   80.52 + */
   80.53 +void presetAdd(size_t which);
   80.54 +
   80.55 +/**
   80.56 + * Unpacks the preset buffer onto a view.
   80.57 + */
   80.58 +void presetFinish(struct LJView *v);
   80.59 +
   80.60 +extern const Preset *loadedPresets;
   80.61 +extern size_t nLoadedPresets;
   80.62 +
   80.63 +#endif
    81.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    81.2 +++ b/src/speed.c	Fri Mar 13 00:39:12 2009 -0700
    81.3 @@ -0,0 +1,431 @@
    81.4 +/* Speed curve tables for LOCKJAW, an implementation of the Soviet Mind Game
    81.5 +
    81.6 +Copyright (C) 2006-2007 Damian Yerrick <tepples+lj@spamcop.net>
    81.7 +
    81.8 +This work is free software; you can redistribute it and/or modify
    81.9 +it under the terms of the GNU General Public License as published by
   81.10 +the Free Software Foundation; either version 2 of the License, or
   81.11 +(at your option) any later version.
   81.12 +
   81.13 +This program is distributed in the hope that it will be useful,
   81.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   81.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   81.16 +GNU General Public License for more details.
   81.17 +
   81.18 +You should have received a copy of the GNU General Public License
   81.19 +along with this program; if not, write to the Free Software
   81.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   81.21 +
   81.22 +Original game concept and design by Alexey Pajitnov.
   81.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   81.24 +or The Tetris Company LLC.
   81.25 +
   81.26 +*/
   81.27 +#include "lj.h"
   81.28 +#include "ljcontrol.h"
   81.29 +
   81.30 +static const LJFixed initialGravity[LJSPD_N_CURVES] = {
   81.31 +  [LJSPD_RHYTHMZERO] = 0,
   81.32 +  [LJSPD_RHYTHM] = LJITOFIX(20)
   81.33 +};
   81.34 +
   81.35 +/**
   81.36 + * The default speed curve for each gimmick.  A negative value
   81.37 + * means that the gimmick uses the player's chosen speed curve.
   81.38 + */
   81.39 +static const signed char defaultSpeedCurve[LJGM_N_GIMMICKS] = {
   81.40 +  [LJGM_ATYPE] = -1,
   81.41 +  [LJGM_BTYPE] = -1,
   81.42 +  [LJGM_ULTRA] = -1,
   81.43 +  [LJGM_DRILL] = -1,
   81.44 +  [LJGM_ITEMS] = -1,
   81.45 +  [LJGM_BABY] = LJSPD_ZERO
   81.46 +};
   81.47 +
   81.48 +/* The new speed curve code ****************************************/
   81.49 +
   81.50 +#define SCGRAV(n, d) ((n) * 2048 / (d))
   81.51 +
   81.52 +enum {
   81.53 +  SPEEDTYPE_RHYTHM,
   81.54 +  SPEEDTYPE_TGM,
   81.55 +  SPEEDTYPE_LINES,
   81.56 +  SPEEDTYPE_PIECES
   81.57 +};
   81.58 +
   81.59 +typedef struct LJSpeedStep {
   81.60 +  unsigned short level, gravity;
   81.61 +  signed char are, das, lock, line;
   81.62 +} LJSpeedStep;
   81.63 +
   81.64 +typedef struct LJSpeedCurve {
   81.65 +  char name[32];
   81.66 +  unsigned char type, sectionLen;
   81.67 +  unsigned char nSteps;
   81.68 +  const LJSpeedStep steps[];
   81.69 +} LJSpeedCurve;
   81.70 +
   81.71 +const LJSpeedCurve scMaster = {
   81.72 +  "Master",
   81.73 +  SPEEDTYPE_TGM, 100,
   81.74 +  33,
   81.75 +  {
   81.76 +    {  0, SCGRAV(  4,256),25,16,30,40},
   81.77 +    { 30, SCGRAV(  6,256),25,16,30,40},
   81.78 +    { 35, SCGRAV(  8,256),25,16,30,40},
   81.79 +    { 40, SCGRAV( 10,256),25,16,30,40},
   81.80 +    { 50, SCGRAV( 12,256),25,16,30,40},
   81.81 +    { 60, SCGRAV( 16,256),25,16,30,40},
   81.82 +    { 70, SCGRAV( 32,256),25,16,30,40},
   81.83 +    { 80, SCGRAV( 48,256),25,16,30,40},
   81.84 +    { 90, SCGRAV( 64,256),25,16,30,40},
   81.85 +    {100, SCGRAV( 80,256),25,16,30,40},
   81.86 +    {120, SCGRAV( 96,256),25,16,30,40},
   81.87 +    {140, SCGRAV(112,256),25,16,30,40},
   81.88 +    {160, SCGRAV(128,256),25,16,30,40},
   81.89 +    {170, SCGRAV(144,256),25,16,30,40},
   81.90 +    {200, SCGRAV(  4,256),25,16,30,40},
   81.91 +    {220, SCGRAV( 32,256),25,16,30,40},
   81.92 +    {230, SCGRAV( 64,256),25,16,30,40},
   81.93 +    {233, SCGRAV( 96,256),25,16,30,40},
   81.94 +    {236, SCGRAV(128,256),25,16,30,40},
   81.95 +    {239, SCGRAV(160,256),25,16,30,40},
   81.96 +    {243, SCGRAV(192,256),25,16,30,40},
   81.97 +    {247, SCGRAV(224,256),25,16,30,40},
   81.98 +    {251, SCGRAV( 1,1),   25,16,30,40},
   81.99 +    {300, SCGRAV( 2,1),   25,16,30,40},
  81.100 +    {330, SCGRAV( 3,1),   25,16,30,40},
  81.101 +    {360, SCGRAV( 4,1),   25,16,30,40},
  81.102 +    {400, SCGRAV( 5,1),   25,16,30,40},
  81.103 +    {450, SCGRAV( 3,1),   25,16,30,40},
  81.104 +    {500, SCGRAV(20,1),   25,10,30,25},
  81.105 +    {600, SCGRAV(20,1),   25,10,30,16},
  81.106 +    {700, SCGRAV(20,1),   16,10,30,12},
  81.107 +    {800, SCGRAV(20,1),   12,10,30, 6},
  81.108 +    {900, SCGRAV(20,1),   12, 8,17, 6}
  81.109 +  }
  81.110 +};
  81.111 +
  81.112 +const LJSpeedCurve scDeath = {
  81.113 +  "Death",
  81.114 +  SPEEDTYPE_TGM, 100,
  81.115 +  6,
  81.116 +  {
  81.117 +    {  0, SCGRAV(20,1),   18,12,30, 8},
  81.118 +    {100, SCGRAV(20,1),   14,12,26, 0},
  81.119 +    {200, SCGRAV(20,1),   14,11,22, 0},
  81.120 +    {300, SCGRAV(20,1),    8,10,18, 6},
  81.121 +    {400, SCGRAV(20,1),    7, 8,15, 5},
  81.122 +    {500, SCGRAV(20,1),    6, 8,15, 4}
  81.123 +  }
  81.124 +};
  81.125 +
  81.126 +const LJSpeedCurve scNES = {
  81.127 +  "NES",
  81.128 +  SPEEDTYPE_LINES, 10,
  81.129 +  15,
  81.130 +  {
  81.131 +    {  0, SCGRAV(1,48), 10,-1, 0,30},
  81.132 +    { 10, SCGRAV(1,43), 10,-1, 0,30},
  81.133 +    { 20, SCGRAV(1,38), 10,-1, 0,30},
  81.134 +    { 30, SCGRAV(1,33), 10,-1, 0,30},
  81.135 +    { 40, SCGRAV(1,28), 10,-1, 0,30},
  81.136 +    { 50, SCGRAV(1,23), 10,-1, 0,30},
  81.137 +    { 60, SCGRAV(1,18), 10,-1, 0,30},
  81.138 +    { 70, SCGRAV(1,13), 10,-1, 0,30},
  81.139 +    { 80, SCGRAV(1, 8), 10,-1, 0,30},
  81.140 +    { 90, SCGRAV(1, 6), 10,-1, 0,30},
  81.141 +    {100, SCGRAV(1, 5), 10,-1, 0,30},
  81.142 +    {130, SCGRAV(1, 4), 10,-1, 0,30},
  81.143 +    {160, SCGRAV(1, 3), 10,-1, 0,30},
  81.144 +    {190, SCGRAV(1, 2), 10,-1, 0,30},
  81.145 +    {290, SCGRAV(1, 1), 10,-1, 0,30},
  81.146 +  }
  81.147 +};
  81.148 +
  81.149 +const LJSpeedCurve scGB = {
  81.150 +  "Game Boy",
  81.151 +  SPEEDTYPE_LINES, 10,
  81.152 +  18,
  81.153 +  {
  81.154 +    {  0, SCGRAV(1,53), 0,-1, 0,90},
  81.155 +    { 10, SCGRAV(1,49), 0,-1, 0,90},
  81.156 +    { 20, SCGRAV(1,45), 0,-1, 0,90},
  81.157 +    { 30, SCGRAV(1,41), 0,-1, 0,90},
  81.158 +    { 40, SCGRAV(1,37), 0,-1, 0,90},
  81.159 +    { 50, SCGRAV(1,33), 0,-1, 0,90},
  81.160 +    { 60, SCGRAV(1,28), 0,-1, 0,90},
  81.161 +    { 70, SCGRAV(1,22), 0,-1, 0,90},
  81.162 +    { 80, SCGRAV(1,17), 0,-1, 0,90},
  81.163 +    { 90, SCGRAV(1,11), 0,-1, 0,90},
  81.164 +    {100, SCGRAV(1,10), 0,-1, 0,90},
  81.165 +    {110, SCGRAV(1, 9), 0,-1, 0,90},
  81.166 +    {120, SCGRAV(1, 8), 0,-1, 0,90},
  81.167 +    {130, SCGRAV(1, 7), 0,-1, 0,90},
  81.168 +    {140, SCGRAV(1, 6), 0,-1, 0,90},
  81.169 +    {160, SCGRAV(1, 5), 0,-1, 0,90},
  81.170 +    {180, SCGRAV(1, 4), 0,-1, 0,90},
  81.171 +    {200, SCGRAV(1, 3), 0,-1, 0,90},
  81.172 +  }
  81.173 +};
  81.174 +
  81.175 +const LJSpeedCurve scZero = {
  81.176 +  "Zero",
  81.177 +  SPEEDTYPE_LINES, 10,
  81.178 +  1,
  81.179 +  {
  81.180 +    {  0, SCGRAV(0, 1),-1,-1,40,-1}
  81.181 +  }
  81.182 +};
  81.183 +
  81.184 +const LJSpeedCurve scExponential = {
  81.185 +  "Exponential",
  81.186 +  SPEEDTYPE_PIECES, 60,
  81.187 +  34,
  81.188 +  {
  81.189 +    {  0, SCGRAV(  1,60),-1,-1,40,-1 },
  81.190 +    { 30, SCGRAV(  1,42),-1,-1,40,-1 },
  81.191 +    { 60, SCGRAV(  2,60),-1,-1,40,-1 },
  81.192 +    { 90, SCGRAV(  2,42),-1,-1,40,-1 },
  81.193 +    {120, SCGRAV(  4,60),-1,-1,40,-1 },
  81.194 +    {150, SCGRAV(  4,42),-1,-1,40,-1 },
  81.195 +    {180, SCGRAV(  8,60),-1,-1,40,-1 },
  81.196 +    {210, SCGRAV(  8,42),-1,-1,40,-1 },
  81.197 +    {240, SCGRAV( 16,60),-1,-1,40,-1 },
  81.198 +    {270, SCGRAV( 16,42),-1,-1,40,-1 },
  81.199 +    {300, SCGRAV( 32,60),-1,-1,40,-1 },
  81.200 +    {330, SCGRAV( 32,42),-1,-1,40,-1 },
  81.201 +    {360, SCGRAV( 64,60),-1,-1,40,-1 },
  81.202 +    {390, SCGRAV( 64,42),-1,-1,40,-1 },
  81.203 +    {420, SCGRAV(128,60),-1,-1,40,-1 },
  81.204 +    {450, SCGRAV(128,42),-1,-1,40,-1 },
  81.205 +    {480, SCGRAV(256,60),-1,-1,40,-1 },
  81.206 +    {510, SCGRAV(256,42),-1,-1,40,-1 },
  81.207 +    {540, SCGRAV(512,60),-1,-1,40,-1 },
  81.208 +    {570, SCGRAV(512,42),-1,-1,40,-1 },
  81.209 +    {600, SCGRAV( 20, 1),-1,-1,40,-1 },
  81.210 +    {630, SCGRAV( 20, 1),-1,-1,30,-1 },
  81.211 +    {660, SCGRAV( 20, 1),-1,-1,24,-1 },
  81.212 +    {690, SCGRAV( 20, 1),-1,-1,20,-1 },
  81.213 +    {720, SCGRAV( 20, 1),-1,-1,17,-1 },
  81.214 +    {750, SCGRAV( 20, 1),-1,-1,15,-1 },
  81.215 +    {780, SCGRAV( 20, 1),-1,-1,13,-1 },
  81.216 +    {810, SCGRAV( 20, 1),-1,-1,12,-1 },
  81.217 +    {840, SCGRAV( 20, 1),-1,-1,11,-1 },
  81.218 +    {870, SCGRAV( 20, 1),-1,-1,10,-1 },
  81.219 +    {900, SCGRAV( 20, 1),-1,-1, 9,-1 },
  81.220 +    {930, SCGRAV( 20, 1),-1,-1, 8,-1 },
  81.221 +    {960, SCGRAV( 20, 1),-1,-1, 7,-1 },
  81.222 +    {990, SCGRAV( 20, 1),-1,-1, 6,-1 }
  81.223 +  }
  81.224 +};
  81.225 +
  81.226 +static const LJSpeedCurve *const newSpeedCurves[LJSPD_N_CURVES] = {
  81.227 +  [LJSPD_EXP] = &scExponential,
  81.228 +  [LJSPD_ZERO] = &scZero,
  81.229 +  [LJSPD_TGM] = &scMaster,
  81.230 +  [LJSPD_DEATH] = &scDeath,
  81.231 +  [LJSPD_DEATH300] = &scDeath,
  81.232 +  [LJSPD_NES] = &scNES,
  81.233 +  [LJSPD_GB] = &scGB,
  81.234 +  [LJSPD_GBHEART] = &scGB,
  81.235 +};
  81.236 +
  81.237 +/**
  81.238 + * Performs a fast binary search of a speed step table.
  81.239 + */
  81.240 +static const LJSpeedStep *getSpeedStep(const LJSpeedStep *steps,
  81.241 +                                       size_t nSteps, int level) {
  81.242 +  unsigned int lo = 0;
  81.243 +  unsigned int hi = nSteps;
  81.244 +
  81.245 +  while (hi - lo > 1) {
  81.246 +    size_t mid = (hi + lo) / 2;
  81.247 +    unsigned int here = steps[mid].level;
  81.248 +    if (here == level) {
  81.249 +      return &(steps[mid]);
  81.250 +    } else if (here < level) {
  81.251 +      lo = mid;
  81.252 +    } else {
  81.253 +      hi = mid;
  81.254 +    }
  81.255 +  }
  81.256 +  return &(steps[lo]);
  81.257 +}
  81.258 +
  81.259 +/**
  81.260 + * Updates the level after each piece has retired.
  81.261 + * @return nonzero iff a new section has happened
  81.262 + */
  81.263 +int updLevelAfterPiece(LJField *p) {
  81.264 +  int curveID = p->speedState.curve;
  81.265 +  const LJSpeedCurve *curve = newSpeedCurves[curveID];
  81.266 +  if (!curve) {
  81.267 +    return 0;
  81.268 +  }
  81.269 +  unsigned int sectionLen = curve->sectionLen;
  81.270 +  unsigned int oldLevel = p->speedState.level;
  81.271 +  unsigned int oldSection = oldLevel / sectionLen;
  81.272 +  unsigned int oldSectionPos = oldLevel % sectionLen;
  81.273 +
  81.274 +  switch (curve->type) {
  81.275 +  case SPEEDTYPE_TGM:
  81.276 +    if (oldSectionPos + 1 >= sectionLen) {
  81.277 +      return 0;
  81.278 +    }
  81.279 +    // otherwise fall through to +1 per piece
  81.280 +
  81.281 +  case SPEEDTYPE_PIECES:
  81.282 +    p->speedState.level = oldLevel + 1;
  81.283 +    break;
  81.284 +
  81.285 +  default:
  81.286 +    return 0;
  81.287 +  }
  81.288 +
  81.289 +  unsigned int newSection = p->speedState.level / sectionLen;
  81.290 +  return newSection > oldSection;
  81.291 +}
  81.292 +
  81.293 +/**
  81.294 + * Updates the level after lines have been cleared.
  81.295 + * @return nonzero iff a new section has happened
  81.296 + */
  81.297 +int updLevelAfterLines(LJField *p, unsigned int nLines) {
  81.298 +  int curveID = p->speedState.curve;
  81.299 +  const LJSpeedCurve *curve = newSpeedCurves[curveID];
  81.300 +  if (!curve) {
  81.301 +    return 0;
  81.302 +  }
  81.303 +  unsigned int sectionLen = curve->sectionLen;
  81.304 +  unsigned int oldLevel = p->speedState.level;
  81.305 +  unsigned int oldSection = oldLevel / sectionLen;
  81.306 +
  81.307 +  switch (curve->type) {
  81.308 +  case SPEEDTYPE_TGM:
  81.309 +  case SPEEDTYPE_LINES:
  81.310 +    p->speedState.level = oldLevel + nLines;
  81.311 +    break;
  81.312 +
  81.313 +  default:
  81.314 +    return 0;
  81.315 +  }
  81.316 +
  81.317 +  unsigned int newSection = p->speedState.level / sectionLen;
  81.318 +  return newSection > oldSection;
  81.319 +}
  81.320 +
  81.321 +void setSpeedNew(LJField *p, LJControl *c) {
  81.322 +  int curveID = p->speedState.curve;
  81.323 +  const LJSpeedCurve *curve = newSpeedCurves[curveID];
  81.324 +  if (!curve) {
  81.325 +    return;
  81.326 +  }
  81.327 +  const LJSpeedStep *step =
  81.328 +      getSpeedStep(curve->steps, curve->nSteps, p->speedState.level);
  81.329 +
  81.330 +  p->speed.gravity = step->gravity << 5;
  81.331 +
  81.332 +  p->speed.entryDelay = step->are;
  81.333 +  if (p->speed.entryDelay > p->areStyle) {
  81.334 +    p->speed.entryDelay = p->areStyle;
  81.335 +  }
  81.336 +
  81.337 +  if (step->das > 0 && c->dasDelay > step->das) {
  81.338 +    c->dasDelay = step->das;
  81.339 +  }
  81.340 +
  81.341 +  if (p->setLockDelay >= 128) {
  81.342 +    p->speed.lockDelay = 127;
  81.343 +  } else if (p->setLockDelay > 0) {
  81.344 +    p->speed.lockDelay = p->setLockDelay;
  81.345 +  } else if (step->lock > 0) {
  81.346 +    p->speed.lockDelay = step->lock;
  81.347 +  }
  81.348 +
  81.349 +  if (p->setLineDelay > 0) {
  81.350 +    p->speed.lineDelay = p->setLineDelay;
  81.351 +  } else if (step->line >= 0) {
  81.352 +    p->speed.lineDelay = step->line;
  81.353 +  } else {
  81.354 +    p->speed.lineDelay = p->speed.entryDelay;
  81.355 +  }
  81.356 +}
  81.357 +
  81.358 +
  81.359 +
  81.360 +/* Old speed curve system is below this line ***********************/
  81.361 +
  81.362 +
  81.363 +
  81.364 +void initSpeed(LJField *p) {
  81.365 +  if (defaultSpeedCurve[p->gimmick] >= 0) {
  81.366 +    p->speedState.curve = defaultSpeedCurve[p->gimmick];
  81.367 +  }
  81.368 +  p->speed.gravity = initialGravity[p->speedState.curve];
  81.369 +  
  81.370 +  switch (p->speedState.curve) {
  81.371 +  case LJSPD_RHYTHM:
  81.372 +  case LJSPD_RHYTHMZERO:
  81.373 +    p->speedState.level = 60;
  81.374 +    p->bpmCounter = 0;
  81.375 +    p->speedupCounter = 0;
  81.376 +    break;
  81.377 +  case LJSPD_TGM:
  81.378 +  case LJSPD_DEATH:
  81.379 +  case LJSPD_EXP:
  81.380 +    p->speedState.level = -1;
  81.381 +    break;
  81.382 +  case LJSPD_DEATH300:
  81.383 +    p->speedState.level = 300;
  81.384 +    break;
  81.385 +  case LJSPD_GBHEART:
  81.386 +    p->speedState.level = 100;
  81.387 +    break;
  81.388 +  default:
  81.389 +    p->speedState.level = 0;
  81.390 +    break;
  81.391 +  }
  81.392 +}
  81.393 +
  81.394 +
  81.395 +void setSpeed(LJField *p, LJControl *c) {
  81.396 +  p->speed.entryDelay = p->areStyle;
  81.397 +
  81.398 +  // Default line delay is equal to the entry delay,
  81.399 +  // but speed curves and setLineDelay can override this
  81.400 +  p->speed.lineDelay = p->speed.entryDelay;
  81.401 +  switch (p->speedState.curve) {
  81.402 +
  81.403 +  case LJSPD_RHYTHM:
  81.404 +  case LJSPD_RHYTHMZERO:
  81.405 +    // If we've already banked five pieces' worth of time,
  81.406 +    // add 20 points instead of banking another.
  81.407 +    if (p->bpmCounter <= -18000) {
  81.408 +      // removed in 0.21 because other curves don't reward for drops
  81.409 +      // p->score += 20;
  81.410 +    } else {
  81.411 +      p->bpmCounter -= 3600;  // number of frames per minute
  81.412 +    }
  81.413 +    p->speed.lockDelay = 3600 / p->speedState.level;
  81.414 +    p->speed.gravity = (p->speedState.curve == LJSPD_RHYTHM) ? ljitofix(20) : 0;
  81.415 +    break;
  81.416 +
  81.417 +  default:
  81.418 +    if (updLevelAfterPiece(p)) {
  81.419 +      p->sounds |= LJSND_SECTIONUP;
  81.420 +    }
  81.421 +    setSpeedNew(p, c);
  81.422 +    break;
  81.423 +  }
  81.424 +  
  81.425 +  if (p->setLockDelay >= 128) {
  81.426 +    p->speed.lockDelay = 127;
  81.427 +  } else if (p->setLockDelay > 0) {
  81.428 +    p->speed.lockDelay = p->setLockDelay;
  81.429 +  }
  81.430 +  if (p->setLineDelay > 0) {
  81.431 +    p->speed.lineDelay = p->setLineDelay;
  81.432 +  }
  81.433 +}
  81.434 +
    82.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    82.2 +++ b/src/talkback.h	Fri Mar 13 00:39:12 2009 -0700
    82.3 @@ -0,0 +1,24 @@
    82.4 +/*
    82.5 +This struct lives in the ARM9's memory. It is used to send data back
    82.6 +to the ARM7.
    82.7 +
    82.8 +By Damian Yerrick. No rights reserved and ABSOLUTELY NO WARRANTY.
    82.9 +*/
   82.10 +#ifndef TALKBACK_H
   82.11 +#define TALKBACK_H
   82.12 +
   82.13 +typedef struct P8A7Talkback {
   82.14 +  unsigned long int sounds;
   82.15 +  signed char countdown;
   82.16 +  unsigned char cmd;
   82.17 +} P8A7Talkback;
   82.18 +
   82.19 +enum {
   82.20 +  TALKBACK_NONE = 0,
   82.21 +  TALKBACK_POWER_OFF = 1,
   82.22 +  TALKBACK_PLAY_MUSIC = 2,
   82.23 +  TALKBACK_STOP_MUSIC = 3,
   82.24 +  TALKBACK_PAUSE_MUSIC = 4
   82.25 +};
   82.26 +
   82.27 +#endif
    83.1 Binary file src/text.chr has changed
    84.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    84.2 +++ b/src/winicon.rc	Fri Mar 13 00:39:12 2009 -0700
    84.3 @@ -0,0 +1,1 @@
    84.4 +allegro_icon ICON PRELOAD "docs/appicon.ico"
    85.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    85.2 +++ b/src/wktables.c	Fri Mar 13 00:39:12 2009 -0700
    85.3 @@ -0,0 +1,454 @@
    85.4 +/* Wall kick tables for LOCKJAW, an implementation of the Soviet Mind Game
    85.5 +
    85.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
    85.7 +
    85.8 +This work is free software; you can redistribute it and/or modify
    85.9 +it under the terms of the GNU General Public License as published by
   85.10 +the Free Software Foundation; either version 2 of the License, or
   85.11 +(at your option) any later version.
   85.12 +
   85.13 +This program is distributed in the hope that it will be useful,
   85.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
   85.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   85.16 +GNU General Public License for more details.
   85.17 +
   85.18 +You should have received a copy of the GNU General Public License
   85.19 +along with this program; if not, write to the Free Software
   85.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   85.21 +
   85.22 +Original game concept and design by Alexey Pajitnov.
   85.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
   85.24 +or The Tetris Company LLC.
   85.25 +
   85.26 +*/
   85.27 +
   85.28 +#define LJ_INTERNAL
   85.29 +#include "lj.h"
   85.30 +
   85.31 +
   85.32 +// These wall kicks are for rotation TO a given orientation.
   85.33 +// Based on http://www.the-shell.net/img/srs_study.html
   85.34 +static const LJRotSystem rotSRS = {
   85.35 +  .kicksL = {1, 0, 0, -1, 0, 0, 0, -1, 0, -1},
   85.36 +  .kicksR = {3, 2, 2, -1, 2, 2, 2, -1, 2, -3},
   85.37 +  .kickTables = {
   85.38 +
   85.39 +    // 0: JLSTZ counterclockwise
   85.40 +    {
   85.41 +      { WK( 0, 0),WK( 1, 0),WK( 1,-1),WK( 0, 2),WK( 1, 2) },  // R->U
   85.42 +      { WK( 0, 0),WK(-1, 0),WK(-1, 1),WK( 0,-2),WK(-1,-2) },  // D->R
   85.43 +      { WK( 0, 0),WK(-1, 0),WK(-1,-1),WK( 0, 2),WK(-1, 2) },  // L->D
   85.44 +      { WK( 0, 0),WK( 1, 0),WK( 1, 1),WK( 0,-2),WK( 1,-2) }   // U->L
   85.45 +    },
   85.46 +
   85.47 +    // 1: I counterclockwise
   85.48 +    {
   85.49 +      { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK(-1,-2),WK( 2, 1) },  // R->U
   85.50 +      { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK( 1,-1),WK(-2, 1) },  // D->R
   85.51 +      { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK(-2,-1),WK( 1, 2) },  // L->D
   85.52 +      { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK( 2,-1),WK(-1, 2) }   // U->L
   85.53 +    },
   85.54 +
   85.55 +    // 2: JLSTZ clockwise
   85.56 +    {
   85.57 +      { WK( 0, 0),WK(-1, 0),WK(-1,-1),WK( 0, 2),WK(-1, 2) },  // L->U
   85.58 +      { WK( 0, 0),WK(-1, 0),WK(-1, 1),WK( 0,-2),WK(-1,-2) },  // U->R
   85.59 +      { WK( 0, 0),WK( 1, 0),WK( 1,-1),WK( 0, 2),WK( 1, 2) },  // R->D
   85.60 +      { WK( 0, 0),WK( 1, 0),WK( 1, 1),WK( 0,-2),WK( 1,-2) },  // D->L
   85.61 +    },
   85.62 +
   85.63 +    // 3: I clockwise
   85.64 +    {
   85.65 +      { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK( 1,-2),WK(-2, 1) },  // L->U
   85.66 +      { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK(-2,-1),WK( 1, 2) },  // U->R
   85.67 +      { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK( 2,-1),WK(-1, 2) },  // R->D
   85.68 +      { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK(-1,-1),WK( 2, 1) }   // D->L
   85.69 +    }
   85.70 +  }
   85.71 +};
   85.72 +
   85.73 +// I: round to top right
   85.74 +// J, L, T: round to bottom
   85.75 +// S: round to bottom left (constant center column)
   85.76 +// Z: round to bottom right (constant center column)
   85.77 +static const LJRotSystem rotSega = {
   85.78 +  .colorScheme = 1,
   85.79 +  .entryOffset = {
   85.80 +    WK( 0, 0),WK( 0, 1),WK( 0, 1),WK( 0, 0),WK( 0, 0),WK( 0, 1),WK( 0, 0),
   85.81 +    WK( 0, 0),WK( 0, 0),WK( 0, 0)
   85.82 +  },
   85.83 +  .entryTheta = { 0, 2, 2, 0, 0, 2, 0, 0, 0, 2 },
   85.84 +  .kicksL = {0, 1, 1, -1, 2, 1, 3, 0, -1, -1},
   85.85 +  .kicksR = {4, 5, 5, -1, 6, 5, 7, 4, -1, -1},
   85.86 +
   85.87 +  .kickTables = {
   85.88 +
   85.89 +    // 0: I counterclockwise
   85.90 +    {
   85.91 +      { WK( 0, 0),WK_END },  // R->U
   85.92 +      { WK( 0,-1),WK_END },  // D->R
   85.93 +      { WK(-1, 1),WK_END },  // L->D
   85.94 +      { WK( 1, 0),WK_END }   // U->L
   85.95 +    },
   85.96 +
   85.97 +    // 1: JLT counterclockwise
   85.98 +    {
   85.99 +      { WK( 0,-1),WK_END },  // R->U
  85.100 +      { WK( 0, 0),WK_END },  // D->R
  85.101 +      { WK( 0, 0),WK_END },  // L->D
  85.102 +      { WK( 0, 1),WK_END }   // U->L
  85.103 +    },
  85.104 +
  85.105 +    // 2: S counterclockwise (round left, like Game Boy)
  85.106 +    {
  85.107 +      { WK( 1,-1),WK_END },  // R->U
  85.108 +      { WK(-1, 0),WK_END },  // D->R
  85.109 +      { WK( 0, 0),WK_END },  // L->D
  85.110 +      { WK( 0, 1),WK_END }   // U->L
  85.111 +    },
  85.112 +
  85.113 +    // 3: Z counterclockwise (round right, like NES)
  85.114 +    {
  85.115 +      { WK( 0,-1),WK_END },  // R->U
  85.116 +      { WK( 0, 0),WK_END },  // D->R
  85.117 +      { WK(-1, 0),WK_END },  // L->D
  85.118 +      { WK( 1, 1),WK_END }   // U->L
  85.119 +    },
  85.120 +
  85.121 +    // 4: I clockwise
  85.122 +    {
  85.123 +      { WK(-1, 0),WK_END },  // L->U
  85.124 +      { WK( 0, 0),WK_END },  // U->R
  85.125 +      { WK( 0, 1),WK_END },  // R->D
  85.126 +      { WK( 1,-1),WK_END }   // D->L
  85.127 +    },
  85.128 +
  85.129 +    // 5: JLT clockwise
  85.130 +    {
  85.131 +      { WK( 0,-1),WK_END },  // L->U
  85.132 +      { WK( 0, 1),WK_END },  // U->R
  85.133 +      { WK( 0, 0),WK_END },  // R->D
  85.134 +      { WK( 0, 0),WK_END }   // D->L
  85.135 +    },
  85.136 +
  85.137 +    // 6: S clockwise (round left)
  85.138 +    {
  85.139 +      { WK( 0,-1),WK_END },  // L->U
  85.140 +      { WK(-1, 1),WK_END },  // U->R
  85.141 +      { WK( 1, 0),WK_END },  // R->D
  85.142 +      { WK( 0, 0),WK_END }   // D->L
  85.143 +    },
  85.144 +
  85.145 +    // 7: Z clockwise (round right)
  85.146 +    {
  85.147 +      { WK(-1,-1),WK_END },  // L->U
  85.148 +      { WK( 0, 1),WK_END },  // U->R
  85.149 +      { WK( 0, 0),WK_END },  // R->D
  85.150 +      { WK( 1, 0),WK_END }   // D->L
  85.151 +    }
  85.152 +  }
  85.153 +};
  85.154 +
  85.155 +// Arika is based on Sega but with a few wall kicks.
  85.156 +// Each free-space kick should be followed by Right, then Left
  85.157 +// but for J, L, and T, kicks to vertical positions (point-right and
  85.158 +// point-left) 
  85.159 +// T when rotating to point-up can also floor kick by one,
  85.160 +// and I when rotating to vertical can floor kick by one or two.
  85.161 +static const LJRotSystem rotArika = {
  85.162 +  .colorScheme = 1,
  85.163 +  .entryOffset = {
  85.164 +    WK( 0, 0),WK( 0, 1),WK( 0, 1),WK( 0, 0),WK( 0, 0),WK( 0, 1),WK( 0, 0),
  85.165 +    WK( 0, 0),WK( 0, 0),WK( 0, 0)
  85.166 +  },
  85.167 +  .entryTheta = { 0, 2, 2, 0, 0, 2, 0, 0, 0, 2 },
  85.168 +  .kicksL = {0, 1, 1, -1, 2, 8, 3, 0, -1, -1},
  85.169 +  .kicksR = {4, 5, 5, -1, 6, 9, 7, 4, -1, -1},
  85.170 +  .kickTables = {
  85.171 +
  85.172 +    // 0: I counterclockwise
  85.173 +    {
  85.174 +      { WK( 0, 0),WK_END },  // R->U
  85.175 +      { WK( 0,-1),WK( 0, 0),WK( 0, 1),WK_END },  // D->R
  85.176 +      { WK(-1, 1),WK_END },  // L->D
  85.177 +      { WK( 1, 0),WK( 1, 1),WK( 1, 2),WK_END }   // U->L
  85.178 +    },
  85.179 +
  85.180 +    // 1: JL counterclockwise
  85.181 +    {
  85.182 +      { WK( 0,-1),WK( 1,-1),WK(-1,-1),WK_END },  // R->U
  85.183 +      { WK( 0, 0),ARIKA_IF_NOT_CENTER,WK( 1, 0),WK(-1, 0),WK_END },  // D->R
  85.184 +      { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END },  // L->D
  85.185 +      { WK( 0, 1),ARIKA_IF_NOT_CENTER,WK( 1, 1),WK(-1, 1),WK_END }   // U->L
  85.186 +    },
  85.187 +
  85.188 +    // 2: S counterclockwise (round left, like Game Boy with WK)
  85.189 +    {
  85.190 +      { WK( 1,-1),WK( 2,-1),WK( 0,-1),WK_END },  // R->U
  85.191 +      { WK(-1, 0),WK( 0, 0),WK(-2, 0),WK_END },  // D->R
  85.192 +      { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END },  // L->D
  85.193 +      { WK( 0, 1),WK( 1, 1),WK(-1, 1),WK_END }   // U->L
  85.194 +    },
  85.195 +
  85.196 +    // 3: Z counterclockwise (round right, like NES with WK)
  85.197 +    {
  85.198 +      { WK( 0,-1),WK( 1,-1),WK(-1,-1),WK_END },  // R->U
  85.199 +      { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END },  // D->R
  85.200 +      { WK(-1, 0),WK( 0, 0),WK(-2, 0),WK_END },  // L->D
  85.201 +      { WK( 1, 1),WK( 2, 1),WK( 0, 1),WK_END }   // U->L
  85.202 +    },
  85.203 +
  85.204 +    // 4: I clockwise
  85.205 +    {
  85.206 +      { WK(-1, 0),WK_END },  // L->U
  85.207 +      { WK( 0, 0),WK( 0, 1),WK( 0, 2),WK_END },  // U->R
  85.208 +      { WK( 0, 1),WK_END },  // R->D
  85.209 +      { WK( 1,-1),WK( 1, 0),WK( 1, 1),WK_END }   // D->L
  85.210 +    },
  85.211 +
  85.212 +    // 5: JLT clockwise
  85.213 +    {
  85.214 +      { WK( 0,-1),WK( 1,-1),WK(-1,-1),WK_END },  // L->U
  85.215 +      { WK( 0, 1),ARIKA_IF_NOT_CENTER,WK( 1, 1),WK(-1, 1),WK_END },  // U->R
  85.216 +      { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END },  // R->D
  85.217 +      { WK( 0, 0),ARIKA_IF_NOT_CENTER,WK( 1, 0),WK(-1, 0),WK_END }   // D->L
  85.218 +    },
  85.219 +
  85.220 +    // 6: S clockwise (round left)
  85.221 +    {
  85.222 +      { WK( 0,-1),WK( 1,-1),WK(-1,-1),WK_END },  // L->U
  85.223 +      { WK(-1, 1),WK( 0, 1),WK(-2, 1),WK_END },  // U->R
  85.224 +      { WK( 1, 0),WK( 2, 0),WK( 0, 0),WK_END },  // R->D
  85.225 +      { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END }   // D->L
  85.226 +    },
  85.227 +
  85.228 +    // 7: Z clockwise (round right)
  85.229 +    {
  85.230 +      { WK(-1,-1),WK( 0,-1),WK(-2,-1),WK_END },  // L->U
  85.231 +      { WK( 0, 1),WK( 1, 1),WK(-1, 1),WK_END },  // U->R
  85.232 +      { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END },  // R->D
  85.233 +      { WK( 1, 0),WK( 2, 0),WK( 0, 0),WK_END }   // D->L
  85.234 +    },
  85.235 +
  85.236 +    // 8: T counterclockwise (with TI floorkick)
  85.237 +    {
  85.238 +      { WK( 0,-1),WK( 1,-1),WK(-1,-1),WK( 0, 0),WK_END },  // R->U
  85.239 +      { WK( 0, 0),ARIKA_IF_NOT_CENTER,WK( 1, 0),WK(-1, 0),WK_END },  // D->R
  85.240 +      { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END },  // L->D
  85.241 +      { WK( 0, 1),ARIKA_IF_NOT_CENTER,WK( 1, 1),WK(-1, 1),WK_END }   // U->L
  85.242 +    },
  85.243 +
  85.244 +    // 9: T clockwise (with TI floorkick)
  85.245 +    {
  85.246 +      { WK( 0,-1),WK( 1,-1),WK(-1,-1),WK( 0, 0),WK_END },  // L->U
  85.247 +      { WK( 0, 1),ARIKA_IF_NOT_CENTER,WK( 1, 1),WK(-1, 1),WK_END },  // U->R
  85.248 +      { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END },  // R->D
  85.249 +      { WK( 0, 0),ARIKA_IF_NOT_CENTER,WK( 1, 0),WK(-1, 0),WK_END }   // D->L
  85.250 +    }
  85.251 +  }
  85.252 +};
  85.253 +
  85.254 +// All pieces are started with their left side in column 5 and flat side up.
  85.255 +// All pieces stick to the top of the bounding box.
  85.256 +// All 3-wide pieces stick to the left side of the bounding box.
  85.257 +// I sticks to the top when left and right and occupies the second column
  85.258 +// when up and down.
  85.259 +// Try here, then try kicking one space left.  Discovered by zaphod77:
  85.260 +// http://www.tetrisconcept.com/forum/viewtopic.php?t=877
  85.261 +static const LJRotSystem rotTengen = {
  85.262 +  .colorScheme = 1,
  85.263 +  .entryOffset = {
  85.264 +    WK( 1, 0),WK( 1, 1),WK( 1, 1),WK( 0, 0),WK( 1, 0),WK( 1, 1),WK( 1, 0),
  85.265 +    WK( 0, 0),WK( 1, 0),WK( 0, 0)
  85.266 +  },
  85.267 +  .entryTheta = { 0, 2, 2, 0, 0, 2, 0, 0, 0, 2 },
  85.268 +  .kicksL = {0, 3, 3, -1, 3, 3, 3, 0, 2, 5},
  85.269 +  .kicksR = {1, 4, 4, -1, 4, 4, 4, 1, 3, 5},
  85.270 +  .kickTables = {
  85.271 +
  85.272 +    // 0: I counterclockwise
  85.273 +    {
  85.274 +      { WK( 1, 1),WK( 0, 1),WK_END },  // R->U
  85.275 +      { WK(-1,-2),WK(-2,-2),WK_END },  // D->R
  85.276 +      { WK( 0, 2),WK(-1, 2),WK_END },  // L->D
  85.277 +      { WK( 0,-1),WK(-1,-1),WK_END }   // U->L
  85.278 +    },
  85.279 +
  85.280 +    // 1: I clockwise
  85.281 +    {
  85.282 +      { WK( 0, 1),WK(-1, 1),WK_END },  // L->U
  85.283 +      { WK(-1,-1),WK(-2,-1),WK_END },  // U->R
  85.284 +      { WK( 1, 2),WK( 0, 2),WK_END },  // R->D
  85.285 +      { WK( 0,-2),WK(-1,-2),WK_END }   // D->L
  85.286 +    },
  85.287 +
  85.288 +    // 2: I3
  85.289 +    {
  85.290 +      { WK( 0, 1),WK(-1, 1),WK_END },  // L->U
  85.291 +      { WK( 0,-1),WK(-1,-1),WK_END },  // U->R
  85.292 +      { WK( 0, 1),WK(-1, 1),WK_END },  // R->D
  85.293 +      { WK( 0,-1),WK(-1,-1),WK_END }   // D->L
  85.294 +    },
  85.295 +
  85.296 +    // 3: JLSTZ counterclockwise
  85.297 +    {
  85.298 +      { WK( 1, 0),WK( 0, 0),WK_END },  // R->U
  85.299 +      { WK(-1,-1),WK(-2,-1),WK_END },  // D->R
  85.300 +      { WK( 0, 1),WK(-1, 1),WK_END },  // L->D
  85.301 +      { WK( 0, 0),WK(-1, 0),WK_END }   // U->L
  85.302 +    },
  85.303 +
  85.304 +    // 4: JLSTZ clockwise
  85.305 +    {
  85.306 +      { WK( 0, 0),WK(-1, 0),WK_END },  // L->U
  85.307 +      { WK(-1, 0),WK(-2, 0),WK_END },  // U->R
  85.308 +      { WK( 1, 1),WK( 0, 1),WK_END },  // R->D
  85.309 +      { WK( 0,-1),WK(-1,-1),WK_END }   // D->L
  85.310 +    },
  85.311 +
  85.312 +    // 5: L3
  85.313 +    {
  85.314 +      { WK( 0, 0),WK(-1, 0),WK_END },  // L->U
  85.315 +      { WK(-1, 0),WK(-2, 0),WK_END },  // U->R
  85.316 +      { WK( 1, 1),WK( 0, 1),WK_END },  // R->D
  85.317 +      { WK( 0,-1),WK(-1,-1),WK_END }   // D->L
  85.318 +    }
  85.319 +  }
  85.320 +};
  85.321 +
  85.322 +// NES: No wall kick
  85.323 +// 3-wide pieces start out one block to the right
  85.324 +// I, S and Z round to the right and use effective positions R and D
  85.325 +static const LJRotSystem rotNES = {
  85.326 +  .colorScheme = 1,
  85.327 +  .entryOffset = {
  85.328 +    WK( 0, 0),WK( 1, 1),WK( 1, 1),WK( 0, 0),WK( 1, 0),WK( 1, 1),WK( 1, 0),
  85.329 +    WK( 0, 0),WK( 0, 0),WK( 0, 0)
  85.330 +  },
  85.331 +  .entryTheta = { 0, 2, 2, 0, 0, 2, 0, 0, 0, 2 },
  85.332 +  .kicksL = {0, -1, -1, -1, 0, -1, 0, 0, -1, -1},
  85.333 +  .kicksR = {1, -1, -1, -1, 1, -1, 1, 1, -1, -1},
  85.334 +  .kickTables = {
  85.335 +
  85.336 +    // 0: counterclockwise (round right)
  85.337 +    {
  85.338 +      { WK( 0,-1),WK_END },  // R->U
  85.339 +      { WK( 0, 0),WK_END },  // D->R
  85.340 +      { WK(-1, 0),WK_END },  // L->D
  85.341 +      { WK( 1, 1),WK_END }   // U->L
  85.342 +    },
  85.343 +    // 1: clockwise (round right)
  85.344 +    {
  85.345 +      { WK(-1,-1),WK_END },  // L->U
  85.346 +      { WK( 0, 1),WK_END },  // U->R
  85.347 +      { WK( 0, 0),WK_END },  // R->D
  85.348 +      { WK( 1, 0),WK_END }   // D->L
  85.349 +    }
  85.350 +  }
  85.351 +};
  85.352 +
  85.353 +// GB: No wall kick
  85.354 +// I, S and Z round to the left and use effective positions L and D
  85.355 +static const LJRotSystem rotGB = {
  85.356 +  .colorScheme = 1,
  85.357 +  .entryOffset = {
  85.358 +    WK( 0, 0),WK( 0, 1),WK( 0, 1),WK( 0, 0),WK( 0, 0),WK( 0, 1),WK( 0, 0),
  85.359 +    WK( 0, 0),WK( 0, 0),WK( 0, 0)
  85.360 +  },
  85.361 +  .entryTheta = { 0, 2, 2, 0, 0, 2, 0, 0, 0, 2 },
  85.362 +  .kicksL = {0, -1, -1, -1, 0, -1, 0, 0, -1, -1},
  85.363 +  .kicksR = {1, -1, -1, -1, 1, -1, 1, 1, -1, -1},
  85.364 +  .kickTables = {
  85.365 +
  85.366 +    // 0: counterclockwise (round left)
  85.367 +    {
  85.368 +      { WK( 1,-1),WK_END },  // R->U
  85.369 +      { WK(-1, 0),WK_END },  // D->R
  85.370 +      { WK( 0, 0),WK_END },  // L->D
  85.371 +      { WK( 0, 1),WK_END }   // U->L
  85.372 +    },
  85.373 +    // 1: clockwise (round left)
  85.374 +    {
  85.375 +      { WK( 0,-1),WK_END },  // L->U
  85.376 +      { WK(-1, 1),WK_END },  // U->R
  85.377 +      { WK( 1, 0),WK_END },  // R->D
  85.378 +      { WK( 0, 0),WK_END }   // D->L
  85.379 +    },
  85.380 +  }
  85.381 +};
  85.382 +
  85.383 +
  85.384 +// The rotation system of LOCKJAW: The Overdose (GBA) and
  85.385 +// Tetramino (NES) TOD is simple.  In free space it behaves like SRS.
  85.386 +// If that's blocked, kick right, kick left, kick up.
  85.387 +static const LJRotSystem rotTOD = {
  85.388 +  .kicksL = {0, 0, 0, -1, 0, 0, 0, 0, 0, 0},
  85.389 +  .kicksR = {0, 0, 0, -1, 0, 0, 0, 0, 0, 0},
  85.390 +  .kickTables = {
  85.391 +
  85.392 +    // 0: JLSTZ counterclockwise
  85.393 +    {
  85.394 +      { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK( 0, 1),WK_END },  // ->U
  85.395 +      { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK( 0, 1),WK_END },  // ->R
  85.396 +      { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK( 0, 1),WK_END },  // ->D
  85.397 +      { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK( 0, 1),WK_END }   // ->L
  85.398 +    },
  85.399 +
  85.400 +    // 1: I counterclockwise
  85.401 +    {
  85.402 +      { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK(-1,-2),WK( 2, 1) },  // R->U
  85.403 +      { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK( 1,-1),WK(-2, 1) },  // D->R
  85.404 +      { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK(-2,-1),WK( 1, 2) },  // L->D
  85.405 +      { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK( 2,-1),WK(-1, 2) }   // U->L
  85.406 +    },
  85.407 +
  85.408 +    // 2: JLSTZ clockwise
  85.409 +    {
  85.410 +      { WK( 0, 0),WK(-1, 0),WK(-1,-1),WK( 0, 2),WK(-1, 2) },  // L->U
  85.411 +      { WK( 0, 0),WK(-1, 0),WK(-1, 1),WK( 0,-2),WK(-1,-2) },  // U->R
  85.412 +      { WK( 0, 0),WK( 1, 0),WK( 1,-1),WK( 0, 2),WK( 1, 2) },  // R->D
  85.413 +      { WK( 0, 0),WK( 1, 0),WK( 1, 1),WK( 0,-2),WK( 1,-2) },  // D->L
  85.414 +    },
  85.415 +
  85.416 +    // 3: I clockwise
  85.417 +    {
  85.418 +      { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK( 1,-2),WK(-2, 1) },  // L->U
  85.419 +      { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK(-2,-1),WK( 1, 2) },  // U->R
  85.420 +      { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK( 2,-1),WK(-1, 2) },  // R->D
  85.421 +      { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK(-1,-1),WK( 2, 1) }   // D->L
  85.422 +    }
  85.423 +  }
  85.424 +};
  85.425 +
  85.426 +static const LJRotSystem rotTDX = {
  85.427 +  .entryOffset = {
  85.428 +    WK( 0, 0),WK( 1, 1),WK( 1, 1),WK( 0, 0),WK( 1, 0),WK( 1, 1),WK( 1, 0),
  85.429 +    WK( 0, 0),WK( 0, 0),WK( 0, 0)
  85.430 +  },
  85.431 +  .entryTheta = { 0, 2, 2, 0, 0, 2, 0, 0, 0, 2 },
  85.432 +  .kicksL = {0, 0, 0, -1, 0, 0, 0, 0, 0, 0},
  85.433 +  .kicksR = {1, 1, 1, -1, 1, 1, 1, 1, 1, 1},
  85.434 +  .kickTables = {
  85.435 +
  85.436 +    // 0: counterclockwise
  85.437 +    {
  85.438 +      { WK( 0, 0),WK( 1,-1),WK_END },  // R->U
  85.439 +      { WK( 0, 0),WK(-1,-1),WK_END },  // D->R
  85.440 +      { WK( 0, 0),WK(-1, 1),WK_END },  // L->D
  85.441 +      { WK( 0, 0),WK( 1, 1),WK_END }   // U->L
  85.442 +    },
  85.443 +
  85.444 +    // 1: clockwise
  85.445 +    {
  85.446 +      { WK( 0, 0),WK(-1,-1),WK_END },  // L->U
  85.447 +      { WK( 0, 0),WK(-1, 1),WK_END },  // U->R
  85.448 +      { WK( 0, 0),WK( 1, 1),WK_END },  // R->D
  85.449 +      { WK( 0, 0),WK( 1,-1),WK_END }   // D->L
  85.450 +    }
  85.451 +  }
  85.452 +};
  85.453 +
  85.454 +const LJRotSystem *const rotSystems[N_ROTATION_SYSTEMS] = {
  85.455 +  &rotSRS, &rotSega, &rotArika, &rotTengen,
  85.456 +  &rotNES, &rotGB, &rotTOD, &rotTDX
  85.457 +};
    86.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    86.2 +++ b/tools/fontconv.c	Fri Mar 13 00:39:12 2009 -0700
    86.3 @@ -0,0 +1,296 @@
    86.4 +#define USE_CONSOLE
    86.5 +#include <allegro.h>
    86.6 +#include <stdio.h>
    86.7 +
    86.8 +const RGB fontPalette[6] = {
    86.9 +  {63, 0,63},
   86.10 +  {63,63,63},
   86.11 +  {42,42,42},
   86.12 +  { 0, 0, 0},
   86.13 +  {63, 0, 0},
   86.14 +  {63,63, 0}
   86.15 +};
   86.16 +
   86.17 +typedef struct Rect {
   86.18 +  signed short l, t, r, b;
   86.19 +} Rect;
   86.20 +
   86.21 +
   86.22 +BITMAP *fontImg;
   86.23 +const int cellHeight = 12;
   86.24 +Rect glyphs[256];
   86.25 +unsigned short glyphDataOffsets[256];
   86.26 +
   86.27 +#define MIN_GLYPH 0
   86.28 +#define N_GLYPHS 256
   86.29 +
   86.30 +char fontdata[65536];
   86.31 +size_t fontdata_len = 0;
   86.32 +size_t fontdata_shift = 0;
   86.33 +
   86.34 +// stats
   86.35 +size_t eols = 0;
   86.36 +size_t xparents = 0;
   86.37 +size_t opaques = 0;
   86.38 +size_t xlucents = 0;
   86.39 +
   86.40 +void collectStats(int c) {
   86.41 +  for (int y = glyphs[c].t; y < glyphs[c].b; ++y) {
   86.42 +
   86.43 +    // Find right side of each row of glyph
   86.44 +    int right = glyphs[c].l;
   86.45 +    for (int x = glyphs[c].l; x < glyphs[c].r; ++x) {
   86.46 +      int c = getpixel(fontImg, x, y);
   86.47 +      if (c == 2 || c == 3) {
   86.48 +        right = x + 1;
   86.49 +      }
   86.50 +    }
   86.51 +
   86.52 +    // Count transparent, semitransparent, and opaque
   86.53 +    // pixels within row
   86.54 +    for (int x = glyphs[c].l; x < right; ++x) {
   86.55 +      switch (getpixel(fontImg, x, y)) {
   86.56 +      case 1:
   86.57 +        ++xparents;
   86.58 +        break;
   86.59 +      case 2:
   86.60 +        ++xlucents;
   86.61 +        break;
   86.62 +      case 3:
   86.63 +        ++opaques;
   86.64 +        break;
   86.65 +      }
   86.66 +    }
   86.67 +    ++eols;
   86.68 +  }
   86.69 +}
   86.70 +
   86.71 +
   86.72 +void drawChar(int c) {
   86.73 +  int left = glyphs[c].l;
   86.74 +  int top = glyphs[c].t;
   86.75 +  int right = glyphs[c].r;
   86.76 +  int bottom = glyphs[c].b;
   86.77 +  if ((c & 0x1F) == 0) {
   86.78 +    rectfill(screen,
   86.79 +             0, 288, SCREEN_W - 1, 288 + 32 * 4,
   86.80 +             5);
   86.81 +  }
   86.82 +  
   86.83 +  int x = (c & 0x07) * (SCREEN_W / 8);
   86.84 +  int y = 288 + 32 * ((c & 0x18) >> 3);
   86.85 +  
   86.86 +  textprintf_ex(screen, font, x, y + bottom - top - 4, 3, -1,
   86.87 +                "%c=", c);
   86.88 +  stretch_blit(fontImg, screen,
   86.89 +               left, top, right - left, bottom - top,
   86.90 +               x + 16, y, (right - left) * 2, (bottom - top) * 2);
   86.91 +  if ((c & 0x1F) == 0x1F) {
   86.92 +    readkey();
   86.93 +  }
   86.94 +}
   86.95 +
   86.96 +// color 0: border around kernbox
   86.97 +// color 1: background
   86.98 +// color 2: mix bg+fg
   86.99 +// color 3: foreground
  86.100 +void findCharacters(void) {
  86.101 +  int curChar = MIN_GLYPH;
  86.102 +  int x = 0;
  86.103 +  int y = 12;
  86.104 +
  86.105 +  for(y = 0; y < fontImg->h; y += cellHeight) {
  86.106 +    for (x = 0; x < fontImg->w; ++x) {
  86.107 +      // Skip if border pixel
  86.108 +      if (getpixel(fontImg, x, y) != 0) {
  86.109 +      
  86.110 +        // find left and right extents
  86.111 +        int left = x;
  86.112 +        int right;
  86.113 +        for (right = x;
  86.114 +             right < fontImg->w && getpixel(fontImg, right, y) != 0;
  86.115 +             ++right) {
  86.116 +        }
  86.117 +
  86.118 +        // record them
  86.119 +        glyphs[curChar].l = left;
  86.120 +        glyphs[curChar].t = y;
  86.121 +        glyphs[curChar].r = right;
  86.122 +        glyphs[curChar].b = y + cellHeight;
  86.123 +        x = right;
  86.124 +        ++curChar;
  86.125 +      }
  86.126 +    }
  86.127 +  }
  86.128 +
  86.129 +#if 0  
  86.130 +  int maxChar = curChar;
  86.131 +  for (curChar = MIN_GLYPH; curChar < maxChar; ++curChar) {
  86.132 +    collectStats(curChar);
  86.133 +    drawChar(curChar);
  86.134 +  }
  86.135 +#endif
  86.136 +}
  86.137 +
  86.138 +void displayStats(void) {
  86.139 +  rectfill(screen, 0, 288, 255, 479, 5);
  86.140 +  textprintf_ex(screen, font, 4, 298, 3, -1,
  86.141 +                "xparents: %d", xparents);
  86.142 +  textprintf_ex(screen, font, 4, 308, 3, -1,
  86.143 +                "xlucents: %d", xlucents);
  86.144 +  textprintf_ex(screen, font, 4, 318, 3, -1,
  86.145 +                "opaques: %d", opaques);
  86.146 +  textprintf_ex(screen, font, 4, 328, 3, -1,
  86.147 +                "end of lines: %d", eols);
  86.148 +  textprintf_ex(screen, font, 4, 338, 3, -1,
  86.149 +                "total number of bits: %d",
  86.150 +                (xparents + xlucents + opaques + eols + 3) * 2);
  86.151 +}
  86.152 +
  86.153 +/* Details of the font encoding
  86.154 +
  86.155 +Header:
  86.156 +u8 offsetToEncodingTable;
  86.157 +u8 minGlyph;
  86.158 +u8 nGlyphs;
  86.159 +u8 fontHeight;
  86.160 +
  86.161 +First the encoding table (nglyphs/2 bytes):
  86.162 +u16 offsetToGlyphData;
  86.163 +u8 glyphWidth;
  86.164 +u8 reserved;
  86.165 +
  86.166 +Each glyph data element consists of a stream of bytes.
  86.167 +Each byte contains four 2-bit values packed little-endian:
  86.168 +0: Move pen to start of next line
  86.169 +1: Move pen to right
  86.170 +2: Draw pixel using 50% opacity and move pen to right
  86.171 +3: Draw pixel using 100% opacity and move pen to right
  86.172 +The glyph data is done when "Move pen to start of next line"
  86.173 +has run fontHeight times.
  86.174 +
  86.175 +*/
  86.176 +void compressPadByte(void) {
  86.177 +  if (fontdata_shift > 0) {
  86.178 +    fontdata_shift = 0;
  86.179 +    ++fontdata_len;
  86.180 +  }
  86.181 +}
  86.182 +
  86.183 +void compressWriteCode(unsigned int code) {
  86.184 +  fontdata[fontdata_len] |= code << fontdata_shift;
  86.185 +  fontdata_shift += 2;
  86.186 +  if (fontdata_shift >= 8) {
  86.187 +    compressPadByte();
  86.188 +  }
  86.189 +}
  86.190 +
  86.191 +void compressGlyph(unsigned int c) {
  86.192 +  glyphDataOffsets[c] = fontdata_len;
  86.193 +  for (int y = glyphs[c].t; y < glyphs[c].b; ++y) {
  86.194 +
  86.195 +    // Find right side of each row of glyph
  86.196 +    int right = glyphs[c].l;
  86.197 +    for (int x = glyphs[c].l; x < glyphs[c].r; ++x) {
  86.198 +      int c = getpixel(fontImg, x, y);
  86.199 +      if (c == 2 || c == 3) {
  86.200 +        right = x + 1;
  86.201 +      }
  86.202 +    }
  86.203 +
  86.204 +    // Write transparent, semitransparent, and opaque
  86.205 +    // pixels within row
  86.206 +    for (int x = glyphs[c].l; x < right; ++x) {
  86.207 +      int code = getpixel(fontImg, x, y) & 3;
  86.208 +      if (code == 0) {
  86.209 +        code = 1;
  86.210 +      }
  86.211 +      compressWriteCode(code);
  86.212 +    }
  86.213 +    
  86.214 +    // Signal eol
  86.215 +    compressWriteCode(0);
  86.216 +  }
  86.217 +  compressPadByte();
  86.218 +}
  86.219 +
  86.220 +void compressFont(void) {
  86.221 +  unsigned int minGlyph = MIN_GLYPH;
  86.222 +  size_t nGlyphs = N_GLYPHS;
  86.223 +  for (size_t i = minGlyph; i < minGlyph + nGlyphs; ++i) {
  86.224 +    compressGlyph(i);
  86.225 +  }
  86.226 +}
  86.227 +
  86.228 +int writeFont(const char *dstName) {
  86.229 +  FILE *dst = fopen(dstName, "wb");
  86.230 +  unsigned int minGlyph = MIN_GLYPH;
  86.231 +  size_t nGlyphs = N_GLYPHS;
  86.232 +  size_t headerLen = 4;
  86.233 +  size_t glyphDataBase = nGlyphs * 4 + headerLen;
  86.234 +
  86.235 +  if (!dst) {
  86.236 +    return EXIT_FAILURE;
  86.237 +  }
  86.238 +  fputc(4, dst); // offset to encoding table
  86.239 +  fputc(minGlyph, dst);
  86.240 +  fputc(nGlyphs, dst);
  86.241 +  fputc(cellHeight, dst);
  86.242 +  
  86.243 +  for (size_t i = minGlyph; i < minGlyph + nGlyphs; ++i) {
  86.244 +    size_t glyphDataOffset = glyphDataBase + glyphDataOffsets[i];
  86.245 +    fputc(glyphDataOffset & 0xFF, dst);
  86.246 +    fputc((glyphDataOffset >> 8) & 0xFF, dst);
  86.247 +    fputc(glyphs[i].r - glyphs[i].l, dst);
  86.248 +    fputc(0, dst);
  86.249 +  }
  86.250 +  fwrite(fontdata, fontdata_len, 1, dst);
  86.251 +  fclose(dst);
  86.252 +  return 0;
  86.253 +}
  86.254 +
  86.255 +#define WATCHING 0
  86.256 +
  86.257 +int main(int argc, const char *const *argv) {
  86.258 +
  86.259 +  if (argc != 3) {
  86.260 +    fputs("syntax: fontconv fontbitmapname outname\n", stderr);
  86.261 +    return EXIT_FAILURE;
  86.262 +  }
  86.263 +
  86.264 +  allegro_init();
  86.265 +  install_timer();
  86.266 +
  86.267 +  set_color_depth(8);
  86.268 +#if WATCHING
  86.269 +  if (set_gfx_mode(GFX_AUTODETECT_WINDOWED, 256, 480, 0, 0) < 0) {
  86.270 +    allegro_message("Could not open a dual screen sized window.\n");
  86.271 +    return EXIT_FAILURE;
  86.272 +  }
  86.273 +  set_palette_range(fontPalette,
  86.274 +                    0, sizeof(fontPalette)/sizeof(fontPalette[0]) - 1,
  86.275 +                    1);
  86.276 +  clear_bitmap(screen);
  86.277 +  rectfill(screen, 0, 192, 255, 287, 2);
  86.278 +  rectfill(screen, 0, 288, 255, 479, 5);
  86.279 +  install_keyboard();
  86.280 +#endif
  86.281 +  
  86.282 +  fontImg = load_bitmap(argv[1], NULL);
  86.283 +  if (!fontImg) {
  86.284 +    allegro_exit();
  86.285 +    fprintf(stderr, "fontconv could not load %s\n",
  86.286 +            argv[1]);
  86.287 +    return EXIT_FAILURE;
  86.288 +  }
  86.289 +  findCharacters();
  86.290 +#if WATCHING
  86.291 +  blit(fontImg, screen, 0, 0, 0, 0, fontImg->w, fontImg->h);
  86.292 +  readkey();
  86.293 +  displayStats();
  86.294 +  readkey();
  86.295 +#endif
  86.296 +  compressFont();
  86.297 +  return writeFont(argv[2]);
  86.298 +} END_OF_MAIN();
  86.299 +
    87.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    87.2 +++ b/tools/mktables.c	Fri Mar 13 00:39:12 2009 -0700
    87.3 @@ -0,0 +1,88 @@
    87.4 +#include <stdlib.h>
    87.5 +#include <stdio.h>
    87.6 +#include <math.h>
    87.7 +
    87.8 +/*
    87.9 +Pin Eight standard tuning defines middle C as 2093/8 Hz.
   87.10 +It uses equal temperament: a semitone difference between two pitches
   87.11 +is a factor of 2^(1/12) between their frequencies.
   87.12 +Thus, MIDI 0 = 60, or 2093/256.
   87.13 +*/
   87.14 +#define C_IN_OCTAVE(octave) \
   87.15 +  ((double)((long int)2093 << octave) / 256.0)
   87.16 +#define TWELFTH_ROOT_OF_TWO 1.0594630943592952645618252949463
   87.17 +
   87.18 +/*
   87.19 +DS note frequencies for the tone generator at 8 samples per period
   87.20 +are ((-0x1000000 / (n * 8)))
   87.21 +*/
   87.22 +#define FREQ_TO_PSG_PERIOD(freq) \
   87.23 +  ((unsigned short)(-0x1000000 / (freq * 8)))
   87.24 +
   87.25 +  
   87.26 +int writePSG(FILE *fp) {
   87.27 +
   87.28 +  // write header  
   87.29 +  fputs(".section .rodata\n"
   87.30 +        ".balign 4\n"
   87.31 +        ".global midi2psgFreq\n"
   87.32 +        "midi2psgFreq:", fp);
   87.33 +  
   87.34 +  int octave = 0;
   87.35 +  
   87.36 +  for (; octave < 2; ++octave) {
   87.37 +    fprintf(fp, "\n@ unsupported octave %d\n.byte ", octave);
   87.38 +    for (int semitone = 0; semitone < 12; ++semitone) {
   87.39 +      if (semitone) {
   87.40 +        fputs(", ", fp);
   87.41 +      }
   87.42 +      fputs("  0,  0", fp);
   87.43 +    }
   87.44 +  }
   87.45 +  for (; octave < 10; ++octave) {
   87.46 +    double freq = C_IN_OCTAVE(octave);
   87.47 +    fprintf(fp, "\n@ octave %d\n.byte ", octave);
   87.48 +    for (int semitone = 0; semitone < 12; ++semitone) {
   87.49 +      if (semitone) {
   87.50 +        fputs(", ", fp);
   87.51 +        freq *= TWELFTH_ROOT_OF_TWO;
   87.52 +      }
   87.53 +      unsigned int psgFreq = FREQ_TO_PSG_PERIOD(freq);
   87.54 +      printf("midi %2d = %.2f = psg %5d\n",
   87.55 +             octave * 12 + semitone,
   87.56 +             freq,
   87.57 +             psgFreq);
   87.58 +      fprintf(fp, "%3u,%3u",
   87.59 +              psgFreq & 0xFF,
   87.60 +              (psgFreq >> 8) & 0xFF);
   87.61 +    }    
   87.62 +  }
   87.63 +  
   87.64 +  fputs("\n", fp);
   87.65 +  return 0;
   87.66 +}
   87.67 +
   87.68 +/**
   87.69 + * Writes lookup tables used by the ARM7 code.
   87.70 + * Currently, these include the PSG pitch values.
   87.71 + */
   87.72 +int writeARM7(const char *filename) {
   87.73 +  FILE *fp = fopen(filename, "wt");
   87.74 +  if (!fp) {
   87.75 +    return -1;
   87.76 +  }
   87.77 +  if (writePSG(fp) < 0) {
   87.78 +    fclose(fp);
   87.79 +    return -1;
   87.80 +  }
   87.81 +  fclose(fp);
   87.82 +  return 0;
   87.83 +}
   87.84 +
   87.85 +
   87.86 +int main(void) {
   87.87 +  if (writeARM7("obj/ds/lookup_tables7.s") < 0) {
   87.88 +    return EXIT_FAILURE;
   87.89 +  }
   87.90 +  return EXIT_SUCCESS;
   87.91 +}
    88.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    88.2 +++ b/zip.in	Fri Mar 13 00:39:12 2009 -0700
    88.3 @@ -0,0 +1,88 @@
    88.4 +src/debrief.c
    88.5 +src/dsarm7.c
    88.6 +src/dsdebrief.c
    88.7 +src/dsjoy.c
    88.8 +src/dssound7.c
    88.9 +src/dssleep.c
   88.10 +src/explode.c
   88.11 +src/font.bmp
   88.12 +src/fontdraw.c
   88.13 +src/fontdraw_engine.c
   88.14 +src/fontdraw.iwram.c
   88.15 +src/fontdraw.h
   88.16 +src/gba_asm.s
   88.17 +src/gbablk.chr
   88.18 +src/gbaisr.iwram.c
   88.19 +src/gbamenus.c
   88.20 +src/gbamenus.h
   88.21 +src/gbanotefreq.c
   88.22 +src/gbaopt.c
   88.23 +src/gbasound.c
   88.24 +src/gimmicks.c
   88.25 +src/lj.c
   88.26 +src/lj.h
   88.27 +src/ljcontrol.h
   88.28 +src/ljds.c
   88.29 +src/ljds.h
   88.30 +src/ljgba.c
   88.31 +src/ljgba.h
   88.32 +src/ljgbads.inc
   88.33 +src/ljlocale.c
   88.34 +src/ljlocale.h
   88.35 +src/ljmusic.c
   88.36 +src/ljmusic.h
   88.37 +src/ljpath.c
   88.38 +src/ljpath.h
   88.39 +src/ljpc.c
   88.40 +src/ljpc.h
   88.41 +src/ljplay.c
   88.42 +src/ljplay.h
   88.43 +src/ljreplay.c
   88.44 +src/ljreplay.h
   88.45 +src/ljtypes.h
   88.46 +src/ljvorbis.c
   88.47 +src/ljvorbis.h
   88.48 +src/macro.c
   88.49 +src/old_pc_options.c
   88.50 +src/options.c
   88.51 +src/options.h
   88.52 +src/pcdebrief.c
   88.53 +src/pcjoy.c
   88.54 +src/pcjoy.h
   88.55 +src/pcsound.c
   88.56 +src/pin8gba_sound.h
   88.57 +src/random.c
   88.58 +src/scenario.c
   88.59 +src/scenario.h
   88.60 +src/speed.c
   88.61 +src/talkback.h
   88.62 +src/text.chr
   88.63 +src/winicon.rc
   88.64 +src/wktables.c
   88.65 +docs/favicon.ico
   88.66 +docs/ljlogo192.png
   88.67 +docs/titlebarbg.png
   88.68 +docs/ljsnap018.png
   88.69 +docs/ijlo-stz.png
   88.70 +docs/appicon.ico
   88.71 +docs/dsicon.bmp
   88.72 +docs/appicon.xcf
   88.73 +docs/ljhtml.css
   88.74 +docs/Compiling_on_Windows.txt
   88.75 +obj/win32/index.txt
   88.76 +obj/gba/index.txt
   88.77 +obj/ds/index.txt
   88.78 +tools/fontconv.c
   88.79 +tools/mktables.c
   88.80 +makefile
   88.81 +gbamakefile
   88.82 +dsmakefile
   88.83 +COPYING.txt
   88.84 +GPL.txt
   88.85 +zip.in
   88.86 +binzip.in
   88.87 +mkzip.bat
   88.88 +README.html
   88.89 +CHANGES.txt
   88.90 +TODO.txt
   88.91 +BUGS.txt