Name | Author | Game Mode | Rating | |||||
---|---|---|---|---|---|---|---|---|
Miscellaneous stuff | Violet CLM | Multiple | N/A |
array<array<array<array<bool>>>> tetrominoes = //You can tell what this array is for just by looking at it.
{ //If you were feeling fancy, you could calculate the rotated
{ //versions of each one on the fly, but this is fine for an example.
//Also, if nothing else manages to teach you what array syntax looks like...
{
{false, false, false, false},
{true , true , true , true },
{false, false, false, false},
{false, false, false, false}
},{
{false, false, true , false},
{false, false, true , false},
{false, false, true , false},
{false, false, true , false}
},{
{false, false, false, false},
{false, false, false, false},
{true , true , true , true },
{false, false, false, false}
},{
{false, true , false, false},
{false, true , false, false},
{false, true , false, false},
{false, true , false, false}
}
},{
{
{false, false, false, false},
{false, true , true , false},
{false, true , true , false},
{false, false, false, false}
},{
{false, false, false, false},
{false, true , true , false},
{false, true , true , false},
{false, false, false, false}
},{
{false, false, false, false},
{false, true , true , false},
{false, true , true , false},
{false, false, false, false}
},{
{false, false, false, false},
{false, true , true , false},
{false, true , true , false},
{false, false, false, false}
}
},{
{
{false, false, false, false},
{false, false, true , false},
{true , true , true , false},
{false, false, false, false}
},{
{false, true , false, false},
{false, true , false, false},
{false, true , true , false},
{false, false, false, false}
},{
{false, false, false, false},
{false, true , true , true },
{false, true , false, false},
{false, false, false, false}
},{
{false, false, false, false},
{false, true , true , false},
{false, false, true , false},
{false, false, true , false}
}
},{
{
{false, false, false, false},
{true , true , true , false},
{false, false, true , false},
{false, false, false, false}
},{
{false, false, true , false},
{false, false, true , false},
{false, true , true , false},
{false, false, false, false}
},{
{false, false, false, false},
{false, true , false, false},
{false, true , true , true },
{false, false, false, false}
},{
{false, false, false, false},
{false, true , true , false},
{false, true , false, false},
{false, true , false, false}
}
},{
{
{false, false, false, false},
{false, true , true , false},
{true , true , false, false},
{false, false, false, false}
},{
{true , false, false, false},
{true , true , false, false},
{false, true , false, false},
{false, false, false, false}
},{
{false, false, false, false},
{false, true , true , false},
{true , true , false, false},
{false, false, false, false}
},{
{true , false, false, false},
{true , true , false, false},
{false, true , false, false},
{false, false, false, false}
}
},{
{
{false, false, false, false},
{true , true , false, false},
{false, true , true , false},
{false, false, false, false}
},{
{false, false, false, false},
{false, true , false, false},
{true , true , false, false},
{true , false, false, false}
},{
{false, false, false, false},
{true , true , false, false},
{false, true , true , false},
{false, false, false, false}
},{
{false, false, false, false},
{false, true , false, false},
{true , true , false, false},
{true , false, false, false}
}
},{
{
{false, true , false, false},
{false, true , true , false},
{false, true , false, false},
{false, false, false, false}
},{
{false, false, false, false},
{true , true , true , false},
{false, true , false, false},
{false, false, false, false}
},{
{false, true , false, false},
{true , true , false, false},
{false, true , false, false},
{false, false, false, false}
},{
{false, true , false, false},
{true , true , true , false},
{false, false, false, false},
{false, false, false, false}
}
}
};
const int8 WIDTH = 10;
const int8 HEIGHT = 17;
array<array<array<bool>>> blocks(2, array<array<bool>>(WIDTH, array<bool>(HEIGHT, false)));
//Like the timeTillNext* stuff farther down, this is a two-long array
//on the top, to accomodate two players. Bumping it up to support three or four players
//would be pretty simple from a data-storing perspective, but you'd need to make HEIGHT
//no longer a constant so that three/four play areas could fit in a single screen. But
//is anyone really going to break out their console controller just so that they can
//play tetris three-player inside of JJ2? Let's hope not.
bool onDrawAmmo(jjPLAYER@ play, jjCANVAS@ canvas) { return true; }
bool onDrawLives(jjPLAYER@ play, jjCANVAS@ canvas) { return true; }
bool onDrawHealth(jjPLAYER@ play, jjCANVAS@ canvas) { return true; }
bool onDrawScore(jjPLAYER@ play, jjCANVAS@ canvas) {
canvas.drawString(125, 13, "Clear: " + formatInt(play.score, "0", 2), STRING::MEDIUM, STRING::RIGHTALIGN);
jjTexturedBGFadePositionY = play.localPlayerID; //makes the two player screens look obviously separate
return true;
}
void onLevelLoad() {
jjObjectPresets[OBJECT::APPLE].behavior = Tetromino;
maskTilesWithFallenBlocksInThem(); //erase the play area, since no blocks have fallen yet
jjWeapons[WEAPON::BLASTER].infinite = jjWeapons[WEAPON::BLASTER].replenishes = false; //renders keyFire meaningless
}
void onLevelBegin() { //camera freezing can't be done in onLevelLoad, but jjWEAPON::replenishes and jjObjectPresets can't be done in onLevelBegin... such is life.
if (jjLocalPlayerCount == 1) {
jjLocalPlayers[0].cameraFreeze(0, 64, false, true); //non-centered, instant
jjLocalPlayers[0].ammo[WEAPON::BLASTER] = 0; //in case you came here from another level (plusMenu.j2l) and jjWEAPON::replenishes didn't matter
} else { //== 2
jjLocalPlayers[0].cameraFreeze(160, 64, false, true);
jjLocalPlayers[1].cameraFreeze(640, 64, false, true);
jjLocalPlayers[0].ammo[WEAPON::BLASTER] = 0;
jjLocalPlayers[1].ammo[WEAPON::BLASTER] = 0;
}
}
array<uint8> timeTillNextRotate(2, 0); //This is also valid array syntax, and evaluates to array<uint8> timeTillNextRotate = {0, 0};
array<uint8> timeTillNextLeft(2, 0); //The difference, naturally, is more useful when your arrays are longer, since (11, 1) is a
array<uint8> timeTillNextRight(2, 0); //lot quicker to type than {1,1,1,1,1,1,1,1,1,1,1};
const uint8 KEYPRESSINTERVAL = 22;
void onPlayerInput(jjPLAYER@ play) {
uint idx = play.localPlayerID; //can't add properties to the jjPLAYER class, so instead we use its localPlayerID as an index for other arrays
play.keyJump = false;
play.keyRun = true; //prevents idle animations, and more importantly idle noises
play.blink = 10; //intangible, so can't collide with the other player in 2p mode
if (play.keyFire) { //There is no such thing as key "events" in JJ2+,
if (timeTillNextRotate[idx] > 0) { //so you'll need to figure out your own way of handling
timeTillNextRotate[idx] = timeTillNextRotate[idx] - 1; //keys that you don't want to be run constantly. This is
play.keyFire = false; //a pretty good pattern right here: if you press the key
} else //out of nowhere it gets evaluated, but if you hold it down,
timeTillNextRotate[idx] = KEYPRESSINTERVAL; //you must wait KEYPRESSINTERVAL (22) ticks until it next
} else timeTillNextRotate[idx] = 0; //works again. plusHeaven.j2l uses a simpler system that
//doesn't allow holding at all... figure out what works for you.
if (play.keyLeft) {
if (timeTillNextLeft[idx] > 0) {
timeTillNextLeft[idx] = timeTillNextLeft[idx] - 1;
play.keyLeft = false;
} else
timeTillNextLeft[idx] = KEYPRESSINTERVAL;
} else timeTillNextLeft[idx] = 0;
if (play.keyRight) {
if (timeTillNextRight[idx] > 0) {
timeTillNextRight[idx] = timeTillNextRight[idx] - 1;
play.keyRight = false;
} else
timeTillNextRight[idx] = KEYPRESSINTERVAL;
} else timeTillNextRight[idx] = 0;
}
const array<int8> XLEFT = {5, 20};
const uint8 TIMETILLABSORPTION = 40; //how long a tetromino can sit on top of a solid tile before turning gray
const array<SOUND::Sample> RotationNoises = {SOUND::COMMON_SWISH1, SOUND::COMMON_SWISH2, SOUND::COMMON_SWISH3, SOUND::COMMON_SWISH4, SOUND::COMMON_SWISH5, SOUND::COMMON_SWISH6, SOUND::COMMON_SWISH7, SOUND::COMMON_SWISH8};
void Tetromino(jjOBJ@ obj) {
if (obj.state == STATE::START) {
obj.doesHurt = jjParameterGet(obj.xOrg / 32, obj.yOrg / 32, 0, 1); //does this tetromino belong to local player 0 or 1?
if (obj.doesHurt >= uint(jjLocalPlayerCount)) { //if playing single player, delete the second one
obj.delete();
return;
}
obj.curAnim = jjRandom() % tetrominoes.length(); //This is the obvious place to begin if you want to improve
//this level as a coding exercise. Proper tetris implementations
//show you what your next tetromino will be while the old one is
//still falling. How can you do the same? Tile ID 663 may be useful,
//or else the TILE::Quadrant enum.
obj.frameID = jjRandom() % tetrominoes[0].length(); //angle
obj.state = STATE::FALL;
obj.ySpeed = 0;
obj.xSpeed = 2;
obj.yAcc = .33 + ((jjLocalPlayers[obj.doesHurt].score / 10) * .2); //every time your score hits 10, 20, 30, etc., the base falling speed increases
obj.yPos = 0;
obj.xPos = 96 + (XLEFT[obj.doesHurt]) * 32;
obj.counterEnd = 0;
} else if (obj.state == STATE::FALL) { //the main part of the file
uint8 localPlayerID = obj.doesHurt; //index for all those arrays of settings that aren't helpfully built into the jjPLAYER class
jjPLAYER@ player = jjLocalPlayers[localPlayerID];
int8 xAxis; int8 yAxis;
if (player.keyFire) { //rotate
array<array<bool>> proposedPiece = tetrominoes[obj.curAnim][(obj.frameID+1)&3];
for (xAxis = 0; xAxis < 4; ++xAxis)
if (player.keyFire) for (yAxis = 0; yAxis < 4; ++yAxis)
if (
proposedPiece[yAxis][xAxis] && (
jjMaskedPixel(obj.xPos + xAxis*32, obj.yPos + yAxis*32) ||
jjMaskedPixel(obj.xPos + xAxis*32 + 31, obj.yPos + yAxis*32) ||
jjMaskedPixel(obj.xPos + xAxis*32, obj.yPos + yAxis*32 + 31) ||
jjMaskedPixel(obj.xPos + xAxis*32 + 31, obj.yPos + yAxis*32 + 31)
)) { player.keyFire = false; break; } //it only takes one overlapping pixel to make a rotation impossible
if (player.keyFire) { //rotation successful
jjSamplePriority(RotationNoises[jjRandom() & 7]);
player.keyFire = false;
++obj.frameID; obj.frameID &= 3;
} else { //unsuccessful; add an explosion at each tile you tried to rotate to, to show why it wouldn't work
jjSamplePriority(SOUND::COMMON_HORN1);
for (xAxis = 0; xAxis < 4; ++xAxis)
for (yAxis = 0; yAxis < 4; ++yAxis)
if (proposedPiece[yAxis][xAxis])
jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos + 16 + xAxis*32, obj.yPos + 16 + yAxis*32)].determineCurAnim(ANIM::AMMO, 71);
}
}
array<array<bool>>@ curPiece = @tetrominoes[obj.curAnim][obj.frameID]; //the @ signs mean we're referencing the existing array section, not cloning a new one
if (player.keyLeft) {
for (xAxis = 0; xAxis < 4; ++xAxis)
if (player.keyLeft) for (yAxis = 0; yAxis < 4; ++yAxis)
if ( curPiece[yAxis][xAxis] && (
jjMaskedPixel(obj.xPos + xAxis*32 - 32, obj.yPos + yAxis*32) ||
jjMaskedPixel(obj.xPos + xAxis*32 - 32, obj.yPos + yAxis*32 + 31)
)) { player.keyLeft = false; timeTillNextLeft[localPlayerID] = 0; break; }
if (player.keyLeft)
{ player.keyLeft = false; obj.xPos = obj.xPos - 32; }
} else if (player.keyRight) {
for (xAxis = 0; xAxis < 4; ++xAxis)
if (player.keyRight) for (yAxis = 0; yAxis < 4; ++yAxis)
if ( curPiece[yAxis][xAxis] && (
jjMaskedPixel(obj.xPos + xAxis*32 + 63, obj.yPos + yAxis*32) ||
jjMaskedPixel(obj.xPos + xAxis*32 + 63, obj.yPos + yAxis*32 + 31)
)) { player.keyRight = false; timeTillNextRight[localPlayerID] = 0; break; }
if (player.keyRight)
{ player.keyRight = false; obj.xPos = obj.xPos + 32; }
}
obj.ySpeed = obj.ySpeed + obj.yAcc + ((player.keyDown) ? 2 : 0);
bool canGoDown = true;
while (obj.ySpeed > 1) {
if (canGoDown) for (xAxis = 0; xAxis < 4; ++xAxis)
for (yAxis = 0; yAxis < 4; ++yAxis)
if ( curPiece[yAxis][xAxis] && (
jjMaskedPixel(obj.xPos + xAxis*32, obj.yPos + yAxis*32 + 32) ||
jjMaskedPixel(obj.xPos + xAxis*32 + 31, obj.yPos + yAxis*32 + 32)
)) { canGoDown = false; break; }
obj.ySpeed = obj.ySpeed - 1;
if (canGoDown)
{ obj.yPos = obj.yPos + 1; obj.counterEnd = 0; }
}
if (++obj.counterEnd >= TIMETILLABSORPTION) { //too long without going down
jjSamplePriority(SOUND::COMMON_LAND1);
for (xAxis = 0; xAxis < 4; ++xAxis)
for (yAxis = 0; yAxis < 4; ++yAxis)
if (curPiece[yAxis][xAxis])
blocks[localPlayerID][obj.xPos / 32 + xAxis-XLEFT[localPlayerID]][obj.yPos / 32 + yAxis] = true;
obj.state = STATE::START; //Go back up to the top, and choose a new shape and angle.
//If you wanted to implement a losing condition, having the whole play
//area filled up with no chance of adding more blocks and completing
//more rows, this would probably be the place to do it.
testForFullLines(localPlayerID);
} else for (xAxis = 0; xAxis < 4; xAxis++)
for (yAxis = 0; yAxis < 4; yAxis++)
if (curPiece[yAxis][xAxis])
jjDrawTile(obj.xPos + xAxis*32, obj.yPos + yAxis*32, 327);
}
}
void maskTilesWithFallenBlocksInThem() {
for (uint8 c = 0; c < XLEFT.length(); ++c)
for (uint8 x = 0; x < WIDTH; x++)
for (uint8 y = 0; y < HEIGHT; y++)
jjTileSet(4, (x+XLEFT[c]), y, (blocks[c][x][y]) ? 317 : 0); //317, of course, is the gray star block
}
void testForFullLines(uint8 c) {
array<bool> fullLines(HEIGHT, false);
bool atLeastOneFullRow = false;
for (int8 y = 0; y < HEIGHT; ++y) {
bool fullSoFar = true;
for (int8 x = 0; x < WIDTH; ++x)
if (!blocks[c][x][y]) { fullSoFar = false; break; }
if (fullSoFar) {
fullLines[y] = true;
++jjLocalPlayers[c].score; //a naive implementation. you might at the least want to add some reward for making four lines at once?
atLeastOneFullRow = true;
for (int8 x = 0; x < WIDTH; ++x)
jjAddParticleTileExplosion(x+XLEFT[c], y, 0 | TILE::ANIMATED, false);
}
}
if (atLeastOneFullRow) { //the code should run the same way regardless, but why bother checking if we know there's nothing to find?
int8 drop = 0;
for (int8 y = HEIGHT - 1; y >= 0; --y) {
if (y-drop >= 0 && fullLines[y-drop]) {
++drop;
++y;
continue;
}
if (drop > 0) {
jjSamplePriority(SOUND::COMMON_DAMPED1);
if(y-drop >= 0)
for (int8 x = 0; x < WIDTH; ++x)
blocks[c][x][y] = blocks[c][x][y-drop];
else
for (int8 x = 0; x < WIDTH; ++x)
blocks[c][x][y] = false;
}
}
}
maskTilesWithFallenBlocksInThem();
}
Jazz2Online © 1999-INFINITY (Site Credits). We have a Privacy Policy. Jazz Jackrabbit, Jazz Jackrabbit 2, Jazz Jackrabbit Advance and all related trademarks and media are ™ and © Epic Games. Lori Jackrabbit is © Dean Dodrill. J2O development powered by Loops of Fury and Chemical Beats.
Eat your lima beans, Johnny.