Artificial intelligence (AI) is one of those subjects I really wish I'd taken while at university. Philosophical questions about intelligence aside, much of what AI comes down to in practice is techniques for solving problems with computers, which is really what computer science is all about.

A classic AI endeavour is programming computers to play chess. Shannon wrote an article about it already in 1948, and in 1997 the computer Deep Blue defeated humankind represented by world champion Garry Kasparov. A similar but much simpler board game is Othello (sometimes called Reversi), and writing an Othello program has become a popular exercise in AI classes.

I wanted to try my hand at writing an Othello game, but just doing a text-based version seemed lame; it really needs a graphical user interface. I never did much GUI programming (we did some in Java at university, but those weren't real programs), so it would be a good exercise for that too. The question was what platform to target. I use Linux, but most desktop computers run Windows or macOS. Also, most people run programs on smart-phones these days, and isn't the web the future of computing anyway? It would be interesting to learn about all of these platforms. The project scope had suddenly expanded.

This post describes the implementation of a basic Othello game engine in C with native user interfaces for Linux (X11), Windows, Mac, iOS, Android, and the web (asm.js and WebAssembly).

The source code is available in othello.tar.gz, the Windows executable in othello.exe, macOS application in MacOthello.zip, the iOS game in the App Store, the Android game on Google Play, and the web version right here.

Thanks to Nico who provided valuable feedback on drafts of this post!

Update 2019-11-05: Added dark mode support to the iOS port.

Table of Contents

The Othello Engine

This section describes the implementation of a simple Othello engine. It's by no means very strong — other engines can beat it easily — but in practice it seems to win over most human players.

The code is in othello.h and othello.c.

Game Representation: Bitboards

One particularly appealing aspect of Othello (and chess too) is that it is played on an 8-by-8 board, which means the number of squares on the board equals the number of bits in the machine word of a modern 64-bit computer.

Each cell on the board is assigned a corresponding bit position:

A B C D E F G H 1 0 1 2 3 4 5 6 7 2 8 9 10 11 12 13 14 15 3 16 17 18 19 20 21 22 23 4 24 25 26 27 28 29 30 31 5 32 33 34 35 36 37 38 39 6 40 41 42 43 44 45 46 47 7 48 49 50 51 52 53 54 55 8 56 57 58 59 60 61 62 63

We use two computer words, or bitboards, to keep track of the black and white disks. Each bitboard has the (row * 8 + col)th least significant bit set if and only if there is a disk in that position:

typedef struct { uint64_t disks [ 2 ]; } othello_t ; typedef enum { CELL_BLACK = 0 , CELL_WHITE = 1 , CELL_EMPTY = 2 } cell_state_t ; typedef enum { PLAYER_BLACK = 0 , PLAYER_WHITE = 1 } player_t ; cell_state_t othello_cell_state ( const othello_t * o , int row , int col ) { uint64_t mask = 1ULL << ( row * 8 + col ); assert ( row >= 0 && row <= 7 ); assert ( col >= 0 && col <= 7 ); if ( o -> disks [ PLAYER_BLACK ] & mask ) { return CELL_BLACK ; } if ( o -> disks [ PLAYER_WHITE ] & mask ) { return CELL_WHITE ; } return CELL_EMPTY ; } void othello_set_cell_state ( othello_t * o , int row , int col , cell_state_t s ) { uint64_t mask = 1ULL << ( row * 8 + col ); assert ( row >= 0 && row <= 7 ); assert ( col >= 0 && col <= 7 ); o -> disks [ PLAYER_BLACK ] &= ~ mask ; o -> disks [ PLAYER_WHITE ] &= ~ mask ; if ( s == CELL_BLACK ) { o -> disks [ PLAYER_BLACK ] |= mask ; } else if ( s == CELL_WHITE ) { o -> disks [ PLAYER_WHITE ] |= mask ; } }

To set up a new game, we put disks in the starting position: black disks on D5 and E4, white disks on D4 and E5.

void othello_init ( othello_t * o ) { o -> disks [ PLAYER_BLACK ] = 0 ; o -> disks [ PLAYER_WHITE ] = 0 ; othello_set_cell_state ( o , 3 , 4 , CELL_BLACK ); othello_set_cell_state ( o , 4 , 3 , CELL_BLACK ); othello_set_cell_state ( o , 3 , 3 , CELL_WHITE ); othello_set_cell_state ( o , 4 , 4 , CELL_WHITE ); }

Counting the number of disks in a bitboard (the population count) is useful, for example to compute a player's score. Some processors provide special instructions for this (POPCNT on x86), and GCC provides a built-in function to use such an instruction if available. For non-GCC we use a for-loop (see Hacker's Delight, Chapter 5 for many ways of doing this).

static int popcount ( uint64_t x ) { #ifdef __GNUC__ return __builtin_popcountll ( x ); #else int n = 0 ; while ( x ) { x &= x - 1 ; n ++ ; } return n ; #endif } int othello_score ( const othello_t * o , player_t p ) { return popcount ( o -> disks [ p ]); }

Storing the disks in bitboards is memory efficient, but it's nothing more than a cute trick unless we can operate on them efficiently. It turns out that we can.

Using bitwise operators, we can perform set operations on the disks. For example, (black_disks | white_disks) computes the union of both sets of disks, and ~(black_disks | white_disks) gets the complement, that is the set of empty squares on the board.

This is exciting because we are using a few very fast instructions to operate on many disks at the same time.

But it gets better! Using bitwise shift operations, we can move disks around. Looking at the bits with the least significant bit at the lower right, see what happens when we do bitboard >> 1:

bitboard 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 bitboard >> 1 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

Each bit has moved to the right, except the bits in the right-most column which wrapped around, or in the case of zero was pushed off the board. To exclude the bits that wrap around, we use a bitwise and to mask them off (0x7F in binary is 01111111):

(bitboard >> 1) & 0x7F7F7F7F7F7F7F7FULL 63 62 61 60 59 58 57 55 54 53 52 51 50 49 47 46 45 44 43 42 41 39 38 37 36 35 34 33 31 30 29 28 27 26 25 23 22 21 20 19 18 17 15 14 13 12 11 10 9 7 6 5 4 3 2 1

Since the bit in each position represents a disk on the board, we now have a way of moving all disks one step to the right with only a shift and an and.

The same technique can be used for all eight directions. To move the disks left, left-shift one step and mask off the rightmost column. To move up or down, shift left or right 8 bits respectively, no masking necessary. To move down-and-left, shift right 7 bits, mask off the rightmost column and top row. And so on.

The function below shifts disks one step in a certain direction:

#define NUM_DIRS 8 /* Shift disks in direction dir. */ static uint64_t shift ( uint64_t disks , int dir ) { /* Note: the directions refer to how we shift the bits, not the positions on the board (where the least significant bit is the top-left corner). */ static const uint64_t MASKS [] = { 0x7F7F7F7F7F7F7F7FULL , /* Right. */ 0x007F7F7F7F7F7F7FULL , /* Down-right. */ 0xFFFFFFFFFFFFFFFFULL , /* Down. */ 0x00FEFEFEFEFEFEFEULL , /* Down-left. */ 0xFEFEFEFEFEFEFEFEULL , /* Left. */ 0xFEFEFEFEFEFEFE00ULL , /* Up-left. */ 0xFFFFFFFFFFFFFFFFULL , /* Up. */ 0x7F7F7F7F7F7F7F00ULL /* Up-right. */ }; static const uint64_t LSHIFTS [] = { 0 , /* Right. */ 0 , /* Down-right. */ 0 , /* Down. */ 0 , /* Down-left. */ 1 , /* Left. */ 9 , /* Up-left. */ 8 , /* Up. */ 7 /* Up-right. */ }; static const uint64_t RSHIFTS [] = { 1 , /* Right. */ 9 , /* Down-right. */ 8 , /* Down. */ 7 , /* Down-left. */ 0 , /* Left. */ 0 , /* Up-left. */ 0 , /* Up. */ 0 /* Up-right. */ }; assert ( dir >= 0 && dir < NUM_DIRS ); if ( dir < NUM_DIRS / 2 ) { assert ( LSHIFTS [ dir ] == 0 && "Shifting right." ); return ( disks >> RSHIFTS [ dir ]) & MASKS [ dir ]; } else { assert ( RSHIFTS [ dir ] == 0 && "Shifting left." ); return ( disks << LSHIFTS [ dir ]) & MASKS [ dir ]; } }

(Why shift all bits and mask afterwards instead of masking first, thereby only shifting the bits we want? It's because later we will do shift(x, dir) & opp_disks repeatedly, and this way masks[dir] & opp_disks becomes a common subexpression that only needs to be computed once.)

Move Generation and Resolution

Computing the set of legal moves for a player, a process known as move generation, is the first step towards computing the best move to make. The operations explained above allow us to do it efficiently.

A legal Othello move is one which captures one or more of the opponent's disks between the new disk and one of the player's current disks in a straight line — horizontally, vertically or diagonally.

For example, on the board below the legal moves for black are C3, C4 and C6 (marked with X):

A B C D E F G H 1 2 3 X 4 X 5 6 X 7 8

Consider the black disk at F6 above. Moving up-and-left, it has a white neighbour disk (E5), and then another one (D4), and then an empty square at C3 which means that's a valid move.

This reasoning can be turned into an algorithm. For each of our disks, we follow opponent neighbour disks in some direction until we hit an empty square, which is then a legal move. Since the board is 8 squares wide, that empty square can be a maximum of 7 squares away from the original disk. Repeating this for each direction will find all legal moves.

The bitboard representation allows us to work with all the disks simultaneously. For example, to get the opponent disks immediately to the right of ours, we do x = shift(my_disks, 0) & opp_disks, that is we take the intersection of our disks shifted one step right and the set of opponent disks. To add the opponent disks adjacent to those, we do it again: x |= shift(x, 0) & opp_disks. After seven shifts we intersect with the set of empty cells to get the of valid moves in that direction. The function below implements this technique to generate the moves:

static uint64_t generate_moves ( uint64_t my_disks , uint64_t opp_disks ) { int dir ; uint64_t x ; uint64_t empty_cells = ~ ( my_disks | opp_disks ); uint64_t legal_moves = 0 ; assert (( my_disks & opp_disks ) == 0 && "Disk sets should be disjoint." ); for ( dir = 0 ; dir < NUM_DIRS ; dir ++ ) { /* Get opponent disks adjacent to my disks in direction dir. */ x = shift ( my_disks , dir ) & opp_disks ; /* Add opponent disks adjacent to those, and so on. */ x |= shift ( x , dir ) & opp_disks ; x |= shift ( x , dir ) & opp_disks ; x |= shift ( x , dir ) & opp_disks ; x |= shift ( x , dir ) & opp_disks ; x |= shift ( x , dir ) & opp_disks ; /* Empty cells adjacent to those are valid moves. */ legal_moves |= shift ( x , dir ) & empty_cells ; } return legal_moves ; }

(This method of "following" connected disks is a flood fill algorithm, and because we do it seven times, it is sometimes referred to as Dumb7Fill.)

(While the bitwise operations are very efficient, having to repeat the whole process eight times might seem a little annoying. But note that except for the update of legal_moves, each iteration of the loop body performs exactly the same operations over independent sets of data — it is perfect for SIMD machine instructions. Compiled with GCC 6 on my Haswell machine, the function can execute about 23 million times per second, but with AVX2 instructions enabled it runs 85 million times per second. Pretty fast!)

We use generate_moves to check whether a player has legal moves or whether a certain proposed move is valid in the code below. (Since p is 1 or 0, p ^ 1, gets us the other player. !p would also work, but the XOR version tends to yield smaller and faster code.)

bool othello_has_valid_move ( const othello_t * o , player_t p ) { return generate_moves ( o -> disks [ p ], o -> disks [ p ^ 1 ]) != 0 ; } bool othello_is_valid_move ( const othello_t * o , player_t p , int row , int col ) { uint64_t mask = 1ULL << ( row * 8 + col ); assert ( row >= 0 && row <= 7 ); assert ( col >= 0 && col <= 7 ); return ( generate_moves ( o -> disks [ p ], o -> disks [ p ^ 1 ]) & mask ) != 0 ; }

To update the board after a move has been made, a process called move resolution, we add the new disk to the board and flip the opponent disks that were captured. To find the captured disks, we use a similar method to the one above. For each direction, we follow opponent disks adjacent to the new disk, and if we end up at one of our own disks, the opponent disks we followed are captured:

static void resolve_move ( uint64_t * my_disks , uint64_t * opp_disks , int board_idx ) { int dir ; uint64_t x , bounding_disk ; uint64_t new_disk = 1ULL << board_idx ; uint64_t captured_disks = 0 ; assert ( board_idx < 64 && "Move must be within the board." ); assert (( * my_disks & * opp_disks ) == 0 && "Disk sets must be disjoint." ); assert ( ! (( * my_disks | * opp_disks ) & new_disk ) && "Target not empty!" ); * my_disks |= new_disk ; for ( dir = 0 ; dir < NUM_DIRS ; dir ++ ) { /* Find opponent disk adjacent to the new disk. */ x = shift ( new_disk , dir ) & * opp_disks ; /* Add any adjacent opponent disk to that one, and so on. */ x |= shift ( x , dir ) & * opp_disks ; x |= shift ( x , dir ) & * opp_disks ; x |= shift ( x , dir ) & * opp_disks ; x |= shift ( x , dir ) & * opp_disks ; x |= shift ( x , dir ) & * opp_disks ; /* Determine whether the disks were captured. */ bounding_disk = shift ( x , dir ) & * my_disks ; captured_disks |= ( bounding_disk ? x : 0 ); } assert ( captured_disks && "A valid move must capture disks." ); * my_disks ^= captured_disks ; * opp_disks ^= captured_disks ; assert ( ! ( * my_disks & * opp_disks ) && "The sets must still be disjoint." ); } void othello_make_move ( othello_t * o , player_t p , int row , int col ) { assert ( othello_is_valid_move ( o , p , row , col )); resolve_move ( & o -> disks [ p ], & o -> disks [ p ^ 1 ], row * 8 + col ); }

Position Evaluation

In order to find a good move, the program needs to be able to differentiate between good and bad game positions. Obviously a won game is good and a lost one is bad, but since the program cannot look ahead until the end of the game most of the time, it needs to evaluate intermediate positions as well.

The evaluation function is a heuristic that estimates how good the position is for the current player: the better the position, the larger the returned value. If the position is better for the opponent, the value is negative.

Because the objective of Othello is to have the most disks in the end, a naive way of evaluating positions would be to simply compare the number of disks for both players. This is a bad idea since disks can flip and many disks for one player could become many disks for the opponent after the next move.

Another idea is to assign weights to the different squares on the board and compute a score based on disks in those squares. For example, corner disks are valuable because they cannot be flipped, and the squares next to the corners are dangerous because they potentially give the opponent access to the corner. Coming up with good weights for the whole board is tricky, though.

My evaluation function is based on the mobility-based approach from Gunnar Andersson's homepage. It computes a score using three features of the position: the number of available moves, frontier disks (disks adjacent to empty cells), and captured corners:

static void frontier_disks ( uint64_t my_disks , uint64_t opp_disks , uint64_t * my_frontier , uint64_t * opp_frontier ) { uint64_t empty_cells = ~ ( my_disks | opp_disks ); uint64_t x ; int dir ; * my_frontier = 0 ; * opp_frontier = 0 ; for ( dir = 0 ; dir < NUM_DIRS ; dir ++ ) { /* Check cells adjacent to empty cells. */ x = shift ( empty_cells , dir ); * my_frontier |= x & my_disks ; * opp_frontier |= x & opp_disks ; } } #define WIN_BONUS (1 << 20) static int eval ( uint64_t my_disks , uint64_t opp_disks , uint64_t my_moves , uint64_t opp_moves ) { static const uint64_t CORNER_MASK = 0x8100000000000081ULL ; int my_disk_count , opp_disk_count ; uint64_t my_corners , opp_corners ; uint64_t my_frontier , opp_frontier ; int score = 0 ; if ( ! my_moves && ! opp_moves ) { /* Terminal state. */ my_disk_count = popcount ( my_disks ); opp_disk_count = popcount ( opp_disks ); return ( my_disk_count - opp_disk_count ) * WIN_BONUS ; } my_corners = my_disks & CORNER_MASK ; opp_corners = opp_disks & CORNER_MASK ; frontier_disks ( my_disks , opp_disks , & my_frontier , & opp_frontier ); /* Optimize for corners, mobility and few frontier disks. */ score += ( popcount ( my_corners ) - popcount ( opp_corners )) * 16 ; score += ( popcount ( my_moves ) - popcount ( opp_moves )) * 2 ; score += ( popcount ( my_frontier ) - popcount ( opp_frontier )) * - 1 ; assert ( abs ( score ) < WIN_BONUS ); return score ; }

Note that scores in the end state are computed separately; an actual win is better than even the most promising non-final position.

The evaluation function is very much a "careful what you wish for" kind of thing. If you give a large bonus for capturing corners, the program will try to capture corners at the expense of the other criteria. A program with a sufficiently strong evaluation function will beat an opponent with a weaker one, even if the opponent is considering moves further ahead in the game; optimizing for the wrong metric is no use in the end, no matter how well one does it.

Computing the Best Move

Now that we know what legal moves are available and have a heuristic for scoring different positions, how do we compute the best move?

One optimistic approach would be to consider each move and pick the one that could lead to the best future position. The only problem is that our opponent is likely to do their best in preventing us from reaching it.

A more carefully calculated approach is to assume our opponent is doing their best to defeat us. For each move we could make, we assume that the opponent would play to minimize the score for us, and so we pick the move which yields the largest, maximum, of those scores — the minimax value.

How do we know what move our opponent would make to minimize the score? We use the same idea: the opponent considers how we would follow their move, and picks the minimum of those scores. This gives us a recursive algorithm:

function minimax(position, player): if position is terminal or max depth reached: return eval(position) if player == black: max_score = -infinity for each move: new_position = resolve(position, move) score = minimax(new_position, white) if score > max_score: max_score = score best_move = move return max_score if player == white: min_score = infinity for each move: new_position = resolve(position, move) score = minimax(new_position, black) if score < min_score: min_score = score best_move = move return min_score

Alpha-beta pruning is an important optimization of this algorithm. The idea is to keep track of the best guaranteed result for black (alpha) and for white (beta) in the search so far, that is alpha <= score <= beta. As long as those bounds can be tightened, in other words while alpha < beta, the search continues, but scores outside the range are not worth considering since they cannot impact the result. This significantly reduces the number of moves that need to be considered:

function minimax(position, player, alpha, beta): if position is terminal or max depth reached: return eval(position) if player == black: max_score = -infinity for each move: new_position = resolve(position, move) score = minimax(new_position, white, alpha, beta) if score > max_score: max_score = score best_move = move alpha = max(alpha, score) if alpha >= beta: break return max_score if player == white: min_score = infinity for each move: new_position = resolve(position, move) score = minimax(new_position, black, alpha, beta) if score < min_score: min_score = score best_move = move beta = min(beta, score) if alpha >= beta: break return min_score

Patrick Winston explains minimax with alpha-beta pruning in his lecture from the MIT AI course (minimax starts at 16:30).

As a convenience when implementing this, to avoid having separate cases for black and white in the minimax function, we will use a formulation of the algorithm called negamax. Instead of letting the white player search for a minimum score from the position after our move, we reverse the position (black disks become white and vice versa) and search for a maximum score from there. We then negate the result of that (high scores for the opponent are low scores for us). This computes the exact same value as before, but with the same code for both players:

function negamax(position, alpha, beta): if position is terminal or max depth reached: return eval(position) max_score = -infinity for each move: new_position = resolve(position, move) score = -negamax(reverse(new_position), -beta, -alpha) if score > max_score: max_score = score best_move = move alpha = max(alpha, score) if alpha >= beta: break return max_score

This is what the implementation looks like:

static int negamax ( uint64_t my_disks , uint64_t opp_disks , int max_depth , int alpha , int beta , int * best_move , int * eval_count ) { uint64_t my_moves , opp_moves ; uint64_t my_new_disks , opp_new_disks ; int i , s , best ; /* Generate moves. */ my_moves = generate_moves ( my_disks , opp_disks ); opp_moves = generate_moves ( opp_disks , my_disks ); if ( ! my_moves && opp_moves ) { /* Null move. */ return - negamax ( opp_disks , my_disks , max_depth , - beta , - alpha , best_move , eval_count ); } if ( max_depth == 0 || ( ! my_moves && ! opp_moves )) { /* Maximum depth or terminal state reached. */ ++* eval_count ; return eval ( my_disks , opp_disks , my_moves , opp_moves ); } /* Find the best move. */ assert ( alpha < beta ); best = - INT_MAX ; for ( i = 0 ; i < 64 ; i ++ ) { if ( ! ( my_moves & ( 1ULL << i ))) { continue ; } my_new_disks = my_disks ; opp_new_disks = opp_disks ; resolve_move ( & my_new_disks , & opp_new_disks , i ); s = - negamax ( opp_new_disks , my_new_disks , max_depth - 1 , - beta , - alpha , NULL , eval_count ); if ( s > best ) { best = s ; if ( best_move ) { * best_move = i ; } alpha = s > alpha ? s : alpha ; if ( alpha >= beta ) { break ; } } } return best ; }

How deep should we search? That depends on how long we're willing to wait and where we are in the game: towards the end of the game we can search deeper because there are fewer possible moves.

The technique we will use is called iterative deepening. The idea is to keep searching deeper until the best move is a win or loss, or we run out of time. However, since I would like the program to play identically across all platforms and devices, instead of a time budget we will use a budget for number of evaluations:

static int iterative_negamax ( uint64_t my_disks , uint64_t opp_disks , int start_depth , int eval_budget ) { int depth , best_move , eval_count , s ; assert ( start_depth > 0 && "At least one move must be explored." ); eval_count = 0 ; best_move = - 1 ; for ( depth = start_depth ; eval_count < eval_budget ; depth ++ ) { s = negamax ( my_disks , opp_disks , depth , - INT_MAX , INT_MAX , & best_move , & eval_count ); if ( s >= WIN_BONUS || - s >= WIN_BONUS ) { break ; } } assert ( best_move != - 1 && "No move found?" ); return best_move ; } void othello_compute_move ( const othello_t * o , player_t p , int * row , int * col ) { int move_idx ; static const int START_DEPTH = 8 ; static const int EVAL_BUDGET = 500000 ; assert ( othello_has_valid_move ( o , p )); move_idx = iterative_negamax ( o -> disks [ p ], o -> disks [ p ^ 1 ], START_DEPTH , EVAL_BUDGET ); * row = move_idx / 8 ; * col = move_idx % 8 ; }

User Interfaces

X11

The X Window System, X11 (11 being the latest major revision, released in 1987), or just X, is like an ancient beast lurking at the bottom of the lake of Unix, old and ever-present. First developed at MIT in the eighties, it's still at the centre of desktop environments today. It is slated to be replaced by Wayland, but that hasn't happened yet.

While virtually all graphical Unix applications use it, almost none interact with X directly. Instead, a graphical toolkit library such as GTK+ or Qt is normally used. Besides providing a more modern programming interface, such toolkits provide routines for using application widgets — buttons, text boxes, menus, etc. Even really old X programs used toolkits such as Xaw or Motif, because X itself only deals with primitives such as windows, lines, circles, etc.

The Othello game doesn't really need any widgets though, and I would like to learn about X at a low level, so we will use it directly through its client library, Xlib. (One could go even deeper: since X is a network protocol, we could send packets to the server directly, no client library needed. However, that might be one step too far even for this post.)

I learned about X from the classic manuals published by O'Reilly: Xlib Programming Manual (Vol 1) and Xlib Reference Manual (Vol 2) edited by Adrian Nye. (According to legend, the X manuals were what got O'Reilly started.) These are now out of print, but used copies are easy to come by. X.Org has more documentation. See also Jasper St. Pierre's Xplain series.

The code is available in x11_othello.c.

Our Othello program will be organized more or less according to the Model-view-controller pattern. We model the game using the structures and routines defined in the previous section, and an enum to keep track of whose turn it is:

static othello_t board ; static enum { BLACKS_MOVE , WHITES_MOVE , GAME_OVER } state ;

The view is what the user sees, the visualization of the game. It is drawn as a grid with disks in the appropriate cells, etc. We keep track of the grid's size and position in the window, as well as the currently selected cell:

static struct { int x , y ; /* Position of the grid relative to window origin. */ int size ; /* Size (width and height are equal) of the grid. */ int cell_size ; /* Size of a grid cell, not including its border. */ int sel_row ; /* Currently selected row, or -1 if none. */ int sel_col ; /* Currently selected column. */ } grid ;

To draw our grid and interact with X in general, we need a few more things:

static Display * display ; static Window win ; static GC black_gc ; static GC white_gc ; static GC board_gc ; /* For the board background. */ static GC highlight_gc ; /* For selected grid cells. */ static XFontStruct * font ; static Atom wm_delete_window ;

X11 is a network protocol, the idea being that an application does not necessarily have to be running on the same computer through which a user interacts with it. The application (X client) could be running on one machine, perhaps a beefy computer in a data centre, while the user interacts with it through the keyboard, mouse and monitor of a computer (X server) in their office. The Display object represents the connection to the X server, which these days is normally on the same machine as the application itself.

The Window object represents the window our program will use to interact with the user. The GC objects are graphics contexts. They determine the style (colour, stroke width, etc) when drawing lines and such. The XFontStruct is what we'll use for text, and the Atom refers to a message we need to handle.

Initialization

We need a fair amount of boilerplate code to initialize our application. (This is what we get for using a low-level library.) First, we attempt to connect to the server with XOpenDisplay:

#define CELL_GAP 1 /* Cell gap in pixels. */ #define FONT_NAME "9x15" /* Font for labels and status. */ #define MIN_SIZE 300 /* Minimum window size. */ #define INIT_SIZE 450 /* Initial window size. */ static void init ( int argc , char ** argv ) { XSizeHints * size_hints ; XWMHints * wm_hints ; XClassHint * class_hint ; unsigned long black_color , white_color , grey_color ; unsigned long board_color , hl_color ; char * window_name = "Othello" ; XTextProperty window_name_prop ; /* Connect to the display. */ if ( ! ( display = XOpenDisplay ( NULL ))) { err ( "cannot connect to X server %s" , XDisplayName ( NULL )); }

The NULL argument specifies that we want the default screen (specified by the DISPLAY envionment variable, usually as :0, meaning the first display on the local machine).

Once connected, we allocate some colours. XAllocColor asks the server to add a specific colour (or as close an approximation as possible) to its colormap, or palette, and returns an index into this map. We wrap the call in a convenience function:

static unsigned long alloc_color ( uint8_t red , uint8_t green , uint8_t blue ) { XColor color ; Colormap map ; map = DefaultColormap ( display , DefaultScreen ( display )); color . red = red * 256 ; color . green = green * 256 ; color . blue = blue * 256 ; if ( ! XAllocColor ( display , map , & color )) { err ( "XAllocColor failed" ); } return color . pixel ; }

and use that to allocate the colours we'll need for drawing (back in init):

/* Allocate colours. */ black_color = alloc_color ( 0x00 , 0x00 , 0x00 ); white_color = alloc_color ( 0xFF , 0xFF , 0xFF ); grey_color = alloc_color ( 0xC0 , 0xC0 , 0xC0 ); board_color = alloc_color ( 0x00 , 0x80 , 0x00 ); hl_color = alloc_color ( 0x00 , 0xAA , 0x00 );

Then we create the window with a call to XCreateSimpleWindow:

/* Create the window. */ win = XCreateSimpleWindow ( display , RootWindow ( display , DefaultScreen ( display )), 0 , 0 , INIT_SIZE , INIT_SIZE , CELL_GAP , black_color , grey_color );

The parameters specify the display (X server), and that we want our window to be the child of the root window of the default screen on that server. (Historically there could be multiple X screens if multiple monitors were attached to one machine; these days, there is usually just one screen that covers all monitors.) The other parameters specify the initial position (0,0), size, border width (mostly ignored these days I think), foreground and background colour.

Note that the new window is not shown yet. We will reveal it later, after everything has been initialized.

Next, we pass more information about the window to the window manager. This includes the title of the window (in the form of an XTextProperty, created with XStringListToTextProperty), the minimum size, initial state (minimized or normal), whether the window takes input, the name of the application, and so on. For both size_hints and wm_hints, we use the flags field to indicate which parts of the structure we're filling in.

In wm_hints we also set the icon for the application. It is loaded from an XPM file, which is #included directly into our source, using XpmCreatePixmapFromData. That function isn't strictly part of Xlib, but a separate XPM library. That is normally always available on systems that have X, though.

/* Prepare window name property. */ if ( ! XStringListToTextProperty ( & window_name , 1 , & window_name_prop )) { err ( "XStringListToTextProperty failed" ); } /* Prepare size hints. */ if ( ! ( size_hints = XAllocSizeHints ())) { err ( "XAllocSizeHints() failed" ); } size_hints -> flags = PMinSize ; size_hints -> min_width = MIN_SIZE ; size_hints -> min_height = MIN_SIZE ; /* Prepare window manager hints. */ if ( ! ( wm_hints = XAllocWMHints ())) { err ( "XAllocWMHints() failed" ); } wm_hints -> initial_state = NormalState ; wm_hints -> input = True ; wm_hints -> flags = StateHint | InputHint ; if ( XpmCreatePixmapFromData ( display , win , othello_icon , & wm_hints -> icon_pixmap , & wm_hints -> icon_mask , NULL ) == XpmSuccess ) { wm_hints -> flags |= IconPixmapHint | IconMaskHint ; } /* Prepare class hint. */ if ( ! ( class_hint = XAllocClassHint ())) { err ( "XAllocClassHint() failed" ); } class_hint -> res_name = argv [ 0 ]; class_hint -> res_class = window_name ; /* Set name property, size, wm and class hints for the window. */ XSetWMProperties ( display , win , & window_name_prop , & window_name_prop , argv , argc , size_hints , wm_hints , class_hint ); XFree ( window_name_prop . value ); XFree ( size_hints ); XFree ( wm_hints ); XFree ( class_hint );

We tell the X server what type of events we want to be notified of, by passing an event mask to XSelectInput. We also tell the window manager that we would like to use the WM_DELETE_WINDOW protocol, which means we want to be notified if the user clicks the close button on the window.

/* Register for events. */ XSelectInput ( display , win , ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask | PointerMotionMask | PointerMotionHintMask ); wm_delete_window = XInternAtom ( display , "WM_DELETE_WINDOW" , False ); if ( ! XSetWMProtocols ( display , win , & wm_delete_window , 1 )) { err ( "XSetWMProtocols failed" ); }

As the final steps of initialization, we load the font and create the graphics contexts that we'll need for drawing:

/* Load the font. */ if ( ! ( font = XLoadQueryFont ( display , FONT_NAME ))) { err ( "cannot open %s font" , FONT_NAME ); } /* Set up GCs. */ black_gc = XCreateGC ( display , win , 0 , NULL ); XSetForeground ( display , black_gc , black_color ); XSetFont ( display , black_gc , font -> fid ); white_gc = XCreateGC ( display , win , 0 , NULL ); XSetForeground ( display , white_gc , white_color ); board_gc = XCreateGC ( display , win , 0 , NULL ); XSetForeground ( display , board_gc , board_color ); highlight_gc = XCreateGC ( display , win , 0 , NULL ); XSetForeground ( display , highlight_gc , hl_color );

And now we're ready to request the window to be shown, or mapped in X parlance:

/* Show the window. */ XMapWindow ( display , win ); }

Drawing

Before drawing the grid, we need to know exactly where to draw it. We divide the height or width (whichever is smallest) of the window to fit a 10-by-10 grid of equal-size quadratic cells with CELL_GAP pixels in between them. Then we position the grid in the centre of the window:

/* Compute the grid's size and position in the window. */ static void compute_grid_position ( int win_width , int win_height ) { /* The grid is a 10x10 grid. The 8x8 centre is the Othello board, the top row and left column are used for labels, and the bottom row for status text. */ grid . cell_size = ( min ( win_width , win_height ) - 9 * CELL_GAP ) / 10 ; grid . size = grid . cell_size * 10 + 9 * CELL_GAP ; grid . x = win_width / 2 - grid . size / 2 ; grid . y = win_height / 2 - grid . size / 2 ; }

Knowing the size and position of the grid is also necessary for hit testing, checking whether a position (the mouse position in our case) intersects with an Othello cell:

/* Check whether the position is over an Othello cell. */ static bool grid_hit_test ( int x , int y , int * row , int * col ) { * row = ( y - grid . y ) / ( grid . cell_size + CELL_GAP ) - 1 ; * col = ( x - grid . x ) / ( grid . cell_size + CELL_GAP ) - 1 ; if ( * row >= 0 && * row < 8 && * col >= 0 && * col < 8 ) { return true ; } return false ; }

The code below is used to draw an Othello cell. First, the cell background is drawn using XFillRectangle. A different graphics context is used for different colour depending on whether the cell is currently selected. If there is a disk in the cell, XFillArc is used to draw it as a filled circle.

/* Draw an Othello cell and its contents. */ static void draw_othello_cell ( int row , int col ) { int x , y ; bool highlight ; cell_state_t cs ; x = grid . x + ( col + 1 ) * ( grid . cell_size + CELL_GAP ); y = grid . y + ( row + 1 ) * ( grid . cell_size + CELL_GAP ); highlight = ( row == grid . sel_row && col == grid . sel_col && state == BLACKS_MOVE ); /* Draw the cell background. */ XFillRectangle ( display , win , highlight ? highlight_gc : board_gc , x , y , grid . cell_size , grid . cell_size ); if (( cs = othello_cell_state ( & board , row , col )) != CELL_EMPTY ) { /* Draw the disk. */ XFillArc ( display , win , cs == CELL_BLACK ? black_gc : white_gc , x , y , grid . cell_size , grid . cell_size , 0 , 360 * 64 ); } }

To draw row and column labels around the board, and status text below it, we use a helper function for drawing text centered at a specific position:

/* Draw string s of length len centered at (x,y). */ static void draw_string ( const char * s , int len , int x , int y ) { int width , height ; width = XTextWidth ( font , s , len ); height = font -> ascent + font -> descent ; XDrawString ( display , win , black_gc , x - width / 2 , y + height / 2 , s , len ); }

Finally, the function below is used to draw the whole grid:

/* Draw the grid and its contents. */ static void draw_grid ( void ) { int row , col , x , y , bs , ws ; char status [ 128 ]; XClearWindow ( display , win ); /* Draw a background square around the 8x8 centre cells. */ XFillRectangle ( display , win , black_gc , grid . x + grid . cell_size , grid . y + grid . cell_size , 8 * grid . cell_size + 9 * CELL_GAP , 8 * grid . cell_size + 9 * CELL_GAP ); /* Draw labels. */ for ( row = 0 ; row < 8 ; row ++ ) { x = grid . x + grid . cell_size / 2 ; y = grid . y + ( row + 1 ) * ( grid . cell_size + CELL_GAP ) + grid . cell_size / 2 ; draw_string ( & "12345678" [ row ], 1 , x , y ); } for ( col = 0 ; col < 8 ; col ++ ) { x = grid . x + ( col + 1 ) * ( grid . cell_size + CELL_GAP ) + grid . cell_size / 2 ; y = grid . y + grid . cell_size / 2 ; draw_string ( & "ABCDEFGH" [ col ], 1 , x , y ); } /* Draw status text. */ switch ( state ) { case BLACKS_MOVE : sprintf ( status , "Human's move." ); break ; case WHITES_MOVE : sprintf ( status , "Computer's move.." ); break ; case GAME_OVER : bs = othello_score ( & board , PLAYER_BLACK ); ws = othello_score ( & board , PLAYER_WHITE ); if ( bs > ws ) { sprintf ( status , "Human wins %d-%d!" , bs , ws ); } else if ( ws > bs ) { sprintf ( status , "Computer wins %d-%d!" , ws , bs ); } else { sprintf ( status , "Draw!" ); } } draw_string ( status , strlen ( status ), grid . x + grid . size / 2 , grid . y + grid . size - grid . cell_size / 2 ); /* Draw cells. */ for ( row = 0 ; row < 8 ; row ++ ) { for ( col = 0 ; col < 8 ; col ++ ) { draw_othello_cell ( row , col ); } } }

Handling Events

The program needs to handle three main user events: mouse moves, mouse clicks, and keyboard presses.

When the mouse moves, we'd like to select and highlight the cell currently under the cursor, if any:

static void select_othello_cell ( int row , int col ) { int old_row = grid . sel_row ; int old_col = grid . sel_col ; if ( row == old_row && col == old_col ) { /* This cell is already selected. */ return ; } grid . sel_row = row ; grid . sel_col = col ; if ( old_row >= 0 ) { /* Re-draw the previously selected cell. */ draw_othello_cell ( old_row , old_col ); } if ( row >= 0 ) { /* Draw the newly selected cell. */ draw_othello_cell ( row , col ); } } static void on_mouse_move ( void ) { Window root , child ; int root_x , root_y , win_x , win_y , row , col ; unsigned mask ; if ( ! XQueryPointer ( display , win , & root , & child , & root_x , & root_y , & win_x , & win_y , & mask )) { return ; } if ( grid_hit_test ( win_x , win_y , & row , & col )) { select_othello_cell ( row , col ); } else { select_othello_cell ( - 1 , - 1 ); } }

Because we used PointerMotionHintMask when registering for events, we are not guaranteed to receive an event for each position the mouse moves through. Instead, we receive notifications after the mouse has moved, and then requests the exact position with XQueryPointer. This cuts down on the number of events that has to be processed.

A mouse click is taken as a request to make a move in the currently selected cell, or to start a new game if the current one has finished:

static void new_game ( void ) { othello_init ( & board ); state = BLACKS_MOVE ; } static void on_mouse_click ( void ) { if ( state == GAME_OVER ) { new_game (); return ; } if ( state == BLACKS_MOVE && grid . sel_row >= 0 ) { make_move ( grid . sel_row , grid . sel_col ); } }

The keyboard can be used to quit the game, make a move or select a cell. XLookupKeysym is used to determine which key, or KeySym, was pressed:

static void on_key_press ( XKeyEvent * xkey , bool * quit , bool * draw ) { int row , col ; row = grid . sel_row ; col = grid . sel_col ; switch ( XLookupKeysym ( xkey , 0 )) { default : return ; case XK_q : * quit = true ; return ; case XK_space : case XK_Return : on_mouse_click (); * draw = true ; return ; case XK_Right : col ++ ; break ; case XK_Left : col -- ; break ; case XK_Down : row ++ ; break ; case XK_Up : row -- ; break ; case XK_a : col = 0 ; break ; case XK_b : col = 1 ; break ; case XK_c : col = 2 ; break ; case XK_d : col = 3 ; break ; case XK_e : col = 4 ; break ; case XK_f : col = 5 ; break ; case XK_g : col = 6 ; break ; case XK_h : col = 7 ; break ; case XK_1 : row = 0 ; break ; case XK_2 : row = 1 ; break ; case XK_3 : row = 2 ; break ; case XK_4 : row = 3 ; break ; case XK_5 : row = 4 ; break ; case XK_6 : row = 5 ; break ; case XK_7 : row = 6 ; break ; case XK_8 : row = 7 ; break ; } select_othello_cell ( max ( 0 , min ( row , 7 )), max ( 0 , min ( col , 7 ))); }

Making Moves

When either player makes a move, we need to update the board, figure out whose turn it is or whether the game is over, and if it becomes white's turn we need to compute its next move:

/* Make a move for the current player and transition the game state. */ static void make_move ( int row , int col ) { assert ( state == BLACKS_MOVE || state == WHITES_MOVE ); if ( state == BLACKS_MOVE ) { if ( ! othello_is_valid_move ( & board , PLAYER_BLACK , row , col )) { /* Illegal move; ignored. */ return ; } othello_make_move ( & board , PLAYER_BLACK , row , col ); state = WHITES_MOVE ; } else { othello_make_move ( & board , PLAYER_WHITE , row , col ); state = BLACKS_MOVE ; } if ( ! othello_has_valid_move ( & board , PLAYER_BLACK ) && ! othello_has_valid_move ( & board , PLAYER_WHITE )) { state = GAME_OVER ; } else if ( state == WHITES_MOVE && ! othello_has_valid_move ( & board , PLAYER_WHITE )) { state = BLACKS_MOVE ; } else if ( state == BLACKS_MOVE && ! othello_has_valid_move ( & board , PLAYER_BLACK )) { state = WHITES_MOVE ; } if ( state == WHITES_MOVE ) { compute_white_move (); } }

When computing white's move, it's important not to do so on the main thread. Doing non-trivial work on the same thread that handles user interface events is a cardinal sin, as it makes the program feel unresponsive and frustrates the user. In our case, we must for example be able to redraw the board if the window is moved.

It turns out that interacting with Xlib from multiple threads is fraught with peril. In particular, I couldn't find a reliable way to send an event from a background thread to the main thread.

Ideally, we would like to be able to wait for two events at the same time: regular X events and notifications that white's move has been computed. Remember that X is a network protocol, so Xlib is using a socket to communicate with the server. We can get the file descriptor for this socket using XConnectionNumber, and we can use select(2) to wait for events on that and other file descriptors at the same time.

Therefore, we will use a pipe(2) to communicate white's move back to the event loop, and we use fork(2) to create a separate process for computing the move — classic Unix inter-process communication:

static int white_move_pipe [ 2 ]; /* [0] for reading, [1] for writing. */ static void compute_white_move ( void ) { int x , row , col ; char c ; assert ( state == WHITES_MOVE ); /* Compute white's move in a background process. */ if (( x = fork ()) == - 1 ) { err ( "fork() failed: %s" , strerror ( errno )); } else if ( x != 0 ) { /* Parent process. */ return ; } /* Child process: compute the move and send it through the pipe. */ othello_compute_move ( & board , PLAYER_WHITE , & row , & col ); c = ( char )( row * 8 + col ); if ( write ( white_move_pipe [ 1 ], & c , 1 ) != 1 ) { err ( "write() failed" ); } exit ( EXIT_SUCCESS ); }

Event Loop and main

The event loop is the heart of our program. It calls XPending to check if there are events ready to be processed, otherwise it uses the technique explained above to wait for either a new event or a white move.

In addition to the mouse and keyboard events described previously, there are some other events we need to handle. ConfigureNotify reports changes to a window's configuration (size, position and such), including its initial one. Expose events fire when the window (or part of it) becomes exposed, such as when it is first shown, or if it was previously hidden by another window.

After creating and mapping the window, we will first receive a ConfigureNotify event, then an Expose event, and then we're in business, ready for the user's input.

static void event_loop ( void ) { int display_fd ; bool quit , draw ; fd_set fds ; XEvent event ; char c ; display_fd = XConnectionNumber ( display ); quit = false ; draw = false ; while ( ! quit ) { if ( draw ) { draw_grid (); draw = false ; } if ( XPending ( display ) == 0 ) { /* Wait for X event or a white move. */ FD_ZERO ( & fds ); FD_SET ( display_fd , & fds ); FD_SET ( white_move_pipe [ 0 ], & fds ); if ( select ( max ( display_fd , white_move_pipe [ 0 ]) + 1 , & fds , NULL , NULL , NULL ) == - 1 ) { err ( "select() failed: %s" , strerror ( errno )); } if ( FD_ISSET ( white_move_pipe [ 0 ], & fds )) { /* Read white move from the pipe. */ if ( read ( white_move_pipe [ 0 ], & c , 1 ) != 1 ) { err ( "read() failed" ); } make_move ( c / 8 , c % 8 ); draw = true ; continue ; } } XNextEvent ( display , & event ); switch ( event . type ) { case ConfigureNotify : /* The window's configuration has changed. */ compute_grid_position ( event . xconfigure . width , event . xconfigure . height ); break ; case Expose : /* The window has become visible. */ if ( event . xexpose . count == 0 ) { draw = true ; } break ; case MotionNotify : on_mouse_move (); break ; case KeyPress : on_key_press ( & event . xkey , & quit , & draw ); break ; case ButtonPress : on_mouse_move (); on_mouse_click (); draw = true ; break ; case ClientMessage : if ( event . xclient . data . l [ 0 ] == wm_delete_window ) { /* Window closed. */ quit = true ; } break ; } } }

Finally, main ties it all together: initializing, running the event loop until it's time to quit, and freeing resources in the end.

int main ( int argc , char ** argv ) { if ( pipe ( white_move_pipe ) != 0 ) { err ( "pipe() failed: %s

" , strerror ( errno )); } grid . sel_row = - 1 ; init ( argc , argv ); new_game (); event_loop (); XFreeGC ( display , black_gc ); XFreeGC ( display , white_gc ); XFreeGC ( display , board_gc ); XFreeGC ( display , highlight_gc ); XFreeFont ( display , font ); XCloseDisplay ( display ); return 0 ; }

Building

If it's not already installed, you may need to install Xlib and XPM in order to build the program. On a Debian system:

$ sudo apt-get install libx11-dev libxpm-dev

To compile, link and run:

$ gcc -O3 -march = native -DNDEBUG othello.c x11_othello.c -lX11 -lXpm -o othello $ ./othello

On my machine, it looks like the first of the images below (click for larger versions).

Because it's written in C and has few dependencies, the program should be extremely portable and run on most Unix systems. The second image shows it in the Common Desktop Environment on an old version of Solaris.

It is also possible to run the program on Mac. macOS is based on Unix, but no longer comes with an X server. However, the one that used to be included can be downloaded from XQuartz.org. Once that's installed, our program can be compiled with:

$ clang -O3 -march = native -DNDEBUG othello.c x11_othello.c \ -I/usr/X11/include -L/usr/X11/lib -lX11 -lXpm -o othello

The third image below shows the program running on Mac. Note that it doesn't blend in as well as the native Mac port.

Windows

Besides a few early years of using MS-DOS, Windows is the operating system I grew up with, and it is still the most widely used OS on desktop computers. After having switched to Linux, using Windows mostly feels cumbersome; however, learning Windows programming does have a certain thrill to it. Perhaps it's the nostalgia, or maybe it's the engineering challenge of overcoming the ugliness and building something useful in a hostile environment — a little bit of order out of chaos.

Win32, the 32-bit version of the Windows API, was introduced with the release of Windows NT in July 1993. There have been many APIs and technologies for Windows development since (see Joel Spolsky's rant), but Win32 remains the truly native way for applications to interact with Windows.

I used Charles Petzold's Programming Windows (5th ed.) as a tutorial to Win32. It appears to be out of print, but used copies are easy to buy online. There is a sixth edition available, but that doesn't seem to cover Win32, instead focusing on C# and XAML to build Metro/UWP (the name keeps changing) apps.

Initialization and Message Loop

Graphical Windows programs start in a function called WinMain as opposed to main used by standard C programs. This allows the operating system to pass in a few extra parameters (and tells the linker which subsystem to use for the executable). In win_othello.c:

#define CELL_GAP 1 /* Cell gap in pixels. */ #define MIN_SIZE 300 /* Minimum window size. */ #define INIT_SIZE 450 /* Initial window size. */ #define WM_WHITE_MOVE (WM_USER + 0) static const char APP_NAME [] = "othello" ; static HBRUSH background_brush , board_brush , highlight_brush , white_brush , black_brush ; static othello_t board ; static enum { BLACKS_MOVE , WHITES_MOVE , GAME_OVER } state ; int WINAPI WinMain ( HINSTANCE instance , HINSTANCE prev_instance , LPSTR cmd_line , int cmd_show ) { WNDCLASS window_class ; HWND window ; HACCEL accelerators ; MSG message ;

The instance parameter is a handle to the current instance of the program. It will be used for example when requesting resources from the program, such as the icon. The prev_instance parameter was historically (before 32-bit Windows) used to refer to any already running instance of the program. It is no longer used.

The cmd_line parameter is a char* pointing at the command-line arguments, not including the program name, as a single string. (CommandLineToArgvW allows for getting an argument array instead, similar to argc and argv in regular C programs.)

The cmd_show parameter specifies how the window should be shown initially (minimized, maximized or normal). It will be used when the window is shown later.

WINAPI is a macro that expands to __stdcall, a calling convention used by most functions in the Windows API. It specifies that arguments to the function should be passed on the stack like regular C-style (__cdecl) calls, but that the called function must remove the arguments from the stack before returning ("callee cleanup"), relieving the calling function of that responsibility. The idea was probably to allow user programs to save a few instructions by letting the system libraries do the cleanup. (See Raymond Chen's The History of Calling Conventions, part 1 2 3 4 5.)

First we initialize the game state:

grid . sel_row = - 1 ; new_game ();

Then we define brushes for the various colours that will be used. A brush is used when drawing filled shapes like circles or rectangles, and when drawing text. There are also pen objects which are used for drawing outlines, like the border around a square.

background_brush = CreateSolidBrush ( GetSysColor ( COLOR_BTNFACE )); board_brush = CreateSolidBrush ( RGB ( 0x00 , 0x80 , 0x00 )); highlight_brush = CreateSolidBrush ( RGB ( 0x00 , 0xAA , 0x00 )); white_brush = CreateSolidBrush ( RGB ( 0xFF , 0xFF , 0xFF )); black_brush = CreateSolidBrush ( RGB ( 0 , 0 , 0 ));

The WNDCLASS object is used to specify properties of the window we are about to create:

window_class . style = CS_HREDRAW | CS_VREDRAW ; window_class . lpfnWndProc = & wnd_proc ; window_class . cbClsExtra = 0 ; window_class . cbWndExtra = 0 ; window_class . hInstance = instance ; window_class . hIcon = LoadIcon ( instance , MAKEINTRESOURCE ( IDI_ICON )); window_class . hCursor = LoadCursor ( NULL , IDC_ARROW ); window_class . hbrBackground = background_brush ; window_class . lpszMenuName = MAKEINTRESOURCE ( IDM_MENU ); window_class . lpszClassName = APP_NAME ;

The most important property is lpfnWndProc which specifies the window procedure, a function which handles messages sent to a window of that class. (lpfn stands for "long pointer to function"; Win32 uses Hungarian notation for most names.) CS_HREDRAW and CS_VREDRAW specify that the window must be redrawn if the horizontal or vertical size changes. We also specify the cursor and background colour, as well as the icon, which is loaded from a resource identified as IDI_ICON. The menu is specified by lpszMenuName, referring to another resource, and finally we set lpszClassName which is the name by which we will refer to the window class.

The window class is registered with a call to RegisterClassA, and we create a window of that class with CreateWindowA, specifying the window name (title), initial size, etc.

if ( ! RegisterClassA ( & window_class )) { err ( "RegisterClassA failed!" ); } window = CreateWindowA ( APP_NAME , "Othello" , WS_OVERLAPPEDWINDOW , CW_USEDEFAULT , CW_USEDEFAULT , INIT_SIZE , INIT_SIZE , NULL , NULL , instance , NULL );

Like the two functions called above, many functions in the Win32 API have an A or W suffix. These are function variants that take ASCII or UTF-16 (wide character) strings. There is usually a macro, for example RegisterClass, that expands to the A or W variant based on whether the UNICODE macro is defined. The TEXT (or _T) macros can be used for string literals, for example TEXT("Othello") expands to "Othello" (of type char[]) or L"Othello (of type wchar_t[]) depending on whether UNICODE is defined. The motivation for this clever but confusing mechanism is to allow for building the same program with or without Unicode support based on a single macro. However, modern programs should probably always support Unicode if they accept text input, and referring to functions explicitly by their real name, A or W suffix included, avoids potential confusion about which function is being called. (See the UTF-8 Everywhere Manifesto, in particular How to do text on Windows.)

Now we are ready to show the window (ShowWindow) and tell it to paint itself (UpdateWindow):

ShowWindow ( window , cmd_show ); UpdateWindow ( window );

Once the window is ready, the program spends the rest of its lifetime processing messages, or events, in what is called a message loop:

accelerators = LoadAccelerators ( instance , MAKEINTRESOURCE ( IDA_ACC )); while ( GetMessage ( & message , NULL , 0 , 0 )) { if ( ! TranslateAccelerator ( window , accelerators , & message )) { TranslateMessage ( & message ); DispatchMessage ( & message ); } }

LoadAccelerators loads a table of accelerators, that is, keyboard shortcuts (a resource defined together with the menu). GetMessage waits for and gets the next window message from the application's message queue. It returns a non-zero value unless the message is WM_QUIT, in which case we exit the message loop.

We use TranslateAccelerator to check if the message is the invocation of a keyboard shortcut. If it is, the function will call the window procedure, and we don't need to process the message further here.

TranslateMessage is used to translate keystroke messages to character messages. For example, if an "a" key press message is received while the "shift" key is pressed, TranslateMessage will generate an "A" character message to be fetched with the next GetMessage call. Since our program doesn't accept text input, this isn't strictly necessary, but writing the message loop like this is a standard pattern.

After any "translation", the message is dispatched to the message destination's window procedure with DispatchMessage.

After the message loop, we clean up and exit with the parameter of the WM_QUIT message as status code:

DeleteObject ( background_brush ); DeleteObject ( board_brush ); DeleteObject ( highlight_brush ); DeleteObject ( white_brush ); DeleteObject ( black_brush ); return message . wParam ; }

Resources

Resources are various pieces of data that can be included in an executable (or DLL) file and referred to by programs. In WinMain above, we refer to resources for the program's icon, menu and accelerator table.

The resources we use have numerical identifiers (strings can also be used as identifiers), which we define in a regular header file, win_othello_res.h:

#define IDI_ICON 1 #define IDM_MENU 2 #define IDM_NEW_GAME 3 #define IDM_EXIT 4 #define IDA_ACC 5

A resource-definition script, win_othello_res.rc, is then used to define the resources:

#include "win_othello_res.h" #include "windows.h" IDI_ICON ICON "icons/othello.ico" IDM_MENU MENU BEGIN POPUP "&Game" BEGIN MENUITEM "&New Game \t F2" , IDM_NEW_GAME MENUITEM SEPARATOR MENUITEM "E&xit" , IDM_EXIT END END IDA_ACC ACCELERATORS BEGIN VK_F2 , IDM_NEW_GAME , VIRTKEY END

The .rc file supports C preprocessor directives like #include, but mainly consists of resource-definition statements. Each such statement defines a resource with a numeric or string id, and type.

The first statement defines an ICON resource with the numerical identifier IDI_ICON (defined as 1 in the header file). Its content is provided by the othello.ico file.

We define a MENU resource with the identifier IDI_MENU to use as our main menu. The sub-statements define the structure and contents of the menu, and numerical identifiers to use when each menu item is activated. (The identifier will be a parameter of a WM_COMMAND message). Using & in a title makes the following character appear underlined and act as a keyboard shortcut. For example, ALT-G will open the "Game" menu.

The ACCELERATORS resource defines keyboard shortcuts and what numeric id to use when they're invoked. In our case we want the F2 key to have the same effect as the "New Game" menu option.

These are the most common types of resources, but there are several others, and one can define custom ones to include any kind of data.

To include the resources in our program, the Resource Compiler rc.exe is used to compile the .rc file and its dependencies (like othello.ico) into an intermediate .res file, which the linker will then bake into the executable of the program (see Building below).

Drawing

The drawing strategy is exactly the same as for the X11 port: we use a 10-by-10 grid of equal-size quadratic cells, with the Othello board occupying the middle 8-by-8 cells.

Computing the grid's position and hit testing is done exactly as for X11:

static struct { int x , y ; /* Position of the grid relative to window origin. */ int size ; /* Size (width and height are equal) of the grid. */ int cell_size ; /* Size of a grid cell, not including its border. */ int sel_row ; /* Currently selected row, or -1 if none. */ int sel_col ; /* Currently selected column. */ } grid ; /* Compute the grid's size and position in the window. */ static void compute_grid_position ( int win_width , int win_height ) { /* The grid is a 10x10 grid. The 8x8 centre is the Othello board, the top row and left column are used for labels, and the bottom row for status text. */ grid . cell_size = ( min ( win_width , win_height ) - 9 * CELL_GAP ) / 10 ; grid . size = grid . cell_size * 10 + 9 * CELL_GAP ; grid . x = win_width / 2 - grid . size / 2 ; grid . y = win_height / 2 - grid . size / 2 ; } /* Check whether the position is over an Othello cell. */ static bool grid_hit_test ( int x , int y , int * row , int * col ) { * row = ( y - grid . y ) / ( grid . cell_size + CELL_GAP ) - 1 ; * col = ( x - grid . x ) / ( grid . cell_size + CELL_GAP ) - 1 ; if ( * row >= 0 && * row < 8 && * col >= 0 && * col < 8 ) { return true ; } return false ; }

Drawing is done with a part of the Win32 API called GDI (Graphics Device Interface). The drawing commands are performed with reference to a Device Context which the system provides for us.

/* Draw an Othello cell and its contents. */ static void draw_othello_cell ( HDC dc , int row , int col ) { int x , y ; bool highlight ; cell_state_t cs ; x = grid . x + ( col + 1 ) * ( grid . cell_size + CELL_GAP ); y = grid . y + ( row + 1 ) * ( grid . cell_size + CELL_GAP ); highlight = ( row == grid . sel_row && col == grid . sel_col && state == BLACKS_MOVE ); /* Draw the cell background. */ SelectObject ( dc , GetStockObject ( NULL_PEN )); SelectObject ( dc , highlight ? highlight_brush : board_brush ); Rectangle ( dc , x , y , x + grid . cell_size , y + grid . cell_size ); if (( cs = othello_cell_state ( & board , row , col )) != CELL_EMPTY ) { /* Draw the disk. */ SelectObject ( dc , cs == CELL_BLACK ? black_brush : white_brush ); Ellipse ( dc , x , y , x + grid . cell_size , y + grid . cell_size ); } } /* Draw string s of length len centered at (x,y). */ static void draw_string ( HDC dc , const char * s , int len , int x , int y ) { SIZE size ; GetTextExtentPoint32A ( dc , s , len , & size ); TextOut ( dc , x - size . cx / 2 , y - size . cy / 2 , s , len ); } /* Draw the grid and its contents. */ static void draw_grid ( HDC dc ) { int row , col , x , y , bs , ws ; char status [ 128 ]; /* Draw a background square around the 8x8 centre cells. */ SelectObject ( dc , GetStockObject ( NULL_PEN )); SelectObject ( dc , black_brush ); Rectangle ( dc , grid . x + grid . cell_size , grid . y + grid . cell_size , grid . x + 9 * grid . cell_size + 9 * CELL_GAP , grid . y + 9 * grid . cell_size + 9 * CELL_GAP ); /* Draw labels. */ for ( row = 0 ; row < 8 ; row ++ ) { x = grid . x + grid . cell_size / 2 ; y = grid . y + ( row + 1 ) * ( grid . cell_size + CELL_GAP ) + grid . cell_size / 2 ; draw_string ( dc , & "12345678" [ row ], 1 , x , y ); } for ( col = 0 ; col < 8 ; col ++ ) { x = grid . x + ( col + 1 ) * ( grid . cell_size + CELL_GAP ) + grid . cell_size / 2 ; y = grid . y + grid . cell_size / 2 ; draw_string ( dc , & "ABCDEFGH" [ col ], 1 , x , y ); } /* Draw status text. */ switch ( state ) { case BLACKS_MOVE : sprintf ( status , "Human's move." ); break ; case WHITES_MOVE : sprintf ( status , "Computer's move.." ); break ; case GAME_OVER : bs = othello_score ( & board , PLAYER_BLACK ); ws = othello_score ( & board , PLAYER_WHITE ); if ( bs > ws ) { sprintf ( status , "Human wins %d-%d!" , bs , ws ); } else if ( ws > bs ) { sprintf ( status , "Computer wins %d-%d!" , ws , bs ); } else { sprintf ( status , "Draw!" ); } } draw_string ( dc , status , strlen ( status ), grid . x + grid . size / 2 , grid . y + grid . size - grid . cell_size / 2 ); /* Draw cells. */ for ( row = 0 ; row < 8 ; row ++ ) { for ( col = 0 ; col < 8 ; col ++ ) { draw_othello_cell ( dc , row , col ); } } }

Handling Events

Separate functions are used to handle events that need more than a few lines of code to process. We call InvalidateRect when the window needs repainting, causing the system to send us a WM_PAINT message.

static void new_game ( void ) { othello_init ( & board ); state = BLACKS_MOVE ; } static void on_mouse_click ( HWND window ) { if ( state == GAME_OVER ) { new_game (); InvalidateRect ( window , NULL , TRUE ); return ; } if ( state == BLACKS_MOVE && grid . sel_row >= 0 ) { make_move ( window , grid . sel_row , grid . sel_col ); } }

When a cell is selected, we take care to only invalidate that part of the window, and not clear (the false argument) but to paint over it. When only part of the window is invalidated, the next WM_PAINT message will run as usual, but only the painting operations inside the invalidated area will be applied. This avoids flickering from repainting the whole window each time a new cell is selected.

/* Invalidate an Othello cell, causing it to be repainted. */ static void invalidate_othello_cell ( HWND window , int row , int col ) { RECT r ; r . left = grid . x + ( col + 1 ) * ( grid . cell_size + CELL_GAP ); r . top = grid . y + ( row + 1 ) * ( grid . cell_size + CELL_GAP ); r . right = r . left + grid . cell_size ; r . bottom = r . top + grid . cell_size ; InvalidateRect ( window , & r , false ); } static void select_othello_cell ( HWND window , int row , int col ) { int old_row = grid . sel_row ; int old_col = grid . sel_col ; if ( row == old_row && col == old_col ) { /* This cell is already selected. */ return ; } grid . sel_row = row ; grid . sel_col = col ; if ( old_row >= 0 ) { /* Re-draw the previously selected cell. */ invalidate_othello_cell ( window , old_row , old_col ); } if ( row >= 0 ) { /* Need to draw the newly selected cell. */ invalidate_othello_cell ( window , row , col ); } } static void on_key_press ( HWND window , WPARAM wparam ) { int row , col ; row = grid . sel_row ; col = grid . sel_col ; switch ( wparam ) { default : return ; case VK_SPACE : case VK_RETURN : on_mouse_click ( window ); return ; case VK_RIGHT : col ++ ; break ; case VK_LEFT : col -- ; break ; case VK_DOWN : row ++ ; break ; case VK_UP : row -- ; break ; case 'A' : case 'B' : case 'C' : case 'D' : case 'E' : case 'F' : case 'G' : case 'H' : col = wparam - 'A' ; break ; case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' : case '8' : row = wparam - '1' ; break ; } select_othello_cell ( window , max ( 0 , min ( row , 7 )), max ( 0 , min ( col , 7 ))); }

Events, or messages, are processed by the window procedure, which in our program is implemented by the wnd_proc function.

Each message can take two mysteriously named parameters, wparam and lparam. The names are historical: before 32-bit Windows, the procedure used to take a 16-bit "word" parameter, used for handles and integers, and a 32-bit "long" parameter, used for pointers. With the introduction of Win32, both parameters became 32-bit values. (See Raymond Chen.)

The CALLBACK macro sets the calling convention to __stdcall, just as WINAPI did for WinMain. Again, the different macros exist for historical reasons; on 16-bit Windows they expanded to different things.

LRESULT is just a macro which expands to long. The appropriate return value depends on the message type, but returning zero usually means we processed the message successfully.

static LRESULT CALLBACK wnd_proc ( HWND window , UINT message , WPARAM wparam , LPARAM lparam ) { HDC dc ; PAINTSTRUCT ps ; int row , col ; switch ( message ) { case WM_SIZE : /* The window size changed. */ compute_grid_position ( LOWORD ( lparam ), HIWORD ( lparam )); return 0 ; case WM_GETMINMAXINFO : /* Windows is asking about the size constraints. */ (( MINMAXINFO * ) lparam ) -> ptMinTrackSize . x = MIN_SIZE ; (( MINMAXINFO * ) lparam ) -> ptMinTrackSize . y = MIN_SIZE ; return 0 ; case WM_PAINT : /* The window needs a re-paint. */ EnableMenuItem ( GetMenu ( window ), IDM_NEW_GAME , state != WHITES_MOVE ? MF_ENABLED : MF_GRAYED ); dc = BeginPaint ( window , & ps ); SetBkMode ( dc , TRANSPARENT ); draw_grid ( dc ); EndPaint ( window , & ps ); return 0 ; case WM_MOUSEMOVE : /* The mouse moved. */ if ( grid_hit_test ( LOWORD ( lparam ), HIWORD ( lparam ), & row , & col )) { select_othello_cell ( window , row , col ); } else { select_othello_cell ( window , - 1 , - 1 ); } return 0 ; case WM_LBUTTONUP : /* Left mouse button up. */ on_mouse_click ( window ); return 0 ; case WM_KEYDOWN : /* Keyboard key down. */ on_key_press ( window , wparam ); return 0 ; case WM_COMMAND : if ( lparam == 0 ) { /* Menu item activated. */ switch ( LOWORD ( wparam )) { case IDM_NEW_GAME : assert ( state != WHITES_MOVE ); new_game (); InvalidateRect ( window , NULL , TRUE ); return 0 ; case IDM_EXIT : PostQuitMessage ( 0 ); return 0 ; } } break ; case WM_WHITE_MOVE : /* White computed a move. */ assert ( state == WHITES_MOVE ); make_move ( window , wparam , lparam ); return 0 ; case WM_DESTROY : /* The window is closing. */ PostQuitMessage ( 0 ); return 0 ; } return DefWindowProc ( window , message , wparam , lparam ); }

Making Moves

The code for updating the game state with a new move is almost exactly the same as for the X11 version:

/* Make a move for the current player and transition the game state. */ static void make_move ( HWND window , int row , int col ) { assert ( state == BLACKS_MOVE || state == WHITES_MOVE ); if ( state == BLACKS_MOVE ) { if ( ! othello_is_valid_move ( & board , PLAYER_BLACK , row , col )) { /* Illegal move; ignored. */ return ; } othello_make_move ( & board , PLAYER_BLACK , row , col ); state = WHITES_MOVE ; } else { othello_make_move ( & board , PLAYER_WHITE , row , col ); state = BLACKS_MOVE ; } if ( ! othello_has_valid_move ( & board , PLAYER_BLACK ) && ! othello_has_valid_move ( & board , PLAYER_WHITE )) { state = GAME_OVER ; } else if ( state == WHITES_MOVE && ! othello_has_valid_move ( & board , PLAYER_WHITE )) { state = BLACKS_MOVE ; } else if ( state == BLACKS_MOVE && ! othello_has_valid_move ( & board , PLAYER_BLACK )) { state = WHITES_MOVE ; } if ( state == WHITES_MOVE ) { _beginthread ( compute_white_move , 0 , ( void * ) window ); } InvalidateRect ( window , NULL , TRUE ); }

One thing that's different is how we compute white's move. On Windows, it's easy to send a message from one thread to another in a safe way, so that's what we do. _beginthread starts execution of compute_white_move on a new thread. When the computation is done, it simply sends a message to our window that we handle in the message loop:

static void compute_white_move ( void * window ) { int row , col ; assert ( state == WHITES_MOVE ); othello_compute_move ( & board , PLAYER_WHITE , & row , & col ); SendMessage (( HWND ) window , WM_WHITE_MOVE , row , col ); }

WM_WHITE_MOVE is a message number that we defined ourselves, based on the WM_USER macro. We exploit the fact that messages take two parameters, and pass the computed row in wparam and the column in lparam.

We have to take care to make sure our program is thread safe. While compute_white_move accesses the Othello board on its thread, the main thread must not be allowed to change the board. The code makes sure that while in the WHITES_MOVE state, the board is never changed until the WM_WHITE_MOVE message is received. Note that for this reason, the "New Game" menu option is disabled when it's white's turn.

Building

The program can be built with the compiler from Visual Studio (I used the free version, Visual Studio Express) by running the following commands in a Visual Studio Developer Command Prompt (the name varies a little between versions, but it should be available in the Start menu, somewhere in the Visual Studio folder).

First we compile the resource file to a .res file:

C:\othello>rc win_othello_res.rc Microsoft (R) Windows (R) Resource Compiler Version 10.0.10011.16384 Copyright (C) Microsoft Corporation. All rights reserved.

Next, the C files are compiled. /Ox means maximum optimization and /DNDEBUG disables the asserts. /MT makes our program link statically against the C run-time library, which means we don't need to distribute the C run-time DLL with our executable.

C:\othello>cl /c /Ox /DNDEBUG /MT win_othello.c othello.c Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23506 for x86 Copyright (C) Microsoft Corporation. All rights reserved. win_othello.c othello.c Generating Code...

And finally we link it all together into an executable:

C:\othello>link /out:othello.exe win_othello.obj othello.obj win_othello_res.res user32.lib gdi32.lib Microsoft (R) Incremental Linker Version 14.00.23506.0 Copyright (C) Microsoft Corporation. All rights reserved.

This creates an executable that will run on Windows Vista or later. However, our code is not using any new fancy APIs — in fact everything it uses was present in Win32 from the very beginning. Would it be possible to build our program so that it runs on all versions of Windows that support Win32?

The problem is that the libraries in current versions of the Windows SDK only work with Windows XP and later, as they end up referencing functionality that was not present in older versions of Windows. We need an older SDK. The Wikipedia article provides some download links, but another way is to install an older version of Visual Studio, as they include contemporary SDKs.

I used "Visual C++ .NET Standard 2003". Unfortunately, despite the year it was released in, the included compiler cannot handle basic C99 features (such as stdint.h) that are used in the Othello program. But since all we wanted was the libraries, we can use Clang for the actual compilation.

In a Visual Studio .NET 2003 Command Prompt:

C:\othello>rc win_othello_res.rc C:\othello>clang-cl /c /Ox /DNDEBUG /MT /arch:IA32 win_othello.c othello.c C:\othello>link /out:othello.exe win_othello.obj othello.obj win_othello_res.res user32.lib gdi32.lib Microsoft (R) Incremental Linker Version 7.10.3077 Copyright (C) Microsoft Corporation. All rights reserved.

That produces an executable that will run on Windows NT 4.0 or later. However, the binary might still work if we could convince older versions of Windows to load it. This is determined by the subsystem version field in the PE header of the executable (see Ones and Zeros, Part 2: Executables), and we can change it to 3.10, the version number of the first Windows NT release, like this:

C:\othello>editbin /subsystem:windows,3.10 othello.exe Microsoft (R) COFF/PE Editor Version 7.10.3077 Copyright (C) Microsoft Corporation. All rights reserved.

It works! The screenshots below show the executable running on Windows NT 3.51 and Windows 10, versions that were released more than twenty years apart. For some reason, I find this extremely satisfying. (Perhaps in this sense, our executable is a lot more universal than programs using the newfangled Universal Windows Platform model?)

The Windows executable can also be built on Linux using the mingw-w64 toolchain:

$ sudo apt-get install mingw-w64 $ i686-w64-mingw32-windres win_othello_res.rc win_othello_res.o $ i686-w64-mingw32-gcc -O3 -DNDEBUG othello.c win_othello.c win_othello_res.o \ -o othello.exe -luser32 -lgdi32 -mwindows

Mac

Applications for macOS (originally called Mac OS X and later just OS X) have traditionally been written in Objective-C using the Cocoa framework. The history of this operating system is quite fascinating, and Amit Singh provides an excellent account of it in the online extended version of the first chapter of his book, Mac OS X Internals.

Objective-C is a Smalltalk-inspired object-oriented extension of C created by Brad Cox and Tom Love in the eighties. It was licensed by NeXT, Steve Jobs's start-up after his ejection from Apple, for use in their NeXTSTEP operating system. After Apple's acquisition of NeXT, much of the software ended up in Mac OS X, including the use of Objective-C and the software "kits" which became Cocoa. (One plausible theory is that the name is a play on Java, considered hot stuff at the time, and a way to recycle the trademark of an old Cocoa product.)

As opposed to C, C++, and other languages that are standardised by organizations such as ISO, Objective-C is implementation defined: the language is what the toolchain in Xcode implements. There are plenty of guides and such, but frustratingly there is no actual language specification.

(In 2014, Apple introduced the Swift programming language, which seems to have become the language of choice for new development on Mac. However, I'm sticking with Objective-C here because I was curious about its history and I run into it at work sometimes.)

To learn about Mac programming, I mainly used the fourth edition (the last one based on Objective-C) of Hillegass & Preble "Cocoa Programming For Mac OS X" (Addison-Wesley 2012). I also read parts of Amit Singh's Mac OS X Internals, some of Brad Cox's "Object Oriented Programming: An Evolutionary Approach" (Addison-Wesley 1986) which is the original description of the language, and I peeked in David Chisnall's "Cocoa Programming Developer's Handbook" (Addison-Wesley 2009) which is not new but very rich in technical detail.

Anatomy of a Mac Application

On Linux and Windows, our Othello programs consist of a single executable file. On Mac however, most apps consist of multiple files that make up an application bundle.

A bundle is a directory that gets special treatment from the operating system due to its filename extension, such as .app for application bundles. For example, our application will be a directory called Othello.app. In Finder, it looks like a single file called Othello, and starts when double-clicked on, but in a terminal we can see that it is just a directory of files:

(To start the application bundle from a terminal, run open Othello.app. To see the contents of a bundle in Finder, Ctrl-click on it and select Show Package Contents.)

Bundles are a nice way to keep applications in a self-contained package. Installing a program on macOS usually just means dragging the bundle into the Applications folder.

Inside the bundle is an information property list, Info.plist, which contains metadata about the bundle:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version= "1.0" > <dict> <key> CFBundleDevelopmentRegion </key> <string> en </string> <key> CFBundleDisplayName </key> <string> Othello </string> <key> CFBundleExecutable </key> <string> Othello </string> <key> CFBundleIconFile </key> <string> Othello </string> <key> CFBundleIdentifier </key> <string> net.hanshq.Othello </string> <key> CFBundleInfoDictionaryVersion </key> <string> 6.0 </string> <key> CFBundleName </key> <string> Othello </string> <key> CFBundlePackageType </key> <string> APPL </string> <key> CFBundleShortVersionString </key> <string> 1.2 </string> <key> CFBundleVersion </key> <string> 1.2 </string> <key> NSHumanReadableCopyright </key> <string> Copyright © 2017 Hans Wennborg </string> <key> NSMainNibFile </key> <string> MainMenu </string> <key> NSPrincipalClass </key> <string> NSApplication </string> <key> LSMinimumSystemVersion </key> <string> 10.7 </string> </dict> </plist>

The file specifies things like version number, icon (Othello.icns from the Contents/Resources folder in the bundle), and most importantly, which executable to run when starting the application. In our case, the executable is called Othello (in the Contents/MacOS folder), and its main function is defined in main.m:

#import <Cocoa/Cocoa.h> int main ( int argc , const char ** argv ) { return NSApplicationMain ( argc , argv ); }

This is an Objective-C source file (its extension is .m), but it doesn't really use any Objective-C features: main is the usual function from C, and it contains a regular function call and return statement. However, it does include an Objective-C header, Cocoa.h (for the declaration of NSApplicationMain). Objective-C headers are normally included with an #import directive, which is a language extension that works just like #include except that it guarantees to only include the file once, without any need for include guards.

(It's not entirely clear why Objective-C uses .m as the file extension. The best theory indicates that it is for "messages", a core concept in Objective-C.)

The only thing main does is call on NSApplicationMain, which will fire up the Cocoa application framework and load the main nib file from our application bundle, as specified by NSMainNibFile in the property list.

Nibs

Nibs are files generated by Interface Builder, which is part of Xcode these days. It was originally called NeXT Interface Builder, which is why the files had a .nib extension. Current versions of Interface Builder generate XML files with the extension .xib instead, but the files are still referred to as nibs, and they are converted to .nib files as part of the build process.

Nib files contain the serialization of an object graph (sometimes called a set of freeze-dried objects). That is, they describe a set of objects and connections between them. Those objects can be user interface elements such as windows, buttons, etc. along with attributes that define their appearance, or other objects. Connections allow objects to reference each other; for example, clicking a button might invoke some method on another object. When the framework loads a nib file, it instantiates the objects from the file and sets up the connections between them.

The screenshot below shows the nib for our Othello program in Xcode:

Here, Xcode has been used to create a custom OthelloView object that fills the window, as well as the main menu for our program.

Interface Builder has also been used to set up connections between objects. For example, the delegate outlet of the OthelloView (an outlet is a kind of instance variable) is connected to the AppDelegate object, which acts as the controller for our program.

Other connections, which are not visible in the screenshot above, can be seen in the .xib file itself, MainMenu.xib: (Apologies for the huge blob of XML, but that is what we get for using this kind of tool.)

<?xml version="1.0" encoding="UTF-8"?> <document type= "com.apple.InterfaceBuilder3.Cocoa.XIB" version= "3.0" toolsVersion= "11762" systemVersion= "16D32" targetRuntime= "MacOSX.Cocoa" propertyAccessControl= "none" useAutolayout= "YES" customObjectInstantitationMethod= "direct" > <dependencies> <deployment identifier= "macosx" /> <plugIn identifier= "com.apple.InterfaceBuilder.CocoaPlugin" version= "11762" /> <capability name= "documents saved in the Xcode 8 format" minToolsVersion= "8.0" /> </dependencies> <objects> <customObject id= "-2" userLabel= "File's Owner" customClass= "NSApplication" > <connections> <outlet property= "delegate" destination= "1" id= "15" /> </connections> </customObject> <customObject id= "-1" userLabel= "First Responder" customClass= "FirstResponder" /> <customObject id= "-3" userLabel= "Application" customClass= "NSObject" /> <customObject id= "1" customClass= "AppDelegate" > <connections> <outlet property= "newgameMenu" destination= "2" id= "12" /> <outlet property= "othelloView" destination= "3" id= "13" /> <outlet property= "window" destination= "4" id= "14" /> </connections> </customObject> <customObject id= "16" customClass= "NSFontManager" /> <menu title= "Main Menu" systemMenu= "main" id= "5" > <items> <menuItem title= "Othello" id= "6" > <modifierMask key= "keyEquivalentModifierMask" /> <menu key= "submenu" title= "Othello" systemMenu= "apple" autoenablesItems= "NO" id= "7" > <items> <menuItem title= "New Game" keyEquivalent= "n" id= "2" > <connections> <action selector= "newGame:" target= "1" id= "8" /> </connections> </menuItem> <menuItem isSeparatorItem= "YES" id= "9" /> <menuItem title= "Quit Othello" keyEquivalent= "q" id= "10" > <connections> <action selector= "terminate:" target= "-1" id= "11" /> </connections> </menuItem> </items> </menu> </menuItem> </items> </menu> <window title= "Othello" allowsToolTipsWhenApplicationIsInactive= "NO" autorecalculatesKeyViewLoop= "NO" releasedWhenClosed= "NO" animationBehavior= "default" id= "4" > <windowStyleMask key= "styleMask" titled= "YES" closable= "YES" miniaturizable= "YES" resizable= "YES" /> <windowPositionMask key= "initialPositionMask" leftStrut= "YES" rightStrut= "YES" topStrut= "YES" bottomStrut= "YES" /> <rect key= "contentRect" x= "335" y= "390" width= "360" height= "360" /> <rect key= "screenRect" x= "0.0" y= "0.0" width= "1920" height= "1177" /> <view key= "contentView" id= "17" > <rect key= "frame" x= "0.0" y= "0.0" width= "360" height= "360" /> <autoresizingMask key= "autoresizingMask" /> <subviews> <customView fixedFrame= "YES" translatesAutoresizingMaskIntoConstraints= "NO" id= "3" customClass= "OthelloView" > <rect key= "frame" x= "20" y= "20" width= "320" height= "320" /> <autoresizingMask key= "autoresizingMask" widthSizable= "YES" heightSizable= "YES" /> <connections> <outlet property= "delegate" destination= "1" id= "18" /> </connections> </customView> </subviews> </view> </window> </objects> </document>

The connections element for the AppDelegate shows how it's connected to the "New Game" menu item, the OthelloView and the window. That means that when the nib is loaded, AppDelegate will be able to access those objects through its instance variables.

The file also defines actions. For example, when the user clicks the "New Game" menu item, a newGame: message will be sent to (Objective-C lingo for invoking a method) the AppDelegate.

There are some objects in the nib that were not defined by us. The "File's Owner" object (id -2) is the object that is loading the nib, in our case an instance of NSApplication whose actions were set in motion by the call to NSApplicationMain in the main function of our program. This is a big difference from Linux and Windows where we did a lot of the low-level work ourselves: here, NSApplication is running the show, and we cater to it with nibs and delegate classes.

Another special object is the "First Responder" (id -1). In the nib file, this isn't a specific object, but a place-holder to allow binding actions to the object that's currently chosen to receive keyboard events. That object is known as the first responder because it comes first in the chain of objects that respond to events. In our case, the "Quit Othello" menu item's action is set up to send a terminate: message to the first responder when invoked. The first responder for our program will be the OthelloView, but since it doesn't handle terminate: messages, they will get passed up the chain until they reach NSApplication, whose terminate: method terminates the application.

(It is possible to build Cocoa applications without nibs, which is appealing in some ways. However, certain things, such as defining menus, are tricky without using nibs.)

The Controller

Compared to the X and Windows ports, because we're using an object-oriented programming language and working with the Cocoa framework, the Model-View-Controller organization of our program becomes more explicit. The game is still modelled by the Othello engine we wrote above, the view will be implemented further below, and the controller is implemented here in the AppDelegate class. Perhaps it's not the best of names, but it is what Xcode gave me.

This is its header file, AppDelegate.h:

#import <Cocoa/Cocoa.h> #include "../othello.h" enum State { BLACKS_MOVE , WHITES_MOVE , GAME_OVER }; @interface AppDelegate : NSObject < NSApplicationDelegate > { othello_t board ; enum State state ; } // Get a pointer to the Othello board. - ( const othello_t * ) board ; // Get the current state. - ( enum State ) state ; // To be called when a cell on the board is clicked by the user. - ( void ) boardWasClickedAtRow: ( int ) row Column: ( int ) col ; // Reset the game state. - ( IBAction ) newGame: ( id ) sender ; @end

We recognize some of this as ordinary C code: the #include of our othello.h header and the declaration of the enumeration. Being able to interoperate easily with C is a key strength of Objective-C. Through the backwards compatibility with C, it lowers the threshold for adoption, as programmers can easily reuse their existing code. In our case, it means the Othello engine can be used seamlessly.

The block starting with @interface and ending with @end declares a class interface. Here we declare the AppDelegate class, which inherits from NSObject (all classes usually do), and conforms to the NSApplicationDelegate protocol.

A protocol is like an interface in Java: a set of methods, some of which may be optional, that a class can declare that it implements. A class can only have one superclass, but may conform to multiple protocols.

The declarations of board and state within the curly braces are instance variable declarations for the class. These variables are private to the implementation of the class.

Following the closing curly brace are method declarations. The return type is provided in parenthesis, followed by the name of the method with method arguments and their types separated by colons. The colon syntax comes from Smalltalk and aims to make method invocations more explicative. For example, when cell (1,2) is clicked on the board, we will send a boardWasClickedAtRow:1 Column:2 message. Apple's documentation provides some naming guidelines.

Method declarations are prefixed with either + or - signs. A plus sign signifies a class method, which is a method that can be invoked directly on the class, similar to a static method in Java and C++. One common use is the alloc method (typically inherited from NSObject) and other methods which create instances of a class. Method declarations prefixed by a minus sign are instance methods: they can only be invoked on instances of the class, not on the class itself.

IBAction is a macro that expands to void, but signals to Interface Builder that this method should be exposed as an action which other objects can invoke. In our case, we have connected the "New Game" menu option to this method.

The definition of our class is provided in AppDelegate.m, which begins like this:

#import "AppDelegate.h" #import "OthelloView.h" @interface AppDelegate () - ( void ) makeMoveAtRow :( int ) row Column :( int ) col ; @property ( weak ) IBOutlet NSWindow * window ; @property ( weak ) IBOutlet OthelloView * othelloView ; @property ( weak ) IBOutlet NSMenuItem * newgameMenu ; @end

This @interface declaration is a class extension of AppDelegate. The methods declared in the header file are all public. (Unlike C++ and Java, Objective-C does not have access modifiers such as public: and private:). Using a class extension allows us to add a private method makeMoveAtRow:Column: which is only available for internal use.

We also add properties, which are instance variables synthesized by the compiler. For each property, the compiler will generate getter and setter methods (for example, window and setWindow) to access the instance variable. Since we don't want the setters and getters to be public, the properties are declared in this class extension, rather than the public interface. The properties are marked IBOutlet, which means they will show up as outlets in Interface Builder, where we connected them to other objects. When our nib file is loaded, these properties will be set to reference the objects they're connected to.

Objective-C uses reference counting for memory management. The weak attribute on our properties mean that they are weak references, that is, they don't contribute to the reference count of the objects they refer to. Strong references would increment the reference count in the property's setter.

After the class extension, we begin the method definitions:

@implementation AppDelegate - ( id ) init { self = [ super init ]; if ( self ) { [ self newGame : nil ]; } return self ; } - ( BOOL ) applicationShouldTerminateAfterLastWindowClosed: ( NSApplication * ) app { ( void ) app ; return YES ; }

The syntax of a method definition matches that of a method declaration (prefixed by + or - followed by return type in parenthesis, etc), except that it includes the method body surrounded by curly braces.

The init method initializes our object. It is important to call the initializer of the superclass first (and abort if that returns nil). [super init] is a message expression. It sends an init message to the super object (super refers to the instance of our object's parent class). The message terminology comes from Smalltalk, and is similar to a method invocation in other languages. If initialization of the parent succeeded, we initialize ourselves with the newGame: method, by sending a message to ourselves (self is similar to this in Java and C++).

The applicationShouldTerminateAfterLastWindowClosed: method is part of the NSApplicationDelegate protocol which our class conforms to. It gets called by NSApplication to ask if we would like the application to terminate after the last window closed, to which the answer is yes. (YES and NO, of type BOOL correspond to true and false in other languages.) The app argument is cast to void to suppress compiler warnings about unused variables.

The following method definitions are straight-forward, mixing regular C code with a few message expressions:

- ( const othello_t * ) board { return & board ; } - ( enum State ) state { return state ; } - ( void ) boardWasClickedAtRow: ( int ) row Column: ( int ) col { if ( state == BLACKS_MOVE ) { [ self makeMoveAtRow : row Column : col ]; } } - ( IBAction ) newGame: ( id ) sender { ( void ) sender ; othello_init ( & board ); state = BLACKS_MOVE ; [ self . newgameMenu setEnabled : YES ]; [ self . othelloView setNeedsDisplay : YES ]; }

Note the self.othelloView syntax which is shorthand for accessing a property. It is equivalent to invoking the getter method with [self othelloView], but cuts down on the use of angle brackets.

makeMoveAtRow:Column:, the final method, is similar to make_move in the Windows and X11 versions, making a move for the current player and transitioning to the next game state:

- ( void ) makeMoveAtRow: ( int ) row Column: ( int ) col { assert ( state == BLACKS_MOVE || state == WHITES_MOVE ); if ( state == BLACKS_MOVE ) { if ( ! othello_is_valid_move ( & board , PLAYER_BLACK , row , col )) { // Illegal move; ignored. return ; } othello_make_move ( & board , PLAYER_BLACK , row , col ); state = WHITES_MOVE ; } else { othello_make_move ( & board , PLAYER_WHITE , row , col ); state = BLACKS_MOVE ; } if ( ! othello_has_valid_move ( & board , PLAYER_BLACK ) && ! othello_has_valid_move ( & board , PLAYER_WHITE )) { state = GAME_OVER ; } else if ( state == BLACKS_MOVE && ! othello_has_valid_move ( & board , PLAYER_BLACK )) { state = WHITES_MOVE ; } else if ( state == WHITES_MOVE && ! othello_has_valid_move ( & board , PLAYER_WHITE )) { state = BLACKS_MOVE ; } if ( state == WHITES_MOVE ) { dispatch_async ( dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT , 0 ), ^ { int row , col ; othello_compute_move ( & board , PLAYER_WHITE , & row , & col ); dispatch_async ( dispatch_get_main_queue (), ^ { [ self makeMoveAtRow : row Column : col ]; }); }); } [ self . newgameMenu setEnabled :( state != WHITES_MOVE )]; [ self . othelloView setNeedsDisplay : YES ]; } @end

As in the other versions, it is important that othello_compute_move is called on a background thread. To accomplish this, we use dispatch_async which takes a dispatch queue argument (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) gets us a background thread) and an Objective-C block, a form of anonymous function created with the ^{ ... } syntax. After the move is computed, we use the same technique to call back to makeMoveAtRow:Column: with the result.

Similarly to the Windows version, we need to be careful not to change the game state on the main thread while a move is being computed on another thread. For that reason, we disable the "New Game" menu item while it's white's turn.

The View

The OthelloView class is responsible for displaying the game state and letting the AppDelegate know when the user clicks on or selects a cell using the keyboard.

The class declaration, in OthelloView.h is very simple:

#import <Cocoa/Cocoa.h> @interface OthelloView : NSView { int selCol , selRow ; int cellSize , xOffset , yOffset , size ; } @end

All it does is declare a few instance variables to keep track of which cell is currently selected, and the position that the board is drawn at. It doesn't declare any methods in addition to those it inherits from NSView.

As with AppDelegate, we use a class extension to declare a few private methods and a property which we connected in Interface Builder. In OthelloView.m:

#import "OthelloView.h" #import "AppDelegate.h" #include "../othello.h" @interface OthelloView () - ( void ) drawChar :( char ) c atPoint :( NSPoint ) p ; - ( void ) drawString: ( NSString * ) s atPoint: ( NSPoint ) p ; - ( BOOL ) hitTest: ( NSPoint ) p withHitRow: ( int * ) row hitCol: ( int * ) col ; @property ( weak ) IBOutlet AppDelegate * delegate ; @end static const int CELL_GAP = 1 ;

The delegate property will be used to get the game state, and for sending notifications when the user clicks a cell.

First we define a few methods inherited from NSView:

@implementation OthelloView - ( void ) awakeFromNib { [ super awakeFromNib ]; selRow = - 1 ; selCol = - 1 ; [ self . window setAcceptsMouseMovedEvents : YES ]; [ self setNeedsDisplay : YES ]; } - ( BOOL ) acceptsFirstResponder { return YES ; } - ( BOOL ) isFlipped { return YES ; }

awakeFromNib gets called after our object is loaded from the nib file. We initialize the selected cell variables, let the window (NSView has a window getter) know that we would like to receive mouse-motion events, and then call setNeedsDisplay: to let the framework know that we'd like to be painted.

Returning YES from acceptsFirstResponder allows our view to receive keyboard events.

Returning YES from isFlipped means our view will draw in a "flipped" coordinate system, that is, the origin will be in the upper-left corner instead of the default which is lower-left. This is useful because it gives us the same coordinate system that we used for X11 and Windows, so we can reuse our calculations in the drawing code.

The drawing is performed by drawRect: which gets called when the framework wants to draw the view:

- ( void ) drawRect: ( NSRect ) dirtyRect { [ super drawRect : dirtyRect ]; // Compute size and position of the 10x10 grid. NSRect bounds = [ self bounds ]; float viewSize = MIN ( bounds . size . height , bounds . size . width ); cellSize = ( viewSize - 9 * CELL_GAP ) / 10 ; size = cellSize * 10 + 9 * CELL_GAP ; xOffset = bounds . origin . x + bounds . size . width / 2 - size / 2 ; yOffset = bounds . origin . y + bounds . size . height / 2 - size / 2 ; // Draw a background square around the 8x8 centre cells. [[ NSColor blackColor ] set ]; NSRect backgroundRect = NSMakeRect ( xOffset + cellSize , yOffset + cellSize , 8 * cellSize + 9 * CELL_GAP , 8 * cellSize + 9 * CELL_GAP ); [ NSBezierPath fillRect : backgroundRect ]; // Draw labels. for ( int row = 0 ; row < 8 ; row ++ ) { float x = xOffset + cellSize / 2 ; float y = yOffset + ( row + 1 ) * ( cellSize + CELL_GAP ) + cellSize / 2 ; [ self drawChar :( '1' + row ) atPoint : NSMakePoint ( x , y )]; } for ( int col = 0 ; col < 8 ; col ++ ) { float x = xOffset + ( col + 1 ) * ( cellSize + CELL_GAP ) + cellSize / 2 ; float y = yOffset + cellSize / 2 ; [ self drawChar :( 'A' + col ) atPoint : NSMakePoint ( x , y )]; } const othello_t * board = [ self . delegate board ]; // Draw status text. NSString * statusText ; switch ([ self . delegate state ]) { case BLACKS_MOVE : statusText = @"Human's move." ; break ; case WHITES_MOVE : statusText = @"Computer's move..." ; break ; case GAME_OVER : { int blackScore = othello_score ( board , PLAYER_BLACK ); int whiteScore = othello_score ( board , PLAYER_WHITE ); if ( blackScore > whiteScore ) { statusText = [ NSString stringWithFormat : @"Human wins %d-%d!" , blackScore , whiteScore ]; } else if ( whiteScore > blackScore ) { statusText = [ NSString stringWithFormat : @"Computer wins %d-%d!" , whiteScore , blackScore ]; } else { statusText = @"Draw!" ; } break ; } } [ self drawString : statusText atPoint : NSMakePoint ( xOffset + size / 2 , yOffset + size - cellSize / 2 )]; // Draw the cells. NSColor * boardColor = [ NSColor colorWithCalibratedRed : 0 green :( 0x80 / 256.0 ) blue : 0 alpha : 1 ]; NSColor * highlightColor = [ NSColor colorWithCalibratedRed : 0 green :( 0xAA / 256.0 ) blue : 0 alpha : 1 ]; for ( int row = 0 ; row < 8 ; row ++ ) { for ( int col = 0 ; col < 8 ; col ++ ) { float x = xOffset + ( col + 1 ) * ( cellSize + CELL_GAP ); float y = yOffset + ( row + 1 ) * ( cellSize + CELL_GAP ); bool highlight = ( row == selRow && col == selCol && [ self . delega