81349e2548e1972450ce7e3560c08d0b930170ba
[src/app-framework-binder.git] / plugins / samples / tic-tac-toe.c
1 /*
2  * Copyright (C) 2016 "IoT.bzh"
3  * Author José Bollo <jose.bollo@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #define _GNU_SOURCE
19 #include <stdio.h>
20 #include <string.h>
21 #include <json-c/json.h>
22
23 #include <afb/afb-plugin.h>
24
25 /*
26  * the interface to afb-daemon
27  */
28 const struct AFB_interface *afbitf;
29
30 /*
31  * definition of a board
32  */
33 struct board
34 {
35         struct board *next;
36         int refcount;
37         int moves;
38         int history[9];
39         int id;
40         int level;
41         char board[9];
42 };
43
44 /*
45  * list of boards
46  */
47 static struct board *all_boards;
48
49 /*
50  * Search a board
51  */
52 static struct board *search_board(int id)
53 {
54         struct board *board = all_boards;
55         while (board != NULL && board->id != id)
56                 board = board->next;
57         return board;
58 }
59
60 /*
61  * Creates a new board
62  */
63 static struct board *get_new_board()
64 {
65         struct board *board = calloc(1, sizeof *board);
66         memset(board->board, ' ', sizeof board->board);
67         board->refcount = 1;
68         board->level = 1;
69         board->moves = 0;
70         do {
71                 board->id = (rand() >> 2) % 1000;
72         } while(board->id == 0 || search_board(board->id) != NULL);
73         board->next = all_boards;
74         all_boards = board;
75         return board;
76 }
77
78 /*
79  * Release a board
80  */
81 static void release_board(struct board *board)
82 {
83         /* decrease the reference count ... */
84         if (--board->refcount == 0) {
85                 /* ... no more use */
86                 /* unlink from the list of boards */
87                 struct board **prv = &all_boards;
88                 while (*prv != NULL && *prv != board)
89                         prv = &(*prv)->next;
90                 if (*prv != NULL)
91                         *prv = board->next;
92                 /* release the used memory */
93                 free(board);
94         }
95 }
96
97 /*
98  * Checks who wins
99  * Returns zero if there is no winner
100  * Returns the char of the winner if a player won
101  */
102 static char winner(const char b[9])
103 {
104         int i;
105         char c;
106
107         /* check diagonals */
108         c = b[4];
109         if (c != ' ') {
110                 if (b[0] == c && b[8] == c)
111                         return c;
112                 if (b[2] == c && b[6] == c)
113                         return c;
114         }
115
116         /* check lines */
117         for (i = 0 ; i <= 6 ; i += 3) {
118                 c = b[i];
119                 if (c != ' ' && b[i+1] == c && b[i+2] == c)
120                         return c;
121         }
122
123         /* check columns */
124         for (i = 0 ; i <= 2 ; i++) {
125                 c = b[i];
126                 if (c != ' ' && b[i+3] == c && b[i+6] == c)
127                         return c;
128         }
129
130         return 0;
131 }
132
133 /* get the color (X or 0) of the move of index 'move' */
134 static char color(int move)
135 {
136         return (move & 1) == 0 ? 'X' : '0';
137 }
138
139 /* adds the move to the board */
140 static void add_move(struct board *board, int index)
141 {
142         int imove = board->moves++;
143         board->history[imove] = index;
144         board->board[index] = color(imove);
145 }
146
147 /* get a random possible move index from the board described by 'b' */
148 static int get_random_move(char b[9])
149 {
150         int index = rand() % 9;
151         while (b[index] != ' ')
152                 index = (index + 1) % 9;
153         return index;
154 }
155
156 /*
157  * Scores the position described by 'b'
158  * for the player of color 'c' using an analysis of 'depth'.
159  * Returns 1 if player 'c' will win.
160  * Returns -1 if opponent of player 'c' will win.
161  * returns 0 otherwise.
162  */
163 static int score_position(char b[9], char c, int depth)
164 {
165         int i, t, r;
166
167         /* check if winner */
168         if (winner(b) == c)
169                 return 1;
170
171         /* when depth of analysis is reached return unknown case */
172         if (--depth == 0)
173                 return 0;
174
175         /* switch to the opponent */
176         c = (char)('O' + 'X' - c);
177
178         /* inspect opponent moves */
179         r = 1;
180         for (i = 0 ; i < 9 ; i++) {
181                 if (b[i] == ' ') {
182                         b[i] = c;
183                         t = score_position(b, c, depth);
184                         b[i] = ' ';
185                         if (t > 0)
186                                 return -1; /* opponent will win */
187
188                         if (t == 0)
189                                 r = 0; /* something not clear */
190                 }
191         }
192         return r;
193 }
194
195 /* get one move: return the computed index of the move */
196 static int get_move(struct board *board)
197 {
198         int index, depth, t, f;
199         char c;
200         char b[9];
201
202         /* compute the depth */
203         depth = board->level - 1;
204         if (board->moves + depth > 9)
205                 depth = 9 - board->moves;
206
207         /* case of null depth */
208         if (depth == 0)
209                 return get_random_move(board->board);
210
211         /* depth and more */
212         memcpy(b, board->board, 9);
213         c = color(board->moves);
214         f = 0;
215         for (index = 0 ; index < 9 ; index++) {
216                 if (board->board[index] == ' ') {
217                         board->board[index] = c;
218                         t = score_position(board->board, c, depth);
219                         board->board[index] = ' ';
220                         if (t > 0)
221                                 return index;
222                         if (t < 0)
223                                 b[index] = '+';
224                         else
225                                 f = 1;
226                 }
227         }
228         return get_random_move(f ? b : board->board);
229 }
230
231 /*
232  * get the board description
233  */
234 static struct json_object *describe(struct board *board)
235 {
236         int i;
237         char w;
238         struct json_object *resu, *arr;
239
240         resu = json_object_new_object();
241
242         json_object_object_add(resu, "boardid", json_object_new_int(board->id));
243         json_object_object_add(resu, "level", json_object_new_int(board->level));
244
245         arr = json_object_new_array();
246         json_object_object_add(resu, "board", arr);
247         for (i = 0 ; i < 9 ; i++)
248                 json_object_array_add(arr,
249                                 json_object_new_string_len(&board->board[i], 1));
250
251         arr = json_object_new_array();
252         json_object_object_add(resu, "history", arr);
253         for (i = 0 ; i < board->moves ; i++)
254                 json_object_array_add(arr, json_object_new_int(board->history[i]));
255
256         w = winner(board->board);
257         if (w)
258                 json_object_object_add(resu, "winner", json_object_new_string_len(&w, 1));
259         else if (board->moves == 9)
260                 json_object_object_add(resu, "winner", json_object_new_string("none"));
261
262         return resu;
263 }
264
265 /*
266  * signals a change of the board
267  */
268 static void changed(struct board *board, const char *reason)
269 {
270         /* TODO */
271         WARNING(afbitf, "changed is unimplmented (reason was %s)", reason);
272 }
273
274 /*
275  * retrieves the board of the request
276  */
277 static struct board *board_of_req(struct afb_req req)
278 {
279         return afb_req_context(req, (void*)get_new_board, (void*)release_board);
280 }
281
282 /*
283  * start a new game
284  */
285 static void new(struct afb_req req)
286 {
287         struct board *board;
288
289         /* retrieves the context for the session */
290         board = board_of_req(req);
291
292         /* reset the game */
293         memset(board->board, ' ', sizeof board->board);
294         board->moves = 0;
295
296         /* replies */
297         afb_req_success(req, NULL, NULL);
298
299         /* signal change */
300         changed(board, "new");
301 }
302
303 /*
304  * get the board
305  */
306 static void board(struct afb_req req)
307 {
308         afb_req_success(req, describe(board_of_req(req)), NULL);
309 }
310
311 /*
312  * move a piece
313  */
314 static void move(struct afb_req req)
315 {
316         struct board *board;
317         int i;
318         const char *index;
319
320         /* retrieves the context for the session */
321         board = board_of_req(req);
322
323         /* retrieves the parameters of the move */
324         index = afb_req_value(req, "index");
325         i = index == NULL ? -1 : atoi(index);
326
327         /* checks validity of parameters */
328         if (i < 0 || i > 8) {
329                 afb_req_fail(req, "error", "bad request");
330                 return;
331         }
332
333         /* checks validity of the state */
334         if (winner(board->board) != 0) {
335                 afb_req_fail(req, "error", "game terminated");
336                 return;
337         }
338
339         /* checks validity of the move */
340         if (board->board[i] != ' ') {
341                 afb_req_fail(req, "error", "occupied");
342                 return;
343         }
344
345         /* applies the move */
346         add_move(board, i);
347
348         /* replies */
349         afb_req_success(req, NULL, NULL);
350
351         /* signals change */
352         changed(board, "move");
353 }
354
355 /*
356  * set the level
357  */
358 static void level(struct afb_req req)
359 {
360         struct board *board;
361         int l;
362         const char *level;
363
364         /* retrieves the context for the session */
365         board = board_of_req(req);
366
367         /* retrieves the parameters */
368         level = afb_req_value(req, "level");
369         l = level == NULL ? -1 : atoi(level);
370
371         /* check validity of parameters */
372         if (l < 1 || l > 10) {
373                 afb_req_fail(req, "error", "bad request");
374                 return;
375         }
376         board->level = l;
377
378         /* replies */
379         afb_req_success(req, NULL, NULL);
380
381         /* signals change */
382         changed(board, "level");
383 }
384
385 /*
386  * Join a board
387  */
388 static void join(struct afb_req req)
389 {
390         struct board *board;
391         const char *id;
392
393         /* retrieves the context for the session */
394         board = board_of_req(req);
395
396         /* retrieves the parameters */
397         board = board_of_req(req);
398         id = afb_req_value(req, "boardid");
399         if (id == NULL)
400                 goto bad_request;
401
402         /* check validity of parameters */
403         if (strcmp(id, "none")) {
404                 board = get_new_board();
405                 goto success;
406         }
407         board = search_board(atoi(id));
408         if (board == NULL)
409                 goto bad_request;
410
411         board->refcount++;
412 success:
413         afb_req_context_set(req, board, (void*)release_board);
414
415         /* replies */
416         afb_req_success(req, NULL, NULL);
417         return;
418
419 bad_request:
420         afb_req_fail(req, "error", "bad request");
421         return;
422 }
423
424 /*
425  * Undo the last move
426  */
427 static void undo(struct afb_req req)
428 {
429         struct board *board;
430         int i;
431
432         /* retrieves the context for the session */
433         board = board_of_req(req);
434
435         /* checks the state */
436         if (board->moves == 0) {
437                 afb_req_fail(req, "error", "bad request");
438                 return;
439         }
440
441         /* undo the last move */
442         i = board->history[--board->moves];
443         board->board[i] = ' ';
444
445         /* replies */
446         afb_req_success(req, NULL, NULL);
447
448         /* signals change */
449         changed(board, "undo");
450 }
451
452 /*
453  * computer plays
454  */
455 static void play(struct afb_req req)
456 {
457         struct board *board;
458         int index;
459
460         /* retrieves the context for the session */
461         board = board_of_req(req);
462
463         /* checks validity of the state */
464         if (winner(board->board) != 0 || board->moves == 9) {
465                 afb_req_fail(req, "error", "game terminated");
466                 return;
467         }
468
469         /* gets the move and plays it */
470         index = get_move(board);
471         add_move(board, index);
472
473         /* replies */
474         afb_req_success(req, describe(board), NULL);
475
476         /* signals change */
477         changed(board, "play");
478 }
479
480 /*
481  * array of the verbs exported to afb-daemon
482  */
483 static const struct AFB_verb_desc_v1 verbs[] = {
484    /* VERB'S NAME     SESSION MANAGEMENT          FUNCTION TO CALL  SHORT DESCRIPTION */
485    { .name= "new",   .session= AFB_SESSION_NONE, .callback= new,   .info= "Starts a new game" },
486    { .name= "play",  .session= AFB_SESSION_NONE, .callback= play,  .info= "Tells the server to play" },
487    { .name= "move",  .session= AFB_SESSION_NONE, .callback= move,  .info= "Tells the client move" },
488    { .name= "board", .session= AFB_SESSION_NONE, .callback= board, .info= "Get the current board" },
489    { .name= "level", .session= AFB_SESSION_NONE, .callback= level, .info= "Set the server level" },
490    { .name= "join",  .session= AFB_SESSION_NONE, .callback= join,  .info= "Join a board" },
491    { .name= "undo",  .session= AFB_SESSION_NONE, .callback= undo,  .info= "Undo the last move" },
492    /* marker for end of the array */
493    { .name= NULL }
494 };
495
496 /*
497  * description of the plugin for afb-daemon
498  */
499 static const struct AFB_plugin plugin_description =
500 {
501    /* description conforms to VERSION 1 */
502    .type= AFB_PLUGIN_VERSION_1,
503    .v1= {                               /* fills the v1 field of the union when AFB_PLUGIN_VERSION_1 */
504       .prefix= "tictactoe",             /* the API name (or plugin name or prefix) */
505       .info= "Sample tac-tac-toe game", /* short description of of the plugin */
506       .verbs = verbs                    /* the array describing the verbs of the API */
507    }
508 };
509
510 /*
511  * activation function for registering the plugin called by afb-daemon
512  */
513 const struct AFB_plugin *pluginAfbV1Register(const struct AFB_interface *itf)
514 {
515    afbitf = itf;         // records the interface for accessing afb-daemon
516    return &plugin_description;  // returns the description of the plugin
517 }
518