You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

389 lines
15 KiB

  1. /**
  2. * @file MinMaxPlayer.java
  3. *
  4. * @author
  5. * Anastasia Foti AEM:8959
  6. * <anastaskf@ece.auth.gr>
  7. *
  8. * @author
  9. * Christos Choutouridis AEM:8997
  10. * <cchoutou@ece.auth.gr>
  11. */
  12. package host.labyrinth;
  13. /**
  14. * @brief
  15. * This class represents the game's minimax player.
  16. */
  17. class MinMaxPlayer extends Player {
  18. /** @name Constructors */
  19. /** @{ */
  20. /**
  21. * Create a new player and put him at the row-column coordinates
  22. * @param name The name of the player
  23. * @param champion Flag to indicate if a player is a `champion`
  24. * @param board Reference to the board of the game
  25. * @param row The row coordinate of initial player position
  26. * @param column The column coordinate of initial player's position
  27. */
  28. public MinMaxPlayer(String name, boolean champion, Board board, int row, int column) throws Exception {
  29. super(name, champion, board, row, column);
  30. }
  31. /**
  32. * Create a new player and put him at the row-column coordinates
  33. * @param name The name of the player
  34. * @param champion Flag to indicate if a player is a `champion`
  35. * @param board Reference to the board of the game
  36. * @param tileId The tileId coordinate of player's initial position
  37. */
  38. public MinMaxPlayer(String name, boolean champion, Board board, int tileId) throws Exception {
  39. super(name, champion, board, tileId);
  40. }
  41. /** @} */
  42. /** @name Board's main application interface */
  43. /** @{ */
  44. /**
  45. * Utility to get the distance of a possible supply in some direction
  46. * @param currentPos The current position of the player
  47. * @param direction The direction to check
  48. * @param board Reference to the Board object to use
  49. *
  50. * @return The distance or Const.noView
  51. */
  52. int supplyInDirection(int currentPos, int direction, Board board) {
  53. Position pos = new Position(Position.toRow(currentPos), Position.toCol(currentPos));
  54. for (int i=0 ; i<=Const.viewDistance ; ++i) {
  55. if (board.hasSupply(pos.getId()))
  56. return i;
  57. if (board.isWalkable(pos.getId(), direction))
  58. pos = new Position(pos.getRow(), pos.getCol(), direction);
  59. else
  60. break;
  61. }
  62. return Const.noView;
  63. }
  64. /**
  65. * Utility to get the distance of a possible opponent in some direction
  66. * @param currentPos The current position of the player
  67. * @param direction The direction to check
  68. * @param board Reference to the Board object to use
  69. *
  70. * @return The distance or Const.noView
  71. */
  72. int opponetInDirection(int currentPos, int direction, Board board) {
  73. Position pos = new Position(Position.toRow(currentPos), Position.toCol(currentPos));
  74. int[] opp = board.getOpponentMove(playerId);
  75. for (int i=0 ; i<=Const.viewDistance ; ++i) {
  76. if (opp[MOVE_TILE_ID] == pos.getId())
  77. return i;
  78. if (board.isWalkable(pos.getId(), direction))
  79. pos = new Position(pos.getRow(), pos.getCol(), direction);
  80. else
  81. break;
  82. }
  83. return Const.noView;
  84. }
  85. /**
  86. * This is the main move evaluation function.
  87. *
  88. * @param currentPos The current position of the player (before the move to evaluate)
  89. * @param direction The direction (a.k.a. the move) to evaluate
  90. * @param board Reference to the Board object to use
  91. * @return A signed real number. The higher the output, the higher the evaluation.
  92. */
  93. double evaluate (int currentPos, int direction, Board board) {
  94. Position next = new Position (Position.toRow(currentPos), Position.toCol(currentPos), direction);
  95. int preOpDist = opponetInDirection (currentPos, direction, board);
  96. int preSupDist = supplyInDirection(currentPos, direction, board);
  97. int postOpDist = opponetInDirection (next.getId(), direction, board);
  98. int postSupDist = supplyInDirection(next.getId(), direction, board);
  99. return ((preSupDist != Const.noView) ? Const.preMoveFactor *(1.0/(preSupDist+1) * Const.supplyFactor) : 0)
  100. - ((preOpDist != Const.noView) ? Const.preMoveFactor *(1.0/(preOpDist+1) * Const.opponentFactor) : 0)
  101. + ((postSupDist != Const.noView)? Const.postMoveFactor*(1.0/(preSupDist+1) * Const.supplyFactor) : 0)
  102. - ((postOpDist != Const.noView) ? Const.postMoveFactor*(1.0/(preOpDist+1) * Const.opponentFactor) : 0);
  103. }
  104. /**
  105. * Executes the minimax algorithm and return a reference to selected move
  106. * @param node The root node to start
  107. * @return Reference to the selected move
  108. */
  109. Node chooseMinMaxMove(Node node) {
  110. node.setNodeEvaluation(maxValue(node));
  111. return node.getPath();
  112. }
  113. /**
  114. * Selects the best possible move to return
  115. * @param currentPos Player's current position to the board
  116. * @return The move array
  117. *
  118. * @note
  119. * This function always return a new move.
  120. */
  121. int[] getNextMove(int currentPos) {
  122. Node root = new Node (board);
  123. int [] opp = board.getOpponentMove(playerId);
  124. createMySubtree(currentPos, opp[MOVE_TILE_ID], root, root.getNodeDepth()+1);
  125. return chooseMinMaxMove(root).getNodeMove();
  126. }
  127. /**
  128. * MinMaxPlayer's move.
  129. *
  130. * A player of this kind cheats. He does not throw a dice to get a direction. In contrary he
  131. * calculates his next move very carefully.
  132. * If the player is a champion then he also picks up a possible supply from the tile.
  133. *
  134. * @param id The id of the starting tile.
  135. * @return An array containing player's final position and possible supply of that position.
  136. * The array format is:
  137. * <ul>
  138. * <li> int[0]: The tileId of the final player's position.
  139. * <li> int[1]: The row of the final player's position.
  140. * <li> int[2]: The column of the final player's position.
  141. * <li> int[3]: The dice/direction of the move.
  142. * </ul>
  143. */
  144. @Override
  145. int[] move(int id) {
  146. // Initialize return array with the current data
  147. int[] ret = getNextMove(id);
  148. y = Position.toRow(ret[MOVE_TILE_ID]);
  149. x = Position.toCol(ret[MOVE_TILE_ID]);
  150. int supplyFlag =0, moveFlag =1;
  151. // In case of a champion player, try also to pick a supply
  152. if (champion && (board.tryPickSupply(ret[MOVE_TILE_ID]) != Const.noSupply)) {
  153. ++score; // keep score
  154. ++supplyFlag;
  155. }
  156. ++dirCounter[ret[MOVE_DICE]]; // update direction counters
  157. board.updateMove(ret, playerId);
  158. // Update supply and opponent distance
  159. int smin =Const.noView, omin =Const.noView;
  160. for (int d = DirRange.Begin ; d<DirRange.End ; d += DirRange.Step) {
  161. int s = supplyInDirection (ret[MOVE_TILE_ID], d, board);
  162. int o = opponetInDirection(ret[MOVE_TILE_ID], d, board);
  163. if (s < smin) smin = s;
  164. if (o < omin) omin = o;
  165. }
  166. // update path
  167. Integer[] p = {
  168. ret[MOVE_TILE_ID], ret[MOVE_DICE], moveFlag, supplyFlag,
  169. dirCounter[Direction.UP], dirCounter[Direction.RIGHT], dirCounter[Direction.DOWN], dirCounter[Direction.LEFT],
  170. smin, omin
  171. };
  172. path.add(p);
  173. return ret;
  174. }
  175. /**
  176. * Prints round information for the player
  177. */
  178. void statistics() {
  179. if (!path.isEmpty()) {
  180. Integer[] last = path.get(path.size()-1);
  181. String who = String.format("%12s", name);
  182. System.out.print(who + ": score[" + score + "]" + ", dice =" + last[1] + ", tileId =" + last[0] + " (" + Position.toRow(last[0]) + ", " + Position.toCol(last[0]) + ")");
  183. if (last[2] == 0)
  184. System.out.println(" *Can not move.");
  185. else if (last[3] != 0)
  186. System.out.println(" *Found a supply.");
  187. else
  188. System.out.println("");
  189. // extra prints for minimax player
  190. if (last[8] != Const.noView) System.out.println(" supply =" + last[8]);
  191. else System.out.println(" supply = blind");
  192. if (last[9] != Const.noView) System.out.println(" opponent =" + last[9]);
  193. else System.out.println(" opponent = blind");
  194. }
  195. }
  196. /**
  197. * Prints final statistics for the player
  198. */
  199. void final_statistics () {
  200. String who = String.format("%12s", name);
  201. System.out.println();
  202. System.out.println(who + ": score[" + score + "]");
  203. System.out.println(" Moves up: " + dirCounter[Direction.UP]);
  204. System.out.println(" Moves right: " + dirCounter[Direction.RIGHT]);
  205. System.out.println(" Moves down: " + dirCounter[Direction.DOWN]);
  206. System.out.println(" Moves left: " + dirCounter[Direction.LEFT]);
  207. }
  208. /** @} */
  209. /** @name Minimax algorithm related part */
  210. /** @{ */
  211. /**
  212. * Get the previous direction of the player
  213. * @param parent Reference to previous nNode
  214. * @return The previous direction if exist, else return Direction.NONE
  215. */
  216. private int prevDirection(Node parent) {
  217. if (parent != null && parent.getParent() != null)
  218. return parent.getParent().getNodeMove()[MOVE_DICE];
  219. return Direction.NONE;
  220. }
  221. /**
  222. * A simulated move in a copy of the bard.
  223. *
  224. * @param board The board on witch we simulate the move
  225. * @param currentPos The current position of the player to the @c board
  226. * @param dir The direction of the move
  227. * @param champion Flag to indicate if the player is champion or not
  228. * @return The move array
  229. */
  230. private int[] dryMove (Board board, int currentPos, int dir, boolean champion) {
  231. int[] ret = new int[MOVE_DATA_SIZE];
  232. Position p = new Position(Position.toRow(currentPos), Position.toCol(currentPos), dir);
  233. ret[MOVE_TILE_ID] = p.getId();
  234. ret[MOVE_ROW] = p.getRow();
  235. ret[MOVE_COLUMN] = p.getCol();
  236. ret[MOVE_DICE] = dir;
  237. board.updateMove(ret, (champion) ? playerId : board.getOpponentId(playerId));
  238. return ret;
  239. }
  240. /**
  241. * One of the 2 recursive functions for creating the minimax tree. This one
  242. * creates children for the MinMax player.
  243. *
  244. * @param parent The parent Node
  245. * @param depth The current depth for the children
  246. * @param currentPos The tile of MinMax player
  247. * @param oppCurrentPos The tile of the opponent
  248. *
  249. * @note
  250. * Even though unnecessary we calculate the evaluation for every node and not only for the leafs.
  251. * This follows the exercise instructions. We could also rely on lazy evaluation of "evaluation()"
  252. * and use AB pruning but the depth of the tree is not worth the try.
  253. */
  254. private void createMySubtree (int currentPos, int oppCurrentPos, Node parent, int depth) {
  255. ShuffledRange dirs = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step);
  256. int [] nodeMove;
  257. for (int dir = dirs.get() ; dir != Const.EOR ; dir = dirs.get()) {
  258. if ((dir != Direction.opposite(prevDirection(parent)))
  259. && parent.getNodeBoard().isWalkable(currentPos, dir)) {
  260. Board nodeBoard = new Board (parent.getNodeBoard()); // clone board
  261. double eval = evaluate (currentPos, dir, nodeBoard); // evaluate the move
  262. nodeMove = dryMove (nodeBoard, currentPos, dir, true); // simulate the move
  263. // make child Node
  264. Node child = new Node (parent, depth, nodeMove, nodeBoard, eval);
  265. parent.addChild(child); // add child to tree
  266. createOppSubtree (nodeMove[MOVE_TILE_ID], oppCurrentPos, child, depth+1);
  267. }
  268. }
  269. }
  270. /**
  271. * One of the 2 recursive functions for creating the minimax tree. This one
  272. * creates children for the opponent player.
  273. *
  274. * @param parent The parent Node
  275. * @param depth The current depth for the children
  276. * @param currentPos The tile of MinMax player
  277. * @param oppCurrentPos The tile of the opponent
  278. *
  279. * @note
  280. * Even though unnecessary we calculate the evaluation for every node and not only for the leafs.
  281. * This follows the exercise instructions. We could also rely on lazy evaluation of "evaluation()"
  282. * and use AB pruning but the depth of the tree is not worth the try.
  283. */
  284. private void createOppSubtree (int currentPos, int oppCurrentPos, Node parent, int depth) {
  285. ShuffledRange dirs = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step);
  286. int [] nodeMove;
  287. for (int dir = dirs.get() ; dir != Const.EOR ; dir = dirs.get()) {
  288. if ((dir != Direction.opposite(prevDirection(parent)))
  289. && parent.getNodeBoard().isWalkable(oppCurrentPos, dir)) {
  290. Board nodeBoard = new Board(parent.getNodeBoard()); // clone board
  291. nodeMove = dryMove (nodeBoard, oppCurrentPos, dir, false); // simulate move
  292. Position init = new Position( // evaluate from "My" perspective the move
  293. parent.getNodeMove()[MOVE_ROW],
  294. parent.getNodeMove()[MOVE_COLUMN],
  295. Direction.opposite(parent.getNodeMove()[MOVE_DICE])
  296. );
  297. double eval = evaluate(init.getId(), parent.getNodeMove()[MOVE_DICE], nodeBoard);
  298. // make child Node
  299. Node child = new Node (parent, depth, nodeMove, nodeBoard, eval);
  300. parent.addChild(child); // add child to tree
  301. if (depth < Const.minimaxTreeDepth) {
  302. createMySubtree (currentPos, nodeMove[MOVE_TILE_ID], child, depth+1);
  303. }
  304. }
  305. }
  306. }
  307. /**
  308. * The Minimax recursive function for the maximizing part.
  309. *
  310. * @param node The current Node
  311. * @return The selected Node
  312. */
  313. private double maxValue (Node node) {
  314. if (node.getChildren() == null) {
  315. //node.setPath(node);
  316. return node.getNodeEvaluation();
  317. }
  318. else {
  319. double M = Double.NEGATIVE_INFINITY;
  320. for (Node n : node.getChildren()) {
  321. n.setNodeEvaluation(minValue(n)); // evaluation propagation
  322. if (M < n.getNodeEvaluation()) {
  323. M = n.getNodeEvaluation();
  324. node.setPath(n); // path propagation
  325. }
  326. }
  327. return M;
  328. }
  329. }
  330. /**
  331. * The Minimax recursive function for the minimizing part.
  332. *
  333. * @param node The current Node
  334. * @return The selected Node
  335. */
  336. private double minValue (Node node) {
  337. if (node.getChildren() == null) {
  338. //node.setPath(node);
  339. return node.getNodeEvaluation();
  340. }
  341. else {
  342. double m = Double.POSITIVE_INFINITY;
  343. for (Node n : node.getChildren()) {
  344. n.setNodeEvaluation(maxValue(n)); // evaluation propagation
  345. if (m > n.getNodeEvaluation()) {
  346. m = n.getNodeEvaluation();
  347. node.setPath(n); // path propagation
  348. }
  349. }
  350. return m;
  351. }
  352. }
  353. /** @} */
  354. }