20130313
[gdash.git] / src / cave / cavetypes.cpp
blobaeb32430218cfc39121ae7549a3ba85dd0fc9deb
1 /*
2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 #include "config.h"
19 #include <glib.h>
20 #include <glib/gi18n.h>
21 #include <sstream>
22 #include <cstdlib>
23 #include <cstring>
24 #include <iomanip>
25 #include <stdexcept>
27 #include "cave/cavetypes.hpp"
28 #include "cave/elementproperties.hpp"
29 #include "misc/printf.hpp"
30 #include "misc/logger.hpp"
31 #include "cave/helper/namevaluepair.hpp"
34 /* TRANSLATORS: None here means "no direction to move"; when there is no gravity while stirring the pot. */
35 static const char* direction_name[]={ N_("None"), N_("Up"), N_("Up+right"), N_("Right"), N_("Down+right"), N_("Down"), N_("Down+left"), N_("Left"), N_("Up+left") };
36 static const char* direction_filename[]={ "none", "up", "upright", "right", "downright", "down", "downleft", "left", "upleft" };
38 static const char* scheduling_name[]={ N_("Milliseconds"), "BD1", "BD2", "Construction Kit", "Crazy Dream 7", "Atari BD1", "Atari BD2/Construction Kit" };
39 static const char* scheduling_filename[]={ "ms", "bd1", "bd2", "plck", "crdr7", "bd1atari", "bd2ckatari" };
41 /* used for bdcff engine flag. */
42 static const char *engines_name[]={"BD1", "BD2", "PLCK", "1stB", "Crazy Dream", "Crazy Light"};
43 static const char *engines_filename[]={"BD1", "BD2", "PLCK", "1stB", "CrDr", "CrLi"};
45 /// Write a coordinate to an output stream.
46 /// Delimits the x and y components with space.
47 std::ostream& operator<<(std::ostream &os, Coordinate const &p) {
48 return (os << p.x << ' ' << p.y);
51 /// Read a coordinate from an input stream.
52 /// Reads x and y coordinates; if both could be read, set p.
53 std::istream& operator>>(std::istream &is, Coordinate &p) {
54 int x, y;
55 is >> x >> y;
56 /* only modify p if read both parameters correctly */
57 if (is) {
58 p.x=x;
59 p.y=y;
61 return is;
64 /// Add a vector to a coordinate.
65 /// @param p The vector to add.
66 Coordinate& Coordinate::operator+=(Coordinate const &p) {
67 x+=p.x;
68 y+=p.y;
69 return *this;
72 /// Add two coordinates (vectors).
73 Coordinate Coordinate::operator+(Coordinate const& rhs) const {
74 return Coordinate(x+rhs.x, y+rhs.y);
77 /// Compare two coordinates for equality.
78 /// @return True, if they are the same.
79 bool Coordinate::operator==(Coordinate const& rhs) const {
80 return x==rhs.x && y==rhs.y;
83 /// Get on-screen description of a coordinate.
84 std::string visible_name(Coordinate const &p) {
85 std::ostringstream os;
86 os<<'('<<p.x<<','<<p.y<<')';
87 return os.str();
90 /// Drag the corners of the rectangle set by p1 and p2.
91 /// This is used for many objects in the editor. When clicking and dragging
92 /// one of the corners of a square, its size can be changed.
93 /// Whereas by clicking and dragging the edges, the whole square is moved.
94 /// We can detect clicking on the edges by comparing the coordinate clicked
95 /// with p1 and p2; all four possibilities have to be taken into account.
96 /// @param p1 One corner of the rectangle.
97 /// @param p2 The other corner of the rectangle.
98 /// @param current The coordinate clicked.
99 /// @param displacement The movement vector.
100 void Coordinate::drag_rectangle(Coordinate &p1, Coordinate &p2, Coordinate current, Coordinate displacement) {
101 /* dragging objects which are box-shaped */
102 if (current.x==p1.x && current.y==p1.y) { /* try to drag (x1;y1) corner. */
103 p1.x+=displacement.x;
104 p1.y+=displacement.y;
106 else if (current.x==p2.x && current.y==p1.y) { /* try to drag (x2;y1) corner. */
107 p2.x+=displacement.x;
108 p1.y+=displacement.y;
110 else if (current.x==p1.x && current.y==p2.y) { /* try to drag (x1;y2) corner. */
111 p1.x+=displacement.x;
112 p2.y+=displacement.y;
114 else if (current.x==p2.x && current.y==p2.y) { /* try to drag (x2;y2) corner. */
115 p2.x+=displacement.x;
116 p2.y+=displacement.y;
118 else {
119 /* drag the whole thing */
120 p1.x+=displacement.x;
121 p1.y+=displacement.y;
122 p2.x+=displacement.x;
123 p2.y+=displacement.y;
128 /// get on-screen visible "name" of an int
129 std::string visible_name(GdInt const &i) {
130 std::ostringstream os;
131 os<<i;
132 return os.str();
135 /// get on-screen visible "name" of a probability
136 /// @todo change 1000000.0 to a constans EVERYWHERE in the code
137 /// @todo check everywhere when reading and writing if a +0.5 is needed, and explain why
138 std::string visible_name(GdProbability const &p) {
139 std::ostringstream os;
140 os<<std::fixed<<std::setprecision(2)<<p*100.0/1000000.0<<'%';
141 return os.str();
144 /// get on-screen visible "name" of an int
145 const char *visible_name(GdBool const &b) {
146 return b?N_("Yes"):N_("No");
149 /// get on-screen visible name of a direction
150 const char *visible_name(GdDirectionEnum dir) {
151 g_assert(dir>=0 && unsigned(dir)<G_N_ELEMENTS(direction_name));
152 return direction_name[dir];
155 /// get on-screen visible name of a scheduling
156 const char *visible_name(GdSchedulingEnum sched) {
157 g_assert(sched>=0 && unsigned(sched)<G_N_ELEMENTS(scheduling_name));
158 return scheduling_name[sched];
161 /// get on-screen visible name of a scheduling
162 const char *visible_name(GdEngineEnum eng) {
163 g_assert(eng>=0 && unsigned(eng)<G_N_ELEMENTS(engines_name));
164 return engines_name[eng];
167 /// get on-screen visible name of a scheduling
168 const char *visible_name(GdElementEnum elem) {
169 return gd_element_properties[elem].visiblename;
173 /// Creates a CharToElementTable for conversion.
174 /// Adds all fixed elements, read from the gd_element_properties array.
175 CharToElementTable::CharToElementTable() {
176 for (unsigned i=0; i<ArraySize; i++)
177 table[i]=O_UNKNOWN;
179 /* then set fixed characters */
180 for (unsigned i=0; i<O_MAX; i++) {
181 int c=gd_element_properties[i].character;
183 if (c!=0) {
184 /* check if already used for element */
185 g_assert(table[c]==O_UNKNOWN);
186 table[c]=GdElementEnum(i);
192 * @brief Return the GdElementEnum assigned to the character.
194 * @param i The character.
195 * @return The element, or O_UNKNOWN if character is invalid.
197 GdElementEnum CharToElementTable::get(unsigned i) const {
198 if (i>=ArraySize || table[i]==O_UNKNOWN) {
199 gd_warning(CPrintf("Invalid character representing element: %c") % char(i));
200 return O_UNKNOWN;
202 return table[i];
206 * @brief Find an empty character to store the element in a map.
207 * If finds a suitable character, also remembers.
209 * @param e The element to find place for.
210 * @return The (new) character for the element.
212 unsigned CharToElementTable::find_place_for(GdElementEnum e) {
213 const char *not_allowed="<>&[]/=\\";
215 // first check if it is already in the array.
216 for (unsigned i=32; i<ArraySize; ++i)
217 if (table[i]==e)
218 return i;
220 unsigned i;
221 for (i=32; i<ArraySize; ++i)
222 // if found a good empty char, break
223 if (table[i]==O_UNKNOWN && strchr(not_allowed, i)==NULL)
224 break;
225 if (i>=ArraySize)
226 throw std::runtime_error("no more characters");
227 table[i]=e;
228 return i;
232 * @brief Set an element assigned to a character.
234 * @param i The character.
235 * @param e The element assigned.
237 void CharToElementTable::set(unsigned i, GdElementEnum e) {
238 if (i>=ArraySize) {
239 gd_warning(CPrintf("Invalid character representing element: %c") % char(i));
240 return;
243 if (table[i]!=O_UNKNOWN)
244 gd_warning(CPrintf("Character %c already used by elements %s") % char(i) % visible_name(table[i]));
246 table[i]=e;
250 static NameValuePair<GdElementEnum> name_to_element;
252 void gd_cave_types_init() {
253 /* put names to a hash table */
254 /* this is a helper for file read operations */
256 for (int i=0; i<O_MAX; i++) {
257 const char *key=gd_element_properties[i].filename;
259 g_assert(key!=NULL && !gd_str_equal(key, ""));
260 /* check if every name is used once */
261 g_assert(!name_to_element.has_name(key));
262 name_to_element.add(key, GdElementEnum(i));
264 /* for compatibility with tim stridmann's memorydump->bdcff converter... .... ... */
265 name_to_element.add("HEXPANDING_WALL", O_H_EXPANDING_WALL);
266 name_to_element.add("FALLING_DIAMOND", O_DIAMOND_F);
267 name_to_element.add("FALLING_BOULDER", O_STONE_F);
268 name_to_element.add("EXPLOSION1S", O_EXPLODE_1);
269 name_to_element.add("EXPLOSION2S", O_EXPLODE_2);
270 name_to_element.add("EXPLOSION3S", O_EXPLODE_3);
271 name_to_element.add("EXPLOSION4S", O_EXPLODE_4);
272 name_to_element.add("EXPLOSION5S", O_EXPLODE_5);
273 name_to_element.add("EXPLOSION1D", O_PRE_DIA_1);
274 name_to_element.add("EXPLOSION2D", O_PRE_DIA_2);
275 name_to_element.add("EXPLOSION3D", O_PRE_DIA_3);
276 name_to_element.add("EXPLOSION4D", O_PRE_DIA_4);
277 name_to_element.add("EXPLOSION5D", O_PRE_DIA_5);
278 name_to_element.add("WALL2", O_STEEL_EXPLODABLE);
279 /* compatibility with old bd-faq (pre disassembly of bladder) */
280 name_to_element.add("BLADDERd9", O_BLADDER_8);
282 /* create table to show errors at the start of the application */
283 CharToElementTable _ctet;
285 /* check element database for faults. */
286 for (int i=0; gd_element_properties[i].element!=-1; i++) {
287 g_assert(gd_element_properties[i].element==i);
288 /* game pixbuf should not use (generated) editor pixbuf */
289 g_assert(abs(gd_element_properties[i].image_game)<NUM_OF_CELLS_X*NUM_OF_CELLS_Y);
290 /* editor pixbuf should not be animated */
291 g_assert(gd_element_properties[i].image>=0);
292 if (gd_element_properties[i].flags&P_CAN_BE_HAMMERED)
293 g_assert(gd_element_get_hammered(GdElementEnum(i))!=O_NONE);
295 /* if its pair is not the same as itself, it is a scanned pair. */
296 if (gd_element_properties[i].pair!=i) {
297 /* check if it has correct scanned pair, a->b, b->a */
298 g_assert(gd_element_properties[gd_element_properties[i].pair].pair==i);
299 if (gd_element_properties[i].flags & P_SCANNED) {
300 /* if this one is the scanned */
301 /* check if non-scanned pair is not tagged as scanned */
302 g_assert((gd_element_properties[gd_element_properties[i].pair].flags&P_SCANNED)==0);
303 /* check if no ckdelay */
304 g_assert(gd_element_properties[i].ckdelay==0);
305 } else if (gd_element_properties[gd_element_properties[i].pair].flags & P_SCANNED) {
306 /* if this one is the non-scanned */
307 g_assert((gd_element_properties[gd_element_properties[i].pair].flags & P_SCANNED)!=0);
308 } else {
309 /* scan pair - one of them should be scanned */
310 g_assert_not_reached();
315 g_assert(GD_SCHEDULING_MAX==G_N_ELEMENTS(scheduling_filename));
316 g_assert(GD_SCHEDULING_MAX==G_N_ELEMENTS(scheduling_name));
317 g_assert(MV_MAX==G_N_ELEMENTS(direction_filename));
318 g_assert(MV_MAX==G_N_ELEMENTS(direction_name));
319 g_assert(GD_ENGINE_MAX==G_N_ELEMENTS(engines_filename));
320 g_assert(GD_ENGINE_MAX==G_N_ELEMENTS(engines_name));
323 /// Load an element from a stream, where it is stored in its name.
324 /// If loading fails, the stream is set to an error state.
325 /// If the element name is not found, an error state is also set.
326 /// @param is The istream to load from.
327 /// @param e The element to store to.
328 std::istream& operator>>(std::istream &is, GdElementEnum &e) {
329 std::string s;
330 if (is>>s) {
331 if (!name_to_element.has_name(s))
332 is.setstate(std::ios::failbit);
333 else
334 e=name_to_element.lookup_name(s);
336 return is;
339 /// Save a GdBool to a stream, by writing either "false" or "true".
340 std::ostream& operator<<(std::ostream& os, GdBool const& b) {
341 os << (b?"true":"false");
342 return os;
345 /// Convert a string to a GdBool.
346 /// If conversion succeeds, sets b; otherwise b is left untouched.
347 /// @param s The string to convert. Can contain 0, 1, true, false, on, off, yes, no.
348 /// @param b The GdBool to write to.
349 /// @return true, if the conversion succeeded.
350 bool read_from_string(const std::string& s, GdBool& b) {
351 if (s=="1"
352 || gd_str_ascii_caseequal(s, "true")
353 || gd_str_ascii_caseequal(s, "on")
354 || gd_str_ascii_caseequal(s, "yes")) {
355 b=true;
356 return true;
358 else if (s=="0"
359 || gd_str_ascii_caseequal(s, "false")
360 || gd_str_ascii_caseequal(s, "off")
361 || gd_str_ascii_caseequal(s, "no")) {
362 b=false;
363 return true;
365 return false;
368 /// Save a GdInt to an ostream.
369 std::ostream& operator<<(std::ostream& os, GdInt const& i) {
370 /* have to convert, or else it would be infinite recursion? */
371 int conv=i;
372 os << conv;
373 return os;
376 /// Load a GdInt from a string.
377 /// If conversion succeeds, sets i; otherwise it is left untouched.
378 /// @param s The string to convert.
379 /// @param i The GdInt to write to.
380 /// @return true, if the conversion succeeded.
381 bool read_from_string(const std::string& s, GdInt& i) {
382 std::istringstream is(s);
383 /* was saved as a normal int */
384 int read;
385 bool success=(is>>read);
386 if (success)
387 i=read;
388 return success;
392 /// Save a GdProbability to an ostream.
393 std::ostream& operator<<(std::ostream& os, GdProbability const& i) {
394 double conv=i/1000000.0;
395 os << conv;
396 return os;
399 /// Load a GdProbability (stored as a floating point number) from a string.
400 /// If conversion succeeds, sets i; otherwise it is left untouched.
401 /// @param s The string to convert.
402 /// @param p The GdProbability to write to.
403 /// @return true, if the conversion succeeded.
404 bool read_from_string(const std::string& s, GdProbability& p) {
405 std::istringstream is(s);
406 double read;
407 bool success=(is>>read) && (read>=0 && read<=1);
408 if (success)
409 p=read*1000000+0.5;
410 return success;
413 /// Load a GdInt (stored as a floating point number) from a string.
414 /// If conversion succeeds, sets i; otherwise it is left untouched.
415 /// @param s The string to convert.
416 /// @param i The GdInt to write to.
417 /// @param conversion_ratio The number to multiply the converted value with. (1million for probabilities, cave width*height for ratios)
418 /// @return true, if the conversion succeeded.
419 bool read_from_string(const std::string& s, GdInt& i, double conversion_ratio) {
420 std::istringstream is(s);
421 double read;
422 bool success=(is>>read) && (read>=0 && read<=1);
423 if (success)
424 i=read*conversion_ratio+0.5;
425 return success;
428 /// Save a GdScheduling to an ostream with its name.
429 std::ostream& operator<<(std::ostream& os, GdScheduling const& s) {
430 os << scheduling_filename[s];
431 return os;
434 /// Read a GdScheduling from a string.
435 /// If conversion succeeds, sets sch; otherwise it is left untouched.
436 /// @param s The string to convert.
437 /// @param sch The GdScheduling to write to.
438 /// @return true, if the conversion succeeded.
439 bool read_from_string(const std::string& s, GdScheduling& sch) {
440 for (unsigned i=0; i<G_N_ELEMENTS(scheduling_filename); ++i)
441 if (gd_str_ascii_caseequal(s, scheduling_filename[i])) {
442 sch=GdSchedulingEnum(i);
443 return true;
445 return false;
448 /// Save a GdDirection to an ostream with its name.
449 std::ostream& operator<<(std::ostream& os, GdDirection const& d) {
450 os << direction_name[d];
451 return os;
454 /// Read a GdDirection from a string.
455 /// If conversion succeeds, sets d; otherwise it is left untouched.
456 /// @param s The string to convert.
457 /// @param d The GdDirection to write to.
458 /// @return true, if the conversion succeeded.
459 bool read_from_string(const std::string& s, GdDirection& d) {
460 for (unsigned i=0; i<G_N_ELEMENTS(direction_filename); ++i)
461 if (gd_str_ascii_caseequal(s, direction_filename[i])) {
462 d=GdDirectionEnum(i);
463 return true;
465 return false;
468 /// Save a GdElement to an ostream with its name.
469 std::ostream& operator<<(std::ostream& os, GdElement const& e) {
470 os << gd_element_properties[e].filename;
471 return os;
474 /// Read a GdElement from a string.
475 /// If conversion succeeds, sets e; otherwise it is left untouched.
476 /// @param s The string to convert.
477 /// @param e The GdElement to write to.
478 /// @return true, if the conversion succeeded.
479 bool read_from_string(const std::string& s, GdElement& e) {
480 if (!name_to_element.has_name(s))
481 return false;
482 e=name_to_element.lookup_name(s);
483 return true;
486 /// Read a GdEngine from a string.
487 /// If conversion succeeds, sets e; otherwise it is left untouched.
488 /// @param s The string to convert.
489 /// @param e The GdEngine to write to.
490 /// @return true, if the conversion succeeded.
491 bool read_from_string(const std::string& s, GdEngine& e) {
492 for (unsigned i=0; i<G_N_ELEMENTS(engines_filename); ++i)
493 if (gd_str_ascii_caseequal(s, engines_filename[i])) {
494 e=GdEngineEnum(i);
495 return true;
497 return false;