Jocul Java Battleship

Mi s-a dat o declarație de problemă pentru a crea un joc Battleship în java.

Codul meu de lucru (Spring Boot + Web) este plasat aici împreună cu declarația de problemă. https://github.com/ankidaemon/BattleShip

Această întrebare se concentrează în principal pe proiectare, vă rog să mă ajutați să dau seama, cum îl pot face decuplat și pot aplica modele de proiectare adecvate.

StartGame.java – apelarea de la controler

@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; } } 

Coordinate.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; } } 

Introducere eșantion

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 

Reguli
1. Player1 va declanșa primul. Fiecare jucător va avea încă o șansă până (lovit == reușit).
2. Navele de luptă vor fi plasate orizontal.
3. Nava de tip Q necesită 2 rachete lovite pentru a fi distruse.
4. Nava de tip P necesită o lovitură de rachetă pentru a fi distrusă.

Intrare
Prima linie de intrare conține dimensiuni ale zonei de luptă având lățimea și înălțimea separate de spațiu.
A doua linie va avea numărul (B) de nave de luptă pe care le are fiecare jucător.
Apoi, în următoarea linie de tip cuirasat, dimensiunile (lățimea și înălțimea) & vor fi pozițiile (coordonata Y și coordonata X) pentru Player-1 și apoi pentru Player-2 dat separat de spațiu.
Și apoi în următoarea linie se vor da secvența jucătorului-1 (separată prin spațiu) a rachetelor coordonatele locației țintei (Y și X) și apoi pentru secvența pentru jucătorul-2.

Constrângeri:
1 < = Lățimea zonei de luptă (M) < = 9
A < = Înălțimea zonei de luptă (N ) < = Z
1 < = Numărul de corăbii < = M * N
Tipul navei = {P, Q}
1 < = Lățimea corăbiei < = M
A < = Înălțimea corăbiei < = N
1 < = X coordonata navei < = M
A < = coordonata Y a navei < = N

Comentarii

  • Puteți adăuga regulile jocului și comentariile cel puțin în cele mai importante părți ale codului. De asemenea, ați putea muta acele părți critice ale codului în subrutine pentru a face codul mult mai lizibil, deși apelarea a atât de multe funcții ar face codul mai lent, dar cel puțin va deveni mult mai lizibil pentru alții, astfel încât să poată menține mai târziu actualizările mai bune . Puteți adăuga codul original și un cod mai modular / comentat însoțit de regulile jocului, poate bazând comentariile pe aceste reguli.
  • @ alt.126 au adăugat reguli și intrări. StartGame citește intrarea dintr-un fișier, face validarea și creează BattleArea și BattleShips pentru fiecare jucător. BattleShips sunt POJO. BattleArea are metode de a plasa nave și fișiere de rachete pe baza regulilor. Thnx

Răspuns

ok lasă mâna pe:

Numele clasei pentru StartGame nu este util, redenumiți-l într-un nume mai potrivit, cred ca BattleShipGame și porniți jocul în loc de controlerul dvs.

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

init – metoda este mult prea mare și nu face init , dar face chiar și mai multe lucruri … așa că hai să descompunem puțin:

  • init ar trebui returnează un boolean (sau un Result) care indică faptul că init a reușit.
  • init arată ca „un metode delegat ceea ce înseamnă ar trebui să existe foarte puține informații logice – în schimb este util să puneți cele mai multe lucruri în metode
  • doar inițiați lucruri și nu faceți alte lucruri
  • utilizați Player obiecte …
  • mutați logica jocului din metodă

ar putea arăta astfel

NOTĂ: metoda init ar putea fi scurtată mult mai mult, dar cred că subliniez într-un mod bun ceea ce ar trebui cu adevărat init faceți …

așa cum s-a menționat mai sus, ați mutat logica jocului din metoda inițială și ați pus-o în metoda playGame().

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; } 

BattleShipGame ar începe acum în acest mod:

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

pentru BattleShip-ul tău mai sunt câteva probleme despre care se poate vorbi. Cred că a fost o idee foarte bună să folosești o clasă Coordinate care la prima vedere arată bine. Dar nu îl utilizați în consecință. Gândiți-vă cum ar fi dacă ați utiliza Coordinate pentru a expedia în loc de int[] care ar face codul dvs. este, de asemenea, mai ușor de citit, iar matematica ar fi mult mai ușoară. Și nu utilizați un char pentru tipul de navă, utilizați un enum. Dar să fim sinceri, nu aveți o „poziție, lățime și înălțime” ceea ce aveți cu adevărat este un dreptunghi – deci folosiți un dreptunghi!

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(); } ... } 

dimensiunea dreptunghiului (lățimea / înălțimea) și cantitatea de puncte de viață pot fi determinate de ShipType

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(); } } 

BattleArea este acum mult mai ușor de folosiți, gândiți-vă cât de simplu puteți placeShips acum:

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); } } } 

nu a fost compilat tot codul de mai sus, așa că acolo pot fi unele erori de ortografie și o mulțime de metode nici măcar nu sunt implementate încă (cum ar fi Rectangle.contains() sau altele).

rezumat

„Uită-te la ceea ce avem acum:

  • poți schimba tipul navei destul de ușor fără a modifica orice cod !!! (trebuie pur și simplu să adăugați un alt tip de navă în ShipType)
  • ați redus complexitatea codului dvs. foarte departe, nu „Nu am calcul periculos ioni.
  • aveți probleme separate, obiectele fac acum ceea ce ar trebui să facă.
  • ați putea schimba cu ușurință codul pentru un alt jucător (joc cu trei jucători)
  • ai putea testa codul tău acum

Comentarii

  • știu că sunt încă atât de multe probleme aici, nu aș putea ‘ nu le rezolvați pe toate – acest răspuns a fost chiar în partea de sus, fără a intra în niciun detaliu

Răspuns

Pentru decuplare trebuie să vă asigurați că funcțiile dvs. nu trebuie să se apeleze reciproc pentru a-și face treaba. Dacă este posibil, singurele funcții care ar trebui să apeleze altele sunt funcțiile driverului fiecărui subsistem care sunt destinate a fi numite ca interfața API.

Gândiți-vă la modul în care ar trebui să portați o mulțime de cod doar pentru a adăuga o funcție utilă dacă acea funcție apelează alte funcții sau folosește variabile din alte subsisteme mari. Dacă depuneți eforturi suplimentare pentru a implementa acea funcție într-un mod în care nu depinde de nimic altceva, chiar dacă pare cod duplicat, o veți putea transporta individual pe alt program, și chiar mai mult dacă nu o faceți să depindă de caracteristicile de limbă sau de funcțiile de bibliotecă care nu sunt prezente pe fiecare compilator și limbaj de programare pe care le utilizați pentru a face posibilă portarea codului pe care îl scrieți în orice mediu de care aveți nevoie , asta este ceea ce ” Se numește decuplare .

După cum puteți vedea, decuplarea poate fi aplicată la nivel de compilator, limbă, mediu de sistem, funcții și subsistem. Ar putea implica duplicarea codului și rescrierea pentru a avea rutine independente, fără dependență. S-ar putea implica, de asemenea, utilizarea unor caracteristici mai standardizate pentru a face codul mai portabil și ar putea avea nevoie, de asemenea, să lucrați la implementarea sau portarea funcționalității lipsă în toate mediile de programare / sistem, astfel încât, indiferent de sistem / limbă / compilator, utilizați, veți avea întotdeauna aceeași funcționalitate disponibilă.





Despre modelele de proiectare.

Dacă doriți să faceți codul dvs. extrem de reutilizabil și dacă doriți să dureze decenii, puteți utiliza abordarea la nivel scăzut a programării ansamblului procesorului.

Gândiți-vă la o sarcină sau o microtask pe care doriți să o efectuați într-un mod care va lua întotdeauna același tip de parametrii și care vor returna întotdeauna un rezultat exact în același mod.

Apoi dați-i un nume FOARTE specific acestei rutine. Acesta va fi un opcode, o instrucțiune, implementată ca funcție / subrutină, pe care o puteți reutiliza la fel ca orice altă instrucțiune CPU nativă. Acest mod de proiectare a codului este foarte reutilizabil și stabil. Dacă doriți o variantă a ceea ce trebuie să faceți, trebuie doar să adăugați o nouă funcție opcode în loc să distrugeți funcționalitatea validă anterioară.

Aplicând acest lucru pe tot parcursul programului ca abordare principală de proiectare, puteți face codul mai structurat încă mai ușor de urmărit.

Comentarii

  • Mulțumesc, @ alt.126. În timp ce aceasta este o soluție generalizată (+1), de fapt, speram mai mult la modelele de proiectare Java / făcând mai puține obiecte / eliminând codul redundant etc.

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *