2 * Copyright (C) 2016 "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 #include <afb/afb-binding.h>
26 * the interface to afb-daemon
28 const struct afb_binding_interface *afbitf;
31 * definition of waiters
40 * definition of a board
51 struct waiter *waiters;
57 static struct board *all_boards;
60 * Searchs a board having the 'id'.
61 * Returns it if found or NULL otherwise.
63 static struct board *search_board(int id)
65 struct board *board = all_boards;
66 while (board != NULL && board->id != id)
72 * Creates a new board and returns it.
74 static struct board *get_new_board()
77 struct board *board = calloc(1, sizeof *board);
80 memset(board->board, ' ', sizeof board->board);
85 board->id = (rand() >> 2) % 1000;
86 } while(board->id == 0 || search_board(board->id) != NULL);
89 board->next = all_boards;
97 static void release_board(struct board *board)
99 /* decrease the reference count ... */
100 if (--board->use_count == 0) {
101 /* ... no more use */
102 /* unlink from the list of boards */
103 struct board **prv = &all_boards;
104 while (*prv != NULL && *prv != board)
108 /* release the used memory */
115 * Returns zero if there is no winner
116 * Returns the char of the winner if a player won
118 static char winner(const char b[9])
123 /* check diagonals */
126 if (b[0] == c && b[8] == c)
128 if (b[2] == c && b[6] == c)
133 for (i = 0 ; i <= 6 ; i += 3) {
135 if (c != ' ' && b[i+1] == c && b[i+2] == c)
140 for (i = 0 ; i <= 2 ; i++) {
142 if (c != ' ' && b[i+3] == c && b[i+6] == c)
149 /* get the color (X or 0) of the move of index 'move' */
150 static char color(int move)
152 return (move & 1) == 0 ? 'X' : '0';
155 /* adds the move to the board */
156 static void add_move(struct board *board, int index)
158 int imove = board->moves++;
159 board->history[imove] = index;
160 board->board[index] = color(imove);
163 /* get a random possible move index from the board described by 'b' */
164 static int get_random_move(char b[9])
166 int index = rand() % 9;
167 while (b[index] != ' ')
168 index = (index + 1) % 9;
173 * Scores the position described by 'b'
174 * for the player of color 'c' using an analysis of 'depth'.
175 * Returns 1 if player 'c' will win.
176 * Returns -1 if opponent of player 'c' will win.
177 * returns 0 otherwise.
179 static int score_position(char b[9], char c, int depth)
183 /* check if winner */
187 /* when depth of analysis is reached return unknown case */
191 /* switch to the opponent */
192 c = (char)('O' + 'X' - c);
194 /* inspect opponent moves */
196 for (i = 0 ; i < 9 ; i++) {
199 t = score_position(b, c, depth);
202 return -1; /* opponent will win */
205 r = 0; /* something not clear */
211 /* get one move: return the computed index of the move */
212 static int get_move(struct board *board)
214 int index, depth, t, f;
218 /* compute the depth */
219 depth = board->level - 1;
220 if (board->moves + depth > 9)
221 depth = 9 - board->moves;
223 /* case of null depth */
225 return get_random_move(board->board);
228 memcpy(b, board->board, 9);
229 c = color(board->moves);
231 for (index = 0 ; index < 9 ; index++) {
232 if (board->board[index] == ' ') {
233 board->board[index] = c;
234 t = score_position(board->board, c, depth);
235 board->board[index] = ' ';
244 return get_random_move(f ? b : board->board);
248 * get the board description
250 static struct json_object *describe(struct board *board)
254 struct json_object *resu, *arr;
256 resu = json_object_new_object();
258 json_object_object_add(resu, "boardid", json_object_new_int(board->id));
259 json_object_object_add(resu, "level", json_object_new_int(board->level));
261 arr = json_object_new_array();
262 json_object_object_add(resu, "board", arr);
263 for (i = 0 ; i < 9 ; i++)
264 json_object_array_add(arr,
265 json_object_new_string_len(&board->board[i], 1));
267 arr = json_object_new_array();
268 json_object_object_add(resu, "history", arr);
269 for (i = 0 ; i < board->moves ; i++)
270 json_object_array_add(arr, json_object_new_int(board->history[i]));
272 w = winner(board->board);
274 json_object_object_add(resu, "winner", json_object_new_string_len(&w, 1));
275 else if (board->moves == 9)
276 json_object_object_add(resu, "winner", json_object_new_string("none"));
282 * signals a change of the board
284 static void changed(struct board *board, const char *reason)
286 struct waiter *waiter, *next;
287 struct json_object *description;
289 /* get the description */
290 description = describe(board);
292 waiter = board->waiters;
293 board->waiters = NULL;
294 while (waiter != NULL) {
296 afb_req_success(waiter->req, json_object_get(description), reason);
297 afb_req_unref(waiter->req);
302 afb_daemon_broadcast_event(afbitf->daemon, reason, description);
306 * retrieves the board of the request
308 static inline struct board *board_of_req(struct afb_req req)
310 return afb_req_context(req, (void*)get_new_board, (void*)release_board);
316 static void new(struct afb_req req)
320 /* retrieves the context for the session */
321 board = board_of_req(req);
322 INFO(afbitf, "method 'new' called for boardid %d", board->id);
325 memset(board->board, ' ', sizeof board->board);
329 afb_req_success(req, NULL, NULL);
332 changed(board, "new");
338 static void board(struct afb_req req)
341 struct json_object *description;
343 /* retrieves the context for the session */
344 board = board_of_req(req);
345 INFO(afbitf, "method 'board' called for boardid %d", board->id);
347 /* describe the board */
348 description = describe(board);
350 /* send the board's description */
351 afb_req_success(req, description, NULL);
357 static void move(struct afb_req req)
363 /* retrieves the context for the session */
364 board = board_of_req(req);
365 INFO(afbitf, "method 'move' called for boardid %d", board->id);
367 /* retrieves the arguments of the move */
368 index = afb_req_value(req, "index");
369 i = index == NULL ? -1 : atoi(index);
371 /* checks validity of arguments */
372 if (i < 0 || i > 8) {
373 WARNING(afbitf, "can't move to %s: %s", index?:"?", index?"wrong value":"not set");
374 afb_req_fail(req, "error", "bad request");
378 /* checks validity of the state */
379 if (winner(board->board) != 0) {
380 WARNING(afbitf, "can't move to %s: game is terminated", index);
381 afb_req_fail(req, "error", "game terminated");
385 /* checks validity of the move */
386 if (board->board[i] != ' ') {
387 WARNING(afbitf, "can't move to %s: room occupied", index);
388 afb_req_fail(req, "error", "occupied");
392 /* applies the move */
393 INFO(afbitf, "method 'move' for boardid %d, index=%s", board->id, index);
397 afb_req_success(req, NULL, NULL);
400 changed(board, "move");
406 static void level(struct afb_req req)
412 /* retrieves the context for the session */
413 board = board_of_req(req);
414 INFO(afbitf, "method 'level' called for boardid %d", board->id);
416 /* retrieves the arguments */
417 level = afb_req_value(req, "level");
418 l = level == NULL ? -1 : atoi(level);
420 /* check validity of arguments */
421 if (l < 1 || l > 10) {
422 WARNING(afbitf, "can't set level to %s: %s", level?:"?", level?"wrong value":"not set");
423 afb_req_fail(req, "error", "bad request");
428 INFO(afbitf, "method 'level' for boardid %d, level=%d", board->id, l);
432 afb_req_success(req, NULL, NULL);
435 changed(board, "level");
441 static void join(struct afb_req req)
443 struct board *board, *new_board;
446 /* retrieves the context for the session */
447 board = board_of_req(req);
448 INFO(afbitf, "method 'join' called for boardid %d", board->id);
450 /* retrieves the arguments */
451 id = afb_req_value(req, "boardid");
455 /* none is a special id for joining a new session */
456 if (strcmp(id, "none") == 0) {
457 new_board = get_new_board();
461 /* searchs the board to join */
462 new_board = search_board(atoi(id));
463 if (new_board == NULL)
467 * joining its board is stupid but possible
468 * however when called with the same stored pointer
469 * afb_req_context_set will not call the release
470 * function 'release_board'. So the use_count MUST not
473 if (new_board != board)
474 new_board->use_count++;
477 /* set the new board (and leaves the previous one) */
478 afb_req_context_set(req, new_board, (void*)release_board);
481 afb_req_success(req, NULL, NULL);
485 WARNING(afbitf, "can't join boardid %s: %s", id ? : "?", !id ? "no boardid" : atoi(id) ? "not found" : "bad boardid");
486 afb_req_fail(req, "error", "bad request");
493 static void undo(struct afb_req req)
498 /* retrieves the context for the session */
499 board = board_of_req(req);
500 INFO(afbitf, "method 'undo' called for boardid %d", board->id);
502 /* checks the state */
503 if (board->moves == 0) {
504 WARNING(afbitf, "can't undo");
505 afb_req_fail(req, "error", "bad request");
509 /* undo the last move */
510 i = board->history[--board->moves];
511 board->board[i] = ' ';
514 afb_req_success(req, NULL, NULL);
517 changed(board, "undo");
523 static void play(struct afb_req req)
528 /* retrieves the context for the session */
529 board = board_of_req(req);
530 INFO(afbitf, "method 'play' called for boardid %d", board->id);
532 /* checks validity of the state */
533 if (winner(board->board) != 0 || board->moves == 9) {
534 WARNING(afbitf, "can't play: game terminated (%s)", winner(board->board) ? "has winner" : "no room left");
535 afb_req_fail(req, "error", "game terminated");
539 /* gets the move and plays it */
540 index = get_move(board);
541 add_move(board, index);
544 afb_req_success(req, describe(board), NULL);
547 changed(board, "play");
550 static void wait(struct afb_req req)
553 struct waiter *waiter;
555 /* retrieves the context for the session */
556 board = board_of_req(req);
557 INFO(afbitf, "method 'wait' called for boardid %d", board->id);
559 /* creates the waiter and enqueues it */
560 waiter = calloc(1, sizeof *waiter);
562 waiter->next = board->waiters;
564 board->waiters = waiter;
568 * array of the verbs exported to afb-daemon
570 static const struct afb_verb_desc_v1 binding_verbs[] = {
571 /* VERB'S NAME SESSION MANAGEMENT FUNCTION TO CALL SHORT DESCRIPTION */
572 { .name= "new", .session= AFB_SESSION_NONE, .callback= new, .info= "Starts a new game" },
573 { .name= "play", .session= AFB_SESSION_NONE, .callback= play, .info= "Asks the server to play" },
574 { .name= "move", .session= AFB_SESSION_NONE, .callback= move, .info= "Tells the client move" },
575 { .name= "board", .session= AFB_SESSION_NONE, .callback= board, .info= "Get the current board" },
576 { .name= "level", .session= AFB_SESSION_NONE, .callback= level, .info= "Set the server level" },
577 { .name= "join", .session= AFB_SESSION_CHECK,.callback= join, .info= "Join a board" },
578 { .name= "undo", .session= AFB_SESSION_NONE, .callback= undo, .info= "Undo the last move" },
579 { .name= "wait", .session= AFB_SESSION_NONE, .callback= wait, .info= "Wait for a change" },
580 { .name= NULL } /* marker for end of the array */
584 * description of the binding for afb-daemon
586 static const struct afb_binding binding_description =
588 /* description conforms to VERSION 1 */
589 .type= AFB_BINDING_VERSION_1,
590 .v1= { /* fills the v1 field of the union when AFB_BINDING_VERSION_1 */
591 .prefix= "tictactoe", /* the API name (or binding name or prefix) */
592 .info= "Sample tac-tac-toe game", /* short description of of the binding */
593 .verbs = binding_verbs /* the array describing the verbs of the API */
598 * activation function for registering the binding called by afb-daemon
600 const struct afb_binding *afbBindingV1Register(const struct afb_binding_interface *itf)
602 afbitf = itf; // records the interface for accessing afb-daemon
603 return &binding_description; // returns the description of the binding