/* Copyright (C) 2010 - 2011 Fabian Neundorf, Philip Caroli, * Maximilian Madlung, Usman Ghani Ahmed, Jeremias Mechler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.ojim.client.ai.valuation; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.PriorityQueue; import java.util.logging.Level; import java.util.logging.Logger; import org.ojim.client.SimpleClient; import org.ojim.client.ai.commands.AcceptCommand; import org.ojim.client.ai.commands.AuctionBidCommand; import org.ojim.client.ai.commands.BuildHouseCommand; import org.ojim.client.ai.commands.Command; import org.ojim.client.ai.commands.DeclineCommand; import org.ojim.client.ai.commands.EndTurnCommand; import org.ojim.client.ai.commands.NullCommand; import org.ojim.client.ai.commands.OutOfPrisonCommand; import org.ojim.client.ai.commands.SellCommand; import org.ojim.client.ai.commands.ToggleMortgageCommand; import org.ojim.client.ai.commands.TradeCommand; import org.ojim.log.OJIMLogger; import org.ojim.logic.Logic; import org.ojim.logic.state.Auction; import org.ojim.logic.state.Player; import org.ojim.logic.state.fields.BuyableField; import org.ojim.logic.state.fields.Field; import org.ojim.logic.state.fields.FieldGroup; import org.ojim.logic.state.fields.Jail; import org.ojim.logic.state.fields.Street; import edu.kit.iti.pse.iface.IServer; /** * Valuator - returns the best command * * @author Jeremias Mechler * */ public class Valuator extends SimpleClient { /** * */ private static final long serialVersionUID = -1145966856856523668L; private double[] weights; private ValuationFunction[] valuationFunctions; private Logic logic; private int playerID; private IServer server; private Logger logger; private int auctionBid; private int auctionSteps = 11; private int currentStep = 1; private boolean endTurn = false; private boolean auctionEndTurn = false; private int[] trade; private int count = 0; private int[] toSell; private ValuationParameters parameters; /** * Constructor * * @param logic * reference to logic * @param server * reference to server * @param playerID * The player's ID */ public Valuator(Logic logic, IServer server, int playerID) { super(logic, playerID, server); assert (logic != null); assert (server != null); this.logic = logic; this.server = server; this.playerID = playerID; weights = new double[ValuationFunction.COUNT]; for (int i = 0; i < weights.length; i++) { weights[i] = 1; } weights[0] = 100000; weights[2] = 50000; this.logger = OJIMLogger.getLogger(this.getClass().toString()); valuationFunctions = new ValuationFunction[6]; valuationFunctions[0] = CapitalValuator.getInstance(); valuationFunctions[1] = PropertyValuator.getInstance(); valuationFunctions[2] = PrisonValuator.getInstance(); valuationFunctions[3] = MortgageValuator.getInstance(); valuationFunctions[4] = PropertyGroupValuator.getInstance(); valuationFunctions[5] = BuildingOnPropertyValuator.getInstance(); for (int i = 0; i < ValuationFunction.COUNT; i++) { assert (valuationFunctions[i] != null); } ValuationParametersOld.init(logic); trade = new int[40]; parameters = new ValuationParameters(logic); } /** * Returns the best command * * @param position * current position * @return command */ public Command returnBestCommand(int position) { if (position >= 40) { throw new IllegalArgumentException("Invalid position: " + position); } PriorityQueue<Command> queue = new PriorityQueue<Command>(); Field field = getGameState().getFieldAt(Math.abs(position)); initFunctions(); // OJIMLogger.changeGlobalLevel(Level.WARNING); // OJIMLogger.changeLogLevel(logger, Level.FINE); // Feld potenziell kaufbar if (!endTurn && !auctionEndTurn) { // System.out.println("turn"); queue.add(buyFields(field)); // Feld potentiell bebaubar queue.add(upgradeStreet()); queue.add(tradeStreet(logic.getGameState().getFieldAt(position))); queue.add(getOutOfJail()); // reicht das? if (queue.peek() instanceof NullCommand) { endTurn = true; } return queue.peek(); } if (endTurn) { endTurn = false; return new EndTurnCommand(logic, server, playerID); } if (auctionEndTurn) { auctionEndTurn = false; return new NullCommand(logic, server, playerID); } assert (false); return new NullCommand(logic, server, playerID); } /** * Acting on a trade offer * * @return Command */ public Command actOnTradeOffer() { initFunctions(); System.out.println("o_O"); assert (this.getTradeState() == TradeState.WAITING_PROPOSED); boolean restricted = false; if (getRequiredEstates() != null) { for (BuyableField field : getRequiredEstates()) { if (field.isMortgaged()) { // TODO necessary? assert (false); restricted = true; } if (field instanceof Street && ((Street) field).getBuiltLevel() > 0) { // TODO necessary? assert (false); restricted = true; } } } if (!restricted) { double value = getOfferedCash(); double minus = tradeValuateRequestedEstates(); value += tradeValuateJailCards(); value += tradeValuateOfferedEstates(); minus -= getRequiredCash(); minus += valuationFunctions[0].returnValuation(playerID, this.getRequiredCash()); // missing: out of jail cards! if (value + minus > 0) { logger.log(Level.INFO, "Accepting trade!"); return new AcceptCommand(logic, server, playerID); } } logger.log(Level.INFO, "Declining trade!"); return new DeclineCommand(logic, server, playerID); } /** * Pays back mortgages * * @return command */ public Command paybackMortgages() { initFunctions(); PriorityQueue<Command> queue = new PriorityQueue<Command>(); Command command = new NullCommand(logic, server, playerID); command.setValuation(0); queue.add(command); for (BuyableField field : getMe().getFields()) { if (field.isMortgaged()) { field.setSelected(true); double valuation = getResults(field.getPosition(), field.getMortgagePrice()); System.out.println(valuation); field.setSelected(false); if (valuation >= 0) { command = new ToggleMortgageCommand(logic, server, playerID, field); command.setValuation(valuation); queue.add(command); } } } return queue.peek(); } /** * Acts on auction * * @return Command */ public Command actOnAuction() { initFunctions(); Auction auction = this.getGameState().getAuction(); assert (auction.getState() != AuctionState.NOT_RUNNING); int realValue = (int) getResults(auction.objective.getPosition(), 0); int bidder; if (auction.getHighestBidder() == null) { bidder = -1; } else { bidder = auction.getHighestBidder().getId(); } logger.log(Level.FINE, "state = " + auction.getState().value + " bidder = " + bidder + " highesBid = " + auction.getHighestBid() + " value = " + realValue); if (auction.getState() != AuctionState.THIRD && auction.getHighestBidder() != getMe() && auction.getHighestBid() < realValue && currentStep < auctionSteps) { logger.log(Level.FINE, "Highest bid = " + auction.getHighestBid()); if (auction.getHighestBid() < realValue) { logger.log(Level.FINE, "Valuation " + realValue); double factor = Math.log(0.1 + currentStep++) / Math.log(0.1 + auctionSteps); auctionBid = (int) (factor * realValue); logger.log(Level.FINE, "Bidding " + auctionBid); if (getResults(auction.objective.getPosition(), auctionBid) > 0) { return new AuctionBidCommand(logic, server, playerID, auctionBid); } else { auctionEndTurn = true; return new NullCommand(logic, server, playerID); } } } if (auction.getState() == AuctionState.THIRD) { currentStep = 1; } if (auction.getHighestBidder() != getMe()) { auctionEndTurn = true; } return new NullCommand(logic, server, playerID); } private double getResults(int position, int amount) { assert (position <= 40 && position >= 0); assert (amount >= 0); double result = weights[0] * valuationFunctions[0].returnValuation(playerID, amount); for (int i = 1; i < valuationFunctions.length; i++) { result += weights[i] * valuationFunctions[i].returnValuation(playerID, position); } logger.log(Level.INFO, "gesamt = " + result); return result; } private double tradeValuateJailCards() { int offeredCards = getNumberOfOfferedGetOutOfJailCards(); int difference = ValuationParametersOld.desiredNumberOfOutOfOjailCards - this.getMe().getNumberOfGetOutOfJailCards(); if (difference > 0) { if (offeredCards >= difference) { return ((Jail) getGameState().getFieldAt(10)).getMoneyToPay() * difference; } else { return ((Jail) getGameState().getFieldAt(10)).getMoneyToPay() * offeredCards; } } else { return 0; } } private double tradeValuateEstates(BuyableField[] estates) { assert (estates != null); double result = 0; for (BuyableField estate : estates) { result += getResults(estate.getPosition(), 0); } return result; } private double tradeValuateRequestedEstates() { return (-1) * tradeValuateEstates(getRequiredEstates()); } private double tradeValuateOfferedEstates() { return tradeValuateEstates(getOfferedEstate()); } private void getPropertiesToSell(int requiredCash, boolean mortgage) { int cash = 0; ArrayList<BuyableField> list = new ArrayList<BuyableField>(); PriorityQueue<BuyableField> queue = getMe().getQueue(); // TODO better condition? while (cash < requiredCash && !queue.isEmpty()) { BuyableField temp = queue.poll(); cash += temp.getPrice() / 2; list.add(temp); revaluate(queue); } int[] result = new int[list.size()]; int i = 0; for (BuyableField field : list) { result[i++] = field.getPosition(); } toSell = result; } // beim Hinzufügen alle neu validieren? private void revaluate(PriorityQueue<BuyableField> queue) { BuyableField[] fields = new BuyableField[queue.size()]; int i = 0; while (!queue.isEmpty()) { BuyableField field = queue.poll(); field.setValuation(getResults(field.getPosition(), 0)); fields[i++] = field; } for (BuyableField field : fields) { queue.add(field); } assert (queue.size() == fields.length); } private void sell(int requiredCash) { int initialCash = getLogic().getGameState().getActivePlayer().getBalance(); int newCash = 0; // Property[] toBeSold = new Property[list.size()]; // toBeSold = list.toArray(new Property[0]); // Iterator-Zugriffsfehler? for (BuyableField field : this.getMe().getQueue()) { if (newCash < requiredCash) { int faceValue = field.getPrice(); new SellCommand(logic, server, playerID, field.getPosition(), (int) Math.max(faceValue, field.getValuation()), faceValue / 2).execute(); newCash = logic.getGameState().getActivePlayer().getBalance() - initialCash; assert (newCash != initialCash); // property = null; // field.setSelected(true); } else { field.setSelected(false); } } revaluate(getMe().getQueue()); } private int getPrice(int position) { assert (position < 40 && position >= 0); Field field = logic.getGameState().getFieldAt(position); assert (field instanceof BuyableField); return ((BuyableField) field).getPrice(); } private Command tradeStreet(Field blaField) { Command result = new NullCommand(logic, server, playerID); // Command result = new DeclineCommand(logic, server, playerID); result.setValuation(0); count++; if (true) { // Command result = new EndTurnCommand(logic, server, playerID); if (getTradeState() == TradeState.DECLINED) { System.out.println("o_O"); return result; } result.setValuation(0.01); assert (blaField != null); if (trade[blaField.getPosition()] != count - 1 && blaField instanceof BuyableField && ((BuyableField) blaField).getOwner() != null && ((BuyableField) blaField).getOwner() != getMe() && ((BuyableField) blaField).getFieldGroup().getFields().length > 1 && fieldsOwned(((BuyableField) blaField).getFieldGroup()) > 0) { trade[blaField.getPosition()] = count; PriorityQueue<BuyableField> queue = new PriorityQueue<BuyableField>(); double streetValue = getResults(blaField.getPosition(), 0); // get possible trade street Field[] myFields = getMe().getFields(); Field[] hisFields = ((BuyableField) blaField).getOwner().getFields(); HashSet<Integer> colorList = new HashSet<Integer>(); for (Field field : hisFields) { if (field instanceof BuyableField) { int color = (((BuyableField) field).getFieldGroup().getColor()); if (color != blaField.getFieldGroup().getColor()) { colorList.add(color); } } } for (Field field : myFields) { if (field instanceof BuyableField) { int color = (((BuyableField) field).getFieldGroup().getColor()); if (colorList.contains(color)) { queue.add((BuyableField) field); } } } revaluate(queue); if (!queue.isEmpty()) { // System.out.println("Val = " + queue.peek().getValuation()); result = new TradeCommand(logic, playerID, server, queue.peek(), (BuyableField) blaField); result.setValuation(1000 * streetValue); } } System.out.println("!!" + result.getClass().getName()); } return result; } private int fieldsOwned(FieldGroup group) { Field[] fields = group.getFields(); int count = 0; for (Field field : fields) { if (((BuyableField) field).getOwner() == getMe()) { count++; } } return count; } /** * Acts on negative money * * @return Command */ public Command negative() { if (getMe().getBalance() > 0) { throw new IllegalStateException("Positive cash!"); } logger.log(Level.FINE, "Negative cash!"); int balance = Math.abs(getMe().getBalance()); assert (balance > 0); BuyableField[] result = mortgageBuildings(balance); boolean success = false; if (result.length > 0) { success = true; return new ToggleMortgageCommand(logic, server, playerID, result[0]); } else { LinkedList<BuyableField> list = new LinkedList<BuyableField>(); result = mortgageBuildings(0); if (result.length != 0) { int sum = 0; for (int i = 0; i < result.length; i++) { if (sum < balance) { list.add(result[i]); sum += result[i].getMortgagePrice(); } } if (sum >= balance) { success = true; return new ToggleMortgageCommand(logic, server, playerID, list.toArray(new BuyableField[0])); } } } return new NullCommand(logic, server, playerID); } private BuyableField[] mortgageBuildings(int money) { LinkedList<BuyableField> list = new LinkedList<BuyableField>(); for (BuyableField field : getMe().getFields()) { if (getGameRules().isFieldMortgageable(getMe(), field) && field.getMortgagePrice() >= Math.abs(money)) { list.add(field); } } // assert(getMe().getFields().length > 0); /** * Compares the price of two fields */ final class MoneyComparator implements Comparator<BuyableField> { @Override public int compare(BuyableField o1, BuyableField o2) { return o1.getPrice() - o2.getPrice(); } } BuyableField[] array = list.toArray(new BuyableField[0]); Arrays.sort(array, new MoneyComparator()); return array; } private Command buyFields(Field field) { assert (field != null); if (field instanceof BuyableField && !endTurn) { logger.log(Level.FINE, "BuyableField!"); Player owner = ((BuyableField) field).getOwner(); double realValue = getResults(field.getPosition(), 0); // Feld gehört mir (noch) nicht if (owner != getMe()) { int price = ((BuyableField) field).getPrice(); double valuation = getResults(field.getPosition(), price); // double realValue = getResults(position, 0); // Feld gehört der Bank if (owner == null) { if (valuation > 0) { logger.log(Level.FINE, "Granted"); assert (logic != null); assert (server != null); logger.log(Level.FINE, "Accept"); return new AcceptCommand(logic, server, playerID); // nicht ausreichend, ab wann verkaufen oder handeln? } else if (realValue > 0 && false) { // preis? // getPropertiesToSell(); // sell(); // return new DeclineCommand(logic, server, playerID); } else { // Ablehnen logger.log(Level.FINE, "Decline"); endTurn = true; return new DeclineCommand(logic, server, playerID); } } else { // Trade! logger.log(Level.FINE, playerID + " Soon trade"); // return new NullCommand(logic, server, playerID); } } } Command result = new NullCommand(logic, server, playerID); result.setValuation(0); return result; } private Command upgradeStreet() { PriorityQueue<Street> upgradeableStreets = new PriorityQueue<Street>(); for (int i = 0; i < 40; i++) { Field field = getGameState().getFieldAt(i); if (field instanceof Street) { Street street = (Street) field; // assert(getGameRules().isFieldUpgradable(getMe(), field, street.getBuiltLevel() + 1)); if (getGameRules().isFieldUpgradable(getMe(), field, street.getBuiltLevel() + 1)) { street.setSelected(true); street.setValuation(getResults(street.getPosition(), server.getEstateHousePrice(street.getPosition()))); street.setSelected(false); upgradeableStreets.add((Street) field); } } } if (upgradeableStreets.size() > 0 && upgradeableStreets.peek().getValuation() > 0) { Command result = new BuildHouseCommand(logic, server, playerID, upgradeableStreets.peek()); result.setValuation(upgradeableStreets.peek().getValuation()); return result; } else { Command result = new NullCommand(logic, server, playerID); result.setValuation(0); return result; } } private Command getOutOfJail() { assert (getMe() == logic.getGameState().getActivePlayer()); Jail jail = getMe().getJail(); if (jail != null) { // assert (false); double valuation = getResults(jail.getPosition(), jail.getMoneyToPay()); if (valuation > 0) { Command result = new OutOfPrisonCommand(logic, server, playerID); result.setValuation(valuation); return result; } } Command result = new EndTurnCommand(logic, server, playerID); result.setValuation(0); return result; } private void initFunctions() { for (ValuationFunction function : valuationFunctions) { assert (function != null); function.setServer(server); function.setParameters(parameters, logic); } } }