From 7666891baa976d3ffde7e505999efd8e47e69f21 Mon Sep 17 00:00:00 2001 From: Niels Serup Date: Wed, 8 Aug 2012 18:37:09 +0200 Subject: [PATCH] Fixed a few bugs and added laser mirror room generator. --- robotgame/logic/colourboxes.py | 4 ++ robotgame/logic/direction.py | 10 ++- robotgame/logic/lasermirror.py | 116 ++++++++++++++++++++++++++++++++ robotgame/logic/rollingstone.py | 18 +++-- tests/lasermirror_tests.py | 15 +++++ tests/rollingstone_tests.py | 6 ++ 6 files changed, 157 insertions(+), 12 deletions(-) create mode 100644 robotgame/logic/lasermirror.py create mode 100644 tests/lasermirror_tests.py diff --git a/robotgame/logic/colourboxes.py b/robotgame/logic/colourboxes.py index e4d1fd7..d5a03f2 100644 --- a/robotgame/logic/colourboxes.py +++ b/robotgame/logic/colourboxes.py @@ -23,6 +23,10 @@ # copyright : (C) 2012 Niels G. W. Serup # maintained by : Niels G. W. Serup +""" +Colour boxes. +""" + import random def generate_colour_boxes(nwells, nboxes): diff --git a/robotgame/logic/direction.py b/robotgame/logic/direction.py index d747b2c..59e7ba2 100644 --- a/robotgame/logic/direction.py +++ b/robotgame/logic/direction.py @@ -53,9 +53,7 @@ class Left(Direction): x, y = pos return x - 1, y -all_directions = set((Up, Left, Down, Right)) - -succ = lambda d: all_directions[(all_directions.index(d) + 1) % 4] -pred = lambda d: all_directions[(all_directions.index(d) - 1) % 4] - -isDirection = lambda obj: obj in (Up, Left, Down, Right) +all_directions = (Up, Right, Down, Left) +_sp = lambda n: lambda d: all_directions[(all_directions.index(d) + n) % 4] +succ, pred = _sp(1), _sp(-1) +isDirection = all_directions.__contains__ diff --git a/robotgame/logic/lasermirror.py b/robotgame/logic/lasermirror.py new file mode 100644 index 0000000..4cde2f7 --- /dev/null +++ b/robotgame/logic/lasermirror.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of ROBOTGAME +# +# ROBOTGAME 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. +# +# ROBOTGAME 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 +# ROBOTGAME. 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 robotgame.logic.direction import * +import robotgame.logic.rollingstone as rstone + +class Mirror(object): + pass + +class Lever(object): + pass + +class Target(object): + pass + +def generate_simple_playfield(nmirrors): + """ + Generate a completable 12x12 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 (11, 0) starts in (12, 0), heading left + + the one in the lower right corner (11, 11) starts in (11, 12), heading up + + the one in the lower left corner (0, 11) starts in (-1, 11), 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 | Mirror | rstone.Blocker | Lever} + """ + playfield = {(4, 4): Target, + (7, 4): Target, + (4, 7): Target, + (7, 7): Target, + (5, 5): rstone.Blocker, + (5, 6): rstone.Blocker, + (6, 5): rstone.Blocker, + (6, 6): 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( + 5, 5, nm, 0, False, False) + for pos, direc in stone_playfield.items(): + if direc is not None and pos >= (0, 0): + playfield[_adjust(source_direc, 12 - 1, 12 - 1, *pos)] = Mirror + succs = (lambda s: lambda d: succ(s(d)))(succs) + source_direc = succ(source_direc) + + emptys = set(itertools.product(range(12), range(12))) \ + - set(playfield.keys()) + emptys = set([(0, y) for y in range(12)] + + [(11, y) for y in range(12)] + + [(x, 0) for x in range(12)] + + [(x, 11) for x in range(12)]) + for _ in range(nlevers): + if not emptys: + raise Exception("Not enough space for all levers!") + pos = random.choice(list(emptys)) + playfield[pos] = Lever + emptys.remove(pos) + 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 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)) + diff --git a/robotgame/logic/rollingstone.py b/robotgame/logic/rollingstone.py index 77d119e..38b58cc 100644 --- a/robotgame/logic/rollingstone.py +++ b/robotgame/logic/rollingstone.py @@ -30,9 +30,9 @@ direction-changing turns. Also has a pseudo-random playfield generator. from __future__ import print_function import math +import random import itertools from robotgame.logic.direction import * -import random class Blocker(object): pass @@ -70,7 +70,8 @@ def reaches_goal(playfield, width, height, max_steps, start_pos, goal_pos): return False -def generate_simple_playfield(width, height, nturns, nstones): +def generate_simple_playfield(width, height, nturns, nstones, + do_transpose=None, start_inside=True): """ Generate a completable playfield where: * the starting position is in the upper left corner @@ -78,7 +79,7 @@ def generate_simple_playfield(width, height, nturns, nstones): * the playfield is completable in nturns or less * the playfield has at most nstones stones - Return (playfield : {(x, y): Direction | Blocker}, + Return (playfield : {(x, y): Direction | Blocker | None}, steps : int) where (x, y) : (int, int) @@ -95,11 +96,13 @@ def generate_simple_playfield(width, height, nturns, nstones): nturns = min(2 * (width - 1), 2 * (height - 1) - 1) min_width, min_height = _min_play_size(nturns) - do_transpose = random.choice((True, False)) + if do_transpose is None: + do_transpose = random.choice((True, False)) if do_transpose: width, height = height, width - turns, stones = [((0, 0), None)], [] + turns = [((0, 0), None)] + stones = [] x, y = (0, 0) not_allowed_y = [] offset_x = 0 @@ -110,6 +113,7 @@ def generate_simple_playfield(width, height, nturns, nstones): turns.append(((x, height - 1), Right)) break elif missing == 0: + turns[-1] = ((width - 1, turns[-1][0][1]), Down) break else: allowed = set(range(0, height)) - set(not_allowed_y) @@ -129,6 +133,8 @@ def generate_simple_playfield(width, height, nturns, nstones): turns.append(((x1, y1), None)) x, y = x1, y1 turns.append(((width - 1, height - 1), None)) + if not start_inside: + del turns[0] if do_transpose: turns[:] = [((y, x), { @@ -181,7 +187,7 @@ def print_playfield(playfield, width, height, hide_directions): for (x, y), val in playfield.items(): if isDirection(val) and hide_directions: continue - text[y][x] = '%' if val == Blocker else repr(val).rsplit('.', 1)[1][0] \ + text[y][x] = '%' if val is Blocker else repr(val).rsplit('.', 1)[1][0] \ if isDirection(val) else 'G' print('\n'.join(''.join(line) for line in text)) diff --git a/tests/lasermirror_tests.py b/tests/lasermirror_tests.py new file mode 100644 index 0000000..bda9483 --- /dev/null +++ b/tests/lasermirror_tests.py @@ -0,0 +1,15 @@ + +from __future__ import print_function +import unittest +from robotgame.logic.lasermirror import * +from robotgame.logic.direction import * + + +class LaserMirrorTest(unittest.TestCase): + def test_playfield_generation(self): + print() + playfield = generate_simple_playfield(13) + print_playfield(playfield, 12, 12) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/rollingstone_tests.py b/tests/rollingstone_tests.py index ec94472..af78519 100755 --- a/tests/rollingstone_tests.py +++ b/tests/rollingstone_tests.py @@ -35,5 +35,11 @@ class RollingStoneTest(unittest.TestCase): self.assertTrue( reaches_goal(playfield, 10, 10, steps, (0, 0), (9, 9))) + print() + playfield, steps = generate_simple_playfield(10, 10, 4, 11) + print_playfield(playfield, 10, 10, True) + self.assertTrue( + reaches_goal(playfield, 10, 10, steps, (0, 0), (9, 9))) + if __name__ == '__main__': unittest.main()