Assorted project boilerplate.
authorJeremy Thurgood <firxen@gmail.com>
Sun, 11 May 2014 08:51:52 +0000 (10:51 +0200)
committerJeremy Thurgood <firxen@gmail.com>
Sun, 11 May 2014 08:52:23 +0000 (10:52 +0200)
README.txt [new file with mode: 0644]
naja/__init__.py [new file with mode: 0644]
naja/__main__.py [new file with mode: 0644]
naja/constants.py [new file with mode: 0644]
naja/options.py [new file with mode: 0644]
pyweek_upload.py [new file with mode: 0644]
requirements.txt [new file with mode: 0644]
run_game.py [new file with mode: 0755]
scripts/naja [new file with mode: 0755]
setup.py [new file with mode: 0644]

diff --git a/README.txt b/README.txt
new file mode 100644 (file)
index 0000000..651b790
--- /dev/null
@@ -0,0 +1,71 @@
+Codename Naja
+=============
+
+Entry in PyWeek #18  <http://www.pyweek.org/18/>
+
+URL:
+    http://pyweek.org/e/naja
+Team:
+    Naja
+Members:
+    Simon Cross
+    Neil Muller
+    Adrianna Pinska
+    Stefano Rivera
+    David Sharpe
+    Jeremy Thurgood
+License:
+    see LICENSE.txt
+
+
+Requirements
+============
+
+The game requires pygame and pymunk. Requirements can be installed by
+
+  pip install -e .
+
+Or
+
+  pip install -r requirements.txt
+
+It was developed using python 2.7 and pygame 1.9.2. Older versions may or may
+not work.
+
+
+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
+--------------------
+
+There are no ducks.
+
+
+Development notes
+-----------------
+
+Creating a source distribution with::
+
+   ./scripts/build_unix.sh
+
+You may also generate Windows executables and OS X applications::
+
+   python setup.py py2exe
+   python setup.py py2app
+
+Later you might be able to upload files to PyWeek with::
+
+   python pyweek_upload.py
+
+Later you might be able to upload to the Python Package Index with::
+
+   python setup.py register
+   python setup.py sdist upload
diff --git a/naja/__init__.py b/naja/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/naja/__main__.py b/naja/__main__.py
new file mode 100644 (file)
index 0000000..a4f4aa3
--- /dev/null
@@ -0,0 +1,14 @@
+import sys
+
+import pygame
+
+from naja.options import parse_args
+
+
+def main():
+    '''Launch the nagslang'''
+    parse_args(sys.argv)
+    pygame.display.init()
+    pygame.font.init()
+
+    raise NotImplementedError("Sorry, we haven't written a game yet.")
diff --git a/naja/constants.py b/naja/constants.py
new file mode 100644 (file)
index 0000000..faa8c55
--- /dev/null
@@ -0,0 +1,18 @@
+SCREEN = (800, 600)
+FPS = 40
+FONT = 'DejaVuSans.ttf'
+FONT_SIZE = 16
+
+DEFAULTS = dict(
+    debug=False,
+    sound=True,
+    music=True,
+)
+
+# Sound constants
+FREQ = 44100   # same as audio CD
+BITSIZE = -16  # unsigned 16 bit
+CHANNELS = 2   # 1 == mono, 2 == stereo
+BUFFER = 1024  # audio buffer size in no. of samples
+DEFAULT_SOUND_VOLUME = 1.0  # sound volume
+DEFAULT_MUSIC_VOLUME = 0.3  # music volume
diff --git a/naja/options.py b/naja/options.py
new file mode 100644 (file)
index 0000000..cf5715d
--- /dev/null
@@ -0,0 +1,40 @@
+import optparse
+import os
+
+from naja.constants import DEFAULTS
+
+
+class AttrDict(dict):
+    '''A dict with attribute access'''
+    def __getattr__(self, attr):
+        return self[attr]
+
+
+options = AttrDict()
+
+
+def parse_args(args):
+    '''
+    Parse arguments and store them in the options dictionary.
+
+    Note: If you add arguments, you need to add an appropriate default to the
+    DEFAULTS dict.
+    '''
+    options.update(DEFAULTS)
+
+    options.debug = 'DEBUG' in os.environ
+
+    parser = optparse.OptionParser()
+    parser.add_option('--no-sound',
+                      dest='sound', action='store_false', default=True,
+                      help='Disable all sound, including music')
+
+    parser.add_option('--no-music',
+                      dest='music', action='store_false', default=True,
+                      help='Disable music (but not sound)')
+
+    opts, _ = parser.parse_args(args)
+
+    for k in DEFAULTS:
+        if getattr(opts, k, None) is not None:
+            options[k] = getattr(opts, k)
diff --git a/pyweek_upload.py b/pyweek_upload.py
new file mode 100644 (file)
index 0000000..f9201be
--- /dev/null
@@ -0,0 +1,223 @@
+'''
+Upload script specifically engineered for the PyWeek challenge.
+
+Handles authentication and gives upload progress feedback.
+'''
+import sys
+import os
+import httplib
+import cStringIO
+import socket
+import time
+import getopt
+
+
+class Upload(object):
+    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 not isinstance(value, list):
+            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(object):
+    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)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644 (file)
index 0000000..0b5bd7a
--- /dev/null
@@ -0,0 +1,2 @@
+# Our dependencies are all specified in setup.py.
+-e .
diff --git a/run_game.py b/run_game.py
new file mode 100755 (executable)
index 0000000..e76b9eb
--- /dev/null
@@ -0,0 +1,5 @@
+#! /usr/bin/env python
+
+import naja.__main__
+if __name__ == "__main__":
+    naja.__main__.main()
diff --git a/scripts/naja b/scripts/naja
new file mode 100755 (executable)
index 0000000..e76b9eb
--- /dev/null
@@ -0,0 +1,5 @@
+#! /usr/bin/env python
+
+import naja.__main__
+if __name__ == "__main__":
+    naja.__main__.main()
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..a54b907
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,120 @@
+# setup.py
+# -*- coding: utf8 -*-
+# vim:fileencoding=utf8 ai ts=4 sts=4 et sw=4
+
+"""Setuptools setup.py file for naja."""
+
+from setuptools import setup, find_packages
+
+try:
+    import py2exe
+    py2exe  # To make pyflakes happy.
+except ImportError:
+    pass
+
+# This should probably be pulled from constants.py
+VERSION_STR = "0.1"
+
+setup(
+    name="naja",
+    version=VERSION_STR,
+    description="naja: Game for PyWeek 18",
+
+    author=(", ".join([
+        "Simon Cross",
+        "Neil Muller",
+        "Adrianna Pinska",
+        "Stefano Rivera",
+        "David Sharpe",
+        "Jeremy Thurgood",
+    ])),
+    author_email="ctpug@googlegroups.com",
+
+    maintainer="Naja Team",
+    maintainer_email="ctpug@googlegroups.com",
+
+    url="http://ctpug.org.za/",
+    download_url="http://www.ctpug.org.za/gitweb/?p=naja.git",
+
+    license="MIT",
+
+    classifiers=[
+        'Development Status :: 4 - Beta',
+        'Environment :: MacOS X',
+        'Environment :: Win32 (MS Windows)',
+        'Environment :: X11 Applications',
+        'Intended Audience :: End Users/Desktop',
+        'License :: OSI Approved :: MIT License',
+        'Natural Language :: English',
+        'Operating System :: Microsoft :: Windows',
+        'Operating System :: POSIX',
+        'Operating System :: MacOS :: MacOS X',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
+        'Topic :: Games/Entertainment',
+    ],
+
+    platforms=[
+        'Linux',
+        'Mac OS X',
+        'Windows',
+    ],
+
+    # Dependencies
+    install_requires=['pygame'],
+
+    # Files
+    packages=find_packages(),
+    scripts=[
+        'scripts/naja',
+    ],
+
+    # py2exe
+    windows=[{
+        'script': 'scripts/naja',
+        'icon_resources': [(0, "data/icons/naja.ico")],
+    }],
+    app=['scripts/naja'],
+    options={
+        'py2exe': {
+            'skip_archive': 1,
+            'dist_dir': 'dist/naja-%s' % VERSION_STR,
+            'packages': [
+                'logging', 'encodings', 'naja',
+            ],
+            'includes': [
+                'pygame', 'pymunk',
+            ],
+            'excludes': [
+                'numpy',
+            ],
+            'ignores': [
+                # all database modules
+                'pgdb', 'Sybase', 'adodbapi',
+                'kinterbasdb', 'psycopg', 'psycopg2', 'pymssql',
+                'sapdb', 'pysqlite2', 'sqlite', 'sqlite3',
+                'MySQLdb', 'MySQLdb.connections',
+                'MySQLdb.constants.CR', 'MySQLdb.constants.ER',
+                # old datetime equivalents
+                'DateTime', 'DateTime.ISO',
+                'mx', 'mx.DateTime', 'mx.DateTime.ISO',
+                # email modules
+                'email.Generator', 'email.Iterators', 'email.Utils',
+            ],
+        },
+        'py2app': {
+            'app': ['run_game.py'],
+            'argv_emulation': True,
+            'iconfile': 'data/icons/program/icon.icns',
+            'packages': [
+                'logging', 'encodings', 'pygame', 'naja', 'data',
+            ],
+            'excludes': ['numpy'],
+        }},
+    data_files=[
+        # 'COPYRIGHT',
+        'LICENSE.txt',
+        'README.txt',
+    ],
+    include_package_data=True,
+)