# HG changeset patch # User paulo@localhost # Date 1236929952 25200 # Node ID c84446dfb3f5e3218beda42f554f5c2ac6daf1f9 initial add diff -r 000000000000 -r c84446dfb3f5 BUGS.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BUGS.txt Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,906 @@ +bug 0001: Dual Marathon gimmick + +The keys control both a platform game (in the top window) and a +tetromino stacking game (in the bottom window). If you die on +either window, you lose. + + +bug 0002: Customizable top out behavior +Parity: Tetris (NES), freepuzzlearena + +A rule can dictate that "top out" occurs when the following happens: +* Overlap or lock out: Entry overlapping blocks, or lock entirely + above ceiling (current behavior) +* Partial lock out: Lock with any block above ceiling +* Garbage out: Entry with any block above ceiling +* Loose block out: Lock overlapping blocks; block can be shifted + out of the way in some cases (like Tetris for NES) + + +bug 0003: Mode 7 style blitter for PC +Platform: PC +Parity: TOD + +Extend blitField(LJView *) to scale and rotate the field as it's +being copied to the screen. + + +bug 0004: Mode 7 style blitter for GBA and DS +Platform: GBA + +Extend blitField(LJView *) to calculate scaling and rotation factors +for each scanline, and extend other functions to start DMA. + + +bug 0005: Port Lockjaw: The Overdose (TOD) +Requires: 0002, 0003, 0004 +Parity: TOD + +TOD used Carbon Engine. I want to migrate away from Carbon Engine. + + +bug 0006: Ridin' Spinners +Requires: 0003, 0004 +SRS and Infinity are turned on. If you abuse lock delay, or you make +a "spin triple" twist, the screen begins to spin, and the music +changes to a cover of "Ridin' Spinners" by Three 6 Mafia. + + +bug 0007: Improve Items gimmick +Requires: 0008 +Votes: Caithness +Parity: Tetris DS + +Give only one item at a time in the early stages. + + +bug 0008: Prerotated next queue +Parity: Tetris DS + +Store the orientation of each piece in the next queue. + + +bug 0009: TDS 4-player worldwide simulation +Parity: Tetris DS + +3/4 of the time you start it, it will play annoying music for +two minutes, give an 86420 error, and quit. The rest of the +time, it will play annoying music for between 15 seconds and +two minutes and then switch to Items gimmick. + + +bug 0010: Colored blocks +Parity: freepuzzlearena + +In active, hold, and next, have each block store its color. + + +bug 0011: H&R Blocks scoring method +Parity: freepuzzlearena + +First spotted in freepuzzlearena Tetanus. Each piece is either +entirely dark or entirely light. A line of all dark or all light +scores as 2 lines. Score for n lines with one piece is n*(n+1)*50. + + +bug 0012: Two physical keys per vkey +Platform: PC + +Allow the player to set more than one key that operates a given, +game function, like StepMania does. + + +bug 0013: Two-player mode +Parity: freepuzzlearena + +Allow two players to play at once, and allow garbage to be sent back +and forth. + + +bug 0014: Store and display assertion text + +Lockjaw Engine has ljassert(). If an assertion fails, have some way +for front-ends to retrieve the text. + + +bug 0015: Rotation system should be array of structs + +A single struct should describe all aspects of a rotation system. +The descriptions in wktables.c should be an array of structs, not +struct of arrays. While you're at it, extend the kick tables +from 5 to 8 positions. + + +bug 0016: Rotation system editor +Platform: PC +Requires: 0015 +Votes: Needle, DIGITAL, (RA) Red Star, jujube, fnord + +Before developing Lockjaw Engine, I described Wall Kick Explorer, an +editor for rotation systems. Make this. +* initial shift and orientation of each piece +* kick tables for +90deg and +270deg + + +0.44 inverts rotation systems into a format that a loader might be +able to produce. + + +bug 0017: Adjustable window size +Votes: Bloodstar +Resolution: Fixed in 0.42 + +The skin should control the screen resolution or the size of the +window. This would make camstudio recording on older PCs feasible. + + +bug 0018: Store keypress count in .ljm + +Replays should store the number of keypresses, so that Baboo! result +screens are correct. + + +bug 0019: Start recording .ljm only during ARE + +It would probably simplify the code to start a replay only during +entry delay. This would also allow playback to become slightly +more resilient to game engine changes, as the viewer would at least +be able to view block placements. + + +bug 0020: Option for fractional row kick +Parity: Tetris DX + +Right now, a floor kick will move a piece by the minimal amount +needed to place a block in the correct row. This means downward +kicks move to the top of the row, and upward kicks move to the bottom +and immediately start lock delay. Other games don't do this. +To simulate Tetris DX properly, we need a new option: + +Fractional floor kick + Unchanged +> Minimal move (current behavior) + Move to top (Tetris DX behavior) + + +bug 0021: Double buffer entire screen +Platform: PC +Votes: 4matsy + +On some video cards, the score flashes. Double buffering the entire +screen, not just the well, would fix this. + + +bug 0022: Automatic exit on Vista +Platform: PC +Resolution: Fixed in 0.42 + +One machine running Windows Vista had Esc perform an immediate exit +rather than a pause > exit. + + +Might it have been caused by vsync() returning immediately? +If so, 0.42 fixes that. + + +bug 0023: Draw internal preview without clipping to well edge +Requires: 0020 +Votes: 4matsy + +Internal preview should extend outside the well if the piece is close +enough to the wall. + + +bug 0024: Improve garbage options +Votes: Bloodstar, Lardarse +Parity: Tetris (NES) + +Add option for garbage density, especially for B-type + +Garbage density + Half + Two holes +> One hole + +Add option for garbage randomness (1-100) + + +bug 0025: Internet play +Requires: 0013 +Votes: Bloodstar +Parity: Tetris DS + +Allow sending garbage over the Internet. + + +bug 0026: FreeType font support +Platform: PC +Votes: Bloodstar + +Use TrueType fonts instead of bitmapped fonts. + + +bug 0027: AVI export +Platform: PC +Votes: Bloodstar +Parity: Tetris (NES) in emulation + +FCE Ultra and Nestopia can convert a replay to an AVI. +Lockjaw should too. + + +bug 0028: Game_Music_Emu music support +Platform: PC +Votes: Bloodstar +Parity: Tetris (NES) in emulation + +I'd like to play nsf/sid/spc music without having to +convert it to .ogg first. + + +bug 0029: Height-sensitive music +Platform: PC +Votes: Bloodstar +Parity: Tetris (NES), Tetris (GB) + +Tetris DX changes the music when the stack surpasses a certain +height. Lockjaw should too. + + +bug 0030: Music on GBA +Platform: GBA +Votes: Bloodstar +Parity: TOD + +GBA has sound effects. I'd like some sort of music too. Pimpmobile? + + +bug 0031: Debrief on handhelds +Platform: GBA, DS +Votes: Bloodstar, zzo38computer +Resolution: Fixed in 0.43 + +PC has a result screen. I want this on GBA and DS + + +Fixed in 0.42 for DS. To get this onto the GBA, we'd have to +eliminate three lines of text from the result screen. + + +kesiev gave me a few ideas of how I could slim it down. Fixing. + + +bug 0032: Baboo! should force infinite lock delay +Votes: caffeine + +right now, Baboo! is defined by use of 0g. it should be +infinite lock delay instead. + + +Why? It would add an extra keystroke to Zangi-moves. + + +bug 0033: Move all skin-related files to a folder +Platform: PC +Votes: caffeine +Resolution: Fixed in 0.43 + +Skin-related files should be kept together in the folder "skin". + + +bug 0034: Configure console buttons +Platform: PC +Votes: caffeine, 4matsy +Parity: StepMania + +Escape to pause, [ to record, ] to play should be configurable. + + +4matsy said that MenuUp, MenuDown, MenuLeft, MenuRight, +MenuStart, and MenuBack also being configurable might help. + + +bug 0035: skip ready go and game over animations +Votes: caffeine + +I like to kill and restart games when I make one little mistake, +and startingAnimation and gameOverAnimation slow me down. +I want to skip them, even though each is less than 2 seconds. + + +bug 0036: Skip to mistakes +Platform: PC +Votes: caffeine + +While watching a replay, I want to skip to a piece that increases +the number of holes or whose active time is greater than three +times the average. + + +bug 0037: Hide active times in results +Votes: caffeine + +The Quadra-style "active time" statistics confuse me. I want to +see stats based on wall time only. + + +bug 0038: Result time unit +Votes: caffeine + +I'd like to control whether statistics are expressed per minute +or per second. + + +Why? DDR is measured in beats per minute, and gasoline engine speed +is measured in revolutions per minute. + + +bug 0039: More garbage options +Votes: Caithness + +I want more realistic garbage. + + +In what way? + + +bug 0040: Custom speed curves +Votes: Caithness, caffeine, Ezzelin, zzo38computer, DIGITAL, mushroom + +Let the user modify speed curves: for example, user should be +able to make rhythm speed up slower + + +0.45 introduces a new speed curve format + + +bug 0041: Saved rule sets +Votes: Needle, kotetsu213, jujube, DIGITAL, Rich Nagel, Deets +Votes: Kukuunen +Parity: Tetris Worlds, Heboris + +Let the user create sets of options that override the user's +current options, and replace the gimmick selection screen +with preset selection. + + +0.42 adds scenario support for the PC. A scenario editor and +scenario support for handhelds will have to wait. + + +bug 0042: Release Lockjaw XLII +Requires: 0041 + +Lockjaw 0.42 won't be released until noticeable progress is made +on these issues. + + +As of Monday, January 28, 2008, scenario support is in, so Lockjaw +will be released the morning before the Super Bowl. + + +bug 0043: More rotation systems +Requires: 0016 +Votes: herc + +Once the rotation system editor is in place, create these: +* Flat-up SRS + + +bug 0044: Vanish zone +Platform: PC +Votes: DIGITAL + +The skin should allow showing the vanish zone. + + +bug 0045: Handheld skin support +Platform: GBA +Votes: Dood77, matt_hatter83 +Parity: TOD + +GBA and DS should have skins too. + + +bug 0046: Pussy 20G +Votes: herc, DIGITAL + +Option for moving a piece up 1 cell when shifting it, +counting it as a floor kick. + + +bug 0047: Pentominoes +Votes: Dood77, jujube, colour_thief, Lardarse +Parity: Bombliss + +Add the 5-block pieces. + + +bug 0048: Bastet randomizer +Votes: zzo38computer +Parity: Abandoned Blocks + +In bastet, AI plays each possible next piece in each +possible position and gives one of the worst 3. + + +bug 0049: lj-contrib as skins +Platform: PC +Votes: Rich Nagel + +lj-contrib was written before .skin files were done. + + +bug 0050: Next above shadow (faint) +Platform: PC +Votes: cdsboy + +Draw next above shadow, but draw it semitransparent. + + +bug 0051: Separate keys for initial actions +Votes: DIGITAL + +I like initial actions, but not ARE. Can I get separate +keys to perform initial actions on the next piece before +this one locks down? + + +bug 0052: Speed graph in debrief +Platform: PC +Votes: Cubicz +Parity: StepMania + +Screen 1 is results. Screen 2 is settings. +Why not put a speed graph on screen 3? + + +bug 0053: High score table +Requires: 0041 +Votes: Cubicz, Ezzelin, Rich Nagel, Kuukunen + +For each ruleset, keep the highest scores achieved on that +ruleset. + + +bug 0054: Disable ljconn +Platform: PC +Votes: kotetsu213, Needle +Parity: Tetris The Grand Master, The New Tetris + +The skin should be able to turn off ljconn for falling pieces +(as opposed to blocks in the stack). That would improve +accuracy of TNT and TGM simulation. + + +bug 0055: Skin control for sound effects +Platform: PC +Votes: Cubicz, Needle +Parity: Lumines + +Allow skins to specify a .dat file or set of .wav files used +for sound effects. + + +bug 0056: Bleep gimmick +Votes: Lardarse + +Player must clear row 1, 2, 1, 3, 1, 4, 1, 5, 1, ..., 20, 1. + + +bug 0057: Bravo +Votes: Lardarse +Parity: Luminesweeper + +Several other falling block games display "BRAVO!" or another similar +message when there are no blocks left in the well after a line clear, +and award lots of bonus points. Detect and score for this situation. + + +bug 0058: Vs. Elite garbage +Votes: PetitPrince + +you get 7 garbages after random(1,7) seconds, then random(3,5) +garbage every random (5,20) seconds + + +bug 0059: Wall kick control for IRS +Votes: jagorochi +Parity: TGM + +In TGM, initial rotation does not perform kicks. +Change the IRS option: + +Initial rotation + Off: do not IRS (current behavior) + On, no kick: if collision, fail +> On: if collision, try wall kick (current behavior) + + +0.43 disallows IRS kick entirely, changing the current behavior +from "On" to "On, no kick", by adding a new argument to +doRotateLeft and doRotateRight. The change to this option (search +for !isFirstFrame) could be used to implement this option. + + +bug 0060: Menu background +Platform: PC +Votes: lvankeulen, Kuukunen +Parity: Tetris (NES) + +The skin should specify a background image to be drawn behind +the menu text. + + +bug 0061: Frame stepping +Platform: PC +Votes: jagorochi +Parity: Tetris (NES) in emulation + +To check the event loop for off-by-one errors that affect +accuracy against other games, it would be useful to be able +to step the game engine forward by one frame at a time. + + +bug 0062: Replay stepping by piece +Platform: PC +Votes: Lardarse + +Pause demo playback (with well still visible) with hold button; +advance to next lock with rotate button + + +bug 0063: Option for goal +Votes: Lardarse, caffeine, Rosti LFC, fnord, M.Bison + +Separate goal and countdown code out of gimmicks into an option, +controlled by LJField::goalType. Goals include none, lines, +level, score, time, and keys, which activate comparisons against +goalCount. This would obsolete many gimmicks. + + +bug 0064: Fill in missing sound effects on handhelds +Platform: GBA, DS +Votes: Lardarse + +These sounds are missing: +GBA: Ready, go, countdown +DS: Ready, go, lose, distinct countdown + + +0.42 adds "lose" on DS + + +bug 0065: Bombliss game mode +Requires: 0010 +Votes: Joshua +Parity: Bombliss + +When game mode is set to Bombliss, line clears no longer remove +lines from the field. Instead, one block of each piece is +colored differently and explodes, removing blocks, when in a +line clear. + + +bug 0066: Dr. Mario clone +Requires: 0010 +Votes: Lardarse +Parity: freepuzzlearena + +You made Vitamins. Can you make domino rotation, color matching, +and floating garbage with fast DAS, IRS, and 20G? + + +bug 0067: Puyo clone +Requires: 0010 +Votes: Lardarse +Parity: freepuzzlearena + +You made a puyo clone in FPA. Can you port it to LJ? + + +bug 0068: Preload hold piece +Votes: Lardarse + +Option to load a specific piece (such as I or T) into the hold box +at game start. + + +bug 0069: Bag with good start +Votes: Rosti LFC +Parity: Tetris The Grand Master ACE + +Randomizer should always give a "preferred piece" (e.g. I, J, L, T) +first, like history does. + + +bug 0070: Small skins shouldn't hide the score +Votes: Rich Nagel +Platform: PC + +Make wiki.skin (and other small-blkW skins) not hide the score + + +bug 0071: Preview at left +Votes: DIGITAL +Platform: PC + +Skin should specify that the vertical preview ("Next at right") +can be moved to the left side of the playfield. + + +bug 0072: Volume for sfx +Votes: DIGITAL +Platform: PC + +Skin should specify volume for sound effects, as it does for music. + + +bug 0073: Make PC use GBA options +Votes: jujube +Platform: PC +Resolution: Fixed in 0.43 + +Move non-GBA/DS specific code from gbaopt.c to options.c, +then take out everything in old_pc_options.c that isn't +load, save, or LJPCView related + + +0.42 takes some steps in this direction. + + +bug 0074: Macro editor +Votes: deepdorp + +Make the actions performed by the macro vkeys editable. + + +bug 0075: Pattern randomizer +Votes: Rich Nagel + +Allow the player to define a piece sequence, such as the Sega +power-on pattern, and deal that. It could replace iCheat(tm). + + +bug 0076: Skin should vary per section +Votes: Needle, Rich Nagel, DIGITAL +Parity: freepuzzlearena, Luminesweeper + +For speed curves that have sections, it should be possible for a +skin to specify a background image and music for each section. + + +Option to load all skin files before the game starts, so as not to +make an unpredictable ARE at the start of a section. + + +bug 0077: Define conditions that trigger initial drop +Votes: Needle +Parity: Tetris The Absolute The Grand Master 2 PLUS + +In TAP, initial hard drop and initial soft drop don't get turned on +in Master 900+ or in Death 0+. Find an appropriate rule for whether +to do initial hard drop, and document it. + + +bug 0078: Set random seed +Votes: Rich Nagel, colour_thief, Lardarse +Parity: FreeCell + +For competition purposes, it should be possible to seed the +randomizer to a constant value. Note that this differs from the +"power on pattern" proposal of bug 0075. + + +bug 0079: Customizable speedometer sample window +Votes: matt_hatter83 + +The speedometer uses a sliding window of 10 pieces. Can one make it +shorter in options? + + +bug 0080: Portrait monitor support +Votes: PetitPrince + +Skin setting to rotate the entire playfield by 90 degrees. + + +bug 0081: DTET rotation system +Requires: 0015 +Votes: cdsboy, caffeine, fnord +Parity: DTET + +Implement the rotation system of DTET, as described on the wiki. +This includes a third set of wall kick tables for each piece in each +rotation system for +180 (in addition to existing +90 and +270). + + +bug 0082: Paged options on PC +Votes: Cubicz +Resolution: Fixed in 0.43 + +Break the PC options menu into pages like it is on the handhelds. + + +bug 0083: Undo +Votes: Kuukunen + +Press a button to take back one drop. Useful for training. + + +bug 0084: Manual entry delay +Votes: Kuukunen + +Pause after each piece with game displayed. Press a button to +resume. Useful for training. + + +bug 0085: Forward progress lock delay reset +Votes: zaphod77 + +Keep track of the lowest space that this piece has fallen to. +A floor kick should never reset lock delay, and only a net +downward movement below where it was when the lock delay was +last reset should reset lock delay. + + +bug 0086: Carbon Engine style lock delay reset +Parity: TOD + +Carbon Engine implemented lock delay as follows: When a piece +rotates or shifts from landed to falling, recharge one row's +worth of lock delay for each row that the piece moves down. + +To implement this in Lockjaw Engine, move the piece down by +(lockDelay - stateTime). If the piece is less than +(lockDelay - stateTime) * gravity above the shadow, only add +(y - shadowY) / gravity to stateTime. + + +bug 0087: Blackjack randomizer framework +Votes: Lardarse + +Lardarse and tepples described a language for describing randomizers +on tetrisconcept.com's wiki. It is called Blackjack. + + +bug 0088: Option for piece color +Votes: Lardarse +Requires: 0010 + +Once we have colors per block, add an option for piece colors, +which might replace the ljconnSRS vs. ljconnSega divide. +Skins would always be in Guideline order. + +> Set by rotation system (current behavior) + SRS + Sega + Dualism (formerly H&R) + Shuffle (randomize at game start) + Super Shuffle (randomize every piece) + + +bug 0089: Long debrief lines are clipped +Requires: 0031 +Votes: Lardarse, kesiev +Resolution: Fixed in 0.43 + +The DS doesn't have a small font, so any debrief line longer than +about 48 characters will get clipped. But in order to make this +line shorter, I need to move things around anyway. + + +bug 0089: Merge preliminary GNU/Linux support +Requires: 0091 +Votes: kesiev +Resolution: Fixed in 0.43 + +kesiev wrote a patch to let Lockjaw work with separate read-only +and read-write folders, as is often the case in GNU/Linux. +Merge these changes. + + +kesiev's patch adds a function called 'turnslash' that turns '\' into +'/' in all path names. In fact, I can use this on all platforms: +Mac and Linux use '/' as the native path separator, and the Windows +API accepts it even if some legacy command-line programs do not. + +LJVorbis_close: I must have missed that line in the ov_clear +documentation. + + +bug 0090: Process IRS before checking for block out +Votes: Kitaru +Parity: TGM series +Resolution: Fixed in 0.43 + +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 +the end of the first frame (LJSND_SPAWN|LJSND_HOLD) of FALLING_PIECE. + + +bug 0091: Read-only vs. read-write directories +Votes: Ezzelin, kesiev +Resolution: Fixed in 0.43 + +Lockjaw for PC currently runs as a "portable application". But it +should support being installed in the same way as any other program +for Windows or Linux. If an empty file called "installed" is in the +same folder as argv[0], switch to installed mode. + +Installed mode ignores the working directory when loading .dat, +.skin, .ini, etc. Instead, it loads from the save data folder +($HOME/.lockjaw on Linux; %APPDATA%/Pin Eight/Lockjaw on Windows) +and then from the folder that contains argv[0]. + +The program would usually be installed to /opt/Lockjaw under Linux +or %ProgramFiles%/Lockjaw under Windows. + + +bug 0092: Option to limit IRS to 90 degrees +Votes: jujube +Parity: TGM series + +Rotation limit + 90 degrees +> Unlimited (current behavior) + +And make Master and Death use 90 degrees. + + +bug 0093: Option to disable instant shifting +Parity: TGM series + +Add an option only to scenarios that limits shifting to 1G, +disallowing cheating with far left, far right, DAS=Instant. + + +bug 0094: World reverse +Votes: jujube +Parity: Ti + +Option in Game Keys to reverse rotation directions when playing +rotation systems that start the T flat-down (SRS, TOD M4). + + +bug 0095: Move mouse pointer out of the way +Votes: jujube +Resolution: Fixed in 0.43 + +Some players navigate Lockjaw for PC entirely with the keyboard. +For these people, the mouse gets in the way after it has been used +in Replay or Skin (or, soon, Options and Game Keys). Drop it to +the bottom of the window when the player goes to a screen that +doesn't use the mouse. + + +bug 0096: Allow options to "underride" skin settings +Votes: Rich Nagel + +Allow the user to change shift sound scale, next piece position, +playfield position, as it was in 0.41. The game would use the +skin setting, or the user setting only if the skin does not specify. + + +bug 0097: Package as Windows installer +Requires: 0091 + +Package the Windows executable as an installer that automatically +puts itself in %ProgramFiles%/Lockjaw. + + +bug 0098: Reactive speed curve and scoring +URL: http://www.jenovachen.com/flowingames/flowtheory.htm +Votes: herc + +Start with zero speed. Track the player's speed using the existing +speedometer code. Then once the player has dropped a few pieces, +start scaling up the delays and such so that entry + (20/gravity) + +lock delay equals the average time that the player took to place +a piece. Then multiply all line clear scores by the current speed. + + +bug 0100: Import old bugs +Parity: bugzilla.mozilla.org + +Bring some order to the old 'todo.txt' enhancement list. + diff -r 000000000000 -r c84446dfb3f5 CHANGES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CHANGES.txt Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,1067 @@ +0.46a (2008-11-09) + * Preview at top is copied correctly from the back buffer + (0.45a regression, reported by rednefed). + +_____________________________________________________________________ +0.46 (2008-11-08) + * Tracks number of 4x4 squares that the player forms + (TOD parity; requested by Ghett0). + * Added T-Party randomizer (requested by Lardarse). + * Default lock delay for Zero set to 40 frames, even if it is + played first (0.45 regression, reported by rck). + * "[TC]" removed from scenario names (requested by caffeine). + * PC: Uses fourCCs for option values in lj.ini. + * PC: Uses double buffer for entire background (bug 0021). + * PC: Can disable sound effects by deleting or renaming sound.dat + (requested by moxie101). + * PC: More of the code is 2-player-clean. + +_____________________________________________________________________ +0.45 (2008-04-20) + * New unified speed curve system: less code, more tables, for + future loading from a file (bug 0040). + * All speed curves except Rhythm and Rhythm Zero have been ported + to the new system. Notably, Master has been rewritten based on + the tables from the wiki, and Exponential uses sections. The + lock delay speeds up as brutally after 600 pieces as always. + * Added kludge in the basic makefile that allows building on *BSD + and GNU/Linux: 'make linux' (patch by kesiev). + * ljpath: Fixed a couple misspellings in the non-Windows code path + (reported by kesiev). + * ljpath: Supports libfat when running on Nintendo DS. + * A few descriptions of options have been shortened to fit into + the new GBA/DS look (reported by Bloodstar; patch by caffeine). + * Writes result and options text to standard output as well as to + lj-scores.txt. This allows you to do 'lj | postprocessor' even + if you can't do 'tail -f lj-scores.txt' (requested by kesiev). + * DS: Writes result and options to /data/lockjaw/lj-scores.txt. + +_____________________________________________________________________ +0.44 (2008-02-20) + * Computes the preview and hold offset of all pieces (was + wkNextMove in <= 0.43) from the entry position of the I + tetromino (was wkSpawnMove in <= 0.43). + * Kick tables have been inverted to a single struct per rotation + system, for future loading from a file (bug 0016). + * SKIP_IF system for handling J/L/T exceptions in Arika is replaced + with IF_NOT_CENTER: wall kicks are allowed if the free-space + orientation overlaps any blocks other than in the center column. + * Options DAS doesn't repeat too fast, even on those systems where + vsync() is a no-op (0.43 regression, reported by caffeine). + This has the same cause as 0022: Windows Vista ignores vsync(). + * Options DAS doesn't continue across page boundaries. + * GBA/DS: Options has new look. + * DS: Reads the touch screen and the X and Y buttons. Right now, + touch isn't mapped to anything, but X and Y are Far Right and + Far Left. + +_____________________________________________________________________ +0.43a (2008-02-11) + * Arika modified to use SKIP_IF3 after (not before) the basic + position. This restores ability to rotate J, L, and T with + kicks turned off, such as in IRS (reported by Lion). + * PC: Uses shfolder.dll so that installability works even on + Windows 98 (0.43 regression, reported by Rich Nagel). + * PC: Key bindings to J, M, and Z are correctly saved (0.43 + regression, reported by DIGITAL). + * PC: In Game Keys, the names of Up and Down key bindings are + clearer (requested by 4matsy). + +_____________________________________________________________________ +0.43 (2008-02-10) + * Player can initial-rotate out of block out (bug 0090). + * Initial rotation doesn't use wall kick (bug 0059). + * Names for values in Drop scoring and Shadow fixed + (0.42 regression, reported by Lardarse). + * Number of line clears in DS debrief should be cut off at the + right side less often (bug 0089; fix based on a patch by kesiev). + * Fixed line clear gravity in cascade preset (reported by 4matsy). + * Fixed Fibonacci scoring with more than 8 lines per piece + (reported by 4matsy). + * Debrief is more compact, allowing GBA to use debrief (bug 0031; + fix based on a patch by kesiev). + * Debrief: Zigzag secret grade requires the hole in the top row to + be covered (reported by Kitaru). + * PC: If installed.txt is in the same folder as the program, + ignores the current directory and stores all writable files + to the user's application data folder (bug 0091). + * PC: When loading skins, searches for files in the folder with + the .skin file instead of the current directory (bug 0033). + * PC: Replay sets up playback gimmick (0.42 regression, + reported by DIGITAL). + * PC: LJVorbis doesn't double-fclose the .ogg file + (reported by kesiev). + * PC: Mouse drops to bottom after skin or replay selection + (requested by jujube). + * PC: Options is separated into pages, like on GBA/DS (bug 0082). + This frees up room for larger type (requested by jujube). + +_____________________________________________________________________ +0.42 (2008-02-03) aka LOCKJAW XLII + * Docs: Most of TODO.txt has been converted to a numbered list + of bugs (bug 0100). + * An off-by-1/65536 error in enter below ceiling was fixed. + This means it doesn't give one extra row of room to slide on + at the top of the well (reported by Caithness and Kukuunen). + * In case of a block out, draws the offending piece in the well + before entering game over animation. + * Extra 5-frame cascade delay removed. + * With Lockdown set to Step reset, lock delay resets on the + first floor kick. + * Tengen rotation system tries kicking one space to the left + (requested by zaphod77). + * Options' names and values are looked up from fourCCs for future + localizability. + * Options: Names of speeds in fractions of 1G are computed. + * Options skips unpacking options with a value of 255. + This allows multiple pref structures to be unpacked on top + of each other, and each overrides the ones before it. + * Debrief displays options on a second page. + * Debrief correctly displays soft drop speed as a fraction (0.41 + regression, reported by Lardarse). + * Debrief: PC specific parts moved to separate source code file. + * Debrief uses a lower-level function to format ISO 8601 date + and time so as not to bring in the floating-point library's + space overhead on platforms without an FPU. + * Debrief plays a sound when turning the page. + * PC: Delay for holding Esc to quit no longer depends on + display refresh rate. Instead, it uses the global 60 Hz + timer (reported by Sara). + * PC: Draws speedometer even when preview is at right, + as long as there is room, that is, 3 or fewer next pieces + (requested by Cubicz). + * PC: Gimmick choice moved to Options to match GBA/DS. + * PC: Select scenario before starting game (bug 0041). + * PC options uses standardized GBA names (OPTIONS_*) instead of + PC names (LJPREFS_*). + * PC options Shift sound scale, Side, and Next position moved to + skin. + * PC options sets initial options using initOptions() from GBA/DS. + Incidentally, this restores the initial randomizer to bag + (0.41 regression and may make similar regressions less likely. + * PC options uses unpackOptions() from GBA/DS. + * PC options sound is also routed through the cross-platform + sound manager. + * PC: lj.ini uses fourCCs as names for easier saving and loading. + * PC: Switching between windowed and full screen in Options + no longer needs the user to restart the program. + * PC: LJVorbis doesn't crash if polled while the music is stopped. + This can happen when cross-platform code plays sound effects + during debrief. + * PC: Skin controls window size, or full screen resolution + (bug 0017). + * PC/DS: Displays a description of some option values in addition + to descriptions of options themselves. + * GBA/DS: Options disabled on the PC are disabled here. + * GBA: Menus are faster now that part of the text engine has been + moved to fast RAM. + * DS: Plays sound for game over (bug 0064). + * DS: Options displayed on touch screen for future touch operation. + * DS: Displays debrief after game ends (bug 0031). + +_____________________________________________________________________ +0.41 (2007-12-18) + * Piece set and randomizer are decoupled (requested by Lardarse + and Dood77). + * New piece set: iCheat(tm) deals only I tetrominoes, like an + infamous cheat code for some other game (requested by DIGITAL). + * New garbage style "Preload zigzag", which preloads a zigzag + pattern of holes. + * At debrief time, if at least two rows of the zigzag pattern + or at least four rows of a rectum (1-block-wide opening) are + formed, shows a secret grade based on the number of pattern + rows instead of the number of blocks left in the playfield. + * Debrief options section sorted by position in options menu. + * Debrief allows for multiple pages of reports. + +_____________________________________________________________________ +0.40a (2007-11-30) + * Does not attempt to create a ghost piece before the first + falling piece. This may fix a crash on some systems if the + first game played during a session has deep drop turned on + (reported by Rich Nagel on Windows 98 Second Edition). + * Deep drop ghost piece code stops checking at the ceiling. + +_____________________________________________________________________ +0.40 (2007-11-27) + * Split out speed curves and randomizers to separate files called + speed.c and random.c. + * Adjusted master delays to be closer to TAP; 600-999 no longer + overlap Death 000-399. + * Displays short description of each option (requested by AXYPB). + * Drill gimmick is back, with the new goal of clearing the bottom + row (requested by DIGITAL). + * Begin to add code for explosive line clear gimmick; not yet + integrated (requested by Joshua). + * Debrief shows number of floor kicks. + * Bottom blocks is called Deep drop to match recent Multiblocks. + * PC: Close box works in Options and Game Keys. + * PC: Does not redraw title screen if close box is clicked. + * PC: If skin sets transparentPF=1, hotlines and line clear + animations are drawn transparent (requested by Rich Nagel). + * GBA/DS: Can quit game from pause screen (requested by AXYPB). + * GBA/DS: Copyright notice and options screen drawn with + proportional font for readability. + * GBA/DS: Menu uses the same sound manager that the game play + uses, not the platform-specific sound manager. + * GBA: Sound for end of game (requested by Lardarse). + * DS: Closing the lid pauses the game and turns off the screens + (requested by AXYPB). + * DS: Unavailable hold piece is really grayed out. + * DS: Real music and sound effects engine (requested by + bob_fossil). + +_____________________________________________________________________ +0.39 (2007-10-24) + * Does not double line clear delay (unknown regression). + * Source code includes new files options.h and old_pc_options.c + which were mistakenly left out of 0.38 (reported by tr3). + * Draws "hotline" symbol on lines being cleared (requested by + Rosti LFC and kiwibonga). + * Debrief no longer counts a zero line T-spin as a home run + (0.28 regression; reported by Lardarse). + * New randomizer 7+1-piece Bag adds one randomly selected + tetromino to each bag (requested by colour_thief). + * Refactored speed curve state information for pseudo-OO + refactoring. + * New option: Bottom blocks allows the falling piece to fall past + blocks into an appropriately shaped hole (requested by mar). + * Added bottom blocks and well size to debrief. + * New gluing options Sticky and Sticky by color. + * PC: Skin specifies the loop point and whether the music starts + before or after the "Ready, Go" animation. + * PC: Skin specifies music volume (requested by DIGITAL). + * PC: Fixed off-by-one in sideways delay (reported by Lardarse). + * PC: Fields with blkH > 24, especially blkH == 48 and Well height + 10, are more likely to fit on screen (reported by Needle). + +_____________________________________________________________________ +0.38 (2007-09-11) + * Plays a .ljm specified at the command line (requested by + Rich Nagel). + * More refactoring for multiplayer. + * Added wall kick opcode SKIP_IF3, similar to SKIP_IF except it + also checks the two cells above the specified cell. + * L, J, and T in Arika use SKIP_IF3 to check the entire center + column when rotating to vertical (requested by edo). + * Moves new tetrominoes to be between the walls (fixing Tengen in + width 4) and, if "Enter above playfield" is off, down far enough + so that they're within the playfield (requested by Caithness). + * New rotation system: Climbing, based on Tetris DX. (No, you + can't wall-climb yet because the sub-block behavior differs.) + * Option "4x4 squares" is called "Gluing". Other gluing options + are planned. + * In Square gluing, formation of a square adds a delay + equal to line clear delay. + * PC: Refactored to share some of the GBA/DS options logic. + * PC: Moved most sound effect code into pcsound.c. + * PC: Sound for 4x4 square formation (cribbed from TOD). + * PC: Reloads the default skin's configuration before loading + the skin, so that skins always inherit from the default skin + and not the last chosen skin (requested by Rich Nagel). + * GBA: Uses duration as priority for square wave sound effects, + which should prevent the section up sound from getting + interrupted as often. + * GBA/DS: Draws real-time speedometer. + +_____________________________________________________________________ +0.37 (2007-07-09) + * Requires "Up" and "Alt firm drop" to be pressed again when + lock delay is turned off or less than DAS delay + (requested by reivilo). + * Keypress count is reset to 0 even in "Play .ljm" (reported by + jujube). + * Playing a broken .ljm from the gimmick menu gives an informative + error message instead of crashing (0.32a regression; reported by + Rosti LFC). + * Rhythm section-up logic waits for 64 beats at L bpm, not L+1 bpm. + * Fixed a potential buffer overflow in shuffleColumns (the "banana" + item), brought on by the expansion of the playfield to 12 columns + (0.33 regression). + * Better comments in source code about wall kick entry macros + WK(x, y), WKX(wk), WKY(wk) in lj.h (requested by Lardarse). + * PC: Reinstate Bach music while investing a trademark claim. + * PC: Separate music for Rhythm speed curves. + * PC: Does not use stretch blitter when drawing the blocks of + sprite pieces at full size (e.g. falling piece). + * PC: blitField() blits adjacent rows in one call, which is + faster on slow machines (requested by Matthew). + * PC: Size of hold piece respects blkW and blkH. + * README describes build instructions for GBA and DS. + * For user convenience, the Allegro DLL is included with the + executable, and the source code is in a separate archive. + +_____________________________________________________________________ +0.36 (2007-05-21) + * Allows DAS delay up to 400 ms (requested by Rich Nagel). + * Option for entry within or above ceiling (requested by DIGITAL + and Cubicz). + * Option for hold piece behavior (requested by Lardarse). + * PC: User can change individual game keys, like in StepMania + (requested by DIGITAL and Rosti LFC). + * PC: Minor changes to included background music. + * PC: Option for playing a scale during sideways motion + (requested by herc). + * PC: Selecting a skin saves options (reported by Rich Nagel). + * PC: When loading saved options, treats 16 ms DAS delay as valid + (reported by jujube). + * PC: Skin setting for a transparent background (requested by + cdsboy and Bloodstar). + * PC: Demo recording uses "Rec" and "Play" in addition to icons. + * PC: Reallocates back buffer after each skin switch, avoiding + problems related to changes in blkW and blkH (reported by + Rich Nagel). + * GBA/DS: Fixed line clear options (0.34 regression). + * GBA/DS: Press Start after lose, rather than timing out to options + (requested by Webby). + * "Default" means not to pay back a loan. Manual uses "initial" + or "preset" instead (requested by Bruce Tognazzini). + +_____________________________________________________________________ +0.35 (2007-04-25) + * New speed curve Game Boy Heart. + * New speed curve Death 300+, which starts three sections into + Death just like cgwg's cheat for TAP (requested by Amnesia). + * New randomizer 10-piece Memoryless. + * First garbage line takes into account well width (reported by + Lardarse). + * Displays section number for NES, Game Boy, and Game Boy Heart + speed curves (requested by Rich Nagel). + * Debrief displays Quadra-style pieces per active minute + (requested by Cubicz). + * Debrief calls pieces "pieces" instead of "tetrominoes" in + 10-piece randomizers because I2, I3, L3 are not tetrominoes. + * PC: Gimmick and level are displayed in skin foreground color + (missed in 0.30; reported by Rich Nagel). + * PC: Skin file name suffix is now .skin not .ini, so that skin.ini + cannot be confused with lj.ini. The default skin is called + default.skin not skin.ini. + * PC: Title screen has option to select skin (requested by + cdsboy). + * PC: Block size is no longer hard-coded at 24x24 pixels. + The new .skin commands blkW= and blkH= control their size + (requested by Bloodstar). + * PC: Source code comes with new installation guide written by + Lardarse in "docs/Compiling_on_Windows.txt". + +_____________________________________________________________________ +0.34a (2007-03-27) + * PC: Fixed hidden level in Next at right mode (0.33 regression). + * DS: Fixed hidden sprites due to bug in devkitARM R20 libnds + (0.34 regression). + * DS: New touch screen code should eliminate erroneous low notes + played upon pen-up. + +_____________________________________________________________________ +0.34 (2007-03-23) + * Fixed Move to Back piece set (0.14 regression: dang!). + * Separate options for number of previewed pieces above shadow and + outside field (requested by kiwibonga, caffeine, and Cubicz). + * Option to disable IRS (requested by DIGITAL). + * Garbage gimmicks (vs., drill 40, hr derby) replaced with new + garbage option (requested by Cubicz). + * In garbage style "Vs.", player can choose difficulty 1 through 4 + (requested by Caithness). + * Option for line clear delay, independent of entry delay + (requested by kiwibonga). + * Garbage style Drill no longer automatically tops out on + short playfields. + * Garbage style Vs. generates garbage with SZSZ randomizer. + * Frequency of Vs. w/Items bananas is independent of randomizer. + * Fixed self-clearing garbage (0.33 regression). + * GBA/DS: Fixed pause button hiding left and right walls + (0.33 regression). + * GBA/DS: R button also works as hold piece (requested by + PetitPrince). + +_____________________________________________________________________ +0.33 (2007-03-08) + * New speed curve Rhythm Zero is identical to Rhythm except it + operates at 0G instead of 20G (requested by Cubicz and caffeine). + * Speed curves that do not use level reset level to 0. + Therefore, playing a game of Death then a game of Exponential + on GBA/DS no longer shows the level that the player finished + Death on as the level during Exponential. + * Maximum well width increased to 12 (requested by Zed0). + * Hold piece does not reset floor kicks, lock delay time in entry + reset, or Rhythm's placement timer. + * GBA/DS: More shared code moved to file "ljgbads.inc". + * PC: Stops end-of-section sound when game is over + (requested by Bloodstar). + +_____________________________________________________________________ +0.32a (2007-02-25) + * PC: Fixed demo recording (0.32 regression; reported by Rosti LFC) + * PC: Option to record all games from the start + (requested by cdsboy, DIGITAL, Cubicz, and colour_thief). + * PC: Added Play .ljm gimmick: + Allegro file selector pops up, and the player can choose the + filename of a demo to play back (requested by Lardarse). + +_____________________________________________________________________ +0.32 (2007-02-13) + * Changed a CPU yield behavior that was affecting battery + efficiency on the GBA and DS front ends (reported by Mighty Max). + * Removed Low Rider gimmick in favor of option for well height + (requested by Matthew). + * Option for well width, like Shimizu's Tetris Semipro-68k. + Combine this with a low well height to simulate BIG mode of + TGM and Heboris (requested by Matthew). + * Options for entry delay and sideways delay have "max" added to + their description to clarify things (requested by kotetsu213 and + Ezzelin). + * All speed options given in Hz or G are given in both. + * PC: Next above shadow works in all playfield positions (reported + by caffeine). + * GBA/DS: Added option for soft drop speed (requested by Ezzelin). + * GBA/DS: Some shared code moved to file "ljgbads.inc". + * GBA: Unavailable hold piece is grayed out. + * GBA/DS: Lock delay = no lock works (reported by Ezzelin). + * GBA/DS: Fixed frames/ms display of delays (reported by Ezzelin). + +_____________________________________________________________________ +0.31 (2007-02-03) + * Draws the score and next pieces before blitting the playfield + in case a front-end draws the score or next pieces inside the + playfield. + * In Baboo!, speed curve Zero sets the level to the number of + keypresses so far. + * PC, GBA: Plays sound effect for end of section in speed curves + that use sections (Master, Death, NES, Game Boy). + * PC: Option to draw next pieces inside the playfield, above the + shadow (requested by caffeine, cdsboy, and Cubicz). + * GBA, DS: Blocks within a piece are drawn connected once they lock + (TOD parity, requested by Lardarse and Bloodstar). + * GBA, DS: Baboo! no longer double-counts console buttons passed + through the joypad reading code. + * README: "color=image" clone-and-hack error fixed (reported by + Bloodstar). + * README: Explains black rectangle in upper left corner as an + icon for stop (requested by herc). + * README: Explains controls on GBA and DS. + +_____________________________________________________________________ +0.30 (2007-02-01) + * Fixed ARE display in debrief, which could cause a crash in some + situations (0.26 regression?). + * Garbage no longer self-clears in Cascade gravity (reported by + Bloodstar). + * In high gravity, land a T with one block over an overhang and + rotate it once so that it falls and clears a line. This is no + longer counted as a T-spin. + * During a replay, counts keypresses by the replay player. + * PC: Scales undersized background images to the size of the window + (requested by Bloodstar). + * PC: If a piece enters during a skipped frame, a second upward + trail is no longer drawn (reported by Bloodstar and caffeine). + * PC: "Next at top" layout draws speedometer (requested by + matt_hatter83). + * PC: Trails option is saved properly (reported by caffeine). + * PC: User can customize the game's text and background colors + using skin.ini (requested by Bloodstar). + * Added rudimentary port to Nintendo DS for people with MAX Media + Dock, M3 Pro, SuperCard Rumble, or SLOT-1 cards, which can run + DS homebrew but not GBA homebrew. + * Moscow Nights removed from lj-contrib due to research into the + copyright term extensions enacted by Russian Federation during + the 1990s. + +_____________________________________________________________________ +0.29 (2007-01-17) + * TDS scoring recognizes chains properly (reported by Lardarse). + * TDS scoring section factor stops increasing after 190 lines. + * Added NES scoring method. + * Added scoring for soft and hard drops to options and debrief. + * Cascade no longer deletes the bottom row when lines high on the + playfield are cleared (reported by Lardarse). + * PC: Draws trails when a tetromino goes up or down rapidly. + (Option to turn them off.) + * PC: Handles dirty rectangles for next pieces and score + separately. + * Scoring section of README describes all scoring methods. + +_____________________________________________________________________ +0.28 (2007-01-04) + * Added Cascade gravity, as seen in Quadra, Tetris Worlds Cascade, + and Tetris DS Touch. + * Master/Death: Level starts at -1, so that the first piece is + played at level 0 (requested by Lardarse). + * Changed T-spin detection to save whether or not a rotation + involved a kick (0: move; 1: rotate; 2: rotate with kick) so + that scoring methods such as TDS can score T-spins with and + without kicks differently. + * Added TDS scoring method (line clears only). + * New, more general, possibly easier to understand reshuffle code + in bag randomizer (requested by Lardarse) + * GBA: Gold and silver square colors added (requested by Lardarse). + * PC: Game over in low ceiling no longer fades the area outside the + playfield (reported by Lardarse). + * PC: Close box interrupts starting and game over animations. + * README warns that 4x4 squares mode needs ljconn. + +_____________________________________________________________________ +0.27 (2006-12-20) + * GBA version includes a valid header (requested by Ezzelin). + * Fixed incorrect spawn orientations for Game Boy rotation system + (0.25? regression, reported by Lardarse). + * Fixed incorrect win/loss sound early in 180 seconds (0.25 + regression). + * New randomizer 10-piece Bag, including the domino and both + trominoes. + * GBA version now reports entry and lock delays in both frames + and milliseconds. + * Possibly fixed off-by-one in Game Boy speed curve section + computation, which caused an incorrect slowdown in 210-219 + section (reported by Ezzelin). + +_____________________________________________________________________ +0.26 (2006-12-15) + * Option to allow game to proceed in the background (requested by + Ezzelin). However, Windows doesn't appear to pass joystick + presses to the game running in the background. FCE Ultra has + what appears to be exactly the same problem. + * Added Game Boy and NES speed curves. + * Options and Game Keys are accessed through title screen, not + gimmick selection. + * Option to hide playfield without changing the skin. + * Rearranged rules section of options to correspond more closely to + the sequence of operations for each tetromino. + * Soft or hard drop set to "lock on release" no longer produces + double locks at 20G. + * Disable initial hard drop when set to "lock" and entry delay is + greater than 0 but less than DAS delay. + * Checks for 4x4 squares top to bottom per comparison with The New + Tetris (N64). + * Finer grained selections in entry delay (requested by + matt_hatter83) and lock delay (requested by caffeine). + They now are at 50ms increments at the low end. + * Expanded TGM speed curve to 12 sections. One comparatively slow + section at 20G was added to Master before Death-equivalent starts + (now Death 0 is equivalent to Master 600, not 500), and one + faster section was added to the end of Death based on info posted + to wiki by colour_thief. + * Added H.R. Derby gimmick: + Like Marathon, but every line you clear other than with a + home run or a T-spin gives you garbage. + * Ready Go animation is 1.2 seconds, not 2.0 seconds. + * Preliminary support for Game Boy Advance, with experimental + paged options replacing scrolling options. + +_____________________________________________________________________ +0.25 (2006-11-28) + * Fixed History 6 Rolls deciding between the first piece algorithm + and the subsequent pieces algorithm (reported by Lardarse). + * Fixed a signedness issue that broke 180 seconds gimmick in the + case that a tetromino was kept active between 3:00 and 3:01 (0.24 + regression; reported by caffeine). + * Allows ending the game with a piece in mid-air (0.24 regression). + * Removed Arika and renamed Arika+TI to Arika. For the old + Arika behavior, set Options:Floor kicks to 0. + * Options screen allows DAS, allowing future versions to make more + options and more values for each option available in a less + cumbersome way. + +_____________________________________________________________________ +0.24 (2006-11-16) + * Added Score style to options and debrief. + * Choice of LJ or TNT64 scoring is based on chosen score style, not + whether 4x4 squares are turned on. + * Added Hotline scoring method, where only lines cleared on + specific rows count toward scoring. When enabled, draws white + lines through empty spaces in these rows. + * New soft drop and hard drop lock setting: Lock on release. It's + a spring-loaded system: When you press the button, it doesn't + lock, but when you let go, it locks (requested by colour_thief). + * Option to override lock delay time (requested by caffeine). + * Option to limit upward kicks (requested by Needle). This may + obviate the difference between Arika and Arika+TI. + * Sped up line clear in Master 300-499 to make less of a jarring + transition to "death" style timings. + * LJM loading fails even more gracefully on wrong format version, + properly treating an LJM of the wrong version as not existing. + * Fixed bug in 0.23's application switch pausing where switching + away while already paused would require two Esc presses to + continue (reported by caffeine). + * Does not stop play until after the line clear animation finishes. + This allows the game to properly update the single, double, + triple, etc. counts for the last line that completes the goal + (0.19 scoring regression?; reported by caffeine). + * Refactored duplicated option loading code. + * Options: Ditched parallel struct and array in favor of a + single list of named indices. + * Corrected Moscow Nights and Kalinka in lj-contrib for greater + compatibility with obscure s3m players that can't handle odd + numbers of channels (requested by Lardarse). + +_____________________________________________________________________ +0.23 (2006-11-04) + * Fixed S and Z in Sega 1988. + * DUMB has been wrapped in a library called LJMusic and can be + turned off at compile time (edit ljmusic.c and makefile) if you + don't want to install DUMB. + * Added support for Xiph.org's OggVorbis codec to LJMusic + (requested by Needle). + * Option to ignore sideways movement on first frame (like TGM + series) (requested by Needle, seconded by caffeine). + * Properly pauses the game when the player switches away from the + window (requested by Lardarse). + * Fails gracefully (does nothing) instead of crashing when loading + replay files of a different format version. + * Moved as much as possible below setting display mode so that + people porting LJ to other platforms can toss up working alert() + boxes earlier (requested by cdsboy). + +_____________________________________________________________________ +0.22 (2006-10-23) + * Fixed color system change after reloading (0.20 regression?, + reported by lardarse). Should always unpack options just before + loading skin. + * All numbers in saved states are stored big-endian, making movies + compatible between 0.22 for Windows and Intel Mac and 0.22 for + PowerPC Mac (requested by cdsboy). + * Sega rotation systems renamed to Arika for less confusion with + the rotation systems in Sega's Tetris games from 1988 and 1998 + (requested by Needle). + * Added Sega 1988 rotation system (like Arika without wall kicks). + * Preliminary Vorbis playback code is included in the source code + distribution but has not been activated in the program. + (The keys to compiling OggVorbis on MinGW+MSYS are 1. set the + prefix to match the MINGDIR you used to build Allegro, and + 2. disable creation of the shared library.) + +_____________________________________________________________________ +0.21 (2006-10-21) + * Added new T-spin detection rule "3-corner T no kick", identical + to 3-corner T except that a wallkick is not counted as a rotation + (requested by kotetsu213). + * Options that don't apply because of how another option is set + are grayed out with an explanation (requested by Needle). + * Debrief tells number of blocks left in the playfield and whether + saved state was used (requested by caffeine). + * Debrief tells what level the player stopped on. + * Added support for saving input stream ("demo" or "movie") to file + "demo.ljm". Press [ to start/stop recording and ] to start/stop + playback (requested by caffeine). + * Pieces are actually random again (0.20 regression). + * New lockdown mode: Entry reset. Instead of resetting, the lock + timer pauses while the piece is falling (requested by caffeine). + * No +20 for banking more than 5 beats (requested by caffeine). + * 250 ms entry delay option (requested by matt_hatter83). + * Entry and sideways delays in Master and Death use the shorter of + the speed curve's delay and the user's delay. + * Added soft drop speed to options. + * Added settings 8.6 Hz through 6.7 Hz to Sideways speed. + +_____________________________________________________________________ +0.20 (2006-10-15) + * Fixed color system change after options (0.19 regression). + * Restored ability to hold first piece (0.17 regression?). + * Requires down to be re-pressed only when entry delay is less + than sideways delay. + * In modes with ARE, allow initial hard drop even on first piece + (requested by caffeine). + * Visual refresh in Game Keys. + * Game Keys ignores keypresses for 1/4 second after each key is + set, which should fix problems with GameCube to USB adapters + (requested by Caithness). + * All platforms use a single randomizer, whose state is associated + with the playfield. + * ARE is a binary option again, as several speed curves ignored the + old version's difference between constant and decreasing. + * Moved version display to title screen. + * Sound effect for win differs from that for game over. + * Sound effect for rotating a piece on its first active frame. + * Begun to add assertions, causing game over if they fail. + * Added support for saving the state of the playfield. + Press [ to save or ] to load. Movies might be next. + +_____________________________________________________________________ +0.19 (2006-09-28): + * Use of SRS or Sega colors is determined by a flag set for each + rotation system. The rule is that rotation systems that use + bounding box rotation should use SRS colors. + * Added TOD M4 rotation system (face up entry + bounding box + rotation + roughly TGM style compensation). + * List items in Options flicker much less. + * Beginning of an actual title screen. + * Removed Rhythm gimmick in favor of speed curve option. + * Added Master and Death speed curves (requested by Needle). + Death hasn't been tested thoroughly because the developer + sucks at death. + * Blits only those playfield rows that have changed, making + animation smoother on old, cheap, or mobile video cards + (requested by cosmonaut). + * Rhythm speed curve no longer has what feels like a big elbow + (requested by caffeine). Specifically it gives 20 points + instead of banking more than 5 pieces' worth of time. + * Doesn't draw the state after the tetromino spawns but before + initial rotation has taken effect (requested by Needle). + * Added sound effect for hold piece (requested by Needle). + * Land and lock sound much better (requested by Needle). + * Shuffle columns (seen only in Vs. w/ Items) correctly disconnects + blocks horizontally. + * In Square mode, T-spins cause avalanches. + * In Square mode, lines containing a piece of a 4x4 block are worth + more, and homers are worth less. + * Fixed ignoring diagonal presses (0.17 regression). + * Created a new struct "LJPrefs" to hold preferences set by the + player in the Options menu separately from the parameters + that the game actually uses. This allows gimmicks to override + preferences more cleanly. + * Added support for future scoring methods where the score per line + at a given point in the chain is based on a formula. + * Created a union that combines the new struct with the array that + the Options menu edits to improve maintainability of Options. + +_____________________________________________________________________ +0.18 (2006-09-16): + * Fixed Classic lock reset (unknown regression). + * Fixed misbehavior when hold is pressed after a piece + lands but before it locks (0.17 regression). + * Shadow is drawn using the piece's color, at 25% opacity, + from rows 4 and 5 of ljblocks. + * Fixed shadow option saving (0.17a regression). + * Added option to hide shadow color or change shadow opacity. + * Split Sega rotation system into one with and one without + TGM3's upward kicks. + * Added NES and Game Boy rotation systems. + * README: Illustration of game play has an image map. The reader + can select (hover over) a region of the screenshot, and the + browser will show the title of the region as a tooltip. + * BPM is now called speed level in preparation for other + speed curves (including TGM and TA Death). + +_____________________________________________________________________ +0.17 (2006-09-13): + * Option for sticky gravity per color, as seen in The Next Tetris. + * "Ready, Go" is centered, even in low ceiling gimmicks. + * Added TGM3's upward kick to Sega T rotations. + * Added Tengen rotation system. + * Removed TGM gimmick in favor of entry delay option. + * Initial hold works at any time. The key can be pressed and + released during entry delay or even during line clear delay. + * Initial actions do not inflate keypress count + (helpful in Baboo! with entry delay). + * Draws hold piece in garbage colors when it is not available. + * Options scroll. + * Option to draw next pieces to right in constant size or above. + * Better looking scroll bar in options. + * Debrief: In naive gravity, reports T-spin singles, doubles, + and triples separately from non-T-spins. In other line + clear gravity modes, "home run" is now called "quad". + * Fixed a buffer overflow bug in sticky: fillCLoop() no longer + treats the right side of one row and the left side of the + row above it as one region. + * Clarified manual as to the purpose of the Vs. gimmicks. + * Separated platform specific stuff into a separate struct. + * Refactored scoring into a separate function for future movement + to gimmick and option control. + * Debrief formats the report into a string and then writes it + to a file and the screen all at once. + * Refactored play() to move everything in the game loop that is + not platform specific out of ljpc.c into new file ljplay.c. + * Wall kick table is no longer flipped, making it easier to add + new rotation systems. + * Unified counterclockwise and clockwise rotation code. + +_____________________________________________________________________ +0.16 (2006-09-03): + * Fixed instant sideways speed (0.15 regression). + * In countdown modes, debrief screen displays whether clear was + successful. + * 6-piece Bag no longer makes Vs. ridiculously easy. + * Counts score and garbage separately for future shift to + gimmick-controlled scoring. + * Displays score instead of garbage on the play screen. + * Counts singles, doubles, triples, and homers, and displays them + in debrief. + * Option to hide falling piece in addition to shadow. + * Can load mod, xm, or it music in addition to s3m. + * Moved skin description from lj.ini to skin.ini, and the name of + this skin description file name can be changed with the Skin= + command in lj.ini. + * Closes lj.ini when reading it. + * All vkey->action game key handling code moved to macro.c for + future refactoring. + * Build process uses automatic generation of C files' dependencies + for future refactoring of header files. + * Added two versions of NES-style blocks to lj-contrib: + one by tepples and one by deepdorp. + +_____________________________________________________________________ +0.15 (2006-08-27): + * Options for DAS delay, number of next pieces, and window vs. + full screen. + * Options scrolling doesn't cause the dialog's title and + instructions to flicker. + * Small font's 5 glyph is the same width as other digits. + * Fixed O shape table. + * Macros (vkey->action mappings past the first 8) are in a + lookup table in the new file macro.c. This paves the way + for a future macro editor. + * Added macros Alt. Firm Drop and Alt. Hold. + * Game Keys displays names for all keys, not just printable keys. + * In Sega rotation mode, reads block images from ljblocks-sega.bmp + and ljconn-sega.bmp if available. + * If ljbg.jpg is not available, uses a plain white backdrop. + * Reads file names for block images, background image, and music + from lj.ini, which the player can edit with a text editor. + * New "6-piece Bag" randomizer is one stick short of a bundle. + * Added an arrangement of the minuet from JS Bach's French Suite + in B minor (BWV 814) to lj-contrib. + +_____________________________________________________________________ +0.14 (2006-08-18): + * pause() renamed to pauseGame() to fix namespace collision on + platforms with unistd.h (Linux, BSD, Mac). + * Gimmick settings for initial gravity, ARE, and ceiling height + are initialized from a lookup table. + * History 6 Rolls randomizer uses correct initial history (ZSZS) + and correct selection for the first piece (I, J, L, or T). + * Blocks are drawn connected within each tetromino if ljconn.bmp + is present. + * Corrected connection table for O tetromino. + * Rhythm's BPM counter increases after every 64 beats, not every + 64 tetrominoes. + * Added Low Vs. gimmick: + Like Vs. CPU with a low ceiling. + +_____________________________________________________________________ +0.13 (2006-08-15): + * Hard drop lock option "Zangi" renamed to "Slide". + * Added soft drop lock option. + * Ignores diagonal presses differently: instead of pretending the + keys are up, it pretends that they haven't changed from the last + frame. UP, UP+RIGHT, UP no longer makes a double hard drop. + * Esc pauses the game and music instead of immediately quitting. + * Added contributions from cdsboy to lj-contrib. + +_____________________________________________________________________ +0.12 (2006-08-13): + * Creates lj.ini if it doesn't exist, instead of crashing on + startup. + * Added half-size Aver16 font. + * Zero gravity (as in Baboo!) + Zangi hard drop = land and lock, + instead of just sit there + * Option to use classic (Game Boy style) or step-reset (TGM style) + lockdown instead of move-reset (SRS style) lockdown. + * Contains connection tables for all pieces. Not used yet, + but will be used for Cascade (where gravity is based on shapes + of pieces), Square (which forms big squares only out of whole + tetrominoes), and Bombliss (which doesn't place a bomb in a block + that has blocks on opposite sides of it within the piece, such as + the middle of an I, J, L, or T). + * Pieces can carry bomb blocks within them. Not used yet, + but will be used for Bombliss. + * Option for sticky gravity. This will also be used for Bombliss. + * Options rearranged: game on top, control in middle, + view on bottom. + * Options menu uses a smaller font and scrolls if necessary (but + it's not necessary just yet). + * Zero isn't wider than other digits in the large or small font. + * Split debrief screen into debrief.c. + * Debrief displays lockdown and line clear gravity type. + * List of options in debrief is written with smaller font. + * Config screen does a better job of ignoring simultaneous presses + on USB joystick adapters that map the Control Pad to both axes + and buttons (such as EMS USB2). + * Main menu and options always respond to the arrow keys (in + addition to the keys set in Game Keys), allowing players to + reset Game Keys even when the keys have become corrupted. + +_____________________________________________________________________ +0.11 (2006-08-01): + * Added Instant, 20 Hz, 15 Hz, and 12 Hz sideways speeds. + * Added randomizer History 6 Rolls, very similar to Move to Back + but sometimes repeating recent tetrominoes. + * Allows separate wall kick tables for clockwise and anticlockwise + rotation. + * New "theta=default" in piece->block expansion reflects the + rotation system's initial orientations for new pieces, next + pieces, and the hold piece. + * Option to use Sega rotations instead of SRS rotations. + * Option for ignoring diagonal presses. + * Option for locking or not when using hard drop (Up). + * Option to turn off smooth gravity animation. + * Option to use TNT style T-spin detection instead of TDS style. + * Debrief displays rotation system and T-spin detection. + * Saves options to lj.ini. + * Split options screen and wall kick table into separate source + code files. + * Plays music in stereo. + * Site distributes contributed graphics and music files to + replace the defaults. + +_____________________________________________________________________ +0.10 (2006-07-27): + * Set default full screen color depth to 16-bit which may be more + compatible than 15-bit. + * Press Print Screen to save the current screen as ljsnap.bmp. + * Plays next piece sound whenever the next column moves up. + 0.08-0.09 didn't do so for the first hold in a game. + * No longer tries rotating right when rotating left fails. + * Handles DirectDraw amnesia (screen corruption in Alt+Tab) + correctly in menu, game play, and debrief screens. + * Asset and video buffer cleanup consolidated into one function. + * Gravity speeds up 50% faster. + * Added options menu for selecting sideways movement speed and + randomizer. + * Added more randomizers: 14-piece bag, move-to-back (TGM style), + memoryless (classic), and SZSZ (used in the well-known proof + that memoryless cannot be played indefinitely). + * Debrief screen displays date and time of report generation as + well as randomizer and sideways speed, and logs what it displays + to lj-scores.txt. + * Moved all gimmick-specific code from lj.c to new file gimmicks.c. + * Built against Allegro 4.2.0. (Users of previous versions will + need the new DLL.) + * Countdown based gimmicks (40 lines, 180 seconds, Baboo!, + TGM World, Drill 40) use the same countdown variable for + end state detection that they had used for countdown sound. + * Support for tracked music using DUMB. + * Manual is HTML. + +_____________________________________________________________________ +0.09 (2006-07-16): + * Reverted sideways kicks of I tetromino rotating from horizontal + to vertical based on testing I-I interactions in Tetris DS. + * Key pretending is more consistent. + * TGM World stops at 290 lines, more like Tetris The Grand Master. + * In gimmicks with ARE (currently TGM World), allows initial + hard drop. + * Licensed as free software under GNU General Public License. + * Fixed incomplete first bag (0.08 regression). + * Has its own icon instead of the wall kick editor's icon. + * No double initial rotation when using initial hold in TGM World. + * Input actions are a 32-bit record for future recording. + * Hidden next piece is a separate variable, which next piece + sound also respects, fixing Items. + +_____________________________________________________________________ +0.08 (2006-07-08): + * Plays sounds for the next piece like TGM. + * No longer allows 800x552 window now that 0.08 displays the + gimmick name. + * Supports spawn delay and line delay. + * Supports initial hold and rotation. + * Supports variable garbage randomness. + * Begun to move gimmick code out of core. + * Added TGM World gimmick: + Standard S.M.G., except there's a half-second delay before each + tetromino, and a half-second delay after each tetromino that + forms at least one line. So if you want fast play, make more + lines at once. + * Added Drill 40 gimmick: + Like 40 lines, but the screen starts with 18 rows of garbage. + Be prepared to rely on SRS infinite spin for the first few + lines until your skill improves. + +_____________________________________________________________________ +0.07 (2006-06-30): + * Added Baboo gimmick: + Standard S.M.G., except there is no gravity, and the game + ends after 300 keypresses. + * Cleaned up the font a bit. + * Corrected wall kicks for I tetromino to match an updated + description provided by nicholas. Specifically, I tetromino + prefers kick-down to kick-up. + * Removed sound-not-found debug message. + * Plays "ready, go!" like TGM. + * Bigger font for lines and time. + +_____________________________________________________________________ +0.06 (2006-06-25): + * Gimmicks are sorted into columns. + * Debrief responds to controller mapping. + * Debrief shows keypresses per tetromino and garbage per minute. + * Loads/saves vkey configuration from/to disk instead of + prompting the user every time the program starts. + * Build system switched to GNU Make. + * Terminal window ("DOS box") hidden. + * Responds to window system's close button. + * Counts down 5-4-3-2-1 in line or time limited gimmicks. + * Icon in the top left corner of the window is customized. + * Displays ljbg.jpg in the window's blank space. + * Displays blocks from ljblocks.bmp. + * Releases the CPU for 5ms per frame when possible, so that + background tasks such as a music player can keep up. + * Increased sound effects' volume so that music players do not + completely overpower them. + * Added macros for rotate twice, move far left, and move far right. + * Corrected fix to alternation between falling and landed states + that occurred especially when sliding a tetromino to the right + under high gravity. + * Added Items gimmick: + Standard S.M.G., except 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 are given an + I piece, either you get 2 lines of garbage or the columns of + blocks in the well are shuffled. + * Fixed scoring tables in README.txt. + +_____________________________________________________________________ +0.05 (2006-06-23): + * Included readme.txt. + * Debrief report shows more decimal places in time and PPM. + * Game over plays a simple animation and sound. + * Playfield can be resized (with a recompile). + * Removed hardcoded key binding help text. + * Window is bigger in preparation for wallpaper. + * Display mode is 15/16/24/32-bit in preparation for wallpaper. + * Added Low Rider gimmick: + Standard S.M.G. with an 8-block-tall visible playfield. + +_____________________________________________________________________ +0.04 (2006-06-22): + * Uses an OS-native bitmap rather than an Allegro bitmap, + which may allow faster drawing with some video cards. + * Allows play from a keyboard or a joystick. + * Presents a key configuration screen when the program starts. + * A 500ms delay after the game ends. Drama will come soon to this + space. + +_____________________________________________________________________ +0.03 (2006-06-21): + * Debriefing responds only to Esc, Enter, and keypad Enter. + * Compensates for refresh rates other than 60 Hz. + * Change log included with program. + * Plays sound effects for shift, rotate, land, lock, line, b2b. + * Bigger text using the "Aver" font. + * Lock delay in Marathon decreases gradually after gravity + surpasses 20G. + * Reads keys through a bitfield, allowing for custom key->vkey + bindings to be implemented in the future. + * (Internal) Does not alternate between "falling" and "landed" + states when resetting lock delay on slide. + +_____________________________________________________________________ +0.02 (2006-06-19): + * Speed level progresses 1/3 as fast. + * Added menu for selecting a gimmick (game mode) to play. + * Added Marathon gimmick: + Like Vs. but no garbage. + * Added 40 lines gimmick: + Like Marathon but ends after 40 lines. + * Added 3 minutes gimmick (like Marathon but ends after 10800 + frames). + * Added Rhythm gimmick: + 20G, and if you fall below the minimum PPM, it automatically + locks the tetromino, and the minimum PPM increases by 10 every + 64 tetrominoes. + * Debriefing computes PPM and score. + * Debriefing returns to gimmick selection instead of exiting + the program. + +_____________________________________________________________________ +0.01 (2006-06-14): + * initial release + * full SRS implemented + * 7-piece bag randomizer + * 1G DAS + * smooth falling animation + * playfield size: 10w x 24h; pieces start above row 20 + * 8 next pieces and 1 hold piece + * Vs. gimmick: + Standard S.M.G., except before every I tetromino after the first, + you get 4 garbage lines. + diff -r 000000000000 -r c84446dfb3f5 COPYING.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/COPYING.txt Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,52 @@ +Copying conditions for LOCKJAW: + +Copyright 2007 Damian Yerrick + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +(GPL.txt) along with this program. If not, see +. + +_____________________________________________________________________ +Copying conditions for LJVorbis: + +Copyright 2006 Damian Yerrick +Copyright 2002-2004 Xiph.org Foundation + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +- Neither the name of the Xiph.org Foundation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_____________________________________________________________________ diff -r 000000000000 -r c84446dfb3f5 GPL.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GPL.txt Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,342 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + + diff -r 000000000000 -r c84446dfb3f5 README.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.html Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,546 @@ + + + +LOCKJAW - About + + + + + +

LOCKJAW Tetromino Game: About

+ +
+

+Installing | Skinning | Controls | Scoring | Scenario | Options | Discussion | Legal +

+LOCKJAW is a free software 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 TETRIS®.* Other products implementing the Soviet Mind Game include +Quadra, +Abandoned Bricks, +KSirtet, +F.B.G., +N-Blox, and +Heboris. 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. +

+Tetrominoes are geometric shapes made of four connected square blocks. There are seven distinct tetrominoes, shaped roughly like letters of the Latin alphabet: +

+(Illustration of the seven tetrominoes)
+Tetrominoes. Top row: I, J, L, O. Bottom row: S, T, Z. +

+Some products spell the term as "tetramino", "tetrimino", or "tetrad". +

+
+(Game screenshot) +

+LOCKJAW gameplay screen. Hover over objects to see their names. +

+ +Hold piece +Status +Falling piece +Shadow +Well +Game mode +Next pieces +Gus + +
+ +

+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. +

+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. +

+ +

Installing

+

+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 lj.exe. (If you do not want to allow users to write to the program's folder, place an empty file called installed.ini in the same folder as lj.exe. This will make Game Keys and Options write settings to the user's application data folder, commonly C:\Documents and Settings\Gus\Application Data\Pin Eight\Lockjaw.) +

+The files lj.exe, lj.dat, ljblocks.bmp, and alleg42.dll are required to play. (If you get an error about alleg42.dll, see the instructions on the LOCKJAW download page.) To start the game, run lj.exe in Windows Explorer. +

+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. +Its source code is portable to any platform that supports the Allegro library, but it is tested only on Microsoft Windows because the author has access only to machines that run Windows. +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, get in touch with the author. +

+For the handheld version, you only need the file lj.gba or lj.nds. Due to limitations in the DS homebrew operating system, the DS version always acts installed, writing user files to the folder /data/lockjaw on the memory card. +

+ +

Installing from source

+

+To recompile the program, such as if you are testing a patch or porting it to another system: +

    +
  1. Install GNU Coreutils and GNU Make. These are packaged as MSYS from MinGW.org
  2. +
  3. Install a GCC toolchain. This is packaged as MinGW from MinGW.org
  4. +
  5. Install Allegro 4.2.1 from SourceForge.net
  6. +
  7. Install JPGalleg 2.5 from Enhanced Creations++
  8. +
  9. Install DUMB 0.9.3 from SourceForge.net
    +(Editor's note: DUMB has a dumb license, but section 6 allows relicensing under GNU GPL. Update: It appears Debian got the author to relicense it under a straight zlib style license.)
  10. +
  11. Open a command prompt and cd to the folder containing the file makefile
  12. +
  13. Type make
  14. +

+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. +

+To recompile the program for Game Boy Advance or Nintendo DS: +

    +
  1. Install devkitARM, libgba, libnds, and MSYS using devkitPro Updater at devkitPro.org.
  2. +
  3. Install a GCC toolchain, such as MinGW from MinGW.org
  4. +
  5. Open a command prompt and cd to the folder containing the file gbamakefile or dsmakefile
  6. +
  7. Type make -f gbamakefile or make -f dsmakefile
  8. +

+To recompile all three ports, type make all +

+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 -j2 to the end of a Make command line to compile two files at once, or if you have a dual-core CPU, add -j3 to have GCC run one core, the other core, and the disk at the same time. +

+ +

Skinning

+

+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 .skin. (You can create and edit .ini and .skin files using Notepad, Notepad++, vi, Emacs, or any other plain text editor.) LOCKJAW recognizes the following commands in a skin description: +

+
ljblocksSRS=image
+
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 blkW or blkH is present, the program uses that size instead of 24x24.
+
ljblocksSega=image
+
Same as ljblocksSRS, but used in other rotation systems.
+
ljconnSRS=image
+
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, ljblocksSRS will be used instead.
+
ljconnSega=image +
Same as ljconnSRS, but used in other rotation systems. If this file is not present, ljblocksSega will be used instead.
+
color=#RRGGBB
+
A 3- or 6-digit hexadecimal color (e.g. #ABC or #D0FFE3) for text in the menus and during the game. If not present, the game will use black.
+
bgcolor=#RRGGBB
+
A color for the menus' background. If not present, the game will use white.
+
hilitecolor=#RRGGBB
+
A color for the background of highlighted text. If not present, the game will use pale yellow (#FFC).
+
pfcolor=#RRGGBB
+
A color for text in front of the playfield. If not present, the game will use white.
+
pfbgcolor=#RRGGBB
+
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.
+
ljbg=#RRGGBB
+
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 bgcolor.
+
blkW=length
+
The width in pixels of each block in the ljblocks and ljconn, if it is not 24 pixels.
+
blkH=length
+
The height in pixels of each block in the ljblocks and ljconn, if it is not 24 pixels. This is allowed to differ from blkW
+
transparentPF=boolean
+
If this is set to the value 0, tile 0 of ljblocksSRS covers up the background within the playfield. If this is set to nonzero, the playfield background shows through.
+
bgm=music file
+
A music file in Vorbis format (.ogg) or tracker format (.mod, .s3m, .xm, or .it) to be played during the game. You can create Vorbis format files by extracting audio from your CDs to .wav format using CDex software and converting them with OggDropXPd software. You can download tracker format files from The Mod Archive or create them yourself using the OpenMPT music editor, the continuation of MODPlug Tracker. If this file is not present, the game will not play music.
+
bgmLoopPoint=sampleNumber
+
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.
+
bgmRhythm=music file
+
Like bgm, 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.
+
bgmReadyGo=Boolean
+
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.
+
bgmVolume=volume
+
Sets the volume of the music to balance it against the sound effects, where 256 is full volume. If not specified, uses 128; 1 dB louder than this would be 144.
+
shiftScale=Boolean
+
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)
+
baseX=distance
+
This moves all gameplay graphics to the left (0), middle (200), or right (400) of the screen.
+
nextPos=style number
+
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.
+
wndW=length
+
Sets the width of the game window in pixels.
+
wndH=length
+
Sets the height of the game window in pixels.
+

+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 lj.ini: +

+
Skin=skin description file
+
A skin description file. If the skin is not present, the game uses preset file names (ljblocks.bmp, ljblocks-sega.bmp, ljconn.bmp, ljconn-sega.bmp, ljbg.jpg, and bgm.s3m).
+

+Images can be in Windows bitmap (.bmp), PC-Paintbrush (.pcx), Truevision TGA (.tga), or JFIF/JPEG (.jpg) format. All color depths should be supported. Paths are interpreted relative to the folder containing the .skin file, that is, specifying ljblocksSega=Gradient Blocks.bmp will try to pull Gradient Blocks.bmp from the skin's folder. If you want to create a folder structure inside your skin package, it is best to use forward slashes ('/') instead of backslashes ('\') so that users of Mac OS X and GNU/Linux will be able to use your skin. +

+Example image and sound files are located in lj-contrib.zip, available from the Skins section of the download page. You can also customize the sound effects by using Allegro Grabber software to edit lj.dat. +

+ +

Controls

+

PC

+

+The controls in LOCKJAW Tetromino Game for PC are initially set as follows: +

+
+
← Shift left
Left arrow key
+
→ Shift right
Right arrow key
+
↓ Soft drop
Down arrow key
+
Hard drop
Up arrow key
+
↰ Rotate left
Z, C
+
↱ Rotate right
X
+
↶ Rotate twice
W
+
↖ Hold piece
S, D
+
⇤ Shift far left
Q
+
⇥ Shift far right
E
+
Firm drop
Enter
+

+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 lj-keys.043; if they become unusable, you can delete this file to reset them to the initial settings. +

+Some controls during game play are hard-coded to keyboard keys: +

    +
  • Esc pauses and resumes the game. Holding Esc for one second stops the game and goes to a result screen.
  • +
  • [ (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 demo.ljm. Caution: 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.
  • +

+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 ljsnap.bmp. +

+ +

GBA and DS

+

+Controls in the Game Boy Advance and Nintendo DS versions are hardcoded as follows: +

+
← Shift left
Left on Control Pad
+
→ Shift right
Right on Control Pad
+
↓ Soft drop
Down on Control Pad
+
Hard drop
Up on Control Pad
+
↰ Rotate left
B Button
+
↱ Rotate right
A Button
+
↖ Hold piece
L Button
+

+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. +

+ +

Movement features

+

+Unlike some other S.M.G. 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. +

+LOCKJAW also implements the so-called Super Rotation System (SRS), which allows a tetromino to rotate around obstacles for more mobility across the pile. Some players abuse SRS by rotating a piece in place repeatedly, but this will result in poorer scores in timed gimmicks. To replace this behavior with the "Sega style" behavior used in Arika's Tetris The Grand Master 3: Terror-Instinct, change the Options described below. +

+ +

Scoring

+

+LOCKJAW Tetromino Game allows the player to choose among several methods of calculating the score for a line clear. +The terms "single", "double", "triple", and "home run" refer to clearing 1, 2, 3, or 4 lines with one tetromino. +(A "home run" is called a "tetris" in some other games.) +"T-spin" means rotating a T piece as it lands to fit into a tight space. +

+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. +

+ +

LJ

+

+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. +

+ + + + + + + + + + + + + + +
LJ scoring method
LinesGarbageScoreBonus
1 line 10100No
2 lines21400No
3 lines32700No
4 lines441200Yes
1 line T-spin 12500Yes
2 lines T-spin241000Yes
3 lines T-spin361500Yes
Back-to-back bonus 1200Yes

+LJ has a variant called "nerfed spin" where T-spins produce half the garbage and only 300 points per line. +

+ +

Fibonacci

+

+This extension of the scoring curve seen in The New Tetris is based on the Fibonacci sequence. +

+ + + + + + + + + + + + + + + + + + + +
TNT scoring method
LinesScore
1100
2200
3300
4500
5800
61300
72100
83400
95500
108900
1114400
1223300
1337700

+Further lines score 20,000 points per line. +

+ +

Hotline

+

+Only lines cleared on specific rows are worth points. There is no back-to-back bonus nor T-spin bonus. +

+ + + + + + + + + + + + +
Hotline scoring method
RowScore
5100
10200
14300
17400
19500
20600
+ +

TDS

+

+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. +

+ + + + + + + + + + + + + + + + +
TDS scoring method
LinesScoreBonus
1 line 1100 * sectionNo
2 lines2300 * sectionNo
3 lines3500 * sectionNo
4 lines4800 * sectionYes
0 lines T-spin (wall kick) 0100 * section
0 lines T-spin (no wall kick)0400 * section
1 line T-spin (wall kick) 1200 * sectionYes
1 line T-spin (no wall kick) 1800 * sectionYes
2 lines T-spin 21200 * sectionYes
3 lines T-spin 31600 * sectionYes
Back-to-back bonus 50% moreYes
+ +

NES

+

+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. +

+ + + + + + + + + +
NES scoring method
LinesScore
140 * section
2100 * section
3300 * section
41200 * section
+ +

Drop scoring

+

+Some games award extra points every time the tetromino lands if the player used soft drop or hard drop. LOCKJAW can simulate these. +

+
None
Award no points.
+
Continuous
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.
+
Drop
Award 1 point per row for hard drops and for soft drops, whether or not they are interrupted.
+
Soft x1 Hard x2
Award 1 point per row for soft drops and 2 points per row for hard drops.
+
+ +

lj-scores.txt

+

+On the PC and DS version, each game's results are written to standard output and lj-scores.txt once it ends. This way, the user can copy the results to an online forum, or the user can lj | something else or tail -f lj-scores.txt | something else in order to have an external program analyze each game as it finishes. +

+ +

Scenarios

+

+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. +

+Future versions of LOCKJAW Tetromino Game will allow the user to edit scenarios and to use scenarios on handhelds. +

+ +

Options

+

+In Tetris, rules change you. But in LOCKJAW, you change the rules: +

+ +

Definitions

+
+
Frame
+
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.
+
G
+
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.
+
Hertz (Hz)
+
1 Hz is a rate of 1 event per second.
+
+ +

Gimmicks

+
+
Marathon
+
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.
+
40 lines
+
Play until you clear 40 lines, or until you top out, whatever comes first. The author's record is 1:00.70.
+
180 seconds
+
Play for three minutes, or until you top out, whatever comes first.
+
Vs. w/Items
+
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 Tetris DS.
+
Baboo!
+
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.
+
+ +

Well rules

+
+
Well width
+
Standard S.M.G. uses 10 columns; it can be set anywhere between 4 and 12.
+
Well height
+
Standard S.M.G. uses 20 rows; it can be decreased to 8.
+
Enter above ceiling
+
Normally, tetrominoes enter the well above the ceiling. But when this is turned off, tetrominoes enter inside the well.
+
Speed curve
+
This controls how fast tetrominoes fall: +
    +
  • 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 * (n - 609)) seconds.
  • +
  • In Zero, tetrominoes do not fall on their own. It's pointless for setting endurance records but useful for practicing tricky stacking methods.
  • +
  • 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.
  • +
  • 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.)
  • +
  • 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.
  • +
+
Max entry delay
+
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. +(So if you want fast play, make more lines at once.) +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. +This delay is considered a "maximum", as it decreases over the course of the game in Master and Death speed curves. +Caution: When entry delay is on, hard drop also functions as an Initial Action.
+
Piece set
+
This selects which pieces are used: +
    +
  • "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.
  • +
  • "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.
  • +
  • "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.
  • +
  • "SZSZ" deals only S and Z tetrominoes. History randomizers will deal an alternating sequence (S, Z, S, Z). It was used in a proof that S.M.G. with the Memoryless randomizer cannot be played forever.
  • +
  • "iCheat™" generates only I tetrominoes, simulating cheat modes on some other games.
  • +
  • "T-Party" generates only T tetrominoes.
  • +
+
Randomizer
+
This selects the function used to shuffle the pieces: +
    +
  • "Memoryless" has a more or less equal probability of choosing each piece at any given time, as seen in most S.M.G. implementations (including Tetris brand games from before 2001).
  • +
  • "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 played forever.
  • +
  • "Bag + 1" is like Bag but tosses a duplicate piece into each bag.
  • +
  • "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.
  • +
  • "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.
  • +
  • "History 6 rolls" is similar to strict history but sometimes (about 3.5%) generates repeats of recent pieces.
  • +
+
+ +

Movement rules

+
+
Hold piece
+
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.
+
Rotation system
+
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 .skin commands to select replacement images that contain other colors. It also does not affect lockdown behavior.
+
Floor kicks
+
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.
+
Lockdown
+
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.)
+
Lock delay
+
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.
+
Deep drop
+
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.
+
+ +

Line clear rules

+
+
Line clear delay
+
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.
+
Clear gravity
+
Controls what happens to blocks after a line clear. +Naive means that blocks move down by exactly the number of cleared lines below them, potentially causing floating blocks. +In Sticky, tetrominoes stick together when they lock, and contiguous groups fall together. +It's possible for a group to fall past the cleared lines, causing chain reactions. +Sticky by color is similar to Sticky except only blocks of a single color stick together. +Cascade treats each piece as a separate entity unless Gluing is turned on.
+
Gluing
+
When turned on, this controls how pieces glue themselves together after they land: +
    +
  • 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. Caution: This mode is displayed incorrectly if the current skin lacks the appropriate ljconn file.
  • +
  • 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.
  • +
+
+
Scoring
+
Choose a scoring method for line clears, as described above.
+
Drop scoring
+
Choose a scoring method for drops, as described above.
+
T-spin detection
+
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: +
    +
  • The line is completed with a T tetromino.
  • +
  • At least 3 of the 4 boxes diagonal to the center of the tetromino are either the wall or occupied with a block.
  • +
  • The tetromino has not shifted sideways or down by one or more spaces since it was last rotated.
  • +
+3-corner T no kick adds an additional restriction: The rotation itself did not move the tetromino.
+
Garbage
+
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.
+
+ +

Control options

+
+
Max sideways delay
+
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 ms increments from 16.7 ms to 300 ms. +This delay is considered a "maximum", as it decreases over the course of the game in Master and Death speed curves.
+
Sideways speed
+
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.
+
Initial sideways motion
+
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).
+
Initial rotation
+
When this is turned on, the player can rotate a piece before it enters by holding down a rotate button.
+
Allow diagonal motion
+
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.
+
Soft drop speed
+
Controls how fast the tetromino moves downward in a soft drop. Can be set to 1G, 1/2G, or 1/3G.
+
Soft drop
+
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.
+
Hard drop
+
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.
+
+ +

Display options

+

+The details may vary between the PC version and the GBA and DS versions: +

+
Shadow
+
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.
+
Hide blocks in well
+
When turned on, hides the blocks that have locked down in the well. Useful for practicing for "invisible challenge" in another game.
+
Next pieces
+
Controls the number of previewed pieces displayed next to the well.
+
Smooth gravity
+
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.
+
Next above shadow
+
Controls the number of previewed pieces displayed above the shadow. (PC version only)
+
Drop trails
+
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)
+
Pause on task switch
+
When turned on, automatically pauses the game when the player switches to another window. (PC version only; DS version always pauses on lid close)
+
Record all games
+
Automatically records each game to demo.ljm. Remember to rename your record-setting replays. (PC version only)
+
Display
+
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)
+

+The PC version saves these options to lj.ini every time you exit the options screen. Other options not shown are also saved to lj.ini and can be changed with a text editor. +

+ +

Discussion

+

+If you want to discuss the game, you can do so at TetrisConcept.com forum. +

+If you want a Tetris shot, ask your doctor. +

+ +

Legal

+
+

+Copyright 2006–2008 Damian Yerrick <tepples+lockjaw (at) spamcop (full stop) net>. This manual is under the following license: +

+This work is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this work. +

+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: +

    +
  1. 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.
  2. +
  3. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original work.
  4. +
  5. This notice may not be removed or altered from any source distribution.
  6. +

+The term "source" refers to the preferred form of a work for making changes to it. +

+
+

+The LOCKJAW software described by this manual is distributed under the GNU General Public License, version 2 or later, with absolutely no warranty. See COPYING.txt for details. +

+* 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. +

+
+ + \ No newline at end of file diff -r 000000000000 -r c84446dfb3f5 TODO.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TODO.txt Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,13 @@ +As of LOCKJAW 0.42, TODO.txt is being migrated to a bug list format; +see BUGS.txt. Not yet migrated: + +cdsboy wants: + * DTET rush mode (please describe this in detail) + * DTET lives (please describe this in detail) + * Editable game keys presets (please describe the UI) + +DIGITAL wants: + * Ability to switch tetromino colors in game (huh?) + +Zed0 wants: + * Tetrinet client diff -r 000000000000 -r c84446dfb3f5 binzip.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/binzip.in Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,25 @@ +alleg42.dll +lj.exe +lj.dat +COPYING.txt +GPL.txt +README.html +CHANGES.txt +TODO.txt +BUGS.txt +ljbg.jpg +ljblocks.bmp +ljconn.bmp +ljblocks-sega.bmp +ljconn-sega.bmp +bgm.s3m +bgm-rhythm.s3m +sound.dat +lj.gba +lj.nds +docs/ljlogo192.png +docs/titlebarbg.png +docs/ljsnap033.png +docs/ijlo-stz.png +docs/appicon.ico +docs/ljhtml.css diff -r 000000000000 -r c84446dfb3f5 docs/Compiling_on_Windows.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/Compiling_on_Windows.txt Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,103 @@ +______________________________________________________________________ + +Guide to compiling LOCKJAW on Microsoft Windows +by Lardarse and tepples + +You will need the following files: + +* Dev-C++ 5.0 Beta + http://prdownloads.sourceforge.net/dev-cpp/devcpp-4.9.9.2_setup.exe +* devkitPro Updater + http://www.devkitpro.org/ +* Allegro 4.2.0 + http://www.pineight.com/lj/all420.zip +* Minimal DirectX 7 SDK for MinGW + http://alleg.sourceforge.net/files/dx70_mgw.zip +* libogg 1.1.3 + http://www.pineight.com/lj/libogg-1.1.3.zip +* libvorbis 1.1.2 + http://www.pineight.com/lj/libvorbis-1.1.2.zip +* DUMB 0.9.3 + http://www.pineight.com/lj/dumb-0.9.3.zip +* JPGalleg 2.5 + http://www.pineight.com/lj/jpgalleg-2.5.tar.gz + +______________________________________________________________________ +Install MinGW + +Put all of those files into a folder on your desktop. Run the Dev-C++ +installer, installing into C:\Dev-Cpp, and using the default settings, +unless you have a reason for not wanting to associate C source file +types with Dev-C++. + +______________________________________________________________________ +Install MSYS and devkitARM + +Run the devkitPro Updater. It will ask you if you want to get a newer +version. Answer yes to this, and then install everything that it asks +to, except devkitPSP and devkitPPC. MSYS should be installed into +C:\devkitPro\msys, and just press Enter to all of the questions that +it asks when installing it. The other parts will install without user +intervention, unless the downloading is interrupted. + +______________________________________________________________________ +Make MinGW and MSYS available + +Right-click My Computer and go to Properties -> Advanced -> +Environment Variables. Add MINGDIR = C:\Dev-Cpp and make sure +that Path starts with C:\Dev-Cpp\Bin;c:\devkitPro\msys\bin; + +Extract dx70_mgw.zip into C:\Dev-Cpp, over-writing files as +necessary. Extract the other 5 files that were downloaded earlier +into C:\, where each one will be in its own folder. Now open a +command prompt (Start -> Run -> cmd) and enter the following commands: + +______________________________________________________________________ +Make and install required libraries + +cd \ +cd allegro +fix.bat mingw32 +make +make install +cd \ +cd libogg-1.1.3 +sh configure --prefix=C:\Dev-Cpp --disable-shared +make +make install +cd \ +cd libvorbis-1.1.2 +sh configure --prefix=C:\Dev-Cpp --disable-shared +make +make install +cd \ +cd dumb-0.9.3 +make + +This command will ask you 2 questions. Answer M to the first +and Y to the second. + +make install +cd \ +cd jpgalleg-2.5 +fix.bat mingw32 +make +make install + +At this point you can now close this window. + +Now open another command window and navigate to the folder where you +have Lockjaw installed. You may prefer to use a different folder +than your usual playing folder. If you are going to be compiling +a lot, and you are using Windows XP, then you may want to install +the Open Command Window Here powertoy: +http://www.microsoft.com/windowsxp/downloads/powertoys/xppowertoys.mspx + +Once you are in the folder, just type in make, and within a few +seconds, it should be compiled. The lj.exe will be roughly twice the +size of that in the official distribution; you can fix this with UPX. +http://upx.sourceforge.net/ + +Now to try to do something intersting with it... + +Now grab yourself a drink. You deserve it... diff -r 000000000000 -r c84446dfb3f5 docs/appicon.ico Binary file docs/appicon.ico has changed diff -r 000000000000 -r c84446dfb3f5 docs/appicon.xcf Binary file docs/appicon.xcf has changed diff -r 000000000000 -r c84446dfb3f5 docs/dsicon.bmp Binary file docs/dsicon.bmp has changed diff -r 000000000000 -r c84446dfb3f5 docs/favicon.ico Binary file docs/favicon.ico has changed diff -r 000000000000 -r c84446dfb3f5 docs/ijlo-stz.png Binary file docs/ijlo-stz.png has changed diff -r 000000000000 -r c84446dfb3f5 docs/ljhtml.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/ljhtml.css Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,47 @@ +body { + margin: 0 0 1em 0; font-family: Bitstream Vera Sans, sans-serif +} +h1 { + height: 192px; margin: 0; padding: 0 1em; background: url(titlebarbg.png); color: #FFF +} +h1 img { + vertical-align: middle; +} +#content { + margin: 0; padding: 1em 2em; + color: #000; background: #FFF +} +#linkbar { + position: absolute; top: 1em; left: 480px; color: #fff; margin: 0; font-size: 120%; + padding-left: 1em +} +#linkbar a:link { color: #99f } +#linkbar a:visited { color: #f69 } +#linkbar a:hover { color: #ccf } +#linkbar a:active { color: #fff } + +.precis { + margin: 2em 2em 2em 2em; + border: 1px solid #c00; + background: #ffc; + padding: 1em 2em 1em 2em; + font-size: 120%; +} + +.precis-ad { + margin: 2em 160px 2em 2em; + border: 1px solid #c00; + background: #ffc; + padding: 1em 2em 1em 2em; + font-size: 120%; +} + +.screenshot { + float: right; + margin: 0 0 1em 2em; + font-size: 80%; +} + +img.screenshot, .screenshot img { + border: 0px none; +} diff -r 000000000000 -r c84446dfb3f5 docs/ljlogo192.png Binary file docs/ljlogo192.png has changed diff -r 000000000000 -r c84446dfb3f5 docs/ljsnap018.png Binary file docs/ljsnap018.png has changed diff -r 000000000000 -r c84446dfb3f5 docs/titlebarbg.png Binary file docs/titlebarbg.png has changed diff -r 000000000000 -r c84446dfb3f5 dsmakefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dsmakefile Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,136 @@ +# Makefile for Nintendo DS version of LOCKJAW +# +# Copr. 2006-2007 Damian Yerrick +# +# This work is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +srcdir := src +objdir := obj/ds +EXE := $(objdir)/ljds.elf +EXE7 := $(objdir)/ljds7.elf +ASSETSFILE := $(objdir)/assets.s +ASSETS := $(srcdir)/text.chr $(srcdir)/gbablk.chr $(objdir)/vwfont.bin +ARMGCC=arm-eabi-gcc +ARMOBJ=arm-eabi-objcopy +CFLAGS7=-std=gnu99 -Wall -O2 -mcpu=arm7tdmi -mtune=arm7tdmi -fomit-frame-pointer -ffast-math -mthumb-interwork +CFLAGS9=-std=gnu99 -Wall -O2 -mcpu=arm9tdmi -mtune=arm9tdmi -fomit-frame-pointer -ffast-math -mthumb-interwork -DDEBRIEF_SHORT -DHAS_FOPEN +LDLIBS9=-lfat -lnds9 +NDSLIB_INCLUDE=$(DEVKITPRO)/libnds/include +NDSLIB_LIB=$(DEVKITPRO)/libnds/lib +DEPOBJS := \ +$(objdir)/ljds.o $(objdir)/ljplay.o $(objdir)/lj.o \ +$(objdir)/gimmicks.o $(objdir)/wktables.o $(objdir)/macro.o \ +$(objdir)/gbaopt.o $(objdir)/speed.o $(objdir)/options.o \ +$(objdir)/fontdraw.o $(objdir)/dssleep.o $(objdir)/random.o \ +$(objdir)/ljlocale.o $(objdir)/debrief.o $(objdir)/dsdebrief.o \ +$(objdir)/gbamenus.o $(objdir)/ljpath.o $(objdir)/dsjoy.o +OBJS := $(DEPOBJS) $(ASSETSFILE) +EMU := start +GAMEICON := docs/dsicon.bmp + +CC := gcc +CFLAGS := + +# The wintermute-approved way to ensure devkitARM is in the PATH +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=devkitPro) +endif +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM) +endif +export PATH := $(DEVKITARM)/bin:$(PATH) + +.PHONY: run clean cf + +run: lj.nds + $(EMU) $< + +lj.nds: $(objdir)/ljds.bin $(objdir)/ljds7.bin $(GAMEICON) + ndstool -c $@ -9 $(objdir)/ljds.bin -7 $(objdir)/ljds7.bin -b $(GAMEICON) "LOCKJAW DS;The Soviet Mind Game" + +%.nds.gba: %.nds + dsbuild $< -o $@ + +%.bin: %.elf + $(ARMOBJ) -O binary $< $@ + +$(EXE): $(OBJS) + $(ARMGCC) -g -mthumb-interwork -mno-fpu -specs=ds_arm9.specs $^ -L$(NDSLIB_LIB) $(LDLIBS9) -o $@ + +$(objdir)/ljds7.elf: $(objdir)/dsarm7.o $(objdir)/lookup_tables7.o $(objdir)/dssound7.o + $(ARMGCC) -g -mthumb-interwork -mno-fpu -specs=ds_arm7.specs $^ -L$(NDSLIB_LIB) -lnds7 -o $@ + +$(objdir)/%7.o: $(srcdir)/%7.c + $(ARMGCC) $(CFLAGS7) -I$(NDSLIB_INCLUDE) -MMD -DARM7 -c $< -o $@ + @cp $(objdir)/$*.d $(objdir)/$*.P; \ + sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ + -e '/^$$/ d' -e 's/$$/ :/' < $(objdir)/$*.d >> $(objdir)/$*.P; \ + rm -f $(objdir)/$*.d + +$(objdir)/%.o: $(srcdir)/%.c $(srcdir)/ljds.h + $(ARMGCC) $(CFLAGS9) -I$(NDSLIB_INCLUDE) -MMD -DARM9 -c $< -o $@ + @cp $(objdir)/$*.d $(objdir)/$*.P; \ + sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ + -e '/^$$/ d' -e 's/$$/ :/' < $(objdir)/$*.d >> $(objdir)/$*.P; \ + rm -f $(objdir)/$*.d + +%.o: %.s + $(ARMGCC) $(CFLAGS9) -I$(NDSLIB_INCLUDE) -MMD -DARM9 -c $< -o $@ + @cp $(objdir)/$*.d $(objdir)/$*.P; \ + sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ + -e '/^$$/ d' -e 's/$$/ :/' < $(objdir)/$*.d >> $(objdir)/$*.P; \ + rm -f $(objdir)/$*.d + +$(ASSETSFILE): $(ASSETS) + bin2s $^ > $@ + +# Handle header dependencies + +-include $(DEPOBJS:%.o=%.P) +# The master copy of the variable width font library is in another folder + +src/fontdraw%: /e/games/ac/double/rac/src/fontdraw% + cp $< $@ + +#ejector is at http://jimprice.com/jim-soft.shtml#eject +#and is not necessary for building the program +cf: lj.nds + cp lj.nds /h/ + ejector H: + +# PC side rules + +tools/fontconv.exe: tools/fontconv.o + gcc -Wall -s $^ -lalleg -o $@ + +tools/mktables.exe: tools/mktables.o + gcc -Wall -s $^ -o $@ + +tools/%.o: tools/%.c + gcc -Wall -O2 -std=gnu99 -c $< -o $@ + +$(objdir)/vwfont.bin: tools/fontconv.exe $(srcdir)/font.bmp + $^ $@ + +$(objdir)/lookup_tables7.s: tools/mktables.exe + $< + +# Cleanup rules + +clean: + -rm $(objdir)/*.s + -rm $(objdir)/*.o + -rm $(objdir)/*.P + diff -r 000000000000 -r c84446dfb3f5 gbamakefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gbamakefile Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,135 @@ +# Makefile for Game Boy Advance version of LOCKJAW +# +# Copr. 2006-2007 Damian Yerrick +# +# This work is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +srcdir := src +objdir := obj/gba +EXE := $(objdir)/lj.elf +ASSETSFILE := $(objdir)/assets.s +INCGBA := -I$(DEVKITPRO)/libgba/include +LIBGBA := -L$(DEVKITPRO)/libgba/lib -lgba +THUMB_CFLAGS := -mthumb -Wall -O2 -std=gnu99 -mcpu=arm7tdmi \ +-DFONTDRAW_SPLIT_COMPILE -DNO_DATETIME -DDEBRIEF_SHORT +ARM_CFLAGS := -marm -Wall -O2 -std=gnu99 -mcpu=arm7tdmi \ +-DFONTDRAW_SPLIT_COMPILE +CC := arm-eabi-gcc -mthumb-interwork +LD := arm-eabi-gcc -mthumb-interwork +LDFLAGS := -mthumb -Wall -specs=gba_mb.specs + +MUSICOBJS := $(objdir)/gbasound.o $(objdir)/gbanotefreq.o +MUSICLIBS := + +# The wintermute-approved way to ensure devkitARM is in the PATH +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=devkitPro) +endif +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM) +endif +export PATH := $(DEVKITARM)/bin:$(PATH) + +# Objects + +LDLIBS := $(LIBGBA) +DEPOBJS := \ +$(objdir)/ljgba.o $(objdir)/gbaopt.o $(objdir)/gbaisr.iwram.o \ +$(objdir)/ljplay.o $(objdir)/lj.o $(objdir)/speed.o $(objdir)/gimmicks.o \ +$(objdir)/options.o $(objdir)/wktables.o $(objdir)/fontdraw.o \ +$(objdir)/fontdraw.iwram.o $(objdir)/macro.o $(objdir)/random.o \ +$(objdir)/gba_asm.o $(objdir)/ljlocale.o $(objdir)/debrief.o \ +$(objdir)/dsdebrief.o $(objdir)/gbamenus.o $(objdir)/dsjoy.o \ +$(MUSICOBJS) +ASSETS := $(srcdir)/text.chr $(srcdir)/gbablk.chr $(objdir)/vwfont.bin +#deleted: ljpc pcjoy options debrief ljreplay ljmusic + +OTHEROBJS := $(ASSETSFILE) + +run: lj.gba + start $< + +#ejector is at http://jimprice.com/jim-soft.shtml#eject +#and is not necessary for building the program +cf: lj.gba + cp lj.gba /h/gba/ + ejector H: + +$(EXE): $(DEPOBJS) $(OTHEROBJS) + $(LD) $(LDFLAGS) $^ $(MUSICLIBS) $(LDLIBS) -o $@ + +$(ASSETSFILE): $(ASSETS) + bin2s $^ > $@ + +# Compilation rules + +%.gba: $(objdir)/%.elf + arm-eabi-objcopy -O binary $< $@ + gbafix -tLOCKJAW $@ + +$(objdir)/%.iwram.o: $(srcdir)/%.iwram.c + $(CC) $(ARM_CFLAGS) -MMD -c -o $@ $< $(INCGBA) + @cp $(objdir)/$*.iwram.d $(objdir)/$*.iwram.P; \ + sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ + -e '/^$$/ d' -e 's/$$/ :/' < $(objdir)/$*.iwram.d >> $(objdir)/$*.iwram.P; \ + rm -f $(objdir)/$*.iwram.d + +$(objdir)/%.o: $(srcdir)/%.c + $(CC) $(THUMB_CFLAGS) -MMD -c -o $@ $< $(INCGBA) + @cp $(objdir)/$*.d $(objdir)/$*.P; \ + sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ + -e '/^$$/ d' -e 's/$$/ :/' < $(objdir)/$*.d >> $(objdir)/$*.P; \ + rm -f $(objdir)/$*.d + +$(objdir)/%.o: $(srcdir)/%.s + $(CC) -MMD -c -o $@ $< $(INCGBA) + @cp $(objdir)/$*.d $(objdir)/$*.P; \ + sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ + -e '/^$$/ d' -e 's/$$/ :/' < $(objdir)/$*.d >> $(objdir)/$*.P; \ + rm -f $(objdir)/$*.d + +$(objdir)/%.o: $(objdir)/%.s + $(CC) -c -o $@ $< + +# The master copy of the variable width font library is in another folder + +src/fontdraw%: /e/games/ac/double/rac/src/fontdraw% + cp $< $@ + +src/font.bmp: /e/games/ac/double/rac/src/font.bmp + cp $< $@ + +$(objdir)/vwfont.bin: tools/fontconv.exe $(srcdir)/font.bmp + $^ $@ + +tools/fontconv.exe: tools/fontconv.o + gcc -Wall -s $^ -lalleg -o $@ + +tools/%.o: tools/%.c + gcc -Wall -O2 -std=gnu99 -c $< -o $@ + +# Header dependencies + +-include $(DEPOBJS:%.o=%.P) + + +# Cleanup rules +.PHONY: clean run cf + +clean: + -rm $(objdir)/*.s + -rm $(objdir)/*.o + -rm $(objdir)/*.P diff -r 000000000000 -r c84446dfb3f5 makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/makefile Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,108 @@ +# Makefile for Allegro version of LOCKJAW +# under Windows and Linux +# +# Copr. 2006-2007 Damian Yerrick +# +# This work is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +EXE := lj.exe +CFLAGS := -Wall -O2 -std=gnu99 -DWITH_REPLAY=1 -DHAS_FPU +CC := gcc +LD := gcc +LDFLAGS := -Wall -s -mwindows +srcdir := src +objdir := obj/win32 + +# Set this to the drive letter of your CF or SD card adapter +# so that 'make cf' works +# for info about Ejector by Jim Price, see the GBA makefile +CFDRIVE := h + +MUSICOBJS := $(objdir)/ljvorbis.o +MUSICLIBS := -laldmb -ldumb -lvorbisfile -lvorbis -logg + +LDLIBS := -lshfolder -ljpgal -lalleg +LINUXLDLIBS := -ljpgal `allegro-config --libs` +DEPOBJS := $(objdir)/ljpc.o $(objdir)/lj.o $(objdir)/ljplay.o \ +$(objdir)/pcjoy.o $(objdir)/gimmicks.o $(objdir)/random.o \ +$(objdir)/wktables.o $(objdir)/speed.o $(objdir)/options.o \ +$(objdir)/debrief.o $(objdir)/macro.o $(objdir)/ljreplay.o \ +$(objdir)/ljmusic.o $(objdir)/old_pc_options.o $(objdir)/pcsound.o \ +$(objdir)/pcdebrief.o $(objdir)/ljlocale.o $(objdir)/scenario.o \ +$(objdir)/ljpath.o \ +$(MUSICOBJS) + +OTHEROBJS := $(objdir)/winicon.o + +.PHONY: pc clean lj.gba lj.nds cf all run + +# Scripts to coordinate building the PC, GBA, and DS versions + +pc: $(EXE) + +run: $(EXE) + ./lj + +all: $(EXE) lj.gba lj.nds + +cf: /$(CFDRIVE)/gba/lj.gba /$(CFDRIVE)/lj.nds + #ejector from http://www.jimprice.com/jim-soft.shtml#eject + ejector $(CFDRIVE): + +/$(CFDRIVE)/gba/lj.gba: lj.gba + cp $< $@ + +/$(CFDRIVE)/lj.nds: lj.nds + cp $< $@ + +lj.gba: + +make -f gbamakefile $@ + +lj.nds: + +make -f dsmakefile $@ + +# Content of executable + +$(EXE): $(DEPOBJS) $(OTHEROBJS) + $(LD) $(LDFLAGS) $^ $(MUSICLIBS) $(LDLIBS) -o $@ + +# kesiev's makefile for GNU/Linux + +linux: $(DEPOBJS) + $(LD) $(LDFLAGS) $^ $(MUSICLIBS) $(LINUXLDLIBS) -o lj + +# Compilation rules + +$(objdir)/%.o: $(srcdir)/%.c + $(CC) $(CFLAGS) -MMD -c -o $@ $< + @cp $(objdir)/$*.d $(objdir)/$*.P; \ + sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ + -e '/^$$/ d' -e 's/$$/ :/' < $(objdir)/$*.d >> $(objdir)/$*.P; \ + rm -f $(objdir)/$*.d + +$(objdir)/%.o: src/%.rc docs/favicon.ico + windres -i $< -o $@ + +# Header dependencies + +-include $(DEPOBJS:%.o=%.P) + + +# Cleanup rules + +clean: + -rm $(objdir)/*.o + -rm $(objdir)/*.P diff -r 000000000000 -r c84446dfb3f5 mkzip.bat --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mkzip.bat Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,16 @@ +@echo off +echo Compressing +upx -9 lj.exe + +echo Sending zip to local copy of Pin Eight +zip -9 -u lj-src.zip -@ < zip.in + +zip -9 -u lj.zip -@ < binzip.in + +copy lj.zip E:\p8\lj\ +copy lj-src.zip E:\p8\lj\ +copy lj-contrib.zip E:\p8\lj\ +copy README.html E:\p8\lj\ +copy docs\ljhtml.css E:\p8\lj\docs\ +start E:\p8\lj +echo Now FTP this to the server. \ No newline at end of file diff -r 000000000000 -r c84446dfb3f5 obj/ds/index.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/obj/ds/index.txt Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,1 @@ +Windows object files go here. diff -r 000000000000 -r c84446dfb3f5 obj/gba/index.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/obj/gba/index.txt Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,1 @@ +Windows object files go here. diff -r 000000000000 -r c84446dfb3f5 obj/win32/index.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/obj/win32/index.txt Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,1 @@ +Windows object files go here. diff -r 000000000000 -r c84446dfb3f5 src/debrief.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/debrief.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,603 @@ +/* PC frontend for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ +#include "ljplay.h" +#include "options.h" +#include +#ifndef NO_DATETIME +#include +#endif + +#define USING_DEBRIEF_PAGE 1 +#define MIN_RECTUM_ROWS 4 +#define MIN_ZIGZAG_ROWS 2 + +// On newlib, use a variant of sprintf that doesn't handle floating +// point formats. On other C libraries, use standard sprintf. +#ifdef HAS_FPU +#define siprintf sprintf +#ifndef HAS_FOPEN +#define HAS_FOPEN +#endif +#define DEBRIEF_TO_STDOUT +#endif + +#ifdef HAS_FOPEN +#include "ljpath.h" // for ljfopen +#endif + +const char *const debriefBoolNames[2] = { + "Off", "On" +}; + +typedef struct TimeResultMSF { + unsigned int pieces_100m; + unsigned int garbage_100m; + unsigned int minutes; + unsigned char seconds; + unsigned char frames; +} TimeResultMSF; + +void frames2msf(unsigned int gameTime, + unsigned int nPieces, + unsigned int outGarbage, + TimeResultMSF *out) { + unsigned int gameSeconds = gameTime / 60U; + unsigned int gameMinutes = gameSeconds / 60U; + out->pieces_100m = gameTime + ? 360000ULL * nPieces / gameTime + : 0; + out->garbage_100m = gameTime + ? 360000ULL * outGarbage / gameTime + : 0; + out->minutes = gameMinutes; + out->seconds = gameSeconds - gameMinutes * 60U; + out->frames = gameTime - gameSeconds * 60U; +} + +static unsigned int countBlocksLeft(const LJField *p) { + unsigned int n = 0; + + for (int y = 0; y < LJ_PF_HT; ++y) { + for (int x = 0; x < LJ_PF_WID; ++x) { + if (p->b[y][x]) { + ++n; + } + } + } + return n; +} + +/** + * Calculates the number of rows starting at the bottom that + * conform to a zigzag pattern. + * @param p the playfield to test + * @return the number of rows successfully completed + */ +unsigned int calcZigzagGrade(const LJField *p) { + unsigned int hole = p->leftWall; + int delta = 1; + + for (size_t y = 0; y < p->ceiling; ++y) { + + // invariant: + // at this point, y equals the number of rows known to conform + for (size_t x = p->leftWall; x < p->rightWall; ++x) { + unsigned int blk = p->b[y][x]; + + // A block should be in all cells of the row + // except for the hole cell, which should be empty. + if (x == hole) { + + // if there's a block where a hole should be, this row + // doesn't conform, but all the rows below do conform + if (blk) { + return y; + } + } else { + + // if there's a hole where a block should be, this row + // doesn't conform, but all the rows below do conform + if (!blk) { + return y; + } + } + } + + // if this hole isn't covered up on the next row, + // this row doesn't conform either + // changed in 0.43 after a clarification from Kitaru + if (!p->b[y + 1][hole]) { + return y; + } + + // by now we know that the row conforms, + // so move the hole for the next row + if (hole == p->rightWall - 1) { + delta = -1; + } else if (hole == p->leftWall) { + delta = 1; + } + hole += delta; + + } + return p->ceiling; +} + + +/** + * Calculates the number of rows in a playfield that were prepared + * for an I tetromino. + * Rule for this pattern: + * 1. Only one hole on the bottom row. + * 2. This column must be unoccupied below the ceiling. + * 3. Conforming rows must be full except for the hole. + * + * Easy test procedure for this pattern: + * Level 4 garbage + * @return the number of rows that conform to constraint 3, or 0 if the + * field does not conform to constraint 1 or 2. + */ +unsigned int calcRectumGrade(const LJField *p) { + unsigned int hole = LJ_PF_WID; + + // search for the first hole on the bottom row + for (unsigned int x = p->leftWall; + x < p->rightWall; + ++x) { + if (!p->b[0][x]) { + hole = x; + break; + } + } + + // If there is no hole in the bottom row, then 0 rows conform. + // This shouldn't happen in standard smg because the line would be + // cleared, but eventually LJ will support games other than SMG, + // and checking the array bounds is O(1), so why not? + if (hole >= p->rightWall) { + return 0; + } + + // make sure that the row is clear through the whole visible + // portion of the playfield + for (unsigned int y = 0; y < p->ceiling; ++y) { + + // If this column isn't empty, the whole field doesn't conform. + if (p->b[y][hole]) { + return 0; + } + } + + for (unsigned int y = 0; y < p->ceiling; ++y) { + + // At this point, the bottom y rows conform. + for (unsigned int x = p->leftWall; x < p->rightWall; ++x) { + + // Disregarding the hole column, if there's an empty cell + // in this row, then this row does not conform. + if (x != hole && !p->b[y][x]) { + return y; + } + } + } + return p->ceiling; +} + +/** + * Converts a rank number (0-19) to a text rank. + * 0-9: 10-1 kyu; 10-99: 1-90 dan; 100+: grandmaster + * @return number of characters written + */ +static size_t printSecretGrade(char *dst, int rank) { + if (rank >= 100) { + return siprintf(dst, "GM"); + } else if (rank >= 10) { + return siprintf(dst, "S%d", rank - 9); + } else { + return siprintf(dst, "%d", 10 - rank); + } +} + +static size_t printFracG(char *dst, unsigned int value) { + if (value == 0) { + return siprintf(dst, "Instant"); + } else if (value == 1) { + return siprintf(dst, "1G"); + } else { + return siprintf(dst, "1/%dG", value); + } +} + +#include // remove once fourcc transition is complete +static size_t printFourCC(char *dst, FourCC f) { + const char *data = ljGetFourCCName(f); + if (data) { + int pos = 0; + for(pos = 0; *data; ++pos) { + dst[pos] = *data++; + } + return pos; + } else { + int pos = 0; + for(pos = 0; f.c[pos] && pos < 4; ++pos) { + dst[pos] = f.c[pos]; + } + return pos; + } +} + +size_t buildOptionsReportPage(char *dst, const LJView *v) { + size_t pos = 0; + + +#if USING_DEBRIEF_PAGE + const LJField *p = v->field; + + pos += siprintf(dst + pos, + "Options\n\n" + "Well: %dx%d%s, Enter: %s\n" + "Speed: ", + p->rightWall - p->leftWall, + p->ceiling, + v->hidePF ? " invisible" : "", + p->enterAbove ? "Above" : "Below"); + pos += printFourCC(dst + pos, + optionsSpeedCurveNames[(size_t)p->speedState.curve]); + pos += siprintf(dst + pos, + ", ARE: %d ms\n" + "Randomizer: ", + v->field->areStyle * 50 / 3); + pos += printFourCC(dst + pos, + optionsRandNames[(size_t)p->randomizer]); + pos += siprintf(dst + pos, " of "); + pos += printFourCC(dst + pos, + optionsPieceSetNames[(size_t)p->pieceSet]); + pos += siprintf(dst + pos, "\nHold: "); + pos += printFourCC(dst + pos, + optionsHoldStyleNames[(size_t)p->holdStyle]); + pos += siprintf(dst + pos, ", Rotation: "); + pos += printFourCC(dst + pos, + optionsRotNames[(size_t)p->rotationSystem]); + if (p->maxUpwardKicks < 20) { + pos += siprintf(dst + pos, + " %d FK", + (int)p->maxUpwardKicks); + } + if (v->control->initialRotate) { + pos += siprintf(dst + pos, "+initial"); + } + + pos += siprintf(dst + pos, + "\nLock: "); + if (p->setLockDelay >= 128) { + pos += siprintf(dst + pos, "Never"); + } else { + if (p->setLockDelay > 0) { + pos += siprintf(dst + pos, + "%d ms ", + p->setLockDelay * 50 / 3); + } + pos += printFourCC(dst + pos, + optionsLockdownNames[(size_t)p->lockReset]); + } + pos += siprintf(dst + pos, + ", Deep: %s\n", + debriefBoolNames[p->bottomBlocks]); + + pos += siprintf(dst + pos, + "Line clear: %d ms ", + p->speed.lineDelay * 50 / 3); + pos += printFourCC(dst + pos, + optionsGravNames[(size_t)p->clearGravity]); + pos += siprintf(dst + pos, + ", Gluing: "); + pos += printFourCC(dst + pos, + optionsGluingNames[(size_t)p->gluing]); + pos += siprintf(dst + pos, + "\nDrop score: "); + pos += printFourCC(dst + pos, + optionsDropScoringNames[(size_t)p->dropScoreStyle]); + pos += siprintf(dst + pos, + ", T-spin: "); + pos += printFourCC(dst + pos, + optionsTspinNames[(size_t)p->tSpinAlgo]); + pos += siprintf(dst + pos, + "\nGarbage: "); + pos += printFourCC(dst + pos, + optionsGarbageNames[(size_t)p->garbageStyle]); + pos += siprintf(dst + pos, + ", DAS: %d ms ", + v->control->dasDelay * 50 / 3); + pos += printFracG(dst + pos, v->control->dasSpeed); + pos += siprintf(dst + pos, + "\nSoft drop: "); + pos += printFracG(dst + pos, v->control->softDropSpeed + 1); + dst[pos++] = ' '; + pos += printFourCC(dst + pos, + optionsZangiNames[v->control->softDropLock]); + pos += siprintf(dst + pos, + ", Hard drop: "); + pos += printFourCC(dst + pos, + optionsZangiNames[v->control->hardDropLock]); + pos += siprintf(dst + pos, "\nShadow: "); + pos += printFourCC(dst + pos, + optionsShadowNames[v->hideShadow]); + pos += siprintf(dst + pos, + ", Next: %d\n", + v->nextPieces); +#else + pos += siprintf(dst, "\n\nDebrief disabled.\n"); +#endif + + dst[pos] = 0; + return pos; +} + +#ifdef DEBRIEF_SHORT +static const char *const naiveGravity[8] = { + "1L", "2L", "3L", "4L", + "T1", "T2", "T3" +}; +static const char *const cascadeGravity[8] = { + "1L", "2L", "3L", "4L", + "5L", "6L", "7L", "8L+" +}; +#else +static const char *const naiveGravity[8] = { + "single", "double", "triple", "home run", + "T single", "T double", "T triple" +}; +static const char *const cascadeGravity[8] = { + "single", "double", "triple", "quad", + "5L", "6L", "7L", "8L+" +}; +#endif + +static size_t printLineCounts(char *dst, const LJField *p) { + size_t pos = 0; + const char *const *names = (p->clearGravity == LJGRAV_NAIVE) + ? naiveGravity : cascadeGravity; + dst[pos++] = '('; + for (int i = 0; i < 8; ++i) { + if (names[i]) { + pos += siprintf(dst + pos, + "%s%s: %u", + i ? "; " : "", + names[i], + p->nLineClears[i]); + } + } + dst[pos++] = ')'; + dst[pos++] = '\n'; + return pos; +} + + +size_t buildDebriefPage(char *dst, const LJView *v) { + size_t pos = 0; + +#if USING_DEBRIEF_PAGE +#ifndef NO_DATETIME + const time_t finishTimeUNIX = time(NULL); + const struct tm *finishTime = localtime(&finishTimeUNIX); + char finishTimeStr[64]; + + /* I would have used + strftime(finishTimeStr, sizeof(finishTimeStr), "%Y-%m-%d at %H:%M", finishTime); + but it brings in the floating-point library on GBA/DS. */ + siprintf(finishTimeStr, + "%04d-%02d-%02d at %02d:%02d", + finishTime->tm_year + 1900, + finishTime->tm_mon + 1, finishTime->tm_mday, + finishTime->tm_hour, finishTime->tm_min); +#endif + + const LJField *p = v->field; + unsigned long int keys_100m = p->nPieces + ? 100U * v->control->presses / p->nPieces + : 666; + TimeResultMSF gameMSF, activeMSF; + const char *wordPieces = (p->nPieces != 1) ? "tetrominoes" : "tetromino"; + if (p->pieceSet == LJRAND_234BLK) { + wordPieces = (p->nPieces != 1) ? "pieces" : "piece"; + } + + /* Secret grades */ + unsigned int nBlocksLeft = countBlocksLeft(p); + unsigned int nZigzagRows = calcZigzagGrade(p); + unsigned int nRectumRows = calcRectumGrade(p); + + pos += siprintf(dst + pos, + "Result:\n\n%s ", + v->control->countdown <= 0 ? "Cleared" : "Stopped"); + pos += printFourCC(dst + pos, gimmickNames[p->gimmick]); + pos += siprintf(dst + pos, + " at level %d\n", + p->speedState.level); +#if !defined(NO_DATETIME) || defined(WITH_REPLAY) +#ifndef NO_DATETIME + pos += siprintf(dst + pos, "on %s", finishTimeStr); +#endif +#ifdef WITH_REPLAY + if (p->reloaded) { + pos += siprintf(dst + pos, " from saved state"); + } +#endif + dst[pos++] = '\n'; +#endif + + frames2msf(p->gameTime, p->nPieces, p->outGarbage, &gameMSF); + frames2msf(p->activeTime, p->nPieces, p->outGarbage, &activeMSF); + + pos += siprintf(dst + pos, + "Played %d %s in %u:%02u.%02u (%u.%02u/min)\n", + p->nPieces, + wordPieces, + gameMSF.minutes, + (unsigned int)gameMSF.seconds, + gameMSF.frames * 5U / 3U, + (unsigned int) (gameMSF.pieces_100m / 100), + (unsigned int) (gameMSF.pieces_100m % 100)); + pos += siprintf(dst + pos, + "(active time only: %u:%02u.%02u, %u.%02u/min)\n", + activeMSF.minutes, + (unsigned int)activeMSF.seconds, + activeMSF.frames * 5U / 3U, + (unsigned int) (activeMSF.pieces_100m / 100), + (unsigned int) (activeMSF.pieces_100m % 100)); + pos += siprintf(dst + pos, + "Pressed %u keys (%u.%02u/piece)\n\n", + v->control->presses, + (unsigned int) (keys_100m / 100), + (unsigned int) (keys_100m % 100)); + + pos += siprintf(dst + pos, + "Made %u lines", + p->lines); + if (p->gluing == LJGLUING_SQUARE) { + pos += siprintf(dst + pos, + " and %u pure + %u squares", + p->monosquares, p->multisquares); + } + dst[pos++] = '\n'; + pos += printLineCounts(dst + pos, p); + pos += siprintf(dst + pos, + "Sent %u garbage (%u.%02u per minute)\n", + p->outGarbage, + (unsigned int) (gameMSF.garbage_100m / 100), + (unsigned int) (gameMSF.garbage_100m % 100)); + if (nZigzagRows >= MIN_ZIGZAG_ROWS) { + pos += siprintf(dst + pos, + "Made %u rows of > for grade ", + nZigzagRows); + pos += printSecretGrade(dst + pos, + nZigzagRows >= 19 ? 100 : nZigzagRows); + } else if (nRectumRows >= MIN_RECTUM_ROWS) { + pos += siprintf(dst + pos, + "Made %u-row rectum for grade ", + nRectumRows); + pos += printSecretGrade(dst + pos, + nRectumRows >= 20 ? 100 : nRectumRows - 1); + } else if (nBlocksLeft >= 110 && nBlocksLeft <= 111) { + pos += siprintf(dst + pos, + "Secret grade: Eleventy%s", + (nBlocksLeft & 1) ? "-one" : ""); + } else { + pos += siprintf(dst + pos, + "Left %d blocks behind", + nBlocksLeft); + } + dst[pos++] = '\n'; + dst[pos++] = '\n'; + pos += printFourCC(dst + pos, optionsScoringNames[p->scoreStyle]); + + pos += siprintf(dst + pos, + " score: %d\n", + p->score); + + /* + tod stats not in lj + + points/line, 40 lines score, silver squares, gold squares + + */ + +#else + pos += siprintf(dst, "\n\nDebrief disabled. Score: %u\n", v->field->score); +#endif + return pos; +} + +void debriefDrawPage(const char *page, size_t pageNumber); +LJBits debriefHandleKeys(void); +extern volatile char redrawWholeScreen; + + +/** + * Reports the player's performance. + */ +void debrief(LJView *v) { + char pageData[2000]; // current length is under 1000 chars + char *page[2] = {pageData, "Page coming soon!"}; + int curPage = 0; + + { + size_t pos; + + pos = buildDebriefPage(page[0], v); + page[1] = page[0] + (++pos); + pos += buildOptionsReportPage(page[1], v); + } +#ifdef HAS_FOPEN + FILE *logFile = ljfopen("lj-scores.txt", "at"); + if (logFile) { + fputs("\n\n\n", logFile); + fputs(page[0], logFile); + fputs(page[1], logFile); +#ifdef DEBRIEF_TO_STDOUT + fputs(page[0], stdout); + fputs(page[1], stdout); +#endif + fclose(logFile); + } +#endif + + LJBits lastKeys = ~0; + redrawWholeScreen = 1; + debriefHandleKeys(); // call once to clear key buffer + + for (;;) { + LJBits sounds = 0; + + if (redrawWholeScreen) { + redrawWholeScreen = 0; + debriefDrawPage(page[curPage], curPage); + } + LJBits keys = debriefHandleKeys(); + LJBits newKeys = keys & ~lastKeys; + + if (newKeys & VKEY_LEFT) { + if (curPage > 0) { + curPage -= 1; + redrawWholeScreen = 1; + sounds |= LJSND_HOLD; + } + } + + if (newKeys & VKEY_RIGHT) { + if (curPage + 1 < sizeof(page) / sizeof(page[0])) { + curPage += 1; + redrawWholeScreen = 1; + sounds |= LJSND_HOLD; + } + } + + if (newKeys & (VKEY_ROTR | VKEY_ROTL)) { + break; + } else { + lastKeys = keys; + } + playSoundEffects(v, sounds, 100); + } +} diff -r 000000000000 -r c84446dfb3f5 src/dsarm7.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dsarm7.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,200 @@ +/*--------------------------------------------------------------------------------- + + LOCKJAW Tetromino Game: ARM7 core + + Copyright (C) 2005-2007 + Damian Yerrick (tepples) + + based on + default ARM7 core + + Copyright (C) 2005-2007 + Michael Noland (joat) + Jason Rogers (dovoto) + Dave Murphy (WinterMute) + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + 3. This notice may not be removed or altered from any source + distribution. + +---------------------------------------------------------------------------------*/ +#include +#include +#include "talkback.h" // custom talkback + +void ljSoundEffects(unsigned long int sounds, + int countdown); +void ljMusic(void); +extern int order; +extern char ticksLeft; +extern char row; +extern char musicPaused; + + + + +void powerOffDS(void) +{ + writePowerManagement(PM_CONTROL_REG, 1 << 6);//6 DS power (0: on, 1: shut down!) +} + +void doSleep(void) { + + // Check if the lid has been closed. + if(IPC->buttons & BIT(7)) { + // Save the current interrupt sate. + u32 ie_save = REG_IE; + // Turn the speaker down. + swiChangeSoundBias(0,0x400); + // Save current power state. + int power = readPowerManagement(PM_CONTROL_REG); + // Set sleep LED. + writePowerManagement(PM_CONTROL_REG, PM_LED_CONTROL(1)); + // Register for the lid interrupt. + REG_IE = IRQ_LID; + + // Power down till we get our interrupt. + swiSleep(); //waits for PM (lid open) interrupt + + REG_IF = ~0; + // Restore the interrupt state. + REG_IE = ie_save; + // Restore power state. + writePowerManagement(PM_CONTROL_REG, power); + + // Turn the speaker up. + swiChangeSoundBias(1,0x400); + IPC->buttons &= ~BIT(7); + } +} + +void VblankHandler(void) { + + doSleep(); + + volatile P8A7Talkback *tb = (P8A7Talkback *)IPC->soundData; + if (tb) { + int cmd = tb->cmd; + tb->cmd = 0; + + switch (cmd) { + case TALKBACK_POWER_OFF: + powerOFF(POWER_SOUND); + powerOffDS(); + swiWaitForVBlank(); + break; + case TALKBACK_PLAY_MUSIC: + musicPaused = 0; + break; + case TALKBACK_STOP_MUSIC: + musicPaused = 1; + order = 0; + row = 0; + ticksLeft = 1; + break; + case TALKBACK_PAUSE_MUSIC: + musicPaused = 1; + } + unsigned long int fx = tb->sounds; + tb->sounds = 0; + + ljSoundEffects(fx, tb->countdown); + } + + ljMusic(); + +} + + + +// All below this is wintermute's and may need to be replaced +// with new versions of libnds + + +touchPosition first,tempPos; + +//--------------------------------------------------------------------------------- +void VcountHandler() { +//--------------------------------------------------------------------------------- + static int lastbut = -1; + + uint16 but=0, x=0, y=0, xpx=0, ypx=0, z1=0, z2=0; + + but = REG_KEYXY; + + if (!( (but ^ lastbut) & (1<<6))) { + + tempPos = touchReadXY(); + + if ( tempPos.x == 0 || tempPos.y == 0 ) { + but |= (1 <<6); + lastbut = but; + } else { + x = tempPos.x; + y = tempPos.y; + xpx = tempPos.px; + ypx = tempPos.py; + z1 = tempPos.z1; + z2 = tempPos.z2; + } + + } else { + lastbut = but; + but |= (1 <<6); + } + + IPC->touchX = x; + IPC->touchY = y; + IPC->touchXpx = xpx; + IPC->touchYpx = ypx; + IPC->touchZ1 = z1; + IPC->touchZ2 = z2; + IPC->buttons = but; + +} + + + +//--------------------------------------------------------------------------------- +int main(int argc, char ** argv) { +//--------------------------------------------------------------------------------- + + // set up custom ipc struct + IPC->soundData = 0; + + // read User Settings from firmware + readUserSettings(); + + //enable sound + powerON(POWER_SOUND); + writePowerManagement(PM_CONTROL_REG, ( readPowerManagement(PM_CONTROL_REG) & ~PM_SOUND_MUTE & ~(1 << 6) ) | PM_SOUND_AMP ); + SOUND_CR = SOUND_ENABLE | SOUND_VOL(0x7F); + + irqInit(); + + // Start the RTC tracking IRQ + initClockIRQ(); + + SetYtrigger(80); + irqSet(IRQ_VCOUNT, VcountHandler); + irqSet(IRQ_VBLANK, VblankHandler); + + + irqEnable( IRQ_VBLANK | IRQ_VCOUNT); + + // Keep the ARM7 mostly idle + while (1) swiWaitForVBlank(); +} diff -r 000000000000 -r c84446dfb3f5 src/dsdebrief.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dsdebrief.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,77 @@ +/* +GBA/DS debrief code for LOCKJAW Tetromino Game + +Copr. 2008 Damian Yerrick +also GPL + +*/ +#include +#ifdef ARM9 +#include "ljds.h" +#define vwfOptions vwfTouch +#else +#include "ljgba.h" +#define vwfOptions vwfTop +#endif +#include "fontdraw.h" + +void debriefDrawPage(const char *page, size_t pageNumber) { + int y = 0; + char line[256]; + int linePos = 0; + int done = 0; + int scrW = vwfOptions.width * 8; + int scrH = vwfOptions.height * 8; + +#ifdef ARM9 + BG_PALETTE_SUB[0] = RGB5(31,31,31); + BG_PALETTE_SUB[1] = RGB5(21,21,21); + BG_PALETTE_SUB[2] = RGB5(0, 0, 0); + BG_PALETTE_SUB[3] = RGB5(0, 0, 0); + videoSetModeSub(MODE_0_2D + | DISPLAY_BG0_ACTIVE); +#else + BG_PALETTE[0] = RGB5(31,31,31); + BG_PALETTE[1] = RGB5(21,21,21); + BG_PALETTE[2] = RGB5(0, 0, 0); + BG_PALETTE[3] = RGB5(0, 0, 0); + REG_DISPCNT = 0 | BG0_ON; +#endif + vwfWinInit(&vwfOptions); + vwfPuts(&vwfOptions, "GAME OVER", (scrW - 64) / 2, y); + siprintf(line, "Page %u", (unsigned int)pageNumber + 1); + vwfPuts(&vwfOptions, line, scrW - 40, y); + vwfPuts(&vwfOptions, "Left/Right: change; Rotate: close", + 4, scrH - 12); + + while (!done && y <= scrH - 24) { + int c = *page++; + + // Break at newline and at end of text + if (c == '\n' || c == 0) { + + // Terminate the line of text and print it + line[linePos] = 0; + vwfPuts(&vwfOptions, line, 4, y); + + y += linePos ? 12 : 6; // Line feed + linePos = 0; // Carriage return + } else { + if (linePos + 2 < sizeof(line)) { + line[linePos++] = c; + } + } + if (c == 0) { + done = 1; + } + } +} + +LJBits debriefHandleKeys(void) { + int j = readPad(0); + if (j & (KEY_START << 16)) { + j |= VKEY_ROTR; + } + vsync(); + return j; +} diff -r 000000000000 -r c84446dfb3f5 src/dsjoy.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dsjoy.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,48 @@ +#include "ljcontrol.h" +#ifdef ARM9 +#include +#else +#include +#endif + +LJBits readHWKeys(void); + +LJBits readPad(unsigned int player) { + LJBits hwKeys = readHWKeys(); + LJBits out = 0; + + if (hwKeys & KEY_UP) { + out |= VKEY_UP; + } + if (hwKeys & KEY_DOWN) { + out |= VKEY_DOWN; + } + if (hwKeys & KEY_LEFT) { + out |= VKEY_LEFT; + } + if (hwKeys & KEY_RIGHT) { + out |= VKEY_RIGHT; + } + if (hwKeys & KEY_B) { + out |= VKEY_ROTL; + } + if (hwKeys & KEY_A) { + out |= VKEY_ROTR; + } + if (hwKeys & (KEY_L | KEY_R)) { + out |= VKEY_HOLD; + } + +#ifdef ARM9 + if (hwKeys & KEY_X) { + out |= VKEY_MACRO(3); + } + if (hwKeys & KEY_Y) { + out |= VKEY_MACRO(2); + } +#endif + + // on the GBA and DS, we need to add the console buttons in + out |= hwKeys << 16; + return out; +} diff -r 000000000000 -r c84446dfb3f5 src/dssleep.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dssleep.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,35 @@ +#include + +/* +by Mighty Max and melw, +after http://forum.gbadev.org/viewtopic.php?t=12011 +*/ + +int needLidSleep(void) { + // when reading keys + if (IPC->buttons & 0x0080) { + /* hinge is closed */ + + /* Step 1: Turn off all interrupts but vblank for waking up */ + unsigned long oldIE = REG_IE; + REG_IE = IRQ_VBLANK; + + /* Step 2: Power off most of the system */ + powerOFF(POWER_LCD); + + /* Step 3: Wait one vblank at a time until the ARM7 tells us + that the lid is no longer closed */ + while (IPC->buttons & 0x0080) { + swiWaitForVBlank(); + } + + /* Step 4: Wait a bit more (necessary?) */ + swiWaitForVBlank(); + + /* Step 5: Restore old machine state */ + powerON(POWER_LCD); + REG_IE = oldIE; + return 1; + } + return 0; +} diff -r 000000000000 -r c84446dfb3f5 src/dssound7.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dssound7.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,429 @@ +/*--------------------------------------------------------------------------------- + + LOCKJAW Tetromino Game: ARM7 sound code + + based on + default ARM7 core + + Copyright (C) 2007 + Damian Yerrick (tepples) + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + 3. This notice may not be removed or altered from any source + distribution. + +---------------------------------------------------------------------------------*/ +#include +#include + +extern const unsigned short midi2psgFreq[120]; + +#define C(octave) (octave * 12 + 0) +#define CS(octave) (octave * 12 + 1) +#define D(octave) (octave * 12 + 2) +#define DS(octave) (octave * 12 + 3) +#define E(octave) (octave * 12 + 4) +#define F(octave) (octave * 12 + 5) +#define FS(octave) (octave * 12 + 6) +#define G(octave) (octave * 12 + 7) +#define GS(octave) (octave * 12 + 8) +#define A(octave) (octave * 12 + 9) +#define AS(octave) (octave * 12 + 10) +#define B(octave) (octave * 12 + 11) + + +#define N_MUSIC_CHANNELS 2 + +static unsigned short chNote[N_MUSIC_CHANNELS]; +static unsigned char chVol[N_MUSIC_CHANNELS]; + + +static const unsigned char startVol[N_MUSIC_CHANNELS] = {32, 30}; +static const unsigned char fade[N_MUSIC_CHANNELS] = {0, 1}; +static const unsigned char chDuty[N_MUSIC_CHANNELS] = {0, 3}; + +static const signed char bassLine[64] = { + A(2), 0, A(3), 0, -1, 0, A(2), 0, + A(3), 0, -1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + C(3), 0, C(4), 0, G(2), 0, G(3), 0, + A(2), 0, A(3), 0, -1, 0, A(2), 0, + A(3), 0, -1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + C(3), 0, C(4), 0, G(2), 0, G(3), 0, +}; + +static const signed char mel1[64] = { + E(6), 0, 0, 0, B(5), 0, C(6), 0, + D(6), 0, 0, 0, C(6), 0, B(5), 0, + A(5), 0, 0, 0, A(5), 0, C(6), 0, + E(6), 0, 0, 0, D(6), 0, C(6), 0, + B(5), 0, 0, 0, 0, 0, C(6), 0, + D(6), 0, 0, 0, E(6), 0, 0, 0, + C(6), 0, 0, 0, A(5), 0, 0, 0, + A(5) +}; + +static const signed char mel2[64] = { + 0, 0, D(6), 0, 0, 0, F(6), 0, + A(6), 0, 0, 0, G(6), 0, F(6), 0, + E(6), 0, 0, 0, 0, 0, C(6), 0, + E(6), 0, 0, 0, D(6), 0, C(6), 0, + B(5), 0, 0, 0, B(5), 0, C(6), 0, + D(6), 0, 0, 0, E(6), 0, 0, 0, + C(6), 0, 0, 0, A(5), 0, 0, 0, + A(5) +}; + +static const signed char *const scripts[4][N_MUSIC_CHANNELS] = { + { bassLine, mel1 }, + { bassLine, mel1 }, + { bassLine, mel2 }, + { bassLine, mel2 } +}; + +int order = 0; +char ticksLeft = 1; +char row = 0; +char musicPaused = 1; + +/* + * Plays a repeating drum track and bass track. + */ +void ljMusic(void) { + if (musicPaused) { + for (int ch = 0; ch < N_MUSIC_CHANNELS; ++ch) { + chVol[ch] = 0; + } + } else if (--ticksLeft <= 0) { + ticksLeft += 6; + for (int ch = 0; ch < N_MUSIC_CHANNELS; ++ch) { + const signed char *script = scripts[order][ch]; + int note = script ? script[(int)row] : 0; + if (note < 0) { + chVol[ch] = 0; + } else if (note > 0) { + chVol[ch] = startVol[ch]; + chNote[ch] = midi2psgFreq[note]; + } + } + ++row; + if (row >= 64) { + row = 0; + ++order; + if (order == 4) { + order = 0; + } + } + } + + + // Play notes + for (int ch = 0; ch < N_MUSIC_CHANNELS; ++ch) { + SCHANNEL_CR(8 + ch) = SCHANNEL_ENABLE | SOUND_FORMAT_PSG | SOUND_PAN(64) + | (chDuty[ch] << 24) | chVol[ch]; + SCHANNEL_TIMER(8 + ch) = chNote[ch]; + + // update volumes + if (chVol[ch] > fade[ch]) { + chVol[ch] -= fade[ch]; + } else { + chVol[ch] = 0; + } + } +} + +#define FX(note, volume, duty) ((note) | ((volume) << 7) | ((duty) << 14)) + +static const u16 rotateEffect[] = { + 0, 6, + FX(A(5), 32, 1), FX(A(5), 8, 1), + FX(D(6), 32, 1), FX(D(6), 8, 1), + FX(G(6), 32, 1), FX(G(6), 8, 1) +}; + +static const u16 moveEffect[] = { + 0, 2, + FX(A(6), 32, 1), FX(A(6), 8, 1) +}; + +static const u16 landEffect[] = { + 0, 8, + FX(C(4), 64, 3), FX(GS(3), 56, 3), + FX(F(3), 48, 3), FX(D(3), 40, 3), + FX(C(3), 32, 3), FX(B(2), 24, 3), + FX(C(3), 16, 3), FX(B(2), 8, 3), +}; + +static const u16 lockEffect[] = { + 1, 2, + FX(C(8), 16, 1), FX(C(8), 4, 1) +}; + +static const u16 lineEffect[] = { + 0, 15, + FX(C(6), 64, 2), FX(DS(6), 59, 2), FX(F(6), 54, 2), + FX(C(6), 49, 2), FX(D(6), 44, 2), FX(F(6), 39, 2), + FX(C(6), 34, 3), FX(DS(6), 29, 3), FX(F(6), 24, 3), + FX(C(6), 20, 3), FX(D(6), 16, 3), FX(F(6), 12, 3), + FX(C(6), 9, 3), FX(DS(6), 6, 3), FX(F(6), 3, 3) +}; + +static const u16 homerEffect[] = { + 0, 25, + FX(C(6), 64, 2), FX(DS(6), 59, 2), FX(F(6), 54, 2), + FX(C(6), 49, 2), FX(D(6), 44, 2), FX(F(6), 39, 2), + FX(C(6), 34, 3), FX(DS(6), 29, 3), FX(F(6), 24, 3), + FX(C(6), 20, 3), + FX(DS(6), 64, 2), FX(FS(6), 59, 2), FX(GS(6), 54, 2), + FX(DS(6), 49, 2), FX(F(6), 44, 2), FX(GS(6), 39, 2), + FX(DS(6), 34, 3), FX(FS(6), 29, 3), FX(GS(6), 24, 3), + FX(DS(6), 20, 3), FX(F(6), 16, 3), FX(GS(6), 12, 3), + FX(DS(6), 9, 3), FX(F(6), 6, 3), FX(GS(6), 3, 3) +}; + +static const u16 streakEffect[] = { + 0, 35, + FX(C(6), 64, 2), FX(DS(6), 59, 2), FX(F(6), 54, 2), + FX(C(6), 49, 2), FX(D(6), 44, 2), FX(F(6), 39, 2), + FX(C(6), 34, 3), FX(DS(6), 29, 3), FX(F(6), 24, 3), + FX(C(6), 20, 3), + FX(DS(6), 64, 2), FX(FS(6), 59, 2), FX(GS(6), 54, 2), + FX(DS(6), 49, 2), FX(F(6), 44, 2), FX(GS(6), 39, 2), + FX(DS(6), 34, 3), FX(FS(6), 29, 3), FX(GS(6), 24, 3), + FX(DS(6), 20, 3), + FX(FS(6), 64, 2), FX(A(6), 59, 2), FX(B(6), 54, 2), + FX(FS(6), 49, 2), FX(GS(6), 44, 2), FX(B(6), 39, 2), + FX(FS(6), 34, 3), FX(A(6), 29, 3), FX(B(6), 24, 3), + FX(FS(6), 20, 3), FX(GS(6), 16, 3), FX(B(6), 12, 3), + FX(FS(6), 9, 3), FX(GS(6), 6, 3), FX(B(6), 3, 3) +}; + +static const u16 holdEffect[] = { + 1, 10, + FX(E(5), 20, 0), FX(G(5), 25, 0), + FX(B(5), 30, 0), FX(B(5), 30, 0), + FX(G(5), 28, 0), FX(E(5), 24, 0), + FX(D(5), 20, 0), FX(C(5), 15, 0), + FX(C(5), 10, 0), FX(C(5), 5, 0) +}; + + +static const u16 irsEffect[] = { + 0, 12, + FX(CS(7), 40, 3), FX(CS(7), 40, 3), + FX(FS(7), 40, 3), FX(FS(7), 40, 3), + FX(B(7), 40, 3), FX(B(7), 35, 3), + FX(B(7), 30, 3), FX(B(7), 25, 3), + FX(B(7), 20, 3), FX(B(7), 15, 3), + FX(B(7), 10, 3), FX(B(7), 5, 3), +}; + +static const u16 countEffect[] = { + 0, 14, + FX(D(5), 64, 0), FX(D(5), 64, 0), + FX(CS(5), 64, 0), FX(CS(5), 64, 0), + FX(C(5), 64, 1), FX(C(5), 64, 1), + FX(B(4), 64, 1), FX(AS(4), 64, 1), + FX(A(4), 64, 2), FX(GS(4), 64, 2), + FX(GS(4), 64, 3), FX(G(4), 48, 3), + FX(G(4), 32, 3), FX(G(4), 16, 3), +}; + +static const u16 winEffect1[] = { + 0, 54, + FX(C(6), 32, 2), FX(C(6), 48, 1), FX(C(6), 48, 1), FX(C(6), 48, 1), + FX(C(6), 32, 2), 0, 0, 0, 0, + FX(AS(5), 32, 2), FX(AS(5), 48, 1), FX(AS(5), 48, 1), FX(AS(5), 48, 1), + FX(AS(5), 32, 2), 0, 0, 0, 0, + FX(C(6), 32, 2), FX(C(6), 48, 1), FX(C(6), 48, 1), FX(C(6), 48, 1), + FX(D(6), 32, 2), 0, 0, 0, 0, + + FX(D(6), 32, 2), FX(D(6), 48, 1), FX(D(6), 48, 1), FX(D(6), 48, 1), + FX(D(6), 47, 1), FX(D(6), 46, 1), FX(D(6), 45, 1), FX(D(6), 44, 1), + FX(D(6), 43, 1), FX(D(6), 42, 1), FX(D(6), 41, 1), FX(D(6), 40, 1), + FX(D(6), 39, 1), FX(D(6), 38, 1), FX(D(6), 37, 1), FX(D(6), 36, 1), + FX(D(6), 35, 1), FX(D(6), 34, 1), FX(D(6), 33, 1), FX(D(6), 32, 1), + FX(D(6), 31, 1), FX(D(6), 30, 1), FX(D(6), 29, 1), FX(D(6), 28, 1), + FX(D(6), 27, 1), FX(D(6), 26, 1), FX(D(6), 25, 1), FX(D(6), 24, 1), + FX(D(6), 18, 2), FX(D(6), 12, 2), FX(D(6), 6, 2) +}; + +static const u16 winEffect2[] = { + 0, 54, + FX(E(6), 32, 2), FX(E(6), 48, 1), FX(E(6), 48, 1), FX(E(6), 48, 1), + FX(E(6), 32, 2), 0, 0, 0, 0, + FX(D(6), 32, 2), FX(D(6), 48, 1), FX(D(6), 48, 1), FX(D(6), 48, 1), + FX(D(6), 32, 2), 0, 0, 0, 0, + FX(E(6), 32, 2), FX(E(6), 48, 1), FX(E(6), 48, 1), FX(E(6), 48, 1), + FX(E(6), 32, 2), 0, 0, 0, 0, + + FX(FS(6), 32, 2), FX(FS(6), 48, 1), FX(FS(6), 48, 1), FX(FS(6), 48, 1), + FX(FS(6), 47, 1), FX(FS(6), 46, 1), FX(FS(6), 45, 1), FX(FS(6), 44, 1), + FX(FS(6), 43, 1), FX(FS(6), 42, 1), FX(FS(6), 41, 1), FX(FS(6), 40, 1), + FX(FS(6), 39, 1), FX(FS(6), 38, 1), FX(FS(6), 37, 1), FX(FS(6), 36, 1), + FX(FS(6), 35, 1), FX(FS(6), 34, 1), FX(FS(6), 33, 1), FX(FS(6), 32, 1), + FX(FS(6), 31, 1), FX(FS(6), 30, 1), FX(FS(6), 29, 1), FX(FS(6), 28, 1), + FX(FS(6), 27, 1), FX(FS(6), 26, 1), FX(FS(6), 25, 1), FX(FS(6), 24, 1), + FX(FS(6), 18, 2), FX(FS(6), 12, 2), FX(FS(6), 6, 2) +}; + +static const u16 dieEffect1[] = { + 0, 60, + FX(E(3), 60, 2), FX(E(3), 59, 2), FX(E(3), 58, 2), FX(E(3), 57, 2), + FX(E(3), 56, 2), FX(E(3), 55, 2), FX(E(3), 54, 2), FX(E(3), 53, 2), + FX(E(3), 52, 2), FX(E(3), 51, 2), FX(E(3), 50, 2), FX(E(3), 49, 2), + FX(E(3), 48, 2), FX(E(3), 47, 2), FX(E(3), 46, 2), FX(E(3), 45, 2), + FX(E(3), 44, 2), FX(E(3), 43, 2), FX(E(3), 42, 2), FX(E(3), 41, 2), + FX(E(3), 40, 2), FX(E(3), 39, 2), FX(E(3), 38, 2), FX(E(3), 37, 2), + FX(E(3), 36, 2), FX(E(3), 35, 2), FX(E(3), 34, 2), FX(E(3), 33, 2), + FX(E(3), 32, 2), FX(E(3), 31, 2), FX(E(3), 30, 2), FX(E(3), 29, 2), + FX(E(3), 28, 2), FX(E(3), 27, 2), FX(E(3), 26, 2), FX(E(3), 25, 2), + FX(E(3), 24, 2), FX(E(3), 23, 2), FX(E(3), 22, 2), FX(E(3), 21, 2), + FX(E(3), 20, 2), FX(E(3), 19, 2), FX(E(3), 18, 2), FX(E(3), 17, 2), + FX(E(3), 16, 2), FX(E(3), 15, 2), FX(E(3), 14, 2), FX(E(3), 13, 2), + FX(E(3), 12, 2), FX(E(3), 11, 2), FX(E(3), 10, 2), FX(E(3), 9, 2), + FX(E(3), 8, 2), FX(E(3), 7, 2), FX(E(3), 6, 2), FX(E(3), 5, 2), + FX(E(3), 4, 2), FX(E(3), 3, 2), FX(E(3), 2, 2), FX(E(3), 1, 2) +}; + +static const u16 dieEffect2[] = { + 1, 60, + FX(E(4), 60, 2), FX(B(3), 59, 2), FX(E(3), 58, 2), FX(E(3), 57, 2), + FX(E(3), 56, 2), FX(E(3), 55, 2), FX(E(3), 54, 2), FX(E(3), 53, 2), + FX(E(3), 52, 2), FX(E(3), 51, 2), FX(E(3), 50, 2), FX(E(3), 49, 2), + FX(E(3), 48, 2), FX(E(3), 47, 2), FX(E(3), 46, 2), FX(E(3), 45, 2), + FX(E(3), 44, 2), FX(E(3), 43, 2), FX(E(3), 42, 2), FX(E(3), 41, 2), + FX(E(3), 40, 2), FX(E(3), 39, 2), FX(E(3), 38, 2), FX(E(3), 37, 2), + FX(E(3), 36, 2), FX(E(3), 35, 2), FX(E(3), 34, 2), FX(E(3), 33, 2), + FX(E(3), 32, 2), FX(E(3), 31, 2), FX(E(3), 30, 2), FX(E(3), 29, 2), + FX(E(3), 28, 2), FX(E(3), 27, 2), FX(E(3), 26, 2), FX(E(3), 25, 2), + FX(E(3), 24, 2), FX(E(3), 23, 2), FX(E(3), 22, 2), FX(E(3), 21, 2), + FX(E(3), 20, 2), FX(E(3), 19, 2), FX(E(3), 18, 2), FX(E(3), 17, 2), + FX(E(3), 16, 2), FX(E(3), 15, 2), FX(E(3), 14, 2), FX(E(3), 13, 2), + FX(E(3), 12, 2), FX(E(3), 11, 2), FX(E(3), 10, 2), FX(E(3), 9, 2), + FX(E(3), 8, 2), FX(E(3), 7, 2), FX(E(3), 6, 2), FX(E(3), 5, 2), + FX(E(3), 4, 2), FX(E(3), 3, 2), FX(E(3), 2, 2), FX(E(3), 1, 2) +}; + +static const u16 *effects[32] = { + rotateEffect, + moveEffect, + NULL, // drop: not used + landEffect, + lockEffect, + lineEffect, + homerEffect, + streakEffect, + NULL, // spawn + holdEffect, // hold + irsEffect, // irs + NULL, // 4x4 square + NULL, NULL, NULL, NULL, // unused + dieEffect1, dieEffect2 +}; + + + +static const u16 *fxPtr[5]; +static u8 fxLen[5]; +static s8 lastCD; + + + +static void startEffect(const u16 *s) { + + // skip effects that lack an actual effect + if (!s) { + return; + } + + unsigned int type = s[0]; + unsigned int len = s[1]; + unsigned int min = 0; + unsigned int max = 2; + + if (type > 0) { + min = 3; + max = 4; + } + + // prepare to kill the channel with the shortest time remaining + unsigned int shortestLen = fxLen[min]; + unsigned int shortestCh = min; + for (int ch = min + 1; ch <= max; ++ch) { + if (fxLen[ch] < shortestLen) { + shortestLen = fxLen[ch]; + shortestCh = ch; + } + } + + // if the channel is suitable, kill it + if (shortestLen < len) { + fxPtr[shortestCh] = s + 2; + fxLen[shortestCh] = len; + } +} + +static void pollEffects(void) { + for (int ch = 0; ch < 5; ++ch) { + unsigned int note = 36; + unsigned int vol = 0; + unsigned int duty = 0; + if (fxLen[ch] > 0) { + unsigned int data = *fxPtr[ch]++; + duty = (data >> 14) & 0x03; + vol = (data >> 7) & 0x7F; + note = data & 0x7F; + --fxLen[ch]; + } + SCHANNEL_CR(11 + ch) = SCHANNEL_ENABLE | SOUND_FORMAT_PSG | SOUND_PAN(64) + | (duty << 24) | vol; + SCHANNEL_TIMER(11 + ch) = midi2psgFreq[note]; + } +} + +void ljSoundEffects(unsigned long int sounds, + int countdown) { + + // cancel out overlapping line clear sounds + if (sounds & 0x0080) { + sounds &= ~0x0060; + } else if (sounds & 0x0040) { + sounds &= ~0x0020; + } + + for (int y = 0; sounds; ++y, sounds >>= 1) { + if (sounds & 1) { + startEffect(effects[y]); + } + } + if (countdown < 0) { + countdown = 0; + } else if (countdown > 6) { + countdown = 6; + } + if (countdown < lastCD) { + if (countdown > 0) { + startEffect(countEffect); + } else { + startEffect(winEffect1); + startEffect(winEffect2); + } + } + lastCD = countdown; + pollEffects(); +} diff -r 000000000000 -r c84446dfb3f5 src/explode.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/explode.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,142 @@ +/* +Bombl*ss extension for LOCKJAW Tetromino Game +Copr. 2007 Damian Yerrick + +*/ + + +/* + +During falling, the auxiliary playfield p->c[][] contains a map of +individual regions used by the falling code. But during explosion, +it serves a different purpose: counting the number of frames before +a given block is erased. + +*/ + +const char explodeWidth[nLines] = { + 4, // megabomb + 3, 3, 3, 3, 4, 5, 5, 6, 6, 7 // 1-10 lines +}; +const char explodeHeight[nLines] = { + 3, // megabomb + 0, 1, 2, 3, 4, 5, 5, 6, 6, 7 // 1-10 lines +}; + + +/** + * Sets the number of frames before a block is erased based on + * the explosion. + * @param p the playfield + * @param x the column where the bomb is centered + * @param y the row where the bomb is centered + * @param bombColor the color of the bomb + * @param nLines the number of lines cleared, which determines the explosion size + */ +void explodeAt(LJField *p, int x, int y, int bombColor, int nLines) { + int boom = nLines; + + if ((bombColor & COLOR_MASK) == BOMBCOLOR_SQUARE) { + boom = 4; + } else if (boom > 10) { + boom = 10; + } + + int boomLeft = x - explodeWidth[boom]; + if (boomLeft < p->leftWall) { + boomLeft = p->leftWall; + } + int boomRight = x + explodeWidth[boom]; + if (boomRight > p->rightWall - 1) { + boomLeft = p->rightWall - 1; + } + int boomBottom = y - explodeHeight[boom]; + if (boomLeft < 0) { + boomLeft = 0; + } + int boomTop = y + explodeHeight[boom]; + if (boomRight > LJ_PF_HT - 1) { + boomLeft = LJ_PF_HT - 1; + } + + for (by = boomBottom; bx < boomTop; ++bx) { + int dy = y - by; + int dy2 = dy * dy; + for (bx = boomLeft; bx < boomRight; ++bx) { + int dx = x - bx; + int dx2 = dx * dx; + unsigned int time = (dx2 + dy2) / EXPLODE_SPEED + 2; + if (p->c[y][x] > time) { + p->c[y][x] = time; + } + } + } +} + +static int blockIsBomb(unsigned int blk) { + return (blk & COLOR_MASK) == BOMBCOLOR_NORMAL + || (blk & COLOR_MASK) == BOMBCOLOR_SQUARE; +} + +/** + * Scans the exploding areas, erases blocks, and sets up bombs to explode. + * @param p the playfield + * @return the rows that were changed + */ +LJBits explodeFrame(LJField *p) { + LJBits changedRows = 0; + LJBits changeAny = 0; + + int nLines = countRows(p->tempRows); + + // first pass: destruction + for (int y = 0; y < LJ_PF_HT; ++y) { + for (int x = p->leftWall; x < p->rightWall; ++x) { + if (p->c[y][x] == 1) { + unsigned int blk = p->b[y][x]; + + if (blk) { + changedRows |= 1 << y; + p->b[y][x] = 0; + if (blockIsBomb(blk)) { + explodeAt(p, x, y, blk, nLines); + } + } + } + } + } + + // second pass: countdown timer per block + for (int y = 0; y < LJ_PF_HT; ++y) { + for (int x = p->leftWall; x < p->rightWall; ++x) { + if (p->c[y][x] > 0) { + --p->c[y][x] > 0; + changeAny = 1; + } + } + } + + return changeAny ? (1 << (LJ_PF_HT - 1)) : changedRows; +} + +/** + * Scans the cleared lines for bombs and marks c[] to trigger them. + * @param p the playfield, where tempRows holds the cleared lines + * @return the rows where bombs were triggered + */ +LJBits triggerBombs(LJField *p) { + LJBits bombRows = 0; + + for (int y = 0; y < LJ_PF_HT; ++y) { + if (p->tempRows & (1 << y)) { + for (int x = p->leftWall; x < p->rightWall; ++x) { + bool isBomb = blockIsBomb(p->b[y][x]); + p->c[y][x] = isBomb; + if (isBomb) { + bombRows |= 1 << y; + } + } + } + } + return bombRows; +} diff -r 000000000000 -r c84446dfb3f5 src/font.bmp Binary file src/font.bmp has changed diff -r 000000000000 -r c84446dfb3f5 src/fontdraw.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fontdraw.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,372 @@ +/* +Variable width font drawing library for DS (and GBA) + +Copyright 2007-2008 Damian Yerrick + +This work is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this work. + +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: + +1. 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. +2. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original work. +3. This notice may not be removed or altered from any source + distribution. + +"Source" is the preferred form of a work for making changes to it. + +*/ + +#include +#include +#include "fontdraw.h" + + +extern const unsigned char vwfont_bin[]; + +typedef struct GlyphRec { + unsigned short dataOffset; + unsigned char glyphWidth; + unsigned char reserved; +} GlyphRec; + +#ifndef FONTDRAW_SPLIT_COMPILE +#include "fontdraw_engine.c" +#else +__attribute__((long_call)) +unsigned int fontdraw_putchar(u32 *dst, unsigned int colStride, int wid, int x, int glyph); +__attribute__((long_call)) +void vwfRectfillColumn(u32 *dst, unsigned int colStride, + unsigned int l, unsigned int t, + unsigned int r, unsigned int b, + unsigned int c); +#endif + + +unsigned int fontdraw_charWidth(int glyph) { + glyph &= 0xFF; + if (glyph < vwfont_bin[1]) { + return 0; + } + glyph -= vwfont_bin[1]; + if (vwfont_bin[2] != 0 && glyph >= vwfont_bin[2]) { + return 0; + } + const GlyphRec *glyphRec = + ((const GlyphRec *)(vwfont_bin + vwfont_bin[0])) + glyph; + return glyphRec->glyphWidth; +} + +unsigned int fontdraw_strWidth(const char *s) { + unsigned int width = 0; + for (; *s; ++s) { + width += fontdraw_charWidth(*s); + } + return width; +} + +size_t fontdraw_cutStr(const char *s, int targetWidth) { + size_t len; + for (len = 0; s[len]; ++len) { + int charWidth = fontdraw_charWidth(s[len]); + if (charWidth > targetWidth) { + return len; + } + targetWidth -= charWidth; + } + return len; +} + +static unsigned int fontdraw_putline(u32 *dst, unsigned int colStride, int wid, int x, const char *src) { + unsigned int startX = x; + for (int c = *src; c != 0 && wid > 0; c = *++src) { + int chWidth = fontdraw_putchar(dst, colStride, wid, x, c & 0xFF); + x += chWidth; + wid -= chWidth; + } + return x - startX; +} + +#ifdef ARM9 +const VWFWindow vwfTop = { + .left = 0, .top = 0, .width = 32, .height = 24, + .chrBase = (u32 *)BG_TILE_RAM(0), + .map = 31, + .core = 0, + .mapTileBase = 0 +}; + +const VWFWindow vwfTouch = { + .left = 0, .top = 0, .width = 32, .height = 24, + .chrBase = (u32 *)BG_TILE_RAM_SUB(0), + .map = 31, + .core = 1, + .mapTileBase = 0 +}; +#else +const VWFWindow vwfTop = { + .left = 0, .top = 0, .width = 30, .height = 20, + .chrBase = (u32 *)PATRAM4(0, 0), + .map = 31, + .core = 0, + .mapTileBase = 0 +}; +#endif + +void vwfRectfill(const VWFWindow *v, int l, int t, int r, int b, int c) +{ + u32 *dst = v->chrBase; + c &= 0x0000000F; + c |= c << 4; + c |= c << 8; + c |= c << 16; + + unsigned int x = l; + unsigned int stride = v->height * 8; + u32 *tile = dst + stride * (l >> 3); + + if (t < 0) { + t = 0; + } + if (b > v->height * 8) { + b = v->height * 8; + } + + for(x = l; x < r; x = (x + 8) & -8) { + vwfRectfillColumn(tile, stride, x & 7, t, + (r & -8) > (x & -8) ? 8 : (r & 7), + b, c); + tile += stride; + } +} + +void vwfHline(const VWFWindow *v, int l, int t, int r, int c) { + if (r < l) { + int temp = l; + l = r; + r = temp; + } + vwfRectfill(v, l, t, r, t + 1, c); +} + +void vwfVline(const VWFWindow *v, int l, int t, int b, int c) { + if (b < t) { + int temp = t; + t = b; + b = temp; + } + vwfRectfill(v, l, t, l + 1, b, c); +} + +void vwfRect(const VWFWindow *v, int l, int t, int r, int b, int c) { + vwfVline(v, l, t, b, c); + vwfVline(v, r - 1, t, b, c); + vwfHline(v, l, t, r, c); + vwfHline(v, l, b - 1, r, c); +} + +void vwfWinClear(const VWFWindow *w) { + size_t nBytes = 32 * w->width * w->height; + memset(w->chrBase, 0, nBytes); +} + +static inline NAMETABLE *vwfGetMapBase(int core, int map) { +#if ARM9 + NAMETABLE *dst = core ? &(MAP_SUB[map]) : &(MAP[map]); +#else + NAMETABLE *dst = &(MAP[map]); +#endif + + return dst; +} + +void vwfPutMap(const VWFWindow *w, + int l, int t, int r, int b, + unsigned int orMask) { + + if (r > (int)w->width) { + r = (int)w->width; + } + if (l < 0) { + l = 0; + } + if (r <= l) { + return; + } + + if (b > (int)w->height) { + b = (int)w->height; + } + if (t < 0) { + t = 0; + } + if (b <= t) { + return; + } + + NAMETABLE *dst = vwfGetMapBase(w->core, w->map); + + int mapTile = (w->mapTileBase + w->height * l + t) | orMask; + for (int x = w->left + l; x < w->left + r; ++x) { + for (int y = w->top + t; y < w->top + b; ++y) { + (*dst)[y][x] = mapTile++; + } + mapTile += w->height + t - b; + } +} + +void vwfWinInit(const VWFWindow *w) { + vwfWinClear(w); + vwfPutMap(w, 0, 0, w->width, w->height, 0x0000); +} + +#if 0 +void vwfWinInit(const VWFWindow *w) { +#if ARM9 + NAMETABLE *dst = w->core ? &(MAP_SUB[w->map]) : &(MAP[w->map]); +#else + NAMETABLE *dst = &(MAP[w->map]); +#endif + + int mapTile = w->mapTileBase; + for (int x = w->left; x < w->left + w->width; ++x) { + for (int y = w->top; y < w->top + w->height; ++y) { + (*dst)[y][x] = mapTile++; + } + } + vwfWinClear(w); +} +#endif + +unsigned int vwfPuts(const VWFWindow *w, + const char *str, + int x, int y) { + return fontdraw_putline(w->chrBase + y, 8 * w->height, + w->width * 8 - x, x, str); +} + +unsigned int vwfPutc(const VWFWindow *w, + int c, + int x, int y) { + int width = 8 * w->width - x; + if (width >= fontdraw_charWidth(c)) { + return fontdraw_putchar(w->chrBase + y, 8 * w->height, + w->width * 8 - x, x, c & 0xFF); + } else { + return 0; + } +} + +// old api emulation + +#if 0 +void fontdraw_demo_putline(int x, int y, const char *str) { + const VWFWindow *w = &vwfTop; +#if ARM9 + if (y >= 288) { + w = &vwfTouch; + y -= 288; + } +#endif + vwfPuts(w, str, x, y); +} +#endif + +void fontdraw_setupVRAM(int sub) { +#if ARM9 + const VWFWindow *w = sub ? &vwfTouch : &vwfTop; +#else + const VWFWindow *w = &vwfTop; +#endif + +#if ARM9 + if (sub == 0) { + BGCTRL_SUB[0] = BG_MAP_BASE(31) | BG_TILE_BASE(0); + BG_OFFSET_SUB[0].x = 0; + BG_OFFSET_SUB[0].y = 0; + } else +#endif + { + BGCTRL[0] = BG_MAP_BASE(31) | BG_TILE_BASE(0); + BG_OFFSET[0].x = 0; + BG_OFFSET[0].y = 0; + } + + vwfWinInit(w); +} + +void fontdraw_cls(int sub) { +#if ARM9 + vwfWinClear(sub ? &vwfTouch : &vwfTop); +#else + vwfWinClear(&vwfTop); +#endif +} + +void vwfBlitAligned(const VWFWindow *src, const VWFWindow *dst, + int srcX, int srcY, int dstX, int dstY, + int w, int h) { + + /* Clip in X */ + if (srcX < 0) { + dstX -= srcX; + w -= srcX; + srcX = 0; + } + if (dstX < 0) { + srcX -= dstX; + w -= dstX; + dstX = 0; + } + if (srcX + w > (int)src->width) { + w = src->width - srcX; + } + if (dstX + w > (int)dst->width) { + w = dst->width - dstX; + } + if (w <= 0) { + return; + } + + /* Clip in Y */ + if (srcY < 0) { + dstY -= srcY; + h -= srcY; + srcY = 0; + } + if (dstY < 0) { + srcY -= dstY; + h -= dstY; + dstY = 0; + } + if (srcY + h > src->height * 8) { + h = src->height * 8 - srcY; + } + if (dstX + w > dst->height * 8) { + h = dst->height * 8 - dstY; + } + if (h < 0) { + return; + } + + { + const u32 *srcCol = src->chrBase + srcX * src->height * 8 + srcY; + u32 *dstCol = dst->chrBase + dstX * dst->height * 8 + dstY; + + for (; w > 0; --w) { + for (unsigned int y = 0; y < h; ++y) { + dstCol[y] = srcCol[y]; + } + srcCol += src->height * 8; + dstCol += dst->height * 8; + } + } + +} diff -r 000000000000 -r c84446dfb3f5 src/fontdraw.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fontdraw.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,127 @@ +/* +Variable width font drawing library for DS (and GBA) + +Copyright 2007 Damian Yerrick + +This work is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this work. + +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: + +1. 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. +2. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original work. +3. This notice may not be removed or altered from any source + distribution. + +"Source" is the preferred form of a work for making changes to it. + +*/ + +#ifndef FONTDRAW_H +#define FONTDRAW_H +#include + +#ifdef ARM9 +// DS specific macros +#include +#ifndef BG_OFFSET_SUB +#define BG_OFFSET_SUB ((bg_scroll *)(0x04001010)) +#endif + +// macros from libgba that didn't make it to libnds +#ifndef MAP +typedef u16 NAMETABLE[32][32]; +#define MAP ((NAMETABLE *)BG_MAP_RAM(0)) +#define MAP_SUB ((NAMETABLE *)BG_MAP_RAM_SUB(0)) +#endif + +#else +// GBA specific macros +#include + +#endif + +unsigned int fontdraw_charWidth(int glyph); +unsigned int fontdraw_strWidth(const char *s); + +/** + * Returns the number of characters in s that fit within targetWidth pixels. + */ +size_t fontdraw_cutStr(const char *s, int targetWidth); + +void fontdraw_setupVRAM(int sub); + +// New API + +typedef struct VWFWindow { + u8 left; // in 8 pixel units on nametable + u8 top; // in 8 pixel units on nametable + u8 width; // in 8 pixel units on nametable + u8 height; // in 8 pixel units on nametable + u32 *chrBase; + u8 map; // in 2 KiB units on VRAM + u8 core; // 0: main; 1: sub + u16 mapTileBase; +} VWFWindow; + +void vwfWinInit(const VWFWindow *vwf); +void vwfWinClear(const VWFWindow *vwf); +/** + * Sets up a portion of a window. + * @param vwf the window + * @param l distance in tiles from the left side of the window to the + * left side of the area to be updated + * @param t distance in tiles from the top of the window to the + * top of the area to be updated + * @param r distance in tiles from the left side of the window to the + * right side of the area to be updated + * @param b distance in tiles from the top of the window to the + * bottom of the area to be updated + * @param orMask the data to be OR'd with each map space, typically + * containing a palette number in bits 12 to 15 + */ +void vwfPutMap(const VWFWindow *vwf, + int l, int t, int r, int b, + unsigned int orMask); + +unsigned int vwfPutc(const VWFWindow *w, + int c, + int x, int y); +unsigned int vwfPuts(const VWFWindow *src, + const char *str, + int x, int y); +void vwfRectfill(const VWFWindow *v, + int l, int t, int r, int b, + int c); +void vwfHline(const VWFWindow *v, int l, int t, int r, int c); +void vwfVline(const VWFWindow *v, int l, int t, int b, int c); +void vwfRect(const VWFWindow *v, int l, int t, int r, int b, int c); + +/** + * Replaces a rectangle of pixels in dst with pixels from src. + * @param src the bitmap to copy from + * @param dst the bitmap to copy to + * @param srcX the left side of the part of src to copy, + * in 8-pixel units + * @param srcY the top of the part of src to copy, + * in pixels + * @param dstX the left side of the part of dst to be replaced, + * in 8-pixel units + * @param dstY the top of the part of src dst to be replaced, + * in pixels + */ +void vwfBlitAligned(const VWFWindow *src, const VWFWindow *dst, + int srcX, int srcY, int dstX, int dstY, + int w, int h); + +extern const VWFWindow vwfTop, vwfTouch; + + +#endif diff -r 000000000000 -r c84446dfb3f5 src/fontdraw.iwram.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fontdraw.iwram.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,1 @@ +#include "fontdraw_engine.c" diff -r 000000000000 -r c84446dfb3f5 src/fontdraw_engine.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fontdraw_engine.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,135 @@ +/* +Variable width font drawing library for DS (and GBA) + +Copyright 2007-2008 Damian Yerrick + +This work is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this work. + +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: + +1. 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. +2. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original work. +3. This notice may not be removed or altered from any source + distribution. + +"Source" is the preferred form of a work for making changes to it. + +*/ +/* + +Set FONTDRAW_SPLIT_COMPILE when compiling the time-sensitve parts +into a separate file to be placed in "fast" memory. + +*/ + + +#ifdef FONTDRAW_SPLIT_COMPILE +typedef unsigned int u32; +extern const unsigned char vwfont_bin[]; + +typedef struct GlyphRec { + unsigned short dataOffset; + unsigned char glyphWidth; + unsigned char reserved; +} GlyphRec; +#endif + +unsigned int fontdraw_putchar(u32 *dst, unsigned int colStride, int wid, int x, int glyph) { + glyph &= 0xFF; + if (glyph < vwfont_bin[1]) { + return 0; + } + glyph -= vwfont_bin[1]; + if (vwfont_bin[2] != 0 && glyph >= vwfont_bin[2]) { + return 0; + } + const GlyphRec *glyphRec = + ((const GlyphRec *)(vwfont_bin + vwfont_bin[0])) + glyph; + const unsigned char *data = vwfont_bin + glyphRec->dataOffset; + unsigned int dataShift = 2; + unsigned int dataBits = *data++; + unsigned int pixelCode = dataBits & 0x03; + + // Convert x to tile column address and bit address within tile + dst += colStride * (x >> 3); + x = (x & 0x07) << 2; + + if (dataShift >= 8) { + dataShift = 2; + dataBits = *data++; + } + + for (unsigned int height = vwfont_bin[3]; + height > 0; + --height, ++dst) { + int eol = 0; + int xLine = x; + int widLeft = wid; + u32 *dstLine = dst; + u32 dstBits = *dstLine; + while (!eol) { + + // Process a pixel instruction + if (pixelCode == 0) { + eol = 1; + } else if (widLeft > 0) { + + // Change pixel and move to next pixel + if (pixelCode > 1) { + //dstBits &= ~(0x0F << xLine); + dstBits |= (pixelCode - 1) << xLine; + } + xLine += 4; + --widLeft; + } + + // If finished with this tile, write back changed bits + if (xLine >= 32) { + xLine = 0; + *dstLine = dstBits; + dstLine += colStride; + dstBits = *dstLine; + } + + // Decode next pixel instruction + if (dataShift >= 8) { + dataShift = 0; + dataBits = *data++; + } + + // Decode a byte into pixel instructions + pixelCode = (dataBits >> dataShift) & 0x03; + dataShift += 2; + } + + // Write back changed bits + if (xLine > 0) { + *dstLine = dstBits; + } + } + return glyphRec->glyphWidth; +} + +void vwfRectfillColumn(u32 *dst, unsigned int colStride, + unsigned int l, unsigned int t, + unsigned int r, unsigned int b, + unsigned int c) +{ + u32 mask = 0xffffffffU << (4 * l); + mask &= 0xffffffffU >> (4 * (8 - r)); + c &= mask; + mask = ~mask; + + for (; t < b; t++) { + dst[t] = (dst[t] & mask) | c; + } +} + diff -r 000000000000 -r c84446dfb3f5 src/gba_asm.s --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gba_asm.s Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,73 @@ +@ fixed fastfmul(fixed x, fixed y) +@ Multiply two 16.16 fixed-point numbers. + +.ARM +.ALIGN +.GLOBL fastfmul + +fastfmul: + smull r1,r2,r0,r1 + mov r0,r2,LSL#16 + orr r0,r0,r1,LSR#16 + bx lr + + +@ int dv(int num, int den) +@ Divide two signed integers. + +.THUMB +.THUMB_FUNC +.ALIGN +.GLOBL dv + +dv: + cmp r1, #0 + beq 0f + swi 6 + bx lr +0: + ldr r0, =0x7fffffff + bx lr + + +@ int fracmul(signed int x, signed int frac) +@ Multiply by a 0.32 fractional number between -0.5 and 0.5. +@ Used for fast division by a constant. + +.ARM +.ALIGN +.GLOBL fracmul + +fracmul: + smull r1,r2,r0,r1 + mov r0, r2 + bx lr + + +@ int fracumul(unsigned int x, unsigned int frac) +@ Multiply by a 0.32 fractional number between 0 and 1. +@ Used for fast division by a constant. + +.ARM +.ALIGN +.GLOBL fracumul + +fracumul: + umull r1,r2,r0,r1 + mov r0, r2 + bx lr + + +@ void gblz_unpack(const void *src, void *dst) +@ Unpack GB LZSS format data. + +.THUMB +.THUMB_FUNC +.ALIGN +.GLOBL _gblz_unpack + +_gblz_unpack: + swi 0x11 @ LZ77UnCompWRAM + bx lr + + diff -r 000000000000 -r c84446dfb3f5 src/gbablk.chr Binary file src/gbablk.chr has changed diff -r 000000000000 -r c84446dfb3f5 src/gbaisr.iwram.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gbaisr.iwram.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,15 @@ +#include "ljgba.h" +#define BIOS_IF (*(volatile unsigned short *)0x03FFFFF8) + +volatile int curTime = 0; + +void isr(void) { + unsigned int interrupts = REG_IF; + + if (interrupts & IRQ_VBLANK) { + ++curTime; + } + + BIOS_IF |= interrupts; + REG_IF = interrupts; +} diff -r 000000000000 -r c84446dfb3f5 src/gbamenus.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gbamenus.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,458 @@ +/* + +BGCTRL layout +Layer 0: Current menu +Layer 1: Menu that is fading away +Layer 2: Gradient background + +VRAM layout +(1 map == 64 tiles) +Tiles 0.000-2FF: layer 0 bitmap +Tiles 0.300-3FF: unused +Tiles 2.000-2FF: layer 1 bitmap +Tiles 2.300-33F: unused +Tiles 2.340-36F: map 29 (layer 2) +Tiles 2.370-37F: layer 2 gradient tiles +Tiles 2.380-3FF: layer 0 map +Tiles 2.3C0-3FF: layer 1 map + + +*/ + +#include +#include +#include "fontdraw.h" +typedef u32 TileSliver; + +#ifdef ARM9 +#include +#define MENU_GFX_CORE 1 +#define MENU_GFX_VRAM(bank, tile) (TileSliver *)(BG_TILE_RAM_SUB(bank) + 32 * tile) +#define MENU_GFX_MAP ((NAMETABLE *)0x06200000) +#define MENU_GFX_BGCTRL BGCTRL_SUB +#define MENU_GFX_OFFSET ((bg_scroll *)(0x04001010)) +#define HIDDEN_ROWS 0 +#define HIDDEN_COLS 0 +#define BG0_ON DISPLAY_BG0_ACTIVE +#define BG1_ON DISPLAY_BG1_ACTIVE +#define BG2_ON DISPLAY_BG2_ACTIVE +#define videoSetModeMenu(x) videoSetModeSub(x) +#define MENU_GFX_PALETTE BG_PALETTE_SUB +#define USING_TOUCH 1 +#else +#include +#define MENU_GFX_CORE 0 +#define MENU_GFX_VRAM(bank, tile) (TileSliver *)PATRAM4(bank, tile) +#define MENU_GFX_MAP MAP +#define MENU_GFX_BGCTRL BGCTRL +#define MENU_GFX_OFFSET BG_OFFSET +#define HIDDEN_ROWS 2 +#define HIDDEN_COLS 1 +#define videoSetModeMenu(x) (REG_DISPCNT = x) +#define MODE_0_2D 0 +#define MENU_GFX_PALETTE BG_PALETTE +#define USING_TOUCH 0 +#endif + +static const VWFWindow vwfLayer0 = { + .left = 0, .top = 0, .width = 32, .height = 24, + .chrBase = MENU_GFX_VRAM(0, 0), + .map = 30, + .core = MENU_GFX_CORE, + .mapTileBase = 0 +}; + +static const VWFWindow vwfLayer1 = { + .left = 0, .top = 0, .width = 32, .height = 24, + .chrBase = MENU_GFX_VRAM(2, 0), + .map = 31, + .core = MENU_GFX_CORE, + .mapTileBase = 0 +}; + + +void vsync(void); + +static const TileSliver gradientBackgroundDelta[8] = { + 0x00000000, + 0x10101010, + 0x00000000, + 0x01010101, + 0x10101010, + 0x01010101, + 0x11111111, + 0x10101010 +}; + +/* Makes 12 gradient background tiles starting at dst. +*/ + +static void makeGradientBackgroundTiles(TileSliver *dst) { + for (TileSliver tile = 0x11111111; + tile < 0xDDDDDDDD; + tile += 0x11111111) { + for (unsigned int y = 0; y < 8; ++y) { + *dst++ = tile + gradientBackgroundDelta[y]; + } + } +} + +static void makeLayer2(void) { + makeGradientBackgroundTiles(MENU_GFX_VRAM(2, 0x300)); + for (int y = 0; y < 12; ++y) { + for (int x = 0; x < 32; ++x) { + MENU_GFX_MAP[29][y][x] = 0xE300 + y; + } + } + for (int y = 0; y < 12; ++y) { + for (int x = 0; x < 32; ++x) { + MENU_GFX_MAP[29][y + 12][x] = 0xF300 + y; + } + } + MENU_GFX_BGCTRL[2] = BG_TILE_BASE(2) | BG_MAP_BASE(29); + MENU_GFX_OFFSET[2].x = HIDDEN_COLS * 8; + MENU_GFX_OFFSET[2].y = HIDDEN_ROWS * 8; +} + + + +/* +Button palette: +0 transparent +1 White (button top) +2 Medium gray (button side) +3 Darkest gray (button bottom) +4 Light gray (button background) +5 Dark gray (button text aa, lower corner) +6 Black (button text) +7 Black + +This comes in both ordinary and highlighted versions. There are two copies of the highlighted text. + +*/ + +static const TileSliver buttonSideTiles[16] = { + 0x11111114, + 0x11111142, + 0x11111422, + 0x44444222, + 0x44444222, + 0x44444222, + 0x44444222, + 0x44444222, + 0x44444222, + 0x44444222, + 0x44444222, + 0x44444222, + 0x44444222, + 0x33333522, + 0x33333352, + 0x33333335 +}; + +static void loadButtonSideTiles(void) { + for (int bank = 0; bank <= 2; bank += 2) { + TileSliver *dst = MENU_GFX_VRAM(bank, 0x30C); + memcpy(dst, buttonSideTiles, 32); + memcpy(dst + 8, buttonSideTiles + 4, 32); + memcpy(dst + 16, buttonSideTiles + 8, 32); + } +} + +static const u8 buttonIntensity[8] = + {28, 31, 23, 15, 28, 19, 0, 0}; + +/** + * Loads the unhighlighted (gray) button palette. + */ +static void loadButtonPalette(void) { + for (int i = 0; i < 8; ++i) { + unsigned int intensity = buttonIntensity[i]; + + MENU_GFX_PALETTE[192 + i] = intensity * RGB5(1, 1, 1); + } +} + +void ljmenu_setHilitePalette(int phase) { + + // generate triangle wave + phase = (phase & 0x3F) ^ 0x20; + if (phase & 0x20) { + phase ^= 0x3F; + } + + for (int i = 0; i < 8; ++i) { + int intensity = buttonIntensity[i]; + int intensity34 = (3 * intensity) >> 2; + int rg = intensity34 + (phase >> 2) + 4; + int b = rg >> 1; + if (rg > 31) { + rg = 31; + } + unsigned int c = RGB5(1, 1, 0) * rg + RGB5(0, 0, 1) * b; + + MENU_GFX_PALETTE[200 + i] = c; + MENU_GFX_PALETTE[208 + i] = c; + } +} + +#define TILE_HFLIP 0x0400 + +void ljmenu_hiliteButton(int l, int t, int r, int b, int hilite) { + hilite = hilite ? 0xD000 : 0xC000; + + /* Draw sides of button */ + MENU_GFX_MAP[30][t][l] = 0x30C | hilite; + MENU_GFX_MAP[30][t][r - 1] = 0x30C | TILE_HFLIP | hilite; + for (int y = t + 1; y < b - 1; ++y) { + MENU_GFX_MAP[30][y][l] = 0x30D | hilite; + MENU_GFX_MAP[30][y][r - 1] = 0x30D | TILE_HFLIP | hilite; + } + MENU_GFX_MAP[30][b - 1][l] = 0x30E | hilite; + MENU_GFX_MAP[30][b - 1][r - 1] = 0x30E | TILE_HFLIP | hilite; + vwfPutMap(&vwfLayer0, l + 1, t, r - 1, b, hilite); +} + +void ljmenu_drawButton(int l, int t, int r, int b, const char *text) { + vwfRectfill(&vwfLayer0, + l * 8 + 8, t * 8, r * 8 - 8, t * 8 + 3, 1); + vwfRectfill(&vwfLayer0, + l * 8 + 8, t * 8 + 3, r * 8 - 8, b * 8 - 3, 4); + vwfRectfill(&vwfLayer0, + l * 8 + 8, b * 8 - 3, r * 8 - 8, b * 8, 3); + + int w = fontdraw_strWidth(text); + int x = l * 8 + (r - l) * 4 - w / 2; + int y = t * 8 + (b - t) * 4 - 12 / 2; + + vwfPuts(&vwfLayer0, text, x, y); + ljmenu_hiliteButton(l, t, r, b, 0); +} + +static void makePalettes(void) { + // make background layer palettes + for (unsigned int i = 0; i <= 12; ++i) { + int blue = 2 * i; + MENU_GFX_PALETTE[225 + i]= RGB5(0, 0, blue); + } + for (unsigned int i = 0; i <= 12; ++i) { + int red = ((i + 9) * 3) >> 1; + MENU_GFX_PALETTE[241 + i]= RGB5(red, red / 2, 0); + } + + MENU_GFX_PALETTE[1] = RGB5(21,21,23); + MENU_GFX_PALETTE[2] = RGB5(31,31,31); + MENU_GFX_PALETTE[3] = RGB5(31,31,31); + loadButtonPalette(); +} + +void ljmenu_cls(void) { + vwfWinInit(&vwfLayer0); + vwfPutMap(&vwfLayer0, 2, 4, 30, 22, 0xC000); +} + +void ljmenu_init(void) { + makeLayer2(); + loadButtonSideTiles(); + makePalettes(); + ljmenu_cls(); + MENU_GFX_BGCTRL[0] = BG_TILE_BASE(0) | BG_MAP_BASE(30); + MENU_GFX_OFFSET[0].x = HIDDEN_COLS * 8; + MENU_GFX_OFFSET[0].y = HIDDEN_ROWS * 8; + MENU_GFX_BGCTRL[1] = BG_TILE_BASE(2) | BG_MAP_BASE(31); + MENU_GFX_OFFSET[1].x = HIDDEN_COLS * 8; + MENU_GFX_OFFSET[1].y = HIDDEN_ROWS * 8; + videoSetModeMenu(MODE_0_2D | BG0_ON | BG1_ON | BG2_ON); +} + +void ljmenu_freeze(void) { + + // Copy background + memcpy(vwfLayer0.chrBase, vwfLayer1.chrBase, 240*160/2); + + // Copy map + memcpy(MENU_GFX_MAP[30], MENU_GFX_MAP[31], sizeof(NAMETABLE)); +} + +void ljmenu_setTitle(const char *topLeft, const char *topRight) { + vwfRectfill(&vwfLayer0, 16, 16, 240, 16 + 12, 0); + vwfPuts(&vwfLayer0, topLeft, 16, 16); + if (topRight) { + int x = 240 - fontdraw_strWidth(topRight); + vwfPuts(&vwfLayer0, topRight, x, 16); + } +} + +static unsigned short tabsX; +static unsigned short tabsPadding; +#define TAB_TOP 32 +#define TAB_BOTTOM 44 +#define TAB_LEFT 16 +#define TAB_RIGHT 240 + +void ljmenu_beginTabs(unsigned int padding) { + tabsX = TAB_LEFT; + tabsPadding = padding; +} + +void ljmenu_addTab(const char *text, int hilite) { + unsigned int bgColor = hilite ? 12 : 4; + unsigned int w = text ? fontdraw_strWidth(text) : 0; + unsigned int left = tabsX; + unsigned int right = left + 2 * tabsPadding + w; + + if (right > TAB_RIGHT) { + return; + } + vwfRectfill(&vwfLayer0, + left, TAB_TOP, right, TAB_BOTTOM, + bgColor); + if (hilite) { + vwfHline(&vwfLayer0, + left, TAB_TOP, right, + bgColor + 1); + vwfVline(&vwfLayer0, + left, TAB_TOP, TAB_BOTTOM, + bgColor + 1); + vwfVline(&vwfLayer0, + right - 1, TAB_TOP, TAB_BOTTOM, + bgColor + 1); + } else { + vwfHline(&vwfLayer0, + left, TAB_BOTTOM - 1, right, + bgColor + 1); + } + if (text) { + vwfPuts(&vwfLayer0, text, left + tabsPadding, TAB_TOP); + } + tabsX = right; +} + +void ljmenu_endTabs(void) { + vwfRectfill(&vwfLayer0, + tabsX, TAB_TOP, TAB_RIGHT, TAB_BOTTOM - 1, + 4); + vwfHline(&vwfLayer0, + tabsX, TAB_BOTTOM - 1, TAB_RIGHT, + 5); +} + +#define PROPPANEL_TOP TAB_BOTTOM +#define PROPPANEL_HT 12 +#define PROPPANEL_ROWS 7 +#define PROPPANEL_BOTTOM (PROPPANEL_TOP + PROPPANEL_HT * PROPPANEL_ROWS) + +void ljmenu_propPanelClear(unsigned int nRows) { + if (nRows > PROPPANEL_ROWS) { + nRows = PROPPANEL_ROWS; + } + int y = PROPPANEL_TOP + PROPPANEL_HT * nRows; + vwfRectfill(&vwfLayer0, + TAB_LEFT, PROPPANEL_TOP, TAB_RIGHT, y, + 4); + vwfRectfill(&vwfLayer0, + TAB_LEFT, y, TAB_RIGHT, PROPPANEL_BOTTOM, + 0); +} + +void ljmenu_propPanelDrawRow(const char *name, const char *value, + unsigned int y, unsigned int hilite) { + if (y > PROPPANEL_ROWS) { + return; + } + y = PROPPANEL_TOP + PROPPANEL_HT * y; + unsigned int bgColor = hilite ? 12 : 4; + + vwfRectfill(&vwfLayer0, + TAB_LEFT, y, TAB_RIGHT, y + PROPPANEL_HT, + bgColor); + if (hilite) { + vwfRect(&vwfLayer0, + TAB_LEFT, y, TAB_RIGHT, y + PROPPANEL_HT, + bgColor + 1); + } + if (name) { + vwfPuts(&vwfLayer0, name, TAB_LEFT + 8, y); + } + if (value) { + int x = TAB_RIGHT - 8 - fontdraw_strWidth(value); + vwfPuts(&vwfLayer0, value, x, y); + } +} + +static void roundrect(const VWFWindow *w, + int l, int t, int r, int b, int c) { + vwfRectfill(w, l, t + 2, r, b - 2, c); + vwfHline(w, l + 2, t, r - 2, c); + vwfHline(w, l + 1, t + 1, r - 1, c); + vwfHline(w, l + 1, b - 2, r - 1, c); + vwfHline(w, l + 2, b - 1, r - 2, c); +} + +static void ljmenu_balloon(const char *text1, const char *text2, + int l, int t, int r) { + int b = t + (text2 ? 24 : 12); + roundrect(&vwfLayer0, l, t, r, b, 4); + vwfPuts(&vwfLayer0, text1, l + 4, t); + if (text2) { + vwfPuts(&vwfLayer0, text2, l + 4, t + 12); + } +} + +void ljmenu_propPanelDrawDesc(const char *text1, const char *text2) { + ljmenu_balloon(text1, text2, TAB_LEFT, PROPPANEL_BOTTOM + 4, TAB_RIGHT); +} + +void ljmenu_propPanelDrawHelp(const char *text1, const char *text2) { + int y = text2 ? 176 - 24 : 176 - 12; + ljmenu_balloon(text1, text2, TAB_LEFT, y, TAB_RIGHT); +} + +#if 0 +static void ljmenu_pressA(void) { + int phase = 0; + while (!(REG_KEYINPUT & KEY_A)) { + ++phase; + vsync(); + ljmenu_setHilitePalette(phase); + } + while (REG_KEYINPUT & KEY_A) { + ++phase; + vsync(); + ljmenu_setHilitePalette(phase); + } +} + +void ljmenuTest(void) { + ljmenu_init(); + ljmenu_cls(); + ljmenu_setTitle("LOCKJAW 0.43", "© 2008 Damian Yerrick"); + ljmenu_drawButton(22, 18, 22 + 8, 18 + 3, "OK"); + ljmenu_hiliteButton(22, 18, 22 + 8, 18 + 3, 1); + ljmenu_drawButton(13, 18, 13 + 8, 18 + 3, "Cancel"); + ljmenu_drawButton(2, 8, 30, 11, + "coming soon: the new look of LOCKJAW"); + ljmenu_pressA(); + + ljmenu_cls(); + ljmenu_setTitle("LOCKJAW 0.43", "© 2008 Damian Yerrick"); + ljmenu_beginTabs(4); + ljmenu_addTab("Game", 1); + ljmenu_addTab("Well", 0); + ljmenu_addTab("Move", 0); + ljmenu_addTab("Line", 0); + ljmenu_addTab("Ctrl", 0); + ljmenu_addTab("Drop", 0); + ljmenu_addTab("Disp", 0); + ljmenu_endTabs(); + ljmenu_propPanelClear(7); + ljmenu_propPanelDrawRow("Gimmick", "Marathon", 0, 1); + ljmenu_propPanelDrawRow("Mr. Gimmick", "Halo", 1, 0); + ljmenu_propPanelDrawDesc("Goal or other game mode", + "Play until you DIE."); + ljmenu_propPanelDrawHelp("u d: move; l r: change; L R: page; Start: OK", + NULL); + ljmenu_pressA(); +} + +#endif diff -r 000000000000 -r c84446dfb3f5 src/gbamenus.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gbamenus.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,26 @@ +#ifndef GBAMENUS_H +#define GBAMENUS_H + +void ljmenu_init(void); +void ljmenu_cls(void); +void ljmenu_setTitle(const char *topLeft, const char *topRight); +void ljmenu_freeze(void); + +void ljmenu_drawButton(int l, int t, int r, int b, const char *text); +void ljmenu_hiliteButton(int l, int t, int r, int b, int hilite); +void ljmenu_setHilitePalette(int phase); + +void ljmenu_beginTabs(unsigned int padding); +void ljmenu_addTab(const char *text, int hilite); +void ljmenu_endTabs(void); + +void ljmenu_propPanelClear(unsigned int nRows); +void ljmenu_propPanelDrawRow(const char *name, const char *value, + unsigned int y, unsigned int hilite); +void ljmenu_propPanelDrawDesc(const char *text1, const char *text2); +void ljmenu_propPanelDrawHelp(const char *text1, const char *text2); + + +void ljmenuTest(void); + +#endif diff -r 000000000000 -r c84446dfb3f5 src/gbanotefreq.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gbanotefreq.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,25 @@ +/* notefreq.h generated by mkpowers.c + lookup table for note frequencies */ + +/* DMG Sound period values + frequency = 131072 Hz/(2048 + n) */ +const unsigned short note_freqs[64] = { + 44, 156, 262, 362, 457, 546, 630, 710, 785, 856, 923, 986, + 1046, 1102, 1155, 1205, 1252, 1297, 1339, 1379, 1416, 1452, 1485, 1517, + 1547, 1575, 1601, 1626, 1650, 1672, 1693, 1713, 1732, 1750, 1766, 1782, + 1797, 1811, 1824, 1837, 1849, 1860, 1870, 1880, 1890, 1899, 1907, 1915, + 1922, 1929, 1936, 1942, 1948, 1954, 1959, 1964, 1969, 1973, 1977, 1981, + 1985, 1988, 1992, 1995 +}; +#if 0 +/* DMG Sound period values + frequency = base_freq * n / 4096 */ +const unsigned short sampled_freqs[64] = { + 1024, 1084, 1149, 1217, 1290, 1366, 1448, 1534, 1625, 1722, 1824, 1933, + 2048, 2169, 2298, 2435, 2580, 2733, 2896, 3068, 3250, 3444, 3649, 3866, + 4096, 4339, 4597, 4870, 5160, 5467, 5792, 6137, 6501, 6888, 7298, 7732, + 8192, 8679, 9195, 9741,10321,10935,11585,12274,13003,13777,14596,15464, +16384,17358,18390,19483,20642,21870,23170,24548,26007,27554,29192,30928, +32768,34716,36780,38967 +}; +#endif \ No newline at end of file diff -r 000000000000 -r c84446dfb3f5 src/gbaopt.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gbaopt.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,123 @@ +/* + +options for gba port of lockjaw + +*/ + +#include +#include +#include "options.h" +#include "ljlocale.h" +#include "ljplay.h" +#include "gbamenus.h" + +#ifdef ARM9 +#include "ljds.h" +#else +#include "ljgba.h" +#endif + +#define optionsMenu commonOptionsMenu + +unsigned char customPrefs[OPTIONS_MENU_LEN]; + +void unpackOptions(LJView *v, const unsigned char *prefs) { + unpackCommonOptions(v, prefs); +} + +static const char optionsHelpText[] = +"\020\021 page \026\027 move \025\024 change Start:play"; + +void optionsWinInit(void) { + ljmenu_init(); + ljmenu_cls(); + ljmenu_propPanelDrawHelp(optionsHelpText, + NULL); +} + +static void optionsClearRow(int y, int hilite) { + ljmenu_propPanelDrawRow("", "", y, hilite); +} + +static char hiliteBlinkPhase = 1; + +void optionsDrawRow(const unsigned char *prefs, + int y, int line, int value, int hilite) { + char txt[OPTIONS_VALUE_LEN]; + const char *nameText; + const char *valueDesc = NULL; + char altNameText[8]; + const char *valueOverride = isDisabledOption(prefs, line); + + { + nameText = ljGetFourCCName(optionsMenu[line].name); + if (!nameText) { + strncpy(altNameText, optionsMenu[line].name.c, 4); + altNameText[4] = 0; + nameText = altNameText; + } + } + + // Format value + if (valueOverride) { + strcpy(txt, "overridden"); + valueDesc = valueOverride; + } else { + valueDesc = getOptionsValueStr(txt, line, value); + } + + ljmenu_propPanelDrawRow(nameText, txt, y, hilite); + + if (hilite & 1) { + hiliteBlinkPhase = 0; + const char *descText = ljGetFourCCDesc(optionsMenu[line].name); + ljmenu_propPanelDrawDesc(descText ? descText : "", + valueDesc ? valueDesc : ""); + } +} + +static const char *const optionsPageShortNames[7] = { + "Game", "Well", "Move", "Line", "Ctrl", "Drop", "Disp" +}; + +void optionsDrawPage(int page, const unsigned char *prefs) { + int nPages = 0; + for (; optionsPages[nPages].name; ++nPages) { } + + // draw page title + ljmenu_setTitle("LOCKJAW > Options", optionsPages[page].name); + + // draw all tabs + ljmenu_beginTabs(4); + for (int p = 0; p < 7; ++p) { + ljmenu_addTab(optionsPageShortNames[p], p == page); + } + ljmenu_endTabs(); + + for (int i = optionsPages[page].start; + i < optionsPages[page + 1].start; ++i) { + optionsDrawRow(prefs, i - optionsPages[page].start, + i, prefs[i], 0); + } + for (int y = optionsPages[page + 1].start - optionsPages[page].start; + y < 7; ++y) { + optionsClearRow(y, 0); + } +} + +void optionsIdle(void) { + vsync(); + ljmenu_setHilitePalette(++hiliteBlinkPhase); +} + +#if 0 +typedef u16 hicolor_t; + +__attribute__((aligned(4))) +const hicolor_t gbaOptionsPalette[8] = +{ + RGB5(31,31,31), RGB5(20,20,20), RGB5( 0, 0, 0), RGB5( 0, 0, 0), + RGB5(31,31,23), RGB5(20,20,15), RGB5( 0, 0, 0), RGB5( 0, 0, 0) +}; +#endif + diff -r 000000000000 -r c84446dfb3f5 src/gbasound.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gbasound.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,224 @@ +#include +#include "ljgba.h" +#include "pin8gba_sound.h" +extern const unsigned short note_freqs[]; + +static const u16 moveEffect[] = { + 0, 3, + 0x516D, 0x0000, 0x0100 +}; + +static const u16 rotateEffect[] = { + 1, 7, + 0x5161, 0x0000, 0x5166, 0x0000, 0x516b, 0x0000, 0x0100 +}; + +static const u16 landEffect[] = { + 1, 6, + 0x8188, 0x7185, 0x6182, 0x5181, 0x3180, 0x0000 +}; + +static const u16 lockEffect[] = { + 3, 1, + 0x2138 +}; + +static const u16 lineEffect[] = { + 0, 12, + 0xB1A4, 0xA1A6, 0x91A9, 0x81A4, 0x71A7, 0x61A9, + 0x61A4, 0x51A6, 0x41A9, 0x31A4, 0x21A7, 0x11A9 +}; + +static const u16 holdEffect[] = { + 3, 8, + 0x3030, 0x0000, 0x4032, 0x0000, 0x3130, 0x0000, 0x202c, 0x2128 +}; + +static const u16 irsRotateEffect[] = { + 1, 12, + 0x5161, 0x51B1, 0x5166, 0x51B6, 0x516B, 0x51BB, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0100 +}; + +static const u16 homerEffect[] = { + 0, 22, + 0xB1A4, 0xA1A6, 0x91A9, 0x81A4, 0x71A7, 0x61A9, + 0x61A4, 0x51A6, 0x41A9, 0x31A4, + 0xB1A7, 0xA1A9, 0x91AC, 0x81A7, 0x71AA, 0x61AC, + 0x51A7, 0x41A9, 0x31AC, 0x21A7, 0x11AA, 0x01AC +}; + +static const u16 streakEffect[] = { + 0, 30, + 0xB1A4, 0xA1A6, 0x91A9, 0x81A4, 0x71A7, 0x61A9, + 0x61A4, 0x51A6, 0x41A9, 0x31A4, + 0xB1A7, 0xA1A9, 0x91AC, 0x81A7, 0x71AA, 0x61AC, + 0x51A7, 0x41A9, 0x31AC, 0x21A7, + 0xB1AA, 0xA1AC, 0x91AF, 0x71AA, 0x61AD, 0x51AF, + 0x31AA, 0x21AC, 0x11AF, 0x01AA +}; + +static const u16 sectionEffect[] = { + 1, 30, + 0xB1B0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0xB1B4, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0xB1B7, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0xB1BC, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0xB1B0, 0x0000 +}; + +static const u16 gameOverEffect1[] = { + 1, 40, + 0xF504, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; + +static const u16 gameOverEffect2[] = { + 3, 40, + 0xC12C, 0xA725, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; + +static const u16 winEffect1[] = { + 1, 30, + 0xA564, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000, + 0xA562, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000, + 0xA564, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000, + 0xA566, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000 +}; + +static const u16 winEffect2[] = { + 0, 30, + 0xA568, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000, + 0xA566, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000, + 0xA568, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000, + 0xA56A, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000 +}; + + +static const u16 *const effects[] = { + moveEffect, rotateEffect, landEffect, lockEffect, lineEffect, + holdEffect, irsRotateEffect, homerEffect, streakEffect, sectionEffect, + gameOverEffect1, gameOverEffect2, winEffect1, winEffect2 +}; + +const unsigned short tri_vol_table[5] = { + 0, + TRILENVOL_25, + TRILENVOL_50, + TRILENVOL_75, + TRILENVOL_100 +}; + +void play_note(unsigned int ch, unsigned int note, unsigned int instrument) { + switch (ch) { + case 0: + SQR1CTRL = instrument; + SQR1FREQ = note_freqs[note] | FREQ_RESET; + break; + case 1: + SQR2CTRL = instrument; + SQR2FREQ = note_freqs[note] | FREQ_RESET; + break; + case 2: + { + int vol = instrument >> 13; + TRILENVOL = tri_vol_table[(vol + 1) / 2]; + TRIFREQ = note_freqs[note] | FREQ_RESET; + } + break; + case 3: + NOISECTRL = instrument; + { + int octave = (note & 0x3E) << 2; + int pitch = note & 0x03; + NOISEFREQ = (octave | pitch) ^ (0xF7 | FREQ_RESET); + } + break; + } +} + +void gba_poll_sound(struct LJPCView *v) { + for (int i = 0; i < 4; ++i) { + if (v->sndLeft[i]) { + unsigned int data = *v->sndData[i]; + if (data) { + play_note(i, data & 0x003F, data & 0xFFC0); + } + ++v->sndData[i]; + --v->sndLeft[i]; + } + } +} + +void gba_play_sound(struct LJPCView *v, int effect) { + const u16 *data = effects[effect]; + int ch = data[0]; + int len = data[1]; + + // square waves can be played on either channel 0 or 1; + // switch if this channel is occupied and the other is free + if (ch < 2 + && v->sndLeft[ch] > v->sndLeft[1 - ch]) { + ch = 1 - ch; + } + v->sndData[ch] = data + 2; + v->sndLeft[ch] = len; +} + +/** + * Sets the sound bias to mid and resolution to 8-bit. + * Setting bias is needed because some launchers (such as pogoshell) + * set it to a state that mutes the tone generators. + */ +static void set_bias(void) +{ + asm volatile("mov r2, #2; lsl r2, #8; swi 0x19" ::: "r0", "r1", "r2", "r3"); + SETSNDRES(1); +} + +__attribute__((aligned(4))) +static const unsigned char triangleWave[16] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, + 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 +}; +unsigned char lastWaveBank = 0; + +void tri_set_waveform(const void *waveform) { + memcpy((void *)TRIWAVERAM, waveform, 16); + lastWaveBank = !lastWaveBank; + TRICTRL = TRICTRL_2X32 | TRICTRL_BANK(lastWaveBank) | TRICTRL_ENABLE; +} + +void install_sound(struct LJPCView *v) { + SNDSTAT = SNDSTAT_ENABLE; + DMGSNDCTRL = DMGSNDCTRL_LVOL(7) | DMGSNDCTRL_RVOL(7) + | DMGSNDCTRL_LSQR1 | DMGSNDCTRL_RSQR1 + | DMGSNDCTRL_LSQR2 | DMGSNDCTRL_RSQR2 + | DMGSNDCTRL_LTRI | DMGSNDCTRL_RTRI + | DMGSNDCTRL_LNOISE | DMGSNDCTRL_RNOISE; + DSOUNDCTRL = DSOUNDCTRL_DMG100; + set_bias(); + SQR1SWEEP = SQR1SWEEP_OFF; + +#if 0 + TRICTRL = TRICTRL_2X32 | TRICTRL_BANK(0) | TRICTRL_ENABLE; + tri_set_waveform(triangleWave); + TRILENVOL = 0; + TRIFREQ = 1024 | FREQ_RESET; +#endif + + v->sndLeft[0] = 0; + v->sndLeft[1] = 0; + v->sndLeft[2] = 0; + v->sndLeft[3] = 0; +} + diff -r 000000000000 -r c84446dfb3f5 src/gimmicks.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gimmicks.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,295 @@ +/* Gimmick code for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006-2007 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#include "lj.h" +#include "ljcontrol.h" + +void initSpeed(LJField *p); // in speed.c +void setSpeed(LJField *p, LJControl *c); // in speed.c +int updLevelAfterPiece(LJField *p); // in speed.c +int updLevelAfterLines(LJField *p, size_t nLines); // in speed.c + +void initGimmicks(LJField *p) { + initSpeed(p); + p->speed.entryDelay = 0; /* new pieces will increase this */ + p->speed.lineDelay = 0; + if (p->garbageStyle == LJGARBAGE_DRILL + || p->gimmick == LJGM_DRILL) { + p->garbageRandomness = 255; + p->garbage = p->ceiling - 2; + } else if (p->garbageStyle == LJGARBAGE_ZIGZAG) { + setupZigzagField(p, p->ceiling * 3 / 4); + } + +} + +LJBits gimmicks(LJField *p, LJControl *c) { + LJBits changed = 0; + + // In rhythm, lock the tetromino if the player + // isn't keeping up with the rate + if (p->speedState.curve == LJSPD_RHYTHM + || p->speedState.curve == LJSPD_RHYTHMZERO) { + p->bpmCounter += p->speedState.level; + + if (p->bpmCounter >= 0) { + if (p->state == LJS_LANDED) { + p->stateTime = 0; + } else if (p->state == LJS_FALLING) { + p->speed.gravity = ljitofix(LJ_PF_HT); + } + } + + // In rhythm, increase BPM periodically + p->speedupCounter += p->speedState.level; + if(p->speedupCounter >= 60 * 60 * 64) { + p->speedupCounter -= 60 * 60 * 64; + p->speedState.level += 10; + p->sounds |= LJSND_SECTIONUP; + } + } + + // For each piece, set the entry and line delays. + // Don't set it twice when spawning to replace the + // first held piece (both LJSND_SPAWN and LJSND_HOLD + // on the same piece). + if ((p->sounds & (LJSND_SPAWN | LJSND_HOLD)) + == LJSND_SPAWN) { + setSpeed(p, c); + } + + if (p->sounds & LJSND_LOCK) { + if (p->gimmick == LJGM_ITEMS) { + if (p->nPieces >= 7) { + p->canRotate = 0; + p->speed.gravity = ljitofix(1); + } + } + + // Garbage in simulated multiplayer + int simGarbage = p->nPieces >= 7 + && p->garbageStyle >= LJGARBAGE_1 + && p->garbageStyle <= LJGARBAGE_4 + && (p->curPiece[1] == LJP_I + || ((p->pieceSet == LJRAND_SZ + || p->pieceSet == LJRAND_JLOSTZ) + && p->nPieces % 7 == 3)); + if (simGarbage) { + p->garbage += p->garbageStyle; + } + + // Banana attack in "Vs. with Items" gimmick + if (p->gimmick == LJGM_ITEMS + && (ljRand(p) & 0xF00) == 0xF00) { + shuffleColumns(p); + changed |= (1 << LJ_PF_HT) - 1; + } + } + return changed; +} + + + +const char hotlineRows[LJ_PF_HT] = +{ + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 2, + 0, 0, 0, 3, 0, + 0, 4, 0, 5, 6, + 0, 0, 0, 0 +}; + +static const char garbageScoreTable[] = { 0, 0, 1, 2, 4 }; +static const char tSpinGarbageScoreTable[] = { 0, 2, 4, 6, 6 }; +static const unsigned char squareScoreTable[] = + {1, 1, 1, 2, 3, 5, 8, 13, + 21, 34, 55, 89, 144, 200}; + +static const char tdsScoreTable[] = {0, 1, 3, 5, 8}; +static const char tdsTSScoreTable[] = {4, 8, 12, 16}; +static const char nesScoreTable[] = {0, 4, 10, 30, 120}; + + +/** + * Computes the score and outgoing garbage for lines + * and adds them to the player's total. + * @param p The playfield + * @param lines Bit array where 1 means a line clear on this row. + */ +void addLinesScore(LJField *p, LJBits lines) { + const int nLines = countOnes(lines); + int oldLines = p->nLinesThisPiece; + int tdsSection = p->lines / 10 + 1; + + p->lines += nLines; + if (updLevelAfterLines(p, nLines)) { + p->sounds |= LJSND_SECTIONUP; + } + + switch (p->scoreStyle) { + + case LJSCORE_TNT64: + if (nLines < 1) { + return; + } + for (int i = nLines; i > 0; --i) { + if (oldLines > sizeof(squareScoreTable) - 1) { + oldLines = sizeof(squareScoreTable) - 1; + } + p->score += 100 * squareScoreTable[oldLines++]; + } break; + + case LJSCORE_NES: + { + int garbageLevel = (nLines > 4) ? 4 : nLines; + int value = nesScoreTable[garbageLevel]; + + p->score += 10 * tdsSection * value; + if (nLines >= 4) { + p->sounds |= LJSND_SETB2B; + } + } break; + + case LJSCORE_TDS: + { + // Garbage based scoring system. + int garbageLevel = (nLines > 4) ? 4 : nLines; + int chain, value; + + if (p->isSpin) { + chain = (nLines >= 1); + value = tdsTSScoreTable[garbageLevel]; + + // TDS gives fewer points for a T-spin single + // that involves a wall kick. + if (p->isSpin == 2 && nLines < 2) { + value >>= 2; + } + } else { + chain = (nLines >= 4); + value = tdsScoreTable[garbageLevel]; + } + if (chain && p->chain && nLines >= 1) { // b2b + value = value * 3 / 2; + } + + if (tdsSection > 20) { + tdsSection = 20; + } + p->score += 100 * tdsSection * value; + if (nLines >= 1) { + if (chain) { + p->sounds |= LJSND_SETB2B; + if (p->chain) { + p->sounds |= LJSND_B2B; + } + } + p->chain = chain; + } + } break; + + case LJSCORE_LJ: + case LJSCORE_LJ_NERF: + if (nLines >= 1) { + // Garbage based scoring system. + int garbageLevel = (nLines > 4) ? 4 : nLines; + unsigned int chain, garbage; + + if (p->isSpin) { + chain = (nLines >= 1); + garbage = tSpinGarbageScoreTable[garbageLevel]; + if (p->scoreStyle == LJSCORE_LJ_NERF) { + garbage /= 2; + } + garbage += (chain && p->chain); + } else { + chain = (nLines >= 4); + garbage = garbageScoreTable[garbageLevel] + + (chain && p->chain); + } + p->score += 100 * nLines + 200 * garbage; + p->outGarbage += garbage; + if (chain) { + p->sounds |= LJSND_SETB2B; + if (p->chain) { + p->sounds |= LJSND_B2B; + } + } + p->chain = chain; + } break; + + case LJSCORE_HOTLINE: + for (int y = 0; y < 24; ++y) { + if (lines & (1 << y)) { + p->score += 100 * hotlineRows[y]; + } + } break; + + } + + p->nLinesThisPiece += nLines; +} + +/** + * Puts a zigzag pattern of garbage into the playfield. + * Useful as a test suite for calcZigzagGrade() in debrief.c. + * @param p the playfield to set up + * @param height the number of rows to set up + */ +void setupZigzagField(LJField *p, size_t height) { + unsigned int hole = p->leftWall; + int delta = 1; + + if (height > p->ceiling) { + height = p->ceiling; + } + + // set up example pattern + for (size_t y = 0; y < height; ++y) { + for (size_t x = p->leftWall; x < p->rightWall; ++x) { + + // A block should be in all cells of the row + // except for the hole cell, which should be empty. + if (x == hole) { + p->b[y][x] = 0; + } else { + p->b[y][x] = 0x80; + } + } + + if (hole == p->rightWall - 1) { + delta = -1; + } else if (hole == p->leftWall) { + delta = 1; + } + hole += delta; + } + + // clear rows above pattern + for (size_t y = height; y < LJ_PF_HT; ++y) { + for (size_t x = p->leftWall; x < p->rightWall; ++x) { + p->b[y][x] = 0; + } + } +} diff -r 000000000000 -r c84446dfb3f5 src/lj.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lj.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,1516 @@ +/* Engine of LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + + +*/ + +#define LJ_INTERNAL +#include "lj.h" + +unsigned int ljRand(LJField *p) { + p->seed = p->seed * 2147001325 + 715136305; + return p->seed >> 17; +} + +static inline void ljAssert(LJField *p, int shouldBeTrue, const char *reason) { + if (!shouldBeTrue) { + p->state = LJS_GAMEOVER; + } +} + +static const char xShapes[N_PIECE_SHAPES][4][4] = { + { {0,1,2,3}, {2,2,2,2}, {3,2,1,0}, {1,1,1,1} }, // I + { {0,1,2,0}, {1,1,1,2}, {2,1,0,2}, {1,1,1,0} }, // J + { {0,1,2,2}, {1,1,1,2}, {2,1,0,0}, {1,1,1,0} }, // L + { {1,1,2,2}, {1,2,2,1}, {2,2,1,1}, {2,1,1,2} }, // O + { {0,1,1,2}, {1,1,2,2}, {2,1,1,0}, {1,1,0,0} }, // S + { {0,1,2,1}, {1,1,1,2}, {2,1,0,1}, {1,1,1,0} }, // T + { {0,1,1,2}, {2,2,1,1}, {2,1,1,0}, {0,0,1,1} }, // Z + { {0,1,2,3}, {2,2,2,2}, {3,2,1,0}, {1,1,1,1} }, // I2 + { {0,1,2,0}, {1,1,1,2}, {2,1,0,2}, {1,1,1,0} }, // I3 + { {1,1,2,2}, {1,2,2,1}, {2,2,1,1}, {2,1,1,2} }, // L3 +}; + +static const char yShapes[N_PIECE_SHAPES][4][4] = { + { {2,2,2,2}, {3,2,1,0}, {1,1,1,1}, {0,1,2,3} }, // I + { {2,2,2,3}, {3,2,1,3}, {2,2,2,1}, {1,2,3,1} }, // J + { {2,2,2,3}, {3,2,1,1}, {2,2,2,1}, {1,2,3,3} }, // L + { {2,3,3,2}, {3,3,2,2}, {3,2,2,3}, {2,2,3,3} }, // O + { {2,2,3,3}, {3,2,2,1}, {2,2,1,1}, {1,2,2,3} }, // S + { {2,2,2,3}, {3,2,1,2}, {2,2,2,1}, {1,2,3,2} }, // T + { {3,3,2,2}, {3,2,2,1}, {1,1,2,2}, {1,2,2,3} }, // Z + { {2,2,2,2}, {3,2,1,0}, {1,1,1,1}, {0,1,2,3} }, // I2 + { {2,2,2,3}, {3,2,1,3}, {2,2,2,1}, {1,2,3,1} }, // I3 + { {2,3,3,2}, {3,3,2,2}, {3,2,2,3}, {2,2,3,3} }, // L3 +}; + +const char pieceColors[N_PIECE_SHAPES] = { + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, + 0x40, 0x10, 0x20 +}; + +static const signed char connShapes[N_PIECE_SHAPES][4] = { + {CONNECT_R, CONNECT_LR, CONNECT_LR, CONNECT_L}, // I + {CONNECT_UR, CONNECT_LR, CONNECT_L, CONNECT_D}, // J + {CONNECT_R, CONNECT_LR, CONNECT_UL, CONNECT_D}, // L + {CONNECT_UR, CONNECT_DR, CONNECT_DL, CONNECT_UL}, // O + {CONNECT_R, CONNECT_UL, CONNECT_DR, CONNECT_L}, // S + {CONNECT_R, CONNECT_ULR, CONNECT_L, CONNECT_D}, // T + {CONNECT_R, CONNECT_DL, CONNECT_UR, CONNECT_L}, // Z + {-1, CONNECT_R, CONNECT_L, -1}, // I2 + {CONNECT_R, CONNECT_LR, CONNECT_L, -1}, // I3 + {CONNECT_UR, CONNECT_D, -1, CONNECT_L}, // L3 +}; + +static inline int pieceToFieldBlock(int piece, int conn) { + return conn | pieceColors[piece]; +} + +/** + * @theta rotation state: 0-3 normal, 4 for next piece + */ +void expandPieceToBlocks(LJBlkSpec out[], + const LJField *p, int piece, int xBase, int yBase, int theta) { + int shape = piece & LJP_MASK; + + if (theta >= 4) { + const LJRotSystem *rs = rotSystems[p->rotationSystem]; + int kickData = rs->entryOffset[shape]; + xBase += WKX(kickData); + yBase += WKY(kickData); + kickData = rs->entryOffset[LJP_I]; + xBase -= WKX(kickData); + yBase -= WKY(kickData); + theta = rs->entryTheta[shape]; + } + + const char *xBl = xShapes[shape][theta]; + const char *yBl = yShapes[shape][theta]; + + for (int blk = 0; blk < 4; ++blk) { + if (connShapes[shape][blk] == -1) { + out[blk].conn = 0; + } else { + int conn = connShapes[shape][blk] << theta; + int connRotated = (conn | (conn >> 4)) & CONNECT_MASK; + int color = ((0x10 << blk) & piece) ? 8 : piece; + out[blk].y = yBl[blk] + yBase; + out[blk].x = xBl[blk] + xBase; + out[blk].conn = pieceToFieldBlock(color, connRotated); + } + } +} + +int isOccupied(const LJField *p, int x, int y) { + if (x < p->leftWall || x >= p->rightWall || y < 0) + return 1; + if (y > LJ_PF_HT) + return 0; + return p->b[y][x] > 1; +} + +int isCollision(const LJField *p, int x, int y, int theta) { + LJBlkSpec blocks[4]; + int piece = p->curPiece[0]; + + expandPieceToBlocks(blocks, p, piece, x, y, theta); + + for (int blk = 0; blk < 4; ++blk) { + if (blocks[blk].conn + && isOccupied(p, blocks[blk].x, blocks[blk].y)) + return 1; + } + return 0; +} + +/** + * Determines whether the piece that just landed was a T-spin. + * Must be called just BEFORE lockdown writes the blocks to the + * playfield; otherwise TNT will break. + */ +static int isTspin(const LJField *p) { + int blks = 0; + int x = p->x; + int y = p->hardDropY; + + switch (p->tSpinAlgo) { + case LJTS_TNT: + if (!isCollision(p, x, y + 1, p->theta) + || !isCollision(p, x - 1, y, p->theta) + || !isCollision(p, x + 1, y, p->theta)) { + return 0; + } + return p->isSpin; + case LJTS_TDS_NO_KICK: + + // If t-spin involved wall kick, don't count it + if (p->isSpin == 2) { + return 0; + } + + // otherwise fall through + case LJTS_TDS: + // 1. T tetromino + if ((p->curPiece[0] & LJP_MASK) != LJP_T) { + return 0; + } + + // 2. Last move was spin + if (!p->isSpin) { + return 0; + } + + // 3. At least three cells around the rotation center are full + if (isOccupied(p, x, y + 1)) { + ++blks; + } + if (isOccupied(p, x, y + 3)) { + ++blks; + } + if (isOccupied(p, x + 2, y + 1)) { + ++blks; + } + if (isOccupied(p, x + 2, y + 3)) { + ++blks; + } + if (blks < 3) { + return 0; + } + + // 3. Last move was spin + return p->isSpin; + default: + return 0; + } +} + +/** + * Calculates where the active piece in a playfield will fall + * if dropped, and writes it back to the playfield. + * This value is used for ghost piece, gravity, soft drop, and + * hard drop. Call this after the active piece has been spawned, + * moved, or rotated. + * @param p the playfield + */ +static void updHardDropY(LJField *p) { + int x = p->x; + int y = ljfixfloor(p->y); + int theta = p->theta; + + if (p->bottomBlocks) { + y = -2; + while (y < (int)LJ_PF_HT + && y < ljfixfloor(p->y) + && isCollision(p, x, y, theta)) { + ++y; + } + } + else { + while (!isCollision(p, x, y - 1, theta)) { + --y; + } + } + p->hardDropY = y; +} + + +/** + * Look for a TNT square in this position. + * @param x column of left side, such that 0 <= x <= playfield width - 4 + * @param y row of bottom block, such that 0 <= y <= playfield height - 4 + * @param isMulti nonzero for multisquares; 0 for monosquares + * @return nonzero if found; 0 if not found + */ +static int isSquareAt(LJField *p, int x, int y, int isMulti) +{ + int firstColor = p->b[y][x] & COLOR_MASK; + + // Check the frame to make sure it isn't connected to anything else + for(int i = 0; i <= 3; i++) + { + /* don't allow squares within parts of squares */ + if((p->b[y + i][x] & COLOR_MASK) >= 0x80) + return 0; + /* the block doesn't connect on the left */ + if(p->b[y + i][x] & CONNECT_L) + return 0; + /* the block doesn't connect on the right */ + if(p->b[y + i][x + 3] & CONNECT_R) + return 0; + /* the block doesn't connect on the bottom */ + if(p->b[y][x + i] & CONNECT_D) + return 0; + /* the block doesn't connect on the top */ + if(p->b[y + 3][x + i] & CONNECT_U) + return 0; + } + + for(int ySub = 0; ySub < 4; ++ySub) + { + for(int xSub = 0; xSub <= 3; ++xSub) + { + int blkHere = p->b[y + ySub][x + xSub]; + + /* the square contains no nonexistent blocks */ + if(!blkHere) + return 0; + /* the square contains no blocks of garbage or broken pieces */ + if((blkHere & COLOR_MASK) == 0x80) + return 0; + /* if looking for monosquares, disallow multisquares */ + if(isMulti == 0 && (blkHere & COLOR_MASK) != firstColor) + return 0; + } + } + return 1; +} + +/** + * Replaces the 4x4 blocks with a 4x4 square. + * @param x column of left side, such that 0 <= x <= playfield width - 4 + * @param y row of bottom block, such that 0 <= y <= playfield height - 4 + * @param isMulti nonzero for multisquares; 0 for monosquares + * @return the rows that were changed + */ +static LJBits markSquare(LJField *p, int x, int y, int isMulti) +{ + int baseBlk = (isMulti ? 0xA0 : 0xB0) + | CONNECT_MASK; + for(int i = 0; i < 4; ++i) + { + int c; + + if(i == 0) + c = baseBlk & ~CONNECT_D; + else if(i == 3) + c = baseBlk & ~CONNECT_U; + else + c = baseBlk; + + p->b[y + i][x + 0] = c & ~CONNECT_L; + p->b[y + i][x + 1] = c; + p->b[y + i][x + 2] = c; + p->b[y + i][x + 3] = c & ~CONNECT_R; + } + + if (isMulti) { + ++p->multisquares; + } else { + ++p->monosquares; + } + + return 0x0F << y; +} + + +/** + * Marks all 4x4 squares in the playfield. + * In the case that a single tetromino forms multiple overlapping + * squares, prefers gold over silver, high over low, left over right. + * @param isMulti nonzero for multisquares; 0 for monosquares + * @return the rows that were changed + */ +static LJBits findSquares(LJField *p, int isMulti) { + LJBits changed = 0; + + for (int y = LJ_PF_HT - 4; y >= 0; --y) { + for (int x = p->leftWall; x <= p->rightWall - 4; ++x) { + int baseBlk = p->b[y][x]; + + // If this block is filled in and not connected on the left or right + // then do stuff to it + if (baseBlk + && !(baseBlk & (CONNECT_D | CONNECT_L)) + && isSquareAt(p, x, y, isMulti)) { + changed |= markSquare(p, x, y, isMulti); + p->sounds |= LJSND_SQUARE; + } + } + } + return changed; +} + +static LJBits stickyGluing(LJField *p) { + LJBits changed = 0; + int byColor = (p->gluing == LJGLUING_STICKY_BY_COLOR); + + for (int y = 0; y < LJ_PF_HT; ++y) { + for (int x = p->leftWall; x < p->rightWall - 1; ++x) { + int l = p->b[y][x]; + int r = p->b[y][x + 1]; + if (l && r + && (!(l & CONNECT_R) || !(r & CONNECT_L)) + && (!byColor || !((l ^ r) & COLOR_MASK))) { + p->b[y][x] = l | CONNECT_R; + p->b[y][x + 1] = r | CONNECT_L; + changed |= 1 << y; + } + } + } + + for (int y = 0; y < LJ_PF_HT - 1; ++y) { + for (int x = p->leftWall; x < p->rightWall; ++x) { + int b = p->b[y][x]; + int t = p->b[y + 1][x]; + if (b && t + && (!(b & CONNECT_U) || !(t & CONNECT_D)) + && (!byColor || !((b ^ t) & COLOR_MASK))) { + p->b[y][x] = b | CONNECT_U; + p->b[y + 1][x] = t | CONNECT_D; + changed |= 3 << y; + } + } + } + + return changed; +} + +/** + * Locks the current tetromino in the playfield. + * @param p the playfield + * @return the rows in which the tetromino was placed + */ +static LJBits lockPiece(LJField *p) { + LJBits rows = 0; + int xBase = p->x; + int yBase = ljfixfloor(p->y); + LJBlkSpec blocks[4]; + int piece = p->curPiece[0]; + expandPieceToBlocks(blocks, p, piece, xBase, yBase, p->theta); + + p->isSpin = isTspin(p); + + for (int blk = 0; blk < 4; ++blk) { + int blkY = blocks[blk].y; + int blkX = blocks[blk].x; + int blkValue = blocks[blk].conn; + + if(blkValue && blkY >= 0 && blkY < LJ_PF_HT) { + rows |= 1 << blkY; + if (blkX >= p->leftWall && blkX < p->rightWall) { + p->b[blkY][blkX] = blkValue; + } + } + } + p->sounds |= LJSND_LOCK; + p->alreadyHeld = 0; + p->nLinesThisPiece = 0; + + return rows; +} + +void shuffleColumns(LJField *p) { + unsigned int permu[LJ_PF_WID]; + + for (int x = p->leftWall; x < p->rightWall; ++x) { + permu[x] = x; + } + for (int x = p->rightWall - 1; x > p->rightWall + 1; --x) { + int r = ljRand(p) % x; + int t = permu[x]; + permu[x] = permu[r]; + permu[r] = t; + } + + for (int y = 0; y < LJ_PF_HT; ++y) { + unsigned int blk[LJ_PF_WID]; + + // Copy blocks to temporary buffer, eliminating left and right connections + for (int x = p->leftWall; x < p->rightWall; ++x) { + blk[x] = p->b[y][permu[x]] & ~(CONNECT_L | CONNECT_R); + } + for (int x = p->leftWall; x < p->rightWall; ++x) { + p->b[y][x] = blk[x]; + } + } +} + +/** + * Rotates and shifts a new piece per the rotation system. + */ +static void rotateNewPiece(LJField *p) { + const LJRotSystem *rs = rotSystems[p->rotationSystem]; + int shape = p->curPiece[0] & LJP_MASK; + int kickData = rs->entryOffset[shape]; + p->x += WKX(kickData); + p->y += ljitofix(WKY(kickData)); + p->theta = rs->entryTheta[shape]; + + /* Find extents of piece so that it doesn't enter already collided + with the walls (otherwise, in Tengen rotation and well width 4, + spawning I causes block out) */ + + LJBlkSpec blocks[4]; + int minX = LJ_PF_WID - 1, maxX = 0, maxY = 0; + + expandPieceToBlocks(blocks, p, p->curPiece[0], + p->x, ljfixfloor(p->y), p->theta); + for (int blk = 0; blk < 4; ++blk) { + if (blocks[blk].conn) { + if (maxY < blocks[blk].y) { + maxY = blocks[blk].y; + } + if (maxX < blocks[blk].x) { + maxX = blocks[blk].x; + } + if (minX > blocks[blk].x) { + minX = blocks[blk].x; + } + } + } + + if (minX < p->leftWall) { + p->x += minX - p->leftWall; + } else if (maxX >= p->rightWall) { + p->x -= (maxX + 1) - p->rightWall; + } + if (!p->enterAbove && maxY >= p->ceiling) { + p->y -= ljitofix(maxY - p->ceiling) + 1; + } +} + + +/** + * Spawns a tetromino onto the playfield. + * @param p the playfield + * @param hold 0 for spawning from next; nonzero for swapping with hold + * @return the rows that were changed by banana effect + */ +static LJBits newPiece(LJField *p, int hold) { + LJBits changed = 0; + int initial = p->state != LJS_FALLING + && p->state != LJS_LANDED; + int ihs = initial && hold; + + if (hold) { + if (p->state == LJS_LANDED && p->lockReset == LJLOCK_SPAWN) { + p->speed.lockDelay = p->stateTime; + } + } else { + p->upwardKicks = 0; + } + p->x = (LJ_PF_WID - 4) / 2; + p->dropDist = 0; + if (!ihs) { + p->state = LJS_FALLING; + p->stateTime = 0; + } + p->isSpin = 0; + p->y = ljitofix(p->ceiling - 2); + + /* Note: The gimmick sets the gravity speed after frame() finishes. */ + + if (hold) { + int temp; + + if (p->holdStyle != LJHOLD_TO_NEXT) { + temp = p->holdPiece; + p->holdPiece = p->curPiece[ihs]; + } else { + temp = p->curPiece[ihs + 1]; + p->curPiece[ihs + 1] = p->curPiece[ihs]; + } + p->curPiece[ihs] = temp; + p->alreadyHeld = 1; + p->sounds |= LJSND_HOLD; + + // If a negative number was swapped into the current piece, + // then there was nothing in the hold space (e.g. at the + // beginning of a round). In this case, we'll need to fall + // through and generate a new piece. + if (temp >= 0) { + rotateNewPiece(p); + return changed; + } + } + + // Shift the next pieces down + for (int i = 0; i < LJ_NEXT_PIECES; i++) { + p->curPiece[i] = p->curPiece[i + 1]; + } + p->sounds |= LJSND_SPAWN; + + p->curPiece[LJ_NEXT_PIECES] = randomize(p); + ++p->nPieces; + if (!p->canRotate) { + p->theta = (ljRand(p) >> 12) & 0x03; + } else { + rotateNewPiece(p); + } + + return changed; +} + +void newGame(LJField *p) { + + // Clear playfield + for (int y = 0; y < LJ_PF_HT; y++) { + for(int x = 0; x < LJ_PF_WID; x++) { + p->b[y][x] = 0; + } + } + + for (int y = 0; y < LJ_MAX_LINES_PER_PIECE; ++y) { + p->nLineClears[y] = 0; + } + + if (p->holdStyle == LJHOLD_TNT) { + initRandomize(p); + p->holdPiece = randomize(p); + } else { + p->holdPiece = -1; // sentinel for no piece in hold box + } + + // Generate pieces + initRandomize(p); + for(int i = 0; i < LJ_NEXT_PIECES; i++) { + newPiece(p, 0); + } + p->clearedLines = 0; + p->nPieces = 0; + p->state = LJS_NEW_PIECE; + p->stateTime = 1; + p->garbage = 0; + p->outGarbage = 0; + p->score = 0; + p->gameTime = 0; + p->activeTime = 0; + p->lines = 0; + p->alreadyHeld = 0; + p->chain = 0; + p->theta = 0; + p->nLinesThisPiece = 0; + p->canRotate = 1; + p->speed.entryDelay = 0; + p->garbageRandomness = 64; + p->reloaded = 0; + p->monosquares = 0; + p->multisquares = 0; + + p->garbageX = ljRand(p) % (p->rightWall - p->leftWall) + + p->leftWall; +} + +/** + * Handles scoring for hard and soft drops. + * @return 0 for no change or LJ_DIRTY_SCORE for change + */ +LJBits scoreDropRows(LJField *p, LJFixed gravity, LJFixed newY) { + LJBits changed = 0; + if (gravity > 0) { + int fallDist = ljfixfloor(p->y) - ljfixfloor(newY); + + p->dropDist += fallDist; + + // Double scoring for hard drop + if (p->dropScoreStyle == LJDROP_1S_2H + && gravity >= ljitofix(p->ceiling)) { + fallDist *= 2; + } + if (p->dropScoreStyle == LJDROP_1CELL + || p->dropScoreStyle == LJDROP_1S_2H) { + p->score += fallDist; + changed |= LJ_DIRTY_SCORE; + } + + // Handle scoring for continuous drop + if (p->dropScoreStyle == LJDROP_NES + && newY <= ljitofix(p->hardDropY)) { + p->score += p->dropDist; + changed |= LJ_DIRTY_SCORE; + p->dropDist = 0; + } + } else { + p->dropDist = 0; + } + return changed; +} + +/** + * Handles gravity. + * @param p the playfield + * @param gravity amount of additional gravity applied by the player + * @param otherKeys other LJI_* keys being pressed by the player + * (specifically LJI_LOCK) + */ +static LJBits doPieceGravity(LJField *p, LJFixed gravity, LJBits otherKeys) { + int changedRows = 0; + + LJFixed newY = p->y - gravity - p->speed.gravity; + + // Check for landed + if (newY <= ljitofix(p->hardDropY)) { + newY = ljitofix(p->hardDropY); + + // Downward movement does not result in a T-spin + if (ljfixfloor(newY) < ljfixfloor(p->y)) { + p->isSpin = 0; + } + + changedRows |= scoreDropRows(p, gravity, newY); + p->y = newY; + + if (p->state == LJS_FALLING) { + p->state = LJS_LANDED; + p->stateTime = p->speed.lockDelay; + p->sounds |= LJSND_LAND; + } + if (p->stateTime > 0 && !(otherKeys & LJI_LOCK)) { + // lock delay > 128 is a special case for don't lock at all + if (p->setLockDelay < 128) { + --p->stateTime; + } + } else { + LJBits lockRows = lockPiece(p); + p->state = LJS_LINES; + p->stateTime = 0; + changedRows |= lockRows | LJ_DIRTY_NEXT; + + // LOCK OUT rule: If a piece locks + // completely above the ceiling, the game is over. + if (!(lockRows & ((1 << p->ceiling) - 1))) { + p->state = LJS_GAMEOVER; + } + } + } else { + changedRows |= scoreDropRows(p, gravity, newY); + p->state = LJS_FALLING; + + // Downward movement does not result in a T-spin + if (ljfixfloor(newY) < ljfixfloor(p->y)) { + p->isSpin = 0; + } + } + p->y = newY; + return changedRows; +} + +static void updateLockDelayOnMove(LJField *p, int isUpwardKick) { + if (p->state == LJS_LANDED) { + + // if tetromino can move down, go back to falling state; + // otherwise, reset lock delay. + if (!isCollision(p, p->x, ljfixfloor(p->y) - 1, p->theta)) { + p->state = LJS_FALLING; + if (p->lockReset == LJLOCK_SPAWN) { + p->speed.lockDelay = p->stateTime; + } + p->stateTime = 0; + } else { + p->state = LJS_LANDED; + if (p->lockReset == LJLOCK_MOVE + || (p->lockReset == LJLOCK_STEP && isUpwardKick)) { + p->stateTime = p->speed.lockDelay; + } + } + } +} + +static int doRotate(LJField *p, + int newDir, + const WallKickTable *const pieceTable, + int withKicks) { + int baseX = p->x; + int baseY = ljfixfloor(p->y); + + withKicks = withKicks ? KICK_TABLE_LEN : 1; + + // allow specifying null tables for O + if (!pieceTable) { + if (!isCollision(p, baseX, baseY, newDir)) { + p->theta = newDir; + p->sounds |= LJSND_ROTATE; + return 1; + } + return 0; + } + + const unsigned char *const table = (*pieceTable)[newDir]; + int baseKickY = -1000; // sentinel for uninitialized + + for (int kick = 0; kick < withKicks; kick++) { + unsigned int kickData = table[kick]; + if (kickData == WK_END) { + break; + } else if (kickData == ARIKA_IF_NOT_CENTER) { + + // Compute the free space position + kickData = table[0]; + int kickX = WKX(kickData) + baseX; + int kickY = WKY(kickData) + baseY; + LJBlkSpec blocks[4]; + int allowed = 0; + + // If a block other than the center column of this position + // is occupied, go to the next step (that is, + // allow subsequent kicks) + expandPieceToBlocks(blocks, p, p->curPiece[0], + kickX, kickY, newDir); + + for (int blk = 0; blk < 4; ++blk) { + if (blocks[blk].conn + && blocks[blk].x != baseX + 1 + && isOccupied(p, blocks[blk].x, blocks[blk].y)) { + allowed = 1; + break; + } + } + + // Otherwise, only blocks of the center column are occupied, + // and these cannot kick the piece. + if (!allowed) { + return 0; + } + } else { + int kickX = WKX(kickData) + baseX; + int kickY = WKY(kickData) + baseY; + + // If this is the first + if (baseKickY == -1000) { + baseKickY = kickY; + } + if ((kickY <= baseKickY || p->upwardKicks < p->maxUpwardKicks) + && !isCollision(p, kickX, kickY, newDir)) { + p->theta = newDir; + p->x = kickX; + if (kickY > baseKickY) { + p->y = ljitofix(kickY); + + // on the FIRST floor kick of a piece, reset lock delay + if (p->upwardKicks == 0) { + updateLockDelayOnMove(p, 1); + } + ++p->upwardKicks; + } else if (kickY < baseKickY) { + p->y = ljitofix(kickY) + 0xFFFF; + } else { + p->y = ljitofix(kickY) + (p->y & 0xFFFF); + } + p->sounds |= LJSND_ROTATE; + return 1; + } + } + } + return 0; +} + + +/** + * Tries to rotate the current piece 90 degrees counterclockwise, + * using the counterclockwise wall kick tables. + * @param p the playfield + * @param withKicks nonzero for wall kick, zero for none + */ +static int doRotateLeft(LJField *p, int withKicks) { + int newDir = (p->theta + 3) & 0x03; + const LJRotSystem *rs = rotSystems[p->rotationSystem]; + signed int tableNo = rs->kicksL[p->curPiece[0] & LJP_MASK]; + const WallKickTable *pieceTable = tableNo >= 0 + ? &(rs->kickTables[tableNo]) + : NULL; + return doRotate(p, newDir, pieceTable, withKicks); +} + +/** + * Tries to rotate the current piece 90 degrees clockwise, + * using the clockwise wall kick tables. + * @param p the playfield + * @param withKicks nonzero for wall kick, zero for none + */ +static int doRotateRight(LJField *p, int withKicks) { + int newDir = (p->theta + 1) & 0x03; + const LJRotSystem *rs = rotSystems[p->rotationSystem]; + signed int tableNo = rs->kicksR[p->curPiece[0] & LJP_MASK]; + const WallKickTable *pieceTable = tableNo >= 0 + ? &(rs->kickTables[tableNo]) + : NULL; + return doRotate(p, newDir, pieceTable, withKicks); +} + +static LJBits checkLines(const LJField *p, LJBits checkRows) { + LJBits foundLines = 0; + for (int y = 0; + y < LJ_PF_HT && checkRows != 0; + ++y, checkRows >>= 1) { + if (checkRows & 1) { + const unsigned char *row = p->b[y]; + int found = 1; + + for (int x = p->leftWall; x < p->rightWall && found; ++x) { + found = found && (row[x] != 0); + } + if (found) { + foundLines |= 1 << y; + } + } + } + + return foundLines; +} + +static void fillCLoop(LJField *p, int x, int y, unsigned int src, unsigned int dst) +{ + int fillL, fillR, i; + + fillL = fillR = x; + do { + p->c[y][fillL] = dst; + fillL--; + } while ((fillL >= p->leftWall) && (p->c[y][fillL] == src)); + fillL++; + + do { + p->c[y][fillR] = dst; + fillR++; + } while ((fillR < p->rightWall) && (p->c[y][fillR] == src)); + fillR--; + + for(i = fillL; i <= fillR; i++) + { + if(y > 0 && p->c[y - 1][i] == src) { + fillCLoop(p, i, y - 1, src, dst); + } + if(y < LJ_PF_HT - 1 && p->c[y + 1][i] == src) { + fillCLoop(p, i, y + 1, src, dst); + } + } +} + + +static void fillC(LJField *p, int x, int y, int dstC) { + if (p->c[y][x] != dstC) { + fillCLoop(p, x, y, p->c[y][x], dstC); + } +} + +/** + * Locks the block regions that have landed. + * @param p the playfield + */ +void lockLandedRegions(LJField *p) { + // Look for regions that are on top of ground regions, where + // "ground regions" are any block that is solid and whose region ID is 0. + for (int landed = 1; landed != 0; ) { + landed = 0; + // If something hit the ground, erase its floating bit + for (int y = 0; y < LJ_PF_HT; ++y) { + for (int x = p->leftWall; x < p->rightWall; ++x) { + // If there's a floating block here, and a not-floating block below, + // erase this block group's floatiness + if (p->c[y][x] && + (y == 0 || (!p->c[y - 1][x] && p->b[y - 1][x]))) { + fillC(p, x, y, 0); + p->sounds |= LJSND_LAND; + landed = 1; + } + } + } + } +} + +/** + * Separates the playfield into regions that shall fall separately. + * @param p the playfield + * @param byColors Zero: Touching blocks form a region. + * Nonzero: Touching blocks of a single color form a region. + */ +static void stickyMark(LJField *p, int byColors) { + for (unsigned int y = 0; y < LJ_PF_HT; ++y) { + for (unsigned int x = p->leftWall; x < p->rightWall; ++x) { + int blkHere = p->b[y][x] & COLOR_MASK; + + if (!byColors) { + blkHere = blkHere ? 0x10 : 0; + } + p->c[y][x] = blkHere; + } + } + + if (byColors) { + lockLandedRegions(p); + } else { + // mark the bottom row as landed + for (unsigned int x = p->leftWall; x < p->rightWall; ++x) { + if (p->c[0][x]) { + fillC(p, x, 0, 0); + } + } + } + + //p->stateTime = 5; +} + + +/** + * Sets the color of a piece to gray/garbage (0x80). + * @param x column of a block in the piece + * @param y row of a block in the piece + * @param rgn the region ID + */ +static void cascadeMarkPiece(LJField *p, int x, int y, int rgn) { + int blkHere = p->b[y][x]; + + if (blkHere && !p->c[y][x]) { + p->c[y][x] = rgn; + if((blkHere & CONNECT_D) && y > 0) + cascadeMarkPiece(p, x, y - 1, rgn); + if((blkHere & CONNECT_U) && y < LJ_PF_HT - 1) + cascadeMarkPiece(p, x, y + 1, rgn); + if((blkHere & CONNECT_L) && x > p->leftWall) + cascadeMarkPiece(p, x - 1, y, rgn); + if((blkHere & CONNECT_R) && x < p->rightWall - 1 ) + cascadeMarkPiece(p, x + 1, y, rgn); + } +} + +static void cascadeMark(LJField *p) { + int rgn = 0; + + for (unsigned int y = 0; y < LJ_PF_HT; ++y) { + for (unsigned int x = p->leftWall; x < p->rightWall; ++x) { + p->c[y][x] = 0; + } + } + for (unsigned int y = 0; y < LJ_PF_HT; ++y) { + for (unsigned int x = p->leftWall; x < p->rightWall; ++x) { + cascadeMarkPiece(p, x, y, ++rgn); + } + } + lockLandedRegions(p); + //p->stateTime = 5; +} + +static void breakEverything(LJField *p) { + for (unsigned int y = 0; y < LJ_PF_HT; ++y) { + for (unsigned int x = p->leftWall; x < p->rightWall; ++x) { + if (p->b[y][x]) { + p->c[y][x] = x + 1; + p->b[y][x] = 0x80; + } else { + p->c[y][x] = 0; + } + } + } + + // fill bottom row + for (unsigned int x = x = p->leftWall; x < p->rightWall; ++x) { + if (p->c[0][x]) { + fillC(p, x, 0, 0); + } + } + p->stateTime = 5; +} + +/** + * Sets the color of a piece to gray/garbage (0x80). + * @param x column of a block in the piece + * @param y row of a block in the piece + */ +static LJBits breakPiece(LJField *p, int x, int y) { + LJBits changed = 0; + int blkHere = p->b[y][x]; + int colorHere = blkHere & COLOR_MASK; + int connHere = blkHere & CONNECT_MASK; + + if (colorHere != 0x80) { + p->b[y][x] = connHere | 0x80; + changed |= 1 << y; + if((blkHere & CONNECT_D) && y > 0) + changed |= breakPiece(p, x, y - 1); + if((blkHere & CONNECT_U) && y < LJ_PF_HT - 1) + changed |= breakPiece(p, x, y + 1); + if((blkHere & CONNECT_L) && x > p->leftWall) + changed |= breakPiece(p, x - 1, y); + if((blkHere & CONNECT_R) && x < p->rightWall - 1 ) + changed |= breakPiece(p, x + 1, y); + } + return changed; +} + +/** + * Removes blocks in cleared lines from the playfield and marks + * remaining blocks for gravity. + * @param the lines to be cleared + * @return the rows that were changed + */ +static LJBits clearLines(LJField *p, LJBits foundLines) { + LJBits changed = foundLines; + + p->clearedLines = foundLines; + if (foundLines != 0) { + p->sounds |= LJSND_LINE; + } + for (int y = 0; + y < LJ_PF_HT && foundLines != 0; + ++y, foundLines >>= 1) { + if (foundLines & 1) { + + // In square mode, turn broken pieces (but not 4x4 squares) + // into garbage blocks + if (p->gluing == LJGLUING_SQUARE) { + for (int x = p->leftWall; x < p->rightWall; ++x) { + if (p->b[y][x] < 0x80) { + changed |= breakPiece(p, x, y); + } else if ((p->b[y][x] & (0xF0 | CONNECT_R)) == 0xA0) { + p->score += 500; + changed |= LJ_DIRTY_SCORE; + } else if ((p->b[y][x] & (0xF0 | CONNECT_R)) == 0xB0) { + p->score += 1000; + changed |= LJ_DIRTY_SCORE; + } + } + } + + for (int x = p->leftWall; x < p->rightWall; ++x) { + p->b[y][x] = 0; + } + + // break connections up and down (like Tengen Tetyais) + if (y > 0) { + for (int x = p->leftWall; x < p->rightWall; ++x) { + p->b[y - 1][x] &= ~CONNECT_U; + } + changed |= 1 << (y - 1); + } + if (y < LJ_PF_HT - 1) { + for (int x = p->leftWall; x < p->rightWall; ++x) { + p->b[y + 1][x] &= ~CONNECT_D; + } + changed |= 1 << (y + 1); + } + } + } + if (p->gluing == LJGLUING_SQUARE && p->isSpin) { + breakEverything(p); + changed |= (1 << LJ_PF_HT) - 1; + } else if (p->clearGravity == LJGRAV_STICKY) { + stickyMark(p, 0); + } else if (p->clearGravity == LJGRAV_STICKY_BY_COLOR) { + stickyMark(p, 1); + } else if (p->clearGravity == LJGRAV_CASCADE) { + cascadeMark(p); + } else { + p->stateTime = 0; + } + + return changed; +} + +static unsigned int stickyFallLines(LJField *p) { + int minY = LJ_PF_HT; + + // Move floating stuff down by one block + for (int y = 1; y < LJ_PF_HT; ++y) { + for (int x = p->leftWall; x < p->rightWall; ++x) { + int c = p->c[y][x]; + if (c) { + p->c[y - 1][x] = c; + p->c[y][x] = 0; + p->b[y - 1][x] = p->b[y][x]; + p->b[y][x] = 0; + + if (minY > y) { + minY = y; + } + } + } + } + + // If we're done, skip all the rest + if (minY >= LJ_PF_HT) { + return LJ_PF_HT; + } + + lockLandedRegions(p); + return minY - 1; +} + + +unsigned int bfffo(LJBits rowBits) { + unsigned int lineRow = 0; + + if (!rowBits) { + return 32; + } + if ((rowBits & 0xFFFF) == 0) { + rowBits >>= 16; + lineRow += 16; + } + if ((rowBits & 0xFF) == 0) { + rowBits >>= 8; + lineRow += 8; + } + if ((rowBits & 0xF) == 0) { + rowBits >>= 4; + lineRow += 4; + } + if ((rowBits & 0x3) == 0) { + rowBits >>= 2; + lineRow += 2; + } + if ((rowBits & 0x1) == 0) { + rowBits >>= 1; + lineRow += 1; + } + return lineRow; +} + +static unsigned int fallLines(LJField *p) { + LJBits rowBits = p->tempRows; + unsigned int lineRow = 0; + + if (p->clearGravity != LJGRAV_NAIVE + || (p->gluing == LJGLUING_SQUARE && p->isSpin)) { + return stickyFallLines(p); + } + + if (rowBits == 0) { + return LJ_PF_HT; + } + + lineRow = bfffo(rowBits); + p->tempRows = (p->tempRows & (-2 << lineRow)) >> 1; + if (!(p->tempRows & (1 << lineRow))) { + p->sounds |= LJSND_LAND; + } + + // Move stuff down by 1 row + for (int y = lineRow; y < LJ_PF_HT - 1; ++y) { + unsigned char *row0 = p->b[y]; + const unsigned char *row1 = p->b[y + 1]; + for (int x = p->leftWall; x < p->rightWall; ++x) { + row0[x] = row1[x]; + } + } + + // Clear top row + for (int x = p->leftWall; x < p->rightWall; ++x) { + p->b[LJ_PF_HT - 1][x] = 0; + } + + return lineRow; +} + +/** + * Counts the bits in a bit vector that are true (1). + * @param b a bit vector + * @return the number of 1 bits + */ +unsigned int countOnes(LJBits b) { + unsigned int ones = 0; + + while(b) { + ++ones; + b &= b - 1; + } + return ones; +} + +static unsigned int addGarbage(LJField *p) { + // Move stuff up by 1 row + for (int y = LJ_PF_HT - 2; y >= 0; --y) { + unsigned char *row1 = p->b[y + 1]; + const unsigned char *row0 = p->b[y]; + for (int x = p->leftWall; x < p->rightWall; ++x) { + row1[x] = row0[x]; + } + } + + // Garbage in bottom row + for (int x = p->leftWall; x < p->rightWall; ++x) { + p->b[0][x] = 0x80; + } + + // Randomize location of garbage hole + int r = (ljRand(p) >> 7) & 0xFF; + int garbageX = (r <= p->garbageRandomness) + ? (ljRand(p) % (p->rightWall - p->leftWall)) + p->leftWall + : p->garbageX; + p->b[0][garbageX] = 0; + p->garbageX = garbageX; + + // Horizontally connect the blocks that make up garbage in bottom row + for (int x = p->leftWall; x < p->rightWall - 1; ++x) { + if (p->b[0][x] && p->b[0][x + 1]) { + p->b[0][x] |= CONNECT_R; + p->b[0][x + 1] |= CONNECT_L; + } + } + + // Vertically connect the blocks that make up garbage in bottom row + for (int x = p->leftWall; x < p->rightWall; ++x) { + if (p->b[0][x] + && ((p->b[1][x] & COLOR_MASK) == 0x80)) { + p->b[0][x] |= CONNECT_U; + p->b[1][x] |= CONNECT_D; + } + } + + return (1 << LJ_PF_VIS_HT) - 1; +} + +/** + * Computes the score and outgoing garbage for lines + * and adds them to the player's total. + * @param p The playfield + * @param lines Bit array where 1 means a line clear on this row. + */ +void addLinesScore(LJField *p, LJBits lines); + +/** + * Things to do just before launching a new piece. + */ +void prepareForNewPiece(LJField *p) { + int nLines = p->nLinesThisPiece; + + // Add to number of clears of each number of lines. + int idx; + + if (p->clearGravity == LJGRAV_NAIVE) { + // In naive gravity, T-spin single, double, and triple counts + // are stored in 5-row, 6-row, and 7-row slots, and T-spins + // that clear 0 lines are not counted. + idx = (nLines > 4) + ? 4 + : nLines; + + if (nLines >= 1 && p->isSpin) { + idx += 4; + } + } else { + idx = (nLines > LJ_MAX_LINES_PER_PIECE) + ? LJ_MAX_LINES_PER_PIECE + : nLines; + } + + if (nLines < 4 && !p->isSpin && p->garbageStyle == LJGARBAGE_HRDERBY) { + p->garbage += nLines; + } + + ljAssert(p, + idx <= LJ_MAX_LINES_PER_PIECE, + "Number of lines cleared with last piece out of bounds in prepareForNewPiece"); + if (idx > 0) { + p->nLineClears[idx - 1] += 1; + } + + p->state = LJS_NEW_PIECE; + p->stateTime = p->speed.entryDelay; +} + +LJBits frame(LJField *p, const LJInput *in) { + LJBits changedRows = 0; + LJBits tempRows; + int distance; + int moved = 0; + int isFirstFrame = (p->sounds & (LJSND_SPAWN | LJSND_HOLD)) + ? 1 : 0; + + p->sounds = 0; + + // Make hold work at ANY time. + if ((in->other & LJI_HOLD) + && p->holdStyle != LJHOLD_NONE + && !p->alreadyHeld) { + changedRows |= newPiece(p, 1) | LJ_DIRTY_NEXT; + updHardDropY(p); + } + + switch(p->state) { + case LJS_NEW_PIECE: + if (p->garbage > 0) { + changedRows |= addGarbage(p); + --p->garbage; + break; + } + + // ARE + if (p->stateTime > 0) { + --p->stateTime; + } + if (p->stateTime > 0) { + break; + } + + changedRows |= newPiece(p, 0); + updHardDropY(p); + changedRows |= LJ_DIRTY_NEXT; + + /* If the piece spawns over blocks, this is a "block out" and a + loss under most rules. But skip checking it now so that + the player can IRS out of block out. */ + + break; + + // the following executes for both falling and landed + case LJS_FALLING: + case LJS_LANDED: + ++p->activeTime; + if (p->canRotate) { + int oldX = p->x; + int oldY = ljfixfloor(p->y); + distance = in->rotation; + for(; distance < 0; ++distance) { + + // 0.43: Do not apply wall kicks on the first frame (IRS) + if (doRotateLeft(p, !isFirstFrame)) { + moved = 1; + + // isSpin == 1: twist in place + // isSpin == 2: twist with kick + // if (p->tSpinAlgo == LJTS_TDS_NO_KICK) + // then only isSpin == 1 is worth points. + if (p->x == oldX && ljfixfloor(p->y) == oldY) { + p->isSpin = 1; + } else { + p->isSpin = 2; + } + } else { + break; + } + } + for(; distance > 0; --distance) { + if (doRotateRight(p, !isFirstFrame)) { + moved = 1; + if (p->x == oldX && ljfixfloor(p->y) == oldY) { + p->isSpin = 1; + } else { + p->isSpin = 2; + } + } else { + break; + } + } + } + + /* If the piece spawns over blocks, this is a "block out" and a + loss under most rules. Check it now, after rotation, so that + the player can IRS out of block out. */ + if (isFirstFrame + && isCollision(p, p->x, ljfixfloor(p->y), p->theta)) { + changedRows |= lockPiece(p); + p->state = LJS_GAMEOVER; + } + + distance = in->movement; + for(; distance < 0; ++distance) { + if (!isCollision(p, p->x - 1, ljfixfloor(p->y), p->theta)) { + --p->x; + p->sounds |= LJSND_SHIFT; + moved = 1; + p->isSpin = 0; + } + } + for(; distance > 0; --distance) { + if (!isCollision(p, p->x + 1, ljfixfloor(p->y), p->theta)) { + ++p->x; + p->sounds |= LJSND_SHIFT; + moved = 1; + p->isSpin = 0; + } + } + updHardDropY(p); + if (p->state != LJS_GAMEOVER) { + if (moved) { + updateLockDelayOnMove(p, 0); + } + tempRows = doPieceGravity(p, ljitofix(in->gravity) >> 3, in->other); + p->tempRows = tempRows; + changedRows |= tempRows; + } + + // At this point, if the piece locked, + // p->tempRows holds the rows in which the piece landed. + break; + + case LJS_LINES: + if (p->stateTime > 0) { + --p->stateTime; + } + if (p->stateTime > 0) { + break; + } + if (p->gluing == LJGLUING_SQUARE) { + LJBits gluedRows = findSquares(p, 0); + gluedRows |= findSquares(p, 1); + changedRows |= gluedRows; + if (gluedRows) { + + // When a 4x4 block square is formed, a delay + // equal to the line delay is added. + p->stateTime += p->speed.lineDelay; + break; + } + } else if (p->gluing == LJGLUING_STICKY + || p->gluing == LJGLUING_STICKY_BY_COLOR) { + changedRows |= stickyGluing(p); + } + + // At this point, p->tempRows holds the rows in which + // a line could possibly have been made. + tempRows = p->tempRows; + tempRows = checkLines(p, tempRows); + p->tempRows = tempRows; + // At this point, p->tempRows holds the rows in which + // a line HAS been made. + addLinesScore(p, tempRows); + changedRows |= LJ_DIRTY_SCORE; + + // At this point, p->tempRows holds the rows in which a line + // HAS been made. + p->clearedLines = tempRows; + if (!tempRows) { + prepareForNewPiece(p); + break; + } + + changedRows |= clearLines(p, tempRows); + + p->state = LJS_LINES_FALLING; + p->stateTime += p->speed.lineDelay; + break; + + case LJS_LINES_FALLING: + if (p->stateTime > 0) { + --p->stateTime; + } + if (p->stateTime > 0) { + break; + } + moved = fallLines(p); + if (moved >= LJ_PF_HT) { + p->state = LJS_LINES; + p->tempRows = (1 << LJ_PF_HT) - 1; + } + changedRows |= (~0 << moved) & ((1 << LJ_PF_VIS_HT) - 1); + break; + + default: + break; + + } + + ++p->gameTime; + return changedRows; +} + + diff -r 000000000000 -r c84446dfb3f5 src/lj.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lj.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,507 @@ +/* Header file for the engine of LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#ifndef LJ_H +#define LJ_H + +#include +#include "ljtypes.h" + +enum { + CONNECT_U = 0x01, + CONNECT_R = 0x02, + CONNECT_D = 0x04, + CONNECT_L = 0x08, + CONNECT_UD = CONNECT_U | CONNECT_D, + CONNECT_UL = CONNECT_U | CONNECT_L, + CONNECT_DL = CONNECT_D | CONNECT_L, + CONNECT_UR = CONNECT_U | CONNECT_R, + CONNECT_DR = CONNECT_D | CONNECT_R, + CONNECT_LR = CONNECT_L | CONNECT_R, + CONNECT_UDL = CONNECT_UD | CONNECT_L, + CONNECT_UDR = CONNECT_UD | CONNECT_R, + CONNECT_ULR = CONNECT_UL | CONNECT_R, + CONNECT_DLR = CONNECT_DL | CONNECT_R, + CONNECT_UDLR = CONNECT_UD | CONNECT_LR, + CONNECT_MASK = CONNECT_UDLR, + COLOR_MASK = 0xF0 +}; + +typedef unsigned char LJBlock; + +typedef enum LJState { + LJS_INACTIVE, + LJS_NEW_PIECE, /* delay is ARE */ + LJS_FALLING, /* delay is fall delay; NOT IMPLEMENTED */ + LJS_LANDED, /* delay is lock delay */ + LJS_LINES, /* delay is line delay */ + LJS_LINES_FALLING, /* delay is fall delay per line; NOT IMPLEMENTED */ + LJS_GAMEOVER, /* game over animation */ + LJS_EXPLODE /* used for bombliss extension */ +} LJState; + +// for other +enum { + LJI_HOLD = 0x01, + LJI_LOCK = 0x02 +}; + +// for dirty bits +enum { + LJ_DIRTY_NEXT = 0x01000000, + LJ_DIRTY_SCORE = 0x02000000 +}; + +// for gimmick (game mode) +enum { + LJGM_ATYPE, // marathon + LJGM_BTYPE, // 40 lines + LJGM_ULTRA, // 180 seconds + LJGM_DRILL, // clear bottom line + LJGM_ITEMS, // no rotation + no next + fast drop + garbage + banana + LJGM_BABY, // 300 keypresses + LJGM_N_GIMMICKS +}; + +enum { + LJP_I = 0, + LJP_J, + LJP_L, + LJP_O, + LJP_S, + LJP_T, + LJP_Z, + LJP_I2, + LJP_I3, + LJP_L3, + LJP_GARBAGE = 7, + LJP_MASK = 0x0F +}; + +enum { + LJSND_ROTATE = 0x0001, + LJSND_SHIFT = 0x0002, + LJSND_DROP = 0x0004, + LJSND_LAND = 0x0008, + LJSND_LOCK = 0x0010, + LJSND_LINE = 0x0020, // a line was scored + LJSND_SETB2B = 0x0040, // this line is b2b worthy (tetris or t-spin) + LJSND_B2B = 0x0080, // this line AND last line were b2b worthy + LJSND_SPAWN = 0x0100, // next piece was moved up + LJSND_HOLD = 0x0200, // hold piece was activated + LJSND_IRS = 0x0400, // initial rotation: spawn last frame and rotation this frame + LJSND_SQUARE = 0x0800, // formed 4x4 square + LJSND_COUNTDOWN = 0x4000, // countdown value changed + LJSND_SECTIONUP = 0x8000, // section increased +}; + +enum { + LJRAND_PURE, + LJRAND_BAG, + LJRAND_BAGPLUS1, + LJRAND_2BAG, + LJRAND_HIST_INF, + LJRAND_HIST_6, + LJRAND_N_RANDS +}; + +enum { + LJRAND_4BLK, + LJRAND_JLOSTZ, + LJRAND_SZ, + LJRAND_I, + LJRAND_234BLK, + LJRAND_T, + LJRAND_N_PIECE_SETS +}; + +enum { + LJTS_OFF = 0, + LJTS_TNT, + LJTS_TDS, + LJTS_TDS_NO_KICK, + LJTS_N_ALGOS +}; + +enum { + LJSPD_ZERO = 0, + LJSPD_RHYTHMZERO, + LJSPD_EXP, + LJSPD_RHYTHM, + LJSPD_TGM, + LJSPD_DEATH, + LJSPD_DEATH300, + LJSPD_NES, + LJSPD_GB, + LJSPD_GBHEART, + LJSPD_N_CURVES +}; + +enum { + LJLOCK_NOW = 0, + LJLOCK_SPAWN, + LJLOCK_STEP, + LJLOCK_MOVE, + LJLOCK_N_STYLES +}; + +enum { + LJGRAV_NAIVE = 0, + LJGRAV_STICKY, + LJGRAV_STICKY_BY_COLOR, + LJGRAV_CASCADE, + LJGRAV_N_ALGOS +}; + +enum { + LJSCORE_LJ, + LJSCORE_TNT64, + LJSCORE_HOTLINE, + LJSCORE_TDS, + LJSCORE_NES, + LJSCORE_LJ_NERF, + LJSCORE_N_STYLES, + LJSCORE_WORLDS, + LJSCORE_TGM1, + LJSCORE_IPOD +}; + +enum { + LJDROP_NOSCORE, + LJDROP_NES, + LJDROP_1CELL, + LJDROP_1S_2H, + LJDROP_N_STYLES +}; + +enum { + LJGARBAGE_NONE, + LJGARBAGE_1, + LJGARBAGE_2, + LJGARBAGE_3, + LJGARBAGE_4, + LJGARBAGE_HRDERBY, + LJGARBAGE_DRILL, + LJGARBAGE_ZIGZAG, + + LJGARBAGE_N_STYLES +}; + +enum { + LJHOLD_NONE, + LJHOLD_EMPTY, + LJHOLD_TNT, + LJHOLD_TO_NEXT, + LJHOLD_N_STYLES +}; + +enum { + LJGLUING_NONE, + LJGLUING_SQUARE, + LJGLUING_STICKY, + LJGLUING_STICKY_BY_COLOR, + LJGLUING_N_STYLES +}; + +// Guideline says 10 wide, but we want to support tetrinet mode +#define LJ_PF_WID ((size_t)12) +#define LJ_SPAWN_X ((LJ_PF_WID - 3) / 2) +#define LJ_PF_HT ((size_t)24) +#define LJ_PF_VIS_HT ((size_t)20) +#define LJ_NEXT_PIECES 8 +#define LJ_MAX_LINES_PER_PIECE 8 + +typedef struct LJBlkSpec +{ + signed char x, y, conn, reserved; +} LJBlkSpec; + +typedef struct LJInput { + signed char rotation, movement; + unsigned char gravity; /* 5.3 format */ + unsigned char other; +} LJInput; + +typedef struct LJSpeedSetting { + LJFixed gravity; + unsigned char entryDelay, dasDelay, lockDelay, lineDelay; +} LJSpeedSetting; + +typedef struct SpeedStateBase { + unsigned char curve, section; + unsigned char pad[2]; + signed int level; +} SpeedStateBase; + +#define MAX_BAG_LEN 10 +#define MAX_OMINO 4 + +typedef struct LJField +{ + /* Game state */ + LJBlock b[LJ_PF_HT][LJ_PF_WID]; + LJBlock c[LJ_PF_HT][LJ_PF_WID]; + LJBits clearedLines; + LJBits sounds; + LJBits tempRows; + unsigned char curPiece[1 + LJ_NEXT_PIECES]; + signed char permuPiece[2 * MAX_BAG_LEN], permuPhase; + unsigned short nLineClears[LJ_MAX_LINES_PER_PIECE]; // [n-1] = # of n-line clears + LJFixed y; + LJState state; + signed char stateTime; + unsigned char theta; + signed char x; + signed char hardDropY; + char alreadyHeld; + char isSpin; + char nLinesThisPiece; + char canRotate; + unsigned char upwardKicks; + + /* Persist from piece to piece */ + int score, lines; + unsigned int gameTime; // number of frames + unsigned int activeTime; // number of frames + signed short holdPiece; + char chain; + signed char garbage; + unsigned char dropDist; + unsigned char garbageX; + unsigned short nPieces, outGarbage; + unsigned short monosquares, multisquares; + + /* Determined by gimmick */ + signed char gimmick; + signed int bpmCounter; + unsigned int speedupCounter; + unsigned int goalCount; + unsigned int seed; + unsigned char goalType; + + LJSpeedSetting speed; + SpeedStateBase speedState; + + unsigned char lockReset; // lockdown reset rule type + unsigned char areStyle; + unsigned char setLockDelay; // Overridden lock delay (255 = never) + unsigned char setLineDelay; // Overridden line delay + unsigned char garbageStyle; + unsigned char ceiling; + unsigned char enterAbove; + unsigned char leftWall, rightWall; + unsigned char pieceSet; + unsigned char randomizer; + unsigned char rotationSystem; + unsigned char garbageRandomness; // 64: change garbageX in 1/4 of rows; 255: change all + unsigned char tSpinAlgo; // 0: off, 1: TNT, 2: TDS + unsigned char clearGravity; + unsigned char gluing; // 0: off; 1: square + unsigned char scoreStyle; + unsigned char dropScoreStyle; + unsigned char maxUpwardKicks; + unsigned char holdStyle; + unsigned char bottomBlocks; + unsigned char reloaded; // 0: played through; 1: reloaded from saved state +} LJField; + +/** + * Names of the supported rotation systems (wktables.c). + */ +enum { + LJROT_SRS, + LJROT_SEGA, + LJROT_ARIKA, + LJROT_ATARI, + LJROT_NES, + LJROT_GB, + LJROT_TOD, + LJROT_TDX, + N_ROTATION_SYSTEMS +}; + +#define N_PIECE_SHAPES 10 +#define KICK_TABLE_LEN 5 +extern const char pieceColors[N_PIECE_SHAPES]; +typedef unsigned char WallKickTable[4][KICK_TABLE_LEN]; +typedef struct LJRotSystem { + /** + * Color scheme for this rotation system (0: SRS; 1: Sega) + * Use colorset 0 (SRS) if all free-space kicks are WK(0, 0). + * Otherwise, your rotation system has what Eddie Rogers has called + * a topknot, and you should use Sega colors. + */ + unsigned char colorScheme; + unsigned char reserved1[3]; + unsigned char entryOffset[N_PIECE_SHAPES]; + unsigned char entryTheta[N_PIECE_SHAPES]; + /* These control which kick table is used for each piece. + * If negative, no kick table is present. + */ + signed char kicksL[N_PIECE_SHAPES]; + signed char kicksR[N_PIECE_SHAPES]; + WallKickTable kickTables[]; +} LJRotSystem; + +extern const LJRotSystem *const rotSystems[N_ROTATION_SYSTEMS]; + +#ifdef LJ_INTERNAL + // Used for defining wall kick tables: + // WK(x, y) builds a 1-byte record with 2 coordinates, at 4 bits + // per coordinate. + // WKX() and WKY() retrieve the X or Y coordinate from this record. + // The ((stuff ^ 8) - 8) is sign extension magick + #define WK(x, y) (((x) & 0x0F) | ((y) & 0x0F) << 4) + #define WKX(wk) ((((wk) & 0x0F) ^ 8) - 8) + #define WKY(wk) WKX((wk) >> 4) + // If ARIKA_IF_NOT_CENTER is specified, check the free space + // rotation for blocks in columns other than 1, and stop if + // none of them are filled. This obsoletes the old SKIP_IF. + #define SKIP_IF 0x80 + #define SKIP_IF3 0x82 + + #define ARIKA_IF_NOT_CENTER 0x83 + #define WK_END 0x8F +#if 0 + extern const WallKickTable *const wkTablesL[N_ROTATION_SYSTEMS][N_PIECE_SHAPES]; + extern const WallKickTable *const wkTablesR[N_ROTATION_SYSTEMS][N_PIECE_SHAPES]; +#endif +#endif + +/** + * Expands a tetromino to the blocks that make it up. + * @param out an array of length 4 + * @param p the field into which the tetromino will be spawned + * (used for default spawn orientation) + * @param piece a piece number + * @param xBase x coordinate of base position of the tetromino + * @param yBase y coordinate of base position of the tetromino + * @param theta the orientation of the tetromino + * (0-3: use this; 4: use default spawn rotation) + */ +void expandPieceToBlocks(LJBlkSpec out[], + const LJField *p, + int piece, int xBase, int yBase, int theta); + +/** + * Tests whether a particular block is occupied. + * @param p the playfield + * @param x the column + * @param y the row (0 = bottom) + * @return zero iff the space is open + */ +int isOccupied(const LJField *p, int x, int y); + +/** + * Tests whether a particular tetromino would overlap one or more + * blocks, a side wall, or the floor + * @param p the playfield + * @param x the column of the left side of the piece's bounding 4x4 + * @param y the row of the bottom of the piece's bounding 4x4 (0 = bottom) + * @return zero iff the space is open + */ +int isCollision(const LJField *p, int x, int y, int theta); + +/** + * Blanks a playfield and prepares it for a new game. + * @param p the playfield + */ +void newGame(LJField *p); + +/** + * Runs one frame of S.M.G. + * @param p the playfield + * @param in the player's input + * @return the rows that were modified + */ +LJBits frame(LJField *p, const LJInput *in); + +/** + * Applies gimmicks to a new game. + * @param p the playfield + * @param in the player's input + */ +void initGimmicks(LJField *p); + +/** + * Runs gimmicks for one frame of S.M.G. + * @param p the playfield + * @param c the control settings + * @return the rows that were modified + */ +struct LJControl; +LJBits gimmicks(LJField *p, struct LJControl *c); + +/** + * Sets up the randomizer. + * @return the number of this piece + */ +void initRandomize(LJField *p); + +/** + * Chooses a pseudo-random piece. + * @param the field on which the piece will be generated + * @return the number of this piece + * (0=i, 1=j, 2=l, 3=o, 4=s, 5=t, 6=z) + */ +unsigned int randomize(LJField *p); + +/** + * Counts the number of 1 bits in a bitfield. + * @param p the bitfield + * @return the number of bits in p with a value of 1. + */ +unsigned int countOnes(LJBits b); + +/** + * Shuffles the columns of blocks in the playfield. + * @param p the playfield + */ +void shuffleColumns(LJField *p); + +/** + * Random number generator. Use this for all random events + * that affect the game state. + * @param field + * @return uniformly distributed number in 0 to 0x7FFF + */ +unsigned int ljRand(LJField *p); + +/** + * List of lines used for hotline scoring. + * A line clear completed at row hotlineRows[i] is worth 100*(i + 1). + */ +extern const char hotlineRows[LJ_PF_HT]; + +/** + * Counts the number of trailing zero bits on an integer. + * For instance, 00000000 00000000 00000001 11111000 + * has 3 trailing zeroes. + */ +unsigned int bfffo(LJBits rowBits); + +void setupZigzagField(LJField *p, size_t height); +unsigned int calcZigzagGrade(const LJField *p); + +#endif diff -r 000000000000 -r c84446dfb3f5 src/ljcontrol.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljcontrol.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,109 @@ +/* Control for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#ifndef LJCONTROL_H +#define LJCONTROL_H + +#include "ljtypes.h" + +// The cross-platform parts of the view are stored here +typedef struct LJControl { + LJBits lastKeys, repressKeys; + unsigned int presses; + unsigned char dasSpeed; + unsigned char dasDelay; + signed char dasCounter; + unsigned char allowDiagonals; + unsigned char softDropSpeed; + unsigned char softDropLock; + unsigned char hardDropLock; + unsigned char initialDAS; + unsigned char initialRotate; + signed char countdown; + struct LJReplay *replaySrc; + struct LJReplay *replayDst; +} LJControl; + +struct LJField; +struct LJInput; + +#define N_SPEED_METER_PIECES 10 + +typedef struct LJView { + struct LJField *field; + LJControl *control; + LJBits backDirty; + LJBits frontDirty; + struct LJPCView *plat; + unsigned char smoothGravity; + unsigned char hideNext; + unsigned char hideShadow; + unsigned char nextPieces; + unsigned char hidePF; + unsigned char showTrails; + unsigned char nLockTimes; + unsigned int lockTime[N_SPEED_METER_PIECES]; + LJFixed trailY; +} LJView; + +enum { + LJSHADOW_COLORED_25 = 0, + LJSHADOW_COLORED_50, + LJSHADOW_COLORED, + LJSHADOW_COLORLESS, + LJSHADOW_NONE, + LJSHADOW_NO_FALLING, + LJSHADOW_N_STYLES +}; + +#define VKEY_UP 0x0001 +#define VKEY_DOWN 0x0002 +#define VKEY_LEFT 0x0004 +#define VKEY_RIGHT 0x0008 +#define VKEY_ROTL 0x0010 +#define VKEY_ROTR 0x0020 +#define VKEY_HOLD 0x0040 +#define VKEY_ITEM 0x0080 +#define VKEY_MACRO(x) (0x100 << (x)) +#define VKEY_MACROS 0xFF00 +#define VKEY_START 0x00080000 + +enum { + LJZANGI_SLIDE, + LJZANGI_LOCK, + LJZANGI_LOCK_RELEASE, + LJZANGI_N_STYLES +}; + +/* VKEY_MACRO(0) to VKEY_MACRO(7) + +Event planners can restrict how many macros a player can use, +so that keyboardists don't have an unfair advantage over +gamepad users. + +*/ + +void addKeysToInput(struct LJInput *dst, LJBits keys, const struct LJField *p, LJControl *c); + +#endif diff -r 000000000000 -r c84446dfb3f5 src/ljds.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljds.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,334 @@ +/* DS frontend for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006-2007 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#include "ljplay.h" +#include "ljds.h" +#include "talkback.h" +#include +#include +#include "options.h" +#include "gbamenus.h" +#include "ljpath.h" + +#if 1 + #define LJ_VERSION "0.46 ("__DATE__")" +#else + #define LJ_VERSION "WIP ("__DATE__")" +#endif + +#define SCREEN_W 32 +#define SCREEN_H 24 +#define DS_PFTOP 3 +#define DS_PFLEFT 10 + +/* My ghetto IPC system */ +volatile P8A7Talkback tb_cached = { + .cmd = 0, + .sounds = 0 +}; + +#define tb (*(volatile P8A7Talkback *) \ + ((volatile char *)&tb_cached + 0x00400000)) + +short mouse_x, mouse_y; +LJBits mouse_b; + +#include "ljgbads.inc" + +unsigned int nSprites = 0; +volatile int curTime; + +void gba_play_sound(struct LJPCView *v, int n) { + +} + +void gba_poll_sound(struct LJPCView *plat) { + +} + +LJBits readHWKeys(void) { + scanKeys(); + LJBits j = keysHeld(); + touchPosition xy = touchReadXY(); + + if (j & KEY_TOUCH) { + mouse_x = xy.px; + mouse_y = xy.py; + mouse_b = 1; + j &= ~(KEY_TOUCH_RIGHT | KEY_TOUCH_LEFT + | KEY_TOUCH_UP | KEY_TOUCH_DOWN); + if (xy.px < 96) { + j |= KEY_TOUCH_LEFT; + } else if (xy.px >= 160) { + j |= KEY_TOUCH_RIGHT; + } + if (xy.py < 64) { + j |= KEY_TOUCH_UP; + } else if (xy.py >= 128) { + j |= KEY_TOUCH_DOWN; + } + } else { + mouse_b = 0; + } + return j; +} + +void finishSprites(void) { + for (int i = nSprites - 1; i >= 0; --i) { + MAINOAM[i].attribute[0] = 512; + } + nSprites = 128; +} + +void vsync(void) { + swiWaitForVBlank(); + wantPause |= needLidSleep(); +} + +void isr(void) +{ + int interrupts = REG_IF; + + VBLANK_INTR_WAIT_FLAGS |= interrupts; + REG_IF = interrupts; + ++curTime; +} + +#define KEY_X (IPC_X << 16) +#define KEY_Y (IPC_Y << 16) +#define KEY_PEN (IPC_PEN_DOWN << 16) +#define VRAM_MAIN ((uint16 *)0x06000000) +#define VRAM_SUB ((uint16 *)0x06200000) + + + +/** + * Tells the ARM7 to play these sound effects. + */ +void playSoundEffects(LJView *v, LJBits sounds, int countdown) { + tb.countdown = countdown; + tb.sounds |= sounds; +} + +#define SHADOW_BLOCK 0x00 + +/** + * Draws a tetromino whose lower left corner of the bounding box is at (x, y) + * @param b the bitmap to draw to + * @param piece the piece to be drawn + * @param x distance from to left side of 4x4 box + * @param y distance from top of bitmap to bottom of 4x4 box + * @param the rotation state (0: U; 1: R; 2: D; 3: L; 4: Initial position) + * @param w width of each block + * @param h height of each block + * @param color Drawing style + * color == 0: draw shadow + * color == 0x10 through 0x70: draw in that color + * color == 0x80: draw as garbage + * color == -255 through -1: draw with 255 through 1 percent lighting + */ +LJBits drawPiece(LJView *const v, void *const b, + int piece, int x, int y, int theta, + int color, int w, int h) { + // Don't try to draw the -1 that's the sentinel for no hold piece + if (piece < 0) + return 0; + + LJBits rows = 0; + LJBlkSpec blocks[4]; + + expandPieceToBlocks(blocks, v->field, piece, 0, 0, theta); + + for (int blk = 0; blk < 4; ++blk) { + int blkValue = blocks[blk].conn; + if (blkValue) { + int blkX = blocks[blk].x; + int blkY = blocks[blk].y; + const int dstX = x + w * blkX; + const int dstY = y + h * (-1 - blkY); + + if (color == 0x80) { + blkValue = 0x8001; // garbage hold + } else if (color != 0) { + blkValue = ((blkValue & 0xF0) << 8) | 1; + } else if (color == 0) { + if (v->hideShadow == LJSHADOW_COLORED) { + blkValue = ((blkValue & 0xF0) << 8) | 2; + } else { + blkValue = 0x8002; + } + } + + if (dstY > -8 && dstY < 192) { + --nSprites; + MAINOAM[nSprites].attribute[0] = dstY & 0x00FF; + MAINOAM[nSprites].attribute[1] = dstX & 0x01FF; + MAINOAM[nSprites].attribute[2] = blkValue; + } + + rows |= 1 << blkY; + } + } + + return rows; +} + +void openWindow(void) { + videoSetMode(MODE_0_2D + | DISPLAY_BG0_ACTIVE + | DISPLAY_SPR_1D_LAYOUT + | DISPLAY_SPR_ACTIVE); + videoSetModeSub(MODE_0_2D + | DISPLAY_BG0_ACTIVE); + BGCTRL[0] = BG_16_COLOR | BG_TILE_BASE(0) | BG_MAP_BASE(31); + BGCTRL_SUB[0] = BG_16_COLOR | BG_TILE_BASE(0) | BG_MAP_BASE(31); + + vramSetMainBanks(VRAM_A_MAIN_BG, VRAM_B_MAIN_SPRITE_0x06400000, + VRAM_C_SUB_BG, VRAM_D_SUB_SPRITE); + /* load_font(); */ + // Load palette + BG_PALETTE[0] = RGB5(31,31,31); + BG_PALETTE[1] = RGB5( 0, 0,15); + BG_PALETTE_SUB[0] = RGB5(0, 0, 0); + setupPalette(srsColors); + + // Set scrolling + BG_OFFSET[0].x = 0; + BG_OFFSET[0].y = 0; + BG_OFFSET_SUB[0].x = 0; + BG_OFFSET_SUB[0].y = 0; + + SUB_BG2_XDX = 0x100; + SUB_BG2_XDY = 0; + SUB_BG2_YDX = 0; + SUB_BG2_YDY = 0x100; + SUB_BG2_CY = 0; + SUB_BG2_CX = 0; + + lcdMainOnTop(); +} + +void install_sound(void) { + IPC->soundData = (void *)&tb; +} + +#ifdef TRAP_SPRINTF +int sprintf (char *dst, const char *format, ...) { + BG_PALETTE[0] = RGB5(31, 0, 0); + strcpy(dst, "[NO FPU]"); + return 8; +} +#endif + +int main(void) { + LJField p = { + .leftWall = 1, + .rightWall = 11, + .ceiling = 20 + }; + LJControl control = { + .dasSpeed = 1, + .dasDelay = 10, + .initialDAS = 1, + .allowDiagonals = 0, + .softDropSpeed = 0, + .softDropLock = 0, + .hardDropLock = 1 + }; + struct LJPCView platView; + LJView mainView = { + .field = &p, + .control = &control, + .smoothGravity = 1, + .nextPieces = 3, + .plat = &platView, + .backDirty = ~0 + }; + + powerON(POWER_ALL_2D); + initOptions(customPrefs); + install_timer(); + install_sound(); + openWindow(); + + BG_PALETTE_SUB[0] = RGB5( 0, 0, 0); + BG_PALETTE_SUB[1] = RGB5(10,20,10); + BG_PALETTE_SUB[2] = RGB5(15,31,15); + BG_PALETTE_SUB[3] = RGB5(15,31,15); + vwfWinInit(&vwfTouch); + { + int x = vwfPuts(&vwfTouch, "finding memory card... ", 0, 0); + if (ljpathInit("/data/lockjaw/lj.nds")) { + vwfPuts(&vwfTouch, "success!", x, 0); + } else { + vwfPuts(&vwfTouch, "failed.", x, 0); + vwfPuts(&vwfTouch, "To learn how to fix this, see", 0, 12); + vwfPuts(&vwfTouch, "http://dldi.drunkencoders.com/", 0, 24); + } + } + + coprNotice(); + + load_font(); + drawFrame(&mainView); + + while (1) { + LJView *const players[1] = {&mainView}; + + for (int y = 0; y < LJ_PF_VIS_HT; ++y) { + for (int x = 0; x < LJ_PF_WID; ++x) { + p.b[y][x] = 0; + } + } + updField(&mainView, ~0); + + // turn on sub display only long enough for options + videoSetModeSub(MODE_0_2D + | DISPLAY_BG0_ACTIVE); + setupPalette(srsColors); + options(&mainView, customPrefs); + videoSetModeSub(MODE_0_2D); + BGCTRL_SUB[0] = BG_16_COLOR | BG_TILE_BASE(0) | BG_MAP_BASE(31); + BG_PALETTE_SUB[0] = RGB5(0, 0, 0); + unpackCommonOptions(&mainView, customPrefs); + + p.seed = curTime ^ (curTime << 16); + play(players, 1); + + tb.cmd = TALKBACK_STOP_MUSIC; + BG_PALETTE[0] = (control.countdown > 0) + ? RGB5(31, 15, 15) + : RGB5(15, 31, 15); + + // play game over sound + if (control.countdown > 0) { + playSoundEffects(&mainView, 3 << 16, control.countdown); + } + for (int i = 0; i < 60; ++i) { + vsync(); + //gba_poll_sound(&platView); + } + debrief(&mainView); + } +} diff -r 000000000000 -r c84446dfb3f5 src/ljds.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljds.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,77 @@ +/* DS frontend for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#ifndef LJDS_H +#define LJDS_H + +#include +#include +#include "ljcontrol.h" + +//#define SPR_VRAM(x) ((u32 *)(0x06420000 + 32 * x)) +#define SPR_VRAM(x) ((u32 *)(0x06400000 + 32 * x)) +#define MAINOAM ((SpriteEntry *)0x07000000) + +#define PATRAM4(x, tn) ((u32 *)(BG_TILE_RAM(0) | (((x) << 14) + ((tn) << 5)) )) + +#ifndef MAP +typedef u16 NAMETABLE[32][32]; + +#define MAP ((NAMETABLE *)BG_MAP_RAM(0)) +#define MAP_HFLIP 0x0400 +#define MAP_VFLIP 0x0800 +#define MAP_FLIP 0x0c00 +#define MAP_PALETTE(x) ((x) << 12) +#endif + +struct LJPCView { + const u16 *sndData[4]; + u8 sndLeft[4]; +}; + +#define KEY_TOUCH_RIGHT (KEY_RIGHT << 8) +#define KEY_TOUCH_LEFT (KEY_LEFT << 8) +#define KEY_TOUCH_UP (KEY_UP << 8) +#define KEY_TOUCH_DOWN (KEY_DOWN << 8) + +extern short mouse_x, mouse_y; +extern LJBits mouse_b; + +void textout(const char *str, int x, int y, int c); +void isr(void); +void cls(void); +void vsync(void); +LJBits readPad(unsigned int player); +extern volatile int curTime; + +void install_sound(void); +void gba_poll_sound(struct LJPCView *v); +void gba_play_sound(struct LJPCView *v, int effect); + +extern unsigned char customPrefs[]; +void options(LJView *view, unsigned char *prefs); + +int needLidSleep(void); +void debrief(const LJView *v); +#endif diff -r 000000000000 -r c84446dfb3f5 src/ljgba.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljgba.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,230 @@ +/* GBA frontend for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006-2007 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#include +#include +#include "ljgba.h" +#include "ljplay.h" +#include "options.h" + +#if 1 + #define LJ_VERSION "0.46 ("__DATE__")" +#else + #define LJ_VERSION "WIP ("__DATE__")" +#endif + +#define SCREEN_W 30 +#define SCREEN_H 20 +#define DS_PFTOP 0 +#define DS_PFLEFT 9 +#include "ljgbads.inc" + +static unsigned int nSprites; + +LJBits readHWKeys(void) { + return (~REG_KEYINPUT) & 0x03FF; +} + +void finishSprites() { + for (int i = nSprites - 1; i >= 0; --i) { + OAM[i].attr0 = 512; + } + nSprites = 128; +} + +void vsync(void) { + VBlankIntrWait(); +} + +void openWindow(void) { + REG_DISPCNT = MODE_0 | BG0_ON | OBJ_ON; + BGCTRL[0] = BG_TILE_BASE(0) | BG_MAP_BASE(31); + BG_PALETTE[0] = RGB5(31, 31, 31); + BG_PALETTE[1] = RGB5(0, 0, 0); + SPRITE_PALETTE[0] = RGB5(31, 0, 31); + SPRITE_PALETTE[1] = RGB5(0, 0, 0); + setupPalette(srsColors); +} + +void playSoundEffects(LJView *v, LJBits sounds, int countdown) { + if (sounds & LJSND_IRS) { + gba_play_sound(v->plat, 6); + } else if (sounds & LJSND_ROTATE) { + gba_play_sound(v->plat, 1); + } + if (sounds & LJSND_SHIFT) { + gba_play_sound(v->plat, 0); + } + if (sounds & LJSND_LAND) { + gba_play_sound(v->plat, 2); + } + if (sounds & LJSND_LOCK) { + gba_play_sound(v->plat, 3); + } + if (sounds & LJSND_B2B) { + gba_play_sound(v->plat, 8); + } else if (sounds & LJSND_SETB2B) { + gba_play_sound(v->plat, 7); + } else if (sounds & LJSND_LINE) { + gba_play_sound(v->plat, 4); + } + if (sounds & LJSND_HOLD) { + gba_play_sound(v->plat, 5); + } + if (sounds & LJSND_SECTIONUP) { + gba_play_sound(v->plat, 9); + } + gba_poll_sound(v->plat); +} + +/** + * Draws a tetromino whose lower left corner of the bounding box is at (x, y) + * @param b the bitmap to draw to + * @param piece the piece to be drawn + * @param x distance from to left side of 4x4 box + * @param y distance from top of bitmap to bottom of 4x4 box + * @param the rotation state (0: U; 1: R; 2: D; 3: L; 4: Initial position) + * @param w width of each block + * @param h height of each block + * @param color Drawing style + * color == 0: draw shadow + * color == 0x10 through 0x70: draw in that color + * color == 0x80: draw as garbage + * color == -255 through -1: draw with 255 through 1 percent lighting + */ +LJBits drawPiece(LJView *const v, void *const b, + int piece, int x, int y, int theta, + int color, int w, int h) { + // Don't try to draw the -1 that's the sentinel for no hold piece + if (piece < 0) + return 0; + + LJBits rows = 0; + LJBlkSpec blocks[4]; + + expandPieceToBlocks(blocks, v->field, piece, 0, 0, theta); + + for (int blk = 0; blk < 4; ++blk) { + int blkValue = blocks[blk].conn; + if (blkValue) { + int blkX = blocks[blk].x; + int blkY = blocks[blk].y; + const int dstX = x + w * blkX; + const int dstY = y + h * (-1 - blkY); + + if (color == 0x80) { + blkValue = 0x8001; // garbage hold + } else if (color != 0) { + blkValue = ((blkValue & 0xF0) << 8) | 1; + } else if (color == 0) { + if (v->hideShadow == LJSHADOW_COLORED) { + blkValue = ((blkValue & 0xF0) << 8) | 2; + } else { + blkValue = 0x0002; + } + } + + if (dstY > -8 && dstY < 160) { + --nSprites; + OAM[nSprites].attr0 = dstY & 0x00FF; + OAM[nSprites].attr1 = dstX & 0x01FF; + OAM[nSprites].attr2 = blkValue; + } + + rows |= 1 << blkY; + } + } + + return rows; +} + +extern unsigned char prefs[OPTIONS_MENU_LEN]; + +#include "gbamenus.h" + +int main(void) { + LJField p = { + .leftWall = 1, + .rightWall = 11, + .ceiling = 20 + }; + LJControl control = { + .dasSpeed = 1, + .dasDelay = 10, + .initialDAS = 1, + .allowDiagonals = 0, + .softDropSpeed = 0, + .softDropLock = 0, + .hardDropLock = 1 + }; + struct LJPCView platView; + LJView mainView = { + .field = &p, + .control = &control, + .smoothGravity = 1, + .plat = &platView, + .backDirty = ~0 + }; + + videoSetMode(MODE_0_2D); + BG_PALETTE[0] = RGB5(31, 0, 0); + install_timer(); + openWindow(); + install_sound(&platView); + initOptions(customPrefs); + coprNotice(); + + while (1) { + LJView *const players[1] = {&mainView}; + REG_DISPCNT = MODE_0 | BG0_ON; + BG_PALETTE[0] = RGB5(31, 31, 31); + setupPalette(srsColors); + options(&mainView, customPrefs); + unpackCommonOptions(&mainView, customPrefs); + + p.seed = curTime ^ (curTime << 16); + play(players, 1); + BG_PALETTE[0] = (control.countdown > 0) + ? RGB5(31, 15, 15) + : RGB5(15, 31, 15); + if (control.countdown > 0) { + gba_play_sound(&platView, 10); + gba_play_sound(&platView, 11); + } else { + gba_play_sound(&platView, 12); + gba_play_sound(&platView, 13); + } + for (int i = 0; i < 60; ++i) { + vsync(); + gba_poll_sound(&platView); + } +#if 1 + debrief(&mainView); +#else + textout(" Press ", (SCREEN_W - 8) / 2, DS_PFTOP + 8, 0); + textout(" Start ", (SCREEN_W - 8) / 2, DS_PFTOP + 9, 0); + waitForStart(); +#endif + } +} diff -r 000000000000 -r c84446dfb3f5 src/ljgba.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljgba.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,51 @@ +/* GBA frontend for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#ifndef LJGBA_H +#define LJGBA_H + +#include +#include +#include "ljcontrol.h" + +struct LJPCView { + const u16 *sndData[4]; + u8 sndLeft[4]; +}; + +void textout(const char *str, int x, int y, int c); +void isr(void); +void cls(void); +void vsync(void); +LJBits readPad(unsigned int player); +extern volatile int curTime; + +void install_sound(struct LJPCView *v); +void gba_poll_sound(struct LJPCView *v); +void gba_play_sound(struct LJPCView *v, int effect); + +extern unsigned char customPrefs[]; +void options(LJView *view, unsigned char *prefs); +void debrief(const LJView *v); +#endif diff -r 000000000000 -r c84446dfb3f5 src/ljgbads.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljgbads.inc Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,579 @@ +/* DS/GBA frontend for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006-2007 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#include "fontdraw.h" + +unsigned char wantPause; +volatile char redrawWholeScreen = 0; + +void finishSprites(); + +void cls(void) { + for (int y = 0; y < SCREEN_H; ++y) { + for (int x = 0; x < SCREEN_W; ++x) { + MAP[31][y][x] = ' '; + } + } +} + +void drawFrame(LJView *v) { + int left = v->field->leftWall; + int right = v->field->rightWall; + cls(); + if (DS_PFTOP > 0) { + for (int x = left - 1; x < right + 1; ++x) { + MAP[31][DS_PFTOP - 1][DS_PFLEFT + x] = 0x8005; + } + } + if (DS_PFTOP + LJ_PF_VIS_HT < SCREEN_H) { + for (int x = left - 1; x < right + 1; ++x) { + MAP[31][DS_PFTOP + LJ_PF_VIS_HT][DS_PFLEFT + x] = 0x8004; + } + } + for (int i = 0; i < LJ_PF_VIS_HT; ++i) { + MAP[31][i + DS_PFTOP][DS_PFLEFT + left - 1] = 0x8003; + MAP[31][i + DS_PFTOP][DS_PFLEFT + right] = 0x8003; + } + textout("Score", 1, 6 + DS_PFTOP, 0); + textout("Lines", 1, 8 + DS_PFTOP, 0); + textout("Speed", 1, 10 + DS_PFTOP, 0); +} + +static void drawBlock(struct LJPCView *unused, int x, int y, int b) { + if (x >= 0 && x < LJ_PF_WID && y >= 0 && y < LJ_PF_VIS_HT) { + int c; + + if (b == 0x100) { + c = '-'; + } else if (b == 0x110) { + c = 0x8005; // ceiling tile + } else if (b >= 0xA0 && b < 0xC0) { + c = (b & 0xF0) << 8 | (b & 0x0F) | 0x100; + } else if (b >= 0x10) { + c = (b & 0xF0) << 8 | (b & 0x0F) | 0x10; + } else { + c = ' '; + } + MAP[31][DS_PFTOP + LJ_PF_VIS_HT - 1 - y][DS_PFLEFT + x] = c; + } +} + +void updField(const LJView *const v, LJBits rows) { + const LJField *const p = v->field; + + for (int y = 0; + y < LJ_PF_VIS_HT && rows != 0; + ++y, rows >>= 1) { + int blankTile = 0; + + if (y == p->ceiling) { + blankTile = 0x110; + } else if (hotlineRows[y] && v->field->scoreStyle == LJSCORE_HOTLINE) { + blankTile = 0x100; + } + if (p->state == LJS_LINES_FALLING && p->stateTime > 0 + && ((1 << y) & p->tempRows)) { + blankTile = 0x100; + } + if (rows & 1) { + for (int x = p->leftWall; x < p->rightWall; x++) { + int b = v->hidePF ? 0 : p->b[y][x]; + drawBlock(v->plat, x, y, b ? b : blankTile); + } + } + } +} + +void blitField(LJView *v) { + +} + +int getTime() { + return curTime; +} + +#if !defined(DISP_VBLANK_IRQ) +#define DISP_VBLANK_IRQ LCDC_VBL +#endif +#if !defined(IRQ_HANDLER) +#define IRQ_HANDLER INT_VECTOR +#endif + +void install_timer(void) +{ + + // Turn off interrupts before doing anything + REG_IME = 0; + + // Overwrite the ISR + IRQ_HANDLER = isr; + + // Hook up the interrupt destination + REG_IE = IRQ_VBLANK; + + // Acknowledge all pending interrupts + REG_IF = ~0; + + // Set up an interrupt source + REG_DISPSTAT = DISP_VBLANK_IRQ; + + // Turn interrupts back on + REG_IME = 1; +} + +void yieldCPU(void) { + // we're not multitasking so we don't need this + // on the GBA and DS, vsync() does all the waiting we need +} + +static void upcvt_4bit(void *dst, const u8 *src, size_t len) +{ + u32 *out = dst; + + for(; len > 0; len--) + { + u32 dst_bits = 0; + u32 src_bits = *src++; + u32 x; + + for(x = 0; x < 8; x++) + { + dst_bits <<= 4; + dst_bits |= src_bits & 1; + src_bits >>= 1; + } + *out++ = dst_bits; + } +} + +extern const unsigned char text_chr[]; +extern const unsigned int text_chr_size; +extern const unsigned char gbablk_chr[]; +extern const unsigned int gbablk_chr_size; + +static void loadOneConnection(void *in_dst, const void *in_src) { + u16 *dst = in_dst; + const u16 *src = in_src; + for (unsigned int conn = 0; conn < 16; ++conn) { + unsigned int topSegY = (conn & CONNECT_U) ? 32 : 0; + unsigned int botSegY = (conn & CONNECT_D) ? 8 : 40; + unsigned int leftSegX = (conn & CONNECT_L) ? 16 : 0; + unsigned int rightSegX = (conn & CONNECT_R) ? 1 : 17; + for (unsigned int i = 0; i < 8; i += 2) { + *dst++ = src[leftSegX + topSegY + i]; + *dst++ = src[rightSegX + topSegY + i]; + } + for (unsigned int i = 0; i < 8; i += 2) { + *dst++ = src[leftSegX + botSegY + i]; + *dst++ = src[rightSegX + botSegY + i]; + } + } +} + +static void loadConnections(void) { + loadOneConnection(PATRAM4(0, 16), gbablk_chr + 8*32); + loadOneConnection(PATRAM4(0, 256), gbablk_chr + 12*32); +} + +static void load_font(void) { + upcvt_4bit(PATRAM4(0, 0), text_chr, text_chr_size); + memcpy(PATRAM4(0, 0), gbablk_chr, 8*32); + memcpy(SPR_VRAM(0), gbablk_chr, 8*32); + loadConnections(); +} + +void textout(const char *str, int x, int y, int c) { + u16 *dst = &(MAP[31][y][x]); + int spacesLeft = SCREEN_W - x; + + c <<= 12; + while (*str != 0 && spacesLeft > 0) { + *dst++ = c | *(unsigned char *)str++; + --spacesLeft; + } +} + +static const u16 srsColors[12] = { + RGB5(2, 2, 2), + RGB5(0, 3, 3), + RGB5(0, 0, 3), + RGB5(3, 2, 0), + RGB5(3, 3, 0), + RGB5(0, 3, 0), + RGB5(2, 0, 3), + RGB5(3, 0, 0), + RGB5(2, 2, 2), + RGB5(3, 0, 0), + RGB5(2, 2, 2), + RGB5(3, 2, 1), +}; + +static const u16 arsColors[12] = { + RGB5(2, 2, 2), + RGB5(3, 1, 0), + RGB5(0, 0, 3), + RGB5(3, 2, 0), + RGB5(3, 3, 0), + RGB5(2, 0, 3), + RGB5(0, 3, 3), + RGB5(0, 3, 0), + RGB5(2, 2, 2), + RGB5(3, 0, 0), + RGB5(2, 2, 2), + RGB5(3, 2, 1), +}; + +void setupPalette(const u16 *colors) { + for (int i = 1; i < 12; ++i) { + int c = colors[i]; + + BG_PALETTE[i * 16 + 1] = RGB5(22,22,22) + 3 * c; + BG_PALETTE[i * 16 + 2] = RGB5(13,13,13) + 6 * c; + BG_PALETTE[i * 16 + 3] = RGB5( 4, 4, 4) + 9 * c; + BG_PALETTE[i * 16 + 4] = RGB5( 4, 4, 4) + 7 * c; + BG_PALETTE[i * 16 + 5] = RGB5( 4, 4, 4) + 5 * c; + BG_PALETTE[i * 16 + 6] = RGB5( 4, 4, 4) + 3 * c; + } + memcpy(SPRITE_PALETTE, BG_PALETTE, 12 * 32); +} + +// libnds style wrapper around libgba header +#ifndef DISPLAY_BG0_ACTIVE +#define DISPLAY_BG0_ACTIVE BG0_ON +#define DISPLAY_SPR_ACTIVE OBJ_ON +#define MODE_0_2D MODE_0 +#define DISPLAY_SPR_1D_LAYOUT OBJ_1D_MAP +static inline void videoSetMode(int x) { + REG_DISPCNT = x; +} + +#endif + +void waitForStart(void) { + LJBits lastJ = ~0; + LJBits jnew = 0; + + do { + LJBits j = ~REG_KEYINPUT; + jnew = j & ~lastJ; + lastJ = j; + vsync(); + } while(!(jnew & (KEY_A | KEY_START))); +} + + +static const char *const coprNoticeLines[] = { + "LOCKJAW: The Reference", + "Version "LJ_VERSION, + NULL, + "© 2008 Damian Yerrick", + "Not sponsored or endorsed by Nintendo", + "or Tetris Holding.", + "Comes with ABSOLUTELY NO WARRANTY.", + "This is free software, and you are welcome", + "to share it under the conditions described", + "in GPL.txt." +}; + +void coprNotice(void) { + videoSetMode(MODE_0_2D); + BGCTRL[0] = BG_TILE_BASE(0) | BG_MAP_BASE(31); + BG_OFFSET[0].x = 0; + BG_OFFSET[0].y = 0; + + BG_PALETTE[0] = RGB5(31,31,31); + BG_PALETTE[1] = RGB5(20,20,20); + BG_PALETTE[2] = RGB5( 0, 0, 0); + vwfWinInit(&vwfTop); + for (int i = 0, y = 8; + i < sizeof(coprNoticeLines) / sizeof(coprNoticeLines[0]); + ++i) { + if (coprNoticeLines[i]) { + vwfPuts(&vwfTop, coprNoticeLines[i], 8, y); + y += 12; + } else { + y += 6; + } + } + vwfPuts(&vwfTop, "Press Start", 8, SCREEN_H * 8 - 16); + videoSetMode(MODE_0_2D | DISPLAY_BG0_ACTIVE); + waitForStart(); +} + +LJBits menuReadPad(void) { + LJBits keys = readPad(0); + if (keys & VKEY_START) { + keys |= VKEY_ROTR; + } + return keys; +} + +void ljBeginDraw(LJView *v, int sync) { + vsync(); + finishSprites(); +} + +void ljEndDraw(LJView *v) { + +} + +/* Replay stubs */ +void replayRecord(struct LJReplay *r, LJBits keys, const LJInput *in) { + +} + +void replayClose(struct LJReplay *r) { + +} + +int getReplayFrame(struct LJReplay *r, LJInput *d) { + return 0; +} + +#define READY_GO_LINE 13 + +void startingAnimation(LJView *v) { + vsync(); + gba_poll_sound(v->plat); + setupPalette(rotSystems[v->field->rotationSystem]->colorScheme ? arsColors : srsColors); + videoSetMode(MODE_0_2D); + cls(); + load_font(); + BG_PALETTE[0] = RGB5(31,31,31); + BG_PALETTE[1] = RGB5( 0, 0, 0); + drawFrame(v); + finishSprites(); + vsync(); + gba_poll_sound(v->plat); + videoSetMode(MODE_0_2D + | DISPLAY_BG0_ACTIVE); + BGCTRL[0] = BG_TILE_BASE(0) | BG_MAP_BASE(31); + BG_OFFSET[0].x = 0; + BG_OFFSET[0].y = 0; + + textout("Ready", + (LJ_PF_WID - 5) / 2 + DS_PFLEFT, + DS_PFTOP + LJ_PF_VIS_HT - 1 - READY_GO_LINE, + 0); + for (int i = 0; i < 30; ++i) { + vsync(); + gba_poll_sound(v->plat); + } + v->backDirty = ~0; + updField(v, ~0); + videoSetMode(MODE_0_2D + | DISPLAY_BG0_ACTIVE + | DISPLAY_SPR_1D_LAYOUT + | DISPLAY_SPR_ACTIVE); + drawScore(v); + finishSprites(); + + textout(" GO! ", + (LJ_PF_WID - 5) / 2 + DS_PFLEFT, + DS_PFTOP + LJ_PF_VIS_HT - 1 - READY_GO_LINE, + 0); + for (int i = 0; i < 30; ++i) { + vsync(); + gba_poll_sound(v->plat); + } + drawFrame(v); + wantPause = 0; + +#ifdef ARM9 + tb.cmd = TALKBACK_PLAY_MUSIC; +#endif +} + +int pauseGame(struct LJPCView *v) { + LJBits lastKeys = ~0; + int unpaused = 0; + int canceled = 0; + + // hide playfield + for (int y = DS_PFTOP; y < DS_PFTOP + LJ_PF_VIS_HT; ++y) { + for (int x = DS_PFLEFT; + x < DS_PFLEFT + LJ_PF_WID; + ++x) { + MAP[31][y][x] = ' '; + } + } + textout("Game", 2 + DS_PFLEFT, 6 + DS_PFTOP, 0); + textout("Paused", 2 + DS_PFLEFT, 7 + DS_PFTOP, 0); + textout("Start:", 2 + DS_PFLEFT, 10 + DS_PFTOP, 0); + textout("Resume", 2 + DS_PFLEFT, 11 + DS_PFTOP, 0); + textout("Select:", 2 + DS_PFLEFT, 13 + DS_PFTOP, 0); + textout("Exit", 2 + DS_PFLEFT, 14 + DS_PFTOP, 0); + +#ifdef ARM9 + tb.cmd = TALKBACK_PAUSE_MUSIC; +#endif + while (!unpaused || (lastKeys & (KEY_SELECT | KEY_START))) { + int keys = ~REG_KEYINPUT; + if (keys & ~lastKeys & KEY_START) { + unpaused = 1; + } + if (keys & ~lastKeys & KEY_SELECT) { + unpaused = 1; + canceled = 1; + } + finishSprites(); + vsync(); + gba_poll_sound(v); + lastKeys = keys; + } +#ifdef ARM9 + tb.cmd = TALKBACK_PLAY_MUSIC; +#endif + return canceled; +} + +int ljHandleConsoleButtons(LJView *v) { + LJBits keys = ~REG_KEYINPUT; + int canceled = 0; + + wantPause |= !!(keys & KEY_START); + if (wantPause) { + canceled = pauseGame(v->plat); + wantPause = 0; + drawFrame(v); + v->backDirty = ~0; + } + return canceled; +} + +LJBits drawPiece(LJView *const v, void *const b, + int piece, int x, int y, int theta, + int color, int w, int h); + +void drawFallingPiece(LJView *v) { + LJBits bits = 0; + const LJField *const p = v->field; + int piece = p->curPiece[0]; + int y = ljfixfloor(p->y); + const int w = 8; + const int h = 8; + int drawnY = v->smoothGravity ? ljfixfloor(h * p->y) : h * y; + const int color = (p->state == LJS_LANDED) + ? -128 - ((p->stateTime + 1) * 128 / (p->speed.lockDelay + 1)) + : pieceColors[piece]; + + bits = drawPiece(v, NULL, piece, + w * (p->x + DS_PFLEFT), + h * (LJ_PF_VIS_HT + DS_PFTOP) - drawnY, + p->theta, + color, w, h); + bits = (y >= 0) ? bits << y : bits >> -y; + bits &= (1 << LJ_PF_VIS_HT) - 1; + + v->backDirty |= bits | (bits << 1); + v->frontDirty |= bits | (bits << 1); +} + +#define SHADOW_BLOCK 0x00 + +void drawShadow(LJView *v) { + LJBits bits = 0; + const LJField *const p = v->field; + int piece = p->curPiece[0]; + int y = p->hardDropY; + const int w = 8; + const int h = 8; + + bits = drawPiece(v, NULL, piece, + w * (p->x + DS_PFLEFT), + h * (LJ_PF_VIS_HT + DS_PFTOP - y), + p->theta, + SHADOW_BLOCK, w, h); + bits = (y >= 0) ? bits << y : bits >> -y; + bits &= (1 << LJ_PF_VIS_HT) - 1; + + v->backDirty |= bits; + v->frontDirty |= bits; +} + +void drawNextPieces(LJView *v) { + int holdPieceColor = v->field->alreadyHeld + ? 0x80 + : pieceColors[v->field->holdPiece]; + + // Draw hold piece + drawPiece(v, NULL, + v->field->holdPiece, + (DS_PFLEFT - 5) * 8, (DS_PFTOP + 5) * 8, 4, + holdPieceColor, 8, 8); + + // Draw next pieces + int y = 32 + 8 * DS_PFTOP; + int x = (DS_PFLEFT + LJ_PF_WID) * 8; + for(int i = 1; i <= v->nextPieces; ++i) { + int piece = v->field->curPiece[i]; + + if (!v->hideNext) { + drawPiece(v, NULL, + piece, x, y, 4, + pieceColors[piece], 8, 8); + } + y += 20; + } + v->frontDirty &= (1 << LJ_PF_VIS_HT) - 1; +} + +void drawScore(LJView *v) { + char txt[16]; + int tpm = -1; + int lvl = v->field->speedState.level; + + siprintf(txt, "%8u", v->field->score); + textout(txt, 0, 7 + DS_PFTOP, 0); + siprintf(txt, "%8u", v->field->lines); + textout(txt, 0, 9 + DS_PFTOP, 0); + + if (lvl > 0) { + textout("Level:", 1, SCREEN_H - 3, 0); + siprintf(txt, "%9u", lvl); + textout(txt, 0, SCREEN_H - 2, 0); + } + + if (v->nLockTimes >= 2) { + int time = v->lockTime[0] - v->lockTime[v->nLockTimes - 1]; + if (time > 0) { + tpm = 3600 * (v->nLockTimes - 1) / time; + } + } + if (tpm > 0) { + siprintf(txt, "%8d", tpm); + textout(txt, 0, 11 + DS_PFTOP, 0); + } else { + textout(" ---", 0, 11 + DS_PFTOP, 0); + } + + { + int seconds = v->field->gameTime / 60; + int minutes = seconds / 60; + seconds -= minutes * 60; + siprintf(txt, "%6d:%02d", minutes, seconds); + textout(txt, 0, SCREEN_H - 1, 0); + } + drawNextPieces(v); +} diff -r 000000000000 -r c84446dfb3f5 src/ljlocale.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljlocale.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,299 @@ +#include "ljlocale.h" +#include +#include + + +static const FourCCLocaleEntry *curLocale = englishLocale; + +void ljSetLocale(const FourCCLocaleEntry *locale) { + if (locale) { + curLocale = locale; + } +} + +static const FourCCLocaleEntry *ljGetFourCCEntry(unsigned int i) { + const FourCCLocaleEntry *base = curLocale; + + for(; base->code.i != 0; ++base) { + if (base->code.i == i) { + return base; + } + } + return NULL; +} + +const char *ljGetFourCCName(FourCC i) { + const FourCCLocaleEntry *ent = ljGetFourCCEntry(i.i); + return ent ? ent->name : NULL; +} + +const char *ljGetFourCCDesc(FourCC i) { + const FourCCLocaleEntry *ent = ljGetFourCCEntry(i.i); + return ent ? ent->desc : NULL; +} + +const FourCCLocaleEntry englishLocale[] = { + /* Page names */ + /* omitted */ + + /* Option names */ + {{"gimm"}, "Gimmick", + "Goal or other game mode" }, + {{"pfw"}, "Well width", + "Amount of space to build in" }, + {{"pfh"}, "Well height", + "Amount of space to build in" }, + {{"vzsp"}, "Enter above ceiling", + "Pieces start below or above top of well" }, + {{"spdc"}, "Speed curve", + "Controls speed of falling piece and delays" }, + {{"are"}, "Max entry delay", + "Time from lockdown to next piece entry" }, + {{"piec"}, "Piece set", + "Which pieces will be used" }, + {{"rand"}, "Randomizer", + "How the pieces are shuffled" }, + {{"hold"}, "Hold piece", + "Press Hold to save a piece for later" }, + {{"rots"}, "Rotation system", + "How pieces turn and react to walls" }, + {{"upkl"}, "Floor kicks", + "Times each piece can rotate upward" }, + {{"srk"}, "Sub-row kicks", + "" }, + {{"lock"}, "Lockdown", + "Start of time when a piece slides around" }, + {{"sldt"}, "Lock delay", + "Amount of time a piece can slide around" }, + {{"deep"}, "Deep drop", + "Hard drop past blocks in the well" }, + {{"clrd"}, "Line clear delay", + "Time for cleared lines to disappear" }, + {{"grav"}, "Clear gravity", + "How blocks above a cleared line fall" }, + {{"glue"}, "Gluing", + "How blocks stick together" }, + {{"lsco"}, "Line scoring", + "Points awarded for lines" }, + {{"dsco"}, "Drop scoring", + "Points awarded for fast play (Up/Down)" }, + {{"tspn"}, "T-spin detection", + "Detect turning a piece into a tight space" }, + {{"garb"}, "Garbage", + "Blocks added to the bottom of the well" }, + {{"dasd"}, "Max sideways delay", + "Hold left/right this long to move fast" }, + {{"dass"}, "Sideways speed", + "How fast piece moves left/right" }, + {{"idas"}, "Initial sideways motion", + "Allow shifting as a piece is coming out?" }, + {{"irs"}, "Initial rotation", + "Hold rotate to turn each piece as it enters" }, + {{"8way"}, "Allow diagonal motion", + "Turn off to prevent accidental hard drops" }, + {{"sfds"}, "Soft drop speed", + "Hold down to drop the piece this fast" }, + {{"sfdl"}, "Soft drop", + "Behavior when a piece lands with down" }, + {{"hrdl"}, "Hard drop", + "Behavior when a piece lands with up" }, + {{"tls"}, "Shadow", + "Shows where the piece will land" }, + {{"invs"}, "Hide blocks in well", + "Invisible challenge tests your memory" }, + {{"next"}, "Next pieces", + "Number of previewed pieces" }, + {{"srph"}, "Smooth gravity", + "Turn off to make the piece fall row-by-row" }, + {{"inpv"}, "Next above shadow", + "Number of previewed pieces inside the well" }, + {{"mblr"}, "Drop trails", + "Makes hard drops look harder" }, + + {{"lidp"}, "Pause on task switch", + "Makes Alt+Tab or screensaver safe" }, + {{"rec"}, "Record all games", + "Save each game's replay to demo.ljm" }, + {{"wndw"}, "Display mode", + "Shut out distractions" }, + + /* Booleans */ + {{"Off"}, "Off" }, + {{"On"}, "On" }, + + /* Gimmicks */ + {{"Mara"}, "Marathon", + "Play until you top out."}, + {{"Line"}, "40 lines", + "Game ends after you clear this many lines."}, + {{"Time"}, "180 seconds", + "Game ends after this much time."}, + {{"DrlA"}, "Drill Attack", + "Clear the bottom row of garbage to win."}, + {{"Item"}, "Vs. w/Items", + "Three opponents send disabling attacks."}, + {{"Keys"}, "Baboo!", + "What can you do with 300 keystrokes?"}, + + /* Garbage */ + {{"HRD"}, "Home Run Derby", + "Clear 1, 2, or 3 lines and get garbage back"}, + {{"DrlG"}, "Drill", + "Well is filled with random garbage"}, + {{"Zigz"}, "Zigzag", + "Well is filled with zigzag garbage"}, + + /* Piece set */ + {{"AllP"}, "All pieces", + "Pieces made of two, three, or four blocks" }, + {{"IJLO"}, "Tetrominoes", + "Pieces made of four blocks"}, + {{"JLOT"}, "Tetrominoes no I", + "Four blocks, no straight pieces"}, + {{"SZSZ"}, "S and Z only" }, + {{"IIII"}, "iCheat(tm)", + "All I (straight) tetrominoes" }, + {{"TTTT"}, "T-Party", + "All T tetrominoes" }, + + /* Randomizer */ + {{"Unif"}, "Memoryless", + "Equal probability to deal any piece" }, + {{"Bag"}, "Bag", + "Deals one of each piece before repeats"}, + {{"Bag+"}, "Bag + 1", + "Adds one extra to each set of pieces"}, + {{"Bag2"}, "Double bag", + "Deals two of each piece before repeats"}, + {{"Hist"}, "Strict history", + "Does not deal a recent piece"}, + {{"His6"}, "History 6 rolls", + "Rarely deals a recent piece"}, + + /* Speed curve */ + {{"Exp"}, "Exponential", + "Game gradually gets faster"}, + {{"Rh20"}, "Rhythm 20G", + "Slide pieces into place to the beat"}, + {{"Rhy0"}, "Rhythm 0G", + "Drop pieces into place to the beat"}, + {{"TGM2"}, "Master" }, + {{"TAPD"}, "Death" }, + {{"TAD3"}, "Death 300+" }, + {{"NESc"}, "NES" }, + {{"GBc"}, "Game Boy" }, + {{"GBHL"}, "Game Boy Heart" }, + + /* Hold */ + {{"HldE"}, "On, empty", + "First held piece brings out next"}, + {{"HldR"}, "On, random", + "Random piece in hold box at start"}, + {{"HldN"}, "Hold to next", + "Hold swaps falling and next (like Radica)"}, + + /* Rotation systems */ + {{"SRS"}, "SRS", + "LOL T-spin triples"}, + {{"Sega"}, "Sega 1988" }, + {{"ARS"}, "Arika", + "The rotation system of grandmasters"}, + {{"Tngn"}, "Tengen", + "Unlicensed" }, + {{"NRSL"}, "Game Boy", + "Left-handed system"}, + {{"NRSR"}, "NES", + "Right-handed system"}, + {{"TOD4"}, "TOD M4", + "Like SRS but less radical" }, + {{"TDX"}, "Climbing", + "Spider-Man's favorite Tetris game is DX"}, + + /* Lockdown */ + {{"OLD"}, "Classic", + "Lock when piece lands" }, + {{"EntR"}, "Entry reset", + "Lock delay resets for each new piece" }, + {{"StpR"}, "Step reset", + "Lock delay resets when piece moves down" }, + {{"MovR"}, "Move reset", + "Lock delay resets when piece moves" }, + + /* Lock/line delay and floor kicks */ + {{"SBSC"}, "Set by speed curve" }, + {{"Inf"}, "Unlimited" }, + + /* Sub-row kick */ + {{"srkN"}, "Unchanged" }, + {{"srkM"}, "Kick minimal distance" }, + {{"srkU"}, "Always kick up" }, + + /* Gluing and gravity */ + {{"Naiv"}, "Naive", + "Can result in floating blocks"}, + {{"Squ"}, "Square", + "Form a 4x4 square from complete pieces" }, + {{"Stky"}, "Sticky", + "Blocks stick together"}, + {{"byCo"}, "Sticky by color", + "Blocks of each color stick together"}, + {{"Casc"}, "Cascade", + "Each piece falls separately" }, + + /* Line scoring */ + {{"LJ"}, "LJ", + "100, 400, 700, 1200, B2B=200, T=500*n" }, + {{"Fibo"}, "Fibonacci", + "100, 200, 300, 500 (800, 1300, ...)" }, + {{"HotL"}, "Hotline", + "100-600 for clearing lines on specific rows" }, + {{"TDSl"}, "Guideline", + "Level * 100, 300, 500, 800, B2B=50%, T=?" }, + {{"NESl"}, "8-bit", + "Level * 40, 100, 300, 1200" }, + {{"LJns"}, "LJ (nerfed spin)", + "100, 400, 700, 1200, B2B=200, T=300*n" }, + + /* Drop scoring */ + {{"ConD"}, "Continuous drop", + "1 point per row dropped without stopping"}, + {{"S1H1"}, "Drop", + "1 point per row soft or hard dropped"}, + {{"S1H2"}, "Soft x1, Hard x2", + "1 pt/row for soft drop, 2 for hard drop"}, + + /* T-spin */ + {{"Imob"}, "Immobile", + "Rotate any piece so it can't move up"}, + {{"CrnT"}, "3-corner T", + "Rotate T with 3 corners filled" }, + {{"WKT"}, "3-corner T no kick", + "Rotate T with 3 corners filled, without kick" }, + + /* sideways speed, soft drop speed, zangi */ + + /* Zangi */ + {{"Slid"}, "Slide", + "The piece can be dropped and then moved"}, + {{"Lock"}, "Lock", + "The piece locks immediately"}, + {{"LocU"}, "Lock on release", + "Can be moved while drop key is pressed"}, + + /* Shadow */ + {{"tl25"}, "Fainter color" }, + {{"tl50"}, "Faint color" }, + {{"tlsC"}, "Color" }, + {{"tlsM"}, "Monochrome" }, + {{"invF"}, "Off, hide falling piece too"}, + +#ifdef HAS_FPU + {{"FulS"}, "Full screen", + "Try to run the game in the full screen."}, + {{"Wind"}, "Windowed", + "Run the game in a window."}, +#endif + + /* list terminator */ + { {.i=0}, NULL } +}; diff -r 000000000000 -r c84446dfb3f5 src/ljlocale.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljlocale.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,33 @@ +#ifndef LJLOCALE_H +#define LJLOCALE_H +#include + +/* +A FourCC, or four-character code, is a short string that fits in a +32-bit word. +*/ +typedef union FourCC { + char c[4]; + uint32_t i; +} FourCC; + +/* When abusing union, test that the types that the compiler uses are + the same types the developer's compiler uses. */ +extern char sizeof_uint32_t_equals_4[(sizeof(uint32_t) == 4) ? 1 : -1]; + +typedef struct FourCCLocaleEntry { + FourCC code; + const char *name; + const char *desc; +} FourCCLocaleEntry; + +void ljSetLocale(const FourCCLocaleEntry *locale); + +#define _4CC(i) (ljGetFourCCName((FourCC){i})) +const char *ljGetFourCCName(FourCC i); + +#define _4CCDESC(i) (ljGetFourCCDesc((FourCC){i})) +const char *ljGetFourCCDesc(FourCC i); + +extern const FourCCLocaleEntry englishLocale[]; +#endif diff -r 000000000000 -r c84446dfb3f5 src/ljmusic.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljmusic.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,204 @@ +/* FIXME: add license block */ +#include +#include "ljmusic.h" + +#if LJMUSIC_USING_DUMB +#include +static int dumb_inited; +#endif +#if LJMUSIC_USING_VORBIS +#include "ljvorbis.h" +#endif + +#define WARNINGS 0 + +struct LJMusic { + int paused; +#if LJMUSIC_USING_VORBIS + LJVorbis *ogg; +#endif +#if LJMUSIC_USING_DUMB + DUH *duh; + AL_DUH_PLAYER *duhplayer; +#endif +}; + +struct LJMusic *LJMusic_new(void) { + struct LJMusic *m = malloc(sizeof(struct LJMusic)); + if (!m) { + return NULL; + } +#if LJMUSIC_USING_VORBIS + m->ogg = NULL; +#endif +#if LJMUSIC_USING_DUMB + m->duh = NULL; + m->duhplayer = NULL; +#endif + return m; +} + +void LJMusic_delete(struct LJMusic *m) { + if (!m) { + return; + } + LJMusic_unload(m); + free(m); +} + +int LJMusic_load(struct LJMusic *m, const char *filename) { + if (!m || !filename) { + return -1; + } + + LJMusic_unload(m); + const char *ext = get_extension(filename); + +#if LJMUSIC_USING_VORBIS + if (!ustricmp(ext, "ogg")) { + m->ogg = LJVorbis_open(filename); + return m->ogg ? 1 : 0; + } +#endif + +#if LJMUSIC_USING_DUMB + if (!dumb_inited) { + atexit(dumb_exit); + dumb_register_stdfiles(); + } + if (!ustricmp(ext, "it")) { + m->duh = dumb_load_it_quick(filename); + return m->duh ? 1 : 0; + } + if (!ustricmp(ext, "xm")) { + m->duh = dumb_load_xm_quick(filename); + return m->duh ? 1 : 0; + } + if (!ustricmp(ext, "s3m")) { + m->duh = dumb_load_s3m_quick(filename); + return m->duh ? 1 : 0; + } + if (!ustricmp(ext, "mod")) { + m->duh = dumb_load_mod_quick(filename); + return m->duh ? 1 : 0; + } +#endif + + return 0; +} + +void LJMusic_unload(struct LJMusic *m) { + if (!m) { + return; + } + LJMusic_stop(m); + +#if LJMUSIC_USING_DUMB + if (m->duh) { + unload_duh(m->duh); + m->duh = NULL; + } +#endif + +#if LJMUSIC_USING_VORBIS + if (m->ogg) { + LJVorbis_close(m->ogg); + m->ogg = NULL; + } +#endif + +} + +void LJMusic_setLoop(struct LJMusic *m, unsigned long int start) { +#if LJMUSIC_USING_VORBIS + if (m->ogg) { + LJVorbis_setLoop(m->ogg, start); + } +#endif +} + +void LJMusic_start(struct LJMusic *m, int bufferSize, int vol) { + if (!m) { + return; + } + + LJMusic_stop(m); + +#if LJMUSIC_USING_DUMB + if (m->duh) { + m->duhplayer = al_start_duh(m->duh, 2, 0, + vol / 256.0, + bufferSize, + 48000); + } +#endif +#if LJMUSIC_USING_VORBIS + if (m->ogg) { + LJVorbis_start(m->ogg, bufferSize, vol, 128); + } +#endif + m->paused = 0; +} + +void LJMusic_stop(struct LJMusic *m) { + if (!m) { + return; + } + +#if LJMUSIC_USING_DUMB + if (m->duhplayer) { + al_stop_duh(m->duhplayer); + m->duhplayer = NULL; + } +#endif + +#if LJMUSIC_USING_VORBIS + LJVorbis_stop(m->ogg); +#endif +} + +void LJMusic_poll(struct LJMusic *m) { + if (!m) { + return; + } + +#if LJMUSIC_USING_DUMB + if (m->duhplayer) { + al_poll_duh(m->duhplayer); + } +#endif + +#if LJMUSIC_USING_VORBIS + if (m->ogg) { + LJVorbis_poll(m->ogg); + } +#endif +} + +void LJMusic_pause(struct LJMusic *m, int value) { + value = value ? 1 : 0; + if (!m) { + + } + if (WARNINGS && (value == m->paused)) { + alert("LJMusic_pause", "unchanging.", "", "OK", 0, 13, 0); + return; + } +#if LJMUSIC_USING_DUMB + if (m->duhplayer) { + if (value) { + al_pause_duh(m->duhplayer); + } else { + al_resume_duh(m->duhplayer); + } + } +#endif + +#if LJMUSIC_USING_VORBIS + if (m->ogg) { + LJVorbis_pause(m->ogg, value); + } +#endif + m->paused = value; +} + diff -r 000000000000 -r c84446dfb3f5 src/ljmusic.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljmusic.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,22 @@ +/* FIXME: add license block */ +#ifndef LJMUSIC_H +#define LJMUSIC_H + +#define LJMUSIC_USING_DUMB 1 +#define LJMUSIC_USING_VORBIS 1 + +typedef struct LJMusic LJMusic; + +struct LJMusic *LJMusic_new(void); +void LJMusic_delete(struct LJMusic *m); +int LJMusic_load(struct LJMusic *m, const char *filename); +void LJMusic_unload(struct LJMusic *m); +void LJMusic_start(struct LJMusic *m, int bufferSize, int vol); +void LJMusic_setLoop(struct LJMusic *m, unsigned long int start); +void LJMusic_stop(struct LJMusic *m); +void LJMusic_poll(struct LJMusic *m); +void LJMusic_pause(struct LJMusic *m, int value); + + +#endif + diff -r 000000000000 -r c84446dfb3f5 src/ljpath.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljpath.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,294 @@ +/* +ljpath - functions to support an application that can be +installed to either a read-write folder ("portable" config) +or to a read-only folder ("installed" config) + +Copyright 2008 Damian Yerrick + +Insert zlib license here. + +*/ + + +#include +#include +#include "ljpath.h" +#include // for PATH_MAX +#include // for mkdir() +#include + +#ifdef WIN32 +#include +#include +#define USE_WINDOWS_APPDATA +#endif + +// on the DS +#ifdef ARM9 +#include // Chishm's libfat +static char hasFAT; +#include // for MAXPATHLEN +#else +#define hasFAT 1 +#endif + +// http://forum.gbadev.org/viewtopic.php?p=147795#147795 +#if !defined(PATH_MAX) && defined(MAXPATHLEN) +#define PATH_MAX MAXPATHLEN +#endif + +static char readWriteFolder[PATH_MAX]; +static char skinFolder[PATH_MAX]; +static char readOnlyFolder[PATH_MAX]; + + +#ifdef WIN32 +static int ljmkdir(const char *path) { + //allegro_message("mkdir(\"%s\")\n", path); + return _mkdir(path); +} +#else +static int ljmkdir(const char *path) { + //allegro_message("mkdir(\"%s\")\n", path); + return mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); +} +#endif + +/** + * Wrapper around mkdir() that doesn't error if the + * directory already exists. + */ +static int makeFolder(const char *path) { + if (ljmkdir(path) < 0) { + if (errno != EEXIST) { + return -1; + } + } + return 0; +} + + + +/** + * Computes the name of the folder containing filename. + * @param dst destination to receive the folder name, of size PATH_MAX + * @param filename name of a file in this folder + */ +static void ljpathSetFolder(char *dst, const char *filename) { + const char *lastPathSep = NULL; + + // I considered strrchr, but it would need two passes: + // one for '/' and one for the drive-letter ':' under Windows. + for (const char *here = filename; *here != 0; ++here) { + if (*here == '/' || *here == '\\' || *here == ':') { + lastPathSep = here; + } + } + if (lastPathSep == NULL || lastPathSep - filename > PATH_MAX - 1) { + strcpy(dst, "."); + } else { + memcpy(dst, filename, lastPathSep - filename); + dst[lastPathSep - filename] = 0; + } +} + +#if defined(USE_WINDOWS_APPDATA) +/** + * Retrieves the read/write path on a Windows system. + * SHGetFolderPath() is described in + * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/functions/shgetfolderpath.asp + */ +static void ljpathSetRWFolder(void) { + static const char suffix1[] = "/Pin Eight"; + static const char suffix2[] = "/lockjaw"; + char path[MAX_PATH + sizeof(suffix1) + sizeof(suffix2)] = ""; + + int rVal = SHGetFolderPath( + NULL, // owner window used with obscure dial-up functionality + CSIDL_APPDATA, // type of folder to retrieve + NULL, // user ID when impersonating another user + 0, // get the current path, not the default path + path + ); + if (rVal) { + return; + } + + strcat(path, suffix1); + ljmkdir(path); + strcat(path, suffix2); + ljmkdir(path); + if (strlen(path) >= PATH_MAX) { + return; + } + + strncpy(readWriteFolder, path, PATH_MAX - 1); + readWriteFolder[PATH_MAX - 1] = 0; +} +#else +/** + * Retrieves the read/write path on a UNIX system using the HOME + * environment variable. + */ +static void ljpathSetRWFolder(void) { + static const char suffix[] = "/.lockjaw"; + const char *value = getenv("HOME"); + if (!value) { + return; + } + + unsigned int len = strlen(value) + sizeof(suffix); + if (len > PATH_MAX) { + return; + } + + strcpy(readWriteFolder, value); + strcat(readWriteFolder, suffix); + ljmkdir(readWriteFolder); + //allegro_message("readWriteFolder is now \"%s\"\n", skinFolder); +} +#endif + +void ljpathSetSkinFolder(const char *filename) { + //allegro_message("ljpathSetFolder(skinFolder, \"%s\")\n", filename); + ljpathSetFolder(skinFolder, filename); + //allegro_message("skinFolder is now \"%s\"\n", skinFolder); +} + +static int ljpathJoin(char *restrict dst, + const char *restrict folder, + const char *restrict filename) { + size_t len = 0; + + //allegro_message("ljpathJoin(\"%s\", \"%s\")\n", folder, filename); + + // If the path is not absolute, copy the folder and the + // path separator into the destination string. + if (filename[0] != '\\' && filename[0] != '\\' + && filename[1] != ':') { + while (len < PATH_MAX - 1 && *folder != 0) { + dst[len++] = *folder++; + } + /* The path separator '/' is standard under Linux and Mac OS X. + It also works on MS-DOS and Windows API, notwithstanding + certain Microsoft command-line utilities. */ + dst[len++] = '/'; + while (len < PATH_MAX - 1 && *filename != 0) { + dst[len++] = *filename++; + } + } + + if (len < PATH_MAX - 1) { + dst[len] = 0; + //allegro_message("equals \"%s\"\n", dst); + return 1; + } + //allegro_message("failed\n"); + return 0; +} + +/** + * Determines whether a file exists and is readable. + * @param path the path of a file + * @return nonzero iff readable + */ +static int fexists(const char *path) { + struct stat st; + if (!hasFAT) { + return 0; + } + int rval = stat(path, &st); + //allegro_message("stat(\"%s\") returned %d\n", path, rval); + return rval == 0; +} + +int ljpathInit(const char *argv0) { +#ifdef ARM9 + hasFAT = fatInitDefault(); + if (hasFAT) { + if (makeFolder("/data") < 0 || makeFolder("/data/lockjaw") < 0) { + hasFAT = 0; + } + } + strcpy(readOnlyFolder, "/data/lockjaw"); + strcpy(readWriteFolder, "/data/lockjaw"); + strcpy(skinFolder, "/data/lockjaw"); + return hasFAT; +#else + char path[PATH_MAX]; + int installed; + + //allegro_message("argv[0] = \"%s\"", argv0); + ljpathSetFolder(readOnlyFolder, argv0); + strcpy(readWriteFolder, "."); + strcpy(skinFolder, "."); + + // determine whether "installed.ini" (an empty text file) + // exists + installed = ljpathJoin(path, readOnlyFolder, "installed.ini") >= 0 + && fexists(path); + if (installed) { + ljpathSetRWFolder(); + } + return installed; +#endif +} + +/** + * Finds a file name for reading. + * @param dst buffer for path, of size PATH_MAX + * @param src name of file + * @return 0 if not found, nonzero if found + */ +int ljpathFind_r(char *restrict dst, const char *restrict filename) { + if (!hasFAT) { + return 0; + } + if (ljpathJoin(dst, readWriteFolder, filename) >= 0 + && fexists(dst)) { + return 1; + } + if (ljpathJoin(dst, skinFolder, filename) >= 0 + && fexists(dst)) { + return 1; + } + if (ljpathJoin(dst, readOnlyFolder, filename) >= 0 + && fexists(dst)) { + return 1; + } + return 0; +} + +int ljpathFind_w(char *restrict dst, const char *restrict filename) { + return ljpathJoin(dst, readWriteFolder, filename); +} + +FILE *ljfopen(const char *restrict filename, const char *restrict mode) { + char path[PATH_MAX]; + FILE *fp; + + if (!hasFAT) { + return NULL; + } + + //allegro_message("ljfopen(\"%s\", \"%s\")\n", filename, mode); + if (mode[0] == 'r') { + + // For read-only files, search all three paths. + if (ljpathFind_r(path, filename) >= 0 + && (fp = fopen(path, mode)) != 0) { + //allegro_message("fopen(\"%s\", \"%s\") for reading\n", path, mode); + return fp; + } + } else { + + // For read/write files, check only the read/write path. + if (ljpathFind_w(path, filename) >= 0 + && (fp = fopen(path, mode)) != 0) { + //allegro_message("fopen(\"%s\", \"%s\") for writing\n", path, mode); + return fp; + } + } + return NULL; +} + diff -r 000000000000 -r c84446dfb3f5 src/ljpath.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljpath.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,74 @@ +/* +ljpath - functions to support an application that can be +installed to either a read-write folder ("portable" config) +or to a read-only folder ("installed" config) + +Copyright 2008 Damian Yerrick + +Insert zlib license here. + +*/ + +#ifndef LJPATH_H +#define LJPATH_H + +#ifdef __cplusplus +#include +using std::FILE; +extern "C" +{ +#define DISTINCT +#else +#include +#define DISTINCT restrict +#endif + +/** + * Sets up the paths used by ljfopen(). + * Determines whether the program is marked as "installed", by + * the presence of a file called installed.ini in the folder + * containing the executable file. If so, uses a folder in the + * user's home directory instead of the current directory for + * writable files. + * @param argv0 the executable file's path + * @return nonzero for installed; zero for portable + */ +int ljpathInit(const char *argv0); + +/** + * Sets the skin folder to the folder containing a file. For instance, + * in a skinnable falling block game, this would be the folder holding + * the .skin file that describes the path to each graphic used for the + * game display. + * @param filename the name of the file + */ +void ljpathSetSkinFolder(const char *filename); + +/** + * Searches for a file in read-write, skin, and read-only folders + * @param dst pointer to a PATH_MAX-byte buffer to hold the path + * @param filename the name of the file that will be searched for + * @return nonzero if the file was found; 0 if not found + */ +int ljpathFind_r(char *DISTINCT dst, const char *DISTINCT filename); + +int ljpathFind_w(char *DISTINCT dst, const char *DISTINCT filename); + +/** + * Searches for a file and opens it. After it is opened, the + * caller may use stdio.h operations on it and must close it. + * Files being read are searched for using ljpathFind_r; others are + * searched for using ljpathFind_r + * @param filename the name of the file that will be searched for + * @param mode the stdio mode (r, w, a, rb, wb, ab) + * @return a magic cookie suitable for passing to stdio.h if the file + * was opened; 0 if not opened + */ +FILE *ljfopen(const char *DISTINCT filename, const char *DISTINCT mode); + + +#ifdef __cplusplus +} +#endif + +#endif diff -r 000000000000 -r c84446dfb3f5 src/ljpc.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljpc.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,2156 @@ +/* PC frontend for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006-2008 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#include "ljpc.h" +#include "ljreplay.h" +#include +#include +#include "scenario.h" +#include "ljpath.h" + +#if 1 + #define LJ_VERSION "0.46a ("__DATE__")" +#else + #define LJ_VERSION "WIP ("__DATE__")" +#endif + +#define USE_PICS_FOLDER 0 +#define MAX_PLAYERS 2 +#define LJ_TICK_RATE 3600 // frames per minute + +int bgColor, fgColor = 0, hiliteColor, pfBgColor = 0, pfFgColor; +static volatile int curTime = 0; +volatile char redrawWholeScreen = 1; +static volatile int wantPause = 0; +const DATAFILE *dat = NULL, *sound_dat = NULL; +const FONT *aver32 = NULL, *aver16 = NULL; +char withSound = 0; +char autoPause; +int refreshRate = 0; +char demoFilename[512]; + +int withPics = -1; + +char skinName[PATH_MAX] = "default.skin"; +char ljblocksSRSName[PATH_MAX]; +char ljblocksSegaName[PATH_MAX]; +char ljconnSRSName[PATH_MAX]; +char ljconnSegaName[PATH_MAX]; +char ljbgName[PATH_MAX]; +char menubgName[PATH_MAX]; +char bgmName[PATH_MAX]; +char bgmRhythmName[PATH_MAX]; +unsigned long int bgmLoopPoint = 0; +int bgmReadyGo = 0; +int bgmVolume = 128; +int skinW = 800; +int skinH = 600; + + + + +static void incCurTime(void) { + ++curTime; +} END_OF_FUNCTION(incCurTime); + +static void amnesia(void) { + redrawWholeScreen = 1; +} END_OF_FUNCTION(amnesia); + +static void requestPause(void) { + wantPause |= autoPause; +} + + +int getTime(void) { + return curTime; +} + +void yieldCPU(void) { + rest(5); +} + +void ljBeginDraw(LJView *v, int sync) { + vsync(); + if (redrawWholeScreen) { + redrawWholeScreen = 0; + playRedrawScreen(v); + } + + BITMAP *sc = v->plat->skin->fullback; + + // Draw shape + blit(v->plat->skin->bg, sc, 0, 0, 0, 0, 48, 16); + if (v->control->replayDst) { + + // circle: record + circlefill(sc, 8, 8, 7, fgColor); + textout_ex(sc, aver16, "Rec", 18, 2, fgColor, -1); + } else if (v->control->replaySrc) { + + // triangle: play + for (int wid = 0; wid < 7; ++wid) { + hline(sc, 2, 2 + wid, wid * 2 + 3, fgColor); + hline(sc, 2, 14 - wid, wid * 2 + 3, fgColor); + } + textout_ex(sc, aver16, "Play", 18, 2, fgColor, -1); + } else { + + // square: stop + rectfill(sc, 2, 2, 14, 14, fgColor); + } + blit(sc, screen, 0, 0, 0, 0, 48, 16); +} + +void ljEndDraw(LJView *v) { + +} + +static void startRecording(LJView *v) { + withPics = -1; + + // close existing playback + if (v->control->replaySrc) { + replayClose(v->control->replaySrc); + v->control->replaySrc = NULL; + } + + // toggle recording + if (v->control->replayDst) { + replayClose(v->control->replayDst); + v->control->replayDst = NULL; + } else { + char path[PATH_MAX]; + + ljpathFind_w(path, "demo.ljm"); + v->control->replayDst = newReplay(path, v->field); +#if USE_PICS_FOLDER + withPics = 0; +#endif + } +} + +static void startPlaying(LJView *v) { + withPics = -1; + + // close existing recording + if (v->control->replayDst) { + replayClose(v->control->replayDst); + v->control->replayDst = NULL; + } + + // toggle playback + if (v->control->replaySrc) { + replayClose(v->control->replaySrc); + v->control->replaySrc = NULL; + } else { + char path[PATH_MAX]; + + if (ljpathFind_r(path, "demo.ljm")) { + v->control->replaySrc = openReplay(path, v->field); + } + v->nLockTimes = 0; +#if USE_PICS_FOLDER + withPics = 0; +#endif + } + v->backDirty = ~0; + v->field->reloaded = 1; +} + +int ljHandleConsoleButtons(LJView *v) { + int canceled = 0; + + while (keypressed()) { + int scancode; + int codepoint = ureadkey(&scancode); + + if (scancode == KEY_ESC) { + wantPause = 1; + } else if (scancode == KEY_PRTSCR) { + saveScreen(-1); + save_bitmap("ljbackbuf.bmp", v->plat->skin->fullback, NULL); + } else if (codepoint == '[') { + v->plat->wantRecord = 1; + } else if (codepoint == ']') { + v->plat->wantRecord = 0; + startPlaying(v); + } + } + if (v->plat->wantRecord) { + v->plat->wantRecord = 0; + startRecording(v); + } + if (wantPause) { + canceled = pauseGame(v->plat); + wantPause = 0; + } + + if (wantsClose) { + canceled = 1; + } + + return canceled; +} + + +static void drawBlock(const LJPCView *const v, int x, int y, int blk) { + + if (x >= 0 && x < LJ_PF_WID && y >= 0 && y < LJ_PF_VIS_HT) { + const int w = v->skin->blkW; + const int h = v->skin->blkH; + const int dstX = w * x; + const int dstY = h * (LJ_PF_VIS_HT - 1 - y); + + if (v->skin->transparentPF && (blk == 0 || blk == 0x100)) { + + // Copy background + const unsigned int srcX = dstX + v->baseX; + const unsigned int srcY = dstY + v->skin->baseY + - LJ_PF_VIS_HT * h - v->skin->pfElev; + blit(v->skin->bg, v->back, srcX, srcY, dstX, dstY, w, h); + } else if (blk && v->skin->connBlocks) { + + // Copy connected block + const unsigned int srcX = ((blk >> 0) & 15) * w; + const unsigned int srcY = ((blk >> 4) & 15) * h; + blit(v->skin->connBlocks, v->back, srcX, srcY, dstX, dstY, w, h); + } else { + + // Copy lone block + const unsigned int srcX = ((blk >> 4) & 7) * w; + const unsigned int srcY = ((blk >> 7) & 1) * h; + blit(v->skin->blocks, v->back, srcX, srcY, dstX, dstY, w, h); + } + + // 0x100: hotline + if (blk & 0x100) { + hline(v->back, dstX, dstY + h / 2 - 1, dstX + w - 1, pfFgColor); + hline(v->back, dstX, dstY + h / 2 , dstX + w - 1, pfFgColor); + hline(v->back, dstX, dstY + h / 2 + 1, dstX + w - 1, pfBgColor); + } + } +} + +/** + * Draws multiple rows of the playfield + * @param p the playfield + * @param rows the rows to be updated (0x00001 on bottom, 0x80000 on top) + */ +void updField(const LJView *const v, LJBits rows) { + const LJField *const p = v->field; + + acquire_bitmap(v->plat->back); + for (int y = 0; + y < LJ_PF_VIS_HT && rows != 0; + ++y, rows >>= 1) { + int blankTile = 0; + + // hotline: draw 0x100 as the background + if (hotlineRows[y] && v->field->scoreStyle == LJSCORE_HOTLINE) { + blankTile = 0x100; + } + // during line clear delay, draw bars to show which lines were cleared + if (p->state == LJS_LINES_FALLING && p->stateTime > 0 + && ((1 << y) & p->tempRows)) { + blankTile = 0x100; + } + if (rows & 1) { + for (int x = p->leftWall; x < p->rightWall; x++) { + int b = v->hidePF ? 0 : p->b[y][x]; + drawBlock(v->plat, x, y, b ? b : blankTile); + } + } + } + release_bitmap(v->plat->back); +} + + +#define SHADOW_BLOCK 0x00 + +/** + * Draws a tetromino whose lower left corner of the bounding box is at (x, y) + * @param b the bitmap to draw to + * @param piece the piece to be drawn + * @param x distance from to left side of 4x4 box + * @param y distance from top of bitmap to bottom of 4x4 box + * @param the rotation state (0: U; 1: R; 2: D; 3: L; 4: Initial position) + * @param w width of each block + * @param h height of each block + * @param color Drawing style + * color == 0: draw shadow + * color == 0x10 through 0x70: draw in that color + * color == 0x80: draw as garbage + * color == -255 through -1: draw with 255 through 1 percent lighting + */ +LJBits drawPiece(LJView *const v, BITMAP *const b, + int piece, int x, int y, int theta, + int color, int w, int h) { + // Don't try to draw the -1 that's the sentinel for no hold piece + if (piece < 0) + return 0; + + LJBits rows = 0; + LJBlkSpec blocks[4]; + const int srcW = v->plat->skin->blkW; + const int srcH = v->plat->skin->blkH; + BITMAP *singleBlocks = v->plat->skin->blocks; + int singleSeries = (color == 0 + && singleBlocks->h >= 6 * srcH) + ? 4 : 2; + + // color: 0 for shadow, >0 for piece + BITMAP *connBlocks = (color != SHADOW_BLOCK) + ? v->plat->skin->connBlocks : NULL; + + BITMAP *transTemp = color < 0 + || (color == SHADOW_BLOCK && v->hideShadow < LJSHADOW_COLORED) + ? create_bitmap(w, h) : 0; + + if (transTemp) { + set_trans_blender(0, 0, 0, v->hideShadow ? 128 : 64); + } + + expandPieceToBlocks(blocks, v->field, piece, 0, 0, theta); + acquire_bitmap(b); + for (int blk = 0; blk < 4; ++blk) { + int blkValue = blocks[blk].conn; + + // If blkValue == 0 then the block is not part of the piece, + // such as if it's a domino or tromino or (once pentominoes + // are added) a tetromino. + if (blkValue) { + int blkX = blocks[blk].x; + int blkY = blocks[blk].y; + const int dstX = x + w * blkX; + const int dstY = y + h * (-1 - blkY); + + if (color > 0) { + blkValue = color | (blkValue & CONNECT_MASK); + } else if (color == 0 && v->hideShadow == LJSHADOW_COLORLESS) { + blkValue = 0; + } + + if (connBlocks) { + const unsigned int srcX = ((blkValue >> 0) & 15) * srcW; + const unsigned int srcY = ((blkValue >> 4) & 15) * srcH; + + if (transTemp) { + stretch_blit(connBlocks, transTemp, + srcX, srcY, srcW, srcH, + 0, 0, w, h); + if (color < 0) { + draw_lit_sprite(b, transTemp, dstX, dstY, color + 256); + } else { + draw_trans_sprite(b, transTemp, dstX, dstY); + } + } else if (srcW != w || srcH != h) { + stretch_blit(connBlocks, b, + srcX, srcY, srcW, srcH, + dstX, dstY, w, h); + if (dstY > 600) { + allegro_message("dstY = %d\n", dstY); + } + } else { + blit(connBlocks, b, + srcX, srcY, + dstX, dstY, w, h); + } + } else { + const unsigned int srcX = ((blkValue >> 4) & 7) * srcW; + const unsigned int srcY = (((blkValue >> 7) & 1) + singleSeries) * srcH; + + if (transTemp) { + stretch_blit(singleBlocks, transTemp, + srcX, srcY, srcW, srcH, + 0, 0, w, h); + if (color < 0) { + draw_lit_sprite(b, transTemp, dstX, dstY, color + 256); + } else { + draw_trans_sprite(b, transTemp, dstX, dstY); + } + } else { + stretch_blit(singleBlocks, b, + srcX, srcY, srcW, srcH, + dstX, dstY, w, h); + } + } + rows |= 1 << blkY; + } + } + release_bitmap(b); + if (transTemp) { + solid_mode(); + destroy_bitmap(transTemp); + } + + return rows; +} + +void drawPieceInPF(LJView *v, int dstX, LJFixed dstY, int theta, int color) { + LJBits bits = 0; + BITMAP *b = v->plat->back; + const LJField *const p = v->field; + int y = ljfixfloor(dstY); + const int w = v->plat->skin->blkW; + const int h = v->plat->skin->blkH; + int drawnY = fixmul(h, dstY); + + bits = drawPiece(v, b, p->curPiece[0], + w * dstX, h * LJ_PF_VIS_HT - drawnY, + theta, + color, w, h); + bits = (y >= 0) ? bits << y : bits >> -y; + bits &= (1 << LJ_PF_VIS_HT) - 1; + if (dstY & 0xFFFF) { + bits |= bits << 1; + } + + v->backDirty |= bits; + v->frontDirty |= bits; +} + + +void drawFallingPiece(LJView *v) { + const LJField *const p = v->field; + int piece = p->curPiece[0]; + int y = v->smoothGravity + ? p->y + : ljitofix(ljfixfloor(p->y)); + const int color = (p->state == LJS_LANDED) + ? -128 - ((p->stateTime + 1) * 128 / (p->speed.lockDelay + 1)) + : pieceColors[piece]; + + // Draw trails + if (v->showTrails) { + + // trail going up + while (v->trailY - y < ljitofix(-1)) { + v->trailY += ljitofix(1); + drawPieceInPF(v, p->x, v->trailY, p->theta, color); + } + + // trail going down + while (v->trailY - y > ljitofix(1)) { + v->trailY -= ljitofix(1); + drawPieceInPF(v, p->x, v->trailY, p->theta, color); + } + } + drawPieceInPF(v, p->x, y, p->theta, color); + v->trailY = y; +} + +void drawShadow(LJView *v) { + const LJField *const p = v->field; + int y = ljitofix(p->hardDropY); + const int color = SHADOW_BLOCK; + drawPieceInPF(v, p->x, y, p->theta, color); +} + +void drawNextPieces(LJView *v) { + int baseX = v->plat->baseX; + int baseY = v->plat->skin->baseY - v->plat->skin->pfElev; + int blkW = v->plat->skin->blkW; + int blkH = v->plat->skin->blkH; + int holdPieceColor = v->field->alreadyHeld + ? 0x80 + : pieceColors[v->field->holdPiece]; + int ceil = v->field->ceiling; + + BITMAP *sc = v->plat->skin->fullback; + + if (v->frontDirty & LJ_DIRTY_NEXT) { + int holdW = blkW * 2 / 3; + int holdH = blkH * 2 / 3; + int holdX = baseX + blkW; + int holdY = baseY - (ceil - 1) * blkH - 4 * holdH; + + // Move the hold piece within the screen + if (holdY < 0) { + holdY = 0; + } + + // Draw hold piece + blit(v->plat->skin->bg, sc, + holdX, holdY, + holdX, holdY, + holdW * 4, holdH * 2); + drawPiece(v, sc, + v->field->holdPiece, holdX, holdY + 4 * holdH, 4, + holdPieceColor, holdW, holdH); + blit(sc, screen, + holdX, holdY, + holdX, holdY, + holdW * 4, holdH * 2); + } + // Draw next pieces + + switch (v->plat->skin->nextPos) { + case LJNEXT_RIGHT: + case LJNEXT_RIGHT_TAPER: + if (v->frontDirty & LJ_DIRTY_NEXT) { + int y = baseY - ceil * blkH; + int x = baseX + LJ_PF_WID * blkW; + int w = blkW; + int h = blkH; + for (int i = 1; i <= v->nextPieces; ++i) { + int piece = v->field->curPiece[i]; + + blit(v->plat->skin->bg, sc, x, y, x, y, w * 4, h * 2); + if (!v->hideNext) { + drawPiece(v, sc, + piece, x, y + 4 * h, 4, + pieceColors[piece], w, h); + } + blit(sc, screen, x, y, x, y, w * 4, h * 2); + y += 8 + h * 2; + if (v->plat->skin->nextPos == LJNEXT_RIGHT_TAPER) { + --h; + --w; + } + } + } + break; + + case LJNEXT_TOP: + if (v->frontDirty & LJ_DIRTY_NEXT) { + int y = baseY - (ceil + 2) * blkH - 8; + int x = baseX + 4 * blkW; + int blitX = x, blitY = y; + + blit(v->plat->skin->bg, sc, x, y, x, y, blkW * 8, blkH * 2); + if (!v->hideNext) { + if (v->nextPieces >= 1) { + int piece = v->field->curPiece[1]; + drawPiece(v, sc, + piece, x, y + 4 * blkH, 4, + pieceColors[piece], blkW, blkH); + } + if (v->nextPieces >= 2) { + int piece = v->field->curPiece[2]; + x += 4 * blkW; + drawPiece(v, sc, + piece, x, y + 3 * blkH, 4, + pieceColors[piece], blkW/ 2, blkH / 2); + } + if (v->nextPieces >= 3) { + int piece = v->field->curPiece[3]; + x += 2 * blkW; + drawPiece(v, sc, + piece, x, y + 3 * blkH, 4, + pieceColors[piece], blkW / 2, blkH / 2); + } + } + blit(sc, screen, blitX, blitY, blitX, blitY, blkW * 8, blkH * 2); + } + break; + } + + if (v->plat->nextAbove && !v->hideNext) { + int row = (v->field->hardDropY + 4); + int x = (1 + v->field->x) * blkW; + for (int i = 1; + i <= v->plat->nextAbove + && row <= v->field->ceiling - 2; + ++i) { + int y = (LJ_PF_VIS_HT - row) * blkH; + int piece = v->field->curPiece[i]; + + drawPiece(v, v->plat->back, + piece, x, y, 4, + pieceColors[piece], blkW / 2, blkH / 2); + v->backDirty |= 3 << row; + row += 2; + } + } + v->frontDirty &= ~LJ_DIRTY_NEXT; +} + +void drawScore(LJView *v) { + int gameTime = v->field->gameTime; + int seconds = gameTime / 60; + int minutes = seconds / 60; + int baseX = v->plat->baseX; + int tpm = -1; + int spawnLeft = v->plat->skin->blkW * LJ_SPAWN_X + baseX; + int pfRight = v->plat->skin->blkW * LJ_PF_WID + baseX; + BITMAP *sc = v->plat->skin->fullback; + + if (withPics >= 0) { + if (v->field->nPieces != withPics) { + saveScreen(v->field->nPieces); + } + withPics = v->field->nPieces; + } + + if (v->nLockTimes >= 2 ) { + int time = v->lockTime[0] - v->lockTime[v->nLockTimes - 1]; + if (time > 0) { + tpm = 3600 * (v->nLockTimes - 1) / time; + } + } + + if (v->frontDirty & LJ_DIRTY_SCORE) { + switch (v->plat->skin->nextPos) { + case LJNEXT_TOP: + blit(v->plat->skin->bg, sc, + pfRight, 72, + pfRight, 72, + 112, 136); + + textout_ex(sc, aver32, "Lines:", pfRight, 72, fgColor, -1); + textprintf_right_ex(sc, aver32, pfRight + 104, 102, fgColor, -1, + "%d", v->field->lines); + textout_ex(sc, aver32, "Score:", pfRight, 142, fgColor, -1); + textprintf_right_ex(sc, aver32, pfRight + 104, 172, fgColor, -1, + "%d", v->field->score); + textout_ex(sc, aver32, "Level:", pfRight, 212, fgColor, -1); + textprintf_right_ex(sc, aver32, pfRight + 104, 242, fgColor, -1, + "%d", v->field->speedState.level); + blit(sc, screen, + pfRight, 72, + pfRight, 72, + 112, 136); + break; + + default: + blit(v->plat->skin->bg, sc, spawnLeft, 12, spawnLeft, 12, 288, 30); + blit(v->plat->skin->bg, sc, + spawnLeft, 42, spawnLeft, 42, + pfRight - spawnLeft, 30); + textprintf_right_ex(sc, aver32, spawnLeft + 288, 12, fgColor, -1, + "Lv. %d", v->field->speedState.level); + textprintf_ex(sc, aver32, spawnLeft, 12, fgColor, -1, + "Lines: %d", v->field->lines); + textprintf_ex(sc, aver32, spawnLeft, 42, fgColor, -1, + "Score: %d", v->field->score); + blit(sc, screen, spawnLeft, 12, spawnLeft, 12, 288, 60); + break; + } + } + + /* If speed is defined, and there is room to draw it, draw it. */ + if (tpm > 0 && v->nextPieces <= 3) { + blit(v->plat->skin->bg, sc, + pfRight, 282, + pfRight, 282, + 104, 60); + textout_ex(sc, aver32, "Speed:", pfRight, 282, fgColor, -1); + textprintf_right_ex(sc, aver32, pfRight + 104, 312, fgColor, -1, + "%d", tpm); + blit(sc, screen, + pfRight, 282, + pfRight, 282, + 104, 60); + } + + if (v->frontDirty & LJ_DIRTY_NEXT) { + + // Erase gimmick + blit(v->plat->skin->bg, sc, + baseX, v->plat->skin->baseY + 8, + baseX, v->plat->skin->baseY + 8, + v->plat->skin->blkW * LJ_PF_WID, 30); + // Draw gimmick + if (v->field->gimmick >= 0 && v->field->gimmick < LJGM_N_GIMMICKS) { + const char *gimmickName = ljGetFourCCName(gimmickNames[v->field->gimmick]); + textout_centre_ex(sc, aver32, + gimmickName ? gimmickName : "Bad gimmick ID", + baseX + (LJ_PF_WID / 2) * v->plat->skin->blkW, + v->plat->skin->baseY + 8, + fgColor, -1); + blit(sc, screen, + baseX, v->plat->skin->baseY + 8, + baseX, v->plat->skin->baseY + 8, + v->plat->skin->blkW * LJ_PF_WID, 30); + } + } + drawNextPieces(v); + + blit(v->plat->skin->bg, sc, pfRight, 42, pfRight, 42, 96, 30); +#if 0 + // Use this for DEBUG inspection into a variable + textprintf_right_ex(sc, aver16, pfRight + 96, 8, fgColor, -1, + "%d", v->field->bpmCounter); +#endif + textprintf_right_ex(sc, aver32, pfRight + 96, 42, fgColor, -1, + "%d:%02d", minutes, seconds - 60 * minutes); + blit(sc, screen, pfRight, 42, pfRight, 42, 96, 30); + +} + +void blitField(LJView *v) { + int blkH = v->plat->skin->blkH; + int rowY = v->plat->skin->baseY + - v->plat->skin->pfElev + - blkH * v->field->ceiling; + int x = v->plat->skin->blkW * v->field->leftWall; + int w = v->plat->skin->blkW * (v->field->rightWall - v->field->leftWall); + + // Copy each dirty row + for (int y = v->field->ceiling - 1; y >= 0; --y) { + if (v->frontDirty & (1 << y)) { + int top = (LJ_PF_VIS_HT - y - 1) * blkH; + int ht = 0; + + // Find the height of the contiguous rows to blit + do { + ht += blkH; + --y; + } while ((y >= 0) + && (v->frontDirty & (1 << y))); + blit(v->plat->back, screen, + x, top, + x + v->plat->baseX, rowY, + w, ht); + rowY += ht; + } + rowY += blkH; + } + + v->frontDirty &= (~0) << LJ_PF_HT; +} + +void saveScreen(int n) { + BITMAP *b = create_bitmap(SCREEN_W, SCREEN_H); + if (b) { + PALETTE pal; + + get_palette(pal); + blit(screen, b, 0, 0, 0, 0, SCREEN_W, SCREEN_H); + if (n < 0) { + save_bitmap("ljsnap.bmp", b, pal); + } else { + char filename[64]; + sprintf(filename, "pics/lj%05d.bmp", n); + save_bitmap(filename, b, pal); + } + destroy_bitmap(b); + } +} + +#define PRESETS_PER_COL 6 +#define N_NONPRESETS 2 +#define PRESETS_TOP 140 +#define PRESET_COL_WIDTH 250 +#define PRESET_ROW_HT 40 + +static const char *const nonPresetNames[N_NONPRESETS] = { + "Full Custom", "Back" +}; + +static void getPresetDrawRow(unsigned int preset, int hilite) { + unsigned int ht = text_height(aver32); + const char *txt = preset < nLoadedPresets + ? loadedPresets[preset].name + : nonPresetNames[preset - nLoadedPresets]; + if (!txt) { + txt = "Bad"; + } + unsigned int wid = text_length(aver32, txt); + + int buttonCol = preset / PRESETS_PER_COL; + int buttonX = 20 + buttonCol * PRESET_COL_WIDTH; + int buttonY = PRESETS_TOP + + PRESET_ROW_HT * (preset - buttonCol * PRESETS_PER_COL); + unsigned int buttonWidth = wid + 16; + + rectfill(screen, + buttonX, buttonY, + buttonX + buttonWidth - 1, buttonY + PRESET_ROW_HT - 1, + hilite ? hiliteColor : bgColor); + if (hilite) { + rect(screen, + buttonX, buttonY, + buttonX + buttonWidth - 1, buttonY + PRESET_ROW_HT - 1, + fgColor); + } + textout_ex(screen, aver32, txt, + 8 + buttonX, + 20 + buttonY - ht / 2, + fgColor, -1); +} + +int getPreset(int lastPreset) { + LJBits lastKeys = ~0; + redrawWholeScreen = 1; + + clear_keybuf(); + if (lastPreset < 0) { + lastPreset += nLoadedPresets + N_NONPRESETS; + } + + for(int done = 0; done == 0; ) { + if (redrawWholeScreen) { + redrawWholeScreen = 0; + clear_to_color(screen, bgColor); + textout_ex(screen, aver32, "LOCKJAW > Play", 16, 32, fgColor, -1); + textout_ex(screen, aver32, "Select a scenario:", 16, 90, fgColor, -1); + + for (int preset = 0; + preset < nLoadedPresets + N_NONPRESETS; + ++preset) { + getPresetDrawRow(preset, preset == lastPreset); + } + textout_ex(screen, aver16, "Arrows: move; Rotate Right: start", + 16, PRESETS_TOP + 40 * (PRESETS_PER_COL + 1), fgColor, -1); + textout_ex(screen, aver16, "Coming soon: an editor for these!", + 16, PRESETS_TOP + 40 * (PRESETS_PER_COL + 1) + 20, fgColor, -1); + } + while (keypressed()) { + int scancode; + ureadkey(&scancode); + if (scancode == KEY_PRTSCR) { + saveScreen(-1); + } + } + + int preset = lastPreset; + LJBits keys = menuReadPad(); + LJBits newKeys = keys & ~lastKeys; + + if ((newKeys & VKEY_ROTL) || wantsClose) { + preset = nLoadedPresets + N_NONPRESETS - 1; + ezPlaySample("rotate_wav", 128); + } + if ((newKeys & VKEY_ROTR) || wantsClose) { + done = 1; + ezPlaySample("line_wav", 128); + } + + if (newKeys & VKEY_UP) { + if (preset <= 0) { + preset = nLoadedPresets + N_NONPRESETS - 1; + } else { + --preset; + } + ezPlaySample("shift_wav", 128); + } + if (newKeys & VKEY_DOWN) { + ++preset; + if (preset >= nLoadedPresets + N_NONPRESETS) { + preset = 0; + } + ezPlaySample("shift_wav", 128); + } + + if (newKeys & VKEY_LEFT) { + if (preset < PRESETS_PER_COL) { + preset += (((nLoadedPresets + N_NONPRESETS) + / PRESETS_PER_COL) + ) + * PRESETS_PER_COL; + if (preset >= nLoadedPresets + N_NONPRESETS) { + preset -= PRESETS_PER_COL; + } + } else { + preset -= PRESETS_PER_COL; + } + ezPlaySample("shift_wav", 128); + } + if (newKeys & VKEY_RIGHT) { + preset += PRESETS_PER_COL; + if (preset >= nLoadedPresets + N_NONPRESETS) { + preset %= PRESETS_PER_COL; + } + ezPlaySample("shift_wav", 128); + } + + if (preset != lastPreset) { + vsync(); + getPresetDrawRow(lastPreset, 0); + getPresetDrawRow(preset, 1); + lastPreset = preset; + } else { + rest(30); + } + lastKeys = keys; + } + + // Wrap the nonpresets into the negative numbers. + if (lastPreset >= nLoadedPresets) { + lastPreset -= (nLoadedPresets + N_NONPRESETS); + } + + return lastPreset; +} + +/** + * Pauses the game, returning nonzero if the player wants to quit. + */ +int pauseGame(LJPCView *v) { + int escCount = 0; + int quit = 0; + int escHold = curTime; + redrawWholeScreen = 1; + + LJMusic_pause(v->skin->bgm, 1); + while (escCount < 2 && !quit) { + LJMusic_poll(v->skin->bgm); + if (redrawWholeScreen) { + redrawWholeScreen = 0; + clear_to_color(screen, pfBgColor); + textout_centre_ex(screen, aver32, "LOCKJAW: GAME PAUSED", + SCREEN_W / 2, 200, pfFgColor, -1); + textout_centre_ex(screen, aver32, "Press Esc to continue", + SCREEN_W / 2, 250, pfFgColor, -1); + textout_centre_ex(screen, aver32, "or hold Esc to quit", + SCREEN_W / 2, 300, pfFgColor, -1); + } + + if (key[KEY_ESC]) { + if (escHold == 0) { + escHold = curTime; + } + if (curTime - escHold >= 60) { + quit = 1; + } + } else { + if (escHold) { + ++escCount; + } + escHold = 0; + } + rest(30); + if (wantsClose) { + quit = 1; + } + + } + LJMusic_pause(v->skin->bgm, 0); + redrawWholeScreen = 1; + clear_keybuf(); + return quit; +} + + +void pcInit(LJView *v, struct LJPrefs *prefs) { + v->plat->b2bcd1 = 0; + v->plat->b2bcd2 = 0; + v->plat->baseX = v->plat->skin->baseX; + + // If the player has chosen to use more next pieces than the + // next piece position can handle, set the number of + // next pieces for correctness of debrief(). + if (v->nextPieces > 3 && v->plat->skin->nextPos == LJNEXT_TOP) { + v->nextPieces = 3; + } +} + +/** + * Redraws everything on the screen. + * Called when needs redraw. + */ +void playRedrawScreen(LJView *v) { + blit(v->plat->skin->bg, screen, + 0, 0, + 0, 0, + SCREEN_W, SCREEN_H); + v->frontDirty = ~0; +} + +#if 0 +void startingAniWantsSkip(LJView *v) { + LJInput unusedIn; + addKeysToInput(&unusedIn, readPad(), v->field, v->control); +} +#endif + +void playSampleForTetromino(int piece); + +void restPollingMusic(int nFrames, LJMusic *bgm) { + nFrames += curTime; + while (curTime - nFrames < 0) { + if (bgm) { + LJMusic_poll(bgm); + } + } +} + +extern int bgmReadyGo; +void startingAnimation(LJView *v) { + int readyGoX = v->plat->baseX + v->plat->skin->blkW * LJ_PF_WID / 2; + int readyGoY = v->plat->skin->baseY + - v->plat->skin->pfElev + - v->plat->skin->blkH + * v->field->ceiling * 3 / 5; + + clear_keybuf(); + v->backDirty = 0; + + playRedrawScreen(v); + blitField(v); + textout_centre_ex(screen, aver32, "Ready", + readyGoX, readyGoY, pfFgColor, -1); + + ezPlaySample("ready_wav", 128); + restPollingMusic(36, bgmReadyGo ? v->plat->skin->bgm : NULL); + v->frontDirty = ~0; + + if (!wantsClose) { + blitField(v); + textout_centre_ex(screen, aver32, "GO!", + readyGoX, readyGoY, pfFgColor, -1); + drawScore(v); + + ezPlaySample("go_wav", 128); + v->frontDirty = ~0; + restPollingMusic(12, bgmReadyGo ? v->plat->skin->bgm : NULL); + } + if (!wantsClose) { + playSampleForTetromino(v->field->curPiece[1]); + restPollingMusic(24, bgmReadyGo ? v->plat->skin->bgm : NULL); + } +} + + +static void gameOverAnimation(const LJPCView *const v, const LJField *p, int won) { + int ceiling = p->ceiling; + int left = v->baseX + p->leftWall * v->skin->blkW; + int right = v->baseX + p->rightWall * v->skin->blkW - 1; + + ezPlaySample("sectionup_wav", 0); + if (!won) { + ezPlaySample("gameover_wav", 256); + } else { + ezPlaySample("win_wav", 256); + } + + for (int t = ceiling + v->skin->blkH - 2; t >= 0; --t) { + + // FIXME: vsync doesn't work on Vista + vsync(); + for (int row = ceiling - 1; row >= 0; --row) { + int ysub = t - row; + + if (ysub >= 0 && ysub < v->skin->blkH) { + int y = v->skin->baseY - v->skin->pfElev + - row * v->skin->blkH - ysub - 1; + hline(screen, left, y, right, pfBgColor); + } + } + if (wantsClose) { + t = 0; + } + } +} + +#define MENU_COPR_NOTICE_LINES 4 +const char *const menuCoprNotice[MENU_COPR_NOTICE_LINES] = { + "Copr. 2006-2008 Damian Yerrick", + "Not sponsored or endorsed by The Tetris Company.", + "LOCKJAW comes with ABSOLUTELY NO WARRANTY. This is free software, and you are", + "welcome to redistribute it under certain conditions as described in GPL.txt." +}; + +static BITMAP *buildTitleScreen(void) { + BITMAP *back = create_system_bitmap(SCREEN_W, SCREEN_H); + if (!back) { + return NULL; + } + + // Gradient from (0, 0, 0) to (0, 0, 153) + for (int y = 0; y < 192; ++y) { + for (int x = -((y * 13) & 0x1F); + x < SCREEN_W; + x += 32) { + int colValue = y + ((rand() & 0x7000) >> 12); + int c = makecol(0, 0, 153 * colValue / 192); + hline(back, x, y, x + 31, c); + } + } + + // Gradient from (102, 51, 0) to (204, 102, 0) + for (int y = 192; y < 384; ++y) { + for (int x = -((y * 13) & 0x1F); + x < SCREEN_W; + x += 32) { + int colValue = y + ((rand() & 0x7800) >> 11); + int c = makecol(102 * colValue / 192, 51 * colValue / 192, 0); + hline(back, x, y, x + 31, c); + } + } + + // Gradient from (204, 102, 0) to (255, 128, 0) + for (int y = 384; y < SCREEN_H; ++y) { + for (int x = -((y * 13) & 0x1F); + x < SCREEN_W; + x += 32) { + int colValue = y - 400 + ((rand() & 0x7C00) >> 10); + if (colValue > 600 - 384) { + colValue = 600 - 384; + } + int c = makecol(204 + 50 * colValue / (600 - 384), + 102 + 25 * colValue / (600 - 384), + 0); + hline(back, x, y, x + 31, c); + } + } + + DATAFILE *obj = find_datafile_object(dat, "arttitle_bmp"); + BITMAP *logo = obj ? obj->dat : NULL; + obj = find_datafile_object(dat, "arttitle_pal"); + const RGB *pal = obj ? obj->dat : NULL; + + if (logo && pal) { + set_palette(pal); + draw_sprite(back, logo, + (SCREEN_W - logo->w) / 2, (384 - logo->h) / 2); + //unselect_palette(); + } + + textout_centre_ex(back, aver32, "Arrows: change; Enter: choose", + SCREEN_W / 2, 440, + 0, -1); + + textout_centre_ex(back, aver32, "LOCKJAW: The Reference "LJ_VERSION, + SCREEN_W / 2, SCREEN_H - 40, + 0, -1); + + return back; +} + +enum { + TITLE_EXIT = 0, + TITLE_PLAY, + TITLE_REPLAY, + TITLE_OPTIONS, + TITLE_SKIN, + TITLE_KEYS, + N_TITLE_ACTIONS +}; + +static const char *titleActions[N_TITLE_ACTIONS] = { + [TITLE_EXIT] = "Exit", + [TITLE_PLAY] = "Play", + [TITLE_REPLAY] = "Replay", + [TITLE_SKIN] = "Skin...", + [TITLE_OPTIONS] = "Options...", + [TITLE_KEYS] = "Game Keys..." +}; +/* + 0: Exit + 1: Play + 2 +*/ +int title(void) { + + // don't even draw if the player is trying to close the window + if (wantsClose) { + return 0; + } + + BITMAP *back = buildTitleScreen(); + LJBits lastKeys = ~0; + int redraw = 1; + int choice = 1; + + if (!back) { + alert("Not enough memory to display the title screen.", + "(If you don't even have RAM for a title screen,", + "then what do you have RAM for?)", + "Exit", 0, 13, 0); + return 0; + } + + redrawWholeScreen = 1; + + for(int done = 0; done == 0; ) { + if (redrawWholeScreen) { + redrawWholeScreen = 0; + blit(back, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H); + redraw = 1; + } + if (redraw) { + int dehilite = makecol(221, 153, 85); + int white = makecol(255, 255, 255); + redraw = 0; + vsync(); + blit(back, screen, 0, 400, 0, 400, SCREEN_W, 30); + textout_centre_ex(screen, aver32, + titleActions[choice], + SCREEN_W / 2, 400, white, -1); + textout_centre_ex(screen, aver32, + titleActions[choice == 0 ? N_TITLE_ACTIONS - 1 : choice - 1], + SCREEN_W / 2 - 160, 400, dehilite, -1); + textout_centre_ex(screen, aver32, + titleActions[choice == N_TITLE_ACTIONS - 1 ? 0 : choice + 1], + SCREEN_W / 2 + 160, 400, dehilite, -1); + } + + while (keypressed()) { + int scancode; + ureadkey(&scancode); + if (scancode == KEY_PRTSCR) { + saveScreen(-1); + } + } + + LJBits keys = menuReadPad(); + LJBits newKeys = keys & ~lastKeys; + + if (newKeys & VKEY_LEFT) { + --choice; + redraw = 1; + ezPlaySample("shift_wav", 128); + } + if (newKeys & VKEY_RIGHT) { + ++choice; + redraw = 1; + ezPlaySample("shift_wav", 128); + } + if (newKeys & VKEY_ROTL) { + choice = 0; + redraw = 1; + ezPlaySample("rotate_wav", 128); + } + if (newKeys & VKEY_ROTR) { + done = 1; + ezPlaySample("line_wav", 128); + } + if (choice < 0) { + choice += N_TITLE_ACTIONS; + } + if (choice >= N_TITLE_ACTIONS) { + choice -= N_TITLE_ACTIONS; + } + + lastKeys = keys; + + if (!redraw) { + rest(30); + } + if (wantsClose) { + done = 1; + choice = 0; + } + } + destroy_bitmap(back); + return choice; +} + + +void setupWindow(void) { + set_window_title("LOCKJAW"); + bgColor = makecol(255, 255, 255); + pfFgColor = bgColor; + hiliteColor = makecol(255, 255, 204); + refreshRate = get_refresh_rate(); +} + +void drawCoprNotice(void) { + clear_to_color(screen, pfBgColor); + for (int i = 0; i < MENU_COPR_NOTICE_LINES; ++i) { + textout_ex(screen, font, menuCoprNotice[i], + 16, 580 + 12 * (i - MENU_COPR_NOTICE_LINES), + pfFgColor, -1); + } +} + + +/** + * Destroys the back buffers for both playfields. + */ +static void destroyBackBuf(LJPCView *plat) { + for (int i = 0; i < MAX_PLAYERS; ++i) { + if (plat->back) { + destroy_bitmap(plat->back); + plat->back = NULL; + } + } + if (plat->skin->fullback) { + destroy_bitmap(plat->skin->fullback); + plat->skin->fullback = NULL; + } +} + +/** + * Creates the back buffers for both playfields. + */ +static int createBackBuf(LJView *v) { + v->plat->skin->fullback = create_system_bitmap(SCREEN_W, SCREEN_H); + if(!v->plat->skin->fullback) { + allegro_message("Could not create back buffer.\n"); + return 0; + } + + int blkW = v->plat->skin->blkW; + int blkH = v->plat->skin->blkH; + int y = v->plat->skin->baseY + - v->plat->skin->pfElev + - blkH * v->field->ceiling; + + for (int i = 0; i < MAX_PLAYERS; ++i) { + int x = v->plat->skin->baseX + + blkW * v[i].field->leftWall; + + v[i].plat->back = create_sub_bitmap(v->plat->skin->fullback, + x + SCREEN_W / 2 * i, + y, + v->plat->skin->blkW * LJ_PF_WID, + v->plat->skin->blkH * LJ_PF_VIS_HT); + if (!v[i].plat->back) { + destroyBackBuf(v->plat); + return 0; + } + } + return 1; +} + +/** + * Destroys all system bitmaps that a given view owns. + * Useful before changing screen mode. + */ +void destroySystemBitmaps(LJPCView *plat) { + destroyBackBuf(plat); + if (plat->skin->connBlocks) { + destroy_bitmap(plat->skin->connBlocks); + plat->skin->connBlocks = NULL; + } +} + +int openWindow(int windowed) +{ + int depth = desktop_color_depth(); + int card = windowed ? GFX_AUTODETECT_WINDOWED : GFX_AUTODETECT_FULLSCREEN; + + /* Reference implementation for Allegro is not compatible with + indexed color. */ + if (depth < 15) { + depth = 16; + } + + // Full screen procedure + set_color_depth(depth); + if (set_gfx_mode(card, skinW, skinH, 0, 0) == 0) { + setupWindow(); + return 0; + } + + // Windows can't tell 16 bit from 15 bit. If desktop color depth is reported as 16, try 15 too. + if (depth == 16) { + set_color_depth(15); + if (set_gfx_mode(card, skinW, skinH, 0, 0) == 0) { + setupWindow(); + return 0; + } + } + + return -1; +} + +BITMAP *loadConnections(const char *filename, int blkW, int blkH) { + BITMAP *src = load_bitmap(filename, NULL); + + if (!src) { + return NULL; + } + BITMAP *dst = create_system_bitmap(blkW*16, blkH*16); + if (!dst) { + destroy_bitmap(src); + return NULL; + } + acquire_bitmap(dst); + for (unsigned int col = 0; col < 16; ++col) { + unsigned int srcXBase = (col & 0x03) * blkW * 2; + unsigned int srcYBase = (col >> 2) * blkH * 2; + unsigned int dstYBase = col * blkH; + for (unsigned int conn = 0; conn < 16; ++conn) { + unsigned int dstXBase = conn * blkW; + unsigned int topSegY = (conn & CONNECT_U) ? blkH : 0; + unsigned int botSegY = (conn & CONNECT_D) ? blkH/2 : 3*blkH/2; + unsigned int leftSegX = (conn & CONNECT_L) ? blkW : 0; + unsigned int rightSegX = (conn & CONNECT_R) ? blkW/2 : 3*blkW/2; + blit(src, dst, + srcXBase + leftSegX, srcYBase + topSegY, + dstXBase + 0, dstYBase + 0, + blkW/2, blkH/2); + blit(src, dst, + srcXBase + rightSegX, srcYBase + topSegY, + dstXBase + blkW/2, dstYBase + 0, + blkW/2, blkH/2); + blit(src, dst, + srcXBase + leftSegX, srcYBase + botSegY, + dstXBase + 0, dstYBase + blkH/2, + blkW/2, blkH/2); + blit(src, dst, + srcXBase + rightSegX, srcYBase + botSegY, + dstXBase + blkW/2, dstYBase + blkH/2, + blkW/2, blkH/2); + } + } + release_bitmap(dst); + destroy_bitmap(src); + return dst; +} + +void closeWindow(void) { + set_gfx_mode(GFX_TEXT, 0, 0, 0, 0); +} + +static void mainCleanup(LJPCView *v) { + destroyBackBuf(v); + if (v->skin->blocks) { + destroy_bitmap(v->skin->blocks); + } + if (v->skin->connBlocks) { + destroy_bitmap(v->skin->connBlocks); + } + LJMusic_delete(v->skin->bgm); + if (withSound) { + remove_sound(); + } + closeWindow(); +} + +/** + * Resets all skin settings to their initial values + * so that skins can override them. + */ +void loadSkinDefaults(LJPCSkin *s) { + ustrzcpy(ljblocksSRSName, sizeof(ljblocksSRSName) - 1, + "ljblocks.bmp"); + ustrzcpy(ljblocksSegaName, sizeof(ljblocksSegaName) - 1, + "ljblocks-sega.bmp"); + ustrzcpy(ljconnSRSName, sizeof(ljconnSRSName) - 1, + "ljconn.bmp"); + ustrzcpy(ljconnSegaName, sizeof(ljconnSegaName) - 1, + "ljconn-sega.bmp"); + ustrzcpy(ljbgName, sizeof(ljbgName) - 1, + "ljbg.jpg"); + ustrzcpy(menubgName, sizeof(ljbgName) - 1, + "menubg.jpg"); + ustrzcpy(bgmName, sizeof(bgmName) - 1, + "bgm.s3m"); + ustrzcpy(bgmRhythmName, sizeof(bgmRhythmName) - 1, + "bgm-rhythm.s3m"); + bgmLoopPoint = 0; + bgmReadyGo = 0; + bgmVolume = 128; + bgColor = makecol(255, 255, 255); + fgColor = makecol(0, 0, 0); + hiliteColor = makecol(255, 255, 204); + pfBgColor = makecol(0, 0, 0); + pfFgColor = makecol(255, 255, 255); + s->blkW = 24; + s->blkH = 24; + s->transparentPF = 0; + s->shiftScale = 0; + s->baseX = 0; + s->nextPos = 0; +} + +/** + * Converts a single hexadecimal digit to its value. + * @param in USASCII/Unicode value of hex digit character + * @return value +*/ +int hexDigitValue(int in) { + if (in >= '0' && in <= '9') { + return in - '0'; + } else if (in >= 'A' && in <= 'F') { + return in - 'A' + 10; + } else if (in >= 'a' && in <= 'f') { + return in - 'a' + 10; + } else { + return -1; + } +} + +int translateComponent(const char **in, size_t nDigits) { + const char *here = *in; + int hi = hexDigitValue(*here++); + int lo = nDigits > 3 ? hexDigitValue(*here++) : hi; + *in = here; + if (hi >= 0 && lo >= 0) { + return hi * 16 + lo; + } else { + return -1; + } +} + +/** + * Interprets a hexadecimal color specifier. + * @param in hexadecimal color specifier, in #ABC or #AABBCC format + * @return Allegro device-dependent color, in makecol() format + */ +int translateColor(const char *in) { + size_t nDigits = 0; + + // Verify and skip # + if (*in != '#') { + return -1; + } + ++in; + + // Determine whether we have a 3-digit color or a 6-digit color + for (const char *here = in; + hexDigitValue(*here) >= 0; + ++here) { + ++nDigits; + } + if (nDigits != 3 && nDigits != 6) { + return -1; + } + + int red = translateComponent(&in, nDigits); + int green = translateComponent(&in, nDigits); + int blue = translateComponent(&in, nDigits); + if (red >= 0 && green >= 0 && blue >= 0) { + return makecol(red, green, blue); + } else { + return -1; + } +}; + +/** + * Loads skin parameters from the file. + * @param skinName Name of skin ini file + */ +int loadSkinFile(LJPCSkin *s, const char *skinName) { + + // Don't use ljfopen here because lj.ini specifies the absolute skin file + FILE *fp = fopen(skinName, "rt"); + char key[1024], var[1024], val[1024], input_buf[1024]; + + key[0] = 0; + var[0] = 0; + val[0] = 0; + loadSkinDefaults(s); + + if (!fp) return 0; + while(1) { + int rval; + + if(!fgets (input_buf, sizeof(input_buf), fp)) + break; + rval = parse_ini_line(input_buf, key, var, val); + + if(!ustrcmp ("ljblocksSRS", var)) { + ustrzcpy(ljblocksSRSName, sizeof(ljblocksSRSName) - 1, val); + } + else if(!ustrcmp ("ljblocksSega", var)) { + ustrzcpy(ljblocksSegaName, sizeof(ljblocksSegaName) - 1, val); + } + else if(!ustrcmp ("ljconnSRS", var)) { + ustrzcpy(ljconnSRSName, sizeof(ljconnSRSName) - 1, val); + } + else if(!ustrcmp ("ljconnSega", var)) { + ustrzcpy(ljconnSegaName, sizeof(ljconnSegaName) - 1, val); + } + else if(!ustrcmp ("bgm", var)) { + ustrzcpy(bgmName, sizeof(bgmName) - 1, val); + } + else if(!ustrcmp ("bgmRhythm", var)) { + ustrzcpy(bgmRhythmName, sizeof(bgmRhythmName) - 1, val); + } + else if(!ustrcmp ("ljbg", var)) { + ustrzcpy(ljbgName, sizeof(ljbgName) - 1, val); + } + else if(!ustrcmp ("bgmLoopPoint", var)) { + unsigned long int c = strtoul(val, NULL, 0); + if (c >= 0) { + bgmLoopPoint = c; + } + } + else if(!ustrcmp ("bgmVolume", var)) { + unsigned long int c = strtol(val, NULL, 0); + if (c >= 0) { + bgmVolume = c; + } + } + else if(!ustrcmp ("bgmReadyGo", var)) { + unsigned long int c = strtoul(val, NULL, 0); + if (c >= 0) { + bgmReadyGo = c; + } + } + else if(!ustrcmp ("pfbgcolor", var)) { + int c = translateColor(val); + if (c >= 0) { + pfBgColor = c; + } + } + else if(!ustrcmp ("pfcolor", var)) { + int c = translateColor(val); + if (c >= 0) { + pfFgColor = c; + } + } + else if(!ustrcmp ("bgcolor", var)) { + int c = translateColor(val); + if (c >= 0) { + bgColor = c; + } + } + else if(!ustrcmp ("color", var)) { + int c = translateColor(val); + if (c >= 0) { + fgColor = c; + } + } + else if(!ustrcmp ("hilitecolor", var)) { + int c = translateColor(val); + if (c >= 0) { + hiliteColor = c; + } + } + else if(!ustrcmp ("blkW", var)) { + int c = strtol(val, NULL, 0); + if (c >= 0) { + s->blkW = c; + } + } + else if(!ustrcmp ("blkH", var)) { + int c = strtol(val, NULL, 0); + if (c >= 0) { + s->blkH = c; + } + } + else if(!ustrcmp ("transparentPF", var)) { + int c = atoi(val); + if (c >= 0) { + s->transparentPF = c; + } + } + else if(!ustrcmp ("shiftScale", var)) { + int c = atoi(val); + if (c >= 0) { + s->shiftScale = c; + } + } + else if(!ustrcmp ("baseX", var)) { + int c = atoi(val); + if (c >= 0) { + s->baseX = c; + } + } + else if(!ustrcmp ("nextPos", var)) { + int c = atoi(val); + if (c >= 0) { + s->nextPos = c; + } + } + else if(!ustrcmp ("wndW", var)) { + unsigned long int c = strtol(val, NULL, 0); + if (c >= 0) { + skinW = c; + } + } + else if(!ustrcmp ("wndH", var)) { + unsigned long int c = strtoul(val, NULL, 0); + if (c >= 0) { + skinH = c; + } + } + + } + fclose(fp); + ljpathSetSkinFolder(skinName); + return 0; +} + +static void drawProgressSegment(int min, int max) { + min = min * SCREEN_W / 100; + max = max * SCREEN_W / 100; + int blue = makecol(0, 0, 255); + rectfill(screen, min, SCREEN_H - 8, max - 1, SCREEN_H - 5, blue); + int orange = makecol(255, 128, 0); + rectfill(screen, min, SCREEN_H - 4, max - 1, SCREEN_H - 1, orange); +} + +static int loadSkin(LJView *v, const char *skinName) { + BITMAP *bmp; + const LJRotSystem *rs = rotSystems[v->field->rotationSystem]; + int colorScheme = rs->colorScheme; + + rectfill(screen, 0, 592, 799, 599, 0); + loadSkinFile(v->plat->skin, skinName); + + destroyBackBuf(v->plat); + if (!createBackBuf(v)) { + allegro_message("Could not create back buffer.\n"); + return 1; + } + + drawProgressSegment(0, 20); + + // Load background image + char path[PATH_MAX]; + bmp = ljpathFind_r(path, ljbgName) + ? load_bitmap(path, NULL) : NULL; + if (v->plat->skin->bg) { + destroy_bitmap(v->plat->skin->bg); + } + if (bmp) { + + // If the image size doesn't match the window size, resize it + if (bmp->w != SCREEN_W || bmp->h != SCREEN_H) { + BITMAP *resized = create_bitmap(SCREEN_W, SCREEN_H); + + if (resized) { + stretch_blit(bmp, resized, 0, 0, bmp->w, bmp->h, + 0, 0, SCREEN_W, SCREEN_H); + destroy_bitmap(bmp); + } + if (bmp) { + bmp = resized; + } else { + allegro_message("Background image \"%s\" resize failed.\n", + ljbgName); + } + } + } + if(!bmp) { + bmp = create_bitmap(SCREEN_W, SCREEN_H); + if (bmp) { + allegro_message("Background image \"%s\" not found.\n" + "Using plain background instead.\n", + ljbgName); + clear_to_color(bmp, bgColor); + } else { + allegro_message("Background image \"%s\" not found.\n", + ljbgName); + return 0; + } + } + v->plat->skin->bg = bmp; + drawProgressSegment(20, 40); + + // load block images + if (v->plat->skin->blocks) { + destroy_bitmap(v->plat->skin->blocks); + } + bmp = ljpathFind_r(path, colorScheme + ? ljblocksSegaName + : ljblocksSRSName) + ? load_bitmap(path, NULL) : NULL; + v->plat->skin->blocks = bmp; + if(!v->plat->skin->blocks) { + allegro_message("Background image \"%s\" not found.\n", + ljbgName); + return 0; + } + drawProgressSegment(40, 60); + + // load connected block images + if (v->plat->skin->connBlocks) { + destroy_bitmap(v->plat->skin->connBlocks); + } + bmp = ljpathFind_r(path, colorScheme + ? ljconnSegaName + : ljconnSRSName) + ? loadConnections(path, + v->plat->skin->blkW, + v->plat->skin->blkH) + : NULL; + v->plat->skin->connBlocks = bmp; + drawProgressSegment(60, 80); + + // load music + int isRhythm = (v->field->speedState.curve == LJSPD_RHYTHM); + if (ljpathFind_r(path, isRhythm ? bgmRhythmName : bgmName)) { + LJMusic_load(v->plat->skin->bgm, path); + } + if (!isRhythm) { + LJMusic_setLoop(v->plat->skin->bgm, bgmLoopPoint); + } + drawProgressSegment(80, 100); + return 1; +} + +void drop_mouse(void) { + while (mouse_y < SCREEN_H - 25) { + position_mouse(mouse_x, mouse_y + 20); + rest(10); + } + ezPlaySample("land_wav", 192); + position_mouse(mouse_x, SCREEN_H - 5); + ezPlaySample("lock_wav", 192); +} + +int pickReplay(void) { + FONT *oldFont = font; + font = (FONT *)aver16; + install_mouse(); + int got = file_select_ex("Choose a demo:", demoFilename, "ljm", sizeof(demoFilename), 600, 400); + drop_mouse(); + remove_mouse(); + font = oldFont; + return got ? 0 : -1; +} + +void badReplay(void) { + acquire_screen(); + clear_to_color(screen, bgColor); + textout_ex(screen, aver32, "The demo", 100, 100, fgColor, -1); + textout_ex(screen, aver16, demoFilename, 100, 130, fgColor, -1); + textout_ex(screen, aver32, "could not be played because it was", 100, 150, fgColor, -1); + textout_ex(screen, aver32, "recorded with a different version", 100, 180, fgColor, -1); + textout_ex(screen, aver32, "of LOCKJAW software.", 100, 210, fgColor, -1); + release_screen(); + + LJBits lastKeys = ~0; + LJBits keys, newKeys = 0; + + do { + keys = menuReadPad(); + newKeys = keys & ~lastKeys; + lastKeys = keys; + rest(30); + } while (!(newKeys & (VKEY_ROTL | VKEY_ROTR))); +} + +int pickSkin(void) { + FONT *oldFont = font; + font = (FONT *)aver16; + install_mouse(); + int got = file_select_ex("Choose a skin:", skinName, "skin", sizeof(skinName), 600, 400); + drop_mouse(); + remove_mouse(); + font = oldFont; + return got ? 0 : -1; +} + +void calcElev(LJView *v) { + int blkH = v->plat->skin->blkH; + int ceiling = v->field->ceiling; + int elev = (LJ_PF_VIS_HT - ceiling) * blkH; + + if (elev > 480 - ceiling * blkH) { + elev = 480 - ceiling * blkH; + } + if (elev < 0) { + elev = 0; + } + v->plat->skin->pfElev = elev; +} + +int main(const int argc, const char *const *argv) { + const char *cmdLineDemo = NULL; + int lastPreset = -2; // start out with full custom + LJPCSkin skin = { + .baseY = 552, + .blkW = 24, + .blkH = 24 + }; + LJField p[MAX_PLAYERS]; + LJControl control[2] = { + { + .replaySrc = 0, + .replayDst = 0 + } + }; + LJPCView platView[MAX_PLAYERS] = { + { + .skin = &skin + }, + { + .skin = &skin + } + }; + LJView mainView[MAX_PLAYERS] = { + { + .field = &p[0], + .control = &control[0], + .plat = &platView[0], + .backDirty = ~0 + }, + { + .field = &p[1], + .control = &control[1], + .plat = &platView[1], + .backDirty = ~0, + } + }; + + struct LJPrefs prefs = { + .number = { + [OPTIONS_TRAILS] = 1, + [OPTIONS_AUTO_PAUSE] = 1, + [OPTIONS_AUTO_RECORD] = 0, + [OPTIONS_WINDOWED] = 1 + } + }; + + // as of 0.46, we're starting to make it a bit more 2-player-clean + int nPlayers = 1; + + allegro_init(); + ljpathInit(argc > 0 ? argv[0] : "."); + install_timer(); + initOptions(prefs.number); + loadOptions(&prefs); + loadSkinFile(&skin, skinName); + + if (argc > 1) { + if (argv[1][0] == '-') { + if (!ustrcmp("--help", argv[1]) + || !ustrcmp("-h", argv[1])) { + allegro_message("Usage: lj [DEMOFILE]\n"); + return 0; + } + } else { + cmdLineDemo = argv[1]; + } + } + + if (openWindow(prefs.number[OPTIONS_WINDOWED]) != 0) { + allegro_message("LOCKJAW fatal error: Could not open an %dx%d pixel %s.\n" + "Trying %s next time.\n", + skinW, skinH, + prefs.number[OPTIONS_WINDOWED] ? "window": "screen mode", + prefs.number[OPTIONS_WINDOWED] ? "the full screen": "a window"); + prefs.number[OPTIONS_WINDOWED] = !prefs.number[OPTIONS_WINDOWED]; + saveOptions(&prefs); + return EXIT_FAILURE; + } + drawCoprNotice(); + LOCK_FUNCTION(incCurTime); + LOCK_VARIABLE(curTime); + install_int_ex(incCurTime, BPM_TO_TIMER(LJ_TICK_RATE)); + + jpgalleg_init(); + set_color_conversion(COLORCONV_NONE); + { + char path[PATH_MAX]; + if (ljpathFind_r(path, "lj.dat")) { + dat = load_datafile(path); + } + } + set_color_conversion(COLORCONV_TOTAL); + if(!dat) { + closeWindow(); + allegro_message("LOCKJAW fatal error: Could not load datafile lj.dat\n"); + return 1; + } + + { + const DATAFILE *aver16dat = find_datafile_object(dat, "Aver16_bmp"); + aver16 = aver16dat ? aver16dat->dat : font; + const DATAFILE *aver32dat = find_datafile_object(dat, "Aver32_bmp"); + aver32 = aver32dat ? aver32dat->dat : aver16; + } + + LOCK_FUNCTION(amnesia); + LOCK_VARIABLE(redrawWholeScreen); + + // If we can be notified on switching out, take this notification. + if (set_display_switch_mode(SWITCH_BACKGROUND) >= 0 + || set_display_switch_mode(SWITCH_BACKAMNESIA) >= 0) { + set_display_switch_callback(SWITCH_OUT, requestPause); + } + set_display_switch_callback(SWITCH_IN, amnesia); + + install_keyboard(); + initKeys(); + + for (int i = 0; i < MAX_PLAYERS; ++i) { + p[i].seed = time(NULL) + 123456789*i; + } + + reserve_voices(8, 0); + set_volume_per_voice(0); +#ifdef ALLEGRO_WINDOWS + // Under Windows, use the Allegro mixer because on my machine + // and probably others, the built-in mixer will replace the very + // beginning of one sound with the end of the last sound played + // on that voice. + withSound = !install_sound(DIGI_DIRECTAMX(0), MIDI_NONE, NULL); +#else + withSound = !install_sound(DIGI_AUTODETECT, MIDI_NONE, NULL); +#endif + skin.bgm = LJMusic_new(); + { + char path[PATH_MAX]; + if (ljpathFind_r(path, "sound.dat")) { + sound_dat = load_datafile(path); + } + } + + for (int i = 0; i < MAX_PLAYERS; ++i) { + unpackOptions(&mainView[i], &prefs); + } + if (loadSkin(&mainView[0], skinName) < 0) { + mainCleanup(platView); + return EXIT_FAILURE; + } else { + + } + + if(!skin.blocks) { + mainCleanup(platView); + allegro_message("Blocks image \"%s\" not found.\n", + p[0].rotationSystem + ? ljblocksSegaName + : ljblocksSRSName); + return 1; + } + + srand(time(NULL)); + + // Wait for copyright notice to be displayed "conspicuously" + if (curTime < 180) { + rest(3100 - curTime * 16); + } + + for (int action = cmdLineDemo ? TITLE_REPLAY : title(); + action > TITLE_EXIT && !wantsClose; + action = title()) { + switch (action) { + case TITLE_PLAY: + for (int preset = getPreset(lastPreset); + preset != -1 && !wantsClose; + preset = getPreset(lastPreset)) + { + lastPreset = preset; + for (int i = 0; i < MAX_PLAYERS; ++i) { + unpackOptions(&mainView[i], &prefs); + } + if (preset >= 0) { + presetStart(); + presetAdd(preset); + for (int i = 0; i < MAX_PLAYERS; ++i) { + unpackOptions(&mainView[i], &prefs); + presetFinish(&mainView[i]); + } + } + // reload the skin + if (loadSkin(&mainView[0], skinName) < 0) { + mainCleanup(platView); + return EXIT_FAILURE; + }; + for (int i = 0; i < nPlayers; ++i) { + calcElev(&mainView[0]); + pcInit(&mainView[0], &prefs); + } + wantPause = 0; + platView[0].wantRecord = prefs.number[OPTIONS_AUTO_RECORD]; + LJMusic_start(skin.bgm, + 4096, // mix buffer size + bgmVolume); // volume scale + + LJView *const players[MAX_PLAYERS] = {&mainView[0], &mainView[1]}; + play(players, nPlayers); + LJMusic_stop(skin.bgm); + if (!wantsClose) { + gameOverAnimation(&platView[0], &p[0], + control[0].countdown <= 0); + debrief(&mainView[0]); + } + } + break; + + case TITLE_REPLAY: + { + if (cmdLineDemo) { + ustrzcpy(demoFilename, sizeof(demoFilename) - 1, + cmdLineDemo); + cmdLineDemo = NULL; + } else if (pickReplay() < 0) { + break; + } + + unpackOptions(&mainView[0], &prefs); + calcElev(&mainView[0]); + pcInit(&mainView[0], &prefs); + wantPause = 0; + platView[0].wantRecord = 0; + LJMusic_start(skin.bgm, + 4096, // mix buffer size + bgmVolume); // volume scale + + LJView *const players[2] = {&mainView[0], &mainView[1]}; + + p->gimmick = -1; // gimmick must be < 0 to activate replay + play(players, 1); + LJMusic_stop(skin.bgm); + if (p[0].gimmick < 0) { + badReplay(); + } else { + if (!wantsClose) { + gameOverAnimation(&platView[0], &p[0], + control[0].countdown <= 0); + debrief(&mainView[0]); + } + } + } + break; + + case TITLE_SKIN: + pickSkin(); + + // if resolution changed, reopen the window + { + int oldW = skinW; + int oldH = skinH; + loadSkinFile(&skin, skinName); + if (skinH != oldH || skinW != oldW) { + destroySystemBitmaps(&platView[0]); + openWindow(prefs.number[OPTIONS_WINDOWED]); + } + } + + // reload the skin + if (loadSkin(&mainView[0], skinName) < 0) { + mainCleanup(platView); + return EXIT_FAILURE; + }; + + // save options + saveOptions(&prefs); + break; + + case TITLE_OPTIONS: + { + int oldWindowed = prefs.number[OPTIONS_WINDOWED]; + options(&mainView[0], prefs.number); + if (oldWindowed != prefs.number[OPTIONS_WINDOWED]) { + destroySystemBitmaps(&platView[0]); + openWindow(prefs.number[OPTIONS_WINDOWED]); + } + } + saveOptions(&prefs); + + // reload the skin if the player changed the rotation system + unpackOptions(&mainView[0], &prefs); + if (wantsClose) { + break; + } + if (loadSkin(&mainView[0], skinName) < 0) { + mainCleanup(platView); + return EXIT_FAILURE; + }; + break; + + case TITLE_KEYS: + configureKeys(); + break; + + } + } + + mainCleanup(platView); + return 0; +} END_OF_MAIN(); diff -r 000000000000 -r c84446dfb3f5 src/ljpc.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljpc.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,119 @@ +/* Header for PC frontend for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#ifndef LJPC_H +#define LJPC_H +#include +#include "ljmusic.h" +#include "ljplay.h" +#include "pcjoy.h" +#include "ljvorbis.h" +#include "options.h" + +typedef struct LJPCSkin { + BITMAP *bg; + BITMAP *blocks; + BITMAP *connBlocks; + BITMAP *fullback; + unsigned short blkW, blkH; + unsigned short baseX, baseY; + unsigned short pfElev; + unsigned char transparentPF; + unsigned char nextPos; + unsigned char shiftScale; + LJMusic *bgm; +} LJPCSkin; + +typedef struct LJPCView +{ + BITMAP *back; + LJPCSkin *skin; + unsigned short baseX; + unsigned char nextAbove; // number of next pieces above shadow + + // Platform-dependent sound + unsigned char b2bcd1, b2bcd2; + + unsigned char wantRecord; +} LJPCView; + +extern char skinName[PATH_MAX]; +extern char ljblocksSRSName[PATH_MAX]; +extern char ljblocksSegaName[PATH_MAX]; +extern char ljconnSRSName[PATH_MAX]; +extern char ljconnSegaName[PATH_MAX]; +extern char ljbgName[PATH_MAX]; +extern char bgmName[PATH_MAX]; + + +#define N_GIMMICKS LJGM_N_GIMMICKS + + +enum { + LJNEXT_RIGHT = 0, + LJNEXT_RIGHT_TAPER, + LJNEXT_TOP, + LJNEXT_N_STYLES +}; + +/** + * The number of persistent preferences. Must match the number of + * fields in struct LJPrefsNamed. + */ + +/** + * Names of persistent preferences. + * Order must be exactly the same as in + * optionsMenu[] (options.c) + */ +enum { + OPTIONS_NEXT_ABOVE = OPTIONS_MENU_LEN, + OPTIONS_TRAILS, + OPTIONS_AUTO_PAUSE, + OPTIONS_AUTO_RECORD, + OPTIONS_WINDOWED, + PC_OPTIONS_MENU_LEN +}; +struct LJPrefs { + unsigned char number[PC_OPTIONS_MENU_LEN]; +}; + + +/* set by display mode */ +extern int pfBgColor, pfFgColor, bgColor, fgColor, hiliteColor; +extern const FONT *aver32, *aver16; +extern volatile char redrawWholeScreen; +extern char autoPause; + +void saveScreen(int n); +void ezPlaySample(const char *filename, int vol); +int parse_ini_line(const char *in, char *key, char *var, char *val); +int loadOptions(struct LJPrefs *prefs); +void saveOptions(const struct LJPrefs *prefs); +void options(LJView *v, unsigned char *prefs); +void unpackOptions(LJView *v, const struct LJPrefs *prefs); +void debrief(LJView *v); + + +#endif diff -r 000000000000 -r c84446dfb3f5 src/ljplay.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljplay.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,216 @@ +/* Game loop frontend for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov[player], Elorg, +or The Tetris Company LLC. + +*/ + +#include "ljplay.h" +#ifndef WITH_REPLAY +#define WITH_REPLAY 0 +#endif + +#if WITH_REPLAY +#include "ljreplay.h" +extern const char demoFilename[]; + +#endif + + +void play(LJView *const v[], size_t nPlayers) { + int canceled = 0; + + for (unsigned int player = 0; player < nPlayers; ++player) { + LJField *const p = v[player]->field; + LJControl *const ctl = v[player]->control; + + ctl->countdown = 6; + ctl->presses = 0; + + /* Load replay if needed */ +#if WITH_REPLAY + if (p->gimmick < 0) { + ctl->replaySrc = openReplay(demoFilename, p); + if (!ctl->replaySrc) { + return; + } + v[player]->backDirty = ~0; + playRedrawScreen(v[player]); + } else { + ctl->replaySrc = 0; + } +#endif + } + if (!v[0]->control->replaySrc) { + for (unsigned int player = 0; player < nPlayers; ++player) { + LJField *const p = v[player]->field; + newGame(p); + initGimmicks(p); + v[player]->nLockTimes = 0; + v[player]->hideNext = 0; + updField(v[player], ~0); + } + startingAnimation(v[0]); + for (unsigned int player = 0; player < nPlayers; ++player) { + v[player]->control->lastKeys = 0; + v[player]->control->repressKeys = 0; + blitField(v[player]); + } + } + + int lastTime = getTime(); + + while(v[0]->field->state != LJS_GAMEOVER && !canceled) { + if (getTime() == lastTime) { + yieldCPU(); + } + canceled |= ljHandleConsoleButtons(v[0]); + while (getTime() - lastTime > 0) { + for (unsigned int player = 0; player < nPlayers; ++player) { + LJField *const p = v[player]->field; + LJControl *const ctl = v[player]->control; + LJInput in = {0, 0, 0, 0}; + int curTime = getTime(); + + addKeysToInput(&in, readPad(player), p, ctl); + + // when returning from pause, catch up the speed control + if (curTime - lastTime > 10 + || curTime - lastTime < -10) { + lastTime = curTime; + } + + { + int irsAttempted = (p->sounds & (LJSND_SPAWN | LJSND_HOLD)) + && in.rotation && ctl->initialRotate; + + // The next line does ALL the game logic. + LJBits updatedRows = frame(p, &in) | gimmicks(p, ctl); + + v[player]->backDirty |= updatedRows; + if (irsAttempted && (p->sounds & LJSND_ROTATE)) { + p->sounds |= LJSND_IRS; + } + } + + // items is a partly view-based gimmick + if (p->gimmick == LJGM_ITEMS && (p->sounds & LJSND_SPAWN)) { + v[player]->hideNext = (v[player]->field->nPieces > 7); + v[player]->frontDirty |= LJ_DIRTY_NEXT; + } + + // five, four, three, two, one + int curCountdown = ctl->countdown; + if (p->gimmick == LJGM_ULTRA && p->gameTime >= 10500) { + curCountdown = (int)(10859 - p->gameTime) / 60; + } else if (p->gimmick == LJGM_BTYPE) { + curCountdown = 40 - p->lines; + } else if (p->gimmick == LJGM_BABY) { + curCountdown = (309 - v[player]->control->presses) / 10; + } else if (p->gimmick == LJGM_DRILL && (p->sounds & LJSND_LINE)) { + int line = bfffo(p->tempRows); + if (line < curCountdown) + curCountdown = line; + } + + // we have to wait for the new piece to come out + // so that the score is credited properly + if (curCountdown <= 0 + && (p->state == LJS_NEW_PIECE + || p->state == LJS_FALLING + || p->state == LJS_LANDED)) { + p->state = LJS_GAMEOVER; + } + + playSoundEffects(v[player], p->sounds, curCountdown); + ctl->countdown = curCountdown; + + // Update speedometer + if (p->sounds & LJSND_LOCK) { + for (int i = N_SPEED_METER_PIECES - 2; i >= 0; --i) { + v[player]->lockTime[i + 1] = v[player]->lockTime[i]; + } + v[player]->lockTime[0] = p->gameTime; + if (v[player]->nLockTimes < N_SPEED_METER_PIECES) { + ++v[player]->nLockTimes; + } + v[player]->frontDirty = LJ_DIRTY_SCORE; + } + + // If the piece was just spawned, move the trail to the piece's + // starting position and redraw next pieces. + if (p->sounds & (LJSND_SPAWN | LJSND_HOLD)) { + v[player]->trailY = p->y; + v[player]->frontDirty |= LJ_DIRTY_NEXT; + } + } + + ++lastTime; + } + + for (unsigned int player = 0; player < nPlayers; ++player) { + LJField *const p = v[player]->field; + if (p->state == LJS_GAMEOVER && v[player]->hidePF) { + v[player]->hidePF = 0; + v[player]->backDirty |= (1 << LJ_PF_VIS_HT) - 1; + } + + updField(v[player], v[player]->backDirty); + v[player]->frontDirty |= v[player]->backDirty; + v[player]->backDirty = 0; + + // If piece is falling or landed, and it wasn't just spawned, + // draw the piece and its shadow. + + if (p->sounds & (LJSND_SPAWN | LJSND_HOLD)) { + // piece was just spawned, so don't draw the piece + } + // Otherwise, if the piece is falling or landed, draw it. + else if (p->state == LJS_FALLING || p->state == LJS_LANDED + || p->sounds & LJSND_LOCK) { + if (v[player]->hideShadow != LJSHADOW_NO_FALLING) { + if (p->state == LJS_FALLING && v[player]->hideShadow < LJSHADOW_NONE) { + drawShadow(v[player]); + } + drawFallingPiece(v[player]); + } + } + + ljBeginDraw(v[player], getTime() - lastTime < 2); + drawScore(v[player]); + blitField(v[player]); + ljEndDraw(v[player]); + } + } + +#if WITH_REPLAY + { + int player = 0; + if (v[player]->control->replaySrc) { + replayClose(v[player]->control->replaySrc); + v[player]->control->replaySrc = NULL; + } + if (v[player]->control->replayDst) { + replayClose(v[player]->control->replayDst); + v[player]->control->replayDst = NULL; + } + } +#endif +} diff -r 000000000000 -r c84446dfb3f5 src/ljplay.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljplay.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,57 @@ +/* Platform hooks for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#ifndef LJPLAY_H +#define LJPLAY_H +#include "lj.h" +#include "ljcontrol.h" + +/** + * Plays Lockjaw. + * @param v view + */ +void play(LJView *const v[], size_t nPlayers); + +/* + * Platform-native code must implement the following callbacks, + * which will be described later: + */ +LJBits readPad(unsigned int player); +void updField(const LJView *const v, LJBits rows); +void startingAnimation(LJView *v); +void blitField(LJView *v); +int pauseGame(struct LJPCView *v); +void playSoundEffects(LJView *v, LJBits sounds, int countdown); +void drawShadow(LJView *v); +void drawFallingPiece(LJView *v); +void drawScore(LJView *v); +int getTime(void); +void yieldCPU(void); +void ljBeginDraw(LJView *v, int sync); +void ljEndDraw(LJView *v); +int ljHandleConsoleButtons(LJView *v); +void playRedrawScreen(LJView *v); + + +#endif diff -r 000000000000 -r c84446dfb3f5 src/ljreplay.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljreplay.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,335 @@ +/* Replay functionality for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006-2008 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#include "ljreplay.h" +#include + +#define FORMAT_VERSION 0x20080525 +#define LAST_FORMAT_VERSION 20080420 +#define DEBUG_OFFSETS 0 + +#if DEBUG_OFFSETS +#include +#endif + +struct LJReplayFrame { + char length; + char reserved; + unsigned short keys; + LJInput x; +}; + +struct LJReplay { + FILE *file; +}; + +static void fput32(unsigned int src, FILE *dst) { + fputc(src >> 24, dst); + fputc(src >> 16, dst); + fputc(src >> 8, dst); + fputc(src, dst); +} + +static unsigned int fget32(FILE *src) { + int c0 = fgetc(src) & 0xFF; + c0 = (c0 << 8) | (fgetc(src) & 0xFF); + c0 = (c0 << 8) | (fgetc(src) & 0xFF); + c0 = (c0 << 8) | (fgetc(src) & 0xFF); + return c0; +} +static void fput16(unsigned int src, FILE *dst) { + fputc(src >> 8, dst); + fputc(src, dst); +} + +static unsigned int fget16(FILE *src) { + int c0 = fgetc(src) & 0xFF; + c0 = (c0 << 8) | (fgetc(src) & 0xFF); + return c0; +} + +static void LJField_serialize(const LJField *p, FILE *fp) { + fput32(FORMAT_VERSION, fp); + + for (int y = 0; y < LJ_PF_HT; ++y) { + for (int x = 0; x < LJ_PF_WID; ++x) { + fputc(p->b[y][x], fp); + } + } + for (int y = 0; y < LJ_PF_HT; ++y) { + for (int x = 0; x < LJ_PF_WID; ++x) { + fputc(p->c[y][x], fp); + } + } + fput32(p->clearedLines, fp); + fput32(p->sounds, fp); + fput32(p->tempRows, fp); + for (int y = 0; y < 1 + LJ_NEXT_PIECES; ++y) { + fputc(p->curPiece[y], fp); + } + for (int y = 0; y < 2 * MAX_BAG_LEN; ++y) { + fputc(p->permuPiece[y], fp); + } + fputc(p->permuPhase, fp); + for (int y = 0; y < LJ_MAX_LINES_PER_PIECE; ++y) { + fput16(p->nLineClears[y], fp); + } + fput32(p->y, fp); + fputc(p->state, fp); + fputc(p->stateTime, fp); + fputc(p->theta, fp); + fputc(p->x, fp); + fputc(p->hardDropY, fp); + fputc(p->alreadyHeld, fp); + fputc(p->isSpin, fp); + fputc(p->nLinesThisPiece, fp); + fputc(p->canRotate, fp); + +#if DEBUG_OFFSETS + allegro_message("before score, offset = %u\n", (unsigned int)ftell(fp)); +#endif + fput32(p->score, fp); + fput32(p->lines, fp); + fput32(p->gameTime, fp); + fput32(p->activeTime, fp); + fput16(p->holdPiece, fp); + fputc(p->chain, fp); + fputc(p->garbage, fp); + fputc(p->garbageX, fp); + fput16(p->nPieces, fp); + fput16(p->outGarbage, fp); + + fputc(p->gimmick, fp); + fput32(p->speedState.level, fp); + fput32(p->bpmCounter, fp); + fput32(p->speedupCounter, fp); + fput32(p->goalCount, fp); + fput32(p->seed, fp); + fput32(p->speed.gravity, fp); + fputc(p->speedState.curve, fp); + fputc(p->goalType, fp); + fputc(p->speed.entryDelay, fp); + fputc(p->areStyle, fp); + fputc(p->lockReset, fp); + fputc(p->speed.lockDelay, fp); + fputc(p->speed.lineDelay, fp); + fputc(p->ceiling, fp); + fputc(p->enterAbove, fp); + fputc(p->leftWall, fp); + fputc(p->rightWall, fp); + fputc(p->pieceSet, fp); + fputc(p->randomizer, fp); + fputc(p->rotationSystem, fp); + fputc(p->garbageRandomness, fp); + fputc(p->tSpinAlgo, fp); + fputc(p->clearGravity, fp); + fputc(p->gluing, fp); + fputc(p->scoreStyle, fp); + fputc(p->setLockDelay, fp); + fputc(p->upwardKicks, fp); + fputc(p->maxUpwardKicks, fp); + fputc(p->setLineDelay, fp); + fputc(p->garbageStyle, fp); + fputc(p->holdStyle, fp); + fputc(p->bottomBlocks, fp); + + fput16(p->multisquares, fp); + fput16(p->monosquares, fp); + +#if DEBUG_OFFSETS + allegro_message("final offset = %u\n", (unsigned int)ftell(fp)); +#endif +} + +static int LJField_deserialize(LJField *p, FILE *fp) { + int format = fget32(fp); + if (format != FORMAT_VERSION + && format != LAST_FORMAT_VERSION) { + return -1; + } + + for (int y = 0; y < LJ_PF_HT; ++y) { + for (int x = 0; x < LJ_PF_WID; ++x) { + p->b[y][x] = fgetc(fp); + } + } + for (int y = 0; y < LJ_PF_HT; ++y) { + for (int x = 0; x < LJ_PF_WID; ++x) { + p->c[y][x] = fgetc(fp); + } + } + p->clearedLines = fget32(fp); + p->sounds = fget32(fp); + p->tempRows = fget32(fp); + for (int y = 0; y < 1 + LJ_NEXT_PIECES; ++y) { + p->curPiece[y] = fgetc(fp); + } + for (int y = 0; y < 2 * MAX_BAG_LEN; ++y) { + p->permuPiece[y] = fgetc(fp); + } + p->permuPhase = fgetc(fp); + for (int y = 0; y < LJ_MAX_LINES_PER_PIECE; ++y) { + p->nLineClears[y] = fget16(fp); + } + p->y = fget32(fp); + p->state = fgetc(fp); + p->stateTime = fgetc(fp); + p->theta = fgetc(fp); + p->x = fgetc(fp); + p->hardDropY = fgetc(fp); + p->alreadyHeld = fgetc(fp); + p->isSpin = fgetc(fp); + p->nLinesThisPiece = fgetc(fp); + p->canRotate = fgetc(fp); + +#if DEBUG_OFFSETS + allegro_message("before score, offset = %u\n", (unsigned int)ftell(fp)); +#endif + p->score = fget32(fp); + p->lines = fget32(fp); + p->gameTime = fget32(fp); + p->activeTime = fget32(fp); + p->holdPiece = fget16(fp); + p->chain = fgetc(fp); + p->garbage = fgetc(fp); + p->garbageX = fgetc(fp); + p->nPieces = fget16(fp); + p->outGarbage = fget16(fp); + + p->gimmick = fgetc(fp); + p->speedState.level = fget32(fp); + p->bpmCounter = fget32(fp); + p->speedupCounter = fget32(fp); + p->goalCount = fget32(fp); + p->seed = fget32(fp); + p->speed.gravity = fget32(fp); + p->speedState.curve = fgetc(fp); + p->goalType = fgetc(fp); + p->speed.entryDelay = fgetc(fp); + p->areStyle = fgetc(fp); + p->lockReset = fgetc(fp); + p->speed.lockDelay = fgetc(fp); + p->speed.lineDelay = fgetc(fp); + p->ceiling = fgetc(fp); + p->enterAbove = fgetc(fp); + p->leftWall = fgetc(fp); + p->rightWall = fgetc(fp); + p->pieceSet = fgetc(fp); + p->randomizer = fgetc(fp); + p->rotationSystem = fgetc(fp); + p->garbageRandomness = fgetc(fp); + p->tSpinAlgo = fgetc(fp); + p->clearGravity = fgetc(fp); + p->gluing = fgetc(fp); + p->scoreStyle = fgetc(fp); + p->setLockDelay = fgetc(fp); + p->upwardKicks = fgetc(fp); + p->maxUpwardKicks = fgetc(fp); + p->setLineDelay = fgetc(fp); + p->garbageStyle = fgetc(fp); + p->holdStyle = fgetc(fp); + p->bottomBlocks = fgetc(fp); + + if (format == FORMAT_VERSION) { + p->multisquares = fget16(fp); + p->monosquares = fget16(fp); + } +#if DEBUG_OFFSETS + allegro_message("final offset = %u\n", (unsigned int)ftell(fp)); +#endif + return 0; +} + + +LJReplay *newReplay(const char *filename, LJField *p) { + LJReplay *r = malloc(sizeof(struct LJReplay)); + + if (!r) { + return NULL; + } + r->file = fopen(filename, "wb"); + if (!r->file) { + free(r); + return NULL; + } + LJField_serialize(p, r->file); + return r; +} + +void replayRecord(LJReplay *r, LJBits keys, const LJInput *in) { + fputc(0, r->file); + fputc(0, r->file); + fputc(keys >> 8, r->file); + fputc(keys, r->file); + fputc(in->rotation, r->file); + fputc(in->movement, r->file); + fputc(in->gravity, r->file); + fputc(in->other, r->file); +} + +LJReplay *openReplay(const char *filename, LJField *p) { + LJReplay *r = malloc(sizeof(struct LJReplay)); + + if (!r) { + return NULL; + } + r->file = fopen(filename, "rb"); + if (!r->file) { + free(r); + return NULL; + } + + /* This deserialization is still NOT robust. */ + if (LJField_deserialize(p, r->file) < 0) { + fclose(r->file); + free(r); + return 0; + } + return r; +} + +int getReplayFrame(LJReplay *r, LJInput *d) { + fgetc(r->file); + fgetc(r->file); + int keys = fgetc(r->file); + + if (keys == EOF) { + return LJREPLAY_EOF; + } + keys = (keys << 8 & 0xFF) | (fgetc(r->file) & 0xFF); + d->rotation = fgetc(r->file); + d->movement = fgetc(r->file); + d->gravity = fgetc(r->file); + d->other = fgetc(r->file); + return keys; +} + +void replayClose(LJReplay *r) { + if (r) { + if (r->file) { + fclose(r->file); + } + free(r); + } +} diff -r 000000000000 -r c84446dfb3f5 src/ljreplay.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljreplay.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,70 @@ +/* Replay functionality for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ +#ifndef LJREPLAY_H +#define LJREPLAY_H + +#include "lj.h" +#include "ljcontrol.h" + +#define LJREPLAY_EOF (-1) + +typedef struct LJReplay LJReplay; + +/** + * Creates a new replay. + * @param filename The name of the file to which the replay is recorded. + * @param p The field that is observed. + * @return A pointer to the replay object, + * or NULL if allocation failed. + */ +LJReplay *newReplay(const char *filename, LJField *p); + +/** + * Records a single frame of input in the replay. + * If spawn or hold sound is played, records the new piece. + * @param r The replay object. + */ +void replayRecord(LJReplay *r, LJBits keys, const LJInput *in); + +/** + * Stops recording the replay and dumps it to the file. + */ +void replayClose(LJReplay *r); + +/** + * Opens an existing replay. + * @param filename The name of the file to which the replay is recorded. + * @param p The field that is observed. + * @return A pointer to the replay object, + * or NULL if allocation failed. + */ +LJReplay *openReplay(const char *filename, LJField *p); + +/** + * @param d The structure to be filled with input + * @return The keys pressed, or REPLAY_EOF. + */ +int getReplayFrame(LJReplay *r, LJInput *d); + +#endif diff -r 000000000000 -r c84446dfb3f5 src/ljtypes.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljtypes.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,61 @@ +/* Basic data types for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#ifndef LJTYPES_H +#define LJTYPES_H + +/** + * A 16.16 signed number. Used commonly for Y piece positions. + */ +typedef signed int LJFixed; + +static inline signed int ljfixfloor(LJFixed f) __attribute__((const)); + +static inline signed int ljfixfloor(LJFixed f) { + return f >> 16; +} + +static inline LJFixed ljitofix(signed int f) __attribute__((const)); + +static inline LJFixed ljitofix(signed int f) { + return f << 16; +} + +/* + * In most cases, macros are deprecated in favor of static inline functions + * because the latter allow the compiler to perform more type checks. + * However, the C language forbids calling a function in an inline + * constructor, even if it is a static inline function with no side effects. + * For example, GCC gives the misleading error message + * "error: initializer element is not constant". + * Therefore, I have to provide a second implementation of ljitofix() + * as a macro for use in global variable initializers. + */ +#define LJITOFIX(f) ((LJFixed)((f) << 16)) + + +typedef unsigned int LJBits; + +#endif + diff -r 000000000000 -r c84446dfb3f5 src/ljvorbis.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljvorbis.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,206 @@ +/* + +ljvorbis.c +Simple wrapper around vorbisfile for use with the Allegro library +copyright 2006 Damian Yerrick +based on vorbisfile example +copyright 1994-2004 Xiph.Org Foundation +licensed under a BSD style license set forth in COPYING-OGG.txt + +*/ + +#define ALLEGRO_USE_CONSOLE +#include +#include "ljvorbis.h" + +#define VORBIS_BYTEDEPTH 2 // number of bytes per sample; 2 means 16-bit +#define VORBIS_ENDIAN 0 // 0: x86/little; 2: ppc/big +#define VORBIS_SIGNED 0 // 0: unsigned; 1: signed; Allegro uses unsigned + +LJVorbis *LJVorbis_open(const char *filename) { + LJVorbis *ogg = malloc(sizeof(LJVorbis)); + if (!ogg) { + return NULL; + } + ogg->fp = fopen(filename, "rb"); + if (!ogg->fp) { + free(ogg); + return NULL; + } + if (ov_open(ogg->fp, &(ogg->vf), NULL, 0) < 0) { + fclose(ogg->fp); + free(ogg); + return NULL; + } + vorbis_info *vi=ov_info(&(ogg->vf),-1); + ogg->rate = vi->rate; + ogg->channels = vi->channels; + ogg->length = ov_pcm_total(&(ogg->vf),-1); + ogg->loopPoint = 0; + ogg->voice = NULL; + ogg->paused = 0; + return ogg; +} + +void LJVorbis_setLoop(LJVorbis *ogg, unsigned long int loopPoint) { + if (ogg) { + if (loopPoint < ogg->length) { + ogg->loopPoint = loopPoint; + } else { + ogg->loopPoint = 0; + } + } +} + +int LJVorbis_start(LJVorbis *ogg, int bufferSize, int vol, int pan) { + if (ogg) { + + // if restarting, stop first + if (ogg->voice) { + LJVorbis_stop(ogg); + } + ogg->voice = play_audio_stream(bufferSize, + 8 * VORBIS_BYTEDEPTH, + ogg->channels > 1, + ogg->rate, + vol, pan); + ogg->bufferSize = bufferSize; + if (!ogg->voice) { + return -1; + } + ov_pcm_seek(&(ogg->vf), 0); + return 0; + } + return -1; +} + +void LJVorbis_stop(LJVorbis *ogg) { + if (ogg && ogg->voice) { + stop_audio_stream(ogg->voice); + ogg->voice = NULL; + } +} + +void LJVorbis_close(LJVorbis *ogg) { + if (ogg) { + LJVorbis_stop(ogg); + ov_clear(&(ogg->vf)); // finalize decoder and close the file + // thanks kesiev for reminding me that ov_clear closes the file itself + free(ogg); + } +} + +void LJVorbis_pause(LJVorbis *ogg, int value) { + if (ogg && ogg->voice) { + int hwVoice = ogg->voice->voice; + voice_set_frequency(hwVoice, value ? 0 : ogg->rate); + ogg->paused = value ? 1 : 0; + } +} + +int LJVorbis_poll(LJVorbis *ogg) { + if (!ogg || !ogg->voice) { + return -1; + } + char *buf = get_audio_stream_buffer(ogg->voice); + int eofReached = 0; + + if (buf) { + // the number of bytes left in this buffer + long int bytesLeft = ogg->bufferSize * VORBIS_BYTEDEPTH * ogg->channels; + + while (bytesLeft > 0) { + long ret=ov_read(&(ogg->vf), + buf, + bytesLeft, + VORBIS_ENDIAN, + VORBIS_BYTEDEPTH, + VORBIS_SIGNED, + &(ogg->bitstream)); + if (ret == 0) { + // try to seek back to the beginning of the file + int pcmErr = ov_pcm_seek(&(ogg->vf), ogg->loopPoint); + if (pcmErr) { + /* EOF */ + eofReached = 1; + bytesLeft = 0; + } + } else if (ret < 0) { + // Stream error. Just ignore it. + } else { + /* FIXME: handle sample rate changes, etc */ + // advance forward in the buffer + buf += ret; + bytesLeft -= ret; + } + } + free_audio_stream_buffer(ogg->voice); + } + return eofReached; +} + +#ifdef LJVORBIS_DEMO +int main(){ + int eofReached = 0; + LJVorbis *ogg = LJVorbis_open("AM-3P.ogg"); + + if (!ogg) { + fprintf(stderr, "Could not open AM-3P.ogg.\n"); + exit(1); + } + LJVorbis_setLoop(ogg, 650772); + + if (allegro_init() < 0 + || install_timer() < 0 + || set_gfx_mode(GFX_SAFE, 320, 200, 0, 0) < 0 + || install_keyboard() < 0 + || install_sound(DIGI_AUTODETECT, MIDI_NONE, NULL) < 0) { + allegro_exit(); + LJVorbis_close(ogg); + fprintf(stderr, "Could not start Allegro library: %s\n", allegro_error); + exit(1); + } + + /* Throw the comments plus a few lines about the bitstream we're + decoding */ + if (0) + { + char line1[80], line2[80]; + + usprintf(line1,"%d channels, %u Hz", ogg->channels, ogg->rate); + usprintf(line2,"length: %lu samples", ogg->length); + alert(line1, line2, "ready?", + "Play", 0, 13, 0); + } + + if (LJVorbis_start(ogg, 1024, 192, 128) < 0) { + LJVorbis_close(ogg); + alert("Could not allocate voice", + "for playing audio.", + "", + "OK", 0, 13, 0); + exit(1); + } + + while(!eofReached){ + eofReached = LJVorbis_poll(ogg); + rest(16); + + if (keypressed()) { + int scancode; + ureadkey(&scancode); + + if (scancode == KEY_P) { + LJVorbis_pause(ogg, !ogg->paused); + } else if (scancode == KEY_ESC) { + eofReached = 1; + } + } + } + + /* cleanup */ + LJVorbis_close(ogg); + return(0); +} +END_OF_MAIN(); +#endif diff -r 000000000000 -r c84446dfb3f5 src/ljvorbis.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ljvorbis.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,76 @@ +/* + +ljvorbis.h +Simple wrapper around vorbisfile for use with the Allegro library +copyright 2006 Damian Yerrick +based on vorbisfile example +copyright 1994-2004 Xiph.Org Foundation +licensed under a BSD style license set forth in COPYING-OGG.txt + +*/ + +#ifndef LJVORBIS_H +#define LJVORBIS_H + +#include +#include +#include + +typedef struct LJVorbis { + FILE *fp; + AUDIOSTREAM *voice; + OggVorbis_File vf; + int bitstream; + unsigned int bufferSize; + unsigned int rate; + unsigned long int length; + unsigned long int loopPoint; + unsigned char channels; + unsigned char paused; +} LJVorbis; + +/** + * Creates a new LJVorbis instance. + * @param filename the name of the .ogg file to open + * @return an LJVorbis pointer + */ +LJVorbis *LJVorbis_open(const char *filename); + +/** + * Sets the loop point of an LJVorbis. If it is past the end + * of the file, sets the loop point to the start of the file. + * @param loopPoint the sample number to seek back to + */ +void LJVorbis_setLoop(LJVorbis *ogg, unsigned long int loopPoint); + +/** + * Starts or restarts an LJVorbis playing in a new Allegro voice. + * @param bufferSize the size of the Allegro audio buffer in samples + * @param vol the Allegro volume (0-255?) + * @param pan the Allegro pan value (0=left, 256=right) + */ +int LJVorbis_start(LJVorbis *ogg, int bufferSize, int vol, int pan); + +/** + * Stops an LJVorbis and frees its Allegro voice. + */ +void LJVorbis_stop(LJVorbis *ogg); + +/** + * Destroys an LJVorbis instance entirely. + */ +void LJVorbis_close(LJVorbis *ogg); + +/** + * Pauses or resumes an LJVorbis. + * @param value 0 to pause, or nonzero to resume + */ +void LJVorbis_pause(LJVorbis *ogg, int value); + +/** + * Processes an LJVorbis + * Must be called periodically, at least once every bufferSize samples. + */ +int LJVorbis_poll(LJVorbis *ogg); + +#endif diff -r 000000000000 -r c84446dfb3f5 src/macro.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/macro.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,263 @@ +/* Input handling for LOCKJAW Tetromino Game + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ +#include "lj.h" +#include "ljcontrol.h" +#include "ljreplay.h" +#include "pcjoy.h" + +// first is rotation (+1 = 90 deg clockwise) +// second is movement (+1 = right 1 block) +// third is gravity (+1 = down 1/8 block) +// fourth is extra actions (hold, lock) +LJInput macros[8] = { + { -1, 0, 0, 0 }, // default: alt. rotate left + { -2, 0, 0, 0 }, // default: rotate left twice + { 0, -9, 0, 0 }, // default: far left + { 0, 9, 0, 0 }, // default: far right + { 0, 0, 20*8, 0 }, // default: firm drop + { 0, 0, 0, LJI_HOLD }, // default: alternate hold + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 } +}; + +void addMacrosToInput(LJInput *dst, LJBits keys) { + int rotation = dst->rotation; + int movement = dst->movement; + int gravity = dst->gravity; + int other = dst->other; + int macro; + + keys >>= 8; + for (macro = 0; + macro < 8; + keys >>= 1, ++macro) { + if (keys & 1) { + rotation += macros[macro].rotation; + movement += macros[macro].movement; + gravity += macros[macro].gravity; + other |= macros[macro].other; + } + } + + // Clip rotation to [-3, +3] + rotation -= rotation / 4 * 4; + + // Clip movement to playfield width + if (movement < (int)-LJ_PF_WID) { + movement = -LJ_PF_WID; + } else if (movement > (int)LJ_PF_WID) { + movement = LJ_PF_WID; + } + + // Clip gravity to playfield height + if (gravity > LJ_PF_HT * 8) { + gravity = LJ_PF_HT * 8; + } + + dst->rotation = rotation; + dst->movement = movement; + dst->gravity = gravity; + dst->other = other; +} + +static const LJFixed softDropSpeeds[3] = { + LJITOFIX(1), + LJITOFIX(1)/2, + LJITOFIX(1)/3 +}; + +void addKeysToInput(LJInput *dst, LJBits keys, const LJField *p, LJControl *c) { + int actualKeys = keys; + + if (c->replaySrc) { + keys = getReplayFrame(c->replaySrc, dst); + if (keys == LJREPLAY_EOF) { + keys = actualKeys; + replayClose(c->replaySrc); + c->replaySrc = NULL; + } + } + + int lastFrameKeys = c->lastKeys; + + // If diagonal presses are disabled, ignore any changes + if (!c->allowDiagonals + && (keys & (VKEY_UP | VKEY_DOWN)) + && (keys & (VKEY_LEFT | VKEY_RIGHT))) { + keys &= ~(VKEY_UP | VKEY_DOWN | VKEY_LEFT | VKEY_RIGHT); + keys |= lastFrameKeys + & (VKEY_UP | VKEY_DOWN | VKEY_LEFT | VKEY_RIGHT); + } + + LJBits newKeys = keys & ~lastFrameKeys; + c->lastKeys = keys; + + // Count presses for Baboo!, excluding console buttons + c->presses += countOnes(newKeys & 0x0000FFFF); + + // Only once the side effect of counting presses for Baboo! + // is complete can we break out of a replay. + if (c->replaySrc) { + return; + } + + LJBits releasedKeys = ~keys & lastFrameKeys; + + // At this point, c->lastKeys holds the keys actually held + // by the player this frame, and lastFrameKeys holds the keys + // actually held by the player last frame. + + // Handle keys that must be re-pressed + releasedKeys &= ~c->repressKeys; + c->repressKeys &= keys; + keys &= ~c->repressKeys; + + // If locking in a mode without ARE, require + // down to be re-pressed before next piece + if (p->sounds & LJSND_LOCK + && p->speed.entryDelay <= c->dasDelay) { + c->repressKeys |= VKEY_DOWN; + + // Treat up the same way when hard drop lock is set to lock on release. + if (c->hardDropLock != LJZANGI_SLIDE + || p->lockReset == LJLOCK_NOW + || p->speed.lockDelay <= c->dasDelay) { + c->repressKeys |= VKEY_UP | VKEY_MACRO(4); + } + } + + // Initial Rotation System (IRS): + // When a piece spawns from next or hold, and a rotation or macro + // key is held, treat the key as if they had just been pressed. + // Treat hard drop the same way when ARE is turned on. + if ((p->sounds & (LJSND_SPAWN | LJSND_HOLD)) + && c->initialRotate) { + newKeys |= c->lastKeys + & (VKEY_ROTL | VKEY_ROTR | VKEY_MACROS); + if (p->speed.entryDelay > 0) { + newKeys |= c->lastKeys + & VKEY_UP; + } + } + + // if we're pretending that keys are not pressed, + // pretend consistently + newKeys &= keys; + + // TGM does not perform sideways movement on + // the first frame after a piece is spawned. + if (c->initialDAS == 0 && + (p->sounds & (LJSND_SPAWN | LJSND_HOLD))) { + + } else if (keys & VKEY_LEFT) { + if (c->dasCounter > -(int)c->dasDelay) { + if (c->dasCounter >= 0) { + c->dasCounter = -1; + dst->movement = -1; + } else { + c->dasCounter -= 1; + } + } else { + int dasSpeed = c->dasSpeed; + if (dasSpeed) { + dst->movement = -1; + c->dasCounter += dasSpeed - 1; + } else { + dst->movement = -(int)LJ_PF_WID; + } + } + } else if (keys & VKEY_RIGHT) { + if (c->dasCounter < c->dasDelay) { + if (c->dasCounter <= 0) { + c->dasCounter = 1; + dst->movement = 1; + } else { + c->dasCounter += 1; + } + } else { + int dasSpeed = c->dasSpeed; + if (dasSpeed) { + dst->movement = 1; + c->dasCounter -= dasSpeed - 1; + } else { + dst->movement = (int)LJ_PF_WID; + } + } + } else { + c->dasCounter = 0; + } + + if(keys & VKEY_DOWN) { + int g = softDropSpeeds[c->softDropSpeed]; + + // dither speed to 1/8G units + g += ljitofix(p->gameTime % 3) / 24; + dst->gravity += g >> 13; + + if ((newKeys & VKEY_DOWN) + || c->softDropLock == LJZANGI_LOCK) { + dst->other |= LJI_LOCK; + } + } + + if (newKeys & VKEY_ROTL) { + dst->rotation -= 1; + } + if (newKeys & VKEY_ROTR) { + dst->rotation += 1; + } + if (newKeys & VKEY_HOLD) { + dst->other |= LJI_HOLD; + } + if (newKeys & VKEY_UP) { + dst->gravity = LJ_PF_HT << 3; + if (p->state == LJS_LANDED + || c->hardDropLock == LJZANGI_LOCK) { + dst->other |= LJI_LOCK; + } + } + + if (c->hardDropLock == LJZANGI_LOCK_RELEASE) { + if (releasedKeys & VKEY_UP) { + dst->other |= LJI_LOCK; + } + } + if (c->softDropLock == LJZANGI_LOCK_RELEASE) { + if (releasedKeys & VKEY_DOWN) { + dst->other |= LJI_LOCK; + } + } + + addMacrosToInput(dst, newKeys); + + // Baboo! ends with a hard drop + if (p->gimmick == LJGM_BABY && c->presses >= 300) { + dst->gravity = LJ_PF_HT << 3; + dst->other |= LJI_LOCK; + } + + if (c->replayDst) { + replayRecord(c->replayDst, actualKeys, dst); + } +} diff -r 000000000000 -r c84446dfb3f5 src/old_pc_options.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/old_pc_options.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,436 @@ +/* PC option screen for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#include "ljpc.h" +#include +#include "ljpath.h" +#include +#include "options.h" +#include "ljlocale.h" + +#define OPTIONS_OFFSET 0 +#define optionsMenu (commonOptionsMenu + OPTIONS_OFFSET) + + + +#if 0 +static int getOptionValueByName(int row, const char *value) { + for (int x = 0; x < optionsMenu[row].nValues; ++x) { + if (!ustrcmp(value, optionsMenu[row].valueNames[x])) { + return x + optionsMenu[row].minValue; + } + } + return -1; +} + +static void setOptionValueByNumber(struct LJPrefs *prefs, + int row, const char *value) { + if (value[0] >= '0' && value[0] <= '9') { + int num = atoi(value); + if (num >= optionsMenu[row].minValue + && num < optionsMenu[row].minValue + optionsMenu[row].nValues) { + prefs->number[row] = num; + } + } +} + +static void setOptionValueByName(struct LJPrefs *prefs, + int row, const char *value) { + int num = getOptionValueByName(row, value); + + if (num >= 0) { + prefs->number[row] = num; + } +} + +static void setOptionValueByBoolean(struct LJPrefs *prefs, + int row, const char *value) { + int num = atoi(value); + + if (num >= 0) { + prefs->number[row] = num ? 1 : 0; + } +} +#endif + +/* parse_ini_line ******************* +*/ +int parse_ini_line(const char *in, char *key, char *var, char *val) +{ + int c; + char *kstart = key; + + /* Skip whitespace before key */ + while(*in && isspace(*in)) + in++; + + /* Parse key */ + if(*in == '[') /* if we have a new key, load it */ + { + in++; + + /* Skip whitespace before actual key */ + while(*in && isspace(*in)) + in++; + + for(c = *in++; + c != 0 && c != ']' && c != '\n' && c != '\r'; + c = *in++) + { + if(!isspace(c)) + *key++ = c; + } + *key = 0; + /* Strip whitespace after key */ + do { + *key-- = 0; + } while(key >= kstart && isspace(*key)); + } + + /* Skip whitespace before variable */ + while(*in && isspace(*in)) + in++; + if(*in == 0) /* if there is no variable, don't do anything */ + return 0; + + for(c = *in++; + c != 0 && c != '=' && c != '\n' && c != '\r'; + c = *in++) + { + if(!isspace(c)) + { + *var++ = c; + } + } + *var = 0; + + /* Skip whitespace before value */ + while(*in && isspace(*in)) + in++; + + /* Get value */ + kstart = val; + for(c = *in++; + c != 0 && c != '\n' && c != '\r'; + c = *in++) + { + *val++ = c; + } + /* Strip whitespace after value */ + do { + *val-- = 0; + } while(val >= kstart && isspace(*val)); + + return 0; +} + +static const char ljIniName[] = "lj.ini"; + +/** + * Finds a FourCC in a list. + * @param needle the fourCC to search for + * @param haystack a list of fourCCs + * @param haystackLen the length of this list + * @return the index within haystack where needle was found + * or -1 if not found + */ +int findFourCC(const char *needle, + const FourCC *haystack, size_t haystackLen) { + for (size_t i = 0; i < haystackLen; ++i) { + if (!strncmp(haystack[i].c, needle, 4)) { + return i; + } + } + return -1; +} + +void loadOption(struct LJPrefs *prefs, + const char *name, const char *value) { + for (int i = 0; i < PC_OPTIONS_MENU_LEN; ++i) { + if (!strncmp(optionsMenu[i].name.c, name, 4)) { + if (isdigit(value[0])) { + unsigned long int n = strtoul(value, NULL, 10); + if (n >= optionsMenu[i].minValue + && n < optionsMenu[i].minValue + optionsMenu[i].nValues) { + prefs->number[i] = n; + } else { + allegro_message("ignoring out of range %s=%lu\n", + name, n); + } + } else if (optionsMenu[i].valueNames) { + int nValueNames = (optionsMenu[i].style == OPTSTYLE_FRAMES + ? 2 + : optionsMenu[i].nValues); + int foundValue = findFourCC(value, optionsMenu[i].valueNames, nValueNames); + if (optionsMenu[i].style == OPTSTYLE_FRAMES + && foundValue > 0) { + foundValue = optionsMenu[i].nValues - 1; + } + prefs->number[i] = foundValue + optionsMenu[i].minValue; + } + return; + } + } +} + +int loadOptions(struct LJPrefs *prefs) { + FILE *fp = ljfopen(ljIniName, "rt"); + char key[1024], var[1024], val[1024], input_buf[1024]; + + if (!fp) return 0; + + key[0] = 0; + var[0] = 0; + val[0] = 0; + + while(fgets (input_buf, sizeof(input_buf), fp)) { + parse_ini_line(input_buf, key, var, val); + + if(!ustrcmp ("Skin", var)) { + ustrzcpy(skinName, sizeof(skinName) - 1, val); + } else { + loadOption(prefs, var, val); + } + } + fclose(fp); + + if (prefs->number[OPTIONS_SIDEWAYS_DELAY] < prefs->number[OPTIONS_SIDEWAYS_SPEED]) { + prefs->number[OPTIONS_SIDEWAYS_DELAY] = prefs->number[OPTIONS_SIDEWAYS_SPEED]; + } + + return 0; +} + +void saveOption(int i, int n, FILE *out) { + char name[8], value[8]; + int nameIdx = -1; + + strncpy(name, optionsMenu[i].name.c, 4); + name[4] = 0; + + if (optionsMenu[i].valueNames) { + if (optionsMenu[i].style == OPTSTYLE_FRAMES) { + if (n == optionsMenu[i].minValue) { + nameIdx = 0; + } else if (n == optionsMenu[i].minValue + optionsMenu[i].nValues - 1) { + nameIdx = 1; + } + } else { + nameIdx = n - optionsMenu[i].minValue; + } + if (nameIdx >= 0 + && optionsMenu[i].valueNames[nameIdx].i == 0) { + nameIdx = -1; + } + } + + if (nameIdx >= 0) { + strncpy(value, optionsMenu[i].valueNames[nameIdx].c, 4); + value[4] = 0; + fprintf(out, "%s=%s\n", + name, value); + } else { + fprintf(out, "%s=%d\n", + name, n); + } +} + +void saveOptions(const struct LJPrefs *prefs) { + FILE *out = ljfopen(ljIniName, "wt"); + + if (out) { + for (int i = 0; i < PC_OPTIONS_MENU_LEN; ++i) { + saveOption(i, prefs->number[i], out); + } + fprintf(out, "Skin=%s\n", + skinName); + fclose(out); + } +} + +void unpackOptions(LJView *v, const struct LJPrefs *prefs) { + unpackCommonOptions(v, prefs->number); + + v->plat->nextAbove = prefs->number[OPTIONS_NEXT_ABOVE]; + v->showTrails = prefs->number[OPTIONS_TRAILS]; + autoPause = prefs->number[OPTIONS_AUTO_PAUSE]; +} + +/******************************************************************** + +Allegro based option drawing code + +********************************************************************/ + +#define OPTIONS_TOP 100 +#define OPTIONS_ROW_HT 40 +#define OPTIONS_ROW_LEFT 80 +#define OPTIONS_ROW_MID 400 +#define OPTIONS_ROW_RIGHT 760 +#define OPTIONS_MENU_VIS 7 +#define OPTIONS_FONT aver32 + + +/** + * @param y number of option to draw + * @param hilite bitfield: 1=focused, 0=not + */ +void optionsDrawRow(const unsigned char *prefs, + int dstY, int line, int value, int hilite) { + unsigned int ht = text_height(OPTIONS_FONT); + int buttonY = OPTIONS_TOP + OPTIONS_ROW_HT * dstY; + int rowBg = bgColor; + const char *nameText; + char altNameText[8]; + char valueText[OPTIONS_VALUE_LEN]; + const char *valueOverride = isDisabledOption(prefs, line); + int textcolor = fgColor; + const char *valueDesc = NULL; + + { + nameText = ljGetFourCCName(optionsMenu[line].name); + if (!nameText) { + strncpy(altNameText, optionsMenu[line].name.c, 4); + altNameText[4] = 0; + nameText = altNameText; + } + } + + if (valueOverride) { + hilite |= 2; + textcolor = makecol(128, 128, 128); + } + + if (hilite == 3) { + rowBg = makecol(204, 204, 204); + } else if (hilite == 1) { + rowBg = hiliteColor; + } + + // If the value of this option is within range, format it as a string. + if (value >= optionsMenu[line].minValue + && value < optionsMenu[line].minValue + optionsMenu[line].nValues) { + if (valueOverride) { + ustrzcpy(valueText, sizeof(valueText), "overridden"); + valueDesc = valueOverride; + } else { + valueDesc = getOptionsValueStr(valueText, line + OPTIONS_OFFSET, value); + } + } else { + valueText[0] = '\0'; + } + + // draw current option + acquire_screen(); + rectfill(screen, + OPTIONS_ROW_LEFT, buttonY, + OPTIONS_ROW_RIGHT - 1, buttonY + OPTIONS_ROW_HT - 1, + rowBg); + textout_ex(screen, OPTIONS_FONT, nameText, + OPTIONS_ROW_LEFT + 8, buttonY + (OPTIONS_ROW_HT - ht) / 2, + textcolor, rowBg); + textout_ex(screen, OPTIONS_FONT, + valueText, + OPTIONS_ROW_MID, buttonY + (OPTIONS_ROW_HT - ht) / 2, + textcolor, rowBg); + + // For an enabled selected item, draw the frame + if (hilite == 1) { + rect(screen, + OPTIONS_ROW_LEFT, buttonY, + OPTIONS_ROW_RIGHT - 1, buttonY + OPTIONS_ROW_HT - 1, + fgColor); + } + + // For a selected item, draw the help text + if (hilite & 1) { + buttonY = OPTIONS_TOP + OPTIONS_ROW_HT * OPTIONS_MENU_VIS; + rectfill(screen, + OPTIONS_ROW_LEFT, buttonY, + OPTIONS_ROW_RIGHT - 1, buttonY + OPTIONS_ROW_HT * 5 / 2 - 1, + bgColor); + + const char *descText = ljGetFourCCDesc(optionsMenu[line].name); + if (descText) { + textout_ex(screen, OPTIONS_FONT, descText, + OPTIONS_ROW_LEFT, + buttonY + OPTIONS_ROW_HT - ht / 2, + textcolor, bgColor); + } + if (valueDesc) { + textout_ex(screen, OPTIONS_FONT, valueDesc, + OPTIONS_ROW_LEFT, + buttonY + 2 * OPTIONS_ROW_HT - ht / 2, + textcolor, bgColor); + } + } + + release_screen(); +} + +void optionsClearRow(int dstY, int hilite) { + int buttonY = OPTIONS_TOP + OPTIONS_ROW_HT * dstY; + rectfill(screen, + OPTIONS_ROW_LEFT, buttonY, + OPTIONS_ROW_RIGHT - 1, buttonY + OPTIONS_ROW_HT * 5 / 2 - 1, + bgColor); +} + +void optionsDrawPage(int page, const unsigned char *prefs) { + int nPages = 0; + for (; optionsPages[nPages].name; ++nPages) { } + + vsync(); + acquire_screen(); + rectfill(screen, 320, 32, SCREEN_W - 1, 63, bgColor); + textprintf_right_ex(screen, aver32, + SCREEN_W - 16, 32, fgColor, -1, + "(%d/%d) %s", + page + 1, nPages, optionsPages[page].name); + + for (int i = optionsPages[page].start; + i < optionsPages[page + 1].start; ++i) { + optionsDrawRow(prefs, i - optionsPages[page].start, + i, prefs[i], 0); + } + for (int i = optionsPages[page + 1].start; + i < optionsPages[page].start + OPTIONS_MENU_VIS; ++i) { + optionsClearRow(i - optionsPages[page].start, 0); + } + release_screen(); +} + +void optionsWinInit(void) { + acquire_screen(); + clear_to_color(screen, bgColor); + textout_ex(screen, aver32, "LOCKJAW > Options", 16, 32, fgColor, -1); + textout_ex(screen, aver32, "Rotate: change page; Up/Down: select", 16, 522, fgColor, -1); + textout_ex(screen, aver32, "Left/Right change; Enter: exit", 16, 552, fgColor, -1); + release_screen(); + +} + +void optionsIdle(void) { + rest(10); +} diff -r 000000000000 -r c84446dfb3f5 src/options.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/options.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,642 @@ +/* options code for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2007-2008 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + + +#include +#include +#include "options.h" +#include "ljplay.h" +#include "ljlocale.h" + +#ifdef HAS_FPU +#define siprintf sprintf +#define MIN_SHADOW 0 +#define N_SHADOWS LJSHADOW_N_STYLES +#define FACTORY_SHADOW 0 +#include "ljpc.h" +#else +#define MIN_SHADOW LJSHADOW_COLORED +#define N_SHADOWS (LJSHADOW_N_STYLES - 2) +#define FACTORY_SHADOW MIN_SHADOW +#endif + +const FourCC optionsBoolNames[2] = { + {"Off"}, {"On"} +}; + +const FourCC gimmickNames[LJGM_N_GIMMICKS] = { + {"Mara"}, {"Line"}, {"Time"}, {"DrlA"}, {"Item"}, {"Keys"} +}; + +const FourCC optionsGarbageNames[LJGARBAGE_N_STYLES] = { + {"Off"}, {"Lv.1"}, {"Lv.2"}, {"Lv.3"}, {"Lv.4"}, + {"HRD"}, {"DrlG"}, {"Zigz"} +}; + +const FourCC optionsScoringNames[LJSCORE_N_STYLES] = { + {"LJ"}, {"Fibo"}, {"HotL"}, {"TDSl"}, {"NESl"}, {"LJns"} +}; + +const FourCC optionsKickLimitNames[N_KICK_LIMITS] = { + {"Off"}, {"1"}, {"2"}, {"3"}, {"4"}, {"5"}, {"Inf"} +}; + +const FourCC optionsSpeedCurveNames[] = { + {"Zero"}, {"Rhy0"}, {"Exp"}, {"Rh20"}, + {"TGM2"}, {"TAPD"}, {"TAD3"}, + {"NESc"}, {"GBc"}, {"GBHL"} +}; + +static const FourCC optionsWindowedNames[2] = { + {"FulS"}, {"Wind"} +}; + +const FourCC optionsShadowNames[LJSHADOW_N_STYLES] = { + {"tl25"}, {"tl50"}, {"tlsC"}, {"tlsM"}, {"Off"}, {"invF"} +}; + +const FourCC optionsLockDelayNames[2] = { + {"SBSC"}, {"Inf"} +}; + +const FourCC optionsSBSCNames[2] = { + {"SBSC"}, {.i=0} +}; + +const FourCC optionsTspinNames[] = { + {"Off"}, {"Imob"}, {"CrnT"}, {"WKT"} +}; + +const FourCC optionsLockdownNames[] = { + {"OLD"}, {"EntR"}, {"StpR"}, {"MovR"} +}; + +const FourCC optionsHoldStyleNames[] = { + {"Off"}, {"HldE"}, {"HldR"}, {"HldN"} +}; + +const FourCC optionsDropScoringNames[LJDROP_N_STYLES] = { + {"None"}, {"ConD"}, {"S1H1"}, {"S1H2"} +}; + +const FourCC optionsGravNames[] = { + {"Naiv"}, {"Stky"}, {"byCo"}, {"Casc"} +}; + +const FourCC optionsPieceSetNames[LJRAND_N_PIECE_SETS] = { + {"IJLO"}, + {"JLOT"}, + {"SZSZ"}, + {"IIII"}, + {"AllP"}, + {"TTTT"} +}; + +const FourCC optionsRandNames[LJRAND_N_RANDS] = { + {"Unif"}, + {"Bag"}, + {"Bag+"}, + {"Bag2"}, + {"Hist"}, + {"His6"} +}; + +const FourCC optionsLineDelayNames[] = { + {"SBSC"}, {.i=0} +}; + +const FourCC optionsZangiNames[] = { + {"Slid"}, {"Lock"}, {"LocU"} +}; + +const FourCC optionsGluingNames[] = { + {"Off"}, {"Squ"}, {"Stky"}, {"byCo"} +}; + +const FourCC optionsRotNames[N_ROTATION_SYSTEMS] = { + {"SRS"}, {"Sega"}, {"ARS"}, {"Tngn"}, + {"NRSR"}, {"NRSL"}, {"TOD4"}, {"TDX"} +}; + + +const OptionsLine commonOptionsMenu[] = { + {{"gimm"}, gimmickNames, + 0, LJGM_N_GIMMICKS, 0 }, + {{"pfw"}, NULL, + 4, LJ_PF_WID - 4 + 1, 10 }, + {{"pfh"}, NULL, + 8, LJ_PF_VIS_HT - 8 + 1, LJ_PF_VIS_HT }, + {{"vzsp"}, optionsBoolNames, + 0, 2, 1 }, + {{"spdc"}, optionsSpeedCurveNames, + 0, LJSPD_N_CURVES, LJSPD_EXP }, + {{"are"}, NULL, + 0, 121, 0, OPTSTYLE_FRAMES }, + {{"piec"}, optionsPieceSetNames, + 0, LJRAND_N_PIECE_SETS, LJRAND_4BLK }, + {{"rand"}, optionsRandNames, + 0, LJRAND_N_RANDS, LJRAND_BAG }, + + {{"hold"}, optionsHoldStyleNames, + 0, LJHOLD_N_STYLES, LJHOLD_EMPTY }, + {{"rots"}, optionsRotNames, + 0, N_ROTATION_SYSTEMS, 0 }, + {{"upkl"}, optionsKickLimitNames, + 0, N_KICK_LIMITS, N_KICK_LIMITS - 1 }, + {{"lock"}, optionsLockdownNames, + 0, LJLOCK_N_STYLES, LJLOCK_MOVE }, + {{"sldt"}, optionsLockDelayNames, + 0, 129, 0, OPTSTYLE_FRAMES }, + {{"deep"}, optionsBoolNames, + 0, 2, 0 }, + + {{"clrd"}, optionsSBSCNames, + 0, 121, 0, OPTSTYLE_FRAMES }, + {{"grav"}, optionsGravNames, + 0, LJGRAV_N_ALGOS, LJGRAV_NAIVE }, + {{"glue"}, optionsGluingNames, + 0, LJGLUING_N_STYLES, LJGLUING_NONE }, + {{"lsco"}, optionsScoringNames, + 0, LJSCORE_N_STYLES }, + {{"dsco"}, optionsDropScoringNames, + 0, LJDROP_N_STYLES, 0 }, + {{"tspn"}, optionsTspinNames, + 0, LJTS_N_ALGOS, LJTS_TDS }, + {{"garb"}, optionsGarbageNames, + 0, LJGARBAGE_N_STYLES, 0 }, + + {{"dasd"}, NULL, + 1, 120, 10, OPTSTYLE_FRAMES }, + {{"dass"}, NULL, + 0, 10, 1, OPTSTYLE_FRAC_G }, + {{"idas"}, optionsBoolNames, + 0, 2, 1 }, + {{"irs"}, optionsBoolNames, + 0, 2, 1 }, + {{"8way"}, optionsBoolNames, + 0, 2, 0 }, + + {{"sfds"}, NULL, + 1, 3, 1, OPTSTYLE_FRAC_G }, + {{"sfdl"}, optionsZangiNames, + 0, LJZANGI_N_STYLES, LJZANGI_SLIDE }, + {{"hrdl"}, optionsZangiNames, + 0, LJZANGI_N_STYLES, LJZANGI_LOCK }, + + {{"tls"}, optionsShadowNames + MIN_SHADOW, + MIN_SHADOW, N_SHADOWS, FACTORY_SHADOW }, + {{"invs"}, optionsBoolNames, + 0, 2, 0 }, + {{"next"}, NULL, + 0, LJ_NEXT_PIECES + 1, 6 }, + {{"srph"}, optionsBoolNames, + 0, 2, 1 }, + +#ifdef HAS_FPU + {{"inpv"}, NULL, + 0, LJ_NEXT_PIECES + 1, 0 }, + {{"mblr"}, optionsBoolNames, + 0, 2, 1 }, + {{"lidp"}, optionsBoolNames, + 0, 2, 1 }, + {{"rec"}, optionsBoolNames, + 0, 2, 0 }, + {{"wndw"}, optionsWindowedNames, + 0, 2, 1 } +#endif +}; + +const OptionsPage optionsPages[] = { + {OPTIONS_GIMMICK, "Game"}, + {OPTIONS_WIDTH, "Rules: Well"}, + {OPTIONS_HOLD_PIECE, "Rules: Movement"}, + {OPTIONS_LINE_DELAY, "Rules: Line clear"}, + {OPTIONS_SIDEWAYS_DELAY, "Control: Movement"}, + {OPTIONS_SOFT_DROP_SPEED, "Control: Drop"}, + {OPTIONS_SHADOW, "Display"}, +#ifdef HAS_FPU + {OPTIONS_MENU_LEN, "PC"}, + {PC_OPTIONS_MENU_LEN, NULL} +#else + {OPTIONS_MENU_LEN, NULL} +#endif +}; + +void setOptionsValueToFourCC(char *dst, FourCC f) { + const char *valueName = ljGetFourCCName(f); + if (valueName) { + strncpy(dst, + valueName, + OPTIONS_VALUE_LEN - 1); + dst[OPTIONS_VALUE_LEN - 1] = 0; + } else { + strncpy(dst, f.c, 4); + dst[4] = 0; + } +} + +struct DisabledOption { + unsigned char name; + unsigned char value; + unsigned char name2; + char reason[45]; +}; + +/* + Semantics: + If option name is set to value, + then gray out option name2 and draw its value as reason. +*/ +#define N_DISABLED_OPTIONS 12 +const struct DisabledOption disabledOptions[N_DISABLED_OPTIONS] = { + { OPTIONS_LOCKDOWN, LJLOCK_NOW, + OPTIONS_SOFT_DROP, "Lockdown is immediate" }, + { OPTIONS_LOCKDOWN, LJLOCK_NOW, + OPTIONS_HARD_DROP, "Lockdown is immediate" }, + { OPTIONS_LOCKDOWN, LJLOCK_NOW, + OPTIONS_LOCK_DELAY, "Lockdown is immediate" }, + { OPTIONS_LOCK_DELAY, 128, + OPTIONS_LOCKDOWN, "Lockdown is manual" }, + { OPTIONS_SPEED_CURVE, LJSPD_DEATH, + OPTIONS_SOFT_DROP_SPEED,"Death: pieces land instantly" }, + { OPTIONS_SPEED_CURVE, LJSPD_RHYTHM, + OPTIONS_SOFT_DROP_SPEED,"Rhythm: pieces land instantly" }, + { OPTIONS_SPEED_CURVE, LJSPD_DEATH, + OPTIONS_SMOOTH_GRAVITY, "Death: pieces land instantly" }, + { OPTIONS_SPEED_CURVE, LJSPD_RHYTHM, + OPTIONS_SMOOTH_GRAVITY, "Rhythm: pieces land instantly" }, + { OPTIONS_SPEED_CURVE, LJSPD_DEATH, + OPTIONS_SOFT_DROP, "Death: pieces land instantly" }, + { OPTIONS_SPEED_CURVE, LJSPD_RHYTHM, + OPTIONS_SOFT_DROP, "Rhythm: pieces land instantly" }, + { OPTIONS_SPEED_CURVE, LJSPD_DEATH, + OPTIONS_HARD_DROP, "Death: pieces land instantly" }, + { OPTIONS_SPEED_CURVE, LJSPD_RHYTHM, + OPTIONS_HARD_DROP, "Rhythm: pieces land instantly" }, +}; + +const char *isDisabledOption(const unsigned char *prefs, int y) { + for (int i = 0; i < N_DISABLED_OPTIONS; ++i) { + if (y == disabledOptions[i].name2) { + int name = disabledOptions[i].name; + int value = disabledOptions[i].value; + + if (prefs[name] == value) { + return disabledOptions[i].reason; + } + } + } + return NULL; +} + + +const char *getOptionsValueStr(char *dst, int line, int value) { + const OptionsLine *l = &(commonOptionsMenu[line]); + FourCC f = {.i = 0}; + const char *desc = NULL; + + switch (l->style) { + case OPTSTYLE_DEFAULT: + if (l->valueNames) { + f = l->valueNames[value - l->minValue]; + } else { + siprintf(dst, "%d", value); + } + break; + + case OPTSTYLE_FRAMES: + if (l->valueNames + && value == l->minValue + && l->valueNames[0].i) { + + // override first with name 0 + f = l->valueNames[0]; + } else if (l->valueNames + && value == l->minValue + + l->nValues - 1 + && l->valueNames[1].i) { + + // override second with name 1 + f = l->valueNames[1]; + } else { + if (value >= 60) { + int ds = value / 6; + int s = ds / 10; + ds -= s * 10; + siprintf(dst, "%d/60 s (%d.%d s)", value, s, ds); + } else { + int ms = value * 50 / 3; + siprintf(dst, "%d/60 s (%d ms)", value, ms); + } + } break; + + case OPTSTYLE_FRAC_G: + if (value > 6) { + int dHz = 600 / value; + int Hz = dHz / 10; + dHz -= Hz * 10; + siprintf(dst, "1/%dG (%d.%d Hz)", value, Hz, dHz); + } else if (value > 0) { + if (value > 1) { + dst[0] = '1'; + dst[1] = '/'; + dst += 2; + } + siprintf(dst, "%dG (%d Hz)", value, 60 / value); + } else { + strcpy(dst, "Instant"); + } + break; + + default: + strncpy(dst, "Unknown option style.", OPTIONS_VALUE_LEN - 1); + dst[OPTIONS_VALUE_LEN - 1] = 0; + break; + } + + /* If we have a fourCC, use it. */ + if (f.i != 0) { + setOptionsValueToFourCC(dst, f); + desc = ljGetFourCCDesc(f); + } + return desc; +} + +void unpackCommonOptions(LJView *v, const unsigned char *prefs) { + if (prefs[OPTIONS_GIMMICK] < 255) + v->field->gimmick = prefs[OPTIONS_GIMMICK]; + if (prefs[OPTIONS_WIDTH] < 255) { + int width = prefs[OPTIONS_WIDTH]; + v->field->leftWall = (LJ_PF_WID - width) / 2; + v->field->rightWall = v->field->leftWall + width; + } + if (prefs[OPTIONS_HEIGHT] < 255) + v->field->ceiling = prefs[OPTIONS_HEIGHT]; + if (prefs[OPTIONS_ENTER_ABOVE] < 255) + v->field->enterAbove = prefs[OPTIONS_ENTER_ABOVE]; + if (prefs[OPTIONS_SPEED_CURVE] < 255) + v->field->speedState.curve = prefs[OPTIONS_SPEED_CURVE]; + if (prefs[OPTIONS_ENTRY_DELAY] < 255) + v->field->areStyle = prefs[OPTIONS_ENTRY_DELAY]; + if (prefs[OPTIONS_PIECE_SET] < 255) + v->field->pieceSet = prefs[OPTIONS_PIECE_SET]; + if (prefs[OPTIONS_RANDOMIZER] < 255) + v->field->randomizer = prefs[OPTIONS_RANDOMIZER]; + + if (prefs[OPTIONS_ROTATION_SYSTEM] < 255) + v->field->rotationSystem = prefs[OPTIONS_ROTATION_SYSTEM]; + if (prefs[OPTIONS_FLOOR_KICKS] < 255) + v->field->maxUpwardKicks = prefs[OPTIONS_FLOOR_KICKS] == N_KICK_LIMITS - 1 + ? 128 + : prefs[OPTIONS_FLOOR_KICKS]; + if (prefs[OPTIONS_HOLD_PIECE] < 255) + v->field->holdStyle = prefs[OPTIONS_HOLD_PIECE]; + if (prefs[OPTIONS_LOCKDOWN] < 255) + v->field->lockReset = prefs[OPTIONS_LOCKDOWN]; + if (prefs[OPTIONS_LOCK_DELAY] < 255) + v->field->setLockDelay = prefs[OPTIONS_LOCK_DELAY]; + if (prefs[OPTIONS_BOTTOM_BLOCKS] < 255) + v->field->bottomBlocks = prefs[OPTIONS_BOTTOM_BLOCKS]; + + if (prefs[OPTIONS_LINE_DELAY] < 255) + v->field->setLineDelay = prefs[OPTIONS_LINE_DELAY]; + if (prefs[OPTIONS_T_SPIN] < 255) + v->field->tSpinAlgo = prefs[OPTIONS_T_SPIN]; + if (prefs[OPTIONS_CLEAR_GRAVITY] < 255) + v->field->clearGravity = prefs[OPTIONS_CLEAR_GRAVITY]; + if (prefs[OPTIONS_GLUING] < 255) + v->field->gluing = prefs[OPTIONS_GLUING]; + if (prefs[OPTIONS_SCORING] < 255) + v->field->scoreStyle = prefs[OPTIONS_SCORING]; + if (prefs[OPTIONS_DROP_SCORING] < 255) + v->field->dropScoreStyle = prefs[OPTIONS_DROP_SCORING]; + if (prefs[OPTIONS_GARBAGE] < 255) + v->field->garbageStyle = prefs[OPTIONS_GARBAGE]; + + if (prefs[OPTIONS_SIDEWAYS_DELAY] < 255) + v->control->dasDelay = prefs[OPTIONS_SIDEWAYS_DELAY]; + if (prefs[OPTIONS_SIDEWAYS_SPEED] < 255) + v->control->dasSpeed = prefs[OPTIONS_SIDEWAYS_SPEED]; + if (v->control->dasDelay < v->control->dasSpeed) { + v->control->dasDelay = v->control->dasSpeed; + } + if (prefs[OPTIONS_INITIAL_SIDEWAYS] < 255) + v->control->initialDAS = prefs[OPTIONS_INITIAL_SIDEWAYS]; + if (prefs[OPTIONS_IRS] < 255) + v->control->initialRotate = prefs[OPTIONS_IRS]; + if (prefs[OPTIONS_DIAGONAL_MOTION] < 255) + v->control->allowDiagonals = prefs[OPTIONS_DIAGONAL_MOTION]; + if (prefs[OPTIONS_SOFT_DROP_SPEED] < 255) + v->control->softDropSpeed = prefs[OPTIONS_SOFT_DROP_SPEED] - 1; + if (prefs[OPTIONS_SOFT_DROP] < 255) + v->control->softDropLock = prefs[OPTIONS_SOFT_DROP]; + if (prefs[OPTIONS_HARD_DROP] < 255) + v->control->hardDropLock = prefs[OPTIONS_HARD_DROP]; + + if (prefs[OPTIONS_SHADOW] < 255) + v->hideShadow = prefs[OPTIONS_SHADOW]; + if (prefs[OPTIONS_HIDE_PF] < 255) + v->hidePF = prefs[OPTIONS_HIDE_PF]; + if (prefs[OPTIONS_NEXT_PIECES] < 255) + v->nextPieces = prefs[OPTIONS_NEXT_PIECES]; + if (prefs[OPTIONS_SMOOTH_GRAVITY] < 255) + v->smoothGravity = prefs[OPTIONS_SMOOTH_GRAVITY]; +} + +void initOptions(unsigned char *prefs) { + for (int i = 0; i < OPTIONS_MENU_LEN; ++i) { + prefs[i] = commonOptionsMenu[i].startValue; + } +} + +LJBits menuReadPad(void); +void vsync(void); + +void options(LJView *v, unsigned char *prefs) { + int page = 0, y = 0; + int redraw = 2; + int done = 0; + LJBits lastKeys = ~0; + int dasDir = 0; + int dasCounter = 0; + const OptionsLine const *optionsMenu = commonOptionsMenu; + int lastClock = getTime(); + int erase = -1; + + optionsWinInit(); + + while (!done) { + if (redraw) { + if (redraw == 2) { + redraw = 1; + optionsDrawPage(page, prefs); + } + if (redraw == 1) { + if (erase >= 0) { + vsync(); + optionsDrawRow(prefs, + erase - optionsPages[page].start, + erase, prefs[erase], 0); + erase = -1; + } + optionsDrawRow(prefs, + y - optionsPages[page].start, + y, prefs[y], 1); + redraw = 0; + } + } else { + optionsIdle(); + } + + LJBits keys = menuReadPad(); + LJBits sounds = 0; + int lastY = y; + LJBits newKeys = keys & ~lastKeys; + LJBits dasKeys = 0; + + if (getTime() != lastClock) { + // Handle DAS within options (fixed at 250 ms 30 Hz) + lastClock = getTime(); + if (keys & dasDir) { + ++dasCounter; + if (dasCounter >= 15) { + dasCounter -= 2; + dasKeys = dasDir; + } + } else { + dasCounter = 0; + } + } + + if (newKeys & VKEY_UP + || ((dasKeys & VKEY_UP) + && y > optionsPages[page].start)) { + dasDir = VKEY_UP; + if (y <= 0) { + while (optionsPages[page].name) { + ++page; + } + y = optionsPages[page].start - 1; + } else { + --y; + } + } + if (newKeys & VKEY_DOWN + || ((dasKeys & VKEY_DOWN) + && y < optionsPages[page + 1].start - 1)) { + dasDir = VKEY_DOWN; + ++y; + if (y >= optionsPages[page + 1].start + && !optionsPages[page + 1].name) { + y = 0; + } + } + + if (!isDisabledOption(prefs, y)) { + if ((newKeys | dasKeys) & VKEY_RIGHT) { + int num = prefs[y] + 1; + + if (num >= optionsMenu[y].minValue + optionsMenu[y].nValues) { + prefs[y] = optionsMenu[y].minValue; + } else { + prefs[y] = num; + } + + sounds |= LJSND_ROTATE; + // XXX: need to redraw the whole box (redraw = 2) + // if options have become enabled or disabled + redraw = 1; + dasDir = VKEY_RIGHT; + } + + if ((newKeys | dasKeys) & VKEY_LEFT) { + int num = prefs[y] - 1; + + if (num < optionsMenu[y].minValue) { + prefs[y] = optionsMenu[y].minValue + optionsMenu[y].nValues - 1; + } else { + prefs[y] = num; + } + + sounds |= LJSND_ROTATE; + redraw = 1; + dasDir = VKEY_LEFT; + } + } + + // Rotate left: Go to the top of the previous page if it exists. + if (newKeys & VKEY_ROTL) { + if (page > 0) { + y = optionsPages[page - 1].start; + } else { + y = 0; + } + } + + // Rotate right: If on last page, finish; + // otherwise, go to the top of the next page. + if (newKeys & VKEY_ROTR) { + if (!optionsPages[page + 1].name) { + done = 1; + } else { + y = optionsPages[page + 1].start; + } + } + + // Start: finish + if (newKeys & VKEY_START) { + done = 1; + } + + if (lastY != y) { + sounds |= LJSND_SHIFT; + + // calculate which page the cursor has moved to + int lastPage = page; + while (y < optionsPages[page].start) { + --page; + } + while (y >= optionsPages[page + 1].start) { + ++page; + } + + if (lastPage == page) { + erase = lastY; + if (redraw < 1) { + redraw = 1; + } + } else { + // turning the page + sounds |= LJSND_HOLD; + redraw = 2; // redraw the whole screen + } + } + lastKeys = keys; + + if (done) { + sounds |= LJSND_LINE; + } + playSoundEffects(v, sounds, 100); + } +} diff -r 000000000000 -r c84446dfb3f5 src/options.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/options.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,152 @@ +/* options code for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2007 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#ifndef OPTIONS_H +#define OPTIONS_H + +#include "lj.h" +#include "ljcontrol.h" +#include "ljlocale.h" + +typedef struct OptionsLine { + FourCC name; + const FourCC *valueNames; + unsigned char minValue; + unsigned char nValues; + unsigned char startValue; + unsigned char style; + // const char *desc; +} OptionsLine; + +/* + * OPTSTYLE_DEFAULT and no valueNames: + * Render value as a number. + * OPTSTYLE_DEFAULT and valueNames: + * Render value as valueNames[value - minValue] + * OPTSTYLE_FRAMES and no valueNames: + * Render value as a number and as a number times 50/3. + * OPTSTYLE_FRAMES and no valueNames: + * Similar, except override value == minValue with valueNames[0] + * and value == minValue + nValues - 1 with valueNames[1]. + */ +enum { + OPTSTYLE_DEFAULT, // names, or numbers + OPTSTYLE_FRAMES, + OPTSTYLE_FRAC_G +}; + +typedef struct OptionsPage { + unsigned int start; + const char *name; +} OptionsPage; + +#define N_KICK_LIMITS 7 +extern const OptionsLine commonOptionsMenu[]; +extern const OptionsPage optionsPages[]; +extern const FourCC optionsRotNames[N_ROTATION_SYSTEMS]; +extern const FourCC optionsLockdownNames[]; +extern const FourCC gimmickNames[LJGM_N_GIMMICKS]; +extern const FourCC optionsPieceSetNames[]; +extern const FourCC optionsBoolNames[2]; +extern const FourCC optionsZangiNames[]; +extern const FourCC optionsGluingNames[]; +extern const FourCC optionsRandNames[]; +extern const FourCC optionsTspinNames[LJTS_N_ALGOS]; +extern const FourCC optionsGravNames[]; +extern const FourCC optionsSpeedCurveNames[]; +extern const FourCC optionsGarbageNames[]; +extern const FourCC optionsHoldStyleNames[]; +extern const FourCC optionsScoringNames[LJSCORE_N_STYLES]; +extern const FourCC optionsDropScoringNames[LJDROP_N_STYLES]; +extern const FourCC gimmickNames[LJGM_N_GIMMICKS]; +extern const FourCC optionsSideNames[]; +extern const FourCC optionsNextStyleNames[]; +extern const FourCC optionsShadowNames[]; +extern const FourCC optionsDASDelayNames[]; + + +enum { + OPTIONS_GIMMICK, + + OPTIONS_WIDTH, + OPTIONS_HEIGHT, + OPTIONS_ENTER_ABOVE, + OPTIONS_SPEED_CURVE, + OPTIONS_ENTRY_DELAY, + OPTIONS_PIECE_SET, + OPTIONS_RANDOMIZER, + + OPTIONS_HOLD_PIECE, + OPTIONS_ROTATION_SYSTEM, + OPTIONS_FLOOR_KICKS, + OPTIONS_LOCKDOWN, + OPTIONS_LOCK_DELAY, + OPTIONS_BOTTOM_BLOCKS, + + OPTIONS_LINE_DELAY, + OPTIONS_CLEAR_GRAVITY, + OPTIONS_GLUING, + OPTIONS_SCORING, + OPTIONS_DROP_SCORING, + OPTIONS_T_SPIN, + OPTIONS_GARBAGE, + + OPTIONS_SIDEWAYS_DELAY, + OPTIONS_SIDEWAYS_SPEED, + OPTIONS_INITIAL_SIDEWAYS, + OPTIONS_IRS, + OPTIONS_DIAGONAL_MOTION, + OPTIONS_SOFT_DROP_SPEED, + OPTIONS_SOFT_DROP, + OPTIONS_HARD_DROP, + + OPTIONS_SHADOW, + OPTIONS_HIDE_PF, + OPTIONS_NEXT_PIECES, + OPTIONS_SMOOTH_GRAVITY, + + OPTIONS_MENU_LEN +}; + +/** + * Builds a string representing the value of an option. + * @param dst 32-byte buffer to hold this string + * @param line index into optionsMenu + * @param value the (numeric) value of the option + * @return the description of this value, or NULL if unknown + */ +const char *getOptionsValueStr(char *dst, int line, int value); +#define OPTIONS_VALUE_LEN 33 + +void unpackCommonOptions(LJView *v, const unsigned char *prefs); +void initOptions(unsigned char *prefs); +const char *isDisabledOption(const unsigned char *prefs, int y); + +void optionsWinInit(void); +void optionsDrawPage(int scroll, const unsigned char *prefs); +void optionsDrawRow(const unsigned char *prefs, + int dstY, int line, int value, int hilite); +void optionsIdle(void); + +#endif diff -r 000000000000 -r c84446dfb3f5 src/pcdebrief.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pcdebrief.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,72 @@ +#include "ljpc.h" + +void debriefDrawPage(const char *page, size_t pageNumber) { + int y = 40; + char line[256]; + int linePos = 0; + int done = 0; + + acquire_screen(); + clear_to_color(screen, bgColor); + textout_centre_ex(screen, aver32, "GAME OVER", + SCREEN_W / 2, y, fgColor, -1); + textprintf_right_ex(screen, aver32, + SCREEN_W - 40, y, fgColor, -1, + "Page %u", (unsigned int)pageNumber + 1); + textout_centre_ex(screen, aver32, + "Left/Right: Change; Rotate: close", + SCREEN_W / 2, SCREEN_H - 40, fgColor, -1); + + while (!done) { + int c = *page++; + + // Break at newline and at end of text + if (c == '\n' || c == 0) { + + // Draw blank and parenthetical lines in smaller font + const FONT *f = (linePos == 0 || line[0] == '(') + ? aver16 + : aver32; + int lineH = text_height(f); + + // Terminate the line of text and print it + line[linePos] = 0; + textout_ex(screen, f, line, 40, y, fgColor, -1); + + linePos = 0; // Carriage return + y += lineH * 6 / 5; // Line feed + } else { + if (linePos + 2 < sizeof(line)) { + line[linePos++] = c; + } + } + if (c == 0) { + done = 1; + } + } + release_screen(); +} + +extern volatile char redrawWholeScreen; + +LJBits debriefHandleKeys(void) { + int keys = menuReadPad(); + + while (keypressed()) { + int scancode; + ureadkey(&scancode); + + if (scancode == KEY_PRTSCR) { + saveScreen(-1); + } + } + + if (wantsClose) { + keys |= VKEY_ROTL; + } + + if (!(keys & (VKEY_ROTL | VKEY_ROTR))) { + rest(30); + } + return keys; +} diff -r 000000000000 -r c84446dfb3f5 src/pcjoy.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pcjoy.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,503 @@ +/* PC joystick code + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#include "pcjoy.h" +#include +#include +#include "ljpath.h" + +extern const FONT *aver32, *aver16; +extern int bgColor, fgColor, hiliteColor; + +static volatile int lastScancodePressed = -1; +static void (*oldKeyListener)(int scancode); +volatile int wantsClose = 0; +void ezPlaySample(const char *filename, int vol); + +static void ljpcKeyListener(int scancode) { + if (!(scancode & 0x80)) { + lastScancodePressed = scancode; + } + if (oldKeyListener) { + oldKeyListener(scancode); + } +} END_OF_FUNCTION(ljpcKeyListener); + + +/** + * Presses the escape key when the user clicks the close box. + */ +static void setWantsClose() { + wantsClose = 1; +} + +static int withJoystick = 0; + +#define N_VKEYS 14 +#define N_PLAYERS 2 + +static const char keysFileName[] = "lj-keys.043"; + +static const struct pkeyMapping defaultKeymappings[N_PLAYERS][N_VKEYS] = { + { + // [0]: player 1 + {-1, -1, KEY_UP}, + {-1, -1, KEY_DOWN}, + {-1, -1, KEY_LEFT}, + {-1, -1, KEY_RIGHT}, + {-1, -1, KEY_Z}, + {-1, -1, KEY_X}, + {-1, -1, KEY_S}, + {-1, -1, KEY_SPACE}, + {-1, -1, KEY_C}, + {-1, -1, KEY_W}, + {-1, -1, KEY_Q}, + {-1, -1, KEY_E}, + {-1, -1, KEY_ENTER}, + {-1, -1, KEY_D} + }, + { + // [1]: player 2 + {-1, -1, KEY_UP}, + {-1, -1, KEY_DOWN}, + {-1, -1, KEY_LEFT}, + {-1, -1, KEY_RIGHT}, + {-1, -1, KEY_Z}, + {-1, -1, KEY_X}, + {-1, -1, KEY_S}, + {-1, -1, KEY_SPACE}, + {-1, -1, KEY_C}, + {-1, -1, KEY_W}, + {-1, -1, KEY_Q}, + {-1, -1, KEY_E}, + {-1, -1, KEY_ENTER}, + {-1, -1, KEY_D} + } +}; + +static struct pkeyMapping keymappings[N_PLAYERS][N_VKEYS]; + +void getPkeyName(char *dst, int j, int s, int a) { + if (j < 0) { + if (a >= 0 && a < KEY_MAX) { + usprintf(dst, "Key %d (%s)", a, scancode_to_name(a)); + } else { + usprintf(dst, "Key %d (???"")", a); + } + } else if (s < 0) { + usprintf(dst, + "Joy %d button %s", + j, + joy[j].button[a].name); + } else if (a < 0) { + usprintf(dst, + "Joy %d stick %s axis %s -", + j, + joy[j].stick[s].name, + joy[j].stick[s].axis[~a].name); + } else { + usprintf(dst, + "Joy %d stick %s axis %s +", + j, + joy[j].stick[s].name, + joy[j].stick[s].axis[a].name); + } +} + +static int getPkeyState(int j, int s, int a) { + int k; + if (j < 0) { + k = key[a]; + } else if (s < 0) { + k = joy[j].button[a].b; + } else if (a < 0) { + k = joy[j].stick[s].axis[~a].d1; + } else { + k = joy[j].stick[s].axis[a].d2; + } + return k; +} + +const char *const vkeyNames[] = { + "Hard Drop (Up)", + "Soft Drop (Down)", + "Left", + "Right", + "Rotate Left", + "Rotate Right", + "Hold", + "Item (unused)", + "Alt. Rotate Left", + "Rotate Left Twice", + "Far Left", + "Far Right", + "Alt. Firm Drop", + "Alt. Hold", + "Macro G", + "Macro H" +}; + +static int getVkeyState(int player, int vkey) { + int j = keymappings[player][vkey].joy; + int s = keymappings[player][vkey].stick; + int a = keymappings[player][vkey].axis; + + return getPkeyState(j, s, a); +} + +LJBits readPad(unsigned int player) { + int keys = 0; + poll_joystick(); + + for (int i = 0; + i < N_VKEYS; + ++i) { + if (getVkeyState(player, i)) { + keys |= 1 << i; + } + } + return keys; +} + +LJBits menuReadPad(void) { + if (key[KEY_ENTER]) { + return VKEY_ROTR | VKEY_START; + } else if (key[KEY_ESC]) { + return VKEY_ROTL; + } else if (key[KEY_UP]) { + return VKEY_UP; + } else if (key[KEY_DOWN]) { + return VKEY_DOWN; + } else if (key[KEY_LEFT]) { + return VKEY_LEFT; + } else if (key[KEY_RIGHT]) { + return VKEY_RIGHT; + } else { + return readPad(0) | readPad(1); + } +} + +// These contain the states of ALL buttons on ALL +// joysticks +static LJBits lastConfigButtons[8]; +static LJBits lastConfigStickAxis[8]; + +static int newButton(int *outJ, int *outS, int *outA) { + poll_joystick(); + int found = 0; + + if (lastScancodePressed >= 0) { + *outJ = -1; + *outS = -1; + *outA = lastScancodePressed; + lastScancodePressed = -1; + return 1; + } + + for (int j = 0; + j < num_joysticks; + ++j) { + LJBits cur = 0; + + for (int b = 0; b < joy[j].num_buttons; ++b) { + if (joy[j].button[b].b) { + if (!(lastConfigButtons[j] & (1 << b)) && !found) { + *outJ = j; + *outS = -1; + *outA = b; + found = 1; + } + cur |= 1 << b; + } + } + lastConfigButtons[j] = cur; + } + if (found) { + return 1; + } + + for (int j = 0; + j < num_joysticks; + ++j) { + LJBits cur = 0; + LJBits mask = 1; + + for (int s = 0; s < joy[j].num_sticks; ++s) { + for (int a = 0; a < joy[j].stick[s].num_axis; ++a) { + if (joy[j].stick[s].axis[a].d1) { + if (!(lastConfigStickAxis[j] & mask) && !found) { + *outJ = j; + *outS = s; + *outA = ~a; + found = 1; + } + cur |= mask; + } + mask <<= 1; + if (joy[j].stick[s].axis[a].d2) { + if (!(lastConfigStickAxis[j] & mask) && !found) { + *outJ = j; + *outS = s; + *outA = a; + found = 1; + } + cur |= mask; + } + mask <<= 1; + } + } + lastConfigStickAxis[j] = cur; + } + return found; +} + +static void clearNewButton(void) { + int j, s, a; + while (newButton(&j, &s, &a)); +} + +void loadKeys(const char *filename) { + FILE *fp = ljfopen(filename, "rb"); + + memcpy(keymappings, defaultKeymappings, sizeof(keymappings)); + if (fp) { + for (unsigned int player = 0; player < 2; ++player) { + for (unsigned int vkey = 0; + vkey < N_VKEYS; + ++vkey) { + int j = fgetc(fp); + int s = fgetc(fp); + int a = fgetc(fp); + + if (a == EOF) { + break; + } + keymappings[player][vkey].joy = j; + keymappings[player][vkey].stick = s; + keymappings[player][vkey].axis = a; + } + } + fclose(fp); + } +} + +void saveKeys(const char *filename) { + FILE *fp = ljfopen(filename, "wb"); + + if (fp) { + for (unsigned int player = 0; player < 2; ++player) { + for (unsigned int vkey = 0; + vkey < N_VKEYS && !feof(fp); + ++vkey) { + fputc(keymappings[player][vkey].joy, fp); + fputc(keymappings[player][vkey].stick, fp); + fputc(keymappings[player][vkey].axis, fp); + } + } + fclose(fp); + } +} + +#define VKEY_ROWHT 24 +#define VKEY_TOP 120 + +void drawVkeyRow(int vkey, int hilite) { + char name[256]; + int y = VKEY_TOP + vkey * VKEY_ROWHT; + + rectfill(screen, + 16, y, 719, y + VKEY_ROWHT - 1, + hilite ? hiliteColor : bgColor); + textout_ex(screen, aver16, vkeyNames[vkey], 24, y + 4, fgColor, -1); + for (int player = 0; player < N_PLAYERS; ++player) { + if (player + 1 == hilite) { + rect(screen, + 240 + 240 * player, y, + 479 + 240 * player, y + VKEY_ROWHT - 1, + fgColor); + } + getPkeyName(name, + keymappings[player][vkey].joy, + keymappings[player][vkey].stick, + keymappings[player][vkey].axis); + textout_ex(screen, aver16, name, 240 + 240 * player, y + 4, fgColor, -1); + } +} + +/** + * Waits for a key or button press. If any key but Esc is pressed, + * reassigns the vkey. Otherwise, does nothing. + * @param vkey the index of the vkey to reassign + * @return 0 if key not changed; nonzero if key was changed + */ +static int changeKey(int player, int vkey) { + int changed = 0; + int phase = 0; + + clearNewButton(); + while (!changed) { + int j, s, a; + + if (phase == 5) { + drawVkeyRow(vkey, 0); + } else if (phase == 0) { + drawVkeyRow(vkey, player + 1); + phase = 15; + } + --phase; + + if (keypressed()) { + int scancode; + ureadkey(&scancode); + if (scancode == KEY_ESC) { + changed = -1; + } + } + if (vkey < N_VKEYS && newButton(&j, &s, &a)) { + if (j >= 0 || s > 0 || a != KEY_ESC) { + keymappings[player][vkey].joy = j; + keymappings[player][vkey].stick = s; + keymappings[player][vkey].axis = a; + ezPlaySample("nextS_wav", 128); + changed = 1; + } else { + changed = -1; + } + } + if (wantsClose) { + changed = -1; + } + + rest(30); + } + + // Draw new vkey value + drawVkeyRow(vkey, 0); + return changed > 0; +} + +void configureKeys(void) { + clear_to_color(screen, bgColor); + textout_ex(screen, aver32, "LOCKJAW > Game Keys", 16, 32, fgColor, -1); + textout_ex(screen, aver16, + "Use arrow keys to select a key. To reassign the selected key, press Enter", + 40, 80, fgColor, -1); + textout_ex(screen, aver16, + "and then the key you want to use. When done, press Esc.", + 40, 96, fgColor, -1); + + // Draw each vkey's name + for (int vkey = 0; vkey < N_VKEYS; ++vkey) { + drawVkeyRow(vkey, 0); + } + + clearNewButton(); + int vkey = 0; + int player = 0; + + drawVkeyRow(vkey, player + 1); + + while (vkey >= 0 && !wantsClose) { + int scancode = 0; + + rest(30); + if (keypressed()) { + ureadkey(&scancode); + } + + switch (scancode) { + + case KEY_RIGHT: + if (player < N_PLAYERS - 1) { + ezPlaySample("shift_wav", 128); + drawVkeyRow(vkey, 0); + ++player; + drawVkeyRow(vkey, player + 1); + } + break; + + case KEY_LEFT: + if (player > 0) { + ezPlaySample("shift_wav", 128); + drawVkeyRow(vkey, 0); + --player; + drawVkeyRow(vkey, player + 1); + } + break; + + case KEY_UP: + if (vkey > 0) { + ezPlaySample("shift_wav", 128); + drawVkeyRow(vkey, 0); + --vkey; + drawVkeyRow(vkey, player + 1); + } + break; + case KEY_DOWN: + if (vkey < N_VKEYS - 1) { + ezPlaySample("shift_wav", 128); + drawVkeyRow(vkey, 0); + ++vkey; + drawVkeyRow(vkey, player + 1); + } + break; + case KEY_ENTER: + ezPlaySample("rotate_wav", 96); + while (vkey < N_VKEYS && changeKey(player, vkey)) { + rest(150); + ++vkey; + } + if (vkey >= N_VKEYS) { + ezPlaySample("land_wav", 128); + vkey = N_VKEYS - 1; + } else { + ezPlaySample("nextO_wav", 128); + } + drawVkeyRow(vkey, player + 1); + break; + case KEY_ESC: + vkey = -1; + break; + } + } + saveKeys(keysFileName); + ezPlaySample("line_wav", 128); +} + + +void initKeys(void) { + LOCK_FUNCTION(ljpcKeyListener); + LOCK_VARIABLE(lastScancodePressed); + LOCK_VARIABLE(oldKeyListener); + LOCK_FUNCTION(setWantsClose); + LOCK_VARIABLE(wantsClose); + + oldKeyListener = keyboard_lowlevel_callback; + keyboard_lowlevel_callback = ljpcKeyListener; + withJoystick = !install_joystick(JOY_TYPE_AUTODETECT); + loadKeys(keysFileName); + set_close_button_callback(setWantsClose); +} + + diff -r 000000000000 -r c84446dfb3f5 src/pcjoy.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pcjoy.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,70 @@ +/* PC joystick code + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#ifndef PCJOY_H +#define PCJOY_H + +#include "ljtypes.h" +#include "ljcontrol.h" + + +/* Physical key definitions + +m.joy < 0: + key[m.axis] +m.joy >= 0, m.stick < 0: + joy[m.joy].button[m.axis].b +m.joy >= 0, m.stick >= 0, m.axis < 0: + joy[m.joy].stick[m.stick].axis[~m.axis].d1 +m.joy >= 0, m.stick >= 0, m.axis >= 0: + joy[m.joy].stick[m.stick].axis[m.axis].d2 +*/ + +struct pkeyMapping { + signed char joy; + signed char stick; + signed char axis; +}; + +extern volatile int wantsClose; + +void initKeys(void); + +/** + * Reads the physical keys to produce a set of pressed virtual keys. + */ +LJBits readPad(unsigned int player); + +/** + * Reads the physical keys to produce a set of pressed virtual keys, + * hardcoding keyboard Up, Down, Left, Right, Esc, and Enter + * to the appropriate vkeys. + */ +LJBits menuReadPad(void); + +void getPkeyName(char *dst, int j, int s, int a); +extern const char *const vkeyNames[]; +void configureKeys(void); + +#endif diff -r 000000000000 -r c84446dfb3f5 src/pcsound.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pcsound.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,108 @@ +#include "ljpc.h" +extern const DATAFILE *sound_dat; +extern char withSound; + +static const unsigned short shiftPitches[16] = { + 630, 667, 707, 749, 794, 841, 891, 944, + 1000, 1059, 1122, 1189, 1260, 1335, 1424, 1498 +}; + +/** + * Plays a sample from datafile by name, with the specified volume + * and the specified rate scale factor, panned center, without loop. + * @param filename name of object in datafile + * @param vol scale factor for volume (0-255); if set to 0, + * stops all voices playing the sample + * @param pitch scale factor for playback rate (1000 = normal; 2000 = double speed) + */ +static void playPitchSample(const char *filename, int vol, int pitch) { + if (withSound && sound_dat) { + const DATAFILE *entry = find_datafile_object(sound_dat, filename); + + if (entry) { + if (vol < 1) { + stop_sample(entry->dat); + } else { + play_sample(entry->dat, vol, 128, pitch, 0); + } + } + } +} + +void ezPlaySample(const char *filename, int vol) { + playPitchSample(filename, vol, 1000); +} + +void playSampleForTetromino(int piece) { + static const char tetrominoNames[] = "IJLOSTZ22V"; + + piece &= LJP_MASK; + if (piece >= 0 && piece < 10) { + char filename[32]; + usprintf(filename, "next%c_wav", tetrominoNames[piece]); + ezPlaySample(filename, 128); + } +} + +void playSoundEffects(LJView *v, LJBits sounds, int countdown) { + // Handle sound + if ((sounds & LJSND_SPAWN) + && !(v->hideNext)) { + playSampleForTetromino(v->field->curPiece[1]); + } + if (sounds & LJSND_HOLD) { + ezPlaySample("hold_wav", 128); + } + if (sounds & LJSND_ROTATE) { + ezPlaySample("rotate_wav", 128); + } + if (sounds & LJSND_IRS) { + ezPlaySample("irs_wav", 128); + } + if (sounds & LJSND_SQUARE) { + ezPlaySample("square_wav", 128); + } + if (sounds & LJSND_SHIFT) { + int x = v->plat->skin->shiftScale + ? v->field->x + 8 - (LJ_PF_WID - 4) / 2 + : 8; + int pitch = shiftPitches[x]; + playPitchSample("shift_wav", 128, pitch); + } + if (sounds & LJSND_LAND) { + ezPlaySample("land_wav", 192); + } + if (sounds & LJSND_LOCK) { + ezPlaySample("lock_wav", 192); + } + if (sounds & LJSND_LINE) { + ezPlaySample("line_wav", 128); + } + if (sounds & LJSND_SECTIONUP) { + ezPlaySample("sectionup_wav", 128); + } + + if (v->plat->b2bcd1 > 0) { + if (--v->plat->b2bcd1 == 0) { + playPitchSample("line_wav", 148, 1200); + } + } + if (sounds & LJSND_SETB2B) { + v->plat->b2bcd1 = 10; + } + if (v->plat->b2bcd2 > 0) { + if (--v->plat->b2bcd2 == 0) { + playPitchSample("line_wav", 168, 1440); + } + } + if (sounds & LJSND_B2B) { + v->plat->b2bcd2 = 20; + } + if (countdown < v->control->countdown) { + char name[32]; + usprintf(name, "count_%d_wav", countdown); + ezPlaySample(name, 128); + } + LJMusic_poll(v->plat->skin->bgm); +} + diff -r 000000000000 -r c84446dfb3f5 src/pin8gba_sound.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pin8gba_sound.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,117 @@ +/* + * pin8gba_sound.h + * Header file for GBA sound registers + */ + + +/* Copyright 2001-2006 Damian Yerrick + +(insert zlib license here) + +*/ + + +/* Why the funny register names? + +The register names do not match the names in the official GBA +documentation. I choose the names that seem most logical to me. +In addition, I do slick macro tricks with the VRAM addresses for +maps and the like and with registers that control timers, DMA, +and backgrounds. The gba.h that comes with wintermute's libgba +incorporates some but not all of these tricks. In addition, +the names in gba.h for the sound registers aren't descriptive +at all. + +*/ + +#ifndef PIN8GBA_SOUND_H +#ifdef __cplusplus +extern "C" { +#endif +#define PIN8GBA_SOUND_H + + +#define DMGSNDCTRL (*(volatile u16 *)0x04000080) +#define DMGSNDCTRL_LVOL(x) (x) +#define DMGSNDCTRL_RVOL(x) ((x) << 4) +#define DMGSNDCTRL_LSQR1 0x0100 +#define DMGSNDCTRL_LSQR2 0x0200 +#define DMGSNDCTRL_LTRI 0x0400 +#define DMGSNDCTRL_LNOISE 0x0800 +#define DMGSNDCTRL_RSQR1 0x1000 +#define DMGSNDCTRL_RSQR2 0x2000 +#define DMGSNDCTRL_RTRI 0x4000 +#define DMGSNDCTRL_RNOISE 0x8000 + +#define DSOUNDCTRL (*(volatile u16 *)0x04000082) +#define DSOUNDCTRL_DMG25 0x0000 +#define DSOUNDCTRL_DMG50 0x0001 +#define DSOUNDCTRL_DMG100 0x0002 +#define DSOUNDCTRL_A50 0x0000 +#define DSOUNDCTRL_A100 0x0004 +#define DSOUNDCTRL_B50 0x0000 +#define DSOUNDCTRL_B100 0x0008 +#define DSOUNDCTRL_AR 0x0100 +#define DSOUNDCTRL_AL 0x0200 +#define DSOUNDCTRL_ATIMER(x) ((x) << 10) +#define DSOUNDCTRL_ARESET 0x0400 +#define DSOUNDCTRL_BR 0x1000 +#define DSOUNDCTRL_BL 0x2000 +#define DSOUNDCTRL_BTIMER(x) ((x) << 14) +#define DSOUNDCTRL_BRESET 0x8000 + +#define SNDSTAT (*(volatile u16*)0x04000084) +#define SNDSTAT_SQR1 0x0001 +#define SNDSTAT_SQR2 0x0002 +#define SNDSTAT_TRI 0x0004 +#define SNDSTAT_NOISE 0x0008 +#define SNDSTAT_ENABLE 0x0080 + +#define SNDBIAS (*(volatile u16 *)0x04000088) +#define SETSNDRES(x) SNDBIAS = (SNDBIAS & 0x3fff) | (x << 14) + +#define DSOUND_FIFOA (*(volatile u32 *)0x040000a0) +#define DSOUND_FIFOB (*(volatile u32 *)0x040000a4) + + +#define SQR1SWEEP (*(volatile u16 *)0x04000060) +#define SQR1SWEEP_OFF 0x0008 + +#define SQR1CTRL (*(volatile u16 *)0x04000062) +#define SQR2CTRL (*(volatile u16 *)0x04000068) +#define NOISECTRL (*(volatile u16 *)0x04000078) +#define SQR_DUTY(n) ((n) << 6) +#define SQR_VOL(n) ((n) << 12) + +#define SQR1FREQ (*(volatile u16 *)0x04000064) +#define SQR2FREQ (*(volatile u16 *)0x0400006c) +#define TRIFREQ (*(volatile u16 *)0x04000074) +#define FREQ_HOLD 0x0000 +#define FREQ_TIMED 0x4000 +#define FREQ_RESET 0x8000 + +#define NOISEFREQ (*(volatile u16 *)0x0400007c) +#define NOISEFREQ_127 0x0008 +#define NOISEFREQ_OCT(x) ((x) << 4) + +#define TRICTRL (*(volatile u16 *)0x04000070) +#define TRICTRL_2X32 0x0000 +#define TRICTRL_1X64 0x0020 +#define TRICTRL_BANK(x) ((x) << 6) +#define TRICTRL_ENABLE 0x0080 + +#define TRILENVOL (*(volatile u16 *)0x04000072) +#define TRILENVOL_LEN(x) (256 - (x)) +#define TRILENVOL_MUTE 0x0000 +#define TRILENVOL_25 0x6000 +#define TRILENVOL_50 0x4000 +#define TRILENVOL_75 0x8000 +#define TRILENVOL_100 0x2000 + +#define TRIWAVERAM ((volatile u32 *)0x04000090) + + +#ifdef __cplusplus +} +#endif +#endif diff -r 000000000000 -r c84446dfb3f5 src/random.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/random.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,253 @@ +/* Piece randomizers for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006-2007 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#include "lj.h" + +// Order matters. The history randomizers prefer to give out the +// front half early in the game. You don't want to give an S or Z +// first, or you force a slide. You also don't want to give an O +// first, or you force a slide with OS or OZ. + +static const signed char piecesNoI[] = { + 1, 2, 5, // front half: J L T + 3, 4, 6 // back half: O S Z +}; + +static const signed char piecesTetrominoes[] = { + 0, 1, 2, 5, // front half: I J L T + 3, 4, 6 // back half: O S Z +}; + +static const signed char piecesWithSmall[] = { + 0, 1, 2, 5, 9, // front half: I J L T L3 + 3, 7, 8, 4, 6 // back half: O I2 I3 S Z + // make sure that the S and Z are in the back half +}; + +static const signed char piecesSZ[] = { + 4, 6 +}; + +static const signed char piecesI[] = { + 0 +}; + +static const signed char piecesT[] = { + 5 +}; + +static const signed char *const pieceSets[] = { + piecesTetrominoes, + piecesNoI, + piecesSZ, + piecesI, + piecesWithSmall, + piecesT +}; + +static const unsigned char pieceSetSizes[] = { + sizeof(piecesTetrominoes), + sizeof(piecesNoI), + sizeof(piecesSZ), + sizeof(piecesI), + sizeof(piecesWithSmall), + sizeof(piecesT) +}; + +static void bagInitRandomize(LJField *p) { + p->permuPhase = 0; +} + +static unsigned int bagRandomize(LJField *p) { + unsigned int setNo = p->pieceSet; + unsigned int len = pieceSetSizes[setNo]; + const signed char *set = pieceSets[setNo]; + + int piece; + + // If we're out of permutation pieces, make new ones by copying + // from one of the rand#Bag arrays until we hit the -1 sentinel. + if (p->permuPhase == 0) { + int pos; + + for (pos = 0; pos < len; ++pos) { + p->permuPiece[pos] = set[pos]; + p->permuPiece[pos + len] = set[pos]; + } + + // Double bag: Add one randomly chosen piece to the bag + if (p->randomizer == LJRAND_2BAG) { + pos *= 2; + } + // 7+1 Bag (TOJ style): Add one randomly chosen piece to the bag + else if (p->randomizer == LJRAND_BAGPLUS1) { + p->permuPiece[pos++] = set[ljRand(p) % len]; + } + p->permuPhase = pos; + } + + // Choose a position in the remainder of the deck + int r = (p->permuPhase > 1) ? ljRand(p) % p->permuPhase : 0; + + // Swap the top card with the card at this position + piece = p->permuPiece[r]; + p->permuPiece[r] = p->permuPiece[--p->permuPhase]; + return piece; +} + +static void mtbInitRandomize(LJField *p) { + unsigned int setNo = p->pieceSet; + unsigned int len = pieceSetSizes[setNo]; + const signed char *set = pieceSets[setNo]; + + // At game start, front three are I, J, L + // Back four (never dealt as first tetromino) are O, S, T, Z + for (int i = 0; i < len; ++i) { + p->permuPiece[i] = set[i]; + } +} + +static unsigned int mtbRandomize(LJField *p) { + unsigned int setNo = p->pieceSet; + unsigned int len = pieceSetSizes[setNo]; + + // Choose a piece from the three in front + int r = ljRand(p) % (len / 2); + int piece = p->permuPiece[r]; + + // Move it to the back + for (; r < len - 1; ++r) { + p->permuPiece[r] = p->permuPiece[r + 1]; + } + p->permuPiece[len - 1] = piece; + return piece; +} + +static void tgmInitRandomize(LJField *p) { + unsigned int setNo = p->pieceSet; + unsigned int len = pieceSetSizes[setNo]; + + p->permuPiece[0] = 0; + for (int i = len / 2; i < len; ++i) { + p->permuPiece[i] = i & 1 ? LJP_Z : LJP_S; + } +} + +/* State of TGM randomizer: + * permuPiece[3..6]: history, [3] most recent + * permuPiece[2]: 0 for first piece (use equal probability I, J, L, T) + * or 1 for subsequent pieces + */ +static unsigned int tgmRandomize(LJField *p) { + unsigned int setNo = p->pieceSet; + unsigned int len = pieceSetSizes[setNo]; + const signed char *set = pieceSets[setNo]; + + int r = ljRand(p) ^ (ljRand(p) << 15); + int piece; + + if (p->permuPiece[0]) { + + // Roll up to 6 times for pieces + for (int rolls = 6; + rolls > 0; + --rolls, r /= len) { + piece = set[r % len]; + int found = 0; + + // If the piece is not in the history, use it now + for (int histoPos = len / 2; + !found && histoPos < len; + ++histoPos) { + if (piece == p->permuPiece[histoPos]) { + found = 1; + } + } + if (!found) { + break; + } + } + } else { + p->permuPiece[0] = 1; + // Generate only pieces in the first half of the list + piece = set[r % ((len + 1) / 2)]; + if (piece == LJP_O) { + piece = LJP_T; + } + } + + // Move it to the back + for (r = len / 2; r < len - 1; ++r) { + p->permuPiece[r] = p->permuPiece[r + 1]; + } + p->permuPiece[len - 1] = piece; + return piece; +} + +void initRandomize(LJField *p) { + unsigned int setNo = p->pieceSet; + unsigned int len = pieceSetSizes[setNo]; + + if (len < 2) { + p->randomizer = LJRAND_PURE; + } + + switch (p->randomizer) { + case LJRAND_PURE: + break; + case LJRAND_BAG: + case LJRAND_BAGPLUS1: + case LJRAND_2BAG: + bagInitRandomize(p); + break; + case LJRAND_HIST_INF: + mtbInitRandomize(p); + break; + case LJRAND_HIST_6: + tgmInitRandomize(p); + break; + } +} + +unsigned int randomize(LJField *p) { + switch (p->randomizer) { + + case LJRAND_BAG: + case LJRAND_BAGPLUS1: + case LJRAND_2BAG: + return bagRandomize(p); + case LJRAND_HIST_INF: + return mtbRandomize(p); + case LJRAND_HIST_6: + return tgmRandomize(p); + case LJRAND_PURE: + default: + { + unsigned int setNo = p->pieceSet; + unsigned int len = pieceSetSizes[setNo]; + const signed char *set = pieceSets[setNo]; + return set[ljRand(p) % len]; + } + } +} diff -r 000000000000 -r c84446dfb3f5 src/scenario.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/scenario.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,339 @@ +/* Scenario code for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2008 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#include "scenario.h" +#include "options.h" +#include + +#define END_OF_PRESET {OPTIONS_MENU_LEN} + +const Preset predefPresets[] = { + {"Guideline", { + {OPTIONS_WIDTH, 10}, + {OPTIONS_HEIGHT, 20}, + {OPTIONS_ENTRY_DELAY, 0}, + {OPTIONS_PIECE_SET, LJRAND_4BLK}, + {OPTIONS_RANDOMIZER, LJRAND_BAG}, + {OPTIONS_HOLD_PIECE, LJHOLD_EMPTY}, + {OPTIONS_ROTATION_SYSTEM, LJROT_SRS}, + {OPTIONS_FLOOR_KICKS, 6}, + {OPTIONS_LOCKDOWN, LJLOCK_MOVE}, + {OPTIONS_LOCK_DELAY, 30}, + {OPTIONS_BOTTOM_BLOCKS, 0}, + {OPTIONS_LINE_DELAY, 30}, + {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE}, + {OPTIONS_GLUING, 0}, + {OPTIONS_SCORING, LJSCORE_TDS}, + {OPTIONS_DROP_SCORING, LJDROP_1S_2H}, + {OPTIONS_T_SPIN, LJTS_TDS}, + END_OF_PRESET + }}, + + {"Classic", { + {OPTIONS_GIMMICK, LJGM_ATYPE}, + {OPTIONS_WIDTH, 10}, + {OPTIONS_HEIGHT, 20}, + {OPTIONS_ENTER_ABOVE, 0}, + {OPTIONS_ENTRY_DELAY, 10}, + {OPTIONS_SPEED_CURVE, LJSPD_NES}, + {OPTIONS_PIECE_SET, LJRAND_4BLK}, + {OPTIONS_RANDOMIZER, LJRAND_PURE}, + {OPTIONS_HOLD_PIECE, LJHOLD_NONE}, + {OPTIONS_ROTATION_SYSTEM, LJROT_NES}, + {OPTIONS_FLOOR_KICKS, 0}, + {OPTIONS_LOCKDOWN, LJLOCK_NOW}, + {OPTIONS_LOCK_DELAY, 0}, + {OPTIONS_BOTTOM_BLOCKS, 0}, + {OPTIONS_LINE_DELAY, 30}, + {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE}, + {OPTIONS_GLUING, 0}, + {OPTIONS_SCORING, LJSCORE_NES}, + {OPTIONS_DROP_SCORING, LJDROP_NES}, + {OPTIONS_GARBAGE, LJGARBAGE_NONE}, + {OPTIONS_SIDEWAYS_DELAY, 15}, + {OPTIONS_SIDEWAYS_SPEED, 6}, + {OPTIONS_SOFT_DROP_SPEED, 2}, + {OPTIONS_INITIAL_SIDEWAYS, 1}, + {OPTIONS_IRS, 0}, + {OPTIONS_NEXT_PIECES, 1}, + END_OF_PRESET + }}, + + /* http://www.tetrisconcept.com/forum/viewtopic.php?t=892 */ + {"Master", { + {OPTIONS_GIMMICK, LJGM_ATYPE}, + {OPTIONS_WIDTH, 10}, + {OPTIONS_HEIGHT, 20}, + {OPTIONS_ENTER_ABOVE, 0}, + {OPTIONS_SPEED_CURVE, LJSPD_TGM}, + {OPTIONS_ENTRY_DELAY, 30}, + {OPTIONS_PIECE_SET, LJRAND_4BLK}, + {OPTIONS_RANDOMIZER, LJRAND_HIST_6}, + {OPTIONS_HOLD_PIECE, LJHOLD_NONE}, + {OPTIONS_ROTATION_SYSTEM, LJROT_ARIKA}, + {OPTIONS_FLOOR_KICKS, 0}, + {OPTIONS_LOCKDOWN, LJLOCK_STEP}, + {OPTIONS_LOCK_DELAY, 30}, + {OPTIONS_BOTTOM_BLOCKS, 0}, + {OPTIONS_LINE_DELAY, 0}, + {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE}, + {OPTIONS_GLUING, LJGLUING_STICKY}, + {OPTIONS_GARBAGE, LJGARBAGE_NONE}, + {OPTIONS_SIDEWAYS_DELAY, 16}, + {OPTIONS_SIDEWAYS_SPEED, 1}, + {OPTIONS_INITIAL_SIDEWAYS, 0}, + {OPTIONS_IRS, 1}, + {OPTIONS_SOFT_DROP_SPEED, 1}, + {OPTIONS_SOFT_DROP, LJZANGI_LOCK}, + {OPTIONS_HARD_DROP, LJZANGI_SLIDE}, + {OPTIONS_NEXT_PIECES, 1}, + END_OF_PRESET + }}, + + /* http://www.tetrisconcept.com/forum/viewtopic.php?t=892 */ + {"T.A. Death", { + {OPTIONS_GIMMICK, LJGM_ATYPE}, + {OPTIONS_SPEED_CURVE, LJSPD_DEATH}, + {OPTIONS_WIDTH, 10}, + {OPTIONS_HEIGHT, 20}, + {OPTIONS_ENTER_ABOVE, 0}, + {OPTIONS_ENTRY_DELAY, 30}, + {OPTIONS_PIECE_SET, LJRAND_4BLK}, + {OPTIONS_RANDOMIZER, LJRAND_HIST_6}, + {OPTIONS_HOLD_PIECE, LJHOLD_NONE}, + {OPTIONS_ROTATION_SYSTEM, LJROT_ARIKA}, + {OPTIONS_FLOOR_KICKS, 0}, + {OPTIONS_LOCKDOWN, LJLOCK_STEP}, + {OPTIONS_LOCK_DELAY, 30}, + {OPTIONS_BOTTOM_BLOCKS, 0}, + {OPTIONS_LINE_DELAY, 0}, + {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE}, + {OPTIONS_GLUING, LJGLUING_STICKY}, + {OPTIONS_GARBAGE, LJGARBAGE_NONE}, + {OPTIONS_SIDEWAYS_DELAY, 16}, + {OPTIONS_SIDEWAYS_SPEED, 1}, + {OPTIONS_INITIAL_SIDEWAYS, 0}, + {OPTIONS_IRS, 1}, + {OPTIONS_NEXT_PIECES, 1}, + END_OF_PRESET + }}, + + {"Square", { + {OPTIONS_GIMMICK, LJGM_ATYPE}, + {OPTIONS_WIDTH, 10}, + {OPTIONS_HEIGHT, 20}, + {OPTIONS_ENTER_ABOVE, 1}, + {OPTIONS_ENTRY_DELAY, 30}, + {OPTIONS_SPEED_CURVE, LJSPD_EXP}, + {OPTIONS_PIECE_SET, LJRAND_4BLK}, + {OPTIONS_RANDOMIZER, LJRAND_PURE}, + {OPTIONS_HOLD_PIECE, LJHOLD_TNT}, + {OPTIONS_ROTATION_SYSTEM, LJROT_SRS}, + {OPTIONS_FLOOR_KICKS, 6}, + {OPTIONS_LOCKDOWN, LJLOCK_STEP}, + {OPTIONS_LOCK_DELAY, 30}, + {OPTIONS_BOTTOM_BLOCKS, 0}, + {OPTIONS_LINE_DELAY, 40}, + {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE}, + {OPTIONS_GLUING, LJGLUING_SQUARE}, + {OPTIONS_SCORING, LJSCORE_TNT64}, + {OPTIONS_DROP_SCORING, LJDROP_NOSCORE}, + {OPTIONS_T_SPIN, LJTS_TNT}, + {OPTIONS_GARBAGE, LJGARBAGE_NONE}, + {OPTIONS_SIDEWAYS_DELAY, 11}, + {OPTIONS_SIDEWAYS_SPEED, 5}, + {OPTIONS_SOFT_DROP_SPEED, 2}, + {OPTIONS_SOFT_DROP, LJZANGI_SLIDE}, + {OPTIONS_HARD_DROP, LJZANGI_SLIDE}, + {OPTIONS_INITIAL_SIDEWAYS, 0}, + {OPTIONS_IRS, 0}, + {OPTIONS_NEXT_PIECES, 3}, + END_OF_PRESET + }}, + + /* http://www.tetrisconcept.com/forum/viewtopic.php?t=549 */ + {"40 Lines", { + {OPTIONS_GIMMICK, LJGM_BTYPE}, + {OPTIONS_WIDTH, 10}, + {OPTIONS_PIECE_SET, LJRAND_4BLK}, + {OPTIONS_BOTTOM_BLOCKS, 0}, + {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE}, + {OPTIONS_GARBAGE, 0}, + END_OF_PRESET + }}, + + /* http://www.tetrisconcept.com/forum/viewtopic.php?t=549 */ + {"180 Seconds", { + {OPTIONS_GIMMICK, LJGM_ULTRA}, + {OPTIONS_WIDTH, 10}, + {OPTIONS_PIECE_SET, LJRAND_4BLK}, + {OPTIONS_HOLD_PIECE, LJHOLD_EMPTY}, + {OPTIONS_BOTTOM_BLOCKS, 0}, + {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE}, + {OPTIONS_GLUING, 0}, + {OPTIONS_SCORING, LJSCORE_LJ}, + {OPTIONS_DROP_SCORING, 0}, + {OPTIONS_T_SPIN, 0}, + {OPTIONS_GARBAGE, 0}, + END_OF_PRESET + }}, + + /* http://www.tetrisconcept.com/forum/viewtopic.php?t=892 */ + {"Death 300", { + {OPTIONS_GIMMICK, LJGM_ULTRA}, + {OPTIONS_SPEED_CURVE, LJSPD_DEATH300}, + {OPTIONS_WIDTH, 10}, + {OPTIONS_HEIGHT, 20}, + {OPTIONS_ENTER_ABOVE, 0}, + {OPTIONS_ENTRY_DELAY, 30}, + {OPTIONS_PIECE_SET, LJRAND_4BLK}, + {OPTIONS_RANDOMIZER, LJRAND_HIST_6}, + {OPTIONS_HOLD_PIECE, LJHOLD_EMPTY}, + {OPTIONS_ROTATION_SYSTEM, LJROT_ARIKA}, + {OPTIONS_FLOOR_KICKS, 0}, + {OPTIONS_LOCKDOWN, LJLOCK_STEP}, + {OPTIONS_LOCK_DELAY, 30}, + {OPTIONS_BOTTOM_BLOCKS, 0}, + {OPTIONS_LINE_DELAY, 0}, + {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE}, + {OPTIONS_GLUING, LJGLUING_STICKY}, + {OPTIONS_GARBAGE, LJGARBAGE_NONE}, + {OPTIONS_SIDEWAYS_DELAY, 16}, + {OPTIONS_SIDEWAYS_SPEED, 1}, + {OPTIONS_INITIAL_SIDEWAYS, 0}, + {OPTIONS_IRS, 1}, + {OPTIONS_NEXT_PIECES, 1}, + END_OF_PRESET + }}, + + {"M-Roll", { + {OPTIONS_GIMMICK, LJGM_BTYPE}, + {OPTIONS_SPEED_CURVE, LJSPD_DEATH300}, + {OPTIONS_WIDTH, 10}, + {OPTIONS_HEIGHT, 20}, + {OPTIONS_ENTER_ABOVE, 0}, + {OPTIONS_ENTRY_DELAY, 30}, + {OPTIONS_PIECE_SET, LJRAND_4BLK}, + {OPTIONS_RANDOMIZER, LJRAND_HIST_6}, + {OPTIONS_HOLD_PIECE, LJHOLD_EMPTY}, + {OPTIONS_ROTATION_SYSTEM, LJROT_ARIKA}, + {OPTIONS_FLOOR_KICKS, 0}, + {OPTIONS_LOCKDOWN, LJLOCK_STEP}, + {OPTIONS_LOCK_DELAY, 30}, + {OPTIONS_BOTTOM_BLOCKS, 0}, + {OPTIONS_LINE_DELAY, 0}, + {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE}, + {OPTIONS_GLUING, LJGLUING_STICKY}, + {OPTIONS_GARBAGE, LJGARBAGE_NONE}, + {OPTIONS_SIDEWAYS_DELAY, 16}, + {OPTIONS_SIDEWAYS_SPEED, 1}, + {OPTIONS_INITIAL_SIDEWAYS, 0}, + {OPTIONS_IRS, 1}, + {OPTIONS_NEXT_PIECES, 1}, + {OPTIONS_HIDE_PF, 1}, + END_OF_PRESET + }}, + + /* http://www.tetrisconcept.com/forum/viewtopic.php?t=549 */ + {"Cascade", { + {OPTIONS_GIMMICK, LJGM_BTYPE}, + {OPTIONS_WIDTH, 10}, + {OPTIONS_PIECE_SET, LJRAND_4BLK}, + {OPTIONS_HOLD_PIECE, LJHOLD_EMPTY}, + {OPTIONS_CLEAR_GRAVITY, LJGRAV_CASCADE}, + {OPTIONS_GARBAGE, 0}, + END_OF_PRESET + }}, + + /* http://www.tetrisconcept.com/forum/viewtopic.php?t=777 */ + {"Baboo!", { + {OPTIONS_GIMMICK, LJGM_BABY}, + {OPTIONS_WIDTH, 10}, + {OPTIONS_PIECE_SET, LJRAND_4BLK}, + {OPTIONS_SPEED_CURVE, LJSPD_ZERO}, + {OPTIONS_BOTTOM_BLOCKS, 0}, + {OPTIONS_CLEAR_GRAVITY, LJGRAV_NAIVE}, + {OPTIONS_GLUING, 0}, + {OPTIONS_SCORING, LJSCORE_LJ}, + {OPTIONS_DROP_SCORING, 0}, + {OPTIONS_T_SPIN, LJTS_TDS}, + {OPTIONS_GARBAGE, 0} + }}, + +}; + +const Preset *loadedPresets = predefPresets; +size_t nLoadedPresets = sizeof(predefPresets)/sizeof(predefPresets[0]); + +static unsigned char presetBuffer[OPTIONS_MENU_LEN]; + +void presetStart(void) { + memset(&presetBuffer, 255, sizeof(presetBuffer)); +} + +void presetAdd(size_t which) { + if (which >= nLoadedPresets) { + return; + } + const PresetRule *r = loadedPresets[which].rules; + for (size_t i = 0; + i < PRESET_MAX_RULES + && r[i].line < OPTIONS_MENU_LEN; + ++i) { + presetBuffer[r[i].line] = r[i].value; + } +} + +void presetFinish(struct LJView *v) { + unpackCommonOptions(v, presetBuffer); +} + +#if 0 +void loadPresetsFromText(FILE *fp) { + Preset *presets = NULL; + + size_t capacity = 0, n = 0; + + { + // allocate memory for this preset + if (capacity <= n) { + if (capacity < 32) { + capacity += 16; + } else { + capacity += capacity / 2; + } + Preset *newPresets = realloc(presets, + capacity * sizeof(preset)); + // if we can't allocate memory, screw it + if (!newPresets) { + break; + } + } + } + + loadedPresets = presets; + nLoadedPresets = n; +} +#endif + diff -r 000000000000 -r c84446dfb3f5 src/scenario.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/scenario.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,60 @@ +/* PC preset code for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2008 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#ifndef PCPRESET_H +#define PCPRESET_H +#include +#include "ljcontrol.h" + +typedef struct PresetRule { + unsigned char line, value; +} PresetRule; + +#define PRESET_NAME_LEN 32 +#define PRESET_MAX_RULES 32 + +typedef struct Preset { + char name[PRESET_NAME_LEN]; + PresetRule rules[PRESET_MAX_RULES]; +} Preset; + +/** + * Resets all options in the preset buffer to inherit. + */ +void presetStart(void); + +/** + * Adds a preset to the preset buffer. + */ +void presetAdd(size_t which); + +/** + * Unpacks the preset buffer onto a view. + */ +void presetFinish(struct LJView *v); + +extern const Preset *loadedPresets; +extern size_t nLoadedPresets; + +#endif diff -r 000000000000 -r c84446dfb3f5 src/speed.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/speed.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,431 @@ +/* Speed curve tables for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006-2007 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ +#include "lj.h" +#include "ljcontrol.h" + +static const LJFixed initialGravity[LJSPD_N_CURVES] = { + [LJSPD_RHYTHMZERO] = 0, + [LJSPD_RHYTHM] = LJITOFIX(20) +}; + +/** + * The default speed curve for each gimmick. A negative value + * means that the gimmick uses the player's chosen speed curve. + */ +static const signed char defaultSpeedCurve[LJGM_N_GIMMICKS] = { + [LJGM_ATYPE] = -1, + [LJGM_BTYPE] = -1, + [LJGM_ULTRA] = -1, + [LJGM_DRILL] = -1, + [LJGM_ITEMS] = -1, + [LJGM_BABY] = LJSPD_ZERO +}; + +/* The new speed curve code ****************************************/ + +#define SCGRAV(n, d) ((n) * 2048 / (d)) + +enum { + SPEEDTYPE_RHYTHM, + SPEEDTYPE_TGM, + SPEEDTYPE_LINES, + SPEEDTYPE_PIECES +}; + +typedef struct LJSpeedStep { + unsigned short level, gravity; + signed char are, das, lock, line; +} LJSpeedStep; + +typedef struct LJSpeedCurve { + char name[32]; + unsigned char type, sectionLen; + unsigned char nSteps; + const LJSpeedStep steps[]; +} LJSpeedCurve; + +const LJSpeedCurve scMaster = { + "Master", + SPEEDTYPE_TGM, 100, + 33, + { + { 0, SCGRAV( 4,256),25,16,30,40}, + { 30, SCGRAV( 6,256),25,16,30,40}, + { 35, SCGRAV( 8,256),25,16,30,40}, + { 40, SCGRAV( 10,256),25,16,30,40}, + { 50, SCGRAV( 12,256),25,16,30,40}, + { 60, SCGRAV( 16,256),25,16,30,40}, + { 70, SCGRAV( 32,256),25,16,30,40}, + { 80, SCGRAV( 48,256),25,16,30,40}, + { 90, SCGRAV( 64,256),25,16,30,40}, + {100, SCGRAV( 80,256),25,16,30,40}, + {120, SCGRAV( 96,256),25,16,30,40}, + {140, SCGRAV(112,256),25,16,30,40}, + {160, SCGRAV(128,256),25,16,30,40}, + {170, SCGRAV(144,256),25,16,30,40}, + {200, SCGRAV( 4,256),25,16,30,40}, + {220, SCGRAV( 32,256),25,16,30,40}, + {230, SCGRAV( 64,256),25,16,30,40}, + {233, SCGRAV( 96,256),25,16,30,40}, + {236, SCGRAV(128,256),25,16,30,40}, + {239, SCGRAV(160,256),25,16,30,40}, + {243, SCGRAV(192,256),25,16,30,40}, + {247, SCGRAV(224,256),25,16,30,40}, + {251, SCGRAV( 1,1), 25,16,30,40}, + {300, SCGRAV( 2,1), 25,16,30,40}, + {330, SCGRAV( 3,1), 25,16,30,40}, + {360, SCGRAV( 4,1), 25,16,30,40}, + {400, SCGRAV( 5,1), 25,16,30,40}, + {450, SCGRAV( 3,1), 25,16,30,40}, + {500, SCGRAV(20,1), 25,10,30,25}, + {600, SCGRAV(20,1), 25,10,30,16}, + {700, SCGRAV(20,1), 16,10,30,12}, + {800, SCGRAV(20,1), 12,10,30, 6}, + {900, SCGRAV(20,1), 12, 8,17, 6} + } +}; + +const LJSpeedCurve scDeath = { + "Death", + SPEEDTYPE_TGM, 100, + 6, + { + { 0, SCGRAV(20,1), 18,12,30, 8}, + {100, SCGRAV(20,1), 14,12,26, 0}, + {200, SCGRAV(20,1), 14,11,22, 0}, + {300, SCGRAV(20,1), 8,10,18, 6}, + {400, SCGRAV(20,1), 7, 8,15, 5}, + {500, SCGRAV(20,1), 6, 8,15, 4} + } +}; + +const LJSpeedCurve scNES = { + "NES", + SPEEDTYPE_LINES, 10, + 15, + { + { 0, SCGRAV(1,48), 10,-1, 0,30}, + { 10, SCGRAV(1,43), 10,-1, 0,30}, + { 20, SCGRAV(1,38), 10,-1, 0,30}, + { 30, SCGRAV(1,33), 10,-1, 0,30}, + { 40, SCGRAV(1,28), 10,-1, 0,30}, + { 50, SCGRAV(1,23), 10,-1, 0,30}, + { 60, SCGRAV(1,18), 10,-1, 0,30}, + { 70, SCGRAV(1,13), 10,-1, 0,30}, + { 80, SCGRAV(1, 8), 10,-1, 0,30}, + { 90, SCGRAV(1, 6), 10,-1, 0,30}, + {100, SCGRAV(1, 5), 10,-1, 0,30}, + {130, SCGRAV(1, 4), 10,-1, 0,30}, + {160, SCGRAV(1, 3), 10,-1, 0,30}, + {190, SCGRAV(1, 2), 10,-1, 0,30}, + {290, SCGRAV(1, 1), 10,-1, 0,30}, + } +}; + +const LJSpeedCurve scGB = { + "Game Boy", + SPEEDTYPE_LINES, 10, + 18, + { + { 0, SCGRAV(1,53), 0,-1, 0,90}, + { 10, SCGRAV(1,49), 0,-1, 0,90}, + { 20, SCGRAV(1,45), 0,-1, 0,90}, + { 30, SCGRAV(1,41), 0,-1, 0,90}, + { 40, SCGRAV(1,37), 0,-1, 0,90}, + { 50, SCGRAV(1,33), 0,-1, 0,90}, + { 60, SCGRAV(1,28), 0,-1, 0,90}, + { 70, SCGRAV(1,22), 0,-1, 0,90}, + { 80, SCGRAV(1,17), 0,-1, 0,90}, + { 90, SCGRAV(1,11), 0,-1, 0,90}, + {100, SCGRAV(1,10), 0,-1, 0,90}, + {110, SCGRAV(1, 9), 0,-1, 0,90}, + {120, SCGRAV(1, 8), 0,-1, 0,90}, + {130, SCGRAV(1, 7), 0,-1, 0,90}, + {140, SCGRAV(1, 6), 0,-1, 0,90}, + {160, SCGRAV(1, 5), 0,-1, 0,90}, + {180, SCGRAV(1, 4), 0,-1, 0,90}, + {200, SCGRAV(1, 3), 0,-1, 0,90}, + } +}; + +const LJSpeedCurve scZero = { + "Zero", + SPEEDTYPE_LINES, 10, + 1, + { + { 0, SCGRAV(0, 1),-1,-1,40,-1} + } +}; + +const LJSpeedCurve scExponential = { + "Exponential", + SPEEDTYPE_PIECES, 60, + 34, + { + { 0, SCGRAV( 1,60),-1,-1,40,-1 }, + { 30, SCGRAV( 1,42),-1,-1,40,-1 }, + { 60, SCGRAV( 2,60),-1,-1,40,-1 }, + { 90, SCGRAV( 2,42),-1,-1,40,-1 }, + {120, SCGRAV( 4,60),-1,-1,40,-1 }, + {150, SCGRAV( 4,42),-1,-1,40,-1 }, + {180, SCGRAV( 8,60),-1,-1,40,-1 }, + {210, SCGRAV( 8,42),-1,-1,40,-1 }, + {240, SCGRAV( 16,60),-1,-1,40,-1 }, + {270, SCGRAV( 16,42),-1,-1,40,-1 }, + {300, SCGRAV( 32,60),-1,-1,40,-1 }, + {330, SCGRAV( 32,42),-1,-1,40,-1 }, + {360, SCGRAV( 64,60),-1,-1,40,-1 }, + {390, SCGRAV( 64,42),-1,-1,40,-1 }, + {420, SCGRAV(128,60),-1,-1,40,-1 }, + {450, SCGRAV(128,42),-1,-1,40,-1 }, + {480, SCGRAV(256,60),-1,-1,40,-1 }, + {510, SCGRAV(256,42),-1,-1,40,-1 }, + {540, SCGRAV(512,60),-1,-1,40,-1 }, + {570, SCGRAV(512,42),-1,-1,40,-1 }, + {600, SCGRAV( 20, 1),-1,-1,40,-1 }, + {630, SCGRAV( 20, 1),-1,-1,30,-1 }, + {660, SCGRAV( 20, 1),-1,-1,24,-1 }, + {690, SCGRAV( 20, 1),-1,-1,20,-1 }, + {720, SCGRAV( 20, 1),-1,-1,17,-1 }, + {750, SCGRAV( 20, 1),-1,-1,15,-1 }, + {780, SCGRAV( 20, 1),-1,-1,13,-1 }, + {810, SCGRAV( 20, 1),-1,-1,12,-1 }, + {840, SCGRAV( 20, 1),-1,-1,11,-1 }, + {870, SCGRAV( 20, 1),-1,-1,10,-1 }, + {900, SCGRAV( 20, 1),-1,-1, 9,-1 }, + {930, SCGRAV( 20, 1),-1,-1, 8,-1 }, + {960, SCGRAV( 20, 1),-1,-1, 7,-1 }, + {990, SCGRAV( 20, 1),-1,-1, 6,-1 } + } +}; + +static const LJSpeedCurve *const newSpeedCurves[LJSPD_N_CURVES] = { + [LJSPD_EXP] = &scExponential, + [LJSPD_ZERO] = &scZero, + [LJSPD_TGM] = &scMaster, + [LJSPD_DEATH] = &scDeath, + [LJSPD_DEATH300] = &scDeath, + [LJSPD_NES] = &scNES, + [LJSPD_GB] = &scGB, + [LJSPD_GBHEART] = &scGB, +}; + +/** + * Performs a fast binary search of a speed step table. + */ +static const LJSpeedStep *getSpeedStep(const LJSpeedStep *steps, + size_t nSteps, int level) { + unsigned int lo = 0; + unsigned int hi = nSteps; + + while (hi - lo > 1) { + size_t mid = (hi + lo) / 2; + unsigned int here = steps[mid].level; + if (here == level) { + return &(steps[mid]); + } else if (here < level) { + lo = mid; + } else { + hi = mid; + } + } + return &(steps[lo]); +} + +/** + * Updates the level after each piece has retired. + * @return nonzero iff a new section has happened + */ +int updLevelAfterPiece(LJField *p) { + int curveID = p->speedState.curve; + const LJSpeedCurve *curve = newSpeedCurves[curveID]; + if (!curve) { + return 0; + } + unsigned int sectionLen = curve->sectionLen; + unsigned int oldLevel = p->speedState.level; + unsigned int oldSection = oldLevel / sectionLen; + unsigned int oldSectionPos = oldLevel % sectionLen; + + switch (curve->type) { + case SPEEDTYPE_TGM: + if (oldSectionPos + 1 >= sectionLen) { + return 0; + } + // otherwise fall through to +1 per piece + + case SPEEDTYPE_PIECES: + p->speedState.level = oldLevel + 1; + break; + + default: + return 0; + } + + unsigned int newSection = p->speedState.level / sectionLen; + return newSection > oldSection; +} + +/** + * Updates the level after lines have been cleared. + * @return nonzero iff a new section has happened + */ +int updLevelAfterLines(LJField *p, unsigned int nLines) { + int curveID = p->speedState.curve; + const LJSpeedCurve *curve = newSpeedCurves[curveID]; + if (!curve) { + return 0; + } + unsigned int sectionLen = curve->sectionLen; + unsigned int oldLevel = p->speedState.level; + unsigned int oldSection = oldLevel / sectionLen; + + switch (curve->type) { + case SPEEDTYPE_TGM: + case SPEEDTYPE_LINES: + p->speedState.level = oldLevel + nLines; + break; + + default: + return 0; + } + + unsigned int newSection = p->speedState.level / sectionLen; + return newSection > oldSection; +} + +void setSpeedNew(LJField *p, LJControl *c) { + int curveID = p->speedState.curve; + const LJSpeedCurve *curve = newSpeedCurves[curveID]; + if (!curve) { + return; + } + const LJSpeedStep *step = + getSpeedStep(curve->steps, curve->nSteps, p->speedState.level); + + p->speed.gravity = step->gravity << 5; + + p->speed.entryDelay = step->are; + if (p->speed.entryDelay > p->areStyle) { + p->speed.entryDelay = p->areStyle; + } + + if (step->das > 0 && c->dasDelay > step->das) { + c->dasDelay = step->das; + } + + if (p->setLockDelay >= 128) { + p->speed.lockDelay = 127; + } else if (p->setLockDelay > 0) { + p->speed.lockDelay = p->setLockDelay; + } else if (step->lock > 0) { + p->speed.lockDelay = step->lock; + } + + if (p->setLineDelay > 0) { + p->speed.lineDelay = p->setLineDelay; + } else if (step->line >= 0) { + p->speed.lineDelay = step->line; + } else { + p->speed.lineDelay = p->speed.entryDelay; + } +} + + + +/* Old speed curve system is below this line ***********************/ + + + +void initSpeed(LJField *p) { + if (defaultSpeedCurve[p->gimmick] >= 0) { + p->speedState.curve = defaultSpeedCurve[p->gimmick]; + } + p->speed.gravity = initialGravity[p->speedState.curve]; + + switch (p->speedState.curve) { + case LJSPD_RHYTHM: + case LJSPD_RHYTHMZERO: + p->speedState.level = 60; + p->bpmCounter = 0; + p->speedupCounter = 0; + break; + case LJSPD_TGM: + case LJSPD_DEATH: + case LJSPD_EXP: + p->speedState.level = -1; + break; + case LJSPD_DEATH300: + p->speedState.level = 300; + break; + case LJSPD_GBHEART: + p->speedState.level = 100; + break; + default: + p->speedState.level = 0; + break; + } +} + + +void setSpeed(LJField *p, LJControl *c) { + p->speed.entryDelay = p->areStyle; + + // Default line delay is equal to the entry delay, + // but speed curves and setLineDelay can override this + p->speed.lineDelay = p->speed.entryDelay; + switch (p->speedState.curve) { + + case LJSPD_RHYTHM: + case LJSPD_RHYTHMZERO: + // If we've already banked five pieces' worth of time, + // add 20 points instead of banking another. + if (p->bpmCounter <= -18000) { + // removed in 0.21 because other curves don't reward for drops + // p->score += 20; + } else { + p->bpmCounter -= 3600; // number of frames per minute + } + p->speed.lockDelay = 3600 / p->speedState.level; + p->speed.gravity = (p->speedState.curve == LJSPD_RHYTHM) ? ljitofix(20) : 0; + break; + + default: + if (updLevelAfterPiece(p)) { + p->sounds |= LJSND_SECTIONUP; + } + setSpeedNew(p, c); + break; + } + + if (p->setLockDelay >= 128) { + p->speed.lockDelay = 127; + } else if (p->setLockDelay > 0) { + p->speed.lockDelay = p->setLockDelay; + } + if (p->setLineDelay > 0) { + p->speed.lineDelay = p->setLineDelay; + } +} + diff -r 000000000000 -r c84446dfb3f5 src/talkback.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/talkback.h Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,24 @@ +/* +This struct lives in the ARM9's memory. It is used to send data back +to the ARM7. + +By Damian Yerrick. No rights reserved and ABSOLUTELY NO WARRANTY. +*/ +#ifndef TALKBACK_H +#define TALKBACK_H + +typedef struct P8A7Talkback { + unsigned long int sounds; + signed char countdown; + unsigned char cmd; +} P8A7Talkback; + +enum { + TALKBACK_NONE = 0, + TALKBACK_POWER_OFF = 1, + TALKBACK_PLAY_MUSIC = 2, + TALKBACK_STOP_MUSIC = 3, + TALKBACK_PAUSE_MUSIC = 4 +}; + +#endif diff -r 000000000000 -r c84446dfb3f5 src/text.chr Binary file src/text.chr has changed diff -r 000000000000 -r c84446dfb3f5 src/winicon.rc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/winicon.rc Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,1 @@ +allegro_icon ICON PRELOAD "docs/appicon.ico" diff -r 000000000000 -r c84446dfb3f5 src/wktables.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/wktables.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,454 @@ +/* Wall kick tables for LOCKJAW, an implementation of the Soviet Mind Game + +Copyright (C) 2006 Damian Yerrick + +This work is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Original game concept and design by Alexey Pajitnov. +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, +or The Tetris Company LLC. + +*/ + +#define LJ_INTERNAL +#include "lj.h" + + +// These wall kicks are for rotation TO a given orientation. +// Based on http://www.the-shell.net/img/srs_study.html +static const LJRotSystem rotSRS = { + .kicksL = {1, 0, 0, -1, 0, 0, 0, -1, 0, -1}, + .kicksR = {3, 2, 2, -1, 2, 2, 2, -1, 2, -3}, + .kickTables = { + + // 0: JLSTZ counterclockwise + { + { WK( 0, 0),WK( 1, 0),WK( 1,-1),WK( 0, 2),WK( 1, 2) }, // R->U + { WK( 0, 0),WK(-1, 0),WK(-1, 1),WK( 0,-2),WK(-1,-2) }, // D->R + { WK( 0, 0),WK(-1, 0),WK(-1,-1),WK( 0, 2),WK(-1, 2) }, // L->D + { WK( 0, 0),WK( 1, 0),WK( 1, 1),WK( 0,-2),WK( 1,-2) } // U->L + }, + + // 1: I counterclockwise + { + { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK(-1,-2),WK( 2, 1) }, // R->U + { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK( 1,-1),WK(-2, 1) }, // D->R + { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK(-2,-1),WK( 1, 2) }, // L->D + { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK( 2,-1),WK(-1, 2) } // U->L + }, + + // 2: JLSTZ clockwise + { + { WK( 0, 0),WK(-1, 0),WK(-1,-1),WK( 0, 2),WK(-1, 2) }, // L->U + { WK( 0, 0),WK(-1, 0),WK(-1, 1),WK( 0,-2),WK(-1,-2) }, // U->R + { WK( 0, 0),WK( 1, 0),WK( 1,-1),WK( 0, 2),WK( 1, 2) }, // R->D + { WK( 0, 0),WK( 1, 0),WK( 1, 1),WK( 0,-2),WK( 1,-2) }, // D->L + }, + + // 3: I clockwise + { + { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK( 1,-2),WK(-2, 1) }, // L->U + { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK(-2,-1),WK( 1, 2) }, // U->R + { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK( 2,-1),WK(-1, 2) }, // R->D + { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK(-1,-1),WK( 2, 1) } // D->L + } + } +}; + +// I: round to top right +// J, L, T: round to bottom +// S: round to bottom left (constant center column) +// Z: round to bottom right (constant center column) +static const LJRotSystem rotSega = { + .colorScheme = 1, + .entryOffset = { + WK( 0, 0),WK( 0, 1),WK( 0, 1),WK( 0, 0),WK( 0, 0),WK( 0, 1),WK( 0, 0), + WK( 0, 0),WK( 0, 0),WK( 0, 0) + }, + .entryTheta = { 0, 2, 2, 0, 0, 2, 0, 0, 0, 2 }, + .kicksL = {0, 1, 1, -1, 2, 1, 3, 0, -1, -1}, + .kicksR = {4, 5, 5, -1, 6, 5, 7, 4, -1, -1}, + + .kickTables = { + + // 0: I counterclockwise + { + { WK( 0, 0),WK_END }, // R->U + { WK( 0,-1),WK_END }, // D->R + { WK(-1, 1),WK_END }, // L->D + { WK( 1, 0),WK_END } // U->L + }, + + // 1: JLT counterclockwise + { + { WK( 0,-1),WK_END }, // R->U + { WK( 0, 0),WK_END }, // D->R + { WK( 0, 0),WK_END }, // L->D + { WK( 0, 1),WK_END } // U->L + }, + + // 2: S counterclockwise (round left, like Game Boy) + { + { WK( 1,-1),WK_END }, // R->U + { WK(-1, 0),WK_END }, // D->R + { WK( 0, 0),WK_END }, // L->D + { WK( 0, 1),WK_END } // U->L + }, + + // 3: Z counterclockwise (round right, like NES) + { + { WK( 0,-1),WK_END }, // R->U + { WK( 0, 0),WK_END }, // D->R + { WK(-1, 0),WK_END }, // L->D + { WK( 1, 1),WK_END } // U->L + }, + + // 4: I clockwise + { + { WK(-1, 0),WK_END }, // L->U + { WK( 0, 0),WK_END }, // U->R + { WK( 0, 1),WK_END }, // R->D + { WK( 1,-1),WK_END } // D->L + }, + + // 5: JLT clockwise + { + { WK( 0,-1),WK_END }, // L->U + { WK( 0, 1),WK_END }, // U->R + { WK( 0, 0),WK_END }, // R->D + { WK( 0, 0),WK_END } // D->L + }, + + // 6: S clockwise (round left) + { + { WK( 0,-1),WK_END }, // L->U + { WK(-1, 1),WK_END }, // U->R + { WK( 1, 0),WK_END }, // R->D + { WK( 0, 0),WK_END } // D->L + }, + + // 7: Z clockwise (round right) + { + { WK(-1,-1),WK_END }, // L->U + { WK( 0, 1),WK_END }, // U->R + { WK( 0, 0),WK_END }, // R->D + { WK( 1, 0),WK_END } // D->L + } + } +}; + +// Arika is based on Sega but with a few wall kicks. +// Each free-space kick should be followed by Right, then Left +// but for J, L, and T, kicks to vertical positions (point-right and +// point-left) +// T when rotating to point-up can also floor kick by one, +// and I when rotating to vertical can floor kick by one or two. +static const LJRotSystem rotArika = { + .colorScheme = 1, + .entryOffset = { + WK( 0, 0),WK( 0, 1),WK( 0, 1),WK( 0, 0),WK( 0, 0),WK( 0, 1),WK( 0, 0), + WK( 0, 0),WK( 0, 0),WK( 0, 0) + }, + .entryTheta = { 0, 2, 2, 0, 0, 2, 0, 0, 0, 2 }, + .kicksL = {0, 1, 1, -1, 2, 8, 3, 0, -1, -1}, + .kicksR = {4, 5, 5, -1, 6, 9, 7, 4, -1, -1}, + .kickTables = { + + // 0: I counterclockwise + { + { WK( 0, 0),WK_END }, // R->U + { WK( 0,-1),WK( 0, 0),WK( 0, 1),WK_END }, // D->R + { WK(-1, 1),WK_END }, // L->D + { WK( 1, 0),WK( 1, 1),WK( 1, 2),WK_END } // U->L + }, + + // 1: JL counterclockwise + { + { WK( 0,-1),WK( 1,-1),WK(-1,-1),WK_END }, // R->U + { WK( 0, 0),ARIKA_IF_NOT_CENTER,WK( 1, 0),WK(-1, 0),WK_END }, // D->R + { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END }, // L->D + { WK( 0, 1),ARIKA_IF_NOT_CENTER,WK( 1, 1),WK(-1, 1),WK_END } // U->L + }, + + // 2: S counterclockwise (round left, like Game Boy with WK) + { + { WK( 1,-1),WK( 2,-1),WK( 0,-1),WK_END }, // R->U + { WK(-1, 0),WK( 0, 0),WK(-2, 0),WK_END }, // D->R + { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END }, // L->D + { WK( 0, 1),WK( 1, 1),WK(-1, 1),WK_END } // U->L + }, + + // 3: Z counterclockwise (round right, like NES with WK) + { + { WK( 0,-1),WK( 1,-1),WK(-1,-1),WK_END }, // R->U + { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END }, // D->R + { WK(-1, 0),WK( 0, 0),WK(-2, 0),WK_END }, // L->D + { WK( 1, 1),WK( 2, 1),WK( 0, 1),WK_END } // U->L + }, + + // 4: I clockwise + { + { WK(-1, 0),WK_END }, // L->U + { WK( 0, 0),WK( 0, 1),WK( 0, 2),WK_END }, // U->R + { WK( 0, 1),WK_END }, // R->D + { WK( 1,-1),WK( 1, 0),WK( 1, 1),WK_END } // D->L + }, + + // 5: JLT clockwise + { + { WK( 0,-1),WK( 1,-1),WK(-1,-1),WK_END }, // L->U + { WK( 0, 1),ARIKA_IF_NOT_CENTER,WK( 1, 1),WK(-1, 1),WK_END }, // U->R + { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END }, // R->D + { WK( 0, 0),ARIKA_IF_NOT_CENTER,WK( 1, 0),WK(-1, 0),WK_END } // D->L + }, + + // 6: S clockwise (round left) + { + { WK( 0,-1),WK( 1,-1),WK(-1,-1),WK_END }, // L->U + { WK(-1, 1),WK( 0, 1),WK(-2, 1),WK_END }, // U->R + { WK( 1, 0),WK( 2, 0),WK( 0, 0),WK_END }, // R->D + { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END } // D->L + }, + + // 7: Z clockwise (round right) + { + { WK(-1,-1),WK( 0,-1),WK(-2,-1),WK_END }, // L->U + { WK( 0, 1),WK( 1, 1),WK(-1, 1),WK_END }, // U->R + { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END }, // R->D + { WK( 1, 0),WK( 2, 0),WK( 0, 0),WK_END } // D->L + }, + + // 8: T counterclockwise (with TI floorkick) + { + { WK( 0,-1),WK( 1,-1),WK(-1,-1),WK( 0, 0),WK_END }, // R->U + { WK( 0, 0),ARIKA_IF_NOT_CENTER,WK( 1, 0),WK(-1, 0),WK_END }, // D->R + { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END }, // L->D + { WK( 0, 1),ARIKA_IF_NOT_CENTER,WK( 1, 1),WK(-1, 1),WK_END } // U->L + }, + + // 9: T clockwise (with TI floorkick) + { + { WK( 0,-1),WK( 1,-1),WK(-1,-1),WK( 0, 0),WK_END }, // L->U + { WK( 0, 1),ARIKA_IF_NOT_CENTER,WK( 1, 1),WK(-1, 1),WK_END }, // U->R + { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK_END }, // R->D + { WK( 0, 0),ARIKA_IF_NOT_CENTER,WK( 1, 0),WK(-1, 0),WK_END } // D->L + } + } +}; + +// All pieces are started with their left side in column 5 and flat side up. +// All pieces stick to the top of the bounding box. +// All 3-wide pieces stick to the left side of the bounding box. +// I sticks to the top when left and right and occupies the second column +// when up and down. +// Try here, then try kicking one space left. Discovered by zaphod77: +// http://www.tetrisconcept.com/forum/viewtopic.php?t=877 +static const LJRotSystem rotTengen = { + .colorScheme = 1, + .entryOffset = { + WK( 1, 0),WK( 1, 1),WK( 1, 1),WK( 0, 0),WK( 1, 0),WK( 1, 1),WK( 1, 0), + WK( 0, 0),WK( 1, 0),WK( 0, 0) + }, + .entryTheta = { 0, 2, 2, 0, 0, 2, 0, 0, 0, 2 }, + .kicksL = {0, 3, 3, -1, 3, 3, 3, 0, 2, 5}, + .kicksR = {1, 4, 4, -1, 4, 4, 4, 1, 3, 5}, + .kickTables = { + + // 0: I counterclockwise + { + { WK( 1, 1),WK( 0, 1),WK_END }, // R->U + { WK(-1,-2),WK(-2,-2),WK_END }, // D->R + { WK( 0, 2),WK(-1, 2),WK_END }, // L->D + { WK( 0,-1),WK(-1,-1),WK_END } // U->L + }, + + // 1: I clockwise + { + { WK( 0, 1),WK(-1, 1),WK_END }, // L->U + { WK(-1,-1),WK(-2,-1),WK_END }, // U->R + { WK( 1, 2),WK( 0, 2),WK_END }, // R->D + { WK( 0,-2),WK(-1,-2),WK_END } // D->L + }, + + // 2: I3 + { + { WK( 0, 1),WK(-1, 1),WK_END }, // L->U + { WK( 0,-1),WK(-1,-1),WK_END }, // U->R + { WK( 0, 1),WK(-1, 1),WK_END }, // R->D + { WK( 0,-1),WK(-1,-1),WK_END } // D->L + }, + + // 3: JLSTZ counterclockwise + { + { WK( 1, 0),WK( 0, 0),WK_END }, // R->U + { WK(-1,-1),WK(-2,-1),WK_END }, // D->R + { WK( 0, 1),WK(-1, 1),WK_END }, // L->D + { WK( 0, 0),WK(-1, 0),WK_END } // U->L + }, + + // 4: JLSTZ clockwise + { + { WK( 0, 0),WK(-1, 0),WK_END }, // L->U + { WK(-1, 0),WK(-2, 0),WK_END }, // U->R + { WK( 1, 1),WK( 0, 1),WK_END }, // R->D + { WK( 0,-1),WK(-1,-1),WK_END } // D->L + }, + + // 5: L3 + { + { WK( 0, 0),WK(-1, 0),WK_END }, // L->U + { WK(-1, 0),WK(-2, 0),WK_END }, // U->R + { WK( 1, 1),WK( 0, 1),WK_END }, // R->D + { WK( 0,-1),WK(-1,-1),WK_END } // D->L + } + } +}; + +// NES: No wall kick +// 3-wide pieces start out one block to the right +// I, S and Z round to the right and use effective positions R and D +static const LJRotSystem rotNES = { + .colorScheme = 1, + .entryOffset = { + WK( 0, 0),WK( 1, 1),WK( 1, 1),WK( 0, 0),WK( 1, 0),WK( 1, 1),WK( 1, 0), + WK( 0, 0),WK( 0, 0),WK( 0, 0) + }, + .entryTheta = { 0, 2, 2, 0, 0, 2, 0, 0, 0, 2 }, + .kicksL = {0, -1, -1, -1, 0, -1, 0, 0, -1, -1}, + .kicksR = {1, -1, -1, -1, 1, -1, 1, 1, -1, -1}, + .kickTables = { + + // 0: counterclockwise (round right) + { + { WK( 0,-1),WK_END }, // R->U + { WK( 0, 0),WK_END }, // D->R + { WK(-1, 0),WK_END }, // L->D + { WK( 1, 1),WK_END } // U->L + }, + // 1: clockwise (round right) + { + { WK(-1,-1),WK_END }, // L->U + { WK( 0, 1),WK_END }, // U->R + { WK( 0, 0),WK_END }, // R->D + { WK( 1, 0),WK_END } // D->L + } + } +}; + +// GB: No wall kick +// I, S and Z round to the left and use effective positions L and D +static const LJRotSystem rotGB = { + .colorScheme = 1, + .entryOffset = { + WK( 0, 0),WK( 0, 1),WK( 0, 1),WK( 0, 0),WK( 0, 0),WK( 0, 1),WK( 0, 0), + WK( 0, 0),WK( 0, 0),WK( 0, 0) + }, + .entryTheta = { 0, 2, 2, 0, 0, 2, 0, 0, 0, 2 }, + .kicksL = {0, -1, -1, -1, 0, -1, 0, 0, -1, -1}, + .kicksR = {1, -1, -1, -1, 1, -1, 1, 1, -1, -1}, + .kickTables = { + + // 0: counterclockwise (round left) + { + { WK( 1,-1),WK_END }, // R->U + { WK(-1, 0),WK_END }, // D->R + { WK( 0, 0),WK_END }, // L->D + { WK( 0, 1),WK_END } // U->L + }, + // 1: clockwise (round left) + { + { WK( 0,-1),WK_END }, // L->U + { WK(-1, 1),WK_END }, // U->R + { WK( 1, 0),WK_END }, // R->D + { WK( 0, 0),WK_END } // D->L + }, + } +}; + + +// The rotation system of LOCKJAW: The Overdose (GBA) and +// Tetramino (NES) TOD is simple. In free space it behaves like SRS. +// If that's blocked, kick right, kick left, kick up. +static const LJRotSystem rotTOD = { + .kicksL = {0, 0, 0, -1, 0, 0, 0, 0, 0, 0}, + .kicksR = {0, 0, 0, -1, 0, 0, 0, 0, 0, 0}, + .kickTables = { + + // 0: JLSTZ counterclockwise + { + { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK( 0, 1),WK_END }, // ->U + { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK( 0, 1),WK_END }, // ->R + { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK( 0, 1),WK_END }, // ->D + { WK( 0, 0),WK( 1, 0),WK(-1, 0),WK( 0, 1),WK_END } // ->L + }, + + // 1: I counterclockwise + { + { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK(-1,-2),WK( 2, 1) }, // R->U + { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK( 1,-1),WK(-2, 1) }, // D->R + { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK(-2,-1),WK( 1, 2) }, // L->D + { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK( 2,-1),WK(-1, 2) } // U->L + }, + + // 2: JLSTZ clockwise + { + { WK( 0, 0),WK(-1, 0),WK(-1,-1),WK( 0, 2),WK(-1, 2) }, // L->U + { WK( 0, 0),WK(-1, 0),WK(-1, 1),WK( 0,-2),WK(-1,-2) }, // U->R + { WK( 0, 0),WK( 1, 0),WK( 1,-1),WK( 0, 2),WK( 1, 2) }, // R->D + { WK( 0, 0),WK( 1, 0),WK( 1, 1),WK( 0,-2),WK( 1,-2) }, // D->L + }, + + // 3: I clockwise + { + { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK( 1,-2),WK(-2, 1) }, // L->U + { WK( 0, 0),WK( 1, 0),WK(-2, 0),WK(-2,-1),WK( 1, 2) }, // U->R + { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK( 2,-1),WK(-1, 2) }, // R->D + { WK( 0, 0),WK(-1, 0),WK( 2, 0),WK(-1,-1),WK( 2, 1) } // D->L + } + } +}; + +static const LJRotSystem rotTDX = { + .entryOffset = { + WK( 0, 0),WK( 1, 1),WK( 1, 1),WK( 0, 0),WK( 1, 0),WK( 1, 1),WK( 1, 0), + WK( 0, 0),WK( 0, 0),WK( 0, 0) + }, + .entryTheta = { 0, 2, 2, 0, 0, 2, 0, 0, 0, 2 }, + .kicksL = {0, 0, 0, -1, 0, 0, 0, 0, 0, 0}, + .kicksR = {1, 1, 1, -1, 1, 1, 1, 1, 1, 1}, + .kickTables = { + + // 0: counterclockwise + { + { WK( 0, 0),WK( 1,-1),WK_END }, // R->U + { WK( 0, 0),WK(-1,-1),WK_END }, // D->R + { WK( 0, 0),WK(-1, 1),WK_END }, // L->D + { WK( 0, 0),WK( 1, 1),WK_END } // U->L + }, + + // 1: clockwise + { + { WK( 0, 0),WK(-1,-1),WK_END }, // L->U + { WK( 0, 0),WK(-1, 1),WK_END }, // U->R + { WK( 0, 0),WK( 1, 1),WK_END }, // R->D + { WK( 0, 0),WK( 1,-1),WK_END } // D->L + } + } +}; + +const LJRotSystem *const rotSystems[N_ROTATION_SYSTEMS] = { + &rotSRS, &rotSega, &rotArika, &rotTengen, + &rotNES, &rotGB, &rotTOD, &rotTDX +}; diff -r 000000000000 -r c84446dfb3f5 tools/fontconv.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/fontconv.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,296 @@ +#define USE_CONSOLE +#include +#include + +const RGB fontPalette[6] = { + {63, 0,63}, + {63,63,63}, + {42,42,42}, + { 0, 0, 0}, + {63, 0, 0}, + {63,63, 0} +}; + +typedef struct Rect { + signed short l, t, r, b; +} Rect; + + +BITMAP *fontImg; +const int cellHeight = 12; +Rect glyphs[256]; +unsigned short glyphDataOffsets[256]; + +#define MIN_GLYPH 0 +#define N_GLYPHS 256 + +char fontdata[65536]; +size_t fontdata_len = 0; +size_t fontdata_shift = 0; + +// stats +size_t eols = 0; +size_t xparents = 0; +size_t opaques = 0; +size_t xlucents = 0; + +void collectStats(int c) { + for (int y = glyphs[c].t; y < glyphs[c].b; ++y) { + + // Find right side of each row of glyph + int right = glyphs[c].l; + for (int x = glyphs[c].l; x < glyphs[c].r; ++x) { + int c = getpixel(fontImg, x, y); + if (c == 2 || c == 3) { + right = x + 1; + } + } + + // Count transparent, semitransparent, and opaque + // pixels within row + for (int x = glyphs[c].l; x < right; ++x) { + switch (getpixel(fontImg, x, y)) { + case 1: + ++xparents; + break; + case 2: + ++xlucents; + break; + case 3: + ++opaques; + break; + } + } + ++eols; + } +} + + +void drawChar(int c) { + int left = glyphs[c].l; + int top = glyphs[c].t; + int right = glyphs[c].r; + int bottom = glyphs[c].b; + if ((c & 0x1F) == 0) { + rectfill(screen, + 0, 288, SCREEN_W - 1, 288 + 32 * 4, + 5); + } + + int x = (c & 0x07) * (SCREEN_W / 8); + int y = 288 + 32 * ((c & 0x18) >> 3); + + textprintf_ex(screen, font, x, y + bottom - top - 4, 3, -1, + "%c=", c); + stretch_blit(fontImg, screen, + left, top, right - left, bottom - top, + x + 16, y, (right - left) * 2, (bottom - top) * 2); + if ((c & 0x1F) == 0x1F) { + readkey(); + } +} + +// color 0: border around kernbox +// color 1: background +// color 2: mix bg+fg +// color 3: foreground +void findCharacters(void) { + int curChar = MIN_GLYPH; + int x = 0; + int y = 12; + + for(y = 0; y < fontImg->h; y += cellHeight) { + for (x = 0; x < fontImg->w; ++x) { + // Skip if border pixel + if (getpixel(fontImg, x, y) != 0) { + + // find left and right extents + int left = x; + int right; + for (right = x; + right < fontImg->w && getpixel(fontImg, right, y) != 0; + ++right) { + } + + // record them + glyphs[curChar].l = left; + glyphs[curChar].t = y; + glyphs[curChar].r = right; + glyphs[curChar].b = y + cellHeight; + x = right; + ++curChar; + } + } + } + +#if 0 + int maxChar = curChar; + for (curChar = MIN_GLYPH; curChar < maxChar; ++curChar) { + collectStats(curChar); + drawChar(curChar); + } +#endif +} + +void displayStats(void) { + rectfill(screen, 0, 288, 255, 479, 5); + textprintf_ex(screen, font, 4, 298, 3, -1, + "xparents: %d", xparents); + textprintf_ex(screen, font, 4, 308, 3, -1, + "xlucents: %d", xlucents); + textprintf_ex(screen, font, 4, 318, 3, -1, + "opaques: %d", opaques); + textprintf_ex(screen, font, 4, 328, 3, -1, + "end of lines: %d", eols); + textprintf_ex(screen, font, 4, 338, 3, -1, + "total number of bits: %d", + (xparents + xlucents + opaques + eols + 3) * 2); +} + +/* Details of the font encoding + +Header: +u8 offsetToEncodingTable; +u8 minGlyph; +u8 nGlyphs; +u8 fontHeight; + +First the encoding table (nglyphs/2 bytes): +u16 offsetToGlyphData; +u8 glyphWidth; +u8 reserved; + +Each glyph data element consists of a stream of bytes. +Each byte contains four 2-bit values packed little-endian: +0: Move pen to start of next line +1: Move pen to right +2: Draw pixel using 50% opacity and move pen to right +3: Draw pixel using 100% opacity and move pen to right +The glyph data is done when "Move pen to start of next line" +has run fontHeight times. + +*/ +void compressPadByte(void) { + if (fontdata_shift > 0) { + fontdata_shift = 0; + ++fontdata_len; + } +} + +void compressWriteCode(unsigned int code) { + fontdata[fontdata_len] |= code << fontdata_shift; + fontdata_shift += 2; + if (fontdata_shift >= 8) { + compressPadByte(); + } +} + +void compressGlyph(unsigned int c) { + glyphDataOffsets[c] = fontdata_len; + for (int y = glyphs[c].t; y < glyphs[c].b; ++y) { + + // Find right side of each row of glyph + int right = glyphs[c].l; + for (int x = glyphs[c].l; x < glyphs[c].r; ++x) { + int c = getpixel(fontImg, x, y); + if (c == 2 || c == 3) { + right = x + 1; + } + } + + // Write transparent, semitransparent, and opaque + // pixels within row + for (int x = glyphs[c].l; x < right; ++x) { + int code = getpixel(fontImg, x, y) & 3; + if (code == 0) { + code = 1; + } + compressWriteCode(code); + } + + // Signal eol + compressWriteCode(0); + } + compressPadByte(); +} + +void compressFont(void) { + unsigned int minGlyph = MIN_GLYPH; + size_t nGlyphs = N_GLYPHS; + for (size_t i = minGlyph; i < minGlyph + nGlyphs; ++i) { + compressGlyph(i); + } +} + +int writeFont(const char *dstName) { + FILE *dst = fopen(dstName, "wb"); + unsigned int minGlyph = MIN_GLYPH; + size_t nGlyphs = N_GLYPHS; + size_t headerLen = 4; + size_t glyphDataBase = nGlyphs * 4 + headerLen; + + if (!dst) { + return EXIT_FAILURE; + } + fputc(4, dst); // offset to encoding table + fputc(minGlyph, dst); + fputc(nGlyphs, dst); + fputc(cellHeight, dst); + + for (size_t i = minGlyph; i < minGlyph + nGlyphs; ++i) { + size_t glyphDataOffset = glyphDataBase + glyphDataOffsets[i]; + fputc(glyphDataOffset & 0xFF, dst); + fputc((glyphDataOffset >> 8) & 0xFF, dst); + fputc(glyphs[i].r - glyphs[i].l, dst); + fputc(0, dst); + } + fwrite(fontdata, fontdata_len, 1, dst); + fclose(dst); + return 0; +} + +#define WATCHING 0 + +int main(int argc, const char *const *argv) { + + if (argc != 3) { + fputs("syntax: fontconv fontbitmapname outname\n", stderr); + return EXIT_FAILURE; + } + + allegro_init(); + install_timer(); + + set_color_depth(8); +#if WATCHING + if (set_gfx_mode(GFX_AUTODETECT_WINDOWED, 256, 480, 0, 0) < 0) { + allegro_message("Could not open a dual screen sized window.\n"); + return EXIT_FAILURE; + } + set_palette_range(fontPalette, + 0, sizeof(fontPalette)/sizeof(fontPalette[0]) - 1, + 1); + clear_bitmap(screen); + rectfill(screen, 0, 192, 255, 287, 2); + rectfill(screen, 0, 288, 255, 479, 5); + install_keyboard(); +#endif + + fontImg = load_bitmap(argv[1], NULL); + if (!fontImg) { + allegro_exit(); + fprintf(stderr, "fontconv could not load %s\n", + argv[1]); + return EXIT_FAILURE; + } + findCharacters(); +#if WATCHING + blit(fontImg, screen, 0, 0, 0, 0, fontImg->w, fontImg->h); + readkey(); + displayStats(); + readkey(); +#endif + compressFont(); + return writeFont(argv[2]); +} END_OF_MAIN(); + diff -r 000000000000 -r c84446dfb3f5 tools/mktables.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/mktables.c Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,88 @@ +#include +#include +#include + +/* +Pin Eight standard tuning defines middle C as 2093/8 Hz. +It uses equal temperament: a semitone difference between two pitches +is a factor of 2^(1/12) between their frequencies. +Thus, MIDI 0 = 60, or 2093/256. +*/ +#define C_IN_OCTAVE(octave) \ + ((double)((long int)2093 << octave) / 256.0) +#define TWELFTH_ROOT_OF_TWO 1.0594630943592952645618252949463 + +/* +DS note frequencies for the tone generator at 8 samples per period +are ((-0x1000000 / (n * 8))) +*/ +#define FREQ_TO_PSG_PERIOD(freq) \ + ((unsigned short)(-0x1000000 / (freq * 8))) + + +int writePSG(FILE *fp) { + + // write header + fputs(".section .rodata\n" + ".balign 4\n" + ".global midi2psgFreq\n" + "midi2psgFreq:", fp); + + int octave = 0; + + for (; octave < 2; ++octave) { + fprintf(fp, "\n@ unsupported octave %d\n.byte ", octave); + for (int semitone = 0; semitone < 12; ++semitone) { + if (semitone) { + fputs(", ", fp); + } + fputs(" 0, 0", fp); + } + } + for (; octave < 10; ++octave) { + double freq = C_IN_OCTAVE(octave); + fprintf(fp, "\n@ octave %d\n.byte ", octave); + for (int semitone = 0; semitone < 12; ++semitone) { + if (semitone) { + fputs(", ", fp); + freq *= TWELFTH_ROOT_OF_TWO; + } + unsigned int psgFreq = FREQ_TO_PSG_PERIOD(freq); + printf("midi %2d = %.2f = psg %5d\n", + octave * 12 + semitone, + freq, + psgFreq); + fprintf(fp, "%3u,%3u", + psgFreq & 0xFF, + (psgFreq >> 8) & 0xFF); + } + } + + fputs("\n", fp); + return 0; +} + +/** + * Writes lookup tables used by the ARM7 code. + * Currently, these include the PSG pitch values. + */ +int writeARM7(const char *filename) { + FILE *fp = fopen(filename, "wt"); + if (!fp) { + return -1; + } + if (writePSG(fp) < 0) { + fclose(fp); + return -1; + } + fclose(fp); + return 0; +} + + +int main(void) { + if (writeARM7("obj/ds/lookup_tables7.s") < 0) { + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff -r 000000000000 -r c84446dfb3f5 zip.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zip.in Fri Mar 13 00:39:12 2009 -0700 @@ -0,0 +1,88 @@ +src/debrief.c +src/dsarm7.c +src/dsdebrief.c +src/dsjoy.c +src/dssound7.c +src/dssleep.c +src/explode.c +src/font.bmp +src/fontdraw.c +src/fontdraw_engine.c +src/fontdraw.iwram.c +src/fontdraw.h +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 +docs/favicon.ico +docs/ljlogo192.png +docs/titlebarbg.png +docs/ljsnap018.png +docs/ijlo-stz.png +docs/appicon.ico +docs/dsicon.bmp +docs/appicon.xcf +docs/ljhtml.css +docs/Compiling_on_Windows.txt +obj/win32/index.txt +obj/gba/index.txt +obj/ds/index.txt +tools/fontconv.c +tools/mktables.c +makefile +gbamakefile +dsmakefile +COPYING.txt +GPL.txt +zip.in +binzip.in +mkzip.bat +README.html +CHANGES.txt +TODO.txt +BUGS.txt