Download
% Magic Square backtracking solver.
% Fills an NxN grid with values 1..N^2 such that all rows, columns,
% and both diagonals sum to the magic sum S = N*(N^2+1)/2.

% magic_square_bt(+N, -Square)
magic_square_bt(N, Square) :-
    length(Square, N),
    maplist(length_(N), Square),      % create NxN matrix of variables
    append(Square, Vars),             % flatten matrix to a single list
    Max is N * N,
    numlist(1, Max, Domain),          % domain = [1..N^2]
    Sum is N * (Max + 1) // 2,       % magic sum
    fill(Vars, Domain, Square, N, Sum).

% fill(+Vars, +Remaining, +Square, +N, +Sum)
fill([], _, _, _, _).
fill([H|Rest], Remaining, Square, N, Sum) :-
    select(H, Remaining, NewRemaining), % pick an unused value (guarantees all_distinct)
    check_partial(Square, N, Sum),      % check constraints after every assignment
    fill(Rest, NewRemaining, Square, N, Sum).

% Checks rows, columns, and diagonals after each cell assignment.
% check_partial(+Square, +N, +Sum)
check_partial(Square, N, Sum) :-
    maplist(check_line_partial(Sum), Square),       % row sums
    numlist(1, N, Indices),
    maplist(check_col_partial(Square, Sum), Indices), % column sums
    check_diags_partial(Square, N, Sum).            % diagonal sums

% Partial line check (row or column):
% If complete: partial sum must equal Sum exactly.
% If incomplete: partial sum must stay below Sum (each empty cell adds at least 1).
% check_line_partial(+Sum, +Line)
check_line_partial(Sum, Line) :-
    include(nonvar, Line, Filled),     % only already assigned cells
    sum_list(Filled, PartialSum),
    length(Line, Total),
    length(Filled, FilledCount),
    FilledCount < Total,               % line incomplete: partial sum must stay below Sum
    PartialSum < Sum.

check_line_partial(Sum, Line) :-
    include(nonvar, Line, Filled),     % only already assigned cells
    sum_list(Filled, PartialSum),
    length(Line, Total),
    length(Filled, Total),             % line complete: FilledCount equals Total
    PartialSum =:= Sum.

% check_col_partial(+Square, +Sum, +Index)
check_col_partial(Square, Sum, Index) :-
    maplist(nth1(Index), Square, Col), % extract column at given index
    check_line_partial(Sum, Col).

% Diagonal checks use the same partial line check.
% check_diags_partial(+Square, +N, +Sum)
check_diags_partial(Square, N, Sum) :-
    Last is N - 1,
    numlist(0, Last, Indices),
    maplist(diag1_cell(Square), Indices, Diag1),       % main diagonal
    check_line_partial(Sum, Diag1),
    maplist(diag2_cell(Square, N), Indices, Diag2),    % anti-diagonal
    check_line_partial(Sum, Diag2).

% ----------------
% HELPER FUNCTIONS

% length_(+N, ?List): List is a list of N elements
length_(N, List) :- length(List, N).

% diag1_cell(+Square, +I, -Cell): Cell is the (I,I) main-diagonal cell
diag1_cell(Square, I, Cell) :-
    nth0(I, Square, Row),   % row I
    nth0(I, Row, Cell).     % cell I in that row -> main diagonal

% diag2_cell(+Square, +N, +I, -Cell): Cell is the (I,N-1-I) anti-diagonal cell
diag2_cell(Square, N, I, Cell) :-
    nth0(I, Square, Row),   % row I
    J is N - 1 - I,         % mirrored column index
    nth0(J, Row, Cell).     % anti-diagonal