Several predicate classes. AND NOT OR Directional Quantity Combiner predicate classes can hold predicate objects. AND has LIST of predicates to AND NOT has a single predicate. OR is like AND. Test predicate classes Directional predicate has a dictionary of direction keys and type enums as values The "holds" method checks to see if these match, null entries in dictionary are ignored Quantity predicate has a int value which is a number of Class qtpr: color c quantity q operator enumerated type use case/switch statement to do comparison based on operator Actually a shitton of ifs The core method of predicate is "holds". This checks whether or not the predicate is true. Each predicate's hold method takes a neighbors parameter that is the dictionary of neighbors. State is a dictionary {direction:neighbor} Notes about the rule system: Species The rule class has a predicate and an instruction. And an apply method. The instruction class has a direction to move, a collision priority, and a state to enter at that place. When apply does an instruction, it passes a "change" to a change dictionary. This exists in the magical nether. This dictionary has keys which are coordinate tuples, and values which are tuples which are a priority and a state to change to. This change dictionary will run conflict resolution on itself after all the cells in the board have processed. If there are multiple changes for a location, the one with the highest priority wins. If there is a tie, the cell does not change. Onward to species! ********************************************************************* Rule System: ************ The rule class has predicate field and an instruction field. The instruction class has a color and a priority. When a rule runs the 'apply' method, it will take a state, and return true or false. If the return value is false, pass that back to species. If the return value is true, generate a "change" object and pass +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ The Actual Rules System: At the end of every cycle, a cell can take exactly one action. Actions can be: stay, move, or die. A cell has a list of rules to be evaluated in any order. Every rule generates an action, which has a priority. While a cell evaluates its rules, it keeps track of the highest priority action. If a rule is evalulated whose action should be run BUT it has the same priority as the current action to actually do, the action to actually do is set to stay with the priority of the colliding rules. The rest of the rules are then evaluated. When a cell has finished evaluating its rules, it submits the selected action to the MASTER CHANGE LIST (which is probably actually a dictionary) A change takes the format of a coordinate pair, the event that should happen at that location, and a priority. "Die" changes always have a priority of -1 so they cannot prevent something from moving into a location. (0 is the nominal lowest priority.) The master list uses the same priority evaluation strategy as the rule evaluator. If two changes have the same location and priority, they become a STAY change with that priority. The way the MASTER CHANGE LIST is generated: 1) The "getChangeList" method of the board is run. 2) For each cell in the board, getChanges is run. This calls the species' getChanges method, with the parameters being a state dictionary and a location tuple. The return type of this method is a tuple, and each element of it is a change object. 3) Each element of this tuple is appended to the MASTER CHANGE LIST. The way the changesList is generated: This starts with the "getChanges" method being run, as described above. At this point each rule in the species rule list is evaluated. This is done with the testRule method. This method takes a state dictionary and a location tuple as parameters. The return type is a change tuple. A change tuple may contain one or two actions. It may contain a single die or a single instantiate or a single stay or a die and an instantiate. When a cell is evaluating its rules, all rules in the current tuple to submit have the same priority, even though die orders will eventually have a priority of -1. This makes sense because it is an edge case we can explain by drawing a picture. When a cell is ready to submit its order tuple, it will change the priority of any die orders to 0. In algorithm form: 1. call to getChanges() 2. cell makes a call to testRule, giving it a state dictionary and location tuple 3. testRule evaluates the first rule and creates a change tuple. The first item in the change tuple has the highest priority. This means that die orders with priority -1 would appear in the second slot. 4. The rest of the rules are evalated. If a rule holds whose priority equals the current tuple's first item's priority, 5a. If a rule holds whose priority is greater than the current tuple's first item's priority, 5b. 5a. Create a new change tuple whose action is "STAY" and whose priority is the priority of the old change tuple 5b. Create a new change tuple with the appropriate action(s) and the winning priority. 6. Return the tuple. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++== Predicate evaluation Predicates are by design left-associative. A species has a set of rules. Each rule contains one "root" predicate. This root predicate contains a number of subpredicates. This tree is processed recursively. Each rule also contains an instruction, which is the change tuple to return. This state dictionary is created by the board when it calls getChanges. This is done because the board is what posesses the information necessary to make it, and the board is what initiates the change-getting process. State dictionary: contains mappings of directions to neighbors and neighbor colors to their counts. How to make one: 1. For each direction in the list "n, ne, e, se, s, sw, w, nw", look at the neighbor. If there is a neighbor, include a mapping in the state dictionary from that direction to the color (enumrated type) of the neighbor. 2. Check to see if the enumerated type of the neighbor being mapped is in the dictionary as a key. 3a. If it is not a key, map it to 1. 3b. If it is a key, increment the count that it maps to by 1. 4. Return the dictionary. To evaluate a predicate: 1. Get the state dictionary for the cell. 2. Call the holds method of the root predicate, passing it the state. 3. Recursively, all the childl holds methods are called with the state dictionary as a parameter. 4. They return booleans. 5. The bottom of the call stack returns true or false. 6. Done.