My Intuition and Structure page describes how to build decent code from scratch. However, what happens when you are given a awful codebase, and expected to do things with it quickly?
There are 2 obvious options:
I think the best way is to take the best parts of these two ideas by the following process:
Below is some code that allows you to play tick tack toe. I wrote it when I learned programming for the first time. In fact, it is the first code I was proud of. It is doing something kind of amazing, after all: perfectly executing a more or less optimal strategy (at least for the first player). It looks like this:
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
#include <iostream>
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <cstdlib>
#include <ctime>
using namespace std;
COORD coord = {0,0};
void place(int entry, char playchoice)
{int x, y;
if(entry % 3 == 1)
x = 0;
if (entry % 3 == 2)
x = 4;
if (entry % 3 == 0)
x = 8;
if ((entry - 1) / 3 == 0)
y = 11;
else if ((entry - 1) /3 == 1)
y = 13;
if ((entry - 1) / 3 == 2)
y = 15;
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
if (playchoice == 'X')
cout << "X";
else
cout << "O";
coord.X = 0;
coord.Y = 16;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
int main()
{
srand(time(0));
char play;
while (play != 'N'){
int x = 3,y = 10, entry, t = 0, box1 = 0,box2 = 0,box3 = 0,box4 = 0,box5 = 0,box6 = 0,box7 = 0,box8 = 0,box9 = 0;
int num1, num2, num3,num4,num5,num6,num7,num8;
int i = 0, n;
char playchoice;
cout << "Tick Tack Toe\n\n";
cout << "Do you want to be X (first players) or O (second player)?";
cin >> playchoice;
cout << "Enter numbers 1-9(as shown) to play \n";
cout << "1 | 2 | 3\n";
cout << "---------\n";
cout << "4 | 5 | 6\n";
cout << "---------\n";
cout << "7 | 8 | 9\n\n";
if (playchoice == 'X')
cout << "You go first\n";
else
{
cout << "Computer goes first\n";
playchoice = 'O';
}
cout << " | | \n";
cout << "---------\n";
cout << " | | \n";
cout << "---------\n";
cout << " | | \n";
if (playchoice == 'X')
n = 1;
else
n = 0;
while (t < 9)
{
t = t + 1;
i = (n + t) % 2;
if (i == 0){ /* Player move*/
cin >> entry;
if (entry == 1 and box1 == 0)
{
box1 = 1;
place(1, playchoice);
}
else if (entry == 2 and box2 == 0)
{
box2 = 1;
place(2, playchoice);
}
else if (entry == 3 and box3 == 0)
{
box3 = 1;
place(3, playchoice);
}
else if (entry == 4 and box4 == 0)
{
box4 = 1;
place(4, playchoice);
}
else if (entry == 5 and box5 == 0)
{
box5 = 1;
place(5, playchoice);
}
else if (entry == 6 and box6 == 0)
{
box6 = 1;
place(6, playchoice);
}
else if (entry == 7 and box7 == 0)
{
box7 = 1;
place(7, playchoice);
}
else if (entry == 8 and box8 == 0)
{
box8 = 1;
place(8, playchoice);
}
else if (entry == 9 and box9 == 0)
{
box9 = 1;
place(9, playchoice);
}
else
{cout << endl << "Please Enter a number from 1-9 that has not been taken";
t = t - 2;
}
}
else /* Computer move*/
{
if (playchoice == 'X')
playchoice = 'O';
else
playchoice = 'X';
/*first choice*/
if (num1 == 20){
place(1,playchoice);
place(2,playchoice);
place(3,playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num2 == 20){
place(4,playchoice);
place(5,playchoice);
place(6,playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num3 == 20){
place(7,playchoice);
place(8,playchoice);
place(9,playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num4 == 20){
place(1,playchoice);
place(4,playchoice);
place(7,playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num5 == 20){
place(2,playchoice);
place(5,playchoice);
place(8,playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num6 == 20){
place(3,playchoice);
place(6,playchoice);
place(9,playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num7 == 20){
place(1,playchoice);
place(5,playchoice);
place(9,playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num8 == 20){
place(3,playchoice);
place(5,playchoice);
place(7,playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (box5 == 0){
place(5,playchoice);
box5 = 10;
}
/*second choice*/else if ((num1 == 2 or num4 == 2 or num7 == 2) and box1 == 0){
place(1,playchoice);
box1 = 10;
}
else if ((num1 == 2 or num6 == 2 or num8 == 2) and box3 == 0){
place(3,playchoice);
box3 = 10;
}
/*box 5 is taken care of a the beginning of this code*/
else if ((num3== 2 or num4 == 2 or num8 == 2) and box7 == 0){
place(7,playchoice);
box7 = 10;
}
else if ((num3 == 2 or num6 == 2 or num7 == 2) and box9 == 0){
place(9,playchoice);
box9 = 10;
}
else if ((num1 == 2 or num5 == 2) and box2 == 0){
place(2,playchoice);
box2 = 10;
}
else if ((num2 == 2 or num4 == 2) and box4 == 0){
place(4,playchoice);
box4 = 10;
}
else if ((num2 == 2 or num6 == 2) and box6 == 0){
place(6,playchoice);
box6 = 10;
}
else if ((num3 == 2 or num5 == 2) and box8 == 0){
place(8,playchoice);
box8 = 10;
}
/*third choice*/else if ((num1 == 10 or num4 == 10 or num7 == 10) and box1 == 0){
place(1,playchoice);
box1 = 10;
}
else if ((num1 == 10 or num6 == 10 or num8 == 10) and box3 == 0){
place(3,playchoice);
box3 = 10;
}
/*box 5 is taken care of a the beginning of this code*/
else if ((num3== 10 or num4 == 10 or num8 == 10) and box7 == 0){
place(7,playchoice);
box7 = 10;
}
else if ((num3 == 10 or num6 == 10 or num7 == 10) and box9 == 0){
place(9,playchoice);
box9 = 10;
}
else if ((num1 == 10 or num5 == 10) and box2 == 0){
place(2,playchoice);
box2 = 10;
}
else if ((num2 == 10 or num4 == 10) and box4 == 0){
place(4,playchoice);
box4 = 10;
}
else if ((num2 == 10 or num6 == 10) and box6 == 0){
place(6,playchoice);
box6 = 10;
}
else if ((num3 == 10 or num5 == 10) and box8 == 0){
place(8,playchoice);
box8 = 10;
}
/*fourth choice*/else if (box1 == 0)
{
box1 = 10;
place(1,playchoice);
}
else if (box2 == 0)
{
box2 = 10;
place(2,playchoice);
}
else if (box3 == 0)
{
box3 = 10;
place(3,playchoice);
}
else if (box4 == 0)
{
box4 = 10;
place(4,playchoice);
}
else if (box5 == 0)
{
box5 = 10;
place(5,playchoice);
}
else if (box6 == 0)
{
box6 = 10;
place(6,playchoice);
}
else if (box7 == 0)
{
box7 = 10;
place(7,playchoice);
}
else if (box8 == 0)
{
box8 = 10;
place(8,playchoice);
}
else
{
box9 = 10;
place(9,playchoice);
}
if (playchoice == 'O')
playchoice = 'X';
else
playchoice = 'O';
}
num1 = box1 + box2 + box3;
num2 = box4 + box5 + box6;
num3 = box7 + box8 + box9;
num4 = box1 + box4 + box7;
num5 = box2 + box5 + box8;
num6 = box3 + box6 + box9;
num7 = box1 + box5 + box9;
num8 = box3 + box5 + box7;
if (num1 == 30 or num2 == 30 or num3 == 30 or num4 == 30 or num5 == 30 or num6 == 30 or num7 == 30 or num8 == 30)
{
cout << endl << endl;
cout << "COMPUTER WINS!\n";
break;
}
if (num1== 3 or num2 == 3 or num3 == 3 or num4 == 3 or num5 == 3 or num6 == 3 or num7 == 3 or num8 == 3)
{
cout << endl << endl;
cout << "PLAYER WINS!";
break;
}
cout << num1 << " " << num2 << " "<< num3 << " "<< num4 << " "<< num5 << " "<< num6 << " "<< num7 << " " << num8;
}
if (t == 9)
{
cout << endl << endl;
cout << "Draw.\n";
}
if (num1 == 30 or num2 == 30 or num3 == 30 or num4 == 30 or num5 == 30 or num6 == 30 or num7 == 30 or num8 == 30)
{
cout << endl << endl;
cout << "COMPUTER WINS!\n";
}
if (num1== 3 or num2 == 3 or num3 == 3 or num4 == 3 or num5 == 3 or num6 == 3 or num7 == 3 or num8 == 3)
{
cout << endl << endl;
cout << "PLAYER WINS!\n";
}
cout << "\n Do you want to play again (Y/N)";
cin >> play;
system("cls");
}
return 0;
}
code also on a github gist.
Wow, this is super ugly. And there is an easy fix for this: automatic code formatters. The standard code formatter for c and c++ is called clang-format. If you don’t feel like installing it, then there is a website which allows you to use it.
Here is the formatted version. I am using the WebKit formatting, because it is closest to my own preferences.
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
#include <iostream>
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <cstdlib>
#include <ctime>
using namespace std;
COORD coord = { 0, 0 };
void place(int entry, char playchoice)
{
int x, y;
if (entry % 3 == 1)
x = 0;
if (entry % 3 == 2)
x = 4;
if (entry % 3 == 0)
x = 8;
if ((entry - 1) / 3 == 0)
y = 11;
else if ((entry - 1) / 3 == 1)
y = 13;
if ((entry - 1) / 3 == 2)
y = 15;
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
if (playchoice == 'X')
cout << "X";
else
cout << "O";
coord.X = 0;
coord.Y = 16;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
int main()
{
srand(time(0));
char play;
while (play != 'N') {
int x = 3, y = 10, entry, t = 0, box1 = 0, box2 = 0, box3 = 0, box4 = 0, box5 = 0, box6 = 0, box7 = 0, box8 = 0, box9 = 0;
int num1, num2, num3, num4, num5, num6, num7, num8;
int i = 0, n;
char playchoice;
cout << "Tick Tack Toe\n\n";
cout << "Do you want to be X (first players) or O (second player)?";
cin >> playchoice;
cout << "Enter numbers 1-9(as shown) to play \n";
cout << "1 | 2 | 3\n";
cout << "---------\n";
cout << "4 | 5 | 6\n";
cout << "---------\n";
cout << "7 | 8 | 9\n\n";
if (playchoice == 'X')
cout << "You go first\n";
else {
cout << "Computer goes first\n";
playchoice = 'O';
}
cout << " | | \n";
cout << "---------\n";
cout << " | | \n";
cout << "---------\n";
cout << " | | \n";
if (playchoice == 'X')
n = 1;
else
n = 0;
while (t < 9) {
t = t + 1;
i = (n + t) % 2;
if (i == 0) { /* Player move*/
cin >> entry;
if (entry == 1 and box1 == 0) {
box1 = 1;
place(1, playchoice);
}
else if (entry == 2 and box2 == 0) {
box2 = 1;
place(2, playchoice);
}
else if (entry == 3 and box3 == 0) {
box3 = 1;
place(3, playchoice);
}
else if (entry == 4 and box4 == 0) {
box4 = 1;
place(4, playchoice);
}
else if (entry == 5 and box5 == 0) {
box5 = 1;
place(5, playchoice);
}
else if (entry == 6 and box6 == 0) {
box6 = 1;
place(6, playchoice);
}
else if (entry == 7 and box7 == 0) {
box7 = 1;
place(7, playchoice);
}
else if (entry == 8 and box8 == 0) {
box8 = 1;
place(8, playchoice);
}
else if (entry == 9 and box9 == 0) {
box9 = 1;
place(9, playchoice);
}
else {
cout << endl
<< "Please Enter a number from 1-9 that has not been taken";
t = t - 2;
}
}
else /* Computer move*/
{
if (playchoice == 'X')
playchoice = 'O';
else
playchoice = 'X';
/*first choice*/
if (num1 == 20) {
place(1, playchoice);
place(2, playchoice);
place(3, playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num2 == 20) {
place(4, playchoice);
place(5, playchoice);
place(6, playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num3 == 20) {
place(7, playchoice);
place(8, playchoice);
place(9, playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num4 == 20) {
place(1, playchoice);
place(4, playchoice);
place(7, playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num5 == 20) {
place(2, playchoice);
place(5, playchoice);
place(8, playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num6 == 20) {
place(3, playchoice);
place(6, playchoice);
place(9, playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num7 == 20) {
place(1, playchoice);
place(5, playchoice);
place(9, playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num8 == 20) {
place(3, playchoice);
place(5, playchoice);
place(7, playchoice);
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (box5 == 0) {
place(5, playchoice);
box5 = 10;
}
/*second choice*/ else if ((num1 == 2 or num4 == 2 or num7 == 2) and box1 == 0) {
place(1, playchoice);
box1 = 10;
}
else if ((num1 == 2 or num6 == 2 or num8 == 2) and box3 == 0) {
place(3, playchoice);
box3 = 10;
}
/*box 5 is taken care of a the beginning of this code*/
else if ((num3 == 2 or num4 == 2 or num8 == 2) and box7 == 0) {
place(7, playchoice);
box7 = 10;
}
else if ((num3 == 2 or num6 == 2 or num7 == 2) and box9 == 0) {
place(9, playchoice);
box9 = 10;
}
else if ((num1 == 2 or num5 == 2) and box2 == 0) {
place(2, playchoice);
box2 = 10;
}
else if ((num2 == 2 or num4 == 2) and box4 == 0) {
place(4, playchoice);
box4 = 10;
}
else if ((num2 == 2 or num6 == 2) and box6 == 0) {
place(6, playchoice);
box6 = 10;
}
else if ((num3 == 2 or num5 == 2) and box8 == 0) {
place(8, playchoice);
box8 = 10;
}
/*third choice*/ else if ((num1 == 10 or num4 == 10 or num7 == 10) and box1 == 0) {
place(1, playchoice);
box1 = 10;
}
else if ((num1 == 10 or num6 == 10 or num8 == 10) and box3 == 0) {
place(3, playchoice);
box3 = 10;
}
/*box 5 is taken care of a the beginning of this code*/
else if ((num3 == 10 or num4 == 10 or num8 == 10) and box7 == 0) {
place(7, playchoice);
box7 = 10;
}
else if ((num3 == 10 or num6 == 10 or num7 == 10) and box9 == 0) {
place(9, playchoice);
box9 = 10;
}
else if ((num1 == 10 or num5 == 10) and box2 == 0) {
place(2, playchoice);
box2 = 10;
}
else if ((num2 == 10 or num4 == 10) and box4 == 0) {
place(4, playchoice);
box4 = 10;
}
else if ((num2 == 10 or num6 == 10) and box6 == 0) {
place(6, playchoice);
box6 = 10;
}
else if ((num3 == 10 or num5 == 10) and box8 == 0) {
place(8, playchoice);
box8 = 10;
}
/*fourth choice*/ else if (box1 == 0) {
box1 = 10;
place(1, playchoice);
}
else if (box2 == 0) {
box2 = 10;
place(2, playchoice);
}
else if (box3 == 0) {
box3 = 10;
place(3, playchoice);
}
else if (box4 == 0) {
box4 = 10;
place(4, playchoice);
}
else if (box5 == 0) {
box5 = 10;
place(5, playchoice);
}
else if (box6 == 0) {
box6 = 10;
place(6, playchoice);
}
else if (box7 == 0) {
box7 = 10;
place(7, playchoice);
}
else if (box8 == 0) {
box8 = 10;
place(8, playchoice);
}
else {
box9 = 10;
place(9, playchoice);
}
if (playchoice == 'O')
playchoice = 'X';
else
playchoice = 'O';
}
num1 = box1 + box2 + box3;
num2 = box4 + box5 + box6;
num3 = box7 + box8 + box9;
num4 = box1 + box4 + box7;
num5 = box2 + box5 + box8;
num6 = box3 + box6 + box9;
num7 = box1 + box5 + box9;
num8 = box3 + box5 + box7;
if (num1 == 30 or num2 == 30 or num3 == 30 or num4 == 30 or num5 == 30 or num6 == 30 or num7 == 30 or num8 == 30) {
cout << endl
<< endl;
cout << "COMPUTER WINS!\n";
break;
}
if (num1 == 3 or num2 == 3 or num3 == 3 or num4 == 3 or num5 == 3 or num6 == 3 or num7 == 3 or num8 == 3) {
cout << endl
<< endl;
cout << "PLAYER WINS!";
break;
}
cout << num1 << " " << num2 << " " << num3 << " " << num4 << " " << num5 << " " << num6 << " " << num7 << " " << num8;
}
if (t == 9) {
cout << endl
<< endl;
cout << "Draw.\n";
}
if (num1 == 30 or num2 == 30 or num3 == 30 or num4 == 30 or num5 == 30 or num6 == 30 or num7 == 30 or num8 == 30) {
cout << endl
<< endl;
cout << "COMPUTER WINS!\n";
}
if (num1 == 3 or num2 == 3 or num3 == 3 or num4 == 3 or num5 == 3 or num6 == 3 or num7 == 3 or num8 == 3) {
cout << endl
<< endl;
cout << "PLAYER WINS!\n";
}
cout << "\n Do you want to play again (Y/N)";
cin >> play;
system("cls");
}
return 0;
}
link to code. You might want to open this up in another tab, as I’ll be referencing line numbers, and you will want to look at it quite a bit.
Or raw code if you want to copy it into an editor.
Clearly, there are a bunch of problems with this code. But hopefully it looks complicated enough that you don’t want to try to fix it all at once, which is why it makes a good example. I will walk you through the following steps of fixing this code:
The most serious problem here is the Windows specific code. I want my Mac and Linux friends to be able to use this too!
A quick scan through the code shows that there is only a few bits which uses the window.h API: the SetConsoleCursorPosition
call on lines 26 and 36. There is also a system dependent system("cls")
call on line 366. Lets look at the SetConsoleCursorPosition
call first, as it is more complicated:
COORD coord = {0,0};
void place(int entry, char playchoice)
{
int x, y;
if (entry % 3 == 1)
x = 0;
if (entry % 3 == 2)
x = 4;
if (entry % 3 == 0)
x = 8;
if ((entry - 1) / 3 == 0)
y = 11;
else if ((entry - 1) / 3 == 1)
y = 13;
if ((entry - 1) / 3 == 2)
y = 15;
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
if (playchoice == 'X')
cout << "X";
else
cout << "O";
coord.X = 0;
coord.Y = 16;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
How on earth can we turn this mess into something cross-platform? What is this mess? What is it accomplishing in this program?
Remember, the goal is to fix this quickly, and the easiest thing to do here is replace the SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord)
call with a similar cross platform call.
After a quick search, the best I could find is the solution to this question. Copied here:
#ifdef _WIN32
#include <windows.h>
void gotoxy( int x, int y )
{
COORD p = { x, y };
SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ), p );
}
#else
//Make sure to link properly with the Unix version. You'll need to link with one of
//-lcurses
//-lterminfo
//-lncurses
//(whichever is appropriate for your system).
#include <unistd.h>
#include <term.h>
void gotoxy( int x, int y )
{
int err;
if (!cur_term)
if (setupterm( NULL, STDOUT_FILENO, &err ) == ERR)
return;
putp( tparm( tigetstr( "cup" ), y, x, 0, 0, 0, 0, 0, 0, 0 ) );
}
#endif
Wow, this is ugly. And not only is it ugly, we still haven’t finished making it truly portable, because it needs to link with different libraries depending on the Unix system. And if we are using some weird operating system, like a really old DOS OS, then even this code won’t work.
So, if that is the best we can do to replace the specific API call with a cross-platform one, then I consider this a really poor solution. Lets try to find something better.
What can we do if we cannot make a call like that? Since the exact mechanics of the code cannot be matched, lets try to see what sort of functionality it creates, and see if we can implement that in a different way.
Here is the output of the program in a normal play through:
Tick Tack Toe
Do you want to be X (first players) or O (second player)?X
Enter numbers 1-9(as shown) to play
1 | 2 | 3
---------
4 | 5 | 6
---------
7 | 8 | 9
You go first
O | X | O
---------
| X |
---------
| O | X
21 1 11 10 12 11 12 11
There are some odd things here, like the string of numbers below the second grid, which I have no idea why it is there, or even where in the code prints it out. But it is better than nothing, so for now, lets not worry about that at all, instead, lets focus on reproducing this general interface without the windows specific API.
The sane thing to do is to print out this whole thing using the game data. Preferably something that literally looks like this print_game(game_data)
. So that is the structure we want to move to.
On the other hand, lets look try to decipher the structure of what it is actually doing. This is the hardest part, so I’ll take it slowly this time.
If you look back at the bits of code where the place
function is being called, note that a box#
variable is usually updated when place is called. For example, when user input is used on line 109-113:
else if (entry == 5 and box5 == 0) {
box5 = 1;
place(5, playchoice);
}
Now we need to make a few leaps of inference to figure out what is going on here. This is quite difficult, and does not come naturally (yes, for me too). What is important is too explore the code freely and to not be too afraid of being wrong. Just try it and see if it works!
Now I will just explore the code, and write down what I see.
Looking at the box#
variables, it is being used to calculate the num#
variables on line 326, which is used to calculate the strategy and who wins. Making a little leap of inference, the box#
variables seem to be a significant portion of the game data, i.e., they are a part of the conceptual game_data
structure.
Looking closer at the place
function, the variables are talking about setting cursor position, and then it prints Xs or Os based on playchoice
. Looking at the output, this tips me off that place
is simply editing the screen to match the game data.
This is an abstraction of the pattern I keep on seeing:
user-turn and computer-turn:
input = make_choice(game_data)
game_data = update_data(game_data, input)
place(input)
I will call this a “dual update” structure, as there are two different locations of data, both of which are always updated when the state changes. Ideally, we will change this to a ordinary update structure, which has single source of truth, and fits nicely with our goal of creating a “pure output” structure.
Now, how do we achieve our goals of:
This quadruple standard is a little bit daunting, but the bottom 3 goals do not need to be met perfectly, it is perfectly fine to take a little bit too much time, or not really move to a great structure.
To meet the “don’t rewrite code” requirement, lets go ahead and give ourselves a more specific requirement: don’t change interface of the place
function. It is being used in way too many places, any change to that will result in huge code changes.
To meet the goal of moving to a better structure, lets think about how our draw_board(game_data)
function will work. It will probably take in the #box
variables, which we identified as part of the game_data
, and then print out the board, right?
Lets look at this conceptually for a bit. We will end up with something like this:
user-turn and computer-turn:
input = make_choice(game_data)
game_data = update_data(game_data, input)
place(input)
draw_board(game_data)
But changing the board is what the place
function is doing, right? So why do we need the place
function if we have draw_board
? The answer: we probably don’t. Lets try running with that idea for awhile.
user-turn and computer-turn:
input = make_choice(data)
update_data(input)
draw_board(data)
Looking at this concept without place, we end up with a structure that doesn’t really look all that diffrent from the ideal. So now , we can focus on implementing this.
Now, we can easily write draw_board like this:
//sqr_rep is a function that takes in the number frm the box, and outputs 'X', '_' or ' '
void draw_board(int box1, int box2, int box3, int box4, int box5, int box6, int box7, int box8, int box9){
cout << sqr_rep(box1) << "|" << sqr_rep(box2) << "|" << sqr_rep(box3) << endl;
cout << sqr_rep(box4) << "|" << sqr_rep(box5) << "|" << sqr_rep(box6) << endl;
cout << sqr_rep(box7) << "|" << sqr_rep(box8) << "|" << sqr_rep(box9) << endl;
}
This is great, but we run into a little bit of a problem when trying to write sqr_rep
. Note that the following does not quite work.
char sqr_rep(int box_num){
switch(box_num){
case 0: return ' ';
case 1: return 'X';
case 10: return 'O';
default: assert(false && "box_num not a valid quantity");
}
}
It sure seems to work. We know that 10 means computer, 1 means person, and 0 mean empty. But remember that we assigned X or O to the player or computer based on user input at the beginning of the game (stored in the playerchoice
variable). This allows X to go first, whether it is the computer or the player, so that our game matches with common standards in tick tack toe.
Fixing this requires sqr_rep
to know the playerchoice
variable. Lets try rewriting sqr_rep
to take this into account.
char sqr_rep(int box_num, char playerchoice){
switch(box_num){
case 0: return ' ';
case 1: return 'X' == playerchoice ? 'X' : 'O';
case 10: return 'X' == playerchoice ? 'O' : 'X';
default: assert(false && "box_num not a valid quantity");
}
}
and now we have to go back and rewrite draw_board
:
void draw_board(char playerchoice, int box1, int box2, int box3, int box4, int box5, int box6, int box7, int box8, int box9){
char pc = playerchoice;
cout << sqr_rep(box1,pc) << "|" << sqr_rep(box2,pc) << "|" << sqr_rep(box3,pc) << endl;
cout << sqr_rep(box4,pc) << "|" << sqr_rep(box5,pc) << "|" << sqr_rep(box6,pc) << endl;
cout << sqr_rep(box7,pc) << "|" << sqr_rep(box8,pc) << "|" << sqr_rep(box9,pc) << endl;
}
Now things are falling into place. We just need to fit everything in together (the hard part of implementation). The easiest way to do this is to simply try things until they work. This is not trivial, and can take a lot of time. If anything, it should take longer than the first part of identifying structure. But it is easier to brute force, and so it is basically the same process as debugging, so I’ll just give you the finished code.
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
#include <iostream>
#include <stdio.h>
#include <conio.h>
#include <cstdlib>
#include <ctime>
#include <cassert>
using namespace std;
char sqr_rep(char playerchoice, int box_num)
{
switch (box_num) {
case 0:
return ' ';
case 1:
return 'X' == playerchoice ? 'X' : 'O';
case 10:
return 'X' == playerchoice ? 'O' : 'X';
default:
assert(false && "box_num not a valid quantity");
}
}
void draw_board(char playchoice,
int box1,
int box2,
int box3,
int box4,
int box5,
int box6,
int box7,
int box8,
int box9)
{
cout << "Enter numbers 1-9(as shown) to play \n";
cout << "1 | 2 | 3\n";
cout << "---------\n";
cout << "4 | 5 | 6\n";
cout << "---------\n";
cout << "7 | 8 | 9\n\n";
if (playchoice == 'X')
cout << "You go first\n";
else
cout << "Computer goes first\n";
char pc = playchoice;
cout << sqr_rep(pc, box1) << "|" << sqr_rep(pc, box2) << "|"
<< sqr_rep(pc, box3) << endl;
cout << sqr_rep(pc, box4) << "|" << sqr_rep(pc, box5) << "|"
<< sqr_rep(pc, box6) << endl;
cout << sqr_rep(pc, box7) << "|" << sqr_rep(pc, box8) << "|"
<< sqr_rep(pc, box9) << endl;
}
int main()
{
srand(time(0));
char play;
while (play != 'N') {
int x = 3, y = 10, entry, t = 0, box1 = 0, box2 = 0, box3 = 0, box4 = 0,
box5 = 0, box6 = 0, box7 = 0, box8 = 0, box9 = 0;
int num1, num2, num3, num4, num5, num6, num7, num8;
int i = 0, n;
char playchoice;
cout << "Tick Tack Toe\n\n";
cout << "Do you want to be X (first players) or O (second player)?";
cin >> playchoice;
if (playchoice == 'X') {
cout << "You go first\n";
}
else {
cout << "Computer goes first\n";
playchoice = 'O';
}
if (playchoice == 'X')
n = 1;
else
n = 0;
while (t < 9) {
t = t + 1;
i = (n + t) % 2;
draw_board(playchoice, box1, box2, box3, box4, box5, box6, box7, box8,
box9);
if (i == 0) { /* Player move*/
cin >> entry;
if (entry == 1 and box1 == 0) {
box1 = 1;
}
else if (entry == 2 and box2 == 0) {
box2 = 1;
}
else if (entry == 3 and box3 == 0) {
box3 = 1;
}
else if (entry == 4 and box4 == 0) {
box4 = 1;
}
else if (entry == 5 and box5 == 0) {
box5 = 1;
}
else if (entry == 6 and box6 == 0) {
box6 = 1;
}
else if (entry == 7 and box7 == 0) {
box7 = 1;
}
else if (entry == 8 and box8 == 0) {
box8 = 1;
}
else if (entry == 9 and box9 == 0) {
box9 = 1;
}
else {
cout << endl
<< "Please Enter a number from 1-9 that has not been taken";
t = t - 2;
}
}
else /* Computer move*/
{
if (playchoice == 'X')
playchoice = 'O';
else
playchoice = 'X';
/*first choice*/
if (num1 == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num2 == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num3 == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num4 == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num5 == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num6 == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num7 == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num8 == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (box5 == 0) {
box5 = 10;
}
/*second choice*/ else if ((num1 == 2 or num4 == 2 or num7 == 2) and box1 == 0) {
box1 = 10;
}
else if ((num1 == 2 or num6 == 2 or num8 == 2) and box3 == 0) {
box3 = 10;
}
/*box 5 is taken care of a the beginning of this code*/
else if ((num3 == 2 or num4 == 2 or num8 == 2) and box7 == 0) {
box7 = 10;
}
else if ((num3 == 2 or num6 == 2 or num7 == 2) and box9 == 0) {
box9 = 10;
}
else if ((num1 == 2 or num5 == 2) and box2 == 0) {
box2 = 10;
}
else if ((num2 == 2 or num4 == 2) and box4 == 0) {
box4 = 10;
}
else if ((num2 == 2 or num6 == 2) and box6 == 0) {
box6 = 10;
}
else if ((num3 == 2 or num5 == 2) and box8 == 0) {
box8 = 10;
}
/*third choice*/ else if ((num1 == 10 or num4 == 10 or num7 == 10) and box1 == 0) {
box1 = 10;
}
else if ((num1 == 10 or num6 == 10 or num8 == 10) and box3 == 0) {
box3 = 10;
}
/*box 5 is taken care of a the beginning of this code*/
else if ((num3 == 10 or num4 == 10 or num8 == 10) and box7 == 0) {
box7 = 10;
}
else if ((num3 == 10 or num6 == 10 or num7 == 10) and box9 == 0) {
box9 = 10;
}
else if ((num1 == 10 or num5 == 10) and box2 == 0) {
box2 = 10;
}
else if ((num2 == 10 or num4 == 10) and box4 == 0) {
box4 = 10;
}
else if ((num2 == 10 or num6 == 10) and box6 == 0) {
box6 = 10;
}
else if ((num3 == 10 or num5 == 10) and box8 == 0) {
box8 = 10;
}
/*fourth choice*/ else if (box1 == 0) {
box1 = 10;
}
else if (box2 == 0) {
box2 = 10;
}
else if (box3 == 0) {
box3 = 10;
}
else if (box4 == 0) {
box4 = 10;
}
else if (box5 == 0) {
box5 = 10;
}
else if (box6 == 0) {
box6 = 10;
}
else if (box7 == 0) {
box7 = 10;
}
else if (box8 == 0) {
box8 = 10;
}
else {
box9 = 10;
}
if (playchoice == 'O')
playchoice = 'X';
else
playchoice = 'O';
}
num1 = box1 + box2 + box3;
num2 = box4 + box5 + box6;
num3 = box7 + box8 + box9;
num4 = box1 + box4 + box7;
num5 = box2 + box5 + box8;
num6 = box3 + box6 + box9;
num7 = box1 + box5 + box9;
num8 = box3 + box5 + box7;
if (num1 == 30 or num2 == 30 or num3 == 30 or num4 == 30 or num5 == 30 or num6 == 30 or num7 == 30 or num8 == 30) {
cout << endl
<< endl;
cout << "COMPUTER WINS!\n";
break;
}
if (num1 == 3 or num2 == 3 or num3 == 3 or num4 == 3 or num5 == 3 or num6 == 3 or num7 == 3 or num8 == 3) {
cout << endl
<< endl;
cout << "PLAYER WINS!";
break;
}
cout << num1 << " " << num2 << " " << num3 << " " << num4 << " " << num5
<< " " << num6 << " " << num7 << " " << num8;
}
if (t == 9) {
cout << endl
<< endl;
cout << "Draw.\n";
}
if (num1 == 30 or num2 == 30 or num3 == 30 or num4 == 30 or num5 == 30 or num6 == 30 or num7 == 30 or num8 == 30) {
cout << endl
<< endl;
cout << "COMPUTER WINS!\n";
}
if (num1 == 3 or num2 == 3 or num3 == 3 or num4 == 3 or num5 == 3 or num6 == 3 or num7 == 3 or num8 == 3) {
cout << endl
<< endl;
cout << "PLAYER WINS!\n";
}
cout << "\n Do you want to play again (Y/N)";
cin >> play;
//system("cls");
}
return 0;
}
link to code and link to raw code
Hopefully, when going through the previous section, you might have been frustrated at how hard the final step of fitting things together is. A traditional view of refactoring is simply changing the code to make future changes easier. This can make an incredible difference when making changes like the one above, the difference between pulling your hair out for hours vs. finishing the change in minutes. Cumulatively, this can be the difference between a successful project and an unsuccessful one.
I ignored this step in the first part partially to emphasize how important this is. For the next step, which will be testing the code, I will separate out the cleanup step from the testing step somewhat. This is a way of software development, but it is not always obvious when you want to do this cleanup. Many people do it too often, or too infrequently.
So I will try to also introduce you to some of the intuition about when to refractor things.
As I mentioned in my post about structure, data structures are the most important thing to get right. Hopefully you can see how incredibly shitty the current data structures are, and start to see just how seriously they foil our attempts at making clear code. In the above argument, I kept on having to refer to box#
variables instead of a single variable. There were all these cases made necessary by the fact that I had not method to programmatically access different box numbers (e.g., the 9 different cases for user input). Pretty much any more changes to the code (most critically adding testing) will be made far easier by cleaning up these data structures, so lets just get it over with now.
To see why, think about how you might add a test
First, lets identify what sort of data structures there should be.
We don’t want to overstrain our brains, or break the code too badly, so lets just do the obvious things first.
It should be obvious that box#1-9
should just be an array, and same with num#1-8
So lets just do that. This is a very easy change. Just change the two declarations of box#
in the declaration of draw_board
, and
int x = 3, y = 10, entry, t = 0, box1 = 0, box2 = 0, box3 = 0, box4 = 0,
box5 = 0, box6 = 0, box7 = 0, box8 = 0, box9 = 0;
int num1, num2, num3, num4, num5, num6, num7, num8;
to
int x = 3, y = 10, entry, t = 0;//don't touch bad code you aren't dealing with at the moment
int box[9] = {0}; //this initializes every entry in box to 0
int num[8] = {-1}; //-1 is nonsense in the context of num, so it may help with debugging uninitialized variables
Then, you can fix the code just by search and replace on the references to these variables:
find: box1
replace with: box[0]
and then do this with all the numbers 1-9, and also for num
for 1-8. If you are a vim or emacs power-user, or obsessed with scripts, then you may be able to automate this process for all the numbers 1-9, but I just did it by hand in under 2 minutes.
Once you do this, you get the following code: link to raw fixed code
The most productive way to spend your time is deleting code. Because writing code may make your work easier, but it makes every one else’s work harder. Deleting code is the only for sure way to make other’s life easier. –anonymous
When a software engineer said this to me, I realized that it is a great way of reasoning about when to simplify/delete code: If it makes future work much simpler, then go ahead. If not, then don’t bother. The most important part of future work is readability. The second most is edibility.
So lets think through some changes and reason about how easy they are to read and edit. Note that these evaluations differ from person to person, especially between people of different experience levels, so please go through and try to make your own evaluations as well.
Here is a really bad section. The repetition is really obvious, and lots of hardcoded numbers which are hard to read and edit (line 79).
if (entry == 1 and box[0] == 0) {
box[0] = 1;
}
else if (entry == 2 and box[1] == 0) {
box[1] = 1;
}
else if (entry == 3 and box[2] == 0) {
box[2] = 1;
}
else if (entry == 4 and box[3] == 0) {
box[3] = 1;
}
else if (entry == 5 and box[4] == 0) {
box[4] = 1;
}
else if (entry == 6 and box[5] == 0) {
box[5] = 1;
}
else if (entry == 7 and box[6] == 0) {
box[6] = 1;
}
else if (entry == 8 and box[7] == 0) {
box[7] = 1;
}
else if (entry == 9 and box[8] == 0) {
box[8] = 1;
}
else {
cout << endl
<< "Please Enter a number from 1-9 that has not been taken";
t = t - 2;
}
Now, you can see that every number here depends on entry
, so you can change this to
int box_num = entry - 1;
if (box[box_num] == 0) {
box[box_num] = 1;
}
else {
cout << endl
<< "Please Enter a number from 1-9 that has not been taken";
t = t - 2;
}
Now, another part of the code with apparently obvious repetition is the actual AI if-else statement going from line 120-231.
/*first choice*/
if (num[0] == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num[1] == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num[2] == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num[3] == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num[4] == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num[5] == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num[6] == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (num[7] == 20) {
cout << "\n\nComputer Wins!\n\n";
break;
}
else if (box[4] == 0) {
box[4] = 10;
}
/*second choice*/
else if ((num[0] == 2 or num[3] == 2 or num[6] == 2) and box[0] == 0) {
box[0] = 10;
}
else if ((num[0] == 2 or num[5] == 2 or num[7] == 2) and box[2] == 0) {
box[2] = 10;
}
/*box 5 is taken care of a the beginning of this code*/
else if ((num[2] == 2 or num[3] == 2 or num[7] == 2) and box[6] == 0) {
box[6] = 10;
}
else if ((num[2] == 2 or num[5] == 2 or num[6] == 2) and box[8] == 0) {
box[8] = 10;
}
else if ((num[0] == 2 or num[4] == 2) and box[1] == 0) {
box[1] = 10;
}
else if ((num[1] == 2 or num[3] == 2) and box[3] == 0) {
box[3] = 10;
}
else if ((num[1] == 2 or num[5] == 2) and box[5] == 0) {
box[5] = 10;
}
else if ((num[2] == 2 or num[4] == 2) and box[7] == 0) {
box[7] = 10;
}
/*third choice*/
else if ((num[0] == 10 or num[3] == 10 or num[6] == 10) and box[0] == 0) {
box[0] = 10;
}
else if ((num[0] == 10 or num[5] == 10 or num[7] == 10) and box[2] == 0) {
box[2] = 10;
}
/*box 5 is taken care of a the beginning of this code*/
else if ((num[2] == 10 or num[3] == 10 or num[7] == 10) and box[6] == 0) {
box[6] = 10;
}
else if ((num[2] == 10 or num[5] == 10 or num[6] == 10) and box[8] == 0) {
box[8] = 10;
}
else if ((num[0] == 10 or num[4] == 10) and box[1] == 0) {
box[1] = 10;
}
else if ((num[1] == 10 or num[3] == 10) and box[3] == 0) {
box[3] = 10;
}
else if ((num[1] == 10 or num[5] == 10) and box[5] == 0) {
box[5] = 10;
}
else if ((num[2] == 10 or num[4] == 10) and box[7] == 0) {
box[7] = 10;
}
/*fourth choice*/
else if (box[0] == 0) {
box[0] = 10;
}
else if (box[1] == 0) {
box[1] = 10;
}
else if (box[2] == 0) {
box[2] = 10;
}
else if (box[3] == 0) {
box[3] = 10;
}
else if (box[4] == 0) {
box[4] = 10;
}
else if (box[5] == 0) {
box[5] = 10;
}
else if (box[6] == 0) {
box[6] = 10;
}
else if (box[7] == 0) {
box[7] = 10;
}
else {
box[8] = 10;
}
Unfortunately, you start to work with this, and you realize that there is quite a bit of non-trivial logic here. So it will take a lot of work. So instead of changing it right now, lets try to encapsulate it in a functional manner.
So lets look in the codebase for things which given a little effort, will make our life easier.
Here is a relatively simple problem which should demonstrate this. The problem is that I know how to check if someone won in tick tack toe. Simple enough, right? You check the rows, columns, and diagonals, and see if a player occupies all the spots there. But this problem, and other similar to it haunted me for years afterwards, making my code error prone, and difficult to debug.
num1 = box1 + box2 + box3;
num2 = box4 + box5 + box6;
num3 = box7 + box8 + box9;
num4 = box1 + box4 + box7;
num5 = box2 + box5 + box8;
num6 = box3 + box6 + box9;
num7 = box1 + box5 + box9;
num8 = box3 + box5 + box7;
if (num1 == 30 or num2 == 30 or num3 == 30 or num4 == 30 or num5 == 30 or num6 == 30 or num7 == 30 or num8 == 30)
{
cout << "COMPUTER WINS!\n";
break;
}
if (num1 == 3 or num2 == 3 or num3 == 3 or num4 == 3 or num5 == 3 or num6 == 3 or num7 == 3 or num8 == 3)
{
cout << "PLAYER WINS!";
break;
}
As you can see, I could code a solution with the tools I had at hand. And at the time, that was enough. But now, I realize that it is not very good. It has lots and lots of variables and arbitrary constants, which makes checking/debugging the code hard and slow. It is just about impossible to generalize to larger grids. Understandable for my first code, but lets try to do better.
The standard approach would be to iteratively refractor this, until it looks decent. We would put the numbered variables in an array, then loop over them, in some places, take out functions, etc, to make the code more concise and stuff. This is not a bad approach. But it can actually be harder than building it up again from scratch, and it is easier to make poor choices. So instead, lets try to think of things structurally, picking out what each part of the code really accomplishes, and build this up.
First, basic data structures. What is a tick tack toe board anyways? It is a grid that looks like this, right?
X|O|S
X|O|_
O|X|O
We really only care about the rows, columns, and diagonals. If you trace your finger over the rows, columns and diagonals, you might notice that your finger seems to be moving quite freely over a 2 dimensional discrete space. This reveals that the base data structure needs two qualities, it needs to store discrete values. Luckily, there is already a standard solution to general discrete 2d movement, the 2d array.
board = array[3][3];
Now, how do we represent the Xs, Os, and blanks? Again, think of what we they actually are, and what we need of them. They actually are symbols that represent something about the game. We need them to be distinct from each other, and to not allow for anything other than Xs, Os, and blanks. Here there are several options. Strongly typed enums have these qualities. But perhaps this is overkill, as they also have other guarantees, such as efficient use as a key, which we don’t need. So lets make our own type that meets these conditions.
Ok, so that wraps up the data structure. Now we need to look at the tasks we ask of that data. These can be thought of as queries, or manipulations.
Here is a task. We want the user of the software to be able to choose a coordinate, and then set that to be the players symbol if it is not already chosen. If it is already chosen, then we want to change nothing, and tell the user that they need to choose something else.
Wow, that is a lot of requirements. And it is the thing that experienced coders can just crank out without thinking about it at all. So
Here is a more complicated task, the original one.
Lets take