In this challenge, you need to parse morgue files from the roguelike game Dungeon Crawl Stone Soup and output it to STDOUT.

What are these morgue files??

When you die, a text file is generated with that character's data inside. You can see what equipment the character had, what happened during the last few turns, and how many monsters he has killed.

You can find a example morgue file here

The challenge

Your job is to make a program that takes one of those files from STDIN, parses it, and outputs the data to STDOUT.

To make this challenge a bit easier, you only have to parse the first block of text. (up until The game lasted <time> (<turns> turns).

You need to parse and output the following information:

  • The version number.
  • The score.
  • The character name, title, race and class.
  • The character level.
  • The cause of death/victory.
  • The amount of turns the run lasted.


Dungeon Crawl Stone Soup version <version number> character file.

<score> <name> the <title> (level <level>, 224/224 HPs)
         Began as a <race> <class> on Mar 16, 2015.
         Was the Champion of the Shining One.
         <cause of death/victory>

         The game lasted 16:11:01 (<turns> turns).

Test Cases

Test case 1 - Victory

Input file

Example output - Victory:

Version: 0.16.0-8-gd9ae3a8 (webtiles)
Score: 16059087
Name: Ryuzilla the Conqueror
Character: Gargoyle Berserker
Level: 27
Cause of Death/Victory: Escaped with the Orb and 15 runes on Mar 17 2015!
Turns: 97605

Test case 2 - Death

Input file

Example output - Death:

Version: 0.16-a0-3667-g690a316 (webtiles)
Score: 462
Name: 8Escape the Ruffian
Character: Bearkin Transmuter
Level: 6
Cause of Death/Victory: Slain by an orc wielding a +0 trident (3 damage) on level 4 of the Dungeon.
Turns: 3698


  • This is so the shortest code wins.
  • In the event of a tie, the oldest answer wins.
  • No standard loopholes.
  • File input must be taken from STDIN
  • Output must be sent to STDOUT
  • The labels before the output (ex. Turns:) are optional.

Ungolfed sample code for inspiration

Morgue file generation code in DCSS

  • \$\begingroup\$ Does the output actually need to contain the line labels like Version: or is enough to output the pieces of information in the same order, one per line? \$\endgroup\$ Nov 16, 2015 at 10:12
  • \$\begingroup\$ @MartinBüttner The labels are optional. \$\endgroup\$ Nov 16, 2015 at 10:13
  • \$\begingroup\$ Will the race and class always be one word each? \$\endgroup\$ Nov 16, 2015 at 10:20
  • \$\begingroup\$ @MartinBüttner Some races and classes are two words, like Vine Stalker, Abyssal Knight and Deep Elf. \$\endgroup\$ Nov 16, 2015 at 10:24
  • 2
    \$\begingroup\$ Is there a specification of this morgue file format, or just these examples? \$\endgroup\$ Nov 16, 2015 at 19:17

5 Answers 5


Perl, 151 bytes

148 code + 3 switches (-0, -l, -p). I'm sure this can be improved :)

Takes input from STDIN and prints the result on receiving EOF.

perl -lp0e 's/\.{3}|\s/ /g;y/ //s;$_=join$\,(/(\d.*?).{15}\..(\d+).(.+?).\(.+?(\d+).+?\b(?:a|an) (.+?) o.+? ([^.!]+[.!])[^.!]*?(\d+)[^(]+\)..\3/)[0..2,4,3,5..7]'


use strict;
use warnings;

# set the input record separator to undef (the -0 switch)
# read the text (the -l switch)

# replace all '...' and spaces by a ' '
s/\.{3}|\s/ /g;
# squeeze all contiguous spaces into a single space
y/ //s;
# collect the captured groups into @p
my @p=
/(\d.*?).{15}\..      # version is the first string starting with a digit and ending 15 characters before the period
 (\d+).               # points is the next string with only digits
 (.+?).\(.+?          # name starts after a gap of one character
 (\d+).+?\b(?:a|an)\s # level is inside the next open paranthesis
 (.+?)\so.+?\s        # race, class occur after the 'a' or 'an' and end before ' o' i.e. (' on')
 ([^.!]+[.!])[^.!]*?  # cause of death is the a sentence ending with '.' or '!'
 (\d+)[^(]+\)..\3     # turns is the next sentence with digits within parantheses, followed by 2 characters and the player's name
$_=join"\n",@p[0..2,4,3,5..7]; # the level and race lines need to be swapped

# print the output (the -p switch)
print $_;



F#, 377 bytes

open System.Text.RegularExpressions
let s=System.String.IsNullOrWhiteSpace>>not
let m f=Regex.Match((f+"").Split[|'\r';'\n'|]|>Seq.filter s|>Seq.take 8|>Seq.reduce(fun a z->a+z.Trim()), ".*n (.*) c.*\.([0-9]+) (.*) \(l.* (.*),.*a (.*) o.*\.(?:(S.*)|W.*(E.*)).*.T.*\((.*) .*\).").Groups|>Seq.cast<Group>|>Seq.skip 1|>Seq.map(fun z ->z.Value)|>Seq.filter s|>Seq.iter(printfn"%s")

Javascript (ES6), 297 230 bytes

For now, this is a test-driven regular expression.

It simply replaces the unwanted info and keeps the important stuff.

It creates an anonymous function that simply returns the wanted text.

_=>_.replace(/^.+version(.*) character file\.([\n\r]+)(\d+)([^\(]+) \([^\d]+( \d+),.+\n\s+.+as a(.+) on.+\n\s+(?:Was.+One\.\n)?((?:.|\n)+[!.])\n(?:.|\n)+\((\d+)(?:.|\n)+$/,'$1\n$3\n‌​$4\n$6\n$5\n$7\n$8').replace(/\s+(\.{3} ?)?/,' ')

Isn't it a beast?

Thanks for sysreq's tip about the labels being optional. That saved me 67 bytes!

You can test the resulgar expression on: https://regex101.com/r/zY0sQ0/1

  • \$\begingroup\$ The labels are optional; you can save quite a few bytes by omitting them. \$\endgroup\$
    – cat
    Nov 21, 2015 at 15:53
  • 1
    \$\begingroup\$ @sysreq The what...? \$\endgroup\$ Nov 21, 2015 at 15:53
  • \$\begingroup\$ codegolf.stackexchange.com/questions/64033/… \$\endgroup\$
    – cat
    Nov 21, 2015 at 21:50
  • 2
    \$\begingroup\$ I'm saying _=>_.replace(/^.+version(.*) character file\.([\n\r]+)(\d+)([^\(]+) \([^\d]+( \d+),.+\n\s+.+as a(.+) on.+\n\s+(?:Was.+One\.\n)?((?:.|\n)+[!.])\n(?:.|\n)+\((\d+)(?:.|\n)+$/,'$1\n$3\n$4\n$6\n$5\n$7\n$8').replace(/\s+(\.{3} ?)?/,' ') is an acceptable solution at just 230 bytes \$\endgroup\$
    – cat
    Nov 23, 2015 at 3:00
  • 1
    \$\begingroup\$ @sysreq Sorry for taking so long to say anything. I've been seeing the post but I was on a tablet. You have no idea how painful it is to do anything in a tablet. I've replaced my code with your label-less version. Thanks a lot for the tip. \$\endgroup\$ Nov 23, 2015 at 9:08

Python3, 472 bytes

I thought I'd be able to get this far shorter. Not surprised I beat my own submission, though. Run it like python3 dcss.py morgue-file.txt.

import sys
s=" "
for i in m(a(f)):
 for x in m(a(f[i])):
def r(j,u):
 while u in j:
def l(x):
 for i in m(a(x)):
 return c.strip()

Go, 589 502 489 487 bytes

package main;import(."fmt";."io/ioutil";"os";."strings");func d(z,ch string)string{return Map(func(r rune)rune{if IndexRune(ch,r)<0{return r};return -1},z)};func main(){x:=Split;f,_:=ReadFile(os.Args[1]);n:="\n";l:=" ";m:=",";h:=".";q:=x(string(f),n)[:11];k:=x(q[0],l);y:=x(q[10],l);u:=x(q[2],l);g:="";for _,e:=range Fields(d(q[4],n+h)+l+d(q[5],n+h)+l+d(q[6],n+h)+l+d(q[7],n+h)){g=g+e+l};Print(k[6]+l+k[7]+n+u[0]+n+y[0]+l+y[1]+l+y[2]+n+d(y[3]+l+y[4],"()")+n+d(u[5],m)+n+g+n+d(y[17],m))}

after running go fmt, go fix, and go vet here is the "ungolfed" version:

package main

import (
    . "fmt"
    . "io/ioutil"
    . "strings"

func d(z, ch string) string {
    return Map(func(r rune) rune {
        if IndexRune(ch, r) < 0 {
            return r
        return -1
    }, z)
func main() {
    x := Split
    f, _ := ReadFile(os.Args[1])
    n := "\n"
    l := " "
    m := ","
    h := "."
    q := x(string(f), n)[:11]
    k := x(q[0], l)
    y := x(q[10], l)
    u := x(q[2], l)
    g := ""
    for _, e := range Fields(d(q[4], n+h) + l + d(q[5], n+h) + l + d(q[6], n+h) + l + d(q[7], n+h)) {
        g = g + e + l
    Print(k[6] + l + k[7] + n + u[0] + n + y[0] + l + y[1] + l + y[2] + n + d(y[3]+l+y[4], "()") + n + d(u[5], m) + n + g + n + d(y[17], m))

Edit: using dot-imports helps a lot.

Pretty self-explanatory but I can explain if need be. This is my first 'real' Go program and I'm still a beginner at codegolf so tips are welcome!

Edit: you said "take a file from STDIN", and you can run this script (if you have go installed) by running go install <foldername> and then <binaryname> morgue-file.txt or go run main.go morgue.txt


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.