To support drawing tile-based map of arbitrarily large bounds, you want to take the approach of not rendering every tile all the time. You only need to attempt to draw what the player could see, any other draw attempts are a waste of processing time and resources. This is an API-agnostic approach and it doesn't matter whether you are using Pygame, cocos2d, or any other API.
To accomplish this feat, you need to know some information that is basically an extension of that you'd need for handling scrolling in a side-scrolling platformer.
You need to know
- the position in within the global tile map that the player is looking at,
- the size (in tiles) of the visible around centered around that look position
Every update you simply compute the bounding rectangle that describes which tiles are visible based on those two pieces of information and submit only those tiles for rendering. To re-use the code snippet from my answer in the above-linked question, you compute
var viewportLeft = focusPoint.X - (viewportSize.Width / 2);
var viewportRight = focusPoint.X + (viewportSize.Width / 2);
var viewportTop = focusPoint.Y + (viewportHeight / 2);
var viewportBottom = focusPoint.Y - (viewportHeight / 2);
and draw only the tiles bounded by the above four variables.
If your game support smooth scrolling of the tile map, rather than tile-by-tile scrolling, you may want to expand the viewport width and height slightly to account for the potential overlap.
To efficiently render the set of tiles you know about, you want to minimize state changes and draw calls. Ideally, your actual tile sprites are stored in a texture image, a specific type of texture atlas where all the images in the atlas fit on grid. This allows you to have only that one texture and related shader bound, minimizing those state changes.
To reduce draw calls, you want to have all the geometry for all the tiles you are going to render in as few vertex buffers as possible. If you are using the 2D sprite rendering functionality of your higher-level API, most of those will support the batching of geometry (and state changes, for that matter) in their "sprite batch" style classes if they have
end type members. If not you may need to do it manually, usually by making use of a dynamic vertex buffer which you refill with the tile vertices, texture coordinates, and so for all the appropriate tiles every frame.