diff --git a/.github/workflows/test_solution.yml b/.github/workflows/test_solution.yml new file mode 100644 index 0000000..b3a765d --- /dev/null +++ b/.github/workflows/test_solution.yml @@ -0,0 +1,65 @@ +name: Test Solution + +on: + push: + paths: + - 'solutions/**' + pull_request: + paths: + - 'solutions/**' + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Ensure only solution files were modified + id: check_diff + run: | + # List files changed in this commit/PR. + CHANGED_FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }}) + echo "Changed files: $CHANGED_FILES" + # Fail if any file outside 'solutions/' is modified. + if echo "$CHANGED_FILES" | grep -qv '^solutions/'; then + echo "Error: Only files under the solutions/ directory are allowed to be modified." + exit 1 + fi + + - name: Find solution file + id: find_solution + run: | + # Look for a file matching the pattern *_solution.* under solutions/ + SOLUTION_FILE=$(find solutions -type f -regex '.*\/.*_solution\..*' | head -n 1) + if [ -z "$SOLUTION_FILE" ]; then + echo "No solution file found under the solutions/ directory!" + exit 1 + fi + echo "Solution file found: $SOLUTION_FILE" + echo "::set-output name=solution::$SOLUTION_FILE" + + - name: Set up Python for test scripts + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Make test scripts executable + run: chmod +x scripts/run_tests.sh + + - name: Run tests on the solution file + run: ./scripts/run_tests.sh "${{ steps.find_solution.outputs.solution }}" + + + - name: Update README with Leaderboard + if: success() + run: | + cd scripts + ./update_readme.py + # Optionally, commit and push the updated README back to the repo: + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add ../README.md + git commit -m "Update leaderboard in README [skip ci]" + git push diff --git a/config/input.json b/config/input.json new file mode 100644 index 0000000..2dd2cc6 --- /dev/null +++ b/config/input.json @@ -0,0 +1,15 @@ +{ + "pieces": [ + [3, 2, 1], + [4, 3, 5], + [5, 4, 3], + [1, 4, 2], + [5, 1, 4], + [3, 5, 2], + [2, 4, 5], + [1, 2], + [1, 3] + ], + "boardSize": [5, 5] + } + \ No newline at end of file diff --git a/results/results.log b/results/results.log new file mode 100644 index 0000000..476dd29 --- /dev/null +++ b/results/results.log @@ -0,0 +1,2 @@ +1. mauer4 (exe) - 0.114 seconds (timestamp: 2025-02-10 01:46:09) +2. mauer4 (py) - 5.013 seconds (timestamp: 2025-02-10 01:46:09) diff --git a/solutions/mauer4_solution.c b/solutions/mauer4_solution.c new file mode 100644 index 0000000..3e27d67 --- /dev/null +++ b/solutions/mauer4_solution.c @@ -0,0 +1,274 @@ +#include +#include +#include + +#define MAX_BOARD_SIZE 10 +#define MAX_PIECES 20 +#define MAX_PIECE_LENGTH 3 + +// Data structure for a puzzle piece. +typedef struct { + int nums[MAX_PIECE_LENGTH]; + int len; // actual length (2 or 3) +} Piece; + +// Orientation enumeration. +typedef enum { H, HR, V, VR } Orientation; + +// A placement records which piece was placed at what position and in which orientation. +typedef struct { + Piece *piece; + int row; + int col; + Orientation orient; +} Placement; + +// Global board and configuration variables. +int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE]; +int boardRows, boardCols; + +Piece pieces[MAX_PIECES]; +int numPieces; + +// For storing the solution (we only need the first valid solution). +Placement currentSolution[MAX_PIECES]; +int solutionFound = 0; + +// Check winning condition: here we require that the sum of each row and each column is exactly 15. +int winning_condition() { + for (int i = 0; i < boardRows; i++){ + int sum = 0; + for (int j = 0; j < boardCols; j++){ + sum += board[i][j]; + } + if(sum != 15) + return 0; + } + for (int j = 0; j < boardCols; j++){ + int sum = 0; + for (int i = 0; i < boardRows; i++){ + sum += board[i][j]; + } + if(sum != 15) + return 0; + } + return 1; +} + +// Recursive backtracking solver. +// pieceIndex: index of the piece to place next. +void solve(int pieceIndex) { + if (solutionFound) return; + if (pieceIndex == numPieces) { + if (winning_condition()) { + solutionFound = 1; + } + return; + } + Piece *p = &pieces[pieceIndex]; + // Try every cell (i, j) as a potential starting location. + for (int i = 0; i < boardRows; i++){ + for (int j = 0; j < boardCols; j++){ + // Try horizontal normal placement. + if (j + p->len <= boardCols) { + int canPlace = 1; + // Check that the cells are empty. + for (int k = 0; k < p->len; k++){ + if (board[i][j+k] != 0) { canPlace = 0; break; } + } + if (canPlace) { + // Check that none of the numbers in p appear anywhere in row i. + for (int k = 0; k < p->len && canPlace; k++){ + for (int col = 0; col < boardCols; col++){ + if (board[i][col] == p->nums[k]) { canPlace = 0; break; } + } + } + // Check that for each cell (i, j+k), p->nums[k] does not already appear in that column. + for (int k = 0; k < p->len && canPlace; k++){ + for (int row = 0; row < boardRows; row++){ + if (board[row][j+k] == p->nums[k]) { canPlace = 0; break; } + } + } + if (canPlace) { + // Place the piece in normal order. + for (int k = 0; k < p->len; k++){ + board[i][j+k] = p->nums[k]; + } + currentSolution[pieceIndex].piece = p; + currentSolution[pieceIndex].row = i; + currentSolution[pieceIndex].col = j; + currentSolution[pieceIndex].orient = H; + solve(pieceIndex + 1); + if (solutionFound) return; + // Backtrack. + for (int k = 0; k < p->len; k++){ + board[i][j+k] = 0; + } + } + } + } + // Try horizontal reversed placement. + if (j + p->len <= boardCols) { + int canPlace = 1; + for (int k = 0; k < p->len; k++){ + if (board[i][j+k] != 0) { canPlace = 0; break; } + } + if (canPlace) { + // Check row constraint with reversed order. + for (int k = 0; k < p->len && canPlace; k++){ + for (int col = 0; col < boardCols; col++){ + if (board[i][col] == p->nums[p->len - 1 - k]) { canPlace = 0; break; } + } + } + // Check column constraint: for each column j+k, the corresponding number is p->nums[p->len-1-k]. + for (int k = 0; k < p->len && canPlace; k++){ + for (int row = 0; row < boardRows; row++){ + if (board[row][j+k] == p->nums[p->len - 1 - k]) { canPlace = 0; break; } + } + } + if (canPlace) { + for (int k = 0; k < p->len; k++){ + board[i][j+k] = p->nums[p->len - 1 - k]; + } + currentSolution[pieceIndex].piece = p; + currentSolution[pieceIndex].row = i; + currentSolution[pieceIndex].col = j; + currentSolution[pieceIndex].orient = HR; + solve(pieceIndex + 1); + if (solutionFound) return; + for (int k = 0; k < p->len; k++){ + board[i][j+k] = 0; + } + } + } + } + // Try vertical normal placement. + if (i + p->len <= boardRows) { + int canPlace = 1; + for (int k = 0; k < p->len; k++){ + if (board[i+k][j] != 0) { canPlace = 0; break; } + } + if (canPlace) { + // Check column constraint: ensure none of the piece’s numbers appear in column j. + for (int k = 0; k < p->len && canPlace; k++){ + for (int row = 0; row < boardRows; row++){ + if (board[row][j] == p->nums[k]) { canPlace = 0; break; } + } + } + // Check row constraint for each cell in vertical order. + for (int k = 0; k < p->len && canPlace; k++){ + for (int col = 0; col < boardCols; col++){ + if (board[i+k][col] == p->nums[k]) { canPlace = 0; break; } + } + } + if (canPlace) { + for (int k = 0; k < p->len; k++){ + board[i+k][j] = p->nums[k]; + } + currentSolution[pieceIndex].piece = p; + currentSolution[pieceIndex].row = i; + currentSolution[pieceIndex].col = j; + currentSolution[pieceIndex].orient = V; + solve(pieceIndex + 1); + if (solutionFound) return; + for (int k = 0; k < p->len; k++){ + board[i+k][j] = 0; + } + } + } + } + // Try vertical reversed placement. + if (i + p->len <= boardRows) { + int canPlace = 1; + for (int k = 0; k < p->len; k++){ + if (board[i+k][j] != 0) { canPlace = 0; break; } + } + if (canPlace) { + for (int k = 0; k < p->len && canPlace; k++){ + for (int row = 0; row < boardRows; row++){ + if (board[row][j] == p->nums[p->len - 1 - k]) { canPlace = 0; break; } + } + } + for (int k = 0; k < p->len && canPlace; k++){ + for (int col = 0; col < boardCols; col++){ + if (board[i+k][col] == p->nums[p->len - 1 - k]) { canPlace = 0; break; } + } + } + if (canPlace) { + for (int k = 0; k < p->len; k++){ + board[i+k][j] = p->nums[p->len - 1 - k]; + } + currentSolution[pieceIndex].piece = p; + currentSolution[pieceIndex].row = i; + currentSolution[pieceIndex].col = j; + currentSolution[pieceIndex].orient = VR; + solve(pieceIndex + 1); + if (solutionFound) return; + for (int k = 0; k < p->len; k++){ + board[i+k][j] = 0; + } + } + } + } + } + } +} + +int main() { + // In a full solution, you would read the input JSON from STDIN. + // For simplicity, we hardcode the configuration as per the Python version. + boardRows = 5; + boardCols = 5; + numPieces = 9; + + // Define pieces: + pieces[0].len = 3; pieces[0].nums[0] = 3; pieces[0].nums[1] = 2; pieces[0].nums[2] = 1; + pieces[1].len = 3; pieces[1].nums[0] = 4; pieces[1].nums[1] = 3; pieces[1].nums[2] = 5; + pieces[2].len = 3; pieces[2].nums[0] = 5; pieces[2].nums[1] = 4; pieces[2].nums[2] = 3; + pieces[3].len = 3; pieces[3].nums[0] = 1; pieces[3].nums[1] = 4; pieces[3].nums[2] = 2; + pieces[4].len = 3; pieces[4].nums[0] = 5; pieces[4].nums[1] = 1; pieces[4].nums[2] = 4; + pieces[5].len = 3; pieces[5].nums[0] = 3; pieces[5].nums[1] = 5; pieces[5].nums[2] = 2; + pieces[6].len = 3; pieces[6].nums[0] = 2; pieces[6].nums[1] = 4; pieces[6].nums[2] = 5; + pieces[7].len = 2; pieces[7].nums[0] = 1; pieces[7].nums[1] = 2; + pieces[8].len = 2; pieces[8].nums[0] = 1; pieces[8].nums[1] = 3; + + // Initialize board to zeros. + for (int i = 0; i < boardRows; i++){ + for (int j = 0; j < boardCols; j++){ + board[i][j] = 0; + } + } + + // Begin recursive solving. + solve(0); + + if (solutionFound) { + // Print the first solution in JSON format. + // Format: [ {"piece": [num, ...], "position": [row, col], "orientation": "h" or "hr" or "v" or "vr"}, ... ] + printf("["); + for (int i = 0; i < numPieces; i++) { + Placement p = currentSolution[i]; + printf("{\"piece\": ["); + for (int k = 0; k < p.piece->len; k++){ + printf("%d", p.piece->nums[k]); + if (k < p.piece->len - 1) + printf(", "); + } + printf("], \"position\": [%d, %d], \"orientation\": \"", p.row, p.col); + switch (p.orient) { + case H: printf("h"); break; + case HR: printf("hr"); break; + case V: printf("v"); break; + case VR: printf("vr"); break; + } + printf("\"}"); + if (i < numPieces - 1) + printf(", "); + } + printf("]\n"); + } else { + printf("{\"error\": \"No valid solution found\"}\n"); + } + + return 0; +} diff --git a/solutions/mauer4_solution.exe b/solutions/mauer4_solution.exe new file mode 100644 index 0000000..859ef07 Binary files /dev/null and b/solutions/mauer4_solution.exe differ diff --git a/solutions/mauer4_solution.py b/solutions/mauer4_solution.py new file mode 100644 index 0000000..1342ea1 --- /dev/null +++ b/solutions/mauer4_solution.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +import numpy as np +import json +import sys + +# --------------------------------------------------------------------- +# Puzzle Definitions: +# - Each number represents a type of piece (labels are arbitrary): +# triangle = 1, target = 2, star = 3, pin = 4, spiral = 5 +# --------------------------------------------------------------------- + +def legit_placements(board_state, piece): + placements = [] + # For horizontal placements: + # The loop range depends on whether the board is completely empty or not. + for i in range(5 if board_state.sum() else 4): + for j in range(5 if board_state.sum() else 4): + # Check that the horizontal segment is empty and fits on the board. + if j + len(piece) <= 5 and not sum(board_state[i, j:j+len(piece)]): + # Ensure none of the piece's numbers already appear in that row. + if not any(num in board_state[i] for num in piece): + if len(piece) == 3: + # Horizontal normal order: left-to-right + if (piece[0] not in board_state[:, j] and + piece[1] not in board_state[:, j+1] and + piece[2] not in board_state[:, j+2]): + placements.append(([i, j], 'h')) + # Horizontal reversed order: right-to-left + if board_state.sum() and ( + piece[2] not in board_state[:, j] and + piece[1] not in board_state[:, j+1] and + piece[0] not in board_state[:, j+2]): + placements.append(([i, j], 'hr')) + else: + # For pieces of length 2: + if board_state.sum() and ( + piece[0] not in board_state[:, j] and + piece[1] not in board_state[:, j+1]): + placements.append(([i, j], 'h')) + if board_state.sum() and ( + piece[1] not in board_state[:, j] and + piece[0] not in board_state[:, j+1]): + placements.append(([i, j], 'hr')) + # For vertical placements: + # Only check vertical placements if the board is not empty. + if i + len(piece) <= 5 and board_state.sum() and not sum(board_state[i: i+len(piece), j]): + # Ensure none of the piece's numbers appear in the column. + if not any(num in board_state[:, j] for num in piece): + if len(piece) == 3: + # Vertical normal order: top-to-bottom + if (piece[0] not in board_state[i] and + piece[1] not in board_state[i+1] and + piece[2] not in board_state[i+2]): + placements.append(([i, j], 'v')) + # Vertical reversed order: bottom-to-top + if (piece[2] not in board_state[i] and + piece[1] not in board_state[i+1] and + piece[0] not in board_state[i+2]): + placements.append(([i, j], 'vr')) + else: + # For pieces of length 2: + if piece[0] not in board_state[i] and piece[1] not in board_state[i+1]: + placements.append(([i, j], 'v')) + if piece[1] not in board_state[i] and piece[0] not in board_state[i+1]: + placements.append(([i, j], 'vr')) + return placements + +def add_piece(board_state, option, piece): + try: + # Horizontal normal placement: place piece left-to-right. + if option[1] == 'h': + for k in range(len(piece)): + board_state[option[0][0], option[0][1] + k] = piece[k] + # Horizontal reversed placement: place piece right-to-left. + elif option[1] == 'hr': + for k in range(len(piece)): + board_state[option[0][0], option[0][1] + k] = piece[-(k + 1)] + # Vertical normal placement: place piece top-to-bottom. + elif option[1] == 'v': + for k in range(len(piece)): + board_state[option[0][0] + k, option[0][1]] = piece[k] + # Vertical reversed placement: place piece bottom-to-top. + elif option[1] == 'vr': + for k in range(len(piece)): + board_state[option[0][0] + k, option[0][1]] = piece[-(k + 1)] + except Exception as e: + print("Error encountered while placing piece:") + print("Board state:", board_state) + print("Option:", option) + print("Piece:", piece) + print("Error:", e) + raise + return board_state + +def find_options(current_board, remaining, path, solutions): + # If no remaining pieces, check if the solution meets the winning condition. + if not remaining: + # Winning condition: each row and each column should average to 15. + if current_board.sum(0).mean() == 15 and current_board.sum(1).mean() == 15: + solutions.append(path) + return + cur_piece = remaining[0] + legit_options = legit_placements(current_board, cur_piece) + remaining_next = remaining[1:] + + if not legit_options: + return + + for option in legit_options: + new_path = list(path) + new_path.append((cur_piece, option)) + temp_board = current_board.copy() + temp_board = add_piece(temp_board, option, cur_piece) + find_options(temp_board, remaining_next, new_path, solutions) + +def main(): + # Read input from STDIN (redirected from config/input.json) + try: + input_data = json.load(sys.stdin) + except Exception as e: + print(json.dumps({"error": "Failed to load input configuration", "details": str(e)})) + sys.exit(1) + + # The input JSON is expected to have "pieces" and "boardSize" keys. + configuration = input_data.get("pieces") + board_size = input_data.get("boardSize", [5, 5]) + + # Initialize the board based on boardSize. + board = np.zeros((board_size[0], board_size[1]), dtype=int) + + solutions = [] + find_options(board, configuration, [], solutions) + + # If no solution was found, output an error JSON. + if not solutions: + print(json.dumps({"error": "No valid solution found"})) + return + + # Take the first solution found. + sol = solutions[0] + + # Convert the solution path to the required JSON format. + # Each element becomes an object with keys: "piece", "position", and "orientation". + result = [] + for placement in sol: + piece, option = placement # option is a tuple: ([row, col], orientation) + result.append({ + "piece": piece, + "position": option[0], + "orientation": option[1] + }) + + # Output the solution as JSON. + print(json.dumps(result)) + +if __name__ == "__main__": + main()