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-plugin.h>
26 * the interface to afb-daemon
28 const struct AFB_interface *afbitf;
31 * definition of a board
47 static struct board *all_boards;
52 static struct board *search_board(int id)
54 struct board *board = all_boards;
55 while (board != NULL && board->id != id)
63 static struct board *get_new_board()
65 struct board *board = calloc(1, sizeof *board);
66 memset(board->board, ' ', sizeof board->board);
71 board->id = (rand() >> 2) % 1000;
72 } while(board->id == 0 || search_board(board->id) != NULL);
73 board->next = all_boards;
81 static void release_board(struct board *board)
83 /* decrease the reference count ... */
84 if (--board->refcount == 0) {
86 /* unlink from the list of boards */
87 struct board **prv = &all_boards;
88 while (*prv != NULL && *prv != board)
92 /* release the used memory */
99 * Returns zero if there is no winner
100 * Returns the char of the winner if a player won
102 static char winner(const char b[9])
107 /* check diagonals */
110 if (b[0] == c && b[8] == c)
112 if (b[2] == c && b[6] == c)
117 for (i = 0 ; i <= 6 ; i += 3) {
119 if (c != ' ' && b[i+1] == c && b[i+2] == c)
124 for (i = 0 ; i <= 2 ; i++) {
126 if (c != ' ' && b[i+3] == c && b[i+6] == c)
133 /* get the color (X or 0) of the move of index 'move' */
134 static char color(int move)
136 return (move & 1) == 0 ? 'X' : '0';
139 /* adds the move to the board */
140 static void add_move(struct board *board, int index)
142 int imove = board->moves++;
143 board->history[imove] = index;
144 board->board[index] = color(imove);
147 /* get a random possible move index from the board described by 'b' */
148 static int get_random_move(char b[9])
150 int index = rand() % 9;
151 while (b[index] != ' ')
152 index = (index + 1) % 9;
157 * Scores the position described by 'b'
158 * for the player of color 'c' using an analysis of 'depth'.
159 * Returns 1 if player 'c' will win.
160 * Returns -1 if opponent of player 'c' will win.
161 * returns 0 otherwise.
163 static int score_position(char b[9], char c, int depth)
167 /* check if winner */
171 /* when depth of analysis is reached return unknown case */
175 /* switch to the opponent */
176 c = (char)('O' + 'X' - c);
178 /* inspect opponent moves */
180 for (i = 0 ; i < 9 ; i++) {
183 t = score_position(b, c, depth);
186 return -1; /* opponent will win */
189 r = 0; /* something not clear */
195 /* get one move: return the computed index of the move */
196 static int get_move(struct board *board)
198 int index, depth, t, f;
202 /* compute the depth */
203 depth = board->level - 1;
204 if (board->moves + depth > 9)
205 depth = 9 - board->moves;
207 /* case of null depth */
209 return get_random_move(board->board);
212 memcpy(b, board->board, 9);
213 c = color(board->moves);
215 for (index = 0 ; index < 9 ; index++) {
216 if (board->board[index] == ' ') {
217 board->board[index] = c;
218 t = score_position(board->board, c, depth);
219 board->board[index] = ' ';
228 return get_random_move(f ? b : board->board);
232 * get the board description
234 static struct json_object *describe(struct board *board)
238 struct json_object *resu, *arr;
240 resu = json_object_new_object();
242 json_object_object_add(resu, "boardid", json_object_new_int(board->id));
243 json_object_object_add(resu, "level", json_object_new_int(board->level));
245 arr = json_object_new_array();
246 json_object_object_add(resu, "board", arr);
247 for (i = 0 ; i < 9 ; i++)
248 json_object_array_add(arr,
249 json_object_new_string_len(&board->board[i], 1));
251 arr = json_object_new_array();
252 json_object_object_add(resu, "history", arr);
253 for (i = 0 ; i < board->moves ; i++)
254 json_object_array_add(arr, json_object_new_int(board->history[i]));
256 w = winner(board->board);
258 json_object_object_add(resu, "winner", json_object_new_string_len(&w, 1));
259 else if (board->moves == 9)
260 json_object_object_add(resu, "winner", json_object_new_string("none"));
266 * signals a change of the board
268 static void changed(struct board *board, const char *reason)
271 WARNING(afbitf, "changed is unimplmented (reason was %s)", reason);
275 * retrieves the board of the request
277 static struct board *board_of_req(struct afb_req req)
279 return afb_req_context(req, (void*)get_new_board, (void*)release_board);
285 static void new(struct afb_req req)
289 /* retrieves the context for the session */
290 board = board_of_req(req);
293 memset(board->board, ' ', sizeof board->board);
297 afb_req_success(req, NULL, NULL);
300 changed(board, "new");
306 static void board(struct afb_req req)
308 afb_req_success(req, describe(board_of_req(req)), NULL);
314 static void move(struct afb_req req)
320 /* retrieves the context for the session */
321 board = board_of_req(req);
323 /* retrieves the parameters of the move */
324 index = afb_req_value(req, "index");
325 i = index == NULL ? -1 : atoi(index);
327 /* checks validity of parameters */
328 if (i < 0 || i > 8) {
329 afb_req_fail(req, "error", "bad request");
333 /* checks validity of the state */
334 if (winner(board->board) != 0) {
335 afb_req_fail(req, "error", "game terminated");
339 /* checks validity of the move */
340 if (board->board[i] != ' ') {
341 afb_req_fail(req, "error", "occupied");
345 /* applies the move */
349 afb_req_success(req, NULL, NULL);
352 changed(board, "move");
358 static void level(struct afb_req req)
364 /* retrieves the context for the session */
365 board = board_of_req(req);
367 /* retrieves the parameters */
368 level = afb_req_value(req, "level");
369 l = level == NULL ? -1 : atoi(level);
371 /* check validity of parameters */
372 if (l < 1 || l > 10) {
373 afb_req_fail(req, "error", "bad request");
379 afb_req_success(req, NULL, NULL);
382 changed(board, "level");
388 static void join(struct afb_req req)
393 /* retrieves the context for the session */
394 board = board_of_req(req);
396 /* retrieves the parameters */
397 board = board_of_req(req);
398 id = afb_req_value(req, "boardid");
402 /* check validity of parameters */
403 if (strcmp(id, "none")) {
404 board = get_new_board();
407 board = search_board(atoi(id));
413 afb_req_context_set(req, board, (void*)release_board);
416 afb_req_success(req, NULL, NULL);
420 afb_req_fail(req, "error", "bad request");
427 static void undo(struct afb_req req)
432 /* retrieves the context for the session */
433 board = board_of_req(req);
435 /* checks the state */
436 if (board->moves == 0) {
437 afb_req_fail(req, "error", "bad request");
441 /* undo the last move */
442 i = board->history[--board->moves];
443 board->board[i] = ' ';
446 afb_req_success(req, NULL, NULL);
449 changed(board, "undo");
455 static void play(struct afb_req req)
460 /* retrieves the context for the session */
461 board = board_of_req(req);
463 /* checks validity of the state */
464 if (winner(board->board) != 0 || board->moves == 9) {
465 afb_req_fail(req, "error", "game terminated");
469 /* gets the move and plays it */
470 index = get_move(board);
471 add_move(board, index);
474 afb_req_success(req, describe(board), NULL);
477 changed(board, "play");
481 * array of the verbs exported to afb-daemon
483 static const struct AFB_verb_desc_v1 verbs[] = {
484 /* VERB'S NAME SESSION MANAGEMENT FUNCTION TO CALL SHORT DESCRIPTION */
485 { .name= "new", .session= AFB_SESSION_NONE, .callback= new, .info= "Starts a new game" },
486 { .name= "play", .session= AFB_SESSION_NONE, .callback= play, .info= "Tells the server to play" },
487 { .name= "move", .session= AFB_SESSION_NONE, .callback= move, .info= "Tells the client move" },
488 { .name= "board", .session= AFB_SESSION_NONE, .callback= board, .info= "Get the current board" },
489 { .name= "level", .session= AFB_SESSION_NONE, .callback= level, .info= "Set the server level" },
490 { .name= "join", .session= AFB_SESSION_NONE, .callback= join, .info= "Join a board" },
491 { .name= "undo", .session= AFB_SESSION_NONE, .callback= undo, .info= "Undo the last move" },
492 /* marker for end of the array */
497 * description of the plugin for afb-daemon
499 static const struct AFB_plugin plugin_description =
501 /* description conforms to VERSION 1 */
502 .type= AFB_PLUGIN_VERSION_1,
503 .v1= { /* fills the v1 field of the union when AFB_PLUGIN_VERSION_1 */
504 .prefix= "tictactoe", /* the API name (or plugin name or prefix) */
505 .info= "Sample tac-tac-toe game", /* short description of of the plugin */
506 .verbs = verbs /* the array describing the verbs of the API */
511 * activation function for registering the plugin called by afb-daemon
513 const struct AFB_plugin *pluginAfbV1Register(const struct AFB_interface *itf)
515 afbitf = itf; // records the interface for accessing afb-daemon
516 return &plugin_description; // returns the description of the plugin