Skip to main content
Serenity Valley follows a modular architecture with clear separation between game logic, utilities, and UI widgets.

Project Structure

The codebase is organized into several key modules:
source/
├── game.py              # Main game class and entry point
├── widgets.py           # UI widget components
├── vec2d.py             # 2D vector mathematics
├── utils.py             # Utility classes (Timer)
└── simpleanimation.py   # Animation system

Core Components

Game Class

The Game class (game.py:16-258) is the central controller that manages all game systems:
class Game(object):
    # Game parameters
    BG_TILE_IMG = 'images/wood2.png'
    BUTTON_BGIMG = 'images/x.png'
    SCREEN_WIDTH, SCREEN_HEIGHT = 580, 500
    GRID_SIZE = 20
    FIELD_SIZE = 400, 400
Game parameters are defined as class variables, making them globally accessible throughout the game instance.

Initialization

The Game constructor (game.py:29-137) sets up all game systems:
def __init__(self):
    pygame.init()
    
    # Set up screen and background
    self.screen = pygame.display.set_mode(
                    (self.SCREEN_WIDTH, self.SCREEN_HEIGHT), 0, 32)
    self.tile_img = pygame.image.load(self.BG_TILE_IMG).convert_alpha()
    
    # Initialize clock and game state
    self.clock = pygame.time.Clock()
    self.paused = False

Widget System

Widgets are reusable UI components defined in widgets.py. Each widget:
  • Receives a surface parameter (the screen) in its constructor
  • Implements a draw() method for rendering
  • Manages its own state and appearance

Widget Types

WidgetPurposeFile Location
BoxRectangular container with borderswidgets.py:22-68
MessageBoardText display boardwidgets.py:71-143
ButtonInteractive clickable buttonswidgets.py:146-218
ImagesStatic or animated imageswidgets.py:220-252
textEntryUser text inputwidgets.py:254-315
movingRectAnimated rectangleswidgets.py:317-337
movingImgAnimated imageswidgets.py:339-358
circlesCircle primitiveswidgets.py:360-369

Widget Instantiation

Widgets are created during game initialization. Here’s an example of creating a MessageBoard:
self.tboard = MessageBoard(self.screen,
    rect=self.tboard_rect,
    bgcolor=self.tboard_bgcolor,
    border_width=4,
    border_color=pygame.Color('black'),
    text=self.tboard_text,
    padding=5,
    font=('comic sans', 18),
    font_color=pygame.Color('yellow'))
And a Button widget:
self.button = Button(self.screen,
    pos=vec2d(self.tboard_width, self.tboard_y-15),
    btntype='Close',
    imgnames=self.button_bgimgs,
    attached=self.tboard)

The World List Pattern

Serenity Valley uses a world list pattern for managing and rendering game objects:
self.world = [self.button, self.togglebtn, self.clockImg, 
              self.hand, self.textTest, self.moveImg, 
              self.floater, self.ball]

Rendering the World

All objects in the world list are drawn each frame:
for obj in self.world:
    obj.draw()
The world list provides a simple but effective way to manage render order. Objects are drawn in list order, with later objects appearing on top.

Organized Collections

Some widgets are also stored in specialized lists for event handling:
self.buttons = [self.togglebtn]
self.textEntries = [self.textTest]
These collections allow targeted event dispatching:
for button in self.buttons:
    button.mouse_click_event(event.pos)
for entry in self.textEntries:
    entry.mouse_click_event(event.pos)

Coordinate Systems

The game uses two coordinate systems:

1. Pixel Coordinates

Direct screen positions measured in pixels from the top-left corner (0, 0).

2. Grid Coordinates

Grid-based coordinates using rows and columns:
self.GRID_SIZE = 20
self.grid_nrows = 30
self.grid_ncols = 30

Coordinate Conversion

The Game class provides methods to convert between systems:

Pixel to Grid

def xy2coord(self, pos):
    """ Convert a (x, y) pair to a (nrow, ncol) coordinate
    """
    x, y = (pos[0] - self.field_rect.left, pos[1] - self.field_rect.top)
    return (int(y) / self.GRID_SIZE, int(x) / self.GRID_SIZE)

Grid to Pixel (Center)

def coord2xy_mid(self, coord):
    """ Convert a (nrow, ncol) coordinate to a (x, y) pair,
        where x,y is the middle of the square at the coord
    """
    nrow, ncol = coord
    return (
        self.field_rect.left + ncol * self.GRID_SIZE + self.GRID_SIZE / 2, 
        self.field_rect.top + nrow * self.GRID_SIZE + self.GRID_SIZE / 2)
coord2xy_mid() returns the center of a grid cell, which is useful for centering sprites or objects within grid spaces.

Utility Modules

vec2d - 2D Vector Math

The vec2d class (vec2d.py:5-330) provides comprehensive 2D vector operations:
class vec2d(object):
    """2d vector class, supports vector and scalar operators,
       and also provides a bunch of high level functions
    """
    __slots__ = ['x', 'y']
    
    def __init__(self, x_or_pair, y = None):
        if y == None:
            self.x = x_or_pair[0]
            self.y = x_or_pair[1]
        else:
            self.x = x_or_pair
            self.y = y
Used throughout the codebase for positions and velocities:
self.button = Button(self.screen,
    pos=vec2d(self.tboard_width, self.tboard_y-15),
    btntype='Close',
    imgnames=self.button_bgimgs,
    attached=self.tboard)

Timer - Event Scheduling

The Timer class (utils.py:2-37) provides interval-based callbacks:
class Timer(object):
    """ A Timer that can periodically call a given callback 
        function.
    """
    def __init__(self, interval, callback, oneshot=False):
        self.interval = interval
        self.callback = callback
        self.oneshot = oneshot
        self.time = 0
        self.alive = True
Used in the animation system (simpleanimation.py:32-33):
self.scroll_timer = Timer(scroll_period, self._advance_img)
self.active_timer = Timer(duration, self._inactivate, True)

Drawing System

The game uses a layered drawing approach:
def draw(self):
    # Draw background image
    self.draw_background()
    
    # Decide if we should draw grid
    if self.options['draw_grid']:
        self.draw_grid()
        
    self.tboard.draw()
    
    for obj in self.world:
        obj.draw()

Background Tiling

The background is drawn by tiling an image across the screen:
def draw_background(self):
    img_rect = self.tile_img.get_rect()
    nrows = int(self.screen.get_height() / img_rect.height) + 1
    ncols = int(self.screen.get_width() / img_rect.width) + 1
    
    for y in range(nrows):
        for x in range(ncols):
            img_rect.topleft = (x * img_rect.width, 
                                y * img_rect.height)
            self.screen.blit(self.tile_img, img_rect)
The tiling algorithm calculates the number of rows and columns needed to cover the screen, then blits the tile image in a grid pattern.

Entry Point

The game starts through the standard Python entry point:
if __name__ == "__main__":
    print "Creating game object..."
    game = Game()
    print "Done. Starting run method"
    game.run()
This creates a Game instance and starts the main loop immediately.

Build docs developers (and LLMs) love