summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md80
-rw-r--r--example.py221
-rw-r--r--gothpick.py169
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()
+