aboutsummaryrefslogtreecommitdiff
path: root/include/tps.c
blob: 7be0e8d70de355beeb9c9d2c57a80ff569cea70e (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
#include "tps.h"

// ===================================================================
// Load TPS string
// ===================================================================

#define TPS_ASSERT_MORE { if (*tps == 0) return TPS_INVALID; }

enum TPS_RESULT
load_tps(char* tps) {
  // TODO: Ensure NULL termination?
  if (tps == NULL) return TPS_INVALID;

  uint8_t prefix = 0;
  // Check if we're likely of the form [TPS "blah"]
  if (!strncmp(tps, "[TPS \"", 6)) {
    prefix=1;
    // Now we can worry about just the TPS part
    tps += 6;
  }

  // Reset everything
  reset_state(board_size);

  // Parse squares, NOTE: We assume that board_size matches TPS size.
  int col = 0, row = board_size-1, skip, parsing = 1;
  while (parsing) {
    switch (*tps) {
      case ' ': {
	// we're done
	parsing = 0;
	tps++; TPS_ASSERT_MORE;
	break;
      }
      case 'x': {
	// empty squares
	tps++; TPS_ASSERT_MORE;
	skip = 0;
	if (*tps >= '2' && *tps <= '0'+board_size) {
	  skip = *tps - '1';
	  tps++; TPS_ASSERT_MORE;
	} else if (*tps != ',' && *tps != '/' && *tps != ' ') {
	  return TPS_INVALID;
	}
	col += skip;
	if (col >= board_size + 1) return TPS_INVALID;
	break;
      }
      case '/': {
	// next row
	if (col + 1 != board_size) return TPS_INVALID;
	row--; col = 0;
	if (row < 0) return TPS_INVALID;
	tps++; TPS_ASSERT_MORE;
	break;
      }
      case ',': {
	// next column
	col++;
	if (col >= board_size) return TPS_INVALID;
	tps++; TPS_ASSERT_MORE;
	break;
      }
      default: {
	const int l = THE_COORDS(col, row);
	uint8_t num_read = 0, reading = 1;
	// Read in a stack of colours, optionally terminated by an S
	// or C to change the top stone type
	while (reading) {
	  switch (*tps) {
	    // Reading a stone colour
	    case '2': {
	      // check next letter to make sure we have the material
	      tps++; TPS_ASSERT_MORE;
	      if (*tps == 'C') {
		if (black_count & 128) black_count &= 127;
		else return TPS_INVALID;
	      } else if (black_count & 127) {
		black_count--;
	      } else return TPS_INVALID;
	      colours[l] <<= 1;
	      celldat[l] += NUM_INC;
	      colours[l] |= 1;
	      num_read++;
	      break;
	    }
	    case '1': {
	      tps++; TPS_ASSERT_MORE;
	      if (*tps == 'C') {
		if (white_count & 128) white_count &= 127;
		else return TPS_INVALID;
	      } else if (white_count & 127) {
		white_count--;
	      } else return TPS_INVALID;
	      colours[l] <<= 1;
	      celldat[l] += NUM_INC;
	      num_read++;
	      break;
	    }
	    case 'S': {
	      // Have we already read a stone type?
	      if (STONE_AT(l) != STONE_FLAT) return TPS_INVALID;
	      celldat[l] |= STONE_STANDING;
	      tps++; TPS_ASSERT_MORE;
	      break;
	    }
	    case 'C': {
	      if (STONE_AT(l) != STONE_FLAT) return TPS_INVALID;
	      celldat[l] |= STONE_CAPSTONE;
	      tps++; TPS_ASSERT_MORE;
	      break;
	    }
	    case ',': // fall-through
	    case '/': {
	      // done here
	      reading=0;
	      break;
	    }
	    default: return TPS_INVALID;
	  }
	  if (num_read > 0xF) return TPS_INVALID;
	}
      }
    }
  }

  // Now it's time to parse the ply number. First, the active player
  if (*tps != '1' && *tps != '2') return TPS_INVALID;
  ply += *tps - '1';
  tps++; TPS_ASSERT_MORE;

  // Space
  if (*tps != ' ') return TPS_INVALID;
  tps++; TPS_ASSERT_MORE;

  // Turn number, atoi doesn't detect errors so let's do it ourselves
  uint8_t p = 0;
  do {
    p *= 10;
    if (*tps >= '0' && *tps <= '9') {
      p += *tps - '0';
    } else return TPS_INVALID;
    tps++;
  } while ( (prefix && *tps && *tps != '"') || (!prefix && *tps) );
  if (p == 0) return TPS_INVALID;
  ply += 2*(p - 1);

  current_colour = (ply & 1) ? C_BLACK : C_WHITE;
  if (ply < 2) current_colour = C_BLACK - current_colour;

  if (prefix) {
    tps++; TPS_ASSERT_MORE;
    if (*tps != ']' ) return TPS_INVALID;

    tps++;
    if (*tps != 0) return TPS_INVALID;
  }

  return TPS_OK;
}

// ===================================================================
// Generate TPS string
// ===================================================================

void
generate_tps(char *out_tps) {
  strcpy(out_tps, "[TPS \"");
  out_tps += 6;

  for (int8_t row = board_size - 1; row >= 0; row--) {
    for (int8_t col = 0; col < board_size; col++) {
      const int8_t l = THE_COORDS(col, row);
      const uint8_t count = COUNT_AT(l);
      if (count) {
	colour_stack_t c = colours[l], s = 1<<(count - 1);
	for (int k=0; k<count; k++, s >>=1, out_tps++) {
	  if (c & s) *out_tps = '2';
	  else *out_tps = '1';
	}
	switch (STONE_AT(l)) {
	  case STONE_CAPSTONE: {
	    *out_tps = 'C'; out_tps++; break;
	  }
	  case STONE_STANDING: {
	    *out_tps = 'S'; out_tps++; break;
	  }
	  default: break;
	}
      } else {
	int8_t skip = 1;
	while (col < board_size && COUNT_AT(l+skip) == 0) {
	  skip++;
	  col++;
	}
	*out_tps = 'x'; out_tps++;
	if (skip > 1) {
	  *out_tps = '0'+skip; out_tps++;
	}
      }
      if (col + 1 < board_size) {
	*out_tps = ','; out_tps++;
      }
    }
    if (row > 0) {
      *out_tps = '/'; out_tps++;
    }
  }

  *out_tps = ' '; out_tps++;
  *out_tps = '1' + (ply & 1); out_tps++;
  *out_tps = ' '; out_tps++;

  out_tps += sprintf(out_tps, "%d", ply/2 + 1);

  strcpy(out_tps, "\"]");
}