Created
July 26, 2026 11:19
-
-
Save Chlumsky/92c7b7ddf9364f5fa478901e53052b59 to your computer and use it in GitHub Desktop.
Gothic 1 Remake lockpick solver
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //\\//\\//\\//\\//\\//\\//\\//\\//\\//\\// | |
| // GOTHIC 1 REMAKE LOCKPICK SOLVER | |
| // Uses bidirectional Dijkstra's algorithm | |
| // This version only optimizes total moves | |
| // Made by Viktor Chlumsky on 2026-07-26 | |
| /* EXAMPLE RUN (new camp tavern underwater chest): | |
| Initial positions (0 to 6): 41623 | |
| A: -C | |
| B: +A -E | |
| C: +BD -E | |
| D: +E -B | |
| E: -CD | |
| 30-step solution: -2E -2A +B -4C +2B +4D +3B -3A -3C +3B -3A | |
| (linkages can also be abbreviated as "c-be" etc.) */ | |
| #include <cstdlib> | |
| #include <cstdio> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <vector> | |
| #include <queue> | |
| #include <algorithm> | |
| #define LEFTMOST_POSITION 0 // change to 1 to specify initial positions as 1 to 7 instead | |
| #define ONE_WAY_SEARCH 0 // switches to unidirectional Dijkstra | |
| #define MAX_GATES 7 | |
| /// Each 3 bits (starting from LSB) represent 1 gate with values 1 to 7 (0 indicates invalid or unset state) | |
| typedef std::uint32_t State; | |
| /// Each 3 bits (starting from LSB) are the delta for each gate, with valid values 0, 1 and 7 (-1) | |
| typedef std::uint32_t Linkage; | |
| /// Least significant bit indicates direction (0 = right, 1 = left), the following bits represent the moved gate, bit 6 = forward move, bit 7 = backward move | |
| typedef std::uint8_t Move; | |
| #define SOLVED_STATE 0b100100100100100100100100100100 | |
| #define VALIDATION_BITS 0b001001001001001001001001001001 | |
| #define EVEN_GATE_BITS 0b000111000111000111000111000111 | |
| #define ODD_GATE_BITS 0b111000111000111000111000111000 | |
| #define FORWARD Move(0b01000000) | |
| #define BACKWARD Move(0b10000000) | |
| #define REVERSAL Move(0b11000001) | |
| #define DIRECTION_BITS Move(FORWARD|BACKWARD) | |
| State initialState; | |
| /// Each gate has two consecutive linkage entries - for positive (even index) and negative (odd index) moves | |
| Linkage linkages[64]; | |
| Move stateMap[1<<(3*MAX_GATES)] = { }; | |
| std::uint8_t GATES = 0; | |
| State activeGateMask; | |
| State validationMask; | |
| void initializeGateCount(int gates) { | |
| GATES = gates; | |
| activeGateMask = (State(1)<<(3*gates))-1; | |
| validationMask = VALIDATION_BITS&activeGateMask; | |
| } | |
| #define VALIDATE_STATE(state) ((((state)|(state)>>1|(state)>>2)&validationMask) == validationMask) | |
| inline State advanceState(State state, Move move) { | |
| Linkage linkage = linkages[move&63]; | |
| State evenState = (state&EVEN_GATE_BITS)+(linkage&EVEN_GATE_BITS); | |
| State oddState = (state&ODD_GATE_BITS)+(linkage&ODD_GATE_BITS); | |
| state = ((evenState&EVEN_GATE_BITS)|(oddState&ODD_GATE_BITS))&activeGateMask; | |
| if (!VALIDATE_STATE(state)) | |
| return 0; | |
| return state; | |
| } | |
| template <Move direction> | |
| inline void searchBranch(std::queue<State> &dstQueue, State initialState, Move move) { | |
| if (State targetState = advanceState(initialState, move)) { | |
| Move stateEntry = stateMap[targetState]; | |
| #if ONE_WAY_SEARCH | |
| if (stateEntry == Move(1)) | |
| #else | |
| if (stateEntry&(direction^DIRECTION_BITS)) | |
| #endif | |
| throw move; | |
| if (!stateEntry) { | |
| stateMap[targetState] = move; | |
| dstQueue.push(targetState); | |
| } | |
| } | |
| } | |
| template <Move direction> | |
| inline void searchBranches(std::queue<State> &dstQueue, State initialState, Move lastMove) { | |
| unsigned lastMovedGate = GATES; | |
| if (lastMove) { | |
| searchBranch<direction>(dstQueue, initialState, lastMove); | |
| if (!(lastMove&direction)) | |
| searchBranch<direction>(dstQueue, initialState, lastMove^1); | |
| lastMovedGate = lastMove>>1&31; | |
| } | |
| for (Move move = direction, end = move+2*GATES; move < end; ++move) { | |
| if ((move>>1&31) != lastMovedGate) | |
| searchBranch<direction>(dstQueue, initialState, move); | |
| } | |
| } | |
| std::vector<Move> connectSolution(State middleState, Move middleMove) { | |
| std::vector<Move> moves; | |
| State solvedState = SOLVED_STATE&activeGateMask; | |
| State state = middleState; | |
| if (middleMove&BACKWARD) | |
| state = advanceState(state, middleMove); | |
| while (state != initialState) { | |
| Move move = stateMap[state]; | |
| moves.push_back(move); | |
| state = advanceState(state, move^REVERSAL); | |
| } | |
| std::reverse(moves.begin(), moves.end()); | |
| state = middleState; | |
| if (middleMove&BACKWARD) | |
| moves.push_back(middleMove^REVERSAL); | |
| else { | |
| moves.push_back(middleMove); | |
| state = advanceState(state, middleMove); | |
| } | |
| while (state != solvedState) { | |
| Move forwardMove = stateMap[state]^REVERSAL; | |
| moves.push_back(forwardMove); | |
| state = advanceState(state, forwardMove); | |
| } | |
| return moves; | |
| } | |
| std::vector<Move> solve() { | |
| struct { | |
| std::queue<State> forward, backward; | |
| } queues[2], *dstQueues, *srcQueues; | |
| dstQueues = queues+0; | |
| srcQueues = queues+1; | |
| State solvedState = SOLVED_STATE&activeGateMask; | |
| if (initialState != solvedState) { | |
| stateMap[initialState] = (GATES-1)<<1; | |
| stateMap[solvedState] = 1; | |
| dstQueues->forward.push(initialState); | |
| dstQueues->backward.push(solvedState); | |
| } | |
| State curState; | |
| try { | |
| while (std::swap(dstQueues, srcQueues), !(srcQueues->forward.empty() && srcQueues->backward.empty())) { | |
| while (!srcQueues->forward.empty()) { | |
| curState = srcQueues->forward.front(); | |
| srcQueues->forward.pop(); | |
| searchBranches<FORWARD>(dstQueues->forward, curState, stateMap[curState]); | |
| } | |
| #if !ONE_WAY_SEARCH | |
| while (!srcQueues->backward.empty()) { | |
| curState = srcQueues->backward.front(); | |
| srcQueues->backward.pop(); | |
| searchBranches<BACKWARD>(dstQueues->backward, curState, stateMap[curState]); | |
| } | |
| #endif | |
| } | |
| } catch (Move finalMove) { | |
| return connectSolution(curState, finalMove); | |
| } | |
| return std::vector<Move>(); | |
| } | |
| bool verifySolution(const std::vector<Move> &solution) { | |
| State state = initialState; | |
| for (Move move : solution) | |
| state = advanceState(state, move); | |
| return state == (SOLVED_STATE&activeGateMask); | |
| } | |
| bool parseInitialState() { | |
| initialState = 0; | |
| char c; | |
| bool minus = false; | |
| int i = 0; | |
| State pos; | |
| while ((c = getchar()) != '\n') { | |
| switch (c) { | |
| case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': | |
| pos = c-'0'-LEFTMOST_POSITION; | |
| if (i >= MAX_GATES || pos >= 7) | |
| return false; | |
| initialState |= (pos+1)<<(3*i++); | |
| } | |
| } | |
| if (i <= 0) | |
| return false; | |
| initializeGateCount(i); | |
| return true; | |
| } | |
| bool parseLinkage(int i) { | |
| std::uint32_t mask = 0b111u<<(3*i); | |
| linkages[2*i+0] = 0b001u<<(3*i); | |
| linkages[2*i+1] = 0b111u<<(3*i); | |
| char c; | |
| bool minus = false; | |
| while ((c = getchar()) != '\n') { | |
| switch (c) { | |
| case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': | |
| if (c-'A' >= GATES) | |
| return false; | |
| linkages[2*i+0] |= (minus ? 0b111u : 0b001u)<<(3*(c-'A')); | |
| linkages[2*i+1] |= (minus ? 0b001u : 0b111u)<<(3*(c-'A')); | |
| break; | |
| case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': | |
| if (c-'a' >= GATES) | |
| return false; | |
| linkages[2*i+0] |= (minus ? 0b111u : 0b001u)<<(3*(c-'a')); | |
| linkages[2*i+1] |= (minus ? 0b001u : 0b111u)<<(3*(c-'a')); | |
| break; | |
| case '+': | |
| minus = false; | |
| break; | |
| case '-': | |
| minus = true; | |
| break; | |
| } | |
| } | |
| return (linkages[2*i+0]&mask) == (0b001u<<(3*i)) && (linkages[2*i+1]&mask) == (0b111u<<(3*i)); | |
| } | |
| int main() { | |
| printf("Initial positions (%d to %d): ", LEFTMOST_POSITION, LEFTMOST_POSITION+6); | |
| if (!parseInitialState()) { | |
| fputs("Input error\n", stderr); | |
| return -1; | |
| } | |
| for (int i = 0; i < GATES; ++i) { | |
| printf("%c: ", char('A'+i)); | |
| if (!parseLinkage(i)) { | |
| fputs("Input error\n", stderr); | |
| return -1; | |
| } | |
| } | |
| std::vector<Move> solution(solve()); | |
| if (solution.empty()) { | |
| if (initialState == (SOLVED_STATE&activeGateMask)) | |
| puts("Already solved"); | |
| else | |
| puts("Solution not found"); | |
| } else { | |
| if (!verifySolution(solution)) | |
| return -1; | |
| printf("%d-step solution:", int(solution.size())); | |
| #ifdef WITHOUT_MULTIPLES | |
| for (Move move : solution) | |
| printf(" %c%c", move&1 ? '+' : '-', char('A'+(move>>1&31))); | |
| #else | |
| Move *move = solution.data(), *end = move+solution.size(); | |
| while (move < end) { | |
| int multiplier = 1; | |
| while (move+1 < end && move[0] == move[1]) | |
| ++multiplier, ++move; | |
| if (multiplier != 1) | |
| printf(" %c%d%c", *move&1 ? '+' : '-', multiplier, char('A'+(*move>>1&31))); | |
| else | |
| printf(" %c%c", *move&1 ? '+' : '-', char('A'+(*move>>1&31))); | |
| ++move; | |
| } | |
| #endif | |
| } | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment