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(afb_req_t req)
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");
81 afb_req_subscribe(req, board->event);
84 board->next = all_boards;
89 static void *get_new_board_cb(void *closure)
91 afb_req_t req = closure;
92 return get_new_board(req);
98 static void release_board(struct board *board)
100 /* decrease the reference count ... */
101 if (--board->use_count == 0) {
102 /* ... no more use */
103 afb_event_unref(board->event);
104 /* unlink from the list of boards */
105 struct board **prv = &all_boards;
106 while (*prv != NULL && *prv != board)
110 /* release the used memory */
115 static void release_board_cb(void *closure)
117 struct board *board = closure;
118 return release_board(board);
123 * Returns zero if there is no winner
124 * Returns the char of the winner if a player won
126 static char wins(const char b[9], int first, int incr)
130 return c != SPACE && b[first + incr] == c && b[first + incr + incr] == c ? c : 0;
135 * Returns zero if there is no winner
136 * Returns the char of the winner if a player won
138 static char winner(const char b[9])
177 /* get the color (X or 0) of the move of index 'move' */
178 static char color(int move)
180 return (move & 1) == 0 ? CROSS : ROUND;
183 /* adds the move to the board */
184 static void add_move(struct board *board, int index)
186 int imove = board->moves++;
187 board->history[imove] = index;
188 board->board[index] = color(imove);
191 /* get a random possible move index from the board described by 'b' */
192 static int get_random_move(char b[9])
194 int index = rand() % 9;
195 while (b[index] != SPACE)
196 index = (index + 1) % 9;
201 * Scores the position described by 'b'
202 * for the player of color 'c' using an analysis of 'depth'.
203 * Returns 1 if player 'c' will win.
204 * Returns -1 if opponent of player 'c' will win.
205 * returns 0 otherwise.
207 static int score_position(char b[9], char c, int depth)
209 int i, s, nc, wc, lc;
211 /* check if winner */
215 /* when depth of analysis is reached return unknown case */
219 /* switch to the opponent */
220 c = (char)(ROUND + CROSS - c);
222 /* inspect opponent moves */
224 for (i = 0 ; i < 9 ; i++) {
227 s = score_position(b, c, depth);
230 lc++; /* opponent's victory, inc loose count */
232 wc++; /* current's victory, inc win count */
234 nc++; /* none wins, increment null count */
237 return lc ? -lc : wc;
240 /* get one move: return the computed index of the move */
241 static int get_move(struct board *board)
243 int index, depth, f, s, smax, imax;
247 /* compute the depth */
248 depth = board->level - 1;
249 if (board->moves + depth > 9)
250 depth = 9 - board->moves;
252 /* case of null depth */
254 return get_random_move(board->board);
257 memcpy(b, board->board, 9);
259 c = color(board->moves);
260 for (index = 0 ; index < 9 ; index++) {
261 if (board->board[index] == SPACE) {
262 board->board[index] = c;
263 s = score_position(board->board, c, depth);
264 board->board[index] = SPACE;
275 return smax ? imax : get_random_move(f ? b : board->board);
279 * get the board description
281 static struct json_object *describe(struct board *board)
285 struct json_object *resu, *arr;
287 resu = json_object_new_object();
289 json_object_object_add(resu, "boardid", json_object_new_int(board->id));
290 json_object_object_add(resu, "level", json_object_new_int(board->level));
292 arr = json_object_new_array();
293 json_object_object_add(resu, "board", arr);
294 for (i = 0 ; i < 9 ; i++)
295 json_object_array_add(arr,
296 json_object_new_string_len(&board->board[i], 1));
298 arr = json_object_new_array();
299 json_object_object_add(resu, "history", arr);
300 for (i = 0 ; i < board->moves ; i++)
301 json_object_array_add(arr, json_object_new_int(board->history[i]));
303 w = winner(board->board);
305 json_object_object_add(resu, "winner", json_object_new_string_len(&w, 1));
306 else if (board->moves == 9)
307 json_object_object_add(resu, "winner", json_object_new_string("none"));
313 * signals a change of the board
315 static void changed(struct board *board, const char *reason)
317 afb_event_push(board->event, json_object_new_string(reason));
321 * retrieves the board of the request
323 static inline struct board *board_of_req(afb_req_t req)
325 return afb_req_context(req, 0, get_new_board_cb, release_board_cb, req);
331 static void new(afb_req_t req)
335 /* retrieves the context for the session */
336 board = board_of_req(req);
337 AFB_INFO("method 'new' called for boardid %d", board->id);
340 memset(board->board, SPACE, sizeof board->board);
344 afb_req_success(req, NULL, NULL);
347 changed(board, "new");
353 static void board(afb_req_t req)
356 struct json_object *description;
358 /* retrieves the context for the session */
359 board = board_of_req(req);
360 AFB_INFO("method 'board' called for boardid %d", board->id);
362 /* describe the board */
363 description = describe(board);
365 /* send the board's description */
366 afb_req_success(req, description, NULL);
372 static void move(afb_req_t req)
378 /* retrieves the context for the session */
379 board = board_of_req(req);
380 AFB_INFO("method 'move' called for boardid %d", board->id);
382 /* retrieves the arguments of the move */
383 index = afb_req_value(req, "index");
384 i = index == NULL ? -1 : atoi(index);
386 /* checks validity of arguments */
387 if (i < 0 || i > 8) {
388 AFB_WARNING("can't move to %s: %s", index?:"?", index?"wrong value":"not set");
389 afb_req_fail(req, "error", "bad request");
393 /* checks validity of the state */
394 if (winner(board->board) != 0) {
395 AFB_WARNING("can't move to %s: game is terminated", index);
396 afb_req_fail(req, "error", "game terminated");
400 /* checks validity of the move */
401 if (board->board[i] != SPACE) {
402 AFB_WARNING("can't move to %s: room occupied", index);
403 afb_req_fail(req, "error", "occupied");
407 /* applies the move */
408 AFB_INFO("method 'move' for boardid %d, index=%s", board->id, index);
412 afb_req_success(req, NULL, NULL);
415 changed(board, "move");
421 static void level(afb_req_t req)
427 /* retrieves the context for the session */
428 board = board_of_req(req);
429 AFB_INFO("method 'level' called for boardid %d", board->id);
431 /* retrieves the arguments */
432 level = afb_req_value(req, "level");
433 l = level == NULL ? -1 : atoi(level);
435 /* check validity of arguments */
436 if (l < 1 || l > 10) {
437 AFB_WARNING("can't set level to %s: %s", level?:"?", level?"wrong value":"not set");
438 afb_req_fail(req, "error", "bad request");
443 AFB_INFO("method 'level' for boardid %d, level=%d", board->id, l);
447 afb_req_success(req, NULL, NULL);
450 changed(board, "level");
456 static void join(afb_req_t req)
458 struct board *board, *new_board;
461 /* retrieves the context for the session */
462 board = board_of_req(req);
463 AFB_INFO("method 'join' called for boardid %d", board->id);
465 /* retrieves the arguments */
466 id = afb_req_value(req, "boardid");
470 /* none is a special id for joining a new session */
471 if (strcmp(id, "none") == 0) {
472 new_board = get_new_board(req);
476 /* searchs the board to join */
477 new_board = search_board(atoi(id));
478 if (new_board == NULL)
482 * joining its board is stupid but possible
483 * however when called with the same stored pointer
484 * afb_req_context_set will not call the release
485 * function 'release_board'. So the use_count MUST not
488 if (new_board == board)
491 new_board->use_count++;
493 /* set the new board (and leaves the previous one) */
494 afb_req_context(req, 1, NULL, release_board_cb, new_board);
495 afb_req_unsubscribe(req, board->event);
499 afb_req_success(req, NULL, NULL);
503 AFB_WARNING("can't join boardid %s: %s", id ? : "?", !id ? "no boardid" : atoi(id) ? "not found" : "bad boardid");
504 afb_req_fail(req, "error", "bad request");
511 static void undo(afb_req_t req)
516 /* retrieves the context for the session */
517 board = board_of_req(req);
518 AFB_INFO("method 'undo' called for boardid %d", board->id);
520 /* checks the state */
521 if (board->moves == 0) {
522 AFB_WARNING("can't undo");
523 afb_req_fail(req, "error", "bad request");
527 /* undo the last move */
528 i = board->history[--board->moves];
529 board->board[i] = SPACE;
532 afb_req_success(req, NULL, NULL);
535 changed(board, "undo");
541 static void play(afb_req_t req)
546 /* retrieves the context for the session */
547 board = board_of_req(req);
548 AFB_INFO("method 'play' called for boardid %d", board->id);
550 /* checks validity of the state */
551 if (winner(board->board) != 0 || board->moves == 9) {
552 AFB_WARNING("can't play: game terminated (%s)", winner(board->board) ? "has winner" : "no room left");
553 afb_req_fail(req, "error", "game terminated");
557 /* gets the move and plays it */
558 index = get_move(board);
559 add_move(board, index);
562 afb_req_success(req, describe(board), NULL);
565 changed(board, "play");
569 * array of the verbs exported to afb-daemon
571 static const afb_verb_t verbs[] = {
572 { .verb="new", .callback=new },
573 { .verb="play", .callback=play },
574 { .verb="move", .callback=move },
575 { .verb="board", .callback=board },
576 { .verb="level", .callback=level },
577 { .verb="join", .callback=join },
578 { .verb="undo", .callback=undo },
583 * description of the binding for afb-daemon
585 const afb_binding_t afbBindingV3 = {
587 .specification = NULL,