FoxInFlame
FoxInFlame

Reputation: 970

Translating Minecraft Color Codes into HTML

Recently, I was trying to convert Minecraft MOTD into Plain HTML. A Minecraft MOTD is basic text, with Minecraft Color Codes to represent color changes.

Example: "§d§lThe Nexus§8: §6§oO_O §a§l[MUTATION MONDAY]"

Translated: <span style='color:pink'><b>The Nexus</b></span><span style='color:gray'>:</span>...

So on... So basically, there are color codes, which change the color of the text onwards, and formatting codes, which change the formatting of the text onwards.

Now, because the Minecraft Color Codes do not state where the color codes end, I don't know exactly how I could translate this into HTML.

I did manage halfway, by replacing all color codes with </span><span style='color:newcolor'> and replacing all formatting codes with </var><var style='text-decoration:blah'>

This did not work however, because when you have a color code straight before a formatting code, it becomes <span></var><var></span>.

So the question is, how would I do this? Finding where the color codes end and so on.

Color Codes for Minecraft is available here: http://ess.khhq.net/mc/

Since my code is too big, I have it posted on JSFiddle.

My code so far: https://jsfiddle.net/thwe0yek/1/ (First one's fine, second one's messed up..)

Upvotes: 0

Views: 5727

Answers (1)

user4698813
user4698813

Reputation:

Here's a way to do it.

A brief explanation of how this works:

  • It looks for all codes in the string with a regex that matches them (see the very first line in the parseStyle function)
  • It pushes the indexes of where those codes are in the string into an array, and replaces each code with a double \x00 (null char), in order to handle repetition of codes in later parts of the string (see the first for loop).
  • It splits the string into parts, in accordance to the start of one code and the start of another, so that each part can be styled individually, with its own rules.
  • Also in the second loop, the strings are sent to the applyCode function, which applies the codes given to it (through an array), with the help of the styleMap. This function creates a span element for each string it is given. If it finds the k rule, it will also apply the obfuscate function.
  • The obfuscate function takes a string, gets its length, and generates a random character for every character in the string. It does this in an interval, char by char. The range of characters it chooses can be modified by changing the randInt values in thereplaceRand function.
  • Finally, the script returns an object with two properties: one is parsed which returns a DOM element with the 'obfuscators' running, and the other is raw, which returns the span elements as a HTML string (it's just parsed's innerHTML).

To use it, just call mineParse(string), for instance:

var result = mineParse('§6Hello');
document.body.appendChild(result.parsed);

This now supports converting newlines to <br> tags, the §r reset code, and multiple codes applied in any order.

I'm sure the script can be further simplified, though. Also, I'm sure it has bugs, you can modify it as you wish.

http://jsfiddle.net/fku9gsax/12/

(function () {

    'use strict';

    var currId = 0,
        obfuscators = {},
        alreadyParsed = [],
        styleMap = {
            '§0': 'color:#000000',
            '§1': 'color:#0000AA',
            '§2': 'color:#00AA00',
            '§3': 'color:#00AAAA',
            '§4': 'color:#AA0000',
            '§5': 'color:#AA00AA',
            '§6': 'color:#FFAA00',
            '§7': 'color:#AAAAAA',
            '§8': 'color:#555555',
            '§9': 'color:#5555FF',
            '§a': 'color:#55FF55',
            '§b': 'color:#55FFFF',
            '§c': 'color:#FF5555',
            '§d': 'color:#FF55FF',
            '§e': 'color:#FFFF55',
            '§f': 'color:#FFFFFF',
            '§l': 'font-weight:bold',
            '§m': 'text-decoration:line-through',
            '§n': 'text-decoration:underline',
            '§o': 'font-style:italic'
        };

    function obfuscate(elem, string) {
        var multiMagic,
            currNode,
            listLen,
            nodeI;

        function randInt(min, max) {
            return Math.floor(Math.random() * (max - min + 1)) + min;
        }

        function replaceRand(string, i) {
            var randChar = String.fromCharCode(randInt(64, 95));
            return string.substr(0, i) + randChar + string.substr(i + 1, string.length);
        }

        function initMagic(el, str) {
            var i = 0,
                obsStr = str || el.innerHTML,
                strLen = obsStr.length;
            if(!strLen) return;
            obfuscators[currId].push(
                window.setInterval(function () {
                    if (i >= strLen) i = 0;
                    obsStr = replaceRand(obsStr, i);
                    el.innerHTML = obsStr;
                    i++;
                }, 0)
            );
        }

        if (string.indexOf('<br>') > -1) {
            elem.innerHTML = string;
            listLen = elem.childNodes.length;
            for (nodeI = 0; nodeI < listLen; nodeI++) {
                currNode = elem.childNodes[nodeI];
                if (currNode.nodeType === 3) {
                    multiMagic = document.createElement('span');
                    multiMagic.innerHTML = currNode.nodeValue;
                    elem.replaceChild(multiMagic, currNode);
                    initMagic(multiMagic);
                }
            }
        } else {
            initMagic(elem, string);
        }
    }

    function applyCode(string, codes) {
        var elem = document.createElement('span'),
            obfuscated = false;

        string = string.replace(/\x00/g, '');

        codes.forEach(function (code) {
            elem.style.cssText += styleMap[code] + ';';
            if (code === '§k') {
                obfuscate(elem, string);
                obfuscated = true;
            }
        });

        if (!obfuscated) elem.innerHTML = string;

        return elem;
    }

    function parseStyle(string) {
        var finalPre = document.createElement('pre'),
            codes = string.match(/§.{1}/g) || [],
            codesLen = codes.length,
            indexes = [],
            indexDelta,
            apply = [],
            strSlice,
            i;

        if (!obfuscators[currId]) obfuscators[currId] = [];

        string = string.replace(/\n|\\n/g, '<br>');

        for (i = 0; i < codesLen; i++) {
            indexes.push(string.indexOf(codes[i]));
            string = string.replace(codes[i], '\x00\x00');
        }

        if (indexes[0] !== 0) {
            finalPre.appendChild(applyCode(string.substring(0, indexes[0]), []));
        }

        for (i = 0; i < codesLen; i++) {
            indexDelta = indexes[i + 1] - indexes[i];
            if (indexDelta === 2) {
                while (indexDelta === 2) {
                    apply.push(codes[i]);
                    i++;
                    indexDelta = indexes[i + 1] - indexes[i];
                }
                apply.push(codes[i]);
            } else {
                apply.push(codes[i]);
            }
            if (apply.lastIndexOf('§r') > -1) {
                apply = apply.slice(apply.lastIndexOf('§r') + 1);
            }
            strSlice = string.substring(indexes[i], indexes[i + 1]);
            finalPre.appendChild(applyCode(strSlice, apply));
        }

        return finalPre;
    }

    function clearObfuscators(id) {
        obfuscators[id].forEach(function (interval) {
            clearInterval(interval);
        });
        alreadyParsed[id] = [];
        obfuscators[id] = [];
    }

    window.mineParse = function initParser(input) {
        var parsed,
            i = currId;
        if (i > 0) {
            while (i--) {
                if (alreadyParsed[i].nodeType) {
                    if (!document.contains(alreadyParsed[i])) {
                        clearObfuscators(i);
                    }
                }
            }
        }
        parsed = parseStyle(input);
        alreadyParsed.push(parsed);
        currId++;
        return {
            parsed: parsed,
            raw: parsed.innerHTML
        };
    };

}());
@media (max-width: 850px) {
   
   div#inBetween {
        width: 100%;
        box-sizing: border-box;
        padding: 0.05em;
    }
    #inBetween:before {
        display: inline-block;
        font-family:'FontAwesome';
        content:"\f107";
        animation: bounceDown 2s infinite;
    }
}
@media (min-width: 850px) {
    #inBetween:before {
        display: inline-block;
        font-family:'FontAwesome';
        content:"\f105";
        animation: bounceRight 2s infinite;
    }
}
html, body {
    height: 100%;
    width:  100%;
    margin: 0;
    padding: 0;
}
button::-moz-focus-inner {
    border: 0;
}
body {
    text-align: center;
    background: #050A0E;
    font-family:'Open Sans', sans-serif;
    overflow-x: hidden;
}
#mainContainer {
  padding:  5em 0 5em 0;
}
h1 {
    font-weight: bold;
    color: #5EB4E7;
    text-shadow: 0 0 50px #5EB4E7;
    padding: 1em;
    margin:  0;
    animation: fadeIn 2s ease-in-out;
}
#inputContainer, #outputContainer {
    display: inline-block;
    padding: 1em;
    width: 20em;
    height: 21em;
    background: #152E3D;
    box-shadow: 0 0 4px #5EB4E7;
    vertical-align: middle;
    animation: scaleIn 1s ease-in-out;
}
#inBetween {
    display: inline-block;
    vertical-align: middle;
    color: #5EB4E7;
    font-size: 4em;
    padding: 0.5em;
    animation: scaleIn 1s ease-in-out;
}
#outputContainer {
    display: inline-block;
    padding: 1em;
    vertical-align: top;
    border-radius: 0 5px 5px 0;
}
#inputContainer {
    border-radius: 5px 0 0 5px;
}
#input {
    box-sizing: border-box;
    padding: 1em;
    width: 20em;
    max-width: 20em;
    height: 20em;
    max-height: 20em;
}
#output {
    background: rgba(255, 255, 255, 0.97);
    text-align: left;
    animation: bounceInRight 1s;
}
#output pre {
    margin-top: 0;
    padding: 1em;
    border: 4px solid #050A0E;
    overflow: auto;
    max-height: 20em;
    animation: bounceInRight 0.5s;
}
#input, #parse, #parse:active, #parse:focus {
    padding: 1em;
    width: 20em;
    background: #050A0E;
    color: #5EB4E7;
    outline: 0;
    outline-style: none;
    outline-width: 0;
    box-shadow: none;
    border: none;
}
#parse {
    transition: all 225ms ease-in-out;
    border-radius: 0 0 5px 5px;
}
#parse:hover {
    color: rgba(255, 255, 255, 0.8);
    background: #5EB4E7;
    cursor: pointer;
    box-shadow: 0 0 25px #5EB4E7;
}
.containerLabel {
    position: relative;
    top: -0.5em;
    left: -1.5em;
    color: #5EB4E7;
    text-align: left;
    margin-left: 1.5em;
    font-size: 80%;
    font-weight: bold;
    opacity: 0.85;
    margin-bottom: 0.25em;
    text-shadow: 0 0 40px #5EB4E7;
}
.bounceInRight {
    animation: bounceInRight 1s;
}
@keyframes bounceRight {
    0%, 20%, 50%, 80%, 100% {
        transform: translateX(0);
    }
    40% {
        transform: translateX(-10px);
    }
    60% {
        transform: translateX(-5px);
    }
}
@keyframes bounceDown {
    0%, 20%, 50%, 80%, 100% {
        transform: translateY(0);
    }
    40% {
        transform: translateY(-10px);
    }
    60% {
        transform: translateY(-5px);
    }
}
@keyframes bounceInRight {
    0% {
        opacity: 0;
        transform: translateX(2000px);
    }
    60% {
        opacity: 1;
        transform: translateX(-30px);
    }
    80% {
        transform: translateX(10px);
    }
    100% {
        transform: translateX(0);
    }
}
@keyframes flipInY {
    0% {
        transform: perspective(400px) rotateY(90deg);
        opacity: 0;
    }
    40% {
        transform: perspective(400px) rotateY(-10deg);
    }
    70% {
        transform: perspective(400px) rotateY(10deg);
    }
    100% {
        transform: perspective(400px) rotateY(0deg);
        opacity: 1;
    }
}
@keyframes fadeIn {
    from {
        opacity: 0;
    }
    to {
        opacity: 1
    }
}
@keyframes scaleIn {
    0% {
        transform: scale(0);
    }
    80% {
        transform: scale(1.05);
    }
    100% {
        transform: scale(1);
    }
}
<link rel="stylesheet" href="style/style.css">
<script src="js/mine-parse.js"></script>
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div id="mainContainer">
  <h1> <i class="fa fa-code"></i> MineParse </h1>
  <div id="inputContainer">
      <div class="containerLabel">
          <i class="fa fa-pencil-square"></i>
          Input
      </div>
      <textarea id="input">
§nMinecraft Formatting

§r§00 §11 §22 §33
§44 §55 §66 §77
§88 §99 §aa §bb
§cc §dd §ee §ff
    
§r§0k §kMinecraft
§rl §lMinecraft
§rm §mMinecraft
§rn §nMinecraft
§ro §oMinecraft
§rr §rMinecraft
      </textarea>
      <button id="parse">
          <i class="fa fa-angle-right"></i>
          Parse
      </button>
  </div>
  <div id="inBetween"></div>
  <div id="outputContainer">
      <div class="containerLabel">
          <i class="fa fa-eye"></i>
          Output <span id="parseTime"></span>
      </div>
      <div id="output"></div>
  </div>
  <script>
      (function () {
          'use strict';
          var input = document.getElementById('input'),
              output = document.getElementById('output'),
              parseBtn = document.getElementById('parse'),
              parseTime = document.getElementById('parseTime'),
              outputContainer = document.getElementById('outputContainer'),
              scrollInterval;
              
          parseBtn.onclick = function () {
              var scrollY = window.pageYOffset,
                  outputY = outputContainer.offsetTop,
                  parseStart = Date.now(),
                  result = mineParse(input.value);
              parseTime.innerHTML = '~ ' + ((Date.now() - parseStart) / 1000) + ' seconds';
              clearInterval(scrollInterval);
              output.innerHTML = '';
              if (window.innerWidth < 850) {
                  scrollInterval = setInterval(function () {
                      scrollY += 5;
                      window.scrollTo(0, scrollY);
                      if (scrollY >= outputY) {
                          window.scrollTo(0, outputY);
                          clearInterval(scrollInterval);
                      }
                  }, 0);
              }
              output.appendChild(result.parsed);
          };
      }());
  </script>

Upvotes: 10

Related Questions