Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions kaggle_environments/envs/kaggriculture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Each Farmer / Farm Hand can be given an action every turn. Farmer/Farm Hand CAN

#### Movement

- NORTH, SOUTH, EAST, WEST — Move one cell in that direction. Moves off the edge of the board are no-ops. Locked tiles are passable: a unit may move onto and across unbought quadrants, but tile actions (`PLANT`, `WATER`, `BUILD_*`, etc.) all no-op on a locked tile and consume nothing.
- NORTH, SOUTH, EAST, WEST — Move one cell in that direction. Moves off the edge of the board are no-ops. Locked tiles are passable: a unit may move onto and across unbought quadrants, but tile actions (`PLANT`, `WATER`, `BUILD_*`, etc.) all no-op on a locked tile and consume nothing. The exception is the shed actions `PICKUP`, `DROP`, and `PLACE`-into-shed, which work from any shed-access tile even while that tile is locked — they use the tile only as a standing position and never change it.

#### Shed

Expand Down Expand Up @@ -146,7 +146,7 @@ Each player has their own farm with a set number of squares. Players are unable
- Farmer and hired farm hands drop their inventory at the end of the day in the shed (if there is room)
- Limited to 100 items, excluding seeds. Once the shed is full, any further items added (via `PLACE` mid-day or end-of-day inventory drop) are discarded — there is no overflow holding area, so stockpiling on farmer/hand inventories does not bypass the cap.

The shed sits at the center of the board and is not a tile — it never appears in the `tiles` array, whose only values are `None`, `"LOCKED"`, and structure dicts. "Orthogonally adjacent to the shed" means standing on one of the four center tiles, `(half-1, half-1)`, `(half, half-1)`, `(half-1, half)`, `(half, half)` for `half = boardSize // 2`. At the default `boardSize = 10` those are `(4,4)`, `(5,4)`, `(4,5)`, and `(5,5)`, one in each quadrant.
The shed sits at the center of the board and is not a tile — it never appears in the `tiles` array, whose only values are `None`, `"LOCKED"`, and structure dicts. "Orthogonally adjacent to the shed" means standing on one of the four center tiles, `(half-1, half-1)`, `(half, half-1)`, `(half-1, half)`, `(half, half)` for `half = boardSize // 2`. At the default `boardSize = 10` those are `(4,4)`, `(5,4)`, `(4,5)`, and `(5,5)`, one in each quadrant. Since only NW starts unlocked, three of those four tiles begin locked; the shed is reachable from all of them regardless, because the shed itself is never locked.

### Farmer/Farm Hand

Expand Down
79 changes: 44 additions & 35 deletions kaggle_environments/envs/kaggriculture/kaggriculture.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,9 +321,11 @@ def _apply_unit_action(farm, private, idx, action, board_size, day, turns_per_da
return

tile = farm["tiles"][fy][fx]
if tile == "LOCKED":
return

# Shed operations resolve before the LOCKED guard. They use the tile only as
# a standing position -- the shed itself is always owned -- and three of the
# four shed-access tiles start LOCKED, so guarding them first would make the
# shed unreachable from those tiles.
if op == "DROP":
if not _is_shed_adjacent((fx, fy), board_size):
return
Expand Down Expand Up @@ -358,6 +360,46 @@ def _apply_unit_action(farm, private, idx, action, board_size, day, turns_per_da
_inv_add(inv, item, n)
return

if op == "PLACE":
if len(action) < 2:
return
item = action[1]
# Animal placement: standing on a matching unoccupied structure. A LOCKED
# tile is the string "LOCKED", never a dict, so this branch cannot match
# there and PLACE falls through to the shed path below.
if (
item in ANIMALS
and isinstance(tile, dict)
and tile.get("kind") == ANIMALS[item]["structure"]
and "animal" not in tile
):
if _inv_take(inv, item, 1):
farm["tiles"][fy][fx] = _new_animal(item, day)
return
# Shed drop: orthogonally adjacent to the shed; obeys shedCapacity.
if _is_shed_adjacent((fx, fy), board_size):
n = int(action[2]) if len(action) >= 3 else 1
if n <= 0:
return
n = min(n, inv.get(item, 0))
if n <= 0:
return
current = sum(private["shed"].values())
room = max(0, shed_capacity - current)
n = min(n, room)
if n <= 0:
return
inv[item] -= n
if inv[item] == 0:
del inv[item]
private["shed"][item] = private["shed"].get(item, 0) + n
return

# Everything below mutates the tile the unit stands on, so it requires that
# tile to be owned.
if tile == "LOCKED":
return

if op == "PLANT":
if len(action) < 2:
return
Expand Down Expand Up @@ -446,39 +488,6 @@ def _apply_unit_action(farm, private, idx, action, board_size, day, turns_per_da
farm["tiles"][fy][fx] = {"kind": "PASTURE"}
return

if op == "PLACE":
if len(action) < 2:
return
item = action[1]
# Animal placement: standing on a matching unoccupied structure.
if (
item in ANIMALS
and isinstance(tile, dict)
and tile.get("kind") == ANIMALS[item]["structure"]
and "animal" not in tile
):
if _inv_take(inv, item, 1):
farm["tiles"][fy][fx] = _new_animal(item, day)
return
# Shed drop: orthogonally adjacent to the shed; obeys shedCapacity.
if _is_shed_adjacent((fx, fy), board_size):
n = int(action[2]) if len(action) >= 3 else 1
if n <= 0:
return
n = min(n, inv.get(item, 0))
if n <= 0:
return
current = sum(private["shed"].values())
room = max(0, shed_capacity - current)
n = min(n, room)
if n <= 0:
return
inv[item] -= n
if inv[item] == 0:
del inv[item]
private["shed"][item] = private["shed"].get(item, 0) + n
return

if op == "FEED":
if not (isinstance(tile, dict) and "animal" in tile):
return
Expand Down
51 changes: 51 additions & 0 deletions tests/envs/kaggriculture/test_kaggriculture.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,57 @@ def test_movement_blocked_off_map():
assert farm["hands"][0] == [9, 9]


def test_shed_ops_work_from_a_locked_shed_access_tile():
"""Three of the four shed-access tiles start locked; the shed still works."""
farm = _new_farm(10, 100)
private = _new_private()
private["shed"]["WHEAT"] = 5
# (5, 4) is a shed-access tile in the NE quadrant, so it starts locked.
farm["farmer"] = [5, 4]
assert farm["tiles"][4][5] == "LOCKED"

_apply_unit_action(farm, private, 0, ["PICKUP", "WHEAT", 2], 10, 0, 24)
assert private["inventories"][0]["WHEAT"] == 2
assert private["shed"]["WHEAT"] == 3

_apply_unit_action(farm, private, 0, ["PLACE", "WHEAT", 1], 10, 0, 24)
assert private["inventories"][0]["WHEAT"] == 1
assert private["shed"]["WHEAT"] == 4

_apply_unit_action(farm, private, 0, ["DROP"], 10, 0, 24)
assert private["inventories"][0].get("WHEAT", 0) == 0
assert private["shed"]["WHEAT"] == 5


def test_tile_ops_still_noop_on_locked_shed_access_tile():
"""Resolving shed ops early must not let tile ops run on locked ground."""
farm = _new_farm(10, 100)
private = _new_private()
private["seeds"]["CARROT"] = 3
farm["farmer"] = [5, 4]
assert farm["tiles"][4][5] == "LOCKED"

for action in (["PLANT", "CARROT"], ["BUILD_COOP"], ["BUILD_PASTURE"], ["DIG"]):
_apply_unit_action(farm, private, 0, action, 10, 0, 24)
assert farm["tiles"][4][5] == "LOCKED", action
assert private["seeds"]["CARROT"] == 3


def test_place_animal_still_noop_on_locked_tile():
"""PLACE moved above the guard, but animals need an owned structure."""
farm = _new_farm(10, 100)
private = _new_private()
# (5, 4) is locked and shed-adjacent: the animal branch must not match, and
# the shed fallback must not silently swallow the animal either.
farm["farmer"] = [5, 4]
private["inventories"][0]["CHICKEN"] = 1
_apply_unit_action(farm, private, 0, ["PLACE", "CHICKEN"], 10, 0, 24)
assert farm["tiles"][4][5] == "LOCKED"
# It went into the shed as a plain item, not onto the locked tile.
assert private["inventories"][0].get("CHICKEN", 0) == 0
assert private["shed"]["CHICKEN"] == 1


# --- Shed / pickup / inventory ---------------------------------------------

def test_pickup_requires_shed_adjacency():
Expand Down