From 87b2d8df3c95094349d40e5ba6b605d1091a82db Mon Sep 17 00:00:00 2001 From: Urban Modig Date: Tue, 12 Aug 2025 16:34:31 +0200 Subject: [PATCH] Restructuring --- .idea/vcs.xml | 6 ++ .../java/se/urmo/game/CollisionChecker.java | 57 +++++++++++ src/main/java/se/urmo/game/Direction.java | 21 ++++ src/main/java/se/urmo/game/Game.java | 32 ++---- src/main/java/se/urmo/game/GameMap.java | 19 ++-- src/main/java/se/urmo/game/GamePanel.java | 22 ++--- src/main/java/se/urmo/game/PacMan.java | 97 ++++++------------- .../java/se/urmo/game/input/KeyHandler.java | 13 +-- .../se/urmo/game/state/CollisionChecker.java | 39 -------- .../java/se/urmo/game/state/GameState.java | 13 +++ .../se/urmo/game/state/GameStateManager.java | 35 +++++++ .../se/urmo/game/state/GameStateType.java | 5 + src/main/java/se/urmo/game/state/Playing.java | 44 --------- .../java/se/urmo/game/state/PlayingState.java | 46 +++++++++ 14 files changed, 246 insertions(+), 203 deletions(-) create mode 100644 .idea/vcs.xml create mode 100644 src/main/java/se/urmo/game/CollisionChecker.java create mode 100644 src/main/java/se/urmo/game/Direction.java delete mode 100644 src/main/java/se/urmo/game/state/CollisionChecker.java create mode 100644 src/main/java/se/urmo/game/state/GameState.java create mode 100644 src/main/java/se/urmo/game/state/GameStateManager.java create mode 100644 src/main/java/se/urmo/game/state/GameStateType.java delete mode 100644 src/main/java/se/urmo/game/state/Playing.java create mode 100644 src/main/java/se/urmo/game/state/PlayingState.java diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/java/se/urmo/game/CollisionChecker.java b/src/main/java/se/urmo/game/CollisionChecker.java new file mode 100644 index 0000000..b3a8ab4 --- /dev/null +++ b/src/main/java/se/urmo/game/CollisionChecker.java @@ -0,0 +1,57 @@ +package se.urmo.game; + + +import java.awt.Point; +import java.util.Collections; +import java.util.List; + +public class CollisionChecker { + private GameMap map; + + public CollisionChecker(GameMap map) { + this.map = map; + } + + public Point getValidDestination(Direction direction, Point position, int agent_width, int agent_height) { + List list = switch (direction) { + case RIGHT -> List.of( + new Point(position.x + agent_width, position.y), // TOPRIGHT + new Point(position.x + agent_width, position.y + agent_height)); // BOTTOMRIGHT + case LEFT -> List.of( + position, // TOPLEFT + new Point(position.x, position.y + agent_height)); // BOTTOMLEFT + case UP -> List.of( + position, // TOPLEFT + new Point(position.x + agent_width, position.y)); // TOPRIGHT + case DOWN -> List.of( + new Point(position.x, position.y + agent_height), // BOTTOMLEFT + new Point(position.x + agent_width, position.y + agent_height)); // BOTTOMRIGHT + default -> Collections.EMPTY_LIST; + }; + + List list2 = list.stream() + .map(p -> normalizePosition(direction, p, agent_width, agent_height)) + .toList(); + + if (map.isPassable(list2)) { + return normalizePosition(direction, position, agent_width, agent_height); + } + return null; // Blocked + } + + public Point normalizePosition(Direction dir, Point pos, int agent_width, int agent_height) { + int x = pos.x; + int y = pos.y; + int width = map.getWidth(); + int height = map.getHeight(); + + // tunnel + if (x < GameMap.OFFSET_X) x = width - agent_width - GameMap.OFFSET_X; // right + if (x >= (width - GameMap.OFFSET_X)) x = GameMap.OFFSET_X; // left +// +// if (y < 0) y = height - 1; // optional vertical wrap +// else if (y >= height) y = 0; + + return new Point(x, y); + } +} diff --git a/src/main/java/se/urmo/game/Direction.java b/src/main/java/se/urmo/game/Direction.java new file mode 100644 index 0000000..7911bbc --- /dev/null +++ b/src/main/java/se/urmo/game/Direction.java @@ -0,0 +1,21 @@ +package se.urmo.game; + +public enum Direction { + // private static final int RIGHT = 0; +// private static final int LEFT = 1; +// private static final int DOWN = 2; +// private static final int UP = 3; + RIGHT(1, 0), + LEFT(-1, 0), + DOWN(0, 1), + UP(0, -1), + NONE(0, 0); + + public final int dx; + public final int dy; + + Direction(int dx, int dy) { + this.dx = dx; + this.dy = dy; + } +} diff --git a/src/main/java/se/urmo/game/Game.java b/src/main/java/se/urmo/game/Game.java index 8dd8723..f0490bb 100644 --- a/src/main/java/se/urmo/game/Game.java +++ b/src/main/java/se/urmo/game/Game.java @@ -1,9 +1,8 @@ package se.urmo.game; -import se.urmo.game.state.Playing; +import se.urmo.game.state.GameStateManager; import javax.swing.*; -import java.awt.*; public class Game implements Runnable { public final static int FPS_SET = 120; @@ -12,15 +11,20 @@ public class Game implements Runnable { private final static double timePerUpdate = 1000000000.0 / UPS_SET; + private final GameStateManager gameStateManager; private Thread gameThread; private final JFrame window = new JFrame(); - private final GamePanel gamePanel = new GamePanel(this); - private Playing playing = new Playing(this); + private final GamePanel gamePanel; + + public Game() { + this.gameStateManager = new GameStateManager(this); + this.gamePanel = new GamePanel(this, gameStateManager); + } public void start() { window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.setResizable(false); - window.setTitle("2d"); + window.setTitle("PacMan"); window.add(gamePanel); @@ -30,14 +34,9 @@ public class Game implements Runnable { window.setLocationRelativeTo(null); window.setVisible(true); - startGameThread(); - } - - public void startGameThread() { this.gameThread = new Thread(this); gameThread.start(); } - @Override public void run() { long previousTime = System.nanoTime(); @@ -57,7 +56,7 @@ public class Game implements Runnable { previousTime = currentTime; if (deltaU >= 1) { - update(); + gameStateManager.update(); updates++; deltaU--; } @@ -77,18 +76,7 @@ public class Game implements Runnable { } } - private void update() { - playing.update(); - } - - public Playing getPlaying() { - return playing; - } public GamePanel getGamePanel() { return gamePanel; } - - public void render(Graphics g) { - playing.draw(g); - } } diff --git a/src/main/java/se/urmo/game/GameMap.java b/src/main/java/se/urmo/game/GameMap.java index 5289d3d..7eba51f 100644 --- a/src/main/java/se/urmo/game/GameMap.java +++ b/src/main/java/se/urmo/game/GameMap.java @@ -73,17 +73,24 @@ public class GameMap { } } + public boolean isPassable(List list) { + return list.stream().allMatch(p -> isPassable(p.x, p.y)); + } + public boolean isPassable(int x, int y) { int row = (y - OFFSET_Y) / MAP_TILESIZE; int col = (x - OFFSET_X) / MAP_TILESIZE; - - if (row > mapData.length - 1) row = 0; - if (col > mapData[row].length - 1) col = 0; - if (row < 0) row = mapData.length - 1; - if (col < 0) col = mapData[row].length - 1; - + System.out.println("(x,y)" + x+","+y + " -> col,row: " + col + "," +row); boolean passable = mapData[row][col].isPassable(); System.out.println(row + "," + col + "is" +(passable?"":" not") + " passable"); return passable; } + + public int getWidth() { + return GamePanel.SCREEN_WIDTH; + } + + public int getHeight() { + return GamePanel.SCREEN_HEIGHT; + } } diff --git a/src/main/java/se/urmo/game/GamePanel.java b/src/main/java/se/urmo/game/GamePanel.java index 205f10a..7eab92f 100644 --- a/src/main/java/se/urmo/game/GamePanel.java +++ b/src/main/java/se/urmo/game/GamePanel.java @@ -1,10 +1,10 @@ package se.urmo.game; import se.urmo.game.input.KeyHandler; +import se.urmo.game.state.GameStateManager; import javax.swing.*; import java.awt.*; -import java.awt.event.KeyEvent; public class GamePanel extends JPanel { public static final int ORIGINAL_TILE_SIZE = 8; @@ -15,16 +15,15 @@ public class GamePanel extends JPanel { public static final int SCREEN_WIDTH = MAX_SCREEN_COL * TILE_SIZE; public static final int SCREEN_HEIGHT = MAX_SCREEN_ROW * TILE_SIZE; private final Game game; + private final GameStateManager gameStateManager; - - KeyHandler keyHandler = new KeyHandler(this); - - public GamePanel(Game game) { + public GamePanel(Game game, GameStateManager gameStateManager) { this.game = game; + this.gameStateManager = gameStateManager; this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT)); this.setBackground(Color.decode("#473B78")); this.setDoubleBuffered(true); - this.addKeyListener(keyHandler); + this.addKeyListener(new KeyHandler(gameStateManager)); this.setFocusable(true); } @@ -41,15 +40,6 @@ public class GamePanel extends JPanel { int border = 16; g2d.setColor(new Color(20, 20, 28)); // Dark bluish-black like in the image g2d.fillRect(border, border, getWidth() - 2 * border, getHeight() - 2 * border); - game.render(g); - } - - - public void keyPressed(KeyEvent e) { - game.getPlaying().keyPressed(e); - } - - public void keyReleased(KeyEvent e) { - game.getPlaying().keyReleased(); + gameStateManager.render((Graphics2D) g); } } diff --git a/src/main/java/se/urmo/game/PacMan.java b/src/main/java/se/urmo/game/PacMan.java index a51b9f1..b2a2fb6 100644 --- a/src/main/java/se/urmo/game/PacMan.java +++ b/src/main/java/se/urmo/game/PacMan.java @@ -1,6 +1,5 @@ package se.urmo.game; -import se.urmo.game.state.CollisionChecker; import se.urmo.game.util.LoadSave; import se.urmo.game.util.MiscUtil; @@ -10,10 +9,6 @@ import java.util.Arrays; public class PacMan { - private static final int RIGHT = 0; - private static final int LEFT = 1; - private static final int DOWN = 2; - private static final int UP = 3; public static final int PACMAN_SIZE = GamePanel.TILE_SIZE; private final Game game; private int aniTick = 0; @@ -22,14 +17,16 @@ public class PacMan { private int speed = 1; private boolean moving; private final BufferedImage[][] movmentImages = new BufferedImage[4][4]; - private int direction = 0; - private int xPos = 14*16 + 8, yPos = 29*16; // top left of object - private static final BufferedImage innerBox = MiscUtil.createOutlinedBox(8,8, Color.yellow, 2); + //private int xPos = 14 * 16 + 8, yPos = 29 * 16; // top left of object + private Point position; + private static final BufferedImage innerBox = MiscUtil.createOutlinedBox(8, 8, Color.yellow, 2); private CollisionChecker collisionChecker; + private Direction direction = Direction.NONE; public PacMan(Game game, CollisionChecker collisionChecker) { this.game = game; this.collisionChecker = collisionChecker; + position = new Point(26 * 16 + 8 + GameMap.OFFSET_X, 13 * 16 + GameMap.OFFSET_Y); loadAnimation(); } @@ -42,91 +39,46 @@ public class PacMan { image[row][col] = img.getSubimage(32 * col, 32 * row, PACMAN_SIZE, PACMAN_SIZE); } } - movmentImages[RIGHT] = image[0]; - movmentImages[LEFT] = Arrays.stream(image[0]) + movmentImages[Direction.RIGHT.ordinal()] = image[0]; + movmentImages[Direction.LEFT.ordinal()] = Arrays.stream(image[0]) .map(i -> LoadSave.rotate(i, 180)) .toArray(BufferedImage[]::new); - movmentImages[DOWN] = Arrays.stream(image[0]) + movmentImages[Direction.DOWN.ordinal()] = Arrays.stream(image[0]) .map(i -> LoadSave.rotate(i, 90)) .toArray(BufferedImage[]::new); - movmentImages[UP] = Arrays.stream(image[0]) + movmentImages[Direction.UP.ordinal()] = Arrays.stream(image[0]) .map(i -> LoadSave.rotate(i, 270)) .toArray(BufferedImage[]::new); } public void draw(Graphics g) { - g.drawImage(innerBox, xPos, yPos, PACMAN_SIZE, PACMAN_SIZE, null); + g.drawImage(innerBox, position.x, position.y, PACMAN_SIZE, PACMAN_SIZE, null); //g.setColor(Color.RED); //g.drawLine(xPos, yPos, xPos, yPos); //g.drawImage(movmentImages[direction][aniIndex], xPos, yPos, PACMAN_SIZE, PACMAN_SIZE, null); } - public void setLeft() { - direction = LEFT; - } - - public void setRight() { - direction = RIGHT; - } - - public void setUp() { - direction = UP; - } - - public void setDown() { - direction = DOWN; - } - public void update() { - //System.out.println("Pacman current pos: " + xPos + ", " + yPos); updateAnimationTick(); - int nextTileX = xPos; - int nextTileY = yPos; + if(direction == Direction.NONE) return; - switch (direction) { - case RIGHT: { - nextTileX++; - break; - } - case LEFT: - nextTileX--; - break; - case UP: - nextTileY--; - break; - case DOWN: - nextTileY++; - break; - } + Point newPosition = switch (direction){ + case RIGHT -> new Point(position.x + speed, position.y); + case LEFT -> new Point(position.x - speed, position.y); + case UP -> new Point(position.x , position.y - speed); + case DOWN -> new Point(position.x, position.y + speed); + default -> throw new IllegalStateException("Unexpected value: " + direction); + }; + Point destination = collisionChecker.getValidDestination(direction, newPosition, PACMAN_SIZE, PACMAN_SIZE); - if (moving && collisionChecker.canMoveTo(direction, nextTileX, nextTileY, PACMAN_SIZE, PACMAN_SIZE)) { - //boolean b = collisionChecker.canMoveTo(direction, nextTileX, nextTileY, PACMAN_SIZE, PACMAN_SIZE); - //System.out.println("Can" + (b ? " " : "not ") + "move to (" +nextTileX+ "," + nextTileY + ")"); - updatePosition(); + if(destination != null) { + position = destination; + System.out.println("Position: + " + position); } } - private void updatePosition() { - if(moving) { - switch (direction) { - case RIGHT -> { - if(xPos + PACMAN_SIZE < GamePanel.SCREEN_WIDTH) xPos += speed; - } - case LEFT -> { - if(xPos > 0) xPos -= speed; - else xPos = 0; - } - case DOWN -> { - if(yPos + PACMAN_SIZE < GamePanel.SCREEN_HEIGHT) yPos += speed; - } - case UP ->{ - if(yPos > 0) yPos -= speed; - } - } - } - } private void updateAnimationTick() { if (moving) { @@ -145,4 +97,9 @@ public class PacMan { public void setMoving(boolean moving) { this.moving = moving; } + + public void setDirection(Direction direction) { + this.moving = true; + this.direction = direction; + } } diff --git a/src/main/java/se/urmo/game/input/KeyHandler.java b/src/main/java/se/urmo/game/input/KeyHandler.java index b495307..3e7c4b8 100644 --- a/src/main/java/se/urmo/game/input/KeyHandler.java +++ b/src/main/java/se/urmo/game/input/KeyHandler.java @@ -1,28 +1,29 @@ package se.urmo.game.input; -import se.urmo.game.GamePanel; +import se.urmo.game.state.GameStateManager; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; public class KeyHandler implements KeyListener { - private final GamePanel gamePanel; + private final GameStateManager gameStateManager; - public KeyHandler(GamePanel gamePanel) { - this.gamePanel = gamePanel; + public KeyHandler(GameStateManager gameStateManager) { + this.gameStateManager = gameStateManager; } @Override public void keyTyped(KeyEvent e) { + //not in use } @Override public void keyPressed(KeyEvent e) { - gamePanel.keyPressed(e); + gameStateManager.getCurrentState().keyPressed(e); } @Override public void keyReleased(KeyEvent e) { - gamePanel.keyReleased(e); + gameStateManager.getCurrentState().keyReleased(e); } } diff --git a/src/main/java/se/urmo/game/state/CollisionChecker.java b/src/main/java/se/urmo/game/state/CollisionChecker.java deleted file mode 100644 index fb93ff4..0000000 --- a/src/main/java/se/urmo/game/state/CollisionChecker.java +++ /dev/null @@ -1,39 +0,0 @@ -package se.urmo.game.state; - -import se.urmo.game.GameMap; - -public class CollisionChecker { - private GameMap map; - - public CollisionChecker(GameMap map) { - this.map = map; - } - - public boolean canMoveTo(int dir, int objectLeft, int objectTop, int width, int height) { - int objectRight = objectLeft + width; - int objectBotton = objectTop + height; - return switch (dir){ - case 0 -> isPassibleRight(objectRight, objectTop, objectBotton);// right - case 1 -> isPassibleLeft(objectLeft, objectTop, objectBotton);// right - case 2 -> isPassibleBottom(objectLeft, objectRight, objectBotton); - case 3 -> isPassibleTop(objectLeft, objectRight, objectTop);// right - default -> throw new IllegalArgumentException("Invalid dir: " + dir); - }; - } - - private boolean isPassibleBottom(int objectLeft, int objectRight, int objectBotton) { - return map.isPassable(objectLeft, objectBotton) && map.isPassable(objectRight, objectBotton); - } - - private boolean isPassibleTop(int objectLeft, int objectRight, int objectTop) { - return map.isPassable(objectLeft, objectTop) && map.isPassable(objectRight, objectTop); - } - - private boolean isPassibleLeft(int objectLeft, int objectTop, int objectBotton) { - return map.isPassable(objectLeft, objectTop) && map.isPassable(objectLeft, objectBotton); - } - - private boolean isPassibleRight(int objectRight, int objectTop, int objectBotton) { - return map.isPassable(objectRight, objectTop) && map.isPassable(objectRight, objectBotton); - } -} diff --git a/src/main/java/se/urmo/game/state/GameState.java b/src/main/java/se/urmo/game/state/GameState.java new file mode 100644 index 0000000..a2b27e4 --- /dev/null +++ b/src/main/java/se/urmo/game/state/GameState.java @@ -0,0 +1,13 @@ +package se.urmo.game.state; + +import java.awt.*; +import java.awt.event.KeyEvent; + +public interface GameState { + void update(); + void render(Graphics2D g); + + void keyPressed(KeyEvent e); + + void keyReleased(KeyEvent e); +} diff --git a/src/main/java/se/urmo/game/state/GameStateManager.java b/src/main/java/se/urmo/game/state/GameStateManager.java new file mode 100644 index 0000000..aaef185 --- /dev/null +++ b/src/main/java/se/urmo/game/state/GameStateManager.java @@ -0,0 +1,35 @@ +package se.urmo.game.state; + +import se.urmo.game.Game; + +import java.awt.*; +import java.util.HashMap; +import java.util.Map; + +public class GameStateManager { + private final Game game; + private Map states = new HashMap<>(); + private GameState currentState; + + public GameStateManager(Game game) { + this.game = game; + states.put(GameStateType.PLAYING, new PlayingState(game, this)); + setState(GameStateType.PLAYING); + } + + private void setState(GameStateType type) { + currentState = states.get(type); + } + + public void update() { + currentState.update(); + } + + public void render(Graphics2D g) { + currentState.render(g); + } + + public GameState getCurrentState() { + return currentState; + } +} diff --git a/src/main/java/se/urmo/game/state/GameStateType.java b/src/main/java/se/urmo/game/state/GameStateType.java new file mode 100644 index 0000000..4dcd68b --- /dev/null +++ b/src/main/java/se/urmo/game/state/GameStateType.java @@ -0,0 +1,5 @@ +package se.urmo.game.state; + +public enum GameStateType { + PLAYING, +} diff --git a/src/main/java/se/urmo/game/state/Playing.java b/src/main/java/se/urmo/game/state/Playing.java deleted file mode 100644 index 38d55ad..0000000 --- a/src/main/java/se/urmo/game/state/Playing.java +++ /dev/null @@ -1,44 +0,0 @@ -package se.urmo.game.state; - -import se.urmo.game.Game; -import se.urmo.game.GameMap; -import se.urmo.game.PacMan; - -import java.awt.*; -import java.awt.event.KeyEvent; - - -public class Playing { - private PacMan pacMan; - private GameMap map = new GameMap(); - private final CollisionChecker collisionChecker = new CollisionChecker(map); - public Playing(Game game) { - this.pacMan = new PacMan(game, collisionChecker); - } - - public void update() { - pacMan.update(); - } - - public void keyPressed(KeyEvent e) { - pacMan.setMoving(true); - switch (e.getKeyCode()) { - case KeyEvent.VK_A -> pacMan.setLeft(); - case KeyEvent.VK_D -> pacMan.setRight(); - case KeyEvent.VK_W -> pacMan.setUp(); - case KeyEvent.VK_S -> pacMan.setDown(); - } - } - - public void draw(Graphics g) { - map.draw(g); - pacMan.draw(g); - } - - public void keyReleased() { - pacMan.setMoving(false); - } - public GameMap getMap() { - return map; - } -} diff --git a/src/main/java/se/urmo/game/state/PlayingState.java b/src/main/java/se/urmo/game/state/PlayingState.java new file mode 100644 index 0000000..3314788 --- /dev/null +++ b/src/main/java/se/urmo/game/state/PlayingState.java @@ -0,0 +1,46 @@ +package se.urmo.game.state; + +import se.urmo.game.*; + +import java.awt.*; +import java.awt.event.KeyEvent; + +public class PlayingState implements GameState { + private final Game game; + private final GameStateManager gameStateManager; + private PacMan pacman; + private GameMap map; + public PlayingState(Game game, GameStateManager gameStateManager) { + this.game = game; + this.gameStateManager = gameStateManager; + this.map = new GameMap(); + this.pacman = new PacMan(game, new CollisionChecker(map)); + } + + @Override + public void update() { + pacman.update(); + } + + @Override + public void render(Graphics2D g) { + map.draw(g); + pacman.draw(g); + } + + @Override + public void keyPressed(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_W -> pacman.setDirection(Direction.UP); + case KeyEvent.VK_S -> pacman.setDirection(Direction.DOWN); + case KeyEvent.VK_A -> pacman.setDirection(Direction.LEFT); + case KeyEvent.VK_D -> pacman.setDirection(Direction.RIGHT); + } + } + + @Override + public void keyReleased(KeyEvent e) { + pacman.setMoving(false); + pacman.setDirection(Direction.NONE); + } +}