[#2] Winners of Skript Challenge - Numbers to Words

  • Welcome to skUnity!

    Welcome to skUnity! This is a forum where members of the Skript community can communicate and interact. Skript Resource Creators can post their Resources for all to see and use.

    If you haven't done so already, feel free to join our official Discord server to expand your level of interaction with the comminuty!

    Now, what are you waiting for? Join the community now!


I'm Poppy
Staff member
skUnity Legend
Nov 27, 2016
Discord Username
Hi everyone,

After remembering yesterday, then forgetting and remembering now, here are the winners of the second skUnity Skript Challenge! There weren't many entries this time (to be honest, it was slightly more complex), but there were some amazing ones anyway! I've also got the medal system up and running, and have created a medal for the people who win a Skript Challenge! The 3 winners from #1 were award their medals earlier.

Anyway, without further ado, here are the winners:

In first place is @SwiKZiiK - a French Skripter who created the shortest way of doing it, while not using any loops, addons or splitting!
    set {_num} to "Put your number here."
    length of {_num} <= 2:
        return {_num}
    else if length of {_num} = 3:
        return "%first character of {_num}% hundred and %last 2 characters of {_num}%"
    else if length of {_num} <= 6:
        return "%first (length of {_num} - 3) characters of {_num}% thousand, %subtext of {_num} from (length of {_num} - 2) and (length of {_num} - 2)% hundred and %last 2 characters of {_num}%"
    else if length of {_num} <= 9:
        return "%first (length of {_num} - 6) characters of {_num}% million, %subtext of {_num} from (length of {_num} - 5) and (length of {_num} - 3)% thousand, %subtext of {_num} from (length of {_num} - 2) and (length of {_num} - 2)% hundred and %last 2 characters of {_num}%"
(while I'm not looking for the shortest, it amazed me and many others on how this one was so simple for the challenge).

In join-second place is @Rezz - a beautiful and well crafted solution to the challenge. Massively extensive and covers a wide range of features.
    TABLE: number-to-name
    ERROR: [Error]

# Define all masks in a string with pattern:name pairs.

function defineMasksIn(values: text):

    set {_pairs::*} to {_values} split at " "
    loop {_pairs::*}:
        set {_pair::*} to loop-value split at ":"
        set {{@TABLE}::%{_pair::1}%} to {_pair::2}
on script load:

    defineMasksIn("1:one 2:two 3:three 4:four 5:five 6:six 7:seven 8:eight 9:nine")
    defineMasksIn("10:ten 11:eleven 12:twelve 13:thirteen 14:fourteen 15:fifteen 16:sixteen 17:seventeen 18:eighteen 19:nineteen")
    defineMasksIn("2.:twenty 3.:thirty 4.:forty 5.:fifty 6.:sixty 7.:seventy 8.:eighty 9.:ninety")
    defineMasksIn(".__:hundred ...___:thousand ...______:million ..._________:billion ...____________:trillion ..._______________:quadrillion")
    # Number names from: https://en.wikipedia.org/wiki/Names_of_large_numbers
    defineMasksIn("...__________________:quintillion ..._____________________:sextillion ...________________________:septillion")
    defineMasksIn("...___________________________:octillion ...______________________________:nonillion ..._________________________________:decillion")
    defineMasksIn("...____________________________________:undecillion ..._______________________________________:duodecillion")
    defineMasksIn("...__________________________________________:tredecillion ..._____________________________________________:quattuordecillion")
    defineMasksIn("...________________________________________________:quindecillion ...___________________________________________________:sexdecillion")
on script unload:

    delete {{@TABLE}::*}
# Returns the matching mask for a string of digits or an error if nothing is found.
function findMaskFor(number: text) :: text:

    set {_length} to length of {_number}
    set {_digits::*} to {_number} split at ""
    loop {{@TABLE}::*}:
        if {_skip} is set:
            delete {_skip}
        set {_mask} to loop-index
        set {_mask-length} to length of {_mask}
        set {_minimum-length} to {_mask-length}
        set {_chars::*} to {_mask} split at ""
        loop {_chars::*}:
            {_skip} is not set
            set {_char} to loop-value-2
            set {_digit} to {_digits::%loop-index-2%}
            if {_char} is ".":
                set {_minimum-length} to {_minimum-length} - 1
                if {_minimum-length} is greater than {_length}:
                    set {_skip} to true
                else if {_char} is not {_digit} or "_":
                    set {_skip} to true
        {_skip} is not set
        if {_mask-length} is {_length}:
            return {_mask}
        else if {_length} is less than {_mask-length}:
            if {_length} is greater than {_minimum-length}:
                return {_mask}
    return "{@ERROR} Unsupported value: '%{_number}%' with length %{_length}%"

# Determine whether a string starts with another.

function startsWith(start-text: text, full-text: text) :: boolean:

    if the first (length of {_start-text}) characters of {_full-text} is {_start-text}:
        return true
    return false

# Determine whether a string ends with another.

function endsWith(end-text: text, full-text: text) :: boolean:

    if the last (length of {_end-text}) characters of {_full-text} is {_end-text}:
        return true
    return false

# Insert a string into another at a specified index.

function insert(insertion: text, index: number, text: text) :: text:

    set {_left} to the first {_index} characters of {_text}
    set {_right} to the last (length of {_text} - {_index}) characters of {_text}
    return "%{_left}%%{_insertion}%%{_right}%"
# Count the occurrences of a character in a string.
function occurrencesOf(char: text, in: text) :: number:

    set {_count} to 0
    set {_characters::*} to {_in} split at ""
    loop {_characters::*}:
        if "%loop-value%" is {_char}:
            add 1 to {_count}
    return {_count}
# Strip all leading zeroes from a string of digits.
function stripLeadingZeroes(number: text) :: text:

    while startsWith("0", {_number}) is true:
        if length of {_number} is 1:
            set {_number} to ""
            set {_number} to the last (length of {_number} - 1) characters of {_number}

    return {_number}
# Returns the full name of all digits in a string.
function nameOfDigits(number: text) :: text:

    if {_number} is "0":
        return "zero"

    set {_number} to stripLeadingZeroes({_number})
    set {_length} to length of {_number}
    if {_length} is 0:
        return ""
    set {_mask} to findMaskFor({_number})
    if startsWith("{@ERROR}", {_mask}) is true:
        return {_mask}
    set {_mask-length} to length of {_mask}
    set {_mask-cutoff} to {_mask-length} - {_length}
    set {_active-mask} to the last ({_mask-length} - {_mask-cutoff}) characters of {_mask}
    if startsWith(".", {_active-mask}) is true:
        set {_left-side-components} to occurrencesOf(".", {_active-mask})
        set {_split-number::*} to insert(":", {_left-side-components}, {_number}) split at ":"
        set {_left} to nameOfDigits({_split-number::1})   
        set {_right} to nameOfDigits({_split-number::2})   
        set {_name} to {{@TABLE}::%{_mask}%}
        if {_right} is "zero" or "":
            return "%{_left}% %{_name}%"
        else if {_name} is "hundred":
            return "%{_left}% %{_name}% %{_right}%"
            return "%{_left}% %{_name}%, %{_right}%"
    else if endsWith(".", {_active-mask}) is true:
        set {_left} to {{@TABLE}::%{_mask}%}
        set {_digit} to the last character of {_number}
        set {_right} to nameOfDigits({_digit})
        if {_right} is "zero" or "":
            return "%{_left}%"
            return "%{_left}%-%{_right}%"
        return {{@TABLE}::%{_mask}%}
# Converts an integer to a string and returns the result of nameOfDigits()

function nameOfNumber(number: integer) :: text:

    return nameOfDigits("%{_number}%")

command /nameof <integer>:
        set {_result} to nameOfNumber(arg-1)
        send "%arg-1%: &a%{_result}%"
# A function to ensure a string only contains numeric characters.

function validateDigitsOf(text: text) :: boolean:

    set {_chars::*} to {_text} split at ""
    loop {_chars::*}:
        if "0123456789" doesn't contain "%loop-value%":
            return false
    return true
command /nameofdigits <text>:
        if validateDigitsOf(arg-1) is true:
            set {_result} to nameOfDigits(arg-1)
            send "%arg-1%: &a%{_result}%"
            send "&cThe only characters allowed are: 0-9"

In joint-second place is @Tlatoani - his answer is also very extensive and even converts the numbers themselves into words: 2009 -> two thousand and nine

on load:
  set {small numbers::*} to "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
  set {multiples of ten::*} to "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
  set {powers of thousand::*} to "thousand", "million", "billion", "trillion", "quadrillion", "quintillion"

function modulus(num: number, by: number) :: number:
  return {_num} - {_by} * rounded down {_num} / {_by}

function smallNumToEnglish(num: number) :: string:
  if {_num} < 20:
    return {small numbers::%{_num}%}
  if {_num} < 100:
    set {_tens} to rounded down {_num}/10
    set {_result} to {multiples of ten::%{_tens}%}
    if modulus({_num}, 10) > 0:
      set {_result} to "%{_result}%-%smallNumToEnglish(modulus({_num}, 10))%"
    return {_result}
  set {_result} to "%smallNumToEnglish(rounded down {_num}/100)% hundred"
  if modulus({_num}, 100) > 0:
    set {_result} to "%{_result}% and %smallNumToEnglish(modulus({_num}, 100))%"
  return {_result}

function numToEnglish(num: number) :: string:
  if {_num} is 0:
    return "zero"
  set {_power of thousand} to 0
  while {_num} > 0:
    if modulus({_num}, 1000) > 0:
      set {_number name} to {powers of thousand::%{_power of thousand}%}
      set {_result} to "%smallNumToEnglish(modulus({_num}, 1000))% %{_number name}%, %{_result}%"
    set {_num} to rounded down {_num} / 1000
    add 1 to {_power of thousand}
  if {_result} contains ">,":
    return first length of {_result} - 15 characters of {_result} #Removes the extra " <none>, <none>" at the end of the string
    return first length of {_result} - 8 characters of {_result} #Removes the extra ", <none>" at the end of the string

Also in joint-second place is @xXAndrew28Xx - another amazing submission which deserves to be appreciated.
function modulo(dividend: integer, divisor: integer) :: number:
  set {_quotient} to {_dividend} / {_divisor}
  set {_floor} to floor({_quotient})
  return {_dividend} - ({_floor} * {_divisor})
function wholeDivision(dividend: integer, divisor: integer) :: number:
  set {_quotient} to {_dividend} / {_divisor}
  set {_floor} to floor({_quotient})
  return {_floor}
function expandedForm(number: integer) :: text:
  #Letter Prefixes so it loops in the correct order
  set {_format::a-quadrillion} to 1000000000000000
  set {_format::b-trillion} to 1000000000000
  set {_format::c-billion} to 1000000000
  set {_format::d-million} to 1000000
  set {_format::e-thousand} to 1000
  set {_format::f-hundred} to 100
  while {_number} is not 0:
    if {_number} < 100:
      #set {_lowerNumberFormats::*} to "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", and "nineteen"
      #set {_tensFormats::*} to "twenty", "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", and "ninty"
      #if {_number} < 20:
      #  set {_numberFormatted} to {_lowerNumberFormats::%{_number}%}
      #else if {_number} is 100:
      #  set {_numberFormatted} to "one hundred"
      #  set {_tensFormatsIndex} to (wholeDivision({_number}, and 10)) - 1
      #  set {_numberFormatted} to {_tensFormats::%{_tensFormatsIndex}%}
      #  if modulo({_number},10) is not 0:
      #    set {_digitNumber} to modulo({_number}, and 10)
      #    set {_digitNumberFormatted} to {_lowerNumberFormats::%{_digitNumber}%}
      #    set {_numberFormatted} to "%{_numberFormatted}% %{_digitNumberFormatted}%"
      #broadcast "ADDED %{_numberFormatted}%"
      set {_numberFormatted} to "%{_number}%"
      add {_numberFormatted} to {_values::*}
      exit loop
    loop {_format::*}:
      set {_place} to loop-value
      #EVALUATE because {_whole} isn't changing after the first time without it
      evaluate "set {_whole} to wholeDivision(%{_number}%, and %{_place}%)"
      if {_whole} > 0:
        if {_place} is 100:
          if {_whole} > 9:
            exit 3 sections
        set {_number} to {_number} - ({_whole} * {_place})
        set {_placeName} to loop-index
        set {_placeName} to subtext of {_placeName} from characters 3 to length of {_placeName}
        if {_whole} is not 0:
          set {_formattedWhole} to {_whole} #Will update later
          add "%{_formattedWhole}% %{_placeName}%" to {_values::*}
  return  "%{_values::*}%"

I won't normally have 3 joint-seconds or anything like that, but the answers were all amazing but similar in idea, that they all deserved it.

In third place is @Getreidemonster - a submission so beautiful that I cried too much reading it to be able to put it any higher. It had the most winner ratings and even solves the issue of world hunger.
command /SkriptChallenge:
        send "§oDo you really though that I'm posting here the task? ;)"
        send "§a§oOkay, let's start!"
        shutdown server
(This is a joke winner, kinda. This probably won't ever happen again, so please don't try and make jokes to win, this is just a really good first one to ever happen).

A huge thanks to everyone who took part in the second skUnity Skript Challenge! I hope to set up some more soon.

And I also hope you all enjoyed this Skript challenge! If you have an idea for the next one, PM me and I'll see what I can do.