Initial version
This commit is contained in:
298
css/style.css
Normal file
298
css/style.css
Normal file
@@ -0,0 +1,298 @@
|
||||
/* Reset and Base Styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary-color: #5dade2;
|
||||
--secondary-color: #3498db;
|
||||
--success-color: #2ecc71;
|
||||
--warning-color: #f39c12;
|
||||
--error-color: #e74c3c;
|
||||
--bg-color: #1a1a1a;
|
||||
--card-bg: #2d2d2d;
|
||||
--text-color: #e0e0e0;
|
||||
--text-muted: #999999;
|
||||
--border-color: #404040;
|
||||
--shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color);
|
||||
background-color: var(--bg-color);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
header {
|
||||
text-align: center;
|
||||
padding: 2rem 0;
|
||||
background: linear-gradient(135deg, #1e3a5f, #2c5f8d);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
header .subtitle {
|
||||
font-size: 1rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
/* Input Section */
|
||||
.input-section {
|
||||
background: var(--card-bg);
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.input-section label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
#dragon-code-input {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
font-size: 16px;
|
||||
font-family: 'Monaco', 'Courier New', monospace;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
transition: border-color 0.3s;
|
||||
background-color: #1a1a1a;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
#dragon-code-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
#dragon-code-input::placeholder {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.auto-update-note {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.example-section {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.secondary-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 16px;
|
||||
background-color: var(--secondary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.secondary-btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.secondary-btn:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Output Section */
|
||||
.output-section {
|
||||
background: var(--card-bg);
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.welcome-message {
|
||||
text-align: center;
|
||||
padding: 3rem 1rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.welcome-message p {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.welcome-message .hint {
|
||||
font-style: italic;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Category Sections */
|
||||
.category-section {
|
||||
margin-bottom: 2rem;
|
||||
padding-bottom: 1.5rem;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
.category-section:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.category-section h2 {
|
||||
font-size: 1.5rem;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 3px solid var(--secondary-color);
|
||||
}
|
||||
|
||||
.category-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Trait Items */
|
||||
.trait-item {
|
||||
padding: 0.75rem;
|
||||
background: #1a1a1a;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid var(--success-color);
|
||||
}
|
||||
|
||||
.trait-item.warning {
|
||||
border-left-color: var(--warning-color);
|
||||
background: #3a2f1a;
|
||||
}
|
||||
|
||||
.trait-item.error {
|
||||
border-left-color: var(--error-color);
|
||||
background: #3a1a1a;
|
||||
}
|
||||
|
||||
.trait-label {
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.trait-value {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.trait-tag {
|
||||
display: inline-block;
|
||||
font-family: 'Monaco', 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
color: #999;
|
||||
background: #404040;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* Errors Section */
|
||||
.errors-section {
|
||||
background: #3a1a1a;
|
||||
border: 2px solid var(--error-color);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.errors-section h2 {
|
||||
color: var(--error-color);
|
||||
border-bottom-color: var(--error-color);
|
||||
}
|
||||
|
||||
/* Utility Classes */
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 2rem 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: var(--secondary-color);
|
||||
text-decoration: none;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
color: var(--primary-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
header .subtitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.category-section h2 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
.input-section,
|
||||
.output-section {
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
header h1 {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.category-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
794
dc.txt
Normal file
794
dc.txt
Normal file
@@ -0,0 +1,794 @@
|
||||
The Revised Dragon Code V2.6
|
||||
|
||||
What is the Dragon Code?
|
||||
The Dragon Code is a short form way for those in the Dragon community to describe exactly what they are. It is used primarilly in Usenet post or e-mail signatures to reduce the amount of text one would need for a proper description.
|
||||
The code itself consists of a number of 'tags' that represent various characteristics. These tags start with a symbol that indicates what the tag represents, and are followed by other symbols that indicate the strength of (or otherwise modify) that characteristic. The most common modifiers are the + and - symbols, the number of which determines how strong (or weak) that characteristic is.
|
||||
You can easily identify the Dragon Code from all the other codes that are present because it always starts with the letters DC. Because the Original Dragon Code started with DC and this revised code is somewhat different (and not compatible with the old code), all Revised Dragon Codes should start DC2.
|
||||
The Revised Dragon Code's tags are presented in an arbitrary order below, however you may put them in your code in any order you wish (though keeping them in the order they are presented helps others to decode them). You do not even have to include all of the tags - none of the tags are mandatory including the Species tag. When presenting your own Dragon Code you may want to put spaces between the tags to aid others in reading it, but this is not mandatory either as you can omit spaces if there is not enough room; the only time a space is required is when you omit your Species in which case a space is required after the initial DC2. .
|
||||
Still confused? Well scroll down and have a read through the tags. You'll soon get the hang of it!
|
||||
|
||||
The Tags:
|
||||
Central to the whole code are the various tags. When you are first presented with a Dragon Code, it can be quite daunting to decode (especially when the tags are in a strange order). However with a bit of practice you should soon be able to get the hang of it. Of course no one is expected to know the whole code off by heart, or even to be able to decode the more complicated codes, but there are various programs around that allow you to construct your own code and decode others.
|
||||
To date, there are tags that describe almost every aspect of Draconity, both in real and virtual life. Of course there will be many areas that will not be represented here, and in these cases you are free to include "free text" in any part of the code. The only requirement is that you put "double quotes" around any text to distinguish them from other tags.
|
||||
Common to all the tags are two modifiers, ? and ~. The ? modifier indicates that you are unsure or do not know what you are - this is often different from omitting the tag as that can indicate that you don't have that characteristic. The ~ modifier indicates that you do not have a fixed value for the tag, or that you vary. Some of the tags will have these modifiers described, but whether they are or not, they apply to all.
|
||||
The Dragon Code tags are case sensitive. All the initial tag letters must be in upper case whereas any modifying letters are in lower case. This is to prevent tags being confused.
|
||||
There are those who want to represent differences between their virtual lives (on the Internet) and their real lives. The original Dragon Code allowed this for specific codes, but that was felt to be too limiting especially as many are a different species in real life to virtual life. It is now recommended that you have a different code for each life you lead.
|
||||
Lastly, please treat the Dragon Code with an open mind and as nothing more than a bit of fun. If you are not represented here it is purely because it is not possible to think of every possible species or characteristic. That said, you can always e-mail your suggestions to Wyrm <info@dragoncode.org> who promises not to eat them!
|
||||
|
||||
|
||||
Species (DC2.*)
|
||||
Your species follows directly after the initial DC2. identifier. As with all the tags it is not mandatory, however if you do decide to omit your species then you must put a space instead.
|
||||
This tag is hierarchical in that the first symbol represents a large group, the next symbol a sub-group of that, and so on. Generally one would only need two symbols to describe your species fully.
|
||||
If your species is not listed, you can write it out long-hand in "double quotes". If your group already exists, put that first and follow it with the species in "double quotes".
|
||||
The following list shows all of the species available.
|
||||
|
||||
D Dragons - DC2.D indicates 'I am a Dragon! I will not be bothered by humanity's petty attempts to subdivide our race.'
|
||||
a Amphiteres - DC2.Da
|
||||
c Draconids - DC2.Dc
|
||||
d Dragonettes - DC2.Dd
|
||||
e Eastern Dragons - DC2.De
|
||||
f Faerie Dragons - DC2.Df
|
||||
h Hydra - DC2.Dh
|
||||
i Dimar - DC2.Di
|
||||
l Dracolich - DC2.Dl
|
||||
p Pernese Dragons - DC2.Dp
|
||||
r Turtle Dragons - DC2.Dr
|
||||
s Serpents - DC2.Ds
|
||||
s Sea Serpents - DC2.Dss
|
||||
f Fire Serpents - DC2.Dsf
|
||||
t Tarrasques - DC2.Dt
|
||||
u Pseudodragons - DC2.Du
|
||||
v Wyverns - DC2.Dv
|
||||
w Western Dragons - DC2.Dw
|
||||
y Wyrms - DC2.Dy
|
||||
|
||||
H Humanoids - DC2.H indicates 'I am a Human! Need I say any more?'
|
||||
a Apes - DC2.Ha
|
||||
d Dwarves - DC2.Hd
|
||||
e Elves - DC2.He
|
||||
w Wood Elves - DC2.Hew
|
||||
f Fairies - DC2.Hf
|
||||
i Giants - DC2.Hi
|
||||
g Gnomes - DC2.Hg
|
||||
h Hobbits - DC2.Hh
|
||||
k Kender - DC2.Hk
|
||||
y Nymphs - DC2.Hy
|
||||
t Troll - DC2.Ht
|
||||
w Wolfman - DC2.Hw
|
||||
? Unknown (Anthropomorphic) - DC2.H?
|
||||
For those who are humanoid versions of other creatures, put an H^ before the creature's code. E.g. DC2.H^Dw is a humanoid which resembles a Western Dragon.
|
||||
|
||||
A Amphibians - DC2.A indicates 'I am an Amphibian, and that's all you need to know!'
|
||||
f Frogs - DC2.Af
|
||||
n Newts - DC2.An
|
||||
s Salamanders - DC2.As
|
||||
t Toads - DC2.At
|
||||
|
||||
B Birds - DC2.B indicates 'You can try to categorise me, but I'm just a bird!'
|
||||
c Crows - DC2.Bc
|
||||
e Eagles - DC2.Be
|
||||
h Hawks - DC2.Bh
|
||||
p Phoenix - DC2.Bp
|
||||
r Ravens - DC2.Br
|
||||
|
||||
C Crustaceans - DC2.C indicates 'I have a shell, but you'll need x-ray vision to see past it!'
|
||||
c Crabs - DC2.Cc
|
||||
l Lobsters - DC2.Cl
|
||||
s Shrimps - DC2.Cs
|
||||
|
||||
S Dinosaurs - DC2.S indicates 'Look at my teeth ... does it matter what I am?!'
|
||||
a Allosaurs - DC2.Sa
|
||||
c Triceratops - DC2.Sc
|
||||
p Apatosaurs - DC2.Sp
|
||||
s Stegosaurs - DC2.Ss
|
||||
t Tyrannosaurs - DC2.St
|
||||
v Velociraptors - DC2.Sv
|
||||
|
||||
E Extraterrestrial - DC2.E indicates 'I don't exist on this planet.'
|
||||
d Daleks - DC2.Ed
|
||||
t Tribbles - DC2.Et
|
||||
|
||||
F Fish - DC2.F indicates 'I breath water, that should tell you all.'
|
||||
h Sea horses - DC2.Fh
|
||||
f Freshwater fish - DC2.Ff
|
||||
g Goldfish - DC2.Ffg
|
||||
t Trout - DC2.Fft
|
||||
s Sharks - DC2.Fs
|
||||
|
||||
I Insects - DC2.I indicates 'Six legs, a thorax, head, and abdomen. I also buzz a lot!'
|
||||
a Ants - DC2.Ia
|
||||
b Beetles - DC2.Ib
|
||||
f Flies - DC2.If
|
||||
l Locusts - DC2.Il
|
||||
m Moths - DC2.Im
|
||||
u Butterflies - DC2.Iu
|
||||
|
||||
L Legendary - DC2.L indicates 'My name has mythical significance ... what do you mean you've never heard of me!'
|
||||
r Gargoyles - DC2.Lr
|
||||
l Gremlins - DC2.Ll
|
||||
g Griffins or Gryphons - DC2.Lg
|
||||
n Manticores - DC2.Ln
|
||||
m Mermaids and Mermen - DC2.Lm
|
||||
f Salamanders - DC2.Lf - not the Amphibian type.
|
||||
s Sprites - DC2.Ls
|
||||
t Treants and Treefolk - DC2.Lt
|
||||
u Unicorns - DC2.Lu
|
||||
|
||||
M Mammals - DC2.M indicates 'I give birth to live young ... and what a strain it is too!'
|
||||
a Bats (and other avian like mammals) - DC2.Ma
|
||||
b Bears - DC2.Mb
|
||||
c Canines (dogs) - DC2.Mc
|
||||
d Domestic dogs - DC2.Mcd
|
||||
f Foxes - DC2.Mcf
|
||||
w Wolves - DC2.Mcw
|
||||
f Felines (cats) - DC2.Mf
|
||||
b Black panthers - DC2.Mfb
|
||||
c Cheetahs - DC2.Mfc
|
||||
d Domestic cats - DC2.Mfd
|
||||
p Leopard - DC2.Mfp
|
||||
s Snow Leopard - DC2.Mfps
|
||||
l Lions - DC2.Mfl
|
||||
x Lynxs - DC2.Mfx
|
||||
a Panthers - DC2.Mfa
|
||||
u Pumas - DC2.Mfu
|
||||
t Tigers - DC2.Mft
|
||||
h Horses - DC2.Mh
|
||||
m Monkeys - DC2.Mm
|
||||
g Gibbons - DC2.Mmg
|
||||
p Polecats - DC2.Mp
|
||||
f Ferrets - DC2.Mpf
|
||||
m Mink - DC2.Mpm
|
||||
r Rodents - DC2.Mr
|
||||
g Gerbils - DC2.Mrg
|
||||
h Hamsters - DC2.Mrh
|
||||
m Mice - DC2.Mrm
|
||||
r Rats - DC2.Mrr
|
||||
s Squirrels - DC2.Mrs
|
||||
w Cetaceans (whales) - DC2.Mw
|
||||
d Dolphins - DC2.Mwd
|
||||
k Killer Whales - DC2.Mwk
|
||||
p Porpoises - DC2.Mwp
|
||||
|
||||
O Molluscs - DC2.O indicates 'I keep my house with me ... I rarely need to move!'
|
||||
c Cuttlefish - DC2.Oc
|
||||
l Limpets - DC2.Ol
|
||||
o Octopuses - DC2.Oo
|
||||
y Oysters - DC2.Oy
|
||||
s Snails - DC2.Os
|
||||
|
||||
Y Mythical - DC2.Y indicates 'My name is legendary ... what do you mean you've never heard of me!'
|
||||
c Centaurs - DC2.Yc
|
||||
y Cyclopses - DC2.Yy
|
||||
g Golems - DC2.Yg
|
||||
h Hellhounds - DC2.Yh
|
||||
m Minotaurs - DC2.Ym
|
||||
p Pegasi - DC2.Yp
|
||||
t Satyrs - DC2.Yt
|
||||
s Sphinxes or Sphynxes - DC2.Ys
|
||||
|
||||
P Plants - DC2.P indicates 'I'm rooted to the spot, in more ways than one!'
|
||||
c Cacti - DC2.Pc
|
||||
f Fungii - DC2.Pf
|
||||
t Trees - DC2.Pt
|
||||
a Ash trees - DC2.Pta
|
||||
e Elm trees - DC2.Pte
|
||||
o Oak trees - DC2.Pto
|
||||
|
||||
R Reptiles - DC2.R indicates 'Cold blooded and scaly, or so you might think!'
|
||||
a Alligators and Crocodiles - DC2.Ra
|
||||
c Chameleons - DC2.Rc
|
||||
g Geckos - DC2.Rg
|
||||
k Komodo Dragons - DC2.Rk
|
||||
l Lizards - DC2.Rl
|
||||
n Skinks - DC2.Rn
|
||||
f Fire Skinks - DC2.Rnf
|
||||
s Snakes - DC2.Rs
|
||||
t Turtles - DC2.Rt
|
||||
|
||||
Q Spirits - DC2.Q indicates 'I really DO go bump in the night!'
|
||||
a Angels - DC2.Qa
|
||||
d Devils and Demons - DC2.Qd
|
||||
g Ghosts - DC2.Qg
|
||||
i Imps - DC2.Qi
|
||||
p Poltergeists - DC2.Qp
|
||||
s Spectres - DC2.Qs
|
||||
w Will-o-the-wisps - DC2.Qw
|
||||
|
||||
U Undead - DC2.U indicates 'I died, but I haven't stopped moving yet!'
|
||||
g Ghouls - DC2.Ug
|
||||
v Vampires - DC2.Uv
|
||||
z Zombies - DC2.Uz
|
||||
|
||||
~ Shapechangers - DC2.~ indicates 'Try to describe me and I'll change shape again!'
|
||||
Shapechangers who change between distinct types can include those types after the ~ using a / to seperate the species. E.g. DC2.~Dw/H would indicate 'I change between a Western Dragon and Human form.'
|
||||
It's also quite likely that your distinct forms have differring characteristics as well as similar ones. These can be given directly after the species tag using {} to contain the species specific tags. E.g. DC2.~Dw{Gf}/H{Gm}A would indicate 'I have a Western Dragon form that is female and a Human form that is male, but both are mature adults.'
|
||||
If you have a single stable form or a preferred form, you can indicate this by simply specifying that form before the ~, for example DC2.De~ would indicate 'I am an Eastern Dragon who changes shape occasionally.' This is subtely different from DC2.~De which is 'I am a shapechanger who takes the form of an Eastern Dragon.'
|
||||
|
||||
Miscellaneous (modifiers):
|
||||
? Unknown - DC2.? indicates 'I have no idea what I am!' One can also use ? after a Group (or sub-group) to indicate that you are uncertain as to what part of the group you are, e.g. DC2.R? would indicate 'I am a reptile, but I'm not sure which!'
|
||||
^ Shaped - This is used to indicate that you are mainly one form with features of another. For example DC2.H^Dw indicates 'I am Humanoid with Western Dragon features!'
|
||||
[ ] In another form - This is used when you are really one type of species, but on the outside you look like another. For example DC2.D[H] indicates 'I am a Dragon, trapped in Human form!'
|
||||
As for shapechangers, you can specify different tags for each species using {} to contain the species specific tags. E.g. DC2.Dw{Gf}[H{Gm}] would indicate 'I am a female Western Dragon trapped in a male Human body.'
|
||||
+ Crossed - DC2.D+H indicates 'A Dragon mated with a Human ... and I'm the result!'
|
||||
If you have a cross between species in the same group, you can leave out the second group, for example DC2.Dw+p indicates 'I am a cross between a western and a pernese dragon.'
|
||||
You can also have more than two species crossed by adding on more plusses!
|
||||
|
||||
|
||||
Gender (G*)
|
||||
One's Gender is something that can influence how others treat you. However there's more to it than just one's physical appearance.
|
||||
Gf Female
|
||||
Gh Hermaphrodite
|
||||
Gm Male
|
||||
Gn Neuter
|
||||
Gp Pseudo-hermaphrodite (One who has some characteristics of both male and female, but is not fully hermaphrodite, e.g. feminising testicular syndrome).
|
||||
G~ Variable between two or more. If you want to specify the various genders, include them in parenthesis after the ~, for example G~(fm) indicates 'I vary between female and male.'
|
||||
G? Unknown - 'No dice! Cartoon characters aren't anatomically correct!'
|
||||
Modifiers:
|
||||
> In transition between two types. You should indicate what you are changing from and to either side of the >. For example Gm>n indicates 'I am a male who is changing to a neuter.'
|
||||
" " If you have a different gender, you can put it after the G in "double quotes". For example G"sex".
|
||||
/ There are those who want to specify a different mental gender to their physical, in this case you use / to seperate the two with the mental gender going second. For example Gm/f indicates 'I am a female stuck in a male body.'
|
||||
|
||||
|
||||
Length (L*)
|
||||
Given that there is a collosal variation in size between Dragons, Dragon Friends, and other Species, it is important to know just what you're pounce-tackling or snuggle-hugging! You don't want to be accidentally squished after all!
|
||||
There are two methods of specifying ones length, an order of magnitude and an exact measure. You are free to choose whichever you feel is best for you.
|
||||
L+++! Celestial - 'I hate it when planets get in my mouth when I yawn!'
|
||||
L+++ Mistaken for mountain ranges - 'Sorry Mr Battleship, I didn't see you!'
|
||||
L++ Can't see own tail on a foggy day - 'Can you say "jungle gym"?'
|
||||
L+ Godzilla-sized - 'They look up to me. Literally.'
|
||||
L Draco-sized - 'About as normal as dragons get.'
|
||||
L- Human-sized - 'Please, don't step on the tail...'
|
||||
L-- Dog-sized - 'Please, don't step on me...'
|
||||
L--- Pocket Dragon-sized or below - 'Please, don't sneeze...'
|
||||
L---! Microscopic - 'Honey, I shrunk the dragon!'
|
||||
L~ Variable - 'I change size on a whim!'
|
||||
L^ One-Dragon-Sized - 'I am just long enough to reach the ground!'
|
||||
Quantitative method:
|
||||
L<number>i You are about that number of inches long (1 inch = 2.54 centimetres). For example L480i indicates 'I am about 480 inches long.'
|
||||
L<number>f You are about that number of feet long (1 foot = 30.5 centimetres). For example L40f indicates 'I am about 40 feet long.'
|
||||
L<number>y You are about that number of yards long (1 yard = 0.914 metres). For example L13y indicates 'I am about 13 yards long.'
|
||||
L<number>c You are about that number of centimetres long (1 centimetre = 0.393 inches). For example L1220c indicates 'I am about 1220 centimetres long.'
|
||||
L<number>m You are about that number of metres long (1 metre = 3.28 feet). For example L12m indicates 'I am about 12 metres long.'
|
||||
L<number>k You are about that number of kilometres long (1 kilometre = 0.621 miles). For example L0.01k indicates 'I am about a hundredth of a kilometre long.'
|
||||
Measurements are nose to tail for quadrupeds, snakes, serpents, etc; and are full standing height for bipeds (and others that stand upright). If you are not sure how to measure yourself, take the length from nose to tail tip.
|
||||
If you want to be more specific as to your dimensions, you can specify your tail, neck and head, and legs/arms length using these modifiers (which use the same unit of measure as the main length):
|
||||
Modifiers:
|
||||
<number>a Put after the length, this specifies your 'arm' length (primarilly for bipeds). For example L6f2a indicates 'I am 6 foot tall with arms that are 2 feet long each.'
|
||||
<number>l Put after the length, this specified your 'leg' length. For example L30c5l indicates 'I am 30 centimetres long with 5 centimetre long legs.'
|
||||
<number>n Put after the length, this specifies your 'neck and head' length. For example L1k0.2n indicates 'I am a kilometre in length with a neck and head that are 300 metres in length.'
|
||||
<number>t Put after the length, this specifies your 'tail' length. For example L6i2t indicates 'I am 6 inches long with a 2 inch long tail.'
|
||||
<number>w Put after the length, this specifies your 'wingspan' (wing-tip to wing-tip). For example L3i6w indicates 'I am 3 inches tall with a 6 inch wingspan.'
|
||||
You can also combine modifiers, for example L10m4t1l indicates 'I am 10 metres long with a 4 metre long tail and 1 metre long legs.'
|
||||
For plants, treat the body as the trunk or stem, legs as roots, arms as branches, wings as leaves, and the neck and head as the flower and its stem.
|
||||
|
||||
|
||||
Width (W*)
|
||||
There comes a time in a human's life when they stop growing vertically and start to grow horizontally! Well, so do Dragons! This tag is a measure of your spread, or lack of it.
|
||||
W+++! 'I am Athelind! My belly is now several galaxies wide ... while I'm only a few hundred feet long!'
|
||||
W+++ 'Planets have been known to crack in half with my arrival!'
|
||||
W++ 'My digestion of food has been known to cause earthquakes.'
|
||||
W+ 'I move by rolling. Flying has always been an effort for me.'
|
||||
W 'What can I say ... I'm normal, except for a few feasts here or there.'
|
||||
W- 'I'm slightly on the slim side!'
|
||||
W-- 'Ever heard of serpentine?'
|
||||
W--- 'Whoah! Whaddaya mean I look like a long string with wings?'
|
||||
W---! 'I'm one-dimensional - all length and no width or depth. Just one long super-string!'
|
||||
W~ Variable - 'My girth depends on what I've just eaten!'
|
||||
|
||||
|
||||
Weight (Tonnage) (T*)
|
||||
As well as knowing the dimensions of another Dragon, it is also important to know it's weight too. Especially if one intends on having it sit on one's lap!
|
||||
There are two methods of specifying ones weight, an order of magnitude and an exact measure. You are free to choose whichever you feel is best for you.
|
||||
T+++! Black-Hole - 'Everything gravitates towards me eventually!'
|
||||
T+++ Massive - 'I've been eating rocks again!'
|
||||
T++ Obese - 'I'm on a sea-food diet ... I see food and eat it!'
|
||||
T+ Over-weight - 'I've been eating too many doughnuts again!'
|
||||
T Normal - 'I'm as normal a weight as one gets.'
|
||||
T- Under-weight - 'I've not had a good meal for ages!'
|
||||
T-- Buoyant - 'I've been breathing helium again!'
|
||||
T--- Feather-weight - 'I get blown about by the wind.'
|
||||
T---! Weightless - 'I'm made out of gossamer.'
|
||||
Quantitative method:
|
||||
T<number>c You weigh about that number of long hundredweight avoirdupois (1 cwt = 50.8 kilogrammes or 8 stone / 112 pounds). For example T16c indicates 'I am about 16 hundredweight.'
|
||||
T<number>g You weigh about that number of grammes (1 gramme = 0.0353 ounces). For example T800000g indicates 'I am about 800000 grammes.'
|
||||
T<number>k You weigh about that number of kilogrammes (1 kilogramme = 2.205 pounds). For example T800k indicates 'I am about 800 kilogrammes.'
|
||||
T<number>l You weigh about that number of pounds avoirdupois (1 pound = 0.4536 kilogrammes or 16 ounces). For example T1800l indicates 'I am about 1800 pounds.'
|
||||
T<number>o You weigh about that number of ounces avoirdupois (1 ounce = 28.35 grammes). For example T28700o indicates 'I am about 28700 ounces.'
|
||||
T<number>s You weigh about that number of stones avoirdupois (1 stone = 6.35 kilogrammes or 14 pounds). For example T128s indicates 'I am about 128 stones.'
|
||||
T<number>t You weigh about that number of tons avoirdupois or metric tonnes (1 ton = 1016 kilogrammes or 2240 pounds, 1 tonne = 2205 pounds or 1000 kilogrammes). It's up to you which one you mean! For example T0.8t indicates 'I am about 0.8 tons.'
|
||||
|
||||
|
||||
Appendages (P*)
|
||||
This tag describes the numbers of limbs and other appendages on your body. If you have the 'obvious' numbers of limbs for a creature of your species you can skip this tag... but then what's normal?!?!
|
||||
Pa A pair of arms
|
||||
Pf A pair of fore-limbs (e.g. limbs that can be used as both arms and legs).
|
||||
Ph A head. Yes! There are creatures with more than one!
|
||||
Pk A crest. Normally on ones head, but you never know!
|
||||
Pl A pair of legs.
|
||||
Pp A pair of paddles, flukes, or fins.
|
||||
Pt A tail. Again, some creatures do have more than one!
|
||||
Pv A pair of horns or spines on the head.
|
||||
Pw A pair of wings.
|
||||
Pw' A pair of wings that also act as arms, legs, or fore-limbs.
|
||||
Modifiers:
|
||||
^ Appendage ends in a webbed hand or foot. For example Pl^ indicates 'Don't call me a frog!'
|
||||
+ One more than normal. For example Ph+ indicates 'I got ahead in advertising!'
|
||||
- One less than normal. For example Pw- indicates 'I have only one wing!'
|
||||
! I have many of these. For example Pl! indicates 'I am a millipede!'
|
||||
<number> I have this many of these. For example Pl3l- indicates 'I am an octopus with a missing leg!'
|
||||
~ Variable. For example Pl~ indicates 'I grow legs when I feel the need for them!'
|
||||
You can combine the above options together in any you want. For example Phwlwllt indicates that you have a head, a pair of wings, a pair of legs, a pair of wings, then two pairs of legs, and a tail.
|
||||
You can also miss out any options that are obvious for your species - most sensible creatures have only one head and a tail!
|
||||
|
||||
|
||||
Skin-Type (Sk*)
|
||||
What one feels like to the touch is something which can often be nice to know. You can let others know what they're in for by using this tag. If your skin type is what would be considered usual for your species you don't have to use this tag, but what is usual for dragons?
|
||||
Skb Bark.
|
||||
Skc Cellulose.
|
||||
Ske Exoskeleton (shells, Calcium Carbonate).
|
||||
Skf Feathers.
|
||||
Skh Hide.
|
||||
Skk Skin.
|
||||
Skl Leather.
|
||||
Skm Metal.
|
||||
Skr Rock (Stone).
|
||||
Sks Scales.
|
||||
Sku Fur.
|
||||
Skx Crystals.
|
||||
Sk None (just bones).
|
||||
Body parts:
|
||||
,a<type> Arms. Sks,ak is 'My body is scaly, but I have skin on my arms.'
|
||||
,b<type> Belly. Sks,bm is 'My body is scaly, but I have a metal underside.'
|
||||
,h<type> Head. Sku,hf is 'My body is furry, and I have a head of feathers.'
|
||||
,l<type> Legs. Skr,lh is 'My body is rocky, and I have legs of hide.'
|
||||
,n<type> Neck. Skb,nc is 'My body (trunk) has bark, but my neck (flower stem) is cellulose.'
|
||||
,t<type> Tail. Sks,th is 'My body is scaly, but my tail has hide.'
|
||||
,w<type> Wings. Sks,wl is 'My body is scaly, and I have leathery wings.'
|
||||
For plants, treat the body as the trunk or stem, legs as roots, arms as branches, wings as leaves, neck as flower stem, and head as the flower.
|
||||
|
||||
|
||||
Colouration (C*)
|
||||
Ones colour can be very important to some, especially dragons. This tag has been expanded and revolutionised from the original Colour tag such that you can now specify the colour of almost every part of your body!
|
||||
Cag Silver (Argent)
|
||||
Cam Amber
|
||||
Caq Aquamarine
|
||||
Cau Gold (Aurum)
|
||||
Cbk Black
|
||||
Cbl Blue
|
||||
Cbr Brown
|
||||
Cbs Brass
|
||||
Cbz Bronze
|
||||
Cch Chromium
|
||||
Ccu Copper (Cuprum)
|
||||
Ccy Cyan
|
||||
Ceb Ebony
|
||||
Cfs Flesh (Human)
|
||||
Cgr Green
|
||||
Cgy Grey
|
||||
Chg Mercury / Quicksilver (Hydrargyrum)
|
||||
Cin Indigo
|
||||
Civ Ivory
|
||||
Cja Jade
|
||||
Cma Magenta
|
||||
Cmv Mauve
|
||||
Cor Orange
|
||||
Cpi Pink
|
||||
Cpu Purple
|
||||
Crb Rainbow (violet, indigo, blue, green, yellow, orange, red).
|
||||
Cre Red
|
||||
Cta Tan
|
||||
Ctu Turquoise
|
||||
Cmb Umber
|
||||
Cvi Violet
|
||||
Cwh White
|
||||
Cye Yellow
|
||||
C~ Chameleonic. 'I can be whatever colour I want to.'
|
||||
C? Unknown. 'Either I don't know what colour I am, or I'm just not saying!.'
|
||||
C Colourless (ice, crystal, or invisible creatures).
|
||||
Modifiers:
|
||||
+ Put after the colour, this indicates 'light'. The more the plusses, the lighter the colour. For example, Cbl+ is Light Blue.
|
||||
- Put after the colour, this indicates 'dark'. The more the minuses, the lighter the colour. For example, Cbl- is Dark Blue.
|
||||
^ Put after the colour, this indicates 'metallic'. For example, Cbl^ is Metallic Blue.
|
||||
_ Put after the colour, this indicates 'transparent' or 'gemstone'. For example, Cbl_ is Transparent Blue, Blue glass, or Sapphire.
|
||||
' Put after the colour, this indicates 'fiery' or 'luminescent'. For example, Cbl' is Blue flame, or luminous Blue.
|
||||
% Put after the colour, this indicates 'pearlescent' (having the look of mother of pearl). For example, Cbl% is pearlescent Blue.
|
||||
! Put after the colour, this indicates 'glittery' or 'sparkly'. For example, Cbl! is glittery Blue.
|
||||
Further Modifiers:
|
||||
|<colour> Put after the colour and any modifiers, this indicates 'stripes'. For example, Cbl+|au is 'light blue with gold stripes.'
|
||||
=<colour> Put after the colour and any modifiers, this indicates 'bands'. For example, Cbl+=au is 'light blue with gold bands.'
|
||||
:<colour> Put after the colour and any modifiers, this indicates 'spots'. For example, Cbl+:au is 'light blue with gold spots.'
|
||||
*<colour> Put after the colour and any modifiers, this indicates 'stars'. For example, Cbl+*au is 'light blue with gold stars.'
|
||||
@<colour> Put after the colour and any modifiers, this indicates 'mottled'. For example, Cbl+@au is 'light blue mottled with gold.'
|
||||
\<colour> Put after the colour and any modifiers, this indicates 'iridescence', i.e. another colour that shows under a different light or viewing angle. For example, Cbl+\au is 'light blue with gold iridescence.'
|
||||
/<colour> Mix. This goes after a colour part to indicate that one is a random mix of several colours. For example Cpu/ye/wh indicates 'I have areas of purple, yellow, and white!'
|
||||
#<colour> Put after the colour, this indicates 'plaid'. The first colour is the main colour of the plaid with the following colours the overlays. For example, Cre#bl#bk is Red Plaid with Blue and Black.
|
||||
&<colour> Put after the colour, this indicates 'patterned'. The first colour is the main colour with the following colours the overlays of the pattern. For example, Cgy&re&in+ is Grey patterned with Red and light Indigo.
|
||||
&1<colour> Put after the colour, this indicates 'marble patterned'. The first colour is the main colour with the following colours the veins of the marble pattern. For example, Cre&1re-&pu is Red marble with Dark Red and Purple veins. Lovely!
|
||||
><colour> In transition. For those who are changing from one colour to another. For example, Cau>ag is 'gold changing to silver.'
|
||||
Body parts:
|
||||
,a<colour> Arms. Cbl,apu is 'Blue with Purple arms.'
|
||||
,b<colour> Belly or Underside. Cbl,bpu is 'Blue with a Purple belly.'
|
||||
,c<colour> Claws/Feet/Hands. Cbl,cpu is 'Blue with Purple claws.'
|
||||
,e<colour> Eyes. Cbl,epu is 'Blue with Purple eyes.'
|
||||
,f<colour> Fur/Hair. Cbl,fpu is 'Blue with Purple fur.'
|
||||
,h<colour> Head. Cbl,hpu is 'Blue with a Purple head.'
|
||||
,k<colour> Crest. Cbl,kpu is 'Blue with a Purple crest.'
|
||||
,l<colour> Legs. Cbl,lpu is 'Blue with Purple legs.'
|
||||
,n<colour> Neck. Cbl,npu is 'Blue with a Purple neck.'
|
||||
,p<colour> Points. Cbl,ppu is 'Blue with Purple points or highlights.'
|
||||
,s<colour> Spines. Cbl,spu is 'Blue with Purple spines.'
|
||||
,t<colour> Tail. Cbl,tpu is 'Blue with a Purple tail.'
|
||||
,u<colour> Aura. Cbl,upu is 'Blue with a Purple aura.'
|
||||
,v<colour> Horns/Spines. Cbl,vpu is 'Blue with Purple horns.'
|
||||
,w<colour> Wings. Cbl,wpu is 'Blue with Purple wings.'
|
||||
You can combine the above modifiers and body parts to make a complicated colour tag if required. For example Cre-/br,bbl+,wor_,pye,eau indicates 'I am a mix of dark red and brown with a light blue belly, transparent orange wings, yellow points, and golden eyes.'
|
||||
For plants, treat the body as the trunk or stem, legs as roots, arms as branches, wings as leaves, neck as flower stem, and head as the flower.
|
||||
|
||||
|
||||
Breath-Weapon (B*)
|
||||
Quite a few Dragons have been famed for their ability to exhale a stream of fire at their enemies. However there are many other types of Breath-Weapon in use.
|
||||
The following options are by no means a definitive list and are being added to constantly. However, if you don't see your particular type of breath-weapon listed, you can write it out in "double quotes". For example B"skittles" indicates that you breath skittles - presumably not the ten-pin kind!
|
||||
Bac Acid.
|
||||
Bco Cold or frost.
|
||||
Ben Enchantment.
|
||||
Beg Energy.
|
||||
Bfl Flame or fire.
|
||||
Bhe Heat.
|
||||
Bic Ice.
|
||||
Bla Lava or magma.
|
||||
Bph Photons or Light.
|
||||
Bpl Plasma.
|
||||
Bro Rot.
|
||||
Bsm Smoke.
|
||||
Bst Steam.
|
||||
Bsu Sulphur.
|
||||
Bvg Volcanic gasses (e.g. pyroclastics).
|
||||
Bwa Water.
|
||||
Bwi Wind.
|
||||
Bzz Electricity or lightning.
|
||||
B- No Breath-Weapon. You could also use B"none".
|
||||
Modifiers:
|
||||
| Beam - For example Beg| indicates 'I am a Dalek! Exterminate! Exterminate!'
|
||||
# Cloud - For example Bsm# indicates 'When I breathe, ships need to use their fog-horns!'
|
||||
/ If you have several breath-weapons, you can specify them all using / to seperate each one. For example Bfl/ac/"chocolate" indicates 'I breathe fire, acid, and chocolate ... anyone for melted acid drops?'
|
||||
You can also put the specific type of weapon after the option in "double quotes". For example Bac"sulphuric" indicates that you breathe sulphuric acid.
|
||||
|
||||
|
||||
Age (A*)
|
||||
Your age is something that can be very personal, or something to be very proud of!
|
||||
A+++! Ancient
|
||||
A+++ Venerable
|
||||
A++ Old enough to know better!
|
||||
A+ You've been around.
|
||||
A Mature Adult
|
||||
A- Young Adult
|
||||
A-- Still under Mom's (or Dad's) wing.
|
||||
A--- Hatchling
|
||||
A---! An egg (nearly hatched)!
|
||||
A? I have no idea how old I am .. I lost count years ago!
|
||||
Modifiers:
|
||||
r Put after the A, this indicates that one is specifying one's 'Real Life' age. For example Ar- indicates 'I am a young adult in real life.'
|
||||
v Put after the A, this indicates that one is specifying one's 'Virtual Life' age. For example Av+ indicates 'I've been around on the 'net!'
|
||||
( ) You can use both of the above in seperate tags to specify both ages, but you can also use a short-form method by just putting the r and v parts within parenthesis. For example Ar-Av+ and A(r-v+) both indicate 'I am a young adult in real life, but have been around on the 'net.'
|
||||
For reference, Human teenagers would be A-, A would indicate someone in their 20s to mid-30s, and every + would indicate about another 15 years.
|
||||
|
||||
|
||||
Fruitiness (Fr*)
|
||||
The very first on-line meeting place for dragons, and dragon-lovers, was the newsgroup alt.fan.dragons. It's still fondly considered to be home for many. Those who have been there for a while, or who have visibly enriched the newsgroup, or even those just appear to have been there for ages are (jokingly) called 'Fruits', whilst those still new to the AFD community are referred to as 'nuts'.
|
||||
This tag indicates the level of fruitiness, or nuttiness. There are two different standards that you can use, a time based one and an attitude based one. You are free to choose whichever of the two you prefer, and are not at liberty to specify which you have chosen to use!
|
||||
Time-based fruitiness:
|
||||
Fr+++! I remember when humans ruled the newsgroup!
|
||||
Fr+++ I remember Dalvenjah posting regularly.
|
||||
Fr++ I've been here for a couple of years or so.
|
||||
Fr+ I've been here just over a year now.
|
||||
Fr Old fruits know my name and what I'm like.
|
||||
Fr- My name is known, but that's about it.
|
||||
Fr-- They're still responding to my welcome post.
|
||||
Fr--- I haven't posted yet, I'm just lurking!
|
||||
Fr---! I've never heard of AFD!
|
||||
Attitude-based fruitiness:
|
||||
Fr+++! I'm too fruity for my hat!
|
||||
Fr+++ I am well into the fermentation process.
|
||||
Fr++ I have accidentally been used in food fights.
|
||||
Fr+ Friends have spotted me in fruit salads.
|
||||
Fr Squeeze me! I'm ripe!
|
||||
Fr- I haven't fallen off the tree yet!
|
||||
Fr-- It can be said that I am very a-peel-ing!
|
||||
Fr--- A squirrel tried to hoard me the other day.
|
||||
Fr---! I'm only a sparkle in the eye of the tree!
|
||||
Ratings common to both:
|
||||
Fr^ Have joined, left, returned, etc.
|
||||
Fr* I got flamed off the newsgroup for yelling at Shim! :8)
|
||||
If you happen to know what sort of fruit or nut you are, you can specify it after the rating in "double quotes". Apparently Baxil (the original maintainer of the code) is a Fr+++"kiwi"!
|
||||
|
||||
|
||||
Native-Land (N*)
|
||||
The native territory, or natural habitat, of a creature can be quite important to them. Mant dragons are associated with different types of land, as are many creatures. However, if this tag does not apply to you (as it doesn't for many humanoids) then leave it out altogether. A good rule is that if you feel drawn to more than one of the listed land types, you probably don't have a specific native land.
|
||||
Na Air - 'I live in the clouds!'
|
||||
Ne Earth - 'I prefer to dwell underground!'
|
||||
Nf Fire - 'I am a child of the flame!'
|
||||
Ni Ice - 'I thrive in the cold polar winds and the tundra is my playground!'
|
||||
Nj Jungle - 'I only feel happy in the tropical rainforests of my home.'
|
||||
Nm Mountains - 'I am only truly happy when surrounded by tall peaks!'
|
||||
Nn Nature - 'I was born in the wilderness, and that's where I'm happiest!'
|
||||
Np Plains - 'I prefer to walk on a carpet of green!'
|
||||
Nr Rock - 'I have a bed of marble and a granite pillow!'
|
||||
Ns Space - 'The dark lifeless void is my home!'
|
||||
Nt Trees - 'I like swinging through the branches!'
|
||||
Nu Urban - 'Cities and towns hold fascination for me!'
|
||||
Nw Water - 'I am only truly at home in water!'
|
||||
N^ Ethereal - 'I exist in the folds of reality!'
|
||||
N! Imaginary - 'I am a figment of my imagination!'
|
||||
|
||||
|
||||
Mating-Status (M*)
|
||||
Sure, there are those lucky/unlucky dragons among us who are mated/tied-down in reality, but what about those who have a Special Someone on-line? Or are looking ... desperately?! Or are hiding from the lookers?!
|
||||
M+++! I'm mated in real life to my on-line mate's real life self as well!
|
||||
M+++ Am I mated? Ask my hatchlings!
|
||||
M++ Ask my significant other!
|
||||
M+ Ask my mate-to-be!
|
||||
M Ask me, and I might just say yes!
|
||||
M- Don't ask!
|
||||
M-- Ask my (poker/football/bridge/Trivial Pursuit) buddies!
|
||||
M--- I'm single and very proud of it!
|
||||
M---! Not only am I single, but I despise the idea of mating.
|
||||
M/ Ask me, and I'll ask your snout to meet your kidneys!
|
||||
Modifiers:
|
||||
<space> Put before the plusses (doesn't apply to minuses), this indicates that one is distant from one's mate. For example M ++ indicates 'Ask my significant other, who is far from home!'
|
||||
<number> Put after the plusses, this indicates the number of mates one has. For example M++3 indicates 'Ask my three significant others!'
|
||||
| Put after the plusses (doesn't apply to minuses) or the number of mates if specified, this indicates that you are seperated (e.g. divorced). If you have more than one mate, you should put the number of seperated mates after the |, for example M+++2|1 indicates 'Am I mated? Ask my hatchlings of either of my two mates, one of which I am seperated from.'
|
||||
* Identical to | above except that it indicates one was bitter about it!
|
||||
_ Identical to | above except that it indicates your mate is dead.
|
||||
xx Put at the end of the tag, this indicates that one is a card-carrying member of the XX Conspiracy!
|
||||
xy Put at the end of the tag, this indicates that one is a card-carrying member of the XY Conspiracy!
|
||||
r Put after the M, this indicates that one is specifying one's 'Real Life' mating status. For example Mr- indicates 'Don't ask me in real life.'
|
||||
v Put after the M, this indicates that one is specifying one's 'Virtual Life' mating status. For example Mv+ indicates 'Ask my mate-to-be on the 'net.'
|
||||
( ) You can use both of the above in seperate tags to specify both mating statuses, but you can also use a short-form method by just putting the r and v parts within parenthesis. For example Mr-Mv+ and M(r-v+) both indicate 'Don't ask me in real life, but ask my mate-to-be on the 'net.'
|
||||
|
||||
|
||||
Offspring (O*)
|
||||
Many dragons who are mated (and also a few who aren't) have either guarded a clutch or two of eggs or protected some hatchlings against predators (yum yum)! This tag allows you to show how many hatchlings you have raised (or are raising).
|
||||
O+++! I have many of them, and none of them look like leaving yet!
|
||||
O+++ I have several that haven't fledged yet.
|
||||
O++ I have a couple that have not yet left home.
|
||||
O+ I've got one still in the nest.
|
||||
O There's just me (and my mate, if applicable).
|
||||
O- I had one, but they've left home.
|
||||
O-- I had a couple, but they're living their own lives now.
|
||||
O--- I've had several, all successfully fledged.
|
||||
O---! I've had many, but they've all flown away now.
|
||||
O+<number> Indicates exactly how many you have and that they've not left home yet.
|
||||
O-<number> Indicates exactly how many you have had that have flown the coop.
|
||||
O? I lost track long ago. If it meeps, I feed it.
|
||||
O~ I have a variable number, depending on who I'm fostering this month!
|
||||
O/ If I ever heard one was my fault, I'd faint!
|
||||
Modifiers:
|
||||
r Put after the O, this indicates that one is specifying one's 'Real Life' offspring. For example Or- indicates 'I had one in real life, but they left home.'
|
||||
v Put after the O, this indicates that one is specifying one's 'Virtual Life' offspring. For example Ov+ indicates 'I've got one on the 'net, still in the nest.'
|
||||
( ) You can use both of the above in seperate tags to specify both types of offspring, but you can also use a short-form method by just putting the r and v parts within parenthesis. For example Or-Ov+ and O(r-v+) both indicate 'I had one in real life, but they left home; and I've got one on the 'net, still in the nest.'
|
||||
a Put before any plusses or minuses, this indicates that one is specifying adopted offspring. This can be real or virtual. For example Ova+ indicates 'I've got one adopted on the 'net that is still at home.'
|
||||
|
||||
|
||||
Hoard-Size (H*)
|
||||
This should need little explanation!
|
||||
H+++! Governments quake when they hear you're going to sell!
|
||||
H+++ You really can't tell when someone steals an item!
|
||||
H++ You're comfortable, but you wouldn't mind some more.
|
||||
H+ You're not down to your last copper, but...
|
||||
H You've got your own lair, unfurnished.
|
||||
H- You and the bank own your lair.
|
||||
H-- The lair is rented, and cramped.
|
||||
H--- No money, no lair, at least you aren't starving!
|
||||
H---! Look on the bright side. At least you've got friends!
|
||||
|
||||
|
||||
Monetary-Philosophy ($*)
|
||||
Different dragons, surprisingly, have very different attitudes about their money. This tag allows you to let everyone know what your attitude is.
|
||||
$+++! All things of value gravitate towards me. When I've got my claws on them I never let go!
|
||||
$+++ The coinage has my likeness on it. I own the whole kingdom!
|
||||
$++ Besides humans, I kill other dragons for their wealth.
|
||||
$+ Investments in human businesses. I do repossessions "CHOMP" personally!
|
||||
$ Take your hands off my hoard or I'll take your hands.
|
||||
$- Bought up the weapons smiths with my savings. At least there's no more dragon-proof armour or vorpal swords.
|
||||
$-- Some thief stole most of it whilst I was out hunting hobbits!
|
||||
$--- I'm a philanthropist. I gave it all to the poor!
|
||||
$---! I eschew all things of value. I don't need them! Whatever I find I give away.
|
||||
|
||||
|
||||
Diet (F*)
|
||||
Most creatures eat something (or feed on something to be more exact). This simply indicates your favourite food groups and how much you tend to eat.
|
||||
F+++! Insatiable - 'More! More! More!'
|
||||
F+++ Voracious - 'At least you don't have to wash up the plates now!'
|
||||
F++ Glutton - 'I have three square meals an hour!'
|
||||
F+ Overindulgent - 'One more of those won't hurt!'
|
||||
F Normal for your species - 'I have three square meals a day!'
|
||||
F- Dieting - 'Only a soupçon of that if you please!'
|
||||
F-- Efficient - 'Only soup if you please!'
|
||||
F--- Anorexic - 'Eating is something I do every blue moon!'
|
||||
F---! Fasting - 'Duke Humphrey is an ideal dinner guest!'
|
||||
Food groups:
|
||||
Fc Carnivourous.
|
||||
Ff Fluids - 'Here fishy fishy fish!'
|
||||
Fj Junk food addict - 'Chocolate for ME!'
|
||||
Fm Mineral-eater - 'I AM the Big Red Rock Eater!'
|
||||
Fo Omnivorous.
|
||||
Fp Photosynthesiser.
|
||||
Ft Thaumivore - 'I eat magic for breakfast and mana for tea!'
|
||||
Fv Vegetarian.
|
||||
Modifiers:
|
||||
/ If you eat several things, you can seperate their codes with /. For example Fv/f indicates 'I eat and drink normally, but nothing animate!'
|
||||
~ This indicates that you eat anything. If put after a food group it indicates you eat anything of that type. For example Fv~ indicates 'I eat anything vegetable.'
|
||||
You can combine the above in any order, for example F---f is the same as Ff--- which indicates 'I live in a desert!'
|
||||
The amount you tend to eat should only appear once and is not related to the food groups.
|
||||
|
||||
|
||||
Reality-Index (R*)
|
||||
Your Reality tag measures how serious you are about your on-line identity in real life. This applies to all species, even humans as one can be very serious about their humanity!
|
||||
R+++! I AM as I describe myself, and no-one is going to persuade me otherwise. What you see is what your mind wants you to see!
|
||||
R+++ I AM as I describe myself on-line. The human body is either an unfortunate mismatch, an illusion, or just my messenger.
|
||||
R++ I tend to answer to my on-line name quicker than my given one. I strongly identify with this form. It would explain a lot if I wasn't really human, but I'm hedging my bets.
|
||||
R+ It's a role, but it's a role that's part of my self-image, without which I would feel like something was missing.
|
||||
R I haven't given much thought to whether I am more "real" in human form or in my on-line identity. I'm just me.
|
||||
R- It would be nice to be as I say I am on-line, but I've got some real doubts about whether such a thing is possible.'
|
||||
R-- This is a hobby for me. My character is just a product of my imagination, nothing more.
|
||||
R--- I consider all of this imaginary. You R+++ people scare me. Don't you think you're taking things a bit too seriously?
|
||||
R---! I am role-playing. You are role-playing. If you try to correct me on this assumption, I will publicly flame you as insane.
|
||||
R* Don't ask, don't tell.
|
||||
R? The thought just hasn't crossed my mind!
|
||||
|
||||
|
||||
Activity-Index (Ac*)
|
||||
Everyone spends different amounts of time on-line, and it can be nice to know just when you're likely to see someone again. This tag allows you to specify just that! Take the following descriptions as rough guides rather than hard and fast rules.
|
||||
Ac+++! I have a T1 connection and am never off the 'net! I even order pizza across it!
|
||||
Ac+++ This is my home! I spend as many waking moments as possible on-line.
|
||||
Ac++ I'm on-line every day for about eight hours or so!
|
||||
Ac+ I'm on-line for at least two hours every day!
|
||||
Ac I get on every day, briefly, to check my mail and the newsgroups.
|
||||
Ac- I only get on occasionally during the week, but am here every weekend.
|
||||
Ac-- I can barely manage to connect once a week.
|
||||
Ac--- I'm lucky if I get on-line once a month!
|
||||
Ac---! If you see me, it must be raining frogs again!
|
||||
Ac~ I go on-line when I fee like it. It may be hours, it may be months!
|
||||
Ac? I really don't know when I'll be on-line again!
|
||||
|
||||
|
||||
Humour-Index (Joviality) (J*)
|
||||
So! You're about to play this really weird prank on another Dragon involving sparklies and superglue ... but you're not sure how they're going to take it? Well this tag will let you know just that!
|
||||
J+++! Everything is a joke. I find something to laugh at everywhere I look ... and that includes you!
|
||||
J+++ There's very little I won't laugh about ... and very little I won't do to make you laugh!
|
||||
J++ Laughing is just so good for you! Even when you're being serious about something it's good to slip in the odd joke!
|
||||
J+ I appreciate humour of all kinds, but I do know when to take things seriously.
|
||||
J I laugh at many things, but there's a time and a place for joking.
|
||||
J- A joke a day keeps depression at bay ... but laughing out loud is bad for my health!
|
||||
J-- Some things are OK to laugh at, but many things are not. I'll take whatever you say seriously unless you put those cute little smileys by it!
|
||||
J--- I can't think of the time when I last laughed.
|
||||
J---! Jokes, frivolity, they're all just works of the Devil!
|
||||
Modifiers:
|
||||
r Put after the H, this indicates that one is specifying one's 'Real Life' humour index. For example Jr- indicates 'I laugh occasionally in real life.'
|
||||
v Put after the H, this indicates that one is specifying one's 'Virtual Life' humour index. For example Jv+ indicates 'I goof around a bit on the 'net!'
|
||||
( ) You can use both of the above in seperate tags to specify both humour indices, but you can also use a short-form method by just putting the r and v parts within parenthesis. For example Jr-Jv+ and J(r-v+) both indicate 'I don't really find much humour in real life, but I do appreciate jokes on the 'net.'
|
||||
|
||||
|
||||
Social-Life (S*)
|
||||
Some creatures live in secret, but many are open to the world about it. This element describes how open you are to the human world about your involvement with your on-line persona.
|
||||
S+++! I have made a determined effort to advertise the fact that I'm not a human. My close friends have got used to explaining me to strangers to save them time. I'm so militant about my true identity that even the men in white coats were impressed!
|
||||
S+++ My friends' parents all know. I use my on-line name in front of total strangers regularly. I've received letter from people I've never met asking questions about how to deal with being non-human.
|
||||
S++ All of my friends know. I told my parents instead of waiting for them to find out. The local cybercafé's manager calls me by my on-line name.
|
||||
S+ Most of my friends know. I'd tell my parents if I thought they'd take it well. I've used my on-line name in public on occasion.
|
||||
S A few of my friends know. I'm glad my parents have never asked me about this. I'm rather low-key but don't dodge the subject.
|
||||
S- I don't want to be thought crazy in human life. If the subject came up, I'd tell my closest friends only.
|
||||
S-- It's something that would make me rather uncomfortable to have let out. I'm open on-line but try to keep my human side untraceable, and vice versa.
|
||||
S--- I've e-mailed only a few kindred souls I think I can trust, not telling them anything about my human life, naturally. There are great advantages to my continued silence.
|
||||
S---! No on knows, period. I live in fear that my intolerant neighbours, colleagues, or relatives will turn me over to the Thought Police! I post only under condition of anonymity. I'm going out on a limb even assembling my Dragon Code!
|
||||
|
||||
|
||||
Ubiquity (U*)
|
||||
Many dragons have a long and noteworthy history going back hundreds, or even thousands, of years. Long before dealing with the daily grind of this human society they were shaping history! This tag shows how much you have done prior to making yourself known in the dragon community. It also shows how much of it you can remember!
|
||||
U+++! They call me 'The Vanisher'. Odd Beholder creatures that I only vaguely remember are still fighting holy wars over me. I've lost track of the number of planets my friends and I have personally shaped the history of. I'm not a god!
|
||||
U+++ Not only do I remember most of my dozens (or hundreds) of past lives, but I remember a few things about you too!
|
||||
U++ See that book over there? I wrote that in a different life!
|
||||
U+ I can remember several past-life names, and am occasionally recognised too!
|
||||
U I've done some soul-searching, and got real results! This isn't my first time around.
|
||||
U- I'm pretty sure I've done something important, sometime, but I haven't got much of a clue as to what it was.
|
||||
U-- When I tried to do a past-life regression, I got a '404 Not found - Access denied'. Why don't past lives come with search engines?
|
||||
U--- Whatever it was I did, it made me end up here. I don't want to look!
|
||||
U---! I blocked out my memories in self-defence after the incident with the Trout farm, the Spell of Growth, and the women's locker room at the Magic Academy! Total strangers occasionally give me odd looks and move to the other side of the street as I pass.
|
||||
U? What's a past life?
|
||||
U! This is my first life!
|
||||
U* I've been around a bit, I'm just not saying how many times!
|
||||
U2 My name is Bono! :8)
|
||||
|
||||
|
||||
Irritability (I*)
|
||||
This tag should be considered vital - it indicates those you can have fun with and those you should be wary of!
|
||||
I+++! You're just not going to catch me in a good mood! Live with it or you'd better like barbeque!
|
||||
I+++ I'd eat my mother if she looked at me wrong!
|
||||
I++ Come too close and you're a cinder.
|
||||
I+ Call me grumpy.
|
||||
I I will defend my honour, but I will only attack with reason.
|
||||
I- Just don't call me lizard lips and you should be fine.
|
||||
I-- I take everything in my stride, as opposed to my jaws!
|
||||
I--- You could stab me with a ten-foot pike, and I wouldn't blink!
|
||||
I---! There's nothing you can do that will make me lose my cool!
|
||||
Modifiers:
|
||||
# But when I do get angry, I hope you like barbeque!
|
||||
|
||||
|
||||
Magical-Ability (Voodoo) (V*)
|
||||
Magical prowess is often associated with dragons, not least in the ability to shape change or to ride the winds, but in other forms as well. This tag indicates how adept you are in the arcane arts.
|
||||
V+++! I have reached the pinnacle of my profession.
|
||||
V+++ I'm reasonably adept in my field.
|
||||
V++ I know a number of spells.
|
||||
V+ I can perform a few cantrips with candles.
|
||||
V Magic is something I've never really looked into.
|
||||
V- Magicians worry when I'm near.
|
||||
V-- Most magic seems to fail when I'm nearby!
|
||||
V--- Only a few spells seem to have an effect, but that could just be psychological.
|
||||
V---! Magic? What's that?
|
||||
Modifiers:
|
||||
[field] If you really feel it is necessary, you can specify your specialist field within square brackets. For example V++[fire] indicates 'I know a number of spells of fire magic.'
|
||||
It may look as though this tag shows two abilities, but this is not so. If one has no magical ability, but prevents magic occurring around one then one uses the minuses. However if one has magical ability then you would tend to use that to prevent magic from occurring, thus you would use the plusses.
|
||||
|
||||
|
||||
Psy-Power (Q*)
|
||||
You may not know it, but there are those who can read your mind like a magazine, or move objects just by thinking. You can indicate your strength in Psionics using this tag.
|
||||
Q+++! There's almost nothing I can't do if I put my mind to it.
|
||||
Q+++ I can move mountains, and not bit by bit!
|
||||
Q++ I know what you're thinking... did I use six psychic blasts or only five!!!
|
||||
Q+ I can talk to the odd spirit, or move very small rocks (churches, lead, ducks, etc.).
|
||||
Q I'm like a book, but haven't learned to read myself yet!
|
||||
Q- Psychics have trouble communicating when I'm around.
|
||||
Q-- Only my very outer thoughts are exposed.
|
||||
Q--- Psionics just don't seem to have any affect.
|
||||
Q---! Not only am I immune to any Psionic effect, but I prevent them happening around me as well.
|
||||
Modifiers:
|
||||
[ability] If you really feel it is necessary, you can specify your general ability within square brackets (try to abbreviate if possible). For example V++[tk] indicates 'I am fairly good at telekinesis.'
|
||||
As for Magical-Ability, this tag looks like it represents two seperate abilities. However one will only use the minuses if one has no psionic ability (but still resists psionics), and the plusses if one has psionic ability (it is assumed you resist with your own psionics).
|
||||
|
||||
|
||||
Technology (Tc*)
|
||||
The ability to use technology, or cause it to fail as soon as you touch it, can be quite important to a dragon. You can indicate your abilities using this tag.
|
||||
Tc+++! I write microcode in my spare time!
|
||||
Tc+++ I can program computers using assembly language.
|
||||
Tc++ I can program computers using high-level languages.
|
||||
Tc+ I can program the video.
|
||||
Tc I haven't yet learned how to wire a plug!
|
||||
Tc- If a program has a bug, I'll find it!
|
||||
Tc-- Electricity does funny things when I'm at the controls!
|
||||
Tc--- Only the most basic mechanisms survive when I get hold of them!
|
||||
Tc---! All items of technology fail when I'm near!
|
||||
Modifiers:
|
||||
[field] If you really feel it is necessary, you can specify your specialist field within square brackets (try to abbreviate if possible). For example Tc++[sw] indicates 'I am fairly good at writing software.'
|
||||
|
||||
|
||||
Huggability or Emotion (E*)
|
||||
Whether you like being emotional amongst others, or just don't want to be touched at all can be very important - to you at least. This tag can avoid many embarrasing moments!
|
||||
E+++! If it is living or dead, I'll hug it (to death)!
|
||||
E+++ If it is living, I'll hug it freely.
|
||||
E++ I'm fairly free with my hugs, so try me!
|
||||
E+ I'm selective, to a point, but give me a hug and I'll return it.
|
||||
E I don't mind hugs from any of my friends, but I draw the line there.
|
||||
E- I'll accept hugs from my nearest and dearest, but no-one else.
|
||||
E-- Hugging me is something that only my mate is allowed to do!
|
||||
E--- Don't you dare hug me! I mean it!
|
||||
E---! Don't even touch me ... in fact don't even think about touching me!
|
||||
Modifiers:
|
||||
# When you do, expect a violent reaction!. For example E++# indicates 'I am fairly free with my hugs, and when I do expect to be bowled over!'
|
||||
|
||||
|
||||
Dragon-Friend (Df*)
|
||||
If you can't be a dragon, then there's always the next best thing: being friends with them! This tags allows you to indicate how well you have blended into the dragon community.
|
||||
Df+++! Have you noticed? you've started growing scales!
|
||||
Df+++ Popular with dragons - you exchange overnight visits.
|
||||
Df++ Reasonably popular - you exchange social visits.
|
||||
Df+ Polite acquaintance - you exchange social pleasantries.
|
||||
Df Tolerance - they don't eat you, you don't try to slice them!
|
||||
Df- Irritant - they think about having you over for lunch!
|
||||
Df-- Maddening - they think about a quick snack ... now!
|
||||
Df--- Infuriating - you're not good enough for a snack.
|
||||
Df---! Cold fury - they're going to hunt you, and find you, and...
|
||||
This tag is for non-dragons mainly. Most regular dragons should skip this tag.
|
||||
|
||||
|
||||
The Revised Dragon Code (DC2) is maintained by Wyrm <admin@dragoncode.org>.
|
||||
This file was last updated on 10th January 2004.
|
||||
92
index.html
Normal file
92
index.html
Normal file
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dragon Code V2.6 Decoder</title>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>Dragon Code V2.6 Decoder</h1>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="input-section">
|
||||
<label for="dragon-code-input">Enter your Dragon Code:</label>
|
||||
<textarea
|
||||
id="dragon-code-input"
|
||||
placeholder="DC2.Dw Gm L W T+ Phvwalt Sk? Cbk--^\bl+lum B- A+ N? M+++!1 O/ H+++ $ F+o R++ Ac+++! J++ S U- I-- V- Q--- Tc+++[SE] E++"
|
||||
rows="4"
|
||||
></textarea>
|
||||
<p class="auto-update-note">Results update automatically as you type</p>
|
||||
<div class="example-section">
|
||||
<button id="load-example" class="secondary-btn">Load Example</button>
|
||||
<button id="clear-input" class="secondary-btn">Clear</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="output-section" id="output-section">
|
||||
<div class="welcome-message" id="welcome-message">
|
||||
<p>Enter a Dragon Code above to see the decoded characteristics.</p>
|
||||
<p class="hint">Or click "Load Example" to see how it works!</p>
|
||||
</div>
|
||||
|
||||
<div id="decoded-output" class="hidden">
|
||||
<div class="category-section" id="species-section">
|
||||
<h2>Species & Form</h2>
|
||||
<div class="category-content" id="species-content"></div>
|
||||
</div>
|
||||
|
||||
<div class="category-section" id="physical-section">
|
||||
<h2>Physical Characteristics</h2>
|
||||
<div class="category-content" id="physical-content"></div>
|
||||
</div>
|
||||
|
||||
<div class="category-section" id="appearance-section">
|
||||
<h2>Appearance</h2>
|
||||
<div class="category-content" id="appearance-content"></div>
|
||||
</div>
|
||||
|
||||
<div class="category-section" id="abilities-section">
|
||||
<h2>Abilities & Powers</h2>
|
||||
<div class="category-content" id="abilities-content"></div>
|
||||
</div>
|
||||
|
||||
<div class="category-section" id="life-section">
|
||||
<h2>Life & Relationships</h2>
|
||||
<div class="category-content" id="life-content"></div>
|
||||
</div>
|
||||
|
||||
<div class="category-section" id="personality-section">
|
||||
<h2>Personality & Behavior</h2>
|
||||
<div class="category-content" id="personality-content"></div>
|
||||
</div>
|
||||
|
||||
<div class="category-section" id="other-section">
|
||||
<h2>Other Traits</h2>
|
||||
<div class="category-content" id="other-content"></div>
|
||||
</div>
|
||||
|
||||
<div class="errors-section hidden" id="errors-section">
|
||||
<h2>Parsing Warnings</h2>
|
||||
<div class="category-content" id="errors-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p><a href="dc.txt" class="spec-link">Dragon Code V2.6 Specification</a></p>
|
||||
<p><a href="tests/run-tests.html" class="test-link">Run Test Suite</a> to validate implementation</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="js/species-data.js"></script>
|
||||
<script src="js/tags-data.js"></script>
|
||||
<script src="js/parser.js"></script>
|
||||
<script src="js/decoder.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
180
js/app.js
Normal file
180
js/app.js
Normal file
@@ -0,0 +1,180 @@
|
||||
// Main application logic
|
||||
|
||||
let debounceTimer = null;
|
||||
const EXAMPLE_CODE = 'DC2.Dw Gm L W T+ Phvwalt Sk? Cbk--^\\bl+lum B- A+ N? M+++!1 O/ H+++ $ F+o R++ Ac+++! J++ S U- I-- V- Q--- Tc+++[SE] E++';
|
||||
|
||||
// DOM elements
|
||||
const input = document.getElementById('dragon-code-input');
|
||||
const welcomeMessage = document.getElementById('welcome-message');
|
||||
const decodedOutput = document.getElementById('decoded-output');
|
||||
const loadExampleBtn = document.getElementById('load-example');
|
||||
const clearInputBtn = document.getElementById('clear-input');
|
||||
|
||||
// Category sections
|
||||
const sections = {
|
||||
species: document.getElementById('species-content'),
|
||||
physical: document.getElementById('physical-content'),
|
||||
appearance: document.getElementById('appearance-content'),
|
||||
abilities: document.getElementById('abilities-content'),
|
||||
life: document.getElementById('life-content'),
|
||||
personality: document.getElementById('personality-content'),
|
||||
other: document.getElementById('other-content'),
|
||||
errors: document.getElementById('errors-content')
|
||||
};
|
||||
|
||||
// Event listeners
|
||||
input.addEventListener('input', handleInput);
|
||||
loadExampleBtn.addEventListener('click', loadExample);
|
||||
clearInputBtn.addEventListener('click', clearInput);
|
||||
|
||||
function handleInput() {
|
||||
// Debounce input to avoid excessive processing
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => {
|
||||
const code = input.value.trim();
|
||||
if (code) {
|
||||
processCode(code);
|
||||
} else {
|
||||
showWelcome();
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function loadExample() {
|
||||
input.value = EXAMPLE_CODE;
|
||||
processCode(EXAMPLE_CODE);
|
||||
}
|
||||
|
||||
function clearInput() {
|
||||
input.value = '';
|
||||
showWelcome();
|
||||
}
|
||||
|
||||
function showWelcome() {
|
||||
welcomeMessage.classList.remove('hidden');
|
||||
decodedOutput.classList.add('hidden');
|
||||
}
|
||||
|
||||
function processCode(code) {
|
||||
try {
|
||||
// Parse the Dragon Code
|
||||
const parsed = parseDragonCode(code);
|
||||
|
||||
// Decode to human-readable format
|
||||
const decoded = decodeDragonCode(parsed);
|
||||
|
||||
// Render the results
|
||||
renderResults(decoded, parsed.errors);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error processing code:', error);
|
||||
showError('An unexpected error occurred while processing the code.');
|
||||
}
|
||||
}
|
||||
|
||||
function renderResults(decoded, errors) {
|
||||
// Clear all sections
|
||||
Object.values(sections).forEach(section => {
|
||||
section.innerHTML = '';
|
||||
});
|
||||
|
||||
// Hide welcome, show output
|
||||
welcomeMessage.classList.add('hidden');
|
||||
decodedOutput.classList.remove('hidden');
|
||||
|
||||
// Render each category
|
||||
renderCategory('species', decoded.species);
|
||||
renderCategory('physical', decoded.physical);
|
||||
renderCategory('appearance', decoded.appearance);
|
||||
renderCategory('abilities', decoded.abilities);
|
||||
renderCategory('life', decoded.life);
|
||||
renderCategory('personality', decoded.personality);
|
||||
renderCategory('other', decoded.other);
|
||||
|
||||
// Render errors if any
|
||||
if (errors && errors.length > 0) {
|
||||
document.getElementById('errors-section').classList.remove('hidden');
|
||||
errors.forEach(error => {
|
||||
const errorElement = createTraitItem('Warning', error, '', 'warning');
|
||||
sections.errors.appendChild(errorElement);
|
||||
});
|
||||
} else {
|
||||
document.getElementById('errors-section').classList.add('hidden');
|
||||
}
|
||||
|
||||
// Hide empty sections
|
||||
hideEmptySections();
|
||||
}
|
||||
|
||||
function renderCategory(categoryName, items) {
|
||||
if (!items || items.length === 0) return;
|
||||
|
||||
const section = sections[categoryName];
|
||||
items.forEach(item => {
|
||||
const element = createTraitItem(item.label, item.value, item.tag, item.type || 'normal');
|
||||
section.appendChild(element);
|
||||
});
|
||||
}
|
||||
|
||||
function createTraitItem(label, value, tag, type = 'normal') {
|
||||
const div = document.createElement('div');
|
||||
div.className = `trait-item ${type}`;
|
||||
|
||||
const labelDiv = document.createElement('div');
|
||||
labelDiv.className = 'trait-label';
|
||||
labelDiv.textContent = label;
|
||||
|
||||
const valueDiv = document.createElement('div');
|
||||
valueDiv.className = 'trait-value';
|
||||
valueDiv.textContent = value;
|
||||
|
||||
div.appendChild(labelDiv);
|
||||
div.appendChild(valueDiv);
|
||||
|
||||
if (tag) {
|
||||
const tagSpan = document.createElement('span');
|
||||
tagSpan.className = 'trait-tag';
|
||||
tagSpan.textContent = tag;
|
||||
div.appendChild(tagSpan);
|
||||
}
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
function hideEmptySections() {
|
||||
const sectionElements = {
|
||||
species: document.getElementById('species-section'),
|
||||
physical: document.getElementById('physical-section'),
|
||||
appearance: document.getElementById('appearance-section'),
|
||||
abilities: document.getElementById('abilities-section'),
|
||||
life: document.getElementById('life-section'),
|
||||
personality: document.getElementById('personality-section'),
|
||||
other: document.getElementById('other-section')
|
||||
};
|
||||
|
||||
Object.keys(sectionElements).forEach(key => {
|
||||
const content = sections[key];
|
||||
const section = sectionElements[key];
|
||||
|
||||
if (content.children.length === 0) {
|
||||
section.classList.add('hidden');
|
||||
} else {
|
||||
section.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
welcomeMessage.classList.add('hidden');
|
||||
decodedOutput.classList.remove('hidden');
|
||||
|
||||
Object.values(sections).forEach(section => {
|
||||
section.innerHTML = '';
|
||||
});
|
||||
|
||||
document.getElementById('errors-section').classList.remove('hidden');
|
||||
const errorElement = createTraitItem('Error', message, '', 'error');
|
||||
sections.errors.appendChild(errorElement);
|
||||
|
||||
hideEmptySections();
|
||||
}
|
||||
622
js/decoder.js
Normal file
622
js/decoder.js
Normal file
@@ -0,0 +1,622 @@
|
||||
// Dragon Code V2.6 Decoder - Converts parsed data to human-readable text
|
||||
|
||||
function decodeDragonCode(parsed) {
|
||||
const result = {
|
||||
species: [],
|
||||
physical: [],
|
||||
appearance: [],
|
||||
abilities: [],
|
||||
life: [],
|
||||
personality: [],
|
||||
other: []
|
||||
};
|
||||
|
||||
// Decode species
|
||||
if (parsed.species) {
|
||||
const speciesItems = decodeSpecies(parsed.species);
|
||||
result.species.push(...speciesItems);
|
||||
}
|
||||
|
||||
// Decode tags
|
||||
const tags = parsed.tags;
|
||||
|
||||
// Physical characteristics
|
||||
if (tags.gender) {
|
||||
result.physical.push(decodeGender(tags.gender));
|
||||
}
|
||||
if (tags.length) {
|
||||
result.physical.push(decodeLength(tags.length));
|
||||
}
|
||||
if (tags.width) {
|
||||
result.physical.push(decodeSimpleTag('Width', tags.width, TAG_DESCRIPTIONS.width));
|
||||
}
|
||||
if (tags.weight) {
|
||||
result.physical.push(decodeSimpleTag('Weight', tags.weight, TAG_DESCRIPTIONS.weight));
|
||||
}
|
||||
if (tags.age) {
|
||||
result.physical.push(decodeSimpleTag('Age', tags.age, TAG_DESCRIPTIONS.age));
|
||||
}
|
||||
|
||||
// Appearance
|
||||
if (tags.appendages) {
|
||||
result.appearance.push(decodeAppendages(tags.appendages));
|
||||
}
|
||||
if (tags.skinType) {
|
||||
result.appearance.push(decodeSkinType(tags.skinType));
|
||||
}
|
||||
if (tags.color) {
|
||||
result.appearance.push(...decodeColor(tags.color));
|
||||
}
|
||||
|
||||
// Abilities
|
||||
if (tags.breath) {
|
||||
result.abilities.push(decodeBreath(tags.breath));
|
||||
}
|
||||
if (tags.magic) {
|
||||
result.abilities.push(decodeSimpleTag('Magic', tags.magic, TAG_DESCRIPTIONS.magic));
|
||||
}
|
||||
if (tags.psyPower) {
|
||||
result.abilities.push(decodeSimpleTag('Psy-Power', tags.psyPower, TAG_DESCRIPTIONS.psyPower));
|
||||
}
|
||||
|
||||
// Life & Relationships
|
||||
if (tags.nativeLand) {
|
||||
result.life.push(decodeSimpleTag('Native-Land', tags.nativeLand, TAG_DESCRIPTIONS.nativeLand));
|
||||
}
|
||||
if (tags.mating) {
|
||||
result.life.push(decodeMating(tags.mating));
|
||||
}
|
||||
if (tags.offspring) {
|
||||
result.life.push(decodeOffspring(tags.offspring));
|
||||
}
|
||||
if (tags.hoard) {
|
||||
result.life.push(decodeSimpleTag('Hoard', tags.hoard, TAG_DESCRIPTIONS.hoard));
|
||||
}
|
||||
if (tags.money) {
|
||||
result.life.push(decodeMoney(tags.money));
|
||||
}
|
||||
if (tags.diet) {
|
||||
result.life.push(decodeDiet(tags.diet));
|
||||
}
|
||||
|
||||
// Personality & Behavior
|
||||
if (tags.reality) {
|
||||
result.personality.push(decodeSimpleTag('Reality', tags.reality, TAG_DESCRIPTIONS.reality));
|
||||
}
|
||||
if (tags.activity) {
|
||||
result.personality.push(decodeSimpleTag('Activity', tags.activity, TAG_DESCRIPTIONS.activity));
|
||||
}
|
||||
if (tags.humor) {
|
||||
result.personality.push(decodeSimpleTag('Humor', tags.humor, TAG_DESCRIPTIONS.humor));
|
||||
}
|
||||
if (tags.social) {
|
||||
result.personality.push(decodeSimpleTag('Social', tags.social, TAG_DESCRIPTIONS.social));
|
||||
}
|
||||
if (tags.irritability) {
|
||||
result.personality.push(decodeSimpleTag('Irritability', tags.irritability, TAG_DESCRIPTIONS.irritability));
|
||||
}
|
||||
if (tags.emotion) {
|
||||
result.personality.push(decodeSimpleTag('Emotion', tags.emotion, TAG_DESCRIPTIONS.emotion));
|
||||
}
|
||||
|
||||
// Other traits
|
||||
if (tags.ubiquity) {
|
||||
result.other.push(decodeSimpleTag('Ubiquity', tags.ubiquity, TAG_DESCRIPTIONS.ubiquity));
|
||||
}
|
||||
if (tags.technology) {
|
||||
result.other.push(decodeTechnology(tags.technology));
|
||||
}
|
||||
if (tags.dragonFriend) {
|
||||
result.other.push(decodeSimpleTag('Dragon-Friend', tags.dragonFriend, TAG_DESCRIPTIONS.dragonFriend));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Decode species with all special cases
|
||||
function decodeSpecies(species) {
|
||||
const items = [];
|
||||
|
||||
if (species.type === 'custom') {
|
||||
items.push({
|
||||
label: 'Species',
|
||||
value: species.value,
|
||||
tag: species.raw
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
if (species.type === 'shapechanger') {
|
||||
const forms = species.forms.map(form => {
|
||||
if (typeof form === 'object') {
|
||||
const speciesName = resolveSpeciesCode(form.species);
|
||||
if (form.modifier) {
|
||||
return `${speciesName} (${form.modifier})`;
|
||||
}
|
||||
return speciesName;
|
||||
}
|
||||
return resolveSpeciesCode(form);
|
||||
});
|
||||
|
||||
items.push({
|
||||
label: 'Species',
|
||||
value: `I change between ${forms.join(' and ')} form`,
|
||||
tag: species.raw
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
if (species.type === 'cross') {
|
||||
const speciesNames = species.species.map(s => resolveSpeciesCode(s) || s);
|
||||
|
||||
items.push({
|
||||
label: 'Species',
|
||||
value: `A cross between ${speciesNames.join(' and ')}`,
|
||||
tag: species.raw
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
if (species.type === 'trapped') {
|
||||
const trueName = resolveSpeciesCode(species.trueForm);
|
||||
const trappedName = resolveSpeciesCode(species.trappedIn);
|
||||
|
||||
items.push({
|
||||
label: 'Species',
|
||||
value: `I am a ${trueName}, trapped in ${trappedName} form`,
|
||||
tag: species.raw,
|
||||
type: 'warning'
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
if (species.type === 'shaped') {
|
||||
const trueName = resolveSpeciesCode(species.trueForm);
|
||||
const shapedName = resolveSpeciesCode(species.shapedAs);
|
||||
|
||||
items.push({
|
||||
label: 'Species',
|
||||
value: `I am a ${trueName}, currently shaped as ${shapedName}`,
|
||||
tag: species.raw
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
// Simple species
|
||||
const speciesName = resolveSpeciesCode(species.value);
|
||||
if (speciesName) {
|
||||
items.push({
|
||||
label: 'Species',
|
||||
value: speciesName,
|
||||
tag: species.raw
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
// Decode gender
|
||||
function decodeGender(gender) {
|
||||
if (gender.type === 'custom') {
|
||||
return {
|
||||
label: 'Gender',
|
||||
value: gender.value,
|
||||
tag: gender.raw
|
||||
};
|
||||
}
|
||||
|
||||
const genderValue = gender.value.toLowerCase();
|
||||
const description = TAG_DESCRIPTIONS.gender[genderValue] || gender.value;
|
||||
|
||||
let modifierText = '';
|
||||
if (gender.modifiers.question) {
|
||||
modifierText = ' (not telling)';
|
||||
} else if (gender.modifiers.tilde) {
|
||||
modifierText = ' (variable)';
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'Gender',
|
||||
value: description + modifierText,
|
||||
tag: gender.raw
|
||||
};
|
||||
}
|
||||
|
||||
// Decode length
|
||||
function decodeLength(length) {
|
||||
let value = '';
|
||||
|
||||
if (length.value && length.unit) {
|
||||
// Quantitative length (L10m, L10m4t, etc.)
|
||||
const unitName = TAG_DESCRIPTIONS.length.units[length.unit] || length.unit;
|
||||
value = `${length.value} ${unitName}`;
|
||||
|
||||
if (length.dimensions && length.dimensions.length > 0) {
|
||||
const modParts = length.dimensions.map(m => {
|
||||
const dimName = TAG_DESCRIPTIONS.length.dimensions[m];
|
||||
// Extract number before modifier if present
|
||||
const numMatch = length.raw.match(new RegExp(`(\\d+)${m}`));
|
||||
if (numMatch) {
|
||||
return `${numMatch[1]}${unitName} ${dimName}`;
|
||||
}
|
||||
return dimName;
|
||||
});
|
||||
|
||||
value += ` (with ${modParts.join(', ')})`;
|
||||
}
|
||||
} else if (length.modifiers) {
|
||||
// Qualitative length (L+++, L-, L, etc.)
|
||||
const modifierKey = getModifierString(length.modifiers);
|
||||
value = TAG_DESCRIPTIONS.length[modifierKey] || 'Normal (Draco-sized)';
|
||||
} else {
|
||||
// Fallback
|
||||
value = 'Normal (Draco-sized)';
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'Length',
|
||||
value: value,
|
||||
tag: length.raw
|
||||
};
|
||||
}
|
||||
|
||||
// Decode simple tags with +/- modifiers
|
||||
function decodeSimpleTag(label, tag, descriptions) {
|
||||
const modifierKey = getModifierString(tag.modifiers);
|
||||
|
||||
let value = descriptions[modifierKey] || descriptions[''] || 'Normal';
|
||||
|
||||
const type = tag.modifiers.question ? 'warning' : 'normal';
|
||||
|
||||
return {
|
||||
label: label,
|
||||
value: value,
|
||||
tag: tag.raw,
|
||||
type: type
|
||||
};
|
||||
}
|
||||
|
||||
// Decode appendages
|
||||
function decodeAppendages(appendages) {
|
||||
const parts = [];
|
||||
|
||||
appendages.parts.forEach(part => {
|
||||
const typeName = TAG_DESCRIPTIONS.appendages.types[part.type] || part.type;
|
||||
|
||||
let description = typeName;
|
||||
|
||||
// Handle count
|
||||
if (part.count) {
|
||||
description = `${part.count} ${typeName}`;
|
||||
} else if (part.modifiers.plus > 0) {
|
||||
description = `multiple ${typeName}`;
|
||||
}
|
||||
|
||||
// Handle modifiers
|
||||
if (part.modifiers.caret) {
|
||||
description = `retractable ${description}`;
|
||||
}
|
||||
if (part.modifiers.minus > 0) {
|
||||
description = `vestigial ${description}`;
|
||||
}
|
||||
if (part.modifiers.exclaim) {
|
||||
description = `unusual ${description}`;
|
||||
}
|
||||
|
||||
// Add pair for common paired appendages
|
||||
if (['a', 'l', 'w', 'v'].includes(part.type) && !part.count && part.modifiers.plus === 0) {
|
||||
description = `pair of ${typeName}`;
|
||||
}
|
||||
|
||||
parts.push(description);
|
||||
});
|
||||
|
||||
return {
|
||||
label: 'Appendages',
|
||||
value: parts.join(', '),
|
||||
tag: appendages.raw
|
||||
};
|
||||
}
|
||||
|
||||
// Decode skin type
|
||||
function decodeSkinType(skinType) {
|
||||
let value = '';
|
||||
|
||||
// Main skin type
|
||||
if (skinType.mainType) {
|
||||
const mainTypeName = TAG_DESCRIPTIONS.skinType[skinType.mainType] || skinType.mainType;
|
||||
value = mainTypeName;
|
||||
} else {
|
||||
value = 'Unknown';
|
||||
}
|
||||
|
||||
// Body part modifiers (e.g., ",ak" = arms with skin, ",bm" = belly with metal)
|
||||
if (skinType.bodyPartTypes.length > 0) {
|
||||
const partDescriptions = skinType.bodyPartTypes.map(item => {
|
||||
const partName = TAG_DESCRIPTIONS.skinType.bodyParts[item.part] || item.part;
|
||||
const typeName = TAG_DESCRIPTIONS.skinType[item.type] || item.type;
|
||||
return `${typeName} on ${partName}`;
|
||||
});
|
||||
value += `, with ${partDescriptions.join(', ')}`;
|
||||
}
|
||||
|
||||
const hasQuestion = skinType.modifiers.question;
|
||||
|
||||
return {
|
||||
label: 'Skin Type',
|
||||
value: value,
|
||||
tag: skinType.raw,
|
||||
type: hasQuestion ? 'warning' : 'normal'
|
||||
};
|
||||
}
|
||||
|
||||
// Decode color (complex)
|
||||
function decodeColor(color) {
|
||||
const items = [];
|
||||
|
||||
// Build a comprehensive description
|
||||
let fullDescription = '';
|
||||
const colorDescriptions = [];
|
||||
|
||||
color.colors.forEach((colorPart, index) => {
|
||||
let description = '';
|
||||
|
||||
// Base color
|
||||
const baseName = TAG_DESCRIPTIONS.color.bases[colorPart.base] || colorPart.base;
|
||||
|
||||
// Intensity modifiers
|
||||
const intensityParts = [];
|
||||
let darknessLevel = 0;
|
||||
let brightnessLevel = 0;
|
||||
|
||||
colorPart.intensity.forEach(mod => {
|
||||
if (mod === '-') darknessLevel++;
|
||||
else if (mod === '+') brightnessLevel++;
|
||||
else {
|
||||
const modName = TAG_DESCRIPTIONS.color.intensity[mod];
|
||||
if (modName) {
|
||||
intensityParts.push(modName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add darkness/brightness descriptions
|
||||
if (darknessLevel === 1) intensityParts.unshift('dark');
|
||||
else if (darknessLevel === 2) intensityParts.unshift('very dark');
|
||||
else if (darknessLevel >= 3) intensityParts.unshift('extremely dark');
|
||||
|
||||
if (brightnessLevel === 1) intensityParts.unshift('light');
|
||||
else if (brightnessLevel === 2) intensityParts.unshift('very light');
|
||||
else if (brightnessLevel >= 3) intensityParts.unshift('extremely light');
|
||||
|
||||
// Build color description
|
||||
if (intensityParts.length > 0) {
|
||||
description = `${intensityParts.join(', ')} ${baseName}`;
|
||||
} else {
|
||||
description = baseName;
|
||||
}
|
||||
|
||||
// Pattern modifiers
|
||||
const patternParts = [];
|
||||
colorPart.patterns.forEach(pat => {
|
||||
const patternName = TAG_DESCRIPTIONS.color.patterns[pat];
|
||||
if (patternName) {
|
||||
patternParts.push(patternName);
|
||||
}
|
||||
});
|
||||
|
||||
if (patternParts.length > 0) {
|
||||
description += ` (${patternParts.join(', ')})`;
|
||||
}
|
||||
|
||||
// Body parts
|
||||
if (colorPart.bodyParts.length > 0) {
|
||||
const parts = colorPart.bodyParts.map(p =>
|
||||
TAG_DESCRIPTIONS.skinType.bodyParts[p] || p
|
||||
);
|
||||
description += ` on ${parts.join(', ')}`;
|
||||
}
|
||||
|
||||
colorDescriptions.push(description);
|
||||
});
|
||||
|
||||
// Combine all colors
|
||||
if (colorDescriptions.length === 1) {
|
||||
fullDescription = colorDescriptions[0];
|
||||
} else if (colorDescriptions.length === 2) {
|
||||
fullDescription = `${colorDescriptions[0]}, with ${colorDescriptions[1]}`;
|
||||
} else {
|
||||
const last = colorDescriptions.pop();
|
||||
fullDescription = `${colorDescriptions.join(', ')}, with ${last}`;
|
||||
}
|
||||
|
||||
items.push({
|
||||
label: 'Color',
|
||||
value: fullDescription,
|
||||
tag: color.raw
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
// Decode breath weapon
|
||||
function decodeBreath(breath) {
|
||||
if (breath.type === 'custom') {
|
||||
return {
|
||||
label: 'Breath Weapon',
|
||||
value: breath.value,
|
||||
tag: breath.raw
|
||||
};
|
||||
}
|
||||
|
||||
if (breath.simple) {
|
||||
const modifierKey = getModifierString(breath.simple);
|
||||
const value = TAG_DESCRIPTIONS.breath.simple[modifierKey] || 'Normal breath weapon';
|
||||
|
||||
return {
|
||||
label: 'Breath Weapon',
|
||||
value: value,
|
||||
tag: breath.raw
|
||||
};
|
||||
}
|
||||
|
||||
const types = breath.types.map(t => {
|
||||
const cleaned = t.replace(/[+\-!?~]/g, '');
|
||||
return TAG_DESCRIPTIONS.breath.types[cleaned] || cleaned;
|
||||
});
|
||||
|
||||
let value = types.join(' / ');
|
||||
|
||||
if (breath.modifiers.length > 0) {
|
||||
value += ` (${breath.modifiers.join(', ')})`;
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'Breath Weapon',
|
||||
value: value,
|
||||
tag: breath.raw
|
||||
};
|
||||
}
|
||||
|
||||
// Decode mating
|
||||
function decodeMating(mating) {
|
||||
const modifierKey = getModifierString(mating.modifiers);
|
||||
let value = TAG_DESCRIPTIONS.mating[modifierKey] || 'Not specified';
|
||||
|
||||
if (mating.count) {
|
||||
value += `, ${mating.count} mate${mating.count > 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
if (mating.separations) {
|
||||
value += `, ${mating.separations} separation${mating.separations > 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'Mating',
|
||||
value: value,
|
||||
tag: mating.raw
|
||||
};
|
||||
}
|
||||
|
||||
// Decode offspring
|
||||
function decodeOffspring(offspring) {
|
||||
const modifierKey = getModifierString(offspring.modifiers);
|
||||
let value = TAG_DESCRIPTIONS.offspring[modifierKey] || TAG_DESCRIPTIONS.offspring[''];
|
||||
|
||||
if (offspring.count) {
|
||||
value = `${offspring.count} offspring`;
|
||||
}
|
||||
|
||||
if (offspring.adopted) {
|
||||
value += ' (adopted)';
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'Offspring',
|
||||
value: value,
|
||||
tag: offspring.raw
|
||||
};
|
||||
}
|
||||
|
||||
// Decode money
|
||||
function decodeMoney(money) {
|
||||
const dollarSigns = money.raw.match(/\$/g);
|
||||
const count = dollarSigns ? dollarSigns.length : 0;
|
||||
|
||||
let key = '';
|
||||
if (count === 3) key = '$$$';
|
||||
else if (count === 2) key = '$$';
|
||||
else if (count === 1) key = '$';
|
||||
|
||||
const minusSigns = money.raw.match(/-/g);
|
||||
const minusCount = minusSigns ? minusSigns.length : 0;
|
||||
|
||||
if (minusCount === 3) key = '---';
|
||||
else if (minusCount === 2) key = '--';
|
||||
else if (minusCount === 1) key = '-';
|
||||
|
||||
const value = TAG_DESCRIPTIONS.money[key] || TAG_DESCRIPTIONS.money[''];
|
||||
|
||||
return {
|
||||
label: 'Money',
|
||||
value: value,
|
||||
tag: money.raw
|
||||
};
|
||||
}
|
||||
|
||||
// Decode diet
|
||||
function decodeDiet(diet) {
|
||||
const typeNames = diet.types.map(t =>
|
||||
TAG_DESCRIPTIONS.diet.types[t] || t
|
||||
);
|
||||
|
||||
let value = typeNames.join(', ') || 'omnivore';
|
||||
|
||||
const modifierKey = getModifierString(diet.modifiers);
|
||||
const modifierDesc = TAG_DESCRIPTIONS.diet.modifiers[modifierKey];
|
||||
|
||||
if (modifierDesc) {
|
||||
value = `${modifierDesc} ${value}`;
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'Diet',
|
||||
value: value,
|
||||
tag: diet.raw
|
||||
};
|
||||
}
|
||||
|
||||
// Decode technology
|
||||
function decodeTechnology(tech) {
|
||||
const modifierKey = getModifierString(tech.modifiers);
|
||||
let value = TAG_DESCRIPTIONS.technology[modifierKey] || TAG_DESCRIPTIONS.technology[''];
|
||||
|
||||
if (tech.specialist) {
|
||||
value += `, specialist in ${tech.specialist}`;
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'Technology',
|
||||
value: value,
|
||||
tag: tech.raw
|
||||
};
|
||||
}
|
||||
|
||||
// Helper: Convert modifiers object to string key
|
||||
function getModifierString(modifiers) {
|
||||
let key = '';
|
||||
|
||||
// Handle exclamation special cases
|
||||
if (modifiers.exclaim) {
|
||||
if (modifiers.plus === 3) return '+++!';
|
||||
if (modifiers.minus === 3) return '---!';
|
||||
// Exclamation alone (e.g., S!)
|
||||
if (modifiers.plus === 0 && modifiers.minus === 0) return '!';
|
||||
}
|
||||
|
||||
// Build key from +/- counts
|
||||
if (modifiers.plus > 0) {
|
||||
key = '+'.repeat(modifiers.plus);
|
||||
} else if (modifiers.minus > 0) {
|
||||
key = '-'.repeat(modifiers.minus);
|
||||
}
|
||||
|
||||
// Add special modifiers (but not if they're already the key)
|
||||
if (modifiers.question && key !== '!') {
|
||||
key += '?';
|
||||
}
|
||||
if (modifiers.tilde && key === '') {
|
||||
key = '~';
|
||||
}
|
||||
|
||||
// Special case for slash
|
||||
if (modifiers.slash) {
|
||||
key = '/';
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
738
js/parser.js
Normal file
738
js/parser.js
Normal file
@@ -0,0 +1,738 @@
|
||||
// Dragon Code V2.6 Parser
|
||||
|
||||
function parseDragonCode(input) {
|
||||
const result = {
|
||||
species: null,
|
||||
tags: {},
|
||||
errors: []
|
||||
};
|
||||
|
||||
try {
|
||||
// Tokenize the input
|
||||
const tokens = tokenize(input);
|
||||
|
||||
// Process each token
|
||||
tokens.forEach((token, index) => {
|
||||
try {
|
||||
const tagType = identifyTagType(token, index);
|
||||
|
||||
if (tagType === 'species') {
|
||||
result.species = parseSpecies(token);
|
||||
} else if (tagType) {
|
||||
const parsed = parseTag(token, tagType);
|
||||
if (parsed) {
|
||||
result.tags[tagType] = parsed;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
result.errors.push(`Error parsing token "${token}": ${error.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
result.errors.push(`Error tokenizing input: ${error.message}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Tokenizer: splits Dragon Code into individual tokens
|
||||
function tokenize(input) {
|
||||
// Remove DC2. prefix if present
|
||||
let code = input.trim();
|
||||
if (code.startsWith('DC2.')) {
|
||||
code = code.substring(4);
|
||||
} else if (code.startsWith('DC.')) {
|
||||
code = code.substring(3);
|
||||
}
|
||||
|
||||
const tokens = [];
|
||||
let current = '';
|
||||
let inQuotes = false;
|
||||
|
||||
for (let i = 0; i < code.length; i++) {
|
||||
const char = code[i];
|
||||
|
||||
if (char === '"') {
|
||||
inQuotes = !inQuotes;
|
||||
current += char;
|
||||
} else if (char === ' ' && !inQuotes) {
|
||||
if (current.trim()) {
|
||||
tokens.push(current.trim());
|
||||
}
|
||||
current = '';
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
|
||||
if (current.trim()) {
|
||||
tokens.push(current.trim());
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// Identifies the type of tag
|
||||
function identifyTagType(token, index) {
|
||||
// IMPORTANT: Check two-letter tags before single-letter tags to avoid conflicts
|
||||
// (e.g., Tc before T, Ac before A, Sk before S, Df before D)
|
||||
|
||||
// Two-letter tags first
|
||||
if (token.startsWith('Tc')) return 'technology';
|
||||
if (token.startsWith('Ac')) return 'activity';
|
||||
if (token.startsWith('Sk')) return 'skinType';
|
||||
if (token.startsWith('Df')) return 'dragonFriend';
|
||||
if (token.startsWith('Ph') || token.startsWith('Pa') || token.startsWith('Pl') ||
|
||||
token.startsWith('Pw') || token.startsWith('Pt') || token.startsWith('Pv') ||
|
||||
token.startsWith('Pk') || token.startsWith('Pf') || token.startsWith('Pp')) return 'appendages';
|
||||
|
||||
// Species tag is typically first, or contains species indicators
|
||||
if (index === 0 || token.match(/^[~^]?[A-Z][a-z]*[\[+{^~]?/) || token.includes('/') || token.includes('[') || token.includes('^')) {
|
||||
// At index 0, check if it's a single-letter or multi-letter species code first
|
||||
// Species codes can be: D, Dw, H, A, Ag, etc.
|
||||
if (index === 0) {
|
||||
// At index 0, check in this order:
|
||||
// 1. Two-letter tag prefixes (Tc, Ac, Sk, Df, Ph, etc.)
|
||||
// 2. Single-letter tags (G, L, W, T, etc.)
|
||||
// 3. Species indicators (~, ^, [, +) - clear species markers
|
||||
// 4. Multi-letter species codes (Dw, De, etc.)
|
||||
// 5. Fallback to species for remaining patterns
|
||||
|
||||
// Two-letter tags (must check BEFORE single-letter checks)
|
||||
if (token.startsWith('Tc')) return 'technology';
|
||||
if (token.startsWith('Ac')) return 'activity';
|
||||
if (token.startsWith('Sk')) return 'skinType';
|
||||
if (token.startsWith('Df')) return 'dragonFriend';
|
||||
if (token.startsWith('Ph') || token.startsWith('Pa') || token.startsWith('Pl') ||
|
||||
token.startsWith('Pw') || token.startsWith('Pt') || token.startsWith('Pv') ||
|
||||
token.startsWith('Pk') || token.startsWith('Pf') || token.startsWith('Pp')) return 'appendages';
|
||||
|
||||
// Special case: bare "H" at index 0 is ambiguous
|
||||
// "H" alone = Human (species), "H+++" = Hoard (tag)
|
||||
// Check if it's exactly "H" with no modifiers
|
||||
if (token === 'H') {
|
||||
return 'species'; // Treat bare "H" as Human species
|
||||
}
|
||||
|
||||
// Single-letter tags (with or without modifiers)
|
||||
if (token.startsWith('G')) return 'gender';
|
||||
if (token.startsWith('L')) return 'length';
|
||||
if (token.startsWith('W')) return 'width';
|
||||
if (token.startsWith('T')) return 'weight';
|
||||
if (token.startsWith('C') && token[1] && token[1].match(/[a-z]/)) return 'color';
|
||||
if (token.startsWith('B')) return 'breath';
|
||||
if (token.startsWith('A')) return 'age';
|
||||
if (token.startsWith('N')) return 'nativeLand';
|
||||
if (token.startsWith('M')) return 'mating';
|
||||
if (token.startsWith('O')) return 'offspring';
|
||||
if (token.startsWith('H')) return 'hoard';
|
||||
if (token.startsWith('$')) return 'money';
|
||||
if (token.startsWith('F')) return 'diet';
|
||||
if (token.startsWith('R')) return 'reality';
|
||||
if (token.startsWith('J')) return 'humor';
|
||||
if (token.startsWith('S')) return 'social';
|
||||
if (token.startsWith('U')) return 'ubiquity';
|
||||
if (token.startsWith('I')) return 'irritability';
|
||||
if (token.startsWith('V')) return 'magic';
|
||||
if (token.startsWith('Q')) return 'psyPower';
|
||||
if (token.startsWith('E')) return 'emotion';
|
||||
|
||||
// Check for species-specific indicators (after tag checks)
|
||||
// ~ at start (shapeshifter), or [ ] (trapped), or + (cross)
|
||||
if (token.startsWith('~') || token.includes('[') || token.includes('+')) {
|
||||
return 'species';
|
||||
}
|
||||
// ^ for shaped (H^Dw), but not if it's a tag with ^ modifier
|
||||
if (token.includes('^') && token.match(/[A-Z][a-z]*\^[A-Z]/)) {
|
||||
return 'species';
|
||||
}
|
||||
// / for multiple species in shapeshifter (must have species codes on both sides)
|
||||
// E.g., "Dw/H" or within "~Dw/H", but NOT "O/" or "M/"
|
||||
if (token.includes('/') && token.match(/[A-Z][a-z]*\/[A-Z]/)) {
|
||||
return 'species';
|
||||
}
|
||||
|
||||
// Multi-letter species codes (Dw, De, Ag, etc.)
|
||||
if (token.match(/^[A-Z][a-z]+/)) {
|
||||
return 'species';
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's actually a non-species tag
|
||||
if (token.startsWith('G')) return 'gender';
|
||||
if (token.startsWith('L')) return 'length';
|
||||
if (token.startsWith('W')) return 'width';
|
||||
if (token.startsWith('T') && !token.startsWith('Tc')) return 'weight';
|
||||
if (token.startsWith('C') && (token.length > 1 && token[1].match(/[a-z]/))) return 'color';
|
||||
if (token.startsWith('B')) return 'breath';
|
||||
if (token.startsWith('A') && !token.startsWith('Ac')) return 'age';
|
||||
if (token.startsWith('N')) return 'nativeLand';
|
||||
if (token.startsWith('M')) return 'mating';
|
||||
if (token.startsWith('O')) return 'offspring';
|
||||
if (token.startsWith('H')) return 'hoard';
|
||||
if (token.startsWith('$')) return 'money';
|
||||
if (token.startsWith('F')) return 'diet';
|
||||
if (token.startsWith('R')) return 'reality';
|
||||
if (token.startsWith('J')) return 'humor';
|
||||
if (token.startsWith('S') && !token.startsWith('Sk')) return 'social';
|
||||
if (token.startsWith('U')) return 'ubiquity';
|
||||
if (token.startsWith('I')) return 'irritability';
|
||||
if (token.startsWith('V')) return 'magic';
|
||||
if (token.startsWith('Q')) return 'psyPower';
|
||||
if (token.startsWith('E')) return 'emotion';
|
||||
|
||||
// If none of the above, likely species
|
||||
if (index === 0) return 'species';
|
||||
}
|
||||
|
||||
// Single-letter tags (checked after two-letter tags)
|
||||
// Gender
|
||||
if (token.startsWith('G')) return 'gender';
|
||||
|
||||
// Length (with or without numbers)
|
||||
if (token.startsWith('L')) return 'length';
|
||||
|
||||
// Width (with any modifiers)
|
||||
if (token.startsWith('W')) return 'width';
|
||||
|
||||
// Weight (Tonnage) - already checked it's not Technology
|
||||
if (token.startsWith('T')) return 'weight';
|
||||
|
||||
// Color
|
||||
if (token.startsWith('C')) return 'color';
|
||||
|
||||
// Breath Weapon
|
||||
if (token.startsWith('B')) return 'breath';
|
||||
|
||||
// Age - already checked it's not Activity
|
||||
if (token.startsWith('A')) return 'age';
|
||||
|
||||
// Native Land
|
||||
if (token.startsWith('N')) return 'nativeLand';
|
||||
|
||||
// Mating
|
||||
if (token.startsWith('M')) return 'mating';
|
||||
|
||||
// Offspring
|
||||
if (token.startsWith('O')) return 'offspring';
|
||||
|
||||
// Hoard
|
||||
if (token.startsWith('H')) return 'hoard';
|
||||
|
||||
// Money
|
||||
if (token.startsWith('$')) return 'money';
|
||||
|
||||
// Diet
|
||||
if (token.startsWith('F')) return 'diet';
|
||||
|
||||
// Reality
|
||||
if (token.startsWith('R')) return 'reality';
|
||||
|
||||
// Humor
|
||||
if (token.startsWith('J')) return 'humor';
|
||||
|
||||
// Social - already checked it's not Skin Type
|
||||
if (token.startsWith('S')) return 'social';
|
||||
|
||||
// Ubiquity
|
||||
if (token.startsWith('U')) return 'ubiquity';
|
||||
|
||||
// Irritability
|
||||
if (token.startsWith('I')) return 'irritability';
|
||||
|
||||
// Magic
|
||||
if (token.startsWith('V')) return 'magic';
|
||||
|
||||
// Psy Power
|
||||
if (token.startsWith('Q')) return 'psyPower';
|
||||
|
||||
// Emotion
|
||||
if (token.startsWith('E')) return 'emotion';
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse species (handles ~, ^, [], +, {})
|
||||
function parseSpecies(token) {
|
||||
const result = {
|
||||
type: 'simple',
|
||||
value: null,
|
||||
modifiers: [],
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Handle quoted custom species
|
||||
if (token.startsWith('"') && token.endsWith('"')) {
|
||||
result.type = 'custom';
|
||||
result.value = token.substring(1, token.length - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Shapechanger: ~species1/species2
|
||||
if (token.includes('~') && token.includes('/')) {
|
||||
result.type = 'shapechanger';
|
||||
const parts = token.split('/');
|
||||
result.forms = parts.map(p => p.replace('~', '').trim());
|
||||
|
||||
// Check for {} modifiers
|
||||
result.forms = result.forms.map(form => {
|
||||
if (form.includes('{')) {
|
||||
const match = form.match(/([^{]+)\{([^}]+)\}/);
|
||||
if (match) {
|
||||
return { species: match[1], modifier: match[2] };
|
||||
}
|
||||
}
|
||||
return { species: form, modifier: null };
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Cross-breed: species1+species2
|
||||
if (token.includes('+') && !token.match(/^[+\-]/)) {
|
||||
result.type = 'cross';
|
||||
result.species = token.split('+').map(s => s.trim());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Trapped form: species1[species2]
|
||||
if (token.includes('[') && token.includes(']')) {
|
||||
result.type = 'trapped';
|
||||
const match = token.match(/([^\[]+)\[([^\]]+)\]/);
|
||||
if (match) {
|
||||
result.trueForm = match[1];
|
||||
result.trappedIn = match[2];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Shaped: species1^species2
|
||||
if (token.includes('^')) {
|
||||
result.type = 'shaped';
|
||||
const parts = token.split('^');
|
||||
result.trueForm = parts[0];
|
||||
result.shapedAs = parts[1];
|
||||
return result;
|
||||
}
|
||||
|
||||
// Simple species code
|
||||
result.value = token;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse individual tag based on type
|
||||
function parseTag(token, type) {
|
||||
const parsers = {
|
||||
gender: parseGender,
|
||||
length: parseLength,
|
||||
width: parseSimpleModifier,
|
||||
weight: parseSimpleModifier,
|
||||
appendages: parseAppendages,
|
||||
skinType: parseSkinType,
|
||||
color: parseColor,
|
||||
breath: parseBreath,
|
||||
age: parseSimpleModifier,
|
||||
nativeLand: parseSimpleModifier,
|
||||
mating: parseMating,
|
||||
offspring: parseOffspring,
|
||||
hoard: parseSimpleModifier,
|
||||
money: parseSimpleModifier,
|
||||
diet: parseDiet,
|
||||
reality: parseSimpleModifier,
|
||||
activity: parseSimpleModifier,
|
||||
humor: parseSimpleModifier,
|
||||
social: parseSimpleModifier,
|
||||
ubiquity: parseSimpleModifier,
|
||||
irritability: parseSimpleModifier,
|
||||
magic: parseSimpleModifier,
|
||||
psyPower: parseSimpleModifier,
|
||||
technology: parseTechnology,
|
||||
emotion: parseSimpleModifier,
|
||||
dragonFriend: parseSimpleModifier
|
||||
};
|
||||
|
||||
const parser = parsers[type];
|
||||
if (parser) {
|
||||
return parser(token);
|
||||
}
|
||||
|
||||
return { raw: token };
|
||||
}
|
||||
|
||||
// Parse gender
|
||||
function parseGender(token) {
|
||||
// Handle quoted custom gender
|
||||
if (token.includes('"')) {
|
||||
const match = token.match(/"([^"]+)"/);
|
||||
return {
|
||||
type: 'custom',
|
||||
value: match ? match[1] : token,
|
||||
raw: token
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
value: token.substring(1), // Remove 'G' prefix
|
||||
modifiers: extractModifiers(token.substring(1)),
|
||||
raw: token
|
||||
};
|
||||
}
|
||||
|
||||
// Parse length (quantitative with units)
|
||||
function parseLength(token) {
|
||||
const result = {
|
||||
raw: token,
|
||||
value: null,
|
||||
unit: null,
|
||||
dimensions: [], // For quantitative modifiers like 4t (4m tail)
|
||||
modifiers: null // For qualitative modifiers like +++, -, etc.
|
||||
};
|
||||
|
||||
// Check if it has a numeric value (quantitative)
|
||||
const match = token.match(/L(\d+)([a-z]+)?/i);
|
||||
if (match) {
|
||||
result.value = parseInt(match[1]);
|
||||
result.unit = match[2] || null;
|
||||
|
||||
// Extract dimension modifiers (a, l, n, t, w, h)
|
||||
const dimMatch = token.match(/\d+[a-z]*(\d*[alntwh])/);
|
||||
if (dimMatch) {
|
||||
result.dimensions = dimMatch[1].split('').map(m => {
|
||||
if (m.match(/\d/)) return null;
|
||||
return m;
|
||||
}).filter(Boolean);
|
||||
}
|
||||
} else {
|
||||
// Qualitative length (L+++, L-, L, etc.)
|
||||
// Remove 'L' prefix and extract modifiers
|
||||
const modifierPart = token.substring(1);
|
||||
result.modifiers = extractModifiers(modifierPart);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse simple modifier tags (+, -, !, ?, ~, etc.)
|
||||
function parseSimpleModifier(token) {
|
||||
// Remove tag prefix to get modifiers
|
||||
let modifierPart = token.replace(/^[A-Z][a-z]?/, '');
|
||||
|
||||
return {
|
||||
modifiers: extractModifiers(modifierPart),
|
||||
raw: token
|
||||
};
|
||||
}
|
||||
|
||||
// Extract modifiers from a string
|
||||
function extractModifiers(str) {
|
||||
const modifiers = {
|
||||
plus: 0,
|
||||
minus: 0,
|
||||
exclaim: false,
|
||||
question: false,
|
||||
tilde: false,
|
||||
caret: false,
|
||||
slash: false,
|
||||
real: false,
|
||||
virtual: false
|
||||
};
|
||||
|
||||
for (let char of str) {
|
||||
if (char === '+') modifiers.plus++;
|
||||
else if (char === '-') modifiers.minus++;
|
||||
else if (char === '!') modifiers.exclaim = true;
|
||||
else if (char === '?') modifiers.question = true;
|
||||
else if (char === '~') modifiers.tilde = true;
|
||||
else if (char === '^') modifiers.caret = true;
|
||||
else if (char === '/') modifiers.slash = true;
|
||||
}
|
||||
|
||||
// Check for r/v (real/virtual)
|
||||
if (str.includes('r')) modifiers.real = true;
|
||||
if (str.includes('v')) modifiers.virtual = true;
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
// Parse appendages (complex sequence)
|
||||
function parseAppendages(token) {
|
||||
const result = {
|
||||
parts: [],
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Parse the appendage code
|
||||
let current = '';
|
||||
for (let i = 0; i < token.length; i++) {
|
||||
const char = token[i];
|
||||
|
||||
if (char === 'P') continue; // Skip prefix
|
||||
|
||||
if (char.match(/[halvwtkfp']/)) {
|
||||
if (current) {
|
||||
result.parts.push(parseAppendagePart(current));
|
||||
}
|
||||
current = char;
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
|
||||
if (current) {
|
||||
result.parts.push(parseAppendagePart(current));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseAppendagePart(part) {
|
||||
const type = part[0];
|
||||
const modifiers = part.substring(1);
|
||||
|
||||
return {
|
||||
type: type,
|
||||
modifiers: extractModifiers(modifiers),
|
||||
count: modifiers.match(/\d+/) ? parseInt(modifiers.match(/\d+/)[0]) : null
|
||||
};
|
||||
}
|
||||
|
||||
// Parse skin type
|
||||
function parseSkinType(token) {
|
||||
const result = {
|
||||
mainType: null,
|
||||
bodyPartTypes: [], // Array of {part, type} objects
|
||||
modifiers: {},
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Remove Sk prefix
|
||||
let content = token.substring(2);
|
||||
|
||||
// Check for ? modifier
|
||||
if (content.includes('?')) {
|
||||
result.modifiers.question = true;
|
||||
content = content.replace('?', '');
|
||||
}
|
||||
|
||||
// Split by comma to separate main type from body part modifiers
|
||||
const parts = content.split(',');
|
||||
|
||||
// First part is the main skin type (single letter)
|
||||
if (parts[0]) {
|
||||
result.mainType = parts[0][0]; // First character is the main type
|
||||
}
|
||||
|
||||
// Remaining parts are body part modifiers (format: <part><type>)
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
if (part.length >= 2) {
|
||||
// First character is body part, rest is skin type
|
||||
const bodyPart = part[0];
|
||||
const skinType = part.substring(1);
|
||||
result.bodyPartTypes.push({
|
||||
part: bodyPart,
|
||||
type: skinType
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse color (MOST COMPLEX)
|
||||
function parseColor(token) {
|
||||
const result = {
|
||||
colors: [],
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Remove C prefix
|
||||
let content = token.substring(1);
|
||||
|
||||
// Split by / for multiple colors, and handle \ as additional color/effect
|
||||
const colorParts = content.split(/[\/\\]/);
|
||||
|
||||
colorParts.forEach(colorPart => {
|
||||
if (colorPart) {
|
||||
result.colors.push(parseColorPart(colorPart));
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseColorPart(part) {
|
||||
const color = {
|
||||
base: null,
|
||||
intensity: [],
|
||||
patterns: [],
|
||||
bodyParts: [],
|
||||
modifiers: []
|
||||
};
|
||||
|
||||
// Extract base color (2-3 letter code)
|
||||
const baseMatch = part.match(/^([a-z]{2,3})/);
|
||||
if (baseMatch) {
|
||||
color.base = baseMatch[1];
|
||||
part = part.substring(baseMatch[1].length);
|
||||
}
|
||||
|
||||
// Check for 'lum' (luminescent) special modifier
|
||||
if (part.includes('lum')) {
|
||||
color.intensity.push('lum');
|
||||
part = part.replace('lum', '');
|
||||
}
|
||||
|
||||
// Extract intensity modifiers (+, -, ^, _, ', %, !)
|
||||
for (let char of part) {
|
||||
if (['+', '-', '^', '_', "'", '%', '!'].includes(char)) {
|
||||
color.intensity.push(char);
|
||||
} else if (['|', '=', ':', '*', '@', '#', '&', '>'].includes(char)) {
|
||||
color.patterns.push(char);
|
||||
} else if (char === ',') {
|
||||
// Body part modifier follows
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract body part modifiers (,a, ,b, etc.)
|
||||
const bodyPartMatches = part.match(/,([a-z])/g);
|
||||
if (bodyPartMatches) {
|
||||
color.bodyParts = bodyPartMatches.map(m => m.substring(1));
|
||||
}
|
||||
|
||||
// Check for special patterns like &1, &2
|
||||
const specialPattern = part.match(/&(\d+)/);
|
||||
if (specialPattern) {
|
||||
color.patterns.push('&' + specialPattern[1]);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
// Parse breath weapon
|
||||
function parseBreath(token) {
|
||||
// Handle quoted custom breath
|
||||
if (token.includes('"')) {
|
||||
const match = token.match(/"([^"]+)"/);
|
||||
return {
|
||||
type: 'custom',
|
||||
value: match ? match[1] : token,
|
||||
raw: token
|
||||
};
|
||||
}
|
||||
|
||||
const result = {
|
||||
types: [],
|
||||
modifiers: [],
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Remove B prefix
|
||||
let content = token.substring(1);
|
||||
|
||||
// Check for modifiers
|
||||
if (content.includes('|')) result.modifiers.push('beam');
|
||||
if (content.includes('#')) result.modifiers.push('cloud');
|
||||
|
||||
// Handle simple +/- modifiers (no specific type)
|
||||
if (content.match(/^[+\-!?~]+$/)) {
|
||||
result.simple = extractModifiers(content);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Extract breath types (fl, ac, ic, etc.)
|
||||
if (content.length > 0) {
|
||||
const types = content.split('/').map(t => t.replace(/[|#]/g, '').trim()).filter(Boolean);
|
||||
result.types = types;
|
||||
} else {
|
||||
// "B" with no content - normal breath
|
||||
result.simple = extractModifiers('');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse mating
|
||||
function parseMating(token) {
|
||||
const result = {
|
||||
modifiers: extractModifiers(token.substring(1)),
|
||||
count: null,
|
||||
separations: null,
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Extract count (number at end)
|
||||
const countMatch = token.match(/(\d+)$/);
|
||||
if (countMatch) {
|
||||
result.count = parseInt(countMatch[1]);
|
||||
}
|
||||
|
||||
// Extract separations (^number)
|
||||
const sepMatch = token.match(/\^(\d+)/);
|
||||
if (sepMatch) {
|
||||
result.separations = parseInt(sepMatch[1]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse offspring
|
||||
function parseOffspring(token) {
|
||||
const result = {
|
||||
modifiers: extractModifiers(token.substring(1)),
|
||||
count: null,
|
||||
adopted: false,
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Check for adopted (a modifier)
|
||||
if (token.includes('a')) {
|
||||
result.adopted = true;
|
||||
}
|
||||
|
||||
// Extract count
|
||||
const countMatch = token.match(/(\d+)/);
|
||||
if (countMatch) {
|
||||
result.count = parseInt(countMatch[1]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse diet
|
||||
function parseDiet(token) {
|
||||
const result = {
|
||||
modifiers: extractModifiers(token.substring(1)),
|
||||
types: [],
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Extract diet type letters (c, h, o, v)
|
||||
const content = token.substring(1);
|
||||
for (let char of content) {
|
||||
if (['c', 'h', 'o', 'v'].includes(char)) {
|
||||
result.types.push(char);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse technology
|
||||
function parseTechnology(token) {
|
||||
const result = {
|
||||
modifiers: extractModifiers(token.substring(2)),
|
||||
specialist: null,
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Extract specialist field in []
|
||||
const specialistMatch = token.match(/\[([^\]]+)\]/);
|
||||
if (specialistMatch) {
|
||||
result.specialist = specialistMatch[1];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
249
js/species-data.js
Normal file
249
js/species-data.js
Normal file
@@ -0,0 +1,249 @@
|
||||
// Hierarchical species tree based on Dragon Code V2.6 specification
|
||||
const SPECIES_DATA = {
|
||||
// Dragons (D)
|
||||
D: {
|
||||
name: "Dragon",
|
||||
subtypes: {
|
||||
w: "Western Dragon",
|
||||
e: "Eastern Dragon",
|
||||
p: "Pernese Dragon",
|
||||
f: "Faerie Dragon",
|
||||
r: "Reformed Dragon",
|
||||
c: "Chromatic Dragon",
|
||||
m: "Metallic Dragon",
|
||||
g: "Gem Dragon"
|
||||
}
|
||||
},
|
||||
|
||||
// Humanoid (H)
|
||||
H: {
|
||||
name: "Human",
|
||||
subtypes: {}
|
||||
},
|
||||
|
||||
// Avian (A)
|
||||
A: {
|
||||
name: "Avian",
|
||||
subtypes: {
|
||||
c: "Cockatrice",
|
||||
g: "Griffin",
|
||||
h: "Hippogriff",
|
||||
p: "Phoenix",
|
||||
r: "Roc"
|
||||
}
|
||||
},
|
||||
|
||||
// Bovine (B)
|
||||
B: {
|
||||
name: "Bovine",
|
||||
subtypes: {
|
||||
c: "Cattle",
|
||||
m: "Minotaur"
|
||||
}
|
||||
},
|
||||
|
||||
// Canine (C)
|
||||
C: {
|
||||
name: "Canine",
|
||||
subtypes: {
|
||||
c: "Coyote",
|
||||
d: "Dog",
|
||||
f: "Fox",
|
||||
j: "Jackal",
|
||||
w: "Wolf"
|
||||
}
|
||||
},
|
||||
|
||||
// Serpent (S)
|
||||
S: {
|
||||
name: "Serpent",
|
||||
subtypes: {
|
||||
b: "Basilisk",
|
||||
h: "Hydra",
|
||||
n: "Naga",
|
||||
s: "Sea Serpent"
|
||||
}
|
||||
},
|
||||
|
||||
// Equine (E)
|
||||
E: {
|
||||
name: "Equine",
|
||||
subtypes: {
|
||||
c: "Centaur",
|
||||
h: "Horse",
|
||||
p: "Pegasus",
|
||||
u: "Unicorn",
|
||||
z: "Zebra"
|
||||
}
|
||||
},
|
||||
|
||||
// Feline (F)
|
||||
F: {
|
||||
name: "Feline",
|
||||
subtypes: {
|
||||
c: "Cat",
|
||||
h: "Cheetah",
|
||||
j: "Jaguar",
|
||||
l: "Lion",
|
||||
o: "Ocelot",
|
||||
p: "Panther",
|
||||
t: "Tiger",
|
||||
x: "Lynx"
|
||||
}
|
||||
},
|
||||
|
||||
// Insectoid (I)
|
||||
I: {
|
||||
name: "Insectoid",
|
||||
subtypes: {}
|
||||
},
|
||||
|
||||
// Leporine (L)
|
||||
L: {
|
||||
name: "Leporine",
|
||||
subtypes: {
|
||||
h: "Hare",
|
||||
r: "Rabbit"
|
||||
}
|
||||
},
|
||||
|
||||
// Mammal (M)
|
||||
M: {
|
||||
name: "Mammal",
|
||||
subtypes: {
|
||||
a: {
|
||||
name: "Aquatic",
|
||||
subtypes: {
|
||||
d: "Dolphin",
|
||||
o: "Orca",
|
||||
p: "Porpoise",
|
||||
s: "Seal",
|
||||
w: "Walrus",
|
||||
h: "Whale"
|
||||
}
|
||||
},
|
||||
b: "Badger",
|
||||
e: "Elephant",
|
||||
f: {
|
||||
name: "Feral",
|
||||
subtypes: {
|
||||
p: {
|
||||
name: "Pantherine",
|
||||
subtypes: {
|
||||
c: "Cougar",
|
||||
j: "Jaguar",
|
||||
l: "Leopard",
|
||||
n: "Panther",
|
||||
s: "Snow Leopard",
|
||||
t: "Tiger"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
k: "Skunk",
|
||||
m: "Mink",
|
||||
o: "Otter",
|
||||
r: "Rodent",
|
||||
t: "Taur",
|
||||
w: "Weasel"
|
||||
}
|
||||
},
|
||||
|
||||
// Otter (O)
|
||||
O: {
|
||||
name: "Otter",
|
||||
subtypes: {}
|
||||
},
|
||||
|
||||
// Mythological (Y)
|
||||
Y: {
|
||||
name: "Mythological",
|
||||
subtypes: {
|
||||
c: "Chimera",
|
||||
g: "Gargoyle",
|
||||
m: "Manticore",
|
||||
s: "Sphinx"
|
||||
}
|
||||
},
|
||||
|
||||
// Porcine (P)
|
||||
P: {
|
||||
name: "Porcine",
|
||||
subtypes: {
|
||||
b: "Boar",
|
||||
p: "Pig"
|
||||
}
|
||||
},
|
||||
|
||||
// Reptile (R)
|
||||
R: {
|
||||
name: "Reptile",
|
||||
subtypes: {
|
||||
a: "Alligator",
|
||||
c: "Crocodile",
|
||||
d: "Dinosaur",
|
||||
l: "Lizard",
|
||||
t: "Turtle"
|
||||
}
|
||||
},
|
||||
|
||||
// Aquatic (Q)
|
||||
Q: {
|
||||
name: "Aquatic",
|
||||
subtypes: {
|
||||
d: "Dolphin",
|
||||
f: "Fish",
|
||||
m: "Mermaid/Merman",
|
||||
o: "Orca",
|
||||
s: "Shark"
|
||||
}
|
||||
},
|
||||
|
||||
// Ursine (U)
|
||||
U: {
|
||||
name: "Ursine",
|
||||
subtypes: {
|
||||
b: "Bear",
|
||||
g: "Grizzly",
|
||||
p: "Panda",
|
||||
l: "Polar Bear"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Function to resolve a species code to its full name
|
||||
function resolveSpeciesCode(code) {
|
||||
if (!code || code.length === 0) return null;
|
||||
|
||||
const parts = code.split('');
|
||||
let current = SPECIES_DATA;
|
||||
let path = [];
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const char = parts[i];
|
||||
|
||||
if (!current[char]) {
|
||||
// If we can't find the next level, return what we have
|
||||
break;
|
||||
}
|
||||
|
||||
current = current[char];
|
||||
|
||||
if (typeof current === 'string') {
|
||||
// We've reached a leaf node (species name)
|
||||
return current;
|
||||
} else if (current.name) {
|
||||
// We're at a node with a name, but there might be subtypes
|
||||
path.push(current.name);
|
||||
if (current.subtypes) {
|
||||
current = current.subtypes;
|
||||
} else {
|
||||
// No more subtypes, return the name
|
||||
return current.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the accumulated path or the name if we have it
|
||||
return path.length > 0 ? path[path.length - 1] : null;
|
||||
}
|
||||
408
js/tags-data.js
Normal file
408
js/tags-data.js
Normal file
@@ -0,0 +1,408 @@
|
||||
// Tag descriptions and meanings for Dragon Code V2.6
|
||||
|
||||
const TAG_DESCRIPTIONS = {
|
||||
gender: {
|
||||
m: "Male",
|
||||
f: "Female",
|
||||
h: "Hermaphrodite",
|
||||
n: "Neuter",
|
||||
'?': "Not telling",
|
||||
'~': "Variable"
|
||||
},
|
||||
|
||||
length: {
|
||||
'+++!': "Celestial",
|
||||
'+++': "Mistaken for mountain ranges",
|
||||
'++': "Lair fits a regiment",
|
||||
'+': "Bigger than most dragons",
|
||||
'': "Normal (Draco-sized)",
|
||||
'-': "Smaller than most dragons",
|
||||
'--': "Fits on your shoulder",
|
||||
'---': "Fits in your pocket",
|
||||
'---!': "Microscopic",
|
||||
units: {
|
||||
'i': 'inches',
|
||||
'f': 'feet',
|
||||
'y': 'yards',
|
||||
'c': 'centimeters',
|
||||
'm': 'meters',
|
||||
'k': 'kilometers',
|
||||
'mi': 'miles'
|
||||
},
|
||||
dimensions: {
|
||||
'a': 'arm length',
|
||||
'l': 'leg length',
|
||||
'n': 'neck length',
|
||||
't': 'tail length',
|
||||
'w': 'wingspan',
|
||||
'h': 'height'
|
||||
}
|
||||
},
|
||||
|
||||
width: {
|
||||
'+++!': "I am Athelind! My belly is now several galaxies wide ... while I'm only a few hundred feet long!",
|
||||
'+++': "Planets have been known to crack in half with my arrival!",
|
||||
'++': "My digestion of food has been known to cause earthquakes",
|
||||
'+': "I move by rolling. Flying has always been an effort for me",
|
||||
'': "What can I say ... I'm normal, except for a few feasts here or there",
|
||||
'~': "Variable - 'My girth depends on what I've just eaten!'",
|
||||
'-': "I'm slightly on the slim side!",
|
||||
'--': "Ever heard of serpentine?",
|
||||
'---': "Whoah! Whaddaya mean I look like a long string with wings?",
|
||||
'---!': "I'm one-dimensional - all length and no width or depth. Just one long super-string!"
|
||||
},
|
||||
|
||||
weight: {
|
||||
'+++': "Obese",
|
||||
'++': "Fat",
|
||||
'+': "Over-weight",
|
||||
'': "Normal weight",
|
||||
'-': "Under-weight",
|
||||
'--': "Skeleton with scales",
|
||||
'---': "Anorexic"
|
||||
},
|
||||
|
||||
appendages: {
|
||||
types: {
|
||||
'h': 'head',
|
||||
'a': 'arms',
|
||||
'l': 'legs',
|
||||
'v': 'horns',
|
||||
'w': 'wings',
|
||||
't': 'tail',
|
||||
'k': 'spikes/ridge',
|
||||
'f': 'fins',
|
||||
'p': 'pouch',
|
||||
"'": 'feathers'
|
||||
},
|
||||
modifiers: {
|
||||
'^': 'retractable',
|
||||
'+': 'extra/multiple',
|
||||
'-': 'missing/vestigial',
|
||||
'!': 'unusual',
|
||||
'~': 'variable'
|
||||
}
|
||||
},
|
||||
|
||||
skinType: {
|
||||
's': 'scales',
|
||||
'h': 'hide',
|
||||
'u': 'fur',
|
||||
'k': 'skin',
|
||||
'l': 'leather',
|
||||
'm': 'metal',
|
||||
'r': 'rock',
|
||||
'f': 'feathers',
|
||||
'e': 'exoskeleton',
|
||||
'b': 'bark',
|
||||
'c': 'cellulose',
|
||||
'x': 'crystals',
|
||||
'': 'none (just bones)',
|
||||
bodyParts: {
|
||||
'a': 'arms',
|
||||
'b': 'belly',
|
||||
'h': 'head',
|
||||
'l': 'legs',
|
||||
'n': 'neck',
|
||||
't': 'tail',
|
||||
'w': 'wings'
|
||||
}
|
||||
},
|
||||
|
||||
color: {
|
||||
bases: {
|
||||
'bk': 'black',
|
||||
'bl': 'blue',
|
||||
'br': 'brown',
|
||||
'au': 'gold',
|
||||
'ag': 'silver',
|
||||
'gr': 'green',
|
||||
're': 'red',
|
||||
'wh': 'white',
|
||||
'ye': 'yellow',
|
||||
'or': 'orange',
|
||||
'pu': 'purple',
|
||||
'pi': 'pink',
|
||||
'gy': 'grey',
|
||||
'cy': 'cyan',
|
||||
'mg': 'magenta',
|
||||
'tn': 'tan',
|
||||
'be': 'beige',
|
||||
'iv': 'ivory',
|
||||
'cr': 'crimson',
|
||||
'sc': 'scarlet',
|
||||
'bz': 'bronze',
|
||||
'cp': 'copper',
|
||||
'bs': 'brass',
|
||||
'lm': 'lime',
|
||||
'tq': 'turquoise',
|
||||
'aq': 'aqua',
|
||||
'nv': 'navy',
|
||||
'in': 'indigo',
|
||||
'vi': 'violet',
|
||||
'la': 'lavender',
|
||||
'ms': 'mauve',
|
||||
'ch': 'chartreuse',
|
||||
'ol': 'olive',
|
||||
'ma': 'maroon',
|
||||
'rs': 'rust',
|
||||
'sa': 'salmon',
|
||||
'pc': 'peach',
|
||||
'ap': 'apricot',
|
||||
'co': 'coral'
|
||||
},
|
||||
intensity: {
|
||||
'+': 'lighter/brighter',
|
||||
'-': 'darker/duller',
|
||||
'^': 'metallic/shiny',
|
||||
'_': 'matte/flat',
|
||||
"'": 'iridescent',
|
||||
'%': 'translucent',
|
||||
'!': 'glowing',
|
||||
'lum': 'luminescent'
|
||||
},
|
||||
patterns: {
|
||||
'|': 'striped',
|
||||
'=': 'banded',
|
||||
':': 'spotted',
|
||||
'*': 'starred',
|
||||
'@': 'swirled',
|
||||
'\\': 'scaled pattern',
|
||||
'/': 'mixed with',
|
||||
'#': 'checkered',
|
||||
'&': 'gradiated',
|
||||
'&1': 'gradient type 1',
|
||||
'&2': 'gradient type 2',
|
||||
'>': 'highlighted'
|
||||
}
|
||||
},
|
||||
|
||||
breath: {
|
||||
types: {
|
||||
'fl': 'flame',
|
||||
'fi': 'fire',
|
||||
'ac': 'acid',
|
||||
'co': 'corrosive',
|
||||
'ic': 'ice',
|
||||
'fr': 'frost',
|
||||
'li': 'lightning',
|
||||
'el': 'electricity',
|
||||
'ga': 'gas',
|
||||
'po': 'poison',
|
||||
'st': 'steam',
|
||||
'wa': 'water',
|
||||
'wi': 'wind',
|
||||
'so': 'sonic',
|
||||
'ch': 'chlorine',
|
||||
'di': 'disintegration',
|
||||
'ma': 'magic'
|
||||
},
|
||||
simple: {
|
||||
'+++': 'Legendary breath weapon',
|
||||
'++': 'Very powerful breath',
|
||||
'+': 'Above average breath',
|
||||
'': 'Normal breath weapon',
|
||||
'-': 'Weak breath',
|
||||
'--': 'Very weak breath',
|
||||
'---': 'No breath weapon'
|
||||
}
|
||||
},
|
||||
|
||||
age: {
|
||||
'+++!': 'Eternal',
|
||||
'+++': 'Ancient beyond measure',
|
||||
'++': 'Ancient',
|
||||
'+': "You've been around",
|
||||
'': 'Adult',
|
||||
'-': 'Young adult',
|
||||
'--': 'Adolescent',
|
||||
'---': 'Hatchling',
|
||||
'?': 'Unknown age'
|
||||
},
|
||||
|
||||
nativeLand: {
|
||||
'+++': 'Known across the realm',
|
||||
'++': 'Well-known region',
|
||||
'+': 'Specific location',
|
||||
'': 'General area',
|
||||
'-': 'Wanderer',
|
||||
'--': 'Nomad',
|
||||
'?': 'Unknown/uncertain'
|
||||
},
|
||||
|
||||
mating: {
|
||||
'+++!': 'Mated in RL to online mate\'s RL self',
|
||||
'+++': 'Mated to RL person I met online',
|
||||
'++': 'Mated online, not met in RL',
|
||||
'+': 'Mated/married',
|
||||
'': 'Dating',
|
||||
'-': 'Looking',
|
||||
'--': 'Not interested',
|
||||
'---': 'Sworn celibate',
|
||||
'!': 'notation for RL connection'
|
||||
},
|
||||
|
||||
offspring: {
|
||||
'+++': 'Large family',
|
||||
'++': 'Several offspring',
|
||||
'+': 'Have offspring',
|
||||
'': 'Interested in having',
|
||||
'-': 'Not interested',
|
||||
'--': "Don't even think about it",
|
||||
'---': 'Absolutely not',
|
||||
'/': 'If one was my fault, I\'d faint',
|
||||
'a': 'adopted'
|
||||
},
|
||||
|
||||
hoard: {
|
||||
'+++': "Really can't tell when someone steals",
|
||||
'++': 'Large hoard',
|
||||
'+': 'Growing collection',
|
||||
'': 'Small hoard',
|
||||
'-': 'Starting out',
|
||||
'--': 'No hoard yet',
|
||||
'---': 'No interest in hoarding'
|
||||
},
|
||||
|
||||
money: {
|
||||
'$$$': 'Richest dragon alive',
|
||||
'$$': 'Wealthy',
|
||||
'$': "Don't touch my hoard",
|
||||
'': 'Comfortable',
|
||||
'-': 'Getting by',
|
||||
'--': 'Poor',
|
||||
'---': 'Broke'
|
||||
},
|
||||
|
||||
diet: {
|
||||
types: {
|
||||
'c': 'carnivore',
|
||||
'h': 'herbivore',
|
||||
'o': 'omnivore',
|
||||
'v': 'vegetarian'
|
||||
},
|
||||
modifiers: {
|
||||
'+++': 'Glutton',
|
||||
'++': 'Overindulgent',
|
||||
'+': 'Healthy appetite',
|
||||
'': 'Normal',
|
||||
'-': 'Light eater',
|
||||
'--': 'Picky eater',
|
||||
'---': 'Barely eats'
|
||||
}
|
||||
},
|
||||
|
||||
reality: {
|
||||
'+++': 'I AM a dragon',
|
||||
'++': 'Strongly identify with on-line form',
|
||||
'+': 'Identify with character',
|
||||
'': 'Just playing',
|
||||
'-': "It's only a game",
|
||||
'--': "Don't confuse RL with online",
|
||||
'---': 'Completely separate'
|
||||
},
|
||||
|
||||
activity: {
|
||||
'+++!': 'T1 connection, never off the net',
|
||||
'+++': 'Online most of the time',
|
||||
'++': 'Online frequently',
|
||||
'+': 'Online regularly',
|
||||
'': 'Moderate activity',
|
||||
'-': 'Occasional visits',
|
||||
'--': 'Rare appearances',
|
||||
'---': 'Almost never online'
|
||||
},
|
||||
|
||||
humor: {
|
||||
'+++': 'Constantly joking',
|
||||
'++': 'Laughing is good for you',
|
||||
'+': 'Good sense of humor',
|
||||
'': 'Normal humor',
|
||||
'-': 'Serious',
|
||||
'--': 'Very serious',
|
||||
'---': 'No sense of humor'
|
||||
},
|
||||
|
||||
social: {
|
||||
'+++': 'Everyone knows',
|
||||
'++': 'Most people know',
|
||||
'+': 'Several people know',
|
||||
'': 'A few friends know',
|
||||
'-': 'Very select few know',
|
||||
'--': 'One or two people know',
|
||||
'---': 'Complete secret',
|
||||
'!': 'Out and proud'
|
||||
},
|
||||
|
||||
ubiquity: {
|
||||
'+++': 'Legendary status',
|
||||
'++': 'Well known',
|
||||
'+': 'Known in community',
|
||||
'': 'Regular member',
|
||||
'-': 'Pretty sure did something important',
|
||||
'--': 'Mostly unnoticed',
|
||||
'---': 'Unknown'
|
||||
},
|
||||
|
||||
irritability: {
|
||||
'+++': 'Constantly angry',
|
||||
'++': 'Short temper',
|
||||
'+': 'Get annoyed easily',
|
||||
'': 'Normal temperament',
|
||||
'-': 'Patient',
|
||||
'--': 'Take everything in stride',
|
||||
'---': 'Nothing bothers me'
|
||||
},
|
||||
|
||||
magic: {
|
||||
'+++': 'Archmage level',
|
||||
'++': 'Powerful magic',
|
||||
'+': 'Competent magic user',
|
||||
'': 'Some magical ability',
|
||||
'-': 'Minor magic',
|
||||
'--': 'Magicians worry when near',
|
||||
'---': 'Magic has no effect'
|
||||
},
|
||||
|
||||
psyPower: {
|
||||
'+++': 'Master psychic',
|
||||
'++': 'Strong psychic',
|
||||
'+': 'Psychic abilities',
|
||||
'': 'Minor psychic talent',
|
||||
'-': 'Resistant to psionics',
|
||||
'--': 'Immune to psionics',
|
||||
'---': 'Psionics have no effect'
|
||||
},
|
||||
|
||||
technology: {
|
||||
'+++': 'Program in assembly',
|
||||
'++': 'Expert programmer',
|
||||
'+': 'Competent with tech',
|
||||
'': 'Normal tech skills',
|
||||
'-': 'Basic computer use',
|
||||
'--': 'Technology challenged',
|
||||
'---': 'What\'s a computer?'
|
||||
},
|
||||
|
||||
emotion: {
|
||||
'+++': 'Extremely affectionate',
|
||||
'++': 'Fairly free with hugs',
|
||||
'+': 'Affectionate',
|
||||
'': 'Normal emotional expression',
|
||||
'-': 'Reserved',
|
||||
'--': 'Emotionally distant',
|
||||
'---': 'Cold/unemotional'
|
||||
},
|
||||
|
||||
dragonFriend: {
|
||||
'+++!': "Have you noticed? You've started growing scales!",
|
||||
'+++': 'Popular with dragons - you exchange overnight visits',
|
||||
'++': 'Reasonably popular - you exchange social visits',
|
||||
'+': 'Polite acquaintance - you exchange social pleasantries',
|
||||
'': "Tolerance - they don't eat you, you don't try to slice them!",
|
||||
'-': 'Irritant - they think about having you over for lunch!',
|
||||
'--': 'Maddening - they think about a quick snack ... now!',
|
||||
'---': "Infuriating - you're not good enough for a snack",
|
||||
'---!': "Cold fury - they're going to hunt you, and find you, and..."
|
||||
}
|
||||
};
|
||||
38
tests/inline-test.js
Normal file
38
tests/inline-test.js
Normal file
@@ -0,0 +1,38 @@
|
||||
// Quick inline test - can be run with copy-paste in browser console
|
||||
|
||||
// Test cases
|
||||
const tests = [
|
||||
{ code: 'DC2.W+++', expect: 'Planets', desc: 'Width +++' },
|
||||
{ code: 'DC2.W~', expect: 'Variable', desc: 'Width ~' },
|
||||
{ code: 'DC2.Tc+++[SE]', expect: 'assembly', desc: 'Technology with specialist' },
|
||||
{ code: 'DC2.T+', expect: 'Over-weight', desc: 'Weight +' },
|
||||
{ code: 'DC2.Skm', expect: 'metal', desc: 'Skin Type metal' },
|
||||
{ code: 'DC2.Df-', expect: 'Irritant', desc: 'Dragon Friend -' },
|
||||
{ code: 'DC2.L', expect: 'Normal', desc: 'Length normal' },
|
||||
{ code: 'DC2.Ac+++!', expect: 'T1', desc: 'Activity +++!' },
|
||||
{ code: 'DC2.A+++!', expect: 'Eternal', desc: 'Age +++!' },
|
||||
{ code: 'DC2.$', expect: 'hoard', desc: 'Money $' },
|
||||
{ code: 'DC2.O/', expect: 'fault', desc: 'Offspring /' },
|
||||
];
|
||||
|
||||
console.log('=== Running Inline Tests ===\n');
|
||||
|
||||
tests.forEach(test => {
|
||||
try {
|
||||
const parsed = parseDragonCode(test.code);
|
||||
const decoded = decodeDragonCode(parsed);
|
||||
|
||||
let actual = 'NOT FOUND';
|
||||
for (const section of Object.values(decoded)) {
|
||||
if (section && section.length > 0) {
|
||||
actual = section[0].value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const passed = actual.toLowerCase().includes(test.expect.toLowerCase());
|
||||
console.log(`${passed ? '✓' : '✗'} ${test.desc}: ${actual}`);
|
||||
} catch (error) {
|
||||
console.log(`✗ ${test.desc}: ERROR - ${error.message}`);
|
||||
}
|
||||
});
|
||||
98
tests/run-tests.html
Normal file
98
tests/run-tests.html
Normal file
@@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Dragon Code Tests - Auto Runner</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Running Tests...</h1>
|
||||
<pre id="output"></pre>
|
||||
|
||||
<script src="../js/species-data.js"></script>
|
||||
<script src="../js/tags-data.js"></script>
|
||||
<script src="../js/parser.js"></script>
|
||||
<script src="../js/decoder.js"></script>
|
||||
<script src="test-data.js"></script>
|
||||
<script src="test-runner.js"></script>
|
||||
<script>
|
||||
// Auto-run tests and output to console and page
|
||||
const output = document.getElementById('output');
|
||||
const originalLog = console.log;
|
||||
|
||||
let logBuffer = [];
|
||||
console.log = function(...args) {
|
||||
const message = args.join(' ');
|
||||
logBuffer.push(message);
|
||||
output.textContent += message + '\n';
|
||||
originalLog.apply(console, args);
|
||||
};
|
||||
|
||||
// Run tests
|
||||
const runner = new TestRunner();
|
||||
const results = runner.runAll();
|
||||
const summary = runner.generateSummary();
|
||||
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log('DETAILED FAILURES');
|
||||
console.log('='.repeat(80) + '\n');
|
||||
|
||||
// Show only failures
|
||||
let failureCount = 0;
|
||||
results.details.forEach(detail => {
|
||||
if (!detail.passed) {
|
||||
failureCount++;
|
||||
console.log(`[${detail.category.toUpperCase()}] ${detail.test}`);
|
||||
console.log(` Expected: ${JSON.stringify(detail.expected)}`);
|
||||
console.log(` Got: ${detail.actual || 'null'}`);
|
||||
if (detail.error) {
|
||||
console.log(` Error: ${detail.error}`);
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
});
|
||||
|
||||
if (failureCount === 0) {
|
||||
console.log('🎉 All tests passed! No failures to report.\n');
|
||||
}
|
||||
|
||||
console.log('='.repeat(80));
|
||||
console.log('SUMMARY BY CATEGORY');
|
||||
console.log('='.repeat(80) + '\n');
|
||||
|
||||
// Group by category
|
||||
const categoryStats = {};
|
||||
results.details.forEach(detail => {
|
||||
if (!categoryStats[detail.category]) {
|
||||
categoryStats[detail.category] = { passed: 0, failed: 0, total: 0 };
|
||||
}
|
||||
categoryStats[detail.category].total++;
|
||||
if (detail.passed) {
|
||||
categoryStats[detail.category].passed++;
|
||||
} else {
|
||||
categoryStats[detail.category].failed++;
|
||||
}
|
||||
});
|
||||
|
||||
for (const [category, stats] of Object.entries(categoryStats)) {
|
||||
const passRate = ((stats.passed / stats.total) * 100).toFixed(1);
|
||||
const status = stats.failed === 0 ? '✓' : '✗';
|
||||
console.log(`${status} ${category.padEnd(20)} ${stats.passed}/${stats.total} (${passRate}%)`);
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(80));
|
||||
|
||||
// Create downloadable file
|
||||
const blob = new Blob([logBuffer.join('\n')], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = 'test-results.txt';
|
||||
link.textContent = 'Download Test Results';
|
||||
link.style.cssText = 'display:block;margin:20px;padding:10px;background:#3498db;color:white;text-decoration:none;border-radius:4px;width:200px;text-align:center';
|
||||
document.body.insertBefore(link, output);
|
||||
|
||||
// Auto-download (commented out - enable if desired)
|
||||
// link.click();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
332
tests/test-data.js
Normal file
332
tests/test-data.js
Normal file
@@ -0,0 +1,332 @@
|
||||
// Comprehensive test data extracted from Dragon Code V2.6 specification
|
||||
// Each test case includes: tag, expected parsing, and expected decoded output
|
||||
|
||||
const TEST_CASES = {
|
||||
species: [
|
||||
{
|
||||
code: 'DC2.Dw',
|
||||
tag: 'Dw',
|
||||
expected: {
|
||||
parse: { type: 'simple', value: 'Dw' },
|
||||
decode: 'Western Dragon'
|
||||
}
|
||||
},
|
||||
{
|
||||
code: 'DC2.De',
|
||||
tag: 'De',
|
||||
expected: {
|
||||
parse: { type: 'simple', value: 'De' },
|
||||
decode: 'Eastern Dragon'
|
||||
}
|
||||
},
|
||||
{
|
||||
code: 'DC2.H',
|
||||
tag: 'H',
|
||||
expected: {
|
||||
parse: { type: 'simple', value: 'H' },
|
||||
decode: 'Human'
|
||||
}
|
||||
},
|
||||
{
|
||||
code: 'DC2.~Dw/H',
|
||||
tag: '~Dw/H',
|
||||
expected: {
|
||||
parse: { type: 'shapechanger' },
|
||||
decode: 'I change between Western Dragon and Human form'
|
||||
}
|
||||
},
|
||||
{
|
||||
code: 'DC2.Dw+H',
|
||||
tag: 'Dw+H',
|
||||
expected: {
|
||||
parse: { type: 'cross' },
|
||||
decode: 'A cross between Western Dragon and Human'
|
||||
}
|
||||
},
|
||||
{
|
||||
code: 'DC2.D[H]',
|
||||
tag: 'D[H]',
|
||||
expected: {
|
||||
parse: { type: 'trapped' },
|
||||
decode: 'trapped in Human form'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
gender: [
|
||||
{ code: 'Gm', expected: 'Male' },
|
||||
{ code: 'Gf', expected: 'Female' },
|
||||
{ code: 'Gh', expected: 'Hermaphrodite' },
|
||||
{ code: 'Gn', expected: 'Neuter' },
|
||||
{ code: 'G?', expected: 'not telling' },
|
||||
{ code: 'G~', expected: 'variable' }
|
||||
],
|
||||
|
||||
length: [
|
||||
{ code: 'L+++!', expected: 'Celestial' },
|
||||
{ code: 'L+++', expected: 'Mistaken for mountain ranges' },
|
||||
{ code: 'L++', expected: 'Lair fits a regiment' },
|
||||
{ code: 'L+', expected: 'Bigger than most dragons' },
|
||||
{ code: 'L', expected: 'Normal (Draco-sized)' },
|
||||
{ code: 'L-', expected: 'Smaller than most dragons' },
|
||||
{ code: 'L--', expected: 'Fits on your shoulder' },
|
||||
{ code: 'L---', expected: 'Fits in your pocket' },
|
||||
{ code: 'L---!', expected: 'Microscopic' },
|
||||
{ code: 'L10m', expected: '10 meters' },
|
||||
{ code: 'L10f', expected: '10 feet' },
|
||||
{ code: 'L10m4t', expected: '10 meters (with 4meters tail length)' }
|
||||
],
|
||||
|
||||
width: [
|
||||
{ code: 'W+++!', expected: "I am Athelind! My belly is now several galaxies wide" },
|
||||
{ code: 'W+++', expected: "Planets have been known to crack in half with my arrival!" },
|
||||
{ code: 'W++', expected: "My digestion of food has been known to cause earthquakes" },
|
||||
{ code: 'W+', expected: "I move by rolling" },
|
||||
{ code: 'W', expected: "I'm normal" },
|
||||
{ code: 'W~', expected: "Variable" },
|
||||
{ code: 'W-', expected: "slightly on the slim side" },
|
||||
{ code: 'W--', expected: "serpentine" },
|
||||
{ code: 'W---', expected: "long string with wings" },
|
||||
{ code: 'W---!', expected: "one-dimensional" }
|
||||
],
|
||||
|
||||
weight: [
|
||||
{ code: 'T+++', expected: 'Obese' },
|
||||
{ code: 'T++', expected: 'Fat' },
|
||||
{ code: 'T+', expected: 'Over-weight' },
|
||||
{ code: 'T', expected: 'Normal weight' },
|
||||
{ code: 'T-', expected: 'Under-weight' },
|
||||
{ code: 'T--', expected: 'Skeleton with scales' },
|
||||
{ code: 'T---', expected: 'Anorexic' }
|
||||
],
|
||||
|
||||
skinType: [
|
||||
{ code: 'Sks', expected: 'scales' },
|
||||
{ code: 'Skh', expected: 'hide' },
|
||||
{ code: 'Sku', expected: 'fur' },
|
||||
{ code: 'Skk', expected: 'skin' },
|
||||
{ code: 'Skl', expected: 'leather' },
|
||||
{ code: 'Skm', expected: 'metal' },
|
||||
{ code: 'Skr', expected: 'rock' },
|
||||
{ code: 'Skf', expected: 'feathers' },
|
||||
{ code: 'Ske', expected: 'exoskeleton' },
|
||||
{ code: 'Skb', expected: 'bark' },
|
||||
{ code: 'Skc', expected: 'cellulose' },
|
||||
{ code: 'Skx', expected: 'crystals' },
|
||||
{ code: 'Sk?', expected: 'Unknown' },
|
||||
{ code: 'Sks,ak', expected: 'scales, with skin on arms' },
|
||||
{ code: 'Sks,bm', expected: 'scales, with metal on belly' }
|
||||
],
|
||||
|
||||
age: [
|
||||
{ code: 'A+++!', expected: 'Eternal' },
|
||||
{ code: 'A+++', expected: 'Ancient beyond measure' },
|
||||
{ code: 'A++', expected: 'Ancient' },
|
||||
{ code: 'A+', expected: "You've been around" },
|
||||
{ code: 'A', expected: 'Adult' },
|
||||
{ code: 'A-', expected: 'Young adult' },
|
||||
{ code: 'A--', expected: 'Adolescent' },
|
||||
{ code: 'A---', expected: 'Hatchling' },
|
||||
{ code: 'A?', expected: 'Unknown age' }
|
||||
],
|
||||
|
||||
breath: [
|
||||
{ code: 'B+++', expected: 'Legendary breath weapon' },
|
||||
{ code: 'B++', expected: 'Very powerful breath' },
|
||||
{ code: 'B+', expected: 'Above average breath' },
|
||||
{ code: 'B', expected: 'Normal breath weapon' },
|
||||
{ code: 'B-', expected: 'Weak breath' },
|
||||
{ code: 'B--', expected: 'Very weak breath' },
|
||||
{ code: 'B---', expected: 'No breath weapon' },
|
||||
{ code: 'Bfl', expected: 'flame' },
|
||||
{ code: 'Bfi', expected: 'fire' },
|
||||
{ code: 'Bac', expected: 'acid' },
|
||||
{ code: 'Bic', expected: 'ice' },
|
||||
{ code: 'Bli', expected: 'lightning' }
|
||||
],
|
||||
|
||||
reality: [
|
||||
{ code: 'R+++', expected: 'I AM a dragon' },
|
||||
{ code: 'R++', expected: 'Strongly identify with on-line form' },
|
||||
{ code: 'R+', expected: 'Identify with character' },
|
||||
{ code: 'R', expected: 'Just playing' },
|
||||
{ code: 'R-', expected: "It's only a game" },
|
||||
{ code: 'R--', expected: "Don't confuse RL with online" },
|
||||
{ code: 'R---', expected: 'Completely separate' }
|
||||
],
|
||||
|
||||
activity: [
|
||||
{ code: 'Ac+++!', expected: 'T1 connection, never off the net' },
|
||||
{ code: 'Ac+++', expected: 'Online most of the time' },
|
||||
{ code: 'Ac++', expected: 'Online frequently' },
|
||||
{ code: 'Ac+', expected: 'Online regularly' },
|
||||
{ code: 'Ac', expected: 'Moderate activity' },
|
||||
{ code: 'Ac-', expected: 'Occasional visits' },
|
||||
{ code: 'Ac--', expected: 'Rare appearances' },
|
||||
{ code: 'Ac---', expected: 'Almost never online' }
|
||||
],
|
||||
|
||||
technology: [
|
||||
{ code: 'Tc+++', expected: 'Program in assembly' },
|
||||
{ code: 'Tc++', expected: 'Expert programmer' },
|
||||
{ code: 'Tc+', expected: 'Competent with tech' },
|
||||
{ code: 'Tc', expected: 'Normal tech skills' },
|
||||
{ code: 'Tc-', expected: 'Basic computer use' },
|
||||
{ code: 'Tc--', expected: 'Technology challenged' },
|
||||
{ code: 'Tc---', expected: "What's a computer?" },
|
||||
{ code: 'Tc+++[SE]', expected: 'Program in assembly, specialist in SE' }
|
||||
],
|
||||
|
||||
mating: [
|
||||
{ code: 'M+++!1', expected: 'Mated in RL to online mate\'s RL self, 1 mate' },
|
||||
{ code: 'M+++', expected: 'Mated to RL person I met online' },
|
||||
{ code: 'M++', expected: 'Mated online, not met in RL' },
|
||||
{ code: 'M+', expected: 'Mated/married' },
|
||||
{ code: 'M', expected: 'Dating' },
|
||||
{ code: 'M-', expected: 'Looking' },
|
||||
{ code: 'M--', expected: 'Not interested' },
|
||||
{ code: 'M---', expected: 'Sworn celibate' }
|
||||
],
|
||||
|
||||
offspring: [
|
||||
{ code: 'O+++', expected: 'Large family' },
|
||||
{ code: 'O++', expected: 'Several offspring' },
|
||||
{ code: 'O+', expected: 'Have offspring' },
|
||||
{ code: 'O', expected: 'Interested in having' },
|
||||
{ code: 'O-', expected: 'Not interested' },
|
||||
{ code: 'O--', expected: "Don't even think about it" },
|
||||
{ code: 'O---', expected: 'Absolutely not' },
|
||||
{ code: 'O/', expected: "If one was my fault, I'd faint" }
|
||||
],
|
||||
|
||||
hoard: [
|
||||
{ code: 'DC2.Dw H+++', expected: "Really can't tell when someone steals" },
|
||||
{ code: 'DC2.Dw H++', expected: 'Large hoard' },
|
||||
{ code: 'DC2.Dw H+', expected: 'Growing collection' },
|
||||
{ code: 'DC2.Dw H', expected: 'Small hoard' },
|
||||
{ code: 'DC2.Dw H-', expected: 'Starting out' },
|
||||
{ code: 'DC2.Dw H--', expected: 'No hoard yet' },
|
||||
{ code: 'DC2.Dw H---', expected: 'No interest in hoarding' }
|
||||
],
|
||||
|
||||
money: [
|
||||
{ code: '$$$', expected: 'Richest dragon alive' },
|
||||
{ code: '$$', expected: 'Wealthy' },
|
||||
{ code: '$', expected: "Don't touch my hoard" },
|
||||
{ code: '$-', expected: 'Getting by' },
|
||||
{ code: '$--', expected: 'Poor' },
|
||||
{ code: '$---', expected: 'Broke' }
|
||||
],
|
||||
|
||||
diet: [
|
||||
{ code: 'F+++o', expected: 'Glutton omnivore' },
|
||||
{ code: 'F++o', expected: 'Overindulgent omnivore' },
|
||||
{ code: 'F+o', expected: 'Healthy appetite omnivore' },
|
||||
{ code: 'Fc', expected: 'carnivore' },
|
||||
{ code: 'Fh', expected: 'herbivore' },
|
||||
{ code: 'Fo', expected: 'omnivore' },
|
||||
{ code: 'Fv', expected: 'vegetarian' }
|
||||
],
|
||||
|
||||
humor: [
|
||||
{ code: 'J+++', expected: 'Constantly joking' },
|
||||
{ code: 'J++', expected: 'Laughing is good for you' },
|
||||
{ code: 'J+', expected: 'Good sense of humor' },
|
||||
{ code: 'J', expected: 'Normal humor' },
|
||||
{ code: 'J-', expected: 'Serious' },
|
||||
{ code: 'J--', expected: 'Very serious' },
|
||||
{ code: 'J---', expected: 'No sense of humor' }
|
||||
],
|
||||
|
||||
social: [
|
||||
{ code: 'S+++', expected: 'Everyone knows' },
|
||||
{ code: 'S++', expected: 'Most people know' },
|
||||
{ code: 'S+', expected: 'Several people know' },
|
||||
{ code: 'S', expected: 'A few friends know' },
|
||||
{ code: 'S-', expected: 'Very select few know' },
|
||||
{ code: 'S--', expected: 'One or two people know' },
|
||||
{ code: 'S---', expected: 'Complete secret' },
|
||||
{ code: 'S!', expected: 'Out and proud' }
|
||||
],
|
||||
|
||||
ubiquity: [
|
||||
{ code: 'U+++', expected: 'Legendary status' },
|
||||
{ code: 'U++', expected: 'Well known' },
|
||||
{ code: 'U+', expected: 'Known in community' },
|
||||
{ code: 'U', expected: 'Regular member' },
|
||||
{ code: 'U-', expected: 'Pretty sure did something important' },
|
||||
{ code: 'U--', expected: 'Mostly unnoticed' },
|
||||
{ code: 'U---', expected: 'Unknown' }
|
||||
],
|
||||
|
||||
irritability: [
|
||||
{ code: 'I+++', expected: 'Constantly angry' },
|
||||
{ code: 'I++', expected: 'Short temper' },
|
||||
{ code: 'I+', expected: 'Get annoyed easily' },
|
||||
{ code: 'I', expected: 'Normal temperament' },
|
||||
{ code: 'I-', expected: 'Patient' },
|
||||
{ code: 'I--', expected: 'Take everything in stride' },
|
||||
{ code: 'I---', expected: 'Nothing bothers me' }
|
||||
],
|
||||
|
||||
magic: [
|
||||
{ code: 'V+++', expected: 'Archmage level' },
|
||||
{ code: 'V++', expected: 'Powerful magic' },
|
||||
{ code: 'V+', expected: 'Competent magic user' },
|
||||
{ code: 'V', expected: 'Some magical ability' },
|
||||
{ code: 'V-', expected: 'Minor magic' },
|
||||
{ code: 'V--', expected: 'Magicians worry when near' },
|
||||
{ code: 'V---', expected: 'Magic has no effect' }
|
||||
],
|
||||
|
||||
psyPower: [
|
||||
{ code: 'Q+++', expected: 'Master psychic' },
|
||||
{ code: 'Q++', expected: 'Strong psychic' },
|
||||
{ code: 'Q+', expected: 'Psychic abilities' },
|
||||
{ code: 'Q', expected: 'Minor psychic talent' },
|
||||
{ code: 'Q-', expected: 'Resistant to psionics' },
|
||||
{ code: 'Q--', expected: 'Immune to psionics' },
|
||||
{ code: 'Q---', expected: 'Psionics have no effect' }
|
||||
],
|
||||
|
||||
emotion: [
|
||||
{ code: 'E+++', expected: 'Extremely affectionate' },
|
||||
{ code: 'E++', expected: 'Fairly free with hugs' },
|
||||
{ code: 'E+', expected: 'Affectionate' },
|
||||
{ code: 'E', expected: 'Normal emotional expression' },
|
||||
{ code: 'E-', expected: 'Reserved' },
|
||||
{ code: 'E--', expected: 'Emotionally distant' },
|
||||
{ code: 'E---', expected: 'Cold/unemotional' }
|
||||
],
|
||||
|
||||
dragonFriend: [
|
||||
{ code: 'Df+++!', expected: "Have you noticed? You've started growing scales!" },
|
||||
{ code: 'Df+++', expected: 'Popular with dragons - you exchange overnight visits' },
|
||||
{ code: 'Df++', expected: 'Reasonably popular - you exchange social visits' },
|
||||
{ code: 'Df+', expected: 'Polite acquaintance - you exchange social pleasantries' },
|
||||
{ code: 'Df', expected: "Tolerance - they don't eat you" },
|
||||
{ code: 'Df-', expected: 'Irritant - they think about having you over for lunch!' },
|
||||
{ code: 'Df--', expected: 'Maddening - they think about a quick snack ... now!' },
|
||||
{ code: 'Df---', expected: "Infuriating - you're not good enough for a snack" },
|
||||
{ code: 'Df---!', expected: "Cold fury - they're going to hunt you, and find you, and..." }
|
||||
]
|
||||
};
|
||||
|
||||
// Full integration test cases
|
||||
const INTEGRATION_TESTS = [
|
||||
{
|
||||
name: 'Full Example from Spec',
|
||||
code: 'DC2.Dw Gm L W T+ Phvwalt Sk? Cbk--^\\bl+lum B- A+ N? M+++!1 O/ H+++ $ F+o R++ Ac+++! J++ S U- I-- V- Q--- Tc+++[SE] E++',
|
||||
expectedTags: {
|
||||
species: 'Western Dragon',
|
||||
gender: 'Male',
|
||||
length: 'Normal (Draco-sized)',
|
||||
width: "I'm normal",
|
||||
weight: 'Over-weight',
|
||||
skinType: 'Unknown',
|
||||
breath: 'Weak breath',
|
||||
age: "You've been around",
|
||||
technology: 'Program in assembly, specialist in SE'
|
||||
}
|
||||
}
|
||||
];
|
||||
115
tests/test-runner-node.js
Normal file
115
tests/test-runner-node.js
Normal file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Node.js Test Runner for Dragon Code V2.6
|
||||
// Loads all dependencies and runs tests in Node environment
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Simulate browser environment
|
||||
global.window = {};
|
||||
|
||||
// Load dependencies
|
||||
function loadScript(filename) {
|
||||
const filepath = path.join(__dirname, '..', filename);
|
||||
const content = fs.readFileSync(filepath, 'utf8');
|
||||
eval(content);
|
||||
}
|
||||
|
||||
// Load all JS files
|
||||
try {
|
||||
loadScript('js/species-data.js');
|
||||
loadScript('js/tags-data.js');
|
||||
loadScript('js/parser.js');
|
||||
loadScript('js/decoder.js');
|
||||
|
||||
// Load test files
|
||||
const testDataPath = path.join(__dirname, 'test-data.js');
|
||||
const testData = fs.readFileSync(testDataPath, 'utf8');
|
||||
eval(testData);
|
||||
|
||||
const testRunnerPath = path.join(__dirname, 'test-runner.js');
|
||||
const testRunner = fs.readFileSync(testRunnerPath, 'utf8');
|
||||
eval(testRunner);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading scripts:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Run tests
|
||||
console.log('='.repeat(80));
|
||||
console.log('Dragon Code V2.6 - Automated Test Suite');
|
||||
console.log('='.repeat(80));
|
||||
console.log('');
|
||||
|
||||
const runner = new TestRunner();
|
||||
const results = runner.runAll();
|
||||
const summary = runner.generateSummary();
|
||||
|
||||
console.log('');
|
||||
console.log('='.repeat(80));
|
||||
console.log('DETAILED FAILURES');
|
||||
console.log('='.repeat(80));
|
||||
console.log('');
|
||||
|
||||
// Show only failures
|
||||
let failureCount = 0;
|
||||
results.details.forEach(detail => {
|
||||
if (!detail.passed) {
|
||||
failureCount++;
|
||||
console.log(`[${detail.category.toUpperCase()}] ${detail.test}`);
|
||||
console.log(` Expected: ${JSON.stringify(detail.expected)}`);
|
||||
console.log(` Got: ${detail.actual || 'null'}`);
|
||||
if (detail.error) {
|
||||
console.log(` Error: ${detail.error}`);
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
});
|
||||
|
||||
if (failureCount === 0) {
|
||||
console.log('🎉 All tests passed! No failures to report.');
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('='.repeat(80));
|
||||
console.log('SUMMARY BY CATEGORY');
|
||||
console.log('='.repeat(80));
|
||||
console.log('');
|
||||
|
||||
// Group by category and show stats
|
||||
const categoryStats = {};
|
||||
results.details.forEach(detail => {
|
||||
if (!categoryStats[detail.category]) {
|
||||
categoryStats[detail.category] = { passed: 0, failed: 0, total: 0 };
|
||||
}
|
||||
categoryStats[detail.category].total++;
|
||||
if (detail.passed) {
|
||||
categoryStats[detail.category].passed++;
|
||||
} else {
|
||||
categoryStats[detail.category].failed++;
|
||||
}
|
||||
});
|
||||
|
||||
for (const [category, stats] of Object.entries(categoryStats)) {
|
||||
const passRate = ((stats.passed / stats.total) * 100).toFixed(1);
|
||||
const status = stats.failed === 0 ? '✓' : '✗';
|
||||
console.log(`${status} ${category.padEnd(20)} ${stats.passed}/${stats.total} (${passRate}%)`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('='.repeat(80));
|
||||
|
||||
// Write results to file
|
||||
const outputPath = path.join(__dirname, 'test-results.json');
|
||||
fs.writeFileSync(outputPath, JSON.stringify({
|
||||
summary,
|
||||
results: results.details,
|
||||
categoryStats
|
||||
}, null, 2));
|
||||
|
||||
console.log(`\nDetailed results written to: ${outputPath}`);
|
||||
|
||||
// Exit with error code if tests failed
|
||||
process.exit(results.failed > 0 ? 1 : 0);
|
||||
284
tests/test-runner.js
Normal file
284
tests/test-runner.js
Normal file
@@ -0,0 +1,284 @@
|
||||
// Dragon Code V2.6 Test Runner
|
||||
// Validates parser and decoder against specification
|
||||
|
||||
class TestRunner {
|
||||
constructor() {
|
||||
this.results = {
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
total: 0,
|
||||
details: []
|
||||
};
|
||||
}
|
||||
|
||||
// Run all tests
|
||||
runAll() {
|
||||
console.log('=== Dragon Code V2.6 Test Suite ===\n');
|
||||
|
||||
// Test each category
|
||||
for (const [category, tests] of Object.entries(TEST_CASES)) {
|
||||
this.runCategory(category, tests);
|
||||
}
|
||||
|
||||
// Run integration tests
|
||||
this.runIntegrationTests();
|
||||
|
||||
return this.results;
|
||||
}
|
||||
|
||||
// Run tests for a specific category
|
||||
runCategory(category, tests) {
|
||||
console.log(`\n--- Testing ${category} (${tests.length} tests) ---`);
|
||||
|
||||
tests.forEach((test, index) => {
|
||||
this.runTest(category, test, index);
|
||||
});
|
||||
}
|
||||
|
||||
// Run a single test
|
||||
runTest(category, test, index) {
|
||||
this.results.total++;
|
||||
|
||||
try {
|
||||
// Parse the code
|
||||
const fullCode = test.code || `DC2.${test.code}`;
|
||||
const parsed = parseDragonCode(fullCode);
|
||||
|
||||
// Decode the parsed result
|
||||
const decoded = decodeDragonCode(parsed);
|
||||
|
||||
// Validate the result
|
||||
const validation = this.validateResult(category, test, parsed, decoded);
|
||||
|
||||
if (validation.passed) {
|
||||
this.results.passed++;
|
||||
console.log(`✓ Test ${index + 1}: ${test.code}`);
|
||||
} else {
|
||||
this.results.failed++;
|
||||
console.log(`✗ Test ${index + 1}: ${test.code}`);
|
||||
console.log(` Expected: ${test.expected}`);
|
||||
console.log(` Got: ${validation.actual}`);
|
||||
}
|
||||
|
||||
this.results.details.push({
|
||||
category,
|
||||
test: test.code,
|
||||
passed: validation.passed,
|
||||
expected: test.expected,
|
||||
actual: validation.actual,
|
||||
error: validation.error
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
this.results.failed++;
|
||||
console.log(`✗ Test ${index + 1}: ${test.code} - ERROR: ${error.message}`);
|
||||
|
||||
this.results.details.push({
|
||||
category,
|
||||
test: test.code,
|
||||
passed: false,
|
||||
expected: test.expected,
|
||||
actual: null,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Validate parsed and decoded results
|
||||
validateResult(category, test, parsed, decoded) {
|
||||
const result = { passed: false, actual: null, error: null };
|
||||
|
||||
try {
|
||||
// Extract the relevant decoded value based on category
|
||||
let actual = this.extractDecodedValue(category, decoded);
|
||||
|
||||
if (!actual) {
|
||||
result.error = 'No decoded value found';
|
||||
result.actual = 'null';
|
||||
return result;
|
||||
}
|
||||
|
||||
result.actual = actual;
|
||||
|
||||
// Check if the decoded value contains the expected text
|
||||
const expected = test.expected;
|
||||
|
||||
if (typeof expected === 'string') {
|
||||
// For simple string matching, check if the actual contains expected keywords
|
||||
const actualLower = actual.toLowerCase();
|
||||
const expectedLower = expected.toLowerCase();
|
||||
|
||||
// If expected is very short, do exact match
|
||||
if (expectedLower.length <= 5) {
|
||||
result.passed = actualLower === expectedLower || actualLower.includes(expectedLower);
|
||||
} else {
|
||||
// Match if actual contains most of the expected words
|
||||
const expectedWords = expectedLower.split(' ').filter(w => w.length > 3);
|
||||
const matchedWords = expectedWords.filter(word => actualLower.includes(word));
|
||||
|
||||
result.passed = matchedWords.length >= Math.max(1, expectedWords.length * 0.6);
|
||||
}
|
||||
} else if (typeof expected === 'object') {
|
||||
// For complex validation (species with parse structure)
|
||||
result.passed = this.validateComplex(expected, parsed, actual);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
result.error = error.message;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Extract decoded value for a specific category
|
||||
extractDecodedValue(category, decoded) {
|
||||
const categoryMap = {
|
||||
species: { section: 'species', label: 'Species' },
|
||||
gender: { section: 'physical', label: 'Gender' },
|
||||
length: { section: 'physical', label: 'Length' },
|
||||
width: { section: 'physical', label: 'Width' },
|
||||
weight: { section: 'physical', label: 'Weight' },
|
||||
age: { section: 'physical', label: 'Age' },
|
||||
skinType: { section: 'appearance', label: 'Skin Type' },
|
||||
breath: { section: 'abilities', label: 'Breath Weapon' },
|
||||
reality: { section: 'personality', label: 'Reality' },
|
||||
activity: { section: 'personality', label: 'Activity' },
|
||||
humor: { section: 'personality', label: 'Humor' },
|
||||
social: { section: 'personality', label: 'Social' },
|
||||
irritability: { section: 'personality', label: 'Irritability' },
|
||||
emotion: { section: 'personality', label: 'Emotion' },
|
||||
technology: { section: 'other', label: 'Technology' },
|
||||
ubiquity: { section: 'other', label: 'Ubiquity' },
|
||||
dragonFriend: { section: 'other', label: 'Dragon-Friend' },
|
||||
mating: { section: 'life', label: 'Mating' },
|
||||
offspring: { section: 'life', label: 'Offspring' },
|
||||
hoard: { section: 'life', label: 'Hoard' },
|
||||
money: { section: 'life', label: 'Money' },
|
||||
diet: { section: 'life', label: 'Diet' },
|
||||
magic: { section: 'abilities', label: 'Magic' },
|
||||
psyPower: { section: 'abilities', label: 'Psy-Power' }
|
||||
};
|
||||
|
||||
const mapping = categoryMap[category];
|
||||
if (!mapping || !decoded[mapping.section]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the item with matching label in the section
|
||||
const items = decoded[mapping.section];
|
||||
if (!items || items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Search for item with matching label
|
||||
const matchingItem = items.find(item => item.label === mapping.label);
|
||||
if (matchingItem) {
|
||||
return matchingItem.value;
|
||||
}
|
||||
|
||||
// Fallback: if no exact match, return first item (for backward compatibility)
|
||||
return items[0].value;
|
||||
}
|
||||
|
||||
// Validate complex expectations
|
||||
validateComplex(expected, parsed, actual) {
|
||||
if (expected.parse) {
|
||||
// Validate parse structure
|
||||
if (parsed.species && parsed.species.type !== expected.parse.type) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (expected.decode) {
|
||||
// Validate decoded output contains expected text
|
||||
const actualLower = actual.toLowerCase();
|
||||
const expectedLower = expected.decode.toLowerCase();
|
||||
return actualLower.includes(expectedLower);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Run integration tests
|
||||
runIntegrationTests() {
|
||||
console.log('\n--- Integration Tests ---');
|
||||
|
||||
INTEGRATION_TESTS.forEach((test, index) => {
|
||||
this.results.total++;
|
||||
|
||||
try {
|
||||
const parsed = parseDragonCode(test.code);
|
||||
const decoded = decodeDragonCode(parsed);
|
||||
|
||||
let allPassed = true;
|
||||
const failures = [];
|
||||
|
||||
// Check each expected tag
|
||||
for (const [tagType, expectedValue] of Object.entries(test.expectedTags)) {
|
||||
const actual = this.extractDecodedValue(tagType, decoded);
|
||||
|
||||
if (!actual || !actual.toLowerCase().includes(expectedValue.toLowerCase().substring(0, 20))) {
|
||||
allPassed = false;
|
||||
failures.push(`${tagType}: expected "${expectedValue}", got "${actual}"`);
|
||||
}
|
||||
}
|
||||
|
||||
if (allPassed) {
|
||||
this.results.passed++;
|
||||
console.log(`✓ Integration Test ${index + 1}: ${test.name}`);
|
||||
} else {
|
||||
this.results.failed++;
|
||||
console.log(`✗ Integration Test ${index + 1}: ${test.name}`);
|
||||
failures.forEach(f => console.log(` ${f}`));
|
||||
}
|
||||
|
||||
this.results.details.push({
|
||||
category: 'integration',
|
||||
test: test.name,
|
||||
passed: allPassed,
|
||||
expected: test.expectedTags,
|
||||
actual: allPassed ? 'All tags matched' : failures.join('; ')
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
this.results.failed++;
|
||||
console.log(`✗ Integration Test ${index + 1}: ${test.name} - ERROR: ${error.message}`);
|
||||
|
||||
this.results.details.push({
|
||||
category: 'integration',
|
||||
test: test.name,
|
||||
passed: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Generate summary report
|
||||
generateSummary() {
|
||||
const passRate = ((this.results.passed / this.results.total) * 100).toFixed(1);
|
||||
|
||||
console.log('\n=== Test Summary ===');
|
||||
console.log(`Total: ${this.results.total}`);
|
||||
console.log(`Passed: ${this.results.passed}`);
|
||||
console.log(`Failed: ${this.results.failed}`);
|
||||
console.log(`Pass Rate: ${passRate}%`);
|
||||
|
||||
return {
|
||||
total: this.results.total,
|
||||
passed: this.results.passed,
|
||||
failed: this.results.failed,
|
||||
passRate: passRate
|
||||
};
|
||||
}
|
||||
|
||||
// Get detailed results
|
||||
getResults() {
|
||||
return this.results;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-run tests if this script is loaded directly
|
||||
if (typeof window !== 'undefined') {
|
||||
window.TestRunner = TestRunner;
|
||||
}
|
||||
Reference in New Issue
Block a user