/* ********************************** * RuleChecker.java * RuleChecker * **********************************/ package sdg.implementations; import java.util.TreeMap; import gen.*; import general.interfaces.GameStateI; import general.interfaces.RuleCheckerI; import edu.neu.ccs.demeterf.demfgen.lib.List; import sdg.local.utils.comparator.*; import sdg.local.utils.*; /** Class for checking if a player transaction adheres to all game rules * @author animesh, acdubre */ public class RuleChecker implements RuleCheckerI { String message = ""; PlayerTransaction pTrans; Config config; TreeMap derivs; public RuleChecker(PlayerTransaction pT, Config c){ pTrans = pT; config = c; derivs = new TreeMap(); } public RuleChecker(PlayerTransaction pT, Config c, TreeMap derivs) { this(pT, c); this.derivs = derivs; } void addMessage(String s){ message += s+"\n"; } public Pair rulesFollowed(PlayerTransaction t, GameStateI gs) { for(Transaction trans : pTrans.transactions) { Derivative d; if(trans instanceof CreateTrans) { CreateTrans ct = (CreateTrans)trans; d = new Derivative(ct.derivativeName(), pTrans.player.id, ct.price, ct.type); } else { d = DerivativesFinder.derByName(gs.curStore(), trans.derivativeName()); if(d == null) { addMessage("Derivative not found for " + trans.print()); return new Pair(false, message); } else if(trans instanceof ReofferTrans) { d = new Derivative(d.name, pTrans.player.id, ((ReofferTrans)trans).newPrice, d.type); } else if(trans instanceof DeliverTrans) { d = d.deliver(((DeliverTrans)trans).rawMat.instance); } else if(trans instanceof FinishTrans) { d = d.finish(((FinishTrans)trans).finish); } } derivs.put(trans.derivativeName(), d); } Pair gtAnswer = gs.curGameType().rulesFollowed(t, gs); Pair genAnswer; if(gs.curConfig().Rounds >= gs.curRoundNum()) { genAnswer = new RuleChecker(t,gs.curConfig(), derivs).rulesFollowed(gs.curStore(),gs.curAccounts()); } else { genAnswer = new RuleChecker(t,gs.curConfig(), derivs).overtimeRulesFollowed(gs.curStore(), gs.curAccounts()); } return new Pair(gtAnswer.a && genAnswer.a, gtAnswer.b + " " + genAnswer.b); } /** Checks if all rules are followed by a player transaction */ protected Pair rulesFollowed(Store store, Accounts accts) { boolean posAccount = isAccountPositive(accts); boolean buyOrReoffer = isBuyOrReofferRuleFollowed(store); boolean create = isCreateRuleFollowed(store); boolean deliver = isDeliveryRuleFollowed(store); boolean finish = isFinishRuleFollowed(store); return new Pair(posAccount && buyOrReoffer && create && deliver && finish, message); } /** Checks if all overtime rules are followed by a player transaction */ protected Pair overtimeRulesFollowed(Store store, Accounts accts) { boolean posAccount = isAccountPositive(accts); boolean deliver = isDeliveryRuleFollowed(store); boolean finish = isFinishRuleFollowed(store); return new Pair(posAccount && deliver && finish, message); } /** Checks if the player's account is positive after pTrans */ protected boolean isAccountPositive(Accounts accts){ double prevBalance = 0; double spentThisTurn = 0; double earnedThisTurn = 0; for(Pair acct : accts.accounts){ if(acct.a.equals(pTrans.player.id)) prevBalance = acct.b; } for(Transaction trans: pTrans.transactions){ Derivative d = derivs.get(trans.derivativeName()); if(trans instanceof BuyTrans){ spentThisTurn += d.price.val; } if(trans instanceof FinishTrans) { if(d.type.kind.isClassic()) { earnedThisTurn += d.optfinished.inner().quality.val; } else { double sq = ComputeQuality.quality(d.optraw.inner(), d.optraw.inner().instance.secret.inner().assign.inner()).val; double win = d.optfinished.inner().quality.val - sq * d.price.val; double payout = 0; if(win >= 0) { payout = d.price.val + win; } earnedThisTurn += payout; } } } if(prevBalance + earnedThisTurn - spentThisTurn < 0){ addMessage("Negative account balance"); return false; } return true; } /** Checks if buy or reoffer rules are followed */ protected boolean isBuyOrReofferRuleFollowed(Store store){ // Either there's nothing to buy/reoffer if(emptyStoreBuyReoffer(store) && !store.stores.isEmpty() && !DerivativesFinder.findDerivativesForSaleByOthers(store.stores, pTrans.player.id).isEmpty()){ // Or there must be a buy or reoffer transaction if(!((pTrans.transactions.contains(TTypeComparators.buy)) ||(pTrans.transactions.contains(TTypeComparators.reoffer)))){ addMessage("No Buy/Reoffer from a non-empty store"); return false; }else{ if(pTrans.transactions.contains(TTypeComparators.buy)){ return correctBuy(store); }else{ return correctReoffer(store, config.MPD); } } } return true; } protected boolean correctReoffer(final Store store, final double MPD){ List reofferTrans = pTrans.transactions.filter(TTypeComparators.reoffer); List actual = reofferTrans.map(new List.Map(){ public Derivative map(Transaction t){ return derivs.get(t.derivativeName()); } }); List forSale = DerivativesFinder.findDerivativesForSaleByOthers(store.stores, pTrans.player.id); List toReoffer = DerivativesFinder.findUniqueDerivatives(forSale); List expected = toReoffer.map(new List.Map(){ public Derivative map(Derivative d){ double price = d.price.val - MPD; return d.reoffer(pTrans.player.id, new Price(price > 0 ? price : 0.0)); }}); if(actual.length() < expected.length()){ addMessage("Incorrect reoffering - too few Derivatives."); return false; } for(Derivative d : expected) if(!actual.contains(new CheaperDerivative(d))){ addMessage("Incorrect reoffering - price too high"); return false; } return true; } // if there is a buy transaction, then the derivative bought must be on sale and player should not buy from itself protected boolean correctBuy(Store store){ List dersForSale = DerivativesFinder.findDerivativesForSale(store.stores); List buyTs = pTrans.transactions.filter(TTypeComparators.buy); for(Transaction buyTrans:buyTs){ if(!dersForSale.contains(new DerivativeByName(buyTrans.derivativeName()))){ addMessage("Non-existant derivative bought"); return false; } if(derivs.get(buyTrans.derivativeName()).seller.equals(pTrans.player.id)){ addMessage("Player bought from itself :: "+buyTrans.print()); return false; } } return true; } protected boolean emptyStoreBuyReoffer(Store store){ // if the store is empty, then there should not be any buy or reoffer // What we really want is whether there are any derivatives -from others- to reoffer. if(store.stores.isEmpty() || DerivativesFinder.findDerivativesForSaleByOthers(store.stores, pTrans.player.id).isEmpty()){ for(Transaction trans: pTrans.transactions){ if(trans instanceof BuyTrans || trans instanceof ReofferTrans){ addMessage("Buy/Reoffer from an empty store"); return false; } } } return true; } /** Checks if create rules are followed */ protected boolean isCreateRuleFollowed(Store store) { // there must be a create transaction if(!pTrans.transactions.contains(TTypeComparators.create)){ addMessage("Player did not create a derivative"); return false; }else{ List createTrans = pTrans.transactions.filter(TTypeComparators.create); // ** Comment out to not enforce limited Create transactions if(createTrans.length() > config.MaxCreates){ addMessage("Created too many derivative"); return false; } /**/ for(Transaction cTrans:createTrans){ List dersForSale = DerivativesFinder.findDerivativesForSale(store.stores); Derivative deriv = derivs.get(cTrans.derivativeName()); // created derivative must not be of existing types if(dersForSale.contains(new SameDerivativeByType(deriv))){ addMessage("Created a derivative of an existing type"); return false; } // Created derivatives must have 0 <= price <= 1 if(deriv.price.val > 1 || deriv.price.val < 0){ addMessage("Illegally priced derivative"); return false; } if(deriv.optbuyer.isSome()){ addMessage("Created a derivative with a buyer."); return false; } if(deriv.optraw.isSome()){ addMessage("Created a derivative with a Raw Material."); return false; } if(deriv.optfinished.isSome()){ addMessage("Created a derivative with a Finished Product."); return false; } } } return true; } /** Checks if delivery rules are followed */ protected boolean isDeliveryRuleFollowed(Store store) { //see whether the expected and actual lists of derivatives that need RM are same List actualDeliveryTrans = pTrans.transactions.filter(TTypeComparators.deliver); List actualDeliveryDers = actualDeliveryTrans.map(new List.Map(){ public Derivative map(Transaction t){ return derivs.get(t.derivativeName()); }}); List expectedDeliveryDers = DerivativesFinder.findDersThatNeedRM(store.stores, pTrans.player); if(!actualDeliveryDers.same(expectedDeliveryDers, new DerivativeComp())){ addMessage("Incorrect number of delivered Derivatives"); return false; }else{ for(Derivative d : actualDeliveryDers) if(!validRawMaterial(d))return false; return true; } } /** * Checks that: * 1) d's optional RawMaterial exists * 2) All Constraints are of the correct type * 3) All Constraints use unique variables * 4) If Kind == Secret, includes an Assignment, if Kind == Classic, no Assignment */ protected boolean validRawMaterial(Derivative d){ if(!d.optraw.isSome()){ addMessage("No RawMaterial found in delivered Derivative"); return false; }else{ for(Constraint c : d.optraw.inner().instance.cs){ // If we find a Constraint with an invalid type if(!d.type.instances.contains(new TypeInstance(c.r))){ addMessage("RawMaterial with Type not specified in Derivative found"); return false; } List temp = c.vs; if(new Secret().equals(d.type.kind) && !d.optraw.inner().instance.secret.isSome()) { addMessage("Secret derivative delivered without secret."); return false; } else if(new Classic().equals(d.type.kind) && d.optraw.inner().instance.secret.isSome()) { addMessage("Classic derivative delivered with secret."); return false; } if(temp.length() != 3){ addMessage("Constraint with too few Variables found"); return false; }else{ Variable v; while(temp.length() > 0){ v = temp.top(); temp = temp.pop(); // If we remove the first element and the list of vars still contains that element if(temp.contains(v)){ addMessage("Duplicate Variable found in Constraint: " + c.print() + ""); return false; } } } } } return true; } /** Checks if finishing rules are followed */ protected boolean isFinishRuleFollowed(Store store){ //see whether the expected and actual lists of derivatives that need RM are same List actualFinishTrans = pTrans.transactions.filter(TTypeComparators.finish); List actualFinishDers = List.create(); for(Transaction trans: actualFinishTrans){ actualFinishDers = actualFinishDers.push(derivs.get(trans.derivativeName())); } List expectedFinishDers = DerivativesFinder.findDersThatNeedFinishing(store.stores, pTrans.player); if(!actualFinishDers.same(expectedFinishDers,new DerivativeComp())){ addMessage("Incorrect number of finished derivatives"); return false; } for(Derivative der : actualFinishDers){ Assignment a = der.optfinished.inner().ip.assignment; RawMaterial rm = der.optraw.inner(); if(!SufficientAssignment.good(rm, a)){ addMessage("Insufficient assignment"); return false; } } return true; } }