0
\$\begingroup\$

In my game project I create the terrain programmatically by creating tiles on a 2D grid as the player moves around the game map. I have it working where I select the current tile and check all 8 positions around the tile, then I create a new tile at those positions unless one already exists there. This works in a "hard-coded" way, where I can take any given grid position like (x: 0, y: 0) and then I check all the positions around it in this fashion:

(x: thisPosition.x - 1, y: thisPosition.y + 1)  // POSITION UP-LEFT
(x: thisPosition.x,     y: thisPosition.y + 1)  // POSITION UP
(x: thisPosition.x + 1, y: thisPosition.y + 1)  // POSITION UP-RIGHT
(x: thisPosition.x - 1, y: thisPosition.y)      // POSITION LEFT
(x: thisPosition.x + 1, y: thisPosition.y)      // POSITION RIGHT
(x: thisPosition.x - 1, y: thisPosition.y - 1)  // POSITION DOWN-LEFT
(x: thisPosition.x,     y: thisPosition.y - 1)  // POSITION DOWN
(x: thisPosition.x + 1, y: thisPosition.y - 1)  // POSITION DOWN-RIGHT

This works, but I can only check one "ring" around the selected tile at a time (as shown below in the image on the left). I want to replace this function with a new, improved version that works in a more programmatic way, something using nested loops. I want to be able to give the function a number, and then it will check that many "rings" around the selected tile and add terrain tiles to those positions. For example, if I ask it to check 3 rings, it will check all of the tiles shown in the image on the right:

rings

I have some ideas but I'm not sure yet how to put them into code. I'm sure that someone online has solved this exact problem before, only I can't find it. I thought I'd put this question out here and see if this rings any bells for someone.

If anyone has any code suggestions, it would be helpful if you could write pseudocode, since the only language I know right now is Swift. I'd like to learn C and C++ someday, but I'm just a greenhorn hobbyist and starting with Swift was easiest for me.

\$\endgroup\$
4
  • \$\begingroup\$ Do you care about the order of the checks? Because if you don't, you can figure out the range in x and the range y, and iterate over that. \$\endgroup\$
    – Theraot
    Commented Feb 17, 2022 at 17:19
  • \$\begingroup\$ Would a spiral iteration order work for your needs? \$\endgroup\$
    – DMGregory
    Commented Feb 17, 2022 at 17:23
  • \$\begingroup\$ @Theraot No, the order of the checks doesn't matter. \$\endgroup\$
    – peacetype
    Commented Feb 17, 2022 at 19:08
  • \$\begingroup\$ @DMGregory Thanks for the suggestion. I checked out that post but I think the problem it solves is significantly more complex than mine. I ended up going more in the direction that Theraot proposed. \$\endgroup\$
    – peacetype
    Commented Feb 18, 2022 at 11:34

2 Answers 2

1
\$\begingroup\$

When the order does not matter, then you can simply check a whole square from top-left to bottom-down, and skip the position of the origin. I never programmed in Swift before, so here is how I believe how that would look in Swift based on a couple examples I could find:

func checkAround(centerX: Int, centerY: Int, radius: Int) {    
     for var x = centerX - radius; x <= centerX + radius; ++x {
         for var y = centerY - radius; y <= centerY + radius; ++y {
              if x != centerX || y != centerY {
                  check(x: x, y: y);
              }
         }
     }
}
\$\endgroup\$
3
  • \$\begingroup\$ Thanks for this! I like that your answer is more concise than mine, so I added a Swift version to my answer. Aside from the language changes, I had to make one change to the logic. I changed the logical AND operator to a logical OR operator. It wasn't working for me with the AND operator. If you input checkAround(centerX: 0, centerY: 0, radius: 1) it should print out 8 times, but it only printed out 4 times. With the OR operator it prints all the expected values. I wonder if this has something to do with the language changes I made. Did your version work with the AND operator? \$\endgroup\$
    – peacetype
    Commented Feb 18, 2022 at 13:26
  • \$\begingroup\$ @peacetype As i said, I never programmed in Swift, and I am not going to start just for this question. So I did not test the code. But I believe that this correction is correct, so I applied it. \$\endgroup\$
    – Philipp
    Commented Feb 18, 2022 at 13:28
  • \$\begingroup\$ Great! I think it was just the difference between those two operators that would prevent this solution from working. I'll mark this as the accepted answer since it's more C-style code which I think would be more helpful to people here. Thanks! \$\endgroup\$
    – peacetype
    Commented Feb 18, 2022 at 13:31
0
\$\begingroup\$

Theraot's comment got me on the right track. See my function below. I also included a GridPoint structure to represent points on the 2D tile grid.

In my game, the starting position will already have a tile so it doesn't need to be included in the array of points that I will use to generate new tiles. But if you want to include the starting position in the array, just remove this line: if i == 0 && j == 0 { continue }.

I did this in Swift; sorry to the C/C++/C# folks. I will tackle those languages someday...

// Conforming to CustomStringConvertible for nicer printing.
struct GridPoint: CustomStringConvertible {
    var x = 0
    var y = 0
    
    var description: String {
        return "\(x),\(y)"
    }
}

func selectSurroundingPoints(forPosition p: GridPoint, howManyRings r: Int) -> [GridPoint] {
    var arrayOfPoints = [GridPoint]()
    
    for i in 0...r * 2 {
        for j in 0...r * 2 {
            if i == 0 && j == 0 { continue }
            
            var point = GridPoint()
            
            if i <= r {
                point.x = p.x + i
            } else {
                point.x = p.x + (r - i)
            }
            
            if j <= r {
                point.y = p.y + j
            } else {
                point.y = p.y + (r - j)
            }
            
            arrayOfPoints.append(point)
        }
    }
    
    return arrayOfPoints
}

// Usage
let checkPosition = GridPoint(x: 0, y: 0)
let surroundingPoints = selectSurroundingPoints(forPosition: checkPosition, howManyRings: 1)

for point in surroundingPoints {
    print(point)
}

UPDATE:

Philipp's answer is more concise than mine. I like his better so I wrote it here in Swift:

func checkAround(centerX: Int, centerY: Int, radius: Int) {
    for x in (centerX - radius)...(centerX + radius) {
        for y in (centerY - radius)...(centerY + radius) {
            if x != centerX || y != centerY {
                print("\(x), \(y)")
            }
        }
    }
}

// Usage
checkAround(centerX: 0, centerY: 0, radius: 1)

/*
 Prints:
 -1, -1
 -1, 0
 -1, 1
 0, -1
 0, 1
 1, -1
 1, 0
 1, 1
 */
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .