Вы можете хранить индексы, которые составляют выигрышные строки, и использовать один цикл:
int win_row[][3] = {{0, 0, 0}, {1, 1, 1}, {2, 2, 2}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}};
int win_col[][3] = {{0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {0, 0, 0}, {1, 1, 1}, {2, 2, 2}, {0, 1, 2}, {2, 1, 0}};
bool has_winner(char board[][3])
{
//'\0' means unoccupied
for (int i = 0; i != 8; ++i) {
char c = board[win_row[i][0]][win_col[i][0]];
if (c && c == board[win_row[i][1]][win_col[i][1]] && c == board[win_row[i][2]][win_col[i][2]]) {
return true;
}
}
return false;
}
Я также поддерживаю предложения Джеффа хранить ходы игроков в отдельных значениях и использовать побитовые операции.