Updated filenames and file contents to fit the title of the game. Made proper readme.
This commit is contained in:
0
arobotsconundrum/logic/__init__.py
Normal file
0
arobotsconundrum/logic/__init__.py
Normal file
141
arobotsconundrum/logic/colourboxes.py
Normal file
141
arobotsconundrum/logic/colourboxes.py
Normal file
@@ -0,0 +1,141 @@
|
||||
#!/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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
|
||||
#
|
||||
# colourboxes.py
|
||||
# --------------------
|
||||
# date created : Wed Aug 8 2012
|
||||
# copyright : (C) 2012 Niels G. W. Serup
|
||||
# maintained by : Niels G. W. Serup <ngws@metanohi.name>
|
||||
|
||||
"""
|
||||
Colour boxes.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
import random
|
||||
import itertools
|
||||
|
||||
def generate_colour_boxes(nwells, nboxes):
|
||||
"""
|
||||
Generate colour boxes that can be used to make all wells white. None of the
|
||||
generated colour boxes are white.
|
||||
|
||||
Arguments:
|
||||
nwells -- number of wells
|
||||
nboxes -- maximum number of boxes needed to make all wells white.
|
||||
|
||||
Return [[(r, g, b)]]
|
||||
where r : 0|1, g : 0|1, b : 0|1
|
||||
"""
|
||||
|
||||
nbits = nwells * 3
|
||||
data = [[0 for _ in range(nboxes)] for _ in range(nbits)]
|
||||
|
||||
def insert_1(x):
|
||||
t = random.randrange(0, nboxes)
|
||||
x0, x1 = _get_oxs(x)
|
||||
for y in range(t, nboxes) + range(0, t):
|
||||
if data[x][y] == 0 and not (data[x0][y] == 1 and data[x1][y] == 1):
|
||||
data[x][y] = 1
|
||||
break
|
||||
else:
|
||||
raise Exception("Cannot maintain no 111s invariant.")
|
||||
|
||||
def insert_two_1(x):
|
||||
t = random.randrange(0, nboxes)
|
||||
x0, x1 = _get_oxs(x)
|
||||
if len(list(filter(lambda y: data[x][y] == 0 and not (data[x0][y] == 1 and data[x1][y] == 1),
|
||||
range(nboxes)))) < 2:
|
||||
return
|
||||
for _ in range(2):
|
||||
for y in range(t, nboxes) + range(0, t):
|
||||
if data[x][y] == 0 and not (data[x0][y] == 1 and data[x1][y] == 1):
|
||||
data[x][y] = 1
|
||||
break
|
||||
|
||||
for x in range(len(data)):
|
||||
insert_1(x)
|
||||
for x in range(len(data)):
|
||||
for _ in range(random.randrange(0, (nboxes + 1) / 2)):
|
||||
insert_two_1(x)
|
||||
|
||||
boxes = []
|
||||
for y in range(nboxes):
|
||||
box = []
|
||||
boxes.append(box)
|
||||
for x in range(0, nbits, 3):
|
||||
r = data[x][y]
|
||||
g = data[x + 1][y]
|
||||
b = data[x + 2][y]
|
||||
box.append((r, g, b))
|
||||
return boxes
|
||||
|
||||
def _get_oxs(x):
|
||||
return (x + 1, x + 2) if x % 3 == 0 \
|
||||
else (x - 1, x + 1) if x % 3 == 1 \
|
||||
else (x - 2, x - 1)
|
||||
|
||||
def generate_random_box(nwells, min_nonblacks=0):
|
||||
"""
|
||||
Generate a box that triggers nwells wells, with random colors except white
|
||||
(111).
|
||||
|
||||
Arguments:
|
||||
min_nonblacks -- minimum number of well colours in a box required not to be
|
||||
black.
|
||||
"""
|
||||
def gen_wc():
|
||||
wc = [random.choice((0, 1)) for i in range(3)]
|
||||
if all(b == 1 for b in wc):
|
||||
wc[random.randrange(3)] = 0
|
||||
return wc
|
||||
def gen_wc_nonblack():
|
||||
wc = gen_wc()
|
||||
if all(b == 0 for b in wc):
|
||||
wc[random.randrange(3)] = 1
|
||||
return wc
|
||||
colours = [tuple(gen_wc()) for _ in range(nwells)]
|
||||
nonblack = lambda t: any(n == 1 for n in t)
|
||||
missing_nonblacks = min_nonblacks - len(list(filter(nonblack, colours)))
|
||||
i = 0
|
||||
while missing_nonblacks > 0:
|
||||
if not nonblack(colours[i]):
|
||||
colours[i] = gen_wc_nonblack()
|
||||
missing_nonblacks -= 1
|
||||
i += 1
|
||||
return colours
|
||||
|
||||
def get_colours(boxes):
|
||||
colours = []
|
||||
for i in range(len(boxes[0])):
|
||||
r, g, b = boxes[0][i]
|
||||
for j in range(1, len(boxes)):
|
||||
r1, g1, b1 = boxes[j][i]
|
||||
r ^= r1
|
||||
g ^= g1
|
||||
b ^= b1
|
||||
colours.append((r, g, b))
|
||||
return colours
|
||||
|
||||
def makes_all_wells_white(boxes):
|
||||
"""
|
||||
Determine if the boxes make all wells white when XOR'ed together.
|
||||
"""
|
||||
return all(c == 1 for c in itertools.chain(*get_colours(boxes)))
|
||||
87
arobotsconundrum/logic/direction.py
Normal file
87
arobotsconundrum/logic/direction.py
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
|
||||
#
|
||||
# direction.py
|
||||
# --------------------
|
||||
# date created : Tue Aug 7 2012
|
||||
# copyright : (C) 2012 Niels G. W. Serup
|
||||
# maintained by : Niels G. W. Serup <ngws@metanohi.name>
|
||||
|
||||
"""Directions."""
|
||||
|
||||
class Direction(object):
|
||||
@staticmethod
|
||||
def next_pos(pos):
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def to_str():
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def from_sakse(p):
|
||||
return {(0, -1): Up,
|
||||
(0, 1): Down,
|
||||
(-1, 0): Left,
|
||||
(1, 0): Right}[p]
|
||||
|
||||
class Up(Direction):
|
||||
@staticmethod
|
||||
def next_pos(pos):
|
||||
x, y = pos
|
||||
return x, y - 1
|
||||
|
||||
@staticmethod
|
||||
def to_str():
|
||||
return 'up'
|
||||
|
||||
class Right(Direction):
|
||||
@staticmethod
|
||||
def next_pos(pos):
|
||||
x, y = pos
|
||||
return x + 1, y
|
||||
|
||||
@staticmethod
|
||||
def to_str():
|
||||
return 'right'
|
||||
|
||||
class Down(Direction):
|
||||
@staticmethod
|
||||
def next_pos(pos):
|
||||
x, y = pos
|
||||
return x, y + 1
|
||||
|
||||
@staticmethod
|
||||
def to_str():
|
||||
return 'down'
|
||||
|
||||
class Left(Direction):
|
||||
@staticmethod
|
||||
def next_pos(pos):
|
||||
x, y = pos
|
||||
return x - 1, y
|
||||
|
||||
@staticmethod
|
||||
def to_str():
|
||||
return 'left'
|
||||
|
||||
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__
|
||||
218
arobotsconundrum/logic/lasermirror.py
Normal file
218
arobotsconundrum/logic/lasermirror.py
Normal file
@@ -0,0 +1,218 @@
|
||||
#!/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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
|
||||
#
|
||||
# lasermirror.py
|
||||
# --------------------
|
||||
# date created : Tue Aug 7 2012
|
||||
# copyright : (C) 2012 Niels G. W. Serup
|
||||
# maintained by : Niels G. W. Serup <ngws@metanohi.name>
|
||||
|
||||
"""
|
||||
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))
|
||||
|
||||
223
arobotsconundrum/logic/rollingstone.py
Normal file
223
arobotsconundrum/logic/rollingstone.py
Normal file
@@ -0,0 +1,223 @@
|
||||
#!/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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
|
||||
#
|
||||
# rollingstone.py
|
||||
# --------------------
|
||||
# date created : Tue Aug 7 2012
|
||||
# copyright : (C) 2012 Niels G. W. Serup
|
||||
# maintained by : Niels G. W. Serup <ngws@metanohi.name>
|
||||
|
||||
"""
|
||||
Logic for a rolling stone on a playfield of movement-stopping stones and
|
||||
direction-changing turns. Also has a pseudo-random playfield generator.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
import math
|
||||
import random
|
||||
import itertools
|
||||
|
||||
from arobotsconundrum.logic.direction import *
|
||||
import arobotsconundrum.misc as misc
|
||||
|
||||
class Blocker(object):
|
||||
pass
|
||||
|
||||
def step(playfield, width, height, old_pos, direc):
|
||||
"""
|
||||
Return a new (position, direction) tuple based on the location on the
|
||||
playfield.
|
||||
"""
|
||||
pos = direc.next_pos(old_pos)
|
||||
x, y = pos
|
||||
if playfield.get(pos) is Blocker or x < 0 or x >= width \
|
||||
or y < 0 or y >= height:
|
||||
pos = old_pos
|
||||
elif isDirection(playfield.get(pos)):
|
||||
direc = playfield[pos]
|
||||
return pos, direc
|
||||
|
||||
def reaches_goal(playfield, width, height, max_steps, start_pos, goal_pos):
|
||||
"""
|
||||
Determine if the rolling stone reaches the goal within max_steps steps.
|
||||
|
||||
playfield[start_pos] must contain either a Turn(Down) or a Turn(Right)
|
||||
object, or the rolling stone will not roll.
|
||||
"""
|
||||
pos = start_pos
|
||||
direc = playfield[pos]
|
||||
for _ in range(max_steps):
|
||||
new_pos, new_direc = step(playfield, width, height, pos, direc)
|
||||
if new_pos == goal_pos:
|
||||
return True
|
||||
if new_pos == pos:
|
||||
return False
|
||||
pos, direc = new_pos, new_direc
|
||||
return False
|
||||
|
||||
|
||||
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
|
||||
* the goal is in the lower right corner
|
||||
* the playfield is completable in nturns or less
|
||||
* the playfield has at most nstones stones
|
||||
|
||||
Return (playfield : {(x, y): Direction | Blocker},
|
||||
steps : int)
|
||||
where (x, y) : (int, int)
|
||||
|
||||
The returned playfield contains Direction objects which can be used with
|
||||
the step function to move towards the goal. The solution denoted by the
|
||||
Direction objects is not necessarily the only solution.
|
||||
|
||||
'steps' is the number of steps used by the generated solution. It is not
|
||||
necessarily the lowest number of steps the playfield can be completed in.
|
||||
|
||||
This generator favours increasing the turn density the closer to the goal
|
||||
it gets.
|
||||
"""
|
||||
|
||||
min_width, min_height = _min_play_size(nturns)
|
||||
if width < min_width or height < min_height:
|
||||
nturns = min(2 * (width - 1), 2 * (height - 1) - 1)
|
||||
min_width, min_height = _min_play_size(nturns)
|
||||
|
||||
if do_transpose is None:
|
||||
do_transpose = random.choice((True, False))
|
||||
if do_transpose:
|
||||
width, height = height, width
|
||||
min_width, min_height = min_height, min_width
|
||||
|
||||
turns = [((0, 0), None)]
|
||||
stones = []
|
||||
x, y = (0, 0)
|
||||
not_allowed_y = []
|
||||
offset_x = 0
|
||||
while True:
|
||||
missing = nturns - len(turns) + 1
|
||||
if missing == 1:
|
||||
turns[-1] = (turns[-1][0], Down)
|
||||
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)
|
||||
if missing <= 3:
|
||||
allowed -= set((height - 1,))
|
||||
if missing == nturns:
|
||||
allowed -= set((0,))
|
||||
y1 = random.choice(list(allowed))
|
||||
turns[-1] = (turns[-1][0], Down if y1 > y else Up)
|
||||
not_allowed_y.append(y1)
|
||||
if len(not_allowed_y) == 3:
|
||||
del not_allowed_y[0]
|
||||
turns.append(((x, y1), Right))
|
||||
x1p = random.randint(0, width - min_width - offset_x)
|
||||
offset_x += x1p
|
||||
x1 = x + x1p + 1
|
||||
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), {
|
||||
Down: Right,
|
||||
Right: Down,
|
||||
Up: Left,
|
||||
}.get(d)) for ((x, y), d) in turns]
|
||||
width, height = height, width
|
||||
min_width, min_height = min_height, min_width
|
||||
|
||||
used_fields = _fields_from_turns(turns)
|
||||
playfield = {}
|
||||
del turns[-1]
|
||||
for p, d in turns:
|
||||
playfield[p] = d
|
||||
|
||||
for pos in misc.pick_random_elements(
|
||||
list(set(itertools.product(range(width), range(height)))
|
||||
- set(used_fields)), nstones):
|
||||
playfield[pos] = Blocker
|
||||
return playfield, len(used_fields) - 1
|
||||
|
||||
def generate_simple_unsolved_solvable_playfield(*args, **kwds):
|
||||
"""
|
||||
Return a tuple of a playfield without direction objects, its number of
|
||||
steps, and a list of the direction objects.
|
||||
"""
|
||||
playfield, steps = generate_simple_playfield(*args, **kwds)
|
||||
new_playfield, directions = {}, []
|
||||
for pos, val in playfield.items():
|
||||
if val is Blocker:
|
||||
new_playfield[pos] = val
|
||||
else:
|
||||
directions.append(val)
|
||||
return new_playfield, steps, directions
|
||||
|
||||
def generate_simple_unsolved_solvable_extra(*args, **kwds):
|
||||
"""
|
||||
Do the same as generate_simple_unsolved_solvable, but throw in some copies
|
||||
of the direction object not returned by that function. You probably want to
|
||||
use this in your game.
|
||||
"""
|
||||
playfield, steps, directions = generate_simple_unsolved_solvable_playfield(
|
||||
*args, **kwds)
|
||||
missing_dir = list(set(all_directions) - set(directions))[0]
|
||||
return playfield, steps, directions + \
|
||||
[missing_dir] * (len(directions) / 3) + [Right]
|
||||
|
||||
def print_playfield(playfield, width, height, hide_directions):
|
||||
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 Blocker else repr(val).rsplit('.', 1)[1][0] \
|
||||
if isDirection(val) else 'G'
|
||||
print('\n'.join(''.join(line) for line in text))
|
||||
|
||||
def _cells_upto(fields, start, direc, end):
|
||||
(x0, y0), (x2, y2) = start, end
|
||||
if direc in (Up, Down):
|
||||
t = -1 if direc == Up else 1
|
||||
for y in range(y0 + t, y2 + t, t):
|
||||
fields.append((x0, y))
|
||||
else:
|
||||
t = -1 if direc == Left else 1
|
||||
for x in range(x0 + t, x2 + t, t):
|
||||
fields.append((x, y0))
|
||||
|
||||
def _fields_from_turns(turns):
|
||||
fields = [(0, 0)]
|
||||
prev_pos, prev_direc = turns[0]
|
||||
for (pos, direc) in turns:
|
||||
_cells_upto(fields, prev_pos, prev_direc, pos)
|
||||
prev_pos, prev_direc = pos, direc
|
||||
return fields
|
||||
|
||||
def _min_play_size(nturns):
|
||||
return (int(math.ceil(nturns / 2.0)) + 1,
|
||||
int(math.ceil((nturns + 1) / 2.0)) + 1)
|
||||
144
arobotsconundrum/logic/teleportermap.py
Normal file
144
arobotsconundrum/logic/teleportermap.py
Normal file
@@ -0,0 +1,144 @@
|
||||
#!/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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
|
||||
#
|
||||
# teleportermap.py
|
||||
# --------------------
|
||||
# date created : Mon Aug 13 2012
|
||||
# copyright : (C) 2012 Niels G. W. Serup
|
||||
# maintained by : Niels G. W. Serup <ngws@metanohi.name>
|
||||
|
||||
"""
|
||||
Logic for a map with invisible teleporters.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
import math
|
||||
import random
|
||||
import itertools
|
||||
|
||||
from arobotsconundrum.logic.direction import *
|
||||
import arobotsconundrum.misc as misc
|
||||
|
||||
class Empty(object):
|
||||
pass
|
||||
|
||||
class Forbidden(object):
|
||||
pass
|
||||
|
||||
class StrictlyForbidden(object):
|
||||
pass
|
||||
|
||||
class Visited(object):
|
||||
pass
|
||||
|
||||
def generate_teleporter_map(width, height):
|
||||
m = [[Empty for _ in range(height)]
|
||||
for _ in range(width)]
|
||||
|
||||
def _get(p):
|
||||
if p[0] < 0 or p[0] >= width or p[1] < 0 or p[1] >= height:
|
||||
return StrictlyForbidden
|
||||
return m[p[0]][p[1]]
|
||||
|
||||
def _set(p, val):
|
||||
try:
|
||||
m[p[0]][p[1]] = val
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
def insert_random_point_on_line(x):
|
||||
t = random.randrange(height)
|
||||
for y in itertools.chain(xrange(t), xrange(t + 1, height)):
|
||||
_set((x, y), Forbidden)
|
||||
_set((x, t), Visited)
|
||||
return (x, t)
|
||||
|
||||
pos = insert_random_point_on_line(width - 1)
|
||||
|
||||
while True:
|
||||
while True:
|
||||
if pos[0] == 1 and random.randint(0, height) == 0:
|
||||
break
|
||||
t = random.randrange(4)
|
||||
found = None
|
||||
alldirs = all_directions
|
||||
if random.choice((True, False)):
|
||||
alldirs.reverse()
|
||||
for direc in alldirs[t:] + alldirs[:t]:
|
||||
npos = direc.next_pos(pos)
|
||||
if found is None and npos[0] != 0 and _get(npos) is Empty and (
|
||||
(direc is Right and
|
||||
len(filter(lambda c: c is Empty, m[pos[0]][:pos[1]] if
|
||||
opos[1] > pos[1]
|
||||
else m[pos[0]][pos[1] + 1:])) >= 2)
|
||||
or direc is not Right):
|
||||
found = npos
|
||||
_set(npos, Visited)
|
||||
else:
|
||||
if _get(npos) is Empty:
|
||||
_set(npos, Forbidden)
|
||||
if found is None:
|
||||
break
|
||||
opos = pos
|
||||
pos = found
|
||||
if pos[0] == 1:
|
||||
break
|
||||
|
||||
_set(pos, StrictlyForbidden)
|
||||
for direc in all_directions:
|
||||
npos = direc.next_pos(pos)
|
||||
if _get(npos) is Visited:
|
||||
pos = npos
|
||||
break
|
||||
for direc in all_directions:
|
||||
npos = direc.next_pos(pos)
|
||||
if _get(npos) is Forbidden:
|
||||
occup_direc = succ(succ(direc))
|
||||
if all(ndirec is occup_direc
|
||||
or _get(ndirec.next_pos(npos)) in (Forbidden, Empty)
|
||||
for ndirec in all_directions):
|
||||
_set(npos, Empty)
|
||||
|
||||
_set((0, pos[1]), Visited)
|
||||
return m
|
||||
|
||||
def generate_teleporter_map2(width, height):
|
||||
tmap = generate_teleporter_map(width, height)
|
||||
res = set()
|
||||
for y in range(len(tmap[0])):
|
||||
for x in range(len(tmap)):
|
||||
if tmap[x][y] is Visited:
|
||||
res.add((x, y))
|
||||
return res
|
||||
|
||||
def generate_teleporter_map3(width, height):
|
||||
return set(itertools.product(range(width), range(height))) \
|
||||
- generate_teleporter_map2(width, height)
|
||||
|
||||
def print_map(tmap):
|
||||
for y in range(len(tmap[0])):
|
||||
for x in range(len(tmap)):
|
||||
c = tmap[x][y]
|
||||
print({Empty: '%',
|
||||
Forbidden: '#',
|
||||
StrictlyForbidden: '!',
|
||||
Visited: '·'}[c], end='')
|
||||
print()
|
||||
|
||||
Reference in New Issue
Block a user