不久前,我打开了这个话题:Tac脚趾级1:控制台PvP
由于考试,我不得不搁置那个小项目,但几天前我又开始着手做了。现在可以与AI对抗,也可以让两个AI互相对抗。选择可以在菜单中进行。
当然,我同时也学习过,我相信我对面向对象设计的理解已经改进了xD(大喊答题)头第一设计模式。这对我有很大帮助,尽管我只学到了几种模式。我很期待能学到更多的东西!。话虽如此,我清楚地意识到,我仍然缺乏很多。
在我要求进行代码评审之前:我必须在github上打开一个新的存储库,因为我想继续在Eclipse上工作,但是我无法导入旧的存储库。
我现在有19门课,所以我想这篇文章会很长。如果您想查看github上的类,下面是链接:https://github.com/kebiro/TicTacToe/tree/GameMenu/TicTacToe/src/game
总的来说,我相信我成功地解决了我设计中的一些部分,而另一些地方却不是很好。我特别不确定我的输入/输出类和我的GameMenu是否设计正确。Board还需要适当的重构。我一直在尽我所能记住未来的想法,但我不确定我是否成功地保持了我的设计的适当可扩展性。
总之,下面是代码(一些类被排除在外,比如Main.java):
package game;
public class Move {
private String move;
public Move(String move) {
if(move.length() != 1 || !"123456789".contains(move))
throw new IllegalMoveException("move needs to be a digit (1-9)");
this.move = move;
}
public int getMove() {
return Integer.parseInt(move);
}
@Override
public String toString() {
return move;
}
}package game;
public class Position {
private int row, col;
public Position(Move move) {
this.row = (move.getMove() - 1) / 3;
this.col = (move.getMove() - 1) % 3;
}
public int getRow() {
return row;
}
public int getCol() {
return col;
}
}package game;
public class Board {
private String[][] board;
public Board() {
init();
}
public String[][] getBoard() {
return board;
}
private void init() {
board = new String[3][3];
int count = 1;
for(int i=0; i < board.length; ++i) {
for(int j=0; j < board[i].length; ++j) {
// set the field positions, from 1 to 9
board[i][j] = "" + count++;
}
}
}
public boolean isFull() {
boolean isFull = true;
for(int i=0; i < board.length; ++i)
for(int j=0; j < board.length; ++j)
// still contains an unmarked field (indicated by a number)
if("123456789".contains(board[i][j]))
isFull = false;
return isFull;
}
// ???
public void markField(Move move, Sign sign) {
if(!fieldIsMarked(move))
setValueAtPosition(move, sign + "");
}
// ???
public boolean fieldIsMarked(Move move) {
return !getValueAtPosition(move).equals(move.toString());
}
// ???
private String getValueAtPosition(Move move) {
Position pos = new Position(move);
return board[pos.getRow()][pos.getCol()];
}
// ???
private void setValueAtPosition(Move move, String val) {
Position pos = new Position(move);
board[pos.getRow()][pos.getCol()] = val;
}
public boolean hasThreeInARow() {
boolean horizontal =
board[0][0].equals(board[0][1]) && board[0][1].equals(board[0][2])
|| ( board[1][0].equals(board[1][1]) && board[1][1].equals(board[1][2]) )
|| ( board[2][0].equals(board[2][1]) && board[2][1].equals(board[2][2]) );
boolean vertical =
board[0][0].equals(board[1][0]) && board[1][0].equals(board[2][0])
|| board[0][1].equals(board[1][1]) && board[1][1].equals(board[2][1])
|| board[0][2].equals(board[1][2]) && board[1][2].equals(board[2][2]);
boolean diagonal =
board[0][0].equals(board[1][1]) && board[1][1].equals(board[2][2])
|| board[0][2].equals(board[1][1]) && board[1][1].equals(board[2][0]);
return horizontal || vertical || diagonal;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
int formatSize = 0;
for(int i=0; i < board.length; ++i) {
for(int j=0; j < board[i].length; ++j) {
sb.append(board[i][j] + " | ");
}
// #_|_#_|_#_|_ - delete the last two blanks + |
sb.delete(sb.length()-3, sb.length());
// determines how many "-" to draw horizontally to make it look more aligned
formatSize = (formatSize == 0) ? sb.length() : formatSize;
if(i < board.length-1) {
sb.append(System.lineSeparator());
// http://stackoverflow.com/questions/1235179/simple-way-to-repeat-a-string-in-java
sb.append(new String(new char[formatSize]).replace("\0", "-"));
sb.append(System.lineSeparator());
}
}
return sb.toString();
}
}我不确定我是否正确地使用了Position。我用// ???标记这些地方。我还注意到,在编写这篇文章时,markField(Move, Sign)可能可以从Board中提取出来。
hasThreeInARow()已经从Game移到了Board。除此之外,Board和以前几乎是一样的。
package game;
import java.util.*;
import game.io.*;
public class Game {
private Board board;
private Player player1, player2;
private Player activePlayer;
private boolean hasThreeInARow;
private ManagerFactory managerFactory;
private Output outputManager;
public Game(ManagerFactory managerFactory) {
this.managerFactory = managerFactory;
this.outputManager = managerFactory.createOutputManager();
GameMenu menuManager = managerFactory.createMenuManager();
menuManager.menu();
board = menuManager.createBoard();
player1 = menuManager.createPlayer1();
player2 = menuManager.createPlayer2();
}
/**
* play a Tic-Tac-Toe game
*/
public void playGame() {
activePlayer = playerToStart();
while(!board.isFull()) {
outputManager.callForTurn(activePlayer);
outputManager.showBoard(board);
Move move = activePlayer.makeMove();
board.markField(move, activePlayer.getSign());
hasThreeInARow = board.hasThreeInARow();
if(hasThreeInARow)
break;
changeTurns();
}
System.out.println();
outputManager.showBoard(board);
if(hasThreeInARow)
outputManager.declareWinner(activePlayer);
else
outputManager.println("draw");
outputManager.println("===============================================");
new Game(managerFactory).playGame();
}
// what follows are auxiliary methods for playGame(); to make playGame() more readable
private Player playerToStart() {
return (new Random().nextInt(2) == 0) ? player1 : player2;
}
private void changeTurns() {
activePlayer = (activePlayer == player1) ? player2 : player1;
}
}我从Game中提取了很多方法。我相信现在它已经变得相当有凝聚力了。我不确定的是我构建Game的方式是否正确。(但问题来源可能是GameMenu,如果有问题.)
此外,我也不确定playGame()循环是否会像现在这样对GUI工作。(循环后的Sysout很麻烦,我在每次迭代中都调用showBoard(Board),这是控制台所必需的)
package game;
public class Player {
private String name;
private Sign sign;
private MoveStrategy moveStrategy;
public Player(String name, Sign sign, MoveStrategy moveStrategy) {
this.name = name;
this.sign = sign;
this.moveStrategy = moveStrategy;
}
public Move makeMove() {
return moveStrategy.makeMove();
}
public Sign getSign() {
return sign;
}
@Override
public String toString() {
return name;
}
}package game;
public interface MoveStrategy {
public Move makeMove();
}package game;
import game.io.*;
public class HumanMoveStrategy implements MoveStrategy {
private Board board;
private Output outputManager;
private Input inputManager;
public HumanMoveStrategy(Board board, ManagerFactory managerFactory) {
this.board = board;
this.outputManager = managerFactory.createOutputManager();
this.inputManager = managerFactory.createInputManager();
}
@Override
public Move makeMove() {
outputManager.print("Your move: ");
Move move = null;
do {
try {
move = new Move(inputManager.read());
while(board.fieldIsMarked(move)) {
outputManager.print("That move has alreary been made, try again: ");
move = new Move(inputManager.read());
}
} catch(IllegalMoveException ime) {
outputManager.print(ime.getMessage() + ", try again: ");
move = null;
}
} while(move == null);
return move;
}
}package game;
import game.io.*;
import java.util.Random;
public class AIrandomMoveStrategy implements MoveStrategy {
private Board board;
private Output outputManager;
public AIrandomMoveStrategy(Board board, ManagerFactory managerFactory) {
this.board = board;
this.outputManager = managerFactory.createOutputManager();
}
@Override
public Move makeMove() {
Random rand = new Random();
int movePos = Math.floorMod(rand.nextInt(), 9) + 1;
Move move = new Move(movePos + "");
while(board.fieldIsMarked(move)) {
movePos = Math.floorMod(rand.nextInt(), 9) + 1;
move = new Move(movePos + "");
}
outputManager.println("Move made: " + movePos);
return move;
}
}我已经将makeMove()方法从Game中移出并放入Player中。我在考虑如何实现人工智能(计划在下一阶段进行)。我认为人类玩家和人工智能之间唯一的区别就是移动的方式。
这里我采用了战略模式,我认为这是正确的决定。因此,Player委托给MoveStrategy。最初,我计划在第二阶段开始之前等待,直到第一阶段完全完成。虽然这项战略使得人工智能的实现相当简单,所以我做到了。(不过,我会把Minimax推迟一段时间。)
package game.io;
public interface Input {
public String read();
public String readLine();
}package game.io;
import java.util.*;
public class InputConsole implements Input {
private Scanner scanner = new Scanner(System.in);
@Override
public String read() {
return scanner.next();
}
@SuppressWarnings("resource")
@Override
public String readLine() {
Scanner scanner = new Scanner(System.in);
return scanner.nextLine();
}
}package game.io;
import game.Board;
import game.Player;
public interface Output {
public void showBoard(Board board);
public void callForTurn(Player activePlayer);
public void declareWinner(Player winner);
public void print(String s);
public void println(String s);
}package game.io;
import game.Board;
import game.Player;
public class OutputConsole implements Output {
@Override
public void showBoard(Board board) {
System.out.println("=========");
System.out.println(board);
System.out.println("=========");
}
@Override
public void callForTurn(Player activePlayer) {
System.out.printf("%n%s's turn (%s)%n", activePlayer, activePlayer.getSign());
}
@Override
public void declareWinner(Player winner) {
System.out.printf("%s (%s) won%n", winner, winner.getSign());
}
@Override
public void print(String s) {
System.out.print(s);
}
@Override
public void println(String s) {
System.out.println(s);
}
}如前所述,我在这些课程上遇到了麻烦。这是我在上一个问题中提出的,我不知道我成功地实现了这个想法。然而,我注意到,这是战略模式。至少它和它非常相似。为了使输入/输出与GUI一起工作,我只需提供与GUI一起工作的具体实现。
package game;
import game.io.*;
public abstract class GameMenu {
protected Player player1, player2;
protected ManagerFactory managerFactory;
protected Output outputManager;
protected Input inputManager;
protected Board board = new Board();
public abstract void menu();
public abstract Player createPlayer1();
public abstract Player createPlayer2();
public Board createBoard() {
return board;
}
}package game;
public class ConsoleGameMenu extends GameMenu {
public ConsoleGameMenu() {
this.managerFactory = new ConsoleManagerFactory();
this.outputManager = managerFactory.createOutputManager();
this.inputManager = managerFactory.createInputManager();
}
@Override
public void menu() {
outputManager.println("Welcome to Tic Tac Toe");
outputManager.println("Please choose the gmae you would like to play: ");
outputManager.println("\t(1) Singleplayer");
outputManager.println("\t(2) Multiplayer");
outputManager.println("\t(3) watch mode (Computer vs. Computer)");
outputManager.println("-----------------------------------------------");
outputManager.println("For help, press 'h'");
outputManager.println("To exit, press 'x'");
outputManager.println("===============================================");
String input = inputManager.read();
while(!input.matches("[1, 2, 3, h, x]")) {
outputManager.println("Invalid input, please try again");
input = inputManager.read();
}
switch(input) {
case "1": initSingleplayer(); break;
case "2": initMultiplayer(); break;
case "3": initWatchMode(); break;
case "h": showHelp(); break;
case "x": exit(); break;
default: // TODO
outputManager.println("===============================================");
outputManager.println("==================Tic Tac Toe==================");
outputManager.println("===============================================");
}
}
private void initSingleplayer() {
outputManager.println("At what difficulty would you like to play?");
outputManager.println("\t(1) easy");
outputManager.println("\t(2) hard - not implemented yet");
outputManager.println("===============================================");
String input = inputManager.read();
while(!input.matches("[1, 2]")) {
outputManager.println("Invalid input, please try again");
input = inputManager.read();
}
outputManager.print("Player 1 enter your name: ");
player1 = new Player(inputManager.readLine(), Sign.X, new HumanMoveStrategy(board, managerFactory));
switch(input) {
case "1": player2 = new Player("Random AI", Sign.O, new AIrandomMoveStrategy(board, managerFactory)); break;
case "2": break;
default: // TODO
}
}
private void initMultiplayer() {
outputManager.print("Player 1 enter your name: ");
player1 = new Player(inputManager.readLine(), Sign.X, new HumanMoveStrategy(board, managerFactory));
outputManager.print("Player 2 enter your name: ");
player2 = new Player(inputManager.readLine(), Sign.O, new HumanMoveStrategy(board, managerFactory));
}
private void initWatchMode() {
player1 = new Player("AI#1", Sign.X, new AIrandomMoveStrategy(board, managerFactory));
player2 = new Player("AI#2", Sign.O, new AIrandomMoveStrategy(board, managerFactory));
}
private void showHelp() {
outputManager.println("This is the help menu");
outputManager.println("I hope this was helpful");
outputManager.print("To return back to the main menu, press 'r': ");
String input = inputManager.read();
while(!input.matches("[r, R]")) {
input = inputManager.read();
}
outputManager.println("===============================================");
menu();
}
private void exit() {
outputManager.print("Are you sure that you want to end the game (y/n)? ");
String input = inputManager.read();
while(!input.matches("[y, Y, n, N]")) {
outputManager.println("Invalid input. Please answer with yes (y/Y) or no (n/N)");
input = inputManager.read();
}
if(input.equalsIgnoreCase("y")) {
outputManager.println("Good bye!");
System.exit(0);
}
else {
outputManager.println("===============================================");
menu();
}
}
// TODO: PlayerNotInitializedException
@Override
public Player createPlayer1() {
return player1;
}
@Override
public Player createPlayer2() {
return player2;
}
}这也给我带来了一些麻烦。最后,GameMenu变成了一个会说话的抽象工厂,因此它有两种责任(1:它扮演游戏菜单的角色,2:它返回两个Player和Board的实例)。但这可能是合理的,因为这些类的实例化取决于菜单调用。ConsoleGameMenu还没有完全完成,但这是基本结构。
package game;
import game.io.*;
public interface ManagerFactory {
public Output createOutputManager();
public Input createInputManager();
public GameMenu createMenuManager();
}package game;
import game.io.*;
public class ConsoleManagerFactory implements ManagerFactory {
@Override
public Output createOutputManager() {
return new OutputConsole();
}
@Override
public Input createInputManager() {
return new InputConsole();
}
@Override
public GameMenu createMenuManager() {
return new ConsoleGameMenu();
}
}最后,我使用抽象工厂模式来强制执行Game总是用相应的类构造的,也就是说,强制执行类似new Game(new InputConsole(), new OutputGUI(), new GameMenuFile())这样的事情不会发生。(我确信,根据上下文的不同,在控制台上输入和在GUI上输出并不一定是错误的,但我不想这样做。)
在我讲完之前,我还有两个问题:
Javadoc附加到它们上?我对此表示感谢,对此我感到非常抱歉。=/
发布于 2016-07-15 14:22:57
这将是一些挑剔/快速解决的小事情。
package game;是..。非常规的名字。包通常是以拥有的域的反向顺序命名的。因此,我(实际上不拥有该域)使用de.vogel612作为我为自己编写的所有包的前缀。然后我使用应用程序的名称,然后包层次结构开始。这个约定是为了防止包裹的名称冲突。Move.getMove()重复工作。将一个字符串变成一个整数比将一个整数变成一个字符串更困难。您应该考虑使用以下代码。公共类移动{私有最终int迁移;公共移动(字符串移动){ this.move =Integer.parseInt(移动);} // .}这还包括使私有字段成为最终结果。您希望它永远不会更改,并在构造函数中初始化它。使其成为最终结果,编译器将验证该字段永不更改的断言。注意到,在这种情况下,这可能不是绝对必要的,但这是一个很好的习惯。Position。有趣的是Board。Board的时候。我个人不喜欢init()方法,因为它们不算初始化块。这些块有一些次要的特殊规则,特别是在处理最终字段时:D. 我强烈建议您内联init方法,因为它基本上是.适得其反。Position。我用//??标记了这些地方。”是啊。你实际上应该通过那里的位置。那些方法太多了,不知道它们的名字。Player还将受益于final实例字段。我刚刚快速浏览了代码的其余部分,但在我看来,它看起来很好,而且分离得很好。里面可能有一些隐藏的细节,但不幸的是,我没有时间进行更深入的审查。
https://codereview.stackexchange.com/questions/121906
复制相似问题