🇫🇷 Français    🇬🇧 English

Save the Hedgehog!

A poor hedgehog has gotten lost in a series of enchanted gardens… It is up to you to find it!

Step into the shoes of a brave gardener and set off on an adventure through maps full of surprises. Collect all the carrots to open the doors, avoid the wasps (they sting hard but do not survive), and beware of hornets (tougher and even more dangerous).

Each step costs energy depending on the terrain. Stay still for a short while and you will recover some energy. But be careful: if you get stung without enough energy to absorb the hit, the game is over.

So, who will dare cross these gardens and save the hedgehog?

UBGarden

Game Principles

Each garden is a rectangular map made of cells.

Some cells can be crossed, others cannot. The gardener, wasps, and hornets move on this grid. Doors let you move from one garden to another, provided they are open. Wasps and hornets never use doors: they are terrified of drafts and refuse to step on them, whether they are open or closed.

To open the doors, all carrots must be collected. But beware: the gardens are full of wasps and hornets… and you are allergic to stings!

Wasps die after stinging you. Hornets can sting twice before dying. Stings are costly: -20 energy for a wasp, -30 for a hornet. If you have no energy left when you are stung, the game is lost.

Fortunately, insecticide bombs are here to help. Each bomb kills a wasp; two bombs are needed to kill a hornet. Insects may also walk onto bombs, which is bad for their health.

Terrain rules:

Bonuses are collected automatically when walking over them:

The energy level, fatigue level (diseaseLevel), and number of bombs are always displayed in the status bar.

Environment

Here are the elements you will encounter in the game:

Image Name Description
Grass Gardener movement cost: 1 x diseaseLevel
Dirt Gardener movement cost: 2 x diseaseLevel
Flower bush Passable only by wasps and hornets
Wasp nest Spawns one wasp + one insecticide bomb every 5 seconds
Hornet nest Spawns one hornet + two insecticide bombs every 10 seconds
Hedgehog Frightened hedgehog to rescue as quickly as possible
Closed door Impassable until all carrots have been collected
Open door Allows travel to another garden
Carrots Collect them all to open the doors
Poisoned apple Temporary sickness, increased fatigue
Apple Cure + energy boost
Insecticide bomb Kills a wasp or injures a hornet
Wasp Dies after stinging or after touching a bomb
Hornet Can sting twice; deals heavy damage and requires two insecticide bombs
Gardener That’s you!

We can classify these elements as follows:

Getting Started

We provide you with an initial draft of the game.

At startup, a simple map (MapLevelDefaultStart) is loaded: the gardener can move freely on it, regardless of the type of cell.
Game parameters are stored in a .properties file, which is easy to modify.

Java properties files make it easy to store key/value pairs.

Parameter Default value Comment
waspMoveFrequency 1 / s Wasp movement speed
hornetMoveFrequency 2 / s Hornet movement speed
gardenerEnergy 100 Gardener max energy
energyBoost 50 Energy gained from an apple
energyRecoverDuration 1000 ms Idle time needed to recover 1 energy point
diseaseDuration 5000 ms Duration of sickness after eating a rotten core

Work to Do

Starter project archive: UBGarden-student-2025.zip

1. First Steps

2. World Management

3. Bring Wasps to Life

4. Insecticide Bombs

5. Add Hornets

Bonus Management

When the gardener walks over a bonus, it should be picked up. Suppose Apple and PoisonedApple are subclasses of Bonus. One might want to write something like this in the Gardener class:

class Gardener {
    void pickUp(Apple apple) { ... }
    void pickUp(PoisonedApple poisonedApple) { ... }

    void doMove(Direction direction) {
        Position nextPos = direction.nextPosition(getPosition());
        Decor next = game.world().getGrid().get(nextPos);
        setPosition(nextPos);
        if (next != null)
            pickUp(next);
    }
}

But in Java, it is not possible to perform dynamic dispatch based on the runtime type of a method parameter (here, the bonus type). Java only performs dynamic dispatch on the receiver object, not on arguments.

To work around this, we invert the dependency: instead of the gardener “picking up” the bonus, the bonus “gets picked up” by the gardener. With this mechanism, we benefit from dynamic dispatch on the actual bonus type, followed by static dispatch on the gardener’s pickUp(...) method.

class Gardener {
    void pickUp(Apple apple) { ... }
    void pickUp(PoisonedApple poisonedApple) { ... }

    void doMove(Direction direction) {
        // ...
        if (next != null)
            next.pickUpBy(this);
    }
}

public interface Pickupable {
    default void pickUpBy(Gardener gardener) {}
}

public abstract class Bonus implements Pickupable {}

public class Apple extends Bonus {
    @Override
    public void pickUpBy(Gardener gardener) {
        gardener.pickUp(this);
    }
}

public class PoisonedApple extends Bonus {
    @Override
    public void pickUpBy(Gardener gardener) {
        gardener.pickUp(this);
    }
}

In this case, the correct pickUp(Apple) or pickUp(PoisonedApple) method of the gardener is called thanks to double dispatch: