Java-taistelulaivapeli

Minulle on annettu ongelman selvitys taistelulaivapelin luomisesta Java-käyttöjärjestelmään.

Työkoodini (Spring Boot + Web) sijoitetaan tähän ongelman selvityksen kanssa. https://github.com/ankidaemon/BattleShip

Tämä kysymys keskittyy pääasiassa suunnitteluun, auta minua selvittämään, miten voinko tehdä siitä irrotetun ja soveltaa sopivia suunnittelumalleja.

StartGame.java – soittaminen ohjaimelta

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

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

Näytesyöttö

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 

Säännöt
1. Pelaaja1 laukaisee ensin. Jokainen pelaaja saa uuden mahdollisuuden (osuma == onnistunut).
2. Taistelulaivat sijoitetaan vaakasuoraan.
3. Type-Q-alus vaatii 2 ohjusta, jotta ne tuhoutuvat.
4. Tyyppi-P-alus vaatii yhden ohjusosuman tuhoutumiseen.

Syötä
Syötteen ensimmäinen rivi sisältää taistelualueen mitat, joiden leveys ja korkeus on erotettu välilyönnillä.
Toisella rivillä on numero (B) taistelulaivoja jokaisella pelaajalla.
Seuraavassa taistelulaivatyypissä Player-1: n ja Player-2: n mitat (leveys ja korkeus) & -asennot (Y-koordinaatit ja X-koordinaatit) ovat annetaan välilyönnillä erotettuna.
Ja sitten seuraavalla rivillä annetaan Player-1: n ohjusten (välilyönnillä erotettu) kohdekohteiden koordinaatit (Y ja X) ja sitten Player-2: n järjestykselle.

Rajoitukset:
1 < = Taistelualueen leveys (M) < = 9
A < = Taistelualueen korkeus (N ) < = Z
1 < = Taistelulaivojen lukumäärä < = M * N
Aluksen tyyppi = {P, Q}
1 < = Taistelulaivan leveys < = M
A < = taistelulaivan korkeus < = N
1 < = laivan X-koordinaatti < = M
A < = laivan < = N

Kommentit

  • Voit lisätä pelisäännöt ja kommentit ainakin koodin tärkeimmissä osissa. Voit myös siirtää koodin kriittiset osat aliohjelmiin, jotta koodi olisi paljon luettavampi, vaikka niin monien toimintojen kutsuminen tekisi koodista hitaamman, mutta ainakin siitä tulee paljon luettavampaa muille, jotta he voivat myöhemmin ylläpitää päivityksiä paremmin . Voit lisätä alkuperäisen koodin ja modulaarisemman / kommentoidun koodin pelisääntöjen mukana, ehkä perustamalla kommentit kyseisiin sääntöihin.
  • @ alt.126 on lisännyt sääntöjä ja syötettä. StartGame lukee syötteen tiedostosta, tarkistaa ja luo jokaiselle pelaajalle BattleArean ja BattleShipsin. Taistelulaivat ovat POJO. BattleArealla on sääntöjen mukaisesti menetelmiä laivojen sijoittamiseen ja tiedostojen lähettämiseen. Thnx

vastaus

ok antaa kädet:

Luokan nimi StartGame ei ole hyödyllinen, nimeä se uudelleen sopivammaksi nimeksi, mielestäni BattleShipGame ja Käynnistä peli sen sijaan ohjaimestasi

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

init – menetelmä on kaukana suuresta eikä se tee init mutta tekee vielä enemmän asioita … joten hajotetaan se hieman:

  • init pitäisi palauta looginen luku (tai Result), joka osoittaa, että init onnistui.
  • init näyttää siltä, että se on ”sa edustajan metodi , joka tarkoittaa sen pitäisi olla hyvin vähän logiikkaa – sen sijaan on hyödyllistä laittaa suurin osa työstä menetelmiin
  • vain aloittaa asioita ja älä tee muita asioita
  • käytä Player objektit …
  • siirrä pelilogiikka pois metodista

se voi näyttää tältä täältä

HUOMAUTUS: init-menetelmä voisi lyhentää paljon enemmän, mutta mielestäni huomautan hyvällä tavalla, mitä init pitäisi todella tee …

kuten edellä mainittiin, olet siirtänyt pelilogiikan pois init-metodistasi ja asettanut sen playGame() -menetelmään.

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 alkaisi nyt tällä tavalla:

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

BattleShip-laitteellasi on vielä joitain isuksia, joista voidaan puhua. Minusta oli erittäin hyvä ajatus käyttää luokkaa Coordinate, joka näyttää hyvältä ensi silmäyksellä. Mutta älä käytä sitä seuraamuksellisesti. Ajattele, miten olisi, jos käyttäisit Coordinate lähetettäessäsi sen sijaan, että tekisit int[] koodisi on myös helpompi lukea ja matematiikka olisi paljon helpompaa. Ja älä käytä aluksen tyyppiin char, käytä sen sijaan enumia. Mutta olkaamme rehellisiä, sinulla ei ole ”sijaintia ja leveyttä ja korkeutta”, mikä sinulla todella on suorakulmio – joten käytä suorakulmiota!

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

Suorakulmion ulottuvuus (leveys / korkeus) ja elopisteiden määrä voidaan määrittää 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(); } } 

BattleAreaa on nyt paljon helpompi käyttää käytä, mieti kuinka yksinkertaista voit placeShips nyt:

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

kaikkia yllä olevia koodeja ei ole koottu, joten saattaa olla joitain kirjoitusvirheitä, ja monia menetelmiä ei ole vielä edes toteutettu (kuten Rectangle.contains() tai muita).

yhteenveto

mutta anna ”Katsokaa mitä meillä on nyt:

  • voit vaihtaa aluksen tyypin melko helposti muuttamatta mitään koodi !!! (sinun on lisättävä toinen laivatyyppi ShipType)
  • olet vähentänyt koodisi monimutkaisuutta hyvin pitkälle, et ”ei ole vaarallista laskutoimitusta ioneja.
  • sinulla on erillisiä huolenaiheita, objektit tekevät nyt sen, mitä heidän pitäisi tehdä
  • voit helposti vaihtaa koodisi toiselle pelaajalle (kolmen pelaajan peli)
  • voit testata koodisi nyt

Kommentit

  • No, tiedän, että täällä on vielä niin paljon asioita, että en voinut ’ t käsittele niitä kaikkia – tämä vastaus oli aivan ylhäältä katsottuna eikä pääty yksityiskohtiin

Vastaa

Irrottamiseksi sinun on varmistettava, että toimintojesi ei tarvitse kutsua toisiaan tekemään työnsä. Jos mahdollista, ainoat toiminnot, joiden tulisi kutsua muita, ovat jokaisen osajärjestelmän ohjaintoiminnot, jotka on tarkoitus kutsua kuten API-käyttöliittymä.

Mieti, kuinka sinun tarvitsee siirtää paljon koodia vain lisäämistä varten hyödyllinen toiminto, jos se kutsuu muita toimintoja tai käyttää muuttujia muista suurista alijärjestelmistä. Jos yrität lisätä tämän toiminnon toteuttamista siten, että se ei riipu mistään muusta, vaikka se näyttäisikin päällekkäiseltä koodilta, voit siirtää sen erikseen toiseen ohjelmaan ja vielä enemmän, jos Älä tee siitä riippuvaiseksi kieliominaisuuksista tai kirjaston ominaisuuksista, joita ei ole jokaisessa kääntäjässä ja käyttämässäsi ohjelmointikielessä, jotta kirjoittamasi koodi voidaan siirtää mihin tahansa tarvitsemasi ympäristöön Niitä kutsutaan irrotukseksi .

Kuten näette, irrotusta voidaan käyttää kääntäjän, kielen, järjestelmäympäristön, toimintojen ja alijärjestelmien tasolla. Se voi edellyttää koodin kopiointia ja uudelleenkirjoittamista erillisten, riippumattomuudesta riippumattomien rutiinien luomiseksi. Se voi myös tarkoittaa laajempien standardoitujen ominaisuuksien käyttämistä koodin kannettavuuden lisäämiseksi, ja se voi myös vaatia, että työskentelet itse puuttuvien toimintojen toteuttamiseksi tai siirtämiseksi kaikkiin ohjelmointi- / järjestelmäympäristöihisi, jotta järjestelmästä / kielestä / kääntäjästä riippumatta käytössä, sinulla on aina samat toiminnot.





Tietoja suunnittelumalleista.

Jos haluat tehdä koodistasi erittäin uudelleenkäytettävän ja jos haluat sen kestävän vuosikymmeniä, voit käyttää matalan tason lähestymistapaa suorittimen kokoonpanon ohjelmoinnissa.

Ajattele suoritettavaa tehtävää tai mikrotehtävää tavalla, joka vie aina saman tyyppisen parametrit ja se palauttaa tuloksen aina samalla tavalla.

Anna sitten sille ERITTÄIN nimen nimen tähän rutiiniin. Tämä on opkoodi, käsky, joka on toteutettu funktiona / aliohjelmana ja jota voit käyttää uudelleen kuten mitä tahansa muuta natiivia CPU-käskyä. Tämä tapa suunnitella koodi on erittäin uudelleenkäytettävä ja vakaa. Jos haluat muunnelman siitä, mitä tehdä, lisää vain uusi opcode-toiminto sen sijaan, että tuhoaisit aikaisemman kelvollisen toiminnon.

Tämän soveltaminen koko ohjelmassa pääsuunnittelutapana voi tehdä koodista entistä tiukemman. helpompi seurata.

Kommentit

  • Kiitos, @ alt.126. Vaikka tämä on yleinen ratkaisu (+1), toivoin itse asiassa enemmän Java-suunnittelumalleja / vähemmän esineiden tekemistä / turhien koodien poistamista jne.

Vastaa

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *