Cairo support works, both to draw in the window and to write as a png image in a...
[universe.git] / main.cc
1
2 // Written and (C) by Francois Fleuret
3 // Contact <francois.fleuret@idiap.ch> for comments & bug reports
4
5 #include <iostream>
6 #include <fstream>
7 #include <cmath>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <stdint.h>
11 #include <errno.h>
12 #include <string.h>
13
14 using namespace std;
15
16 #include "misc.h"
17 #include "task.h"
18 #include "simple_window.h"
19 #include "universe.h"
20 #include "retina.h"
21 #include "manipulator.h"
22 #include "intelligence.h"
23 #include "xfig_tracer.h"
24
25 #ifdef CAIRO_SUPPORT
26 #include <cairo.h>
27
28 static cairo_status_t write_cairo_to_file(void *closure,
29                                           const unsigned char *data,
30                                           unsigned int length) {
31   fwrite(data, 1, length, (FILE *) closure);
32   return CAIRO_STATUS_SUCCESS;
33 }
34
35 void generate_png(int width, int height, Universe *universe, FILE *file) {
36   const int depth = 4;
37   cairo_surface_t *image;
38   cairo_t* context_resource;
39   unsigned char *data;
40   data = new unsigned char [width * height * depth];
41
42   image = cairo_image_surface_create_for_data(data,
43                                               CAIRO_FORMAT_RGB24,
44                                               width,
45                                               height,
46                                               width * depth);
47
48   context_resource = cairo_create(image);
49
50   cairo_set_source_rgb(context_resource, 1.0, 1.0, 1.0);
51   cairo_rectangle(context_resource, 0, 0, width, height);
52   cairo_fill(context_resource);
53
54   universe->draw(context_resource);
55
56   cairo_surface_write_to_png_stream(image, write_cairo_to_file, file);
57
58   cairo_destroy(context_resource);
59   cairo_surface_destroy(image);
60
61   delete[] data;
62 }
63 #endif
64
65 // To train
66 // ./main --task hit_shape.task 0 --action-mode=random --nb-ticks=5000 --proportion-for-training=0.5 --save-file=dump.mem --no-window
67
68 // To test
69 // ./main --task hit_shape.task 0 --action-mode=intelligent --load-file=dump.mem
70
71 //////////////////////////////////////////////////////////////////////
72
73 void check_opt(int argc, char **argv, int n_opt, int n, const char *help) {
74   if(n_opt + n >= argc) {
75     cerr << "Missing argument for " << argv[n_opt] << "." << endl;
76     cerr << "Expecting " << help << "." << endl;
77     exit(1);
78   }
79 }
80
81 void print_help_and_exit(int e) {
82   cout << "Arguments:" << endl;
83   cout << "  --no-window" << endl;
84   cout << "  --nb-ticks=<int: number of ticks>" << endl;
85   cout << "  --nb-training-iterations=<int: number of training iterations>" << endl;
86   cout << "  --load-file=<filename: dump file>" << endl;
87   cout << "  --save-file=<filename: dump file>" << endl;
88   cout << "  --proportion-for-training=<float: proportion of samples for training>" << endl;
89   cout << "  --action-mode=<idle|random|intelligent>" << endl;
90   cout << "  --task <filename: task to load> <int: degree>" << endl;
91   exit(e);
92 }
93
94 //////////////////////////////////////////////////////////////////////
95
96 int main(int argc, char **argv) {
97
98   const int buffer_size = 256;
99   char intelligence_load_file[buffer_size] = "", intelligence_save_file[buffer_size] = "";
100
101   Task *task = 0;
102   int task_degree = 0;
103
104   int nb_ticks = 10000;
105   int nb_training_iterations = 10;
106   scalar_t proportion_for_training = -1;
107
108   Polygon *grabbed_polygon = 0;
109   scalar_t relative_grab_x = 0, relative_grab_y = 0;
110
111   bool quit = false;
112   bool press_shift = false;
113   enum { IDLE, RANDOM, INTELLIGENT } action_mode = IDLE;
114   bool got_event = true;
115   int current_action = 0;
116
117   scalar_t last_hand_x = 0, last_hand_y = 0;
118   Polygon *last_grabbing = 0;
119   bool no_window = false;
120
121   //////////////////////////////////////////////////////////////////////
122   //                    Parsing the shell arguments
123   //////////////////////////////////////////////////////////////////////
124
125   int i = 1;
126   while(i < argc) {
127     if(argc == 1 || strcmp(argv[i], "--help") == 0) print_help_and_exit(0);
128
129     else if(strcmp(argv[i], "--test") == 0) {
130       test_approximer();
131       exit(0);
132     }
133
134     else if(strcmp(argv[i], "--task") == 0) {
135       check_opt(argc, argv, i, 2, "<filename: task to load> <int: degree>");
136       if(task) {
137         cerr << "Can not load two tasks." << endl;
138         exit(1);
139       }
140       task = load_task(argv[i+1]);
141       task_degree = atoi(argv[i+2]);
142       i += 3;
143
144     } else if(strncmp(argv[i], "--", 2) == 0) {
145       char variable_name[buffer_size] = "", variable_value[buffer_size] = "";
146       char *o = argv[i]+2, *s = variable_name, *u = variable_value;
147       while(*o && *o != '=') *s++ = *o++;
148       if(*o) {
149         o++;
150         while(*o) *u++ = *o++;
151       }
152
153       if(strcmp(variable_name, "nb-ticks") == 0)
154         nb_ticks = atoi(variable_value);
155       else if(strcmp(variable_name, "nb-training-iterations") == 0)
156         nb_training_iterations = atoi(variable_value);
157       else if(strcmp(variable_name, "proportion-for-training") == 0)
158         proportion_for_training = atof(variable_value);
159       else if(strcmp(variable_name, "no-window") == 0)
160         no_window = true;
161       else if(strcmp(variable_name, "save-file") == 0)
162         strcpy(intelligence_save_file, variable_value);
163       else if(strcmp(variable_name, "load-file") == 0)
164         strcpy(intelligence_load_file, variable_value);
165       else if(strcmp(variable_name, "action-mode") == 0) {
166         if(strcmp(variable_value, "idle") == 0) action_mode = IDLE;
167         else if(strcmp(variable_value, "random") == 0) action_mode = RANDOM;
168         else if(strcmp(variable_value, "intelligent") == 0) action_mode = INTELLIGENT;
169         else {
170           cerr << "The only known modes are idle, random and intelligent" << endl;
171           exit(1);
172         }
173       } else {
174         cerr << "Unknown option " << argv[i] << endl;
175         print_help_and_exit(1);
176       }
177       i++;
178     } else {
179       cerr << "Unknown option " << argv[i] << endl;
180       print_help_and_exit(1);
181     }
182   }
183
184   cout << "FlatLand, a toy universe for goal-planning experiments." << endl;
185
186   if(!task) {
187     task = load_task("dummy.task");
188     task_degree = 0;
189   }
190
191   cout << "Loaded task " << task->name()
192        << " with degree " << task_degree << "/" << task->nb_degrees()
193        << endl;
194
195   if(task_degree < 0 || task_degree >= task->nb_degrees()) {
196     cout << "Invalid degree: " << task_degree << "." << endl;
197     exit(1);
198   }
199
200   //////////////////////////////////////////////////////////////////////
201   //                      Various initializations
202   //////////////////////////////////////////////////////////////////////
203
204   Universe universe(100, task->width(), task->height());
205   task->init(&universe, task_degree);
206   Retina retina(&universe);
207   Manipulator manipulator(task);
208   manipulator.force_move(task->width()/2, task->height()/2);
209   retina.set_location(manipulator.hand_x(), manipulator.hand_y());
210
211   SimpleWindow *window_main = 0, *window_brain = 0;
212 #ifdef CAIRO_SUPPORT
213   cairo_t *cairo_cr = 0;
214 #endif
215
216   int window_main_fd = -1;
217
218   MapConcatener sensory_map(2);
219   sensory_map.add_map(&retina);
220   sensory_map.add_map(&manipulator);
221   sensory_map.init();
222
223   MapExpander expanded_map(1000);
224   expanded_map.set_input(&sensory_map);
225   expanded_map.init();
226
227   Intelligence intelligence(&expanded_map, &manipulator, nb_ticks + 1, nb_training_iterations);
228   intelligence.update(0, 0.0);
229
230   if(intelligence_load_file[0]) {
231     cout << "Loading from " << intelligence_load_file << " ... " ;
232     cout.flush();
233     ifstream in(intelligence_load_file);
234     if(in.fail()) {
235       cerr << "error reading " << intelligence_load_file << "." << endl;
236       exit(1);
237     }
238     intelligence.load(in);
239     cout << "done." << endl ;
240   }
241
242   if(no_window) {
243     cout << "Started without windows." << endl;
244   } else {
245     window_main = new SimpleWindow("Universe (main window)", 4, 4, task->width(), task->height());
246     window_main_fd = window_main->file_descriptor();
247     window_main->map();
248 #ifdef CAIRO_SUPPORT
249     cairo_cr = window_main->get_cairo_context_resource();
250 #endif
251     cout << "When the main window has the focus, press `q' to quit and click and drag to move" << endl
252          << "objects." << endl;
253     window_brain = new SimpleWindow("Universe (brain)",
254                                     12 + task->width(), 4,
255                                     retina.width(), retina.height() + manipulator.parameter_height());
256     window_brain->map();
257   }
258
259   int tick = 0;
260   time_t last_t = 0;
261   scalar_t sum_reward = 0;
262
263   //////////////////////////////////////////////////////////////////////
264   //                         The main loop
265   //////////////////////////////////////////////////////////////////////
266
267   while(!quit && tick != nb_ticks) {
268
269     int r;
270     fd_set fds;
271
272
273     if(window_main) {
274       struct timeval tv;
275       FD_ZERO (&fds);
276       FD_SET (window_main_fd, &fds);
277       tv.tv_sec = 0;
278       tv.tv_usec = 5000; // 0.05s
279       r = select(window_main_fd + 1, &fds, 0, 0, &tv);
280     } else r = 0;
281
282     time_t t = time(0);
283
284     if(t > last_t) {
285       last_t = t;
286       cout << tick << " " << sum_reward << "              \r"; cout.flush();
287     }
288
289     if(r == 0) { // No window event, thus it's the clock tick
290
291         int nb_it = 10;
292
293         bool changed = got_event;
294         got_event = false;
295
296         switch(action_mode) {
297         case IDLE:
298           break;
299         case RANDOM:
300           current_action = manipulator.random_action();
301           break;
302         case INTELLIGENT:
303           current_action = intelligence.best_action();
304           //           if(drand48() < 0.5) current_action = intelligence.best_action();
305           //           else                current_action = manipulator.random_action();
306           break;
307         }
308
309         manipulator.do_action(current_action);
310
311         scalar_t dt = 1.0/scalar_t(nb_it);
312         for(int k = 0; k < nb_it; k++) {
313           manipulator.update(dt, &universe);
314           task->update(dt, &universe, &manipulator);
315           changed |= universe.update(dt);
316         }
317
318         tick++;
319
320         changed |= manipulator.hand_x() != last_hand_x ||
321           manipulator.hand_y() != last_hand_y ||
322           manipulator.grabbing() != last_grabbing;
323
324         scalar_t reward = task->reward(&universe, &manipulator);
325         sum_reward += abs(reward);
326         intelligence.update(current_action, reward);
327         expanded_map.update_map();
328
329         if(changed) {
330           last_hand_x = manipulator.hand_x();
331           last_hand_y = manipulator.hand_y();
332           last_grabbing = manipulator.grabbing();
333
334           retina.set_location(manipulator.hand_x(),
335                               manipulator.hand_y());
336
337           if(window_main) {
338             window_main->color(0.0, 0.0, 0.0);
339             window_main->color(1.0, 1.0, 1.0);
340             window_main->fill();
341             task->draw(window_main);
342
343 #ifdef CAIRO_SUPPORT
344             universe.draw(cairo_cr);
345 #else
346             universe.draw(window_main);
347 #endif
348
349             manipulator.draw_on_universe(window_main);
350             retina.draw_on_universe(window_main);
351
352             if(grabbed_polygon) {
353               int x, y, delta = 3;
354               x = int(grabbed_polygon->absolute_x(relative_grab_x, relative_grab_y));
355               y = int(grabbed_polygon->absolute_y(relative_grab_x, relative_grab_y));
356               window_main->color(0.0, 0.0, 0.0);
357               window_main->draw_line(x - delta, y, x + delta, y);
358               window_main->draw_line(x, y - delta, x, y + delta);
359             }
360
361             window_main->show();
362
363             if(window_brain) {
364               retina.draw_parameters(0, 0, window_brain);
365               manipulator.draw_parameters(0, retina.height() + 1, window_brain);
366               window_brain->show();
367             }
368           }
369         }
370
371     } else if(r > 0) { // We got window events, let's process them
372
373       got_event = true;
374
375       if(FD_ISSET(window_main_fd, &fds)) {
376
377         SimpleEvent se;
378
379         do {
380           se = window_main->event();
381
382           switch(se.type) {
383
384           case SimpleEvent::MOUSE_CLICK_PRESS:
385             {
386               switch(se.button) {
387
388               case 1:
389                 if(press_shift) {
390                   manipulator.force_move(se.x, se.y);
391                   manipulator.do_action(Manipulator::ACTION_GRAB);
392                 } else {
393                   grabbed_polygon = universe.pick_polygon(se.x, se.y);
394                   if(grabbed_polygon) {
395                     relative_grab_x = grabbed_polygon->relative_x(se.x, se.y);
396                     relative_grab_y = grabbed_polygon->relative_y(se.x, se.y);
397                   }
398                 }
399                 break;
400               case 4:
401                 {
402                   Polygon *g = universe.pick_polygon(se.x, se.y);
403                   if(g) g->_theta += M_PI/32;
404                 }
405                 break;
406               case 5:
407                 {
408                   Polygon *g = universe.pick_polygon(se.x, se.y);
409                   if(g) g->_theta -= M_PI/32;
410                 }
411                 break;
412               }
413             }
414             break;
415
416           case SimpleEvent::MOUSE_CLICK_RELEASE:
417             switch(se.button) {
418             case 1:
419               if(press_shift) manipulator.do_action(Manipulator::ACTION_RELEASE);
420               else            grabbed_polygon = 0;
421               break;
422             default:
423               break;
424             }
425
426           case SimpleEvent::MOUSE_MOTION:
427             {
428               if(press_shift) {
429                 manipulator.force_move(se.x, se.y);
430               } else if(grabbed_polygon) {
431                 scalar_t xf, yf, force_x, force_y, f, fmax = 100;
432                 xf = grabbed_polygon->absolute_x(relative_grab_x, relative_grab_y);
433                 yf = grabbed_polygon->absolute_y(relative_grab_x, relative_grab_y);
434                 force_x = se.x - xf;
435                 force_y = se.y - yf;
436                 f = sqrt(sq(force_x) + sq(force_y));
437                 if(f > fmax) { force_x = (force_x * fmax)/f; force_y = (force_y * fmax)/f; }
438                 grabbed_polygon->apply_force(0.1, xf, yf, force_x, force_y);
439               }
440               break;
441             }
442             break;
443
444           case SimpleEvent::KEY_PRESS:
445             {
446               if(strcmp(se.key, "q") == 0) {
447                 quit = true;
448               }
449
450               else if(strcmp(se.key, "s") == 0) {
451
452                 retina.save_as_ppm("/tmp/retina.ppm");
453                 cout << "Retina screen shot saved in /tmp/retina.ppm" << endl;
454
455                 {
456                   XFigTracer tracer("/tmp/universe.fig");
457                   universe.print_xfig(&tracer);
458                 }
459
460 #ifdef CAIRO_SUPPORT
461                 {
462                   FILE *file = fopen("/tmp/universe.png", "w");
463                   generate_png(task->width(), task->height(), &universe, file);
464                   // generate_png(task->width(), task->height(), &universe, "/tmp/universe.png");
465                   cout << "Universe image saved in /tmp/universe.png" << endl;
466                 }
467 #endif
468
469               }
470
471               else if(strcmp(se.key, "Shift_L") == 0 || strcmp(se.key, "Shift_R") == 0) {
472                 press_shift = true;
473               }
474
475               else if(strcmp(se.key, "Up") == 0) {
476                 manipulator.do_action(Manipulator::ACTION_MOVE_UP);
477               }
478
479               else if(strcmp(se.key, "Right") == 0) {
480                 manipulator.do_action(Manipulator::ACTION_MOVE_RIGHT);
481               }
482
483               else if(strcmp(se.key, "Down") == 0) {
484                 manipulator.do_action(Manipulator::ACTION_MOVE_DOWN);
485               }
486
487               else if(strcmp(se.key, "Left") == 0) {
488                 manipulator.do_action(Manipulator::ACTION_MOVE_LEFT);
489               }
490
491               else if(strcmp(se.key, "g") == 0) {
492                 manipulator.do_action(Manipulator::ACTION_GRAB);
493               }
494
495               else if(strcmp(se.key, "r") == 0) {
496                 manipulator.do_action(Manipulator::ACTION_RELEASE);
497               }
498
499               else if(strcmp(se.key, "space") == 0) {
500                 switch(action_mode) {
501                 case IDLE:
502                   action_mode = RANDOM;
503                   cout << "Switched to random mode" << endl;
504                   break;
505                 case RANDOM:
506                   action_mode = INTELLIGENT;
507                   cout << "Switched to intelligent mode" << endl;
508                   break;
509                 case INTELLIGENT:
510                   cout << "Switched to idle mode" << endl;
511                   action_mode = IDLE;
512                   break;
513                 }
514               }
515
516               else cout << "Undefined key " << se.key << endl;
517             }
518             break;
519
520           case SimpleEvent::KEY_RELEASE:
521             {
522               if(strcmp(se.key, "Shift_L") == 0 || strcmp(se.key, "Shift_R") == 0) press_shift = false;
523             }
524             break;
525
526           default:
527             break;
528
529           }
530         } while(se.type != SimpleEvent::NO_EVENT);
531       } else {
532         cerr << "Error on select: " << strerror(errno) << endl;
533         exit(1);
534       }
535     }
536   }
537
538   if(proportion_for_training > 0) {
539     cout << "Learning ... "; cout.flush();
540     intelligence.learn(proportion_for_training);
541     cout << "done." << endl;
542   }
543
544   if(intelligence_save_file[0]) {
545     cout << "Saving to " << intelligence_save_file << endl; cout.flush();
546     ofstream os(intelligence_save_file);
547     if(os.fail()) {
548       cerr << "error writing " << intelligence_save_file << "." << endl;
549       exit(1);
550     }
551     cout << "done." << endl;
552     intelligence.save(os);
553   }
554
555   delete window_brain;
556   delete window_main;
557
558 }