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 3
24 #include <afb/afb-binding.h>
26 static const char SPACE = ' ';
27 static const char CROSS = 'X';
28 static const char ROUND = 'O';
29 static const char NHERE = '+';
30 static const int DEFLVL = 8;
33 * definition of a board
50 static struct board *all_boards;
53 * Searchs a board having the 'id'.
54 * Returns it if found or NULL otherwise.
56 static struct board *search_board(int id)
58 struct board *board = all_boards;
59 while (board != NULL && board->id != id)
65 * Creates a new board and returns it.
67 static struct board *get_new_board()
70 struct board *board = calloc(1, sizeof *board);
73 memset(board->board, SPACE, sizeof board->board);
75 board->level = DEFLVL;
78 board->id = (rand() >> 2) % 1000;
79 } while(board->id == 0 || search_board(board->id) != NULL);
80 board->event = afb_daemon_make_event("board");
83 board->next = all_boards;
91 static void release_board(struct board *board)
93 /* decrease the reference count ... */
94 if (--board->use_count == 0) {
96 afb_event_unref(board->event);
97 /* unlink from the list of boards */
98 struct board **prv = &all_boards;
99 while (*prv != NULL && *prv != board)
103 /* release the used memory */
110 * Returns zero if there is no winner
111 * Returns the char of the winner if a player won
113 static char wins(const char b[9], int first, int incr)
117 return c != SPACE && b[first + incr] == c && b[first + incr + incr] == c ? c : 0;
122 * Returns zero if there is no winner
123 * Returns the char of the winner if a player won
125 static char winner(const char b[9])
164 /* get the color (X or 0) of the move of index 'move' */
165 static char color(int move)
167 return (move & 1) == 0 ? CROSS : ROUND;
170 /* adds the move to the board */
171 static void add_move(struct board *board, int index)
173 int imove = board->moves++;
174 board->history[imove] = index;
175 board->board[index] = color(imove);
178 /* get a random possible move index from the board described by 'b' */
179 static int get_random_move(char b[9])
181 int index = rand() % 9;
182 while (b[index] != SPACE)
183 index = (index + 1) % 9;
188 * Scores the position described by 'b'
189 * for the player of color 'c' using an analysis of 'depth'.
190 * Returns 1 if player 'c' will win.
191 * Returns -1 if opponent of player 'c' will win.
192 * returns 0 otherwise.
194 static int score_position(char b[9], char c, int depth)
196 int i, s, nc, wc, lc;
198 /* check if winner */
202 /* when depth of analysis is reached return unknown case */
206 /* switch to the opponent */
207 c = (char)(ROUND + CROSS - c);
209 /* inspect opponent moves */
211 for (i = 0 ; i < 9 ; i++) {
214 s = score_position(b, c, depth);
217 lc++; /* opponent's victory, inc loose count */
219 wc++; /* current's victory, inc win count */
221 nc++; /* none wins, increment null count */
224 return lc ? -lc : wc;
227 /* get one move: return the computed index of the move */
228 static int get_move(struct board *board)
230 int index, depth, f, s, smax, imax;
234 /* compute the depth */
235 depth = board->level - 1;
236 if (board->moves + depth > 9)
237 depth = 9 - board->moves;
239 /* case of null depth */
241 return get_random_move(board->board);
244 memcpy(b, board->board, 9);
246 c = color(board->moves);
247 for (index = 0 ; index < 9 ; index++) {
248 if (board->board[index] == SPACE) {
249 board->board[index] = c;
250 s = score_position(board->board, c, depth);
251 board->board[index] = SPACE;
262 return smax ? imax : get_random_move(f ? b : board->board);
266 * get the board description
268 static struct json_object *describe(struct board *board)
272 struct json_object *resu, *arr;
274 resu = json_object_new_object();
276 json_object_object_add(resu, "boardid", json_object_new_int(board->id));
277 json_object_object_add(resu, "level", json_object_new_int(board->level));
279 arr = json_object_new_array();
280 json_object_object_add(resu, "board", arr);
281 for (i = 0 ; i < 9 ; i++)
282 json_object_array_add(arr,
283 json_object_new_string_len(&board->board[i], 1));
285 arr = json_object_new_array();
286 json_object_object_add(resu, "history", arr);
287 for (i = 0 ; i < board->moves ; i++)
288 json_object_array_add(arr, json_object_new_int(board->history[i]));
290 w = winner(board->board);
292 json_object_object_add(resu, "winner", json_object_new_string_len(&w, 1));
293 else if (board->moves == 9)
294 json_object_object_add(resu, "winner", json_object_new_string("none"));
300 * signals a change of the board
302 static void changed(struct board *board, const char *reason)
304 afb_event_push(board->event, json_object_new_string(reason));
307 static void *get_new_board_cb(void *closure)
309 afb_req_t req = closure;
310 struct board *board = get_new_board();
311 afb_req_subscribe(req, board->event);
315 static void release_board_cb(void *closure)
317 struct board *board = closure;
318 return release_board(board);
322 * retrieves the board of the request
324 static inline struct board *board_of_req(afb_req_t req)
326 return afb_req_context(req, 0, get_new_board_cb, release_board_cb, req);
332 static void new(afb_req_t req)
336 /* retrieves the context for the session */
337 board = board_of_req(req);
338 AFB_INFO("method 'new' called for boardid %d", board->id);
341 memset(board->board, SPACE, sizeof board->board);
345 afb_req_success(req, NULL, NULL);
348 changed(board, "new");
354 static void board(afb_req_t req)
357 struct json_object *description;
359 /* retrieves the context for the session */
360 board = board_of_req(req);
361 AFB_INFO("method 'board' called for boardid %d", board->id);
363 /* describe the board */
364 description = describe(board);
366 /* send the board's description */
367 afb_req_success(req, description, NULL);
373 static void move(afb_req_t req)
379 /* retrieves the context for the session */
380 board = board_of_req(req);
381 AFB_INFO("method 'move' called for boardid %d", board->id);
383 /* retrieves the arguments of the move */
384 index = afb_req_value(req, "index");
385 i = index == NULL ? -1 : atoi(index);
387 /* checks validity of arguments */
388 if (i < 0 || i > 8) {
389 AFB_WARNING("can't move to %s: %s", index?:"?", index?"wrong value":"not set");
390 afb_req_fail(req, "error", "bad request");
394 /* checks validity of the state */
395 if (winner(board->board) != 0) {
396 AFB_WARNING("can't move to %s: game is terminated", index);
397 afb_req_fail(req, "error", "game terminated");
401 /* checks validity of the move */
402 if (board->board[i] != SPACE) {
403 AFB_WARNING("can't move to %s: room occupied", index);
404 afb_req_fail(req, "error", "occupied");
408 /* applies the move */
409 AFB_INFO("method 'move' for boardid %d, index=%s", board->id, index);
413 afb_req_success(req, NULL, NULL);
416 changed(board, "move");
422 static void level(afb_req_t req)
428 /* retrieves the context for the session */
429 board = board_of_req(req);
430 AFB_INFO("method 'level' called for boardid %d", board->id);
432 /* retrieves the arguments */
433 level = afb_req_value(req, "level");
434 l = level == NULL ? -1 : atoi(level);
436 /* check validity of arguments */
437 if (l < 1 || l > 10) {
438 AFB_WARNING("can't set level to %s: %s", level?:"?", level?"wrong value":"not set");
439 afb_req_fail(req, "error", "bad request");
444 AFB_INFO("method 'level' for boardid %d, level=%d", board->id, l);
448 afb_req_success(req, NULL, NULL);
451 changed(board, "level");
457 static void join(afb_req_t req)
459 struct board *board, *new_board;
462 /* retrieves the context for the session */
463 board = board_of_req(req);
464 AFB_INFO("method 'join' called for boardid %d", board->id);
466 /* retrieves the arguments */
467 id = afb_req_value(req, "boardid");
471 /* none is a special id for joining a new session */
472 if (strcmp(id, "none") == 0) {
473 new_board = get_new_board(req);
477 /* searchs the board to join */
478 new_board = search_board(atoi(id));
479 if (new_board == NULL)
483 * joining its board is stupid but possible
484 * however when called with the same stored pointer
485 * afb_req_context_set will not call the release
486 * function 'release_board'. So the use_count MUST not
489 if (new_board == board)
492 new_board->use_count++;
494 /* set the new board (and leaves the previous one) */
495 afb_req_unsubscribe(req, board->event);
496 afb_req_context(req, 1, NULL, release_board_cb, new_board);
497 afb_req_subscribe(req, new_board->event);
501 afb_req_success(req, NULL, NULL);
505 AFB_WARNING("can't join boardid %s: %s", id ? : "?", !id ? "no boardid" : atoi(id) ? "not found" : "bad boardid");
506 afb_req_fail(req, "error", "bad request");
513 static void undo(afb_req_t req)
518 /* retrieves the context for the session */
519 board = board_of_req(req);
520 AFB_INFO("method 'undo' called for boardid %d", board->id);
522 /* checks the state */
523 if (board->moves == 0) {
524 AFB_WARNING("can't undo");
525 afb_req_fail(req, "error", "bad request");
529 /* undo the last move */
530 i = board->history[--board->moves];
531 board->board[i] = SPACE;
534 afb_req_success(req, NULL, NULL);
537 changed(board, "undo");
543 static void play(afb_req_t req)
548 /* retrieves the context for the session */
549 board = board_of_req(req);
550 AFB_INFO("method 'play' called for boardid %d", board->id);
552 /* checks validity of the state */
553 if (winner(board->board) != 0 || board->moves == 9) {
554 AFB_WARNING("can't play: game terminated (%s)", winner(board->board) ? "has winner" : "no room left");
555 afb_req_fail(req, "error", "game terminated");
559 /* gets the move and plays it */
560 index = get_move(board);
561 add_move(board, index);
564 afb_req_success(req, describe(board), NULL);
567 changed(board, "play");
571 * array of the verbs exported to afb-daemon
573 static const afb_verb_t verbs[] = {
574 { .verb="new", .callback=new },
575 { .verb="play", .callback=play },
576 { .verb="move", .callback=move },
577 { .verb="board", .callback=board },
578 { .verb="level", .callback=level },
579 { .verb="join", .callback=join },
580 { .verb="undo", .callback=undo },
585 * description of the binding for afb-daemon
587 const afb_binding_t afbBindingV3 = {
589 .specification = NULL,