MineCraft config editor part 2

Voice Over     And now for the very first time on the silver screen comes the film from two books which once shocked a generation. From Emily Brontë’s ‘Wuthering Heights’ and from the ‘International Guide to Semaphore Code’. Twentieth Century Vole presents ‘The Semaphore Version of Wuthering Heights’.
CAPTION: ‘THE SEMAPHORE VERSION OF WUTHERING HEIGHTS’

In our last tutorial I left you with some homework to produce a docsting describing the steps we need to do in order to get our MineCraft config editor up and running.

This is what I came up with:

'''Minecraft config editor:
This is an editor for the Minecraft server.properties file.  It:
* opens the file server.properties
* reads, then closes the file
* parses each line by
-- stripping leading and trailing whitespace
-- if the line starts with "#", marks it as a comment
-- splits the line into a key, value pair, with the pair separated by a "=" sign
-- if the value of the pair is either "true" or "false", the entry is marked as a boolean (ie its only values are either true or false)
* displays each key, value entry on the screen allowing you to edit it
* renames the server.properties file to server.properties.bup (overwriting any existing file of that name from earlier edits)
* opens a new file called server.properties
* writes each of the entries to that new file
* closes the server.properties file.
'''

Did you get something like that?  This is our script for writing the program. Most of it we can already do.  In fact, the only thing we can’t do at the moment is ” displays each key, value entry on the screen allowing you to edit it”.  Hey, notice anything different about that listing?  I’ve found WordPress’s special tags for source code.  If you run your mouse over it, some widgets will pop up so you can copy and paste the code.

The other part of your homework was to save the contents of the listing in the previous tute to  a file called “server.properties”.  You need to do that in order to run this tute. Post a comment if you have problems.

Before we dive into reading data from the file we need to think about how we will store the data that we read.  From our docstring we can tell that we might need to store a property key, a property value, a comment and whether or not it’s a property which is only true or false.  We could use a dictionary to store these but, since we’ve just found out about classes, we’re going to use a class instead.  Each instance of the class will hold one line from the configuration file.   We can use the From these we can make a class which describes the properties:

class configItem(object):  # name of the class, it is based on an object called 'object'
  def __init__(self, line):# this is called each time an instance of the class is created
    line = line.strip()  # this removes any white space at the start or end of the line
    # if it starts with a # it's a comment so check for it
    if line[:1] == "#":
       self.configKey = "#"
       self.configVal = line[1:]
    else:  # otherwise assume it's of the form x = y
       spam = line.split("=")
       self.configKey = spam[0]
       self.configVal = spam[1]
    # now check to see whether the config item takes only the values "true" and "false"
    if self.configVal.lower() in ["true","false"]:
       self.isTrueFalse = True
    else:
       self.isTrueFalse = False

So, we have our class. What we need to do is read the data from the file (this, of course, won’t work if you haven’t already saved the file).  See this tute for reading data from files.

# get data from the file

fileName = "server.properties"
fileObject = open(fileName,'rb')
fileData = fileObject.read()
fileObject.close()

Now we will create a configItem instance for each line.  However, we’ll need to keep them in something, so we make an array to do that first.

configLines = []
for line in fileData.split('\n'):  # this splits it into individual lines
    configLines.append(configItem(line))

So, our code at the moment looks like this:

class configItem(object):  # name of the class, it is based on an object called 'object'
  def __init__(self, line):# this is called each time an instance of the class is created
    line = line.strip()  # this removes any white space at the start or end of the line
    # if it starts with a # it's a comment so check for it
    if line[:1] == "#":
       self.configKey = "#"
       self.configVal = line[1:]
    else:  # otherwise assume it's of the form x = y
       spam = line.split("=")
       self.configKey = spam[0]
       self.configVal = spam[1]
    # now check to see whether the config item takes only the values "true" and "false"
    if self.configVal.lower() in ["true","false"]:
       self.isTrueFalse = True
    else:
       self.isTrueFalse = False

# get data from the file

fileName = "server.properties"
fileObject = open(fileName,'rb')
fileData = fileObject.read()
fileObject.close()

configLines = []
for line in fileData.split('\n'):  # this splits it into individual lines
    configLines.append(configItem(line))

for c in configLines:
    print "%s: %s, isTruefalse= %s"%(c.configKey, c.configVal, c.isTrueFalse)

Note: see this tute for  what the %s means.

Debugging

When I run this code on my own copy of the server.properties file I get an error:

Traceback (most recent call last):
File "serverEditor.py", line 33, in <module>
configLines.append(configItem(line))
File "serverEditor.py", line 15, in __init__
self.configVal = spam[1]
IndexError: list index out of range

I added a print statement in the class to print the line it received. It turned out that it had trouble because my file had some extra, blank lines at the end of it.  So I have added some code to skip the line if it is empty:

for line in fileData.split('\n'):  # this splits it into individual lines
    if line.strip()=='':
      continue
    configLines.append(configItem(line))

So now I get this output:

>python serverEditor.py
#: Minecraft server properties, isTruefalse= False
#: Date and time of creation of file, isTruefalse= False
allow-nether: true, isTruefalse= True
level-name: world, isTruefalse= False
enable-query: false, isTruefalse= True
allow-flight: false, isTruefalse= True
server-port: 25565, isTruefalse= False
level-type: DEFAULT, isTruefalse= False
enable-rcon: false, isTruefalse= True
level-seed: , isTruefalse= False
server-ip: , isTruefalse= False
spawn-npcs: true, isTruefalse= True
white-list: false, isTruefalse= True
spawn-animals: true, isTruefalse= True
online-mode: true, isTruefalse= True
pvp: true, isTruefalse= True
difficulty: 1, isTruefalse= False
gamemode: 0, isTruefalse= False
max-players: 20, isTruefalse= False
spawn-monsters: true, isTruefalse= True
generate-structures: true, isTruefalse= True
view-distance: 10, isTruefalse= False
motd: A Minecraft Server, isTruefalse= False

You should check that the data is all correct.  Note that if a line has a value of “true” or “false”, the corresponding item has an attribute called isTrueFalse, which is set to True. So far we have:

* created a class called configItem to describe each line in the file

* opened the file, read each line, and created an instance of the class for each line.

* when a configItem is instantiated, it parses the data which it is initialised with.

Next we will have to work out how to edit them.

Homework: think about what might go wrong with this code.

Here’s the complete code again:

'''Minecraft config editor:
This is an editor for the Minecraft server.properties file.  It:
* opens the file server.properties
* reads, then closes the file
* parses each line by
-- stripping leading and trailing whitespace
-- if the line starts with "#", marks it as a comment
-- splits the line into a key, value pair, with the pair separated by a "=" sign
-- if the value of the pair is either "true" or "false", the entry is marked as a boolean (ie its only values are either true or false)
* displays each key, value entry on the screen allowing you to edit it
* renames the server.properties file to server.properties.bup (overwriting any existing file of that name from earlier edits)
* opens a new file called server.properties
* writes each of the entries to that new file
* closes the server.properties file.
'''

class configItem(object):  # name of the class, it is based on an object called 'object'
  def __init__(self, line):# this is called each time an instance of the class is created
    line = line.strip()  # this removes any white space at the start or end of the line
    # if it starts with a # it's a comment so check for it
    if line[:1] == "#":
       self.configKey = "#"
       self.configVal = line[1:]
    else:  # otherwise assume it's of the form x = y
       spam = line.split("=")
       self.configKey = spam[0]
       self.configVal = spam[1]
    # now check to see whether the config item takes only the values "true" and "false"
    if self.configVal.lower() in ["true","false"]:
       self.isTrueFalse = True
    else:
       self.isTrueFalse = False

# get data from the file

fileName = "server.properties"
fileObject = open(fileName,'rb')
fileData = fileObject.read()
fileObject.close()

configLines = []

for line in fileData.split('\n'):  # this splits it into individual lines
    if line.strip()=='':
      continue
    configLines.append(configItem(line))

for c in configLines:
  print "%s: %s, isTruefalse= %s"%(c.configKey, c.configVal, c.isTrueFalse)

Starting our Config Editor

…And Dinsdale’s there in the conversation pit with Doug and Charles Paisley, the baby crusher, and a couple of film producers and a man they called ‘Kierkegaard’

In the last two tutorials we have been revising some things we had learned earlier about classes and GUIs.  I wanted to give you an example of where you can use Python in practice, so we are going to do a short program which will use classes and Tkinter – a configuration editor for Minecraft servers. We haven’t yet seen all of the widgets that we will need for this short project, so we will do it a little slowly.

When people write computer programs they try to write the program they don’t necessarily know all the relevant details about how and where it will be run.  It is often the case that certain data needs to be provided that program by the end user.  For example,  let’s say you wrote a greeting program which said hello to the user when they ran it.   If the end user’s name was Søren Kierkegaard, how would the program know to say “Hello Søren?” – it wouldn’t.  Søren would need to tell it.  What’s more, it would be a little tedious if Søren had to keep telling the program what his name was every time he ran the program, so, ideally, once it was entered, the program would store the data.  One place it could be stored is in a configuration file.

Minecraft is a building game.  If you run your own Minecraft server the server stores its configuration information in a file called “server.properties”.  Apparently, the default server.properties file looks like this:

#Minecraft server properties
#Date and time of creation of file
allow-nether=true
level-name=world
enable-query=false
allow-flight=false
server-port=25565
level-type=DEFAULT
enable-rcon=false
level-seed=
server-ip=
spawn-npcs=true
white-list=false
spawn-animals=true
online-mode=true
pvp=true
difficulty=1
gamemode=0
max-players=20
spawn-monsters=true
generate-structures=true
view-distance=10
motd=A Minecraft Server

When you run the Minecraft server, it reads each line in this file, comprehends the line and does something based on that comprehension.  For example, the lines beginning with “#” are comment lines, so the server ignores them.  The other lines, you’ll notice all have an “=” sign in them and look like a Python assignment statement.  They are doing something similar.  Each line has a key – value pair.  The stuff on the left of the = sign is the key, and the stuff on the right of the = sign is the value that is assigned to that key.  The last line “motd” stands for “Message of the Day”.  When someone logs onto a server with this server.properties file they will be greeted with the words “A Minecraft Server”. Changing this line will change the message that players see.

The act of comprehending data in this way is called “parsing” (more like “par-zing”, than “pass sing”).  To parse this config file would involve: identifying lines which start with “#” (they are comments) and breaking other lines into their key,value pairs.  A more advanced parser might also identify invalid configuration lines.  For example, many lines you see here have values of either “true” or “false”.  These, are, in fact, the only two possibilities for those keys.  A line like “spawn-monsters=Potato chips” would be invalid.  Equally, view-distance needs to be a whole number and so on.

While you can open up a configuration file in a text editor, what we’re going to do is write a program which allows a user to change the server.properties file by using a Tkinter GUI.  We’ll use a lot of what we have already learned.

Homework: save the extract above to a file called “server.properties” in your Python for kids directory.

Homework: write a docstring describing what we need to in order to edit a configuration file called “server.properties”.  This will be our set of instructions for writing the program.
Hint: step 1 will be to open the file, and the last step will be to close the file…

Recap part 2 – Tkinter

Well it certainly looks as though we’re in for a splendid afternoon’s sport in this the 127th Upperclass Twit of the Year Show. Well the competitors will be off in a moment so let me just identify for you. (close-up of the competitors) Vivian Smith-Smythe-Smith has an O-level in chemo-hygiene. Simon-Zinc-Trumpet-Harris, married to a very attractive table lamp.

In the last lesson, I gave you a quick refresher on classes.  In this tute, we’re going to remember what Tkinter is.  Technically, Tkinter is something that allows Python programs to use something called Tk.   According to its website, Tk is “a graphical user interface toolkit that takes developing desktop applications to a higher level than conventional approaches. Tk is the standard GUI not only for Tcl, but for many other dynamic languages, and can produce rich, native applications that run unchanged across Windows, Mac OS X, Linux and more.

In other words, Tkinter is what allows your Python programs to interact with a user through a windowing system, rather than the command line I had been forcing you to use earlier.  Tkinter is not unique, there are other toolkits that Python can use to do exactly the same thing. We are working with Tkinter here because it’s the de facto standard for Python – also because you should have Tkinter (they come together).  Tkinter’s look is pretty ugly, but we are here for practicality, not beauty.  You can research other toolkits on line if you want to have a different look for your application, but you will need to learn how Python calls those other toolkits, as it will be different.  Other toolkits may also provide additional widgets.   That said, there are a number of common elements to any GUI system that you use:

  • GUIs interact with users through areas on the user’s screen called windows;
  • windows typically have an area (such as a top and bottom bar) which are reserved for operating system related functions – such as the window’s title,  minimize, maximize, close etc.  with the balance of the space being available for use by the program;

  • the GUI uses a geometry manager to arrange widgets within the window;
  • different geometry managers can be used depending on how you want your widgets arranged.  In our examples to date we have not specified a geometry manager, so Tkinter has used a default geometry manager;
  • widgets are graphical components which are displayed in the window.  So far we have met buttons and labels, but there are many more.
  • each time a user does something in the window, an event is generated.
  • there are a whole host of things that can generate an event.  These include: moving the mouse pointer, pressing a button on the mouse, releasing a button on the mouse, pressing a key on the keyboard, releasing a key on the keyboard etc.  So, for example, if you move the mouse over the window of your browser and click on this text, your operating system will generate at least two events – an event for when you press down on the mouse and an event for when you take your finger off the mouse.  In reality many more events will be generated;
  • an event is a special type of object which Tkinter uses.  The event object has a number of attributes.  In particular, the event object records the type of event (eg mouse click vs keyboard press) and the location in the window where that event occurred;
  • when Tkinter receives an event it checks to see whether you have told it to use a handler for that event.  If so, it will pass the event to the handler, if not it will ignore the event;
  • an event handler is a function (or a method) which does something when the event occurs, and may receive the event as a parameter.  An example of a handler is the nextImage function in this tutorial;
  • the act of telling Tkinter to use a given handler for a given event is called binding the event to the handler.  In our examples we have bound the handler by setting the ‘command’ key of the relevant widget but there is a separate function called bind();
  • in order to become visible in a Window every widget it needs to be pack() ed.   If you ever seem to be missing a widget, make sure that widget has been pack()ed (and, if the widget is inside another widget that all widgets up the chain are pack()ed)
  • Tkinter is imported with the code: 

from Tkinter import *

This code imports everything from the Tkinter namespace into the program’s namespace.  This is normally bad practice, but Tkinter is an exception.

In the tutorials to follow we will put both Tkinter and our knowledge of classes to some use – what’s more, in a vaguely practical way (if you think minecraft is practical that is…)

 

Welcome back, Class Recap

Happy New Year

Welcome back to Python4Kids! I am sorry for being a little slack since the end of last year, but hopefully I’m over that now and we can start powering on again!

Towards the end of last year we started working on classes and a GUI toolkit called Tkinter.   In this tutorial we will recap classes.  Hopefully, we’ll recap Tkinter next.  If you have data and functions which are related to each other in some way, classes allow you to group them together.  This makes managing them easier, particularly as your programs get bigger.

Example:

>>> class allDads(object):                                                                                                                                           
...    def __init__(self,age=28):                                                                                                                             
...       self.age = age                                                                                                                                                 
...                                                                                                                                                                                                                                                                                                                             
>>> dad1 = allDads()
>>> dad1.age
28                                                                                                                                                                       
>>> dad2 = allDads(35)                                                                                                                                                   
>>> dad2.age
35                        
>>> dad1.age
28

This class has one method (called __init__()) and one attribute (called age).  It inherits from object.

Recap bit

The main things we have learned about classes are:

  • they are based on (“inherit from”) some Python object (usually object <- the italics here refers to the Python thing called object as opposed to object in its ordinary sense)
  • classes are defined with the class statement.  The class statement creates an archetype* – that is, a kind of template – from which specific instances are created.   Thus the class allDads was representing all dads in the world, while myDad, was a specific instance of a dad – that is, my dad in particular.
  • data which is stored in a class or instance is called an attribute. 
  • functions which are part of a class or instance are called methods
  • if you have an instance of a class (eg myDad), then the attributes and methods of myDad are identified by the dot operator “.”.  So, the appearance attribute of the instance myDad is identified by myDad.appearance (see the dot?).  If it had a method called makesARobot() (the parenthesis indicates it is a function or method) that would be identified by myDad.makesARobot().
  • the attributes of an instance are (typically) specific to that instance.  If two instances have an attribute of the same name (which will always be the case for attributes defined by the class), changing the attribute for an instance does not affect the attribute of another instance.
  • classes usually define a special method called __init__() (“dunder init“, among other pronunciations).  This method is run whenever an instance of the class is created.  It INITialises the instance.
  • classes make use of special variable called selfself is a bit tricky to explain, but it will become clear when you start using it a bit.  Self is used when defining the class as a way for each instance to refer to that instance of the class, rather than to the class as a whole.

Python’s classes are a core component of the language.  You will end up using them all the time.  In fact, they are also a core part of object oriented programming.  I have introduced classes at the same time as Tkinter because Tkinter needs them.  In order to program Tkinter, you basically need to use classes!

Homework:

Part 1:

Create a class with a method called __init__(self, firstName = None).  Make the __init__ method assign the value of the firstName parameter to an attribute which is also called firstName.  Hint: check the example above, and use self.firstName).

Part 2:

Create some instances of your class, passing your first name as a string (ie put it in quotation marks) to the class. 

Part 3

Print the firstName attribute of the instances you have created.

Notes:

* an “archetype” is “a universally understood symbol or term or pattern of behavior, a prototype upon which others are copied, patterned, or emulated.

Follow

Get every new post delivered to your Inbox.

Join 75 other followers