Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 20 additions & 18 deletions minesweeper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import collections
import itertools
import operator
from util import *
from .util import *
from functools import reduce

set_ = frozenset

Expand Down Expand Up @@ -176,9 +177,9 @@ def condense_supercells(rules):
cell_rules_map = map_reduce(rules, lambda rule: [(cell, rule) for cell in rule.cells], set_)
# for each 'list of rules appearing in', list of cells that share that ruleset (these cells
# thus only ever appear together in the same rules)
rules_supercell_map = map_reduce(cell_rules_map.iteritems(), lambda (cell, rules): [(rules, cell)], set_)
rules_supercell_map = map_reduce(cell_rules_map.items(), lambda x: [(x[1], x[0])], set_)
# for each original rule, list of 'supercells' appearing in that rule
rule_supercells_map = map_reduce(rules_supercell_map.iteritems(), lambda (rules, cell_): [(rule, cell_) for rule in rules], set_)
rule_supercells_map = map_reduce(rules_supercell_map.items(), lambda x: [(rule, x[1]) for rule in x[0]], set_)

return ([rule.condensed(rule_supercells_map) for rule in rules], rules_supercell_map.values())

Expand Down Expand Up @@ -374,7 +375,7 @@ def combine(self, permu):
"""return a new permutation by combining this permutation with
'permu'
the permutations must be compatible!"""
assert all(permu.mapping[k] == v for k, v in self.mapping.iteritems() if k in permu.mapping)
assert all(permu.mapping[k] == v for k, v in self.mapping.items() if k in permu.mapping)
mapping = dict(self.mapping)
mapping.update(permu.mapping)
return Permutation(mapping)
Expand All @@ -394,13 +395,13 @@ def multiplicity(self):
e.g., N mines in a supercell of M cells has (M choose N) actual
configurations
"""
return product(choose(len(cell_), k) for cell_, k in self.mapping.iteritems())
return product(choose(len(cell_), k) for cell_, k in self.mapping.items())

def _canonical(self):
return tuple(sorted(self.mapping.iteritems(), key=lambda (k, v): hash(k)))
return tuple(sorted(self.mapping.items(), key=lambda x: hash(x[0])))

def __repr__(self):
cell_counts = sorted([(sorted(list(cell)), count) for cell, count in self.mapping.iteritems()])
cell_counts = sorted([(sorted(list(cell)), count) for cell, count in self.mapping.items()])
cell_frags = ['%s:%d' % (','.join(str(c) for c in cell), count) for cell, count in cell_counts]
return '{%s}' % ' '.join(cell_frags)

Expand Down Expand Up @@ -629,7 +630,7 @@ def rereduce(self):

superseded_rules = set()
decompositions = {}
for rule, permu_set in self.permu_map.iteritems():
for rule, permu_set in self.permu_map.items():
decomp = permu_set.decompose()
if len(decomp) > 1:
superseded_rules.add(rule)
Expand Down Expand Up @@ -709,7 +710,7 @@ def __init__(self, ruleset=None):
# the current configuration-in-progress
self.fixed = set()
# subset of ruleset whose permutations are still 'open'
self.free = dict((rule, set(permu_set)) for rule, permu_set in ruleset.permu_map.iteritems())
self.free = dict((rule, set(permu_set)) for rule, permu_set in ruleset.permu_map.items())

# helper function (closure)
self.overlapping_rules = lambda rule: ruleset.cell_rules_map.overlapping_rules(rule)
Expand All @@ -721,15 +722,15 @@ def clone(self):
"""clone this state"""
state = EnumerationState()
state.fixed = set(self.fixed)
state.free = dict((rule, set(permu_set)) for rule, permu_set in self.free.iteritems())
state.free = dict((rule, set(permu_set)) for rule, permu_set in self.free.items())
state.overlapping_rules = self.overlapping_rules
state.compatible_rule_index = self.compatible_rule_index
return state

def build_compatibility_index(self, rspm):
"""build the constraint index"""
index = {}
for rule, permu_set in rspm.iteritems():
for rule, permu_set in rspm.items():
for permu in permu_set:
for rule_ov in self.overlapping_rules(rule):
index[(permu, rule_ov)] = rspm[rule_ov].compatible(permu)
Expand Down Expand Up @@ -845,7 +846,7 @@ def is_static(self):
return len(self.subtallies) == 1

def __iter__(self):
return self.subtallies.iteritems()
return iter(self.subtallies.items())

def normalize(self):
"""normalize sub-tally totals into relative weights such that
Expand All @@ -860,7 +861,7 @@ def collapse(self):
all sub-tallies"""
self.normalize()
collapsed = map_reduce(self.subtallies.values(), lambda subtally: subtally.collapse(), sum)
for entry in collapsed.iteritems():
for entry in collapsed.items():
yield entry

def scale_weights(self, scalefunc):
Expand Down Expand Up @@ -895,7 +896,7 @@ def for_other(num_uncharted_cells, mine_totals):
"""

metacell = UnchartedCell(num_uncharted_cells)
return FrontTally(dict((num_mines, FrontSubtally.mk(k, {metacell: num_mines})) for num_mines, k in mine_totals.iteritems()))
return FrontTally(dict((num_mines, FrontSubtally.mk(k, {metacell: num_mines})) for num_mines, k in mine_totals.items()))

def __repr__(self):
return str(dict(self.subtallies))
Expand All @@ -917,18 +918,18 @@ def add(self, config):
"""add a configuration to the tally"""
mult = config.multiplicity() # weight by multiplicity
self.total += mult
for cell_, n in config.mapping.iteritems():
for cell_, n in config.mapping.items():
self.tally[cell_] += n * mult

def finalize(self):
"""after all configurations have been summed, compute relative
prevalence from totals"""
self.tally = dict((cell_, n / float(self.total)) for cell_, n in self.tally.iteritems())
self.tally = dict((cell_, n / float(self.total)) for cell_, n in self.tally.items())

def collapse(self):
"""helper function for FrontTally.collapse(); emit all cell expected
mine values weighted by this sub-tally's weight"""
for cell_, expected_mines in self.tally.iteritems():
for cell_, expected_mines in self.tally.items():
yield (cell_, self.total * expected_mines)

@staticmethod
Expand Down Expand Up @@ -1064,7 +1065,8 @@ def new_front_total():

tallies = list(tallies) # we need guaranteed iteration order
# iterate over the cartesian product of sub-tallies from each tally
for combination in (combo(e) for e in itertools.product(*tallies)):
for e in itertools.product(*tallies):
combination = combo(e)
num_tallied_mines = sum(s.num_mines for s in combination)
# number of mines lying in the 'other' cells
num_free_mines = at_large_mines - num_tallied_mines
Expand Down
7 changes: 4 additions & 3 deletions util.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import math
import operator
import collections
from functools import reduce

def fact_div(a, b):
"""return a! / b!"""
return product(xrange(b + 1, a + 1)) if a >= b else 1. / fact_div(b, a)
return product(range(b + 1, a + 1)) if a >= b else 1. / fact_div(b, a)

def choose(n, k):
"""return n choose k
Expand All @@ -21,7 +22,7 @@ def peek(iterable):

useful for extracting singletons, or when you're managing iteration
yourself"""
return iter(iterable).next()
return next(iter(iterable))

def product(n):
"""return the product of a set of numbers
Expand Down Expand Up @@ -62,7 +63,7 @@ def map_reduce(data, emitfunc=lambda rec: [(rec,)], reducefunc=lambda v: v):
except ValueError:
k, v = emission[0], None
mapped[k].append(v)
return dict((k, reducefunc(v)) for k, v in mapped.iteritems())
return dict((k, reducefunc(v)) for k, v in mapped.items())

class ImmutableMixin(object):
"""mixin for immutable, hashable objects"""
Expand Down
22 changes: 13 additions & 9 deletions web_demo/manage.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
#!/usr/bin/env python
from django.core.management import execute_manager
try:
import settings # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
import os
import sys

if __name__ == "__main__":
execute_manager(settings)
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'web_demo.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
2 changes: 1 addition & 1 deletion web_demo/minesweepr/static/script/minesweeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ COUNT_FILL = ['blue', 'green', 'red', 'purple', 'brown', 'cyan', 'orange', 'blac
MARGIN = 1;
MINE_RADIUS = .5;
FONT_SIZE = .5;
FONT_OFFSET = ($.browser.mozilla ? .15 : .07);
FONT_OFFSET = .07;
FONT_SCALE_LONG = .8;
HIGHLIGHT_CUR_CELL = 'rgba(0, 0, 0, 0)'; //'rgba(255, 180, 0, .2)';
HIGHLIGHT_NEIGHBOR = 'rgba(255, 220, 255, .2)';
Expand Down
8 changes: 4 additions & 4 deletions web_demo/minesweepr/taskexec.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ def __init__(self, task, *args, **kwargs):
self.kwargs = kwargs

def start(self):
project_root = filter(lambda p: p, sys.path)[0] # sketchy
project_root = next(filter(lambda p: p, sys.path)) # sketchy
self.p = Popen(['python', os.path.join(os.getcwd(), __file__)], cwd=project_root, stdin=PIPE, stdout=PIPE, stderr=PIPE)

threading.Thread.start(self)

def run(self):
payload = {'task': self.taskname, 'args': self.args, 'kwargs': self.kwargs}
try:
out, err = self.p.communicate(ser.dumps(payload))
out, err = self.p.communicate(ser.dumps(payload).encode())
self.result = ((False, err) if err else (True, ser.loads(out)))
except:
# various errors if process is terminated
Expand All @@ -57,7 +57,7 @@ def resolve(self):
if success:
return result
else:
raise Exception('error in task> ' + result)
raise Exception('error in task> ' + result.decode())

def _exec(payload):
taskname = payload['task']
Expand All @@ -70,4 +70,4 @@ def _exec(payload):

if __name__ == "__main__":
sys.path.insert(0, os.getcwd()) # cwd set to django project root dir
print ser.dumps(_exec(ser.load(sys.stdin)))
print(ser.dumps(_exec(ser.load(sys.stdin))))
13 changes: 7 additions & 6 deletions web_demo/minesweepr/templates/_player.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
{% block content_script %}
{% endblock %}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="{{ STATIC_URL }}script/minesweeper.js" type="text/javascript"></script>
<script src="{{ STATIC_URL }}script/player.js" type="text/javascript"></script>
<script src="{{ STATIC_URL }}script/shortcut.js" type="text/javascript"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" integrity="sha384-r2H3/Z2KzIwcAodo3prLbMqa/s1cyh+kByvW1+xEdAt+G0icddK5/pb+yeSf5SjC" crossorigin="anonymous"></script>
{% load static %}
<script src="{% static 'script/minesweeper.js' %}" type="text/javascript"></script>
<script src="{% static 'script/player.js' %}" type="text/javascript"></script>
<script src="{% static 'script/shortcut.js' %}" type="text/javascript"></script>
<script type="text/javascript">

SOLVER_URL = '{% url minesweepr.views.api_solve %}';
SOLVER_URL = '{% url 'minesweepr.views.api_solve' %}';

</script>

Expand Down Expand Up @@ -213,7 +214,7 @@
<table style="height: 100%; width: 10em;">
<tr><td># mines left:</td><td id="num_mines" class="gamestat">&mdash;</td></tr>
<tr><td>total accumulated risk:</td><td id="risk" class="gamestat">&mdash;</td></tr>
<tr id="solving" style="height: 35px;"><td style="vertical-align: top;">solving&hellip;</td><td class="gamestat"><img src="{{ STATIC_URL }}img/loading.gif"></td></tr>
<tr id="solving" style="height: 35px;"><td style="vertical-align: top;">solving&hellip;</td><td class="gamestat"><img src="{% static 'img/loading.gif' %}"></td></tr>
<tr id="solved"><td style="vertical-align: top;">solved in:</td><td class="gamestat"><span id="solve_time"></span></td></tr>
<tr><td colspan="2" height="100%" style="text-align: center; vertical-align: bottom; font-weight: bold; font-size: 48px;">
<span id="win" style="color: green;">WIN</span>
Expand Down
35 changes: 0 additions & 35 deletions web_demo/minesweepr/templates/_query.html

This file was deleted.

1 change: 0 additions & 1 deletion web_demo/minesweepr/templates/query.html

This file was deleted.

21 changes: 7 additions & 14 deletions web_demo/minesweepr/urls.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
from django.conf.urls.defaults import *
from django.conf import settings
from django.urls import path

dynurls = patterns('minesweepr.views',
(r'^api/minesweeper_solve/$', 'api_solve'),
)
from . import views

staticurls = patterns('minesweepr.views',
(r'^player/$', 'template_static'),
(r'^query/$', 'template_static'),
)

urlpatterns = patterns('',
('^%s' % settings.BASE_URL, include(dynurls)),
('^%s' % settings.BASE_STATIC_URL, include(staticurls)),
)
urlpatterns = [
path('player/', views.template_static),
path('query/', views.template_static),
path('api/minesweeper_solve/', views.api_solve, name='minesweepr.views.api_solve'),
]
15 changes: 7 additions & 8 deletions web_demo/minesweepr/views.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.template import RequestContext
from django.shortcuts import render_to_response
from django.shortcuts import render
from django.conf import settings
import json
import logging
import time
from tasks import minesweeper_solve
from taskexec import exec_capped, ExecTimeOut
from .tasks import minesweeper_solve
from .taskexec import exec_capped, ExecTimeOut
import itertools

@csrf_exempt
def api_solve(request):
payload = json.loads(request.raw_post_data)
payload = json.loads(request.body)
logging.debug('>>' + str(payload))

start = time.time()
Expand All @@ -23,7 +22,7 @@ def api_solve(request):
log_result(payload, result, time.time() - start)

logging.debug('<<' + str(result))
return HttpResponse(json.dumps(result), 'text/json')
return JsonResponse(result)

def log_result(payload, result, rtt):
num_rules = len(payload['rules'])
Expand All @@ -42,4 +41,4 @@ def template_static(request):
url = request.path[1:-1]
assert url.startswith(settings.BASE_STATIC_URL)
template = url[len(settings.BASE_STATIC_URL):] + '.html'
return render_to_response(template, {}, context_instance=RequestContext(request))
return render(request, template, {})
Loading