24 * marked as is: WALLS. |
22 * marked as is: WALLS. |
25 * All lines must be of the same length. |
23 * All lines must be of the same length. |
26 */ |
24 */ |
27 |
25 |
28 |
26 |
29 float percent_to_show = 0; |
|
30 int depth_to_show = 0; |
|
31 |
|
32 int max_depth = 0; |
|
33 int min_depth_period = 0; |
|
34 int max_depth_period = 0; |
|
35 struct Map * actual_map; |
|
36 |
27 |
37 |
28 |
38 |
29 |
39 |
30 |
40 int InverseMove(char Dir1, char Dir2) |
|
41 { |
|
42 if ((Dir1 + Dir2 == DIR_LEFT + DIR_RIGHT) || |
|
43 (Dir1 + Dir2 == DIR_UP + DIR_DOWN)) |
|
44 return 1; |
|
45 return 0; |
|
46 } |
|
47 |
|
48 |
|
49 void PrintCombination(int Moves[], int NumMoves) |
|
50 { |
|
51 int i; |
|
52 |
|
53 for (i=0; i < NumMoves; i++) |
|
54 { |
|
55 printf("%i", Moves[i] >> 2); |
|
56 if ((Moves[i] & 0x03) == DIR_LEFT) |
|
57 printf("L"); |
|
58 else if ((Moves[i] & 0x03) == DIR_RIGHT) |
|
59 printf("R"); |
|
60 else if ((Moves[i] & 0x03) == DIR_UP) |
|
61 printf("U"); |
|
62 else |
|
63 printf("D"); |
|
64 printf(","); |
|
65 } |
|
66 printf("\n"); |
|
67 } |
|
68 |
|
69 #if 0 /*** THIS IS AN ERROR!!! The box_will_be_blocked function doesn't work!*/ |
|
70 Situation: |
|
71 |
|
72 ->@$ # |
|
73 $ |
|
74 */ |
|
75 int box_will_be_blocked(const struct Map * m, const struct Position mov, |
|
76 const struct Position box_pos) |
|
77 { |
|
78 struct Position next_pos2, tmp, tmp2[2]; |
|
79 int i; |
|
80 |
|
81 next_pos2.x = box_pos.x + mov.x; |
|
82 next_pos2.y = box_pos.y + mov.y; |
|
83 |
|
84 tmp.x = next_pos2.x + mov.x; |
|
85 tmp.y = next_pos2.y + mov.y; |
|
86 tmp2[0].x = next_pos2.x + mov.y; |
|
87 tmp2[0].y = next_pos2.y + mov.x; |
|
88 tmp2[1].x = next_pos2.x - mov.y; |
|
89 tmp2[1].y = next_pos2.y - mov.x; |
|
90 for (i=0; i < 2; i++) |
|
91 { |
|
92 if (m->man_moves[tmp.y][tmp.x] == WALL && |
|
93 m->man_moves[tmp2[i].y][tmp2[i].x] == BOX) |
|
94 { |
|
95 return TRUE; |
|
96 } |
|
97 else if (m->man_moves[tmp.y][tmp.x] == BOX && |
|
98 m->man_moves[tmp2[i].y][tmp2[i].x] == WALL) |
|
99 { |
|
100 return TRUE; |
|
101 } |
|
102 else if (m->man_moves[tmp.y][tmp.x] == BOX && |
|
103 m->man_moves[tmp2[i].y][tmp2[i].x] == BOX) |
|
104 { |
|
105 return TRUE; |
|
106 } |
|
107 } |
|
108 |
|
109 return FALSE; |
|
110 } |
|
111 |
|
112 int is_corner_area(const struct Map * m, const struct Position p, |
|
113 const struct Position box, const struct Position new_box) |
|
114 { |
|
115 int NumMoves, NewMoves; |
|
116 struct Position pos[MAX_MOVES]; |
|
117 struct Position new_pos[MAX_MOVES]; |
|
118 char corners[MAX_Y][MAX_X]; |
|
119 int i,j; |
|
120 struct Position next_pos; |
|
121 char *next_cell; |
|
122 |
|
123 |
|
124 /* Blank the garden */ |
|
125 for (j = 0; j<m->SizeY; j++) |
|
126 for (i=0; i<m->SizeX; i++) |
|
127 corners[j][i] = m->Cells[j][i]; |
|
128 |
|
129 /* Let's put the boxes */ |
|
130 for (i = 0; i<m->NumBoxes; i++) |
|
131 corners[m->Box[i].y][m->Box[i].x] = BOX; |
|
132 |
|
133 /* Let's put our box - it can be simply added */ |
|
134 corners[new_box.y][new_box.x] = BOX; |
|
135 /* Let's remove the original box. */ |
|
136 corners[box.y][box.x] = BLANK; |
|
137 |
|
138 NewMoves = 1; |
|
139 new_pos[0] = p; |
|
140 while (NewMoves > 0) |
|
141 { |
|
142 /* The before named "New Moves" become the moves we have |
|
143 to analyze */ |
|
144 NumMoves = NewMoves; |
|
145 for (i=0; i<NewMoves; i++) |
|
146 { |
|
147 pos[i] = new_pos[i]; |
|
148 } |
|
149 |
|
150 /* Search new positions for each position */ |
|
151 NewMoves = 0; |
|
152 for (i=0; i<NumMoves; i++) |
|
153 { |
|
154 /* For each direction */ |
|
155 for (j=0; j<4; j++) |
|
156 { |
|
157 next_pos.x = pos[i].x + move_vectors[j].x; |
|
158 next_pos.y = pos[i].y + move_vectors[j].y; |
|
159 next_cell = &corners[next_pos.y][next_pos.x]; |
|
160 if(*next_cell == BLANK || |
|
161 *next_cell == PLATFORM) |
|
162 { |
|
163 return FALSE; |
|
164 } |
|
165 else if(*next_cell == CORNER) |
|
166 { |
|
167 new_pos[NewMoves] = next_pos; |
|
168 *next_cell = MANCANMOVE; |
|
169 NewMoves++; |
|
170 } |
|
171 } |
|
172 } |
|
173 } |
|
174 |
|
175 return TRUE; |
|
176 } |
|
177 |
|
178 int does_box_close_corners(const struct Map * m, const struct Position mov, |
|
179 const struct Position box_pos) |
|
180 { |
|
181 struct Position p, p2; |
|
182 |
|
183 p.x = box_pos.x + mov.x; |
|
184 p.y = box_pos.y + mov.y; |
|
185 |
|
186 /* Let's plan the checks */ |
|
187 /* The point will be marked with a MANCANMOVE */ |
|
188 p2.x = p.x + mov.x; |
|
189 p2.y = p.y + mov.y; |
|
190 if (m->Cells[p2.y][p2.x] == CORNER) |
|
191 { |
|
192 if (is_corner_area(m, p2, box_pos, p)) |
|
193 return TRUE; |
|
194 } |
|
195 |
|
196 p2.x = p.x + mov.y; |
|
197 p2.y = p.y + mov.x; |
|
198 if (m->Cells[p2.y][p2.x] == CORNER) |
|
199 { |
|
200 if (is_corner_area(m, p2, box_pos, p)) |
|
201 return TRUE; |
|
202 } |
|
203 |
|
204 p2.x = p.x - mov.y; |
|
205 p2.y = p.y - mov.x; |
|
206 if (m->Cells[p2.y][p2.x] == CORNER) |
|
207 { |
|
208 if (is_corner_area(m, p2, box_pos, p)) |
|
209 return TRUE; |
|
210 } |
|
211 return FALSE; |
|
212 } |
|
213 #endif |
|
214 |
|
215 /* |
|
216 Catch the situation where a box is moved next to a corner, where the box |
|
217 next to it will not be able to be moved. |
|
218 */ |
|
219 int is_dead1(const struct Map * m, const struct Position mov, |
|
220 const struct Position new_pos) |
|
221 { |
|
222 struct Position opposite1, opposite2; |
|
223 |
|
224 /* The wall corners must exist */ |
|
225 opposite1.x = -mov.y; |
|
226 opposite1.y = -mov.x; |
|
227 opposite2.x = mov.y; |
|
228 opposite2.y = mov.x; |
|
229 |
|
230 #ifdef DEBUG |
|
231 ShowMap(m); |
|
232 #endif |
|
233 |
|
234 /* Test the first corner */ |
|
235 if (m->Cells[new_pos.y+mov.y+opposite1.y][new_pos.x+mov.x+opposite1.x] |
|
236 == WALL) |
|
237 { |
|
238 /* Test wall at opposites 1*/ |
|
239 if (m->Cells[new_pos.y+opposite1.y][new_pos.x+opposite1.x] |
|
240 == WALL && |
|
241 m->man_moves[new_pos.y+mov.y][new_pos.x+mov.x] == BOX) |
|
242 { |
|
243 return TRUE; |
|
244 } |
|
245 else |
|
246 if (m->man_moves[new_pos.y+opposite1.y][new_pos.x+opposite1.x] |
|
247 == BOX && |
|
248 m->Cells[new_pos.y+mov.y][new_pos.x+mov.x] == WALL) |
|
249 { |
|
250 return TRUE; |
|
251 } |
|
252 /* Test wall at opposites 2 */ |
|
253 if (m->Cells[new_pos.y+opposite2.y][new_pos.x+opposite2.x] |
|
254 == WALL && |
|
255 m->man_moves[new_pos.y+mov.y][new_pos.x+mov.x] == BOX) |
|
256 { |
|
257 return TRUE; |
|
258 } |
|
259 else |
|
260 if (m->man_moves[new_pos.y+opposite2.y][new_pos.x+opposite2.x] |
|
261 == BOX && |
|
262 m->Cells[new_pos.y+mov.y][new_pos.x+mov.x] == WALL) |
|
263 { |
|
264 return TRUE; |
|
265 } |
|
266 } |
|
267 |
|
268 /* Test the second corner */ |
|
269 if (m->Cells[new_pos.y+mov.y+opposite2.y][new_pos.x+mov.x+opposite2.x] |
|
270 == WALL) |
|
271 { |
|
272 /* Test wall at opposites 1*/ |
|
273 if (m->Cells[new_pos.y+opposite1.y][new_pos.x+opposite1.x] |
|
274 == WALL && |
|
275 m->man_moves[new_pos.y+mov.y][new_pos.x+mov.x] == BOX) |
|
276 { |
|
277 return TRUE; |
|
278 } |
|
279 else |
|
280 if (m->man_moves[new_pos.y+opposite1.y][new_pos.x+opposite1.x] |
|
281 == BOX && |
|
282 m->Cells[new_pos.y+mov.y][new_pos.x+mov.x] == WALL) |
|
283 { |
|
284 return TRUE; |
|
285 } |
|
286 /* Test wall at opposites 2 */ |
|
287 if (m->Cells[new_pos.y+opposite2.y][new_pos.x+opposite2.x] |
|
288 == WALL && |
|
289 m->man_moves[new_pos.y+mov.y][new_pos.x+mov.x] == BOX) |
|
290 { |
|
291 return TRUE; |
|
292 } |
|
293 else |
|
294 if (m->man_moves[new_pos.y+opposite2.y][new_pos.x+opposite2.x] |
|
295 == BOX && |
|
296 m->Cells[new_pos.y+mov.y][new_pos.x+mov.x] == WALL) |
|
297 { |
|
298 return TRUE; |
|
299 } |
|
300 } |
|
301 |
|
302 return FALSE; |
|
303 } |
|
304 |
|
305 /* |
|
306 Catch the situation where a corner gets surrounded by boxes. |
|
307 */ |
|
308 int is_dead2(const struct Map * m, const struct Position mov, |
|
309 const struct Position new_pos) |
|
310 { |
|
311 struct Position next, next2, opposite1, opposite2; |
|
312 |
|
313 next.x = new_pos.x+mov.x; |
|
314 next.y = new_pos.y+mov.y; |
|
315 |
|
316 /* The corner must exist */ |
|
317 if (m->Cells[next.y][next.x] != CORNER) |
|
318 return FALSE; |
|
319 |
|
320 |
|
321 /* The wall corners must exist */ |
|
322 opposite1.x = next.x -mov.y; |
|
323 opposite1.y = next.y -mov.x; |
|
324 opposite2.x = next.x + mov.y; |
|
325 opposite2.y = next.y + mov.x; |
|
326 |
|
327 if (m->man_moves[opposite1.y][opposite1.x] == BOX) |
|
328 { |
|
329 if(m->Cells[opposite1.y+mov.y][opposite1.x+mov.x] == WALL |
|
330 && m->Cells[opposite1.y-mov.y][opposite1.x-mov.x] == WALL) |
|
331 return TRUE; |
|
332 } |
|
333 |
|
334 if (m->man_moves[opposite2.y][opposite2.x] == BOX) |
|
335 { |
|
336 if(m->Cells[opposite2.y+mov.y][opposite2.x+mov.x] == WALL |
|
337 && m->Cells[opposite2.y-mov.y][opposite2.x-mov.x] == WALL) |
|
338 return TRUE; |
|
339 } |
|
340 return FALSE; |
|
341 } |
|
342 |
|
343 int is_box_movable(const struct Map * m, const struct Position mov, |
|
344 const struct Position box_pos) |
|
345 { |
|
346 struct Position next_pos2; |
|
347 |
|
348 next_pos2.x = box_pos.x + mov.x; |
|
349 next_pos2.y = box_pos.y + mov.y; |
|
350 |
|
351 if((m->Cells[next_pos2.y][next_pos2.x] != BLANK && |
|
352 m->Cells[next_pos2.y][next_pos2.x] != PLATFORM) || |
|
353 m->man_moves[next_pos2.y][next_pos2.x] == BOX) |
|
354 { |
|
355 return FALSE; |
|
356 } |
|
357 else if (is_dead1(m, mov, next_pos2) == TRUE) |
|
358 { |
|
359 return FALSE; |
|
360 } |
|
361 else if (is_dead2(m, mov, next_pos2) == TRUE) |
|
362 { |
|
363 return FALSE; |
|
364 } |
|
365 return TRUE; |
|
366 } |
|
367 |
|
368 /* It modifies m->man_moves */ |
|
369 int get_box_movements(struct Map * m, |
|
370 struct BoxMove movements[]) |
|
371 { |
|
372 struct Position pos[MAX_MOVES]; |
|
373 struct Position new_pos[MAX_MOVES]; |
|
374 int NumMoves, NewMoves; |
|
375 int j, i; |
|
376 struct Position next_pos; |
|
377 char *next_cell; |
|
378 int num_box_movements = 0; |
|
379 |
|
380 /* Let's the map with only walls in man_moves - other, blanks */ |
|
381 for (j = 0; j<m->SizeY; j++) |
|
382 for (i=0; i<m->SizeX; i++) |
|
383 { |
|
384 if (m->Cells[j][i] == WALL) |
|
385 m->man_moves[j][i] = WALL; |
|
386 else |
|
387 m->man_moves[j][i] = BLANK; |
|
388 } |
|
389 |
|
390 /* Let's put the boxes */ |
|
391 for (i = 0; i<m->NumBoxes; i++) |
|
392 { |
|
393 m->man_moves[m->Box[i].y][m->Box[i].x] = BOX; |
|
394 m->cells_boxes[m->Box[i].y][m->Box[i].x] = i; |
|
395 } |
|
396 |
|
397 NewMoves = 1; |
|
398 new_pos[0].x = m->Man.x; |
|
399 new_pos[0].y = m->Man.y; |
|
400 m->man_moves[m->Man.y][m->Man.x] = MANCANMOVE; |
|
401 while (NewMoves > 0) |
|
402 { |
|
403 /* The before named "New Moves" become the moves we have |
|
404 to analyze */ |
|
405 NumMoves = NewMoves; |
|
406 for (i=0; i<NewMoves; i++) |
|
407 { |
|
408 pos[i] = new_pos[i]; |
|
409 } |
|
410 |
|
411 /* Search new positions for each position */ |
|
412 NewMoves = 0; |
|
413 for (i=0; i<NumMoves; i++) |
|
414 { |
|
415 /* For each direction */ |
|
416 for (j=0; j<4; j++) |
|
417 { |
|
418 next_pos.x = pos[i].x + move_vectors[j].x; |
|
419 next_pos.y = pos[i].y + move_vectors[j].y; |
|
420 next_cell = &m->man_moves[next_pos.y][next_pos.x]; |
|
421 if(*next_cell == BLANK) |
|
422 { |
|
423 new_pos[NewMoves] = next_pos; |
|
424 *next_cell = MANCANMOVE; |
|
425 NewMoves++; |
|
426 } |
|
427 else if (*next_cell == BOX) |
|
428 { |
|
429 |
|
430 /* Check if the box is movable */ |
|
431 |
|
432 if (is_box_movable(m, move_vectors[j], |
|
433 next_pos )) |
|
434 { |
|
435 { |
|
436 movements[num_box_movements].box = |
|
437 m->cells_boxes[next_pos.y][next_pos.x]; |
|
438 movements[num_box_movements].dir = |
|
439 move_vectors[j]; |
|
440 num_box_movements++; |
|
441 } |
|
442 } |
|
443 |
|
444 } |
|
445 |
|
446 } |
|
447 } |
|
448 } |
|
449 |
|
450 return num_box_movements; |
|
451 } |
|
452 |
|
453 void force_move_box(struct Map *m, struct BoxMove move) |
|
454 { |
|
455 struct Position newpos; |
|
456 |
|
457 |
|
458 /* Add coords */ |
|
459 newpos.x = m->Box[move.box].x + move.dir.x; |
|
460 newpos.y = m->Box[move.box].y + move.dir.y; |
|
461 |
|
462 /* Be certain the move can be done */ |
|
463 assert(m->Cells[newpos.y][newpos.x] != BOX); |
|
464 assert(m->Cells[newpos.y][newpos.x] != WALL); |
|
465 |
|
466 /* Control if we moved the box to a platform */ |
|
467 if(m->Cells[newpos.y][newpos.x] == PLATFORM) |
|
468 { |
|
469 m->NumBoxesInPlatform++; |
|
470 } |
|
471 |
|
472 /* Control if we moved the box from a platform */ |
|
473 if (m->Cells[m->Box[move.box].y][m->Box[move.box].x] == PLATFORM) |
|
474 { |
|
475 m->NumBoxesInPlatform--; |
|
476 } |
|
477 m->Man = m->Box[move.box]; |
|
478 |
|
479 m->Box[move.box] = newpos; |
|
480 } |
|
481 |
|
482 int are_boxes_equal(const struct Position b1[], const struct Position b2[], |
|
483 int n) |
|
484 { |
|
485 int i; |
|
486 char tmp[MAX_Y][MAX_X]; /* !!!argh */ |
|
487 |
|
488 memset(tmp, 0, sizeof(tmp)); |
|
489 |
|
490 for (i=0; i < n; i++) |
|
491 { |
|
492 tmp[b1[i].y][b1[i].x] = 1; |
|
493 } |
|
494 for (i=0; i < n; i++) |
|
495 { |
|
496 if (tmp[b2[i].y][b2[i].x] != 1) |
|
497 return FALSE; |
|
498 } |
|
499 return TRUE; |
|
500 } |
|
501 |
|
502 int is_new_map(struct Map maps[], int depth) |
|
503 { |
|
504 struct Map *m; |
|
505 int i; |
|
506 |
|
507 m = &maps[depth]; |
|
508 |
|
509 for(i=0; i<max_depth; i++) |
|
510 { |
|
511 /* No l'hem de comparar amb ell mateix */ |
|
512 if (i == depth) |
|
513 continue; |
|
514 |
|
515 if (m->NumBoxesInPlatform != maps[i].NumBoxesInPlatform) |
|
516 continue; |
|
517 else |
|
518 { |
|
519 if (!are_boxes_equal(m->Box, maps[i].Box, m->NumBoxes)) |
|
520 continue; |
|
521 } |
|
522 return FALSE; |
|
523 } |
|
524 return TRUE; |
|
525 } |
|
526 |
|
527 void PrintMove(struct BoxMove b) |
|
528 { |
|
529 printf("Box: %i, Direction: {%i,%i}\n", b.box, b.dir.x, b.dir.y); |
|
530 } |
|
531 |
|
532 void show_percent_callback(int parameter) |
|
533 { |
|
534 fprintf(stderr, "Percent: %2.12f, depth: %i-%i\n", percent_to_show, |
|
535 min_depth_period, max_depth_period); |
|
536 ShowMap(actual_map); |
|
537 fflush(stderr); |
|
538 min_depth_period = MAX_STEPS; |
|
539 max_depth_period = 0; |
|
540 #ifndef DEBUG |
|
541 alarm(ALARM_SECONDS); |
|
542 #endif |
|
543 } |
|
544 |
|
545 int search_depth(struct Map maps[], int depth, |
|
546 struct BoxMove movements[], |
|
547 int num_movements, float percent, float total_percent) |
|
548 { |
|
549 struct BoxMove new_movements[MAX_MOVES]; |
|
550 int num_new_movements; |
|
551 struct Map *m; |
|
552 int i; |
|
553 float next_percent; |
|
554 |
|
555 next_percent = percent / num_movements; |
|
556 |
|
557 m = &maps[depth+1]; |
|
558 if (depth > max_depth) |
|
559 max_depth = depth; |
|
560 if (depth > max_depth_period) |
|
561 max_depth_period = depth; |
|
562 if (depth < min_depth_period) |
|
563 min_depth_period = depth; |
|
564 |
|
565 for (i=0; i < num_movements; i++) |
|
566 { |
|
567 CopyMap(m, &maps[depth]); |
|
568 force_move_box(m, movements[i]); |
|
569 if (m->NumPlatforms == m->NumBoxesInPlatform) |
|
570 { |
|
571 printf("\nSolved!\n"); |
|
572 PrintMove(movements[i]); |
|
573 return 0; |
|
574 } |
|
575 |
|
576 if (is_new_map(maps, depth+1)) |
|
577 { |
|
578 num_new_movements = get_box_movements(m, new_movements); |
|
579 assert(num_new_movements < MAX_MOVES); |
|
580 } |
|
581 else |
|
582 num_new_movements = 0; |
|
583 |
|
584 if (num_new_movements == 0) |
|
585 { |
|
586 percent_to_show = total_percent + next_percent*i; |
|
587 depth_to_show = depth; |
|
588 actual_map = &maps[depth]; |
|
589 #ifdef DEBUG /* to be out */ |
|
590 show_percent_callback(0); |
|
591 #endif |
|
592 |
|
593 } |
|
594 else |
|
595 { |
|
596 if (depth+1 < MAX_STEPS) |
|
597 { |
|
598 if(search_depth(maps, depth+1, new_movements, |
|
599 num_new_movements, next_percent, |
|
600 total_percent + next_percent*i) == 0) |
|
601 { |
|
602 PrintMove(movements[i]); |
|
603 return 0; |
|
604 } |
|
605 } |
|
606 } |
|
607 } |
|
608 return 1; |
|
609 } |
|
610 |
|
611 |
|
612 |
|
613 |
|
614 void program_alarm() |
|
615 { |
|
616 struct sigaction my_action; |
|
617 int result; |
|
618 |
|
619 my_action.sa_handler = show_percent_callback; |
|
620 my_action.sa_flags = 0; |
|
621 memset(&my_action.sa_mask, 0, sizeof(my_action.sa_mask)); |
|
622 |
|
623 result = sigaction(SIGALRM, &my_action, NULL); |
|
624 assert(result == 0); |
|
625 |
|
626 alarm(1); |
|
627 } |
|
628 |
31 |
629 int main(int argn, char **argv) |
32 int main(int argn, char **argv) |
630 { |
33 { |
631 struct Map Morigin; |
34 struct Map Morigin; |
632 struct Map maps[MAX_STEPS+1]; |
|
633 struct BoxMove new_movements[MAX_MOVES]; |
|
634 int num_new_movements; |
|
635 |
|
636 |
35 |
637 if (argn != 2) |
36 if (argn != 2) |
638 { |
37 { |
639 printf("Usage: %s <mapfile>\n", argv[0]); |
38 printf("Usage: %s <mapfile>\n", argv[0]); |
640 exit(3); |
39 exit(3); |
641 } |
40 } |
642 ReadMap(&Morigin, argv[1]); |
41 ReadMap(&Morigin, argv[1]); |
|
42 assert(Morigin.NumPlatforms > Morigin.NumBoxesInPlatform); |
643 |
43 |
644 // Reget the original map |
44 init_os(); |
645 CopyMap(&maps[0], &Morigin); |
|
646 |
45 |
647 assert(maps[0].NumPlatforms > maps[0].NumBoxesInPlatform); |
46 return solve_map(Morigin); |
648 |
|
649 num_new_movements = get_box_movements(&maps[0], new_movements); |
|
650 assert(num_new_movements < MAX_MOVES); |
|
651 assert(num_new_movements > 0); |
|
652 |
|
653 #ifndef DEBUG |
|
654 program_alarm(); |
|
655 #endif |
|
656 search_depth(maps, 0, new_movements, |
|
657 num_new_movements, 100. / num_new_movements, |
|
658 0); |
|
659 |
|
660 return 0; |
|
661 |
|
662 } |
47 } |