changeset 0:f2c3b516741b simon

Theme is 'Caught'. Start with Skellington 1.9.
author Simon Cross <hodgestar+bzr@gmail.com>
date Sun, 22 Aug 2010 09:42:24 +0200
parents
children 5b8762eb8bad 583cddf5f3d8
files README.txt create-upload.py data/REMOVE_ME.txt data/sample.txt gamelib/__init__.py gamelib/data.py gamelib/main.py pyweek-upload.py run_game.py
diffstat 8 files changed, 346 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.txt	Sun Aug 22 09:42:24 2010 +0200
@@ -0,0 +1,40 @@
+Your Game Title
+===============
+
+Entry in PyWeek #4  <http://www.pyweek.org/4/>
+Team: YOUR TEAM NAME (leave the "Team: bit")
+Members: YOUR TEAM MEMBERS (leave the "Members: bit")
+
+
+DEPENDENCIES:
+
+You might need to install some of these before running the game:
+
+  Python:     http://www.python.org/
+  PyGame:     http://www.pygame.org/
+  PyOpenGL:   http://pyopengl.sf.net/
+
+
+
+RUNNING THE GAME:
+
+On Windows or Mac OS X, locate the "run_game.pyw" file and double-click it.
+
+Othewise open a terminal / console and "cd" to the game directory and run:
+
+  python run_game.py
+
+
+
+HOW TO PLAY THE GAME:
+
+Move the cursor around the screen with the mouse.
+
+Press the left mouse button to fire the ducks.
+
+
+
+LICENSE:
+
+This game skellington is placed in the Public Domain.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/create-upload.py	Sun Aug 22 09:42:24 2010 +0200
@@ -0,0 +1,63 @@
+#! /usr/bin/env python
+'''Helper script for bundling up a game in a ZIP file.
+
+This script will bundle all game files into a ZIP file which is named as
+per the argument given on the command-line. The ZIP file will unpack into a
+directory of the same name.
+
+The script ignores:
+
+- CVS or SVN subdirectories
+- any dotfiles (files starting with ".")
+- .pyc and .pyo files
+
+'''
+
+import sys
+import os
+import zipfile
+
+if len(sys.argv) != 2:
+    print '''Usage: python %s <release filename-version>
+
+eg. python %s my_cool_game-1.0'''%(sys.argv[0], sys.argv[0])
+    sys.exit()
+
+base = sys.argv[1]
+zipname = base + '.zip'
+
+try:
+    package = zipfile.ZipFile(zipname, 'w', zipfile.ZIP_DEFLATED)
+except RuntimeError:
+    package = zipfile.ZipFile(zipname, 'w')
+
+# core files
+for name in 'README.txt run_game.py'.split():
+    package.write(name, os.path.join(base, name))
+package.write('run_game.py', os.path.join(base, 'run_game.pyw'))
+
+# utility for adding subdirectories
+def add_files(generator):
+    for dirpath, dirnames, filenames in generator:
+        for name in list(dirnames):
+            if name == 'CVS' or name.startswith('.'):
+                dirnames.remove(name)
+
+        for name in filenames:
+            if name.startswith('.'): continue
+            suffix = os.path.splitext(name)[1]
+            if suffix in ('.pyc', '.pyo'): continue
+            if name[0] == '.': continue
+            filename = os.path.join(dirpath, name)
+            package.write(filename, os.path.join(base, filename))
+
+# add the lib and data directories
+add_files(os.walk('gamelib'))
+add_files(os.walk('data'))
+
+# calculate MD5
+import hashlib
+d = hashlib.md5()
+d.update(file(zipname, 'rb').read())
+print 'Created', zipname
+print 'MD5 hash:', d.hexdigest()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/REMOVE_ME.txt	Sun Aug 22 09:42:24 2010 +0200
@@ -0,0 +1,3 @@
+This is a sentinel to make a simplistic packaging system work.
+
+Feel free to remove this and the "sample.txt" from your game.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/sample.txt	Sun Aug 22 09:42:24 2010 +0200
@@ -0,0 +1,1 @@
+This is just a sample "data" file in your data directory.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gamelib/data.py	Sun Aug 22 09:42:24 2010 +0200
@@ -0,0 +1,24 @@
+'''Simple data loader module.
+
+Loads data files from the "data" directory shipped with a game.
+
+Enhancing this to handle caching etc. is left as an exercise for the reader.
+'''
+
+import os
+
+data_py = os.path.abspath(os.path.dirname(__file__))
+data_dir = os.path.normpath(os.path.join(data_py, '..', 'data'))
+
+def filepath(filename):
+    '''Determine the path to a file in the data directory.
+    '''
+    return os.path.join(data_dir, filename)
+
+def load(filename, mode='rb'):
+    '''Open a file in the data directory.
+
+    "mode" is passed as the second arg to open().
+    '''
+    return open(os.path.join(data_dir, filename), mode)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gamelib/main.py	Sun Aug 22 09:42:24 2010 +0200
@@ -0,0 +1,13 @@
+'''Game main module.
+
+Contains the entry point used by the run_game.py script.
+
+Feel free to put all your game code here, or in other modules in this "gamelib"
+package.
+'''
+
+import data
+
+def main():
+    print "Hello from your game's main()"
+    print data.load('sample.txt').read()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pyweek-upload.py	Sun Aug 22 09:42:24 2010 +0200
@@ -0,0 +1,198 @@
+'''
+Upload script specifically engineered for the PyWeek challenge.
+
+Handles authentication and gives upload progress feedback.
+'''
+import sys, os, httplib, cStringIO, socket, time, getopt
+
+class Upload:
+    def __init__(self, filename):
+        self.filename = filename
+
+boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
+sep_boundary = '\n--' + boundary
+end_boundary = sep_boundary + '--'
+
+def mimeEncode(data, sep_boundary=sep_boundary, end_boundary=end_boundary):
+    '''Take the mapping of data and construct the body of a
+    multipart/form-data message with it using the indicated boundaries.
+    '''
+    ret = cStringIO.StringIO()
+    for key, value in data.items():
+        # handle multiple entries for the same name
+        if type(value) != type([]): value = [value]
+        for value in value:
+            ret.write(sep_boundary)
+            if isinstance(value, Upload):
+                ret.write('\nContent-Disposition: form-data; name="%s"'%key)
+                filename = os.path.basename(value.filename)
+                ret.write('; filename="%s"\n\n'%filename)
+                value = open(os.path.join(value.filename), "rb").read()
+            else:
+                ret.write('\nContent-Disposition: form-data; name="%s"'%key)
+                ret.write("\n\n")
+                value = str(value)
+            ret.write(str(value))
+            if value and value[-1] == '\r':
+                ret.write('\n')  # write an extra newline
+    ret.write(end_boundary)
+    return ret.getvalue()
+
+class Progress:
+    def __init__(self, info, data):
+        self.info = info
+        self.tosend = len(data)
+        self.total = self.tosend/1024
+        self.data = cStringIO.StringIO(data)
+        self.start = self.now = time.time()
+        self.sent = 0
+        self.num = 0
+        self.stepsize = self.total / 100 or 1
+        self.steptimes = []
+        self.display()
+
+    def __iter__(self): return self
+
+    def next(self):
+        self.num += 1
+        if self.sent >= self.tosend:
+            print self.info, 'done', ' '*(75-len(self.info)-6)
+            sys.stdout.flush()
+            raise StopIteration
+
+        chunk = self.data.read(1024)
+        self.sent += len(chunk)
+        #print (self.num, self.stepsize, self.total, self.sent, self.tosend)
+
+        if self.num % self.stepsize:
+            return chunk
+        self.display()
+        return chunk
+
+    def display(self):
+        # figure how long we've spent - guess how long to go
+        now = time.time()
+        steptime = now - self.now
+        self.steptimes.insert(0, steptime)
+        if len(self.steptimes) > 5:
+            self.steptimes.pop()
+        steptime = sum(self.steptimes) / len(self.steptimes)
+        self.now = now
+        eta = steptime * ((self.total - self.num)/self.stepsize)
+
+        # tell it like it is (or might be)
+        if now - self.start > 3:
+            M = eta / 60
+            H = M / 60
+            M = M % 60
+            S = eta % 60
+            if self.total:
+                s = '%s %2d%% (ETA %02d:%02d:%02d)'%(self.info,
+                    self.num * 100. / self.total, H, M, S)
+            else:
+                s = '%s 0%% (ETA %02d:%02d:%02d)'%(self.info, H, M, S)
+        elif self.total:
+            s = '%s %2d%%'%(self.info, self.num * 100. / self.total)
+        else:
+            s = '%s %d done'%(self.info, self.num)
+        sys.stdout.write(s + ' '*(75-len(s)) + '\r')
+        sys.stdout.flush()
+
+class progressHTTPConnection(httplib.HTTPConnection):
+    def progress_send(self, str):
+        """Send `str' to the server."""
+        if self.sock is None:
+            self.connect()
+
+        p = Progress('Uploading', str)
+        for chunk in p:
+            sent = 0
+            while sent != len(chunk):
+                try:
+                    sent += self.sock.send(chunk)
+                except socket.error, v:
+                    if v[0] == 32:      # Broken pipe
+                        self.close()
+                    raise
+                p.display()
+
+class progressHTTP(httplib.HTTP):
+    _connection_class = progressHTTPConnection
+    def _setup(self, conn):
+        httplib.HTTP._setup(self, conn)
+        self.progress_send = self._conn.progress_send
+
+def http_request(data, server, port, url):
+    h = progressHTTP(server, port)
+
+    data = mimeEncode(data)
+    h.putrequest('POST', url)
+    h.putheader('Content-type', 'multipart/form-data; boundary=%s'%boundary)
+    h.putheader('Content-length', str(len(data)))
+    h.putheader('Host', server)
+    h.endheaders()
+
+    h.progress_send(data)
+
+    errcode, errmsg, headers = h.getreply()
+
+    f = h.getfile()
+    response = f.read().strip()
+    f.close()
+
+    print '%s %s'%(errcode, errmsg)
+    if response: print response
+
+def usage():
+    print '''This program is to be used to upload files to the PyWeek system.
+You may use it to upload screenshots or code submissions.
+
+REQUIRED ARGUMENTS:
+ -u   username
+ -p   password
+ -d   description of file
+ -c   file to upload
+ -e   entry short name
+
+OPTIONAL ARGUMENTS:
+ -s   file is a screenshot
+ -f   file is FINAL submission
+ -h   override default host name (www.pyweek.org)
+ -P   override default host port (80)
+
+In order to qualify for judging at the end of the challenge, you MUST
+upload your source and check the "Final Submission" checkbox.
+'''
+
+
+if __name__ == '__main__':
+    try:
+        optlist, args = getopt.getopt(sys.argv[1:], 'e:u:p:sfd:h:P:c:')
+    except getopt.GetoptError, message:
+        print message
+        usage()
+        sys.exit(1)
+    host = 'www.pyweek.org'
+    port = 80
+    data = dict(version=2)
+    optional = {}
+    url = None
+    for opt, arg in optlist:
+        if opt == '-u': data['user'] = arg
+        elif opt == '-p': data['password'] = arg
+        elif opt == '-s': optional['is_screenshot'] = 'yes'
+        elif opt == '-f': optional['is_final'] = 'yes'
+        elif opt == '-d': data['description'] = arg
+        elif opt == '-c': data['content_file'] = Upload(arg)
+        elif opt == '-e': url = '/e/%s/oup/'%arg
+        elif opt == '-h': host = arg
+        elif opt == '-P': port = int(arg)
+
+    if len(data) < 4 or url is None:
+        print 'Required argument missing'
+        usage()
+        sys.exit(1)
+
+    data.update(optional)
+    http_request(data, host, port, url)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/run_game.py	Sun Aug 22 09:42:24 2010 +0200
@@ -0,0 +1,4 @@
+#! /usr/bin/env python
+
+from gamelib import main
+main.main()