Downloads containing PlusTetris.j2as

Downloads
Name Author Game Mode Rating
JJ2 1.23 vanilla: Miscellaneous stuff Violet CLM Multiple N/A Download file

File preview

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();
}