Let's start by using the column name instead of "2". Like you would do in "normal" SQL
Tournament::has('participations', '<', 'slots')->get();
Now, you don't even have to try that because it won't work. But why's that? Because Laravel treats slots like a string and escapes it so SQL does as well.
What you need to do, is use DB::raw(). raw makes sure Laravel changes nothing and just injects it into the SQL query
Tournament::has('participations', '<', DB::raw('slots'))->get();
Update
After some trying out I found this: (Its not very pretty but the only way I got it working)
$subquery = function($q) use ($uid){
$q->where('player_id', $uid);
}
Tournament::whereHas('participations', $subquery)
->whereHas('participations', $subquery, '<', DB::raw('slots'))
->get();
The first whereAs checks for count(*) > 0, the second count(*) < slots and the subquery filters by player id.