1 /* Animal, vegetable, mineral; or "20 questions"
2 * - by Richard W.M. Jones <rich@annexia.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the Free
16 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 * $Id: 04_animal_vegetable_mineral.c,v 1.8 2003/02/08 15:53:32 rich Exp $
31 #include "ml_window.h"
32 #include "ml_dialog.h"
34 #include "ml_form_text.h"
35 #include "ml_form_textarea.h"
36 #include "ml_form_submit.h"
37 #include "ml_table_layout.h"
38 #include "ml_text_label.h"
40 /* Main entry point to the app. */
41 static void app_main (ml_session);
44 handle_request (rws_request rq)
46 return ml_entry_point (rq, app_main);
49 /* This is the location of the questions database. You might want to
50 * put this somewhere more permanent than just in /tmp.
52 #define DATABASE_FILE "/tmp/questions_database"
54 /* Private per-session data. */
57 ml_window win; /* Top-level window. */
58 ml_dialog dialog; /* Dialog widget. */
59 ml_form_text input1; /* Input fields on the final form. */
60 ml_form_textarea input2;
61 ml_form_submit input3, input4;
62 int current_node; /* The node we are currently displaying. */
65 static void start_game (ml_session, void *);
68 app_main (ml_session session)
70 pool pool = ml_session_pool (session);
73 /* Create the per-session data area. */
74 data = pmalloc (pool, sizeof *data);
76 /* Create the top-level window. */
77 data->win = new_ml_window (session, pool);
79 /* Create the dialog widget. */
80 data->dialog = new_ml_dialog (pool);
81 ml_dialog_set_title (data->dialog, "Animal, Vegetable, Mineral game "
82 "(a.k.a. \"20 questions\")");
84 /* Start the game. This populates the initial dialog with the first
87 start_game (session, data);
89 /* Pack everything up. */
90 ml_window_pack (data->win, data->dialog);
93 /* This data is shared between sessions. */
94 static pool avm_pool; /* Pool for global allocations. */
95 static vector nodes; /* List of nodes. */
96 static int a, v, m; /* Index of animal, vegetable and mineral
97 * nodes in the list of nodes. */
99 static void avm_init (void) __attribute__((constructor));
100 static void avm_stop (void) __attribute__((destructor));
102 static void pressed_animal (ml_session, void *);
103 static void pressed_vegetable (ml_session, void *);
104 static void pressed_mineral (ml_session, void *);
105 static void pressed_yes (ml_session session, void *);
106 static void pressed_no (ml_session session, void *);
107 static void update_dialog (ml_session session, struct data *);
108 static void finished_game (ml_session session, void *);
109 static void unknown_thing (ml_session session, void *);
110 static void add_unknown_thing (ml_session session, void *);
111 static void save_database (void);
112 static int is_vowel (const char *);
113 static void remove_newlines (char *);
117 int is_question; /* Either a question or an answer node. */
119 /* For question nodes, this is the text of the question. For answer
120 * nodes, this is the expected answer word.
124 /* For question nodes only, this is the index of the nodes to visit
125 * if the user says 'yes' or 'no' respectively.
137 avm_pool = new_subpool (global_pool);
138 nodes = new_vector (avm_pool, struct node);
140 fp = fopen (DATABASE_FILE, "r");
143 /* No initial database. Don't worry, just create one internally. */
148 /* Node 0 is a dummy node. */
149 vector_push_back (nodes, node);
151 /* Node 1 is the animal node. */
152 node.is_question = 0;
154 vector_push_back (nodes, node);
156 /* Node 2 is the vegetable node. */
157 node.is_question = 0;
159 vector_push_back (nodes, node);
161 /* Node 3 is the mineral node. */
162 node.is_question = 0;
163 node.text = "pumice stone";
164 vector_push_back (nodes, node);
169 while (fgets (buffer, sizeof buffer, fp))
173 if (vector_size (nodes) == 0) /* Node 0 is special. */
175 if (sscanf (buffer, "q %d %d %d", &a, &v, &m)
178 fprintf (stderr, "%s: database is corrupt\n", DATABASE_FILE);
181 vector_push_back (nodes, node);
184 fprintf (stderr, "a = %d, v = %d, m = %d\n",
190 if (buffer[0] == 'q') /* Question node. */
194 node.is_question = 1;
196 if (sscanf (buffer, "q %d %d %n", &node.yes, &node.no, &n) < 2)
198 fprintf (stderr, "%s: database is corrupt\n", DATABASE_FILE);
202 node.text = pstrdup (avm_pool, &buffer[n]);
204 vector_push_back (nodes, node);
207 fprintf (stderr, "yes = %d, no = %d, text = %s\n",
208 node.yes, node.no, node.text);
211 else /* Answer node. */
213 node.is_question = 0;
215 node.text = pstrdup (avm_pool, &buffer[2]);
217 vector_push_back (nodes, node);
220 fprintf (stderr, "text = %s\n",
228 fprintf (stderr, "finished\n");
237 delete_pool (avm_pool);
247 fp = fopen (DATABASE_FILE, "w");
250 perror (DATABASE_FILE);
254 /* Node 0 is special. */
255 fprintf (fp, "q %d %d %d\n", a, v, m);
257 /* Write out the other nodes in order. */
258 for (i = 1; i < vector_size (nodes); ++i)
260 vector_get (nodes, i, node);
262 if (node.is_question)
263 fprintf (fp, "q %d %d %s\n", node.yes, node.no, node.text);
265 fprintf (fp, "a %s\n", node.text);
272 start_game (ml_session session, void *vp)
274 struct data *data = (struct data *) vp;
276 data->current_node = 0;
278 /* Ask the initial standard question. */
279 ml_dialog_set_text (data->dialog,
280 "<p>Think of something, anything.</p>"
281 "<p>I will ask you questions about it "
282 "and try to guess what it is.</p>"
283 "<p>First question: Is it an animal, a vegetable "
284 "or a mineral?</p>");
285 ml_dialog_clear_buttons (data->dialog);
286 ml_dialog_add_button (data->dialog, "Animal",
287 pressed_animal, session, data);
288 ml_dialog_add_button (data->dialog, "Vegetable",
289 pressed_vegetable, session, data);
290 ml_dialog_add_button (data->dialog, "Mineral",
291 pressed_mineral, session, data);
295 pressed_animal (ml_session session, void *vp)
297 struct data *data = (struct data *) vp;
299 data->current_node = a;
300 update_dialog (session, data);
304 pressed_vegetable (ml_session session, void *vp)
306 struct data *data = (struct data *) vp;
308 data->current_node = v;
309 update_dialog (session, data);
313 pressed_mineral (ml_session session, void *vp)
315 struct data *data = (struct data *) vp;
317 data->current_node = m;
318 update_dialog (session, data);
322 pressed_yes (ml_session session, void *vp)
324 struct data *data = (struct data *) vp;
327 vector_get (nodes, data->current_node, node);
328 data->current_node = node.yes;
329 update_dialog (session, data);
333 pressed_no (ml_session session, void *vp)
335 struct data *data = (struct data *) vp;
338 vector_get (nodes, data->current_node, node);
339 data->current_node = node.no;
340 update_dialog (session, data);
344 update_dialog (ml_session session, struct data *data)
346 pool pool = ml_session_pool (session);
349 /* Fetch the current node. */
350 vector_get (nodes, data->current_node, node);
352 if (node.is_question) /* Question node. */
354 /* Present the dialog window. */
355 ml_dialog_set_text (data->dialog, node.text);
357 /* Update the buttons. */
358 ml_dialog_clear_buttons (data->dialog);
359 ml_dialog_add_button (data->dialog, "Yes",
360 pressed_yes, session, data);
361 ml_dialog_add_button (data->dialog, "No",
362 pressed_no, session, data);
364 else /* Answer node. */
366 /* Present the dialog window. */
367 ml_dialog_set_text (data->dialog,
370 (!is_vowel (node.text) ? "a" : "an"),
373 /* Update the buttons. */
374 ml_dialog_clear_buttons (data->dialog);
375 ml_dialog_add_button (data->dialog, "Yes",
376 finished_game, session, data);
377 ml_dialog_add_button (data->dialog, "No",
378 unknown_thing, session, data);
383 is_vowel (const char *text)
387 if (isalpha ((int) *text))
389 return *text == 'a' || *text == 'e' || *text == 'i' ||
390 *text == 'o' || *text == 'u' || *text == 'A' || *text == 'E' ||
391 *text == 'I' || *text == 'O' || *text == 'U';
400 finished_game (ml_session session, void *vp)
402 struct data *data = (struct data *) vp;
404 ml_dialog_set_text (data->dialog, "Thanks for playing!");
405 ml_dialog_clear_buttons (data->dialog);
406 ml_dialog_add_button (data->dialog, "Play again",
407 start_game, session, data);
411 unknown_thing (ml_session session, void *vp)
413 pool pool = ml_session_pool (session);
414 struct data *data = (struct data *) vp;
417 ml_text_label text1, text2, text3;
420 vector_get (nodes, data->current_node, node); /* Fetch the node. */
422 /* Generate the form used to ask the user to input a new question. */
423 form = new_ml_form (pool);
424 ml_form_set_callback (form, add_unknown_thing, session, data);
426 /* The form contains a table layout ... */
427 tbl = new_ml_table_layout (pool, 6, 2);
429 /* Populate the form. */
430 text1 = new_ml_text_label (pool,
431 "I give up! What was the thing you were "
433 ml_table_layout_pack (tbl, text1, 0, 0);
434 ml_table_layout_set_colspan (tbl, 0, 0, 2);
436 data->input1 = new_ml_form_text (pool, form);
437 ml_table_layout_pack (tbl, data->input1, 1, 0);
438 ml_table_layout_set_colspan (tbl, 1, 0, 2);
440 text2 = new_ml_text_label
443 "Give me a simple yes/no question which I can "
444 "ask people, to distinguish between your thing "
446 (!is_vowel (node.text) ? "a" : "an"),
448 ml_table_layout_pack (tbl, text2, 2, 0);
449 ml_table_layout_set_colspan (tbl, 2, 0, 2);
451 data->input2 = new_ml_form_textarea (pool, form, 4, 60);
452 ml_table_layout_pack (tbl, data->input2, 3, 0);
453 ml_table_layout_set_colspan (tbl, 3, 0, 2);
455 text3 = new_ml_text_label
457 "And if I asked the question above of someone, and they "
458 "were thinking of your thing, would they answer Yes or No?");
459 ml_table_layout_pack (tbl, text3, 4, 0);
460 ml_table_layout_set_colspan (tbl, 4, 0, 2);
462 data->input3 = new_ml_form_submit (pool, form, "Yes");
463 ml_table_layout_pack (tbl, data->input3, 5, 0);
465 data->input4 = new_ml_form_submit (pool, form, "No");
466 ml_table_layout_pack (tbl, data->input4, 5, 1);
469 ml_form_pack (form, tbl);
470 ml_window_pack (data->win, form);
474 add_unknown_thing (ml_session session, void *vp)
476 pool pool = ml_session_pool (session);
477 struct data *data = (struct data *) vp;
478 int old_ans, new_ans;
479 struct node old_ans_node, new_q_node, new_ans_node;
480 char *name, *question;
481 int yes_button, no_button;
483 vector_get (nodes, data->current_node, old_ans_node); /* Fetch the node. */
485 /* Extract the values of the input fields. */
486 name = pstrdup (pool, ml_form_input_get_value (data->input1));
487 question = pstrdup (pool, ml_form_input_get_value (data->input2));
488 yes_button = ml_form_input_get_value (data->input3) ? 1 : 0;
489 no_button = ml_form_input_get_value (data->input4) ? 1 : 0;
491 assert ((yes_button || no_button) && ! (yes_button && no_button));
493 /* Remove newlines from the fields (replace with whitespace). This is
494 * because the database file cannot handle newlines.
496 remove_newlines (name);
497 remove_newlines (question);
499 /* Update the tree. */
500 old_ans = vector_size (nodes);
501 vector_push_back (nodes, old_ans_node);
503 new_ans = vector_size (nodes);
504 assert (new_ans == old_ans + 1);
505 new_ans_node.is_question = 0;
506 new_ans_node.text = pstrdup (avm_pool, name);
507 vector_push_back (nodes, new_ans_node);
509 new_q_node.is_question = 1;
510 new_q_node.text = pstrdup (avm_pool, question);
511 new_q_node.yes = yes_button ? new_ans : old_ans;
512 new_q_node.no = no_button ? new_ans : old_ans;
513 vector_replace (nodes, data->current_node, new_q_node);
517 /* Replace the form with the original dialog. */
518 ml_window_pack (data->win, data->dialog);
520 /* Restart the game. */
521 start_game (session, data);
525 remove_newlines (char *str)
529 if (*str == '\n') *str = ' ';