mirror of
https://github.com/mfocko/blog.git
synced 2024-11-22 13:03:47 +01:00
792 lines
22 KiB
Python
792 lines
22 KiB
Python
|
from dataclasses import dataclass
|
||
|
from enum import Enum, IntEnum
|
||
|
from functools import wraps
|
||
|
from itertools import product
|
||
|
from math import radians, sin, cos
|
||
|
from sys import stderr
|
||
|
import tkinter as tk
|
||
|
from typing import Tuple, Union
|
||
|
|
||
|
|
||
|
def check_before(precondition):
|
||
|
"""
|
||
|
Decorator for running methods before executing a method.
|
||
|
|
||
|
Args:
|
||
|
call_before: Function to be called before executing a method.
|
||
|
|
||
|
Returns:
|
||
|
Parametrized decorator that can be used to wrap method with the
|
||
|
`call_before`.
|
||
|
"""
|
||
|
|
||
|
def parametrized(f):
|
||
|
@wraps(f)
|
||
|
def wrapper(self, *args, **kwargs):
|
||
|
if not precondition(self, *args, **kwargs, fn=f):
|
||
|
return
|
||
|
return f(self, *args, **kwargs)
|
||
|
|
||
|
return wrapper
|
||
|
|
||
|
return parametrized
|
||
|
|
||
|
|
||
|
def after(call_after):
|
||
|
"""
|
||
|
Decorator for running methods after executing a method.
|
||
|
|
||
|
Args:
|
||
|
call_after: Function to be called after executing a method.
|
||
|
|
||
|
Returns:
|
||
|
Parametrized decorator that can be used to wrap method with the
|
||
|
`call_after`.
|
||
|
"""
|
||
|
|
||
|
def parametrized(f):
|
||
|
@wraps(f)
|
||
|
def wrapper(self, *args, **kwargs):
|
||
|
result = f(self, *args, **kwargs)
|
||
|
call_after(self, *args, **kwargs, fn=f)
|
||
|
return result
|
||
|
|
||
|
return wrapper
|
||
|
|
||
|
return parametrized
|
||
|
|
||
|
|
||
|
def skip_for_summary(f):
|
||
|
"""
|
||
|
Decorator used for skipping a function call if the Karel is running
|
||
|
in a summary mode.
|
||
|
"""
|
||
|
|
||
|
@wraps(f)
|
||
|
def wrapper(self, *args, **kwargs):
|
||
|
if self.summary_mode:
|
||
|
return
|
||
|
return f(self, *args, **kwargs)
|
||
|
|
||
|
return wrapper
|
||
|
|
||
|
|
||
|
class Block(IntEnum):
|
||
|
"""
|
||
|
Represents a field in the world of the Robot Karel.
|
||
|
It can be either clear or a wall.
|
||
|
"""
|
||
|
|
||
|
Clear = 0
|
||
|
Wall = -1
|
||
|
|
||
|
|
||
|
# Constants for major angles
|
||
|
EAST, NORTH, WEST, SOUTH = 0, 90, 180, 270
|
||
|
|
||
|
|
||
|
# @dataclass(frozen=True, slots=True)
|
||
|
@dataclass(frozen=True)
|
||
|
class _Direction:
|
||
|
"""
|
||
|
angle: Represents heading of the Robot Karel
|
||
|
"""
|
||
|
|
||
|
angle: int
|
||
|
|
||
|
@property
|
||
|
def vector(self) -> Tuple[int, int]:
|
||
|
"""
|
||
|
Get a direction vector in the current direction the robot is heading.
|
||
|
|
||
|
Returns:
|
||
|
Pair of ints, representing a vector of (dx, dy).
|
||
|
"""
|
||
|
angle, x, y = radians(self.angle), 1, 0
|
||
|
return (
|
||
|
x * round(cos(angle)) - y * round(sin(angle)),
|
||
|
x * round(sin(angle)) + y * round(cos(angle)),
|
||
|
)
|
||
|
|
||
|
@property
|
||
|
def in_world(self) -> str:
|
||
|
"""
|
||
|
Get a character that is being put in the graphical representation of
|
||
|
the Robot Karel.
|
||
|
|
||
|
Returns:
|
||
|
Character to be put in the world.
|
||
|
"""
|
||
|
return {
|
||
|
Direction.East.value: ">",
|
||
|
Direction.North.value: "^",
|
||
|
Direction.West.value: "<",
|
||
|
Direction.South.value: "v",
|
||
|
# Direction.East.value: "⮞",
|
||
|
# Direction.North.value: "⮝",
|
||
|
# Direction.West.value: "⮜",
|
||
|
# Direction.South.value: "⮟",
|
||
|
}[self]
|
||
|
|
||
|
@property
|
||
|
def as_string(self) -> str:
|
||
|
"""
|
||
|
Returns a string with a direction for Tk label.
|
||
|
|
||
|
Returns:
|
||
|
Label of the direction
|
||
|
"""
|
||
|
return {
|
||
|
Direction.East.value: "east",
|
||
|
Direction.North.value: "north",
|
||
|
Direction.West.value: "west",
|
||
|
Direction.South.value: "south",
|
||
|
}[self]
|
||
|
|
||
|
def __add__(self, angle: int) -> "_Direction":
|
||
|
return _Direction((360 + self.angle + angle) % 360)
|
||
|
|
||
|
|
||
|
class Direction(Enum):
|
||
|
"""
|
||
|
Provides constants of directions Karel can face.
|
||
|
"""
|
||
|
|
||
|
East = _Direction(EAST)
|
||
|
North = _Direction(NORTH)
|
||
|
West = _Direction(WEST)
|
||
|
South = _Direction(SOUTH)
|
||
|
|
||
|
|
||
|
class KarelWindow:
|
||
|
def __init__(self, karel: "Karel", scale: int, font: str):
|
||
|
self.size = 32 * scale
|
||
|
|
||
|
self.steps = tk.Label(text=karel.steps, anchor="e")
|
||
|
self.steps.grid(row=0, column=0, sticky="e")
|
||
|
|
||
|
self.last_cmd = tk.Label(text=karel.last_command, anchor="w")
|
||
|
self.last_cmd.grid(row=0, column=1, columnspan=3, sticky="w")
|
||
|
|
||
|
tk.Label(text="CORNER").grid(row=1, column=0)
|
||
|
tk.Label(text="FACING").grid(row=1, column=1)
|
||
|
tk.Label(text="BEEP-BAG").grid(row=1, column=2)
|
||
|
tk.Label(text="BEEP-CORNER").grid(row=1, column=3)
|
||
|
|
||
|
self.corner = tk.Label()
|
||
|
self.corner.grid(row=2, column=0)
|
||
|
|
||
|
self.facing = tk.Label()
|
||
|
self.facing.grid(row=2, column=1)
|
||
|
|
||
|
self.beep_bag = tk.Label()
|
||
|
self.beep_bag.grid(row=2, column=2)
|
||
|
|
||
|
self.beep_corner = tk.Label()
|
||
|
self.beep_corner.grid(row=2, column=3)
|
||
|
|
||
|
self.canvas = tk.Canvas(
|
||
|
width=((karel.world.width + 1) // 2 + 2) * self.size,
|
||
|
height=((karel.world.height + 1) // 2 + 2) * self.size,
|
||
|
background="black",
|
||
|
)
|
||
|
self.canvas.grid(column=0, row=3, columnspan=4)
|
||
|
|
||
|
self.font = f"{font} {int(12 * scale)}"
|
||
|
|
||
|
self.__draw_world(karel.world)
|
||
|
self.render(karel)
|
||
|
|
||
|
def __draw_world(self, world: "World"):
|
||
|
m, n = (world.height + 1) // 2, (world.width + 1) // 2
|
||
|
|
||
|
# NUMBERS
|
||
|
self.canvas.create_text(
|
||
|
self.size // 2,
|
||
|
self.size,
|
||
|
text="ST.",
|
||
|
fill="white",
|
||
|
anchor="s",
|
||
|
font=self.font,
|
||
|
)
|
||
|
for i in range(m):
|
||
|
self.canvas.create_text(
|
||
|
self.size - 8,
|
||
|
(i + 2) * self.size - self.size // 2,
|
||
|
text=(m - i),
|
||
|
anchor="e",
|
||
|
fill="white",
|
||
|
font=self.font,
|
||
|
)
|
||
|
y = ((m + 1) * self.size) + 8
|
||
|
self.canvas.create_text(
|
||
|
(n + 1) * self.size + self.size // 2,
|
||
|
y,
|
||
|
text="AVE.",
|
||
|
fill="white",
|
||
|
anchor="n",
|
||
|
font=self.font,
|
||
|
)
|
||
|
for i in range(n):
|
||
|
self.canvas.create_text(
|
||
|
(i + 2) * self.size - self.size // 2,
|
||
|
y,
|
||
|
text=(i + 1),
|
||
|
anchor="n",
|
||
|
fill="white",
|
||
|
font=self.font,
|
||
|
)
|
||
|
|
||
|
# BORDER
|
||
|
self.canvas.create_rectangle(
|
||
|
self.size,
|
||
|
self.size,
|
||
|
self.size * (n + 1),
|
||
|
self.size * (m + 1),
|
||
|
fill="",
|
||
|
outline="white",
|
||
|
)
|
||
|
|
||
|
for row, col in product(range(world.height), range(world.width)):
|
||
|
block = world.data[row][col]
|
||
|
if row % 2 == 0 and col % 2 == 0:
|
||
|
self.__print_beeper(world, block or ".", row // 2, col // 2)
|
||
|
elif block == Block.Wall:
|
||
|
size = self.size
|
||
|
x = size + col // 2 * size
|
||
|
y = (world.width + 1) // 2 * size - row // 2 * size
|
||
|
if row % 2 == 1 and col % 2 == 0:
|
||
|
self.canvas.create_line(x, y, x + size, y, fill="white")
|
||
|
elif row % 2 == 0:
|
||
|
x += size
|
||
|
self.canvas.create_line(x, y, x, y + size, fill="white")
|
||
|
|
||
|
self.canvas.update()
|
||
|
|
||
|
def __print_beeper(
|
||
|
self, world: "World", beeper: Union[str, int], row: int, column: int
|
||
|
):
|
||
|
self.canvas.create_text(
|
||
|
(column + 1) * self.size + self.size // 2,
|
||
|
((world.height + 1) // 2 - row) * self.size + self.size // 2,
|
||
|
text=str(beeper),
|
||
|
anchor="center",
|
||
|
fill="white",
|
||
|
font=self.font,
|
||
|
)
|
||
|
|
||
|
def render(self, karel: "Karel"):
|
||
|
self.facing["text"] = karel.direction.as_string
|
||
|
|
||
|
self.steps["text"] = karel.steps
|
||
|
self.last_cmd["text"] = karel.last_command
|
||
|
self.corner["text"] = f"({(karel.x + 2) // 2}, {(karel.y + 2) // 2})"
|
||
|
self.beep_bag["text"] = karel.beepers
|
||
|
self.beep_corner["text"] = karel.world.data[karel.y][karel.x]
|
||
|
|
||
|
i, j = (karel.y + 2) // 2, (karel.x + 2) // 2
|
||
|
size = self.size
|
||
|
x, y = j * size, ((karel.world.height + 1) // 2 - i + 1) * size
|
||
|
self.canvas.create_rectangle(
|
||
|
x + 1, y + 1, x + size - 1, y + size - 1, fill="black"
|
||
|
)
|
||
|
|
||
|
karel_on_map = karel.direction.in_world
|
||
|
self.canvas.create_text(
|
||
|
x + size // 2,
|
||
|
y + size // 2,
|
||
|
text=karel_on_map,
|
||
|
font=self.font + " bold",
|
||
|
fill="yellow",
|
||
|
anchor="center",
|
||
|
)
|
||
|
|
||
|
self.canvas.update()
|
||
|
self.canvas.after(karel.step_delay)
|
||
|
|
||
|
def update(self, karel: "Karel", dx: int, dy: int):
|
||
|
block = karel.world.data[karel.y - 2 * dy][karel.x - 2 * dx]
|
||
|
|
||
|
i, j = (karel.y - 2 * dy) // 2, (karel.x - 2 * dx) // 2
|
||
|
x, y = (j + 1) * self.size, (
|
||
|
(karel.world.height + 1) // 2 - i
|
||
|
) * self.size
|
||
|
|
||
|
self.canvas.create_rectangle(
|
||
|
x + 1, y + 1, x + self.size - 1, y + self.size - 1, fill="black"
|
||
|
)
|
||
|
self.__print_beeper(karel.world, block or ".", i, j)
|
||
|
|
||
|
def error(self, karel: "Karel", message: str):
|
||
|
self.render(karel)
|
||
|
|
||
|
self.last_cmd["foreground"] = "red"
|
||
|
self.last_cmd["text"] = f"Error Shutoff! ({message})"
|
||
|
|
||
|
|
||
|
class World:
|
||
|
def __init__(self, width, height, data):
|
||
|
self.width = width * 2 - 1
|
||
|
self.height = height * 2 - 1
|
||
|
self.data = [
|
||
|
[0 for _ in range(self.width)] for _ in range(self.height)
|
||
|
]
|
||
|
|
||
|
if self.width > 30 or self.height > 30:
|
||
|
raise ValueError(
|
||
|
"The given world is greater than the max values of [{}x{}]".format(
|
||
|
30, 30
|
||
|
)
|
||
|
)
|
||
|
|
||
|
self._parse_world(data)
|
||
|
|
||
|
def __parse_wall(self, column, row, orientation, line):
|
||
|
column = column * 2 - 2
|
||
|
row = row * 2 - 2
|
||
|
|
||
|
if column % 2 == 1 or row % 2 == 1:
|
||
|
raise ValueError("Wrong position of the wall")
|
||
|
|
||
|
if orientation == "E":
|
||
|
column += 1
|
||
|
elif orientation == "W":
|
||
|
column -= 1
|
||
|
elif orientation == "N":
|
||
|
row += 1
|
||
|
elif orientation == "S":
|
||
|
row -= 1
|
||
|
else:
|
||
|
raise ValueError(
|
||
|
"Unknown wall orientation '{}' on line {} in world file".format(
|
||
|
orientation, line
|
||
|
)
|
||
|
)
|
||
|
|
||
|
self.data[row][column] = Block.Wall
|
||
|
if column % 2 == 1 and row % 2 == 0:
|
||
|
if row + 1 < self.height:
|
||
|
self.data[row + 1][column] = Block.Wall
|
||
|
if row - 1 >= 0:
|
||
|
self.data[row - 1][column] = Block.Wall
|
||
|
else:
|
||
|
if column + 1 < self.width:
|
||
|
self.data[row][column + 1] = Block.Wall
|
||
|
if column - 1 >= 0:
|
||
|
self.data[row][column - 1] = Block.Wall
|
||
|
|
||
|
def __parse_beepers(self, column, row, count):
|
||
|
column = column * 2 - 2
|
||
|
row = row * 2 - 2
|
||
|
self.data[row][column] = count
|
||
|
|
||
|
def _parse_world(self, world_content):
|
||
|
for i, line in enumerate(world_content, 1):
|
||
|
if not line:
|
||
|
continue
|
||
|
|
||
|
block, column, row, arg = [part.upper() for part in line.split()]
|
||
|
if block == "W":
|
||
|
self.__parse_wall(int(column), int(row), arg, i)
|
||
|
elif block == "B":
|
||
|
self.__parse_beepers(int(column), int(row), int(arg))
|
||
|
else:
|
||
|
raise ValueError(
|
||
|
"Unknown block character '{}' on line {} in world file".format(
|
||
|
block, i
|
||
|
)
|
||
|
)
|
||
|
|
||
|
|
||
|
class Karel:
|
||
|
def __init__(
|
||
|
self, filename, summary_mode=False, scale=1, font="monospace"
|
||
|
):
|
||
|
"""
|
||
|
Turn Karel on.
|
||
|
|
||
|
Args:
|
||
|
filename: Path to the file containing Karel's world and initial
|
||
|
settings.
|
||
|
summary_mode: Disable rendering of the Karel's world in the window.
|
||
|
|
||
|
Defaults to `False`.
|
||
|
scale: Change the scaling factor of the font used to render Karel's
|
||
|
world.
|
||
|
|
||
|
Defaults to `1`.
|
||
|
font: Change the default monospace font that is used to render the
|
||
|
world.
|
||
|
|
||
|
Defaults to `"monospace"`.
|
||
|
"""
|
||
|
self.summary_mode = summary_mode
|
||
|
|
||
|
world_content = None
|
||
|
with open(filename) as world_file:
|
||
|
world_content = world_file.read()
|
||
|
|
||
|
first_line, world = world_content.split("\n", maxsplit=1)
|
||
|
width, height, x, y, direction, beepers = first_line.split()
|
||
|
direction = direction.upper()
|
||
|
|
||
|
self.world = World(int(width), int(height), world.split("\n"))
|
||
|
self.x, self.y = int(x) * 2 - 2, int(y) * 2 - 2
|
||
|
|
||
|
if direction == "S":
|
||
|
self.direction = Direction.South.value
|
||
|
elif direction == "W":
|
||
|
self.direction = Direction.West.value
|
||
|
elif direction == "E":
|
||
|
self.direction = Direction.East.value
|
||
|
elif direction == "N":
|
||
|
self.direction = Direction.North.value
|
||
|
else:
|
||
|
raise ValueError("Unknown Karel's direction")
|
||
|
|
||
|
self.beepers = int(beepers)
|
||
|
|
||
|
self.steps = 0
|
||
|
self.step_delay = 1000
|
||
|
self.is_running = True
|
||
|
self.last_command = "turn_on"
|
||
|
|
||
|
self._initialize_window(scale, font)
|
||
|
|
||
|
@skip_for_summary
|
||
|
def _initialize_window(self, scale, font):
|
||
|
"""
|
||
|
Initializes a window where the Karel is drawn.
|
||
|
"""
|
||
|
self._window = KarelWindow(self, scale, font)
|
||
|
|
||
|
@skip_for_summary
|
||
|
def __update(self, dx, dy):
|
||
|
"""
|
||
|
Updates Karel's position in the window.
|
||
|
|
||
|
Args:
|
||
|
dx: Change along the horizontal axis.
|
||
|
dy: Change along the vertical axis.
|
||
|
"""
|
||
|
self._window.update(self, dx, dy)
|
||
|
|
||
|
def __render(self, fn, *args, **kwargs):
|
||
|
"""
|
||
|
Updates step counter and last command for Karel.
|
||
|
If the robot is not running in a summary mode, then the window
|
||
|
is rendered once again.
|
||
|
"""
|
||
|
if not self.is_running:
|
||
|
return
|
||
|
|
||
|
self.steps += 1
|
||
|
self.last_command = fn.__name__
|
||
|
|
||
|
if not self.summary_mode:
|
||
|
self._window.render(self)
|
||
|
|
||
|
def __error(self, message):
|
||
|
"""
|
||
|
Provides error handling.
|
||
|
|
||
|
Args:
|
||
|
message: Error message to be shown.
|
||
|
"""
|
||
|
if not self.summary_mode:
|
||
|
self._window.error(self, message)
|
||
|
self.is_running = False
|
||
|
else:
|
||
|
print(f"Error Shutoff! ({message})", file=stderr)
|
||
|
|
||
|
@skip_for_summary
|
||
|
def __deinit(self):
|
||
|
tk.mainloop()
|
||
|
|
||
|
def __check_state(self, *args, **kwargs):
|
||
|
"""
|
||
|
Assures that commands are not run while the robot is not running.
|
||
|
|
||
|
Robot is not running when error has occurred.
|
||
|
"""
|
||
|
return self.is_running
|
||
|
|
||
|
@check_before(__check_state)
|
||
|
def beepers_in_bag(self):
|
||
|
"""
|
||
|
Check if there are any beepers in the Karel's bag.
|
||
|
|
||
|
Returns:
|
||
|
`True` if there is at least one beeper in Karel's beeper bag,
|
||
|
`False` otherwise.
|
||
|
"""
|
||
|
return self.beepers > 0
|
||
|
|
||
|
def no_beepers_in_bag(self):
|
||
|
"""
|
||
|
Check if there are no beepers in the Karel's bag.
|
||
|
|
||
|
Returns:
|
||
|
`True` if there is no beeper in Karel's beeper bag, `False` otherwise.
|
||
|
"""
|
||
|
return not self.beepers_in_bag()
|
||
|
|
||
|
@check_before(__check_state)
|
||
|
def front_is_clear(self):
|
||
|
"""
|
||
|
Check if there is no wall in front of Karel.
|
||
|
|
||
|
Returns:
|
||
|
`True` if there is no obstacle in front of Karel, `False` otherwise.
|
||
|
"""
|
||
|
dx, dy = self.direction.vector
|
||
|
if not (
|
||
|
0 <= self.x + dx < self.world.width
|
||
|
and 0 <= self.y + dy < self.world.height
|
||
|
):
|
||
|
return False
|
||
|
|
||
|
if self.world.data[self.y + dy][self.x + dx] == Block.Wall:
|
||
|
return False
|
||
|
|
||
|
return True
|
||
|
|
||
|
def front_is_blocked(self):
|
||
|
"""
|
||
|
Check if there is a wall in front of Karel.
|
||
|
|
||
|
Returns:
|
||
|
`True` if there is an obstacle in front of Karel, `False` otherwise.
|
||
|
"""
|
||
|
return not self.front_is_clear()
|
||
|
|
||
|
@check_before(__check_state)
|
||
|
def __side_is_clear(self, d):
|
||
|
"""
|
||
|
Helper function for determining whether the sides of Karel are clear.
|
||
|
|
||
|
Args:
|
||
|
d: Counterclockwise change in angle of Karel's heading.
|
||
|
|
||
|
Returns:
|
||
|
`True` if the side after applying the `d` counterclokwise turn
|
||
|
is clear, `False` otherwise.
|
||
|
"""
|
||
|
original_direction = self.direction
|
||
|
self.direction = self.direction + d
|
||
|
|
||
|
is_clear = self.front_is_clear()
|
||
|
self.direction = original_direction
|
||
|
|
||
|
return is_clear
|
||
|
|
||
|
def left_is_clear(self):
|
||
|
"""
|
||
|
Check if there is no wall to the left side of Karel.
|
||
|
|
||
|
Returns:
|
||
|
`True` if there is no obstacle to the left side of Karel,
|
||
|
`False` otherwise.
|
||
|
"""
|
||
|
return self.__side_is_clear(90)
|
||
|
|
||
|
def left_is_blocked(self):
|
||
|
"""
|
||
|
Check if there is a wall to the left side of Karel.
|
||
|
|
||
|
Returns:
|
||
|
`True` if there is an obstacle to the left side of Karel,
|
||
|
`False` otherwise.
|
||
|
"""
|
||
|
return not self.left_is_clear()
|
||
|
|
||
|
def right_is_clear(self):
|
||
|
"""
|
||
|
Check if there is no wall to the right side of Karel.
|
||
|
|
||
|
Returns:
|
||
|
`True` if there is no obstacle to the right side of Karel,
|
||
|
`False` otherwise.
|
||
|
"""
|
||
|
return self.__side_is_clear(-90)
|
||
|
|
||
|
def right_is_blocked(self):
|
||
|
"""
|
||
|
Check if there is a wall to the right side of Karel.
|
||
|
|
||
|
Returns:
|
||
|
`True` if there is an obstacle to the right side of Karel,
|
||
|
`False` otherwise.
|
||
|
"""
|
||
|
return not self.right_is_clear()
|
||
|
|
||
|
@check_before(__check_state)
|
||
|
def facing_north(self):
|
||
|
"""
|
||
|
Check if Karel is facing north.
|
||
|
|
||
|
Returns:
|
||
|
`True` if Karel is facing north, `False` otherwise.
|
||
|
"""
|
||
|
return self.direction == Direction.North
|
||
|
|
||
|
def not_facing_north(self):
|
||
|
"""
|
||
|
Check if Karel is »not« facing north.
|
||
|
|
||
|
Returns:
|
||
|
`True` if Karel is »not« facing north, `False` otherwise.
|
||
|
"""
|
||
|
return not self.facing_north()
|
||
|
|
||
|
@check_before(__check_state)
|
||
|
def facing_south(self):
|
||
|
"""
|
||
|
Check if Karel is facing south.
|
||
|
|
||
|
Returns:
|
||
|
`True` if Karel is facing south, `False` otherwise.
|
||
|
"""
|
||
|
return self.direction == Direction.South
|
||
|
|
||
|
def not_facing_south(self):
|
||
|
"""
|
||
|
Check if Karel is »not« facing south.
|
||
|
|
||
|
Returns:
|
||
|
`True` if Karel is »not« facing south, `False` otherwise.
|
||
|
"""
|
||
|
return not self.facing_south()
|
||
|
|
||
|
@check_before(__check_state)
|
||
|
def facing_east(self):
|
||
|
"""
|
||
|
Check if Karel is facing east.
|
||
|
|
||
|
Returns:
|
||
|
`True` if Karel is facing east, `False` otherwise.
|
||
|
"""
|
||
|
return self.direction == Direction.East
|
||
|
|
||
|
def not_facing_east(self):
|
||
|
"""
|
||
|
Check if Karel is »not« facing east.
|
||
|
|
||
|
Returns:
|
||
|
`True` if Karel is »not« facing east, `False` otherwise.
|
||
|
"""
|
||
|
return not self.facing_east()
|
||
|
|
||
|
@check_before(__check_state)
|
||
|
def facing_west(self):
|
||
|
"""
|
||
|
Check if Karel is facing west.
|
||
|
|
||
|
Returns:
|
||
|
`True` if Karel is facing west, `False` otherwise.
|
||
|
"""
|
||
|
return self.direction == Direction.West
|
||
|
|
||
|
def not_facing_west(self):
|
||
|
"""
|
||
|
Check if Karel is »not« facing west.
|
||
|
|
||
|
Returns:
|
||
|
`True` if Karel is »not« facing west, `False` otherwise.
|
||
|
"""
|
||
|
return not self.facing_west()
|
||
|
|
||
|
@check_before(__check_state)
|
||
|
def beepers_present(self):
|
||
|
"""
|
||
|
Check whether Karel stands on a position that has any beepers present.
|
||
|
|
||
|
Returns:
|
||
|
`True` if there is at least one beeper present on the current
|
||
|
position, `False` otherwise.
|
||
|
"""
|
||
|
return self.world.data[self.y][self.x] > 0
|
||
|
|
||
|
def no_beepers_present(self):
|
||
|
"""
|
||
|
Check whether Karel stands on a position that has no beepers present.
|
||
|
|
||
|
Returns:
|
||
|
`True` if there is no beeper present on the current position,
|
||
|
`False` otherwise.
|
||
|
"""
|
||
|
return not self.beepers_present()
|
||
|
|
||
|
@check_before(__check_state)
|
||
|
@after(__render)
|
||
|
def step(self):
|
||
|
"""
|
||
|
Move Karel one position in the direction he's facing.
|
||
|
"""
|
||
|
if not self.front_is_clear():
|
||
|
return self.__error("Can't move this way")
|
||
|
|
||
|
dx, dy = self.direction.vector
|
||
|
self.x += 2 * dx
|
||
|
self.y += 2 * dy
|
||
|
self.__update(dx, dy)
|
||
|
|
||
|
@check_before(__check_state)
|
||
|
@after(__render)
|
||
|
def turn_left(self):
|
||
|
"""
|
||
|
Turn Karel counterclockwise, i.e. left, by 90 degrees.
|
||
|
"""
|
||
|
self.direction = self.direction + 90
|
||
|
|
||
|
@after(__render)
|
||
|
def turn_off(self):
|
||
|
"""
|
||
|
Turn Karel off.
|
||
|
"""
|
||
|
self.is_running = False
|
||
|
self.__deinit()
|
||
|
|
||
|
@check_before(__check_state)
|
||
|
@after(__render)
|
||
|
def put_beeper(self):
|
||
|
"""
|
||
|
Take a beeper from Karel's beeper bag and put on the current position.
|
||
|
"""
|
||
|
if self.beepers <= 0:
|
||
|
return self.__error("Karel has no beeper to put at the corner")
|
||
|
|
||
|
self.world.data[self.y][self.x] += 1
|
||
|
self.beepers -= 1
|
||
|
|
||
|
@check_before(__check_state)
|
||
|
@after(__render)
|
||
|
def pick_beeper(self):
|
||
|
"""
|
||
|
Pick a beeper from the current position and put it into Karel's
|
||
|
beeper bag.
|
||
|
"""
|
||
|
if self.world.data[self.y][self.x] <= 0:
|
||
|
return self.__error("There is no beeper at the corner")
|
||
|
|
||
|
self.world.data[self.y][self.x] -= 1
|
||
|
self.beepers += 1
|
||
|
|
||
|
def set_step_delay(self, delay):
|
||
|
"""
|
||
|
Set delay of a Karel's step.
|
||
|
|
||
|
Args:
|
||
|
delay: Delay of one step in milliseconds.
|
||
|
"""
|
||
|
self.step_delay = delay
|
||
|
|
||
|
def get_step_delay(self):
|
||
|
"""
|
||
|
Get current delay of a Karel's step.
|
||
|
|
||
|
Returns:
|
||
|
Current delay in milliseconds.
|
||
|
"""
|
||
|
return self.step_delay
|