From 236d372746dd5ec169d39ee11e20adc92696748a Mon Sep 17 00:00:00 2001
From: lars <larslukasneuhaus@gmx.de>
Date: Mon, 21 Oct 2024 15:55:54 +0200
Subject: [PATCH] fixed collision detection, added rotation

---
 .../game/stateless/types/tetris/Tetris.java   | 18 +++--
 .../types/tetris/game/Playfield.java          | 39 +++++++--
 .../types/tetris/game/TetrisGame.java         | 81 ++++++++++++++++---
 .../types/tetris/game/Tetromino.java          | 39 +++++----
 4 files changed, 139 insertions(+), 38 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 3626e30..f873c62 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
@@ -50,25 +50,31 @@ class Tetris extends StatelessGame {
     }
 
     protected void pressedButton(Button button) {
-        if(lastPresses.getOrDefault(button, 0L) >= System.currentTimeMillis()-200) return;
+        if(lastPresses.getOrDefault(button, 0L) >= System.currentTimeMillis()-100) return;
         lastPresses.put(button, System.currentTimeMillis());
 
         switch (button) {
             case A -> {
                 System.out.println("A");
-                System.out.println(this.tetrisGame.currentTetromino.moveLeft());
+                System.out.println(this.tetrisGame.moveLeft());
             }
             case S -> {
                 System.out.println("S");
-                System.out.println(this.tetrisGame.currentTetromino.moveDown());
+                System.out.println(this.tetrisGame.moveDown());
             }
             case D -> {
                 System.out.println("D");
-                System.out.println(this.tetrisGame.currentTetromino.moveRight());
+                System.out.println(this.tetrisGame.moveRight());
             }
             case W -> System.out.println("W");
-            case mouseLeft -> System.out.println("mouse left");
-            case mouseRight -> System.out.println("mouse right");
+            case mouseLeft -> {
+                System.out.println("mouse left");
+                System.out.println(this.tetrisGame.rotate(false));
+            }
+            case mouseRight -> {
+                System.out.println("mouse right");
+                System.out.println(this.tetrisGame.rotate(true));
+            }
             case space -> System.out.println("space");
         }
     }
diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Playfield.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Playfield.java
index 44bf742..9f1e060 100644
--- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Playfield.java
+++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/tetris/game/Playfield.java
@@ -8,34 +8,61 @@ import net.minestom.server.instance.block.Block;
 
 public class Playfield {
     private final Pos lowerLeftCorner;
+    private final StatelessGame instance;
 
-    public Playfield(Pos lowerLeftCorner) {
+    public Playfield(Pos lowerLeftCorner, StatelessGame instance) {
         this.lowerLeftCorner = lowerLeftCorner;
+        this.instance = instance;
     }
 
     public Pos getPlayerSpawnPosition() {
-        return lowerLeftCorner.add(6, 8, 15);
+        return this.lowerLeftCorner.add(6, 8, 15);
     }
 
     public Pos getTetrominoSpawnPosition() {
         return this.lowerLeftCorner.add(5, 21, 1);
     }
 
-    public void generate(StatelessGame instance) {
+    public void generate() {
         AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
 
         for(int x=0; x<12; x++) {
             for(int y=0; y<21; y++) {
-                batch.setBlock(lowerLeftCorner.add(x, y, 0), Block.STONE);
+                batch.setBlock(this.lowerLeftCorner.add(x, y, 0), Block.STONE);
 
                 if(x==0 || x==11 || y==0) {
-                    batch.setBlock(lowerLeftCorner.add(x, y, 1), Block.GRAY_CONCRETE);
+                    batch.setBlock(this.lowerLeftCorner.add(x, y, 1), Block.GRAY_CONCRETE);
                 }
             }
         }
 
         batch.setBlock(getPlayerSpawnPosition().sub(0, 1, 0), Block.STONE);
 
-        BatchUtil.loadAndApplyBatch(batch, instance, () -> {});
+        BatchUtil.loadAndApplyBatch(batch, this.instance, () -> {});
+    }
+
+    public int removeFullLines() {
+        int removedLinesCounter = 0;
+        for(int y=1; y<21; y++) {
+            boolean isFullLine = true;
+            for(int x=1; x<11; x++) {
+                if(this.instance.getBlock(this.lowerLeftCorner.add(x, y, 1)) == Block.AIR) isFullLine = false;
+            }
+            if(isFullLine) {
+                removeFullLine(y);
+                removedLinesCounter += 1;
+                y -= 1;
+            }
+        }
+        return removedLinesCounter;
+    }
+
+    private void removeFullLine(int positionY) {
+        for(int y=positionY; y<21; y++) {
+            for(int x=1; x<11; x++) {
+                Block blockAbove = this.instance.getBlock(this.lowerLeftCorner.add(x, y+1, 1));
+                this.instance.setBlock(this.lowerLeftCorner.add(x, y, 1), blockAbove);
+            }
+        }
     }
 }
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 d3be554..3bd3414 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
@@ -11,10 +11,13 @@ import java.util.Random;
 public class TetrisGame {
     private final StatelessGame instance;
     private final Playfield playfield;
-    private int speed = 1;
+    private int level = 1;
     private int lines = 0;
+    private int score = 0;
+    private boolean lost = false;
     public Tetromino currentTetromino;
     private Tetromino nextTetromino;
+    private Tetromino holdTetromino;
     private final Random random = new Random();
 
     private final Pos tetrominoSpawnPosition;
@@ -24,10 +27,10 @@ public class TetrisGame {
     }
 
     public TetrisGame(StatelessGame instance, Pos lowerLeftCorner, Tetromino startTetromino, Tetromino nextTetromino) {
-        this.playfield = new Playfield(lowerLeftCorner);
+        this.instance = instance;
+        this.playfield = new Playfield(lowerLeftCorner, this.instance);
         this.currentTetromino = startTetromino;
         this.nextTetromino = nextTetromino;
-        this.instance = instance;
 
         this.tetrominoSpawnPosition = this.playfield.getTetrominoSpawnPosition();
     }
@@ -35,28 +38,53 @@ public class TetrisGame {
     public void start() {
         Scheduler scheduler = MinecraftServer.getSchedulerManager();
         scheduler.submitTask(() -> {
+            if(this.lost) return TaskSchedule.stop();
             this.tick();
-            return TaskSchedule.tick(40/this.speed);
+            return TaskSchedule.tick(40/this.level);
         });
     }
 
     public void generate() {
-        this.playfield.generate(this.instance);
+        this.playfield.generate();
 
         this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
         this.currentTetromino.draw();
     }
 
     public void tick() {
+        if(this.lost) return;
         if(!currentTetromino.moveDown()) {
-            currentTetromino = nextTetromino;
-            currentTetromino.setPosition(this.tetrominoSpawnPosition);
-            currentTetromino.draw();
-            nextTetromino = getNextTetromino();
+            setActiveTetrominoDown();
         }
     }
 
 
+    public boolean rotate(boolean clockwise) {
+        if(this.lost) return false;
+        return this.currentTetromino.rotate(clockwise);
+    }
+
+    public boolean moveLeft() {
+        if(this.lost) return false;
+        return this.currentTetromino.moveLeft();
+    }
+
+    public boolean moveRight() {
+        if(this.lost) return false;
+        return this.currentTetromino.moveRight();
+    }
+
+    public boolean moveDown() {
+        if(this.lost) return false;
+        if(!this.currentTetromino.moveDown()) {
+            this.setActiveTetrominoDown();
+            return false;
+        }
+        this.score += 1;
+        return true;
+    }
+
+
     private Tetromino getNextTetromino() {
         int randomNumber = random.nextInt(1, 7);
         Tetromino.Shape nextShape;
@@ -73,4 +101,39 @@ public class TetrisGame {
 
         return new Tetromino(this.instance, nextShape);
     }
+
+    private void loose() {
+        this.lost = true;
+    }
+
+    private void setActiveTetrominoDown() {
+        currentTetromino = nextTetromino;
+        nextTetromino = getNextTetromino();
+
+        int removedLines = this.playfield.removeFullLines();
+        switch (removedLines) {
+            case 1 -> {
+                this.lines += 1;
+                this.score += 40 * this.level;
+            }
+            case 2 -> {
+                this.lines += 3;
+                this.score += 100 * this.level;
+            }
+            case 3 -> {
+                this.lines += 5;
+                this.score += 300 * this.level;
+            }
+            case 4 -> {
+                this.lines += 8;
+                this.score += 1200 * this.level;
+            }
+        }
+
+        currentTetromino.setPosition(this.tetrominoSpawnPosition);
+        currentTetromino.draw();
+        if(!currentTetromino.moveDown()) {
+            loose();
+        }
+    }
 }
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 00ddf1e..d95c945 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
@@ -5,6 +5,7 @@ import net.minestom.server.coordinate.Pos;
 import net.minestom.server.instance.block.Block;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 public class Tetromino {
@@ -95,14 +96,16 @@ public class Tetromino {
         if(!clockwise) iterations = 3;
 
         int arrayLength = this.shapeArray.length;
+        int[][] startArray = Arrays.stream(this.shapeArray).map(int[]::clone).toArray(int[][]::new);
         int[][] returnArray = new int[arrayLength][arrayLength];
 
         for(int k=0; k<iterations; k++) {
             for(int i=0; i<arrayLength; i++) {
                 for(int j=0; j<arrayLength; j++) {
-                    returnArray[i][arrayLength-1-j] = this.shapeArray[j][i];
+                    returnArray[i][arrayLength-1-j] = startArray[j][i];
                 }
             }
+            startArray = Arrays.stream(returnArray).map(int[]::clone).toArray(int[][]::new);
         }
 
         return returnArray;
@@ -114,20 +117,27 @@ public class Tetromino {
                 return true;
             }
         }
-        System.out.println("Not part of Tetromino!!!");
         return false;
     }
 
     private List<Pos> getBlockPositions() {
-        List<Pos> returnList = new ArrayList<>();
-        if(this.position == null) return returnList;
+        return this.getBlockPositions(this.position, this.shapeArray);
+    }
 
-        int arrayLength = this.shapeArray.length;
+    private List<Pos> getBlockPositions(Pos position, int[][] shapeArray) {
+        List<Pos> returnList = new ArrayList<>();
+        if(position == null) return returnList;
+
+        int arrayLength = shapeArray.length;
 
         for(int x=0; x<arrayLength; x++) {
             for(int y=0; y<arrayLength; y++) {
-                if(this.shapeArray[arrayLength-1-y][x] == 1) {
-                    returnList.add(this.position.add(x-1, y-1, 0));
+                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));
+                    }
                 }
             }
         }
@@ -136,25 +146,20 @@ public class Tetromino {
     }
 
     private boolean checkCollision(Pos newPosition, int[][] newShapeArray) {
-        int arrayLength = newShapeArray.length;
+        List<Pos> newBlockPositions = getBlockPositions(newPosition, newShapeArray);
 
-        for(int x=0; x<arrayLength; x++) {
-            for(int y=0; y<arrayLength; y++) {
-                Pos checkPosition = newPosition.add(x-1, y-1, 0);
-                if(isPartOfTetromino(checkPosition)) continue;
-                if(this.instance.getBlock(checkPosition) != Block.AIR) return true;
-            }
+        for(Pos pos : newBlockPositions) {
+            if(isPartOfTetromino(pos)) continue;
+            if(this.instance.getBlock(pos) != Block.AIR) return true;
         }
 
-        // TODO: collision check to -y not working
-
         return false;
     }
 
     private boolean checkCollisionAndMove(Pos newPosition, int[][] newShapeArray) {
         if(!checkCollision(newPosition, newShapeArray)) {
             this.remove();
-            this.shapeArray = newShapeArray;
+            this.shapeArray = Arrays.stream(newShapeArray).map(int[]::clone).toArray(int[][]::new);
             this.setPosition(newPosition);
             this.draw();
             return true;