472 |
if (Stone *st = GetStone(move(get_pos(), dir))) { |
if (Stone *st = GetStone(move(get_pos(), dir))) { |
473 |
if (state == ACTIVE) { // allowed to trigger? |
if (state == ACTIVE) { // allowed to trigger? |
474 |
state = IDLE; |
state = IDLE; |
|
|
|
|
// if (st->is_kind("st-pull")) { |
|
|
// send_impulse(st->get_pos(), dir); |
|
|
// } |
|
|
// else { |
|
475 |
SendMessage(st, "trigger", Value(dir)); |
SendMessage(st, "trigger", Value(dir)); |
|
// } |
|
476 |
} |
} |
477 |
return true; |
return true; |
478 |
} |
} |
624 |
}; |
}; |
625 |
}; |
}; |
626 |
|
|
627 |
|
// ------------------------ |
628 |
|
// ConnectiveStone |
629 |
|
// ------------------------ |
630 |
|
// base class for PuzzleStone and BigBrick |
631 |
|
|
632 |
|
namespace { |
633 |
|
class ConnectiveStone : public Stone { |
634 |
|
public: |
635 |
|
ConnectiveStone(const char *kind, int connections) |
636 |
|
: Stone(kind) |
637 |
|
{ |
638 |
|
set_attrib("connections", connections); |
639 |
|
} |
640 |
|
|
641 |
|
DirectionBits get_connections(); |
642 |
|
|
643 |
|
private: |
644 |
|
void init_model(); |
645 |
|
virtual int get_modelno() const; |
646 |
|
}; |
647 |
|
} |
648 |
|
|
649 |
|
DirectionBits |
650 |
|
ConnectiveStone::get_connections() |
651 |
|
{ |
652 |
|
int conn=int_attrib("connections") - 1; |
653 |
|
if (conn >=0 && conn <16) |
654 |
|
return DirectionBits(conn); |
655 |
|
else |
656 |
|
return NODIRBIT; |
657 |
|
} |
658 |
|
|
659 |
|
void |
660 |
|
ConnectiveStone::init_model() { |
661 |
|
string modelname = get_kind(); |
662 |
|
char x[10]; |
663 |
|
|
664 |
|
sprintf(x, "%d", get_modelno()); |
665 |
|
modelname += x; |
666 |
|
set_model(modelname); |
667 |
|
} |
668 |
|
|
669 |
|
int |
670 |
|
ConnectiveStone::get_modelno() const { |
671 |
|
return int_attrib("connections"); |
672 |
|
} |
673 |
|
|
674 |
|
// ----------------- |
675 |
|
// BigBrick |
676 |
|
// ----------------- |
677 |
|
// BigBricks allow to build stones of any size |
678 |
|
|
679 |
|
namespace { |
680 |
|
class BigBrick : public ConnectiveStone { |
681 |
|
CLONEOBJ(BigBrick); |
682 |
|
public: |
683 |
|
BigBrick(int connections) |
684 |
|
: ConnectiveStone("st-bigbrick", connections) |
685 |
|
{} |
686 |
|
}; |
687 |
|
} |
688 |
|
|
689 |
|
|
690 |
//---------------------------------------- |
//---------------------------------------- |
691 |
// PuzzleStones |
// PuzzleStones |
692 |
//---------------------------------------- |
//---------------------------------------- |
762 |
*/ |
*/ |
763 |
namespace |
namespace |
764 |
{ |
{ |
765 |
class PuzzleStone : public Stone { |
class PuzzleStone : public ConnectiveStone, public TimeHandler { |
766 |
INSTANCELISTOBJ(PuzzleStone); |
INSTANCELISTOBJ(PuzzleStone); |
767 |
public: |
public: |
768 |
PuzzleStone(int connections, bool oxyd1_compatible_) |
PuzzleStone(int connections, bool oxyd1_compatible_) |
769 |
: Stone("st-puzzle") |
: ConnectiveStone("st-puzzle", connections) |
770 |
|
, state(IDLE) |
771 |
{ |
{ |
|
set_attrib("connections", connections); |
|
772 |
set_attrib("oxyd", int(oxyd1_compatible_)); |
set_attrib("oxyd", int(oxyd1_compatible_)); |
773 |
} |
} |
774 |
private: |
private: |
775 |
typedef vector<GridPos> Cluster; |
typedef vector<GridPos> Cluster; |
776 |
|
|
|
DirectionBits get_connections(); |
|
777 |
bool oxyd1_compatible() const { return int_attrib("oxyd") != 0; } |
bool oxyd1_compatible() const { return int_attrib("oxyd") != 0; } |
778 |
|
|
779 |
static bool visit_dir(vector<GridPos> &stack, |
static bool visit_dir(vector<GridPos> &stack, GridPos curpos, |
780 |
GridPos curpos, Direction dir); |
Direction dir); |
781 |
|
static void visit_adjacent(vector<GridPos>& stack, GridPos curpos, |
782 |
|
Direction dir, int wanted_oxyd_attrib); |
783 |
|
|
784 |
bool find_cluster(Cluster &); |
bool find_cluster(Cluster &); |
785 |
void find_row_or_column_cluster(Cluster &c, |
void find_adjacents(Cluster &); |
786 |
GridPos startpos, Direction dir); |
void find_row_or_column_cluster(Cluster &c, GridPos startpos, |
787 |
|
Direction dir, int wanted_oxyd_attrib); |
788 |
|
|
789 |
bool cluster_complete(); |
bool cluster_complete(); |
790 |
void maybe_move_cluster(Cluster &c, bool is_complete, Direction dir); |
void maybe_move_cluster(Cluster &c, bool is_complete, Direction dir); |
791 |
void rotate_cluster(const Cluster &c); |
void rotate_cluster(const Cluster &c); |
800 |
|
|
801 |
virtual void on_impulse(const Impulse& impulse); |
virtual void on_impulse(const Impulse& impulse); |
802 |
|
|
|
void init_model(); |
|
803 |
StoneResponse collision_response(const StoneContact &sc); |
StoneResponse collision_response(const StoneContact &sc); |
804 |
void actor_hit(const StoneContact &sc); |
void actor_hit(const StoneContact &sc); |
805 |
void actor_inside(Actor *a) { |
void actor_inside(Actor *a) { |
807 |
SendMessage(a, "shatter"); |
SendMessage(a, "shatter"); |
808 |
} |
} |
809 |
|
|
810 |
|
int get_modelno() const; |
811 |
|
|
812 |
|
void trigger_explosion(double delay); |
813 |
|
static void trigger_explosion_at(GridPos p, double delay); |
814 |
|
void explode(); |
815 |
|
void alarm(); |
816 |
|
|
817 |
// Variables |
// Variables |
818 |
bool visited; // flag for DFS |
bool visited; // flag for DFS |
819 |
|
|
820 |
|
enum { IDLE, EXPLODING } state; |
821 |
}; |
}; |
822 |
} |
} |
823 |
|
|
824 |
PuzzleStone::InstanceList PuzzleStone::instances; |
PuzzleStone::InstanceList PuzzleStone::instances; |
825 |
|
|
|
DirectionBits |
|
|
PuzzleStone::get_connections() |
|
|
{ |
|
|
int conn=int_attrib("connections") - 1; |
|
|
if (conn >=0 && conn <16) |
|
|
return DirectionBits(conn); |
|
|
else |
|
|
return NODIRBIT; |
|
|
} |
|
|
|
|
826 |
bool |
bool |
827 |
PuzzleStone::visit_dir(vector<GridPos> &stack, GridPos curpos, Direction dir) |
PuzzleStone::visit_dir(vector<GridPos> &stack, GridPos curpos, Direction dir) |
828 |
{ |
{ |
887 |
} |
} |
888 |
|
|
889 |
void |
void |
890 |
PuzzleStone::find_row_or_column_cluster(Cluster &c, GridPos startpos, Direction dir) |
PuzzleStone::visit_adjacent(vector<GridPos>& stack, GridPos curpos, Direction dir, int wanted_oxyd_attrib) |
891 |
|
{ |
892 |
|
GridPos newpos = move(curpos, dir); |
893 |
|
if (PuzzleStone *pz = dynamic_cast<PuzzleStone*>(GetStone(newpos))) { |
894 |
|
if (!pz->visited) { |
895 |
|
if (wanted_oxyd_attrib == pz->int_attrib("oxyd")) { |
896 |
|
pz->visited = true; |
897 |
|
stack.push_back(newpos); |
898 |
|
} |
899 |
|
} |
900 |
|
} |
901 |
|
} |
902 |
|
|
903 |
|
/* Use a depth first search to determine the group of all puzzle stones |
904 |
|
with the same "oxyd" attrib that are adjacent to the current stone |
905 |
|
(or to any other member of the group). |
906 |
|
*/ |
907 |
|
void |
908 |
|
PuzzleStone::find_adjacents(Cluster &cluster) { |
909 |
|
for (unsigned i=0; i<instances.size(); ++i) |
910 |
|
instances[i]->visited=false; |
911 |
|
|
912 |
|
vector<GridPos> pos_stack; |
913 |
|
pos_stack.push_back(get_pos()); |
914 |
|
this->visited = true; |
915 |
|
|
916 |
|
int wanted_oxyd_attrib = int_attrib("oxyd"); |
917 |
|
|
918 |
|
while (!pos_stack.empty()) { |
919 |
|
GridPos curpos = pos_stack.back(); |
920 |
|
pos_stack.pop_back(); |
921 |
|
|
922 |
|
cluster.push_back(curpos); |
923 |
|
visit_adjacent(pos_stack, curpos, NORTH, wanted_oxyd_attrib); |
924 |
|
visit_adjacent(pos_stack, curpos, SOUTH, wanted_oxyd_attrib); |
925 |
|
visit_adjacent(pos_stack, curpos, EAST, wanted_oxyd_attrib); |
926 |
|
visit_adjacent(pos_stack, curpos, WEST, wanted_oxyd_attrib); |
927 |
|
} |
928 |
|
} |
929 |
|
|
930 |
|
/* searches from 'startpos' into 'dir' for puzzle-stones. |
931 |
|
wanted_oxyd_attrib == -1 -> take any puzzle stone |
932 |
|
else -> take only puzzle stones of same type |
933 |
|
*/ |
934 |
|
|
935 |
|
void |
936 |
|
PuzzleStone::find_row_or_column_cluster(Cluster &c, GridPos startpos, Direction dir, int wanted_oxyd_attrib) |
937 |
{ |
{ |
938 |
assert(dir != NODIR); |
assert(dir != NODIR); |
939 |
|
|
940 |
GridPos p = startpos; |
GridPos p = startpos; |
941 |
while (dynamic_cast<PuzzleStone*>(GetStone(p))) { |
while (Stone *puzz = dynamic_cast<PuzzleStone*>(GetStone(p))) { |
942 |
|
if (wanted_oxyd_attrib != -1 && wanted_oxyd_attrib != puzz->int_attrib("oxyd")) |
943 |
|
break; // stop when an unrequested puzzle stone type is readed |
944 |
c.push_back(p); |
c.push_back(p); |
945 |
p.move(dir); |
p.move(dir); |
946 |
} |
} |
1012 |
return find_cluster(c); |
return find_cluster(c); |
1013 |
} |
} |
1014 |
|
|
|
void |
|
|
PuzzleStone::init_model() |
|
|
{ |
|
|
string modelname = "st-puzzle"; |
|
|
char x[10]; |
|
1015 |
|
|
1016 |
// Every combination of ``sockets'' has its own model. |
int |
1017 |
|
PuzzleStone::get_modelno() const { |
1018 |
int modelno = int_attrib("connections"); |
int modelno = int_attrib("connections"); |
1019 |
if (oxyd1_compatible()) modelno += 16; |
if (oxyd1_compatible()) modelno += 16; |
1020 |
sprintf(x, "%d", modelno); |
return modelno; |
|
modelname += x; |
|
|
set_model(modelname); |
|
1021 |
} |
} |
1022 |
|
|
1023 |
void |
void |
1049 |
if (oxyd1_compatible()) { |
if (oxyd1_compatible()) { |
1050 |
Direction move_dir = Direction(4*(rand()/(RAND_MAX+1.0))); |
Direction move_dir = Direction(4*(rand()/(RAND_MAX+1.0))); |
1051 |
Cluster c; |
Cluster c; |
1052 |
find_row_or_column_cluster(c, get_pos(), reverse(move_dir)); |
find_row_or_column_cluster(c, get_pos(), reverse(move_dir), 1); |
1053 |
if (!c.empty()) { |
if (!c.empty()) { |
1054 |
GridPos last = c.back(); |
GridPos last = c.back(); |
1055 |
c.clear(); |
c.clear(); |
1056 |
find_row_or_column_cluster(c, last, move_dir); |
find_row_or_column_cluster(c, last, move_dir, 1); |
1057 |
|
|
1058 |
// if cluster too small to rotate -> turn direction by 90 deg |
// if cluster too small to rotate -> turn direction by 90 deg |
1059 |
if (c.size() <= 1) { |
if (c.size() <= 1) { |
1060 |
move_dir = rotate(move_dir, true); |
move_dir = rotate(move_dir, true); |
1061 |
c.clear(); |
c.clear(); |
1062 |
find_row_or_column_cluster(c, last, move_dir); |
find_row_or_column_cluster(c, last, move_dir, 1); |
1063 |
} |
} |
1064 |
|
|
1065 |
if (c.size() > 1) { |
if (c.size() > 1) { |
1074 |
} |
} |
1075 |
} |
} |
1076 |
|
|
1077 |
void PuzzleStone::message(const string& msg, const Value &) { |
void |
1078 |
|
PuzzleStone::trigger_explosion(double delay) { |
1079 |
|
if (state == IDLE) { |
1080 |
|
state = EXPLODING; |
1081 |
|
g_timer.set_alarm(this, delay, false); |
1082 |
|
} |
1083 |
|
} |
1084 |
|
|
1085 |
|
void |
1086 |
|
PuzzleStone::trigger_explosion_at(GridPos p, double delay) { |
1087 |
|
PuzzleStone *puzz = dynamic_cast<PuzzleStone*>(GetStone(p)); |
1088 |
|
if (puzz) |
1089 |
|
puzz->trigger_explosion(delay); |
1090 |
|
} |
1091 |
|
|
1092 |
|
void |
1093 |
|
PuzzleStone::explode() { |
1094 |
|
GridPos p = get_pos(); |
1095 |
|
DirectionBits dir = get_connections(); |
1096 |
|
const char *expl = (dir == NODIRBIT || dir == ALL_DIRECTIONS) |
1097 |
|
? "it-explosion2" : "it-explosion1"; |
1098 |
|
|
1099 |
|
// exchange puzzle stone with explosion |
1100 |
|
KillStone(p); |
1101 |
|
SetItem(p, MakeItem(expl)); |
1102 |
|
|
1103 |
|
// trigger connected neighbors: |
1104 |
|
if (dir == NODIRBIT) dir = ALL_DIRECTIONS; |
1105 |
|
|
1106 |
|
const double DEFAULT_DELAY = 0.2; |
1107 |
|
if (dir&NORTHBIT) trigger_explosion_at(move(p, NORTH), DEFAULT_DELAY); |
1108 |
|
if (dir&SOUTHBIT) trigger_explosion_at(move(p, SOUTH), DEFAULT_DELAY); |
1109 |
|
if (dir&EASTBIT) trigger_explosion_at(move(p, EAST), DEFAULT_DELAY); |
1110 |
|
if (dir&WESTBIT) trigger_explosion_at(move(p, WEST), DEFAULT_DELAY); |
1111 |
|
} |
1112 |
|
|
1113 |
|
void |
1114 |
|
PuzzleStone::alarm() { |
1115 |
|
explode(); |
1116 |
|
} |
1117 |
|
|
1118 |
|
void |
1119 |
|
PuzzleStone::message(const string& msg, const Value &) { |
1120 |
if (msg == "init") { // shuffle at level startup |
if (msg == "init") { // shuffle at level startup |
1121 |
int count = options::Difficulty == DIFFICULTY_EASY |
int count = options::Difficulty == DIFFICULTY_EASY |
1122 |
? 1 |
? 1 |
1129 |
void |
void |
1130 |
PuzzleStone::on_impulse(const Impulse& impulse) |
PuzzleStone::on_impulse(const Impulse& impulse) |
1131 |
{ |
{ |
1132 |
if (!oxyd1_compatible()) { |
if (!oxyd1_compatible() && state == IDLE) { |
1133 |
Cluster c; |
Cluster c; |
1134 |
bool is_complete = find_cluster(c); |
bool is_complete = find_cluster(c); |
1135 |
maybe_move_cluster(c, is_complete, impulse.dir); |
maybe_move_cluster(c, is_complete, impulse.dir); |
1136 |
} |
} |
1137 |
} |
} |
1138 |
|
|
|
|
|
|
|
|
1139 |
void |
void |
1140 |
PuzzleStone::actor_hit(const StoneContact &sc) |
PuzzleStone::actor_hit(const StoneContact &sc) |
1141 |
{ |
{ |
1142 |
if (get_connections() == NODIRBIT) |
if (get_connections() == NODIRBIT) |
1143 |
return; |
return; |
1144 |
|
|
1145 |
|
if (state == EXPLODING) |
1146 |
|
return; |
1147 |
|
|
1148 |
bool compatible = oxyd1_compatible(); |
bool compatible = oxyd1_compatible(); |
1149 |
|
|
1150 |
if (compatible) { |
if (compatible) { |
1151 |
Cluster c; |
Cluster c; |
1152 |
if (find_cluster(c)) { |
if (find_cluster(c)) { |
1153 |
// make complete cluster explode: |
Cluster all; |
1154 |
Cluster::iterator e = c.end(); |
find_adjacents(all); |
1155 |
for (Cluster::iterator i = c.begin(); i != e; ++i) { |
|
1156 |
KillStone(*i); |
if (all.size() == c.size()) { |
1157 |
SetItem(*i, MakeItem("it-explosion1")); |
explode(); // explode complete cluster |
1158 |
|
return; |
1159 |
} |
} |
|
play_sound("explosion1"); |
|
|
return; |
|
1160 |
} |
} |
1161 |
} |
} |
1162 |
|
|
1165 |
if (face != NODIR) { // not touching a corner |
if (face != NODIR) { // not touching a corner |
1166 |
Direction move_dir = reverse(face); |
Direction move_dir = reverse(face); |
1167 |
Cluster c; |
Cluster c; |
1168 |
find_row_or_column_cluster(c, get_pos(), move_dir); |
find_row_or_column_cluster(c, get_pos(), move_dir, int(compatible)); |
1169 |
|
|
1170 |
if (c.size() == 1) { |
if (c.size() == 1) { |
1171 |
if (!compatible) { |
if (!compatible) { |
1172 |
// if cluster contains single stone -> move it if |
// if cluster contains single stone |
1173 |
// dest pos is free |
// -> move it if dest pos is free |
1174 |
GridPos dest = move(c[0], move_dir); |
GridPos dest = move(c[0], move_dir); |
1175 |
if (GetStone(dest) == 0) { |
if (GetStone(dest) == 0) { |
1176 |
// check speed. here a lower speed than in |
// check speed. here a lower speed than in |
2647 |
Register("st-puzzle2-esw", new PuzzleStone(8, true)); |
Register("st-puzzle2-esw", new PuzzleStone(8, true)); |
2648 |
Register("st-puzzle2-nesw", new PuzzleStone(16, true)); |
Register("st-puzzle2-nesw", new PuzzleStone(16, true)); |
2649 |
|
|
2650 |
|
Register("st-bigbrick", new BigBrick(1)); |
2651 |
|
Register("st-bigbrick-n", new BigBrick(9)); |
2652 |
|
Register("st-bigbrick-e", new BigBrick(5)); |
2653 |
|
Register("st-bigbrick-s", new BigBrick(3)); |
2654 |
|
Register("st-bigbrick-w", new BigBrick(2)); |
2655 |
|
Register("st-bigbrick-ne", new BigBrick(13)); |
2656 |
|
Register("st-bigbrick-nw", new BigBrick(10)); |
2657 |
|
Register("st-bigbrick-es", new BigBrick(7)); |
2658 |
|
Register("st-bigbrick-sw", new BigBrick(4)); |
2659 |
|
Register("st-bigbrick-ns", new BigBrick(11)); |
2660 |
|
Register("st-bigbrick-ew", new BigBrick(6)); |
2661 |
|
Register("st-bigbrick-nes", new BigBrick(15)); |
2662 |
|
Register("st-bigbrick-new", new BigBrick(14)); |
2663 |
|
Register("st-bigbrick-nsw", new BigBrick(12)); |
2664 |
|
Register("st-bigbrick-esw", new BigBrick(8)); |
2665 |
|
Register("st-bigbrick-nesw", new BigBrick(16)); |
2666 |
|
|
2667 |
Register (new RotatorStone (true)); |
Register (new RotatorStone (true)); |
2668 |
Register (new RotatorStone (false)); |
Register (new RotatorStone (false)); |
2669 |
|
|