diff options
| -rw-r--r-- | README.md | 80 | ||||
| -rw-r--r-- | example.py | 221 | ||||
| -rw-r--r-- | gothpick.py | 169 |
3 files changed, 470 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..c61477a --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# gothic 1 remake lockpicking helper tool +alternative title: +> big titty gothicc gf + +To whoever came up with the change to old lockpicking system for the remake: +you're a psycho +I love it, 10/10 +you're the best there ever was, keep up the good work + +Unfortunately my ADHD craves for instant gratification so I came up with this shitty solver. + +## assumptions +1. the interlocking of each block doesn't change +2. the correct pin slot is always the middle one +## Usage +*tests are provided in example.py* + +### 1. prepare input +you'll need to define: +- how many blocks there are (`numOfBlocks`) +- how many pin slots per block there are (`numOfPinslots`) +- for each block: + - which pin slot the pin sits in (`initialPositions`) + - which block(s) move and in which direction during sliding (`edges`) + +bit of explanation for `edges`: +- when you move the block left or right, other block can move as well (interlocked) +- on top of that, they can move WITH or OPPOSITE TO the selected block +- write down the block numbers that are interlocked + - if the block moves OPPOSITE TO selected block, then prefix it's number with minus sign `-` (negative integer) + +### parse input +##### option A - use built-in parser +run the gothpick.py program and type-in your prepared input +``` +python3 gothpick.py +``` +type in the input when prompted + +##### option B - import the library +import the library and type in your input outside the gothpick.py +``` +import gothpick + +IterateStepsManually = False +numOfBlocks = 5 +numOfPins = 7 +initialPositions = { +0:6, +1:3, +2:2, +3:1, +4:7 +} + +edges = { +0:[(1,0),(1,3)], +1:[(1,1),(1,4)], +2:[(1,2),(-1,4)], +3:[(1,3),(-1,4),(-1,2),(1,1)], +4:[(1,4)] +} + +gothpick.PrintInput(initialPositions,edges) +solvedpath = gothpick.lockpick(numOfBlocks,numOfPins,initialPositions,edges) +gothpick.TranslateResults(solvedpath,IterateStepsManually) +``` + +### use the solution +whether you +- were asked (option A) +- passed an argument (option B, `IterateStepsManually`) +you had to decide if you want **step-by-step** or **complete instructions** + +**step-by-step** +one step at a time, press any key to display next step +(i prefer this one) +**complete instructions** +prints all instructions at once +useful if you want to send the solution to a girl and impress her i guess
\ No newline at end of file diff --git a/example.py b/example.py new file mode 100644 index 0000000..7fb2dbe --- /dev/null +++ b/example.py @@ -0,0 +1,221 @@ +import gothpick + +# example 01 +# scatty's chest in his house +numOfBlocks = 5 +numOfPins = 7 +initialPositions = { +0:6, +1:3, +2:2, +3:1, +4:7 +} + +edges = { +0:[(1,0),(1,3)], +1:[(1,1),(1,4)], +2:[(1,2),(-1,4)], +3:[(1,3),(-1,4),(-1,2),(1,1)], +4:[(1,4)] +} + +gothpick.PrintInput(initialPositions,edges) +solvedpath = gothpick.lockpick(numOfBlocks,numOfPins,initialPositions,edges) +gothpick.TranslateResults(solvedpath,False) + +''' +Gothic 1 Remake - lockpick help +_______________________________ +step-by-step [y] or complete list [n]? : n +How many blocks?: 5 +How many pin slots in a block?: 7 +Initial State +enter initial pin position for block 1: 6 +enter initial pin position for block 2: 3 +enter initial pin position for block 3: 2 +enter initial pin position for block 4: 1 +enter initial pin position for block 5: 7 +connections +format: comma separated block IDs +if the connected block moves in opposite direction, prefix with - +basically positive and negative integers +enter connections for block 1: 1,4 +enter connections for block 2: 2,5 +enter connections for block 3: 3,-5 +enter connections for block 4: 4,-5,-3,2 +enter connections for block 5: 5 +========SETUP======== +pins + 1: 6 + 2: 3 + 3: 2 + 4: 1 + 5: 7 +connections + 1: 1,4 + 2: 2,5 + 3: 3,-5 + 4: 4,-5,-3,2 + 5: 5 +===================== +01/31: 3 -> LEFT + +02/31: 3 -> LEFT + +03/31: 4 -> LEFT + +04/31: 1 -> RIGHT + +05/31: 3 -> LEFT + +06/31: 5 -> LEFT + +07/31: 5 -> LEFT + +08/31: 2 -> RIGHT + +09/31: 4 -> LEFT + +10/31: 5 -> LEFT + +11/31: 1 -> RIGHT + +12/31: 3 -> LEFT + +13/31: 5 -> LEFT + +14/31: 5 -> LEFT + +15/31: 2 -> RIGHT + +16/31: 4 -> LEFT + +17/31: 5 -> LEFT + +18/31: 3 -> LEFT + +19/31: 5 -> LEFT + +20/31: 5 -> LEFT + +21/31: 2 -> RIGHT + +22/31: 4 -> LEFT + +23/31: 5 -> LEFT + +24/31: 3 -> LEFT + +25/31: 5 -> LEFT + +26/31: 5 -> LEFT + +27/31: 2 -> RIGHT + +28/31: 4 -> LEFT + +29/31: 5 -> LEFT + +30/31: 3 -> LEFT + +31/31: 5 -> LEFT + +Done. + +''' + +######################################################################## +# example 02 +# guard post above torrez +numOfBlocks = 5 +numOfPins = 7 + +initialPositions = { +0:1, +1:6, +2:1, +3:1, +4:6 +} + +edges = { +0:[(1,0),(-1,2),(1,3)], +1:[(1,1),(1,2)], +2:[(1,2),(1,1),(-1,0)], +3:[(1,3),(-1,1),(-1,0)], +4:[(1,4),(1,3),(-1,1)] +} +gothpick.PrintInput(initialPositions,edges) +solvedpath = gothpick.lockpick(numOfBlocks,numOfPins,initialPositions,edges) +gothpick.TranslateResults(solvedpath,False) + +''' +expected output: +01/33: 5 -> LEFT + +02/33: 2 -> LEFT + +03/33: 3 -> RIGHT + +04/33: 4 -> LEFT + +05/33: 2 -> LEFT + +06/33: 3 -> RIGHT + +07/33: 2 -> LEFT + +08/33: 4 -> LEFT + +09/33: 2 -> LEFT + +10/33: 3 -> RIGHT + +11/33: 2 -> LEFT + +12/33: 3 -> RIGHT + +13/33: 2 -> LEFT + +14/33: 3 -> RIGHT + +15/33: 2 -> LEFT + +16/33: 4 -> LEFT + +17/33: 5 -> RIGHT + +18/33: 3 -> RIGHT + +19/33: 2 -> LEFT + +20/33: 4 -> LEFT + +21/33: 5 -> RIGHT + +22/33: 3 -> RIGHT + +23/33: 2 -> LEFT + +24/33: 4 -> LEFT + +25/33: 5 -> RIGHT + +26/33: 3 -> RIGHT + +27/33: 2 -> LEFT + +28/33: 4 -> LEFT + +29/33: 1 -> RIGHT + +30/33: 3 -> RIGHT + +31/33: 2 -> LEFT + +32/33: 3 -> RIGHT + +33/33: 2 -> LEFT + +''' diff --git a/gothpick.py b/gothpick.py new file mode 100644 index 0000000..056514a --- /dev/null +++ b/gothpick.py @@ -0,0 +1,169 @@ +import heapq + +# calculate how far away all pins are from the middle +# used for pathfinding +def CalculateDistance(target, PinList): + distance = 0; + for pin in PinList: + distance += abs(target - pin) + return distance + +# pathinding function +def lockpick(blocks, pinnumber, pinpos,connections): + TargetPosition = 1+ pinnumber // 2; #middle pin slot + initialpins = [0]*blocks + for p in pinpos: + initialpins[p]=pinpos[p] + # queue items: + # distance from solution + # pinpositions + # takenpath + queue = [(-1,initialpins,[])] + while queue: + dist,pins,path = heapq.heappop(queue) + # all are in the middle? + if dist == 0: + return path + subqueue = [] + #for each block + for block in range(blocks): + #in each direction + for blockdir in [-1,1]: + #check if all connected blocks can be moved + CanMove = True + PinsAfterMove = [pin for pin in pins] + for conndir,connection in connections[block]: + PinsAfterMove[connection] += conndir*blockdir + CanMove *= PinsAfterMove[connection] in range(1,pinnumber+1) + if not CanMove: + break + if CanMove: + subqueue.append([block,blockdir,PinsAfterMove]) + + for QueueBlock,QueueDirection,UpdatedPins in subqueue: + #prevent going back and forth + if len(path) > 0: + if (QueueBlock,-QueueDirection) == path[-1]: + continue; + + UpdatedDistance = CalculateDistance(TargetPosition,UpdatedPins) + UpdatedPath = [step for step in path]+[(QueueBlock,QueueDirection)] + heapq.heappush(queue,(UpdatedDistance,UpdatedPins,UpdatedPath)) + + return [] + + + +# it's reversed from the pin point of view +verbosedict = {-1:"RIGHT",1:"LEFT "} + +def TranslateResults(PathList,incremental=False): + TotalStepCount = len(PathList) + if TotalStepCount == 0: + print("did not find a solution. Make sure the input is correct") + return + spacefillnum = len(str(TotalStepCount)) + step_i = 0 + for pathblock,pathmove in PathList: + step_i += 1 + b = pathblock + 1 + s = verbosedict[pathmove] + print(f'{str(step_i).zfill(spacefillnum)}/{TotalStepCount}:\t{b} -> {s}') + if incremental: + input() + else: + print() + + + +def ParseInputs(): + increm = None + while increm == None: + decision = input("step-by-step [y] or complete list [n]? : ") + decision = decision.casefold() + if decision not in ["y","n"]: + print("invalid input") + continue + increm = decision == "y" + #if not increm: + + NumberOfBlocks = None + NumberOfBlocks = input("How many blocks?: ") + NumberOfBlocks = int(NumberOfBlocks) + NumberOfPinslots = None + NumberOfPinslots = input("How many pin slots in a block?: ") + NumberOfPinslots = int(NumberOfPinslots) + StartingPositions = {} + BlockConnections = {} + print("Initial State") + for b in range(NumberOfBlocks): + blockpinpos = input(f'enter initial pin position for block {b+1}: ') + StartingPositions[b] = int(blockpinpos) + print("connections") + print("format: comma separated block IDs") + print("if the connected block moves in opposite direction, prefix with -") + print("basically positive and negative integers") + for b in range(NumberOfBlocks): + BlockMapInput = input(f'enter connections for block {b+1}: ') + BlockMap = [int(bm) for bm in BlockMapInput.replace("\n","").split(",")] + BlockMap = [tuple([int(bm)//abs(int(bm)),abs(bm)-1]) for bm in BlockMap] + BlockConnections[b] = [bl for bl in BlockMap] + + PrintInput(StartingPositions,BlockConnections) + if increm: + print("[step-by-step] the solution will be displayed one step at a time") + print("To show next step, press any key") + return increm, NumberOfBlocks, NumberOfPinslots, StartingPositions,BlockConnections + + +def PrintInput(pinpos,edges): + print("========SETUP========") + print("pins") + for p_i,pin in enumerate(pinpos): + print(f' {p_i+1}: {pinpos[pin]}') + + print("connections") + for p_i in range(len(pinpos)): + edgeString = ",".join([ f'{e[0]*(1+e[1])}' for e in edges[p_i]]) + print(f' {p_i+1}: {edgeString}') + print("=====================") + +def RunWithConsoleInput(): + FullPrint, BlockCount, PinCount, StartingPos, BlockGraph = ParseInputs() + solution = lockpick(BlockCount, PinCount, StartingPos, BlockGraph) + TranslateResults(solution,FullPrint) + print("Done.") + + + +''' +# example +# scatty's chest in his house +numOfBlocks = 5 +numOfPins = 7 +initialPositions = { +0:6, +1:3, +2:2, +3:1, +4:7 +} + +edges = { +0:[(1,0),(1,3)], +1:[(1,1),(1,4)], +2:[(1,2),(-1,4)], +3:[(1,3),(-1,4),(-1,2),(1,1)], +4:[(1,4)] +} + +solvedpath = lockpick(numOfBlocks,numOfPins,initialPositions,edges) +TranslateResults(solvedpath,False) +PrintInput(initialPositions,edges) +#''' + +if __name__ == "__main__": + print("Gothic 1 Remake - lockpick help") + print("_______________________________") + RunWithConsoleInput() + |
