Design a Layered ASCII Canvas Command Processor
AsanaSoftware Engineer · OnsiteLast updated
Design a clean, object-oriented command processor for a fixed 10x6 ASCII canvas that supports drawing, layering, and rendering overlapping filled rectangles.
Problem Statement
Design an ASCII canvas system with a fixed width of 10 columns and height of 6 rows. The canvas allows users to place, remove, and reorder rectangular shapes on different layers. Each rectangle is filled with a single ASCII character. When rendered, higher-layer shapes occlude lower-layer shapes. Pixels with no shapes display a default background character '.'. Coordinates use (x, y) where x represents horizontal position (0 <= x < 10, left to right) and y represents vertical position (0 <= y < 6, top to bottom).
Implement a class AsciiCanvas with the following interface:
class AsciiCanvas:
def __init__(self, width: int = 10, height: int = 6):
"""Initializes the canvas with the given dimensions."""
pass
def add_rectangle(self, shape_id: str, x: int, y: int, width: int, height: int, fill: str) -> bool:
"""Adds a new filled rectangle at the top of the layer stack.
Returns False if shape_id already exists or dimensions are invalid (width bool:
"""Removes the rectangle with shape_id. Returns False if shape_id does not exist."""
pass
def bring_to_front(self, shape_id: str) -> bool:
"""Moves shape_id to the topmost layer. Returns False if shape_id does not exist."""
pass
def send_to_back(self, shape_id: str) -> bool:
"""Moves shape_id to the bottommost layer. Returns False if shape_id does not exist."""
pass
def render(self) -> list[str]:
"""Returns the rendered 10x6 grid as a list of 6 strings, each of length 10."""
pass
Example
canvas = AsciiCanvas(10, 6)
canvas.add_rectangle("A", x=1, y=1, width=4, height=3, fill="A")
canvas.add_rectangle("B", x=3, y=2, width=4, height=3, fill="B")
# B is on top of A
print("\n".join(canvas.render()))
# Output:
# ..........
# .AAAA.....
# .AABBBB...
# .AABBBB...
# ...BBBB...
# ..........
canvas.bring_to_front("A")
print("\n".join(canvas.render()))
# Output:
# ..........
# .AAAA.....
# .AAAABB...
# .AAAABB...
# ...BBBB...
# ..........
Expectations
- Separate domain models (e.g.,
Rectangle,LayerStack,CanvasRenderer) cleanly. - Explicitly handle edge cases: shapes partially or fully out of bounds, duplicate IDs, non-existent shape operations.
- Discuss the trade-offs between rendering lazily (rasterizing on
render()) versus eagerly (maintaining a pixel buffer per operation).
Hints
Hint 1
Consider what data structure enables fast layer reordering (`bring_to_front`, `send_to_back`) as well as quick lookups by `shape_id`.
Hint 2
Decide whether to store pixels in a persistent 2D buffer or reconstruct the visible grid during `render()` by iterating through shapes bottom-to-top or top-to-bottom.
Hint 3
Ensure coordinates are clipped against the `[0, width)` and `[0, height)` boundaries so that out-of-canvas shapes do not throw indexing errors.
Follow-up questions
- How would you optimize rendering if the canvas size were 4K (3840x2160) with thousands of overlapping rectangles, most of which are fully occluded?
- How would you extend this system to support undo/redo operations using the Command pattern?
- If multiple clients were issuing canvas commands concurrently, how would you ensure consistency and prevent race conditions?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.