/* ********************************** * RuleChecker.java * RuleChecker * **********************************/ package admin.utils; import admin.utils.Game.GameType; import edu.neu.ccs.demeterf.demfgen.lib.List; import utils.*; import utils.comparator.*; import gen.*; /** Class for checking if a player transaction adheres to all game rules * @author animesh, acdubre */ public class RuleChecker { String message = ""; PlayerTransaction pTrans; Config config; protected RuleChecker(PlayerTransaction pT, Config c){ pTrans = pT; config = c; } void addMessage(String s){ message += s+"\n"; } public static Pair rulesFollowed(PlayerTransaction t, Store s, Accounts a, GameType gt, Config c) { return new RuleChecker(t,c).rulesFollowed(s,a,gt); } public static Pair overtimeRulesFollowed(PlayerTransaction t, Store s, Accounts a, GameType gt, Config c){ return new RuleChecker(t,c).overtimeRulesFollowed(s, a); } /** Checks if all rules are followed by a player transaction */ public Pair rulesFollowed(Store store, Accounts accts, GameType gt) { boolean posAccount = isAccountPositive(accts); boolean buyOrReoffer = isBuyOrReofferRuleFollowed(store); boolean create = isCreateRuleFollowed(store, gt); 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 */ public 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){ if(trans.ttype.getClass().equals(Buy.class)){ // Can't trust the transaction... Need it from the Store spentThisTurn += trans.deriv.price.val; } if(trans.ttype.getClass().equals(Finish.class)){ earnedThisTurn += trans.deriv.optfinished.inner().quality.val; } } 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(new TransactionComparatorByTType(new Buy()))) ||(pTrans.transactions.contains(new TransactionComparatorByTType(new Reoffer()))))){ addMessage("No Buy/Reoffer from a non-empty store"); return false; }else{ if(pTrans.transactions.contains(new TransactionComparatorByTType(new Buy()))){ return correctBuy(store); }else{ Config config = AdminDocumentHandler.getConfig(); return correctReoffer(store, config.MPD); } } } return true; } protected boolean correctReoffer(final Store store, final double MPD){ List reofferTrans = pTrans.transactions.filter(new TransactionComparatorByTType(new Reoffer())); List actual = reofferTrans.map(new List.Map(){ public Derivative map(Transaction t){ return t.deriv; } }); 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(new TransactionComparatorByTType(new Buy())); for(Transaction buyTrans:buyTs){ if(!dersForSale.contains(new SameDerivative(buyTrans.deriv))){ addMessage("Non-existant derivative bought"); return false; } if(buyTrans.deriv.seller.equals(pTrans.player.id)){ addMessage("Player bought from itself :: "+buyTrans.deriv.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. - acdubre if(store.stores.isEmpty() || DerivativesFinder.findDerivativesForSaleByOthers(store.stores, pTrans.player.id).isEmpty()){ for(Transaction trans: pTrans.transactions){ if(trans.ttype.getClass().equals(Buy.class)||trans.ttype.getClass().equals(Reoffer.class)){ addMessage("Buy/Reoffer from an empty store"); return false; } } } return true; } /** Checks if create rules are followed */ protected boolean isCreateRuleFollowed(Store store, GameType gt) { // there must be a create transaction if(!pTrans.transactions.contains(new TransactionComparatorByTType(new Create()))){ addMessage("Player did not create a derivative"); return false; }else{ List createTrans = pTrans.transactions.filter(new TransactionComparatorByTType(new 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); // created derivative must not be of existing types if(dersForSale.contains(new SameDerivativeByType(cTrans.deriv))){ addMessage("Created a derivative of an existing type"); return false; } // Created derivatives must have 0 <= price <= 1 if(cTrans.deriv.price.val > 1 || cTrans.deriv.price.val < 0){ addMessage("Illegally priced derivative"); return false; } // Check additional rules based on GameType if(gt.compareTo(GameType.BASEBALL) == 0) { } else if(gt.compareTo(GameType.FASTPITCH2)==0) { if(cTrans.deriv.type.instances.length() > 2) { addMessage("Violated Fastpitch Level 2 Independent creation rule."); return false; } } else if(gt.compareTo(GameType.FASTPITCH) == 0) { } else if(gt.compareTo(GameType.SLOWPITCH) == 0) { return implicationChain(cTrans.deriv.type); } else if(gt.compareTo(GameType.TBALL) == 0) { if(cTrans.deriv.type.instances.length() > 1) { addMessage("Violated T-Ball creation rule."); return false; } } } } return true; } /** Is t and implication chain? */ protected boolean implicationChain(Type t) { TypeInstance prev = null; List slti = t.instances.sort(new TypeInstanceComparator()); for(TypeInstance cur : slti){ if(prev != null){ if(!implies(prev, cur)){ addMessage("Violated Slowpitch creation rule."); return false; } } prev = cur; } return true; } /** Does a imply b? */ protected static boolean implies(TypeInstance a, TypeInstance b){ return (a.r.v & b.r.v) == a.r.v; } /** 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(new TransactionComparatorByTType(new Deliver())); List actualDeliveryDers = actualDeliveryTrans.map(new List.Map(){ public Derivative map(Transaction t){ return t.deriv; }}); 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 */ 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(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(new TransactionComparatorByTType(new Finish())); List actualFinishDers = List.create(); for(Transaction trans: actualFinishTrans){ actualFinishDers = actualFinishDers.push(trans.deriv); } 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; } }