Javaで戦艦ゲームを作成するための問題ステートメントが表示されました。
私の作業コード(Spring Boot + Web)は、問題の説明とともにここに配置されています。 https://github.com/ankidaemon/BattleShip
この質問は主にデザインに焦点を当てています。理解するのを手伝ってください、方法分離して適切なデザインパターンを適用できますか。
StartGame.java-コントローラーから呼び出されます
@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; } }
サンプル入力
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
ルール
1.Player1が最初に起動します。各プレイヤーは(ヒット==成功)まで別のチャンスを得ます。
2.戦艦は水平に配置されます。
3。タイプQの船は、破壊されるために2発のミサイルをヒットする必要があります。
4。タイプPの船は、破壊されるためにミサイルを1発発射する必要があります。
入力
入力の最初の行には、幅と高さがスペースで区切られた戦闘エリアの寸法が含まれています。
2行目には、各プレイヤーが所有する戦艦の数(B)が表示されます。
次に、次の戦艦タイプでは、Player-1、次にPlayer-2の寸法(幅と高さ)&の位置(Y座標とX座標)は次のようになります。スペースで区切られて与えられます。
そして、次の行で、ミサイルのターゲット位置座標(YとX)のPlayer-1のシーケンス(スペースで区切られている)が与えられ、次にPlayer-2のシーケンスが与えられます。
制約:
1 < =戦闘エリアの幅(M)< = 9
A < =戦闘エリアの高さ(N )< = Z
1 < =戦艦の数< = M * N
船の種類= {P、 Q}
1 < =戦艦の幅< = M
A < =戦艦の高さ< = N
1 < =船のX座標< = M
A < =船のY座標< = N
コメント
- ゲームのルールとコメントを追加できます少なくともコードの最も重要な部分では。コードのこれらの重要な部分をサブルーチンに移動して、コードをはるかに読みやすくすることもできます。ただし、非常に多くの関数を呼び出すとコードが遅くなりますが、少なくとも他の人にとってははるかに読みやすくなるため、後で更新をより適切に維持できます。 。元のコードと、ゲームルールを伴うよりモジュール化された/コメント付きのコードを追加して、それらのルールに基づいてコメントを付けることができます。
- @ alt.126にルールと入力が追加されました。 StartGameはファイルから入力を読み取り、検証を行い、各プレーヤーのBattleAreaとBattleShipsを作成します。 BattleShipsはPOJOです。 BattleAreaには、ルールに基づいて船とfileMissilesを配置するメソッドがあります。 Thnx
回答
わかりました:
あなたの
は役に立ちません。名前をより一致する名前に変更してください。たとえば、BattleShipGame
やコントローラーから代わりにゲームを開始します
BattleShipGame game = new BattleShipGame(); game.start();
init
-メソッドは非常に大きく、 init は実行しませんが、さらに多くのことを実行します…それでは、少し詳しく説明します。
- initはinitが成功したことを示すブール値(または
Result
)を返します。 - initは次のように見えます “sa delegate methode ロジックインデはごくわずかである必要があります。代わりに、ほとんどの作業をメソッドに入れると便利です
- 初期化するだけで、他のことは何もしません
- オブジェクト…
- ゲームロジックをメソッドから移動します
次のようになります
注:initメソッドははるかに短縮される可能性がありますが、init
が実際に何をすべきかを良い方法で指摘していると思いますdo …
上記のように、ゲームロジックをinitメソッドから移動し、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
は次のように開始されます:
public void start(){ init(); Result result = playGame(); ... //do whatever you want with your result - for example print it into the console }
BattleShipには、さらにいくつかの問題があります。一見見栄えのするクラスCoordinate
を使用するのは非常に良い考えだと思います。ただし、結果的に使用することはありません。int[]
の代わりにCoordinate
を使用して出荷した場合、どのようになるかを考えてください。コードも読みやすく、計算もはるかに簡単です。また、船種にchar
を使用せず、代わりに列挙型を使用してください。しかし、正直に言うと、実際に持っているのは長方形である「位置と幅と高さ」がないので、長方形を使用してください!
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(); } ... }
長方形の寸法(幅/高さ)とライフポイントの量は、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ははるかに簡単になりました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); } } }
上記のすべてのコードはコンパイルされていないため、いくつかのスペルミスがあり、多くのメソッドがまだ実装されていない可能性があります(Rectangle.contains()
など)。
概要
「現在の状況を見てみましょう。
- any コード!!!(
ShipType
に別のshiptypeを追加するだけです) - コードの複雑さを大幅に軽減しましたが、 「危険な計算がありません
- 懸念事項を分離しました。オブジェクトは、本来の機能を実行するようになりました。
- 別のプレーヤーのコードを簡単に変更できます(3プレーヤーゲーム)
- 今すぐコードをテストできます
コメント
- ここにはまだ多くの問題があることを知っています’すべてを処理する-この回答は、詳細に触れていない最上部のビューにありました
回答
分離するには、関数が作業を行うために相互に呼び出す必要がないことを確認する必要があります。可能であれば、他の関数を呼び出す必要があるのは、APIインターフェースのように呼び出すことを目的とした各サブシステムのドライバー関数だけです。
追加するためだけに多くのコードを移植する必要がある方法を考えてください。その関数が他の関数を呼び出す場合、または他の大きなサブシステムからの変数を使用する場合に役立つ関数。他に依存しない方法でその関数を実装するために追加の努力をすると、重複したコードのように見えても、別のプログラムに個別に移植できます。使用するすべてのコンパイラおよびプログラミング言語に存在するわけではない言語機能やライブラリ機能に依存させないでください。これにより、作成したコードを必要な環境に移植できるようになります。 sはデカップリングと呼ばれます。
ご覧のとおり、デカップリングはコンパイラ、言語、システム環境、関数、およびサブシステムレベルで適用できます。コードを複製し、依存関係のないスタンドアロンのルーチンを持つように書き直す必要がある場合があります。また、より広く標準化された機能を使用してコードの移植性を高めることを意味する場合もあります。また、システム/言語/コンパイラに関係なく、不足している機能をすべてのプログラミング/システム環境に実装または移植する作業が必要になる場合もあります。を使用すると、常に同じ機能を利用できます。
デザインパターンについて
コードを高度に再利用可能にしたい場合また、それを数十年持続させたい場合は、CPUアセンブリプログラミングの低レベルのアプローチを使用できます。
常に同じタイプの方法で実行するタスクまたはマイクロタスクについて考えてください。パラメータを設定すると、常にまったく同じ方法で結果が返されます。
次に、このルーチンに非常に具体的な名前を付けます。これは、関数/サブルーチンとして実装されたオペコード、命令であり、他のネイティブCPU命令と同じように再利用できます。コードを設計するこの方法は、再利用性が高く、安定しています。何をするかを変更したい場合は、以前の有効な機能を破棄するのではなく、新しいオペコード関数を追加するだけです。
メインの設計アプローチとしてこれをプログラム全体に適用すると、コードをより厳密に構造化できます。わかりやすい。
コメント
- ありがとうございます、@ alt.126。これは一般化されたソリューション(+1)ですが、実際には、Javaデザインパターン/オブジェクトの削減/冗長コードの削除などにもっと期待していました。