17 Commits

Author SHA1 Message Date
ee7d434d7e perfectet colorjump timer, cancelled inventory movement 2026-02-16 22:28:32 +01:00
410f7b4027 perfekted colorjump 2026-02-16 21:07:30 +01:00
e1515200ab stoped task at game end 2026-02-11 20:56:26 +01:00
27005f4fc4 Merge pull request 'added color jump' (#12) from develop-colorjump into develop
Reviewed-on: #12
Reviewed-by: Lars Neuhaus <larslukasneuhaus@gmx.de>
2026-02-10 20:24:17 +00:00
2808393c23 solved pr comments 2026-02-10 21:22:32 +01:00
d0825695b3 added an end to color jump 2026-02-10 19:23:06 +01:00
e822a5c5e2 fixed translations for color jump 2026-02-10 19:17:37 +01:00
bf95c8dcc2 added translations to color jump 2026-02-10 19:12:36 +01:00
2f70b25e87 added ColorJump 2026-02-10 19:08:07 +01:00
cbec1ea7f8 Merge pull request 'develop-tetris-srs' (#11) from develop-tetris-srs into develop
Reviewed-on: #11
Reviewed-by: Elias Müller <elias@elias-mueller.com>
2026-02-06 10:47:27 +00:00
c176cfaf2c solved pr comments; switched from hard lock task to reset counter 2026-02-04 19:25:03 +01:00
56f56d48b6 added airborne lock timer reset 2026-02-03 19:22:33 +01:00
991e51bc10 fixed ground behavior for tetrominoes 2026-02-03 19:08:05 +01:00
ffba86ac41 removed cause of tetris collision bugs (yaw, pitch in Pos) 2026-02-03 17:18:28 +01:00
f95c670514 removed cause of tetris collision bugs (yaw, pitch in Pos) 2026-02-03 17:18:10 +01:00
0b3c757ce0 fixed srs rotations 2026-02-03 15:51:41 +01:00
76df6643db added simplified SRS; not functional for I-Tetromino 2026-02-02 22:37:43 +01:00
8 changed files with 411 additions and 61 deletions

View File

@@ -9,6 +9,7 @@ import eu.mhsl.minenet.minigames.instance.game.stateless.types.blockBattle.Block
import eu.mhsl.minenet.minigames.instance.game.stateless.types.blockBreakRace.BlockBreakRaceFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.blockBreakRace.BlockBreakRaceFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.boatRace.BoatRaceFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.boatRace.BoatRaceFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.bowSpleef.BowSpleefFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.bowSpleef.BowSpleefFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.colorJump.ColorJumpFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.deathcube.DeathcubeFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.deathcube.DeathcubeFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.elytraRace.ElytraRaceFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.elytraRace.ElytraRaceFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.fastbridge.FastbridgeFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.fastbridge.FastbridgeFactory;
@@ -50,7 +51,8 @@ public enum GameList {
SPACESNAKE(new SpaceSnakeFactory(), GameType.PVP), SPACESNAKE(new SpaceSnakeFactory(), GameType.PVP),
BOATRACE(new BoatRaceFactory(), GameType.OTHER), BOATRACE(new BoatRaceFactory(), GameType.OTHER),
PILLARS(new PillarsFactory(), GameType.PROTOTYPE), PILLARS(new PillarsFactory(), GameType.PROTOTYPE),
BLOCKBATTLE(new BlockBattleFactory(), GameType.PROTOTYPE); BLOCKBATTLE(new BlockBattleFactory(), GameType.PVP),
COLORJUMP(new ColorJumpFactory(), GameType.PROTOTYPE);
private final GameFactory factory; private final GameFactory factory;
private final GameType type; private final GameType type;

View File

@@ -0,0 +1,170 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.colorJump;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.event.inventory.InventoryPreClickEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.ItemStack;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
public class ColorJump extends StatelessGame {
private final List<Block> blocks = List.of(
Block.RED_CONCRETE,
Block.ORANGE_CONCRETE,
Block.YELLOW_CONCRETE,
Block.LIME_CONCRETE,
Block.LIGHT_BLUE_CONCRETE,
Block.BLUE_CONCRETE,
Block.PURPLE_CONCRETE,
Block.PINK_CONCRETE
);
private Block currentBlock;
private Block blockLastRound;
private double roundTime = 5;
private final double multiplierNextRoundTime = 0.9;
public ColorJump() {
super(Dimension.THE_END.key, "ColorJump", new LastWinsScore());
this.getScore().setIgnoreLastPlayers(1);
this.setGenerator(new CircularPlateTerrainGenerator(100));
this.eventNode().addListener(
InventoryPreClickEvent.class,
inventoryPreClickEvent -> inventoryPreClickEvent.setCancelled(true)
);
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
this.generate();
}
@Override
protected void onStart() {
this.nextRound();
}
private void nextRound() {
this.generate();
do {
this.currentBlock = this.blocks.get(ThreadLocalRandom.current().nextInt(this.blocks.size()));
} while(this.currentBlock == this.blockLastRound);
this.blockLastRound = this.currentBlock;
ItemStack item = ItemStack.of(Objects.requireNonNull(this.currentBlock.registry().material()));
this.getPlayers().forEach(player -> {
player.getInventory().clear();
for(int i = 0; i < 9; i++) {
player.getInventory().setItemStack(i, item);
}
});
var scheduler = MinecraftServer.getSchedulerManager();
TaskSchedule stop = TaskSchedule.stop();
scheduler.scheduleTask(() -> {
if(!this.isRunning) return stop;
this.destroyAllExcept(this.currentBlock);
scheduler.scheduleTask(() -> {
if(this.isRunning) this.nextRound();
return stop;
}, TaskSchedule.seconds(3));
return stop;
}, TaskSchedule.seconds((long) this.roundTime));
this.roundTime = this.roundTime * this.multiplierNextRoundTime;
if(this.roundTime <= 1) this.roundTime = 1;
long totalTicks = (long) (this.roundTime * 20);
final long[] remainingTicks = { totalTicks };
scheduler.scheduleTask(() -> {
if (!this.isRunning)
return stop;
if (remainingTicks[0] <= 0) {
this.getPlayers().forEach(player -> {
player.setExp(0f);
player.setLevel(0);
});
return stop;
}
float progress = (float) remainingTicks[0] / totalTicks;
this.getPlayers().forEach(player -> {
player.setExp(progress);
player.setLevel((int) Math.ceil(remainingTicks[0] / 20.0));
});
remainingTicks[0]--;
return TaskSchedule.tick(1);
}, TaskSchedule.tick(1));
}
private void generate() {
int y = 30;
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for (int x = -20; x <= 21; x += 2) {
for (int z = -11; z <= 10; z += 2) {
Block randomBlock = this.blocks.get(ThreadLocalRandom.current().nextInt(this.blocks.size()));
for (int dx = 0; dx < 2; dx++) {
for (int dz = 0; dz < 2; dz++) {
batch.setBlock(x + dx, y, z + dz, randomBlock);
}
}
}
}
BatchUtil.loadAndApplyBatch(batch, this, () -> {});
}
private void destroyAllExcept(Block block) {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
int y = 30;
for (int x = -20; x <= 21; x++) {
for(int z = -11; z <= 10; z++) {
Pos blockPos = new Pos(x, y, z);
if(this.getBlock(blockPos) != block)
batch.setBlock(blockPos, Block.AIR);
}
}
BatchUtil.loadAndApplyBatch(batch, this, () -> {});
}
@Override
public Pos getSpawn() {
return new Pos(0, 31, 0);
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
var player = playerMoveEvent.getPlayer();
if(playerMoveEvent.getNewPosition().y() >= 29)
return;
player.teleport(this.getSpawn());
if(this.isRunning) {
this.getScore().insertResult(player);
player.setGameMode(GameMode.SPECTATOR);
}
}
}

View File

@@ -0,0 +1,33 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.colorJump;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class ColorJumpFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_ColorJump#name");
}
@Override
public Material symbol() {
return Material.PINK_CONCRETE;
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_ColorJump#description");
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new ColorJump().setParent(parent);
}
}

View File

@@ -0,0 +1,17 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game;
public enum Orientation {
NONE,
RIGHT,
LEFT,
UPSIDE_DOWN;
public Orientation rotated(boolean clockwise) {
return switch(this) {
case NONE -> clockwise ? Orientation.RIGHT : Orientation.LEFT;
case RIGHT -> clockwise ? Orientation.UPSIDE_DOWN : Orientation.NONE;
case UPSIDE_DOWN -> clockwise ? Orientation.LEFT : Orientation.RIGHT;
case LEFT -> clockwise ? Orientation.NONE : Orientation.UPSIDE_DOWN;
};
}
}

View File

@@ -0,0 +1,53 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game;
import java.util.Map;
import java.util.stream.IntStream;
public final class RotationChecker {
private static final Map<Orientation, int[][]> STANDARD_WALL_KICKS = Map.of(
Orientation.NONE, new int[][] {{0,0}, {0,0}, {0,0}, {0,0}, {0,0}},
Orientation.RIGHT, new int[][] {{0,0}, {1,0}, {1,-1}, {0,2}, {1,2}},
Orientation.UPSIDE_DOWN, new int[][] {{0,0}, {0,0}, {0,0}, {0,0}, {0,0}},
Orientation.LEFT, new int[][] {{0,0}, {-1,0}, {-1,-1}, {0,2}, {-1,2}}
);
private static final Map<Orientation, int[][]> I_WALL_KICKS = Map.of(
Orientation.NONE, new int[][] {{0,0}, {-1,0}, {2,0}, {-1,0}, {2,0}},
Orientation.RIGHT, new int[][] {{-1,0}, {0,0}, {0,0}, {0,1}, {0,-2}},
Orientation.UPSIDE_DOWN, new int[][] {{-1,1}, {1,1}, {-2,1}, {1,0}, {-2,0}},
Orientation.LEFT, new int[][] {{0,1}, {0,1}, {0,1}, {0,-1}, {0,2}}
);
private static final Map<Orientation, int[][]> O_WALL_KICKS = Map.of(
Orientation.NONE, new int[][] {{0,0}},
Orientation.RIGHT, new int[][] {{0,-1}},
Orientation.UPSIDE_DOWN, new int[][] {{-1,-1}},
Orientation.LEFT, new int[][] {{-1,0}}
);
public static int[][] getKicksArray(Orientation from, Orientation to, Tetromino.Shape shape) {
int[][] firstOffsets = getOffsetData(from, shape);
int[][] secondOffsets = getOffsetData(to, shape);
int[][] result = new int[firstOffsets.length][2];
for(int i = 0; i < firstOffsets.length; i++) {
result[i] = subtractInt(firstOffsets[i], secondOffsets[i]);
}
return result;
}
private static int[] subtractInt(int[] first, int[] second) {
return IntStream.range(0, first.length)
.map(i -> first[i] - second[i])
.toArray();
}
private static int[][] getOffsetData(Orientation orientation, Tetromino.Shape shape) {
return switch(shape) {
case J, L, S, T, Z -> STANDARD_WALL_KICKS.get(orientation);
case I -> I_WALL_KICKS.get(orientation);
case O -> O_WALL_KICKS.get(orientation);
};
}
}

View File

@@ -2,10 +2,9 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame; import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Pos;
import net.minestom.server.scoreboard.Sidebar; import net.minestom.server.scoreboard.Sidebar;
import net.minestom.server.timer.Scheduler; import net.minestom.server.timer.Task;
import net.minestom.server.timer.TaskSchedule; import net.minestom.server.timer.TaskSchedule;
import java.util.*; import java.util.*;
@@ -23,6 +22,9 @@ public class TetrisGame {
private final Map<Button, Long> lastPresses = new HashMap<>(); private final Map<Button, Long> lastPresses = new HashMap<>();
private final List<TetrisGame> otherTetrisGames = new ArrayList<>(); private final List<TetrisGame> otherTetrisGames = new ArrayList<>();
private final Random random; private final Random random;
private Task tetrominoLockTask;
private int lockDelayResets;
private int currentTetrominoLowestY = Integer.MAX_VALUE;
public boolean lost = false; public boolean lost = false;
public boolean paused = true; public boolean paused = true;
public Tetromino currentTetromino; public Tetromino currentTetromino;
@@ -54,24 +56,27 @@ public class TetrisGame {
} }
public void pressedButton(Button button) { public void pressedButton(Button button) {
final int standardButtonDelay = 100; final int standardButtonDelay = 95;
final int buttonDebounce = 70; final int wsButtonDebounce = 70;
if(this.lastPresses.getOrDefault(button, 0L) >= System.currentTimeMillis() - standardButtonDelay) return; if(this.lastPresses.getOrDefault(button, 0L) >= System.currentTimeMillis() - standardButtonDelay) return;
this.lastPresses.put(button, System.currentTimeMillis()); switch(button) {
if(button == Button.W) this.lastPresses.put(button, System.currentTimeMillis() + buttonDebounce); case W -> this.lastPresses.put(button, System.currentTimeMillis() + wsButtonDebounce);
if(button == Button.S) this.lastPresses.put(button, System.currentTimeMillis() - buttonDebounce); case S -> this.lastPresses.put(button, System.currentTimeMillis() - wsButtonDebounce);
case mouseLeft, mouseRight -> this.lastPresses.put(button, 0L);
default -> this.lastPresses.put(button, System.currentTimeMillis());
}
if(this.lost || this.paused) return; if(this.lost || this.paused) return;
switch(button) { switch(button) {
case A -> this.currentTetromino.moveLeft(); case A -> this.moveLeft();
case S -> this.moveDown(); case S -> this.moveDown();
case D -> this.currentTetromino.moveRight(); case D -> this.moveRight();
case W -> this.hardDrop(); case W -> this.hardDrop();
case mouseLeft -> this.currentTetromino.rotate(false); case mouseLeft -> this.rotate(false);
case mouseRight -> this.currentTetromino.rotate(true); case mouseRight -> this.rotate(true);
case space -> this.switchHold(); case space -> this.switchHold();
} }
} }
@@ -82,8 +87,7 @@ public class TetrisGame {
public void start() { public void start() {
this.paused = false; this.paused = false;
Scheduler scheduler = MinecraftServer.getSchedulerManager(); this.instance.scheduler().submitTask(() -> {
scheduler.submitTask(() -> {
if(this.lost) return TaskSchedule.stop(); if(this.lost) return TaskSchedule.stop();
int standardTickDelay = 40; int standardTickDelay = 40;
if(this.isFast) standardTickDelay = 20; if(this.isFast) standardTickDelay = 20;
@@ -111,9 +115,12 @@ public class TetrisGame {
public void tick() { public void tick() {
if(this.lost || this.paused) return; if(this.lost || this.paused) return;
if(!this.currentTetromino.moveDown()) { if(this.currentTetromino.moveDown()) {
this.setActiveTetrominoDown(); this.stopTetrominoLockTask(this.currentTetromino.getYPosition() < this.currentTetrominoLowestY, false);
} else {
this.scheduleTetrominoLock();
} }
this.currentTetrominoLowestY = Math.min(this.currentTetrominoLowestY, this.currentTetromino.getYPosition());
} }
public int getScore() { public int getScore() {
@@ -139,33 +146,43 @@ public class TetrisGame {
this.lost = true; this.lost = true;
} }
private boolean moveDown() { private void moveDown() {
if(!this.currentTetromino.moveDown()) { if(!this.currentTetromino.moveDown()) {
this.setActiveTetrominoDown(); this.scheduleTetrominoLock();
return false; return;
} }
this.stopTetrominoLockTask(this.currentTetromino.getYPosition() < this.currentTetrominoLowestY, false);
this.score += 1; this.score += 1;
this.updateInfo(); this.updateInfo();
return true;
} }
private boolean hardDrop() { private void moveLeft() {
if(this.currentTetromino.moveLeft()) this.onSuccessfulMoveOrRotate();
}
private void moveRight() {
if(this.currentTetromino.moveRight()) this.onSuccessfulMoveOrRotate();
}
private void rotate(boolean clockwise) {
if(this.currentTetromino.rotate(clockwise)) this.onSuccessfulMoveOrRotate();
}
private void hardDrop() {
if(!this.currentTetromino.moveDown()) { if(!this.currentTetromino.moveDown()) {
this.setActiveTetrominoDown(); this.lockActiveTetromino();
return false; return;
} }
this.score += 2; do {
this.updateInfo();
while(this.currentTetromino.moveDown()) {
this.score += 2; this.score += 2;
this.updateInfo(); } while(this.currentTetromino.moveDown());
} this.updateInfo();
this.setActiveTetrominoDown(); this.lockActiveTetromino();
return true;
} }
private boolean switchHold() { private void switchHold() {
if(!this.holdPossible) return false; if(!this.holdPossible) return;
this.stopTetrominoLockTask(true);
Tetromino newCurrentTetromino; Tetromino newCurrentTetromino;
if(this.holdTetromino == null) { if(this.holdTetromino == null) {
@@ -181,6 +198,7 @@ public class TetrisGame {
this.holdTetromino = new Tetromino(this.instance, this.currentTetromino.getShape()); this.holdTetromino = new Tetromino(this.instance, this.currentTetromino.getShape());
this.currentTetromino = newCurrentTetromino; this.currentTetromino = newCurrentTetromino;
this.currentTetrominoLowestY = Integer.MAX_VALUE;
this.currentTetromino.setPosition(this.tetrominoSpawnPosition); this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
this.currentTetromino.draw(); this.currentTetromino.draw();
if(!this.currentTetromino.moveDown()) this.loose(); if(!this.currentTetromino.moveDown()) this.loose();
@@ -189,7 +207,12 @@ public class TetrisGame {
this.holdTetromino.setPosition(this.holdPosition.add(xChange, 0, 0)); this.holdTetromino.setPosition(this.holdPosition.add(xChange, 0, 0));
this.holdTetromino.drawAsEntities(); this.holdTetromino.drawAsEntities();
this.holdPossible = false; this.holdPossible = false;
return true; }
private void onSuccessfulMoveOrRotate() {
if(!this.currentTetromino.isGrounded()) return;
this.stopTetrominoLockTask(false);
this.scheduleTetrominoLock();
} }
private void updateNextTetrominoes() { private void updateNextTetrominoes() {
@@ -235,11 +258,39 @@ public class TetrisGame {
this.sidebar.updateLineScore("2", this.level); this.sidebar.updateLineScore("2", this.level);
} }
private void setActiveTetrominoDown() { private void scheduleTetrominoLock() {
if(this.tetrominoLockTask == null || !this.tetrominoLockTask.isAlive())
this.tetrominoLockTask = this.instance.scheduler().scheduleTask(() -> {
if(this.currentTetromino.isGrounded()) {
this.lockActiveTetromino();
} else {
this.stopTetrominoLockTask(false, false);
}
return TaskSchedule.stop();
}, TaskSchedule.millis(500));
}
private void stopTetrominoLockTask(boolean resetHard) {
this.stopTetrominoLockTask(resetHard, true);
}
private void stopTetrominoLockTask(boolean resetHard, boolean addToResets) {
if(resetHard) this.lockDelayResets = 0;
if(this.lockDelayResets >= 15 && addToResets) {
this.lockActiveTetromino();
return;
}
if(this.tetrominoLockTask != null) {
this.tetrominoLockTask.cancel();
this.tetrominoLockTask = null;
if(!resetHard && addToResets) this.lockDelayResets++;
}
}
private void lockActiveTetromino() {
this.stopTetrominoLockTask(true);
this.currentTetromino.removeOwnEntities(); this.currentTetromino.removeOwnEntities();
this.currentTetromino = this.nextTetrominoes.removeFirst();
this.currentTetromino.remove();
this.updateNextTetrominoes();
int removedLines = this.playfield.removeFullLines(); int removedLines = this.playfield.removeFullLines();
int combatLines = 0; int combatLines = 0;
@@ -297,6 +348,11 @@ public class TetrisGame {
this.updateInfo(); this.updateInfo();
this.currentTetromino = this.nextTetrominoes.removeFirst();
this.currentTetromino.remove();
this.updateNextTetrominoes();
this.currentTetrominoLowestY = Integer.MAX_VALUE;
this.currentTetromino.setPosition(this.tetrominoSpawnPosition); this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
this.currentTetromino.draw(); this.currentTetromino.draw();
if(!this.currentTetromino.moveDown()) { if(!this.currentTetromino.moveDown()) {

View File

@@ -8,14 +8,12 @@ import net.minestom.server.entity.metadata.other.FallingBlockMeta;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
import net.minestom.server.tag.Tag; import net.minestom.server.tag.Tag;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
public class Tetromino { public class Tetromino {
private final static EntityType ghostEntityType = EntityType.FALLING_BLOCK; private final static EntityType ghostEntityType = EntityType.FALLING_BLOCK;
private final static Tag<String> uuidTag = Tag.String("uuid"); private final static Tag<String> uuidTag = Tag.String("uuid");
private Orientation orientation = Orientation.NONE;
private final Shape shape; private final Shape shape;
private final StatelessGame instance; private final StatelessGame instance;
private final UUID uuid; private final UUID uuid;
@@ -28,10 +26,10 @@ public class Tetromino {
this.uuid = UUID.randomUUID(); this.uuid = UUID.randomUUID();
switch(this.shape) { switch(this.shape) {
case I -> this.shapeArray = new int[][]{{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}}; case I -> this.shapeArray = new int[][]{{0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 1, 1, 1, 1}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}};
case J -> this.shapeArray = new int[][]{{1, 0, 0}, {1, 1, 1}, {0, 0, 0}}; case J -> this.shapeArray = new int[][]{{1, 0, 0}, {1, 1, 1}, {0, 0, 0}};
case L -> this.shapeArray = new int[][]{{0, 0, 1}, {1, 1, 1}, {0, 0, 0}}; case L -> this.shapeArray = new int[][]{{0, 0, 1}, {1, 1, 1}, {0, 0, 0}};
case O -> this.shapeArray = new int[][]{{1, 1}, {1, 1}}; case O -> this.shapeArray = new int[][]{{0, 1, 1}, {0, 1, 1}, {0, 0, 0}};
case S -> this.shapeArray = new int[][]{{0, 1, 1}, {1, 1, 0}, {0, 0, 0}}; case S -> this.shapeArray = new int[][]{{0, 1, 1}, {1, 1, 0}, {0, 0, 0}};
case T -> this.shapeArray = new int[][]{{0, 1, 0}, {1, 1, 1}, {0, 0, 0}}; case T -> this.shapeArray = new int[][]{{0, 1, 0}, {1, 1, 1}, {0, 0, 0}};
case Z -> this.shapeArray = new int[][]{{1, 1, 0}, {0, 1, 1}, {0, 0, 0}}; case Z -> this.shapeArray = new int[][]{{1, 1, 0}, {0, 1, 1}, {0, 0, 0}};
@@ -43,12 +41,23 @@ public class Tetromino {
} }
public void setPosition(Pos newPosition) { public void setPosition(Pos newPosition) {
this.position = newPosition; this.position = new Pos(newPosition.x(), newPosition.y(), newPosition.z());
} }
public boolean rotate(boolean clockwise) { public boolean rotate(boolean clockwise) {
int[][] newShapeArray = this.getTurnedShapeArray(clockwise); int[][] newShapeArray = this.getTurnedShapeArray(clockwise);
return this.checkCollisionAndMove(this.position, newShapeArray); Orientation newOrientation = this.orientation.rotated(clockwise);
int[][] kicksArray = RotationChecker.getKicksArray(this.orientation, newOrientation, this.shape);
for(int[] k : kicksArray) {
Pos candidate = new Pos(this.position.x() + k[0], this.position.y() + k[1], this.position.z());
if(this.checkCollisionAndMove(candidate, newShapeArray)) {
this.orientation = newOrientation;
return true;
}
}
return false;
} }
public boolean moveDown() { public boolean moveDown() {
@@ -73,7 +82,7 @@ public class Tetromino {
public void draw(boolean withGhost) { public void draw(boolean withGhost) {
if(withGhost) { if(withGhost) {
Pos ghostPos = this.position; Pos ghostPos = this.position;
while(!this.checkCollision(ghostPos.sub(0, 1, 0), this.shapeArray)) { while(!this.hasCollision(ghostPos.sub(0, 1, 0), this.shapeArray)) {
ghostPos = ghostPos.sub(0, 1, 0); ghostPos = ghostPos.sub(0, 1, 0);
} }
Pos positionChange = this.position.sub(ghostPos); Pos positionChange = this.position.sub(ghostPos);
@@ -182,7 +191,7 @@ public class Tetromino {
private boolean isPartOfTetromino(Pos position) { private boolean isPartOfTetromino(Pos position) {
return this.getBlockPositions().stream() return this.getBlockPositions().stream()
.anyMatch(pos -> pos.equals(position)); .anyMatch(pos -> pos.sameBlock(position));
} }
private List<Pos> getBlockPositions() { private List<Pos> getBlockPositions() {
@@ -198,10 +207,10 @@ public class Tetromino {
for(int x = 0; x < arrayLength; x++) { for(int x = 0; x < arrayLength; x++) {
for(int y = 0; y < arrayLength; y++) { for(int y = 0; y < arrayLength; y++) {
if(shapeArray[arrayLength - 1 - y][x] == 1) { if(shapeArray[arrayLength - 1 - y][x] == 1) {
switch(this.shape) { if(Objects.requireNonNull(this.shape) == Shape.I) {
case I -> returnList.add(position.add(x - 1, y - 2, 0)); returnList.add(position.add(x - 2, y - 2, 0));
case O -> returnList.add(position.add(x, y, 0)); } else {
default -> returnList.add(position.add(x - 1, y - 1, 0)); returnList.add(position.add(x - 1, y - 1, 0));
} }
} }
} }
@@ -210,7 +219,7 @@ public class Tetromino {
return returnList; return returnList;
} }
private boolean checkCollision(Pos newPosition, int[][] newShapeArray) { private boolean hasCollision(Pos newPosition, int[][] newShapeArray) {
List<Pos> newBlockPositions = this.getBlockPositions(newPosition, newShapeArray); List<Pos> newBlockPositions = this.getBlockPositions(newPosition, newShapeArray);
for(Pos pos : newBlockPositions) { for(Pos pos : newBlockPositions) {
@@ -222,15 +231,21 @@ public class Tetromino {
return false; return false;
} }
public boolean isGrounded() {
return this.hasCollision(this.position.sub(0, 1, 0), this.shapeArray);
}
private boolean checkCollisionAndMove(Pos newPosition, int[][] newShapeArray) { private boolean checkCollisionAndMove(Pos newPosition, int[][] newShapeArray) {
if(!this.checkCollision(newPosition, newShapeArray)) { if(this.hasCollision(newPosition, newShapeArray)) return false;
this.remove(); this.remove();
this.shapeArray = Arrays.stream(newShapeArray).map(int[]::clone).toArray(int[][]::new); this.shapeArray = Arrays.stream(newShapeArray).map(int[]::clone).toArray(int[][]::new);
this.setPosition(newPosition); this.setPosition(newPosition);
this.draw(); this.draw();
return true; return true;
} }
return false;
public int getYPosition() {
return Math.toIntExact(Math.round(this.position.y()));
} }
public enum Shape { public enum Shape {

View File

@@ -182,4 +182,8 @@ description;Build yourself up with your random blocks to reach your opponents an
ns:game_BlockBattle#;; ns:game_BlockBattle#;;
name;Block Battle;Block Kampf name;Block Battle;Block Kampf
description;The team that fills the center with their color first wins!;Das Team, welches als erstes die Mitte mit seiner Farbe gefüllt hat, gewinnt! description;The team that fills the center with their color first wins!;Das Team, welches als erstes die Mitte mit seiner Farbe gefüllt hat, gewinnt!
itemCount;Block Count;Block Anzahl itemCount;Block Count;Block Anzahl
;;
ns:game_ColorJump#;;
name;Color Jump;Farbsprung
description;Jump on the right color!;Springe auf die richtige Farbe!
1 map en_us de_de
182 ns:game_BlockBattle#
183 name Block Battle Block Kampf
184 description The team that fills the center with their color first wins! Das Team, welches als erstes die Mitte mit seiner Farbe gefüllt hat, gewinnt!
185 itemCount Block Count Block Anzahl
186
187 ns:game_ColorJump#
188 name Color Jump Farbsprung
189 description Jump on the right color! Springe auf die richtige Farbe!