| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
parent directory.. | ||||
The neighborhood package gives Python Lab student code a Painter that walks a square grid and colors squares. It is a port of javalab's org.code.neighborhood.
Student code works with painters two ways. The object form constructs them:
from neighborhood import PainterThe function form skips the constructor and acts on one implicit painter — see The default painter.
Everything else the package exports (World, NeighborhoodTracker, NeighborhoodLog, ...) exists for the harness that runs and validates student code, not for students. from neighborhood import * brings in only the student-facing names.
The grid is always square. (0, 0) is the top-left (north-west) corner. x grows east, y grows south. Moving north decreases y.
x: 0 1 2
y: 0 +---+---+---+
| . | . | . |
1 +---+---+---+
| . | # | . | # = wall or obstacle, not passable
2 +---+---+---+
| . | . | . |
+---+---+---+
A square is passable if its tile type is open, start, finish, or start-and-finish. Walls and obstacles are not passable. Off-grid coordinates count as not passable, so the grid edge and a wall behave identically.
Squares carry two independent things:
A square can never hold both at once, see paint() below.
All Painter instances share one World, and therefore one grid. Two painters on the same grid see the same grid state (bucket contents, painted squares). The first Painter constructed loads the grid from serialized_maze.txt in the working directory if the harness has not already set one.
Painter(x=0, y=0, direction='east', paint=None)| Argument | Type | Meaning |
|---|---|---|
| x | int | Starting column. Defaults to 0. |
| y | int | Starting row. Defaults to 0. |
| direction | str | One of "north", "east", "south", "west", matched case-insensitively. Defaults to "east". |
| paint | int | None | Starting units of paint. None means "decide from the grid size" — see Paint accounting. |
An unrecognized direction raises INVALID_DIRECTION. The constructor does not check that (x, y) is on the grid or passable; a painter placed off-grid fails later, on the first call that touches its square.
Every method documented below is also available as a plain function acting on one implicit painter. Three import forms reach the same functions:
from neighborhood import painter
painter.move()
from neighborhood import move, turn_left, paint
move()
from neighborhood import *
move()The implicit painter starts at (0, 0) facing east and is created the first time one of the functions is called, not when the module is imported. That matters because NeighborhoodLog lists painters in construction order and validation code indexes into that list: a program that never calls one of these functions has no default painter, so painter_logs[0] means what it always did.
The painter is discarded and rebuilt whenever the grid or the run context changes, which is how each run of a program — and each pass validation makes over main.py — starts with it back at (0, 0).
Paint works exactly as it does for a Painter() built with no arguments — see Paint accounting.
Both styles work in one program. The implicit painter takes its place in painter_logs at the point it is created — so a bare move() before an explicit Painter() puts the implicit one first.
Advances one square in the direction faced. Raises INVALID_MOVE if the target square is off-grid or not passable — check with can_move() first if that is a possibility.
Rotates 90° counter-clockwise (north → west → south → east → north). This is the only rotation primitive; turning right means calling it three times.
Colors the current square and spends one unit of paint.
color is either a CSS named color (case-insensitive, e.g. "red", "MediumVioletRed") or a hex string in #RGB or #RRGGBB form. Anything else raises INVALID_COLOR.
Two ways this does not paint:
Clears the current square's color. Prints There's no paint to remove here. if the square had none. The removed paint is destroyed, not returned to the painter's bucket.
Moves one unit from the paint bucket on the current square into the painter's bucket. If the square has no bucket, or the bucket is empty, it prints There is no paint to collect here. and changes nothing.
paint defaults to None, which is not the same as 0. When paint is None and the grid is 20×20 or larger, the painter gets infinite paint: has_paint() always returns True and paint() never runs out. On a smaller grid, None means zero units.
Passing paint explicitly always yields a finite amount of paint, whatever the grid size.
Two consequences of how infinite paint is implemented, both worth knowing before you write a test that asserts on paint counts:
Units of paint in the painter's bucket. See the caveat above.
Sets the painter's bucket to paint units. A negative value prints Paint amount must not be a negative number. and changes nothing. Ignored entirely on an infinite painter.
True if the painter has infinite paint or at least one unit left.
True if the square one step away is on the grid and passable. Checks the direction faced when direction is omitted. An unrecognized direction raises INVALID_DIRECTION.
True if the current square has a color.
True if the current square holds a paint bucket with units remaining.
The current square's color, or None if unpainted. Unlike the other queries, this one emits no signal, so a validator watching the signal stream cannot see that it was called.
Check which direction the painter is facing.
Get the painter's current coordinates.
Direction faced, always lowercase: "north", "east", "south", or "west".
These four affect only what the front end draws. They change no grid or painter state, and a hidden painter still moves and paints.
| Method | Effect |
|---|---|
| hide_painter() | Hides this painter's sprite. |
| show_painter() | Shows it again. |
| hide_buckets() | Hides every paint bucket on the grid. |
| show_buckets() | Shows them again. |
Failures raise NeighborhoodRuntimeException, carrying an ExceptionKey the front end maps to a student-facing message.
| Key | Raised when |
|---|---|
| INVALID_DIRECTION | A direction string is not one of the four compass names. |
| INVALID_MOVE | move() is called into a wall or off the grid. |
| INVALID_COLOR | paint() is given something that is neither a CSS color name nor a hex value. |
| INVALID_PAINT_LOCATION | paint() is called on a square holding a paint bucket with paint remaining. |
| GET_SQUARE_FAILED | A painter's own coordinates are off-grid or impassable. Reachable only from a bad constructor call, since move() refuses to enter such a square. |
| INVALID_GRID | The grid file or string is missing, malformed, empty, or not square. |
Conditions a student can hit through ordinary play — an empty bucket, scraping bare ground, taking paint where there is none — print a message instead of raising, so the program continues.
Every state change prints a line the front end parses to animate the grid:
[NEIGHBORHOOD] MOVE {"direction": "east", "id": "painter-1"}
id distinguishes painters; it is assigned from a class counter, so the first painter constructed in a process is painter-1.
Query methods (can_move, has_paint, is_on_paint, is_on_bucket) produce boolean signals, which behave differently depending on the world's context type:
uv run pytest
from this directory.
| Back | FazBrowse Home | New Git URL |