2 * Copyright (C) 2016, 2017 "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 1
24 #include <afb/afb-binding.h>
27 * the interface to afb-daemon
29 const struct afb_binding_interface *afbitf;
32 * definition of waiters
41 * definition of a board
52 struct waiter *waiters;
58 static struct board *all_boards;
61 * Searchs a board having the 'id'.
62 * Returns it if found or NULL otherwise.
64 static struct board *search_board(int id)
66 struct board *board = all_boards;
67 while (board != NULL && board->id != id)
73 * Creates a new board and returns it.
75 static struct board *get_new_board()
78 struct board *board = calloc(1, sizeof *board);
81 memset(board->board, ' ', sizeof board->board);
86 board->id = (rand() >> 2) % 1000;
87 } while(board->id == 0 || search_board(board->id) != NULL);
90 board->next = all_boards;
98 static void release_board(struct board *board)
100 /* decrease the reference count ... */
101 if (--board->use_count == 0) {
102 /* ... no more use */
103 /* unlink from the list of boards */
104 struct board **prv = &all_boards;
105 while (*prv != NULL && *prv != board)
109 /* release the used memory */
116 * Returns zero if there is no winner
117 * Returns the char of the winner if a player won
119 static char winner(const char b[9])
124 /* check diagonals */
127 if (b[0] == c && b[8] == c)
129 if (b[2] == c && b[6] == c)
134 for (i = 0 ; i <= 6 ; i += 3) {
136 if (c != ' ' && b[i+1] == c && b[i+2] == c)
141 for (i = 0 ; i <= 2 ; i++) {
143 if (c != ' ' && b[i+3] == c && b[i+6] == c)
150 /* get the color (X or 0) of the move of index 'move' */
151 static char color(int move)
153 return (move & 1) == 0 ? 'X' : '0';
156 /* adds the move to the board */
157 static void add_move(struct board *board, int index)
159 int imove = board->moves++;
160 board->history[imove] = index;
161 board->board[index] = color(imove);
164 /* get a random possible move index from the board described by 'b' */
165 static int get_random_move(char b[9])
167 int index = rand() % 9;
168 while (b[index] != ' ')
169 index = (index + 1) % 9;
174 * Scores the position described by 'b'
175 * for the player of color 'c' using an analysis of 'depth'.
176 * Returns 1 if player 'c' will win.
177 * Returns -1 if opponent of player 'c' will win.
178 * returns 0 otherwise.
180 static int score_position(char b[9], char c, int depth)
184 /* check if winner */
188 /* when depth of analysis is reached return unknown case */
192 /* switch to the opponent */
193 c = (char)('O' + 'X' - c);
195 /* inspect opponent moves */
197 for (i = 0 ; i < 9 ; i++) {
200 t = score_position(b, c, depth);
203 return -1; /* opponent will win */
206 r = 0; /* something not clear */
212 /* get one move: return the computed index of the move */
213 static int get_move(struct board *board)
215 int index, depth, t, f;
219 /* compute the depth */
220 depth = board->level - 1;
221 if (board->moves + depth > 9)
222 depth = 9 - board->moves;
224 /* case of null depth */
226 return get_random_move(board->board);
229 memcpy(b, board->board, 9);
230 c = color(board->moves);
232 for (index = 0 ; index < 9 ; index++) {
233 if (board->board[index] == ' ') {
234 board->board[index] = c;
235 t = score_position(board->board, c, depth);
236 board->board[index] = ' ';
245 return get_random_move(f ? b : board->board);
249 * get the board description
251 static struct json_object *describe(struct board *board)
255 struct json_object *resu, *arr;
257 resu = json_object_new_object();
259 json_object_object_add(resu, "boardid", json_object_new_int(board->id));
260 json_object_object_add(resu, "level", json_object_new_int(board->level));
262 arr = json_object_new_array();
263 json_object_object_add(resu, "board", arr);
264 for (i = 0 ; i < 9 ; i++)
265 json_object_array_add(arr,
266 json_object_new_string_len(&board->board[i], 1));
268 arr = json_object_new_array();
269 json_object_object_add(resu, "history", arr);
270 for (i = 0 ; i < board->moves ; i++)
271 json_object_array_add(arr, json_object_new_int(board->history[i]));
273 w = winner(board->board);
275 json_object_object_add(resu, "winner", json_object_new_string_len(&w, 1));
276 else if (board->moves == 9)
277 json_object_object_add(resu, "winner", json_object_new_string("none"));
283 * signals a change of the board
285 static void changed(struct board *board, const char *reason)
287 struct waiter *waiter, *next;
288 struct json_object *description;
290 /* get the description */
291 description = describe(board);
293 waiter = board->waiters;
294 board->waiters = NULL;
295 while (waiter != NULL) {
297 afb_req_success(waiter->req, json_object_get(description), reason);
298 afb_req_unref(waiter->req);
303 afb_daemon_broadcast_event(afbitf->daemon, reason, description);
307 * retrieves the board of the request
309 static inline struct board *board_of_req(struct afb_req req)
311 return afb_req_context(req, (void*)get_new_board, (void*)release_board);
317 static void new(struct afb_req req)
321 /* retrieves the context for the session */
322 board = board_of_req(req);
323 INFO(afbitf, "method 'new' called for boardid %d", board->id);
326 memset(board->board, ' ', sizeof board->board);
330 afb_req_success(req, NULL, NULL);
333 changed(board, "new");
339 static void board(struct afb_req req)
342 struct json_object *description;
344 /* retrieves the context for the session */
345 board = board_of_req(req);
346 INFO(afbitf, "method 'board' called for boardid %d", board->id);
348 /* describe the board */
349 description = describe(board);
351 /* send the board's description */
352 afb_req_success(req, description, NULL);
358 static void move(struct afb_req req)
364 /* retrieves the context for the session */
365 board = board_of_req(req);
366 INFO(afbitf, "method 'move' called for boardid %d", board->id);
368 /* retrieves the arguments of the move */
369 index = afb_req_value(req, "index");
370 i = index == NULL ? -1 : atoi(index);
372 /* checks validity of arguments */
373 if (i < 0 || i > 8) {
374 WARNING(afbitf, "can't move to %s: %s", index?:"?", index?"wrong value":"not set");
375 afb_req_fail(req, "error", "bad request");
379 /* checks validity of the state */
380 if (winner(board->board) != 0) {
381 WARNING(afbitf, "can't move to %s: game is terminated", index);
382 afb_req_fail(req, "error", "game terminated");
386 /* checks validity of the move */
387 if (board->board[i] != ' ') {
388 WARNING(afbitf, "can't move to %s: room occupied", index);
389 afb_req_fail(req, "error", "occupied");
393 /* applies the move */
394 INFO(afbitf, "method 'move' for boardid %d, index=%s", board->id, index);
398 afb_req_success(req, NULL, NULL);
401 changed(board, "move");
407 static void level(struct afb_req req)
413 /* retrieves the context for the session */
414 board = board_of_req(req);
415 INFO(afbitf, "method 'level' called for boardid %d", board->id);
417 /* retrieves the arguments */
418 level = afb_req_value(req, "level");
419 l = level == NULL ? -1 : atoi(level);
421 /* check validity of arguments */
422 if (l < 1 || l > 10) {
423 WARNING(afbitf, "can't set level to %s: %s", level?:"?", level?"wrong value":"not set");
424 afb_req_fail(req, "error", "bad request");
429 INFO(afbitf, "method 'level' for boardid %d, level=%d", board->id, l);
433 afb_req_success(req, NULL, NULL);
436 changed(board, "level");
442 static void join(struct afb_req req)
444 struct board *board, *new_board;
447 /* retrieves the context for the session */
448 board = board_of_req(req);
449 INFO(afbitf, "method 'join' called for boardid %d", board->id);
451 /* retrieves the arguments */
452 id = afb_req_value(req, "boardid");
456 /* none is a special id for joining a new session */
457 if (strcmp(id, "none") == 0) {
458 new_board = get_new_board();
462 /* searchs the board to join */
463 new_board = search_board(atoi(id));
464 if (new_board == NULL)
468 * joining its board is stupid but possible
469 * however when called with the same stored pointer
470 * afb_req_context_set will not call the release
471 * function 'release_board'. So the use_count MUST not
474 if (new_board != board)
475 new_board->use_count++;
478 /* set the new board (and leaves the previous one) */
479 afb_req_context_set(req, new_board, (void*)release_board);
482 afb_req_success(req, NULL, NULL);
486 WARNING(afbitf, "can't join boardid %s: %s", id ? : "?", !id ? "no boardid" : atoi(id) ? "not found" : "bad boardid");
487 afb_req_fail(req, "error", "bad request");
494 static void undo(struct afb_req req)
499 /* retrieves the context for the session */
500 board = board_of_req(req);
501 INFO(afbitf, "method 'undo' called for boardid %d", board->id);
503 /* checks the state */
504 if (board->moves == 0) {
505 WARNING(afbitf, "can't undo");
506 afb_req_fail(req, "error", "bad request");
510 /* undo the last move */
511 i = board->history[--board->moves];
512 board->board[i] = ' ';
515 afb_req_success(req, NULL, NULL);
518 changed(board, "undo");
524 static void play(struct afb_req req)
529 /* retrieves the context for the session */
530 board = board_of_req(req);
531 INFO(afbitf, "method 'play' called for boardid %d", board->id);
533 /* checks validity of the state */
534 if (winner(board->board) != 0 || board->moves == 9) {
535 WARNING(afbitf, "can't play: game terminated (%s)", winner(board->board) ? "has winner" : "no room left");
536 afb_req_fail(req, "error", "game terminated");
540 /* gets the move and plays it */
541 index = get_move(board);
542 add_move(board, index);
545 afb_req_success(req, describe(board), NULL);
548 changed(board, "play");
551 static void wait(struct afb_req req)
554 struct waiter *waiter;
556 /* retrieves the context for the session */
557 board = board_of_req(req);
558 INFO(afbitf, "method 'wait' called for boardid %d", board->id);
560 /* creates the waiter and enqueues it */
561 waiter = calloc(1, sizeof *waiter);
563 waiter->next = board->waiters;
565 board->waiters = waiter;
569 * array of the verbs exported to afb-daemon
571 static const struct afb_verb_desc_v1 binding_verbs[] = {
572 /* VERB'S NAME SESSION MANAGEMENT FUNCTION TO CALL SHORT DESCRIPTION */
573 { .name= "new", .session= AFB_SESSION_NONE, .callback= new, .info= "Starts a new game" },
574 { .name= "play", .session= AFB_SESSION_NONE, .callback= play, .info= "Asks the server to play" },
575 { .name= "move", .session= AFB_SESSION_NONE, .callback= move, .info= "Tells the client move" },
576 { .name= "board", .session= AFB_SESSION_NONE, .callback= board, .info= "Get the current board" },
577 { .name= "level", .session= AFB_SESSION_NONE, .callback= level, .info= "Set the server level" },
578 { .name= "join", .session= AFB_SESSION_CHECK,.callback= join, .info= "Join a board" },
579 { .name= "undo", .session= AFB_SESSION_NONE, .callback= undo, .info= "Undo the last move" },
580 { .name= "wait", .session= AFB_SESSION_NONE, .callback= wait, .info= "Wait for a change" },
581 { .name= NULL } /* marker for end of the array */
585 * description of the binding for afb-daemon
587 static const struct afb_binding binding_description =
589 /* description conforms to VERSION 1 */
590 .type= AFB_BINDING_VERSION_1,
591 .v1= { /* fills the v1 field of the union when AFB_BINDING_VERSION_1 */
592 .prefix= "tictactoe", /* the API name (or binding name or prefix) */
593 .info= "Sample tac-tac-toe game", /* short description of of the binding */
594 .verbs = binding_verbs /* the array describing the verbs of the API */
599 * activation function for registering the binding called by afb-daemon
601 const struct afb_binding *afbBindingV1Register(const struct afb_binding_interface *itf)
603 afbitf = itf; // records the interface for accessing afb-daemon
604 return &binding_description; // returns the description of the binding