summaryrefslogtreecommitdiff
path: root/apps/plugins/goban/util.c
blob: bb783163466b6f963eb910a7eb41fe118589fcb8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
,'I','F','F',//  0 - ChunkID
    0,0,0,0,        //  4 - ChunkSize (filesize-8)
    'W','A','V','E',//  8 - Format
    'f','m','t',' ',// 12 - SubChunkID
    16,0,0,0,       // 16 - SubChunk1ID  // 16 for PCM
    1,0,            // 20 - AudioFormat (1=Uncompressed)
    2,0,            // 22 - NumChannels
    0,0,0,0,        // 24 - SampleRate in Hz
    0,0,0,0,        // 28 - Byte Rate (SampleRate*NumChannels*(BitsPerSample/8)
    4,0,            // 32 - BlockAlign (== NumChannels * BitsPerSample/8)
    16,0,           // 34 - BitsPerSample
    'd','a','t','a',// 36 - Subchunk2ID
    0,0,0,0         // 40 - Subchunk2Size
};

int open_wav(char* filename) {
    int fd;

    fd=open(filename,O_CREAT|O_WRONLY|O_TRUNC,S_IRUSR|S_IWUSR);
    if (fd >= 0) {
        write(fd,wav_header,sizeof(wav_header));
    }
    return(fd);
}

void close_wav(int fd, FLACContext* fc) {
    int x;
    int filesize;
    int bytespersample;

    bytespersample=fc->bps/8;

    filesize=fc->totalsamples*bytespersample*fc->channels+44;

    // ChunkSize
    x=filesize-8;
    wav_header[4]=(x&0xff);
    wav_header[5]=(x&0xff00)>>8;
    wav_header[6]=(x&0xff0000)>>16;
    wav_header[7]=(x&0xff000000)>>24;

    // Number of channels
    wav_header[22]=fc->channels;

    // Samplerate
    wav_header[24]=fc->samplerate&0xff;
    wav_header[25]=(fc->samplerate&0xff00)>>8;
    wav_header[26]=(fc->samplerate&0xff0000)>>16;
    wav_header[27]=(fc->samplerate&0xff000000)>>24;

    // ByteRate
    x=fc->samplerate*(fc->bps/8)*fc->channels;
    wav_header[28]=(x&0xff);
    wav_header[29]=(x&0xff00)>>8;
    wav_header[30]=(x&0xff0000)>>16;
    wav_header[31]=(x&0xff000000)>>24;

    // BlockAlign
    wav_header[32]=(fc->bps/8)*fc->channels;

    // Bits per sample
    wav_header[34]=fc->bps;
    
    // Subchunk2Size
    x=filesize-44;
    wav_header[40]=(x&0xff);
    wav_header[41]=(x&0xff00)>>8;
    wav_header[42]=(x&0xff0000)>>16;
    wav_header[43]=(x&0xff000000)>>24;

    lseek(fd,0,SEEK_SET);
    write(fd,wav_header,sizeof(wav_header));
    close(fd);
}

static void dump_headers(FLACContext *s)
{
    fprintf(stderr,"  Blocksize: %d .. %d\n", s->min_blocksize, 
                   s->max_blocksize);
    fprintf(stderr,"  Framesize: %d .. %d\n", s->min_framesize, 
                   s->max_framesize);
    fprintf(stderr,"  Samplerate: %d\n", s->samplerate);
    fprintf(stderr,"  Channels: %d\n", s->channels);
    fprintf(stderr,"  Bits per sample: %d\n", s->bps);
    fprintf(stderr,"  Metadata length: %d\n", s->metadatalength);
    fprintf(stderr,"  Total Samples: %lu\n",s->totalsamples);
    fprintf(stderr,"  Duration: %d ms\n",s->length);
    fprintf(stderr,"  Bitrate: %d kbps\n",s->bitrate);
}

static bool flac_init(int fd, FLACContext* fc)
{
    unsigned char buf[255];
    struct stat statbuf;
    bool found_streaminfo=false;
    int endofmetadata=0;
    int blocklength;
    uint32_t* p;
    uint32_t seekpoint_lo,seekpoint_hi;
    uint32_t offset_lo,offset_hi;
     *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "util.h"
#include "game.h"


void metadata_summary (void)
{
    char buffer[256] = "";

    if (rb->strlen (header.black) ||
        rb->strlen (header.white) ||
        rb->strlen (header.black_rank) ||
        rb->strlen (header.white_rank))
    rb->snprintf (buffer, sizeof(buffer),
                  "%s [%s] v. %s [%s] ",
                  header.black, header.black_rank,
                  header.white, header.white_rank);

    if (header.handicap > 1)
    {
        rb->snprintf (buffer + rb->strlen(buffer),
                      sizeof (buffer) - rb->strlen (buffer),
                      "%d stones ", header.handicap);
    }

    if (header.komi != 0 && !(header.komi == 1 && header.handicap > 1))
    {
        snprint_fixed (buffer + rb->strlen(buffer),
                        sizeof (buffer) - rb->strlen (buffer),
                        header.komi);
        rb->snprintf (buffer + rb->strlen(buffer),
                      sizeof (buffer) - rb->strlen (buffer),
                      " komi ");
    }

    if (rb->strlen(header.result))
    {
        rb->snprintf (buffer + rb->strlen(buffer),
                      sizeof (buffer) - rb->strlen (buffer),
                      "(%s)", header.result);
    }

    /* waiting for user input messes up the testing code, so ifdef it*/
#if !defined(GBN_TEST)
    if (rb->strlen(buffer))
    {
        rb->splash(0, buffer);
        rb->action_userabort(TIMEOUT_BLOCK);
    }
#endif
}

void *
align_buffer (void *buffer, size_t * buffer_size)
{
    unsigned int wasted = (-(long) buffer) & 3;

    if (!buffer || !buffer_size)
    {
        return NULL;
    }

    if (*buffer_size <= wasted)
    {
        *buffer_size = 0;
        return NULL;
    }

    *buffer_size -= wasted;

    return (void *) (((char *) buffer) + wasted);
}



bool
setup_stack (struct stack_t *stack, void *buffer, size_t buffer_size)
{
    if (!stack || !buffer || !buffer_size)
    {
        DEBUGF ("INVALID STACK SETUP!!\n");
        return false;
    }

    buffer = align_buffer (buffer, &buffer_size);

    if (!buffer || !buffer_size)
    {
        DEBUGF ("Buffer disappeared after alignment!\n");
        return false;
    }

    stack->buffer = buffer;
    stack->size = buffer_size;
    stack->sp = 0;

    return true;
}

bool
push_stack (struct stack_t * stack, void *buffer, size_t buffer_size)
{
    if (stack->sp + buffer_size > stack->size)
    {
        DEBUGF ("stack full!!\n");
        return false;
    }

    rb->memcpy (&stack->buffer[stack->sp], buffer, buffer_size);

    stack->sp += buffer_size;

    return true;
}

bool
pop_stack (struct stack_t * stack, void *buffer, size_t buffer_size)
{
    if (!peek_stack (stack, buffer, buffer_size))
    {
        return false;
    }

    stack->sp -= buffer_size;

    return true;
}

bool
peek_stack (struct stack_t * stack, void *buffer, size_t buffer_size)
{
    if (stack->sp < buffer_size)
    {
        return false;
    }

    rb->memcpy (buffer, &stack->buffer[stack->sp - buffer_size], buffer_size);

    return true;
}

void
empty_stack (struct stack_t *stack)
{
    stack->sp = 0;
}

bool
push_pos_stack (struct stack_t *stack, unsigned short pos)
{
    return push_stack (stack, &pos, sizeof (pos));
}

bool
push_int_stack (struct stack_t *stack, int num)
{
    return push_stack (stack, &num, sizeof (num));
}

bool
push_char_stack (struct stack_t *stack, char num)
{
    return push_stack (stack, &num, sizeof (num));
}



/* IMPORTANT: keep in sync with the enum prop_type_t enum in types.h */
char *prop_names[] = {
    /* look up the SGF specification for the meaning of these */
    "B", "W",
    "AB", "AW", "AE",

    "PL", "C",

    "DM", "GB", "GW", "HO", "UC", "V",

    "BM", "DO", "IT", "TE",

    "CR", "SQ", "TR", "DD", "MA", "SL", "LB", "N",

    "AP", "CA", "FF", "GM", "ST", "SZ",

    "AN", "PB", "PW", "HA", "KM", "TB", "TW", "BR", "WR",
    "BT", "WT", "CP", "DT", "EV", "RO", "GN", "GC", "ON",
    "OT", "PC", "RE", "RU", "SO", "TM", "US",

    "BL", "WL", "OB", "OW", "FG", "PM", "VW"
};

/* These seems to be specified by the SGF specification.  You can do free
   form ones as well, but I haven't implemented that (and don't plan to) */
const char *ruleset_names[] = { "AGA", "Japanese", "Chinese", "NZ", "GOE" };



int
create_or_open_file (const char *filename)
{
    int fd;

    if (!rb->file_exists (filename))
    {
        fd = rb->creat(filename, 0666);
    }
    else
    {
        fd = rb->open (filename, O_RDWR);
    }

    return fd;
}


int
snprint_fixed (char *buffer, int buffer_size, int fixed)
{
    return rb->snprintf (buffer, buffer_size, "%s%d.%d",
                         fixed < 0 ? "-" : "",
                         abs (fixed) >> 1, 5 * (fixed & 1));
}


int
peek_char (int fd)
{
    char peeked_char;

    int result = rb->read (fd, &peeked_char, 1);

    if (result != 1)
    {
        return -1;
    }

    result = rb->lseek (fd, -1, SEEK_CUR);

    if (result < 0)
    {
        return -1;
    }

    return peeked_char;
}


int
read_char (int fd)
{
    char read_char;

    int result = rb->read (fd, &read_char, 1);

    if (result != 1)
    {
        return -1;
    }

    return read_char;
}


bool
write_char (int fd, char to_write)
{
    int result = write_file (fd, &to_write, 1);

    if (result != 1)
    {
        return false;
    }

    return true;
}

ssize_t
write_file (int fd, const void *buf, size_t count)
{
    const char *buffer = buf;
    int result;
    int ret_val = count;

    while (count)
    {
        result = rb->write (fd, buffer, count);

        if (result < 0)
        {
            return -1;
        }

        count -= result;
        buffer += result;
    }

    return ret_val;
}

ssize_t
read_file (int fd, void *buf, size_t count)
{
    char *buffer = buf;
    int result;
    int ret_val = count;

    while (count)
    {
        result = rb->read (fd, buffer, count);

        if (result <= 0)
        {
            return -1;
        }

        count -= result;
        buffer += result;
    }

    return ret_val;
}

int
read_char_no_whitespace (int fd)
{
    int result = peek_char_no_whitespace (fd);

    read_char (fd);

    return result;
}

int
peek_char_no_whitespace (int fd)
{
    int result;

    while (is_whitespace (result = peek_char (fd)))
    {
        read_char (fd);
    }

    return result;
}


void
close_file (int *fd)
{
    if (*fd >= 0)
    {
        rb->close (*fd);
    }

    *fd = -1;
}

bool
is_whitespace (int value)
{
    if (value == ' ' ||
        value == '\t' ||
        value == '\n' || value == '\r' || value == '\f' || value == '\v')
    {
        return true;
    }
    else
    {
        return false;
    }
}

void
sanitize_string (char *string)
{
    bool escaped = false;

    if (!string)
    {
        return;
    }

    while (1)
    {
        switch (*string)
        {
        case '\0':
            return;
        case '\\':
            escaped = !escaped;
            break;
        case ']':
            if (!escaped)
            {
                *string = ']';
            }
            escaped = false;
            break;
        default:
            break;
        };
        ++string;
    }
}


bool
get_header_string_and_size (struct header_t *header,
                            enum prop_type_t type, char **buffer, int *size)
{
    if (buffer == 0 || header == 0)
    {
        return false;
    }

    if (type == PROP_BLACK_NAME)
    {
        *buffer = header->black;
        *size = MAX_NAME;
    }
    else if (type == PROP_WHITE_NAME)
    {
        *buffer = header->white;
        *size = MAX_NAME;
    }
    else if (type == PROP_BLACK_RANK)
    {
        *buffer = header->black_rank;
        *size = MAX_RANK;
    }
    else if (type == PROP_WHITE_RANK)
    {
        *buffer = header->white_rank;
        *size = MAX_RANK;
    }
    else if (type == PROP_BLACK_TEAM)
    {
        *buffer = header->black_team;
        *size = MAX_TEAM;
    }
    else if (type == PROP_WHITE_TEAM)
    {
        *buffer = header->white_team;
        *size = MAX_TEAM;
    }
    else if (type == PROP_DATE)
    {
        *buffer = header->date;
        *size = MAX_DATE;
    }
    else if (type == PROP_ROUND)
    {
        *buffer = header->round;
        *size = MAX_ROUND;
    }
    else if (type == PROP_EVENT)
    {
        *buffer = header->event;
        *size = MAX_EVENT;
    }
    else if (type == PROP_PLACE)
    {
        *buffer = header->place;
        *size = MAX_PLACE;
    }
    else if (type == PROP_OVERTIME)
    {
        *buffer = header->overtime;
        *size = MAX_OVERTIME;
    }
    else if (type == PROP_RESULT)
    {
        *buffer = header->result;
        *size = MAX_RESULT;
    }
    else if (type == PROP_RULESET)
    {
        *buffer = header->ruleset;
        *size = MAX_RULESET;
    }
    else
    {
        return false;
    }

    return true;
}


/* TEST CODE BEGINS HERE define GBN_TEST to run this, either in goban.h or
   in the CFLAGS. The tests will be run when the plugin starts, after
   which the plugin will exit. Any error stops testing since many tests
   depend on previous setup. Note: The testing can take a while as there
   are some big loops.  Be patient. */

#ifdef GBN_TEST

#include "goban.h"
#include "types.h"
#include "board.h"
#include "game.h"
#include "sgf.h"
#include "sgf_storage.h"

/* If this isn't on a single line, the line numbers it reports will be wrong.
 *
 * I'm sure there's a way to make it better, but it's not really worth it.
 */
#define gbn_assert(test) if (test) {DEBUGF("%d passed\n", __LINE__);} else {DEBUGF("%d FAILED!\n", __LINE__); rb->splashf(10 * HZ, "Test on line %d of util.c failed!", __LINE__); return;}

void
run_tests (void)
{
    rb->splash (3 * HZ, "Running tests.  Failures will stop testing.");



    /* allocating and freeing storage units */

    gbn_assert (alloc_storage_sgf ());

    int prevent_infinite = 100000000;

    int count = 1;
    while (alloc_storage_sgf () >= 0 && --prevent_infinite)
    {
        ++count;
    }

    gbn_assert (prevent_infinite);
    gbn_assert (count > 100);

    /* make sure it fails a few times */
    gbn_assert (alloc_storage_sgf () < 0);
    gbn_assert (alloc_storage_sgf () < 0);
    gbn_assert (alloc_storage_sgf () < 0);

    free_storage_sgf (0);

    gbn_assert (alloc_storage_sgf () == 0);

    gbn_assert (alloc_storage_sgf () < 0);

    int i;
    for (i = 0; i <= count; ++i)
    {
        free_storage_sgf (i);
    }

    gbn_assert (alloc_storage_sgf () >= 0);
    --count;

    for (i = 0; i < count; ++i)
    {
        gbn_assert (alloc_storage_sgf () >= 0);
    }

    free_tree_sgf ();



    /* setting up, saving and loading */
    gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 15));
    gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, -30));
    gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 4, 1));
    gbn_assert (setup_game (MIN_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1));

    gbn_assert (setup_game (MIN_BOARD_SIZE, MAX_BOARD_SIZE, 1, 1));
    gbn_assert (setup_game (MAX_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1));

    gbn_assert (!setup_game (MAX_BOARD_SIZE + 1, MAX_BOARD_SIZE + 1, 0, 15));
    gbn_assert (!setup_game (MIN_BOARD_SIZE - 1, MIN_BOARD_SIZE - 1, 0, 15));
    gbn_assert (!setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, -1, 15));

    gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 1, 1));
    gbn_assert (save_game (DEFAULT_SAVE_DIR "/t1.sgf"));
    gbn_assert (load_game (DEFAULT_SAVE_DIR "/t1.sgf"));
    gbn_assert (save_game (DEFAULT_SAVE_DIR "/t2.sgf"));
    gbn_assert (load_game (DEFAULT_SAVE_DIR "/t2.sgf"));

    gbn_assert (!save_game ("/DIR_DOESNT_EXIST/blah.sgf"));
    gbn_assert (!load_game ("/DIR_DOESNT_EXIST/blah.sgf"));
    gbn_assert (!load_game (DEFAULT_SAVE_DIR "/DOESNT_EXIST.sgf"));



    /* test of a long game, captures, illegal moves */
    gbn_assert (load_game (DEFAULT_SAVE_DIR "/long.sgf"));
    while (move_num < 520)
    {
        gbn_assert (num_variations_sgf () == 1);
        gbn_assert (redo_node_sgf ());
    }

    gbn_assert (play_move_sgf (POS (2, 0), BLACK));
    gbn_assert (play_move_sgf (POS (2, 1), WHITE));

    gbn_assert (move_num == 522);

    gbn_assert (white_captures == 261 && black_captures == 0);

    gbn_assert (play_move_sgf (PASS_POS, BLACK));
    gbn_assert (play_move_sgf (PASS_POS, WHITE));

    gbn_assert (move_num == 524);

    int x, y;
    int b_count, w_count, e_count;
    b_count = w_count = e_count = 0;
    for (x = 0; x < 19; ++x)
    {
        for (y = 0; y < 19; ++y)
        {
            gbn_assert (!legal_move_board (POS (x, y), BLACK, false));
            gbn_assert (!play_move_sgf (POS (x, y), BLACK));
            switch (get_point_board (POS (x, y)))
            {
            case BLACK:
                ++b_count;
                break;
            case WHITE:
                ++w_count;
                break;
            case EMPTY:
                ++e_count;
                break;
            default:
                gbn_assert (false);
            }
        }
    }

    gbn_assert (b_count == 0 && w_count == 261 && e_count == 19 * 19 - 261);

    gbn_assert (undo_node_sgf ());
    gbn_assert (move_num == 523);

    int infinite_prevention = 0;
    while (move_num > 0)
    {
        gbn_assert (undo_node_sgf ());

        ++infinite_prevention;
        gbn_assert (infinite_prevention < 100000);
    }

    gbn_assert (save_game (DEFAULT_SAVE_DIR "/long_out.sgf"));


    /* test of basic moves, legal moves, adding and removing stones */
    gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 0));
    gbn_assert (play_move_sgf
                (POS (MAX_BOARD_SIZE / 2, MAX_BOARD_SIZE / 2), BLACK));
    gbn_assert (move_num == 1 && current_player == WHITE);
    gbn_assert (!legal_move_board
                (POS (MAX_BOARD_SIZE / 2, MAX_BOARD_SIZE / 2), WHITE, true));

    int saved_node = current_node;
    gbn_assert (add_stone_sgf (POS (0, 0), BLACK));
    gbn_assert (current_node != saved_node);
    gbn_assert (get_point_board (POS (0, 0)) == BLACK);
    gbn_assert (move_num == 1 && current_player == WHITE);

    saved_node = current_node;
    gbn_assert (add_stone_sgf (POS (0, 1), WHITE));
    gbn_assert (current_node == saved_node);
    gbn_assert (get_point_board (POS (0, 1)) == WHITE);

    gbn_assert (add_stone_sgf (POS (0, 0), EMPTY));
    gbn_assert (add_stone_sgf (POS (0, 1), EMPTY));
    gbn_assert (get_point_board (POS (0, 0)) == EMPTY);
    gbn_assert (get_point_board (POS (0, 1)) == EMPTY);


    /* test captures */
    gbn_assert (load_game (DEFAULT_SAVE_DIR "/cap.sgf"));
    gbn_assert (play_move_sgf (POS (0, 0), BLACK));
    gbn_assert (black_captures == 8);
    gbn_assert (undo_node_sgf ());
    gbn_assert (black_captures == 0);

    gbn_assert (!play_move_sgf (POS (0, 0), WHITE));
    play_mode = MODE_FORCE_PLAY;
    gbn_assert (play_move_sgf (POS (0, 0), WHITE));
    play_mode = MODE_PLAY;

    gbn_assert (black_captures == 9);
    gbn_assert (get_point_board (POS (0, 0)) == EMPTY);
    gbn_assert (undo_node_sgf ());
    gbn_assert (black_captures == 0);

    gbn_assert (play_move_sgf (POS (9, 9), BLACK));
    gbn_assert (black_captures == 44);

    for (x = 0; x < 19; ++x)
    {
        for (y = 0; y < 19; ++y)
        {
            gbn_assert (get_point_board (POS (x, y)) == BLACK ||
                        add_stone_sgf (POS (x, y), BLACK));
        }
    }

    gbn_assert (get_point_board (POS (0, 0)) == BLACK);
    gbn_assert (add_stone_sgf (POS (9, 9), EMPTY));
    gbn_assert (play_move_sgf (POS (9, 9), WHITE));
    gbn_assert (white_captures == 360);

    gbn_assert (undo_node_sgf ());
    gbn_assert (white_captures == 0);

    play_mode = MODE_FORCE_PLAY;
    gbn_assert (play_move_sgf (POS (9, 9), BLACK));
    play_mode = MODE_PLAY;
    gbn_assert (white_captures == 361);

    for (x = 0; x < 19; ++x)
    {
        for (y = 0; y < 19; ++y)
        {
            gbn_assert (get_point_board (POS (x, y)) == EMPTY);
        }
    }


    /* test ko */
    gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 15));

    /*
     * Set up the board to look like this:
     * -X------
     * XO------
     * O-------
     * --------
     */
    gbn_assert (add_stone_sgf (POS (0, 1), BLACK));
    gbn_assert (add_stone_sgf (POS (1, 0), BLACK));
    gbn_assert (add_stone_sgf (POS (1, 1), WHITE));
    gbn_assert (add_stone_sgf (POS (0, 2), WHITE));

    /* take the ko and make sure black can't take back */
    gbn_assert (play_move_sgf (POS (0, 0), WHITE));
    gbn_assert (!play_move_sgf (POS (0, 1), BLACK));

    /* make sure white can fill, even with the ko_pos set */
    gbn_assert (play_move_sgf (POS (0, 1), WHITE));
    /* and make sure undo sets the ko again */
    gbn_assert (undo_node_sgf ());
    gbn_assert (!play_move_sgf (POS (0, 1), BLACK));

    /* make sure ko threats clear the ko */
    gbn_assert (play_move_sgf (POS (2, 2), BLACK));     /* ko threat */
    gbn_assert (play_move_sgf (POS (2, 3), WHITE));     /* response */
    gbn_assert (play_move_sgf (POS (0, 1), BLACK));     /* take ko */

    gbn_assert (undo_node_sgf ());
    gbn_assert (undo_node_sgf ());
    gbn_assert (undo_node_sgf ());

    /* make sure a pass is counted as a ko threat */
    gbn_assert (!play_move_sgf (POS (0, 1), BLACK));
    gbn_assert (play_move_sgf (PASS_POS, BLACK));
    gbn_assert (play_move_sgf (PASS_POS, WHITE));
    gbn_assert (play_move_sgf (POS (0, 1), BLACK));

    /* and finally let's make sure that white can't directly retake */
    gbn_assert (!play_move_sgf (POS (0, 0), WHITE));



    /* test some header information saving/loading as well as comment
       saving loading */
    char some_comment[] =
        "blah blah blah i am a stupid comment. here's some annoying characters: 01234567890!@#$%^&*()[[[[\\\\\\]ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    /* that bit near the end is literally this: \\\] which tests escaping
       of ]s */
    char read_buffer[256];

    gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 5, -20));

    /* this also tests that ko_pos is reset by setuping up a new game */
    gbn_assert (play_move_sgf (POS (0, 0), WHITE));
    gbn_assert (write_comment_sgf (some_comment) > 0);
    gbn_assert (play_move_sgf (POS (0, 1), BLACK));
    rb->strcpy (header.black, "Jack Black");
    rb->strcpy (header.white, "Jill White");

    gbn_assert (save_game (DEFAULT_SAVE_DIR "/head.sgf"));

    gbn_assert (setup_game (MIN_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1));
    gbn_assert (load_game (DEFAULT_SAVE_DIR "/head.sgf"));

    gbn_assert (header.komi == -20 && header.handicap == 5);
    gbn_assert (board_width == MAX_BOARD_SIZE
                && board_height == MAX_BOARD_SIZE);
    gbn_assert (rb->strcmp (header.black, "Jack Black") == 0);
    gbn_assert (rb->strcmp (header.white, "Jill White") == 0);
    gbn_assert (redo_node_sgf ());
    gbn_assert (read_comment_sgf (read_buffer, sizeof (read_buffer)));
    gbn_assert (rb->strcmp (read_buffer, some_comment) == 0);
    gbn_assert (redo_node_sgf ());
    gbn_assert (get_point_board (POS (0, 0)) == WHITE);
    gbn_assert (get_point_board (POS (0, 1)) == BLACK);



    /* test saving and loading a file with unhandled SGF properties. this
       test requires that the user diff unhnd.sgf with unhnd_out.sgf (any
       substantial difference is a bug and should be reported) the
       following are NOT substantial differences: - reordering of
       properties in a node - whitespace changes outside of a comment
       value or other property value - reordering of property values */
    gbn_assert (load_game (DEFAULT_SAVE_DIR "/unhnd.sgf"));
    gbn_assert (save_game (DEFAULT_SAVE_DIR "/unhnd_out.sgf"));



    /* Test variations a bit */
    gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 13));
    /* start at a move, otherwise add_stone won't create a variation */
    gbn_assert (play_move_sgf (POS (5, 5), BLACK));
    /* make sure it doesn't */
    gbn_assert (undo_node_sgf ());
    gbn_assert (add_stone_sgf (POS (4, 5), WHITE));
    gbn_assert (!undo_node_sgf ());
    gbn_assert (num_variations_sgf () == 1);
    gbn_assert (play_move_sgf (POS (5, 5), BLACK));

    gbn_assert (play_move_sgf (POS (0, 0), BLACK));
    gbn_assert (num_variations_sgf () == 1);
    gbn_assert (undo_node_sgf ());
    gbn_assert (play_move_sgf (POS (0, 1), BLACK));
    gbn_assert (num_variations_sgf () == 2);
    gbn_assert (undo_node_sgf ());
    gbn_assert (play_move_sgf (POS (0, 1), BLACK));
    gbn_assert (num_variations_sgf () == 2);
    gbn_assert (undo_node_sgf ());
    gbn_assert (play_move_sgf (POS (0, 2), BLACK));
    gbn_assert (num_variations_sgf () == 3);
    gbn_assert (undo_node_sgf ());
    gbn_assert (play_move_sgf (POS (0, 3), WHITE));
    gbn_assert (num_variations_sgf () == 4);
    gbn_assert (undo_node_sgf ());
    gbn_assert (play_move_sgf (PASS_POS, BLACK));
    gbn_assert (num_variations_sgf () == 5);
    gbn_assert (undo_node_sgf ());
    gbn_assert (add_stone_sgf (POS (1, 1), BLACK));
    gbn_assert (add_stone_sgf (POS (1, 2), BLACK));
    gbn_assert (add_stone_sgf (POS (1, 3), WHITE));
    gbn_assert (num_variations_sgf () == 6);
    gbn_assert (undo_node_sgf ());
    gbn_assert (add_stone_sgf (POS (1, 1), BLACK));
    gbn_assert (add_stone_sgf (POS (1, 2), BLACK));
    gbn_assert (add_stone_sgf (POS (1, 3), WHITE));
    gbn_assert (num_variations_sgf () == 7);
    gbn_assert (next_variation_sgf ());
    gbn_assert (get_point_board (POS (0, 0)) == BLACK);
    gbn_assert (get_point_board (POS (0, 1)) == EMPTY);
    gbn_assert (get_point_board (POS (0, 2)) == EMPTY);
    gbn_assert (get_point_board (POS (1, 1)) == EMPTY);
    gbn_assert (get_point_board (POS (1, 2)) == EMPTY);
    gbn_assert (get_point_board (POS (1, 3)) == EMPTY);

    rb->splash (10 * HZ, "All tests passed.  Exiting");
}
#endif /* GBN_TEST */