Для игры "жизнь" есть несколько вариантов:
1) Увеличить поле на 2 клетки по каждому измерению, поле будет храниться с 1, а индексы 0 и n+1 - всегда будут пустыми. Потребление памяти это почти не увеличит, а код упростит.
2) Если соседние клетки считаются циклами, то можно границы области 3x3 пересечь с полем:
for (int nx = max(0, x-1); nx < min(x+2, n); ++nx) {
for (int ny = max(0, y-1); ny < min(y+2, n); ++ny) {
if (nx == x && ny == y) continue;
// {nx, ny} - сосед в поле, обрабатываем его.
}
}
Можно код чуть ускорить, предподсчитав границы.
3) Более читаемый, но чуть более медленный метод - явно проверять, а не за границей ли соседняя клетка:
for (int nx = x-1; nx <= x+1; ++nx) {
for (int ny = y-1; ny <= y+1; ++ny) {
if ((nx == x && ny == y) || nx < 0 || ny < 0 || nx >= n || ny >= n) continue;
// {nx, ny} - соседняя клетка.
}
}
Я бы просто раздул поле - так код сильно проще.
Но вотрой метод так легко реализуется только тут, где нужно именно количетсво живых соседей и можно просто игнорировать клетки вне поля.