Java csatahajó játék

Problémamegállapítást kaptam egy csatahajó játék létrehozására Java-ban.

A munkakódom (Spring Boot + Web) ide kerül a probléma kimutatással együtt. https://github.com/ankidaemon/BattleShip

Ez a kérdés elsősorban a tervezésre összpontosít, kérem, segítsen nekem kitalálni, hogyan leválaszthatom és alkalmazhatok megfelelő tervezési mintákat.

StartGame.java – hívás kezdeményezése a vezérlőtől

@Component public class StartGame { private static final Logger logger = LoggerFactory.getLogger(StartGame.class); public String init(File inputFile) throws FileNotFoundException, InputException { // TODO Auto-generated method stub ArrayList<BattleShips> p1s = new ArrayList<BattleShips>(); ArrayList<BattleShips> p2s = new ArrayList<BattleShips>(); int areaWidth = 0; int areahight = 0; ArrayList<Coordinate> player1missiles = null; ArrayList<Coordinate> player2missiles = null; try{ Scanner sc = new Scanner(inputFile); areaWidth = sc.nextInt(); if(areaWidth>9 || areaWidth<1){ raiseException("Supplied area width is invalid.",sc); } areahight = sc.next().toUpperCase().charAt(0) - 64; if(areahight>25 || areahight<0){ raiseException("Supplied area height is invalid.",sc); } sc.nextLine(); int noOfships = sc.nextInt(); if(noOfships>areahight*areaWidth || noOfships<1){ raiseException("Supplied no of ships is invalid.",sc); } sc.nextLine(); for (int j = 0; j < noOfships; j++) { char typeOfShip = sc.next().toUpperCase().charAt(0); if(typeOfShip!="P" && typeOfShip!="Q"){ raiseException("Supplied type of ship is invalid.",sc); } int shipWidth = sc.nextInt(); if(shipWidth>areaWidth || shipWidth<0){ raiseException("Supplied ship width is invalid.",sc); } int shiphight = sc.nextInt(); if(shiphight>areahight || shiphight<0){ raiseException("Supplied ship height is invalid.",sc); } BattleShips ship; for (int i = 0; i <= 1; i++) { char[] locCharArr = sc.next().toUpperCase().toCharArray(); int[] loc = new int[2]; loc[0] = locCharArr[0] - 65; loc[1] = locCharArr[1] - 49; if(loc[0]>areahight || loc[0]<0 || loc[1]>areaWidth || loc[1]<0){ raiseException("Supplied ship location is invalid.",sc); } ship = new BattleShips(shipWidth, shiphight, typeOfShip, loc); if (i % 2 == 0) p1s.add(ship); else p2s.add(ship); } sc.nextLine(); } player1missiles = returnMissileCoordinates(sc.nextLine()); player2missiles = returnMissileCoordinates(sc.nextLine()); sc.close(); }catch(InputMismatchException e){ throw new InputException("Invalid Input supplied.",ErrorCode.INVALIDINPUT); } BattleArea player1 = new BattleArea("player1", areaWidth, areahight, p1s); BattleArea player2 = new BattleArea("player2", areaWidth, areahight, p2s); player1.placeShips(); player2.placeShips(); while (!player1.isLost() && !player2.isLost()) { for (int i = 0; i < player1missiles.size();) { Coordinate c = player1missiles.get(i); while (player1.fireMissile(c, player2)) { player1missiles.remove(i); if (i < player1missiles.size()) { c = player1missiles.get(i); } else break; } if (player1missiles.size() > 0) { player1missiles.remove(i); } break; } for (int j = 0; j < player2missiles.size();) { Coordinate c = player2missiles.get(j); while (player2.fireMissile(c, player1)) { player2missiles.remove(j); if (j < player2missiles.size()) { c = player2missiles.get(j); } else break; } if (player2missiles.size() > 0) { player2missiles.remove(j); } break; } } if (player1.isLost()) { logger.info("-------------------------"); logger.info("Player 2 has Won the Game"); logger.info("-------------------------"); return "Player 2 has Won the Game"; } else { logger.info("-------------------------"); logger.info("Player 1 has Won the Game"); logger.info("-------------------------"); return "Player 1 has Won the Game"; } } private static ArrayList<Coordinate> returnMissileCoordinates(String nextLine) { // TODO Auto-generated method stub ArrayList<Coordinate> tmp = new ArrayList<Coordinate>(); String[] arr = nextLine.split("\\ "); Coordinate tmpC; for (String s : arr) { char[] charArr = s.toCharArray(); tmpC = new Coordinate(charArr[1] - 49, charArr[0] - 65); tmp.add(tmpC); } return tmp; } private void raiseException(String message, Scanner sc) throws InputException { sc.close(); throw new InputException(message, ErrorCode.INVALIDINPUT); } } 

BattleArea.java

public class BattleArea { private static final Logger logger = LoggerFactory.getLogger(BattleArea.class); private String belongsTo; private int width,height; private ArrayList<BattleShips> battleShips; private Set<Coordinate> occupied=new TreeSet<Coordinate>(); private int[][] board=null; private boolean lost=false; public BattleArea(String belongsTo, int width, int height, ArrayList<BattleShips> battleShips) { super(); this.belongsTo = belongsTo; this.width = width; this.height = height; this.battleShips = battleShips; this.board=new int[this.width][this.height]; } public void placeShips(){ for(BattleShips ship:this.battleShips){ int x=ship.getLocation()[1]; int y=ship.getLocation()[0]; if(ship.getWidth()+x>this.width || ship.getHeight()+y>this.height){ logger.error("Coordinate x-"+x+" y-"+y+" for "+this.belongsTo+" is not avilable."); throw new ProhibitedException("Ship cannot be placed in this location.",ErrorCode.OUTOFBATTLEAREA); }else{ Coordinate c=new Coordinate(x, y); if(occupied.contains(c)){ logger.error("Coordinate x-"+c.getX()+" y-"+c.getY()+" for "+this.belongsTo+" is already occupied."); throw new ProhibitedException("Ship cann"t be placed in this location.",ErrorCode.ALREADYOCCUPIED); }else{ Coordinate tempC; for(int i=x;i<ship.getWidth()+x;i++){ for(int j=y;j<ship.getHeight()+y;j++){ logger.debug("Placing at x-"+i+" y-"+j+" for "+this.belongsTo); tempC=new Coordinate(i, j); occupied.add(tempC); if(ship.getTypeOfShip()=="P"){ board[i][j]=1; }else if(ship.getTypeOfShip()=="Q"){ board[i][j]=2; } } } } } } } public boolean fireMissile(Coordinate c, BattleArea enemyBattleArea){ int x=c.getX(); int y=c.getY(); logger.info("Firing at "+enemyBattleArea.belongsTo+" x-"+x+" y-"+y+" :"); if(enemyBattleArea.board[x][y]!=0){ if(enemyBattleArea.board[x][y]==-1){ logger.debug("Already blasted!"); return false; } else if(enemyBattleArea.board[x][y]==1){ Coordinate temp=new Coordinate(x,y); enemyBattleArea.occupied.remove(temp); enemyBattleArea.board[x][y]=-1; if(enemyBattleArea.occupied.size()==0){ enemyBattleArea.setLost(true); } logger.debug("Suucessfully blasted!!"); return true; }else{ enemyBattleArea.board[x][y]=enemyBattleArea.board[x][y]-1; logger.debug("Half life left!!"); return true; } }else{ logger.debug("Missed"); return false; } } public boolean isLost() { return lost; } public void setLost(boolean lost) { this.lost = lost; } } 

BattleShips.java

public class BattleShips { private int width,height; private char typeOfShip; private int[] location; public BattleShips(int width, int height, char typeOfShip, int[] loc) { super(); this.width = width; this.height = height; this.typeOfShip = typeOfShip; this.location = loc; } public int getWidth() { return width; } public int getHeight() { return height; } public char getTypeOfShip() { return typeOfShip; } public int[] getLocation() { return location; } } 

Koordinátor.java

public class Coordinate implements Comparable<Coordinate> { private int x,y; public Coordinate(int x, int y) { super(); this.x = x; this.y = y; } @Override public String toString() { return "Coordinate [x=" + x + ", y=" + y + "]"; } @Override public int compareTo(Coordinate o) { // TODO Auto-generated method stub if(this.x==o.x && this.y==o.y) return 0; else if(this.x<o.x && this.y<o.y) return -1; else return 1; } public int getX() { return x; } public int getY() { return y; } } 

Minta bemenet

5 E 2 Q 1 1 A1 B2 P 2 1 D4 C3 A1 B2 B2 B3 A1 B2 B3 A1 D1 E1 D4 D4 D5 D5 

Szabályok
1. Az első játékos elsüt. Minden játékos kap egy újabb esélyt (hit == sikeres).
2. A csatahajókat vízszintesen helyezzük el.
3. A Q-típusú hajó megsemmisítéséhez 2 rakéta szükséges.
4. A P típusú hajó megsemmisítéséhez 1 rakétaütés szükséges.

Input
A bemenet első sora a harctér dimenzióit tartalmazza, szélességük és magasságuk térrel elválasztva.
A második sorban minden játékos csatahajóinak száma (B) lesz.
Ezután a következő sorban a csatahajó típusának méretei (szélessége és magassága) & pozíciók (Y koordináták és X koordináták) lesznek a Player-1, majd a Player-2 esetében. térrel elválasztva adva.
Ezután a következő sorban megadjuk a Player-1 rakéták (szóközzel elválasztott) sorrendjét (Y és X), majd a Player-2 szekvenciáját.

Korlátozások:
1 < = A harctér szélessége (M) < = 9
A < = A csataterület magassága (N ) < = Z
1 < = csatahajók száma < = M * N
A hajó típusa = {P, Q}
1 < = Csatahajó szélessége < = M
A < = Csatahajó magassága < = N
1 < = a hajó X koordinátája < = M
A < = a hajó < = N

Megjegyzések

  • Hozzáadhatja a játékszabályokat és a megjegyzéseket legalább a kód legfontosabb részeiben. A kód kritikus részeit áthelyezheti szubrutinokba is, hogy a kód sokkal olvashatóbb legyen, bár annyi függvény meghívása lassabbá tenné a kódot, de legalább sokkal olvashatóbb lesz mások számára, hogy később jobban tudják fenntartani a frissítéseket . Hozzáadhatja az eredeti kódot és egy modulárisabb / kommentáltabb kódot a játékszabályok kíséretében, esetleg a megjegyzéseket ezekre a szabályokra alapozva.
  • @ alt.126 hozzáadott szabályokat és bevitelt. A StartGame beolvassa a fájl bemenetét, elvégzi az érvényesítést, és létrehoz minden egyes játékos számára BattleArea-t és BattleShip-eket. A BattleShips POJO. A BattleArea módszerekkel rendelkezik a hajók elhelyezésére és a FileMissiles fájlokra a szabályok alapján. Thnx

Válasz

az ok kezet ad:

Osztály neve a StartGame nem hasznos, nevezze át egy megfelelőbb névre, szerintem BattleShipGame és indítsa el a játékot a vezérlő helyett

BattleShipGame game = new BattleShipGame(); game.start(); 

a init – a módszer messze nagy, és nem hajt végre init t, de még több dolgot csinál … szóval ezt bontsuk le egy kicsit:

  • az init-nek adjon vissza egy logikai értéket (vagy egy Result), amely azt jelzi, hogy az init sikeres volt.
  • az init úgy néz ki, mintha “sa delegált metódus ami azt jelenti Nagyon kevés logikai beírásnak kell lennie – ehelyett hasznos a legtöbb munkát módszerekbe foglalni. “>

objektumok …

  • vigye ki a játék logikáját a metódusból
  • akkor ez így nézhet ki

    MEGJEGYZÉS: az init módszer sokkal rövidebb lehet, de azt hiszem, jó értelemben rámutatok arra, amit a init valójában tegye …

    a fent említettek szerint áthelyezte a játék logikáját az init metódusából, és a playGame() metódusba helyezte.

    public Result playGame(){ Result result = new Result(); Scores score = new Score(); while (!player1.isLost() && !player2.isLost()) { for (int i = 0; i < player1missiles.size();) { ... } } result.setWinner(playerOne); result.setScore(scores); return result; } 

    a BattleShipGame most kezdődne:

    public void start(){ init(); Result result = playGame(); ... //do whatever you want with your result - for example print it into the console } 

    a BattleShipedhez van még néhány olyan iszus, amelyekről beszélhetünk. Szerintem nagyon jó ötlet volt az első pillantásra jól kinéző osztály Coordinate használata. De nem következményesen használja. Gondoljon bele, mi lenne, ha a int[] helyett a Coordinate -t használná a szállításhoz a kódod is könnyebben olvasható, és a matematika sokkal könnyebb lenne. És ne használd a char -t a hajó típusához, inkább használj enumot. De legyünk őszinték, nincs “pozíciója, szélessége és magassága”, ami valójában egy téglalap – ezért használjon téglalapot!

    public class BattleShips { private ShipType shipType; private Rectangle bounds; private int lifePoints; public BattleShips(ShipType typeOfShip, Coordinate pos) { super(); shipType = typeOfShip; bounds = new Rectangle(shipType.getDimension, pos); lifePoints = shipType.getLifePoints(); } public Rectangle getBounds() { return bounds(); } ... } 

    a Téglalap méretét (szélesség / magasság) és az életpontok mennyiségét a ShipType segítségével lehet meghatározni

    public Enum Shiptype { DESTROYER(2,4,2), SUBMARINE(1,3,1), ...; //don"t use shiptype P or shiptype Q private final Dimension dimension; final int lifePoints; public ShipType(int w, int h, int life){ dimension = new Dimension(w,h); lifePoints = life; } public Dimension getDimension(){ return dimension; } public int getLifePoints(){ return lifePoints(); } } 

    A BattleArea ma már sokkal könnyebben elérhető használd, gondold át, mennyire egyszerű most placeShips:

    public class BattleArea { private Player owner; private Rectangle boardBounds; private List<BattleShips> battleShips; private List<Coordinates> board; public BattleArea(Player owner, Rectangle bounds, List<BattleShips> battleShips) { super(); this.owner = owner; this.dimension = dimension; this.battleShips = battleShips; board = createBoard(); } public void placeShips(){ List<BattleShip> placedShips = new ArrayList<>(); for(BattleShips ship:this.battleShips){ Bound shipBounds = ship.getBounds(); if(!boardBounds.contains(shipBounds)){ throw new ProhibitedException( "Ship cannot be placed in this location.",ErrorCode.OUTOFBATTLEAREA); } for (BattleShip placedShip: placedShips){ if (bounds.intersects(placedShip.getBounds()){ throw new ProhibitedException( "Ship cann"t be placed in this location.",ErrorCode.ALREADYOCCUPIED); } } placedShips.add(battleShip); } } public boolean fireMissile(Coordinate c, BattleArea enemyBattleArea){ BattleShip shipAt = enemyBattleArea.getShipAt(c); if(shipAt == null){ return false; }else{ handleDamge(shipAt, enemyBattleArea); return true; } } private void handleDamage(BattleShip opponent, BattleArea area){ int lifePointsLeft = opponent.getLifePoints() - 1; //hardcoded damage (that"s bad) if(lifPoints > 0){ //Log damage done }else{ //log destroyed area.removeBattleShip(opponent); } } } 

    az összes fenti kódot nem fordítottuk le, így ott helyesírási hibák lehetnek, és sok módszer még nincs bevezetve (például Rectangle.contains() vagy mások).

    összefoglaló

    de hagyja “Nézzük meg, mi van most:

    • a hajótípust meglehetősen egyszerűen megváltoztathatja anélkül, hogy módosítaná a bármelyik kód !!! (egyszerűen hozzá kell adnia egy másik hajótípust a ShipType mezőbe)
    • nagyon messze csökkentette a kód bonyolultságát, nem szabad nincs veszélyes számítás ionok.
    • külön aggályai vannak, az objektumok most azt csinálják, amire kéne,
    • könnyedén megváltoztathatja a kódját egy másik játékossal (háromjátékos játék)
    • most tesztelheted a kódodat

    Megjegyzések

    • Nos, tudom, hogy még mindig sok probléma van itt, nem tudtam ‘ nem kezeli mindet – ez a válasz a legfelülről nézve volt, és nem került bele a részletekbe

    Válasz

    A leválasztáshoz meg kell győződnie arról, hogy a funkcióinak nem kell egymást felhívniuk a munkájuk elvégzéséhez. Ha lehetséges, csak azoknak a függvényeknek kell meghívniuk másokat, amelyek minden alrendszer meghajtófunkciói, amelyeket úgy hívnak meg, mint az API interfész.

    Gondoljon arra, hogyan kellene sok kódot átvinni csak hozzáadáshoz hasznos funkció, ha ez a függvény más függvényeket hív meg, vagy más nagy alrendszerek változóit használja. Ha további erőfeszítéseket tesz a függvény megvalósítására oly módon, hogy az nem függ mástól, még akkor is, ha duplikált kódnak tűnik, akkor külön-külön portálhatja egy másik programba, és még inkább, ha: ne tegye függővé azoktól a nyelvi jellemzőktől vagy könyvtári jellemzőktől, amelyek nincsenek minden egyes fordítóban és a használt programozási nyelvben, hogy lehetővé tegye az Ön által írt kódok bármely szükséges környezetbe való áthelyezését s úgynevezett szétkapcsolás .

    Mint látható, a szétkapcsolás a fordító, a nyelv, a rendszer környezete, a funkciók és az alrendszer szintjén alkalmazható. Ez magában foglalhatja a kód lemásolását és az újraírást, hogy önálló, függőség nélküli rutinok legyenek. Ez azt is magában foglalhatja, hogy szélesebb körűen szabványosított funkciókat használnak a kód hordozhatóbbá tételéhez, és arra is szükség lehet, hogy a hiányzó funkciók implementálásával vagy az összes programozási / rendszerkörnyezetbe való áthelyezésével saját maga dolgozzon, függetlenül attól, hogy milyen rendszert / nyelvet / fordítót használ használat esetén mindig ugyanazok a funkciók érhetők el.





    A tervezési mintákról.

    Ha a kódot nagyon újrafelhasználhatóvá kívánja tenni és ha azt szeretné, hogy évtizedekig tartson, akkor használhatja a CPU összeszerelés programozásának alacsony szintű megközelítését. paraméterek, és ez mindig pontosan ugyanúgy fog eredményt adni.

    Ezután NAGYON nevet adjon ennek a rutinnak. Ez egy opcode, egy utasítás, amelyet függvényként / alprogramként valósítanak meg, amelyet ugyanúgy használhat fel, mint bármely más natív CPU utasítást. A kód tervezésének ilyen módja rendkívül újrafelhasználható és stabil. Ha változtatni szeretne a teendőiben, akkor csak hozzá kell adnia egy új opcode függvényt a korábbi érvényes funkcionalitás megsemmisítése helyett.

    Ennek alkalmazása a programban, mivel a fő tervezési megközelítés még szigorúbbá teheti a kódot könnyebben követhető.

    Megjegyzések

    • Köszönöm, @ alt.126. Bár ez általánosított megoldás (+1), valójában többet reméltem a Java tervezési mintákban / kevesebb objektum készítésében / a felesleges kód eltávolításában stb.

    Vélemény, hozzászólás?

    Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük