#!/usr/bin/env python # -*- coding: utf-8 -*- # This file is part of A Robot's Conundrum. # # 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. # # 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. # # You should have received a copy of the GNU General Public License along with # A Robot's Conundrum. If not, see . # # ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' # # lasermirror.py # -------------------- # date created : Tue Aug 7 2012 # copyright : (C) 2012 Niels G. W. Serup # maintained by : Niels G. W. Serup """ Management of lasers in rooms of mirrors and targets. """ from __future__ import print_function import math import random import itertools from arobotsconundrum.logic.direction import * import arobotsconundrum.logic.rollingstone as rstone from arobotsconundrum.logic.rollingstone import Blocker import arobotsconundrum.misc as misc class MirrorLeft(object): pass class MirrorRight(object): pass class Lever(object): pass class Target(object): pass class Source(object): def __init__(self, direction): self.__dict__.update(locals()) def generate_simple_playfield(nmirrors): """ Generate a completable 17x17 playfield where: * there are four laser sources, one in each corner + the one in the upper left corner (0, 0) starts in (0, -1) heading down + 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 * there are four laser targets * there are nmirrors mirrors * there are nmirrors levers * all levers are at the wall Return playfield : {(x, y): Target | MirrorLeft | MirrorRight | rstone.Blocker | Lever} """ width, height = 17, 17 playfield = {(0, 0): Source(Down), (width - 1, 0): Source(Left), (width - 1, height - 1): Source(Up), (0, height - 1): Source(Right), (6, 6): Target, (10, 6): Target, (6, 10): Target, (10, 10): Target, (7, 7): rstone.Blocker, (7, 8): rstone.Blocker, (7, 9): rstone.Blocker, (8, 7): rstone.Blocker, (8, 8): rstone.Blocker, (8, 9): rstone.Blocker, (9, 7): rstone.Blocker, (9, 8): rstone.Blocker, (9, 9): rstone.Blocker, } 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( 7, 7, nm, 0, False, False) for pos, direc in stone_playfield.items(): playfield[_adjust(source_direc, width - 1, height - 1, *pos)] \ = random.choice((MirrorLeft, MirrorRight)) succs = (lambda s: lambda d: succ(s(d)))(succs) source_direc = succ(source_direc) 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 = [] for _ in range(nlevers): 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 return playfield 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) def generate_lasers(playfield): """ Generate laser paths. Return [((x, y), direction), ...] """ width, height = 17, 17 sources = ((pos, obj.direction) for pos, obj in filter(lambda posobj: isinstance(posobj[1], Source), playfield.items())) lasers, lasers_flat = [], set() def add(start, end): t = (min(start, end), max(start, end)) if not t in lasers_flat: laser.append(t) lasers_flat.add(t) for start, direc in sources: end = start laser = [] lasers.append(laser) while True: cur = playfield.get(end) if cur is Target: add(start, end) break if cur is Blocker: add(start, end) break if cur in (MirrorLeft, MirrorRight): if (start, end) in ((start, end) for (start, end), direc in lasers_flat): break add(start, end) direc = _mirror_new_direc(cur, direc) start = end new_end = direc.next_pos(end) if new_end[0] < 0 or new_end[1] < 0 or \ new_end[0] >= width or new_end[1] >= height: add(start, new_end) 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] 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))