<commit_before>#!/usr/bin/python
# -*- coding: UTF-8 -*-

import cgi
from modules.gitdox_sql import *
from modules.ether import ether_to_sgml, get_socialcalc, build_meta_tag
from modules.logintools import login
import zipfile
from StringIO import StringIO
from shutil import copyfileobj
import sys, tempfile

def create_zip(content_name_pairs):
	io_file = StringIO()
	zf = zipfile.ZipFile(io_file, mode='w', compression=zipfile.ZIP_DEFLATED)

	for content, name in content_name_pairs:
		zf.writestr(name, content.encode("utf8"))

	return io_file

def export_all_docs(config=None, corpus_filter=None, extension="sgml"):
	docs = get_all_docs(corpus_filter)
	files = []
	for doc in docs:
		doc_id, docname, corpus, mode, content = doc
		if corpus_filter is None:  # All documents exported, use corpus prefix to avoid name clashes
			filename = corpus + "_" + docname
		else:  # Only exporting one user specified corpus, name documents without prefix
			filename = docname
		if mode == "xml":
			content = build_meta_tag(doc_id) + content.strip() + "\n</meta>\n"
			files.append((content,filename + ".xml"))
		elif mode == "ether":
			ether_name = "_".join(["gd",corpus,docname])
			sgml = ether_to_sgml(get_socialcalc(ether_url, ether_name),doc_id,config=config)
			files.append((sgml, filename + "." + extension))

	zip_io = create_zip(files)

	temp = tempfile.NamedTemporaryFile(delete=False, mode='w+b')
	temp.write(zip_io.getvalue())
	temp.close()

	if corpus_filter is not None:
		zipname = corpus_filter + ".zip"
	else:
		zipname = "export.zip"

	print("Content-type: application/download")
	print("Content-Disposition: attachment; filename=" + zipname)
	print("")

	sys.stdout.flush()

	with open(temp.name,'rb') as z:
		copyfileobj(z, sys.stdout)

	os.remove(temp.name)

def export_doc(doc_id, stylesheet=None):
	docname, corpus, filename, status, assignee_username, mode, schema = get_doc_info(doc_id)
	ether_name = "_".join(["gd", corpus, docname])

	sgml = ether_to_sgml(get_socialcalc(ether_url, ether_name), doc_id, config=stylesheet)

	cpout = ""
	cpout += "Content-Type: application/download\n"
	cpout += "Content-Disposition: attachment; filename=" + corpus + "_" + docname + ".sgml\n\n"

	if isinstance(cpout,unicode):
		cpout = str(cpout.encode("utf8"))

	cpout += sgml
	print(cpout)

if __name__ == "__main__":
	import cgitb
	cgitb.enable()
	from paths import ether_url

	thisscript = os.environ.get('SCRIPT_NAME', '')
	action = None
	theform = cgi.FieldStorage()
	scriptpath = os.path.dirname(os.path.realpath(__file__)) + os.sep
	userdir = scriptpath + "users" + os.sep
	action, userconfig = login(theform, userdir, thisscript, action)
	user = userconfig["username"]
	admin = userconfig["admin"]

	export_stylesheet = "--default--"

	if "extension" in theform:
		extension = theform.getvalue("extension")
	else:
		extension = "sgml"
	if "stylesheet" in theform:
		export_stylesheet = theform.getvalue("stylesheet")
	else:
		export_stylesheet = None
	if "status" in theform:
		status = theform.getvalue("status")
	else:
		status = None
	if "corpus" in theform:
		corpus = theform.getvalue("corpus")
	else:
		corpus = None

	if corpus == "--ALL--":
		corpus = None
	if status == "--ALL--":
		status = None

	if "docs" in theform:
		docs = theform.getvalue("docs")
		if docs == "%all%":
			export_all_docs(export_stylesheet,corpus_filter=corpus,extension=extension,status=status)
		else:
			export_doc(docs, export_stylesheet)
<commit_msg>Support corpus metadata in export   * For each corpus, exported zip files contain a file _meta_<CORPUS>.tab   * Meta for corpus is key value pairs, tab delimited   * Closes #56<commit_after>#!/usr/bin/python
# -*- coding: UTF-8 -*-

import cgi
from modules.gitdox_sql import *
from modules.ether import ether_to_sgml, get_socialcalc, build_meta_tag
from modules.logintools import login
import zipfile
from StringIO import StringIO
from shutil import copyfileobj
import sys, tempfile
from collections import defaultdict

def create_zip(content_name_pairs):
	io_file = StringIO()
	zf = zipfile.ZipFile(io_file, mode='w', compression=zipfile.ZIP_DEFLATED)

	for content, name in content_name_pairs:
		zf.writestr(name, content)#.encode("utf8"))

	return io_file

def export_all_docs(config=None, corpus_filter=None, status=None, extension="sgml"):
	docs = get_all_docs(corpus_filter,status)
	files = []
	all_corpus_meta = defaultdict(dict)
	for doc in docs:
		doc_id, docname, corpus, mode, content = doc
		if corpus not in all_corpus_meta:
			corpus_meta = get_doc_meta(doc_id, corpus=True)
			for md in corpus_meta:
				key, val = md[2], md[3]
				all_corpus_meta[corpus][key] = val
		if corpus_filter is None:  # All documents exported, use corpus prefix to avoid name clashes
			filename = corpus + "_" + docname
		else:  # Only exporting one user specified corpus, name documents without prefix
			filename = docname
		if mode == "xml":
			content = build_meta_tag(doc_id) + content.strip() + "\n</meta>\n"
			files.append((content,filename + ".xml"))
		elif mode == "ether":
			ether_name = "_".join(["gd",corpus,docname])
			sgml = ether_to_sgml(get_socialcalc(ether_url, ether_name),doc_id,config=config)
			files.append((sgml, filename + "." + extension))

	for corp in all_corpus_meta:
		serialized_meta = ""
		for key in all_corpus_meta[corp]:
			serialized_meta += key + "\t" + all_corpus_meta[corp][key] + "\n"
		files.append((serialized_meta.encode("utf8"), "_meta_" + corp + ".tab"))

	zip_io = create_zip(files)

	temp = tempfile.NamedTemporaryFile(delete=False, mode='w+b')
	temp.write(zip_io.getvalue())
	temp.close()

	if corpus_filter is not None:
		zipname = corpus_filter + ".zip"
	else:
		zipname = "export.zip"

	print("Content-type: application/download")
	print("Content-Disposition: attachment; filename=" + zipname)
	print("")

	sys.stdout.flush()

	with open(temp.name,'rb') as z:
		copyfileobj(z, sys.stdout)

	os.remove(temp.name)

def export_doc(doc_id, stylesheet=None):
	docname, corpus, filename, status, assignee_username, mode, schema = get_doc_info(doc_id)
	ether_name = "_".join(["gd", corpus, docname])

	sgml = ether_to_sgml(get_socialcalc(ether_url, ether_name), doc_id, config=stylesheet)

	cpout = ""
	cpout += "Content-Type: application/download\n"
	cpout += "Content-Disposition: attachment; filename=" + corpus + "_" + docname + ".sgml\n\n"

	if isinstance(cpout,unicode):
		cpout = str(cpout.encode("utf8"))

	cpout += sgml
	print(cpout)

if __name__ == "__main__":
	#print("Content-type:text/html\r\n\r\n")

	import cgitb
	cgitb.enable()
	from paths import ether_url

	thisscript = os.environ.get('SCRIPT_NAME', '')
	action = None
	theform = cgi.FieldStorage()
	scriptpath = os.path.dirname(os.path.realpath(__file__)) + os.sep
	userdir = scriptpath + "users" + os.sep
	action, userconfig = login(theform, userdir, thisscript, action)
	user = userconfig["username"]
	admin = userconfig["admin"]

	export_stylesheet = "--default--"

	if "extension" in theform:
		extension = theform.getvalue("extension")
	else:
		extension = "sgml"
	if "stylesheet" in theform:
		export_stylesheet = theform.getvalue("stylesheet")
	else:
		export_stylesheet = None
	if "status" in theform:
		status = theform.getvalue("status")
	else:
		status = None
	if "corpus" in theform:
		corpus = theform.getvalue("corpus")
	else:
		corpus = None

	if corpus == "--ALL--":
		corpus = None
	if status == "--ALL--":
		status = None

	if "docs" in theform:
		docs = theform.getvalue("docs")
		if docs == "%all%":
			export_all_docs(export_stylesheet,corpus_filter=corpus,extension=extension,status=status)
		else:
			export_doc(docs, export_stylesheet)
<|endoftext|>


<commit_before>from common import expand_vars, find_nodes_to_build, tsort, PynException

class Builder(object):
    def __init__(self, host, args):
        self._host = host
        self._args = args
        self._mtimes = {}

    def build(self, graph):
        requested_targets = self._args.targets or graph.defaults
        nodes_to_build = find_nodes_to_build(graph, requested_targets)
        sorted_nodes = tsort(graph, nodes_to_build)
        sorted_nodes = [n for n in sorted_nodes
                        if graph.nodes[n].rule_name != 'phony']
        total_nodes = len(sorted_nodes)
        num_builds = 0

        for name in sorted_nodes:
            node = graph.nodes[name]
            my_mtime = self._stat(name)
            do_build = any(self._stat(d) >= my_mtime for d in node.deps)
            if do_build:
                num_builds += 1
                self._build_node(graph, node, num_builds, total_nodes)
                self._restat(name)
            else:
                total_nodes -= 1

        if not num_builds:
            self._host.print_err('pyn: no work to do')

    def _build_node(self, graph, node, cur, total_nodes):
        if node.rule_name == 'phony':
            self._host.print_err('[%d/%d]%s' % (cur, total_nodes, node.name))
            return

        rule = graph.rules[node.rule_name]
        command = expand_vars(rule.scope['command'], node.scope)
        if self._args.dry_run:
            ret, out, err = 0, '', ''
        else:
            ret, out, err = self._host.call(command)
        if ret or out or err or self._args.verbose > 1:
            self._host.print_err('[%d/%d]%s' % (cur, total_nodes, command))
            if out:
                self._host.print_out(out)
            if err:
                self._host.print_err(err)
            if ret:
                raise PynException('build failed')
        else:
            desc = expand_vars(rule.scope['description'], node.scope)
            self._host.print_err('[%d/%d]%s' % (cur, total_nodes, desc))

    def clean(self, graph):
        outputs = [n.name for n in graph.nodes.values()
                   if n.rule_name != 'phony' and self._host.exists(n.name)]
        self._host.print_err('Cleaning...')
        for o in outputs:
            if self._args.verbose:
                self._host.print_err('Remove %s' % o)
            if not self._args.dry_run:
                self._host.remove(o)
        self._host.print_err('%d files' % len(outputs))

    def _stat(self, name):
        if not name in self._mtimes:
            self._restat(name)
        return self._mtimes.get(name, -1)

    def _restat(self, name):
        if self._host.exists(name):
            self._mtimes[name] = self._host.mtime(name)
        else:
            self._mtimes[name] = -1
<commit_msg>print description, not command, when a command has output but does not fail; also fix formatting<commit_after>from common import expand_vars, find_nodes_to_build, tsort, PynException

class Builder(object):
    def __init__(self, host, args):
        self._host = host
        self._args = args
        self._mtimes = {}

    def build(self, graph):
        requested_targets = self._args.targets or graph.defaults
        nodes_to_build = find_nodes_to_build(graph, requested_targets)
        sorted_nodes = tsort(graph, nodes_to_build)
        sorted_nodes = [n for n in sorted_nodes
                        if graph.nodes[n].rule_name != 'phony']
        total_nodes = len(sorted_nodes)
        num_builds = 0

        for name in sorted_nodes:
            node = graph.nodes[name]
            my_mtime = self._stat(name)
            do_build = any(self._stat(d) >= my_mtime for d in node.deps)
            if do_build:
                num_builds += 1
                self._build_node(graph, node, num_builds, total_nodes)
                self._restat(name)
            else:
                total_nodes -= 1

        if not num_builds:
            self._host.print_err('pyn: no work to do')

    def _build_node(self, graph, node, cur, total_nodes):
        if node.rule_name == 'phony':
            self._host.print_err('[%d/%d] %s' % (cur, total_nodes, node.name))
            return

        rule = graph.rules[node.rule_name]
        command = expand_vars(rule.scope['command'], node.scope)
        if self._args.dry_run:
            ret, out, err = 0, '', ''
        else:
            ret, out, err = self._host.call(command)

        desc = expand_vars(rule.scope['description'], node.scope)
        if ret or out or err or self._args.verbose > 1:
            if ret or self._args.verbose > 1:
                self._host.print_err('[%d/%d] %s' % (cur, total_nodes, command))
            else:
                desc = expand_vars(rule.scope['description'], node.scope)
                self._host.print_err('[%d/%d] %s' % (cur, total_nodes, desc))

            if out:
                self._host.print_out(out)
            if err:
                self._host.print_err(err)
            if ret:
                raise PynException('build failed')
        else:
            desc = expand_vars(rule.scope['description'], node.scope)
            self._host.print_err('[%d/%d] %s' % (cur, total_nodes, desc))

    def clean(self, graph):
        outputs = [n.name for n in graph.nodes.values()
                   if n.rule_name != 'phony' and self._host.exists(n.name)]
        self._host.print_err('Cleaning...')
        for o in outputs:
            if self._args.verbose:
                self._host.print_err('Remove %s' % o)
            if not self._args.dry_run:
                self._host.remove(o)
        self._host.print_err('%d files' % len(outputs))

    def _stat(self, name):
        if not name in self._mtimes:
            self._restat(name)
        return self._mtimes.get(name, -1)

    def _restat(self, name):
        if self._host.exists(name):
            self._mtimes[name] = self._host.mtime(name)
        else:
            self._mtimes[name] = -1
<|endoftext|>


<commit_before>"""
A simple drop-in replacement for the matplotlib's object-oriented plotting
behavior. The only functions exported are `figure` and `show` which can be used
as replacements for the `pylab` function of the same name. The resulting figure
and axes objects work as before, but have some additional functionality and
different default styles.
"""

from __future__ import division
from __future__ import absolute_import
from __future__ import print_function

import matplotlib.pyplot as plt

from matplotlib.axes import Axes
from matplotlib.figure import Figure
from matplotlib.gridspec import GridSpec
from matplotlib.projections import register_projection

__all__ = ['figure', 'show']

# FIXME: I don't like doing this because it globally changes whatever plot
# styles or defaults the user may have set. but I'll leave it for now.
plt.rc('lines', linewidth=2.0)
plt.rc('legend', scatterpoints=3)
plt.rc('legend', fontsize=12)
plt.rc('figure', facecolor='white')
plt.rc('axes', grid=True)
plt.rc('grid', color='k', linestyle='-', alpha=0.2, linewidth=0.5)
plt.rc('axes', color_cycle=[
    (0.21568627450980393, 0.49411764705882355, 0.7215686274509804),
    (0.30196078431372547, 0.6862745098039216, 0.2901960784313726),
    (0.596078431372549, 0.3058823529411765, 0.6392156862745098),
    (1.0, 0.4980392156862745, 0.0),
    (0.6509803921568628, 0.33725490196078434, 0.1568627450980392),
    (0.9686274509803922, 0.5058823529411764, 0.7490196078431373)])

class CustomAxes(Axes):
    name = 'custom_axes'

    def cla(self):
        super(CustomAxes, self).cla()
        self.spines['top'].set_visible(False)
        self.spines['right'].set_visible(False)
        self.xaxis.set_tick_params(direction='out', top=False)
        self.yaxis.set_tick_params(direction='out', right=False)
        self.autoscale(tight=True)

    def scatter(self, x, y, **kwargs):
        kwargs.setdefault('s', 40)
        kwargs.setdefault('color', 'k')
        kwargs.setdefault('marker', 'o')
        kwargs.setdefault('linewidths', 1.5)
        kwargs.setdefault('facecolors', 'none')
        super(CustomAxes, self).scatter(x, y, **kwargs)

    def plot_banded(self, x, y=None, b1=None, **kwargs):
        if y is None:
            lines = self.plot(x, **kwargs)
        else:
            lines = self.plot(x, y, **kwargs)
        x = lines[0].get_xdata()
        y = lines[0].get_ydata()
        c = lines[0].get_color()
        a = lines[0].get_alpha()
        a = 1.0 if (a is None) else a
        if b1 is None:
            self.fill_between(x, 0, y, color=c, alpha=0.2*a)
        else:
            self.fill_between(x, y-b1, y+b1, color=c, alpha=0.2*a)

    def axvline(self, x=0, ymin=0, ymax=1, **kwargs):
        kwargs.setdefault('color', 'r')
        kwargs.setdefault('linestyle', '--')
        super(CustomAxes, self).axvline(x, ymin, ymax, **kwargs)

    def axhline(self, y=0, xmin=0, xmax=1, **kwargs):
        kwargs.setdefault('color', 'r')
        kwargs.setdefault('linestyle', '--')
        super(CustomAxes, self).axhline(y, xmin, xmax, **kwargs)

# register the new axes. for some reason this isn't defined to return the class
# object, so we can't use it as a decorator.
register_projection(CustomAxes)

class CustomFigure(Figure):
    """
    A custom figure object which by default creates axes which use the
    'custom_axes' projection. It also adds additional kwargs and methods, but
    if these are ignored the figure acts as a standard matplotlib instance.
    """
    def __init__(self, *args, **kwargs):
        tight_layout = kwargs.pop('tight_layout', True)
        super(CustomFigure, self).__init__(*args, **kwargs)
        self.set_tight_layout(tight_layout)

    def add_subplot(self, *args, **kwargs):
        kwargs.setdefault('projection', 'custom_axes')
        hidex = kwargs.pop('hidex', False)
        hidey = kwargs.pop('hidey', False)
        ax = super(CustomFigure, self).add_subplot(*args, **kwargs)
        if hidex:
            plt.setp(ax.get_xticklabels(), visible=False)
            plt.setp(ax.get_xaxis().get_offset_text(), visible=False)
        if hidey:
            plt.setp(ax.get_yticklabels(), visible=False)
            plt.setp(ax.get_yaxis().get_offset_text(), visible=False)
        return ax

    def add_subplotspec(self, shape, loc, rowspan=1, colspan=1, **kwargs):
        s1, s2 = shape
        subplotspec = GridSpec(s1, s2).new_subplotspec(loc, rowspan, colspan)
        return self.add_subplot(subplotspec, **kwargs)

def figure(*args, **kwargs):
    clear = kwargs.pop('clear', False)
    kwargs.setdefault('FigureClass', CustomFigure)
    fig = plt.figure(*args, **kwargs)
    if clear:
        fig.clear()
    return fig

def show(block=None):
    block = (not plt.isinteractive()) if (block is None) else block
    plt.show(block)
<commit_msg>Update to add bar and some defaults.<commit_after>"""
A simple drop-in replacement for the matplotlib's object-oriented plotting
behavior. The only functions exported are `figure` and `show` which can be used
as replacements for the `pylab` function of the same name. The resulting figure
and axes objects work as before, but have some additional functionality and
different default styles.
"""

from __future__ import division
from __future__ import absolute_import
from __future__ import print_function

import matplotlib.pyplot as plt

from matplotlib.axes import Axes
from matplotlib.figure import Figure
from matplotlib.gridspec import GridSpec
from matplotlib.projections import register_projection

__all__ = ['figure', 'show']

# FIXME: I don't like doing this because it globally changes whatever plot
# styles or defaults the user may have set. but I'll leave it for now.
plt.rc('lines', linewidth=2.0)
plt.rc('legend', scatterpoints=3)
plt.rc('legend', fontsize=12)
plt.rc('figure', facecolor='white')
plt.rc('axes', grid=True)
plt.rc('grid', color='k', linestyle='-', alpha=0.2, linewidth=0.5)
plt.rc('axes', color_cycle=[
    (0.21568627450980393, 0.49411764705882355, 0.7215686274509804),
    (0.30196078431372547, 0.6862745098039216, 0.2901960784313726),
    (0.596078431372549, 0.3058823529411765, 0.6392156862745098),
    (1.0, 0.4980392156862745, 0.0),
    (0.6509803921568628, 0.33725490196078434, 0.1568627450980392),
    (0.9686274509803922, 0.5058823529411764, 0.7490196078431373)])

# FIXME: turn interactive mode on by default. I don't like this, but is
# required to make a few things work for now. Should be taken out soon.
plt.ion()

class CustomAxes(Axes):
    name = 'custom_axes'

    def cla(self):
        super(CustomAxes, self).cla()
        self.spines['top'].set_visible(False)
        self.spines['right'].set_visible(False)
        self.xaxis.set_tick_params(direction='out', top=False)
        self.yaxis.set_tick_params(direction='out', right=False)
        self.autoscale(tight=True)

    def scatter(self, x, y, **kwargs):
        kwargs.setdefault('s', 40)
        kwargs.setdefault('color', 'k')
        kwargs.setdefault('marker', 'o')
        kwargs.setdefault('linewidths', 1.5)
        kwargs.setdefault('facecolors', 'none')
        super(CustomAxes, self).scatter(x, y, **kwargs)

    def bar(self, *args, **kwargs):
        if kwargs.get('color', None) is None:
            kwargs['color'] = next(self._get_lines.color_cycle)
        super(CustomAxes, self).bar(*args, **kwargs)

    def plot_banded(self, x, y=None, b1=None, **kwargs):
        if y is None:
            lines = self.plot(x, **kwargs)
        else:
            lines = self.plot(x, y, **kwargs)
        x = lines[0].get_xdata()
        y = lines[0].get_ydata()
        c = lines[0].get_color()
        a = lines[0].get_alpha()
        a = 1.0 if (a is None) else a
        if b1 is None:
            self.fill_between(x, 0, y, color=c, alpha=0.2*a)
        else:
            self.fill_between(x, y-b1, y+b1, color=c, alpha=0.2*a)

    def axvline(self, x=0, ymin=0, ymax=1, **kwargs):
        kwargs.setdefault('color', 'r')
        kwargs.setdefault('linestyle', '--')
        super(CustomAxes, self).axvline(x, ymin, ymax, **kwargs)

    def axhline(self, y=0, xmin=0, xmax=1, **kwargs):
        kwargs.setdefault('color', 'r')
        kwargs.setdefault('linestyle', '--')
        super(CustomAxes, self).axhline(y, xmin, xmax, **kwargs)

# register the new axes. for some reason this isn't defined to return the class
# object, so we can't use it as a decorator.
register_projection(CustomAxes)

class CustomFigure(Figure):
    """
    A custom figure object which by default creates axes which use the
    'custom_axes' projection. It also adds additional kwargs and methods, but
    if these are ignored the figure acts as a standard matplotlib instance.
    """
    def add_subplot(self, *args, **kwargs):
        kwargs.setdefault('projection', 'custom_axes')
        hidexy = kwargs.pop('hidexy', False)
        hidex = kwargs.pop('hidex', hidexy)
        hidey = kwargs.pop('hidey', hidexy)
        ax = super(CustomFigure, self).add_subplot(*args, **kwargs)
        if hidex:
            plt.setp(ax.get_xticklabels(), visible=False)
            plt.setp(ax.get_xaxis().get_offset_text(), visible=False)
        if hidey:
            plt.setp(ax.get_yticklabels(), visible=False)
            plt.setp(ax.get_yaxis().get_offset_text(), visible=False)
        return ax

    def add_subplotspec(self, shape, loc, rowspan=1, colspan=1, **kwargs):
        s1, s2 = shape
        subplotspec = GridSpec(s1, s2).new_subplotspec(loc, rowspan, colspan)
        return self.add_subplot(subplotspec, **kwargs)

def figure(*args, **kwargs):
    clear = kwargs.pop('clear', False)
    kwargs.setdefault('FigureClass', CustomFigure)
    tight_layout = kwargs.pop('tight_layout', True)
    tight_layout_dict = {k: kwargs.pop(k)
                         for k in ('pad', 'w_pad', 'h_pad')
                         if kwargs.get(k, None) is not None}

    # create the figure
    fig = plt.figure(*args, **kwargs)

    # possibly clear the figure
    if clear:
        fig.clear()

    # set tight layout
    if tight_layout and len(tight_layout_dict) > 0:
        fig.set_tight_layout(tight_layout_dict)
    else:
        fig.set_tight_layout(tight_layout)

    return fig

def show(block=None):
    block = (not plt.isinteractive()) if (block is None) else block
    plt.show(block)
<|endoftext|>


<commit_before>import random
import time

#SCREEN_TYPE = "UNICORN"
#SCREEN_TYPE = "SCROLL"
SCREEN_TYPE = "SCROLLHD"
RGB_ENABLED = False

STUDY_LOOP = False
LOOP = False

TUNNEL_GAP_MIN = 1
TUNNEL_GAP_MAX = 4
TUNNEL_GAP_DIFF_MAX = 1
TUNNEL_MOVE_DIFF_MAX = 1

if SCREEN_TYPE == "UNICORN":
    import unicornhat as unicorn
    unicorn.set_layout(unicorn.AUTO)
    unicorn.rotation(0)
    unicorn.brightness(0.7)
    SCREEN_WIDTH, SCREEN_HEIGHT = unicorn.get_shape()
    screen_size = (SCREEN_WIDTH, SCREEN_HEIGHT)
    RGB_ENABLED = True

    BG_COLOR = [0, 150, 200]
    BG_NOISE = 2
    CAVE_COLOR = [244, 164, 96]
    CAVE_NOISE = 10
    PLAYER_COLOR = [255, 0, 0]

if SCREEN_TYPE == "SCROLL":
    import scrollphat
    SCREEN_WIDTH = 11
    SCREEN_HEIGHT = 5
    scrollphat.set_brightness(1)

if SCREEN_TYPE == "SCROLLHD":
    import scrollphathd as scrollphat
    SCREEN_WIDTH = 17
    SCREEN_HEIGHT = 7
    scrollphat.set_brightness(0.25)

screen_size = (SCREEN_WIDTH, SCREEN_HEIGHT)

class GameOver(Exception):
    def __init__(self):
        print("AI found no possible moves")

class Field:
    def __init__(self, field_size):
        self.buffer = [[0] * field_size[1]] * field_size[0]
        self.gap_buffer = [(1, 3)]

    """
    tunnel_gen - Tunnel Generator
    creates the next column for the tunnel

    Requires two random integers;
        diff_place: in range of +-1, determines where the gap is placed in relation to the last gap
        diff_width: in range of +-1, determines the gap width in relation to the last gap width
    """
    def tunnel_gen(self):
        if self.gap_buffer[-1][0] == 0:                             # Is the current place at screen edge?
            diff_place += random.randint(0, TUNNEL_MOVE_DIFF_MAX)   # Go away or stay at screen edge
        elif self.gap_buffer[-1][0] == SCREEN_HEIGHT - 1:           # Is the current place at screen edge?
            diff_place += random.randint(-TUNNEL_MOVE_DIFF_MAX, 0)  # Go away or stay at screen edge
        else:
            diff_place += random.randint(-TUNNEL_MOVE_DIFF_MAX, TUNNEL_MOVE_DIFF_MAX)  # Not at screen edge, can move freely

        if diff_width == TUNNEL_GAP_MIN:                            # Is gap at minimum?
            diff_width += random.randint(0, TUNNEL_GAP_DIFF_MAX)    # Go larger or stay at same
        if diff_width == TUNNEL_GAP_MAX:                            # Is gap at maximum?
            diff_width += random.randint(-TUNNEL_GAP_DIFF_MAX, 0)   # Go smaller or stay at same
        else:
            diff_width += random.randint(-TUNNEL_GAP_DIFF_MAX, TUNNEL_GAP_DIFF_MAX)   # Adjust freely

        self.gap_buffer.append((self.gap_buffer[-1][0] + diff_place, self.gap_buffer[-1][1] + diff_width))
        if len(self.gap_buffer) > SCREEN_WIDTH: self.gap_buffer.pop(0)

        col = [1] * len(self.buffer[0])
        for pixel_i in range(0, self.gap_buffer[-1][1]):
            try: col[pixel_i + self.gap_buffer[-1][0]] = 0
            except IndexError: pass

        return col

    def update(self):
        new_col = self.tunnel_gen()
        self.buffer.append(new_col)
        self.buffer.pop(0)

class Player:
    def __init__(self):
        self.x = 1
        self.y = 2

class Game:
    def __init__(self, move_weight={-1: -1, 0: 0, 1: -1}, next_layer_weight=3, speed=0.05):
        self.distance = 0
        self.field = Field(screen_size)
        self.ai = AI(self.field, move_weight, next_layer_weight)
        self.speed = speed
        self.run = True

    def start(self):
        while self.run:
            self.step()
            if SCREEN_TYPE == "UNICORN": self.print_unicorn()
            if SCREEN_TYPE == "SCROLL" or SCREEN_TYPE == "SCROLLHD": self.print_scroll()
            time.sleep(self.speed)
        return self.distance

    def step(self):
        self.distance += 1
        self.field.update()
        self.ai.move()
        if self.field.buffer[self.ai.player.x][self.ai.player.y] == 1:
            if SCREEN_TYPE == "UNICORN": self.game_over_unicorn()
            if SCREEN_TYPE == "SCROLL" or SCREEN_TYPE == "SCROLLHD": self.game_over_scroll()

    def game_over_unicorn(self):
        width, height = unicorn.get_shape()
        for x in range(width):
            time.sleep(0.05)
            for y in range(height):
                r, g, b = [200, 0, 0]
                unicorn.set_pixel(x, y, r, g, b)
        time.sleep(0.5)
        self.run = False

    def game_over_scroll(self):
        for i in range(1):
            self.set_checker(0)
            time.sleep(0.5)
            self.set_checker(1)
            time.sleep(0.5)
        self.run = False

    def set_checker(self, offset):
        scrollphat.clear()
        n = offset
        for y in range(SCREEN_HEIGHT):
            for x in range(SCREEN_WIDTH):
                scrollphat.set_pixel(x, y, n % 2 == 0)
                n += 1
        if SCREEN_TYPE == "SCROLL": scrollphat.update()
        if SCREEN_TYPE == "SCROLLHD": scrollphat.show()

    def print_unicorn(self):
        unicorn.clear()
        for x, col in enumerate(self.field.buffer):
            for y, pixel in enumerate(col):
                if pixel: r, g, b = [i + random.randint(0, CAVE_NOISE) for i in CAVE_COLOR]
                else: r, g, b = [i + random.randint(0, BG_NOISE) for i in BG_COLOR]
                unicorn.set_pixel(x, y, r, g, b)
        r, g, b = PLAYER_COLOR
        unicorn.set_pixel(self.ai.player.x, self.ai.player.y, r, g, b)
        unicorn.show()

    def print_scroll(self):
        scrollphat.clear()
        for x, col in enumerate(self.field.buffer):
            for y, pixel in enumerate(col):
                scrollphat.set_pixel(x, y, pixel)
        scrollphat.set_pixel(self.ai.player.x, self.ai.player.y, 1)
        if SCREEN_TYPE == "SCROLL": scrollphat.update()
        if SCREEN_TYPE == "SCROLLHD": scrollphat.show()

    def print_field(self):
        for col in self.field.buffer:
            print(col, self.field.gap_buffer[-1])

class AI:
    def __init__(self, field, move_weight={-1: -1, 0: 0, 1: -1}, next_layer_weight=3):
        self.player = Player()
        self.field = field
        self.move_weight = move_weight  # Every decision is dependent on these numbers. Change these and you will get better or worse AI.
        self.next_layer_weight = next_layer_weight  # This is also very important.

    """
    Filter possible moves

    This is simple fundtion to figure out where player can move
    """
    def filter_moves(self, layer, player_y):
        possible_moves = [-1, 0, 1]  # List all possible moves player can take; move up, move down or keep the place

        # Detect that the player is next to the screen edge
        if player_y == SCREEN_HEIGHT - 1:
            try: possible_moves.remove(1)
            except ValueError: pass
            if layer[player_y - 1] == 1:
                try: possible_moves.remove(-1)
                except ValueError: pass
        elif 1 <= player_y <= 3:
            if layer[player_y - 1] == 1:
                try: possible_moves.remove(-1)
                except ValueError: pass
            if layer[player_y + 1] == 1:
                try: possible_moves.remove(1)
                except ValueError: pass
        elif player_y == 0:
            try: possible_moves.remove(-1)
            except ValueError: pass
            if layer[player_y + 1] == 1:
                try: possible_moves.remove(1)
                except ValueError: pass
        return possible_moves

    """
    Evaluate path

    This function gives score for each path
    """
    def evaluate_path(self, moves):
        score = 0

        for move in moves:
            score += (self.move_weight[move] + self.next_layer_weight)
        return score

    """
    Move player

    Uses some available AI-algo to figure out the next move
    """
    def move(self):
        self.player_coords = {'x': self.player.x, 'y': self.player.y}
        #self.player.y += self.next_move()[0]
        #self.player.y += self.better_move(50)[0]
        next_moves = self.even_better_move(3, [])
        #next_moves = self.better_move(50)
        if len(next_moves) > 0:
            self.player.y += next_moves[0]
        else:
            raise GameOver

    """
    Myopic AI

    This algorithm is very very near-sighted, and it's flying in fog, and it's raining... wait, can that happen? *wanders off to r/askreddit/*
    """
    def next_move(self):
        next_col = self.field.buffer[self.player.x + 1]
        if next_col[self.player.y] == 1:
            if self.player.y < 4:
                if next_col[self.player.y + 1] == 0:
                    return [1]
            if self.player.y > 0:
                if next_col[self.player.y - 1] == 0:
                    return [-1]

    """
    Random path finding AI

    This algorithm takes all the possible moves and in the case of selection,
    takes random direction and continues.

    Algorithm is given number of iterations (computing time) to calculate paths.
    Each path is evaluated based on pretermined weights (more direct path is better,
    path that gives player best chances of survival is also better)

    This is better AI than nothing at all, but it still misses a lot of cleverness.
    It always choses random path, but nothing prevents it to taking the same path twice.
    So, this AI is waisting valuable computing time, most likely comparing very similar paths.
    """
    def better_move(self, iterations):
        best = {"score": 0, "moves": []}
        for iteration in range(iterations):  # Iterations to find most of the move combinations
            self.player_coords = {'x': self.player.x, 'y': self.player.y}
            moves = []
            for layer in self.field.buffer[self.player_coords['x']:]:
                if layer[self.player_coords['y']] == 1:
                    break
                possible_moves = self.filter_moves(layer, self.player_coords['y'])
                move = random.choice(possible_moves)

                self.player_coords['y'] += move
                moves.append(move)
                self.player_coords['x'] += 1

            score = self.evaluate_path(moves)
            if best["score"] < score:
                best["score"] = score
                best["moves"] = moves

        return best["moves"]

    """
    Recursive AI

    Now this starts to be something you can call efficient. (If you disagree, take a look at the other two)

    Idea is simple:
        1) We take a path that has no intersections and continue until we have to make a choise or until player hit's a wall
        2) Start new instances of this function for each choise and wait for them to return their moves.
        3) Calculate scores for the routes we received.
        4) Move player according to the best move we know.

    It is quite clear that if we calculate this after every step player takes, and the field is giving us lots of room to move,
    this recursive tactic spirals out of control pretty fast. It's dependent on how much memory and CPU-power we have, but there
    should be some way of controlling this recursion.
    Thus, the only parameter given to the algorithm is very very very important. It behaves like TTL-value in network packets.
    The depth_limit is reduced by one each time it is passed onwards and checked if it reaches zero. If it does, the recursion ends
    and the result is passed to the parent-function.

    By increasing the depth-limit, more possible routes will be evaluated and also slower the code execution will be.

    """
    def even_better_move(self, depth_limit, moves=[]):
        depth_limit -= 1
        if depth_limit == 0:
            return moves
        else:
            layer = self.field.buffer[self.player_coords['x'] + len(moves)]
            player_y = self.player_coords['y']
            for y_move in moves:
                player_y += y_move
            if layer[player_y] == 1:
                return moves
            possible_moves = self.filter_moves(layer, player_y)

            paths = []
            for move in possible_moves:
                paths.append(self.even_better_move(depth_limit, moves + [move]))

            best_path = max(paths, key=lambda x: self.evaluate_path(x))

            return best_path

def run_game():
    try:
        move_cost = random.randint(-50, 0)
        layer_reward = random.randint(0, 50)
        game = Game(move_weight={-1: move_cost, 0: 0, 1: move_cost}, next_layer_weight=layer_reward, speed=0)
        game.start()
    except GameOver:
        print({"score": game.distance, "move_cost": move_cost, "layer_reward": layer_reward})
    except KeyboardInterrupt:
        print("Quitting")

def study_loop():
    logfile = open("cavejet_AI.log", 'a+')
    record = 0
    history = []
    while True:
        try:
            move_cost = random.randint(-18, -15)
            layer_reward = random.randint(32, 36)
            game = Game(move_weight={-1: move_cost, 0: 0, 1: move_cost}, next_layer_weight=layer_reward, speed=0)
            game.start()
        except GameOver:
            history.append({"score": game.distance, "move_cost": move_cost, "layer_reward": layer_reward})
            logfile.write(str(game.distance) + ',' + str(move_cost) + ',' + str(layer_reward) + '\r\n')
            if record < game.distance:
                record = game.distance
            print("Score:", game.distance, " | Best score:", record)
        except KeyboardInterrupt:
            logfile.close()
            raise

if __name__ == "__main__":
    if STUDY_LOOP:
        study_loop()
    elif LOOP:
        while True:
            run_game()
    else:
        run_game()<commit_msg>Removed returning known bad paths, added handling of no paths returned (all paths hit wall), added fixed weights for normal run.<commit_after>import random
import time

#SCREEN_TYPE = "UNICORN"
#SCREEN_TYPE = "SCROLL"
SCREEN_TYPE = "SCROLLHD"
RGB_ENABLED = False

STUDY_LOOP = False
LOOP = False

TUNNEL_GAP_MIN = 1
TUNNEL_GAP_MAX = 4
TUNNEL_GAP_DIFF_MAX = 1
TUNNEL_MOVE_DIFF_MAX = 1

if SCREEN_TYPE == "UNICORN":
    import unicornhat as unicorn
    unicorn.set_layout(unicorn.AUTO)
    unicorn.rotation(0)
    unicorn.brightness(0.7)
    SCREEN_WIDTH, SCREEN_HEIGHT = unicorn.get_shape()
    screen_size = (SCREEN_WIDTH, SCREEN_HEIGHT)
    RGB_ENABLED = True

    BG_COLOR = [0, 150, 200]
    BG_NOISE = 2
    CAVE_COLOR = [244, 164, 96]
    CAVE_NOISE = 10
    PLAYER_COLOR = [255, 0, 0]

if SCREEN_TYPE == "SCROLL":
    import scrollphat
    SCREEN_WIDTH = 11
    SCREEN_HEIGHT = 5
    scrollphat.set_brightness(1)

if SCREEN_TYPE == "SCROLLHD":
    import scrollphathd as scrollphat
    SCREEN_WIDTH = 17
    SCREEN_HEIGHT = 7
    scrollphat.set_brightness(0.25)

screen_size = (SCREEN_WIDTH, SCREEN_HEIGHT)

class GameOver(Exception):
    def __init__(self):
        print("AI found no possible moves")

class Field:
    def __init__(self, field_size):
        self.buffer = [[0] * field_size[1]] * field_size[0]
        self.gap_buffer = [(1, 3)]

    """
    tunnel_gen - Tunnel Generator
    creates the next column for the tunnel

    Requires two random integers;
        diff_place: in range of +-1, determines where the gap is placed in relation to the last gap
        diff_width: in range of +-1, determines the gap width in relation to the last gap width
    """
    def tunnel_gen(self):
        if self.gap_buffer[-1][0] == 0:                             # Is the current place at screen edge?
            diff_place += random.randint(0, TUNNEL_MOVE_DIFF_MAX)   # Go away or stay at screen edge
        elif self.gap_buffer[-1][0] == SCREEN_HEIGHT - 1:           # Is the current place at screen edge?
            diff_place += random.randint(-TUNNEL_MOVE_DIFF_MAX, 0)  # Go away or stay at screen edge
        else:
            diff_place += random.randint(-TUNNEL_MOVE_DIFF_MAX, TUNNEL_MOVE_DIFF_MAX)  # Not at screen edge, can move freely

        if diff_width == TUNNEL_GAP_MIN:                            # Is gap at minimum?
            diff_width += random.randint(0, TUNNEL_GAP_DIFF_MAX)    # Go larger or stay at same
        if diff_width == TUNNEL_GAP_MAX:                            # Is gap at maximum?
            diff_width += random.randint(-TUNNEL_GAP_DIFF_MAX, 0)   # Go smaller or stay at same
        else:
            diff_width += random.randint(-TUNNEL_GAP_DIFF_MAX, TUNNEL_GAP_DIFF_MAX)   # Adjust freely

        self.gap_buffer.append((self.gap_buffer[-1][0] + diff_place, self.gap_buffer[-1][1] + diff_width))
        if len(self.gap_buffer) > SCREEN_WIDTH: self.gap_buffer.pop(0)

        col = [1] * len(self.buffer[0])
        for pixel_i in range(0, self.gap_buffer[-1][1]):
            try: col[pixel_i + self.gap_buffer[-1][0]] = 0
            except IndexError: pass

        return col

    def update(self):
        new_col = self.tunnel_gen()
        self.buffer.append(new_col)
        self.buffer.pop(0)

class Player:
    def __init__(self):
        self.x = 1
        self.y = 2

class Game:
    def __init__(self, move_weight={-1: -1, 0: 0, 1: -1}, next_layer_weight=3, speed=0.05):
        self.distance = 0
        self.field = Field(screen_size)
        self.ai = AI(self.field, move_weight, next_layer_weight)
        self.speed = speed
        self.run = True

    def start(self):
        while self.run:
            self.step()
            if SCREEN_TYPE == "UNICORN": self.print_unicorn()
            if SCREEN_TYPE == "SCROLL" or SCREEN_TYPE == "SCROLLHD": self.print_scroll()
            time.sleep(self.speed)
        return self.distance

    def step(self):
        self.distance += 1
        self.field.update()
        self.ai.move()
        if self.field.buffer[self.ai.player.x][self.ai.player.y] == 1:
            if SCREEN_TYPE == "UNICORN": self.game_over_unicorn()
            if SCREEN_TYPE == "SCROLL" or SCREEN_TYPE == "SCROLLHD": self.game_over_scroll()

    def game_over_unicorn(self):
        width, height = unicorn.get_shape()
        for x in range(width):
            time.sleep(0.05)
            for y in range(height):
                r, g, b = [200, 0, 0]
                unicorn.set_pixel(x, y, r, g, b)
        time.sleep(0.5)
        self.run = False

    def game_over_scroll(self):
        for i in range(1):
            self.set_checker(0)
            time.sleep(0.5)
            self.set_checker(1)
            time.sleep(0.5)
        self.run = False

    def set_checker(self, offset):
        scrollphat.clear()
        n = offset
        for y in range(SCREEN_HEIGHT):
            for x in range(SCREEN_WIDTH):
                scrollphat.set_pixel(x, y, n % 2 == 0)
                n += 1
        if SCREEN_TYPE == "SCROLL": scrollphat.update()
        if SCREEN_TYPE == "SCROLLHD": scrollphat.show()

    def print_unicorn(self):
        unicorn.clear()
        for x, col in enumerate(self.field.buffer):
            for y, pixel in enumerate(col):
                if pixel: r, g, b = [i + random.randint(0, CAVE_NOISE) for i in CAVE_COLOR]
                else: r, g, b = [i + random.randint(0, BG_NOISE) for i in BG_COLOR]
                unicorn.set_pixel(x, y, r, g, b)
        r, g, b = PLAYER_COLOR
        unicorn.set_pixel(self.ai.player.x, self.ai.player.y, r, g, b)
        unicorn.show()

    def print_scroll(self):
        scrollphat.clear()
        for x, col in enumerate(self.field.buffer):
            for y, pixel in enumerate(col):
                scrollphat.set_pixel(x, y, pixel)
        scrollphat.set_pixel(self.ai.player.x, self.ai.player.y, 1)
        if SCREEN_TYPE == "SCROLL": scrollphat.update()
        if SCREEN_TYPE == "SCROLLHD": scrollphat.show()

    def print_field(self):
        for col in self.field.buffer:
            print(col, self.field.gap_buffer[-1])

class AI:
    def __init__(self, field, move_weight={-1: -1, 0: 0, 1: -1}, next_layer_weight=3):
        self.player = Player()
        self.field = field
        self.move_weight = move_weight  # Every decision is dependent on these numbers. Change these and you will get better or worse AI.
        self.next_layer_weight = next_layer_weight  # This is also very important.

    """
    Filter possible moves

    This is simple fundtion to figure out where player can move
    """
    def filter_moves(self, layer, player_y):
        possible_moves = [-1, 0, 1]  # List all possible moves player can take; move up, move down or keep the place

        # Detect that the player is next to the screen edge
        if player_y == SCREEN_HEIGHT - 1:
            try: possible_moves.remove(1)
            except ValueError: pass
            if layer[player_y - 1] == 1:
                try: possible_moves.remove(-1)
                except ValueError: pass
        elif 1 <= player_y <= 3:
            if layer[player_y - 1] == 1:
                try: possible_moves.remove(-1)
                except ValueError: pass
            if layer[player_y + 1] == 1:
                try: possible_moves.remove(1)
                except ValueError: pass
        elif player_y == 0:
            try: possible_moves.remove(-1)
            except ValueError: pass
            if layer[player_y + 1] == 1:
                try: possible_moves.remove(1)
                except ValueError: pass
        return possible_moves

    """
    Evaluate path

    This function gives score for each path
    """
    def evaluate_path(self, moves):
        score = 0

        for move in moves:
            score += (self.move_weight[move] + self.next_layer_weight)
        return score

    """
    Move player

    Uses some available AI-algo to figure out the next move
    """
    def move(self):
        self.player_coords = {'x': self.player.x, 'y': self.player.y}
        #self.player.y += self.next_move()[0]
        #self.player.y += self.better_move(50)[0]
        next_moves = self.even_better_move(3, [])
        #next_moves = self.better_move(50)
        if len(next_moves) == 0 or next_moves is None:
            raise GameOver
        else:
            self.player.y += next_moves[0]

    """
    Myopic AI

    This algorithm is very very near-sighted, and it's flying in fog, and it's raining... wait, can that happen? *wanders off to r/askreddit/*
    """
    def next_move(self):
        next_col = self.field.buffer[self.player.x + 1]
        if next_col[self.player.y] == 1:
            if self.player.y < 4:
                if next_col[self.player.y + 1] == 0:
                    return [1]
            if self.player.y > 0:
                if next_col[self.player.y - 1] == 0:
                    return [-1]

    """
    Random path finding AI

    This algorithm takes all the possible moves and in the case of selection,
    takes random direction and continues.

    Algorithm is given number of iterations (computing time) to calculate paths.
    Each path is evaluated based on pretermined weights (more direct path is better,
    path that gives player best chances of survival is also better)

    This is better AI than nothing at all, but it still misses a lot of cleverness.
    It always choses random path, but nothing prevents it to taking the same path twice.
    So, this AI is waisting valuable computing time, most likely comparing very similar paths.
    """
    def better_move(self, iterations):
        best = {"score": 0, "moves": []}
        for iteration in range(iterations):  # Iterations to find most of the move combinations
            self.player_coords = {'x': self.player.x, 'y': self.player.y}
            moves = []
            for layer in self.field.buffer[self.player_coords['x']:]:
                if layer[self.player_coords['y']] == 1:
                    break
                possible_moves = self.filter_moves(layer, self.player_coords['y'])
                move = random.choice(possible_moves)

                self.player_coords['y'] += move
                moves.append(move)
                self.player_coords['x'] += 1

            score = self.evaluate_path(moves)
            if best["score"] < score:
                best["score"] = score
                best["moves"] = moves

        return best["moves"]

    """
    Recursive AI

    Now this starts to be something you can call efficient. (If you disagree, take a look at the other two)

    Idea is simple:
        1) We take a path that has no intersections and continue until we have to make a choise or until player hit's a wall
        2) Start new instances of this function for each choise and wait for them to return their moves.
        3) Calculate scores for the routes we received.
        4) Move player according to the best move we know.

    It is quite clear that if we calculate this after every step player takes, and the field is giving us lots of room to move,
    this recursive tactic spirals out of control pretty fast. It's dependent on how much memory and CPU-power we have, but there
    should be some way of controlling this recursion.
    Thus, the only parameter given to the algorithm is very very very important. It behaves like TTL-value in network packets.
    The depth_limit is reduced by one each time it is passed onwards and checked if it reaches zero. If it does, the recursion ends
    and the result is passed to the parent-function.

    By increasing the depth-limit, more possible routes will be evaluated and also slower the code execution will be.

    """
    def even_better_move(self, depth_limit, moves=[]):
        depth_limit -= 1
        if depth_limit == 0:            # Hit depth limit without hitting the wall
            return moves                # Returing route
        else:
            layer = self.field.buffer[self.player_coords['x'] + len(moves)]
            player_y = self.player_coords['y']
            for y_move in moves:
                player_y += y_move
            if layer[player_y] == 1:    # Hit a wall with this route
                return None            # Returning None
            possible_moves = self.filter_moves(layer, player_y)

            paths = []
            for move in possible_moves:
                returned_path = self.even_better_move(depth_limit, moves + [move])
                if returned_path is not None:       # Path did not hit the wall
                    paths.append(returned_path)     # Adding path to list of possible paths

            if len(paths) > 0:
                return max(paths, key=lambda x: self.evaluate_path(x))  # Selecting the best path using evaluation-function
            else:
                return None        # Found no paths that would not hit the wall

def run_game():
    try:
        move_cost = -16
        layer_reward = 34
        game = Game(move_weight={-1: move_cost, 0: 0, 1: move_cost}, next_layer_weight=layer_reward, speed=0)
        game.start()
    except GameOver:
        print({"score": game.distance, "move_cost": move_cost, "layer_reward": layer_reward})
    except KeyboardInterrupt:
        print("Quitting")

def study_loop():
    logfile = open("cavejet_AI.log", 'a+')
    record = 0
    history = []
    while True:
        try:
            move_cost = random.randint(-18, -15)
            layer_reward = random.randint(32, 36)
            game = Game(move_weight={-1: move_cost, 0: 0, 1: move_cost}, next_layer_weight=layer_reward, speed=0)
            game.start()
        except GameOver:
            history.append({"score": game.distance, "move_cost": move_cost, "layer_reward": layer_reward})
            logfile.write(str(game.distance) + ',' + str(move_cost) + ',' + str(layer_reward) + '\r\n')
            if record < game.distance:
                record = game.distance
            print("Score:", game.distance, " | Best score:", record)
        except KeyboardInterrupt:
            logfile.close()
            raise

if __name__ == "__main__":
    if STUDY_LOOP:
        study_loop()
    elif LOOP:
        while True:
            run_game()
    else:
        run_game()<|endoftext|>


<commit_before>import random
import time

#SCREEN_TYPE = "UNICORN"
#SCREEN_TYPE = "SCROLL"
SCREEN_TYPE = "SCROLLHD"
RGB_ENABLED = False

if SCREEN_TYPE == "UNICORN":
    import unicornhat as unicorn
    unicorn.set_layout(unicorn.AUTO)
    unicorn.rotation(0)
    unicorn.brightness(0.7)
    SCREEN_WIDTH, SCREEN_HEIGHT = unicorn.get_shape()
    screen_size = (SCREEN_WIDTH, SCREEN_HEIGHT)
    RGB_ENABLED = True

    BG_COLOR = [0, 150, 200]
    BG_NOISE = 2
    CAVE_COLOR = [244, 164, 96]
    CAVE_NOISE = 10
    PLAYER_COLOR = [255, 0, 0]

if SCREEN_TYPE == "SCROLL":
    import scrollphat
    SCREEN_WIDTH = 11
    SCREEN_HEIGHT = 5
    scrollphat.set_brightness(1)

if SCREEN_TYPE == "SCROLLHD":
    import scrollphathd as scrollphat
    SCREEN_WIDTH = 17
    SCREEN_HEIGHT = 7
    scrollphat.set_brightness(0.5)

screen_size = (SCREEN_WIDTH, SCREEN_HEIGHT)

class GameOver(Exception):
    def __init__(self):
        print("AI found no possible moves")

class Field:
    def __init__(self, field_size):
        self.buffer = [[0] * field_size[0]] * field_size[1]
        self.gap_buffer = [(1, 3)]

    """
    tunnel_gen - Tunnel Generator
    creates the next column for the tunnel

    Requires two random integers;
        gap_place: in range of +-1, determines where the gap is placed in relation to the last gap
        gap_width: in range of 2-5, determines the gap width
    """
    def tunnel_gen(self):
        diff_place = -99
        while (self.gap_buffer[-1][0] + diff_place < 0) or (self.gap_buffer[-1][0] + diff_place > 4):
            diff_place = random.randint(-1, 1)
        diff_width = random.randint(-1, 1)
        if diff_width + self.gap_buffer[-1][1] <= 1: diff_width = 1
        if diff_width + self.gap_buffer[-1][1] > screen_size[0]: diff_width = -1

        self.gap_buffer.append((self.gap_buffer[-1][0] + diff_place, self.gap_buffer[-1][1] + diff_width))
        if len(self.gap_buffer) > SCREEN_WIDTH: self.gap_buffer.pop(0)

        col = [1] * len(self.buffer[0])
        for pixel_i in range(0, self.gap_buffer[-1][1]):
            try: col[pixel_i + self.gap_buffer[-1][0]] = 0
            except IndexError: pass

        return col

    def update(self):
        new_col = self.tunnel_gen()
        self.buffer.append(new_col)
        self.buffer.pop(0)

class Player:
    def __init__(self):
        self.x = 1
        self.y = 2

class Game:
    def __init__(self):
        self.distance = 0
        self.field = Field(screen_size)
        self.ai = AI(self.field)
        self.speed = 0.05
        self.run = True

    def start(self):
        while self.run:
            self.step()
            if SCREEN_TYPE == "UNICORN": self.print_unicorn()
            if SCREEN_TYPE == "SCROLL" or SCREEN_TYPE == "SCROLLHD": self.print_scroll()
            time.sleep(self.speed)
        return self.distance

    def step(self):
        self.distance += 1
        self.field.update()
        self.ai.move()
        if self.field.buffer[self.ai.player.x][self.ai.player.y] == 1:
            if SCREEN_TYPE == "UNICORN": self.game_over_unicorn()
            if SCREEN_TYPE == "SCROLL" or SCREEN_TYPE == "SCROLLHD": self.game_over_scroll()

    def game_over_unicorn(self):
        width, height = unicorn.get_shape()
        for x in range(width):
            time.sleep(0.05)
            for y in range(height):
                r, g, b = [200, 0, 0]
                unicorn.set_pixel(x, y, r, g, b)
        time.sleep(0.5)
        self.run = False

    def game_over_scroll(self):
        for i in range(1):
            self.set_checker(0)
            time.sleep(0.5)
            self.set_checker(1)
            time.sleep(0.5)
        self.run = False

    def set_checker(self, offset):
        scrollphat.clear()
        n = offset
        for y in range(SCREEN_HEIGHT):
            for x in range(SCREEN_WIDTH):
                scrollphat.set_pixel(x, y, n % 2 == 0)
                n += 1
        if SCREEN_TYPE == "SCROLL": scrollphat.update()
        if SCREEN_TYPE == "SCROLLHD": scrollphat.show()

    def print_unicorn(self):
        unicorn.clear()
        for x, col in enumerate(self.field.buffer):
            for y, pixel in enumerate(col):
                if pixel: r, g, b = [i + random.randint(0, CAVE_NOISE) for i in CAVE_COLOR]
                else: r, g, b = [i + random.randint(0, BG_NOISE) for i in BG_COLOR]
                unicorn.set_pixel(x, y, r, g, b)
        r, g, b = PLAYER_COLOR
        unicorn.set_pixel(self.ai.player.x, self.ai.player.y, r, g, b)
        unicorn.show()

    def print_scroll(self):
        scrollphat.clear()
        for x, col in enumerate(self.field.buffer):
            for y, pixel in enumerate(col):
                scrollphat.set_pixel(x, y, pixel)
        scrollphat.set_pixel(self.ai.player.x, self.ai.player.y, 1)
        if SCREEN_TYPE == "SCROLL": scrollphat.update()
        if SCREEN_TYPE == "SCROLLHD": scrollphat.show()

    def print_field(self):
        for col in self.field.buffer:
            print(col, self.field.gap_buffer[-1])

class AI:
    def __init__(self, field):
        self.player = Player()
        self.field = field

    """
    Filter possible moves

    This is simple fundtion to figure out where player can move
    """
    def filter_moves(self, layer, player_y):
        possible_moves = [-1, 0, 1]  # List all possible moves player can take; move up, move down or keep the place

        # Detect that the player is next to the screen edge
        if player_y == SCREEN_HEIGHT - 1:
            try: possible_moves.remove(1)
            except ValueError: pass
            if layer[player_y - 1] == 1:
                try: possible_moves.remove(-1)
                except ValueError: pass
        elif 1 <= player_y <= 3:
            if layer[player_y - 1] == 1:
                try: possible_moves.remove(-1)
                except ValueError: pass
            if layer[player_y + 1] == 1:
                try: possible_moves.remove(1)
                except ValueError: pass
        elif player_y == 0:
            try: possible_moves.remove(-1)
            except ValueError: pass
            if layer[player_y + 1] == 1:
                try: possible_moves.remove(1)
                except ValueError: pass
        return possible_moves

    """
    Evaluate path

    This function gives score for each path
    """
    def evaluate_path(self, moves):
        move_weight = {-1: -1, 0: 0, 1: -1}  # Every decision is dependent on these numbers. Change these and you will get better or worse AI.
        next_layer_weight = 3  # This is also very important.
        score = 0

        for move in moves:
            score += (move_weight[move] + next_layer_weight)
        return score

    """
    Move player

    Uses some available AI-algo to figure out the next move
    """
    def move(self):
        self.player_coords = {'x': self.player.x, 'y': self.player.y}
        #self.player.y += self.next_move()[0]
        #self.player.y += self.better_move(50)[0]
        next_moves = self.even_better_move(3, [])
        #next_moves = self.better_move(50)
        if len(next_moves) > 0:
            self.player.y += next_moves[0]
        else:
            raise GameOver

    """
    Myopic AI

    This algorithm is very very near-sighted, and it's flying in fog, and it's raining... wait, can that happen? *wanders off to r/askreddit/*
    """
    def next_move(self):
        next_col = self.field.buffer[self.player.x + 1]
        if next_col[self.player.y] == 1:
            if self.player.y < 4:
                if next_col[self.player.y + 1] == 0:
                    return [1]
            if self.player.y > 0:
                if next_col[self.player.y - 1] == 0:
                    return [-1]

    """
    Random path finding AI

    This algorithm takes all the possible moves and in the case of selection,
    takes random direction and continues.

    Algorithm is given number of iterations (computing time) to calculate paths.
    Each path is evaluated based on pretermined weights (more direct path is better,
    path that gives player best chances of survival is also better)

    This is better AI than nothing at all, but it still misses a lot of cleverness.
    It always choses random path, but nothing prevents it to taking the same path twice.
    So, this AI is waisting valuable computing time, most likely comparing very similar paths.
    """
    def better_move(self, iterations):
        best = {"score": 0, "moves": []}
        for iteration in range(iterations):  # Iterations to find most of the move combinations
            self.player_coords = {'x': self.player.x, 'y': self.player.y}
            moves = []
            for layer in self.field.buffer[self.player_coords['x']:]:
                if layer[self.player_coords['y']] == 1:
                    break
                possible_moves = self.filter_moves(layer, self.player_coords['y'])
                move = random.choice(possible_moves)

                self.player_coords['y'] += move
                moves.append(move)
                self.player_coords['x'] += 1

            score = self.evaluate_path(moves)
            if best["score"] < score:
                best["score"] = score
                best["moves"] = moves

        return best["moves"]

    """
    Recursive AI

    Now this starts to be something you can call efficient. (If you disagree, take a look at the other two)

    Idea is simple:
        1) We take a path that has no intersections and continue until we have to make a choise or until player hit's a wall
        2) Start new instances of this function for each choise and wait for them to return their moves.
        3) Calculate scores for the routes we received.
        4) Move player according to the best move we know.

    It is quite clear that if we calculate this after every step player takes, and the field is giving us lots of room to move,
    this recursive tactic spirals out of control pretty fast. It's dependent on how much memory and CPU-power we have, but there
    should be some way of controlling this recursion.
    Thus, the only parameter given to the algorithm is very very very important. It behaves like TTL-value in network packets.
    The depth_limit is reduced by one each time it is passed onwards and checked if it reaches zero. If it does, the recursion ends
    and the result is passed to the parent-function.

    By increasing the depth-limit, more possible routes will be evaluated and also slower the code execution will be.

    """
    def even_better_move(self, depth_limit, moves=[]):
        depth_limit -= 1
        if depth_limit == 0:
            return moves
        else:
            layer = self.field.buffer[self.player_coords['x'] + len(moves)]
            player_y = self.player_coords['y']
            for y_move in moves:
                player_y += y_move
            if layer[player_y] == 1:
                return moves
            possible_moves = self.filter_moves(layer, player_y)

            paths = []
            for move in possible_moves:
                paths.append(self.even_better_move(depth_limit, moves + [move]))

            best_path = max(paths, key=lambda x: self.evaluate_path(x))

            return best_path

if __name__ == "__main__":
    record = 0
    try:
        while True:
            game = Game()
            distance = game.start()
            if record < distance: record = distance
    except GameOver:
        print("Best score:", record)
<commit_msg>Moved AI path evaluation weights to parameters<commit_after>import random
import time

#SCREEN_TYPE = "UNICORN"
#SCREEN_TYPE = "SCROLL"
SCREEN_TYPE = "SCROLLHD"
RGB_ENABLED = False

if SCREEN_TYPE == "UNICORN":
    import unicornhat as unicorn
    unicorn.set_layout(unicorn.AUTO)
    unicorn.rotation(0)
    unicorn.brightness(0.7)
    SCREEN_WIDTH, SCREEN_HEIGHT = unicorn.get_shape()
    screen_size = (SCREEN_WIDTH, SCREEN_HEIGHT)
    RGB_ENABLED = True

    BG_COLOR = [0, 150, 200]
    BG_NOISE = 2
    CAVE_COLOR = [244, 164, 96]
    CAVE_NOISE = 10
    PLAYER_COLOR = [255, 0, 0]

if SCREEN_TYPE == "SCROLL":
    import scrollphat
    SCREEN_WIDTH = 11
    SCREEN_HEIGHT = 5
    scrollphat.set_brightness(1)

if SCREEN_TYPE == "SCROLLHD":
    import scrollphathd as scrollphat
    SCREEN_WIDTH = 17
    SCREEN_HEIGHT = 7
    scrollphat.set_brightness(0.5)

screen_size = (SCREEN_WIDTH, SCREEN_HEIGHT)

class GameOver(Exception):
    def __init__(self):
        print("AI found no possible moves")

class Field:
    def __init__(self, field_size):
        self.buffer = [[0] * field_size[0]] * field_size[1]
        self.gap_buffer = [(1, 3)]

    """
    tunnel_gen - Tunnel Generator
    creates the next column for the tunnel

    Requires two random integers;
        gap_place: in range of +-1, determines where the gap is placed in relation to the last gap
        gap_width: in range of 2-5, determines the gap width
    """
    def tunnel_gen(self):
        diff_place = -99
        while (self.gap_buffer[-1][0] + diff_place < 0) or (self.gap_buffer[-1][0] + diff_place > 4):
            diff_place = random.randint(-1, 1)
        diff_width = random.randint(-1, 1)
        if diff_width + self.gap_buffer[-1][1] <= 1: diff_width = 1
        if diff_width + self.gap_buffer[-1][1] > screen_size[0]: diff_width = -1

        self.gap_buffer.append((self.gap_buffer[-1][0] + diff_place, self.gap_buffer[-1][1] + diff_width))
        if len(self.gap_buffer) > SCREEN_WIDTH: self.gap_buffer.pop(0)

        col = [1] * len(self.buffer[0])
        for pixel_i in range(0, self.gap_buffer[-1][1]):
            try: col[pixel_i + self.gap_buffer[-1][0]] = 0
            except IndexError: pass

        return col

    def update(self):
        new_col = self.tunnel_gen()
        self.buffer.append(new_col)
        self.buffer.pop(0)

class Player:
    def __init__(self):
        self.x = 1
        self.y = 2

class Game:
    def __init__(self, move_weight={-1: -1, 0: 0, 1: -1}, next_layer_weight=3, speed=0.05):
        self.distance = 0
        self.field = Field(screen_size)
        self.ai = AI(self.field, move_weight, next_layer_weight)
        self.speed = speed
        self.run = True

    def start(self):
        while self.run:
            self.step()
            if SCREEN_TYPE == "UNICORN": self.print_unicorn()
            if SCREEN_TYPE == "SCROLL" or SCREEN_TYPE == "SCROLLHD": self.print_scroll()
            time.sleep(self.speed)
        return self.distance

    def step(self):
        self.distance += 1
        self.field.update()
        self.ai.move()
        if self.field.buffer[self.ai.player.x][self.ai.player.y] == 1:
            if SCREEN_TYPE == "UNICORN": self.game_over_unicorn()
            if SCREEN_TYPE == "SCROLL" or SCREEN_TYPE == "SCROLLHD": self.game_over_scroll()

    def game_over_unicorn(self):
        width, height = unicorn.get_shape()
        for x in range(width):
            time.sleep(0.05)
            for y in range(height):
                r, g, b = [200, 0, 0]
                unicorn.set_pixel(x, y, r, g, b)
        time.sleep(0.5)
        self.run = False

    def game_over_scroll(self):
        for i in range(1):
            self.set_checker(0)
            time.sleep(0.5)
            self.set_checker(1)
            time.sleep(0.5)
        self.run = False

    def set_checker(self, offset):
        scrollphat.clear()
        n = offset
        for y in range(SCREEN_HEIGHT):
            for x in range(SCREEN_WIDTH):
                scrollphat.set_pixel(x, y, n % 2 == 0)
                n += 1
        if SCREEN_TYPE == "SCROLL": scrollphat.update()
        if SCREEN_TYPE == "SCROLLHD": scrollphat.show()

    def print_unicorn(self):
        unicorn.clear()
        for x, col in enumerate(self.field.buffer):
            for y, pixel in enumerate(col):
                if pixel: r, g, b = [i + random.randint(0, CAVE_NOISE) for i in CAVE_COLOR]
                else: r, g, b = [i + random.randint(0, BG_NOISE) for i in BG_COLOR]
                unicorn.set_pixel(x, y, r, g, b)
        r, g, b = PLAYER_COLOR
        unicorn.set_pixel(self.ai.player.x, self.ai.player.y, r, g, b)
        unicorn.show()

    def print_scroll(self):
        scrollphat.clear()
        for x, col in enumerate(self.field.buffer):
            for y, pixel in enumerate(col):
                scrollphat.set_pixel(x, y, pixel)
        scrollphat.set_pixel(self.ai.player.x, self.ai.player.y, 1)
        if SCREEN_TYPE == "SCROLL": scrollphat.update()
        if SCREEN_TYPE == "SCROLLHD": scrollphat.show()

    def print_field(self):
        for col in self.field.buffer:
            print(col, self.field.gap_buffer[-1])

class AI:
    def __init__(self, field, move_weight={-1: -1, 0: 0, 1: -1}, next_layer_weight=3):
        self.player = Player()
        self.field = field
        self.move_weight = move_weight  # Every decision is dependent on these numbers. Change these and you will get better or worse AI.
        self.next_layer_weight = next_layer_weight  # This is also very important.

    """
    Filter possible moves

    This is simple fundtion to figure out where player can move
    """
    def filter_moves(self, layer, player_y):
        possible_moves = [-1, 0, 1]  # List all possible moves player can take; move up, move down or keep the place

        # Detect that the player is next to the screen edge
        if player_y == SCREEN_HEIGHT - 1:
            try: possible_moves.remove(1)
            except ValueError: pass
            if layer[player_y - 1] == 1:
                try: possible_moves.remove(-1)
                except ValueError: pass
        elif 1 <= player_y <= 3:
            if layer[player_y - 1] == 1:
                try: possible_moves.remove(-1)
                except ValueError: pass
            if layer[player_y + 1] == 1:
                try: possible_moves.remove(1)
                except ValueError: pass
        elif player_y == 0:
            try: possible_moves.remove(-1)
            except ValueError: pass
            if layer[player_y + 1] == 1:
                try: possible_moves.remove(1)
                except ValueError: pass
        return possible_moves

    """
    Evaluate path

    This function gives score for each path
    """
    def evaluate_path(self, moves):
        score = 0

        for move in moves:
            score += (self.move_weight[move] + self.next_layer_weight)
        return score

    """
    Move player

    Uses some available AI-algo to figure out the next move
    """
    def move(self):
        self.player_coords = {'x': self.player.x, 'y': self.player.y}
        #self.player.y += self.next_move()[0]
        #self.player.y += self.better_move(50)[0]
        next_moves = self.even_better_move(3, [])
        #next_moves = self.better_move(50)
        if len(next_moves) > 0:
            self.player.y += next_moves[0]
        else:
            raise GameOver

    """
    Myopic AI

    This algorithm is very very near-sighted, and it's flying in fog, and it's raining... wait, can that happen? *wanders off to r/askreddit/*
    """
    def next_move(self):
        next_col = self.field.buffer[self.player.x + 1]
        if next_col[self.player.y] == 1:
            if self.player.y < 4:
                if next_col[self.player.y + 1] == 0:
                    return [1]
            if self.player.y > 0:
                if next_col[self.player.y - 1] == 0:
                    return [-1]

    """
    Random path finding AI

    This algorithm takes all the possible moves and in the case of selection,
    takes random direction and continues.

    Algorithm is given number of iterations (computing time) to calculate paths.
    Each path is evaluated based on pretermined weights (more direct path is better,
    path that gives player best chances of survival is also better)

    This is better AI than nothing at all, but it still misses a lot of cleverness.
    It always choses random path, but nothing prevents it to taking the same path twice.
    So, this AI is waisting valuable computing time, most likely comparing very similar paths.
    """
    def better_move(self, iterations):
        best = {"score": 0, "moves": []}
        for iteration in range(iterations):  # Iterations to find most of the move combinations
            self.player_coords = {'x': self.player.x, 'y': self.player.y}
            moves = []
            for layer in self.field.buffer[self.player_coords['x']:]:
                if layer[self.player_coords['y']] == 1:
                    break
                possible_moves = self.filter_moves(layer, self.player_coords['y'])
                move = random.choice(possible_moves)

                self.player_coords['y'] += move
                moves.append(move)
                self.player_coords['x'] += 1

            score = self.evaluate_path(moves)
            if best["score"] < score:
                best["score"] = score
                best["moves"] = moves

        return best["moves"]

    """
    Recursive AI

    Now this starts to be something you can call efficient. (If you disagree, take a look at the other two)

    Idea is simple:
        1) We take a path that has no intersections and continue until we have to make a choise or until player hit's a wall
        2) Start new instances of this function for each choise and wait for them to return their moves.
        3) Calculate scores for the routes we received.
        4) Move player according to the best move we know.

    It is quite clear that if we calculate this after every step player takes, and the field is giving us lots of room to move,
    this recursive tactic spirals out of control pretty fast. It's dependent on how much memory and CPU-power we have, but there
    should be some way of controlling this recursion.
    Thus, the only parameter given to the algorithm is very very very important. It behaves like TTL-value in network packets.
    The depth_limit is reduced by one each time it is passed onwards and checked if it reaches zero. If it does, the recursion ends
    and the result is passed to the parent-function.

    By increasing the depth-limit, more possible routes will be evaluated and also slower the code execution will be.

    """
    def even_better_move(self, depth_limit, moves=[]):
        depth_limit -= 1
        if depth_limit == 0:
            return moves
        else:
            layer = self.field.buffer[self.player_coords['x'] + len(moves)]
            player_y = self.player_coords['y']
            for y_move in moves:
                player_y += y_move
            if layer[player_y] == 1:
                return moves
            possible_moves = self.filter_moves(layer, player_y)

            paths = []
            for move in possible_moves:
                paths.append(self.even_better_move(depth_limit, moves + [move]))

            best_path = max(paths, key=lambda x: self.evaluate_path(x))

            return best_path

if __name__ == "__main__":
    record = 0
    while True:
        try:
            move_cost = -1
            layer_reward = 3
            game = Game(move_weight={-1: move_cost, 0: 0, 1: move_cost}, next_layer_weight=layer_reward, speed=0)
            distance = game.start()
            if record < distance: record = distance
        except GameOver:
            print("Score:", distance, " | Best score:", record)
            continue
<|endoftext|>


<commit_before>from survey_element import SurveyElement
from question import Question, InputQuestion, UploadQuestion, MultipleChoiceQuestion
from section import Section, RepeatingSection, GroupedSection
from survey import Survey
import utils
from xls2json import SurveyReader
from question_type_dictionary import DEFAULT_QUESTION_TYPE_DICTIONARY

class SurveyElementBuilder(object):
    # we use this CLASSES dict to create questions from dictionaries
    QUESTION_CLASSES = {
        u"" : Question,
        u"input" : InputQuestion,
        u"select" : MultipleChoiceQuestion,
        u"select1" : MultipleChoiceQuestion,
        u"upload" : UploadQuestion,
        }

    SECTION_CLASSES = {
        u"group" : GroupedSection,
        u"repeat" : RepeatingSection,
        u"survey" : Survey,
        }

    def __init__(self, **kwargs):
        self._question_type_dictionary = kwargs.get(
            u"question_type_dictionary",
            DEFAULT_QUESTION_TYPE_DICTIONARY
            )

    def _get_question_class(self, question_type_str):
        question_type = self._question_type_dictionary.get_definition(question_type_str)
        control_dict = question_type.get(Question.CONTROL, {})
        control_tag = control_dict.get(u"tag", u"")
        return self.QUESTION_CLASSES[control_tag]

    def _create_question_from_dict(self, d):
        """
        This function returns None for unrecognized types.
        """
        question_type_str = d[Question.TYPE]
        d_copy = d.copy()

        if question_type_str.startswith(u"select all that apply"):
            self._add_none_option_to_select_all_that_apply(d_copy)

        # hack job right here to get this to work
        if question_type_str.endswith(u" or specify other"):
            question_type_str = question_type_str[:len(question_type_str)-len(u" or specify other")]
            d_copy[Question.TYPE] = question_type_str
            self._add_other_option_to_multiple_choice_question(d_copy)
            return [self._create_question_from_dict(d_copy),
                    self._create_specify_other_question_from_dict(d_copy)]
        question_class = self._get_question_class(question_type_str)
        if question_class: return question_class(**d_copy)
        return []

    def _add_other_option_to_multiple_choice_question(self, d):
        # ideally, we'd just be pulling from children
        choice_list = d.get(u"choices", d.get(u"children", []))
        assert len(choice_list)>0, "There should be choices for this question."
        other_choice = {
            u"name" : u"other",
            u"label" : u"Other",
            }
        if other_choice not in choice_list:
            choice_list.append(other_choice)

    def _add_none_option_to_select_all_that_apply(self, d_copy):
        choice_list = d_copy.get(u"choices", d_copy.get(u"children", []))
        assert len(choice_list)>0, "There should be choices for this question."
        none_choice = {
            u"name" : u"none",
            u"label" : u"None",
            }
        if none_choice not in choice_list:
            choice_list.append(none_choice)
            none_constraint = u"(.='none' or not(selected(., 'none')))"
            if u"bind" not in d_copy: d_copy[u"bind"] = {}
            if u"constraint" in d_copy[u"bind"]:
                d_copy[u"bind"][u"constraint"] += " and " + none_constraint
            else:
                d_copy[u"bind"][u"constraint"] = none_constraint

    def _create_specify_other_question_from_dict(self, d):
        kwargs = {
            Question.TYPE : u"text",
            Question.NAME : u"%s_other" % d[Question.NAME],
            Question.LABEL : u"Specify other.",
            Question.BIND : {u"relevant" : u"selected(../%s, 'other')" % d[Question.NAME]},
            }
        return InputQuestion(**kwargs)

    def _create_section_from_dict(self, d):
        d_copy = d.copy()
        children = d_copy.pop(Section.CHILDREN)
        section_class = self.SECTION_CLASSES[d_copy[Section.TYPE]]
        result = section_class(**d_copy)
        for child in children:
            survey_element = self.create_survey_element_from_dict(child)
            if survey_element: result.add_child(survey_element)
        return result

    def _create_loop_from_dict(self, d):
        d_copy = d.copy()
        d_copy.pop(u"children", "")
        d_copy.pop(u"columns", "")
        result = GroupedSection(**d_copy)

        # columns is a left over from when this was
        # create_table_from_dict, I will need to clean this up
        for loop_item in d[u"columns"]:
            kwargs = {
                Section.NAME : loop_item.get(Section.NAME, u""),
                Section.LABEL : loop_item.get(Section.LABEL, u""),
                }
            # if this is a none option for a select all that apply
            # question then we should skip adding it to the result
            if kwargs[Section.NAME]=="none": continue

            column = GroupedSection(**kwargs)
            for child in d[SurveyElement.CHILDREN]:
                question_dict = self._create_question_dict_from_template_and_info(child, loop_item)
                question = self.create_survey_element_from_dict(question_dict)
                column.add_child(question)
            result.add_child(column)
        if result.get_name()!=u"": return result
        return result.get_children()

    def _create_question_dict_from_template_and_info(self, question_template, info):
        # if the label in info has multiple languages setup a
        # dictionary by language to do substitutions.
        if type(info[u"label"])==dict:
            info_by_lang = dict(
                [(lang, {u"name" : info[u"name"], u"label" : info[u"label"][lang]}) for lang in info[u"label"].keys()]
                )

        result = question_template.copy()
        for key in result.keys():
            if type(result[key])==unicode:
                result[key] = result[key] % info
            elif type(result[key])==dict:
                result[key] = result[key].copy()
                for key2 in result[key].keys():
                    if type(info[u"label"])==dict:
                        result[key][key2] = result[key][key2] % info_by_lang.get(key2, info)
                    else:
                        result[key][key2] = result[key][key2] % info
        return result

    def create_survey_element_from_dict(self, d):
        if d[SurveyElement.TYPE] in self.SECTION_CLASSES:
            return self._create_section_from_dict(d)
        elif d[SurveyElement.TYPE]==u"loop":
            return self._create_loop_from_dict(d)
        elif d[SurveyElement.TYPE]==u"include":
            path = d[SurveyElement.NAME]
            if path.endswith(".xls"):
                full_survey = create_survey_from_xls(path)
                return full_survey.get_children()
            elif path.endswith(".json"):
                full_survey = create_survey_element_from_json(path)
                return full_survey.get_children()
        else:
            return self._create_question_from_dict(d)

    def create_survey_element_from_json(self, str_or_path):
        d = utils.get_pyobj_from_json(str_or_path)
        return self.create_survey_element_from_dict(d)

def create_survey_element_from_dict(d):
    builder = SurveyElementBuilder()
    return builder.create_survey_element_from_dict(d)

def create_survey_element_from_json(str_or_path):
    d = utils.get_pyobj_from_json(str_or_path)
    return create_survey_element_from_dict(d)

def create_survey_from_xls(path):
    """
    Interestingly enough this behaves differently than a json dump and
    create survey element from json. This is because to questions that
    share the same choice list cannot share the same choice list in
    json. This is definitely something to think about.
    """
    excel_reader = SurveyReader(path)
    d = excel_reader.to_dict()
    return create_survey_element_from_dict(d)
<commit_msg>Fixed bug, questions created by the builder will now point to the correct question type dictionary, before every question pointed to the default dictionary.<commit_after>from survey_element import SurveyElement
from question import Question, InputQuestion, UploadQuestion, MultipleChoiceQuestion
from section import Section, RepeatingSection, GroupedSection
from survey import Survey
import utils
from xls2json import SurveyReader
from question_type_dictionary import DEFAULT_QUESTION_TYPE_DICTIONARY

class SurveyElementBuilder(object):
    # we use this CLASSES dict to create questions from dictionaries
    QUESTION_CLASSES = {
        u"" : Question,
        u"input" : InputQuestion,
        u"select" : MultipleChoiceQuestion,
        u"select1" : MultipleChoiceQuestion,
        u"upload" : UploadQuestion,
        }

    SECTION_CLASSES = {
        u"group" : GroupedSection,
        u"repeat" : RepeatingSection,
        u"survey" : Survey,
        }

    def __init__(self, **kwargs):
        self._question_type_dictionary = kwargs.get(
            u"question_type_dictionary",
            DEFAULT_QUESTION_TYPE_DICTIONARY
            )

    def _get_question_class(self, question_type_str):
        question_type = self._question_type_dictionary.get_definition(question_type_str)
        control_dict = question_type.get(Question.CONTROL, {})
        control_tag = control_dict.get(u"tag", u"")
        return self.QUESTION_CLASSES[control_tag]

    def _create_question_from_dict(self, d):
        """
        This function returns None for unrecognized types.
        """
        question_type_str = d[Question.TYPE]
        d_copy = d.copy()

        if question_type_str.startswith(u"select all that apply"):
            self._add_none_option_to_select_all_that_apply(d_copy)

        # hack job right here to get this to work
        if question_type_str.endswith(u" or specify other"):
            question_type_str = question_type_str[:len(question_type_str)-len(u" or specify other")]
            d_copy[Question.TYPE] = question_type_str
            self._add_other_option_to_multiple_choice_question(d_copy)
            return [self._create_question_from_dict(d_copy),
                    self._create_specify_other_question_from_dict(d_copy)]
        question_class = self._get_question_class(question_type_str)
        # todo: clean up this spaghetti code
        d_copy[u"question_type_dictionary"] = self._question_type_dictionary
        if question_class: return question_class(**d_copy)
        return []

    def _add_other_option_to_multiple_choice_question(self, d):
        # ideally, we'd just be pulling from children
        choice_list = d.get(u"choices", d.get(u"children", []))
        assert len(choice_list)>0, "There should be choices for this question."
        other_choice = {
            u"name" : u"other",
            u"label" : u"Other",
            }
        if other_choice not in choice_list:
            choice_list.append(other_choice)

    def _add_none_option_to_select_all_that_apply(self, d_copy):
        choice_list = d_copy.get(u"choices", d_copy.get(u"children", []))
        assert len(choice_list)>0, "There should be choices for this question."
        none_choice = {
            u"name" : u"none",
            u"label" : u"None",
            }
        if none_choice not in choice_list:
            choice_list.append(none_choice)
            none_constraint = u"(.='none' or not(selected(., 'none')))"
            if u"bind" not in d_copy: d_copy[u"bind"] = {}
            if u"constraint" in d_copy[u"bind"]:
                d_copy[u"bind"][u"constraint"] += " and " + none_constraint
            else:
                d_copy[u"bind"][u"constraint"] = none_constraint

    def _create_specify_other_question_from_dict(self, d):
        kwargs = {
            Question.TYPE : u"text",
            Question.NAME : u"%s_other" % d[Question.NAME],
            Question.LABEL : u"Specify other.",
            Question.BIND : {u"relevant" : u"selected(../%s, 'other')" % d[Question.NAME]},
            }
        return InputQuestion(**kwargs)

    def _create_section_from_dict(self, d):
        d_copy = d.copy()
        children = d_copy.pop(Section.CHILDREN)
        section_class = self.SECTION_CLASSES[d_copy[Section.TYPE]]
        result = section_class(**d_copy)
        for child in children:
            survey_element = self.create_survey_element_from_dict(child)
            if survey_element: result.add_child(survey_element)
        return result

    def _create_loop_from_dict(self, d):
        d_copy = d.copy()
        d_copy.pop(u"children", "")
        d_copy.pop(u"columns", "")
        result = GroupedSection(**d_copy)

        # columns is a left over from when this was
        # create_table_from_dict, I will need to clean this up
        for loop_item in d[u"columns"]:
            kwargs = {
                Section.NAME : loop_item.get(Section.NAME, u""),
                Section.LABEL : loop_item.get(Section.LABEL, u""),
                }
            # if this is a none option for a select all that apply
            # question then we should skip adding it to the result
            if kwargs[Section.NAME]=="none": continue

            column = GroupedSection(**kwargs)
            for child in d[SurveyElement.CHILDREN]:
                question_dict = self._create_question_dict_from_template_and_info(child, loop_item)
                question = self.create_survey_element_from_dict(question_dict)
                column.add_child(question)
            result.add_child(column)
        if result.get_name()!=u"": return result
        return result.get_children()

    def _create_question_dict_from_template_and_info(self, question_template, info):
        # if the label in info has multiple languages setup a
        # dictionary by language to do substitutions.
        if type(info[u"label"])==dict:
            info_by_lang = dict(
                [(lang, {u"name" : info[u"name"], u"label" : info[u"label"][lang]}) for lang in info[u"label"].keys()]
                )

        result = question_template.copy()
        for key in result.keys():
            if type(result[key])==unicode:
                result[key] = result[key] % info
            elif type(result[key])==dict:
                result[key] = result[key].copy()
                for key2 in result[key].keys():
                    if type(info[u"label"])==dict:
                        result[key][key2] = result[key][key2] % info_by_lang.get(key2, info)
                    else:
                        result[key][key2] = result[key][key2] % info
        return result

    def create_survey_element_from_dict(self, d):
        if d[SurveyElement.TYPE] in self.SECTION_CLASSES:
            return self._create_section_from_dict(d)
        elif d[SurveyElement.TYPE]==u"loop":
            return self._create_loop_from_dict(d)
        elif d[SurveyElement.TYPE]==u"include":
            path = d[SurveyElement.NAME]
            if path.endswith(".xls"):
                full_survey = create_survey_from_xls(path)
                return full_survey.get_children()
            elif path.endswith(".json"):
                full_survey = create_survey_element_from_json(path)
                return full_survey.get_children()
        else:
            return self._create_question_from_dict(d)

    def create_survey_element_from_json(self, str_or_path):
        d = utils.get_pyobj_from_json(str_or_path)
        return self.create_survey_element_from_dict(d)

def create_survey_element_from_dict(d):
    builder = SurveyElementBuilder()
    return builder.create_survey_element_from_dict(d)

def create_survey_element_from_json(str_or_path):
    d = utils.get_pyobj_from_json(str_or_path)
    return create_survey_element_from_dict(d)

def create_survey_from_xls(path):
    """
    Interestingly enough this behaves differently than a json dump and
    create survey element from json. This is because to questions that
    share the same choice list cannot share the same choice list in
    json. This is definitely something to think about.
    """
    excel_reader = SurveyReader(path)
    d = excel_reader.to_dict()
    return create_survey_element_from_dict(d)
<|endoftext|>