Konzolová hra OOP Battleship v Javě

Můj druhý pohled na toto najdete zde

Chtěl jsem vytvořit jednoduchou konzolovou hru, abych si procvičil OOP. Opravdu bych ocenil recenzi, která se zaměřuje na čitelnost, údržbu a osvědčené postupy.

To, co mě na tomto kódu trochu štve, je, že nepoužívám rozhraní, abstraktní třídy ani dědičnost, ale nemohl jsem Zde pro ně nenajdeme vhodný případ použití.

Board.java

package com.tn.board; import com.tn.constants.Constants; import com.tn.ship.Ship; import com.tn.utils.Position; import com.tn.utils.Utils; import java.awt.Point; import java.util.Scanner; public class Board { private static final Ship[] ships; private char[][] board; /** * Initialize ships (once). * */ static { ships = new Ship[]{ new Ship("Carrier", Constants.CARRIER_SIZE), new Ship("Battleship", Constants.BATTLESHIP_SIZE), new Ship("Cruiser", Constants.CRUISER_SIZE), new Ship("Submarine", Constants.SUBMARINE_SIZE), new Ship("Destroyer", Constants.DESTROYER_SIZE) }; } /** * Constructor */ public Board() { board = new char[Constants.BOARD_SIZE][Constants.BOARD_SIZE]; for(int i = 0; i < Constants.BOARD_SIZE; i++) { for(int j = 0; j < Constants.BOARD_SIZE; j++) { board[i][j] = Constants.BOARD_ICON; } } placeShipsOnBoard(); } /** * Target ship ship. * * @param point the point * @return ship */ public Ship targetShip(Point point) { boolean isHit = false; Ship hitShip = null; for(int i = 0; i < ships.length; i++) { Ship ship = ships[i]; if(ship.getPosition() != null) { if(Utils.isPointBetween(point, ship.getPosition())) { isHit = true; hitShip = ship; break; } } } final char result = isHit ? Constants.SHIP_IS_HIT_ICON : Constants.SHOT_MISSED_ICON; updateShipOnBoard(point, result); printBoard(); return (isHit) ? hitShip : null; } /** * Place ships on board. */ private void placeShipsOnBoard() { System.out.printf("%nAlright - Time to place out your ships%n%n"); Scanner s = new Scanner(System.in); for(int i = 0; i < ships.length; i++) { Ship ship = ships[i]; boolean isShipPlacementLegal = false; System.out.printf("%nEnter position of %s (length %d): ", ship.getName(), ship.getSize()); while(!isShipPlacementLegal) { try { Point from = new Point(s.nextInt(), s.nextInt()); Point to = new Point(s.nextInt(), s.nextInt()); while(ship.getSize() != Utils.distanceBetweenPoints(from, to)) { System.out.printf("The ship currently being placed on the board is of length: %d. Change your coordinates and try again", ship.getSize()); from = new Point(s.nextInt(), s.nextInt()); to = new Point(s.nextInt(), s.nextInt()); } Position position = new Position(from, to); if(!isPositionOccupied(position)) { drawShipOnBoard(position); ship.setPosition(position); isShipPlacementLegal = true; } else { System.out.println("A ship in that position already exists - try again"); } } catch(IndexOutOfBoundsException e) { System.out.println("Invalid coordinates - Outside board"); } } } } private void updateShipOnBoard(Point point, final char result) { int x = (int) point.getX() - 1; int y = (int) point.getY() - 1; board[y][x] = result; } /** * * @param position * @return */ private boolean isPositionOccupied(Position position) { boolean isOccupied = false; Point from = position.getFrom(); Point to = position.getTo(); outer: for(int i = (int) from.getY() - 1; i < to.getY(); i++) { for(int j = (int) from.getX() - 1; j < to.getX(); j++) { if(board[i][j] == Constants.SHIP_ICON) { isOccupied = true; break outer; } } } return isOccupied; } /** * * @param position */ private void drawShipOnBoard(Position position) { Point from = position.getFrom(); Point to = position.getTo(); for(int i = (int) from.getY() - 1; i < to.getY(); i++) { for(int j = (int) from.getX() - 1; j < to.getX(); j++) { board[i][j] = Constants.SHIP_ICON; } } printBoard(); } /** * Print board. */ private void printBoard() { System.out.print("\t"); for(int i = 0; i < Constants.BOARD_SIZE; i++) { System.out.print(Constants.BOARD_LETTERS[i] + "\t"); } System.out.println(); for(int i = 0; i < Constants.BOARD_SIZE; i++) { System.out.print((i+1) + "\t"); for(int j = 0; j < Constants.BOARD_SIZE; j++) { System.out.print(board[i][j] + "\t"); } System.out.println(); } } } 

Constants.java

package com.tn.constants; public class Constants { private Constants() {} public static final int PLAYER_LIVES = 17; //sum of all the ships public static final int CARRIER_SIZE = 5; public static final int BATTLESHIP_SIZE = 4; public static final int CRUISER_SIZE = 3; public static final int SUBMARINE_SIZE = 3; public static final int DESTROYER_SIZE = 2; public static final char SHIP_ICON = "X"; public static final char BOARD_ICON = "-"; public static final char SHIP_IS_HIT_ICON = "O"; public static final char SHOT_MISSED_ICON = "M"; public static final char[] BOARD_LETTERS = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J"}; public static final int BOARD_SIZE = 10; } 

Player.java

package com.tn.player; import com.tn.board.Board; import com.tn.constants.Constants; import com.tn.ship.Ship; import java.awt.Point; import java.util.HashMap; import java.util.Map; import java.util.Scanner; public class Player { private int id; private int lives; private Board board; private Map<Point, Boolean> targetHistory; private Scanner scanner; /** * Instantiates a new Player. * * @param id the id */ public Player(int id) { System.out.printf("%n=== Setting up everything for Player %s ====", id); this.id = id; this.lives = Constants.PLAYER_LIVES; this.board = new Board(); this.targetHistory = new HashMap<>(); this.scanner = new Scanner(System.in); } /** * Gets id. * * @return the id */ public int getId() { return id; } /** * Gets lives. * * @return the lives */ public int getLives() { return lives; } /** * Decrement live by one. */ public void decrementLiveByOne() { lives--; } /** * Turn to play. * * @param opponent the opponent */ public void turnToPlay(Player opponent) { System.out.printf("%n%nPlayer %d, Choose coordinates you want to hit (x y) ", id); Point point = new Point(scanner.nextInt(), scanner.nextInt()); while(targetHistory.get(point) != null) { System.out.print("This position has already been tried"); point = new Point(scanner.nextInt(), scanner.nextInt()); } attack(point, opponent); } /** * Attack * * @param point * @param opponent */ private void attack(Point point, Player opponent) { Ship ship = opponent.board.targetShip(point); boolean isShipHit = (ship != null) ? true : false; if(isShipHit) { ship.shipWasHit(); opponent.decrementLiveByOne(); } targetHistory.put(point, isShipHit); System.out.printf("Player %d, targets (%d, %d)", id, (int)point.getX(), (int)point.getY()); System.out.println("...and " + ((isShipHit) ? "HITS!" : "misses...")); } } 

Ship.java

package com.tn.ship; import com.tn.utils.Position; public class Ship { private String name; private int size; private int livesLeft; private boolean isSunk; private Position position; public Ship(String name, int size) { this.name = name; this.size = size; this.livesLeft = size; this.isSunk = false; } public String getName() { return name; } public int getSize() { return size; } public int getLivesLeft() { return livesLeft; } public boolean isSunk() { return isSunk; } public void setSunk(boolean sunk) { isSunk = sunk; } public Position getPosition() { return position; } public void setPosition(Position position) { this.position = position; } public void shipWasHit() { if(livesLeft == 0) { isSunk = true; System.out.println("You sunk the " + name); return; } livesLeft--; } } 

Position.java

package com.tn.utils; import com.tn.constants.Constants; import java.awt.Point; public class Position { private Point from; private Point to; /** * Instantiates a new Position. * * @param from the from * @param to the to */ public Position(Point from, Point to) { if(from.getX() > Constants.BOARD_SIZE || from.getX() < 0 || from.getY() > Constants.BOARD_SIZE || from.getY() < 0 || to.getX() > Constants.BOARD_SIZE || to.getX() < 0 || to.getY() > Constants.BOARD_SIZE || to.getY() < 0) { throw new ArrayIndexOutOfBoundsException(); } this.from = from; this.to = to; } /** * Gets from. * * @return the from */ public Point getFrom() { return from; } /** * Gets to. * * @return the to */ public Point getTo() { return to; } } 

Utils.java

package com.tn.utils; import java.awt.Point; public class Utils { private Utils() { } /** * Distance between points double. * * @param from the from * @param to the to * @return the double */ public static double distanceBetweenPoints(Point from, Point to) { double x1 = from.getX(); double y1 = from.getY(); double x2 = to.getX(); double y2 = to.getY(); return Math.sqrt(Math.pow(x1-x2, 2) + Math.pow(y1-y2, 2)) + 1; } /** * Is point between boolean. * * @param point the point * @param position the position * @return the boolean */ public static boolean isPointBetween(Point point, Position position) { Point from = position.getFrom(); Point to = position.getTo(); return from.getY() <= point.getY() && to.getY() >= point.getY() && from.getX() <= point.getX() && to.getX() >= point.getX(); } } 

Game.java

package com.tn.game; import com.tn.player.Player; public class Game { private Player[] players; /** * Instantiates a new Game. */ public Game() { players = new Player[]{ new Player(1), new Player(2) }; } /** * Start. */ public void start() { int i = 0; int j = 1; int size = players.length; Player player = null; while(players[0].getLives() > 0 && players[1].getLives() > 0) { players[i++ % size].turnToPlay(players[j++ % size]); player = (players[0].getLives() < players[1].getLives()) ? players[1] : players[0]; } System.out.printf("Congrats Player %d, you won!",player.getId()); } } 

Mai n.java

package com.tn; import com.tn.game.Game; public class Main { public static void main(String[] args) { Game game = new Game(); game.start(); } } 

Komentáře

  • ‚ zde nebudu psát úplnou recenzi (já ‚ vůbec nejsem odborník na java). Možná vás ale bude zajímat můj poslední bod v zodpovězení této otázky .
  • @ πάνταῥεῖ To ‚ některé skvělé body, děkuji. ‚ Nemyslím si, že jsem byl příliš daleko, pokud jde o vaše návrhy.
  • Ani si to nemyslím, že ‚ proč jsem tam ukázal.
  • Váš kód je celkem dobrý, odpovědi poukazují na věci, na které poukazuji i já ‚ d, kromě jedné věci: ‚ Neexistují žádné testovací případy.

Odpověď

Díky za sdílení kódu.

S tímto kódem mě trochu štve, že nepoužívám rozhraní, abstraktní třídy ani dědičnost,

Provádění OOP znamená, že dodržujete určité zásady, které jsou (mimo jiné):

  • skrývání / zapouzdření informací
  • jednotná odpovědnost
  • oddělení obav
  • KISS (udržujte to jednoduché (a) hloupé.)
  • DRY (neopakujte se.)
  • „Tell! Don“ t ask. “
  • Law of demeter („ Don “t talk to strangers!“)

Rozhraní, abs třídy traktu nebo dědičnost podporují principy klobouku a měly by být použity podle potřeby. Nedefinují „OOP“.

IMHO hlavním důvodem, proč váš přístup selže, je to, že váš „Model“ je pole primitivního typu char. To v konečném důsledku vede k procedurálnímu přístupu k logice hry.

Myslel bych na takové rozhraní:

interface GameField{ char getIcon(); Result shootAt(); } 

where Result by bylo enum:

 enum Result{ NO_HIT, PARTIAL_HIT, DESTROYED } 

A měl bych různé implementace rozhraní:

public class BorderField implements GameField{ private final char borderName; public BorderField(char borderName){ this.borderName = borderName; } @Override public char getIcon(){ return borderName; } @Override public Result shootAt(){ return Result.NO_HIT; } } 

public class WaterField implements GameField{ private boolean isThisFieldHit = false; @Override public char getIcon(){ return isThisFieldHit?"M": " "; } @Override public Result shootAt(){ return Result.NO_HIT; } } 

public class ShipField implements GameField{ private final Ship ship; private boolean isThisFieldHit = false; public ShipField(Ship ship){ this.ship = ship; } @Override public char getIcon(){ Result shipState = ship.getState(); switch(shipState){ case NO_HIT: return " "; case PARTIAL_HIT: return isThisFieldHit?"O":" "; case DESTROYED: return "#"; } @Override public Result shootAt(){ ship.hit(); return ship.getState(); } } 

To by mělo stačit, doufám, že získejte nápad …


Formální problémy

Pojmenování

Hledání dobrých jmen je nejtěžší částí programování. Vždy si tedy udělejte čas a přemýšlejte o svých identifikátorových jménech.

Na druhé straně se budete řídit konvencemi pojmenování Java.

Měli byste ale mít názvy metod začínat slovesem v jeho present tense.Eg: shipWasHit() by měl být pojmenován hit().
Nebo distanceBetweenPoints() by měl být be calculateDistanceBetween(). Zde parametry odhalují, že vzdálenost je mezi body, takže to není nutné uvádět v názvu metody.

Názvy proměnných uveďte podrobně. namísto

 double x1 = from.getX(); double y1 = from.getY(); double x2 = to.getX(); double y2 = to.getY(); 

by tyto proměnné měly být spíše pojmenovaný takto:

 double startPointX = from.getX(); double startPointY = from.getY(); double endPointX = to.getX(); double endPointY = to.getY(); 

Vezměte svá jména z problémové domény, nikoli z technického řešení. např .: SHIP_ICON by měl být SHIP, pouze pokud ve třídě Ship máte jinou konstantu .

Komentáře

Komentáře by měly vysvětlovat proč je kód takový, jaký je . Odeberte všechny ostatní komentáře.

Komentáře by se měly používat pouze na rozhraní nebo abstraktních metodách, pokud obsahují smlouvu , kterou musí implementátor splnit.

Třída konstant

Spojte věci, které k sobě patří. Definujte konstanty ve třídě, která je používá.

Komentáře

  • Všechny platné body a opravdu užitečné. Děkuji!

Odpověď

Existuje již několik dobrých odpovědí, ale myslel jsem si, že přidám některé věci, které stály když jsem si prohlédl kód.

V tuto chvíli je jediným zdrojem vstupu uživatelský vstup ze skeneru. To by docela ztěžovalo, kdybyste chtěli přidat do počítače nějaký druh počítačových oponentů. hrajte proti.

Vypadá to, že ve třídě Board je nějaký kód, který by se ve třídě Player mohl lépe hodit.

Konkrétně obsah metody placeShipsOnBoard ().

Tato metoda přebírá vstup od uživatele a vytváří pozici. Zkusme to restrukturalizovat tak, aby hráč (člověk nebo počítač) mohl vytvořit pozici.

Pojďme vytvořit rozhraní

public interface Player { Position placeNextShip(); void fireAt(Position target); } 

Již máme implementace od člověka,

public class HumanPlayer implements Player { // variables @Override public Position placeNextShip(){ // uses Scanner instance to create a Position } @Override public void fireAt(Position target){ // code from your attack method } } 

A co takhle základní počítačový hráč

public class DumbComputer implements Player { @Override public Position placeNextShip(){ // keep choosing random locations } @Override public void fireAt(Position target){ // keep firing at random positions } } 

Potom v hlavní třídě desky naprogramujeme na rozhraní přehrávače

while(!isShipPlacementLegal){ for(Player player : players){ // some list of players in the game // either scanner input or randomly generated Position Position shipPlacement = player.placeNextShip(); boolean validPosition = validatePos(shipPlacement); if(validPostion){ // good to go! Place ship and continue to next player } else { // prompt again, whatever else you need to do here. } } } 

Pokud se celý kód týká spíše přehrávače (nyní rozhraní) než konkrétní implementace, můžeme snadno přidat nové typy počítačových přehrávačů . např. nový CheatingComputer (), nový HardComputer (), nový MediumComputer (). Každý z nich by jen určil, kde bude pálit dál a kam umístit další loď jiným způsobem.

A pokud bychom to měli, mohli bychom vytvořit hru se 2 počítačovými hráči a nechat ji hrát sama! Vzrušující právo: D

Další související věc, kterou bychom mohli změnit, je, že ve vašem konstruktoru pro hru budete mít vždy 2 lidské hráče. Mohli bychom to přimět k převzetí seznamu hráčů, takže ve hře můžete mít libovolný počet hráčů. Jen proto, že skutečná bitevní loď je omezena na 2 hráče, neznamená to, že vaše musí být.

Mohli bychom umožnit nastavitelnou velikost mřížky a libovolné číslo hráče. Najednou máme mřížku 100×100 s 8 hráč zdarma pro všechny!

(libovolné množství lze ovládat počítačem).

Vaše lodě jsou také inicializovány ve statickém bloku ve vaší palubní třídě. Máte všechny skutečné lodě od bitevní lodi. Ale opět zde povolte větší flexibilitu. Co kdyby se vaše loď skládala ze seznamu bodů? Mohli byste mít lodě ve tvaru písmene S, nemuseli by se omezovat na vodorovnou nebo svislou orientaci. (Může to být nahoře, ale myslím, že je to skvělá věc na přemýšlení!)

Dokončím pár drobností, které mi připadaly vtipné

throw new ArrayIndexOutOfBoundsException(); 

ze třídy Pozice. Zdá se to jako nevhodná výjimka, kterou sem hodit. Ve třídě Position není žádné pole, takže to musí odkazovat na pole ve třídě Board. Myslím, že vhodnějším typem výjimky by byla IllegalArgumentException, ArrayIndexOutofBoundsException je implementační detail (jiné třídy!). Měli byste nezapomeňte také uvést příslušnou chybovou zprávu spolu s vyvoláním výjimky. např. „hodnota musí být v rozsahu x a y“

Řádek

boolean isShipHit = (ship != null) ? true : false; 

lze jednoduše nahradit

boolean isShipHit = ship != null; 

Ternární operátor zde není potřeba.

Použití targetHistory ve třídě Player while (targetHistory.get (point)! = null)

Zde používáte mapu pouze za účelem zjištění, zda je v ní prvek. To je přesně to, čím je sada pro!

targetHistory = new HashSet<>(); while(targetHistory.contains(point)){ // re-prompt } 

Komentáře

  • Moc děkuji za pochopení! Všechny tyto odpovědi doplňují každý ostatní opravdu dobře! Začnu pracovat ‚ na verzi 2.0 s ohledem na toto vše.
  • Žádný problém Jsem ‚ rád, že vám to připadá užitečné! Hodně štěstí v 2.0!

Odpověď

Co to štve trochu s tímto kódem nepoužívám rozhraní, abstraktní třídy ani dědičnost, ale zde pro ně nemohu najít dobrý případ použití.

Na vaší hře není nic špatného. Herní koncept je tak jednoduchý, že žádné z nich nepotřebujete. Problém s malými hračkovými programy spočívá v tom, že obvykle nemusíte používat velká designová řešení. Musíte se jen ujistit, že dodržujete obvyklé SOLID designové principy .


Teď, když jsme to vyjasnili, pojďme podívejte se na některé podrobnosti svého kódu, které by měly být vylepšeny.

První je docela zřejmý. Nepište komentáře kvůli psaní komentářů. Někteří učitelé vás rádi nutí, abyste ke všemu napsali komentář javadoc. Vlastně jsem úplně proti tomu, s výjimkou psaní nějakého balíčku obslužných programů, který musí používat všichni ostatní. Normálně mluvící kód by měl být samodokumentující. A váš kód v tom dělá opravdu dobrou práci. Takže stačí odstranit komentář, v podstatě opakování dobře zvoleného názvu proměnné / funkce / …

Například:

/** * Is point between boolean. * * @param point the point * @param position the position * @return the boolean */ public static boolean isPointBetween(Point point, Position position) { 

Jakou hodnotu přidává tento komentář do funkce?Myslím, že je to také jediná metoda, kterou bych změnil, aby byla čitelnější. Protože není zřejmé, že pozice je tvořena from a to bodem, u kterého zkontrolujeme, zda náš point leží mezi nimi.

Co když by metoda měla tento podpis:

public static boolean containsPoint(Position position, Point point) { 

To by nebylo dávají trochu větší smysl?

Měl bych sem dodat, že nejsem obecně proti komentářům. Ale komentář by měl vysvětlit, PROČ se něco dělá tímto způsobem. Ne jak je to implementováno. Kdybych chtěl vím, jak je implementováno, jen bych se podíval na kód.


Dalším bodem je použití této třídy Util … Na rozdíl od některých puristů nemám nic proti konceptu tříd Util . Třídy nástrojů mohou být užitečné pro sestavení podobných funkcí. Například java.lang.Math, který seskupuje všechny obvyklé aritmetické operace do jedné třídy.

Věc, která mě trápí s vaší třídou Util je to, že pro její existenci opravdu není dobrý důvod. 2 funkce, které tam máte, se používají pouze ve třídě Board. Mohli tedy „být stejně dobře private static pomocnými funkcemi uvnitř této třídy.

Ve skutečnosti si můžeme po změně podpisu na to, co děláme, dokonce udělat trochu lépe Navrhl jsem dříve. Co kdybychom místo třídy Position div vložili containsPoint(Position position, Point point) { do třídy Position > jako parametr metody? Pak ji můžeme použít takto:

Position shipPosition = //some ship"s position if(shipPosition.contains(targetPoint)) { //handle ship hit } 

Hodí se tam opravdu dobře, že?


Když už mluvíme o třídě Positions. Při prohlížení vašeho kódu jsem měl z toho divný pocit. Zpočátku jsem si myslel, že nemáte something[][] představující tabuli. Myslel jsem, že jste reprezentovali vše jako Body v celé kódové základně. To by mohlo fungovat, ale tisk desky by byl nepříjemný. A pak jsem si všiml, že máte char[][] uvnitř vaší Board třídy. Ale pak by nedávalo smysl dávat lodě dovnitř dovnitř tu mřížku, aniž bychom měli střední třídu Pozice?

Také jsem si všiml další nebezpečné věci na placeShipsOnBoard(). Měli byste skutečně důvěřovat svému uživateli, že zadáte 2 platné souřadnice? Co když se uživatel pokusí být zábavný a zadá hodnoty od = (1,1) do = (2,2)? Měli bychom to dovolit? Nebo co když chcete, aby vložil loď o délce 5 a on zadá od = (1,1) do = (1,1) v podstatě zmenšení lodi na jeden čtverec (že musíte zasáhnout 5krát! Protože loď má 5 životů). Neměli bychom mu zabránit v tom, aby takto podváděl?

Pojďme se podívat na alternativní způsob řešení umístění lodí. Nejprve nechejte uživatele, aby si vybral, zda chce loď umístit vodorovně nebo svisle. Pak ho nechte zadat horní / levou souřadnici lodi. Zbývající body si vypočítáme sami.

Zde by mohla vypadat skutečná implementace metody:

private Scanner scanner = new Scanner(System.in); private void placeShipsOnBoard() { System.out.printf("%nAlright - Time to place out your ships%n%n"); for(Ship ship : ships) { //awesome for-each loop is better here boolean horizontal = askValidShipDirection(); Point startingPoint = askValidStartingPoint(ship, horizontal); placeValidShip(ship, startingPoint, boolean horizontal); } } private boolean askValidShipDirection() { do { System.out.println("Do you want to place the ship horizontally (H) or vertically (V)?"); String direction = Scanner.nextLine().trim(); while ( !"H".equals(direction) && !"V".equals(direction); return "H".equals(direction); //note here: use "constant".equals(variable) so nullpointer is impossible. //probably not needed, but it"s best practice in general. } private Point askValidStartingPoint(Ship ship, boolean horizontal) { do { //note: do-while more useful here System.out.printf("%nEnter position of %s (length %d): ", ship.getName(), ship.getSize()); Point from = new Point(scanner.nextInt(), scanner.nextInt()); while(!isValidStartingPoint(from, ship.getLength(), horizontal)); return from; } private boolean isValidStartingPoint(Point from, int length, boolean horizontal) { int xDiff = 0; int yDiff = 0; if(horizontal) { xDiff = 1; } else { yDiff = 1; } for(int i = 0; i < lenth; i++) { if(!isInsideBoard(i,from.getY()) { return false; } if(!Constants.BOARD_ICON.equals(board[from.getY()+i*yDiff][from.getX()+i*xDiff])){ return false; } } return true; } private boolean isInsideBoard(int x, int y){ return x <= Constants.BOARD_SIZE && x >= 0 && y <= Constants.BOARD_SIZE && y >= 0 && x <= Constants.BOARD_SIZE && x >= 0 && y <= Constants.BOARD_SIZE && y >= 0; } private void placeValidShip(Ship ship, Point startingPoint, boolean horizontal) { int xDiff = 0; int yDiff = 0; if(horizontal) { xDiff = 1; } else { yDiff = 1; } for(int i = 0; i < ship.getLenth() ; i++) { board[ship.getY() + i*yDiff][ship.getX()+i*xDiff] = Constants.SHIP_ICON; } } 

Teď už jen umístěte lodě přímo na palubu, můžeme odstranit třídu Position a všechny její odkazy. To znamená, že už nevíme, zda se loď potopila nebo ne.

Během toho jsem si všiml, že @Timothy Truckle již také zveřejnil odpověď. Opravdu se mi líbí jeho řešení použití vyhrazených Field s namísto char „s k zastupování rady.

tak naše metoda lodi se změní na:

private void placeValidShip(Ship ship, Point startingPoint, boolean horizontal) { int xDiff = 0; int yDiff = 0; if(horizontal) { xDiff = 1; } else { yDiff = 1; } for(int i = 0; i < ship.getLenth() ; i++) { board[ship.getY() + i*yDiff][ship.getX()+i*xDiff] = new ShipField(ship); } } 

Tímto způsobem můžeme zkontrolovat, zda je loď zničena úplně nebo jen částečně zasáhnout při útoku na Field.

Nyní, místo pokračování této odpovědi, vám doporučuji přečíst si také @Timothy a podívat se na dobré stránky obou našich odpovědí. Na první pohled buď souhlasíme, nebo se jednoduše navzájem doplňujeme. Měli byste tedy mít nějaké slušné tipy, jak vylepšit svůj kód 🙂

Komentáře

  • Moc děkuji za podrobnou kontrolu! Všichni dáváte solidní body. Jelikož všechny odpovědi byly tak dobré, dám ‚ l zaškrtnutí osobě, která odpověděla první.
  • ahem “ Ve vaší hře není nic špatného. Herní koncept je tak jednoduchý, že nemusíte id = „b83c115eca“>

žádné z nich nepotřebuji. “ – musím nesouhlasit: ‚ je lepší cvičit Nejprve cokoli v jednoduchém režimu. Pokud ‚ nelze použít koncepty a vzory na něco jednoduchého, určitě ‚ t je aplikovat na něco složitějšího.

  • Část o koncesích je obzvláště důležitá. Nadbytečné komentáře jsou úplnou ztrátou času a energie pro udržovat a ověřovat. Komentujte pouze v případě, že nemůžete kód a funkce vysvětlit.
  • Odpověď

    Další odpovědi poukázaly téměř na všechno, takže přidám jen jednu věc. Vypadá to pro mě, že používáte výjimky, abyste nějak prováděli řízení toku. Výjimky nejsou mechanismy řízení toku .

    public Position(Point from, Point to) { if (from.getX() > Constants.BOARD_SIZE || from.getX() < 0 || from.getY() > Constants.BOARD_SIZE || from.getY() < 0 || to.getX() > Constants.BOARD_SIZE || to.getX() < 0 || to.getY() > Constants.BOARD_SIZE || to.getY() < 0) { throw new ArrayIndexOutOfBoundsException(); } this.from = from; this.to = to; } 

    A pak

    } catch (IndexOutOfBoundsException e) { System.out.println("Invalid coordinates - Outside board"); } 

    Před pokusem o vytvoření Position si myslím, že byste měli ověřit, zda jsou dané souřadnice uvnitř desky. Toto je vstup uživatele a je naprosto rozumné to udělat. Tento ship.getSize() != Utils.distanceBetweenPoints(from, to) již ověřujete. Dokonce byste to dělali i v samotném objektu Board, místo abyste měli kontrolu polohy Constants.BOARD_SIZE.

    Napsat komentář

    Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *