00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 #include <fun/DieRoll.h>
00019 #include <fun/Serializer.h>
00020 #include <fun/Deserializer.h>
00021
00022 #include <assert.h>
00023 #include <qdict.h>
00024 #include <qregexp.h>
00025 #include <iostream>
00026 #include <stdlib.h>
00027
00028 namespace fun {
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045 #define USE_DIEHASH 0
00046
00047
00048
00049 #if USE_DIEHASH
00050 #define SP_USE(sp) if (sp && !(sp->refcount++)) { diehash.insert(sp->toString(), sp); }
00051 #define SP_UNUSE(sp) if (sp && (!--sp->refcount)) { diehash.remove(sp->toString()); delete sp; }
00052 static QDict<SolidPlastic> diehash;
00053 #else
00054 #define SP_USE(sp) if (sp) { sp->refcount++; }
00055 #define SP_UNUSE(sp) if (sp && (!--sp->refcount)) { delete sp; }
00056 #endif
00057
00058 static QValueList<PluginDieRollParser> parsers;
00059
00060
00061
00062
00063
00064 SolidPlastic::SolidPlastic() : refcount(0) { }
00065
00066 SolidPlastic::~SolidPlastic()
00067 {
00068 assert(refcount == 0);
00069 }
00070
00071
00072 QValueList<DieRoll::WeightedValue>
00073 SolidPlastic::distribution()
00074 {
00075 return QValueList<DieRoll::WeightedValue>();
00076 }
00077
00078
00079
00080
00081
00082
00083
00084 class SimpleDieRoll : public SolidPlastic
00085 {
00086 public:
00087 SimpleDieRoll(int dice, int sides);
00088
00089 QString toString() const;
00090 int roll() const;
00091 int min() const;
00092 int max() const;
00093 bool isFlat() const;
00094 QValueList<DieRoll::WeightedValue> distribution();
00095
00096 private:
00097 int dice;
00098 int sides;
00099 QString str;
00100 QValueList<DieRoll::WeightedValue> dist;
00101 };
00102
00103 SimpleDieRoll::SimpleDieRoll(int d, int s) : dice(d), sides(s)
00104 {
00105 if( ! ((sides > 0) && (dice > 0)) ) return;
00106 if (sides > 1)
00107 {
00108 if (dice > 1) str.append(QString::number(dice));
00109 str.append("d");
00110 str.append(QString::number(sides));
00111 }
00112 else str.append(QString::number(dice));
00113 }
00114
00115
00116
00117 QString
00118 SimpleDieRoll::toString() const
00119 {
00120 return str;
00121 }
00122
00123 int
00124 SimpleDieRoll::roll() const
00125 {
00126
00127 if (sides == 1) return dice;
00128 int result = 0;
00129 for(int i = 0; i < dice; ++i)
00130 {
00131 result += ((rand() % sides) + 1);
00132 }
00133 return result;
00134 }
00135
00136 int
00137 SimpleDieRoll::min() const
00138 {
00139 return dice;
00140 }
00141
00142 int
00143 SimpleDieRoll::max() const
00144 {
00145 return dice * sides;
00146 }
00147
00148 bool
00149 SimpleDieRoll::isFlat() const
00150 {
00151 return ((dice == 1) || (sides == 1));
00152 }
00153
00154 QValueList<DieRoll::WeightedValue>
00155 SimpleDieRoll::distribution()
00156 {
00157 if (!dist.isEmpty()) return dist;
00158 DieRoll::WeightedValue wv;
00159 if (sides == 1)
00160 {
00161 wv.weight = 1.0;
00162 wv.value = dice;
00163 dist.append(wv);
00164 }
00165 else if (dice == 1)
00166 {
00167 wv.weight = 1.0 / (double)sides;
00168 for (int i = 1; i <= sides; ++i)
00169 {
00170 wv.value = i;
00171 dist.append(wv);
00172 }
00173 }
00174 else
00175 {
00176 NOT_DONE("SimpleDieRoll::distribution() doesn't handle multiple dice!");
00177 }
00178 return dist;
00179 }
00180
00181
00182
00183
00184
00185
00186
00187
00188 class CompoundDieRoll : public SolidPlastic
00189 {
00190 public:
00191
00192 CompoundDieRoll(SolidPlastic *d1, SolidPlastic *d2, int op);
00193 ~CompoundDieRoll();
00194 QString toString() const;
00195 int roll() const;
00196 int min() const;
00197 int max() const;
00198 bool isFlat() const;
00199
00200
00201 private:
00202 int doOp(int arg1, int arg2) const;
00203 SolidPlastic *d1;
00204 SolidPlastic *d2;
00205 int op;
00206 QString str;
00207 };
00208
00209 CompoundDieRoll::CompoundDieRoll(SolidPlastic *ad1, SolidPlastic *ad2, int aop)
00210 : d1(ad1), d2(ad2), op(aop)
00211 {
00212 assert(d1 && d2);
00213 assert((op == '+') || (op == '-') || (op == '*'));
00214 SP_USE(d1)
00215 SP_USE(d2)
00216 str = d1->toString();
00217 str.append(op);
00218 str.append(d2->toString());
00219 }
00220
00221 CompoundDieRoll::~CompoundDieRoll()
00222 {
00223 SP_UNUSE(d1)
00224 SP_UNUSE(d2)
00225 }
00226
00227 QString
00228 CompoundDieRoll::toString() const
00229 {
00230 return str;
00231 }
00232
00233 int
00234 CompoundDieRoll::roll() const
00235 {
00236 return doOp(d1->roll(), d2->roll());
00237 }
00238
00239 int
00240 CompoundDieRoll::min() const
00241 {
00242 if(op == '-') return d1->min() - d2->max();
00243 return doOp(d1->min(), d2->min());
00244 }
00245
00246 int
00247 CompoundDieRoll::max() const
00248 {
00249 if(op == '-') return d1->max() - d2->min();
00250 return doOp(d1->max(), d2->max());
00251 }
00252
00253 bool
00254 CompoundDieRoll::isFlat() const
00255 {
00256 if(!d1->isFlat() || !d2->isFlat()) return false;
00257 return d1->min() == d1->max() || d2->min() == d2->max();
00258 }
00259
00260 int
00261 CompoundDieRoll::doOp(int arg1, int arg2) const
00262 {
00263 if(op == '+') return arg1 + arg2;
00264 if(op == '-') return arg1 - arg2;
00265 if(op == '*') return arg1 * arg2;
00266 int badOpShouldHaveBeenHandledInCtor = 0;
00267 assert(badOpShouldHaveBeenHandledInCtor);
00268 return 0;
00269 }
00270
00271
00272
00273
00274
00275
00276 NAMESPACE_INSTANTIATOR(fun, DieRoll);
00277
00278
00279
00280 extern "C" { LoadableClass *newDieRoll() { return new fun::DieRoll; } }
00281
00282 extern std::ostream & operator<<(std::ostream &os, const DieRoll &d)
00283 {
00284 os << d.toString();
00285 return os;
00286 }
00287
00288 DieRoll::DieRoll()
00289 {
00290 dice = NULL;
00291 }
00292
00293 DieRoll::DieRoll(const DieRoll &other)
00294 {
00295 dice = other.dice;
00296 SP_USE(dice)
00297 }
00298
00299 DieRoll::~DieRoll()
00300 {
00301 SP_UNUSE(dice)
00302 }
00303
00304 DieRoll &
00305 DieRoll::operator=(const DieRoll &other)
00306 {
00307 SP_UNUSE(dice)
00308 dice = other.dice;
00309 SP_USE(dice)
00310 return *this;
00311 }
00312
00313 void
00314 DieRoll::serialize(Serializer &ser) const
00315 {
00316 ser.setSerializableClass("fun::DieRoll");
00317 ser.putString("dice", toString());
00318 }
00319
00320 void
00321 DieRoll::deserialize(const Deserializer &ser)
00322 {
00323 *this = fromString(ser.getString("dice"));
00324 }
00325
00326 int
00327 DieRoll::roll() const
00328 {
00329 return dice ? dice->roll() : 0;
00330 }
00331
00332 int
00333 DieRoll::min() const
00334 {
00335 return dice ? dice->min() : 0;
00336 }
00337
00338 int
00339 DieRoll::max() const
00340 {
00341 return dice ? dice->max() : 0;
00342 }
00343
00344 bool
00345 DieRoll::isFlat() const
00346 {
00347 return dice ? dice->isFlat() : true;
00348 }
00349
00350 QValueList<DieRoll::WeightedValue>
00351 DieRoll::distribution()
00352 {
00353 return dice ? dice->distribution() : QValueList<WeightedValue>();
00354 }
00355
00356 QString
00357 DieRoll::toString() const
00358 {
00359 return dice ? dice->toString() : QString("error");
00360 }
00361
00362
00363 DieRoll::DieRoll(SolidPlastic *sp) : dice(sp)
00364 {
00365 SP_USE(dice);
00366 }
00367
00368
00369
00370
00371
00372
00373
00374
00375
00376
00377
00378
00379
00380
00381
00382
00383
00384
00385
00386
00387
00388
00389
00390 static QRegExp reOperator("[-+*]");
00391
00392
00393
00394 static QRegExp reSimpleDieRoll("^\\d*[dD]\\d+$");
00395 static QRegExp reD("[dD]");
00396 static QRegExp reModifier("^[-+]?\\d*$");
00397
00398 static SolidPlastic *
00399 parse(const QString &diestr)
00400 {
00401 QString str = diestr;
00402 str = str.replace( QRegExp( "\\s" ), "" );
00403 #if USE_DIEHASH
00404
00405 SolidPlastic *sp = diehash.find(str);
00406 if (sp != NULL) return sp;
00407 #endif
00408
00409 if (!parsers.isEmpty())
00410 {
00411 QValueList<PluginDieRollParser>::Iterator it;
00412 for (it = parsers.begin(); it != parsers.end(); ++it)
00413 {
00414 PluginDieRollParser p = (*it);
00415 SolidPlastic *sp = (*p)(str);
00416 if (sp) return sp;
00417 }
00418 }
00419
00420 int pos;
00421 if ((pos = str.find(reOperator)) != -1)
00422 {
00423
00424 QString s1 = str.left(pos);
00425 QString s2 = str.right(str.length() - pos - 1);
00426 int op = str[pos].unicode();
00427 SolidPlastic *sp1, *sp2 = NULL;
00428 sp1 = parse(s1);
00429 if (sp1) sp2 = parse(s2);
00430 if (!sp2) return NULL;
00431 return new CompoundDieRoll(sp1, sp2, op);
00432 }
00433 if (str.find(reSimpleDieRoll) != -1)
00434 {
00435 pos = str.find(reD);
00436 QString ds = str.left(pos);
00437 QString ss = str.right(str.length() - pos - 1);
00438 int dice = ds.length() > 0 ? ds.toInt() : 1;
00439 int sides = ss.toInt();
00440 return new SimpleDieRoll(dice, sides);
00441 }
00442 if ((pos = str.find(reModifier)) != -1)
00443 {
00444 return new SimpleDieRoll(str.toInt(), 1);
00445 }
00446
00447 return NULL;
00448 }
00449
00450
00451 DieRoll
00452 DieRoll::fromString(const QString &src)
00453 {
00454 return DieRoll(parse(src));
00455 }
00456
00457 void
00458 DieRoll::addDieRollParser(PluginDieRollParser dp)
00459 {
00460 if (dp) parsers.append(dp);
00461 }
00462
00463 void
00464 DieRoll::removeDieRollParser(PluginDieRollParser dp)
00465 {
00466 if (dp) parsers.remove(dp);
00467 }
00468
00469 void
00470 DieRoll::testRolls(const DieRoll &d, int rolls, bool showSubs)
00471 {
00472 cout << "Rolling " << d << " " << dec << rolls << " times" << endl;
00473 cout << " " << d << ": min " << dec << d.min() << ", max " << d.max() << ", isFlat " << d.isFlat() << endl;
00474
00475
00476 if (d.min() == d.max())
00477 {
00478 cout << "(You're kidding, right? You rolled a " << dec << d.min() << ".)" << endl;
00479 return;
00480 }
00481
00482 int bucketmod = d.min() * -1;
00483
00484
00485
00486
00487 int buckets = d.max() - d.min() + 1;
00488 cout << dec << buckets << " buckets, " << bucketmod << " bucketmod" << endl;
00489 int *bucket = new int[buckets];
00490 if (!bucket)
00491 {
00492 cerr << "DieRoll::testRolls((): out of memory, bailing" << endl;
00493 return;
00494 }
00495 int *prevbucket = showSubs ? new int[buckets * buckets] : NULL;
00496 if (showSubs && !prevbucket)
00497 {
00498 cerr << "DieRoll::testRolls(): not enough memory to calculate subsequent results" << endl;
00499 showSubs = false;
00500 }
00501 int i, j, prev = -1000;
00502
00503
00504 memset((void *)bucket, 0, sizeof(int) * buckets);
00505 if (prevbucket) memset((void *)prevbucket, 0, sizeof(int) * buckets * buckets);
00506
00507
00508 for(i = 0; i < rolls; ++i)
00509 {
00510 j = d.roll();
00511 bucket[j + bucketmod] += 1;
00512 if(showSubs && (prev + bucketmod >= 0)) prevbucket[((prev + bucketmod) * buckets) + j + bucketmod] += 1;
00513 prev = j;
00514 }
00515
00516
00517 cout << "Results after " << dec << rolls << " rolls:" << endl;
00518 int sum = 0;
00519 for(i = 0; i < buckets; ++i)
00520 {
00521 sum += bucket[i];
00522 cout << " " << dec << (i - bucketmod) << ": " << bucket[i] << "\t" << ((float)(bucket[i]) / rolls * 100) << "%\t" << ((float)sum / rolls * 100) << "%" << endl;
00523 }
00524
00525 if(showSubs)
00526 {
00527 cout << "Subsequent results:" << endl;
00528 for(i = 0; i < buckets; ++i)
00529 {
00530 cout << " " << dec << (i - bucketmod) << ":" << endl;
00531 for(j = 0; j < buckets; ++j)
00532 {
00533 int pb = (i * buckets) + j;
00534 cout << " " << dec << (j - bucketmod) << ": " << prevbucket[pb] << " (" << (bucket[i] ? ((float)(prevbucket[pb]) / bucket[i] * 100) : 0) << " %)" << endl;
00535 }
00536 }
00537 }
00538 delete [] bucket;
00539 if (prevbucket) delete [] prevbucket;
00540 }
00541
00542 }