Package tzar.mafiabot.engine

Source Code of tzar.mafiabot.engine.Actors

package tzar.mafiabot.engine;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.parser.Parser;

import tzar.mafiabot.util.FuzzyMatch;


public class Actors {
  /**
   * This TreeSet keeps track of all the (unique) Player objects in the below TreeMap.
   * The TreeSet sorts its Player elements by name, in ascending order
   */
  // TODO find a way to extract all unique values from the TreeMap so I can get rid of this
  private TreeSet<Player>  uniquePlayers = new TreeSet<Player>();

  /**
   * This TreeMap is used to associate all aliases with their respective Player objects.
   * Each alias is a key in the TreeMap, with the Player object as its value
   * For example, <"alias1", Player1>, <"alias2", Player1>
   **/
  private TreeMap<String, Player> aliasPlayerMap = new TreeMap<String, Player>(String.CASE_INSENSITIVE_ORDER);

  /**
   * This is the Element for the root of the aliases XML document
   */
  private Element aliasDocument = null;

  /**
   * Creates a new Actors object with default NPCs and no players
   */
  public Actors() {
    try {
      aliasDocument = Jsoup.parse(getClass().getClassLoader().getResourceAsStream("aliases.xml"), null, "aURI", Parser.xmlParser());
    } catch (IOException e) {
      System.err.println("Error reading aliases.xml. Continuing execution without aliases.");
      aliasDocument = new Element(null, null);
    }
    addDefaultNpcs();
  }

  /**
   * Creates a new Actors object with default NPCs and the given players
   * @param players An array of names corresponding to the people who are playing
   */
  public Actors(String[] players) throws Exception {
    this();

    // add players and their aliases to the hash table
    System.out.println();
    for (String name : players) {
      addPlayer(name);
    }
    System.out.println();
    //System.out.printf("%nPlayers (%d): %s%n", uniquePlayers.size(), uniquePlayers.toString());
    //System.out.printf("Aliases loaded (%d): %s%n%n", aliasPlayerMap.size(), aliasPlayerMap.toString());
  }

  /**
   * Adds default npcs (such as No Vote) to the game so they can be voted for.
   */
  private void addDefaultNpcs() {
    for (Element npcElement : aliasDocument.getElementsByTag("npc")) {
      String[] names = npcElement.text().split(";");
      Player npc = new Player(names[0]);
      npc.setVoteEligibility(false, true);
      npc.setCountVisiblity(false, false);

      uniquePlayers.add(npc);
      for (String name : names) {
        aliasPlayerMap.put(name.trim(), npc);
      }
    }
  }

  public void addNpc(String name) {
    Player npc = new Player(name);
    npc.setVoteEligibility(false, true);
    npc.setCountVisiblity(false, false);

    if (uniquePlayers.add(npc)) {
      aliasPlayerMap.put(name.trim(), npc);
      System.out.println("Added vote option " + name + " to the game.");
    } else {
      System.out.println("Vote option " + name + " is already in the game.");
    }
  }

  public void addPlayer(String name) {
    // check if a player with this exact name/alias already exists
    Player player = aliasPlayerMap.get(name);
    if (player != null) {
      return;
    }
    // otherwise, create a new player object for this player
    player = new Player(name);
    // add the given name to the aliases
    aliasPlayerMap.put(name, player);
    // add the player object to the set of unique players
    uniquePlayers.add(player);
    System.out.println(player.getName() + " was added to the game.");

    // check the aliases.xml for matches/close matches and import other aliases
    String matchedName = name;
    double matchedPercent = 0;
    String[] matchedLine = null;
    // look at every alias element in the aliases xml
    for (Element aliasesList : aliasDocument.getElementsByTag("alias")) {
      // compare the given name to each name in the alias element
      String[] aliases = aliasesList.text().split(";");
      for (String alias : aliases) {
        // use fuzzy search on the names in the aliases.xml to import aliases even if the name is misspelled slightly
        alias = alias.trim();
        double result = FuzzyMatch.levenshteinDistanceNormalized(name.toLowerCase(), alias.toLowerCase());
        //System.out.println(name + " " + result);
        if (result > matchedPercent && result >= 0.5) {
          matchedLine = aliases;
          matchedPercent = result;
          matchedName = alias;
          break;
        }
      }
    }

    if (matchedLine != null) {
      // aliases found
     
      // put aliases into map with the player object as its value
      for (String aliasFound : matchedLine) {
        aliasPlayerMap.put(aliasFound.trim(), player);
      }
     
      if (matchedPercent != 1) {
        System.out.println("(?) Perhaps you meant " + matchedName + " instead of " + name + "?");
      }
      System.out.println("Imported aliases for " + player.getName() + ": " + Arrays.asList(matchedLine).toString());
    }
  }

  public void removePlayer(String name) {
    Player p = getPlayerFuzzy(name);
    if (p != null) {
      p.unvote();
      p.pardon();
      uniquePlayers.remove(p);
      aliasPlayerMap.remove(name);

      // remove his other aliases from the map
      Element alias = aliasDocument.getElementsMatchingOwnText(name).first();
      if (alias != null) {
        for (Element key : alias.siblingElements()) {
          aliasPlayerMap.remove(key.text());
        }
      }
      System.out.println(p.getName() + " was removed from the game.");
    }
  }

  public void vote(String voterName, String candidateName) {
    // check if the voter is playing and alive
    Player voter = getPlayerFuzzy(voterName);
    if (voter == null) {
      System.out.printf("(x) Rejected spectator vote from \"%s\" on \"%s\".%n", voterName, candidateName);
    } else if (!voter.isAlive()) {
      System.out.printf("(x) Rejected dead vote from \"%s\" on \"%s\".%n", voter, candidateName);
    } else if (!voter.canVote()) {
      System.out.println("Ignored vote from " + voter.getName() + ".");
    } else {
      Player candidate = getPlayerFuzzy(candidateName);
      if (candidate == null) { // could not find player
        System.out.printf("(x) Rejected vote from \"%s\" on unknown player \"%s\".%n", voter, candidateName);
      } else if (!candidate.isAlive()) {
        System.out.printf("(x) Rejected vote from \"%s\" on dead player \"%s\".%n", voter, candidate);
      } else if (!voter.isVoting(candidate)) {
        voter.vote(candidate);

        String output = String.format("%s voted for %s", voter, candidateName);
        if (!candidate.getName().equalsIgnoreCase(candidateName)) {
          output = output + " (" + candidate.getName() + ")";
        }
        if (candidate.getTotalVotes() >= getPlayersAlive().size() / 2 + 1) {
          output = "(!) " + output + ". HAMMER!!";
        }
        System.out.println(output);
      }
    }
  }

  public void unvote(String voter) {
    Player v = aliasPlayerMap.get(voter);
    if (v != null && v.isVoting()) {
      v.unvote();
      System.out.printf("%s unvoted%n", voter);
    }
  }

  public void clearVotes() {
    for (Player p : uniquePlayers) {
      p.pardon();
    }
    System.out.println("\nAll votes have been cleared.\n");
  }

  public void pardon(String name) {
    Player p = getPlayerFuzzy(name);
    if (p != null) {
      p.pardon();
      System.out.println(p.getName() + " was pardoned.");
    } else {
      System.out.println("(x) Pardon failed: " + name + " was not found.");
    }
  }

  public String kill(String name, int dayOfDeath) {
    Player p = getPlayerFuzzy(name);
    if (p != null) {
      if (p.isAlive()) {
        p.pardon();
        p.unvote();
        p.kill(dayOfDeath);
        return p.getName();
      } else {
        System.out.println("(x) Kill failed: " + p.getName() + " is already dead.");
      }
    } else {
      System.out.println("(x) Kill failed: " + name + " was not found.");
    }
    return null;
  }

  public void resurrect(String name) {
    Player p = getPlayerFuzzy(name);
    if (p != null) {
      if (!p.isAlive()) {
        p.resurrect();
        System.out.println(p.getName() + " was resurrected.");
      } else {
        System.out.println("(x) Resurrect failed: " + p.getName() + " is already alive. ");
      }
    } else {
      System.out.println("(x) Resurrect failed: " + name + " was not found.");
    }
  }

  public void takeVote(String name) {
    Player p = getPlayerFuzzy(name);
    if (p != null) {
      p.setVoteEligibility(false, true);
      System.out.println("Removed " + p.getName() +"'s ability to vote!");
    } else {
      System.out.println("(x) Take vote failed: " + name + " was not found.");
    }
  }

  public void giveVote(String name) {
    Player p = getPlayerFuzzy(name);
    if (p != null) {
      p.setVoteEligibility(true, true);
      System.out.println("Reinstated " + p.getName() +"'s ability to vote!");
    } else {
      System.out.println("(x) Give vote failed: " + name + " was not found.");
    }
  }

  public void setVoteWeight(String name, int weight) {
    Player p = getPlayerFuzzy(name);
    if (p == null) {
      System.out.println("(x) Set vote weight failed: " + name + " was not found.");
    } else {
      p.setVotePrestige(p.getVoteNum(), weight);
      System.out.println("Set " + p.getName() + "'s vote weight to " + weight);
    }
  }

  public void setVoteNum(String name, int num) {
    Player p = getPlayerFuzzy(name);
    if (p == null) {
      System.out.println("(x) Set vote num failed: " + name + " was not found.");
    } else {
      p.setVotePrestige(num, p.getVoteWeight());
      System.out.println(p.getName() + " now has " + num + " votes.");
    }
  }


  public void addPost(String name, int day, int length) {
    Player p = aliasPlayerMap.get(name);
    if (p == null) {
      p = getPlayerFuzzy(name);
    }
    if (p != null) {
      p.addPost(day, length);
    }
  }

  /**
   * Prints the post count.
   * It will be displayed as a table, with the rows corresponding to each player, and columns indicating the day
   * @param day The number of days (columns) to display.
   */
  public void printPostCount(int day) {
    System.out.printf("%n[code]%n");
    System.out.println("Post count:");
    // determine longest name for prettier output
    int longestName = 0;
    for (Player p : uniquePlayers) {
      if (p.getName().length() > longestName) {
        longestName = p.getName().length();
      }
    }
    longestName += 3;

    // print out the column header first
    String header = String.format("%-" + longestName + "s", " ");
    for (int i = 1 ; i <= day; i++) {
      header += String.format("[%3s] ", "D" + i);
    }
    header += "[Total]";
    //header.append("[Total] [Avg Words/Post]");
    System.out.println(header.toString());
    // have to use an arraylist to sort by an unnatural order or else players with equal posts would be discarded
    ArrayList<Player> playersSortedByPosts = asSortedList(uniquePlayers, new SortByPosts());
    for (Player p : playersSortedByPosts) {
      if (p.showInPostCount() || p.getTotalPosts() > 0) {
        // make sure the post count has a value for the current day
        ArrayList<Integer> postCount = p.getPostCount();
        while (postCount.size() <= day) {
          postCount.add(0);
        }
        // print name
        String result = String.format("%-" + longestName + "s", p.getName() + ":");
        for (int i = 1; i <= day; i++) {
          // print the post count for each day
          result += String.format("[%3d] ", postCount.get(i));
        }
        // print total posts
        result += String.format("[%4d] ", p.getTotalPosts());
        //result += String.format("[%4d] ", p.getAvgPostLength());
        System.out.println(result);
      }
    }
    System.out.printf("[/code]%n%n");
  }

  /**
   * Prints the vote count.
   * @param header Additional information to display before the vote count is printed
   * @param hasPlayerList Has the GM provided a player list?
   */
  public void printVoteCount(String header, boolean hasPlayerList) {
    // print the header
    System.out.printf("%n[code]%n" + header + "%n");

    // get a list of all players alive
    ArrayList<Player> allPlayersAlive = getPlayersAlive();

    // sort the players by decreasing number of votes
    ArrayList<Player> allPlayersSorted = asSortedList(uniquePlayers, new SortByVotes());

    // keep a list of non-voters
    TreeSet<Player> novotes = new TreeSet<Player>();

    int hammer = allPlayersAlive.size() / 2 + 1;
    boolean candidateExists = false;

    // determine longest name for prettier output
    int longestName = 0;
    for (Player p : allPlayersSorted) {
      if (p.getTotalVotes() > 0 && p.getName().length() > longestName) {
        longestName = p.getName().length();
      }
    }
    longestName += 2;

    for (Player p : allPlayersSorted) {
      // if the player has votes on him, print out the voters
      if (p.getTotalVotes() > 0) {
        candidateExists = true;
        if (hasPlayerList) {
          System.out.printf("(%d/%d) %-" + longestName + "s %s%n", p.getTotalVotes(), hammer, p.getName(), p.getVoters());
        } else {
          System.out.printf("(%d) %-" + longestName + "s %s%n", p.getTotalVotes(), p.getName(), p.getVoters());
        }
      }
      // if the player has not voted, add him to the list of non-voters
      if (p.isAlive() && p.showInVoteCount() && !p.isVoting()) {
        novotes.add(p);
      }

    }

    // if the GM has given the bot a player list, print additional information
    if (hasPlayerList) {
      // list players with no votes
      System.out.printf("(%d/%d) %-" + longestName + "s %s%n", novotes.size(), allPlayersAlive.size(), "No vote:", novotes.toString());

      // list the players that are alive
      System.out.printf("%n%d players alive: %n", allPlayersAlive.size());
      for (Player p : allPlayersAlive) {
        System.out.println(p.getName());
      }

      // list the players that are dead
      ArrayList<Player> allPlayersDead = getPlayersDead();
      if (allPlayersDead.size() > 0) {
        System.out.printf("%n%d players dead:%n", allPlayersDead.size());
        for (Player p : allPlayersDead) {
          System.out.println(p.getName());
        }
      }
    } else if (!candidateExists) {
      System.out.printf("No votes have been cast.%n");
    }
    System.out.printf("[/code]%n%n");

  }

  /**
   * Does a fuzzy search for the player object corresponding to the given name
   * @param name The name of the player to search for
   * @return The Player object whose name is the closest match
   */
  private Player getPlayerFuzzy(String name) {
    Player matchedPlayer = aliasPlayerMap.get(name);
    // check if an exact match for that alias exists
    if (matchedPlayer != null) {
      return matchedPlayer;
    }
    // if not, loop through all the aliases in the TreeMap and use fuzzy match to find the closest one
    double matchedPercent = 0;
    for (String alias : aliasPlayerMap.keySet()) {
      // if the given name is a substring of an alias, that alias is probably the right one
      // so return the player object associated with it
      if (alias.toLowerCase().contains(name.toLowerCase())) {
        return aliasPlayerMap.get(alias);
      }
      // otherwise, do a fuzzy match
      double result = FuzzyMatch.jaroWinklerDistance(name.toLowerCase(), alias.toLowerCase());
      if (result > matchedPercent && result > 0.8) {
        matchedPlayer = aliasPlayerMap.get(alias);
        matchedPercent = result;
      }
    }

    // display matching debug if a match was found
    if (matchedPercent != 0) {
      System.err.format("Closest match to \"%s\" was \"%s\" with %.2f%% matching.%n", name, matchedPlayer.getName(), matchedPercent * 100);
    }

    return matchedPlayer;
  }

  /**
   * Returns a string containing the names of all unique players and npcs
   */
  @Override
  public String toString() {
    return uniquePlayers.toString();
  }

  public void printPlayers() {
    ArrayList<Player> players = getPlayersAlive();
    System.err.println("Players alive at day start: " + players.size() + " " + players.toString());
    players = getPlayersDead();
    System.err.println("Players dead at day start: " + players.size() + " " + players.toString());
  }

  private ArrayList<Player> getPlayersAlive() {
    ArrayList<Player> playersAlive = new ArrayList<Player>();
    for (Player p : uniquePlayers) {
      if (p.isAlive() && p.showInVoteCount()) {
        playersAlive.add(p);
      }
    }
    return playersAlive;
  }

  private ArrayList<Player> getPlayersDead() {
    ArrayList<Player> playersDead = new ArrayList<Player>();
    for (Player p : uniquePlayers) {
      if (!p.isAlive() && p.showInVoteCount()) {
        playersDead.add(p);
      }
    }
    return playersDead;
  }

  /**
   * Sorts a set in an unnatural order. Returns an ArrayList because a set means equal elements are discarded (eg players with equal votes)
   * @return An ArrayList containing the elements of the input set sorted by the order specified by the Comparator
   */
  private <T> ArrayList<T> asSortedList(Set<T> base, Comparator<T> comparator) {
    ArrayList<T> sorted = new ArrayList<T>(base);
    Collections.sort(sorted, comparator);
    return sorted;
  }

  // Comparators
  private static class SortByPosts implements Comparator<Player> {
    public int compare(Player p1, Player p2) {
      /*
      // list alive players before dead players
      if (p1.isAlive() && !p2.isAlive()) {
        return -1;
      } else if (!p1.isAlive() && p2.isAlive()) {
        return 1;
      }
       */
      return p2.getTotalPosts() - p1.getTotalPosts();
    }
  }

  private static class SortByVotes implements Comparator<Player> {
    public int compare(Player p1, Player p2) {
      return p2.getTotalVotes() - p1.getTotalVotes();
    }
  }
}
TOP

Related Classes of tzar.mafiabot.engine.Actors

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.