Stone and Saber is a school project I made in 2017 during my final year of bachelor's degree. It was origininally aimed at making us practice polymorphism, but I ended up achieving way more and really enjoyed the process.
I share the source code because the architecture relies on several design patterns and it may be valuable for others (and future me) to take a look.
Disclaimer: I did not update the code since then, and I am aware that it could be improved in many ways.
- What does the code do?
- Run the code
- Possible output
- Code's entry point
- The Factory pattern for creating different implementations of a same interface
- The Builder pattern for instantiating configurable objects
- Aspect-Oriented Programming for logging
- Null Object Pattern for preventing NPE
When executed, the code:
- Generates a kind-of-medieval world randomly populated with some people
- Simulates the passage of time for 30 generations
- Produces random events at each generation that may affect any people, leading humans to:
- Drink
- Fight
More details in the following sections.
git clone https://github.com/echebbi/stone-and-saber.git
mvn compile assembly:single
java -jar target/stoneandsaber-1.0.0-jar-with-dependencies.jar
Here is an example of what can be printed by the program:
----- CREATING THE WORLD AND ITS INHABITANTS:
----- THE POWER IS OWNED BY:
le seigneur Sototusi,
le clan Lysobua.
----- EVENTS THROUGHOUT 10 GENERATIONS:
([M] Lysoliil) - O rage! O despair! I am as poor as a church mouse!
([Y] Siilan) - Hehe, I am so vilain. I stole 96 blings from Lysoliil. I now have 140 blings.
([S] Absofu) - Hey, merchant, here's 1 blings.
([M] Tupafu) - Oh, thank you for this donation, your Highness. Now, I have 90 blings.
([M] Lysoliil) - Ahhh, a good glass of thé! Gasp!
([M] Lymiriri) - O rage! O despair! I am as poor as a church mouse!
([Y] Zulipabo) - Hehe, I am so vilain. I stole 7 blings from Lymiriri. I now have 66 blings.
([S] Siabmibu) - Hey, merchant, here's 12 blings.
([M] Lymiriri) - Oh, thank you for this donation, your Highness. Now, I have 12 blings.
([Y] Soto) - Ahhh, a good glass of rhum! Gasp!
([Y] Ilsibuzu) - I challenge you, Rito!
([Y] Ilsibuzu) - I use Burning Sword against Rito!
([S] Rito) - I use Dagger against Ilsibuzu!
([Y] Ilsibuzu) - I use Burning Sword against Rito!
([S] Rito) - I use Dagger against Ilsibuzu!
([S] Rito) - Argg, I didn't keep up...
([Y] Ilsibuzu) - Huhu, you fool! Did you really think you could defeat me?
*Rito is dead.
([M] Lymiriri) - O rage! O despair! I am as poor as a church mouse!
([Y] Soto) - Hehe, I am so vilain. I stole 12 blings from Lymiriri. I now have 93 blings.
([S] Riasi) - Hey, merchant, here's 3 blings.
([M] Tupafu) - Oh, thank you for this donation, your Highness. Now, I have 93 blings.
----- GENERATION IS OVER ! Hope you enjoyed it.
In order to understand what is going on several things can be noticed.
First of all, each line starting by (..)
corresponds to something that has been said by someone. The letter surrounded by []
indicates the category to which the person belongs:
- Merchant
- Samuraï
- Yakuza
- Traitor
The category defines what the person can say and which events can happen to him. For instance, samuraïs may challenge yakuzas and yakuzas may extort merchants.
People talk when an event happen to them. For example, Lymiriri says "O rage! O despair! I am as poor as a church mouse!
" because he has been robbed.
The main class is Story
. It is used to tell a new story or, in other words, to:
- Create a new random
World
- Predict the
Fate
of the World, with specific events - Let the World evolve according to its Fate
Concretelly, a Story initialize the simulation and then runs it.
I use factories to:
- Create new humans (see HumanFactory)
- Create new weapons (see WeaponFactory)
I use builders to:
- Create a new world (see WorldSeedParameters)
- Create weapons with different characteristics (see WeaponBuilder)
Aspect-Oriented Programming (AOP) is a powerful way to handle cross-cutting concerns, which are, roughly speaking, everything that is not related to the software's business value (logging, security, etc).
See this article for more details.
In this project, I used AOP for two purposes:
- Printing debug logs
- Letting people speak
My main goal was to experiment with AspectJ, which I had discovered a few days before, but I actually feel that centralizing at the same place all the code that deals with logging makes the code cleaner and easier to maintain.
I defined two aspects:
Both of them expose the LOGGING_IS_ACTIVATED
and DEBUG_MODE
constants. Setting LOGGING_IS_ACTIVATED
to false deactivates any log; it's that simple. DEBUG_MODE
produces more detailed logs. For instance, with this option the output shown above would have been:
----- CREATING THE WORLD AND ITS INHABITANTS:
----- THE POWER IS OWNED BY:
le seigneur Sototusi,
le clan Lysobua.
----- EVENTS THROUGHOUT 10 GENERATIONS:
([M] Lysoliil) - O rage! O despair! I am as poor as a church mouse!
([Y] Siilan) - Hehe, I am so vilain. I stole 96 blings from Lysoliil. I now have 140 blings.
([S] Absofu) - Hey, merchant, here's 1 blings.
([M] Tupafu) - Oh, thank you for this donation, your Highness. Now, I have 90 blings.
([M] Lysoliil) - Ahhh, a good glass of thé! Gasp!
([M] Lymiriri) - O rage! O despair! I am as poor as a church mouse!
([Y] Zulipabo) - Hehe, I am so vilain. I stole 7 blings from Lymiriri. I now have 66 blings.
([S] Siabmibu) - Hey, merchant, here's 12 blings.
([M] Lymiriri) - Oh, thank you for this donation, your Highness. Now, I have 12 blings.
([Y] Soto) - Ahhh, a good glass of rhum! Gasp!
([Y] Ilsibuzu) - I challenge you, Rito!
[Ilsibuzu.weapon=Burning Sword(PHYSICAL:200, FIRE:50)]
[Rito.weapon=Dagger(PHYSICAL:100)]
([Y] Ilsibuzu) - I use Burning Sword against Rito!
[Rito.status=HP:500 STR:100 ARM:100]
([S] Rito) - I use Dagger against Ilsibuzu!
[Ilsibuzu.status=HP:500 STR:100 ARM:100]
([Y] Ilsibuzu) - I use Burning Sword against Rito!
[Rito.status=HP:250 STR:100 ARM:100]
([S] Rito) - I use Dagger against Ilsibuzu!
[Ilsibuzu.status=HP:400 STR:100 ARM:100]
([S] Rito) - Argg, I didn't keep up...
([Y] Ilsibuzu) - Huhu, you fool! Did you really think you could defeat me?
*Rito is dead.
([M] Lymiriri) - O rage! O despair! I am as poor as a church mouse!
([Y] Soto) - Hehe, I am so vilain. I stole 12 blings from Lymiriri. I now have 93 blings.
([S] Riasi) - Hey, merchant, here's 3 blings.
([M] Tupafu) - Oh, thank you for this donation, your Highness. Now, I have 93 blings.
----- GENERATION IS OVER ! Hope you enjoyed it.
I also took the opportunity to experiment with reactive programming thanks to the RxJava library.
I used it to:
- Broadcast an event each time a new generation begins
- React to such event by letting world's inhabitant live their lives (drinking, fighting, etc.)
The corresponding code can be found within the Fate class. I also implemented an EventBus that helps me to support an Observer pattern in HumanFactory but, to be honest, it is used poorly.
The Null Object Pattern allows to get rid of null
values by providing a concrete class that does nothing. It's kind of a placeholder that prevents NullPointerException
without hurting the logic of the code.
I hence created the NoWeapon which can be used transparently by Duelists (i.e. humans owning weapons and able to fight).
Indeed, instead of writing:
if (this.weapon != null) {
this.weapon.useOn(opponent);
}
the NoWeapon
class ensures that the following is enough:
this.weapon.useOn(opponent);