Package pdp.scrabble.game.impl

Source Code of pdp.scrabble.game.impl.SearchPlacementImpl$Loc

package pdp.scrabble.game.impl;

import java.util.TreeSet;
import java.util.SortedSet;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import javax.swing.JFrame;
import pdp.scrabble.Game;
import pdp.scrabble.game.AIConfig;
import pdp.scrabble.game.SearchLevel;
import pdp.scrabble.game.SearchStrategy;
import pdp.scrabble.game.Dictionary;
import pdp.scrabble.game.Bag;
import pdp.scrabble.game.Rack;
import pdp.scrabble.game.Letter;
import pdp.scrabble.game.BoardCase;
import pdp.scrabble.game.Board;
import pdp.scrabble.game.Placement;
import pdp.scrabble.game.Player;
import pdp.scrabble.game.SearchPriority;
import pdp.scrabble.ihm.BoardPanel;
import pdp.scrabble.utility.Anagram;
import pdp.scrabble.utility.Tool;
import static pdp.scrabble.Factory.FACTORY;
import static pdp.scrabble.game.BoardCaseState.FREE;
import static pdp.scrabble.game.BoardCaseState.OLD;
import static pdp.scrabble.game.Board.HORI_DIM;
import static pdp.scrabble.game.Board.VERT_DIM;

public class SearchPlacementImpl extends Thread {

    private SearchPriority priority = null;
    private SearchStrategy strategy = null;
    private SearchLevel level = null;

    private Dictionary dico = null;
    private Bag bag = null;

    /** Board reference (cloned). */
    private Board board = null;

    /** Player reference (cloned). */
    private Player player = null;

    /** Original player reference. */
    private Player playerOrigin = null;

    /** Best placement found. */
    private Placement bestPlacement = null;

    /** List of required letters. */
    private List<Letter> required = null;

    /** Number of required letters. */
    private int requiredNum = 0;

    /** List of excluded letters. */
    private List<Letter> excluded = null;

    /** Number of excluded letters. */
    private int excludedNum = 0;

    /** Maximal window search. */
    private int maximalWindowSize = 0;

    /** Number of anagram to skip. */
    private int anagramSkip = 0;

    /** Min search score. */
    private int scoreMin = 0;

    /** Max search score. */
    private int scoreMax = 0;

    /** Search axis. */
    private boolean vertical = false;

    /** Debug board. */
    private BoardPanel debug = null;

    /** Debug frame. */
    private JFrame frame = null;

    /** Active state for debug view (show search steps). */
    private boolean debugActive = false;

    /** First placement state, true when it is the first (first turn). */
    private boolean firstPlacement = false;

    /** List of best placements. */
    private SortedSet<Placement> bestPlacements = null;

    /** Last placement id. */
    private int placementID = 0;

    /** Contain anagram on specified length. */
    private static class AnagramLength {
  private List<String> anag = null;

  AnagramLength() {
      this.anag = new ArrayList<String>(16);
  }

  public void add(String s) {
      this.anag.add(s);
  }

  public Iterator<String> get() {
      return this.anag.iterator();
  }
    }
    /** List of all anagram length. */
    private static List<AnagramLength> anagrams = null;

    /** Build anagram length list. */
    public static void initAnagrams() {
  anagrams = new ArrayList<AnagramLength>(Rack.MAX_RACK_LETTERS);
  Anagram anagram = new Anagram("0123456");

  for (int i = 0; i < Rack.MAX_RACK_LETTERS; i++) {
      anagrams.add(new AnagramLength());
  }

  Iterator<String> itr = anagram.get();
  while (itr.hasNext()) {
      String s = itr.next();
      int i = s.length() - 1;
      if (i > -1) {
    anagrams.get(i).add(s);
      }
  }
    }

    /** Create a new placement search.
     * @param game game reference.
     * @param player original player reference (will be cloned).
     * @param config search configuration.
     * @param required list of required letters for this search.
     * @param excluded list of excluded letters for this search.
     * @param vertical search axis (true for vertical, false for horizontal).
     */
    public SearchPlacementImpl(
      Game game, Player player, AIConfig config, List<Letter> required,
      List<Letter> excluded, boolean vertical) {

  super("Search placement");
  this.dico = game.dictionary();
  this.bag = game.bag();
  this.board = game.board().clone();
  this.player = player.clone();
  this.playerOrigin = player;
  this.vertical = vertical;
  this.required = required;
  this.excluded = excluded;
  this.bestPlacement = FACTORY.createPlacement();
  this.bestPlacements = new TreeSet<Placement>();
  this.firstPlacement = false;
  this.placementID = 0;

  // Setup configuration
  this.level = config.getLevel();
  this.strategy = config.getStrategy();
  this.priority = config.getPrioriry();

  // In case of automatic strategy
  if (this.strategy == SearchStrategy.AUTOMATIC) {
      int score = player.getScore();
      Iterator<Player> itr = game.getPlayersList();
      while (itr.hasNext()) {
    Player p = itr.next();
    if (p != player) {
        if (score >= p.getScore()) {
      this.strategy = SearchStrategy.DEFENSIVE;
        }
        else {
      this.strategy = SearchStrategy.OFFENSIVE;
        }
    }
      }
  }

  // Apply configuration
  this.maximalWindowSize = 15;
  this.anagramSkip = 0;
  this.scoreMin = -1;
  this.scoreMax = -1;

  if (this.level == SearchLevel.NORMAL) {
      this.maximalWindowSize = 6;
      this.anagramSkip = 2;
      this.scoreMin = 14;
      this.scoreMax = 28;
  }
  else if (this.level == SearchLevel.EASY) {
      this.maximalWindowSize = 4;
      this.anagramSkip = 4;
      this.scoreMin = 4;
      this.scoreMax = 14;
  }

  if (required != null) {
      this.requiredNum = this.required.size();
  }
  else {
      this.requiredNum = 0;
  }

  if (excluded != null) {
      this.excludedNum = this.excluded.size();
  }
  else {
      this.excludedNum = 0;
  }

  if (player.showDebug()) {
      this.debugActive = true;
  }

  if (this.debugActive) {
      if (vertical) {
    this.frame = new JFrame("Search best vertical placement");
      }
      else {
    this.frame = new JFrame("Search best horizontal placement");
      }
      this.debug = new BoardPanel(this.board);
      this.frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
      this.frame.setResizable(false);
      this.frame.add(this.debug);
      this.frame.pack();
      this.frame.validate();
      this.frame.setLocationRelativeTo(null);

      if (vertical) {
    this.frame.setLocation(
      this.frame.getX() - this.frame.getWidth() / 2,
      this.frame.getY());
      }
      else {
    this.frame.setLocation(
      this.frame.getX() + this.frame.getWidth() / 2,
      this.frame.getY());
      }
      this.repaint();
  }
    }

    /** Update and redraw debug screen. */
    private void repaint() {
  if (this.debugActive) {
      this.debug.paintImmediately(0, 0, 600, 600);
      Tool.pause(250);
  }
    }

    @Override
    public void run() {
  Placement current = null;
  if (this.debugActive) {
      this.frame.setVisible(true);
  }

  // Vertical search ?
  if (this.vertical) {
      if (this.board.getCase(VERT_DIM / 2, HORI_DIM / 2).getState() == FREE) {
    this.firstPlacement = true;
    this.bestPlacement = this.tryAllLocations(
      this.board, this.player, true, true, HORI_DIM / 2);
      }
      else {
    this.firstPlacement = false;
    for (int h = 0; h < HORI_DIM; h++) {
        current = this.tryAllLocations(
          this.board, this.player, false, true, h);

        this.bestPlacement = this.getBestPlacement(
          this.bestPlacement, current, this.priority);
    }
      }
  }
  else {
      if (this.board.getCase(VERT_DIM / 2, HORI_DIM / 2).getState() == FREE) {
    this.firstPlacement = true;
    this.bestPlacement = this.tryAllLocations(
      this.board, this.player, true, false, VERT_DIM / 2);
      }
      else {
    this.firstPlacement = false;
    for (int v = 0; v < VERT_DIM; v++) {
        current = this.tryAllLocations(
          this.board, this.player, false, false, v);

        this.bestPlacement = this.getBestPlacement(
          this.bestPlacement, current, this.priority);
    }
      }
  }

  if (this.debugActive) {
      this.frame.setVisible(false);
      this.frame.dispose();
  }

  this.board = null;
  this.player = null;
    }

    /** Keep best placement between last best and current placement.
     * @param last last best placement.
     * @param cur current placement found.
     * @param priority priority used to designate the best placement.
     * @return best placement.
     */
    private Placement getBestPlacement(
      Placement last, Placement cur, SearchPriority priority) {

  boolean currentIsBetter = false;

  // Check first placement case
  if (this.level == SearchLevel.HARD) {
      if (this.firstPlacement) {
    if (cur.getScore() < 50) {
        cur.clearActions();
        return last;
    }
      }
  }

  // Check score limit
  if (this.scoreMin > -1) {
      if (cur.getScore() >= this.scoreMin) {
    last.clearActions();
    return cur;
      }
  }
  if (this.scoreMax > -1) {
      if (cur.getScore() > this.scoreMax) {
    cur.clearActions();
    return last;
      }
  }

  // Minimal rules
  if (this.canUseJoker(cur)
      && this.lettersPriority(cur, this.required, this.requiredNum, true)
      && this.lettersPriority(cur, this.excluded, this.excludedNum, false)) {

      // Apply priority
      if (priority == SearchPriority.HIGHER_SCORE) {
    if (cur.getScore() > last.getScore()) {
        currentIsBetter = true;
    }
      }
      else if (priority == SearchPriority.LOWER_SCORE) {
    if (cur.getScore() > -1 && cur.getScore() < last.getScore()
        || last.getScore() == -1) {

        currentIsBetter = true;
    }
      }
      else if (priority == SearchPriority.LONGEST_WORD) {
    if (cur.getLength() > last.getLength()) {
        currentIsBetter = true;
    }
      }
      else if (priority == SearchPriority.SHORTEST_WORD) {
    if (cur.getLength() > -1 && cur.getLength() < last.getLength()
        || last.getLength() == -1) {

        currentIsBetter = true;
    }
      }

      // Differences between same score
      if (this.level == SearchLevel.HARD) {
    if (cur.getScore() == last.getScore()) {

        // Keep best reliquat
        if (cur.getReliquatNum() <= last.getReliquatNum()) {

      // Defensive keep uncompletable word
      if (this.strategy == SearchStrategy.DEFENSIVE) {
          if (this.isUncompletable(cur)) {
        currentIsBetter = true;
          }
      }
      else {
          currentIsBetter = true;
      }
        }
    }
      }
  }

  // Better ?
  if (currentIsBetter) {
      last.clearActions();
      return cur;
  }
  else {
      cur.clearActions();
      return last;
  }
    }

    /** Check if the joker is usefull (at least 50pts).
     * @param current current placement.
     * @return true if usefull, false else.
     */
    private boolean canUseJoker(Placement current) {
  if (this.level == SearchLevel.HARD) {
      if (current.getJokersNumber() > 0 && !this.bag.isEmpty()) {
    if (current.getJokersNumber() == 1) {
        if (this.playerOrigin.getRack().getLetterAmount(this.bag.JOKER) == 2) {
      return (current.getScore() >= 30);
        }
        else {
      return (current.getScore() >= 50);
        }
    }
    else {
        return (current.getScore() >= 50);
    }
      }
      // No joker, skip
      else {
    return true;
      }
  }
  else {
      return true;
  }
    }

    /** Check if word is uncompletable.
     * @param current current placement.
     * @return true if uncompletable, false else.
     */
    private boolean isUncompletable(Placement current) {
  return this.dico.isLast(current.getFirstWord());
    }

    /** Check letter rule (required or excluded).
     * @param current current placement to check.
     * @param rule list of letter of the rule.
     * @param number number of letters to check.
     * @param defaut default state.
     * @return rules result.
     */
    private boolean lettersPriority(Placement current, List<Letter> rule,
            int number, boolean defaut) {

  if (number > 0) {
      List<Letter> usedLetters = current.getLetters();
      int len = usedLetters.size();
      HashSet<Integer> used = new HashSet<Integer>(1);

      // For each required letter
      for (int i = 0; i < number; i++) {
    boolean found = false;

    // For each used letter
    for (int j = 0; j < len; j++) {
        Letter letter = usedLetters.get(j);

        if (!used.contains(letter.getID())) {
      char a = rule.get(i).getName();
      char b = letter.getName();

      // Letter found, mark index as used
      if (a == b) {
          found = true;
          used.add(letter.getID());
          break;
      }
        }
    }

    if (defaut) {
        if (!found) {
      used.clear();
      return false;
        }
    }
    else {
        if (found) {
      used.clear();
      return false;
        }
    }
      }
      used.clear();
      used = null;
  }
  return true;
    }

    /** Establish all available windows size, excluding non valid windows.
     * @param board reference.
     * @param player player which is playing.
     * @param center true when center need to be used.
     * @param vertical true for vertical check, false for horizontal.
     * @param axis axis index (vertical or horizontal index).
     * @return best placement found.
     */
    private Placement tryAllLocations(Board board, Player player, boolean center,
              boolean vertical, int axis) {

  Placement best = FACTORY.createPlacement();
  int maxWindowSize = this.maximalWindowSize;
  boolean ignore = true;

  // Ensure there is an adjacent old letter on current line
  // Or center is free (first turn)
  int boardMax = HORI_DIM;
  if (vertical) {
      boardMax = VERT_DIM;
  }

  for (int i = 0; i < boardMax; i++) {
      if (vertical) {
    if (this.hitOldCase(i, axis)) {
        ignore = false;
        break;
    }
      }
      else {
    if (this.hitOldCase(axis, i)) {
        ignore = false;
        break;
    }
      }
  }

  // Search best placement for each window size (word length)
  // Ignore empty lines
  if (!ignore || center) {
      for (int window = 2; window <= maxWindowSize; window++) {

    // Window is the number of letter for the word
    // It include both rack + board letter
    // Just the result on board is important
    // Minimum window size is 2, because with one letter,
    // we got a word length of 2
    Placement current = this.tryWindow(
      board, player, center, vertical, axis, window);

    // Update best placement
    if (current != null) {
        best = this.getBestPlacement(best, current, this.priority);
    }
      }
  }

  return best;
    }

    /** Try with current window size, and all window possible locations.
     * @param board board reference.
     * @param player player which is playing.
     * @param vertical true for vertical check, false for horizontal.
     * @param axis axis index (vertical or horizontal index).
     * @param windowSize current window size.
     * @return
     */
    private Placement tryWindow(Board board, Player player, boolean center,
        boolean vertical, int axis, int windowSize) {

  Placement best = FACTORY.createPlacement();
  Placement current = null;
  BoardCase boardCase = null;
  int index = 0;
  int boardMax = HORI_DIM;
  if (vertical) {
      boardMax = VERT_DIM;
  }

  // For each cases (vertical or horizontal)
  for (int i = 0; i < boardMax; i++) {

      // Ensure window is not too large
      if (i + windowSize - 1 >= boardMax) {
    break;
      }

      // Ensure window contains at least an old horizontal letter
      boolean ignoreLarge = false, ignoreOld = true, ignoreFree = true;
      boolean ignoreCenter = true;

      if (center) {
    if (i <= boardMax / 2 && (i + windowSize) > boardMax / 2) {
        ignoreCenter = false;
    }
    else {
        continue;
    }
      }

      // Check each window case
      for (int j = 0; j < windowSize; j++) {
    index = i + j;

    if (vertical) {
        boardCase = board.getCase(index, axis);
    }
    else {
        boardCase = board.getCase(axis, index);
    }

    // Search old and free cases
    if (index < boardMax) {

        if (vertical) {
      if (this.hitOldCase(index, axis)) {
          ignoreOld = false;
      }
        }
        else {
      if (this.hitOldCase(axis, index)) {
          ignoreOld = false;
      }
        }

        if (boardCase.getState() == FREE) {
      ignoreFree = false;
        }
    } // Window size too large, can't place word, ignore
    else {
        ignoreLarge = true;
        break;
    }
      }

      // If has something to do (no ignore)
      if (!ignoreLarge && (!ignoreOld || !ignoreCenter) && !ignoreFree) {

    // Check each free location in window from starting coordinate
    StringBuilder str = new StringBuilder("");
    for (int j = 0; j < windowSize; j++) {
        index = i + j;

        if (vertical) {
      if (board.getCase(index, axis).getState() == FREE) {
          str = str.append(Tool.getCharacter(index));
      }
        }
        else {
      if (board.getCase(axis, index).getState() == FREE) {
          str = str.append(Tool.getCharacter(index));
      }
        }
    }

    // Apply placement
    int len = str.length();
    if (len <= Rack.MAX_RACK_LETTERS) {
        current = this.tryLocation(
          board, str, len, player, vertical, axis);
    }
    else {
        current = null;
    }
    str = null;

    // Update best placement
    if (current != null) {
        best = this.getBestPlacement(best, current, this.priority);
    }
      }
  }

  return best;
    }

    /** Represents location structure of a I letter on the J board coordinate. */
    private static class Loc {
  private int i = 0, j = 0;

  private Loc(int i, int j) {
      this.i = i;
      this.j = j;
  }

  public int getI() {
      return this.i;
  }

  public int getJ() {
      return this.j;
  }
    }

    /** Try specified location by using all available letters in any order.
     * @param location location to test (containing location as letter index).
     * @param player player which is playing.
     * @param vertical true for vertical check, false for horizontal.
     * @param axis axis index (vertical or horizontal index).
     * @param len number of letter needed.
     * @param letterIDStart first letter to take in rack.
     */
    private Placement tryLocation(Board board, StringBuilder location, int len,
          Player player, boolean vertical, int axis) {

  Placement best = FACTORY.createPlacement();
  HashSet<Loc> allowed = new HashSet<Loc>(1);
  Letter letter = null;
  int index = 0;
  int skip = 0;
  int i = 0, j = 0;

  // Find existing letters
  for (i = 0; i < len; i++) {
      j = Tool.getNumericValue(location.charAt(i));
      allowed.add(new Loc(i, j));
  }

  // Apply all possible letter positions from rack
  Iterator<String> rackItr = anagrams.get(len - 1).get();
  while (rackItr.hasNext()) {

      // Skip anagram (depending of the difficulty
      for (skip = 0; skip < this.anagramSkip; skip++) {
    if (rackItr.hasNext()) {
        rackItr.next();
    }
      }
      if (!rackItr.hasNext()) {
    break;
      }

      // Current anagram
      String ana = rackItr.next();
      Placement current = FACTORY.createPlacement();

      // For each available location indexs
      Iterator<Loc> locations = allowed.iterator();
      boolean done = false;
      while (locations.hasNext()) {
    Loc loc = locations.next();
    i = loc.getI();
    j = loc.getJ();
    index = Integer.parseInt(ana.charAt(i) + "");
    letter = player.getRack().getLetter(index);

    // Apply location
    if (vertical) {
        done = this.applyLocation(
          board, j, axis, letter, current, vertical);
    }
    else {
        done = this.applyLocation(
          board, axis, j, letter, current, vertical);
    }

    loc = null;
    if (!done) {
        break;
    }
      }

      locations = null;

      // Check if location found is good
      if (board.validateAI() && done) {
    current.set(board.getFormedWords(), board.getWordPoints(),
          board.getWordLength());

    current.setID(this.placementID);
    this.placementID++;
    this.bestPlacements.add(current);

    best = this.getBestPlacement(best, current, this.priority);

    this.repaint();
      }
      else {
    current.clearActions();
    current = null;
      }

      board.cancel(null);
  }

  // Clear
  rackItr = null;
  allowed.clear();
  allowed = null;
  letter = null;

  return best;
    }

    /** Apply location on board.
     * @param board board reference.
     * @param v vertical location.
     * @param h horizontal location.
     * @param letter letter to drop.
     * @param placement current placement.
     * @return true if board modified, false if no change.
     */
    private boolean applyLocation(Board board, int v, int h, Letter letter,
          Placement placement, boolean vertical) {

  if (letter != null) {
      letter.setColor(this.player.getColor());
      board.setCaseLetter(v, h, letter, true);
      if (Tool.isReliquat(letter.getName())) {
    placement.setReliquatNum(placement.getReliquatNum() + 1);
      }
      placement.addAction(FACTORY.createLocation(v, h), letter);

      return true;
  }
  else {
      return false;
  }
    }

    /** Check if the current boardcase is near a multiplicator (defensive).
     * @param boardCase from this board case.
     * @return true if near, false else.
     */
    private boolean hasMultiplicatorProximity(int v, int h, boolean vertical) {
  int vCheck = 1, hCheck = 0;
  if (vertical) {
      vCheck = 0;
      hCheck = 1;
  }

  if (this.hasMultiplicator(this.board.getCase(v - vCheck, h - hCheck))
      || this.hasMultiplicator(this.board.getCase(v + vCheck, h + hCheck))) {
      return true;
  }

  return false;
    }

    /** Check if current case has a multiplicator (>1).
     * @param boardCase board case to check
     * @return true if has multiplicator, false else.
     */
    private boolean hasMultiplicator(BoardCase boardCase) {
  return (boardCase.getLetterMult() > 1);
    }

    /** Check if this coordinate hits an old case.
     * @param v vertical location.
     * @param horizontal location.
     * @return
     */
    private boolean hitOldCase(int v, int h) {
  if (this.board.getCase(v - 1, h).getState() == OLD
    || this.board.getCase(v, h - 1).getState() == OLD
    || this.board.getCase(v + 1, h).getState() == OLD
    || this.board.getCase(v, h + 1).getState() == OLD) {
      return true;
  }
  else {
      return false;
  }
    }

    /** Get best placement found.
     * @return best placement found.
     */
    public Placement getBestPlacement() {
  return this.bestPlacement;
    }

    /** Get list of best placements.
     * @return list of best placements.
     */
    public Iterator<Placement> getBestPlacements() {
  return this.bestPlacements.iterator();
    }
}
TOP

Related Classes of pdp.scrabble.game.impl.SearchPlacementImpl$Loc

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.