Skip to content

Navigation Menu

Sign in
Sign up

Latest commit

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

irrgarten

java ruby build license

A turn-based dungeon game, built twice: once in Java and once in Ruby, from the same class design.

Context

Coursework for PDOO — Object Oriented Programming and Design, year 3 of the double degree in Computer Science and Business Administration, University of Granada (2024–2025). Solo work.

The course builds one project across five practicals. Each one adds a layer to the same design, and the last two ask for the same model in a second language. That constraint is the point of the assignment: you cannot paper over a weak design, because you have to express it twice.

The problem

Build a game where several players move through a labyrinth, fight the monsters they walk into, and race to reach the exit. The rules:

  • The labyrinth is a grid with walls, monsters, players and one exit.
  • On each turn a player moves up, down, left or right. Walking into a monster starts a fight.
  • A fight is a series of rounds. Each side rolls its attack, the defender rolls to block, and whoever runs out of tries loses.
  • Players collect weapons and shields, which add to attack and defence and wear out after a few uses.
  • The game ends when a player reaches the exit or the round limit runs out.

Two design constraints came with it: every random decision goes through a single Dice class, and the game core must not know what the interface looks like.

The solution

One place for randomness. Dice holds every random draw in the game: where characters spawn, how much damage a hit does, whether a shield is discarded. Nothing else calls a random number generator. That is what makes the game reproducible and testable at all — if random draws were scattered across ten classes, no test could pin down behaviour.

What a monster and a player share, and where they split. Both stand on a cell, have health and can fight, so that goes in an abstract base. Only the player carries weapons, shields and a name, so that stays in Player:

LabyrinthCharacter (abstract) position, health, attack, defend, fight
├── Monster
└── Player + name, number, weapons, shields
 └── FuzzyPlayer same player, picks its move at random

FuzzyPlayer extends Player and only overrides how the next move is chosen. Every other rule is inherited untouched. It is what lets the game play itself while testing, without a second copy of the rules.

Weapons and shields are the same thing. Both add power, both wear out, both get discarded. CombatElement holds that behaviour, and Weapon and Shield only differ in what they contribute to. In Java the decks that hand them out are one generic class bounded by that base:

abstract class CardDeck<T extends CombatElement>
 ├── WeaponCardDeck extends CardDeck<Weapon>
 └── ShieldCardDeck extends CardDeck<Shield>

The Ruby version has no equivalent. Without generics the deck buys much less, and the practical that introduced it only asked for Java.

The core does not know about the screen. Controller runs the turn loop and holds a UI view, an interface. GraphicUI implements it with Swing. No model class imports anything from the UI package, so the game core never learns what a screen is.

The idea works, the execution is half done: TextUI prints the board to the terminal but never declares implements UI, so Controller cannot take it. That is why the text UI is commented out in Main instead of being a one-line swap. See known limitations.

GameState exists for the same reason: instead of letting the UI reach into the model, the game hands out a flat snapshot of the board, the players, the monsters and the log.

Layout

src/java/irrgarten/ Java version: model, controller, text UI and Swing UI
src/ruby/irrgarten/ Ruby version: same model, text UI only
src/exercises/ 62 theory exercises from the problem sets, Java and Ruby
docs/diagrams/ class diagrams handed in for practical 4
docs/assignment/ the exam brief
docs/latex/ the theory write-ups src/exercises is generated from
docs/notes/ two worked explanations written alongside them
docs/variants/ alternative builds kept for exam practice, see its README
tools/ the extractor
Makefile build and run, both languages

src/exercises/ is the other half of the course. The game is the project; the problem sets are where the design ideas were worked out first:

Folder Files Topic
problem-set-1/ 6 classes, references, copying
problem-set-2/ 1 associations
problem-set-3/ 34 inheritance hierarchies, abstract classes, polymorphism
visibility/ 16 access modifiers, packages, encapsulation
inheritance/ 5 slide exercises on overriding

They came out of the same LaTeX documents the PDFs are built from, using each listing's style=customjava or style=customrb to pick the language.

The four earlier practicals (P1 to P4) are not in the tree. They were delivered as zip archives holding mostly generated Javadoc, .class files and NetBeans project files, and their sources are superseded by src/. They are attached to the v1.0 release instead, so the record is kept without carrying 8 MB of build output in every clone.

Requirements

  • JDK 17 — tested with OpenJDK 17.0.19. The sources target Java 11 and compile on both.
  • Ruby 3.2 — tested with Ruby 3.2.3. Standard library only.
  • make

No external libraries. The Swing UI ships with the JDK.

Build and run

# Java: compile
make build-java
# Java: play. Main wires the Swing UI, so this opens a window
make run-java
# Java: run the practical 1 checks
make test-java
# Ruby: play in the terminal
make run-ruby
# Rebuild the API docs into build/javadoc
make docs-java

make run-ruby needs a real terminal: the text UI reads single keypresses through io/console, so it fails if you pipe input into it.

Results

The Ruby build renders the labyrinth in the terminal. - is a free cell, X a wall, M a monster, and a digit is the player with that number:

nRows: 7, nCols: 7, exitRow: 6, exitCol: 6
 Laberinto:
 [ - ] [ - ] [ - ] [ - ] [ - ] [ - ] [ - ]
 [ - ] [ - ] [ M ] [ - ] [ X ] [ - ] [ - ]
 [ - ] [ M ] [ - ] [ - ] [ X ] [ - ] [ - ]
 [ - ] [ - ] [ - ] [ M ] [ - ] [ - ] [ - ]
 [ 0 ] [ - ] [ - ] [ - ] [ - ] [ - ] [ X ]

The Java build shows the same board in a Swing window.

The class diagrams handed in for practical 4 are in docs/diagrams/: DC-P4-1.pdf is the full diagram and P4-1.pdf the written design notes.

What I learned

  • Writing the same design twice is the real exercise. The two versions match everywhere the design was about the model, and drift apart exactly where it leaned on a language feature. CardDeck<T> is the clearest case: it is a Java answer, and Ruby never grew one.
  • One Dice class was worth more than any test. Funnelling every random draw through one place turned "the game does something odd sometimes" into a bug I could actually find.
  • Half an abstraction is worth less than none. I wrote the UI interface, pointed Controller at it, and then never made TextUI implement it. The design is right and the code does not honour it, so the swap I designed for is a comment in Main. Declaring an interface is the easy half.
  • The tests are not tests. TestP1 prints values to stdout and I check them by eye. That was enough to hand in and it is not enough to trust. Proper assertions were the obvious next step and I did not take it.

Known limitations

  • TextUI does not implement UI. Controller takes a UI, so the text interface cannot be passed to it. Main.java hardcodes GraphicUI and leaves the TextUI lines commented out. Fixing it is one implements UI plus whatever signatures do not line up.
  • The Ruby version stops earlier than the Java one. No CardDeck, no weapon or shield decks, no graphic UI. Only Java has the complete game.
  • src/ruby/irrgarten/test_p1.rb fails. It calls gamestate1.getlabyrinth(), a Java-style name left over from the port; GameState exposes attr_reader :labyrinth. The game itself runs fine.
  • src/java/irrgarten/TestP2.java is commented out in full, so it never compiles into a class. Its Ruby counterpart, test_p2.rb, does exist.
  • GameCharacter is an enum, not a class. It tags a cell as PLAYER or MONSTER. The name suggests a base class and it is not one.

Author and licence

Ismael Sallami Moreno. Released under the MIT licence (see LICENSE).

About

A turn-based dungeon game, built twice from the same class design: once in Java, once in Ruby. PDOO coursework, UGR.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /