2012-08-08 18:37:09 +02:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2012-10-12 13:50:02 +02:00
|
|
|
# This file is part of A Robot's Conundrum.
|
2012-08-08 18:37:09 +02:00
|
|
|
#
|
2012-10-12 13:50:02 +02:00
|
|
|
# A Robot's Conundrum is free software: you can redistribute it and/or modify it
|
|
|
|
# under the terms of the GNU General Public License as published by the Free
|
|
|
|
# Software Foundation, either version 3 of the License, or (at your option) any
|
|
|
|
# later version.
|
2012-08-08 18:37:09 +02:00
|
|
|
#
|
2012-10-12 13:50:02 +02:00
|
|
|
# A Robot's Conundrum is distributed in the hope that it will be useful, but
|
|
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
|
|
# details.
|
2012-08-08 18:37:09 +02:00
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License along with
|
2012-10-12 13:50:02 +02:00
|
|
|
# A Robot's Conundrum. If not, see <http://www.gnu.org/licenses/>.
|
2012-08-08 18:37:09 +02:00
|
|
|
#
|
|
|
|
# ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
|
|
|
|
#
|
|
|
|
# lasermirror.py
|
|
|
|
# --------------------
|
2012-08-11 21:47:38 +02:00
|
|
|
# date created : Tue Aug 7 2012
|
|
|
|
# copyright : (C) 2012 Niels G. W. Serup
|
2012-10-12 13:50:02 +02:00
|
|
|
# maintained by : Niels G. W. Serup <ngws@metanohi.name>
|
2012-08-08 18:37:09 +02:00
|
|
|
|
|
|
|
"""
|
|
|
|
Management of lasers in rooms of mirrors and targets.
|
|
|
|
"""
|
|
|
|
|
|
|
|
from __future__ import print_function
|
|
|
|
import math
|
|
|
|
import random
|
|
|
|
import itertools
|
2012-10-12 13:50:02 +02:00
|
|
|
from arobotsconundrum.logic.direction import *
|
|
|
|
import arobotsconundrum.logic.rollingstone as rstone
|
|
|
|
from arobotsconundrum.logic.rollingstone import Blocker
|
|
|
|
import arobotsconundrum.misc as misc
|
2012-08-08 18:37:09 +02:00
|
|
|
|
2012-08-11 21:47:38 +02:00
|
|
|
class MirrorLeft(object):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class MirrorRight(object):
|
2012-08-08 18:37:09 +02:00
|
|
|
pass
|
|
|
|
|
|
|
|
class Lever(object):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class Target(object):
|
|
|
|
pass
|
|
|
|
|
2012-08-12 17:01:58 +02:00
|
|
|
class Source(object):
|
|
|
|
def __init__(self, direction):
|
|
|
|
self.__dict__.update(locals())
|
|
|
|
|
2012-08-08 18:37:09 +02:00
|
|
|
def generate_simple_playfield(nmirrors):
|
|
|
|
"""
|
2012-08-21 18:36:51 +02:00
|
|
|
Generate a completable 17x17 playfield where:
|
2012-08-08 18:37:09 +02:00
|
|
|
* there are four laser sources, one in each corner
|
|
|
|
+ the one in the upper left corner (0, 0) starts in (0, -1) heading down
|
2012-08-21 18:36:51 +02:00
|
|
|
+ the one in the upper right corner (16, 0) starts in (17, 0), heading left
|
|
|
|
+ the one in the lower right corner (16, 16) starts in (16, 17), heading up
|
|
|
|
+ the one in the lower left corner (0, 16) starts in (-1, 16), heading right
|
2012-08-08 18:37:09 +02:00
|
|
|
* there are four laser targets
|
|
|
|
* there are nmirrors mirrors
|
|
|
|
* there are nmirrors levers
|
|
|
|
* all levers are at the wall
|
|
|
|
|
2012-08-11 21:47:38 +02:00
|
|
|
Return playfield : {(x, y):
|
|
|
|
Target | MirrorLeft | MirrorRight | rstone.Blocker | Lever}
|
2012-08-08 18:37:09 +02:00
|
|
|
"""
|
2012-08-12 17:01:58 +02:00
|
|
|
|
2012-08-21 18:36:51 +02:00
|
|
|
width, height = 17, 17
|
2012-08-12 17:01:58 +02:00
|
|
|
|
|
|
|
playfield = {(0, 0): Source(Down),
|
|
|
|
(width - 1, 0): Source(Left),
|
|
|
|
(width - 1, height - 1): Source(Up),
|
|
|
|
(0, height - 1): Source(Right),
|
|
|
|
(6, 6): Target,
|
2012-08-21 18:36:51 +02:00
|
|
|
(10, 6): Target,
|
|
|
|
(6, 10): Target,
|
|
|
|
(10, 10): Target,
|
2012-08-11 02:22:29 +02:00
|
|
|
(7, 7): rstone.Blocker,
|
|
|
|
(7, 8): rstone.Blocker,
|
2012-08-21 18:36:51 +02:00
|
|
|
(7, 9): rstone.Blocker,
|
2012-08-11 02:22:29 +02:00
|
|
|
(8, 7): rstone.Blocker,
|
|
|
|
(8, 8): rstone.Blocker,
|
2012-08-21 18:36:51 +02:00
|
|
|
(8, 9): rstone.Blocker,
|
|
|
|
(9, 7): rstone.Blocker,
|
|
|
|
(9, 8): rstone.Blocker,
|
|
|
|
(9, 9): rstone.Blocker,
|
2012-08-08 18:37:09 +02:00
|
|
|
}
|
2012-08-11 21:47:38 +02:00
|
|
|
|
2012-08-08 18:37:09 +02:00
|
|
|
succs = lambda d: d
|
|
|
|
source_direc = Up
|
|
|
|
nlevers = nmirrors
|
|
|
|
for missing in range(4, 0, -1):
|
|
|
|
nm = nmirrors / missing
|
|
|
|
nmirrors -= nm
|
|
|
|
stone_playfield, _ = rstone.generate_simple_playfield(
|
2012-08-11 02:22:29 +02:00
|
|
|
7, 7, nm, 0, False, False)
|
2012-08-08 18:37:09 +02:00
|
|
|
for pos, direc in stone_playfield.items():
|
2012-08-12 17:01:58 +02:00
|
|
|
playfield[_adjust(source_direc, width - 1, height - 1, *pos)] \
|
2012-08-11 21:47:38 +02:00
|
|
|
= random.choice((MirrorLeft, MirrorRight))
|
2012-08-08 18:37:09 +02:00
|
|
|
succs = (lambda s: lambda d: succ(s(d)))(succs)
|
|
|
|
source_direc = succ(source_direc)
|
|
|
|
|
2012-08-11 21:47:38 +02:00
|
|
|
occup = set(playfield.keys())
|
|
|
|
|
|
|
|
is_empty = lambda x, y: (x, y) not in occup
|
|
|
|
|
|
|
|
ok_a = lambda y: is_empty(1, y)
|
|
|
|
ok_b = lambda y: is_empty(width - 2, y)
|
|
|
|
ok_c = lambda x: is_empty(x, 1)
|
|
|
|
ok_d = lambda x: is_empty(x, height - 2)
|
|
|
|
no_block = lambda x, y: \
|
|
|
|
all((ok_a(y) if x == 0 else True,
|
|
|
|
ok_b(y) if x == width - 1 else True,
|
|
|
|
ok_c(x) if y == 0 else True,
|
|
|
|
ok_d(x) if y == height - 1 else True))
|
|
|
|
|
|
|
|
emptys = set([(0, y) for y in filter(ok_a, range(height))]
|
|
|
|
+ [(width - 1, y) for y in filter(ok_b, range(height))]
|
|
|
|
+ [(x, 0) for x in filter(ok_c, range(width))]
|
|
|
|
+ [(x, height - 1) for x in filter(ok_d, range(width))]) - occup
|
|
|
|
emptys_full = set(itertools.product(range(width), range(height))) - occup
|
|
|
|
|
|
|
|
emptys = list(emptys)
|
|
|
|
random.shuffle(emptys)
|
|
|
|
emptys = set(emptys)
|
|
|
|
|
|
|
|
is_empty = lambda x, y: (x, y) in emptys_full
|
|
|
|
|
|
|
|
levers = []
|
2012-08-11 02:22:29 +02:00
|
|
|
for _ in range(nlevers):
|
2012-08-11 21:47:38 +02:00
|
|
|
while True:
|
|
|
|
pos = next(iter(emptys))
|
|
|
|
emptys.remove(pos)
|
|
|
|
emptys_full.remove(pos)
|
|
|
|
if no_block(*pos):
|
|
|
|
playfield[pos] = Lever
|
|
|
|
if not all(no_block(*pos) for pos in levers):
|
|
|
|
del playfield[pos]
|
|
|
|
else:
|
|
|
|
levers.append(pos)
|
|
|
|
break
|
|
|
|
|
2012-08-08 18:37:09 +02:00
|
|
|
return playfield
|
|
|
|
|
2012-08-11 21:47:38 +02:00
|
|
|
|
2012-08-08 18:37:09 +02:00
|
|
|
def _adjust(source_direc, w, h, x, y):
|
|
|
|
return {
|
|
|
|
Up: lambda x, y: (x, y),
|
|
|
|
Right: lambda x, y: (w - y, x),
|
|
|
|
Down: lambda x, y: (w - x, h - y),
|
|
|
|
Left: lambda x, y: (y, h - x),
|
|
|
|
}[source_direc](x, y)
|
2012-08-11 21:47:38 +02:00
|
|
|
|
|
|
|
def generate_lasers(playfield):
|
2012-08-12 17:01:58 +02:00
|
|
|
"""
|
|
|
|
Generate laser paths.
|
|
|
|
|
|
|
|
Return [((x, y), direction), ...]
|
|
|
|
"""
|
2012-08-21 18:36:51 +02:00
|
|
|
width, height = 17, 17
|
2012-08-13 16:20:45 +02:00
|
|
|
sources = ((pos, obj.direction) for pos, obj
|
|
|
|
in filter(lambda posobj: isinstance(posobj[1], Source),
|
|
|
|
playfield.items()))
|
2012-08-13 21:42:46 +02:00
|
|
|
lasers, lasers_flat = [], set()
|
2012-08-13 16:20:45 +02:00
|
|
|
def add(start, end):
|
|
|
|
t = (min(start, end), max(start, end))
|
2012-08-13 21:42:46 +02:00
|
|
|
if not t in lasers_flat:
|
|
|
|
laser.append(t)
|
|
|
|
lasers_flat.add(t)
|
2012-08-11 21:47:38 +02:00
|
|
|
for start, direc in sources:
|
|
|
|
end = start
|
2012-08-13 21:42:46 +02:00
|
|
|
laser = []
|
|
|
|
lasers.append(laser)
|
2012-08-11 21:47:38 +02:00
|
|
|
while True:
|
|
|
|
cur = playfield.get(end)
|
|
|
|
if cur is Target:
|
2012-08-13 16:20:45 +02:00
|
|
|
add(start, end)
|
2012-08-11 21:47:38 +02:00
|
|
|
break
|
|
|
|
if cur is Blocker:
|
2012-08-13 16:20:45 +02:00
|
|
|
add(start, end)
|
2012-08-11 21:47:38 +02:00
|
|
|
break
|
|
|
|
if cur in (MirrorLeft, MirrorRight):
|
2012-08-13 21:42:46 +02:00
|
|
|
if (start, end) in ((start, end) for (start, end), direc in lasers_flat):
|
2012-08-11 21:47:38 +02:00
|
|
|
break
|
2012-08-13 16:20:45 +02:00
|
|
|
add(start, end)
|
2012-08-11 21:47:38 +02:00
|
|
|
direc = _mirror_new_direc(cur, direc)
|
|
|
|
start = end
|
|
|
|
new_end = direc.next_pos(end)
|
2012-08-13 16:20:45 +02:00
|
|
|
if new_end[0] < 0 or new_end[1] < 0 or \
|
|
|
|
new_end[0] >= width or new_end[1] >= height:
|
|
|
|
add(start, new_end)
|
2012-08-11 21:47:38 +02:00
|
|
|
break
|
|
|
|
end = new_end
|
|
|
|
return lasers
|
|
|
|
|
|
|
|
def _mirror_new_direc(mirror_type, old_direc):
|
|
|
|
return {Down: (Left, Right),
|
|
|
|
Left: (Down, Up),
|
|
|
|
Up: (Right, Left),
|
|
|
|
Right: (Up, Down)}[old_direc][
|
|
|
|
0 if mirror_type is MirrorLeft else 1]
|
|
|
|
|
2012-08-08 18:37:09 +02:00
|
|
|
def print_playfield(playfield, width, height, hide_directions=False):
|
|
|
|
text = [['·' for _ in range(width)] for _ in range(height)]
|
|
|
|
for (x, y), val in playfield.items():
|
|
|
|
if isDirection(val) and hide_directions:
|
|
|
|
continue
|
|
|
|
text[y][x] = '%' if val is rstone.Blocker \
|
|
|
|
else 'x' if val is Mirror \
|
|
|
|
else 'L' if val is Lever \
|
|
|
|
else 'T' if val is Target else 'N'
|
|
|
|
print('\n'.join(''.join(line) for line in text))
|
|
|
|
|