The issue with the code, ultimately, is that you need to set up a proper pattern for returning the list. A common pattern in Prolog is to pass in a variable argument that is carried through all of the recursions and is finally instantiated in the terminal case. The variable is set up initially in the first clause.
depth_first(N, ReturnList) :-
df_real(2:1, N, [2:1], ReturnList).
So here the first query is initiated with the 4th argument as the variable for returning the answer list, ReturnList. So far, so good.
df_real(_:NextRankC, Size, Q, ReturnList) :-
genInt(Size, Row),
Column is NextRankC + 1,
Rank = Row:Column,
not(list_attack(Rank, Q)),
add(Rank, Q, NewList),
df_real(1:Column, Size, NewList, NewList).
Initially, you had _ as the 4th argument, which was incorrect because you need the 4th argument as a variable in which you can return the answer. Having the 4th argument as ReturnList now, is good, but there's no call currently inside of this clause which instantiates it. So your trace shows _XXXX for its value (it's uninstantiated). The problem is with the last call:
df_real(1:Column, Size, NewList, NewList).
For some reason, you have NewList as both the 3rd and 4th arguments. You probably want ReturnList as the 4th argument so that it is carried through the recursion and will finally be set:
df_real(1:Column, Size, NewList, ReturnList).
Lastly, the terminal (or base) case:
% Recursive Case: Exit when list is full.
df_real(_, N, Q, Newlist) :-
length(Q, Length),
N = Length.
NewList as the 4th argument here does nothing. It's a singleton variable and doesn't get filled in with anything. So in a trace, it will show up as _XXXX and you'll have no answer in ReturnList. The clause is indicated that you're done when the Q list has reached length N. I'm supposing that, at that point, Q is your answer that you want. So you just need to say so, in Prolog:
% Recursive Case: When list is full, it's the answer
df_real(_, N, Q, Q) :-
length(Q, N).
You can also tidy up a little by simplifying the first argument. It's a term of the form X:Y and you never use X, so why carry it around?
depth_first(N, ReturnList) :-
df_real(1, N, [2:1], ReturnList).
df_real(_:NextRankC, Size, Q, ReturnList) :-
genInt(Size, Row),
Column is NextRankC + 1,
Rank = Row:Column,
\+ list_attack(Rank, Q), % Note use of ISO negation predicate, \+
add(Rank, Q, NewList),
df_real(Column, Size, NewList, ReturnList).
% Recursive Case: When list is full, it's the answer
df_real(_, N, Q, Q) :-
length(Q, N).
df_real(_:NextRankC, Size, Q, _) :- ...has an anonymous variable,_(which remains uninstantiated) where I'm supposing you want to return your list. Make it,df_real(_:NextRankC, Size, Q, NewList) :- ...instead. Also, for brevity, your first predicate could be written,depth_first(N, ReturnList) :- df_real(2:1, N, [2:1], ReturnList).- lurkerNewListas the last argument todf_realinstead of_? I don't see how it could not make a difference in results. When you put it there, what results do you get? It must make some difference even if it's not correct yet. Judging from your partial trace, it should work, or at least get you a lot closer to what you want. - lurker_as well (in the_:NextRankCterm). I'm not sure what you intend to do with that argument, but it gets lost because you made it anonymous. Maybe it doesn't matter since you already pass the same thing in the 3rd argument anyway. - lurker