Minecrafting

So, I’ve been having a great deal of fun playing Minecraft with my friends lately. I’m hosting a server on the Ubuntu 12.04 box in my basement that doubles as my media center, and it works pretty well supporting 6 or 7 simultaneous players.

One of my favorite things about minecraft is the high detail that the log files contain: when players join, when they leave, number and causes of player deaths. Being the data-driven type that I am, I’ve been writing some quick python scripts to combine the log files and strip out extraneous information (MC_LogParse.py), make a nice csv file of the deaths of each player (MC_DeathLog.py), and add up total time played for each player (MC_Playtime.py). I thought I’d share them below, for anyone interested– all should be compatible with stock Minecraft 1.7.4. They work on Ubuntu and OSX, at least. I also included the list of terms I strip out of log files (log_terms.txt).

MC_LogParse.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/env python
# Minecraft Log File Parser
# v 0.1
# Copyright 2014, Andrew J. Bonham
 
#Imports
import os
import argparse
import glob
from subprocess import Popen, PIPE
 
#Set up a command line parser to help use the script effectively
parser = argparse.ArgumentParser(description='Process Minecraft Log Files')
parser.add_argument("directory", help="select a directory of logfiles to process")
parser.add_argument("logterms", help="text file with a list of terms to exclude from the logs (one term per line)")
parser.add_argument("output", help="specify an output file")
args = parser.parse_args()
 
#Grab the list of log files and sort by date
os.chdir(args.directory)
files = glob.glob('*.log.gz')
files.sort()
bigLog = []
 
#File Processing - read each file and remove unnecessary lines ("Served Saved", etc)
for each in files:
   bigLog.append(str(each))
   process=Popen(['zcat', each],stdout=PIPE)
   process2=Popen(['grep','-i','-v','-f',str(args.logterms)],stdin=process.stdout,stdout=PIPE)
   out2,err2 = process2.communicate()
   bigLog.append(out2)
 
#Write it all to a final log file
logfile = open(args.output,'w')
for item in bigLog:
   logfile.write("{}\n".format(item))
logfile.close()

MC_DeathLog.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/usr/bin/env python
# Add up deaths in Minecraft Logs
# v 0.1
# Copyright 2014, Andrew J. Bonham
 
#Imports and constants
import re
import argparse
import csv
 
#Parse command line
parser = argparse.ArgumentParser()
parser.add_argument("logfile", help="specify a logfile to process")
parser.add_argument("output", help="specify an output file")
args = parser.parse_args()
 
#Read the logfile into a list of lines
theLog = []
fileObject = open(args.logfile,'r')
for line in fileObject:
    theLog.append(line)
fileObject.close()
 
#Setup an output list
outputDict = {}
 
#Player List  
#TODO: Extract names with a regex \w+ search
nameList = ['mrwynd','Paradoxdruid',
            'duv67','BoulderMeg','chrreev','Moakalot','Siruial','tetriseyes',
            'wolpoff','Davide303','Ryujinamida','SusaBee']
 
#Have a set of death messages to match
deathMsg= ["was squashed by a falling anvil","was pricked to death",
            "walked into a cactus whilst trying to escape",
            "was shot by arrow",
            "drowned",
            "blew up",
            "was blown up by",
            "hit the ground too hard",
            "fell from a high place",
            "fell off a ladder",
            "fell off some vines",
            "fell out of the water",
            "fell into a patch of fire",
            "fell into a patch of cacti",
            "was doomed to fall",
            "was shot off some vines by",
            "was shot off a ladder by",
            "was blown from a high place by",
            "went up in flames",
            "burned to death",
            "was burnt to a crisp whilst fighting",
            "walked into a fire whilst fighting",
            "was slain by",
            "was shot by",
            "was fireballed by",
            "was killed by",
            "got finished off by",
            "was slain by",
            "tried to swim in lava",
            "died",
            "got finished off by",
            "was slain by",
            "was shot by",
            "was killed by",
            "was killed by magic",
            "starved to death",
            "suffocated in a wall",
            "was killed while trying to hurt",
            "fell out of the world",
            "fell from a high place and fell out of the world",
            "was knocked into the void by",
            "withered away"]
 
#Add names to the Dictionary
for name in nameList:
    outputDict[name] = []
 
#Process the logfile looking for death events
for line in theLog:
    for message in deathMsg:
        diedOnServer = re.search(message,line)
        if diedOnServer:
            for name in nameList:
                playerName = re.search(name,line)
                if playerName:
                    chatter = re.search(r".*\< .*",line)
                    if chatter:  #if people chatted using the keywords, exclude it
                        pass
                    else:
                        cause = re.search(r"\[(.*)\] \[Server thread/INFO\]: \w+ (.*)$",line)
                        if cause.group(2):
                            outputDict[name].append(cause.group(2))
 
#Tally Dictionary
tallyDict = {}
for key, value in outputDict.items():
    if not key in tallyDict.keys():
        tallyDict[key] = {}
    else:
        pass
    for item in value:
        if not item in tallyDict[key].keys():
            tallyDict[key][item] = 0
        tallyDict[key][item] += 1
 
#Write the output to a CSV file
with open(args.output,'wb') as csvfile:
    writer = csv.writer(csvfile)
    for key, value in tallyDict.items():
        writer.writerow([key])
        for key_, value_ in value.items():
            writer.writerow([key_,value_])

MC_Playtime.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#!/usr/bin/env python
# Add up playtime in Minecraft Logs
# v 0.1
# Copyright 2014, Andrew J. Bonham
 
#Imports and constants
import time
import re
import argparse
import csv
import datetime
 
#Parse command line
parser = argparse.ArgumentParser()
parser.add_argument("logfile", help="specify a logfile to process")
parser.add_argument("output", help="specify an output file")
args = parser.parse_args()
 
#Read the logfile into a list of lines
theLog = []
fileObject = open(args.logfile,'r')
for line in fileObject:
    theLog.append(line)
fileObject.close()
 
#Setup an output list
outputDict = {}
 
#Player list
#TODO: Extract automagically
nameList = ['mrwynd','Paradoxdruid',
            'duv67','BoulderMeg','chrreev','Moakalot','Siruial','tetriseyes',
            'wolpoff','Davide303','Ryujinamida','SusaBee']
 
date_pattern = '%H:%M:%S %Y-%m-%d'
 
#Add names to the Dictionary
for name in nameList:
    outputDict[name] = 0
 
#Process the logfile looking for join and leave events
for line in theLog:
 
    #find each log file start
    logstart = re.search(r"^(201\d-\d+-\d+).*",line)
    if logstart:
        start_time = logstart.group(1)
        continue
 
    #Find player joined notifications
    joinedServer = re.search(r"\[(.*)\] \[Server thread/INFO\]: (.*) joined the game",line)
    if joinedServer:
        full_start = str(joinedServer.group(1))+" "+str(start_time)
        log_start_time = int(time.mktime(time.strptime(full_start,date_pattern)))
 
        outputDict[joinedServer.group(2)] -= log_start_time
        continue
 
    #Find player left notifications
    leftServer = re.search(r"\[(.*)\] \[Server thread/INFO\]: (.*) left the game",line)
    if leftServer:
        full_stop = str(leftServer.group(1))+" "+str(start_time)
        log_stop_time = int(time.mktime(time.strptime(full_stop,date_pattern)))
 
        outputDict[leftServer.group(2)] += log_stop_time
        continue
 
#Convert seconds into human readable format
for key,value in outputDict.items():
    outputDict[key] = str(datetime.timedelta(seconds=value))
 
#Write the output to a CSV file
with open(args.output,'wb') as csvfile:
    writer = csv.writer(csvfile)
    for key, value in outputDict.items():
        writer.writerow([key])
        writer.writerow([value])

Log_Terms.txt

1
2
3
4
5
6
7
8
9
saved
saving
online
given
uuid
connection
overloaded
backup
game mode

Leave a Response (or trackback on your own site)

You must be logged in to post a comment.

Welcome

Welcome to Paradoxdruid's Rants... a community based webblog. Feel free to snag an account and post.

Contributors Login

Linkdump

My first first-author paper!

Just wanted to share that my first first-author paper is now online! In the journal Stem Cells and Development, here’s my paper on “Roles of Integrins in Human Induced Pluripotent Stem Cell Growth on Matrigel and Vitronectin.”


The Future of Scientific Publishing

Just read a fascinating (if lengthy) essay on disruptive technology and the future of scientific publishing. Well worth the read!


Deflation!

Just wanted to share Mint.com’s Visual Guide to Deflation, which is quite explanatory.


All Things Stem Cell

Hey all Paradoxdruid readers! I recently started up a blog on stem cells that I’d love you all to take a look at: http://www.allthingsstemcell.com/


Barely Literate: The Fermata

I participated in another Barely literate book review podcast, this time on Nicholson Baker’s “The Fermata”. Give it a listen!


Time for Change

Obama has outlined a strategy for America, in great depth. Read all about Change.gov!


Free Rice

Okay, I’ll admit that it’s entirely possible that I am the last person to learn about this website*, but it’s really addictive. 
(continued)


About

Site best viewed in Mozilla Firefox. Site CSS template by Andrea Pitschmann. Banner photo by photocase.