As you described, the solution is to use nested loops and loopindex - For "x" from 1 to 29, then For "y" from 1 to 31 as a sub event. Your action would be to create a tile object at x, y where x is loopindex("x")*gridwidth and y is loopindex("y")*gridheight. You can add a constant value to each of these to offset the start position.
In terms of optimization though... I personally would not create a potentially unused object for all the empty spaces and only create them when needed, in the right place.
General words of wisdom - Never fear running into a dead end. In the process of doing so, you will learn exactly why and how a dead end can occur, and how to work around it. Also, there is no such thing as starting from scratch, as long as you have the experience of going through whatever you did previously. A program that takes you a month to write the first time, even if you were to lose everything, you could probably recreate in just a few days the second time you do so.