How to handle simular funtcions

I am making a checkers game in Zig, and I have a function that checks if you can capture the enemies checker if you move in a direction from a position.

fn checkCaptureDirection(board: [8][8]u8, start_pos: Position, color: MoveColor, limit: bool, move_row: i2, move_col: i2) bool {
    if (findClosestChecker(board, start_pos, move_row, move_col)) |checker| {
        //Can't capture your own pieces
        if (checker.color == color) return false;

        //If at the edge we can't capture it
        if (checker.position.col == 0 or checker.position.col == 7 or checker.position.row == 0 or checker.position.row == 7) return false;

        //Find a space behind the other piece in the direction
        const behind_other: Position = .{
            .row = @intCast(@as(i4, checker.position.row) + move_row),
            .col = @intCast(@as(i4, checker.position.col) + move_col),
        };

        //We move diagnaly and so we only need to find length on one axis
        const move_len_row = @abs(@as(i4, behind_other.row) - @as(i4, start_pos.row));

        //Limit the normal checker to only be close
        if (limit and move_len_row > 2) return false;

        //Can't move onto the other piece
        if (getPieceAtPosition(board, behind_other) != 0) return false;

        std.debug.assert(validateCapture(board, .{
            .color = color,
            .from = start_pos,
            .to = behind_other,
        }));

        return true;
    }

    //No checker in that direction so can't capture anything
    return false;
}

Now I need a function that would not only check if you can, but also find all possible capture moves in that direction and save them to an arrayList that I would pass into it.

On one hand the logic will be quite simular and I don’t want to duplicate code, but on the other hand, it feels like this function will do too much and adding an allocator and arrayList to parameters when they will only be used half of the time and error to return seems very ugly.

Any suggestion on how to better handle that?

it sounds like you can use that existing function to implement this new function.

If you think you would duplicate code then I’m guessing you need slightly different semantics?

Yes, the are would be mostly same but would behave slightly differently. The yes or no just does one check for if your piece can move behind the other one (which would capture it) and returns, the new one would need to also check all spaces after it and add all those moves to the list

Unless I am missing something, it sounds like you can just call the existing function repeatedly to implement the new one.

I will also suggest that you shouldn’t be so wary of code duplication, it is not inherently bad.

That will not work. This is what that function looks like

fn checkCaptureDirection(board: [8][8]u8, start_pos: Position, color: MoveColor, limit: bool, move_row: i2, move_col: i2) bool {
    if (findClosestChecker(board, start_pos, move_row, move_col)) |checker| {
        //Can't capture your own pieces
        if (checker.color == color) return false;

        //If at the edge we can't capture it
        if (checker.position.col == 0 or checker.position.col == 7 or checker.position.row == 0 or checker.position.row == 7) return false;

        //Find a space behind the other piece in the direction
        const behind_other: Position = .{
            .row = @intCast(@as(i4, checker.position.row) + move_row),
            .col = @intCast(@as(i4, checker.position.col) + move_col),
        };

        //We move diagnaly and so we only need to find length on one axis
        const move_len_row = @abs(@as(i4, behind_other.row) - @as(i4, start_pos.row));

        //Limit the normal checker to only be close
        if (limit and move_len_row > 2) return false;

        //Can't move onto the other piece
        if (getPieceAtPosition(board, behind_other) != 0) return false;

        std.debug.assert(validateCapture(board, .{
            .color = color,
            .from = start_pos,
            .to = behind_other,
        }));

        return true;
    }

    //No checker in that direction so can't capture anything
    return false;
}

Seems to me like you have one common thing like
fn getNextCaptureAndReturnNewPosition(startPosition, ...) ?Position (needs better name)
Then two functions who utilize this.
fn checkCaptureDirection which just checks if the above returns null or not
And you new function which uses the return position to do a new captureCheck. But I am also not expierenced with checkers, so I may be missing something.

That is an interesting idea that could work