From 39f49f9579bdee2ad449246d1370a0be27186ed9 Mon Sep 17 00:00:00 2001 From: Jeremy Thurgood Date: Sun, 11 May 2014 10:51:52 +0200 Subject: [PATCH] Assorted project boilerplate. --- README.txt | 71 +++++++++++++++ naja/__init__.py | 0 naja/__main__.py | 14 +++ naja/constants.py | 18 ++++ naja/options.py | 40 +++++++++ pyweek_upload.py | 223 ++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 + run_game.py | 5 ++ scripts/naja | 5 ++ setup.py | 120 +++++++++++++++++++++++++ 10 files changed, 498 insertions(+) create mode 100644 README.txt create mode 100644 naja/__init__.py create mode 100644 naja/__main__.py create mode 100644 naja/constants.py create mode 100644 naja/options.py create mode 100644 pyweek_upload.py create mode 100644 requirements.txt create mode 100755 run_game.py create mode 100755 scripts/naja create mode 100644 setup.py diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..651b790 --- /dev/null +++ b/README.txt @@ -0,0 +1,71 @@ +Codename Naja +============= + +Entry in PyWeek #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 index 0000000..e69de29 diff --git a/naja/__main__.py b/naja/__main__.py new file mode 100644 index 0000000..a4f4aa3 --- /dev/null +++ b/naja/__main__.py @@ -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 index 0000000..faa8c55 --- /dev/null +++ b/naja/constants.py @@ -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 index 0000000..cf5715d --- /dev/null +++ b/naja/options.py @@ -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 index 0000000..f9201be --- /dev/null +++ b/pyweek_upload.py @@ -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 index 0000000..0b5bd7a --- /dev/null +++ b/requirements.txt @@ -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 index 0000000..e76b9eb --- /dev/null +++ b/run_game.py @@ -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 index 0000000..e76b9eb --- /dev/null +++ b/scripts/naja @@ -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 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, +) -- 2.34.1