From 76df6643db553626c8b0c449f55ca630b5b556d4 Mon Sep 17 00:00:00 2001 From: lars Date: Mon, 2 Feb 2026 22:37:43 +0100 Subject: [PATCH 1/6] added simplified SRS; not functional for I-Tetromino --- .../types/tetris/game/Orientation.java | 28 ++++++++++++++ .../types/tetris/game/RotationChecker.java | 38 +++++++++++++++++++ .../types/tetris/game/Tetromino.java | 15 +++++++- 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Orientation.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/RotationChecker.java diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Orientation.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Orientation.java new file mode 100644 index 0000000..6ebf10d --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Orientation.java @@ -0,0 +1,28 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game; + +public enum Orientation { + NONE, + RIGHT, + LEFT, + UPSIDE_DOWN; + + public Orientation rotated(boolean clockwise) { + switch(this) { + case NONE -> { + return clockwise ? Orientation.RIGHT : Orientation.LEFT; + } + case RIGHT -> { + return clockwise ? Orientation.UPSIDE_DOWN : Orientation.NONE; + } + case UPSIDE_DOWN -> { + return clockwise ? Orientation.LEFT : Orientation.RIGHT; + } + case LEFT -> { + return clockwise ? Orientation.NONE : Orientation.UPSIDE_DOWN; + } + default -> { + return Orientation.NONE; + } + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/RotationChecker.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/RotationChecker.java new file mode 100644 index 0000000..893b1f4 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/RotationChecker.java @@ -0,0 +1,38 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game; + +import java.util.Map; + +public final class RotationChecker { + private record RotationKey(Orientation from, Orientation to) {} + + private static final Map STANDARD_WALL_KICKS = Map.of( + new RotationKey(Orientation.NONE, Orientation.RIGHT), new int[][] {{0,0}, {-1,0}, {-1,1}, {0,-2}, {-1,-2}}, + new RotationKey(Orientation.RIGHT, Orientation.NONE), new int[][] {{0,0}, {1,0}, {1,-1}, {0,2}, {1,2}}, + new RotationKey(Orientation.RIGHT, Orientation.UPSIDE_DOWN), new int[][] {{0,0}, {1,0}, {1,-1}, {0,2}, {1,2}}, + new RotationKey(Orientation.UPSIDE_DOWN, Orientation.RIGHT), new int[][] {{0,0}, {-1,0}, {-1,1}, {0,-2}, {-1,-2}}, + new RotationKey(Orientation.UPSIDE_DOWN, Orientation.LEFT), new int[][] {{0,0}, {1,0}, {1,1}, {0,-2}, {1,-2}}, + new RotationKey(Orientation.LEFT, Orientation.UPSIDE_DOWN), new int[][] {{0,0}, {-1,0}, {-1,-1}, {0,2}, {-1,2}}, + new RotationKey(Orientation.LEFT, Orientation.NONE), new int[][] {{0,0}, {-1,0}, {-1,-1}, {0,2}, {-1,2}}, + new RotationKey(Orientation.NONE, Orientation.LEFT), new int[][] {{0,0}, {1,0}, {1,1}, {0,-2}, {1,-2}} + ); + + private static final Map I_WALL_KICKS = Map.of( + new RotationKey(Orientation.NONE, Orientation.RIGHT), new int[][] {{0,0}, {-2,0}, {1,0}, {-2,-1}, {1,2}}, + new RotationKey(Orientation.RIGHT, Orientation.NONE), new int[][] {{0,0}, {2,0}, {-1,0}, {2,1}, {-1,-2}}, + new RotationKey(Orientation.RIGHT, Orientation.UPSIDE_DOWN), new int[][] {{0,0}, {-1,0}, {2,0}, {-1,2}, {2,-1}}, + new RotationKey(Orientation.UPSIDE_DOWN, Orientation.RIGHT), new int[][] {{0,0}, {1,0}, {-2,0}, {1,-2}, {-2,1}}, + new RotationKey(Orientation.UPSIDE_DOWN, Orientation.LEFT), new int[][] {{0,0}, {2,0}, {-1,0}, {2,1}, {-1,-2}}, + new RotationKey(Orientation.LEFT, Orientation.UPSIDE_DOWN), new int[][] {{0,0}, {-2,0}, {1,0}, {-2,-1}, {1,2}}, + new RotationKey(Orientation.LEFT, Orientation.NONE), new int[][] {{0,0}, {1,0}, {-2,0}, {1,-2}, {-2,1}}, + new RotationKey(Orientation.NONE, Orientation.LEFT), new int[][] {{0,0}, {-1,0}, {2,0}, {-1,2}, {2,-1}} + ); + + public static int[][] getKicksArray(Orientation from, Orientation to, Tetromino.Shape shape) { + RotationKey rotationKey = new RotationKey(from, to); + return switch(shape) { + case J, L, S, T, Z -> STANDARD_WALL_KICKS.get(rotationKey); + case I -> I_WALL_KICKS.get(rotationKey); + default -> new int[][]{{0, 0}}; + }; + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java index 7acf4fe..4e2de8b 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java @@ -16,6 +16,7 @@ import java.util.UUID; public class Tetromino { private final static EntityType ghostEntityType = EntityType.FALLING_BLOCK; private final static Tag uuidTag = Tag.String("uuid"); + private Orientation orientation = Orientation.NONE; private final Shape shape; private final StatelessGame instance; private final UUID uuid; @@ -48,7 +49,19 @@ public class Tetromino { public boolean rotate(boolean clockwise) { int[][] newShapeArray = this.getTurnedShapeArray(clockwise); - return this.checkCollisionAndMove(this.position, newShapeArray); + Orientation newOrientation = this.orientation.rotated(clockwise); + + // TODO: doesn't work for I-Tetromino + 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() { -- 2.30.2 From 0b3c757ce09d9b73a247bac6f735a301bf01ffb7 Mon Sep 17 00:00:00 2001 From: lars Date: Tue, 3 Feb 2026 15:51:41 +0100 Subject: [PATCH 2/6] fixed srs rotations --- .../types/tetris/game/RotationChecker.java | 64 +++++++++++-------- .../types/tetris/game/Tetromino.java | 47 +++++++++----- 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/RotationChecker.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/RotationChecker.java index 893b1f4..f0f91ef 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/RotationChecker.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/RotationChecker.java @@ -1,38 +1,52 @@ 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 record RotationKey(Orientation from, Orientation to) {} - - private static final Map STANDARD_WALL_KICKS = Map.of( - new RotationKey(Orientation.NONE, Orientation.RIGHT), new int[][] {{0,0}, {-1,0}, {-1,1}, {0,-2}, {-1,-2}}, - new RotationKey(Orientation.RIGHT, Orientation.NONE), new int[][] {{0,0}, {1,0}, {1,-1}, {0,2}, {1,2}}, - new RotationKey(Orientation.RIGHT, Orientation.UPSIDE_DOWN), new int[][] {{0,0}, {1,0}, {1,-1}, {0,2}, {1,2}}, - new RotationKey(Orientation.UPSIDE_DOWN, Orientation.RIGHT), new int[][] {{0,0}, {-1,0}, {-1,1}, {0,-2}, {-1,-2}}, - new RotationKey(Orientation.UPSIDE_DOWN, Orientation.LEFT), new int[][] {{0,0}, {1,0}, {1,1}, {0,-2}, {1,-2}}, - new RotationKey(Orientation.LEFT, Orientation.UPSIDE_DOWN), new int[][] {{0,0}, {-1,0}, {-1,-1}, {0,2}, {-1,2}}, - new RotationKey(Orientation.LEFT, Orientation.NONE), new int[][] {{0,0}, {-1,0}, {-1,-1}, {0,2}, {-1,2}}, - new RotationKey(Orientation.NONE, Orientation.LEFT), new int[][] {{0,0}, {1,0}, {1,1}, {0,-2}, {1,-2}} +public final class RotationChecker {private static final Map 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 I_WALL_KICKS = Map.of( - new RotationKey(Orientation.NONE, Orientation.RIGHT), new int[][] {{0,0}, {-2,0}, {1,0}, {-2,-1}, {1,2}}, - new RotationKey(Orientation.RIGHT, Orientation.NONE), new int[][] {{0,0}, {2,0}, {-1,0}, {2,1}, {-1,-2}}, - new RotationKey(Orientation.RIGHT, Orientation.UPSIDE_DOWN), new int[][] {{0,0}, {-1,0}, {2,0}, {-1,2}, {2,-1}}, - new RotationKey(Orientation.UPSIDE_DOWN, Orientation.RIGHT), new int[][] {{0,0}, {1,0}, {-2,0}, {1,-2}, {-2,1}}, - new RotationKey(Orientation.UPSIDE_DOWN, Orientation.LEFT), new int[][] {{0,0}, {2,0}, {-1,0}, {2,1}, {-1,-2}}, - new RotationKey(Orientation.LEFT, Orientation.UPSIDE_DOWN), new int[][] {{0,0}, {-2,0}, {1,0}, {-2,-1}, {1,2}}, - new RotationKey(Orientation.LEFT, Orientation.NONE), new int[][] {{0,0}, {1,0}, {-2,0}, {1,-2}, {-2,1}}, - new RotationKey(Orientation.NONE, Orientation.LEFT), new int[][] {{0,0}, {-1,0}, {2,0}, {-1,2}, {2,-1}} + private static final Map 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 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) { - RotationKey rotationKey = new RotationKey(from, to); + 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(rotationKey); - case I -> I_WALL_KICKS.get(rotationKey); - default -> new int[][]{{0, 0}}; + 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); }; } } diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java index 4e2de8b..0a48247 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java @@ -29,10 +29,10 @@ public class Tetromino { this.uuid = UUID.randomUUID(); 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 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 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}}; @@ -53,9 +53,12 @@ public class Tetromino { // TODO: doesn't work for I-Tetromino int[][] kicksArray = RotationChecker.getKicksArray(this.orientation, newOrientation, this.shape); + System.out.println(this.shape.toString() + ": " + Arrays.deepToString(kicksArray)); 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)) { + boolean moved = this.checkCollisionAndMove(candidate, newShapeArray); + System.out.println("Candidate: " + Arrays.toString(k) + " " + moved); + if(moved) { this.orientation = newOrientation; return true; } @@ -86,7 +89,7 @@ public class Tetromino { public void draw(boolean withGhost) { if(withGhost) { 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); } Pos positionChange = this.position.sub(ghostPos); @@ -212,9 +215,8 @@ public class Tetromino { for(int y = 0; y < arrayLength; y++) { if(shapeArray[arrayLength - 1 - y][x] == 1) { switch(this.shape) { - case I -> returnList.add(position.add(x - 1, y - 2, 0)); - case O -> returnList.add(position.add(x, y, 0)); - default -> returnList.add(position.add(x - 1, y - 1, 0)); + case I -> returnList.add(new Pos(position).add(x - 2, y - 2, 0)); + default -> returnList.add(new Pos(position).add(x - 1, y - 1, 0)); } } } @@ -223,7 +225,22 @@ public class Tetromino { return returnList; } - private boolean checkCollision(Pos newPosition, int[][] newShapeArray) { +// private List getBlockPositions(Pos position, int[][] shapeArray) { +// List returnList = new ArrayList<>(); +// if(position == null) return returnList; +// +// for(int x = 0; x < shapeArray.length; x++) { +// for(int y = 0; y < shapeArray.length; y++) { +// if(shapeArray[x][y] == 1) { +// returnList.add(new Pos(position).sub(1, 1, 0).add(x, y, 0)); +// } +// } +// } +// +// return returnList; +// } + + private boolean hasCollision(Pos newPosition, int[][] newShapeArray) { List newBlockPositions = this.getBlockPositions(newPosition, newShapeArray); for(Pos pos : newBlockPositions) { @@ -236,14 +253,12 @@ public class Tetromino { } private boolean checkCollisionAndMove(Pos newPosition, int[][] newShapeArray) { - if(!this.checkCollision(newPosition, newShapeArray)) { - this.remove(); - this.shapeArray = Arrays.stream(newShapeArray).map(int[]::clone).toArray(int[][]::new); - this.setPosition(newPosition); - this.draw(); - return true; - } - return false; + if(this.hasCollision(newPosition, newShapeArray)) return false; + this.remove(); + this.shapeArray = Arrays.stream(newShapeArray).map(int[]::clone).toArray(int[][]::new); + this.setPosition(newPosition); + this.draw(); + return true; } public enum Shape { -- 2.30.2 From f95c6705145bc7bca659de1571801acfb75654e2 Mon Sep 17 00:00:00 2001 From: lars Date: Tue, 3 Feb 2026 17:18:10 +0100 Subject: [PATCH 3/6] removed cause of tetris collision bugs (yaw, pitch in Pos) --- .../types/tetris/game/Tetromino.java | 37 ++++--------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java index 0a48247..69cbdb6 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java @@ -8,10 +8,7 @@ import net.minestom.server.entity.metadata.other.FallingBlockMeta; import net.minestom.server.instance.block.Block; import net.minestom.server.tag.Tag; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; +import java.util.*; public class Tetromino { private final static EntityType ghostEntityType = EntityType.FALLING_BLOCK; @@ -44,21 +41,17 @@ public class Tetromino { } public void setPosition(Pos newPosition) { - this.position = newPosition; + this.position = new Pos(newPosition.x(), newPosition.y(), newPosition.z()); } public boolean rotate(boolean clockwise) { int[][] newShapeArray = this.getTurnedShapeArray(clockwise); Orientation newOrientation = this.orientation.rotated(clockwise); - // TODO: doesn't work for I-Tetromino int[][] kicksArray = RotationChecker.getKicksArray(this.orientation, newOrientation, this.shape); - System.out.println(this.shape.toString() + ": " + Arrays.deepToString(kicksArray)); for(int[] k : kicksArray) { Pos candidate = new Pos(this.position.x() + k[0], this.position.y() + k[1], this.position.z()); - boolean moved = this.checkCollisionAndMove(candidate, newShapeArray); - System.out.println("Candidate: " + Arrays.toString(k) + " " + moved); - if(moved) { + if(this.checkCollisionAndMove(candidate, newShapeArray)) { this.orientation = newOrientation; return true; } @@ -198,7 +191,7 @@ public class Tetromino { private boolean isPartOfTetromino(Pos position) { return this.getBlockPositions().stream() - .anyMatch(pos -> pos.equals(position)); + .anyMatch(pos -> pos.sameBlock(position)); } private List getBlockPositions() { @@ -214,9 +207,10 @@ public class Tetromino { for(int x = 0; x < arrayLength; x++) { for(int y = 0; y < arrayLength; y++) { if(shapeArray[arrayLength - 1 - y][x] == 1) { - switch(this.shape) { - case I -> returnList.add(new Pos(position).add(x - 2, y - 2, 0)); - default -> returnList.add(new Pos(position).add(x - 1, y - 1, 0)); + if(Objects.requireNonNull(this.shape) == Shape.I) { + returnList.add(position.add(x - 2, y - 2, 0)); + } else { + returnList.add(position.add(x - 1, y - 1, 0)); } } } @@ -225,21 +219,6 @@ public class Tetromino { return returnList; } -// private List getBlockPositions(Pos position, int[][] shapeArray) { -// List returnList = new ArrayList<>(); -// if(position == null) return returnList; -// -// for(int x = 0; x < shapeArray.length; x++) { -// for(int y = 0; y < shapeArray.length; y++) { -// if(shapeArray[x][y] == 1) { -// returnList.add(new Pos(position).sub(1, 1, 0).add(x, y, 0)); -// } -// } -// } -// -// return returnList; -// } - private boolean hasCollision(Pos newPosition, int[][] newShapeArray) { List newBlockPositions = this.getBlockPositions(newPosition, newShapeArray); -- 2.30.2 From ffba86ac418f901f7dfc51c45f07b22d0bb2f15e Mon Sep 17 00:00:00 2001 From: lars Date: Tue, 3 Feb 2026 17:18:28 +0100 Subject: [PATCH 4/6] removed cause of tetris collision bugs (yaw, pitch in Pos) --- .../game/stateless/types/tetris/game/Tetromino.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java index 69cbdb6..943204a 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java @@ -44,7 +44,7 @@ public class Tetromino { this.position = new Pos(newPosition.x(), newPosition.y(), newPosition.z()); } - public boolean rotate(boolean clockwise) { + public void rotate(boolean clockwise) { int[][] newShapeArray = this.getTurnedShapeArray(clockwise); Orientation newOrientation = this.orientation.rotated(clockwise); @@ -53,11 +53,10 @@ public class Tetromino { 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; } } - return false; } public boolean moveDown() { @@ -65,14 +64,14 @@ public class Tetromino { return this.checkCollisionAndMove(newPosition, this.shapeArray); } - public boolean moveLeft() { + public void moveLeft() { Pos newPosition = this.position.sub(1, 0, 0); - return this.checkCollisionAndMove(newPosition, this.shapeArray); + this.checkCollisionAndMove(newPosition, this.shapeArray); } - public boolean moveRight() { + public void moveRight() { Pos newPosition = this.position.add(1, 0, 0); - return this.checkCollisionAndMove(newPosition, this.shapeArray); + this.checkCollisionAndMove(newPosition, this.shapeArray); } public void draw() { -- 2.30.2 From 991e51bc1018d883d16b97ab75273f42de046f59 Mon Sep 17 00:00:00 2001 From: lars Date: Tue, 3 Feb 2026 19:08:05 +0100 Subject: [PATCH 5/6] fixed ground behavior for tetrominoes --- .../game/stateless/types/tetris/Tetris.java | 14 +-- .../types/tetris/game/TetrisGame.java | 98 +++++++++++++------ .../types/tetris/game/Tetromino.java | 13 +-- 3 files changed, 81 insertions(+), 44 deletions(-) diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/Tetris.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/Tetris.java index 0f3336f..80318c8 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/Tetris.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/Tetris.java @@ -82,11 +82,11 @@ class Tetris extends StatelessGame { private void onPlayerInteract(@NotNull PlayerUseItemEvent event) { event.setItemUseTime(0); - this.tetrisGames.get(event.getPlayer()).pressedButton(TetrisGame.Button.mouseRight); + this.tetrisGames.get(event.getPlayer()).pressedButtonRaw(TetrisGame.Button.mouseRight); } private void onPlayerAttack(@NotNull PlayerHandAnimationEvent event) { - this.tetrisGames.get(event.getPlayer()).pressedButton(TetrisGame.Button.mouseLeft); + this.tetrisGames.get(event.getPlayer()).pressedButtonRaw(TetrisGame.Button.mouseLeft); } private void onPlayerTick(PlayerTickEvent event) { @@ -100,11 +100,11 @@ class Tetris extends StatelessGame { if(player.getGameMode() == GameMode.SPECTATOR) return; - if(player.inputs().forward()) tetrisGame.pressedButton(TetrisGame.Button.W); - if(player.inputs().backward()) tetrisGame.pressedButton(TetrisGame.Button.S); - if(player.inputs().right()) tetrisGame.pressedButton(TetrisGame.Button.D); - if(player.inputs().left()) tetrisGame.pressedButton(TetrisGame.Button.A); - if(player.inputs().jump()) tetrisGame.pressedButton(TetrisGame.Button.space); + if(player.inputs().forward()) tetrisGame.pressedButtonRaw(TetrisGame.Button.W); + if(player.inputs().backward()) tetrisGame.pressedButtonRaw(TetrisGame.Button.S); + if(player.inputs().right()) tetrisGame.pressedButtonRaw(TetrisGame.Button.D); + if(player.inputs().left()) tetrisGame.pressedButtonRaw(TetrisGame.Button.A); + if(player.inputs().jump()) tetrisGame.pressedButtonRaw(TetrisGame.Button.space); } private void letPlayerLoose(Player player) { diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/TetrisGame.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/TetrisGame.java index 44dbe5f..5c3ff67 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/TetrisGame.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/TetrisGame.java @@ -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 net.kyori.adventure.text.Component; -import net.minestom.server.MinecraftServer; import net.minestom.server.coordinate.Pos; 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 java.util.*; @@ -23,6 +22,8 @@ public class TetrisGame { private final Map lastPresses = new HashMap<>(); private final List otherTetrisGames = new ArrayList<>(); private final Random random; + private Task tetrominoPlacementTask; + private Task hardTetrominoPlacementTask; public boolean lost = false; public boolean paused = true; public Tetromino currentTetromino; @@ -53,25 +54,28 @@ public class TetrisGame { } } - public void pressedButton(Button button) { - final int standardButtonDelay = 100; - final int buttonDebounce = 70; + public void pressedButtonRaw(Button button) { + final int standardButtonDelay = 95; + final int wsButtonDebounce = 70; if(this.lastPresses.getOrDefault(button, 0L) >= System.currentTimeMillis() - standardButtonDelay) return; - this.lastPresses.put(button, System.currentTimeMillis()); - if(button == Button.W) this.lastPresses.put(button, System.currentTimeMillis() + buttonDebounce); - if(button == Button.S) this.lastPresses.put(button, System.currentTimeMillis() - buttonDebounce); + switch(button) { + case W -> this.lastPresses.put(button, System.currentTimeMillis() + wsButtonDebounce); + 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; switch(button) { - case A -> this.currentTetromino.moveLeft(); + case A -> this.moveLeft(); case S -> this.moveDown(); - case D -> this.currentTetromino.moveRight(); - case W -> this.hardDrop(); - case mouseLeft -> this.currentTetromino.rotate(false); - case mouseRight -> this.currentTetromino.rotate(true); + case D -> this.moveRight(); + case W -> this.hardDrop(true); + case mouseLeft -> this.rotate(false); + case mouseRight -> this.rotate(true); case space -> this.switchHold(); } } @@ -82,8 +86,7 @@ public class TetrisGame { public void start() { this.paused = false; - Scheduler scheduler = MinecraftServer.getSchedulerManager(); - scheduler.submitTask(() -> { + this.instance.scheduler().submitTask(() -> { if(this.lost) return TaskSchedule.stop(); int standardTickDelay = 40; if(this.isFast) standardTickDelay = 20; @@ -112,7 +115,7 @@ public class TetrisGame { public void tick() { if(this.lost || this.paused) return; if(!this.currentTetromino.moveDown()) { - this.setActiveTetrominoDown(); + this.scheduleTetrominoPlacement(); } } @@ -139,33 +142,42 @@ public class TetrisGame { this.lost = true; } - private boolean moveDown() { + private void moveDown() { if(!this.currentTetromino.moveDown()) { - this.setActiveTetrominoDown(); - return false; + this.scheduleTetrominoPlacement(); + return; } this.score += 1; this.updateInfo(); - return true; } - private boolean hardDrop() { + private void moveLeft() { + if(this.currentTetromino.moveLeft()) this.stopTetrominoPlacementTask(false); + } + + private void moveRight() { + if(this.currentTetromino.moveRight()) this.stopTetrominoPlacementTask(false); + } + + private void rotate(boolean clockwise) { + if(this.currentTetromino.rotate(clockwise)) this.stopTetrominoPlacementTask(false); + } + + private void hardDrop(boolean addScore) { if(!this.currentTetromino.moveDown()) { this.setActiveTetrominoDown(); - return false; + return; } - this.score += 2; - this.updateInfo(); - while(this.currentTetromino.moveDown()) { - this.score += 2; + do { + if(addScore) this.score += 2; this.updateInfo(); - } + } while(this.currentTetromino.moveDown()); this.setActiveTetrominoDown(); - return true; } - private boolean switchHold() { - if(!this.holdPossible) return false; + private void switchHold() { + if(!this.holdPossible) return; + this.stopTetrominoPlacementTask(true); Tetromino newCurrentTetromino; if(this.holdTetromino == null) { @@ -189,7 +201,6 @@ public class TetrisGame { this.holdTetromino.setPosition(this.holdPosition.add(xChange, 0, 0)); this.holdTetromino.drawAsEntities(); this.holdPossible = false; - return true; } private void updateNextTetrominoes() { @@ -235,7 +246,32 @@ public class TetrisGame { this.sidebar.updateLineScore("2", this.level); } + private void scheduleTetrominoPlacement() { + if(this.tetrominoPlacementTask == null || !this.tetrominoPlacementTask.isAlive()) + this.tetrominoPlacementTask = this.instance.scheduler().scheduleTask(() -> { + this.hardDrop(false); + return TaskSchedule.stop(); + }, TaskSchedule.millis(500)); + if(this.hardTetrominoPlacementTask == null || !this.hardTetrominoPlacementTask.isAlive()) + this.hardTetrominoPlacementTask = this.instance.scheduler().scheduleTask(() -> { + this.hardDrop(false); + return TaskSchedule.stop(); + }, TaskSchedule.millis(6000)); + } + + private void stopTetrominoPlacementTask(boolean resetHard) { + if(this.tetrominoPlacementTask != null) { + this.tetrominoPlacementTask.cancel(); + this.tetrominoPlacementTask = null; + } + if(resetHard && this.hardTetrominoPlacementTask != null) { + this.hardTetrominoPlacementTask.cancel(); + this.hardTetrominoPlacementTask = null; + } + } + private void setActiveTetrominoDown() { + this.stopTetrominoPlacementTask(true); this.currentTetromino.removeOwnEntities(); this.currentTetromino = this.nextTetrominoes.removeFirst(); this.currentTetromino.remove(); diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java index 943204a..69cbdb6 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java @@ -44,7 +44,7 @@ public class Tetromino { this.position = new Pos(newPosition.x(), newPosition.y(), newPosition.z()); } - public void rotate(boolean clockwise) { + public boolean rotate(boolean clockwise) { int[][] newShapeArray = this.getTurnedShapeArray(clockwise); Orientation newOrientation = this.orientation.rotated(clockwise); @@ -53,10 +53,11 @@ public class Tetromino { 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; + return true; } } + return false; } public boolean moveDown() { @@ -64,14 +65,14 @@ public class Tetromino { return this.checkCollisionAndMove(newPosition, this.shapeArray); } - public void moveLeft() { + public boolean moveLeft() { Pos newPosition = this.position.sub(1, 0, 0); - this.checkCollisionAndMove(newPosition, this.shapeArray); + return this.checkCollisionAndMove(newPosition, this.shapeArray); } - public void moveRight() { + public boolean moveRight() { Pos newPosition = this.position.add(1, 0, 0); - this.checkCollisionAndMove(newPosition, this.shapeArray); + return this.checkCollisionAndMove(newPosition, this.shapeArray); } public void draw() { -- 2.30.2 From 56f56d48b63114f55604692b779f4217821385d4 Mon Sep 17 00:00:00 2001 From: lars Date: Tue, 3 Feb 2026 19:22:33 +0100 Subject: [PATCH 6/6] added airborne lock timer reset --- .../types/tetris/game/TetrisGame.java | 69 +++++++++++-------- .../types/tetris/game/Tetromino.java | 4 ++ 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/TetrisGame.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/TetrisGame.java index 5c3ff67..8695f9f 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/TetrisGame.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/TetrisGame.java @@ -22,8 +22,8 @@ public class TetrisGame { private final Map lastPresses = new HashMap<>(); private final List otherTetrisGames = new ArrayList<>(); private final Random random; - private Task tetrominoPlacementTask; - private Task hardTetrominoPlacementTask; + private Task tetrominoLockTask; + private Task hardTetrominoLockTask; public boolean lost = false; public boolean paused = true; public Tetromino currentTetromino; @@ -73,7 +73,7 @@ public class TetrisGame { case A -> this.moveLeft(); case S -> this.moveDown(); case D -> this.moveRight(); - case W -> this.hardDrop(true); + case W -> this.hardDrop(); case mouseLeft -> this.rotate(false); case mouseRight -> this.rotate(true); case space -> this.switchHold(); @@ -114,8 +114,9 @@ public class TetrisGame { public void tick() { if(this.lost || this.paused) return; + if(!this.currentTetromino.isGrounded()) this.stopTetrominoLockTask(true); if(!this.currentTetromino.moveDown()) { - this.scheduleTetrominoPlacement(); + this.scheduleTetrominoLock(); } } @@ -144,7 +145,7 @@ public class TetrisGame { private void moveDown() { if(!this.currentTetromino.moveDown()) { - this.scheduleTetrominoPlacement(); + this.scheduleTetrominoLock(); return; } this.score += 1; @@ -152,32 +153,32 @@ public class TetrisGame { } private void moveLeft() { - if(this.currentTetromino.moveLeft()) this.stopTetrominoPlacementTask(false); + if(this.currentTetromino.moveLeft()) this.stopTetrominoLockTask(false); } private void moveRight() { - if(this.currentTetromino.moveRight()) this.stopTetrominoPlacementTask(false); + if(this.currentTetromino.moveRight()) this.stopTetrominoLockTask(false); } private void rotate(boolean clockwise) { - if(this.currentTetromino.rotate(clockwise)) this.stopTetrominoPlacementTask(false); + if(this.currentTetromino.rotate(clockwise)) this.stopTetrominoLockTask(false); } - private void hardDrop(boolean addScore) { + private void hardDrop() { if(!this.currentTetromino.moveDown()) { - this.setActiveTetrominoDown(); + this.lockActiveTetromino(); return; } do { - if(addScore) this.score += 2; - this.updateInfo(); + this.score += 2; } while(this.currentTetromino.moveDown()); - this.setActiveTetrominoDown(); + this.updateInfo(); + this.lockActiveTetromino(); } private void switchHold() { if(!this.holdPossible) return; - this.stopTetrominoPlacementTask(true); + this.stopTetrominoLockTask(true); Tetromino newCurrentTetromino; if(this.holdTetromino == null) { @@ -246,32 +247,40 @@ public class TetrisGame { this.sidebar.updateLineScore("2", this.level); } - private void scheduleTetrominoPlacement() { - if(this.tetrominoPlacementTask == null || !this.tetrominoPlacementTask.isAlive()) - this.tetrominoPlacementTask = this.instance.scheduler().scheduleTask(() -> { - this.hardDrop(false); + 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(true); + } return TaskSchedule.stop(); }, TaskSchedule.millis(500)); - if(this.hardTetrominoPlacementTask == null || !this.hardTetrominoPlacementTask.isAlive()) - this.hardTetrominoPlacementTask = this.instance.scheduler().scheduleTask(() -> { - this.hardDrop(false); + if(this.hardTetrominoLockTask == null || !this.hardTetrominoLockTask.isAlive()) + this.hardTetrominoLockTask = this.instance.scheduler().scheduleTask(() -> { + if(this.currentTetromino.isGrounded()) { + this.lockActiveTetromino(); + } else { + this.stopTetrominoLockTask(true); + } return TaskSchedule.stop(); }, TaskSchedule.millis(6000)); } - private void stopTetrominoPlacementTask(boolean resetHard) { - if(this.tetrominoPlacementTask != null) { - this.tetrominoPlacementTask.cancel(); - this.tetrominoPlacementTask = null; + private void stopTetrominoLockTask(boolean resetHard) { + if(this.tetrominoLockTask != null) { + this.tetrominoLockTask.cancel(); + this.tetrominoLockTask = null; } - if(resetHard && this.hardTetrominoPlacementTask != null) { - this.hardTetrominoPlacementTask.cancel(); - this.hardTetrominoPlacementTask = null; + if(resetHard && this.hardTetrominoLockTask != null) { + this.hardTetrominoLockTask.cancel(); + this.hardTetrominoLockTask = null; } } - private void setActiveTetrominoDown() { - this.stopTetrominoPlacementTask(true); + private void lockActiveTetromino() { + this.stopTetrominoLockTask(true); this.currentTetromino.removeOwnEntities(); this.currentTetromino = this.nextTetrominoes.removeFirst(); this.currentTetromino.remove(); diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java index 69cbdb6..bce04ed 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Tetromino.java @@ -231,6 +231,10 @@ public class Tetromino { return false; } + public boolean isGrounded() { + return this.hasCollision(this.position.sub(0, 1, 0), this.shapeArray); + } + private boolean checkCollisionAndMove(Pos newPosition, int[][] newShapeArray) { if(this.hasCollision(newPosition, newShapeArray)) return false; this.remove(); -- 2.30.2