Эта игра довольно распространена для начинающих проектов. Я написал версию этого раньше всего в Main — поэтому здесь я ставлю перед собой задачу воссоздать это в более объектно-ориентированном стиле.
Я хотел использовать более объектно-ориентированный подход, поэтому я буду благодарен за любые отзывы о том, где я могу добиться большего успеха или делаю некоторые ошибки. У меня также есть некоторая личная путаница, если в некоторых частях я кодирую это процедурно. Я могу ошибаться, но я думаю, что этот код частично соответствует обоим стилям, и если да, может ли кто-нибудь помочь объяснить отличительные различия? В некоторых частях слишком много рекурсии? Как появляется читабельность? о чем ты думаешь?
Игра сгенерирует случайное секретное число и попросит пользователя ввести свои предположения в течение заданного количества попыток. Код ниже:
import java.util.InputMismatchException;
import java.util.Random;
import java.util.Scanner;
public class GuessGame {
private static final Scanner SC = new Scanner(System.in);
private static final Random R = new Random();
private static int secret = R.nextInt(100) + 1;
private static final int GIVEN_ATTEMPTS = 10;
private int attempts = 0;
public void playGame() {
promptGuess();
}
private int promptGuess() {
try {
printLine("Guess a number from 1-100");
int guess = SC.nextInt();
if (guess < 1) {
promptGuess();
} else {
return compare(guess);
}
} catch(InputMismatchException e) {
printLine("Only numbers please");
SC.nextLine();
promptGuess();
}
return 0;
}
private int compare(int guess) {
attempts++;
if (attempts == GIVEN_ATTEMPTS) {
return promptResult(4);
}
int difference = secret - guess;
if (difference > 0) {
return promptResult(1);
} else if (difference < 0) {
return promptResult(2);
} else {
return promptResult(3);
}
}
private int promptResult(int n) {
switch (n) {
case 1:
printLine("Guess was too low, try again");
promptGuess();
break;
case 2:
printLine("Guess was too high, try again");
promptGuess();
break;
case 3:
printLine("You got it! Nice guess! The number was " + secret);
printLine("In " + attempts + " attempts");
printLine("Game Over");
attempts = 0;
secret = R.nextInt(100) + 1;
playAgain();
case 4:
printLine("Too many guesses! " + GIVEN_ATTEMPTS + "/" + GIVEN_ATTEMPTS + " used.");
printLine("Secret number was: " + secret + " Game Over");
attempts = 0;
secret = R.nextInt(100) + 1;
playAgain();
}
return 0;
}
private void printLine(String s) {
System.out.println(s);
}
private void playAgain() {
printLine("Play again? (Enter Y for yes or N for no)");
String reply = SC.next();
if (reply.equalsIgnoreCase("Y")) {
playGame();
} else if (reply.equalsIgnoreCase("N")) {
printLine("Thanks for playing");
System.exit(0);
} else {
System.out.println("Please enter a valid answer");
playAgain();
}
}
}
public class Main {
public static void main(String[] args) {
GuessGame g = new GuessGame();
g.playGame();
}
}
1 ответ
Любопытно, что этот код очень похож на код в этом вопросе. Я не говорю, что у вас есть другая учетная запись, но вы случайно не из того же класса или что-то в этом роде?
Ваше форматирование кажется непоследовательным, используйте автоматическое форматирование кода (скорее всего, в вашей IDE он есть).
private static final Scanner SC = new Scanner(System.in);
Не сокращайте имена только потому, что можете, это затруднит понимание и поддержку кода.
Скорее всего, следует ограничить использование сканера (или потоков в целом). Потоки довольно легко ассоциируются с собственными ресурсами, которые для освобождения должны быть явно уничтожены.
private static final Scanner SC = new Scanner(System.in);
private static final Random R = new Random();
private static int secret = R.nextInt(100) + 1;
private static final int GIVEN_ATTEMPTS = 10;
private int attempts = 0;
Некоторые из вашего состояния static
, все ваши методы instance
. Скорее всего, вы хотите, чтобы все ваше состояние instance
.
if (guess < 1) {
promptGuess();
} else {
return compare(guess);
}
Здесь вы рекурсивно повторяете, поэтому нажатие Return без ввода чего-либо должно в какой-то момент привести к сбою вашей игры из-за переполнения стека.
if (attempts == GIVEN_ATTEMPTS) {
Проверка на большее качество будет безопаснее.
private int promptResult(int n) {
Это не запрашивает результат, а показывает, было ли предположение верным или нет, метод следует переименовать.
switch (n) {
case 1:
Вместо того, чтобы использовать здесь числа, вы можете использовать константы или перечисление. Таким образом, никому не нужно будет помнить об этом 2
означает «слишком высоко».
private void printLine(String s) {
System.out.println(s);
}
Хорошая идея, но вы получаете очень и очень мало, когда дело касается читабельности с помощью этого метода.
System.exit(0);
Следует иметь в виду, что System.exit
не «выйти из приложения», а «убить процесс JVM». При вызове этого даже не finally
блоки могут работать. В данном случае это не имеет значения, но нужно иметь в виду.
Ваша логика повсюду. Вместо того, чтобы связывать методы вместе, как вы, вам следует подумать о наличии основного метода, который обрабатывает поток и вызывает методы по мере необходимости. Таким образом, у вас будет логика вашей игры в одном месте, но «детали» будут скрыты в функциях, что позволит довольно легко не только понять, как работает игра, но и упростить изменение отдельных функции.
Что касается того, как сделать это более правильным, то здесь особо нечего делать, кроме очистки смешанного состояния. Начать можно с того, чтобы логика игры была отделена от логики вывода на терминал. Так что у вас будет Game
класс, и тот примет GameUi
интерфейс как параметр, например:
public interface GameUi {
public abstract int getGuess();
// ...
}
public class SimpleTerminalGameUi implements GameUi;
public class Game {
public Game(GameUi gameUi);
// Somewhere in your logic:
int guess = gameUi.getGuess();
}
new Game(new SimpleTerminalGameUi());
Это позволит вам отделить презентацию от внутреннего состояния игры.
В качестве дальнейшего упражнения проделайте то же самое с секретным провайдером.
Я могу ошибаться, но я думаю, что этот код частично соответствует обоим стилям, и если да, может ли кто-нибудь помочь объяснить отличительные различия?
Ваш код вообще не объектно-ориентирован, вы просто используете один объект в качестве контейнера. Но, честно говоря, не так уж много логики для начала раскладывать на разные объекты.