diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..38f5d98 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM openjdk:8-slim-buster + +COPY . . +RUN apt update && \ + apt install -y vim git && \ + apt install -y libxext6 libxrender1 libxtst6 libfreetype6 libfontconfig1 && \ + echo "Done!" + +# Dev environment: +# docker run -d --name bom-ai -e DISPLAY="host.docker.internal:0" -v /tmp/.X11-unix:/tmp/.X11-unix -v $HOME:/root openjdk:8-slim-buster sleep 999999999 \ No newline at end of file diff --git a/res/font/Minecraft.ttf b/res/font/Minecraft.ttf new file mode 100644 index 0000000..85c1472 Binary files /dev/null and b/res/font/Minecraft.ttf differ diff --git a/res/font/SuperPixel-m2L8j.ttf b/res/font/SuperPixel-m2L8j.ttf new file mode 100644 index 0000000..f7691c8 Binary files /dev/null and b/res/font/SuperPixel-m2L8j.ttf differ diff --git a/res/levels/Level1.txt b/res/levels/Level1P_1.txt similarity index 93% rename from res/levels/Level1.txt rename to res/levels/Level1P_1.txt index 6f00589..75c2936 100644 --- a/res/levels/Level1.txt +++ b/res/levels/Level1P_1.txt @@ -4,9 +4,9 @@ # # # #*# # #*#*# # # #*#*#*# # # x* b** * * * * # # # # # # #*# # #*#*# # # # #*# -#f x ** * * # +# x ** * * # # # # # # # # # # #*# #*# # # # -#* * * * * # +# * * * * # # # # # #*# # # #*#*# # # # # # #* ** * * # # #*# # # # # # #*# # # # # # # diff --git a/res/levels/Level2.txt b/res/levels/Level1P_2.txt similarity index 100% rename from res/levels/Level2.txt rename to res/levels/Level1P_2.txt diff --git a/res/levels/Level1P_3.txt b/res/levels/Level1P_3.txt new file mode 100644 index 0000000..b064569 --- /dev/null +++ b/res/levels/Level1P_3.txt @@ -0,0 +1,14 @@ +3 13 31 +############################### +#p * *3 * ** * * *# +# # # # #*# # #*# # # # # #*# # +# * * *** ** # * ** # +# #*# # # # #4# #b#1# # # # #*# +# * ** * **# +# # #*# 3 # # # #*#*#*#*# # # # +# * * * *x* * 4 # +# # # # #*# # # # # # #*# #*# # +# **2* *2 # +# # # # # # # # # #*# # # # #*# +# **4 ** * 5 # +############################### diff --git a/res/levels/Level2P_1.txt b/res/levels/Level2P_1.txt new file mode 100644 index 0000000..a551e0c --- /dev/null +++ b/res/levels/Level2P_1.txt @@ -0,0 +1,14 @@ +1 13 31 +############################### +#p s* * 1 * * * * * # +# # # #*# # #*#*# # # #*#*#*# # +# x* b** * * * * # +# # # # # #*# # #*#*# # # # #*# +# x ** * * # +# # # # # # # # # #*# #*# # # # +# * * * * # +# # # # #*# # # #*#*# # # # # # +#* ** * * # +# #*# # # # # # #*# # # # # # # +#a * * * # +############################### \ No newline at end of file diff --git a/res/levels/Level2P_2.txt b/res/levels/Level2P_2.txt new file mode 100644 index 0000000..b88931e --- /dev/null +++ b/res/levels/Level2P_2.txt @@ -0,0 +1,14 @@ +2 13 31 +############################### +#p * *2 * ** * * *# +# # # # #*# # #*# # # # # #*# # +# * * *** ** * ** # +# #*# # # # #1# #b#1# # # # #*# +# * ** * **# +# # #*# # # # # #*#*#*#*# # # # +# * * * *x* * # +# # # # #*# # # # # # #*# #*# # +# **** *1 # +# # # # # # # # # #*# # # # #*# +#a ** * * 3 # +############################### diff --git a/res/levels/Level2P_3.txt b/res/levels/Level2P_3.txt new file mode 100644 index 0000000..b29f0ef --- /dev/null +++ b/res/levels/Level2P_3.txt @@ -0,0 +1,14 @@ +3 13 31 +############################### +#p * *3 * ** * * *# +# # # # #*# # #*# # # # # #*# # +# * * *** ** # * ** # +# #*# # # # #4# #b#1# # # # #*# +# * ** * **# +# # #*# 3 # # # #*#*#*#*# # # # +# * * * *x* * 4 # +# # # # #*# # # # # # #*# #*# # +# **2* *2 # +# # # # # # # # # #*# # # # #*# +#a**4 ** * 5 # +############################### diff --git a/res/menu/7729559.svg b/res/menu/7729559.svg new file mode 100644 index 0000000..32a2e57 --- /dev/null +++ b/res/menu/7729559.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/menu/9165683_home_house_icon.png b/res/menu/9165683_home_house_icon.png new file mode 100644 index 0000000..df9e3c5 Binary files /dev/null and b/res/menu/9165683_home_house_icon.png differ diff --git a/res/menu/background.jpg b/res/menu/background.jpg new file mode 100644 index 0000000..233135e Binary files /dev/null and b/res/menu/background.jpg differ diff --git a/res/menu/f15591389f71a57ec5e54ba0f48ed385.jpg b/res/menu/f15591389f71a57ec5e54ba0f48ed385.jpg new file mode 100644 index 0000000..49189ad Binary files /dev/null and b/res/menu/f15591389f71a57ec5e54ba0f48ed385.jpg differ diff --git a/res/menu/forest_by_forheksed_d9q4k94-fullview 1.png b/res/menu/forest_by_forheksed_d9q4k94-fullview 1.png new file mode 100644 index 0000000..b7fe19d Binary files /dev/null and b/res/menu/forest_by_forheksed_d9q4k94-fullview 1.png differ diff --git a/res/menu/gameover.png b/res/menu/gameover.png new file mode 100644 index 0000000..1020913 Binary files /dev/null and b/res/menu/gameover.png differ diff --git a/res/menu/icons8-menu-50.png b/res/menu/icons8-menu-50.png new file mode 100644 index 0000000..5a897f7 Binary files /dev/null and b/res/menu/icons8-menu-50.png differ diff --git a/res/menu/icons8-restart-50.png b/res/menu/icons8-restart-50.png new file mode 100644 index 0000000..a206a9b Binary files /dev/null and b/res/menu/icons8-restart-50.png differ diff --git a/res/menu/restart.png b/res/menu/restart.png new file mode 100644 index 0000000..d4b6a8c Binary files /dev/null and b/res/menu/restart.png differ diff --git a/res/sprites/fireIcon.png b/res/sprites/fireIcon.png new file mode 100644 index 0000000..2b2f529 Binary files /dev/null and b/res/sprites/fireIcon.png differ diff --git a/res/sprites/player2_down.png b/res/sprites/player2_down.png new file mode 100644 index 0000000..2735443 Binary files /dev/null and b/res/sprites/player2_down.png differ diff --git a/res/sprites/player2_down_1.png b/res/sprites/player2_down_1.png new file mode 100644 index 0000000..8eddb58 Binary files /dev/null and b/res/sprites/player2_down_1.png differ diff --git a/res/sprites/player2_down_2.png b/res/sprites/player2_down_2.png new file mode 100644 index 0000000..46092ac Binary files /dev/null and b/res/sprites/player2_down_2.png differ diff --git a/res/sprites/player2_left.png b/res/sprites/player2_left.png new file mode 100644 index 0000000..e0b74d4 Binary files /dev/null and b/res/sprites/player2_left.png differ diff --git a/res/sprites/player2_left_1.png b/res/sprites/player2_left_1.png new file mode 100644 index 0000000..e8c99b2 Binary files /dev/null and b/res/sprites/player2_left_1.png differ diff --git a/res/sprites/player2_left_2.png b/res/sprites/player2_left_2.png new file mode 100644 index 0000000..8d68f29 Binary files /dev/null and b/res/sprites/player2_left_2.png differ diff --git a/res/sprites/player2_right.png b/res/sprites/player2_right.png new file mode 100644 index 0000000..6589405 Binary files /dev/null and b/res/sprites/player2_right.png differ diff --git a/res/sprites/player2_right_1.png b/res/sprites/player2_right_1.png new file mode 100644 index 0000000..bb03279 Binary files /dev/null and b/res/sprites/player2_right_1.png differ diff --git a/res/sprites/player2_right_2.png b/res/sprites/player2_right_2.png new file mode 100644 index 0000000..da73c79 Binary files /dev/null and b/res/sprites/player2_right_2.png differ diff --git a/res/sprites/player2_up.png b/res/sprites/player2_up.png new file mode 100644 index 0000000..38dd3ab Binary files /dev/null and b/res/sprites/player2_up.png differ diff --git a/res/sprites/player2_up_1.png b/res/sprites/player2_up_1.png new file mode 100644 index 0000000..a1f927a Binary files /dev/null and b/res/sprites/player2_up_1.png differ diff --git a/res/sprites/player2_up_2.png b/res/sprites/player2_up_2.png new file mode 100644 index 0000000..df6d5d0 Binary files /dev/null and b/res/sprites/player2_up_2.png differ diff --git a/res/textures/classic.png b/res/textures/classic.png index 33db1e3..e93685c 100644 Binary files a/res/textures/classic.png and b/res/textures/classic.png differ diff --git a/src/uet/oop/bomberman/Board.java b/src/uet/oop/bomberman/Board.java index 4130228..360f986 100644 --- a/src/uet/oop/bomberman/Board.java +++ b/src/uet/oop/bomberman/Board.java @@ -1,358 +1,115 @@ package uet.oop.bomberman; -import uet.oop.bomberman.entities.Entity; -import uet.oop.bomberman.entities.Message; -import uet.oop.bomberman.entities.bomb.Bomb; -import uet.oop.bomberman.entities.bomb.FlameSegment; -import uet.oop.bomberman.entities.character.Bomber; -import uet.oop.bomberman.entities.character.Character; -import uet.oop.bomberman.exceptions.LoadLevelException; +import uet.oop.bomberman.agent.Agent; +import uet.oop.bomberman.base.Copyable; +import uet.oop.bomberman.base.IEntityManager; +import uet.oop.bomberman.base.IGameInfoManager; +import uet.oop.bomberman.base.ILevelManager; +import uet.oop.bomberman.entities.character.action.Action; +import uet.oop.bomberman.entities.character.exceptions.CharacterActionException; import uet.oop.bomberman.graphics.IRender; import uet.oop.bomberman.graphics.Screen; -import uet.oop.bomberman.input.Keyboard; -import uet.oop.bomberman.level.FileLevelLoader; -import uet.oop.bomberman.level.LevelLoader; +import uet.oop.bomberman.manager.EntityManager; +import uet.oop.bomberman.manager.GameInfoManager; +import uet.oop.bomberman.manager.LevelManager; -import java.awt.*; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; /** * Quản lý thao tác điều khiển, load level, render các màn hình của game */ -public class Board implements IRender { - protected LevelLoader _levelLoader; +public class Board implements Copyable, IRender { protected Game _game; - protected Keyboard _input; - protected Screen _screen; - - public Entity[] _entities; - public List _characters = new ArrayList<>(); - protected List _bombs = new ArrayList<>(); - private List _messages = new ArrayList<>(); - - private int _screenToShow = -1; //1:endgame, 2:changelevel, 3:paused - - private int _time = Game.TIME; - private int _points = Game.POINTS; - - public Board(Game game, Keyboard input, Screen screen) { + + private List agents = new ArrayList<>(); + + private IEntityManager entityManager; + private IGameInfoManager gameInfoManager; + private ILevelManager levelManager; + + public Board(Game game, Screen screen) { _game = game; - _input = input; - _screen = screen; - - loadLevel(1); //start in level 1 - } - - @Override - public void update() { - if( _game.isPaused() ) return; - - updateEntities(); - updateCharacters(); - updateBombs(); - updateMessages(); - detectEndGame(); - - for (int i = 0; i < _characters.size(); i++) { - Character a = _characters.get(i); - if(a.isRemoved()) _characters.remove(i); - } + levelManager = new LevelManager(this); + levelManager.loadGlobalLevel(); } @Override - public void render(Screen screen) { - if( _game.isPaused() ) return; - - //only render the visible part of screen - int x0 = Screen.xOffset >> 4; //tile precision, -> left X - int x1 = (Screen.xOffset + screen.getWidth() + Game.TILES_SIZE) / Game.TILES_SIZE; // -> right X - int y0 = Screen.yOffset >> 4; - int y1 = (Screen.yOffset + screen.getHeight()) / Game.TILES_SIZE; //render one tile plus to fix black margins - - for (int y = y0; y < y1; y++) { - for (int x = x0; x < x1; x++) { - _entities[x + y * _levelLoader.getWidth()].render(screen); - } - } - - renderBombs(screen); - renderCharacter(screen); - - } - - public void nextLevel() { - Game.setBombRadius(1); - Game.setBombRate(1); - Game.setBomberSpeed(1.0); - loadLevel(_levelLoader.getLevel() + 1); - } - - public void loadLevel(int level) { - _time = Game.TIME; - _screenToShow = 2; - _game.resetScreenDelay(); - _game.pause(); - _characters.clear(); - _bombs.clear(); - _messages.clear(); - - try { - _levelLoader = new FileLevelLoader(this, level); - _entities = new Entity[_levelLoader.getHeight() * _levelLoader.getWidth()]; - - _levelLoader.createEntities(); - } catch (LoadLevelException e) { - endGame(); - } - } - - protected void detectEndGame() { - if(_time <= 0) - endGame(); - } - - public void endGame() { - _screenToShow = 1; - _game.resetScreenDelay(); - _game.pause(); - } - - public boolean detectNoEnemies() {// phat hien enemies - int total = 0; - for (int i = 0; i < _characters.size(); i++) { - if(_characters.get(i) instanceof Bomber == false) - ++total; - } - - return total == 0; - } - - public void drawScreen(Graphics g) { - switch (_screenToShow) { - case 1: - _screen.drawEndGame(g, _points); - break; - case 2: - _screen.drawChangeLevel(g, _levelLoader.getLevel()); - break; - case 3: - _screen.drawPaused(g); - break; - } - } - - public Entity getEntity(double x, double y, Character m) { - - Entity res = null; - - res = getFlameSegmentAt((int)x, (int)y); - if( res != null) return res; - - res = getBombAt(x, y); - if( res != null) return res; - - res = getCharacterAtExcluding((int)x, (int)y, m); - if( res != null) return res; - - res = getEntityAt((int)x, (int)y); - - return res; - } - - public List getBombs() { - return _bombs; - } - - public Bomb getBombAt(double x, double y) { - Iterator bs = _bombs.iterator(); - Bomb b; - while(bs.hasNext()) { - b = bs.next(); - if(b.getX() == (int)x && b.getY() == (int)y) - return b; - } - - return null; - } + public synchronized void update() { + if (gameInfoManager.isPaused()) + return; - public Bomber getBomber() { - Iterator itr = _characters.iterator(); - - Character cur; - while(itr.hasNext()) { - cur = itr.next(); - - if(cur instanceof Bomber) - return (Bomber) cur; - } - - return null; - } - - public Character getCharacterAtExcluding(int x, int y, Character a) { - Iterator itr = _characters.iterator(); - - Character cur; - while(itr.hasNext()) { - cur = itr.next(); - if(cur == a) { - continue; - } - - if(cur.getXTile() == x && cur.getYTile() == y) { - return cur; - } - - } - - return null; - } - - public FlameSegment getFlameSegmentAt(int x, int y) { - Iterator bs = _bombs.iterator(); - Bomb b; - while(bs.hasNext()) { - b = bs.next(); - - FlameSegment e = b.flameAt(x, y); - if(e != null) { - return e; - } - } - - return null; - } - - public Entity getEntityAt(double x, double y) { - return _entities[(int)x + (int)y * _levelLoader.getWidth()]; - } - - public void addEntity(int pos, Entity e) { - _entities[pos] = e; - } - - public void addCharacter(Character e) { - _characters.add(e); - } - - public void addBomb(Bomb e) { - _bombs.add(e); - } - - public void addMessage(Message e) { - _messages.add(e); - } + entityManager.update(); + gameInfoManager.update(); - protected void renderCharacter(Screen screen) { - Iterator itr = _characters.iterator(); - - while(itr.hasNext()) - itr.next().render(screen); - } - - protected void renderBombs(Screen screen) { - Iterator itr = _bombs.iterator(); - - while(itr.hasNext()) - itr.next().render(screen); - } - - public void renderMessages(Graphics g) { - Message m; - for (int i = 0; i < _messages.size(); i++) { - m = _messages.get(i); - - g.setFont(new Font("Arial", Font.PLAIN, m.getSize())); - g.setColor(m.getColor()); - g.drawString(m.getMessage(), (int)m.getX() - Screen.xOffset * Game.SCALE, (int)m.getY()); - } - } - - protected void updateEntities() { - if( _game.isPaused() ) return; - for (int i = 0; i < _entities.length; i++) { - _entities[i].update(); - } - } - - protected void updateCharacters() { - if( _game.isPaused() ) return; - Iterator itr = _characters.iterator(); - - while(itr.hasNext() && !_game.isPaused()) - itr.next().update(); - } - - protected void updateBombs() { - if( _game.isPaused() ) return; - Iterator itr = _bombs.iterator(); - - while(itr.hasNext()) - itr.next().update(); - } - - protected void updateMessages() { - if( _game.isPaused() ) return; - Message m; - int left; - for (int i = 0; i < _messages.size(); i++) { - m = _messages.get(i); - left = m.getDuration(); - - if(left > 0) - m.setDuration(--left); - else - _messages.remove(i); - } + processAgentAction(); } - public int subtractTime() { - if(_game.isPaused()) - return this._time; - else - return this._time--; + private void clearAgents() { + agents.clear(); } - public Keyboard getInput() { - return _input; + public void addAgent(Agent agent) { + agents.add(agent); } - public LevelLoader getLevel() { - return _levelLoader; - } + private void processAgentAction() { - public Game getGame() { - return _game; + for (Agent agent : agents) { + List actions = agent.getNextActions(); + for (Action action : actions) { + try { + agent.getCharacter().performAction(action); + } catch (CharacterActionException ignored) { + } + } + } } - public int getShow() { - return _screenToShow; + @Override + public synchronized void render(Screen screen) { + if (gameInfoManager.isPaused()) + return; + if (gameInfoManager.getTime() <= 0) { + levelManager.endGame(); + } + entityManager.render(screen); + } + + public synchronized void init() { + gameInfoManager = new GameInfoManager(); + entityManager = new EntityManager( + levelManager.getBoardWidth(), + levelManager.getBoardHeight(), + gameInfoManager, + levelManager + ); + gameInfoManager.setEntityManager(entityManager); + gameInfoManager.pause(); + _game.setScreenToShow(2); + _game.resetScreenDelay(); } - public void setShow(int i) { - _screenToShow = i; + public void clear() { + clearAgents(); } - public int getTime() { - return _time; + public IEntityManager getEntityManager() { + return entityManager; } - public int getPoints() { - return _points; + public IGameInfoManager getGameInfoManager() { + return gameInfoManager; } - public void addPoints(int points) { - this._points += points; - } - - public int getWidth() { - return _levelLoader.getWidth(); + public ILevelManager getLevelManager() { + return levelManager; } - public int getHeight() { - return _levelLoader.getHeight(); + @Override + public Board copy() { + // TODO Auto-generated method stub + return null; } - + } diff --git a/src/uet/oop/bomberman/BombermanGame.java b/src/uet/oop/bomberman/BombermanGame.java index 0000736..959fb1c 100644 --- a/src/uet/oop/bomberman/BombermanGame.java +++ b/src/uet/oop/bomberman/BombermanGame.java @@ -4,9 +4,9 @@ import uet.oop.bomberman.sound.Sound; public class BombermanGame { - + public static void main(String[] args) { - Sound.play("soundtrack"); + Sound.play("soundtrack"); new Frame(); } } diff --git a/src/uet/oop/bomberman/Game.java b/src/uet/oop/bomberman/Game.java index 53e5c53..8ae33bf 100644 --- a/src/uet/oop/bomberman/Game.java +++ b/src/uet/oop/bomberman/Game.java @@ -1,13 +1,21 @@ package uet.oop.bomberman; +import uet.oop.bomberman.base.IGameInfoManager; +import uet.oop.bomberman.entities.Entity; import uet.oop.bomberman.graphics.Screen; import uet.oop.bomberman.gui.Frame; import uet.oop.bomberman.input.Keyboard; +import uet.oop.bomberman.screen.SelectGameModeScreen; +import uet.oop.bomberman.screen.DeadScreen; +import uet.oop.bomberman.screen.SelectLevelScreen; +import uet.oop.bomberman.utils.EScreenName; +import uet.oop.bomberman.utils.Global; import java.awt.*; import java.awt.image.BufferStrategy; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; +import java.util.Optional; /** * Tạo vòng lặp cho game, lưu trữ một vài tham số cấu hình toàn cục, @@ -15,171 +23,233 @@ */ public class Game extends Canvas { - - public static final int TILES_SIZE = 16, - WIDTH = TILES_SIZE * (31 / 2), - HEIGHT = 13 * TILES_SIZE; + WIDTH = TILES_SIZE * (31 / 2), + HEIGHT = 13 * TILES_SIZE; public static int SCALE = 3; - + public static final String TITLE = "BombermanGame"; - - private static final int BOMBRATE = 1; - private static final int BOMBRADIUS = 1; - private static final double BOMBERSPEED = 1.0;//toc do bomber - + public static final int TICKS_PER_SECOND = 60; + + public static final int BOMBRATE = 1; + public static final int BOMBRADIUS = 1; + public static final double BOMBERSPEED = 3.0;// toc do bomber + public static final int TIME = 200; public static final int POINTS = 0; - + protected static int SCREENDELAY = 3; - protected static int bombRate = BOMBRATE; - protected static int bombRadius = BOMBRADIUS; - protected static double bomberSpeed = BOMBERSPEED; - - protected int _screenDelay = SCREENDELAY; - - private Keyboard _input; + private boolean _running = false; - private boolean _paused = true; - private Board _board; private Screen screen; private Frame _frame; - + private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); - private int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData(); - + private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + + // game variable + private int frames; + private int updates; + private long timer; + + // game screens + public SelectLevelScreen selectLevelScreen; + private SelectGameModeScreen selectGameModeScreen; + public DeadScreen deadScreen; + + private int _screenToShow = -1; // 1:endgame, 2:changelevel, 3:paused + public Game(Frame frame) { _frame = frame; _frame.setTitle(TITLE); - + screen = new Screen(WIDTH, HEIGHT); - _input = new Keyboard(); - - _board = new Board(this, _input, screen); - addKeyListener(_input); + + _board = new Board(this, screen); + addKeyListener(Keyboard.i()); + + initScreen(); + } - - - private void renderGame() { - BufferStrategy bs = getBufferStrategy(); - if(bs == null) { - createBufferStrategy(3); - return; - } - + + private void renderGame(Graphics g) { screen.clear(); - + _board.render(screen); - + for (int i = 0; i < pixels.length; i++) { pixels[i] = screen._pixels[i]; } - - Graphics g = bs.getDrawGraphics(); - + g.drawImage(image, 0, 0, getWidth(), getHeight(), null); - _board.renderMessages(g); - - g.dispose(); - bs.show(); + _board.getGameInfoManager().render(screen, g); + } + + private void renderScreen(Graphics g) { + screen.clear(); + drawScreen(g); } - - private void renderScreen() { + + private void drawScreen(Graphics g) { + switch (getScreenToShow()) { + case 1: + screen.drawEndGame(g, _board.getGameInfoManager().getPoints()); + break; + case 2: + screen.drawChangeLevel(g, Global.gameLevel); + break; + case 3: + screen.drawPaused(g); + break; + } + } + + private void initScreen() { + Global.currentScreen = EScreenName.SELECT_GAME_MODE; + this.selectGameModeScreen = new SelectGameModeScreen(); + this.selectLevelScreen = new SelectLevelScreen(_board); + this.deadScreen = new DeadScreen(this); + } + + private void update() { + Keyboard.i().update(); + switch (Global.currentScreen) { + case GAME_PLAY_SCREEN: + _board.update(); + if (Keyboard.i().pause) { // Kiểm tra nếu phím "p" được nhấn + _screenToShow = 3; // Hiển thị màn hình tạm dừng + _board.getGameInfoManager().pause(); // Đặt trạng thái game là tạm dừng + return; + } + snapCameraToPlayer(); + break; + case SELECT_LEVEL_SCREEN: + selectLevelScreen.update(); + break; + case SELECT_GAME_MODE: + selectGameModeScreen.update(); + break; + case END_GAME_SCREEN: + deadScreen.update(); + break; + } + + } + + private void showScreen() { BufferStrategy bs = getBufferStrategy(); - if(bs == null) { + if (bs == null) { createBufferStrategy(3); return; } - - screen.clear(); - Graphics g = bs.getDrawGraphics(); - - _board.drawScreen(g); + + IGameInfoManager gameInfoManager = _board.getGameInfoManager(); + switch (Global.currentScreen) { + case GAME_PLAY_SCREEN: + Keyboard.i().keyboardInputCallback = Optional.empty(); + if (gameInfoManager.isPaused()) { + if (_screenDelay <= 0) { + _screenToShow = -1; + gameInfoManager.unpause(); + } + + renderScreen(g); + } else { + renderGame(g); + } + + if (Keyboard.i().resume) { + gameInfoManager.unpause(); + _screenToShow = -1; + _screenDelay = 0; + } + frames++; + if (System.currentTimeMillis() - timer > 1000) { + _frame.setTime(gameInfoManager.subtractTime()); + _frame.setPoints(gameInfoManager.getPoints()); + _frame.renderItemTime(); + timer += 1000; + _frame.setTitle(TITLE + " | " + updates + " rate, " + frames + " fps"); + updates = 0; + frames = 0; + + if (_screenToShow == 2) + --_screenDelay; + } + break; + case SELECT_LEVEL_SCREEN: + // TODO: render select level screen + selectLevelScreen.setInput(Keyboard.i()); + selectLevelScreen.drawScreen(g); + break; + case SELECT_GAME_MODE: + selectGameModeScreen.setInput(Keyboard.i()); + selectGameModeScreen.drawScreen(g); + break; + case END_GAME_SCREEN: + deadScreen.setInput(); + deadScreen.drawScreen(g); + break; + } g.dispose(); bs.show(); } - private void update() { - _input.update(); - _board.update(); + private void initGame() { + this.timer = System.currentTimeMillis(); + this.frames = 0; + this.updates = 0; } - + public void start() { _running = true; - - long lastTime = System.nanoTime(); - long timer = System.currentTimeMillis(); - final double ns = 1000000000.0 / 60.0; //nanosecond, 60 frames per second + + initGame(); + + long lastTime = System.nanoTime(); + final double ns = 1000000000.0 / 60.0; // nanosecond, 60 frames per second double delta = 0; - int frames = 0; - int updates = 0; requestFocus(); - while(_running) { + while (_running) { long now = System.nanoTime(); delta += (now - lastTime) / ns; lastTime = now; - while(delta >= 1) { + while (delta >= 1) { update(); updates++; delta--; } - - if(_paused) { - if(_screenDelay <= 0) { - _board.setShow(-1); - _paused = false; - } - - renderScreen(); - } else { - renderGame(); - } - - - frames++; - if(System.currentTimeMillis() - timer > 1000) { - _frame.setTime(_board.subtractTime()); - _frame.setPoints(_board.getPoints()); - timer += 1000; - _frame.setTitle(TITLE + " | " + updates + " rate, " + frames + " fps"); - updates = 0; - frames = 0; - - if(_board.getShow() == 2) - --_screenDelay; - } + + showScreen(); } } - - public static double getBomberSpeed() { - return bomberSpeed; - } - - public static int getBombRate() { - return bombRate; - } - - public static int getBombRadius() { - return bombRadius; - } - - public static void addBomberSpeed(double i) { - bomberSpeed += i; - } - - public static void addBombRadius(int i) { - bombRadius += i; - } - - public static void addBombRate(int i) { - bombRate += i; - } + + private void snapCameraToPlayer() { + int xScroll = calculateXOffset(_board.getEntityManager().getPlayer()); + Screen.setOffset(xScroll, 0); + } + + private int calculateXOffset(Entity entity) { + if(entity == null) return 0; + int temp = Screen.xOffset; + + double x = entity.getX() / 16; + double complement = 0.5; + int firstBreakpoint = _board.getLevelManager().getBoardWidth() / 4; + int lastBreakpoint = _board.getLevelManager().getBoardWidth() - firstBreakpoint; + + if( x > firstBreakpoint + complement && x < lastBreakpoint - complement) { + temp = (int)entity.getX() - (Game.WIDTH / 2); + } + + return temp; + } public void resetScreenDelay() { _screenDelay = SCREENDELAY; @@ -189,22 +259,21 @@ public Board getBoard() { return _board; } - public boolean isPaused() { - return _paused; + public void restartGame() { + Global.currentScreen = EScreenName.GAME_PLAY_SCREEN; + _board.getLevelManager().loadGlobalLevel(); } - - public void pause() { - _paused = true; + + public void startNewGame() { + Global.currentScreen = EScreenName.SELECT_LEVEL_SCREEN; } - public static void setBombRate(int bombRate) { - Game.bombRate = bombRate; - } - public static void setBombRadius(int bombRadius) { - Game.bombRadius = bombRadius; - } + public int getScreenToShow() { + return _screenToShow; + } + + public void setScreenToShow(int screenToShow) { + this._screenToShow = screenToShow; + } - public static void setBomberSpeed(double bomberSpeed) { - Game.bomberSpeed = bomberSpeed; - } } diff --git a/src/uet/oop/bomberman/agent/Agent.java b/src/uet/oop/bomberman/agent/Agent.java new file mode 100644 index 0000000..9bad770 --- /dev/null +++ b/src/uet/oop/bomberman/agent/Agent.java @@ -0,0 +1,17 @@ +package uet.oop.bomberman.agent; + +import uet.oop.bomberman.entities.character.Character; + +public abstract class Agent implements IAgent { + + protected Character character; + + public Agent(Character character) { + this.character = character; + } + + public Character getCharacter() { + return character; + } + +} diff --git a/src/uet/oop/bomberman/agent/IAgent.java b/src/uet/oop/bomberman/agent/IAgent.java new file mode 100644 index 0000000..8f5ffd2 --- /dev/null +++ b/src/uet/oop/bomberman/agent/IAgent.java @@ -0,0 +1,11 @@ +package uet.oop.bomberman.agent; + +import java.util.List; + +import uet.oop.bomberman.entities.character.action.Action; + +public interface IAgent { + + public List getNextActions(); + +} diff --git a/src/uet/oop/bomberman/agent/KeyboardAgent.java b/src/uet/oop/bomberman/agent/KeyboardAgent.java new file mode 100644 index 0000000..77a1e3d --- /dev/null +++ b/src/uet/oop/bomberman/agent/KeyboardAgent.java @@ -0,0 +1,50 @@ +package uet.oop.bomberman.agent; + +import java.util.ArrayList; +import java.util.List; + +import uet.oop.bomberman.entities.character.Bomber; +import uet.oop.bomberman.entities.character.Character; +import uet.oop.bomberman.entities.character.action.Action; +import uet.oop.bomberman.entities.character.action.ActionConstants; +import uet.oop.bomberman.entities.character.action.ActionMove; +import uet.oop.bomberman.input.Keyboard; + +public class KeyboardAgent extends Agent { + + public KeyboardAgent(Character character) { + super(character); + } + + @Override + public List getNextActions() { + List actions = getMoveActions(); + + if (character instanceof Bomber) { + if (Keyboard.i().space) { + actions.add(ActionConstants.PLACE_BOMB); + } + } + return actions; + } + + private List getMoveActions() { + int xa = 0, ya = 0; + if (Keyboard.i().up) + ya--; + if (Keyboard.i().down) + ya++; + if (Keyboard.i().left) + xa--; + if (Keyboard.i().right) + xa++; + + List actions = new ArrayList<>(); + if (xa != 0 || ya != 0) { + ActionMove actionMove = new ActionMove(xa, ya); + actions.add(actionMove); + } + return actions; + } + +} diff --git a/src/uet/oop/bomberman/agent/KeyboardAgentPlayer1.java b/src/uet/oop/bomberman/agent/KeyboardAgentPlayer1.java new file mode 100644 index 0000000..5e95761 --- /dev/null +++ b/src/uet/oop/bomberman/agent/KeyboardAgentPlayer1.java @@ -0,0 +1,51 @@ +package uet.oop.bomberman.agent; + +import java.util.ArrayList; +import java.util.List; + +import uet.oop.bomberman.entities.character.Bomber; +import uet.oop.bomberman.entities.character.Character; +import uet.oop.bomberman.entities.character.action.Action; +import uet.oop.bomberman.entities.character.action.ActionConstants; +import uet.oop.bomberman.entities.character.action.ActionMove; +import uet.oop.bomberman.input.Keyboard; + +public class KeyboardAgentPlayer1 extends KeyboardAgent { + // biến + + public KeyboardAgentPlayer1(Character character) { + super(character); + } + + @Override + public List getNextActions() { + List actions = getMoveActions(); + + if (character instanceof Bomber) { + if (Keyboard.i().player1_bomb) { + actions.add(ActionConstants.PLACE_BOMB); + } + } + return actions; + } + + private List getMoveActions() { + int xa = 0, ya = 0; + if (Keyboard.i().player1_up) + ya--; + if (Keyboard.i().player1_down) + ya++; + if (Keyboard.i().player1_left) + xa--; + if (Keyboard.i().player1_right) + xa++; + + List actions = new ArrayList<>(); + if (xa != 0 || ya != 0) { + ActionMove actionMove = new ActionMove(xa, ya); + actions.add(actionMove); + } + return actions; + } + +} diff --git a/src/uet/oop/bomberman/agent/KeyboardAgentPlayer2.java b/src/uet/oop/bomberman/agent/KeyboardAgentPlayer2.java new file mode 100644 index 0000000..1724ced --- /dev/null +++ b/src/uet/oop/bomberman/agent/KeyboardAgentPlayer2.java @@ -0,0 +1,51 @@ +package uet.oop.bomberman.agent; + +import java.util.ArrayList; +import java.util.List; + +import uet.oop.bomberman.entities.character.Bomber; +import uet.oop.bomberman.entities.character.Character; +import uet.oop.bomberman.entities.character.action.Action; +import uet.oop.bomberman.entities.character.action.ActionConstants; +import uet.oop.bomberman.entities.character.action.ActionMove; +import uet.oop.bomberman.input.Keyboard; + +public class KeyboardAgentPlayer2 extends KeyboardAgent { + + public KeyboardAgentPlayer2(Character character) { + super(character); + // TODO Auto-generated constructor stub + } + + @Override + public List getNextActions() { + List actions = getMoveActions(); + + if (character instanceof Bomber) { + if (Keyboard.i().player2_bomb) { + actions.add(ActionConstants.PLACE_BOMB); + } + } + return actions; + } + + private List getMoveActions() { + int xa = 0, ya = 0; + if (Keyboard.i().player2_up) + ya--; + if (Keyboard.i().player2_down) + ya++; + if (Keyboard.i().player2_left) + xa--; + if (Keyboard.i().player2_right) + xa++; + + List actions = new ArrayList<>(); + if (xa != 0 || ya != 0) { + ActionMove actionMove = new ActionMove(xa, ya); + actions.add(actionMove); + } + return actions; + } + +} diff --git a/src/uet/oop/bomberman/agent/MovingAgent.java b/src/uet/oop/bomberman/agent/MovingAgent.java new file mode 100644 index 0000000..0f4e6c2 --- /dev/null +++ b/src/uet/oop/bomberman/agent/MovingAgent.java @@ -0,0 +1,43 @@ +package uet.oop.bomberman.agent; + +import java.util.ArrayList; +import java.util.List; + +import uet.oop.bomberman.entities.character.Character; +import uet.oop.bomberman.entities.character.action.Action; +import uet.oop.bomberman.entities.character.action.ActionConstants; +import uet.oop.bomberman.entities.character.enemy.ai.AI; + +public class MovingAgent extends Agent { + + private AI ai; + + public MovingAgent(Character character, AI ai) { + super(character); + this.ai = ai; + } + + public Action getNextAction() { + int direction = ai.calculateDirection(); + switch (direction) { + case 0: + return ActionConstants.MOVE_UP; + case 1: + return ActionConstants.MOVE_RIGHT; + case 2: + return ActionConstants.MOVE_DOWN; + case 3: + return ActionConstants.MOVE_LEFT; + default: + return ActionConstants.DO_NOTHING; + } + } + + @Override + public List getNextActions() { + List actions = new ArrayList<>(); + actions.add(getNextAction()); + return actions; + } + +} diff --git a/src/uet/oop/bomberman/base/Copyable.java b/src/uet/oop/bomberman/base/Copyable.java new file mode 100644 index 0000000..bd9721e --- /dev/null +++ b/src/uet/oop/bomberman/base/Copyable.java @@ -0,0 +1,7 @@ +package uet.oop.bomberman.base; + +public interface Copyable { + + public Copyable copy(); + +} diff --git a/src/uet/oop/bomberman/base/IBombManager.java b/src/uet/oop/bomberman/base/IBombManager.java new file mode 100644 index 0000000..24e321d --- /dev/null +++ b/src/uet/oop/bomberman/base/IBombManager.java @@ -0,0 +1,16 @@ +package uet.oop.bomberman.base; + +import java.util.List; + +import uet.oop.bomberman.entities.bomb.Bomb; +import uet.oop.bomberman.entities.bomb.FlameSegment; +import uet.oop.bomberman.graphics.IRender; + +public interface IBombManager extends IRender { + + public List getBombs(); + public Bomb getBombAt(double x, double y); + public void addBomb(Bomb e); + public FlameSegment getFlameSegmentAt(int x, int y); + +} \ No newline at end of file diff --git a/src/uet/oop/bomberman/base/ICharacterManager.java b/src/uet/oop/bomberman/base/ICharacterManager.java new file mode 100644 index 0000000..7f457bb --- /dev/null +++ b/src/uet/oop/bomberman/base/ICharacterManager.java @@ -0,0 +1,27 @@ +package uet.oop.bomberman.base; + +import java.util.List; + +import uet.oop.bomberman.entities.character.Character; +import uet.oop.bomberman.graphics.IRender; + +public interface ICharacterManager extends IRender { + + public List getCharacters(); + + public Character getCharacterAtExcluding(int x, int y, Character a); + + public void addCharacter(Character e); + + public void setPlayer(Character character); + + public Character getPlayer(); + + public void handleOnDeath(Character character, Character killer); + + public void handleAfterDeath(Character character); + + public void addPlayer(Character e); + + public List getPlayers(); +} \ No newline at end of file diff --git a/src/uet/oop/bomberman/base/IEntityManager.java b/src/uet/oop/bomberman/base/IEntityManager.java new file mode 100644 index 0000000..eaf805d --- /dev/null +++ b/src/uet/oop/bomberman/base/IEntityManager.java @@ -0,0 +1,29 @@ +package uet.oop.bomberman.base; + +import java.util.List; + +import uet.oop.bomberman.entities.Entity; +import uet.oop.bomberman.entities.character.Character; +import uet.oop.bomberman.graphics.IRender; + +public interface IEntityManager extends IRender { + + public Entity getEntityAtExcluding(double x, double y, Character m); + + public default Entity getEntityAt(double x, double y) { + return getEntityAtExcluding(x, y, null); + }; + + public boolean isEnemyCleared(); + + public Character getPlayer(); + + public List getPlayers(); + + public ITileManager getTileManager(); + + public ICharacterManager getCharacterManager(); + + public IBombManager getBombManager(); + +} \ No newline at end of file diff --git a/src/uet/oop/bomberman/base/IGameInfoManager.java b/src/uet/oop/bomberman/base/IGameInfoManager.java new file mode 100644 index 0000000..823ba93 --- /dev/null +++ b/src/uet/oop/bomberman/base/IGameInfoManager.java @@ -0,0 +1,32 @@ +package uet.oop.bomberman.base; + +import java.awt.Graphics; +import java.util.List; + +import uet.oop.bomberman.entities.tile.item.Item; +import uet.oop.bomberman.graphics.IRender; +import uet.oop.bomberman.graphics.Screen; + +public interface IGameInfoManager extends IMessageManager, IRender { + + public int subtractTime(); + + public int getTime(); + + public int getPoints(); + + public void addPoints(int points); + + public boolean isPaused(); + + public void pause(); + + public void unpause(); + + public List getPlayerActiveItems(); + + public void setEntityManager(IEntityManager entityManager); + + public void render(Screen screen, Graphics g); + +} \ No newline at end of file diff --git a/src/uet/oop/bomberman/base/ILevelManager.java b/src/uet/oop/bomberman/base/ILevelManager.java new file mode 100644 index 0000000..98ce798 --- /dev/null +++ b/src/uet/oop/bomberman/base/ILevelManager.java @@ -0,0 +1,14 @@ +package uet.oop.bomberman.base; + +public interface ILevelManager { + + public void nextLevel(); + + public void loadGlobalLevel(); + + public void endGame(); + + public int getBoardWidth(); + public int getBoardHeight(); + +} diff --git a/src/uet/oop/bomberman/base/IMessageManager.java b/src/uet/oop/bomberman/base/IMessageManager.java new file mode 100644 index 0000000..ec01310 --- /dev/null +++ b/src/uet/oop/bomberman/base/IMessageManager.java @@ -0,0 +1,9 @@ +package uet.oop.bomberman.base; + +import uet.oop.bomberman.entities.Message; + +public interface IMessageManager { + + public void addMessage(Message e); + +} \ No newline at end of file diff --git a/src/uet/oop/bomberman/base/ITileManager.java b/src/uet/oop/bomberman/base/ITileManager.java new file mode 100644 index 0000000..5b74d39 --- /dev/null +++ b/src/uet/oop/bomberman/base/ITileManager.java @@ -0,0 +1,11 @@ +package uet.oop.bomberman.base; + +import uet.oop.bomberman.entities.tile.Tile; +import uet.oop.bomberman.graphics.IRender; + +public interface ITileManager extends IRender { + + public Tile getTileAt(double x, double y); + public void addTile(int pos, Tile e); + +} \ No newline at end of file diff --git a/src/uet/oop/bomberman/entities/Entity.java b/src/uet/oop/bomberman/entities/Entity.java index c1021cb..c7ad8c8 100644 --- a/src/uet/oop/bomberman/entities/Entity.java +++ b/src/uet/oop/bomberman/entities/Entity.java @@ -40,13 +40,27 @@ public Sprite getSprite() { return _sprite; } + public double getCenterX() { + return _x + _sprite.SIZE / 2; + } + + public double getCenterY() { + return _y - _sprite.SIZE / 2; + } + /** * Phương thức này được gọi để xử lý khi hai entity va chạm vào nhau * @param e * @return */ public abstract boolean collide(Entity e); - //xu li 2 entity va cham + + /** + * Check if other Entity can pass through this + * @param other + * @return + */ + public abstract boolean canBePassedThroughBy(Entity other); public double getX() { return _x; @@ -57,11 +71,11 @@ public double getY() { } public int getXTile() { - return Coordinates.pixelToTile(_x + _sprite.SIZE / 2); + return Coordinates.pixelToTile(getCenterX()); } public int getYTile() { - return Coordinates.pixelToTile(_y - _sprite.SIZE / 2); + return Coordinates.pixelToTile(getCenterY()); } diff --git a/src/uet/oop/bomberman/entities/LayeredEntity.java b/src/uet/oop/bomberman/entities/LayeredEntity.java index e183f24..7d824a3 100644 --- a/src/uet/oop/bomberman/entities/LayeredEntity.java +++ b/src/uet/oop/bomberman/entities/LayeredEntity.java @@ -1,5 +1,6 @@ package uet.oop.bomberman.entities; +import uet.oop.bomberman.entities.tile.Tile; import uet.oop.bomberman.entities.tile.destroyable.DestroyableTile; import uet.oop.bomberman.graphics.Screen; @@ -9,11 +10,12 @@ * Chứa và quản lý nhiều Entity tại cùng một vị trí * Ví dụ: tại vị trí dấu Item, có 3 Entity [Grass, Item, Brick] */ -public class LayeredEntity extends Entity { +public class LayeredEntity extends Tile { - protected LinkedList _entities = new LinkedList<>(); + protected LinkedList _entities = new LinkedList<>(); - public LayeredEntity(int x, int y, Entity ... entities) { + public LayeredEntity(int x, int y, Tile ... entities) { + super(x, y, null); _x = x; _y = y; @@ -38,7 +40,7 @@ public void render(Screen screen) { getTopEntity().render(screen); } - public Entity getTopEntity() { + public Tile getTopEntity() { return _entities.getLast(); } @@ -51,15 +53,24 @@ private void clearRemoved() { } } - public void addBeforeTop(Entity e) { + public void addBeforeTop(Tile e) { _entities.add(_entities.size() - 1, e); } @Override public boolean collide(Entity e) { // TODO: lấy entity trên cùng ra để xử lý va chạm - return getTopEntity().collide(e); } + @Override + public boolean canBePassedThroughBy(Entity other) { + return getTopEntity().canBePassedThroughBy(other); + } + + @Override + public boolean isDestroyable() { + return getTopEntity().isDestroyable(); + } + } diff --git a/src/uet/oop/bomberman/entities/Message.java b/src/uet/oop/bomberman/entities/Message.java index 1e743e7..74cc779 100644 --- a/src/uet/oop/bomberman/entities/Message.java +++ b/src/uet/oop/bomberman/entities/Message.java @@ -1,5 +1,6 @@ package uet.oop.bomberman.entities; +import uet.oop.bomberman.Game; import uet.oop.bomberman.graphics.Screen; import java.awt.*; @@ -27,7 +28,7 @@ public Message(String message, double x, double y, int duration, Color color, in _x =x; _y = y; _message = message; - _duration = duration * 60; //seconds + _duration = duration * Game.TICKS_PER_SECOND; //seconds _color = color; _size = size; } @@ -64,6 +65,11 @@ public void render(Screen screen) { public boolean collide(Entity e) { return true; } + + @Override + public boolean canBePassedThroughBy(Entity other) { + return true; + } } diff --git a/src/uet/oop/bomberman/entities/bomb/Bomb.java b/src/uet/oop/bomberman/entities/bomb/Bomb.java index 34e1a20..6ad7436 100644 --- a/src/uet/oop/bomberman/entities/bomb/Bomb.java +++ b/src/uet/oop/bomberman/entities/bomb/Bomb.java @@ -1,7 +1,7 @@ package uet.oop.bomberman.entities.bomb; -import uet.oop.bomberman.Board; import uet.oop.bomberman.Game; +import uet.oop.bomberman.base.IEntityManager; import uet.oop.bomberman.entities.AnimatedEntitiy; import uet.oop.bomberman.entities.Entity; import uet.oop.bomberman.entities.character.Bomber; @@ -10,120 +10,136 @@ import uet.oop.bomberman.entities.character.Character; import uet.oop.bomberman.level.Coordinates; import uet.oop.bomberman.sound.Sound; + public class Bomb extends AnimatedEntitiy { - protected double _timeToExplode = 120; //2 seconds - thoi gian phat no + protected double _timeToExplode = 120; // 2 seconds - thoi gian phat no public int _timeAfter = 20;// thoi gian de no - - protected Board _board; + + protected IEntityManager _board; protected Flame[] _flames; protected boolean _exploded = false; protected boolean _allowedToPassThru = true; - - public Bomb(int x, int y, Board board) { + + private final int bombRadius; + + public Bomb(int x, int y, int bombRadius, IEntityManager board) { _x = x; _y = y; _board = board; _sprite = Sprite.bomb; + this.bombRadius = bombRadius; } - + @Override public void update() { - if(_timeToExplode > 0) + if (_timeToExplode > 0) _timeToExplode--; else { - if(!_exploded) + if (!_exploded) explode(); else updateFlames(); - - if(_timeAfter > 0) + + if (_timeAfter > 0) _timeAfter--; else remove(); } - + animate(); } - + @Override public void render(Screen screen) { - if(_exploded) { - _sprite = Sprite.bomb_exploded2; + if (_exploded) { + _sprite = Sprite.bomb_exploded2; renderFlames(screen); } else - _sprite = Sprite.movingSprite(Sprite.bomb, Sprite.bomb_1, Sprite.bomb_2, _animate, 60); - - int xt = (int)_x << 4; - int yt = (int)_y << 4; - - screen.renderEntity(xt, yt , this); + _sprite = Sprite.movingSprite(Sprite.bomb, Sprite.bomb_1, Sprite.bomb_2, _animate, Game.TICKS_PER_SECOND); + + int xt = (int) _x << 4; + int yt = (int) _y << 4; + + screen.renderEntity(xt, yt, this); } - + public void renderFlames(Screen screen) { for (int i = 0; i < _flames.length; i++) { _flames[i].render(screen); } } - + public void updateFlames() { for (int i = 0; i < _flames.length; i++) { _flames[i].update(); } } - /** - * Xử lý Bomb nổ - */ - protected void explode() {//nổ + /** + * Xử lý Bomb nổ + */ + protected void explode() {// nổ _exploded = true; _allowedToPassThru = true; // TODO: xử lý khi Character đứng tại vị trí Bomb - Character x = _board.getCharacterAtExcluding((int)_x, (int)_y, null); - if(x != null){ - x.kill(); - } + Character x = _board.getCharacterManager().getCharacterAtExcluding((int) _x, (int) _y, null); + if (x != null) { + x.handleOnDeath(); + } // TODO: tạo các Flame - _flames = new Flame[4]; - for (int i = 0; i < _flames.length; i++) { - _flames[i] = new Flame((int) _x, (int) _y, i, Game.getBombRadius(), _board); - } - Sound.play("BOM_11_M"); + _flames = new Flame[4]; + for (int i = 0; i < _flames.length; i++) { + _flames[i] = new Flame((int) _x, (int) _y, i, bombRadius, _board); + } + Sound.play("BOM_11_M"); } - public void time_explode() { + + public void handleChainExplode() { _timeToExplode = 0; } + public FlameSegment flameAt(int x, int y) { - if(!_exploded) return null; - + if (!_exploded) + return null; + for (int i = 0; i < _flames.length; i++) { - if(_flames[i] == null) return null; + if (_flames[i] == null) + return null; FlameSegment e = _flames[i].flameSegmentAt(x, y); - if(e != null) return e; + if (e != null) + return e; } - + return null; } @Override public boolean collide(Entity e) { - // TODO: xử lý khi Bomber đi ra sau khi vừa đặt bom (_allowedToPassThru) - - if(e instanceof Bomber) { - double diffX = e.getX() - Coordinates.tileToPixel(getX()); - double diffY = e.getY() - Coordinates.tileToPixel(getY()); - - if(!(diffX >= -10 && diffX < 16 && diffY >= 1 && diffY <= 28)) { // differences to see if the player has moved out of the bomb, tested values + // Xử lý va chạm với Flame của Bomb khác: chain explosion + if (e instanceof Flame) { + handleChainExplode(); + return true; + } + return false; + } + + @Override + public boolean canBePassedThroughBy(Entity other) { + // Xử lý khi Bomber đi ra sau khi vừa đặt bom (_allowedToPassThru) + if (other instanceof Bomber) { + double diffX = other.getX() - Coordinates.tileToPixel(getX()); + double diffY = other.getY() - Coordinates.tileToPixel(getY()); + + if (!(diffX >= -10 && diffX < 16 && diffY >= 1 && diffY <= 28)) { // differences to see if the player has + // moved out of the bomb, tested values _allowedToPassThru = false; } - + return _allowedToPassThru; } - // TODO: xử lý va chạm với Flame của Bomb khác - if(e instanceof Flame ) { - time_explode(); - return true; - } + return false; } + } diff --git a/src/uet/oop/bomberman/entities/bomb/Flame.java b/src/uet/oop/bomberman/entities/bomb/Flame.java index 721bc25..bf25642 100644 --- a/src/uet/oop/bomberman/entities/bomb/Flame.java +++ b/src/uet/oop/bomberman/entities/bomb/Flame.java @@ -1,14 +1,14 @@ package uet.oop.bomberman.entities.bomb; -import uet.oop.bomberman.Board; +import uet.oop.bomberman.base.IEntityManager; import uet.oop.bomberman.entities.Entity; -import uet.oop.bomberman.entities.character.Bomber; -import uet.oop.bomberman.entities.character.enemy.Enemy; +import uet.oop.bomberman.entities.character.Character; +import uet.oop.bomberman.entities.tile.Tile; import uet.oop.bomberman.graphics.Screen; public class Flame extends Entity { - protected Board _board; + protected IEntityManager entityManager; protected int _direction; private int _radius; protected int xOrigin, yOrigin; @@ -21,14 +21,14 @@ public class Flame extends Entity { * @param direction là hướng của Flame * @param radius độ dài cực đại của Flame */ - public Flame(int x, int y, int direction, int radius, Board board) { + public Flame(int x, int y, int direction, int radius, IEntityManager entityManager) { xOrigin = x; yOrigin = y; _x = x; _y = y; _direction = direction; _radius = radius; - _board = board; + this.entityManager = entityManager; createFlameSegments(); } @@ -60,6 +60,10 @@ private void createFlameSegments() { case 3: x--; break; } _flameSegments[i] = new FlameSegment(x, y, _direction, last); + Entity entity = entityManager.getEntityAt(x, y); + if (entity!=null) { + entity.collide(this); + } } } @@ -78,17 +82,32 @@ private int calculatePermitedDistance() { if(_direction == 2) y++; if(_direction == 3) x--; - Entity a = _board.getEntity(x, y, null); + Entity a = entityManager.getEntityAt(x, y); if(a instanceof Bomb) ++radius; //explosion has to be below the bom - if(a.collide(this) == false) //cannot pass thru + if(!canSpawnFlameOn(a)) { break; + } ++radius; + + // Stop if encounter brick + if (!a.canBePassedThroughBy(this)) { + break; + } } return radius; } + + private boolean canSpawnFlameOn(Entity entity) { + if (entity.canBePassedThroughBy(this)) return true; + if (entity instanceof Tile) { + Tile tile = (Tile) entity; + if (tile.isDestroyable()) return true; + } + return false; + } public FlameSegment flameSegmentAt(int x, int y) { for (int i = 0; i < _flameSegments.length; i++) { @@ -110,9 +129,15 @@ public void render(Screen screen) { @Override public boolean collide(Entity e) { - // TODO: xử lý va chạm với Bomber, Enemy. Chú ý đối tượng này có vị trí chính là vị trí của Bomb đã nổ - if(e instanceof Bomber) ((Bomber) e).kill(); - if(e instanceof Enemy) ((Enemy) e).kill(); - return true; + if (e instanceof Character) { + ((Character)e).handleOnDeath(); + } + return true; + } + + @Override + public boolean canBePassedThroughBy(Entity other) { + return true; } + } diff --git a/src/uet/oop/bomberman/entities/bomb/FlameSegment.java b/src/uet/oop/bomberman/entities/bomb/FlameSegment.java index 8a28539..cbbb2a5 100644 --- a/src/uet/oop/bomberman/entities/bomb/FlameSegment.java +++ b/src/uet/oop/bomberman/entities/bomb/FlameSegment.java @@ -1,8 +1,7 @@ package uet.oop.bomberman.entities.bomb; import uet.oop.bomberman.entities.Entity; -import uet.oop.bomberman.entities.character.Bomber; -import uet.oop.bomberman.entities.character.enemy.Enemy; +import uet.oop.bomberman.entities.character.Character; import uet.oop.bomberman.graphics.Screen; import uet.oop.bomberman.graphics.Sprite; @@ -69,11 +68,13 @@ public void update() {} @Override public boolean collide(Entity e) { - // TODO: xử lý khi FlameSegment va chạm với Character - if(e instanceof Bomber) ((Bomber) e).kill(); - if(e instanceof Enemy) ((Enemy) e).kill(); + if (e instanceof Character) ((Character)e).handleOnDeath(); + return true; + } + + @Override + public boolean canBePassedThroughBy(Entity other) { return true; } - } \ No newline at end of file diff --git a/src/uet/oop/bomberman/entities/character/Bomber.java b/src/uet/oop/bomberman/entities/character/Bomber.java index bcc797d..a292697 100644 --- a/src/uet/oop/bomberman/entities/character/Bomber.java +++ b/src/uet/oop/bomberman/entities/character/Bomber.java @@ -1,64 +1,61 @@ package uet.oop.bomberman.entities.character; import java.util.ArrayList; -import uet.oop.bomberman.Board; -import uet.oop.bomberman.Game; +import uet.oop.bomberman.base.IEntityManager; import uet.oop.bomberman.entities.Entity; import uet.oop.bomberman.entities.bomb.Bomb; +import uet.oop.bomberman.entities.character.action.Action; +import uet.oop.bomberman.entities.character.action.ActionConstants; +import uet.oop.bomberman.entities.character.action.ActionPlaceBomb; +import uet.oop.bomberman.entities.character.exceptions.ActionOnCooldownException; +import uet.oop.bomberman.entities.character.exceptions.BombQuotaReachedException; +import uet.oop.bomberman.entities.character.exceptions.CannotPerformActionException; +import uet.oop.bomberman.entities.character.exceptions.InvalidActionException; import uet.oop.bomberman.graphics.Screen; import uet.oop.bomberman.graphics.Sprite; -import uet.oop.bomberman.input.Keyboard; -import java.util.Iterator; import java.util.List; -import uet.oop.bomberman.entities.LayeredEntity; -import uet.oop.bomberman.entities.bomb.Flame; -import uet.oop.bomberman.entities.character.enemy.Enemy; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import uet.oop.bomberman.entities.tile.item.BombItem; +import uet.oop.bomberman.entities.tile.item.FlameItem; import uet.oop.bomberman.entities.tile.item.Item; +import uet.oop.bomberman.entities.tile.item.SpeedItem; import uet.oop.bomberman.level.Coordinates; import uet.oop.bomberman.sound.Sound; -public class Bomber extends Character { - - private List _bombs; - protected Keyboard _input; - public static List _items = new ArrayList();//xu li Item - /** - * nếu giá trị này < 0 thì cho phép đặt đối tượng Bomb tiếp theo, - * cứ mỗi lần đặt 1 Bomb mới, giá trị này sẽ được reset về 0 và giảm dần trong mỗi lần update() - */ - protected int _timeBetweenPutBombs = 0; - - public Bomber(int x, int y, Board board) { - super(x, y, board); - _bombs = _board.getBombs(); - _input = _board.getInput(); +public class Bomber extends Character implements CanUseItem { + + private List _bombs = new ArrayList<>(); + private List activeItems = new ArrayList<>(); + + private final int baseBombLimit; + protected int bombCooldown = 0; + + private final int baseBombRadius; + + public Bomber(int x, int y, double baseSpeed, int baseBombLimit, int baseBombRadius, IEntityManager entityManager) { + super(x, y, baseSpeed, entityManager); + this.baseBombLimit = baseBombLimit; + this.baseBombRadius = baseBombRadius; _sprite = Sprite.player_right; } @Override - public void update() { - clearBombs(); - if (!_alive) { - afterKill(); - return; - } - - if (_timeBetweenPutBombs < -7500) _timeBetweenPutBombs = 0; - else _timeBetweenPutBombs--; - + public void handleUpdate() { + clearExpiredBombs(); + if (bombCooldown < -7500) + bombCooldown = 0; + else + bombCooldown--; animate(); - calculateMove(); - - detectPlaceBomb(); } @Override public void render(Screen screen) { - calculateXOffset(); - - if (_alive) + if (isAlive()) chooseSprite(); else _sprite = Sprite.player_dead1; @@ -66,170 +63,151 @@ public void render(Screen screen) { screen.renderEntity((int) _x, (int) _y - _sprite.SIZE, this); } - public void calculateXOffset() { - int xScroll = Screen.calculateXOffset(_board, this); - Screen.setOffset(xScroll, 0); - } - - /** - * Kiểm tra xem có đặt được bom hay không? nếu có thì đặt bom tại vị trí hiện tại của Bomber - */ - private void detectPlaceBomb() { - // TODO: kiểm tra xem phím điều khiển đặt bom có được gõ và giá trị _timeBetweenPutBombs, Game.getBombRate() có thỏa mãn hay không - // TODO: Game.getBombRate() sẽ trả về số lượng bom có thể đặt liên tiếp tại thời điểm hiện tại - // TODO: _timeBetweenPutBombs dùng để ngăn chặn Bomber đặt 2 Bomb cùng tại 1 vị trí trong 1 khoảng thời gian quá ngắn - // TODO: nếu 3 điều kiện trên thỏa mãn thì thực hiện đặt bom bằng placeBomb() - // TODO: sau khi đặt, nhớ giảm số lượng Bomb Rate và reset _timeBetweenPutBombs về 0 - if(_input.space && Game.getBombRate() > 0 && _timeBetweenPutBombs < 0) { - - int xt = Coordinates.pixelToTile(_x + _sprite.getSize() / 2); - int yt = Coordinates.pixelToTile( (_y + _sprite.getSize() / 2) - _sprite.getSize() ); //subtract half player height and minus 1 y position - - placeBomb(xt,yt); - Game.addBombRate(-1); - - _timeBetweenPutBombs = 30; - } - } - - protected void placeBomb(int x, int y) { - // TODO: thực hiện tạo đối tượng bom, đặt vào vị trí (x, y) - Bomb b = new Bomb(x, y, _board); - _board.addBomb(b); - Sound.play("BOM_SET"); + public int getBombLimit() { + int countActiveItem = (int) getActiveItems().filter(item -> item instanceof BombItem).count(); + int bombLimitBonus = countActiveItem * BombItem.BOMB_LIMIT_BONUS; + return this.baseBombLimit + bombLimitBonus; } - private void clearBombs() { - Iterator bs = _bombs.iterator(); - - Bomb b; - while (bs.hasNext()) { - b = bs.next(); - if (b.isRemoved()) { - bs.remove(); - Game.addBombRate(1); - } - } - + public int getBombRemainingQuota() { + return getBombLimit() - _bombs.size(); } - @Override - public void kill() { - if (!_alive) return; - _alive = false; - Sound.play("endgame3"); + public int getBombRadius() { + int countActiveItem = (int) getActiveItems().filter(item -> item instanceof FlameItem).count(); + int bombRadiusBonus = countActiveItem * FlameItem.BOMB_RADIUS_BONUS; + return this.baseBombRadius + bombRadiusBonus; } - @Override - protected void afterKill() { - if (_timeAfter > 0) --_timeAfter; - else { - _board.endGame(); - } + public int getBombCooldown() { + return bombCooldown; } - @Override - protected void calculateMove() { - // TODO: xử lý nhận tín hiệu điều khiển hướng đi từ _input và gọi move() để thực hiện di chuyển - // TODO: nhớ cập nhật lại giá trị cờ _moving khi thay đổi trạng thái di chuyển - int xa = 0, ya = 0; - if(_input.up) ya--; - if(_input.down) ya++; - if(_input.left) xa--; - if(_input.right) xa++; - - if(xa != 0 || ya != 0) { - move(xa * Game.getBomberSpeed(), ya * Game.getBomberSpeed()); - _moving = true; - } else { - _moving = false; - } + public boolean placeBomb() { + if (getBombRemainingQuota() > 0 && bombCooldown < 0) { + + int xt = Coordinates.pixelToTile(_x + _sprite.getSize() / 2); + int yt = Coordinates.pixelToTile((_y + _sprite.getSize() / 2) - _sprite.getSize()); // subtract half player + // height and minus 1 y + // position + + placeBomb(xt, yt); + + bombCooldown = 30; + return true; + } + return false; } - @Override - public boolean canMove(double x, double y) { - // TODO: kiểm tra có đối tượng tại vị trí chuẩn bị di chuyển đến và có thể di chuyển tới đó hay không - for (int c = 0; c < 4; c++) { //colision detection for each corner of the player - double xt = ((_x + x) + c % 2 * 9) / Game.TILES_SIZE; //divide with tiles size to pass to tile coordinate - double yt = ((_y + y) + c / 2 * 10 - 13) / Game.TILES_SIZE; //these values are the best from multiple tests - - Entity a = _board.getEntity(xt, yt, this); - - if(!a.collide(this)) - return false; - } - - return true; - //return false; + public void placeBomb(int x, int y) { + Bomb b = new Bomb(x, y, getBombRadius(), entityManager); + this._bombs.add(b); + entityManager.getBombManager().addBomb(b); + Sound.play("BOM_SET"); } - @Override - public void move(double xa, double ya) { - // TODO: sử dụng canMove() để kiểm tra xem có thể di chuyển tới điểm đã tính toán hay không và thực hiện thay đổi tọa độ _x, _y - // TODO: nhớ cập nhật giá trị _direction sau khi di chuyển - if(xa > 0) _direction = 1; - if(xa < 0) _direction = 3; - if(ya > 0) _direction = 2; - if(ya < 0) _direction = 0; - - if(canMove(0, ya)) { //separate the moves for the player can slide when is colliding - _y += ya; - } - - if(canMove(xa, 0)) { - _x += xa; - } + private void clearExpiredBombs() { + _bombs = _bombs.stream() + .filter(bomb -> !bomb.isRemoved()) + .collect(Collectors.toList()); } @Override public boolean collide(Entity e) { - // TODO: xử lý va chạm với Flame - // TODO: xử lý va chạm với Enemy - if(e instanceof Flame){ - this.kill(); + if (!super.collide(e)) return false; - } - if(e instanceof Enemy){ - this.kill(); - return true; - } - if( e instanceof LayeredEntity) return(e.collide(this)); return true; } - //sprite - private void chooseSprite() { - switch (_direction) { + // sprite + // protected + protected void chooseSprite() { + switch (getDirection()) { case 0: _sprite = Sprite.player_up; - if (_moving) { + if (isMoving()) { _sprite = Sprite.movingSprite(Sprite.player_up_1, Sprite.player_up_2, _animate, 20); } break; case 1: _sprite = Sprite.player_right; - if (_moving) { + if (isMoving()) { _sprite = Sprite.movingSprite(Sprite.player_right_1, Sprite.player_right_2, _animate, 20); } break; case 2: _sprite = Sprite.player_down; - if (_moving) { + if (isMoving()) { _sprite = Sprite.movingSprite(Sprite.player_down_1, Sprite.player_down_2, _animate, 20); } break; case 3: _sprite = Sprite.player_left; - if (_moving) { + if (isMoving()) { _sprite = Sprite.movingSprite(Sprite.player_left_1, Sprite.player_left_2, _animate, 20); } break; default: _sprite = Sprite.player_right; - if (_moving) { + if (isMoving()) { _sprite = Sprite.movingSprite(Sprite.player_right_1, Sprite.player_right_2, _animate, 20); } break; } } + + @Override + public int getPoints() { + return 0; + } + + private static final List VALID_ACTIONS = new ArrayList() { + { + addAll(ActionConstants.LIST_ACTION_MOVE); + add(ActionConstants.PLACE_BOMB); + } + }; + + @Override + protected List getValidActions() { + return VALID_ACTIONS; + } + + @Override + protected void performAction(Action action, boolean isDryRun) + throws InvalidActionException, CannotPerformActionException { + super.performAction(action, isDryRun); + if (action instanceof ActionPlaceBomb) { + if (getBombRemainingQuota() < 0) + throw new BombQuotaReachedException(); + if (bombCooldown > 0) + throw new ActionOnCooldownException(); + if (!isDryRun) + placeBomb(); + } + } + + @Override + public Stream getActiveItems() { + return activeItems.stream().filter(Item::isActive); + } + + @Override + public void addActiveItem(Item item) { + this.activeItems.add(item); + } + + @Override + protected double getSpeedMultiplier() { + double speedMultiplier = 1; + for (Item item : activeItems) { + if (!item.isActive()) + continue; + if (item instanceof SpeedItem) { + speedMultiplier += SpeedItem.SPEED_MULTIPLIER; + } + } + return speedMultiplier; + } + } diff --git a/src/uet/oop/bomberman/entities/character/Bomber2.java b/src/uet/oop/bomberman/entities/character/Bomber2.java new file mode 100644 index 0000000..aca61f1 --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/Bomber2.java @@ -0,0 +1,60 @@ +package uet.oop.bomberman.entities.character; + +import uet.oop.bomberman.base.IEntityManager; +import uet.oop.bomberman.entities.Entity; +import uet.oop.bomberman.graphics.Screen; +import uet.oop.bomberman.graphics.Sprite; + +public class Bomber2 extends Bomber { + + public Bomber2(int x, int y, double baseSpeed, int baseBombLimit, int baseBombRadius, + IEntityManager entityManager) { + super(x, y, baseSpeed, baseBombLimit, baseBombRadius, entityManager); + } + + @Override + public void render(Screen screen) { + if (isAlive()) + chooseSprite(); + else + _sprite = Sprite.player_dead1; + + screen.renderEntity((int) _x, (int) _y - _sprite.SIZE, this); + } + + @Override + protected void chooseSprite() { + switch (getDirection()) { + case 0: + _sprite = Sprite.player2_up; + if (isMoving()) { + _sprite = Sprite.movingSprite(Sprite.player2_up_1, Sprite.player2_up_2, _animate, 20); + } + break; + case 1: + _sprite = Sprite.player2_right; + if (isMoving()) { + _sprite = Sprite.movingSprite(Sprite.player2_right_1, Sprite.player2_right_2, _animate, 20); + } + break; + case 2: + _sprite = Sprite.player2_down; + if (isMoving()) { + _sprite = Sprite.movingSprite(Sprite.player2_down_1, Sprite.player2_down_2, _animate, 20); + } + break; + case 3: + _sprite = Sprite.player2_left; + if (isMoving()) { + _sprite = Sprite.movingSprite(Sprite.player2_left_1, Sprite.player2_left_2, _animate, 20); + } + break; + default: + _sprite = Sprite.player2_right; + if (isMoving()) { + _sprite = Sprite.movingSprite(Sprite.player2_right_1, Sprite.player2_right_2, _animate, 20); + } + break; + } + } +} diff --git a/src/uet/oop/bomberman/entities/character/CanUseItem.java b/src/uet/oop/bomberman/entities/character/CanUseItem.java new file mode 100644 index 0000000..0d1103a --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/CanUseItem.java @@ -0,0 +1,12 @@ +package uet.oop.bomberman.entities.character; + +import java.util.stream.Stream; + +import uet.oop.bomberman.entities.tile.item.Item; + +public interface CanUseItem { + + public Stream getActiveItems(); + public void addActiveItem(Item item); + +} \ No newline at end of file diff --git a/src/uet/oop/bomberman/entities/character/Character.java b/src/uet/oop/bomberman/entities/character/Character.java index 52842a9..c3ba74d 100644 --- a/src/uet/oop/bomberman/entities/character/Character.java +++ b/src/uet/oop/bomberman/entities/character/Character.java @@ -1,64 +1,297 @@ package uet.oop.bomberman.entities.character; -import uet.oop.bomberman.Board; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.stream.Collectors; + import uet.oop.bomberman.Game; +import uet.oop.bomberman.base.IEntityManager; import uet.oop.bomberman.entities.AnimatedEntitiy; +import uet.oop.bomberman.entities.Entity; +import uet.oop.bomberman.entities.LayeredEntity; +import uet.oop.bomberman.entities.bomb.Flame; +import uet.oop.bomberman.entities.character.action.Action; +import uet.oop.bomberman.entities.character.action.ActionConstants; +import uet.oop.bomberman.entities.character.action.ActionMove; +import uet.oop.bomberman.entities.character.exceptions.ActionOnCooldownException; +import uet.oop.bomberman.entities.character.exceptions.CannotPerformActionException; +import uet.oop.bomberman.entities.character.exceptions.CharacterActionException; +import uet.oop.bomberman.entities.character.exceptions.InvalidActionException; import uet.oop.bomberman.graphics.Screen; +import uet.oop.bomberman.level.Coordinates; /** * Bao gồm Bomber và Enemy */ public abstract class Character extends AnimatedEntitiy { - - protected Board _board; - protected int _direction = -1; - protected boolean _alive = true; - protected boolean _moving = false; - public int _timeAfter = 40; - - public Character(int x, int y, Board board) { + + protected IEntityManager entityManager; + + private int direction = -1; + private boolean alive = true; + private int timerDeathAnimation = 40; + + private final double baseSpeed; + private Queue waypoints = new LinkedList<>(); + + public Character(int x, int y, double baseSpeed, IEntityManager entityManager) { _x = x; _y = y; - _board = board; + this.entityManager = entityManager; + this.baseSpeed = baseSpeed; } - + @Override - public abstract void update(); - + public final void update() { + if (!alive) { + if (timerDeathAnimation > 0) { + timerDeathAnimation -= 1; + } else { + handleAfterDeath(); + } + return; + } + updateMove(); + handleUpdate(); + }; + + protected abstract void handleUpdate(); + @Override public abstract void render(Screen screen); + private static final List VALID_ACTIONS = new ArrayList() { + { + addAll(ActionConstants.LIST_ACTION_MOVE); + add(ActionConstants.DO_NOTHING); + } + }; + + protected List getValidActions() { + return VALID_ACTIONS; + } + + public boolean isValidAction(Action action) { + return getValidActions().contains(action); + } + + public final void performAction(Action action) throws InvalidActionException, CannotPerformActionException { + performAction(action, false); + } + + protected void performAction(Action action, boolean isDryRun) + throws InvalidActionException, CannotPerformActionException { + if (!isValidAction(action)) + throw new InvalidActionException(); + if (action instanceof ActionMove) { + ActionMove actionMove = (ActionMove) action; + double dx = actionMove.getDx() * Game.TILES_SIZE; + double dy = actionMove.getDy() * Game.TILES_SIZE; + if (isMoving()) + throw new ActionOnCooldownException(); + if (!isDryRun) + move(dx, dy); + } + }; + + public final boolean canPerformAction(Action action) { + try { + performAction(action, true); + return true; + } catch (CharacterActionException ex) { + return false; + } + }; + + public List getPerformableActions() { + return getValidActions().stream() + .filter(this::canPerformAction) + .collect(Collectors.toList()); + } + /** - * Tính toán hướng đi + * Check if can be moved with vector (xa, ya). + * + * @param xa + * @param ya */ - protected abstract void calculateMove(); - - protected abstract void move(double xa, double ya); + public void move(double xa, double ya) { + double moveDurationBase = Game.TICKS_PER_SECOND / getSpeed(); + Waypoint waypointX = new Waypoint( + xa, + 0, + moveDurationBase); + Waypoint waypointY = new Waypoint( + 0, + ya, + moveDurationBase); + if (xa != 0 && ya != 0 && canMove(xa, ya) && canMove(xa, 0)) { + waypoints.add(waypointX); + waypoints.add(waypointY); + } else if (xa != 0 && ya != 0 && canMove(xa, ya) && canMove(0, ya)) { + waypoints.add(waypointY); + waypoints.add(waypointX); + } else if (xa != 0 && canMove(xa, 0)) { + waypoints.add(waypointX); + } else if (ya != 0 && canMove(0, ya)) { + waypoints.add(waypointY); + } else { + System.out.println(String.format( + "Cannot move character %s to (%s, %s)", + getClass().getSimpleName(), xa, ya)); + } + } + + private void updateMove() { + if (waypoints.isEmpty()) + return; + Waypoint waypoint = waypoints.peek(); + if (!waypoint.started) { + waypoint.started = true; + waypoint.moveDestX = _x + waypoint.moveX; + waypoint.moveDestY = _y + waypoint.moveY; + } + + waypoint.moveDuration -= 1; + if (waypoint.moveDuration > 0) { + _x += waypoint.moveDx; + _y += waypoint.moveDy; + } else { + // Correct rounding errors by force teleporting to destination + _x = waypoint.moveDestX; + _y = waypoint.moveDestY; + // Remove waypoint + waypoints.poll(); + } + + // Adjust direction + if (waypoint.moveDx > 0) + direction = 1; + if (waypoint.moveDx < 0) + direction = 3; + if (waypoint.moveDy > 0) + direction = 2; + if (waypoint.moveDy < 0) + direction = 0; + + Entity collidingEntity = entityManager.getEntityAtExcluding( + Coordinates.pixelToTile(getCenterX()), + Coordinates.pixelToTile(getCenterY()), + this); + if (collidingEntity != null) { + this.collide(collidingEntity); + collidingEntity.collide(this); + } + } /** * Được gọi khi đối tượng bị tiêu diệt */ - public abstract void kill(); + public final void handleOnDeath() { + if (!alive) + return; + alive = false; + // TODO: determine killer + entityManager.getCharacterManager().handleOnDeath(this, null); + } /** * Xử lý hiệu ứng bị tiêu diệt */ - protected abstract void afterKill(); + private void handleAfterDeath() { + entityManager.getCharacterManager().handleAfterDeath(this); + remove(); + } /** * Kiểm tra xem đối tượng có di chuyển tới vị trí đã tính toán hay không + * * @param x * @param y * @return */ - protected abstract boolean canMove(double x, double y); + public boolean canMove(double dx, double dy) { + double x = getCenterX() + dx; + double y = getCenterY() + dy; + Entity a = entityManager.getEntityAtExcluding( + Coordinates.pixelToTile(x), + Coordinates.pixelToTile(y), + this); + if (a == null) + return true; + return a.canBePassedThroughBy(this); + } protected double getXMessage() { return (_x * Game.SCALE) + (_sprite.SIZE / 2 * Game.SCALE); } - + protected double getYMessage() { - return (_y* Game.SCALE) - (_sprite.SIZE / 2 * Game.SCALE); + return (_y * Game.SCALE) - (_sprite.SIZE / 2 * Game.SCALE); + } + + public boolean isPlayer() { + return entityManager.getPlayers().contains(this); + } + + public boolean isAlive() { + return alive; + } + + public boolean isMoving() { + return waypoints.size() > 0; + } + + public final double getSpeed() { + double speedMultiplier = getSpeedMultiplier(); + return speedMultiplier * this.baseSpeed; + } + + protected double getSpeedMultiplier() { + return 1; + } + + public abstract int getPoints(); + + public int getDirection() { + return direction; + } + + public int getTimerDeathAnimation() { + return timerDeathAnimation; } - + + public void setTimerDeathAnimation(int timerDeathAnimation) { + this.timerDeathAnimation = timerDeathAnimation; + } + + @Override + public boolean collide(Entity e) { + if (e instanceof Flame) { + this.handleOnDeath(); + return false; + } + if (e instanceof Character) { + Character other = (Character) e; + if (this.isPlayer() && !other.isPlayer()) { + this.handleOnDeath(); + return false; + } + if (other.isPlayer() && !this.isPlayer()) { + other.handleOnDeath(); + return false; + } + } + if (e instanceof LayeredEntity) + return (e.collide(this)); + return true; + } + + @Override + public boolean canBePassedThroughBy(Entity other) { + return true; + } + } diff --git a/src/uet/oop/bomberman/entities/character/Waypoint.java b/src/uet/oop/bomberman/entities/character/Waypoint.java new file mode 100644 index 0000000..417df96 --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/Waypoint.java @@ -0,0 +1,21 @@ +package uet.oop.bomberman.entities.character; + +class Waypoint { + double moveX; + double moveY; + double moveDuration; + double moveDx; + double moveDy; + + public boolean started = false; + public double moveDestX; + public double moveDestY; + + public Waypoint(double moveX, double moveY, double moveDuration) { + this.moveX = moveX; + this.moveY = moveY; + this.moveDuration = moveDuration; + this.moveDx = moveX / moveDuration; + this.moveDy = moveY / moveDuration; + } +} \ No newline at end of file diff --git a/src/uet/oop/bomberman/entities/character/action/Action.java b/src/uet/oop/bomberman/entities/character/action/Action.java new file mode 100644 index 0000000..13b7f5a --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/action/Action.java @@ -0,0 +1,4 @@ +package uet.oop.bomberman.entities.character.action; + +public abstract class Action { +} diff --git a/src/uet/oop/bomberman/entities/character/action/ActionConstants.java b/src/uet/oop/bomberman/entities/character/action/ActionConstants.java new file mode 100644 index 0000000..332f398 --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/action/ActionConstants.java @@ -0,0 +1,32 @@ +package uet.oop.bomberman.entities.character.action; + +import java.util.Arrays; +import java.util.List; + +public interface ActionConstants { + + public static final ActionMove MOVE_UP = new ActionMove(0, -1); + public static final ActionMove MOVE_DOWN = new ActionMove(0, +1); + public static final ActionMove MOVE_LEFT = new ActionMove(-1, 0); + public static final ActionMove MOVE_RIGHT = new ActionMove(+1, 0); + + public static final ActionMove MOVE_UP_LEFT = new ActionMove(-1, -1); + public static final ActionMove MOVE_UP_RIGHT = new ActionMove(+1, -1); + public static final ActionMove MOVE_DOWN_LEFT = new ActionMove(-1, +1); + public static final ActionMove MOVE_DOWN_RIGHT = new ActionMove(+1, +1); + + public static final List LIST_ACTION_MOVE = Arrays.asList(new ActionMove[] { + MOVE_UP, + MOVE_DOWN, + MOVE_LEFT, + MOVE_RIGHT, + MOVE_UP_LEFT, + MOVE_UP_RIGHT, + MOVE_DOWN_LEFT, + MOVE_DOWN_RIGHT, + }); + + public static final ActionPlaceBomb PLACE_BOMB = new ActionPlaceBomb(); + public static final Action DO_NOTHING = new ActionNoop(); + +} diff --git a/src/uet/oop/bomberman/entities/character/action/ActionMove.java b/src/uet/oop/bomberman/entities/character/action/ActionMove.java new file mode 100644 index 0000000..38ed878 --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/action/ActionMove.java @@ -0,0 +1,45 @@ +package uet.oop.bomberman.entities.character.action; + +import java.util.Objects; + +public class ActionMove extends Action { + + private final double dx; + private final double dy; + + public ActionMove(double dx, double dy) { + this.dx = dx; + this.dy = dy; + } + + public double getDx() { + return dx; + } + + public double getDy() { + return dy; + } + + @Override + public int hashCode() { + return Objects.hash(dx, dy); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ActionMove other = (ActionMove) obj; + if (Double.doubleToLongBits(dx) != Double.doubleToLongBits(other.dx)) + return false; + if (Double.doubleToLongBits(dy) != Double.doubleToLongBits(other.dy)) + return false; + return true; + } + + +} diff --git a/src/uet/oop/bomberman/entities/character/action/ActionNoop.java b/src/uet/oop/bomberman/entities/character/action/ActionNoop.java new file mode 100644 index 0000000..df0795b --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/action/ActionNoop.java @@ -0,0 +1,5 @@ +package uet.oop.bomberman.entities.character.action; + +public class ActionNoop extends Action { + +} diff --git a/src/uet/oop/bomberman/entities/character/action/ActionPlaceBomb.java b/src/uet/oop/bomberman/entities/character/action/ActionPlaceBomb.java new file mode 100644 index 0000000..93b6556 --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/action/ActionPlaceBomb.java @@ -0,0 +1,21 @@ +package uet.oop.bomberman.entities.character.action; + +public class ActionPlaceBomb extends Action { + + protected ActionPlaceBomb() {} + + @Override + public boolean equals(Object obj) { + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + return true; + } + + @Override + public int hashCode() { + return 0; + } + +} diff --git a/src/uet/oop/bomberman/entities/character/enemy/Balloon.java b/src/uet/oop/bomberman/entities/character/enemy/Balloon.java index e0d43dc..2cee00e 100644 --- a/src/uet/oop/bomberman/entities/character/enemy/Balloon.java +++ b/src/uet/oop/bomberman/entities/character/enemy/Balloon.java @@ -1,35 +1,29 @@ package uet.oop.bomberman.entities.character.enemy; -import static java.lang.Math.random; -import java.util.Random; -import uet.oop.bomberman.Board; import uet.oop.bomberman.Game; -import uet.oop.bomberman.entities.character.enemy.ai.AILow; +import uet.oop.bomberman.base.IEntityManager; import uet.oop.bomberman.graphics.Sprite; public class Balloon extends Enemy { - public Balloon(int x, int y, Board board) { - super(x, y, board, Sprite.balloom_dead, 0.5, 100); + public Balloon(int x, int y, IEntityManager entityManager) { + super(x, y, entityManager, Sprite.balloom_dead, Game.BOMBERSPEED / 2, 100); _sprite = Sprite.balloom_left1; - _ai = new AILow(); - _direction = _ai.calculateDirection(); - } @Override protected void chooseSprite() { - switch(_direction) { + switch(getDirection()) { case 0: case 1: - _sprite = Sprite.movingSprite(Sprite.balloom_right1, Sprite.balloom_right2, Sprite.balloom_right3, _animate, 60); + _sprite = Sprite.movingSprite(Sprite.balloom_right1, Sprite.balloom_right2, Sprite.balloom_right3, _animate, Game.TICKS_PER_SECOND); break; case 2: case 3: - _sprite = Sprite.movingSprite(Sprite.balloom_left1, Sprite.balloom_left2, Sprite.balloom_left3, _animate, 60); + _sprite = Sprite.movingSprite(Sprite.balloom_left1, Sprite.balloom_left2, Sprite.balloom_left3, _animate, Game.TICKS_PER_SECOND); break; } } diff --git a/src/uet/oop/bomberman/entities/character/enemy/Doll.java b/src/uet/oop/bomberman/entities/character/enemy/Doll.java index dc85fce..b4e2b0e 100644 --- a/src/uet/oop/bomberman/entities/character/enemy/Doll.java +++ b/src/uet/oop/bomberman/entities/character/enemy/Doll.java @@ -5,42 +5,36 @@ */ package uet.oop.bomberman.entities.character.enemy; -import uet.oop.bomberman.Board; -import uet.oop.bomberman.entities.character.enemy.ai.AILow; -import uet.oop.bomberman.entities.character.enemy.ai.AIMedium; +import uet.oop.bomberman.Game; +import uet.oop.bomberman.base.IEntityManager; import uet.oop.bomberman.graphics.Sprite; -/** - * - * @author TUNG318 - */ -public class Doll extends Enemy{ - - public Doll(int x, int y, Board board) { - super(x, y, board, Sprite.balloom_dead, 0.8, 100); +public class Doll extends Enemy { - _sprite = Sprite.balloom_left1; + public Doll(int x, int y, IEntityManager entityManager) { + super(x, y, entityManager, Sprite.balloom_dead, Game.BOMBERSPEED, 100); - _ai = new AIMedium(_board.getBomber(), this); - _direction = _ai.calculateDirection(); + _sprite = Sprite.doll_left1; } @Override protected void chooseSprite() { - switch (_direction) { + switch (getDirection()) { case 0: case 1: - if (_moving) { - _sprite = Sprite.movingSprite(Sprite.doll_right1, Sprite.doll_right2, Sprite.doll_right3, _animate, 60); + if (isMoving()) { + _sprite = Sprite.movingSprite(Sprite.doll_right1, Sprite.doll_right2, Sprite.doll_right3, _animate, + Game.TICKS_PER_SECOND); } else { _sprite = Sprite.doll_left1; } break; case 2: case 3: - if (_moving) { - _sprite = Sprite.movingSprite(Sprite.doll_left1, Sprite.doll_left2, Sprite.doll_left3, _animate, 60); + if (isMoving()) { + _sprite = Sprite.movingSprite(Sprite.doll_left1, Sprite.doll_left2, Sprite.doll_left3, _animate, + Game.TICKS_PER_SECOND); } else { _sprite = Sprite.doll_left1; } diff --git a/src/uet/oop/bomberman/entities/character/enemy/Enemy.java b/src/uet/oop/bomberman/entities/character/enemy/Enemy.java index 61a7b99..3839cb9 100644 --- a/src/uet/oop/bomberman/entities/character/enemy/Enemy.java +++ b/src/uet/oop/bomberman/entities/character/enemy/Enemy.java @@ -1,26 +1,17 @@ package uet.oop.bomberman.entities.character.enemy; -import uet.oop.bomberman.Board; import uet.oop.bomberman.Game; +import uet.oop.bomberman.base.IEntityManager; import uet.oop.bomberman.entities.Entity; -import uet.oop.bomberman.entities.Message; -import uet.oop.bomberman.entities.bomb.Flame; -import uet.oop.bomberman.entities.character.Bomber; import uet.oop.bomberman.entities.character.Character; -import uet.oop.bomberman.entities.character.enemy.ai.AI; import uet.oop.bomberman.graphics.Screen; import uet.oop.bomberman.graphics.Sprite; -import uet.oop.bomberman.level.Coordinates; - -import java.awt.*; -import uet.oop.bomberman.sound.Sound; public abstract class Enemy extends Character { protected int _points; protected double _speed; - protected AI _ai; protected final double MAX_STEPS; protected final double rest; @@ -28,10 +19,10 @@ public abstract class Enemy extends Character { protected int _finalAnimation = 30; protected Sprite _deadSprite; - - public Enemy(int x, int y, Board board, Sprite dead, double speed, int points) { - super(x, y, board); - + + public Enemy(int x, int y, IEntityManager entityManager, Sprite dead, double speed, int points) { + super(x, y, speed, entityManager); + _points = points; _speed = speed; @@ -39,34 +30,25 @@ public Enemy(int x, int y, Board board, Sprite dead, double speed, int points) { rest = (MAX_STEPS - (int) MAX_STEPS) / MAX_STEPS; _steps = MAX_STEPS; - _timeAfter = 20; + setTimerDeathAnimation(20); _deadSprite = dead; } @Override - public void update() { + public void handleUpdate() { animate(); - - if(!_alive) { - afterKill(); - return; - } - - if(_alive) - calculateMove(); } @Override public void render(Screen screen) { - - if(_alive) + if(isAlive()) chooseSprite(); else { - if(_timeAfter > 0) { + if(getTimerDeathAnimation() > 0) { _sprite = _deadSprite; _animate = 0; } else { - _sprite = Sprite.movingSprite(Sprite.mob_dead1, Sprite.mob_dead2, Sprite.mob_dead3, _animate, 60); + _sprite = Sprite.movingSprite(Sprite.mob_dead1, Sprite.mob_dead2, Sprite.mob_dead3, _animate, Game.TICKS_PER_SECOND); } } @@ -74,94 +56,16 @@ public void render(Screen screen) { screen.renderEntity((int)_x, (int)_y - _sprite.SIZE, this); } - @Override - public void calculateMove() { - // TODO: Tính toán hướng đi và di chuyển Enemy theo _ai và cập nhật giá trị cho _direction - // TODO: sử dụng canMove() để kiểm tra xem có thể di chuyển tới điểm đã tính toán hay không - // TODO: sử dụng move() để di chuyển - // TODO: nhớ cập nhật lại giá trị cờ _moving khi thay đổi trạng thái di chuyển - int xa = 0, ya = 0; - if(_steps <= 0){ - _direction = _ai.calculateDirection(); - _steps = MAX_STEPS; - } - - if(_direction == 0) ya--; - if(_direction == 2) ya++; - if(_direction == 3) xa--; - if(_direction == 1) xa++; - - if(canMove(xa, ya)) { - _steps -= 1 + rest; - move(xa * _speed, ya * _speed); - _moving = true; - } else { - _steps = 0; - _moving = false; - } - } - - @Override - public void move(double xa, double ya) { - if(!_alive) return; - _y += ya; - _x += xa; - } - - @Override - public boolean canMove(double x, double y) { - double xr = _x, yr = _y - 16; //subtract y to get more accurate results - - //the thing is, subract 15 to 16 (sprite size), so if we add 1 tile we get the next pixel tile with this - //we avoid the shaking inside tiles with the help of steps - if(_direction == 0) { yr += _sprite.getSize() -1 ; xr += _sprite.getSize()/2; } - if(_direction == 1) {yr += _sprite.getSize()/2; xr += 1;} - if(_direction == 2) { xr += _sprite.getSize()/2; yr += 1;} - if(_direction == 3) { xr += _sprite.getSize() -1; yr += _sprite.getSize()/2;} - - int xx = Coordinates.pixelToTile(xr) +(int)x; - int yy = Coordinates.pixelToTile(yr) +(int)y; - - Entity a = _board.getEntity(xx, yy, this); //entity of the position we want to go - - return a.collide(this); - } - @Override public boolean collide(Entity e) { - if(e instanceof Flame){ - this.kill(); - return false; - } - if(e instanceof Bomber){ - ((Bomber) e).kill(); - return false; - } + if (!super.collide(e)) return false; return true; } - @Override - public void kill() { - if(!_alive) return; - _alive = false; - - _board.addPoints(_points); + protected abstract void chooseSprite(); - Message msg = new Message("+" + _points, getXMessage(), getYMessage(), 2, Color.white, 14); - _board.addMessage(msg); - Sound.play("AA126_11"); - } - - @Override - protected void afterKill() { - if(_timeAfter > 0) --_timeAfter; - else { - if(_finalAnimation > 0) --_finalAnimation; - else - remove(); - } + public int getPoints() { + return _points; } - - protected abstract void chooseSprite(); } diff --git a/src/uet/oop/bomberman/entities/character/enemy/Kondoria.java b/src/uet/oop/bomberman/entities/character/enemy/Kondoria.java new file mode 100644 index 0000000..0f7db66 --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/enemy/Kondoria.java @@ -0,0 +1,36 @@ +package uet.oop.bomberman.entities.character.enemy; + +import uet.oop.bomberman.Game; +import uet.oop.bomberman.base.IEntityManager; +import uet.oop.bomberman.graphics.Sprite; + +public class Kondoria extends Enemy { + public Kondoria(int x, int y, IEntityManager entityManager) { + super(x, y, entityManager, Sprite.balloom_dead, Game.BOMBERSPEED / 4, 1000); + + _sprite = Sprite.kondoria_right1; + + } + + @Override + protected void chooseSprite() { + switch (getDirection()) { + case 0: + case 1: + if (isMoving()) + _sprite = Sprite.movingSprite(Sprite.kondoria_right1, Sprite.kondoria_right2, + Sprite.kondoria_right3, _animate, Game.TICKS_PER_SECOND); + else + _sprite = Sprite.kondoria_left1; + break; + case 2: + case 3: + if (isMoving()) + _sprite = Sprite.movingSprite(Sprite.kondoria_left1, Sprite.kondoria_left2, Sprite.kondoria_left3, + _animate, Game.TICKS_PER_SECOND); + else + _sprite = Sprite.kondoria_left1; + break; + } + } +} diff --git a/src/uet/oop/bomberman/entities/character/enemy/Minvo.java b/src/uet/oop/bomberman/entities/character/enemy/Minvo.java new file mode 100644 index 0000000..43cf3c4 --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/enemy/Minvo.java @@ -0,0 +1,34 @@ +package uet.oop.bomberman.entities.character.enemy; + +import uet.oop.bomberman.Game; +import uet.oop.bomberman.base.IEntityManager; +import uet.oop.bomberman.graphics.Sprite; + +public class Minvo extends Enemy { + public Minvo(int x, int y, IEntityManager entityManager) { + super(x, y, entityManager, Sprite.minvo_dead, Game.BOMBERSPEED * 1.5, 800); + _sprite = Sprite.minvo_right1; + } + + @Override + protected void chooseSprite() { + switch (getDirection()) { + case 0: + case 1: + if (isMoving()) + _sprite = Sprite.movingSprite(Sprite.minvo_right1, Sprite.minvo_right2, Sprite.minvo_right3, + _animate, Game.TICKS_PER_SECOND); + else + _sprite = Sprite.minvo_left1; + break; + case 2: + case 3: + if (isMoving()) + _sprite = Sprite.movingSprite(Sprite.minvo_left1, Sprite.minvo_left2, Sprite.minvo_left3, _animate, + Game.TICKS_PER_SECOND); + else + _sprite = Sprite.minvo_left1; + break; + } + } +} diff --git a/src/uet/oop/bomberman/entities/character/enemy/Oneal.java b/src/uet/oop/bomberman/entities/character/enemy/Oneal.java index 3ad4b23..e353833 100644 --- a/src/uet/oop/bomberman/entities/character/enemy/Oneal.java +++ b/src/uet/oop/bomberman/entities/character/enemy/Oneal.java @@ -1,40 +1,33 @@ package uet.oop.bomberman.entities.character.enemy; - -import java.util.Random; -import uet.oop.bomberman.Board; import uet.oop.bomberman.Game; -import uet.oop.bomberman.entities.character.enemy.ai.AILow; -import uet.oop.bomberman.entities.character.enemy.ai.AIMedium; +import uet.oop.bomberman.base.IEntityManager; import uet.oop.bomberman.graphics.Sprite; public class Oneal extends Enemy { - //private Random random = new Random(); - public Oneal(int x, int y, Board board) { - super(x, y, board, Sprite.balloom_dead, 0.8 , 100); - - _sprite = Sprite.balloom_left1; - - _ai = new AILow(); - _direction = _ai.calculateDirection(); - //this._speed += random.nextDouble()/2; - + // private Random random = new Random(); + public Oneal(int x, int y, IEntityManager entityManager) { + super(x, y, entityManager, Sprite.balloom_dead, Game.BOMBERSPEED, 100); + + _sprite = Sprite.oneal_left1; } - + @Override protected void chooseSprite() { - switch(_direction) { + switch (getDirection()) { case 0: case 1: - if(_moving) - _sprite = Sprite.movingSprite(Sprite.oneal_right1, Sprite.oneal_right2, Sprite.oneal_right3, _animate, 60); + if (isMoving()) + _sprite = Sprite.movingSprite(Sprite.oneal_right1, Sprite.oneal_right2, Sprite.oneal_right3, + _animate, Game.TICKS_PER_SECOND); else _sprite = Sprite.oneal_left1; break; case 2: case 3: - if(_moving) - _sprite = Sprite.movingSprite(Sprite.oneal_left1, Sprite.oneal_left2, Sprite.oneal_left3, _animate, 60); + if (isMoving()) + _sprite = Sprite.movingSprite(Sprite.oneal_left1, Sprite.oneal_left2, Sprite.oneal_left3, _animate, + Game.TICKS_PER_SECOND); else _sprite = Sprite.oneal_left1; break; diff --git a/src/uet/oop/bomberman/entities/character/enemy/ai/AIMedium.java b/src/uet/oop/bomberman/entities/character/enemy/ai/AIMedium.java index 1fa7960..9ad759f 100644 --- a/src/uet/oop/bomberman/entities/character/enemy/ai/AIMedium.java +++ b/src/uet/oop/bomberman/entities/character/enemy/ai/AIMedium.java @@ -1,23 +1,21 @@ package uet.oop.bomberman.entities.character.enemy.ai; -import uet.oop.bomberman.entities.character.Bomber; -import uet.oop.bomberman.entities.character.enemy.Enemy; +import uet.oop.bomberman.base.ICharacterManager; +import uet.oop.bomberman.entities.character.Character; public class AIMedium extends AI { - Bomber _bomber; - Enemy _e; + + private final Character character; + private final ICharacterManager characterManager; - public AIMedium(Bomber bomber, Enemy e) { - _bomber = bomber; - _e = e; + public AIMedium(Character character, ICharacterManager entityManager) { + this.character = character; + this.characterManager = entityManager; } @Override public int calculateDirection() { - // TODO: cài đặt thuật toán tìm đường đi - if(_bomber == null) - return random.nextInt(4); - + int vertical = random.nextInt(2); if(vertical == 1) { @@ -36,19 +34,24 @@ public int calculateDirection() { return calculateRowDirection(); } } - protected int calculateColDirection() { - if(_bomber.getXTile() < _e.getXTile()) + + protected int calculateColDirection() { + Character player = characterManager.getPlayer(); + + if(player.getXTile() < character.getXTile()) return 3; - else if(_bomber.getXTile() > _e.getXTile()) + else if(player.getXTile() > character.getXTile()) return 1; return -1; } protected int calculateRowDirection() { - if(_bomber.getYTile() < _e.getYTile()) + Character player = characterManager.getPlayer(); + + if(player.getYTile() < character.getYTile()) return 0; - else if(_bomber.getYTile() > _e.getYTile()) + else if(player.getYTile() > character.getYTile()) return 2; return -1; } diff --git a/src/uet/oop/bomberman/entities/character/exceptions/ActionOnCooldownException.java b/src/uet/oop/bomberman/entities/character/exceptions/ActionOnCooldownException.java new file mode 100644 index 0000000..665dc0c --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/exceptions/ActionOnCooldownException.java @@ -0,0 +1,5 @@ +package uet.oop.bomberman.entities.character.exceptions; + +public class ActionOnCooldownException extends CannotPerformActionException { + +} diff --git a/src/uet/oop/bomberman/entities/character/exceptions/BombQuotaReachedException.java b/src/uet/oop/bomberman/entities/character/exceptions/BombQuotaReachedException.java new file mode 100644 index 0000000..6d263d8 --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/exceptions/BombQuotaReachedException.java @@ -0,0 +1,5 @@ +package uet.oop.bomberman.entities.character.exceptions; + +public class BombQuotaReachedException extends CannotPerformActionException { + +} diff --git a/src/uet/oop/bomberman/entities/character/exceptions/CannotPerformActionException.java b/src/uet/oop/bomberman/entities/character/exceptions/CannotPerformActionException.java new file mode 100644 index 0000000..eea7279 --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/exceptions/CannotPerformActionException.java @@ -0,0 +1,5 @@ +package uet.oop.bomberman.entities.character.exceptions; + +public class CannotPerformActionException extends CharacterActionException { + +} diff --git a/src/uet/oop/bomberman/entities/character/exceptions/CharacterActionException.java b/src/uet/oop/bomberman/entities/character/exceptions/CharacterActionException.java new file mode 100644 index 0000000..7ce9a17 --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/exceptions/CharacterActionException.java @@ -0,0 +1,5 @@ +package uet.oop.bomberman.entities.character.exceptions; + +public class CharacterActionException extends Exception { + +} diff --git a/src/uet/oop/bomberman/entities/character/exceptions/InvalidActionException.java b/src/uet/oop/bomberman/entities/character/exceptions/InvalidActionException.java new file mode 100644 index 0000000..43dc374 --- /dev/null +++ b/src/uet/oop/bomberman/entities/character/exceptions/InvalidActionException.java @@ -0,0 +1,5 @@ +package uet.oop.bomberman.entities.character.exceptions; + +public class InvalidActionException extends CharacterActionException { + +} diff --git a/src/uet/oop/bomberman/entities/tile/Grass.java b/src/uet/oop/bomberman/entities/tile/Grass.java index 6046143..acd57c0 100644 --- a/src/uet/oop/bomberman/entities/tile/Grass.java +++ b/src/uet/oop/bomberman/entities/tile/Grass.java @@ -4,7 +4,7 @@ import uet.oop.bomberman.entities.Entity; import uet.oop.bomberman.graphics.Sprite; -public class Grass extends Tile { +public class Grass extends NonDestroyableTile { public Grass(int x, int y, Sprite sprite) { super(x, y, sprite); @@ -19,4 +19,9 @@ public Grass(int x, int y, Sprite sprite) { public boolean collide(Entity e) { return true; } + + @Override + public boolean canBePassedThroughBy(Entity other) { + return true; + } } diff --git a/src/uet/oop/bomberman/entities/tile/NonDestroyableTile.java b/src/uet/oop/bomberman/entities/tile/NonDestroyableTile.java new file mode 100644 index 0000000..53b7101 --- /dev/null +++ b/src/uet/oop/bomberman/entities/tile/NonDestroyableTile.java @@ -0,0 +1,16 @@ +package uet.oop.bomberman.entities.tile; + +import uet.oop.bomberman.graphics.Sprite; + +public abstract class NonDestroyableTile extends Tile { + + public NonDestroyableTile(int x, int y, Sprite sprite) { + super(x, y, sprite); + } + + @Override + public boolean isDestroyable() { + return false; + } + +} diff --git a/src/uet/oop/bomberman/entities/tile/Portal.java b/src/uet/oop/bomberman/entities/tile/Portal.java index 9b077d7..0495f25 100644 --- a/src/uet/oop/bomberman/entities/tile/Portal.java +++ b/src/uet/oop/bomberman/entities/tile/Portal.java @@ -1,40 +1,48 @@ package uet.oop.bomberman.entities.tile; -import uet.oop.bomberman.Board; -import uet.oop.bomberman.Game; +import uet.oop.bomberman.base.IEntityManager; +import uet.oop.bomberman.base.ILevelManager; import uet.oop.bomberman.entities.Entity; -import uet.oop.bomberman.entities.character.Bomber; -import uet.oop.bomberman.entities.tile.item.Item; +import uet.oop.bomberman.entities.character.Character; import uet.oop.bomberman.graphics.Sprite; import uet.oop.bomberman.sound.Sound; -public class Portal extends Tile { - protected Board _board; - public Portal(int x, int y,Board board, Sprite sprite) { +public class Portal extends NonDestroyableTile { + protected ILevelManager levelManager; + protected IEntityManager entityManager; + + public Portal(int x, int y, ILevelManager levelManager, IEntityManager entityManager, Sprite sprite) { super(x, y, sprite); - _board = board; + this.levelManager = levelManager; + this.entityManager = entityManager; } - + @Override - public boolean collide(Entity e) {//xu li khi 2 entity va cham - //true cho di qua - //false khong cho di qua + public boolean collide(Entity e) {// xu li khi 2 entity va cham + // true cho di qua + // false khong cho di qua // TODO: xử lý khi Bomber đi vào - if(e instanceof Bomber ) { - - if(_board.detectNoEnemies() == false) - return false; - - if(e.getXTile() == getX() && e.getYTile() == getY()) { - if(_board.detectNoEnemies()){ - _board.nextLevel(); - Sound.play("CRYST_UP"); - } + if (e instanceof Character && ((Character) e).isPlayer()) { + + if (canBePassedThroughBy(e)) { + levelManager.nextLevel(); + Sound.play("CRYST_UP"); } - + + } + + return true; + } + + @Override + public boolean canBePassedThroughBy(Entity other) { + if (other instanceof Character && ((Character) other).isPlayer()) { + + if (!entityManager.isEnemyCleared()) + return false; + return true; } - return true; } diff --git a/src/uet/oop/bomberman/entities/tile/Tile.java b/src/uet/oop/bomberman/entities/tile/Tile.java index 33f9272..f2036c9 100644 --- a/src/uet/oop/bomberman/entities/tile/Tile.java +++ b/src/uet/oop/bomberman/entities/tile/Tile.java @@ -26,6 +26,12 @@ public boolean collide(Entity e) { return false;//khong cho di qua } + + @Override + public boolean canBePassedThroughBy(Entity other) { + return false; + } + @Override public void render(Screen screen) { screen.renderEntity( Coordinates.tileToPixel(_x), Coordinates.tileToPixel(_y), this); @@ -33,4 +39,6 @@ public void render(Screen screen) { @Override public void update() {} + + public abstract boolean isDestroyable(); } diff --git a/src/uet/oop/bomberman/entities/tile/Wall.java b/src/uet/oop/bomberman/entities/tile/Wall.java index 3af3dad..4404dc1 100644 --- a/src/uet/oop/bomberman/entities/tile/Wall.java +++ b/src/uet/oop/bomberman/entities/tile/Wall.java @@ -3,7 +3,7 @@ import uet.oop.bomberman.graphics.Sprite; -public class Wall extends Tile { +public class Wall extends NonDestroyableTile { public Wall(int x, int y, Sprite sprite) { super(x, y, sprite); diff --git a/src/uet/oop/bomberman/entities/tile/destroyable/DestroyableTile.java b/src/uet/oop/bomberman/entities/tile/destroyable/DestroyableTile.java index 5148855..6c2683f 100644 --- a/src/uet/oop/bomberman/entities/tile/destroyable/DestroyableTile.java +++ b/src/uet/oop/bomberman/entities/tile/destroyable/DestroyableTile.java @@ -42,6 +42,11 @@ public boolean collide(Entity e) { return false; } + @Override + public boolean canBePassedThroughBy(Entity other) { + return false; + } + public void addBelowSprite(Sprite sprite) { _belowSprite = sprite; } @@ -59,5 +64,10 @@ protected Sprite movingSprite(Sprite normal, Sprite x1, Sprite x2) { return x2; } + + @Override + public boolean isDestroyable() { + return true; + } } diff --git a/src/uet/oop/bomberman/entities/tile/item/BombItem.java b/src/uet/oop/bomberman/entities/tile/item/BombItem.java index cfdceba..a288df4 100644 --- a/src/uet/oop/bomberman/entities/tile/item/BombItem.java +++ b/src/uet/oop/bomberman/entities/tile/item/BombItem.java @@ -1,27 +1,25 @@ package uet.oop.bomberman.entities.tile.item; -import uet.oop.bomberman.Game; -import uet.oop.bomberman.entities.Entity; -import uet.oop.bomberman.entities.character.Bomber; import uet.oop.bomberman.graphics.Sprite; -import uet.oop.bomberman.sound.Sound; public class BombItem extends Item { + public static final int BOMB_LIMIT_BONUS = 1; + public BombItem(int x, int y, Sprite sprite) { super(x, y, sprite); } @Override - public boolean collide(Entity e) { - // TODO: xử lý Bomber ăn Item - if (e instanceof Bomber) { - - Sound.play("Item"); - Game.addBombRate(1); - remove(); - } - return false; + protected void handleItemActive() { + } + + @Override + protected void handleItemInactive() { } + @Override + public String getDisplayActiveItem() { + return "Bomb:"; + } } diff --git a/src/uet/oop/bomberman/entities/tile/item/FlameItem.java b/src/uet/oop/bomberman/entities/tile/item/FlameItem.java index e251754..cf3c0d1 100644 --- a/src/uet/oop/bomberman/entities/tile/item/FlameItem.java +++ b/src/uet/oop/bomberman/entities/tile/item/FlameItem.java @@ -1,26 +1,25 @@ package uet.oop.bomberman.entities.tile.item; -import uet.oop.bomberman.Game; -import uet.oop.bomberman.entities.Entity; -import uet.oop.bomberman.entities.character.Bomber; import uet.oop.bomberman.graphics.Sprite; -import uet.oop.bomberman.sound.Sound; public class FlameItem extends Item { + public static final int BOMB_RADIUS_BONUS = 1; + public FlameItem(int x, int y, Sprite sprite) { super(x, y, sprite); } @Override - public boolean collide(Entity e) { - // TODO: xử lý Bomber ăn - if (e instanceof Bomber) { - - Sound.play("Item"); - Game.addBombRadius(1); - remove(); - } - return false; + protected void handleItemActive() { + } + + @Override + protected void handleItemInactive() { + } + + @Override + public String getDisplayActiveItem() { + return "Flame:"; } } diff --git a/src/uet/oop/bomberman/entities/tile/item/Item.java b/src/uet/oop/bomberman/entities/tile/item/Item.java index b3a1b8f..f48d13c 100644 --- a/src/uet/oop/bomberman/entities/tile/item/Item.java +++ b/src/uet/oop/bomberman/entities/tile/item/Item.java @@ -1,17 +1,66 @@ package uet.oop.bomberman.entities.tile.item; +import uet.oop.bomberman.Game; import uet.oop.bomberman.entities.Entity; -import uet.oop.bomberman.entities.character.Bomber; -import uet.oop.bomberman.entities.tile.Tile; +import uet.oop.bomberman.entities.character.CanUseItem; +import uet.oop.bomberman.entities.character.Character; +import uet.oop.bomberman.entities.tile.NonDestroyableTile; import uet.oop.bomberman.graphics.Sprite; import uet.oop.bomberman.sound.Sound; -public abstract class Item extends Tile { - protected int _duration = -1; //thoi gian cua item ,-1 la vo han +public abstract class Item extends NonDestroyableTile { + protected int _duration = 30 * Game.TICKS_PER_SECOND; // 30s protected boolean _active = false; + protected int _level; + public Item(int x, int y, Sprite sprite) { super(x, y, sprite); } - + + protected abstract void handleItemActive(); + + protected abstract void handleItemInactive(); + + @Override + public boolean collide(Entity e) { + if (isRemoved()) + return false; + if (e instanceof CanUseItem) { + CanUseItem player = (CanUseItem) e; + Sound.play("Item"); + handleItemActive(); + _active = true; + player.addActiveItem(this); + remove(); + } + return false; + } + + @Override + public boolean canBePassedThroughBy(Entity e) { + return (e instanceof CanUseItem); + } + + @Override + public void update() { + if (!_active) + return; + if (_duration > 0) { + _duration--; + } else { + handleItemInactive(); + _active = false; + } + } + + public int getDuration() { + return _duration; + } + + public abstract String getDisplayActiveItem(); + + public boolean isActive() { + return _active; + } } diff --git a/src/uet/oop/bomberman/entities/tile/item/SpeedItem.java b/src/uet/oop/bomberman/entities/tile/item/SpeedItem.java index 7bc50b7..bcdeb48 100644 --- a/src/uet/oop/bomberman/entities/tile/item/SpeedItem.java +++ b/src/uet/oop/bomberman/entities/tile/item/SpeedItem.java @@ -1,26 +1,25 @@ package uet.oop.bomberman.entities.tile.item; -import uet.oop.bomberman.Game; -import uet.oop.bomberman.entities.Entity; -import uet.oop.bomberman.entities.character.Bomber; import uet.oop.bomberman.graphics.Sprite; -import uet.oop.bomberman.sound.Sound; public class SpeedItem extends Item { + public static final double SPEED_MULTIPLIER = 0.5; + public SpeedItem(int x, int y, Sprite sprite) { super(x, y, sprite); } @Override - public boolean collide(Entity e) { - // TODO: xử lý Bomber ăn Item - if (e instanceof Bomber) { - - Sound.play("Item"); - Game.addBomberSpeed(0.5); - remove(); - } - return false; + protected void handleItemActive() { + } + + @Override + protected void handleItemInactive() { + } + + @Override + public String getDisplayActiveItem() { + return "Speed:"; } } diff --git a/src/uet/oop/bomberman/graphics/Screen.java b/src/uet/oop/bomberman/graphics/Screen.java index 2494ea2..dd8f1b1 100644 --- a/src/uet/oop/bomberman/graphics/Screen.java +++ b/src/uet/oop/bomberman/graphics/Screen.java @@ -1,9 +1,8 @@ package uet.oop.bomberman.graphics; -import uet.oop.bomberman.Board; import uet.oop.bomberman.Game; import uet.oop.bomberman.entities.Entity; -import uet.oop.bomberman.entities.character.Bomber; +import uet.oop.bomberman.utils.Global; import java.awt.*; @@ -14,7 +13,7 @@ public class Screen { protected int _width, _height; public int[] _pixels; private int _transparentColor = 0xffff00ff; - + public static int xOffset = 0, yOffset = 0; public Screen(int width, int height) { @@ -22,15 +21,10 @@ public Screen(int width, int height) { _height = height; _pixels = new int[width * height]; - - } - - public void clear() { - for (int i = 0; i < _pixels.length; i++) { - _pixels[i] = 0; - } + + Global.screenWidth = this.getRealWidth(); + Global.screenHeight = this.getRealHeight(); } - public void renderEntity(int xp, int yp, Entity entity) { //save entity pixels xp -= xOffset; yp -= yOffset; @@ -63,28 +57,16 @@ public void renderEntityWithBelowSprite(int xp, int yp, Entity entity, Sprite be } } } - + public void clear() { + for (int i = 0; i < _pixels.length; i++) { + _pixels[i] = 0; + } + } public static void setOffset(int xO, int yO) { xOffset = xO; yOffset = yO; } - public static int calculateXOffset(Board board, Bomber bomber) { - if(bomber == null) return 0; - int temp = xOffset; - - double BomberX = bomber.getX() / 16; - double complement = 0.5; - int firstBreakpoint = board.getWidth() / 4; - int lastBreakpoint = board.getWidth() - firstBreakpoint; - - if( BomberX > firstBreakpoint + complement && BomberX < lastBreakpoint - complement) { - temp = (int)bomber.getX() - (Game.WIDTH / 2); - } - - return temp; - } - public void drawEndGame(Graphics g, int points) { g.setColor(Color.black); g.fillRect(0, 0, getRealWidth(), getRealHeight()); @@ -92,13 +74,18 @@ public void drawEndGame(Graphics g, int points) { Font font = new Font("Arial", Font.PLAIN, 20 * Game.SCALE); g.setFont(font); g.setColor(Color.white); - drawCenteredString("GAME OVER", getRealWidth(), getRealHeight(), g); + drawCenteredString("GAME OVER", getRealWidth(), getRealHeight()/4, g); font = new Font("Arial", Font.PLAIN, 10 * Game.SCALE); g.setFont(font); g.setColor(Color.yellow); - drawCenteredString("POINTS: " + points, getRealWidth(), getRealHeight() + (Game.TILES_SIZE * 2) * Game.SCALE, g); - } + drawCenteredString("POINTS: " + points, getRealWidth(), getRealHeight()/3 + (Game.TILES_SIZE * 2) * Game.SCALE, g); + font = new Font("Arial", Font.PLAIN, 8 * Game.SCALE); + g.setFont(font); + g.setColor(Color.white); + drawCenteredString("Press R to retry", getRealWidth(), getRealHeight()/2 + (Game.TILES_SIZE * 4) * Game.SCALE, g); + drawCenteredString("Press B to Back Home", getRealWidth(), getRealHeight()/2 + (Game.TILES_SIZE * 5) * Game.SCALE, g); + } public void drawChangeLevel(Graphics g, int level) { g.setColor(Color.black); diff --git a/src/uet/oop/bomberman/graphics/Sprite.java b/src/uet/oop/bomberman/graphics/Sprite.java index fb1d38c..8cfdc69 100644 --- a/src/uet/oop/bomberman/graphics/Sprite.java +++ b/src/uet/oop/bomberman/graphics/Sprite.java @@ -4,170 +4,192 @@ * Lưu trữ thông tin các pixel của 1 sprite (hình ảnh game) */ public class Sprite { - + public final int SIZE; private int _x, _y; public int[] _pixels; protected int _realWidth; protected int _realHeight; private SpriteSheet _sheet; - private SpriteSheet1 _sheet1; + private SpriteSheet1 _sheet1; /* - |-------------------------------------------------------------------------- - | Board sprites - |-------------------------------------------------------------------------- + * |-------------------------------------------------------------------------- + * | Board sprites + * |-------------------------------------------------------------------------- */ public static Sprite grass = new Sprite(16, 8, 4, SpriteSheet1.tiles, 16, 16); public static Sprite brick = new Sprite(16, 7, 0, SpriteSheet.tiles, 16, 16); public static Sprite wall = new Sprite(16, 3, 13, SpriteSheet1.tiles, 16, 16); public static Sprite portal = new Sprite(16, 8, 7, SpriteSheet1.tiles, 14, 14); - + /* - |-------------------------------------------------------------------------- - | Bomber Sprites - |-------------------------------------------------------------------------- + * |-------------------------------------------------------------------------- + * | Bomber Sprites + * |-------------------------------------------------------------------------- */ public static Sprite player_up = new Sprite(16, 0, 0, SpriteSheet.tiles, 15, 16); public static Sprite player_down = new Sprite(16, 2, 0, SpriteSheet.tiles, 12, 15); public static Sprite player_left = new Sprite(16, 3, 0, SpriteSheet.tiles, 10, 15); public static Sprite player_right = new Sprite(16, 1, 0, SpriteSheet.tiles, 10, 16); - + public static Sprite player_up_1 = new Sprite(16, 0, 1, SpriteSheet.tiles, 12, 16); public static Sprite player_up_2 = new Sprite(16, 0, 2, SpriteSheet.tiles, 12, 15); - + public static Sprite player_down_1 = new Sprite(16, 2, 1, SpriteSheet.tiles, 12, 15); public static Sprite player_down_2 = new Sprite(16, 2, 2, SpriteSheet.tiles, 12, 16); - + public static Sprite player_left_1 = new Sprite(16, 3, 1, SpriteSheet.tiles, 11, 16); - public static Sprite player_left_2 = new Sprite(16, 3, 2, SpriteSheet.tiles, 12 ,16); - + public static Sprite player_left_2 = new Sprite(16, 3, 2, SpriteSheet.tiles, 12, 16); + public static Sprite player_right_1 = new Sprite(16, 1, 1, SpriteSheet.tiles, 11, 16); public static Sprite player_right_2 = new Sprite(16, 1, 2, SpriteSheet.tiles, 12, 16); - + public static Sprite player_dead1 = new Sprite(16, 4, 2, SpriteSheet.tiles, 14, 16); public static Sprite player_dead2 = new Sprite(16, 5, 2, SpriteSheet.tiles, 13, 15); public static Sprite player_dead3 = new Sprite(16, 6, 2, SpriteSheet.tiles, 16, 16); - + + // Render player 2 + public static Sprite player2_up = new Sprite(16, 8, 9, SpriteSheet.tiles, 15, 16); + public static Sprite player2_down = new Sprite(16, 10, 9, SpriteSheet.tiles, 12, 15); + public static Sprite player2_left = new Sprite(16, 11, 9, SpriteSheet.tiles, 10, 15); + public static Sprite player2_right = new Sprite(16, 9, 9, SpriteSheet.tiles, 10, 16); + + public static Sprite player2_up_1 = new Sprite(16, 8, 10, SpriteSheet.tiles, 12, 16); + public static Sprite player2_up_2 = new Sprite(16, 8, 11, SpriteSheet.tiles, 12, 15); + + public static Sprite player2_down_1 = new Sprite(16, 10, 10, SpriteSheet.tiles, 12, 15); + public static Sprite player2_down_2 = new Sprite(16, 10, 11, SpriteSheet.tiles, 12, 16); + + public static Sprite player2_left_1 = new Sprite(16, 11, 10, SpriteSheet.tiles, 11, 16); + public static Sprite player2_left_2 = new Sprite(16, 11, 11, SpriteSheet.tiles, 12, 16); + + public static Sprite player2_right_1 = new Sprite(16, 9, 10, SpriteSheet.tiles, 11, 16); + public static Sprite player2_right_2 = new Sprite(16, 9, 11, SpriteSheet.tiles, 12, 16); + + public static Sprite player2_dead1 = new Sprite(16, 12, 11, SpriteSheet.tiles, 14, 16); + public static Sprite player2_dead2 = new Sprite(16, 13, 11, SpriteSheet.tiles, 13, 15); + public static Sprite player2_dead3 = new Sprite(16, 14, 11, SpriteSheet.tiles, 16, 16); + /* - |-------------------------------------------------------------------------- - | Character - |-------------------------------------------------------------------------- + * |-------------------------------------------------------------------------- + * | Character + * |-------------------------------------------------------------------------- */ - //BALLOM + // BALLOM public static Sprite balloom_left1 = new Sprite(16, 9, 0, SpriteSheet.tiles, 16, 16); public static Sprite balloom_left2 = new Sprite(16, 9, 1, SpriteSheet.tiles, 16, 16); public static Sprite balloom_left3 = new Sprite(16, 9, 2, SpriteSheet.tiles, 16, 16); - + public static Sprite balloom_right1 = new Sprite(16, 10, 0, SpriteSheet.tiles, 16, 16); public static Sprite balloom_right2 = new Sprite(16, 10, 1, SpriteSheet.tiles, 16, 16); public static Sprite balloom_right3 = new Sprite(16, 10, 2, SpriteSheet.tiles, 16, 16); - + public static Sprite balloom_dead = new Sprite(16, 9, 3, SpriteSheet.tiles, 16, 16); - - //ONEAL + + // ONEAL public static Sprite oneal_left1 = new Sprite(16, 11, 0, SpriteSheet.tiles, 16, 16); public static Sprite oneal_left2 = new Sprite(16, 11, 1, SpriteSheet.tiles, 16, 16); public static Sprite oneal_left3 = new Sprite(16, 11, 2, SpriteSheet.tiles, 16, 16); - + public static Sprite oneal_right1 = new Sprite(16, 12, 0, SpriteSheet.tiles, 16, 16); public static Sprite oneal_right2 = new Sprite(16, 12, 1, SpriteSheet.tiles, 16, 16); public static Sprite oneal_right3 = new Sprite(16, 12, 2, SpriteSheet.tiles, 16, 16); - + public static Sprite oneal_dead = new Sprite(16, 11, 3, SpriteSheet.tiles, 16, 16); - - //Doll + + // Doll public static Sprite doll_left1 = new Sprite(16, 13, 0, SpriteSheet.tiles, 16, 16); public static Sprite doll_left2 = new Sprite(16, 13, 1, SpriteSheet.tiles, 16, 16); public static Sprite doll_left3 = new Sprite(16, 13, 2, SpriteSheet.tiles, 16, 16); - + public static Sprite doll_right1 = new Sprite(16, 14, 0, SpriteSheet.tiles, 16, 16); public static Sprite doll_right2 = new Sprite(16, 14, 1, SpriteSheet.tiles, 16, 16); public static Sprite doll_right3 = new Sprite(16, 14, 2, SpriteSheet.tiles, 16, 16); - + public static Sprite doll_dead = new Sprite(16, 13, 3, SpriteSheet.tiles, 16, 16); - - //Minvo + + // Minvo public static Sprite minvo_left1 = new Sprite(16, 8, 5, SpriteSheet.tiles, 16, 16); public static Sprite minvo_left2 = new Sprite(16, 8, 6, SpriteSheet.tiles, 16, 16); public static Sprite minvo_left3 = new Sprite(16, 8, 7, SpriteSheet.tiles, 16, 16); - + public static Sprite minvo_right1 = new Sprite(16, 9, 5, SpriteSheet.tiles, 16, 16); public static Sprite minvo_right2 = new Sprite(16, 9, 6, SpriteSheet.tiles, 16, 16); public static Sprite minvo_right3 = new Sprite(16, 9, 7, SpriteSheet.tiles, 16, 16); - + public static Sprite minvo_dead = new Sprite(16, 8, 8, SpriteSheet.tiles, 16, 16); - - //Kondoria + + // Kondoria public static Sprite kondoria_left1 = new Sprite(16, 10, 5, SpriteSheet.tiles, 16, 16); public static Sprite kondoria_left2 = new Sprite(16, 10, 6, SpriteSheet.tiles, 16, 16); public static Sprite kondoria_left3 = new Sprite(16, 10, 7, SpriteSheet.tiles, 16, 16); - + public static Sprite kondoria_right1 = new Sprite(16, 11, 5, SpriteSheet.tiles, 16, 16); public static Sprite kondoria_right2 = new Sprite(16, 11, 6, SpriteSheet.tiles, 16, 16); public static Sprite kondoria_right3 = new Sprite(16, 11, 7, SpriteSheet.tiles, 16, 16); - + public static Sprite kondoria_dead = new Sprite(16, 10, 8, SpriteSheet.tiles, 16, 16); - - //ALL + + // ALL public static Sprite mob_dead1 = new Sprite(16, 15, 0, SpriteSheet.tiles, 16, 16); public static Sprite mob_dead2 = new Sprite(16, 15, 1, SpriteSheet.tiles, 16, 16); public static Sprite mob_dead3 = new Sprite(16, 15, 2, SpriteSheet.tiles, 16, 16); - + /* - |-------------------------------------------------------------------------- - | Bomb Sprites - |-------------------------------------------------------------------------- + * |-------------------------------------------------------------------------- + * | Bomb Sprites + * |-------------------------------------------------------------------------- */ public static Sprite bomb = new Sprite(16, 0, 3, SpriteSheet.tiles, 15, 15); public static Sprite bomb_1 = new Sprite(16, 1, 3, SpriteSheet.tiles, 13, 15); public static Sprite bomb_2 = new Sprite(16, 2, 3, SpriteSheet.tiles, 12, 14); - + /* - |-------------------------------------------------------------------------- - | FlameSegment Sprites - |-------------------------------------------------------------------------- + * |-------------------------------------------------------------------------- + * | FlameSegment Sprites + * |-------------------------------------------------------------------------- */ public static Sprite bomb_exploded = new Sprite(16, 0, 4, SpriteSheet.tiles, 16, 16); public static Sprite bomb_exploded1 = new Sprite(16, 0, 5, SpriteSheet.tiles, 16, 16); public static Sprite bomb_exploded2 = new Sprite(16, 0, 6, SpriteSheet.tiles, 16, 16); - + public static Sprite explosion_vertical = new Sprite(16, 1, 5, SpriteSheet.tiles, 16, 16); public static Sprite explosion_vertical1 = new Sprite(16, 2, 5, SpriteSheet.tiles, 16, 16); public static Sprite explosion_vertical2 = new Sprite(16, 3, 5, SpriteSheet.tiles, 16, 16); - + public static Sprite explosion_horizontal = new Sprite(16, 1, 7, SpriteSheet.tiles, 16, 16); public static Sprite explosion_horizontal1 = new Sprite(16, 1, 8, SpriteSheet.tiles, 16, 16); public static Sprite explosion_horizontal2 = new Sprite(16, 1, 9, SpriteSheet.tiles, 16, 16); - + public static Sprite explosion_horizontal_left_last = new Sprite(16, 0, 7, SpriteSheet.tiles, 16, 16); public static Sprite explosion_horizontal_left_last1 = new Sprite(16, 0, 8, SpriteSheet.tiles, 16, 16); public static Sprite explosion_horizontal_left_last2 = new Sprite(16, 0, 9, SpriteSheet.tiles, 16, 16); - + public static Sprite explosion_horizontal_right_last = new Sprite(16, 2, 7, SpriteSheet.tiles, 16, 16); public static Sprite explosion_horizontal_right_last1 = new Sprite(16, 2, 8, SpriteSheet.tiles, 16, 16); public static Sprite explosion_horizontal_right_last2 = new Sprite(16, 2, 9, SpriteSheet.tiles, 16, 16); - + public static Sprite explosion_vertical_top_last = new Sprite(16, 1, 4, SpriteSheet.tiles, 16, 16); public static Sprite explosion_vertical_top_last1 = new Sprite(16, 2, 4, SpriteSheet.tiles, 16, 16); public static Sprite explosion_vertical_top_last2 = new Sprite(16, 3, 4, SpriteSheet.tiles, 16, 16); - + public static Sprite explosion_vertical_down_last = new Sprite(16, 1, 6, SpriteSheet.tiles, 16, 16); public static Sprite explosion_vertical_down_last1 = new Sprite(16, 2, 6, SpriteSheet.tiles, 16, 16); public static Sprite explosion_vertical_down_last2 = new Sprite(16, 3, 6, SpriteSheet.tiles, 16, 16); - + /* - |-------------------------------------------------------------------------- - | Brick FlameSegment - |-------------------------------------------------------------------------- + * |-------------------------------------------------------------------------- + * | Brick FlameSegment + * |-------------------------------------------------------------------------- */ public static Sprite brick_exploded = new Sprite(16, 7, 1, SpriteSheet.tiles, 16, 16); public static Sprite brick_exploded1 = new Sprite(16, 7, 2, SpriteSheet.tiles, 16, 16); public static Sprite brick_exploded2 = new Sprite(16, 7, 3, SpriteSheet.tiles, 16, 16); - + /* - |-------------------------------------------------------------------------- - | Powerups - |-------------------------------------------------------------------------- + * |-------------------------------------------------------------------------- + * | Powerups + * |-------------------------------------------------------------------------- */ public static Sprite powerup_bombs = new Sprite(16, 0, 10, SpriteSheet.tiles, 16, 16); public static Sprite powerup_flames = new Sprite(16, 1, 10, SpriteSheet.tiles, 16, 16); @@ -176,7 +198,7 @@ public class Sprite { public static Sprite powerup_detonator = new Sprite(16, 4, 10, SpriteSheet.tiles, 16, 16); public static Sprite powerup_bombpass = new Sprite(16, 5, 10, SpriteSheet.tiles, 16, 16); public static Sprite powerup_flamepass = new Sprite(16, 6, 10, SpriteSheet.tiles, 16, 16); - + public Sprite(int size, int x, int y, SpriteSheet sheet, int rw, int rh) { SIZE = size; _pixels = new int[SIZE * SIZE]; @@ -187,6 +209,7 @@ public Sprite(int size, int x, int y, SpriteSheet sheet, int rw, int rh) { _realHeight = rh; load(); } + public Sprite(int size, int x, int y, SpriteSheet1 sheet1, int rw, int rh) { SIZE = size; _pixels = new int[SIZE * SIZE]; @@ -197,12 +220,13 @@ public Sprite(int size, int x, int y, SpriteSheet1 sheet1, int rw, int rh) { _realHeight = rh; load1(); } + public Sprite(int size, int color) { SIZE = size; _pixels = new int[SIZE * SIZE]; setColor(color); } - + private void setColor(int color) { for (int i = 0; i < _pixels.length; i++) { _pixels[i] = color; @@ -216,6 +240,7 @@ private void load() { } } } + private void load1() { for (int y = 0; y < SIZE; y++) { for (int x = 0; x < SIZE; x++) { @@ -223,26 +248,27 @@ private void load1() { } } } + public static Sprite movingSprite(Sprite normal, Sprite x1, Sprite x2, int animate, int time) { int calc = animate % time; int diff = time / 3; - - if(calc < diff) { + + if (calc < diff) { return normal; } - - if(calc < diff * 2) { + + if (calc < diff * 2) { return x1; } - + return x2; } - + public static Sprite movingSprite(Sprite x1, Sprite x2, int animate, int time) { int diff = time / 2; - return (animate % time > diff) ? x1 : x2; + return (animate % time > diff) ? x1 : x2; } - + public int getSize() { return SIZE; } diff --git a/src/uet/oop/bomberman/gui/Frame.java b/src/uet/oop/bomberman/gui/Frame.java index 1a331e4..52252de 100644 --- a/src/uet/oop/bomberman/gui/Frame.java +++ b/src/uet/oop/bomberman/gui/Frame.java @@ -4,6 +4,8 @@ import javax.swing.*; import java.awt.*; +import java.io.File; +import java.io.IOException; /** * Swing Frame chứa toàn bộ các component @@ -17,8 +19,18 @@ public class Frame extends JFrame { private Game _game; public Frame() { - - _containerpane = new JPanel(new BorderLayout()); + + try { + Font f = Font.createFont(Font.TRUETYPE_FONT, new File("res/font/Minecraft.ttf")); + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + ge.registerFont(f); + } catch (FontFormatException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + + _containerpane = new JPanel(new BorderLayout()); _gamepane = new GamePanel(this); _infopanel = new InfoPanel(_gamepane.getGame()); @@ -45,5 +57,9 @@ public void setTime(int time) { public void setPoints(int points) { _infopanel.setPoints(points); } + + public void renderItemTime() { + _infopanel.renderItemTime(); + } } diff --git a/src/uet/oop/bomberman/gui/GameScreen.java b/src/uet/oop/bomberman/gui/GameScreen.java new file mode 100644 index 0000000..a473496 --- /dev/null +++ b/src/uet/oop/bomberman/gui/GameScreen.java @@ -0,0 +1,9 @@ +package uet.oop.bomberman.gui; + +import java.awt.*; + +public abstract class GameScreen { + public abstract void drawScreen(Graphics g); + public abstract void update(); + public abstract void onDestroy(); +} diff --git a/src/uet/oop/bomberman/gui/InfoPanel.java b/src/uet/oop/bomberman/gui/InfoPanel.java index de01310..543f6bd 100644 --- a/src/uet/oop/bomberman/gui/InfoPanel.java +++ b/src/uet/oop/bomberman/gui/InfoPanel.java @@ -1,36 +1,46 @@ package uet.oop.bomberman.gui; import uet.oop.bomberman.Game; +import uet.oop.bomberman.base.IGameInfoManager; +import uet.oop.bomberman.entities.tile.item.Item; import javax.swing.*; import java.awt.*; +import java.util.List; /** * Swing Panel hiển thị thông tin thời gian, điểm mà người chơi đạt được */ public class InfoPanel extends JPanel { - private JLabel timeLabel; private JLabel pointsLabel; + private JLabel itemTimeLabel; + + private final Game game; public InfoPanel(Game game) { + this.game = game; setLayout(new GridLayout()); - - timeLabel = new JLabel("Time: " + game.getBoard().getTime()); + timeLabel = new JLabel("Time: " + game.getBoard().getGameInfoManager().getTime()); timeLabel.setForeground(Color.white); timeLabel.setHorizontalAlignment(JLabel.CENTER); - - pointsLabel = new JLabel("Points: " + game.getBoard().getPoints()); + + pointsLabel = new JLabel("Points: " + game.getBoard().getGameInfoManager().getPoints()); pointsLabel.setForeground(Color.white); pointsLabel.setHorizontalAlignment(JLabel.CENTER); - + + itemTimeLabel = new JLabel(""); + itemTimeLabel.setForeground(Color.white); + itemTimeLabel.setHorizontalAlignment(JLabel.LEFT); + itemTimeLabel.setBorder(BorderFactory.createEmptyBorder(0, 40, 0, 0)); + + add(itemTimeLabel); add(timeLabel); add(pointsLabel); - setBackground(Color.black); setPreferredSize(new Dimension(0, 40)); } - + public void setTime(int t) { timeLabel.setText("Time: " + t); } @@ -38,5 +48,18 @@ public void setTime(int t) { public void setPoints(int t) { pointsLabel.setText("Score: " + t); } - + + public void renderItemTime() { + String label = ""; + List items = game.getBoard().getGameInfoManager().getPlayerActiveItems(); + for (int i = 0; i < items.size(); i++) { + Item item = items.get(i); + if ((item.getDuration()) == 0) { + continue; + } + label += item.getDisplayActiveItem() + item.getDuration() / Game.TICKS_PER_SECOND + " "; + } + itemTimeLabel.setText(label); + } + } diff --git a/src/uet/oop/bomberman/input/Keyboard.java b/src/uet/oop/bomberman/input/Keyboard.java index bb8daad..09d8878 100644 --- a/src/uet/oop/bomberman/input/Keyboard.java +++ b/src/uet/oop/bomberman/input/Keyboard.java @@ -1,37 +1,113 @@ package uet.oop.bomberman.input; +import uet.oop.bomberman.utils.EGameControl; + +import java.awt.RenderingHints.Key; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; +import java.util.Optional; /** * Tiếp nhận và xử lý các sự kiện nhập từ bàn phím */ public class Keyboard implements KeyListener { - - private boolean[] keys = new boolean[120]; //120 is enough to this game - public boolean up, down, left, right, space; - + + private static Keyboard INST = null; + + public static Keyboard i() { + if (INST == null) { + INST = new Keyboard(); + } + return INST; + } + + private Keyboard() { + } + + public interface KeyboardInputCallback { + void onKeyPressed(EGameControl gameControl); + } + + private boolean[] keys = new boolean[65536]; // 120 is enough to this game + public boolean up, down, left, right, space, x, pause, resume; + public Optional keyboardInputCallback; + + public boolean player1_up, player1_down, player1_left, player1_right, player1_bomb; + public boolean player2_up, player2_down, player2_left, player2_right, player2_bomb; + public void update() { up = keys[KeyEvent.VK_UP] || keys[KeyEvent.VK_W]; down = keys[KeyEvent.VK_DOWN] || keys[KeyEvent.VK_S]; left = keys[KeyEvent.VK_LEFT] || keys[KeyEvent.VK_A]; right = keys[KeyEvent.VK_RIGHT] || keys[KeyEvent.VK_D]; - space = keys[KeyEvent.VK_SPACE] || keys[KeyEvent.VK_X]; + + space = keys[KeyEvent.VK_SPACE]; + x = keys[KeyEvent.VK_X]; + + // Player 1 + player1_up = keys[KeyEvent.VK_W]; + player1_down = keys[KeyEvent.VK_S]; + player1_left = keys[KeyEvent.VK_A]; + player1_right = keys[KeyEvent.VK_D]; + player1_bomb = keys[KeyEvent.VK_X]; + + // Player 2 + player2_up = keys[KeyEvent.VK_UP]; + player2_down = keys[KeyEvent.VK_DOWN]; + player2_left = keys[KeyEvent.VK_LEFT]; + player2_right = keys[KeyEvent.VK_RIGHT]; + player2_bomb = keys[KeyEvent.VK_SPACE]; + + } + + private EGameControl keyToGameControl(int keyCode) { + if (keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_W) { + return EGameControl.UP; + } + + if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_S) { + return EGameControl.DOWN; + } + + if (keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_A) { + return EGameControl.LEFT; + } + + if (keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_D) { + return EGameControl.RIGHT; + } + + if (keyCode == KeyEvent.VK_SPACE) { + return EGameControl.SPACE; + } + + if (keyCode == KeyEvent.VK_ENTER) { + return EGameControl.ENTER; + } + if (keyCode == KeyEvent.VK_X) { + return EGameControl.X; + } + + return EGameControl.NONE; } @Override - public void keyTyped(KeyEvent e) {} + public void keyTyped(KeyEvent e) { + } @Override public void keyPressed(KeyEvent e) { keys[e.getKeyCode()] = true; - + if (keyboardInputCallback.isPresent()) { + KeyboardInputCallback callback = keyboardInputCallback.get(); + callback.onKeyPressed(keyToGameControl(e.getKeyCode())); + } } @Override public void keyReleased(KeyEvent e) { keys[e.getKeyCode()] = false; - + } -} +} \ No newline at end of file diff --git a/src/uet/oop/bomberman/level/FileLevelLoader.java b/src/uet/oop/bomberman/level/FileLevelLoader.java index 3432f1e..636d786 100644 --- a/src/uet/oop/bomberman/level/FileLevelLoader.java +++ b/src/uet/oop/bomberman/level/FileLevelLoader.java @@ -6,11 +6,22 @@ import java.util.List; import uet.oop.bomberman.Board; import uet.oop.bomberman.Game; +import uet.oop.bomberman.agent.Agent; +import uet.oop.bomberman.agent.KeyboardAgent; +import uet.oop.bomberman.agent.KeyboardAgentPlayer1; +import uet.oop.bomberman.agent.KeyboardAgentPlayer2; +import uet.oop.bomberman.agent.MovingAgent; import uet.oop.bomberman.entities.LayeredEntity; import uet.oop.bomberman.entities.character.Bomber; +import uet.oop.bomberman.entities.character.Bomber2; import uet.oop.bomberman.entities.character.enemy.Balloon; import uet.oop.bomberman.entities.character.enemy.Doll; +import uet.oop.bomberman.entities.character.enemy.Enemy; +import uet.oop.bomberman.entities.character.enemy.Kondoria; +import uet.oop.bomberman.entities.character.enemy.Minvo; import uet.oop.bomberman.entities.character.enemy.Oneal; +import uet.oop.bomberman.entities.character.enemy.ai.AILow; +import uet.oop.bomberman.entities.character.enemy.ai.AIMedium; import uet.oop.bomberman.entities.tile.Grass; import uet.oop.bomberman.entities.tile.Portal; import uet.oop.bomberman.entities.tile.Wall; @@ -21,6 +32,8 @@ import uet.oop.bomberman.exceptions.LoadLevelException; import uet.oop.bomberman.graphics.Screen; import uet.oop.bomberman.graphics.Sprite; +import uet.oop.bomberman.utils.EGameMode; +import uet.oop.bomberman.utils.Global; public class FileLevelLoader extends LevelLoader { @@ -36,41 +49,45 @@ public FileLevelLoader(Board board, int level) throws LoadLevelException { @Override public void loadLevel(int level) { - // TODO: đọc dữ liệu từ tệp cấu hình /levels/Level{level}.txt - // TODO: cập nhật các giá trị đọc được vào _width, _height, _level, _map List list = new ArrayList<>(); try { - FileReader fr = new FileReader("res\\levels\\Level" + level + ".txt");//doc tep luu map + String filePath; + if (Global.gameMode == EGameMode.ONE_PLAYER) { + filePath = "res/levels/Level1P_" + level + ".txt"; + } else { + filePath = "res/levels/Level2P_" + level + ".txt"; + } + + FileReader fr = new FileReader(filePath); // Đọc tệp lưu map BufferedReader br = new BufferedReader(fr); String line = br.readLine(); - while (!line.equals("")) { + while (line != null && !line.isEmpty()) { list.add(line); line = br.readLine(); - //doc file txt luu vao list } } catch (Exception e) { e.printStackTrace(); } - String[] arrays = list.get(0).trim().split(" "); - _level = Integer.parseInt(arrays[0]); - _height = Integer.parseInt(arrays[1]); - _width = Integer.parseInt(arrays[2]); - _map = new char[_height][_width]; - for (int i = 0; i < _height; i++) { - for (int j = 0; j < _width; j++) { - _map[i][j] = list.get(i + 1).charAt(j); + + if (!list.isEmpty()) { + String[] arrays = list.get(0).trim().split(" "); + _level = Integer.parseInt(arrays[0]); + _height = Integer.parseInt(arrays[1]); + _width = Integer.parseInt(arrays[2]); + _map = new char[_height][_width]; + for (int i = 0; i < _height; i++) { + for (int j = 0; j < _width; j++) { + _map[i][j] = list.get(i + 1).charAt(j); + } } } - //gan cac phan tu cho mang } @Override public void createEntities() { - // TODO: tạo các Entity của màn chơi - // TODO: sau khi tạo xong, gọi _board.addEntity() để thêm Entity vào game - - // TODO: phần code mẫu ở dưới để hướng dẫn cách thêm các loại Entity vào game - // TODO: hãy xóa nó khi hoàn thành chức năng load màn chơi từ tệp cấu hình + Enemy enemy; + Agent agent; + LayeredEntity layeredEntity; for (int y = 0; y < getHeight(); y++) { for (int x = 0; x < getWidth(); x++) { int pos = x + y * getWidth(); @@ -78,78 +95,162 @@ public void createEntities() { switch (c) { // Thêm grass case ' ': - _board.addEntity(pos, new Grass(x, y, Sprite.grass)); + _board.getEntityManager().getTileManager().addTile(pos, new Grass(x, y, Sprite.grass)); break; // Thêm Wall case '#': - _board.addEntity(pos, new Wall(x, y, Sprite.wall)); + _board.getEntityManager().getTileManager().addTile(pos, new Wall(x, y, Sprite.wall)); break; // Thêm Portal case 'x': - _board.addEntity(pos, new LayeredEntity(x, y, + layeredEntity = new LayeredEntity( + x, y, new Grass(x, y, Sprite.grass), - new Portal(x, y, _board, Sprite.portal), - new Brick(x, y, Sprite.brick))); + new Portal(x, y, _board.getLevelManager(), _board.getEntityManager(), Sprite.portal), + new Brick(x, y, Sprite.brick)); + _board.getEntityManager().getTileManager().addTile(pos, layeredEntity); break; // Thêm brick case '*': - _board.addEntity(x + y * _width, - new LayeredEntity(x, y, - new Grass(x, y, Sprite.grass), - new Brick(x, y, Sprite.brick) - ) - ); + layeredEntity = new LayeredEntity( + x, y, + new Grass(x, y, Sprite.grass), + new Brick(x, y, Sprite.brick)); + _board.getEntityManager().getTileManager().addTile(x + y * _width, layeredEntity); break; - // Thêm Bomber + // Thêm Bomber player case 'p': - _board.addCharacter(new Bomber(Coordinates.tileToPixel(x), Coordinates.tileToPixel(y) + Game.TILES_SIZE, _board)); + Bomber bomber = new Bomber( + Coordinates.tileToPixel(x), + Coordinates.tileToPixel(y) + Game.TILES_SIZE, + Game.BOMBERSPEED, + Game.BOMBRATE, + Game.BOMBRADIUS, + _board.getEntityManager()); + _board.getEntityManager().getCharacterManager().addCharacter(bomber); + _board.getEntityManager().getCharacterManager().setPlayer(bomber); + _board.getEntityManager().getCharacterManager().addPlayer(bomber); Screen.setOffset(0, 0); - _board.addEntity(x + y * _width, new Grass(x, y, Sprite.grass)); + _board.getEntityManager().getTileManager().addTile(x + y * _width, + new Grass(x, y, Sprite.grass)); + // if + if (Global.gameMode == EGameMode.TWO_PLAYER) { + agent = new KeyboardAgentPlayer1(bomber); + } else { + agent = new KeyboardAgent(bomber); + } + _board.addAgent(agent); + break; + // Thêm player 2: + case 'a': + Bomber bomber2 = new Bomber2( + Coordinates.tileToPixel(x), + Coordinates.tileToPixel(y) + Game.TILES_SIZE, + Game.BOMBERSPEED, + Game.BOMBRATE, + Game.BOMBRADIUS, + _board.getEntityManager()); + _board.getEntityManager().getCharacterManager().addCharacter(bomber2); + _board.getEntityManager().getCharacterManager().setPlayer(bomber2); + _board.getEntityManager().getCharacterManager().addPlayer(bomber2); + Screen.setOffset(0, 0); + _board.getEntityManager().getTileManager().addTile(x + y * _width, + new Grass(x, y, Sprite.grass)); + agent = new KeyboardAgentPlayer2(bomber2); + _board.addAgent(agent); break; - // Thêm balloon case '1': - _board.addCharacter(new Balloon(Coordinates.tileToPixel(x), Coordinates.tileToPixel(y) + Game.TILES_SIZE, _board)); - _board.addEntity(x + y * _width, new Grass(x, y, Sprite.grass)); + enemy = new Balloon( + Coordinates.tileToPixel(x), + Coordinates.tileToPixel(y) + Game.TILES_SIZE, + _board.getEntityManager()); + _board.getEntityManager().getCharacterManager().addCharacter(enemy); + _board.getEntityManager().getTileManager().addTile(x + y * _width, + new Grass(x, y, Sprite.grass)); + agent = new MovingAgent(enemy, new AILow()); + _board.addAgent(agent); break; // Thêm oneal case '2': - _board.addCharacter(new Oneal(Coordinates.tileToPixel(x), Coordinates.tileToPixel(y) + Game.TILES_SIZE, _board)); - _board.addEntity(pos, new Grass(x, y, Sprite.grass)); + enemy = new Oneal( + Coordinates.tileToPixel(x), + Coordinates.tileToPixel(y) + Game.TILES_SIZE, + _board.getEntityManager()); + _board.getEntityManager().getCharacterManager().addCharacter(enemy); + _board.getEntityManager().getTileManager().addTile(pos, new Grass(x, y, Sprite.grass)); + agent = new MovingAgent(enemy, new AILow()); + _board.addAgent(agent); break; // Thêm doll case '3': - _board.addCharacter(new Doll(Coordinates.tileToPixel(x), Coordinates.tileToPixel(y) + Game.TILES_SIZE, _board)); - _board.addEntity(x + y * _width, new Grass(x, y, Sprite.grass)); + enemy = new Doll( + Coordinates.tileToPixel(x), + Coordinates.tileToPixel(y) + Game.TILES_SIZE, + _board.getEntityManager()); + _board.getEntityManager().getCharacterManager().addCharacter(enemy); + _board.getEntityManager().getTileManager().addTile(x + y * _width, + new Grass(x, y, Sprite.grass)); + agent = new MovingAgent(enemy, + new AIMedium(enemy, _board.getEntityManager().getCharacterManager())); + _board.addAgent(agent); break; - // Thêm oneal - // Thêm BomItem + // Thêm minvo + case '4': + enemy = new Minvo( + Coordinates.tileToPixel(x), + Coordinates.tileToPixel(y) + Game.TILES_SIZE, + _board.getEntityManager()); + _board.getEntityManager().getCharacterManager().addCharacter(enemy); + _board.getEntityManager().getTileManager().addTile(x + y * _width, + new Grass(x, y, Sprite.grass)); + agent = new MovingAgent(enemy, + new AIMedium(enemy, _board.getEntityManager().getCharacterManager())); + _board.addAgent(agent); + break; + // Thêm kondoria + case '5': + enemy = new Kondoria( + Coordinates.tileToPixel(x), + Coordinates.tileToPixel(y) + Game.TILES_SIZE, + _board.getEntityManager()); + _board.getEntityManager().getCharacterManager().addCharacter(enemy); + _board.getEntityManager().getTileManager().addTile(x + y * _width, + new Grass(x, y, Sprite.grass)); + agent = new MovingAgent(enemy, + new AIMedium(enemy, _board.getEntityManager().getCharacterManager())); + _board.addAgent(agent); + break; + // Thêm BomItem case 'b': - LayeredEntity layer = new LayeredEntity(x, y, + layeredEntity = new LayeredEntity( + x, y, new Grass(x, y, Sprite.grass), new BombItem(x, y, Sprite.powerup_bombs), new Brick(x, y, Sprite.brick)); - _board.addEntity(pos, layer); + _board.getEntityManager().getTileManager().addTile(pos, layeredEntity); break; // Thêm SpeedItem case 's': - layer = new LayeredEntity(x, y, + layeredEntity = new LayeredEntity( + x, y, new Grass(x, y, Sprite.grass), new SpeedItem(x, y, Sprite.powerup_speed), new Brick(x, y, Sprite.brick)); - _board.addEntity(pos, layer); + _board.getEntityManager().getTileManager().addTile(pos, layeredEntity); break; // Thêm FlameItem case 'f': - layer = new LayeredEntity(x, y, + layeredEntity = new LayeredEntity( + x, y, new Grass(x, y, Sprite.grass), new FlameItem(x, y, Sprite.powerup_flames), new Brick(x, y, Sprite.brick)); - _board.addEntity(pos, layer); + _board.getEntityManager().getTileManager().addTile(pos, layeredEntity); break; default: - _board.addEntity(pos, new Grass(x, y, Sprite.grass)); + _board.getEntityManager().getTileManager().addTile(pos, new Grass(x, y, Sprite.grass)); break; } diff --git a/src/uet/oop/bomberman/manager/BombManager.java b/src/uet/oop/bomberman/manager/BombManager.java new file mode 100644 index 0000000..096b63e --- /dev/null +++ b/src/uet/oop/bomberman/manager/BombManager.java @@ -0,0 +1,72 @@ +package uet.oop.bomberman.manager; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import uet.oop.bomberman.base.IBombManager; +import uet.oop.bomberman.entities.bomb.Bomb; +import uet.oop.bomberman.entities.bomb.FlameSegment; +import uet.oop.bomberman.graphics.Screen; + +public class BombManager implements IBombManager { + + private List bombs = new ArrayList<>(); + + public BombManager() { + } + + @Override + public void addBomb(Bomb e) { + bombs.add(e); + } + + @Override + public List getBombs() { + return bombs; + } + + @Override + public Bomb getBombAt(double x, double y) { + Iterator bs = bombs.iterator(); + Bomb b; + while (bs.hasNext()) { + b = bs.next(); + if (b.getX() == (int) x && b.getY() == (int) y) + return b; + } + + return null; + } + + @Override + public FlameSegment getFlameSegmentAt(int x, int y) { + Iterator bs = bombs.iterator(); + Bomb b; + while (bs.hasNext()) { + b = bs.next(); + + FlameSegment e = b.flameAt(x, y); + if (e != null) { + return e; + } + } + + return null; + } + + @Override + public void update() { + bombs.forEach(Bomb::update); + bombs = bombs.stream() + .filter(bomb -> !bomb.isRemoved()) + .collect(Collectors.toList()); + } + + @Override + public void render(Screen screen) { + bombs.forEach(bomb -> bomb.render(screen)); + } + +} diff --git a/src/uet/oop/bomberman/manager/CharacterManager.java b/src/uet/oop/bomberman/manager/CharacterManager.java new file mode 100644 index 0000000..3a8c473 --- /dev/null +++ b/src/uet/oop/bomberman/manager/CharacterManager.java @@ -0,0 +1,126 @@ +package uet.oop.bomberman.manager; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import uet.oop.bomberman.Game; +import uet.oop.bomberman.base.ICharacterManager; +import uet.oop.bomberman.base.IGameInfoManager; +import uet.oop.bomberman.base.ILevelManager; +import uet.oop.bomberman.entities.Message; +import uet.oop.bomberman.entities.character.CanUseItem; +import uet.oop.bomberman.entities.character.Character; +import uet.oop.bomberman.entities.tile.item.Item; +import uet.oop.bomberman.graphics.Screen; +import uet.oop.bomberman.sound.Sound; + +public class CharacterManager implements ICharacterManager { + + private List characters = new ArrayList<>(); + // list + private Character player; + private List players = new ArrayList<>(); + + private final IGameInfoManager gameInfoManager; + private final ILevelManager levelManager; + + public CharacterManager(IGameInfoManager gameInfoManager, ILevelManager levelManager) { + this.gameInfoManager = gameInfoManager; + this.levelManager = levelManager; + } + + @Override + public Character getCharacterAtExcluding(int x, int y, Character a) { + Iterator itr = characters.iterator(); + + Character cur; + while (itr.hasNext()) { + cur = itr.next(); + if (cur == a) { + continue; + } + + if (cur.getXTile() == x && cur.getYTile() == y) { + return cur; + } + + } + + return null; + } + + @Override + public void addCharacter(Character e) { + characters.add(e); + } + + @Override + public void setPlayer(Character character) { + this.player = character; + } + + @Override + public Character getPlayer() { + return this.player; + } + + @Override + public void addPlayer(Character e) { + players.add(e); + } + + @Override + public List getPlayers() { + return players; + } + + @Override + public void handleOnDeath(Character character, Character killer) { + if (character.isPlayer()) { + Sound.play("endgame3"); + } else { + double messageX = (character.getX() * Game.SCALE) + (character.getSprite().SIZE / 2 * Game.SCALE); + double messageY = (character.getY() * Game.SCALE) - (character.getSprite().SIZE / 2 * Game.SCALE); + int points = character.getPoints(); + gameInfoManager.addPoints(points); + Message msg = new Message("+" + points, messageX, messageY, 2, Color.white, 14); + gameInfoManager.addMessage(msg); + Sound.play("AA126_11"); + } + } + + @Override + public void handleAfterDeath(Character character) { + if (character.isPlayer()) { + levelManager.endGame(); + } + } + + @Override + public void update() { + for (Character character : characters) { + character.update(); + if (character instanceof CanUseItem) { + CanUseItem characterCanUseItem = ((CanUseItem) character); + characterCanUseItem.getActiveItems().forEach(Item::update); + } + } + characters = characters.stream() + .filter(character -> !character.isRemoved()) + .collect(Collectors.toList()); + } + + @Override + public void render(Screen screen) { + characters.forEach(character -> character.render(screen)); + } + + @Override + public List getCharacters() { + return characters; + } + +} diff --git a/src/uet/oop/bomberman/manager/EntityManager.java b/src/uet/oop/bomberman/manager/EntityManager.java new file mode 100644 index 0000000..2b624c9 --- /dev/null +++ b/src/uet/oop/bomberman/manager/EntityManager.java @@ -0,0 +1,114 @@ +package uet.oop.bomberman.manager; + +import java.util.List; + +import uet.oop.bomberman.base.IBombManager; +import uet.oop.bomberman.base.ICharacterManager; +import uet.oop.bomberman.base.IEntityManager; +import uet.oop.bomberman.base.IGameInfoManager; +import uet.oop.bomberman.base.ILevelManager; +import uet.oop.bomberman.base.ITileManager; +import uet.oop.bomberman.entities.Entity; +import uet.oop.bomberman.entities.character.Character; +import uet.oop.bomberman.graphics.Screen; + +public class EntityManager implements IEntityManager { + + private ITileManager tileManager; + private ICharacterManager characterManager; + private IBombManager bombManager; + + private final int boardWidth; + private final int boardHeight; + + public EntityManager(int boardWidth, int boardHeight, IGameInfoManager gameInfoManager, ILevelManager levelManager) { + this.boardWidth = boardWidth; + this.boardHeight = boardHeight; + this.tileManager = new TileManager(boardWidth, boardHeight); + this.characterManager = new CharacterManager(gameInfoManager, levelManager); + this.bombManager = new BombManager(); + } + + @Override + public Entity getEntityAtExcluding(double x, double y, Character m) { + + Entity res = null; + + if (x < 0) return null; + if (y < 0) return null; + if (x >= boardWidth) return null; + if (y >= boardHeight) return null; + + res = bombManager.getFlameSegmentAt((int) x, (int) y); + if (res != null) + return res; + + res = bombManager.getBombAt(x, y); + if (res != null) + return res; + + res = characterManager.getCharacterAtExcluding((int) x, (int) y, m); + if (res != null) + return res; + + res = tileManager.getTileAt((int) x, (int) y); + + return res; + } + + @Override + public boolean isEnemyCleared() { + // viết lại thành dòng for: kiểm tra trong list character xem có ai nằm trong + // list players hay không + // Nếu có thì chưa clear -> false + // Nếu không còn thì clear -> true + // return !characterManager.getCharacters().stream() + // .anyMatch(character -> characterManager.getPlayers().contains(character)); + for (Character character : characterManager.getCharacters()) { + if (!characterManager.getPlayers().contains(character)) { + return false; + } + } + return true; + } + + @Override + public void update() { + tileManager.update(); + characterManager.update(); + bombManager.update(); + } + + @Override + public void render(Screen screen) { + tileManager.render(screen); + characterManager.render(screen); + bombManager.render(screen); + } + + @Override + public Character getPlayer() { + return characterManager.getPlayer(); + } + + @Override + public List getPlayers() { + return characterManager.getPlayers(); + } + + @Override + public ITileManager getTileManager() { + return tileManager; + } + + @Override + public ICharacterManager getCharacterManager() { + return characterManager; + } + + @Override + public IBombManager getBombManager() { + return bombManager; + } + +} diff --git a/src/uet/oop/bomberman/manager/GameInfoManager.java b/src/uet/oop/bomberman/manager/GameInfoManager.java new file mode 100644 index 0000000..f141e53 --- /dev/null +++ b/src/uet/oop/bomberman/manager/GameInfoManager.java @@ -0,0 +1,123 @@ +package uet.oop.bomberman.manager; + +import java.awt.Font; +import java.awt.Graphics; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import uet.oop.bomberman.Game; +import uet.oop.bomberman.base.IEntityManager; +import uet.oop.bomberman.base.IGameInfoManager; +import uet.oop.bomberman.entities.Message; +import uet.oop.bomberman.entities.character.CanUseItem; +import uet.oop.bomberman.entities.character.Character; +import uet.oop.bomberman.entities.tile.item.Item; +import uet.oop.bomberman.graphics.Screen; + +public class GameInfoManager implements IGameInfoManager { + + private int time; + private int points; + private boolean paused; + private List messages = new ArrayList<>(); + + private IEntityManager entityManager; + + public GameInfoManager() { + this.time = Game.TIME; + this.points = Game.POINTS; + } + + public void setEntityManager(IEntityManager entityManager) { + this.entityManager = entityManager; + } + + @Override + public List getPlayerActiveItems() { + Character player = entityManager.getPlayer(); + if (!(player instanceof CanUseItem)) return new ArrayList<>(); + return ((CanUseItem)player).getActiveItems().collect(Collectors.toList()); + } + + @Override + public void addMessage(Message e) { + messages.add(e); + } + + @Override + public int subtractTime() { + if (!isPaused() && time > 0) + return --time; + else + return time; + } + + @Override + public int getTime() { + return time; + } + + @Override + public int getPoints() { + return points; + } + + @Override + public void addPoints(int points) { + this.points += points; + } + + @Override + public void update() { + updateMessages(); + } + + @Override + public void render(Screen screen) {} + + private void updateMessages() { + Message m; + int left; + for (int i = 0; i < messages.size(); i++) { + m = messages.get(i); + left = m.getDuration(); + + if (left > 0) + m.setDuration(--left); + else + messages.remove(i); + } + } + + public void render(Screen screen, Graphics g) { + renderMessages(g); + } + + public void renderMessages(Graphics g) { + Message m; + for (int i = 0; i < messages.size(); i++) { + m = messages.get(i); + + g.setFont(new Font("Arial", Font.PLAIN, m.getSize())); + g.setColor(m.getColor()); + g.drawString(m.getMessage(), (int) m.getX() - Screen.xOffset * Game.SCALE, (int) m.getY()); + } + } + + @Override + public boolean isPaused() { + return paused; + } + + @Override + public void pause() { + paused = true; + } + + @Override + public void unpause() { + paused = false; + } + +} diff --git a/src/uet/oop/bomberman/manager/LevelManager.java b/src/uet/oop/bomberman/manager/LevelManager.java new file mode 100644 index 0000000..5fa3631 --- /dev/null +++ b/src/uet/oop/bomberman/manager/LevelManager.java @@ -0,0 +1,60 @@ +package uet.oop.bomberman.manager; + +import uet.oop.bomberman.Board; +import uet.oop.bomberman.base.ILevelManager; +import uet.oop.bomberman.exceptions.LoadLevelException; +import uet.oop.bomberman.level.FileLevelLoader; +import uet.oop.bomberman.level.LevelLoader; +import uet.oop.bomberman.utils.EScreenName; +import uet.oop.bomberman.utils.Global; + +public class LevelManager implements ILevelManager { + + private LevelLoader levelLoader; + private Board board; + + public LevelManager(Board board) { + this.board = board; + } + + @Override + public void nextLevel() { + Global.gameLevel += 1; + loadGlobalLevel(); + } + + @Override + public void loadGlobalLevel() { + loadLevel(Global.gameLevel); + } + + private void loadLevel(int level) { + board.clear(); + try { + levelLoader = new FileLevelLoader(board, level); + } catch (LoadLevelException e) { + e.printStackTrace(); + } + synchronized (board) { + board.init(); + levelLoader.createEntities(); + } + } + + @Override + public void endGame() { + Global.currentScreen = EScreenName.END_GAME_SCREEN; + board.getGameInfoManager().pause(); + } + + @Override + public int getBoardWidth() { + return levelLoader.getWidth(); + } + + @Override + public int getBoardHeight() { + return levelLoader.getHeight(); + } + +} diff --git a/src/uet/oop/bomberman/manager/TileManager.java b/src/uet/oop/bomberman/manager/TileManager.java new file mode 100644 index 0000000..5867774 --- /dev/null +++ b/src/uet/oop/bomberman/manager/TileManager.java @@ -0,0 +1,52 @@ +package uet.oop.bomberman.manager; + +import uet.oop.bomberman.Game; +import uet.oop.bomberman.base.ITileManager; +import uet.oop.bomberman.entities.tile.Tile; +import uet.oop.bomberman.graphics.Screen; + +public class TileManager implements ITileManager { + + private int width; + @SuppressWarnings("unused") private int height; + private final Tile[] tiles; + + public TileManager(int width, int height) { + this.width = width; + this.height = height; + tiles = new Tile[width * height]; + } + + @Override + public Tile getTileAt(double x, double y) { + return tiles[(int) x + (int) y * width]; + } + + @Override + public void addTile(int pos, Tile e) { + tiles[pos] = e; + } + + @Override + public void update() { + for (Tile tile: tiles) { + tile.update(); + } + } + + @Override + public void render(Screen screen) { + // only render the visible part of screen + int x0 = Screen.xOffset / Game.TILES_SIZE; // tile precision, -> left X + int x1 = (Screen.xOffset + screen.getWidth() + Game.TILES_SIZE) / Game.TILES_SIZE; // -> right X + int y0 = Screen.yOffset / Game.TILES_SIZE; + int y1 = (Screen.yOffset + screen.getHeight()) / Game.TILES_SIZE; // render one tile plus to fix black margins + + for (int y = y0; y < y1; y++) { + for (int x = x0; x < x1; x++) { + tiles[x + y * width].render(screen); + } + } + } + +} diff --git a/src/uet/oop/bomberman/screen/DeadScreen.java b/src/uet/oop/bomberman/screen/DeadScreen.java new file mode 100644 index 0000000..04e28cf --- /dev/null +++ b/src/uet/oop/bomberman/screen/DeadScreen.java @@ -0,0 +1,194 @@ +package uet.oop.bomberman.screen; +import uet.oop.bomberman.Game; +import uet.oop.bomberman.gui.GameScreen; +import uet.oop.bomberman.input.Keyboard; +import uet.oop.bomberman.utils.EGameControl; +import uet.oop.bomberman.utils.Global; +import java.awt.*; +import java.util.ArrayList; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.IOException; + +public class DeadScreen extends GameScreen { + ArrayList options = new ArrayList<>(); + int selectorIndex = 0; + private Game game; + private BufferedImage restartIcon; + private BufferedImage homeIcon; + private BufferedImage backgroundImage; + private BufferedImage gameover; + public DeadScreen(Game game) { + this.game = game; + options.add("Restart"); + options.add("Back Home"); + try { + gameover = ImageIO.read(getClass().getResource("/menu/gameover.png")); + restartIcon = ImageIO.read(getClass().getResource("/menu/restart.png")); + homeIcon = ImageIO.read(getClass().getResource("/menu/9165683_home_house_icon.png")); + backgroundImage = ImageIO.read(getClass().getResource("/menu/forest_by_forheksed_d9q4k94-fullview 1.png")); + homeIcon = resizeImage(homeIcon, 11 * Game.SCALE, 11 * Game.SCALE); + restartIcon = resizeImage(restartIcon, 11 * Game.SCALE, 11 * Game.SCALE); + restartIcon = colorizeIcon(restartIcon, Color.black); + homeIcon = colorizeIcon(homeIcon, Color.black); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + public void setInput() { + Keyboard.i().keyboardInputCallback = java.util.Optional.of(new Keyboard.KeyboardInputCallback() { + @Override + public void onKeyPressed(EGameControl gameControl) { + switch (gameControl) { + case LEFT: + selectorIndex--; + break; + case RIGHT: + selectorIndex++; + break; + case ENTER: + if (selectorIndex == 0) { + game.restartGame(); + } else if (selectorIndex == 1) { + game.startNewGame(); + } + break; + } + + if (selectorIndex < 0) { + selectorIndex = options.size() - 1; + } else if (selectorIndex > options.size() - 1) { + selectorIndex = 0; + } + } + }); + } + private BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) { + BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = resizedImage.createGraphics(); + g2d.drawImage(originalImage, 0, 0, targetWidth, targetHeight, null); + g2d.dispose(); + return resizedImage; + } + // Rest of the class remains unchanged + private BufferedImage colorizeIcon(BufferedImage icon, Color color) { + BufferedImage newIcon = new BufferedImage(icon.getWidth(), icon.getHeight(), BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < icon.getWidth(); x++) { + for (int y = 0; y < icon.getHeight(); y++) { + int argb = icon.getRGB(x, y); + if ((argb >> 24) != 0x00) { // Check if pixel is not transparent + newIcon.setRGB(x, y, color.getRGB()); + } else { + newIcon.setRGB(x, y, argb); + } + } + } + return newIcon; + } + + @Override + public void drawScreen(Graphics g) { + + if (backgroundImage != null) { + g.drawImage(backgroundImage, 0, 0, Global.screenWidth, Global.screenHeight, null); + } else { + g.setColor(Color.BLACK); + g.fillRect(0, 0, Global.screenWidth, Global.screenHeight); + } + drawTitle(g); + drawPOINTS(g, game.getBoard().getGameInfoManager().getPoints()); + drawTIMES(g, game.getBoard().getGameInfoManager().getTime()); + drawOptions(g); + drawSelector(g); + g.drawImage(gameover, Global.screenWidth/3 + 40, Global.screenHeight/2 + 80, 50*Game.SCALE, 50*Game.SCALE, null); + } + + private void drawTitle(Graphics g) { + String title = "GAME OVER"; + Font font = new Font("Arial", Font.BOLD, 22 * Game.SCALE); + g.setFont(font); + g.setColor(Color.BLACK); + + FontMetrics fm = g.getFontMetrics(); + int x = (Global.screenWidth - fm.stringWidth(title)) / 2; + int marginTop = 180; + int y = marginTop + fm.getAscent(); + + g.drawString(title, x, y); + } + private void drawPOINTS(Graphics g,int points) + { + String Point = "POINTS: " + points; + Font font = new Font("Arial", Font.BOLD, 6 * Game.SCALE); + g.setFont(font); + g.setColor(Color.BLACK); + + FontMetrics fm = g.getFontMetrics(); + int textWidth = fm.stringWidth(Point); + int x = (Global.screenWidth - textWidth) / 2; // Vị trí x để chuỗi ở giữa màn hình + int marginTop = 255; + int y = marginTop + fm.getAscent(); + + g.drawString(Point, x, y); + + } + private void drawTIMES(Graphics g,int times) + { + String Point = "TIME : " + times; + Font font = new Font("Arial", Font.BOLD, 6 * Game.SCALE); + g.setFont(font); + g.setColor(Color.BLACK); + + FontMetrics fm = g.getFontMetrics(); + int textWidth = fm.stringWidth(Point); + int x = (Global.screenWidth - textWidth) / 2; // Vị trí x để chuỗi ở giữa màn hình + int marginTop = 280; + int y = marginTop + fm.getAscent(); + + g.drawString(Point, x, y); + + } + private void drawOptions(Graphics g) { + int w = Global.screenWidth; + int h = Global.screenHeight; + int iconHeight = restartIcon.getHeight(); + int marginTop = (h - iconHeight + 50) / (2); + + int spacing = 70; + int totalOptionsWidth = restartIcon.getWidth() + spacing + homeIcon.getWidth(); + + int startX = (w - totalOptionsWidth) / 2; + + g.drawImage(restartIcon, startX, marginTop, null); + + int homeIconX = startX + restartIcon.getWidth() + spacing; + g.drawImage(homeIcon, homeIconX, marginTop, null); + } + + private void drawSelector(Graphics g) { + int w = Global.screenWidth; + int h = Global.screenHeight; + int iconHeight = restartIcon.getHeight(); + int marginTop = (h - iconHeight) / 2; + + int spacing = 80; + int totalOptionsWidth = restartIcon.getWidth() + spacing + homeIcon.getWidth(); + + int startX = (w - totalOptionsWidth) / 2; + + int selectorX = selectorIndex == 0 ? startX - 20 : startX + restartIcon.getWidth() + spacing - 30; + int y = marginTop + (iconHeight / 2) + 35; + + g.drawString(">", selectorX, y); + } + + + @Override + public void update() { + } + @Override + public void onDestroy() { + } +} diff --git a/src/uet/oop/bomberman/screen/SelectGameModeScreen.java b/src/uet/oop/bomberman/screen/SelectGameModeScreen.java new file mode 100644 index 0000000..41aa8b1 --- /dev/null +++ b/src/uet/oop/bomberman/screen/SelectGameModeScreen.java @@ -0,0 +1,143 @@ +package uet.oop.bomberman.screen; + +import uet.oop.bomberman.Game; +import uet.oop.bomberman.gui.GameScreen; +import uet.oop.bomberman.input.Keyboard; +import uet.oop.bomberman.utils.*; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Optional; + +public class SelectGameModeScreen extends GameScreen { + ArrayList gameModes = new ArrayList(); + int selectorIndex = 0; + private Optional _input; + private BufferedImage backgroundImage; + private int OFFSET = 20; + + public SelectGameModeScreen() { + gameModes.add(EGameMode.ONE_PLAYER.getStringLevel()); + gameModes.add(EGameMode.TWO_PLAYER.getStringLevel()); + + try { + backgroundImage = ImageIO.read(getClass().getResource("/menu/background.jpg")); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void setInput(Keyboard input) { + _input = Optional.ofNullable(input); + + _input.get().keyboardInputCallback = Optional.of(new Keyboard.KeyboardInputCallback() { + @Override + public void onKeyPressed(EGameControl gameControl) { + switch (gameControl) { + case UP: + selectorIndex--; + break; + case DOWN: + selectorIndex++; + break; + case ENTER: + Global.currentScreen = EScreenName.SELECT_LEVEL_SCREEN; + if (selectorIndex == 1) { + Global.gameMode = EGameMode.TWO_PLAYER; + } else { + Global.gameMode = EGameMode.ONE_PLAYER; + } + break; + } + + if (selectorIndex < 0) { + selectorIndex = gameModes.size() - 1; + } else if (selectorIndex > gameModes.size() - 1) { + selectorIndex = 0; + } + } + }); + } + + @Override + public void drawScreen(Graphics g) { + // set background + if (backgroundImage != null) { + g.drawImage(backgroundImage, 0, 0, Global.screenWidth, Global.screenHeight, null); + } else { + g.setColor(Color.BLACK); + g.fillRect(0, 0, Global.screenWidth, Global.screenHeight); + } + + drawTitle(g); + drawOptions(g); + drawSelector(g); + } + + private void drawTitle(Graphics g) { + String title = "SELECT GAME MODE"; + Font font = new Font("Minecraft", Font.BOLD, 20 * Game.SCALE); + g.setFont(font); + g.setColor(Color.white); + + FontMetrics fm = g.getFontMetrics(); + int x = (Global.screenWidth - fm.stringWidth(title)) / 2; + int marginTop = 20; + int y = marginTop + fm.getAscent(); + + g.drawString(title, x, y); + } + + private void drawOptions(Graphics g) { + Font font = new Font("Minecraft", Font.PLAIN, 10 * Game.SCALE); + g.setFont(font); + g.setColor(Color.white); + + int w = Global.screenWidth; + int h = Global.screenHeight; + FontMetrics fm = g.getFontMetrics(); + int textHeight = fm.getAscent() + fm.getDescent(); + int boxHeight = textHeight * this.gameModes.size(); + int marginTop = (h - boxHeight) / 2; + + for (int i = 0; i < this.gameModes.size(); i++) { + String level = this.gameModes.get(i); + int x = (w - fm.stringWidth(level)) / 2; + int y = marginTop + fm.getAscent() + textHeight * i; + + g.drawString(level, x, y+ OFFSET); + } + } + + private void drawSelector(Graphics g) { + Font font = new Font("Minecraft", Font.PLAIN, 10 * Game.SCALE); + g.setFont(font); + g.setColor(Color.white); + + String level = this.gameModes.get(selectorIndex); + int w = Global.screenWidth; + int h = Global.screenHeight; + FontMetrics fm = g.getFontMetrics(); + int textHeight = fm.getAscent() + fm.getDescent(); + int boxHeight = textHeight * this.gameModes.size(); + int marginTop = (h - boxHeight) / 2; + + int x = (w - fm.stringWidth(level)) / 2 - 30; + int y = marginTop + fm.getAscent() + textHeight * selectorIndex; + + g.drawString(">", x, y+ OFFSET); + } + + @Override + public void update() { + + } + + @Override + public void onDestroy() { + this._input.get().keyboardInputCallback = Optional.ofNullable(null); + } +} diff --git a/src/uet/oop/bomberman/screen/SelectLevelScreen.java b/src/uet/oop/bomberman/screen/SelectLevelScreen.java new file mode 100644 index 0000000..7719e79 --- /dev/null +++ b/src/uet/oop/bomberman/screen/SelectLevelScreen.java @@ -0,0 +1,151 @@ +package uet.oop.bomberman.screen; + +import uet.oop.bomberman.Board; +import uet.oop.bomberman.Game; +import uet.oop.bomberman.gui.GameScreen; +import uet.oop.bomberman.input.Keyboard; + +import uet.oop.bomberman.utils.EGameControl; +import uet.oop.bomberman.utils.EGameLevel; +import uet.oop.bomberman.utils.EGameMode; +import uet.oop.bomberman.utils.EScreenName; +import uet.oop.bomberman.utils.Global; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Optional; + +public class SelectLevelScreen extends GameScreen { + ArrayList levels = new ArrayList(); + int selectorIndex = 0; + private Optional _input; + private Board _board; + private BufferedImage backgroundImage; + + public SelectLevelScreen(Board board) { + _board = board; + + levels.add(EGameLevel.EASY.getStringLevel()); + levels.add(EGameLevel.MEDIUM.getStringLevel()); + levels.add(EGameLevel.HARD.getStringLevel()); + + try { + backgroundImage = ImageIO.read(getClass().getResource("/menu/background.jpg")); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void setInput(Keyboard input) { + _input = Optional.ofNullable(input); + + _input.get().keyboardInputCallback = java.util.Optional.of(new Keyboard.KeyboardInputCallback() { + @Override + public void onKeyPressed(EGameControl gameControl) { + switch (gameControl) { + case UP: + selectorIndex--; + break; + case DOWN: + selectorIndex++; + break; + case ENTER: + Global.gameLevel = selectorIndex + 1; + _board.getLevelManager().loadGlobalLevel(); + Global.currentScreen = EScreenName.GAME_PLAY_SCREEN; + onDestroy(); + break; + default: break; + } + + if (selectorIndex < 0) { + selectorIndex = levels.size() - 1; + } else if (selectorIndex > levels.size() - 1) { + selectorIndex = 0; + } + } + }); + } + + @Override + public void drawScreen(Graphics g) { + // set background + if (backgroundImage != null) { + g.drawImage(backgroundImage, 0, 0, Global.screenWidth, Global.screenHeight, null); + } else { + g.setColor(Color.BLACK); + g.fillRect(0, 0, Global.screenWidth, Global.screenHeight); + } + + drawTitle(g); + drawOptions(g); + drawSelector(g); + } + + private void drawTitle(Graphics g) { + String title = "SELECT LEVEL"; + Font font = new Font("Minecraft", Font.BOLD, 20 * Game.SCALE); + g.setFont(font); + g.setColor(Color.white); + + FontMetrics fm = g.getFontMetrics(); + int x = (Global.screenWidth - fm.stringWidth(title)) / 2; + int marginTop = 20; + int y = marginTop + fm.getAscent(); + + g.drawString(title, x, y); + } + + private void drawOptions(Graphics g) { + Font font = new Font("Minecraft", Font.PLAIN, 10 * Game.SCALE); + g.setFont(font); + g.setColor(Color.white); + + int w = Global.screenWidth; + int h = Global.screenHeight; + FontMetrics fm = g.getFontMetrics(); + int textHeight = fm.getAscent() + fm.getDescent(); + int boxHeight = textHeight * this.levels.size(); + int marginTop = (h - boxHeight) / 2; + + for (int i = 0; i < this.levels.size(); i++) { + String level = this.levels.get(i); + int x = (w - fm.stringWidth(level)) / 2; + int y = marginTop + fm.getAscent() + textHeight * i; + + g.drawString(level, x, y); + } + } + + private void drawSelector(Graphics g) { + Font font = new Font("Minecraft", Font.PLAIN, 10 * Game.SCALE); + g.setFont(font); + g.setColor(Color.white); + + String level = this.levels.get(selectorIndex); + int w = Global.screenWidth; + int h = Global.screenHeight; + FontMetrics fm = g.getFontMetrics(); + int textHeight = fm.getAscent() + fm.getDescent(); + int boxHeight = textHeight * this.levels.size(); + int marginTop = (h - boxHeight) / 2; + + int x = (w - fm.stringWidth(level)) / 2 - 30; + int y = marginTop + fm.getAscent() + textHeight * selectorIndex; + + g.drawString(">", x, y); + } + + @Override + public void update() { + } + + @Override + public void onDestroy() { + this._input.get().keyboardInputCallback = Optional.ofNullable(null); + ; + } +} diff --git a/src/uet/oop/bomberman/sound/Sound.java b/src/uet/oop/bomberman/sound/Sound.java index efafdc1..b231175 100644 --- a/src/uet/oop/bomberman/sound/Sound.java +++ b/src/uet/oop/bomberman/sound/Sound.java @@ -18,13 +18,14 @@ public void run() { clip.open(inputStream); clip.start(); } catch (Exception e) { - System.err.println(e.getMessage()); + System.err.println("Failed playing sound '" + sound + "': " + e.getMessage()); } } }).start(); - + } - public static void stop(String sound){ + + public static void stop(String sound) { new Thread(new Runnable() { public void run() { try { @@ -34,7 +35,7 @@ public void run() { clip.open(inputStream); clip.stop(); } catch (Exception e) { - System.err.println(e.getMessage()); + System.err.println("Failed stopping sound '" + sound + "': " + e.getMessage()); } } }).start(); diff --git a/src/uet/oop/bomberman/utils/EGameControl.java b/src/uet/oop/bomberman/utils/EGameControl.java new file mode 100644 index 0000000..4d1e3f8 --- /dev/null +++ b/src/uet/oop/bomberman/utils/EGameControl.java @@ -0,0 +1,12 @@ +package uet.oop.bomberman.utils; + +public enum EGameControl { + NONE, + UP, + DOWN, + LEFT, + RIGHT, + ENTER, + SPACE, + X, +} diff --git a/src/uet/oop/bomberman/utils/EGameLevel.java b/src/uet/oop/bomberman/utils/EGameLevel.java new file mode 100644 index 0000000..7644772 --- /dev/null +++ b/src/uet/oop/bomberman/utils/EGameLevel.java @@ -0,0 +1,17 @@ +package uet.oop.bomberman.utils; + +public enum EGameLevel { + EASY("Easy"), + MEDIUM("Medium"), + HARD("Hard"); + + private final String level; + + EGameLevel(String level) { + this.level = level; + } + + public String getStringLevel() { + return this.level; + } +} diff --git a/src/uet/oop/bomberman/utils/EGameMode.java b/src/uet/oop/bomberman/utils/EGameMode.java new file mode 100644 index 0000000..d45b474 --- /dev/null +++ b/src/uet/oop/bomberman/utils/EGameMode.java @@ -0,0 +1,16 @@ +package uet.oop.bomberman.utils; + +public enum EGameMode { + ONE_PLAYER("1 Player"), + TWO_PLAYER("2 Player"); + + private final String mode; + + EGameMode(String level) { + this.mode = level; + } + + public String getStringLevel() { + return this.mode; + } +} diff --git a/src/uet/oop/bomberman/utils/EScreenName.java b/src/uet/oop/bomberman/utils/EScreenName.java new file mode 100644 index 0000000..9410891 --- /dev/null +++ b/src/uet/oop/bomberman/utils/EScreenName.java @@ -0,0 +1,8 @@ +package uet.oop.bomberman.utils; + +public enum EScreenName { + SELECT_GAME_MODE, + SELECT_LEVEL_SCREEN, + GAME_PLAY_SCREEN, + END_GAME_SCREEN, +} diff --git a/src/uet/oop/bomberman/utils/Global.java b/src/uet/oop/bomberman/utils/Global.java new file mode 100644 index 0000000..ff4616e --- /dev/null +++ b/src/uet/oop/bomberman/utils/Global.java @@ -0,0 +1,13 @@ +package uet.oop.bomberman.utils; + +public class Global { + public static int screenWidth; + public static int screenHeight; + + public static EScreenName currentScreen = EScreenName.SELECT_GAME_MODE; + public static EScreenName previousScreen = EScreenName.GAME_PLAY_SCREEN; + + // GAME PLAY + public static int gameLevel = 1; + public static EGameMode gameMode; +}