package org.ggp.base.util.gdl.scrambler; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import org.ggp.base.util.game.Game; import org.ggp.base.util.game.GameRepository; import org.ggp.base.util.gdl.GdlVisitor; import org.ggp.base.util.gdl.GdlVisitors; import org.ggp.base.util.gdl.factory.GdlFactory; import org.ggp.base.util.gdl.factory.exceptions.GdlFormatException; import org.ggp.base.util.gdl.grammar.Gdl; import org.ggp.base.util.gdl.grammar.GdlConstant; import org.ggp.base.util.gdl.grammar.GdlVariable; import org.ggp.base.util.statemachine.implementation.prover.ProverStateMachine; import org.ggp.base.util.symbol.factory.exceptions.SymbolFormatException; import org.junit.Assert; import org.junit.Test; /** * Unit tests for the GdlScrambler class, which provides a way * to scramble and unscramble Gdl objects without changing the * underlying physics of the games they represent. * * @author Sam */ public class GdlScramblerTest extends Assert { /** * When scrambling is disabled, the "NoOpGdlScrambler" is used. This class * simply renders the Gdl and parses it in the naive way, without doing any * special modification. This is the trivial case of "scrambling". */ @Test public void testNoOpScrambler() throws GdlFormatException, SymbolFormatException { runScramblerTest(new NoOpGdlScrambler()); } /** * When scrambling is enabled, the "MappingGdlScrambler" is used. This class * systematically replaces all of the constant and variable names in the Gdl * with scrambled versions, drawing new random tokens first from a list of * English words, and appending suffixes when the original list is exhausted. */ @Test public void testMappingScrambler() throws GdlFormatException, SymbolFormatException { runScramblerTest(new MappingGdlScrambler(new Random())); } /** * Furthermore, the mapping scrambler can be initialized with a Random object, * which can be used to ensure deterministic, reproducible scrambling. This can * be used to scramble a specific match in the same way, even if it stored and * reloaded in the meantime. */ @Test public void testMappingScramblerConsistency() { GdlScrambler aScrambler = new MappingGdlScrambler(new Random(123)); GdlScrambler bScrambler = new MappingGdlScrambler(new Random(123)); GdlScrambler cScrambler = new MappingGdlScrambler(new Random(234)); GameRepository repo = GameRepository.getDefaultRepository(); for (String gameKey : repo.getGameKeys()) { Game game = repo.getGame(gameKey); StringBuilder aScrambledRules = new StringBuilder(); StringBuilder bScrambledRules = new StringBuilder(); StringBuilder cScrambledRules = new StringBuilder(); StringBuilder dScrambledRules = new StringBuilder(); StringBuilder eScrambledRules = new StringBuilder(); StringBuilder fScrambledRules = new StringBuilder(); for(Gdl rule : game.getRules()) { aScrambledRules.append(aScrambler.scramble(rule)+"\n"); bScrambledRules.append(bScrambler.scramble(rule)+"\n"); cScrambledRules.append(cScrambler.scramble(rule)+"\n"); } for(Gdl rule : game.getRules()) { dScrambledRules.append(aScrambler.scramble(rule)+"\n"); eScrambledRules.append(bScrambler.scramble(rule)+"\n"); fScrambledRules.append(cScrambler.scramble(rule)+"\n"); } assertEquals(aScrambledRules.toString(), bScrambledRules.toString()); assertEquals(aScrambledRules.toString(), dScrambledRules.toString()); assertEquals(aScrambledRules.toString(), eScrambledRules.toString()); assertFalse(aScrambledRules.toString().equals(cScrambledRules.toString())); assertEquals(cScrambledRules.toString(), fScrambledRules.toString()); } } private void runScramblerTest(GdlScrambler scrambler) throws SymbolFormatException, GdlFormatException { GameRepository repo = GameRepository.getDefaultRepository(); for (String gameKey : repo.getGameKeys()) { Game game = repo.getGame(gameKey); List<Gdl> theScrambledRules = new ArrayList<Gdl>(); for(Gdl rule : game.getRules()) { String renderedRule = rule.toString(); String renderedScrambledRule = scrambler.scramble(rule).toString(); String renderedUnscrambledRule = scrambler.unscramble(renderedScrambledRule).toString(); theScrambledRules.add(GdlFactory.create(renderedScrambledRule)); // If the scrambler claims that it scrambles the game, then the // scrambled rules should be different than the original rules. // Otherwise they should be identical. if (scrambler.scrambles() && !isNeverScrambled(rule)) { assertNotEquals(gameKey, renderedRule, renderedScrambledRule); } else { assertEquals(gameKey, renderedRule, renderedScrambledRule); } // One important property for any scrambler is that the original // and the unscrambled Gdl must be the same. This guarantees that // the server can correctly unscramble responses from the players. assertEquals(gameKey, renderedRule, renderedUnscrambledRule); } // An important property for any scrambler is that the scrambled rules // have the same physics as the regular rules. For example, the number // of roles in each game should be the same, and the number of facts // that are true in the initial state should be the same. There could // be more thorough verification here, like looking at the number of // legal joint moves in the first state, or simulating entire matches, // but that would be expensive. ProverStateMachine pNormal = new ProverStateMachine(); ProverStateMachine pScrambled = new ProverStateMachine(); pNormal.initialize(game.getRules()); pScrambled.initialize(theScrambledRules); assertEquals(gameKey, pNormal.getRoles().size(), pScrambled.getRoles().size()); assertEquals(gameKey, pNormal.getInitialState().getContents().size(), pScrambled.getInitialState().getContents().size()); } } private boolean isNeverScrambled(Gdl gdl) { final AtomicBoolean containsMappedConstant = new AtomicBoolean(false); GdlVisitors.visitAll(gdl, new GdlVisitor() { @Override public void visitConstant(GdlConstant constant) { if (MappingGdlScrambler.shouldMap(constant.getValue())) { containsMappedConstant.set(true); } } @Override public void visitVariable(GdlVariable variable) { //Right now, these are (effectively) always mapped containsMappedConstant.set(true); } }); return !containsMappedConstant.get(); } }