2 * Copyright (C) 2016, 2017, 2018 "IoT.bzh"
3 * Author José Bollo <jose.bollo@iot.bzh>
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
21 #include <json-c/json.h>
23 #define AFB_BINDING_VERSION 2
24 #include <afb/afb-binding.h>
27 * definition of waiters
36 * definition of a board
47 struct waiter *waiters;
53 static struct board *all_boards;
56 * Searchs a board having the 'id'.
57 * Returns it if found or NULL otherwise.
59 static struct board *search_board(int id)
61 struct board *board = all_boards;
62 while (board != NULL && board->id != id)
68 * Creates a new board and returns it.
70 static struct board *get_new_board()
73 struct board *board = calloc(1, sizeof *board);
76 memset(board->board, ' ', sizeof board->board);
81 board->id = (rand() >> 2) % 1000;
82 } while(board->id == 0 || search_board(board->id) != NULL);
85 board->next = all_boards;
93 static void release_board(struct board *board)
95 /* decrease the reference count ... */
96 if (--board->use_count == 0) {
98 /* unlink from the list of boards */
99 struct board **prv = &all_boards;
100 while (*prv != NULL && *prv != board)
104 /* release the used memory */
111 * Returns zero if there is no winner
112 * Returns the char of the winner if a player won
114 static char winner(const char b[9])
119 /* check diagonals */
122 if (b[0] == c && b[8] == c)
124 if (b[2] == c && b[6] == c)
129 for (i = 0 ; i <= 6 ; i += 3) {
131 if (c != ' ' && b[i+1] == c && b[i+2] == c)
136 for (i = 0 ; i <= 2 ; i++) {
138 if (c != ' ' && b[i+3] == c && b[i+6] == c)
145 /* get the color (X or 0) of the move of index 'move' */
146 static char color(int move)
148 return (move & 1) == 0 ? 'X' : '0';
151 /* adds the move to the board */
152 static void add_move(struct board *board, int index)
154 int imove = board->moves++;
155 board->history[imove] = index;
156 board->board[index] = color(imove);
159 /* get a random possible move index from the board described by 'b' */
160 static int get_random_move(char b[9])
162 int index = rand() % 9;
163 while (b[index] != ' ')
164 index = (index + 1) % 9;
169 * Scores the position described by 'b'
170 * for the player of color 'c' using an analysis of 'depth'.
171 * Returns 1 if player 'c' will win.
172 * Returns -1 if opponent of player 'c' will win.
173 * returns 0 otherwise.
175 static int score_position(char b[9], char c, int depth)
179 /* check if winner */
183 /* when depth of analysis is reached return unknown case */
187 /* switch to the opponent */
188 c = (char)('O' + 'X' - c);
190 /* inspect opponent moves */
192 for (i = 0 ; i < 9 ; i++) {
195 t = score_position(b, c, depth);
198 return -1; /* opponent will win */
201 r = 0; /* something not clear */
207 /* get one move: return the computed index of the move */
208 static int get_move(struct board *board)
210 int index, depth, t, f;
214 /* compute the depth */
215 depth = board->level - 1;
216 if (board->moves + depth > 9)
217 depth = 9 - board->moves;
219 /* case of null depth */
221 return get_random_move(board->board);
224 memcpy(b, board->board, 9);
225 c = color(board->moves);
227 for (index = 0 ; index < 9 ; index++) {
228 if (board->board[index] == ' ') {
229 board->board[index] = c;
230 t = score_position(board->board, c, depth);
231 board->board[index] = ' ';
240 return get_random_move(f ? b : board->board);
244 * get the board description
246 static struct json_object *describe(struct board *board)
250 struct json_object *resu, *arr;
252 resu = json_object_new_object();
254 json_object_object_add(resu, "boardid", json_object_new_int(board->id));
255 json_object_object_add(resu, "level", json_object_new_int(board->level));
257 arr = json_object_new_array();
258 json_object_object_add(resu, "board", arr);
259 for (i = 0 ; i < 9 ; i++)
260 json_object_array_add(arr,
261 json_object_new_string_len(&board->board[i], 1));
263 arr = json_object_new_array();
264 json_object_object_add(resu, "history", arr);
265 for (i = 0 ; i < board->moves ; i++)
266 json_object_array_add(arr, json_object_new_int(board->history[i]));
268 w = winner(board->board);
270 json_object_object_add(resu, "winner", json_object_new_string_len(&w, 1));
271 else if (board->moves == 9)
272 json_object_object_add(resu, "winner", json_object_new_string("none"));
278 * signals a change of the board
280 static void changed(struct board *board, const char *reason)
282 struct waiter *waiter, *next;
283 struct json_object *description;
285 /* get the description */
286 description = describe(board);
288 waiter = board->waiters;
289 board->waiters = NULL;
290 while (waiter != NULL) {
292 afb_req_success(waiter->req, json_object_get(description), reason);
293 afb_req_unref(waiter->req);
298 afb_daemon_broadcast_event(reason, description);
302 * retrieves the board of the request
304 static inline struct board *board_of_req(struct afb_req req)
306 return afb_req_context(req, (void*)get_new_board, (void*)release_board);
312 static void new(struct afb_req req)
316 /* retrieves the context for the session */
317 board = board_of_req(req);
318 AFB_INFO("method 'new' called for boardid %d", board->id);
321 memset(board->board, ' ', sizeof board->board);
325 afb_req_success(req, NULL, NULL);
328 changed(board, "new");
334 static void board(struct afb_req req)
337 struct json_object *description;
339 /* retrieves the context for the session */
340 board = board_of_req(req);
341 AFB_INFO("method 'board' called for boardid %d", board->id);
343 /* describe the board */
344 description = describe(board);
346 /* send the board's description */
347 afb_req_success(req, description, NULL);
353 static void move(struct afb_req req)
359 /* retrieves the context for the session */
360 board = board_of_req(req);
361 AFB_INFO("method 'move' called for boardid %d", board->id);
363 /* retrieves the arguments of the move */
364 index = afb_req_value(req, "index");
365 i = index == NULL ? -1 : atoi(index);
367 /* checks validity of arguments */
368 if (i < 0 || i > 8) {
369 AFB_WARNING("can't move to %s: %s", index?:"?", index?"wrong value":"not set");
370 afb_req_fail(req, "error", "bad request");
374 /* checks validity of the state */
375 if (winner(board->board) != 0) {
376 AFB_WARNING("can't move to %s: game is terminated", index);
377 afb_req_fail(req, "error", "game terminated");
381 /* checks validity of the move */
382 if (board->board[i] != ' ') {
383 AFB_WARNING("can't move to %s: room occupied", index);
384 afb_req_fail(req, "error", "occupied");
388 /* applies the move */
389 AFB_INFO("method 'move' for boardid %d, index=%s", board->id, index);
393 afb_req_success(req, NULL, NULL);
396 changed(board, "move");
402 static void level(struct afb_req req)
408 /* retrieves the context for the session */
409 board = board_of_req(req);
410 AFB_INFO("method 'level' called for boardid %d", board->id);
412 /* retrieves the arguments */
413 level = afb_req_value(req, "level");
414 l = level == NULL ? -1 : atoi(level);
416 /* check validity of arguments */
417 if (l < 1 || l > 10) {
418 AFB_WARNING("can't set level to %s: %s", level?:"?", level?"wrong value":"not set");
419 afb_req_fail(req, "error", "bad request");
424 AFB_INFO("method 'level' for boardid %d, level=%d", board->id, l);
428 afb_req_success(req, NULL, NULL);
431 changed(board, "level");
437 static void join(struct afb_req req)
439 struct board *board, *new_board;
442 /* retrieves the context for the session */
443 board = board_of_req(req);
444 AFB_INFO("method 'join' called for boardid %d", board->id);
446 /* retrieves the arguments */
447 id = afb_req_value(req, "boardid");
451 /* none is a special id for joining a new session */
452 if (strcmp(id, "none") == 0) {
453 new_board = get_new_board();
457 /* searchs the board to join */
458 new_board = search_board(atoi(id));
459 if (new_board == NULL)
463 * joining its board is stupid but possible
464 * however when called with the same stored pointer
465 * afb_req_context_set will not call the release
466 * function 'release_board'. So the use_count MUST not
469 if (new_board != board)
470 new_board->use_count++;
473 /* set the new board (and leaves the previous one) */
474 afb_req_context_set(req, new_board, (void*)release_board);
477 afb_req_success(req, NULL, NULL);
481 AFB_WARNING("can't join boardid %s: %s", id ? : "?", !id ? "no boardid" : atoi(id) ? "not found" : "bad boardid");
482 afb_req_fail(req, "error", "bad request");
489 static void undo(struct afb_req req)
494 /* retrieves the context for the session */
495 board = board_of_req(req);
496 AFB_INFO("method 'undo' called for boardid %d", board->id);
498 /* checks the state */
499 if (board->moves == 0) {
500 AFB_WARNING("can't undo");
501 afb_req_fail(req, "error", "bad request");
505 /* undo the last move */
506 i = board->history[--board->moves];
507 board->board[i] = ' ';
510 afb_req_success(req, NULL, NULL);
513 changed(board, "undo");
519 static void play(struct afb_req req)
524 /* retrieves the context for the session */
525 board = board_of_req(req);
526 AFB_INFO("method 'play' called for boardid %d", board->id);
528 /* checks validity of the state */
529 if (winner(board->board) != 0 || board->moves == 9) {
530 AFB_WARNING("can't play: game terminated (%s)", winner(board->board) ? "has winner" : "no room left");
531 afb_req_fail(req, "error", "game terminated");
535 /* gets the move and plays it */
536 index = get_move(board);
537 add_move(board, index);
540 afb_req_success(req, describe(board), NULL);
543 changed(board, "play");
546 static void wait(struct afb_req req)
549 struct waiter *waiter;
551 /* retrieves the context for the session */
552 board = board_of_req(req);
553 AFB_INFO("method 'wait' called for boardid %d", board->id);
555 /* creates the waiter and enqueues it */
556 waiter = calloc(1, sizeof *waiter);
558 waiter->next = board->waiters;
560 board->waiters = waiter;
564 * array of the verbs exported to afb-daemon
566 static const struct afb_verb_v2 verbs[] = {
567 { .verb="new", .callback=new },
568 { .verb="play", .callback=play },
569 { .verb="move", .callback=move },
570 { .verb="board", .callback=board },
571 { .verb="level", .callback=level },
572 { .verb="join", .callback=join },
573 { .verb="undo", .callback=undo },
574 { .verb="wait", .callback=wait },
579 * description of the binding for afb-daemon
581 const afb_binding_v2 afbBindingV2 = {
583 .specification = NULL,