🎉 Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io 🎉
Avatar of glennj

glennj's solution

to D&D Character in the Tcl Track

Published at Sep 22 2020 · 0 comments
Instructions
Test suite
Solution

Note:

This exercise has changed since this solution was written.

For a game of Dungeons & Dragons, each player starts by generating a character they can play with. This character has, among other things, six abilities; strength, dexterity, constitution, intelligence, wisdom and charisma. These six abilities have scores that are determined randomly. You do this by rolling four 6-sided dice and record the sum of the largest three dice. You do this six times, once for each ability.

Your character's initial hitpoints are 10 + your character's constitution modifier. You find your character's constitution modifier by subtracting 10 from your character's constitution, divide by 2 and round down.

Write a random character generator that follows the rules above.

For example, the six throws of four dice may look like:

  • 5, 3, 1, 6: You discard the 1 and sum 5 + 3 + 6 = 14, which you assign to strength.
  • 3, 2, 5, 3: You discard the 2 and sum 3 + 5 + 3 = 11, which you assign to dexterity.
  • 1, 1, 1, 1: You discard the 1 and sum 1 + 1 + 1 = 3, which you assign to constitution.
  • 2, 1, 6, 6: You discard the 1 and sum 2 + 6 + 6 = 14, which you assign to intelligence.
  • 3, 5, 3, 4: You discard the 3 and sum 5 + 3 + 4 = 12, which you assign to wisdom.
  • 6, 6, 6, 6: You discard the 6 and sum 6 + 6 + 6 = 18, which you assign to charisma.

Because constitution is 3, the constitution modifier is -4 and the hitpoints are 6.

Notes

Most programming languages feature (pseudo-)random generators, but few programming languages are designed to roll dice. One such language is Troll.

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

Running the tests

To run the test suite, execute one of the following commands:

tclsh dnd-character.test            # Will stop testing after the first failure.
RUN_ALL=1 tclsh dnd-character.test  # Will run all tests and report all failures.

Feedback, Issues, Pull Requests

The exercism/tcl repository on GitHub is the home for all of the Tcl exercises on Exercism.

If you have feedback about an exercise, or want to help implementing a new one, head over there and create an issue. We'll do our best to help you!

Source

Simon Shine, Erik Schierboom https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945

dnd-character.test

#!/usr/bin/env tclsh
set version 1.1.0
package require tcltest
namespace import ::tcltest::*
source "dnd-character.tcl"

proc fail_fast {} {
    return [expr {
        ![info exists ::env(RUN_ALL)]
        || [string is boolean -strict $::env(RUN_ALL)]
        && !$::env(RUN_ALL)
    }]
}

proc failed {} {
    return [expr {$::tcltest::numTests(Failed) > 0}]
}

if {[fail_fast]} {
    proc test args {
        if {[failed]} {::tcltest::configure -skip *}
        uplevel [list ::tcltest::test {*}$args]
    }
}

proc cleanupTests {} {
    set failed [failed]
    uplevel 1 ::tcltest::cleanupTests
    if {$failed} {exit 1}
}

if {$::argv0 eq [info script]} {

    set cases {
        dnd-1 "ability modifier for score 3 is -4" 3 -4
        dnd-2 "ability modifier for score 4 is -3" 4 -3
        dnd-3 "ability modifier for score 5 is -3" 5 -3
        dnd-4 "ability modifier for score 6 is -2" 6 -2
        dnd-5 "ability modifier for score 7 is -2" 7 -2
        dnd-6 "ability modifier for score 8 is -1" 8 -1
        dnd-7 "ability modifier for score 9 is -1" 9 -1
        dnd-8 "ability modifier for score 10 is 0" 10 0
        dnd-9 "ability modifier for score 11 is 0" 11 0
        dnd-10 "ability modifier for score 12 is +1" 12 1
        dnd-11 "ability modifier for score 13 is +1" 13 1
        dnd-12 "ability modifier for score 14 is +2" 14 2
        dnd-13 "ability modifier for score 15 is +2" 15 2
        dnd-14 "ability modifier for score 16 is +3" 16 3
        dnd-15 "ability modifier for score 17 is +3" 17 3
        dnd-16 "ability modifier for score 18 is +4" 18 4
    }

    foreach {name description score modifier} $cases {
        test $name $description -body {
            dnd modifier $score
        } -returnCodes ok -result $modifier
    }

    test dnd-17 "random ability is within range" -body {
        set result true
        for {set i 0} {$i < 10000} {incr i} {
            set a [dnd ability]
            if {$a < 3 || $score > 18} {
                set result false
                break
            }
        }
        set result
    } -returnCodes ok -result true

    test dnd-18 "a character has the correct attributes" -body {
        set character [dnd character]
        lsort [dict keys $character]
    } -returnCodes ok -result {charisma constitution dexterity hitpoints intelligence strength wisdom}

    test dnd-19 "characteristics have the correct values" -body {
        set character [dnd character]
        dict with character {
            expr {
                3 <= $charisma && $charisma <= 18 &&
                3 <= $constitution && $constitution <= 18 &&
                3 <= $dexterity && $dexterity <= 18 &&
                3 <= $intelligence && $intelligence <= 18 &&
                3 <= $strength && $strength <= 18 &&
                3 <= $wisdom && $wisdom <= 18 &&
                $hitpoints == 10 + [dnd modifier $constitution]
            }
        }
    } -returnCodes ok -result 1

    cleanupTests
}
namespace eval dnd {
    namespace export character ability modifier
    namespace ensemble create

    variable characteristics {
        strength
        dexterity
        constitution
        intelligence
        wisdom
        charisma
    }

    proc modifier {score} {
        # floor() rounds towards -Inf
        expr {int(floor(($score - 10) / 2.0))}
    }

    proc d6 {} {
        expr {1 + int(6 * rand())}
    }

    proc ability {} {
        set dice [list [d6] [d6] [d6] [d6]]
        set sum [::tcl::mathop::+ {*}$dice]
        set min [::tcl::mathfunc::min {*}$dice]
        expr {$sum - $min}
    }

    proc hitpoints {constitution} {
        expr {10 + [modifier $constitution]}
    }

    proc character {} {
        variable characteristics
        set character [dict create]
        foreach c $characteristics {
            dict set character $c [ability]
        }
        dict set character hitpoints \
            [hitpoints [dict get $character constitution]]
        return $character
    }
}

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?