You need a way of remembering what signal was set for a given input pin. This is what the map is for. Here's an implementation to give you an idea:
At some point you'll find that many gates have a lot in common, so you may make a base
AbstractGate class that implements all the
Cell methods, and has an abstract
Map<Side, Signal> applyLogic(Map<Side, Signal> inputs) method that converts input signals to output signals.
The
Side enum is simple and only contains constants for
LEFT,
RIGHT,
TOP and
BOTTOM. The
Signal enum contains
LOW and
HIGH, and some logical operators like
not(),
and(Signal),
or(Signal),
xor(Signal), etc... If you want, you can use a
boolean instead of
Signal, but I think
Signal is more expressive. It's probably easier to use if your
Signal enum has a way to convert between the two types:
boolean toBoolean() and
static Signal forBoolean(boolean).
I used an interface for
Cell, because it makes your application flexible. When you write an abstract base class,
you should almost always write an interface it implements, and use the interface in your code wherever you can. For instance, most of the
Cells will be some implementation of
AbstractGate, but the
Grid should contain a
Cell[][] field, because you never know if you want to implement your
Cell in a completely different way than the
AbstractGate class does.