249 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			249 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
 | |
| import copy
 | |
| from worlds.sm.variaRandomizer.utils import log
 | |
| from worlds.sm.variaRandomizer.logic.smbool import SMBool, smboolFalse
 | |
| from worlds.sm.variaRandomizer.logic.smboolmanager import SMBoolManager
 | |
| from collections import Counter
 | |
| 
 | |
| class ItemLocation(object):
 | |
|     __slots__ = ( 'Item', 'Location', 'Accessible' )
 | |
| 
 | |
|     def __init__(self, Item=None, Location=None, accessible=True):
 | |
|         self.Item = Item
 | |
|         self.Location = Location
 | |
|         self.Accessible = accessible
 | |
| 
 | |
|     def json(self):
 | |
|         return {'Item': self.Item.json(), 'Location': self.Location.json()}
 | |
| 
 | |
| def getItemListStr(items):
 | |
|     return str(dict(Counter(["%s/%s" % (item.Type,item.Class) for item in items])))
 | |
| 
 | |
| def getLocListStr(locs):
 | |
|     return str([loc.Name for loc in locs])
 | |
| 
 | |
| def getItemLocStr(itemLoc):
 | |
|     return itemLoc.Item.Type + " at " + itemLoc.Location.Name
 | |
| 
 | |
| def getItemLocationsStr(itemLocations):
 | |
|     return str([getItemLocStr(il) for il in itemLocations])
 | |
| 
 | |
| class ContainerSoftBackup(object):
 | |
|     def __init__(self, container):
 | |
|         self.itemLocations = container.itemLocations[:]
 | |
|         self.itemPool = container.itemPool[:]
 | |
|         self.unusedLocations = container.unusedLocations[:]
 | |
|         self.currentItems = container.currentItems[:]
 | |
| 
 | |
|     def restore(self, container, resetSM=True):
 | |
|         # avoid costly deep copies of locations
 | |
|         container.itemLocations = self.itemLocations[:]
 | |
|         container.itemPool = self.itemPool[:]
 | |
|         container.unusedLocations = self.unusedLocations[:]
 | |
|         container.currentItems = self.currentItems[:]
 | |
|         if resetSM:
 | |
|             container.sm.resetItems()
 | |
|             container.sm.addItems([it.Type for it in container.currentItems])
 | |
| 
 | |
| # Holds items yet to place (itemPool), locations yet to fill (unusedLocations),
 | |
| # placed items/locations (itemLocations).
 | |
| # If logic is needed, also holds a SMBoolManager (sm) and collected items so far
 | |
| # (collectedItems)
 | |
| class ItemLocContainer(object):
 | |
|     def __init__(self, sm, itemPool, locations):
 | |
|         self.sm = sm
 | |
|         self.itemLocations = []
 | |
|         self.unusedLocations = locations
 | |
|         self.currentItems = []
 | |
|         self.itemPool = itemPool
 | |
|         self.itemPoolBackup = None
 | |
|         self.unrestrictedItems = set()
 | |
|         self.log = log.get('ItemLocContainer')
 | |
|         self.checkConsistency()
 | |
| 
 | |
|     def checkConsistency(self):
 | |
|         assert len(self.unusedLocations) == len(self.itemPool), "Item({})/Locs({}) count mismatch".format(len(self.itemPool), len(self.unusedLocations))
 | |
| 
 | |
|     def __eq__(self, rhs):
 | |
|         eq = self.currentItems == rhs.currentItems
 | |
|         eq &= getLocListStr(self.unusedLocations) == getLocListStr(rhs.unusedLocations)
 | |
|         eq &= self.itemPool == rhs.itemPool
 | |
|         eq &= getItemLocationsStr(self.itemLocations) == getItemLocationsStr(rhs.itemLocations)
 | |
| 
 | |
|         return eq
 | |
| 
 | |
|     def __copy__(self):
 | |
|         locs = copy.copy(self.unusedLocations)
 | |
|         # we don't copy restriction state on purpose: it depends on
 | |
|         # outside context we don't want to bring to the copy
 | |
|         ret = ItemLocContainer(SMBoolManager(self.sm.player, self.sm.maxDiff, self.sm.onlyBossLeft),
 | |
|                                self.itemPoolBackup[:] if self.itemPoolBackup != None else self.itemPool[:],
 | |
|                                locs)
 | |
|         ret.currentItems = self.currentItems[:]
 | |
|         ret.unrestrictedItems = copy.copy(self.unrestrictedItems)
 | |
|         ret.itemLocations = [ ItemLocation(
 | |
|             il.Item,
 | |
|             copy.copy(il.Location)
 | |
|         ) for il in self.itemLocations ]
 | |
|         ret.sm.addItems([item.Type for item in ret.currentItems])
 | |
|         return ret
 | |
| 
 | |
|     # create a new container based on slice predicates on items and
 | |
|     # locs.  both predicates must result in a consistent container
 | |
|     # (same number of unused locations and not placed items)
 | |
|     def slice(self, itemPoolCond, locPoolCond):
 | |
|         assert self.itemPoolBackup is None, "Cannot slice a constrained container"
 | |
|         locs = self.getLocs(locPoolCond)
 | |
|         items = self.getItems(itemPoolCond)
 | |
|         cont = ItemLocContainer(self.sm, items, locs)
 | |
|         cont.currentItems = self.currentItems
 | |
|         cont.itemLocations = self.itemLocations
 | |
|         return copy.copy(cont)
 | |
| 
 | |
|     # transfer collected items/locations to another container
 | |
|     def transferCollected(self, dest):
 | |
|         dest.currentItems = self.currentItems[:]
 | |
|         dest.sm = SMBoolManager(self.sm.player, self.sm.maxDiff, self.sm.onlyBossLeft)
 | |
|         dest.sm.addItems([item.Type for item in dest.currentItems])
 | |
|         dest.itemLocations = copy.copy(self.itemLocations)
 | |
|         dest.unrestrictedItems = copy.copy(self.unrestrictedItems)
 | |
| 
 | |
|     # reset collected items/locations. if reassignItemLocs is True,
 | |
|     # will re-fill itemPool and unusedLocations as they were before
 | |
|     # collection
 | |
|     def resetCollected(self, reassignItemLocs=False):
 | |
|         self.currentItems = []
 | |
|         if reassignItemLocs == False:
 | |
|             self.itemLocations = []
 | |
|         else:
 | |
|             while len(self.itemLocations) > 0:
 | |
|                 il = self.itemLocations.pop()
 | |
|                 self.itemPool.append(il.Item)
 | |
|                 self.unusedLocations.append(il.Location)
 | |
|         self.unrestrictedItems = set()
 | |
|         self.sm.resetItems()
 | |
| 
 | |
|     def dump(self):
 | |
|         return "ItemPool(%d): %s\nLocPool(%d): %s\nCollected: %s" % (len(self.itemPool), getItemListStr(self.itemPool), len(self.unusedLocations), getLocListStr(self.unusedLocations), getItemListStr(self.currentItems))
 | |
| 
 | |
|     # temporarily restrict item pool to items fulfilling predicate
 | |
|     def restrictItemPool(self, predicate):
 | |
|         assert self.itemPoolBackup is None, "Item pool already restricted"
 | |
|         self.itemPoolBackup = self.itemPool
 | |
|         self.itemPool = [item for item in self.itemPoolBackup if predicate(item)]
 | |
|         self.log.debug("restrictItemPool: "+getItemListStr(self.itemPool))
 | |
| 
 | |
|     # remove a placed restriction
 | |
|     def unrestrictItemPool(self):
 | |
|         assert self.itemPoolBackup is not None, "No pool restriction to remove"
 | |
|         self.itemPool = self.itemPoolBackup
 | |
|         self.itemPoolBackup = None
 | |
|         self.log.debug("unrestrictItemPool: "+getItemListStr(self.itemPool))
 | |
| 
 | |
|     def removeLocation(self, location):
 | |
|         if location in self.unusedLocations:
 | |
|             self.unusedLocations.remove(location)
 | |
| 
 | |
|     def removeItem(self, item):
 | |
|         self.itemPool.remove(item)
 | |
|         if self.itemPoolBackup is not None:
 | |
|             self.itemPoolBackup.remove(item)
 | |
| 
 | |
|     # collect an item at a location. if pickup is True, also affects logic (sm) and collectedItems
 | |
|     def collect(self, itemLocation, pickup=True):
 | |
|         item = itemLocation.Item
 | |
|         location = itemLocation.Location
 | |
|         if not location.restricted:
 | |
|             self.unrestrictedItems.add(item.Type)
 | |
|         if pickup == True:
 | |
|             self.currentItems.append(item)
 | |
|             self.sm.addItem(item.Type)
 | |
|         self.removeLocation(location)
 | |
|         self.itemLocations.append(itemLocation)
 | |
|         self.removeItem(item)
 | |
| 
 | |
|     def isPoolEmpty(self):
 | |
|         return len(self.itemPool) == 0
 | |
| 
 | |
|     def getNextItemInPool(self, t):
 | |
|         return next((item for item in self.itemPool if item.Type == t), None)
 | |
| 
 | |
|     def getNextItemInPoolMatching(self, predicate):
 | |
|         return next((item for item in self.itemPool if predicate(item) == True), None)
 | |
| 
 | |
|     def hasItemTypeInPool(self, t):
 | |
|         return any(item.Type == t for item in self.itemPool)
 | |
| 
 | |
|     def hasItemInPool(self, predicate):
 | |
|         return any(predicate(item) == True for item in self.itemPool)
 | |
| 
 | |
|     def hasItemCategoryInPool(self, cat):
 | |
|         return any(item.Category == cat for item in self.itemPool)
 | |
| 
 | |
|     def getNextItemInPoolFromCategory(self, cat):
 | |
|         return next((item for item in self.itemPool if item.Category == cat), None)
 | |
| 
 | |
|     def getAllItemsInPoolFromCategory(self, cat):
 | |
|         return [item for item in self.itemPool if item.Category == cat]
 | |
| 
 | |
|     def countItemTypeInPool(self, t):
 | |
|         return sum(1 for item in self.itemPool if item.Type == t)
 | |
| 
 | |
|     def countItems(self, predicate):
 | |
|         return sum(1 for item in self.itemPool if predicate(item) == True)
 | |
| 
 | |
|     # gets the items pool in the form of a dicitionary whose keys are item types
 | |
|     # and values list of items of this type
 | |
|     def getPoolDict(self):
 | |
|         poolDict = {}
 | |
|         for item in self.itemPool:
 | |
|             if item.Type not in poolDict:
 | |
|                 poolDict[item.Type] = []
 | |
|             poolDict[item.Type].append(item)
 | |
|         return poolDict
 | |
| 
 | |
|     def getLocs(self, predicate):
 | |
|         return [loc for loc in self.unusedLocations if predicate(loc) == True]
 | |
| 
 | |
|     def getItems(self, predicate):
 | |
|         return [item for item in self.itemPool if predicate(item) == True]
 | |
| 
 | |
|     def getUsedLocs(self, predicate):
 | |
|         return [il.Location for il in self.itemLocations if predicate(il.Location) == True]
 | |
| 
 | |
|     def getItemLoc(self, loc):
 | |
|         for il in self.itemLocations:
 | |
|             if il.Location == loc:
 | |
|                 return il
 | |
| 
 | |
|     def getCollectedItems(self, predicate):
 | |
|         return [item for item in self.currentItems if predicate(item) == True]
 | |
| 
 | |
|     def hasUnrestrictedLocWithItemType(self, itemType):
 | |
|         return itemType in self.unrestrictedItems
 | |
| 
 | |
|     def getLocsForSolver(self):
 | |
|         locs = []
 | |
|         for il in self.itemLocations:
 | |
|             loc = il.Location
 | |
|             self.log.debug("getLocsForSolver: {}".format(loc.Name))
 | |
|             # filter out restricted locations
 | |
|             if loc.restricted:
 | |
|                 self.log.debug("getLocsForSolver: restricted, remove {}".format(loc.Name))
 | |
|                 continue
 | |
|             loc.itemName = il.Item.Type
 | |
|             locs.append(loc)
 | |
|         return locs
 | |
| 
 | |
|     def cleanLocsAfterSolver(self):
 | |
|         # restricted locs can have their difficulty set, which can cause them to be reported in the
 | |
|         # post randomization warning message about locs with diff > max diff.
 | |
|         for il in self.itemLocations:
 | |
|             loc = il.Location
 | |
|             if loc.restricted and loc.difficulty == True:
 | |
|                 loc.difficulty = smboolFalse
 | |
| 
 | |
|     def getDistinctItems(self):
 | |
|         itemTypes = {item.Type for item in self.itemPool}
 | |
|         return [self.getNextItemInPool(itemType) for itemType in itemTypes]
 | 
