Игры Броненосец


Мне были даны постановка задачи на создание игры Морской бой на Java.

Мой рабочий код (Весна загрузки+веб) находится здесь вместе с описанием задачи. 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. Игрок1 будет стрелять первым. Каждый игрок получит еще один шанс до ( нажмите == успешный ).
2. Линкоры будут располагаться горизонтально.
3. Тип-Q корабля требует, чтобы ракеты 2 Нажмите, чтобы получить уничтожены.
4. Тип-П корабль требует ракеты 1 Нажмите, чтобы получить уничтожены.

Вход
Первая строка входных данных содержит размеры боевой зоны, имеющей ширину и высоту, разделенных пробелом.
Вторая линия будет иметь номер (Б) линкора каждого игрока.
Затем в следующей строке Тип линкора, размеры (ширина и высота) и (y координата и координата X) для игрока 1, и тогда для игрока-2 будут даны через пробел.
А затем в следующей строке-плеер-1 последовательности (через пробел) ракет целевые координаты (Y и X) и затем по последовательности для игрока-2.

Ограничения:
1 <= Ширина зоны битвы (м) <= 9
А <= Высота зоны битвы (Н) <= З
1 <= Количество линкоров <= М * Н
Тип корабля = {‘П’, ‘Щ’}
1 <= Ширина броненосец <= М
А <= Высота броненосец <= Н
1 <= X координат корабля <= М
А <= Y координата корабля <= Н



1566
5
задан 7 марта 2018 в 09:03 Источник Поделиться
Комментарии
2 ответа

ок, давайте поставим руки:

Класс имя StartGame не полезно, переименовать его в более подходящее имя, я думаю, как BattleShipGame и запустить игры, а не от контроллера

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

в init - метод до сих пор не большой и это не инит , но даже и больше вещей... так что давайте разорвать этот вниз немного:


  • init должен возвращать логическое значение (или Result), что свидетельствует о том, что init-инициализация прошла успешно.

  • инит выглядит это делегат метод , который означает, что там должно быть очень мало логики внутри - вместо это полезно, чтобы положить большинство работы в методы

  • просто инит вещи и не делать любые другие вещи

  • использовать Player объекты...

  • перенести игровую логику из метода

это может выглядеть так Затем

private Player playerOne;
private Player playerTwo;

public boolean init(){

playerOne = new Player("player1");
playerTwo = new Player("player2");
GameSetup setup = readFile(inputFile);

ArrayList<BattleShips> p1bs = setup.getFirstBattleShips();
ArrayList<BattleShips> p2bs = setup.getSecondBattleShips();
playerOne.setBattleShips(p1bs);
playerTwo.setBattleShips(p2bs);

playerOne.setMissiles(setup.getFirstMissileCoordinates());
playerTwo.setMissiles(setup.getSecondMissileCoordinates());

playerOne.setBoard(new BattleShipBoard(setup.getDimension());
playerTwo.setBoard(new BattleShipBoard(setup.getDimension());

playerOne.placeShips();
playerTwo.placeShips();

return true;
}

Примечание: init метод может быть shortenend гораздо больше, но я думаю, я в хорошем смысле, что init действительно надо делать...

как уже упоминалось выше, вы перешли на игровую логику из вашего 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
}

для вашего линкора есть еще проблема, которая может быть говорили. Я думаю, что это была очень хорошая идея, чтобы использовать класс Coordinate что хорошо выглядит на первый взгляд. Но вы не используете его consequencically. подумайте о том, как было бы, если бы вы использовали Coordinate для вас корабль вместо int[] что бы сделать ваш код также легче читать и математику бы гораздо легче. И не использовать char для вашего shiptype, использовать перечисления вместо этого. Но давайте будем честными, у вас нет позиции, а ширина и высота-что у вас действительно есть это прямоугольник - так что используйте прямоугольник!

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

...
}

размеры прямоугольника (ширина/высота) и объем lifepoints можно соответствует требованиям к 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() или другим).

резюме

но давайте посмотрим на то, что мы имеем сейчас:


  • вы можете изменить тип судна достаточно легко, без необходимости модифицировать любой код !!! (вы просто должны добавить еще один shiptype в ShipType )

  • вы упростили ваш код очень далеко, у вас нет опасных расчеты.

  • вы отделили проблемы, объекты теперь делать то, что они должны делать

  • вы можете легко изменить код для другого игрока (трех игроков)

  • вы можете проверить ваш код сейчас

2
ответ дан 9 марта 2018 в 08:03 Источник Поделиться

Отцеплять вы должны убедиться, что ваши функции не нужно звонить друг другу делать свою работу. Если это возможно, только те функции, которые должны вызывать другие функции, водитель каждой из подсистем, которые предназначены для вызова как интерфейс API.

Подумайте, как бы вы собираетесь портировать много кода только для добавления полезной функции, если эта функция вызывает другие функции и использует переменные из других крупных подсистем. Если вы сделали дополнительные усилия, чтобы реализовать эту функцию таким образом, что она не зависит ни от чего другого, даже если это выглядит как дублирование кода, Вы сможете в индивидуальном порядке перевести его на другую программу, и даже больше, если вы не сделаете это, зависит от особенностей языка или библиотеки функций, которые не присутствуют на каждой компилятор и язык программирования вы используете, чтобы сделать это возможным в порт любой код, который вы напишете в любой среде, необходимо, что называется развязкой.

Как видите, развязка может быть уже на компилятор, язык, системы окружающей среды, функции и подсистемы, уровни. Это может быть связано дублирования кода и переписывание иметь изолированного, зависимость-меньше рутины. Это может также означать, используя более широко-стандартизированные функции, чтобы сделать код более переносимым, и это может также нужно, что вы работаете на выполнении или переносе недостающих функций для всех ваших программ/система окружения себя так, что никакое дело система/язык/компилятор вы используете, вы всегда будете иметь такую же функциональность доступна.





О шаблонах проектирования.

Если вы хотите сделать ваш код очень многоразовые и если вы хотите, чтобы это длилось десятилетиями, можно использовать снижение уровня программирования сборка процессора.

Думать о задаче или microtask вы хотите выполнить таким образом, что всегда будет иметь тот же тип параметров, и которые всегда возвращают результат в тот же путь.

Затем дать ему очень конкретное название этой рутины. Это будет опкод, инструкции, реализованы как функции/подпрограммы, которые можно использовать как и любой другой родной команды процессора. Этот способ проектирования код многоразовый и стабильный. Если вы хотите изменения в Что делать, вы просто добавить новую функцию в коде вместо уничтожения предыдущей работоспособной функциональности.

Применение этого по всей программе в качестве основного подхода к проектированию может сделать код более строго структурированные еще легче выполнять.

1
ответ дан 7 марта 2018 в 03:03 Источник Поделиться