Linux Game Programming for PC & Embedded Systems using SDL
Presented by
Fore June
Author of Windows Fan, Linux Fan

Games and SDL
SDL Installation
SDL for Embedded
SDL API
SDL Events
 SDL Graphics
SDL Threads
Thread Example
SDL Animation
SDL Sound
 Raw Video Player
Video Formats
Video Compression
 Game Trees
About The Author
@Copyright by Fore June, 2006

An SDL Multi-threaded Example: Tic-Tac-Toe

    We have discussed how to use SDL to create graphics, load images, process I/O events and develop multi-threaded programs. Here we present a simple tic-tac-toe program that will use all the SDL features discussed earlier and the SDL sound processing functions that we shall discuss in a later chapter in writing the program. Most readers may have known about the tic-tac-toe game in which two players alternatively put marks in a 3 x 3 array. One marks crosses ( X ), and the other marks circles ( O ). Whoever first fills a row, column or diagonal with her marks wins the game.

    In the game here, a user plays against the computer. The player can mark a quadrant by clicking the mouse or hitting a numeric key with a value corresponding to the desired quadrant number. Some background music is played while the game proceeds. When the game ends, the music stops and special sounds depending on the winning outcome is played.

    We shall also use a tic-tac-toe game to illustrate the concept of game trees in a later chapter. At the moment we simply use a simple table lookup technique to make the move rather than using the more sophisticated technique 'minimax search' presented in the Game Tree chapter to find an optimal move.

    The application consists of four short programs:

    The program tictactoe.cpp listed below is the main entry of the tic-tac-toe game.

    It uses SDL_CreateThread() to create four threads, namely, kthread for handling keyboard inputs, mthread for handling mouse inputs, gthread for handling the game, and sthread for handling sound. Prior to creating the threads, it also creates two mutexes, key_mutex and mouse_mutex which will be used to enforce that only a single thread can access shared key and mouse variables. The function SDL_EventFilter() sets up a filter to process all events before they are posted to the event queue; this feature makes handling SDL events very flexible. In our case the filter function is FilterEvents(), which is defined in the program "io_threads.cpp".

    Note that the Surface class discussed in Chapter SDL Graphics is used to create an SDL surface to present the graphics.

    After created the threads, the program sits in a while loop to update the screen until the global variable quit is set to true by a thread. It can also break out of the while loop if the event 'SDL_QUITMASK' is detected by SDL_PeepEvents(). The details of SDL_PeepEvents() is listed below. Each thread takes care of her own business and the threads may communicate via a few shared variables.

    The program game_thread.cpp listed below is responsible for playing the game. It contains the logic of making a best move. The function game_thread() is the entry point of the thread. After calling init_board() to initialize the 'board' for playing, it enters a while loop to check if the mouse queue or num queue is nonempty. If yes, it means that the player has made a move; the computer then takes appropriate action depending on the player's move. In particular, it calls the function load_image() to load a cross or a circle image on the board. When an outcome is decided, a music is played by calling the function play_sfx (). A player can start another game by hitting the key 's'. As mentioned above, a simple table-lookup technique is used to decide the computer's move.

    The program io_threads.cpp listed below takes care of the keyboard and mouse events. The function FilterEvents() is to customize the actions we want to take when we detect an event; we can either post the event in the SDL event queue or discard. This function is activated by SDL_setEventFilter ( FilterEvents ) in main() of tictactoe.cpp. The two threads, mouse_thread() and key_thread() run independently. The mouse_thread() detects if any mouse event has been posted to the SDL event queue; if there is, it puts the mouse coordinate into the queue mouseq. The key_thread() handles keyboard events posted to the SDL event queue. It saves any key value in the queue keyq and also in numq if the value is between 0 and 8; additionally, if the 'ESC' key is pressed, it sets the global variable quit to true to signal all the threads to quit. It is up to the game_thread() of game_thread.cpp to determine what to do with the mouse and key values. By separating the functionalities between threads cleanly, it makes programming easier and less error-prone. However, because the queues mouseq, keyq, and numq are shared between threads, we need to lock them using mutexes before operate on them and unlock afterward; this is accomplished by the functions SDL_mutexP() and SDL_mutexV() respectively. ( Practically, the program still works even if you do not use any of these locking mechanisms but then your program thus written is not robust. )

    Finally, the program sound.cpp listed below handles the music and sound effects. The thread sound_thread() plays music at the background. It first calls Mix_LoadMUS("music.ogg") to load the music file "music.ogg". It then plays the music by calling Mix_PlayMusic(music, -1) . The -1 in the argument of Mix_PlayMusic() informs the sound engine to play music again and again; if the argument value is 0, the music is played once only. We shall discuss more about sounds and music in a later chapter.

    The source code package including the music sound files and images can be obtained by clicking at tictactoe.tgz here. You can untar the package by the command,