1 /*
2 * Copyright (C) 2004 Ivo Danihelka (ivo@danihelka.net)
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 */
9 #include "Rules.h"
10
11 #include "Cube.h"
12 #include "MarkMask.h"
13
14 #include "Log.h"
15 #include "LayoutException.h"
16 #include "OnStack.h"
17 #include "OnWall.h"
18 #include "OnStrongPad.h"
19 #include "OptionAgent.h"
20
21 #include <assert.h>
22
23 //-----------------------------------------------------------------
24 /**
25 * Create new rules for model.
26 */
Rules(Cube * model)27 Rules::Rules(Cube *model)
28 {
29 m_readyToDie = false;
30 m_readyToTurn = false;
31 m_readyToActive = false;
32 m_dir = Dir::DIR_NO;
33 m_pushing = false;
34 m_outDepth = 0;
35 m_touchDir = Dir::DIR_NO;
36
37 m_model = model;
38 m_mask = NULL;
39 m_lastFall = false;
40 }
41 //-----------------------------------------------------------------
42 /**
43 * Unmask from field.
44 */
~Rules()45 Rules::~Rules()
46 {
47 if (m_mask) {
48 m_mask->unmask();
49 delete m_mask;
50 }
51 }
52 //-----------------------------------------------------------------
53 /**
54 * Connect model with field.
55 * @throws LayoutException when location is occupied
56 */
57 void
takeField(Field * field)58 Rules::takeField(Field *field)
59 {
60 if (m_mask) {
61 m_mask->unmask();
62 delete m_mask;
63 m_mask = NULL;
64 }
65
66 m_mask = new MarkMask(m_model, field);
67 Cube::t_models resist = m_mask->getResist(Dir::DIR_NO);
68 if (!resist.empty()) {
69 throw LayoutException(ExInfo("position is occupied")
70 .addInfo("model", m_model->toString())
71 .addInfo("resist", resist.front()->toString()));
72 }
73
74 m_mask->mask();
75 }
76
77 //-----------------------------------------------------------------
78 /**
79 * Accomplish last move in m_dir direction.
80 * Mask to a new position.
81 * Change model position.
82 *
83 * It complements the unmasking in freeOldPos().
84 */
85 void
occupyNewPos()86 Rules::occupyNewPos()
87 {
88 m_touchDir = Dir::DIR_NO;
89 if (m_dir != Dir::DIR_NO) {
90 m_pushing = false;
91
92 V2 shift = Dir::dir2xy(m_dir);
93 V2 oldLoc = m_model->getLocation();
94 m_model->change_setLocation(oldLoc.plus(shift));
95
96 m_mask->mask();
97 }
98 }
99 //-----------------------------------------------------------------
100 /**
101 * Force model to a new position.
102 * Used just for undo loading.
103 */
104 void
change_setLocation(const V2 & loc)105 Rules::change_setLocation(const V2 &loc)
106 {
107 m_mask->unmask();
108 //HACK: model.out flag is recognized by its location
109 if (loc.getX() < 0 && loc.getY() < 0) {
110 m_model->change_goOut();
111 }
112 else {
113 m_model->change_setLocation(loc);
114 m_mask->mask();
115 }
116 }
117 //-----------------------------------------------------------------
118 /**
119 * Check dead fishes.
120 * Fish is dead:
121 * - when any model moves in dir != Dir::DIR_UP
122 * and new position is SOLELY on a fish
123 * - when any model moves in Dir::DIR_DOWN
124 * and new position is SOLELY on models SOLELY on a fish
125 * - when any model rests SOLELY on models SOLELY on a fish
126 * with fish.power < model.weight
127 *
128 * @return true when fish has died
129 */
130 bool
checkDead(Cube::eAction lastAction)131 Rules::checkDead(Cube::eAction lastAction)
132 {
133 //NOTE: after falling phase is sufficient to check only DeadFall
134 bool dead = false;
135
136 if (m_model->isAlive()) {
137 switch (lastAction) {
138 case Cube::ACTION_FALL:
139 dead = checkDeadFall();
140 break;
141 case Cube::ACTION_MOVE:
142 dead = checkDeadMove();
143 break;
144 default:
145 dead = false;
146 break;
147 }
148
149 if (!dead) {
150 dead = checkDeadStress();
151 }
152
153 if (dead) {
154 m_readyToDie = true;
155 }
156 }
157
158 return dead;
159 }
160
161 //-----------------------------------------------------------------
162 /**
163 * Return true when any model moves in dir != Dir::DIR_UP
164 * and new position is SOLELY on a fish.
165 * @return true when fish is dead
166 */
167 bool
checkDeadMove()168 Rules::checkDeadMove()
169 {
170 bool strict = OptionAgent::agent()->getAsBool("strict_rules", true);
171
172 Cube::t_models resist = m_mask->getResist(Dir::DIR_UP);
173 Cube::t_models::iterator end = resist.end();
174 for (Cube::t_models::iterator i = resist.begin(); i != end; ++i) {
175 if (!(*i)->isAlive()) {
176 Dir::eDir resist_dir = (*i)->rules()->getDir();
177 if (resist_dir != Dir::DIR_NO && resist_dir != Dir::DIR_UP) {
178 if (strict) {
179 if ((*i)->rules()->isOnHolderBacks()) {
180 return true;
181 }
182 } else {
183 if (!(*i)->rules()->isOnStack()) {
184 return true;
185 }
186 }
187 }
188 }
189 }
190
191 return false;
192 }
193 //-----------------------------------------------------------------
194 /**
195 * Whether object is under falling object.
196 *
197 * @return true when fish is dead
198 */
199 bool
checkDeadFall()200 Rules::checkDeadFall()
201 {
202 Cube::t_models killers = whoIsFalling();
203
204 Cube::t_models::iterator end = killers.end();
205 for (Cube::t_models::iterator i = killers.begin(); i != end; ++i) {
206 if (!(*i)->rules()->isOnWall()) {
207 return true;
208 }
209 }
210
211 return false;
212 }
213 //-----------------------------------------------------------------
214 /**
215 * Whether object is under hight stress.
216 *
217 * @return true when fish is dead
218 */
219 bool
checkDeadStress()220 Rules::checkDeadStress()
221 {
222 Cube::t_models killers = whoIsHeavier(m_model->getPower());
223
224 Cube::t_models::iterator end = killers.end();
225 for (Cube::t_models::iterator i = killers.begin(); i != end; ++i) {
226 if (!(*i)->rules()->isOnStrongPad((*i)->getWeight())) {
227 return true;
228 }
229 }
230
231 return false;
232 }
233
234 //-----------------------------------------------------------------
235 /**
236 * Finish events from last round.
237 * Change model state.
238 */
239 void
changeState()240 Rules::changeState()
241 {
242 m_dir = Dir::DIR_NO;
243
244 if (!m_model->isLost() && m_model->isDisintegrated()) {
245 m_mask->unmask();
246 m_model->change_remove();
247 }
248
249 if (m_readyToTurn) {
250 m_readyToTurn = false;
251 m_model->change_turnSide();
252 }
253
254 m_readyToActive = false;
255
256 if (m_readyToDie) {
257 m_readyToDie = false;
258 m_model->change_die();
259 }
260 }
261 //-----------------------------------------------------------------
262 /**
263 * Let model to go out of room.
264 * @return out depth, 0 for normal, 1 for going out,
265 * 2... for on the way, -1 for out of screen
266 */
267 int
actionOut()268 Rules::actionOut()
269 {
270 if (!m_model->isLost() && !m_model->isBusy() && m_dir == Dir::DIR_NO)
271 {
272 //NOTE: normal objects are not allowed to go out of screen
273 if (m_model->shouldGoOut()) {
274 if (m_mask->isFullyOut()) {
275 m_model->change_goOut();
276 m_outDepth = -1;
277 } else {
278 Dir::eDir borderDir = m_mask->getBorderDir();
279 if (borderDir != Dir::DIR_NO) {
280 m_model->change_goingOut();
281 moveDirBrute(borderDir);
282 m_outDepth += 1;
283 } else {
284 m_outDepth = 0;
285 }
286 }
287 }
288 }
289
290 return m_outDepth;
291 }
292 //-----------------------------------------------------------------
293 /**
294 * Let model fall.
295 */
296 void
actionFall()297 Rules::actionFall()
298 {
299 m_dir = Dir::DIR_DOWN;
300 m_lastFall = true;
301 }
302 //-----------------------------------------------------------------
303 /**
304 * Unset falling flag.
305 * @return last value of the flag
306 */
307 bool
clearLastFall()308 Rules::clearLastFall()
309 {
310 bool last = m_lastFall;
311 m_lastFall = false;
312 return last;
313 }
314 //-----------------------------------------------------------------
315 /**
316 * Unmask from old position.
317 */
318 void
freeOldPos()319 Rules::freeOldPos()
320 {
321 if (m_dir != Dir::DIR_NO) {
322 m_mask->unmask();
323 }
324 }
325
326 //-----------------------------------------------------------------
327 /**
328 * Whether object is direct or undirect on something specific.
329 * @param cond condition which will be satify when object is on.
330 */
331 bool
isOnCond(const OnCondition & cond)332 Rules::isOnCond(const OnCondition &cond)
333 {
334 bool result = false;
335 if (cond.isSatisfy(m_model)) {
336 result = true;
337 }
338 else if (cond.isWrong(m_model)) {
339 result = false;
340 }
341 else {
342 m_mask->unmask();
343
344 result = false;
345 Cube::t_models resist = m_mask->getResist(Dir::DIR_DOWN);
346 Cube::t_models::iterator end = resist.end();
347 for (Cube::t_models::iterator i = resist.begin(); i != end; ++i) {
348 if ((*i)->rules()->isOnCond(cond)) {
349 //NOTE: don't forget to mask()
350 result = true;
351 break;
352 }
353 }
354
355 m_mask->mask();
356 }
357
358 return result;
359 }
360 //-----------------------------------------------------------------
361 /**
362 * Whether object is on another unalive object.
363 * And this another object must be on something fixed
364 * (except on original object).
365 *
366 * Such object can be moved over fish.
367 */
368 bool
isOnStack()369 Rules::isOnStack()
370 {
371 return isOnCond(OnStack());
372 }
373 //-----------------------------------------------------------------
374 /**
375 * Whether object is direct or undirect on a wall.
376 */
377 bool
isOnWall()378 Rules::isOnWall()
379 {
380 return isOnCond(OnWall());
381 }
382 //-----------------------------------------------------------------
383 /**
384 * Whether object is direct or undirect on Wall or on powerful fish.
385 *
386 * @param weight stress weight which must fish carry
387 * @return whether Wall or a strong fish carry this object
388 */
389 bool
isOnStrongPad(Cube::eWeight weight)390 Rules::isOnStrongPad(Cube::eWeight weight)
391 {
392 return isOnCond(OnStrongPad(weight));
393 }
394 //-----------------------------------------------------------------
395 /**
396 * Returns true if the object is laying just on alive holders
397 * and they all have at least a part of their backs
398 * directly under this object.
399 *
400 * Pushing the object would kill all the holders.
401 * The object would be free to fall.
402 */
403 bool
isOnHolderBacks()404 Rules::isOnHolderBacks()
405 {
406 unsigned int numDirectHolders = 0;
407 Cube::t_models resist = m_mask->getResist(Dir::DIR_DOWN);
408 Cube::t_models::iterator end = resist.end();
409 for (Cube::t_models::iterator i = resist.begin(); i != end; ++i) {
410 if ((*i)->isAlive()) {
411 ++numDirectHolders;
412 }
413 }
414
415 Cube::t_models pads = getPads();
416 MarkMask::unique(&pads);
417 return numDirectHolders == pads.size();
418 }
419 //-----------------------------------------------------------------
420 /**
421 * Returns all alive fish and walls under this object.
422 */
423 Cube::t_models
getPads()424 Rules::getPads()
425 {
426 Cube::t_models pads;
427 m_mask->unmask();
428
429 Cube::t_models resist = m_mask->getResist(Dir::DIR_DOWN);
430 Cube::t_models::iterator end = resist.end();
431 for (Cube::t_models::iterator i = resist.begin(); i != end; ++i) {
432 if ((*i)->isAlive() || (*i)->isWall()) {
433 pads.push_back(*i);
434 } else {
435 Cube::t_models distance_pads = (*i)->rules()->getPads();
436 pads.insert(pads.end(), distance_pads.begin(),
437 distance_pads.end());
438 }
439 }
440
441 m_mask->mask();
442 return pads;
443 }
444 //-----------------------------------------------------------------
445 /**
446 * Whether object is falling.
447 */
448 bool
isFalling() const449 Rules::isFalling() const
450 {
451 bool result = false;
452 if (!m_model->isAlive()) {
453 result = (m_dir == Dir::DIR_DOWN);
454 }
455 return result;
456 }
457 //-----------------------------------------------------------------
458 /**
459 * Who is falling on us.
460 * @return array of killers, they can fall undirect on us
461 */
462 Cube::t_models
whoIsFalling()463 Rules::whoIsFalling()
464 {
465 Cube::t_models result;
466 m_mask->unmask();
467
468 Cube::t_models resist = m_mask->getResist(Dir::DIR_UP);
469 Cube::t_models::iterator end = resist.end();
470 for (Cube::t_models::iterator i = resist.begin(); i != end; ++i) {
471 //NOTE: falling is not propagated over fish
472 if (!(*i)->isWall() && !(*i)->isAlive()) {
473 if ((*i)->rules()->isFalling()) {
474 result.push_back(*i);
475 }
476 else {
477 Cube::t_models distance_killers = (*i)->rules()->whoIsFalling();
478 result.insert(result.end(), distance_killers.begin(),
479 distance_killers.end());
480 }
481 }
482 }
483
484 m_mask->mask();
485 return result;
486 }
487 //-----------------------------------------------------------------
488 /**
489 * Whether object is heavier than our power.
490 * @param power our max power
491 * @return whether object is heavier
492 */
493 bool
isHeavier(Cube::eWeight power) const494 Rules::isHeavier(Cube::eWeight power) const
495 {
496 bool result = false;
497 if (!m_model->isWall() && !m_model->isAlive()) {
498 if (m_model->getWeight() > power) {
499 result = true;
500 }
501 }
502
503 return result;
504 }
505 //-----------------------------------------------------------------
506 /**
507 * Who is heavier than our power.
508 * @param power our max power
509 * @return array of killers, they can lie undirect on us
510 */
511 Cube::t_models
whoIsHeavier(Cube::eWeight power)512 Rules::whoIsHeavier(Cube::eWeight power)
513 {
514 Cube::t_models result;
515 m_mask->unmask();
516
517 Cube::t_models resist = m_mask->getResist(Dir::DIR_UP);
518 Cube::t_models::iterator end = resist.end();
519 for (Cube::t_models::iterator i = resist.begin(); i != end; ++i) {
520 if (!(*i)->isWall()) {
521 if ((*i)->rules()->isHeavier(power)) {
522 result.push_back(*i);
523 }
524 else {
525 Cube::t_models distance_killers =
526 (*i)->rules()->whoIsHeavier(power);
527 result.insert(result.end(), distance_killers.begin(),
528 distance_killers.end());
529 }
530 }
531 }
532
533 m_mask->mask();
534 return result;
535 }
536
537 //-----------------------------------------------------------------
538 /**
539 * Whether other will retreat before us.
540 *
541 * @param power we will use this power
542 */
543 bool
canMoveOthers(Dir::eDir dir,Cube::eWeight power)544 Rules::canMoveOthers(Dir::eDir dir, Cube::eWeight power)
545 {
546 bool result = true;
547 //NOTE: make place after oneself, e.g. fish in U
548 m_mask->unmask();
549
550 Cube::t_models resist = m_mask->getResist(dir);
551 Cube::t_models::iterator end = resist.end();
552 for (Cube::t_models::iterator i = resist.begin(); i != end; ++i) {
553 if (m_model->shouldGoOut() && (*i)->isBorder()) {
554 continue;
555 }
556 if (!(*i)->rules()->canDir(dir, power)) {
557 result = false;
558 break;
559 }
560 }
561
562 m_mask->mask();
563 return result;
564 }
565 //-----------------------------------------------------------------
566 /**
567 * Whether others can move us.
568 * Live models cannot be moved by others.
569 * Power and weight is compared.
570 *
571 * @param dir move direction
572 * @param power others power
573 * @return whether we can move
574 */
575 bool
canDir(Dir::eDir dir,Cube::eWeight power)576 Rules::canDir(Dir::eDir dir, Cube::eWeight power)
577 {
578 bool result = false;
579 if (!m_model->isAlive() && power >= m_model->getWeight()) {
580 // A special case when outgoing object is pushing with FIXED power.
581 if (m_model->isWall() && !m_model->shouldGoOut()) {
582 return false;
583 }
584 result = canMoveOthers(dir, power);
585 }
586
587 return result;
588 }
589 //-----------------------------------------------------------------
590 /**
591 * There is one special case.
592 * When model touches output_DIR then it goes out.
593 * This is used only in level 'windoze'.
594 */
595 bool
touchSpec(Dir::eDir dir)596 Rules::touchSpec(Dir::eDir dir)
597 {
598 bool result = false;
599 Cube::t_models resist = m_mask->getResist(dir);
600 if (resist.size() == 1) {
601 if (resist[0]->isOutDir(dir)) {
602 resist[0]->decOutCapacity();
603 m_mask->unmask();
604 m_model->change_goOut();
605 result = true;
606 }
607 }
608 return result;
609 }
610 //-----------------------------------------------------------------
611 /**
612 * Marks all resisted models as touched.
613 */
614 void
setTouched(Dir::eDir dir)615 Rules::setTouched(Dir::eDir dir)
616 {
617 m_touchDir = dir;
618 if (!m_model->isWall()) {
619 m_mask->unmask();
620 Cube::t_models resist = m_mask->getResist(dir);
621 Cube::t_models::iterator end = resist.end();
622 for (Cube::t_models::iterator i = resist.begin(); i != end; ++i) {
623 if (!(*i)->isAlive()) {
624 (*i)->rules()->setTouched(dir);
625 }
626 }
627 m_mask->mask();
628 }
629 }
630 //-----------------------------------------------------------------
631 /**
632 * Try to move.
633 * Only m_dir will be set.
634 * NOTE: we can move all resist or none
635 *
636 * @return whether we have moved
637 */
638 bool
actionMoveDir(Dir::eDir dir)639 Rules::actionMoveDir(Dir::eDir dir)
640 {
641 bool result = false;
642 if (canMoveOthers(dir, m_model->getPower())) {
643 moveDirBrute(dir);
644 result = true;
645 }
646 else {
647 if (touchSpec(dir)) {
648 result = true;
649 }
650 else {
651 setTouched(dir);
652 }
653 }
654
655 return result;
656 }
657 //-----------------------------------------------------------------
658 /**
659 * Irrespective move.
660 * Set m_dir to this dir and do the same for all resist.
661 * Only m_dir and m_pushing will be set.
662 */
663 void
moveDirBrute(Dir::eDir dir)664 Rules::moveDirBrute(Dir::eDir dir)
665 {
666 //NOTE: make place after oneself, e.g. object in U
667 m_mask->unmask();
668
669 Cube::t_models resist = m_mask->getResist(dir);
670 Cube::t_models::iterator end = resist.end();
671 for (Cube::t_models::iterator i = resist.begin(); i != end; ++i) {
672 if (!(*i)->isBorder()) {
673 (*i)->rules()->moveDirBrute(dir);
674 m_pushing = true;
675 }
676 }
677
678 m_dir = dir;
679 m_mask->mask();
680 }
681
682 //-----------------------------------------------------------------
683 /**
684 * Return what we do the last round.
685 * Useful for script functions.
686 * NOTE: dead is not action
687 */
688 std::string
getAction() const689 Rules::getAction() const
690 {
691 if (m_readyToTurn) {
692 return "turn";
693 }
694 else if (m_readyToActive) {
695 return "activate";
696 }
697 else if (m_model->isBusy()) {
698 return "busy";
699 }
700
701 switch (m_dir) {
702 case Dir::DIR_LEFT: return "move_left";
703 case Dir::DIR_RIGHT: return "move_right";
704 case Dir::DIR_UP: return "move_up";
705 case Dir::DIR_DOWN: return "move_down";
706 case Dir::DIR_NO: return "rest";
707 default: assert(!"unknown dir"); break;
708 }
709
710 return "rest";
711 }
712 //-----------------------------------------------------------------
713 /**
714 * Return how we have feel the last round.
715 * Useful for script functions.
716 *
717 * States:
718 * "goout" ... go out of room
719 * "dead" ... is dead
720 * "talking" ... is talking
721 * "pushing" ... is pushing
722 * "normal" ... is alive and resting
723 */
724 std::string
getState() const725 Rules::getState() const
726 {
727 if (m_outDepth == 1) {
728 return "goout";
729 }
730 else if (!m_model->isAlive()) {
731 return "dead";
732 }
733 else if (m_model->isTalking()) {
734 return "talking";
735 }
736 else if (m_pushing) {
737 return "pushing";
738 }
739 else {
740 return "normal";
741 }
742 }
743 //-----------------------------------------------------------------
744 bool
isAtBorder() const745 Rules::isAtBorder() const
746 {
747 return m_mask->getBorderDir() != Dir::DIR_NO;
748 }
749 //-----------------------------------------------------------------
750 bool
isFreePlace(const V2 & loc) const751 Rules::isFreePlace(const V2 &loc) const
752 {
753 return m_mask->getPlacedResist(loc).empty();
754 }
755 //-----------------------------------------------------------------
756 const Cube::t_models
getResist(Dir::eDir dir) const757 Rules::getResist(Dir::eDir dir) const
758 {
759 return m_mask->getResist(dir);
760 }
761
762