18 Commits
Author SHA1 Message Date
Anastasia Foti 83d5217151 New release file 2020-11-08 23:14:24 +02:00
Anastasia Foti 4745c1403e Anastasia joined the project 2020-11-08 22:51:08 +02:00
hoo2 ba5b297078 Some small changes 2020-11-08 21:10:22 +02:00
hoo2 be9db10803 Anastasia joined the project 2020-11-07 21:23:38 +02:00
hoo2 7dd8ede2f0 RC: part A 2020-10-28 18:18:17 +02:00
hoo2 5d8d06c793 Small code changes to match the current report state 2020-10-28 00:23:55 +02:00
hoo2 960dd89f4e First draft of the report 2020-10-28 00:23:29 +02:00
hoo2 ebced2aa74 WIP: report part 3 2020-10-26 23:27:22 +02:00
hoo2 70ae98f31a WIP: report part 2 2020-10-25 22:07:59 +02:00
hoo2 35044f91c7 DEV: Doxyfile added 2020-10-25 22:06:45 +02:00
hoo2 a96bb62966 WIP: Report part 1 2020-10-25 22:06:04 +02:00
hoo2 899b9a892e DEV: Comment rework 2020-10-24 22:40:12 +03:00
hoo2 b1c59453c1 DEV: Comment rework 2020-10-24 22:37:41 +03:00
hoo2 5a3c8c5413 DEV: An extra closed room preventing algorithm added. 2020-10-24 22:25:04 +03:00
hoo2 79bb675c94 FIX: Range get stoped use exception as termination case sentinel 2020-10-24 22:23:40 +03:00
hoo2 524988079f DEV: Player has a champion flag to indicate the supply pickup ability 2020-10-24 22:22:09 +03:00
hoo2 f1e7235253 DEV: Frist working draft (clarifications needed)
To be clarified
 - Can Minotaur collect supplies?
 - Do we need an optimal solution to boards wall placement algorithm?
 - Do we have to find for possible closed wall loops?
2020-10-22 20:24:44 +03:00
hoo2 256cd86e29 Report: An empty report added 2020-10-22 20:23:12 +03:00
20 changed files with 3712 additions and 375 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
bin/ bin/
doc/ doc/
deliverable/ out/
.classpath .classpath
.project .project
+3
View File
@@ -0,0 +1,3 @@
[submodule "report/config"]
path = report/config
url = https://git.hoo2.net/hoo2/LaTeX_confing.git
+2494
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
*.aux
*.log
*.out
*.gz
*.svg
*.xcf
Submodule
+1
Submodule report/config added at 4b3765f4ab
Binary file not shown.

After

Width:  |  Height:  |  Size: 527 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
View File
Binary file not shown.
+360
View File
@@ -0,0 +1,360 @@
%
% Report for data structures 2020-2021 assignment part A.
%
% authors:
% Αναστασία Φώτη ΑΕΜ 8959
% anastaskf@ece.auth.gr
%
% Χρήστος Χουτουρίδης ΑΕΜ 8997
% cchoutou@ece.auth.gr
% AuthReportConfig requirements
% ===============================================================
\newcommand{\AuthorName}{Αναστασία Φώτη}
\newcommand{\AuthorMail}{anastaskf@ece.auth.gr}
\newcommand{\AuthorAEM}{8959}
\newcommand{\CoAuthorName}{Χρήστος Χουτουρίδης}
\newcommand{\CoAuthorMail}{cchoutou@ece.auth.gr}
\newcommand{\CoAuthorAEM}{8997}
\newcommand{\DocTitle}{Λαβύρινθος: Ο Θησέας και ο Μινώταυρος Α}
\newcommand{\Department}{Τμημα ΗΜΜΥ. Τομεάς Ηλεκτρονικής}
\newcommand{\ClassName}{Δομές δεδομένων}
\newcommand{\InstructorName}{Σταυρούλα Σιάχαλου}
\newcommand{\InstructorMail}{ssiachal@auth.gr}
\newcommand{\CurrentDate}{\today}
\input{config/AuthReportConfig.tex}
\setFancyHeadLR{\ClassName}{\DocTitle}
%\setFancyHeadLERO{\ClassName}{\DocTitle}
%\BottomTitleSpace{8em}
% Document
% =================
\begin{document}
\FirstPage
%\tableofcontents
%\listoffigures
%\listoftables
\section{Εισαγωγή}
Η παρούσα εργασία αφορά τη δημιουργία ενός παιχνιδιού λαβυρίνθου με θέμα \textit{“Μια νύχτα στο μουσείο”}.
Στο συγκεκριμένο παιχνίδι καλούμαστε να δημιουργήσουμε ένα λαβύρινθο μέσα στον οποίο κινούνται με τυχαίο τρόπο δύο παίχτες, ο Θησέας και ο Μινώταυρος.
Στόχος του Μινώταυρου είναι να “πιάσει” τον Θησέα και στόχος του Θησέα είναι να βρει όλα τα εφόδια που είναι τυχαία κατανεμημένα στο ταμπλό, πριν ξημερώσει και πριν τον πιάσει ο Μινώταυρος.
Στο παιχνίδι αυτό υπάρχουν δύο βασικά προβλήματα τα οποία χρειάζεται να λύσουμε.
Το πρόβλημα της δημιουργίας του λαβύρινθου και το πρόβλημα της λειτουργίας των παιχτών και του υπόλοιπου παιχνιδιού.
\par Κατά την άποψή μας, σε αυτό το πρώτο μέρος της εργασίας, η δημιουργία του ταμπλό είναι το κυριότερο από τα δύο προβλήματα.
Το ταμπλό αποτελείται από πλακίδια και τοίχους.
Τα πλακίδια είναι διατεταγμένα σε τετραγωνικό σχήμα και ανάμεσά τους τοποθετούνται οι τοίχοι.
Το πρόβλημα έγκειται στην επιλογή και τοποθέτηση τοίχων με τέτοιο τρόπο ώστε να πληρούνται οι προδιαγραφές του παιχνιδιού όπως πχ κάθε πλακίδιο να έχει το πολύ δύο τοίχους ή τα εξωτερικά πλακίδια να έχουν τοίχο από την έξω μεριά.
Μετά από μια πιο λεπτομερή ανάλυση του προβλήματος, διαπιστώσαμε πως οι δοθείσες προδιαγραφές δεν αποτρέπουν τη δημιουργία κλειστών δωματίων, κάτι που θεωρήσαμε άδικο, με αποτέλεσμα, όπως περιγράφουμε και αναλυτικά παρακάτω, \textbf{να προσθέσουμε έναν ακόμη περιορισμό}.
\textbf{Την αποτροπή κλειστών δωματίων στο ταμπλό}.
\par Το πρόβλημα της λειτουργίας του υπόλοιπου παιχνιδιού έχει να κάνει με τη δημιουργία των παιχτών καθώς και τις κινήσεις τους.
Οι προδιαγραφές αφορούν περιορισμούς στην κίνηση των παιχτών και τον τρόπο με τον οποίο λειτουργεί το παιχνίδι.
Για παράδειγμα οι παίχτες δεν μπορούν να περάσουν μέσα από τοίχους, ή οι παίχτες κινούνται κατά ένα πλακίδιο τη φορά κλτ.
Σε αντίθεση με το πρόβλημα του ταμπλό εδώ η λύση ήταν τετριμμένη.
\section{Παραδοτέα}
Τα επισυναπτόμενα παραδοτέα αποτελούνται από:
\begin{itemize}
\item Τον \eng{\textbf{root}} κατάλογο στον οποίο υπάρχει και το \eng{project} του \eng{eclipse}.
\item Ένας υποκατάλογος \eng{\textbf{src/}} με τον κώδικα της \eng{java}, αποτελούμενο από ένα αριθμό αντικειμένων ενσωματωμένο στο πακέτο \eng{host.labyrinth}.
\item Ένας υποκατάλογος \eng{\textbf{out/}} που περιέχει το παραγόμενο \eng{command line jar} του παιχνιδιού.
\item Ένας υποκατάλογος \eng{\textbf{doc/}} με την τεκμηρίωση του κώδικα όπως αυτή έχει παραχθεί από τα σχόλια, με το εργαλείο \eng{doxygen}.
Το αρχείο ρυθμίσεων του \eng{doxygen} είναι στον \eng{root} με το όνομα \eng{Doxyfile}.
Η πλοήγηση στην τεκμηρίωση μπορεί να γίνει ανοίγοντας το αρχείο \eng{doc/html/index.html}
\item Ένας υποκατάλογος \eng{\textbf{report/}} που περιέχει την \textbf{παρούσα αναφορά}.
\end{itemize}
Εκτός από τα επισυναπτόμενα αρχεία διαθέσιμο υπάρχει και το \textbf{\eng{git} αποθετήριο} ολόκληρης της εργασίας \href{https://git.hoo2.net/hoo2/Labyrinth}{εδώ}.
Αυτό περιέχει τόσο τον κώδικα της εφαρμογής όσο και τον κώδικα της αναφοράς.
\section{Σχεδιαστικές επιλογές}
Πριν ασχοληθούμε όμως με τα ζητηθέντα αντικείμενα του προγράμματος, θα πρέπει να αναφερθούμε σε ορισμένες δομές που προστέθηκαν, αλλά και κάποιες σχεδιαστικές επιλογές που έγιναν για να απλοποιήσουν τον κώδικα.
\subsection{\eng{Accessor - mutator idiom}}
Στις προδιαγραφές της εργασίας αφήνεται να εννοηθεί πως ζητείται η χρήση του \eng{\textit{accessor - mutator idiom}}.
Θα πρέπει να παραδεχτούμε όμως, πως \textbf{θεωρούμε το συγκεκριμένο ιδίωμα ιδιαίτερα προβληματικό}.
Ο κύριος λόγος είναι πως παραβιάζει θεμελιακά τις αφαιρέσεις προδίδοντας τον εσωτερικό σχεδιασμό του αντικειμένου.
Ακόμα δίνει πρόσβαση στην εσωτερική δομή του “πίσω από την πλάτη” του αντικειμένου.
Εν αντιθέτως με το ιδίωμα αυτό, \textbf{τα αντικείμενα που υλοποιούνται ως αφαιρέσεις θα μπορούσαν να προσφέρουν μεθόδους που εκτελούν κάποια λειτουργία, κρύβοντας τελείως τις εσωτερικές λεπτομέρειες της υλοποίησης}.
Αυτός είναι και ο δρόμος που διαλέξαμε για το σχεδιασμό του προγράμματος.
Η κάθε τάξη του προγράμματός μας προσφέρει δημόσια ένα αριθμό από μεθόδους που είναι απαραίτητες για την εκάστοτε απαιτούμενη λειτουργικότητα και κρύβει όσο καλύτερα γίνεται την εσωτερική υλοποίηση.
Ενώ λοιπόν υλοποιήσαμε το ζητηθέν \eng{get-set} ζευγάρι για την κάθε μεταβλητή των τάξεων, δεν το χρησιμοποιήσαμε πουθενά μέσα στο πρόγραμμα.
\subsection{Ενοποιημένο σύστημα συντεταγμένων}
Στις προδιαγραφές της εργασίας περιγράφεται επίσης ένα διπλό σύστημα συντεταγμένων, τόσο για τα πλακίδια όσο και για τα εφόδια.
Ένα καρτεσιανό που διευθυνσιοδοτεί ως προς δύο άξονες και περιέχει ένα ζευγάρι γραμμής και στήλης και ένα μονοδιάστατο που αποτελείται από τον γραμμικό συνδυασμό των προηγουμένων.
Το μονοδιάστατο αντικατοπτρίζει και την απεικόνιση στη μνήμη ενός πίνακα 2 διαστάσεων σε \eng{row major order}.
\par Γενικά θεωρούμε πως κάτι τέτοιο δημιουργεί πλεονασμό δεδομένων και επομένως είναι κακή πρακτική.
Αυτό γιατί μεταξύ άλλων μειονεκτημάτων, που κυρίως αφορά τον πολυνηματικό προγραμματισμό, οδηγεί και σε προγραμματιστικά λάθη που πιθανώς θα αφήνουν τα δύο συστήματα ασυγχρόνιστα.
Για να λύσουμε αυτό το πρόβλημα \textbf{δημιουργήσαμε την τάξη \eng{Position}}, στην οποία εσωτερικά χρησιμοποιούμε μόνο το ένα από τα δύο συστήματα, για την ακρίβεια το μονοδιάστατο και ταυτόχρονα παρέχουμε μεθόδους για την πρόσβαση στη θέση και από τα δύο συστήματα.
Η τάξη μεταξύ άλλων προσφέρει και \textbf{στατικές μεθόδους για τις μετατροπές} προσφέροντας έτσι μια είδους εργαλειοθήκη για την εφαρμογή.
Για την παρούσα εργασία χρησιμοποιήσαμε την \eng{Position} όπου ήταν δυνατό.
\subsection{Αναβάθμιση της τάξης \eng{Tile}}
Κατά τον προγραμματισμό του παιχνιδιού παρατηρήσαμε πως τόσο η τάξη \eng{Tile} όσο και η \eng{Supply} έχουν πληροφορίες για τη θέση τους στο ταμπλό.
Αυτό σημαίνει πως τόσο τα πλακίδια όσο και τα εφόδια ανήκουν στο ταμπλό.
Ακόμα σημαίνει πως δημιουργούν επιπλέον πλεονασμό σε δεδομένα, καθώς απαιτείται οι συντεταγμένες του πλακιδίου που βρίσκεται το εφόδιο να επαναληφθούν μέσα στην \eng{Supply}.
Κάτι τέτοιο γίνεται αντιληπτό και από τις προδιαγραφές της \eng{Board} η οποία είναι αυτή που περιέχει τους πίνακες αναφορών τόσο των πλακιδίων όσο και των εφοδίων.
\textbf{Μια πιο διαισθητική προσέγγιση βέβαια θα ήθελε τα εφόδια να ανήκουν στα πλακίδια} και όχι στο ταμπλό.
Με αυτό τον τρόπο η τάξη \eng{Supply} δεν θα είχε τις επαναλαμβανόμενες πληροφορίες θέσης, αλλά αντίθετα η τάξη \eng{Tile} θα είχε απλώς μια επιπρόσθετη πληροφορία για το αν υπάρχει εφόδιο ή όχι.
\par Όπως είναι φυσικό θελήσαμε να υλοποιήσουμε αυτή την προσέγγιση.
Αν όμως μετακινούσαμε τις αναφορές των εφοδίων στην \eng{Tile} θα αλλοιώναμε τις προδιαγραφές της εκφώνησης.
Επομένως επιλέξαμε μια μέση οδό.
Εμπλουτίσαμε την \eng{Tile} με μεθόδους που αφορούν τα εφόδια.
Αυτές είναι οι:
\begin{itemize}
\item \eng{\textbf{\textit{int hasSupply (Supply[] supplies)}}}\\
που δίνει την δυνατότητα να ελέγξουμε αν ένα πλακίδιο έχει κάποιο ενεργό εφόδιο. Και
\item \eng{\textbf{\textit{void pickSupply (Supply[] supplies, int supplyId)}}}\\
που δίνει την δυνατότητα σε κάποιο παίκτη να “σηκώσει” το εφόδιο.
\end{itemize}
Έτσι έχουμε ομοιομορφία με τις αντίστοιχες μεθόδους \eng{\textit{boolean hasWall(int direction)}} και \eng{\textit{int hasWalls()}} της \eng{Tile}.
\par Ένας παρατηρητικός αναγνώστης θα διαπιστώσει πως οι συναρτήσεις για τα εφόδια είναι αναγκασμένες να πάρουν τον πίνακα αναφορών στα εφόδια ως όρισμα.
Αυτό είναι το τίμημα που πρέπει να πληρώσουμε προωθώντας τις μεθόδους αυτές στην \eng{Tile}.
\section{\eng{Concepts}}
Η δημιουργία της \eng{Board} αποτέλεσε τον μεγαλύτερο όγκο του κώδικα της παρούσας εργασίας.
Για να κάνουμε τον κώδικα καθαρότερο αλλά και ευκολότερο στην κατανόηση επινοήσαμε κάποια \eng{\textbf{concepts}}.
Η ιδέα των \eng{concepts} προέρχεται από την \eng{C++} όπου τα \eng{concepts} είναι ένα είδους \eng{compile time predicate} και εφαρμόζεται στους τύπους δεδομένων.
Εμείς για την εργασία υλοποιήσαμε κάποια \eng{concepts} σε μορφή συναρτήσεων κατά την εκτέλεση του προγράμματος.
Τα \eng{concepts} αυτά αφορούν έννοιες σχετικές με την εφαρμογή και έχουν την μορφή \eng{predicate}.
Μας δίνεται έτσι η δυνατότητα να ελέγχουμε αν κάποια είσοδός ενός \eng{predicate-concept} πληροί τις προδιαγραφές του ή όχι.
Στο σχήμα \ref{fig:concepts} φαίνεται μια οπτικοποιημένη έκδοση τους.
\subsection{Πλακίδιο φρουρός - \textit{\eng{isSentinel()}}}
\WrapFigure{0.45}{r}{fig:concepts}{images/concepts.png}{
Οπτική αναπαράσταση των \eng{concepts} που χρησιμοποιούμε σε ένα ταμπλό $7x7$.
Με πράσινο αναπαρίστανται όσα πληρούν κάποιο \eng{concept} και με κόκκινο όσα όχι.
Τα πλακίδια με το γκρι χρώμα, είναι τα πλακίδια φρουροί.
}
Πρόκειται για \eng{concept} που μας επιτρέπει να ελέγξουμε αν το πλακίδιο είναι \textit{”πλακίδιο φρουρός”}.
Αν δηλαδή βρίσκεται στα εξωτερικά άκρα του ταμπλό.
Η υλοποίηση αυτού του \eng{concept} γίνεται μέσω τεσσάρων συναρτήσεων που ελέγχουν χωριστά τις τέσσερεις διευθύνσεις του ταμπλό.
\par Για την παράδειγμα η \eng{\textit{boolean isLeftSentinel (int tileId)}} μας δίνει την δυνατότητα να ελέγξουμε αν το πλακίδιο είναι \textit{πλακίδιο φρουρού} αριστερά του ταμπλό.
Αυτό είναι χρήσιμο για λειτουργίες όπως για παράδειγμα αν θέλουμε να δούμε μήπως χρειάζεται να τοποθετηθεί εξωτερικός τοίχος αριστερά του πλακιδίου.
Αντίστοιχα υπάρχουν και οι υπόλοιπες συναρτήσεις για τις υπόλοιπες διευθύνσεις.
\subsection{Διασχίσιμη διεύθυνση - \textit{\eng{isWalkable()}}}
Πρόκειται για \eng{predicate} που μας επιτρέπει να ελέγξουμε αν μια διεύθυνση σε κάποιο πλακίδιο είναι \textit{”διασχίσημη διεύθυνση”}.
Αν δηλαδή κάποιος παίκτης μπορεί να κινηθεί σε αυτή.
Για να ισχύει κάτι τέτοιο θα πρέπει:
\begin{itemize}
\item Η διεύθυνση να μην έχει τοίχο.
\item Η διεύθυνση να μην είναι η “Κάτω” διεύθυνση του πλακιδίου εισόδου στο λαβύρινθο.
\end{itemize}
\subsection{Χτίσιμη διεύθυνση - \textit{\eng{isWallableDir()}}}
Πρόκειται για \eng{predicate} που μας επιτρέπει να ελέγξουμε αν μια διεύθυνση κάποιου πλακιδίου είναι \textit{”χτίσιμη διεύθυνση”}.
Αν δηλαδή μπορούμε να τοποθετήσουμε τοίχο στη διεύθυνση αυτή.
Για να είναι μια διεύθυνση χτίσιμη θα πρέπει:
\begin{itemize}
\item Η διεύθυνση να είναι \textit{διασχίσιμη}.
\item Το γειτονικό πλακίδιο σε αυτή τη διεύθυνση να μην περιέχει ήδη τον μέγιστο επιτρεπτό αριθμό τοίχων.
\item Ο τοίχος να μην δημιουργεί κάποιο κλειστό δωμάτιο.\\
\end{itemize}
Αυτή η τελευταία \textbf{απαίτηση δεν υπάρχει στις προδιαγραφές} και την παίρνουμε υπόψιν, όπως θα δούμε και στην ενότητα \ref{sec:excecutable}, μόνο αν ο χρήστης την έχει ζητήσει από την γραμμή εντολών.
Ο λόγος είναι γιατί ο υπολογισμός της κοστίζει και αυτό μπορεί να μην παίζει ρόλο για ταμπλό μεγέθους $15x15$, αλλά αν ζητηθεί κάποιο πολύ μεγαλύτερο τότε ο χρόνος είναι υπολογίσιμος.
Φυσικά στον κώδικα κάνουμε χρήση αυτού του \eng{concept} μόνο κατά τη δημιουργία του ταμπλό, με αποτέλεσμα να μην επιβαρύνεται καθόλου η λειτουργία του προγράμματος κατά τη διάρκεια του παιχνιδιού.
Έτσι προτείνουμε στον αναγνώστη και χρήστη της εφαρμογής μας να κάνει χρήση αυτής της επιλογής.
Αναφερόμαστε αναλυτικά σε αυτό τον αλγόριθμο στην ενότητα \ref{sec:isRoomCreator}
\subsection{Χτίσιμο πλακίδιο - \textit{\eng{isWallable()}}}
Πρόκειται για \eng{predicate} που μας επιτρέπει να ελέγξουμε αν κάποιο πλακίδιο είναι \textit{”χτίσιμo πλακίδιο”}.
Αν δηλαδή υπάρχει κάποια πλευρά του πλακιδίου στην οποία μπορούμε να τοποθετήσουμε τοίχο.
Για να ισχύει αυτό θα πρέπει:
\begin{itemize}
\item Να υπάρχει τουλάχιστον μία \textit{χτίσιμη διεύθυνση} στο πλακίδιο.
\item Το πλακίδιο να μην έχει ήδη τον μέγιστο επιτρεπτό αριθμό τοίχων.
\end{itemize}
\subsection{Δημιουργός κλειστού δωματίου - \textit{\eng{isRoomCreator()}}} \label{sec:isRoomCreator}
Όπως αναφέραμε και παραπάνω οι προδιαγραφές της εργασίας δεν αποτρέπουν τη δημιουργία κλειστών δωματίων.
Για το λόγο αυτό υλοποιήσαμε ένα αλγόριθμο που ανιχνεύει την πιθανότητα δημιουργίας κλειστών δωματίων του οποίου η ενεργοποίηση γίνεται κατόπιν επιλογής του χρήστη από τη γραμμή εντολών.
\par Ένας αλγόριθμος για να είναι λειτουργικός χρειάζεται δεδομένα.
Στη δική μας περίπτωση τα δεδομένα είναι οι τοίχοι.
Θέλαμε λοιπόν ένα τρόπο αναπαράστασης των τοίχων που να βολεύει για το συγκεκριμένο πρόβλημα.
Η λύση που χρησιμοποιήσαμε συνοψίζεται στα εξής:
\begin{itemize}
\item Οι γωνίες των πλακιδίων ονομάζονται κόμβοι και η θέση τους αναπαρίστανται με έναν αύξοντα αριθμό που ισοδυναμεί με την αναπαράσταση δισδιάστατου πίνακα σε \eng{row major order}.
\item Ο κάθε κόμβος αποτελεί τον κόμβο-γωνία \eng{(vertex)} ενός γράφου.
\item Ο κάθε τοίχος αναπαρίσταται ως ακμή \eng{(edge)} στον γράφο.
\end{itemize}
Για παράδειγμα στο σχήμα \ref{fig:graph}, που φαίνεται και ένα παράδειγμα αναπαράστασης, ο τοίχος αριστερά του πλακιδίου `3` αναπαρίσταται ως η ακμή `(4,8)`.
\InsertFigure{0.6}{fig:graph}{images/graph.png}{
\\(α) Διευθυνσιοδότηση πλακιδίων(τετραγωνάκια) και κόμβων(σφαίρες) ενός ταμπλό $3x3$.\\
(β) Ένας συνεκτικός γράφος που προκύπτει από το (α) ξεκινώντας από τον τοίχο (7, 11).
Για τον εν λόγο τοίχο θέλουμε να διαπιστώσουμε αν δημιουργεί κλειστό δωμάτιο.
Κάτι που σε αυτή την περίπτωση δεν συμβαίνει.
}
\par Για τον αλγόριθμο υλοποιήσαμε δύο τάξεις.
Την \eng{\textbf{Edge}} που δημιουργεί ζευγάρια κόμβων ώστε να μπορεί να αποθηκεύσει τον κάθε τοίχο(ακμή) και την \eng{\textbf{Graph}} που προσφέρει λειτουργίες δημιουργίας συνεκτικού γράφου λαμβάνοντας ως είσοδο τοίχους(ακμές).
\par Η λειτουργία του είναι απλή.
Κάθε φορά που ελέγχουμε αν μία διεύθυνση πλακιδίου είναι \textit{χτίσιμη}, \textbf{δημιουργούμε το μεγαλύτερο δυνατό συνεκτικό γράφο που περιέχει τον εν λόγο τοίχο} και όλους τους ήδη τοποθετημένους τοίχους.
Αν στον γράφο που προκύπτει υπάρχει κάποιος κόμβος περισσότερες από μία φορές, δηλαδή ο γράφος δεν είναι απλός, τότε αυτό σημαίνει πως στον εν λόγο κόμβο μπορούμε να πάμε ακολουθώντας τοίχους από τουλάχιστον δύο κατευθύνσεις.
Άρα το ταμπλό περιέχει κάποιο κλειστό δωμάτιο.
Αφού τον αλγόριθμο τον εκτελούμε για κάθε πιθανή \textit{χτίσιμη διεύθυνση}, τότε ο τοίχος που προκαλεί το κλειστό δωμάτιο είναι ο τρέχον.
\par Για την λειτουργία του αλγόριθμου χρειαζόμαστε όλους τους τοίχους που είναι ήδη τοποθετημένοι στο ταμπλό στη μορφή \eng{Edge}.
Γιαυτό προσθέσαμε στην τάξη \eng{Board} μια λίστα αναφορών \eng{(ArrayList)} και σε αυτήν αποθηκεύουμε κάθε τοίχο που δημιουργούμε.
Τον αλγόριθμο μπορούμε να τον ενεργοποιήσουμε αν περάσουμε στο πρόγραμμα ως επιλογή το όρισμα \eng{\texttt{-{}-norooms}} από τη γραμμή εντολών
\footnote{Συνίσταται ανεπιφύλακτα η χρήση αυτής της επιλογής}.
\par Δυστυχώς η κωδικοποίηση που χρησιμοποιούμε εδώ δεν ταιριάζει με αυτή της υπόλοιπης εφαρμογής.
Αυτό έχει σαν αποτέλεσμα να πρέπει να δημιουργούμε τον γράφο κάθε φορά.
Αυτό έχει κόστος σε χρόνο.
Για την ακρίβεια $O(N^{2}logN)$, όπου $Ν$, ο συνολικός αριθμός πλακιδίων του ταμπλό.
Αυτό το κόστος αφορά τον έλεγχο του κάθε πλακιδίου.
Για όλο το ταμπλό το κόστος είναι $O(N^{4}logN)$.
Φυσικά θα μπορούσαμε να χρησιμοποιήσουμε υπομνηματισμό και να αποθηκεύουμε τους γράφους
Όμως λόγο του ότι η επιβάρυνση λαμβάνει χώρα μόνο μία φορά κατά την εκκίνηση, σε συνδυασμό με το μικρό μέγεθος του ταμπλό, αποφασίσαμε να μην προχωρήσουμε σε περαιτέρω βελτιστοποίηση.
\section{Υλοποίηση}
Για την μεταγλώττιση της εφαρμογής, απαιτείται \eng{java} έκδοση 8 ή και μεταγενέστερη καθώς έχουμε κάνει χρήση \eng{lambdas}.
Όσο αφορά την υλοποίηση, εκτός από τα ζητηθέντα αντικείμενα υλοποιήσαμε και τα παρακάτω.
\begin{itemize}
\item \eng{\textbf{Const}}\\
Το αντικείμενο αυτό περιέχει σταθερές για όλη την εφαρμογή.
\item \eng{\textbf{Session}}\\
Το αντικείμενο αυτό περιέχει όλες τις τιμές της εφαρμογής που αποτελούν ρυθμίσεις ή επιλογές, όπως πχ το μέγεθος του ταμπλό, τον αριθμό των εφοδίων κτλ.
\item \eng{\textbf{Direction}}\\
Το αντικείμενο αυτό λειτουργεί σαν \eng{C++ enumerator} και παρέχει ονοματολογία στις διευθύνσεις που χρησιμοποιούμε στην εφαρμογή.
\item \eng{\textbf{DirRange}}\\
Ομοίως ένα βοηθητικό αντικείμενο αυτή τη φορά για την αυτόματη δημιουργία διευθύνσεων σε βρόχους επανάληψης.
\item \eng{\textbf{Edge}}\\
Το αντικείμενο αυτό όπως είδαμε και στην ενότητα \ref{sec:isRoomCreator}, λειτουργεί ως αναπαράσταση των τοίχων με τη μορφή ακμών ενός γράφου.
Προσφέρει \eng{constructor} που δέχεται για ορίσματα συντεταγμένες από πλακίδια και διευθύνσεις.
Με αυτό τον τρόπο λειτουργεί ως διεπαφή ανάμεσα στην κωδικοποίηση που χρησιμοποιείται στην υπόλοιπη εφαρμογή και στην κωδικοποίηση που χρησιμοποιείται για την εύρεση κλειστών δωματίων.
\item \eng{\textbf{Graph}}\\
Το αντικείμενο αυτό υλοποιεί λειτουργίες ενός συνεκτικού γράφου.
Ο \eng{constructor} της τάξης δέχεται ως όρισμα μια ακμή και στην ουσία δημιουργεί τους 2 πρώτους κόμβους.
Οι βασικές λειτουργίες είναι η \eng{\textit{attach()}} η οποία δέχεται μια ακμή και προσπαθεί να τοποθετήσει τους κόμβους της στο γράφο με συνεκτικό τρόπο.
Και η \eng{\textit{count()}} η οποία δέχεται ένα κόμβο και μετράει πόσες φορές ο κόμβος αυτός περιέχεται στον γράφο.
\item \eng{\textbf{Position}}\\
Το αντικείμενο αυτό χρησιμοποιείται ως ένα κοινό σύστημα αναπαράστασης συντεταγμένων για την εφαρμογή.
Ακόμα προσφέρει μεθόδους μετατροπής της μιας αναπαράστασης στην άλλη.
Οι αναπαραστάσεις αυτές όπως αναφερθήκαμε και παραπάνω είναι η καρτεσιανή που περιέχει δύο μεταβλητές για την γραμμή και τη στήλη και η μονοδιάστατη \eng{(id)}.
\item \eng{\textbf{Range}}\\
Το αντικείμενο χρησιμοποιείται για να δημιουργεί εύρη τιμών.
Για παράδειγμα ο κώδικας παρακάτω δημιουργεί ένα \eng{range} με όλες τις διευθύνσεις.
\setEnglish
\begin{verbatim}
Range dirs = new Range(DirRange.Begin, DirRange.End, DirRange.Step);
for (int dir = dirs.get() ; dir != Const.EOR ; dir = dirs.get()) {
// use dir
}
\end{verbatim}
\setGreek
\item \eng{\textbf{ShuffledRange}}\\
Το αντικείμενο αυτό χρησιμοποιείται για να δημιουργεί \textit{“τυχαίως ανακατεμένα”} εύρη τιμών.
Η τάξη αυτή κληρονομεί την \eng{Range} και προσθέτει τη λειτουργία του τυχαίου ανακατέματος των τιμών.
Για παράδειγμα παρακάτω δημιουργούμε μια τυχαία σειρά από όλα τα πλακίδια του ταμπλό.
\setEnglish
\begin{verbatim}
ShuffledRange rand = new ShuffledRange(0, N*N);
for (int tileId =rand.get(); tileId!=Const.EOR ; tileId=rand.get()){
// use tileId
}
\end{verbatim}
\setGreek
\end{itemize}
Τόσο η \eng{Range} όσο και η \eng{ShuffledRange} έχουν μια μέθοδο \eng{\textit{get()}} η οποία επιστρέφει και αφαιρεί το πρώτο στοιχείο από το \eng{range}.
Όταν το \eng{range} είναι άδειο τότε επιστρέφει την τιμή φρουρό \eng{EOR - End of Range}.
\par Τα υπόλοιπα αντικείμενα είναι τα ζητηθέντα.
Σε αυτά δεν έχουμε αλλάξει τις προδιαγραφές με εξαίρεση τις μεθόδους \eng{\textit{createBoard()}} και \eng{\textit{createSupply()}} στις οποίες προσθέσαμε σαν ορίσματα τα πλακίδια του Θησέα και του Μινώταυρου.
Ο λόγος είναι γιατί για τα εφόδια, θέλουμε να γνωρίζουμε σε ποια πλακίδια είναι οι παίχτες, ώστε να μην τοποθετήσουμε εκεί κάποιο εφόδιο.
Για το ταμπλό δε, γιατί θέλουμε να ξέρουμε σε ποια πλακίδια θα τοποθετήσουμε τους παίχτες.
\section{Εκτελέσιμο} \label{sec:excecutable}
Όπως αναφέραμε και στην παράγραφο με τα παραδοτέα, σε αυτά υπάρχει και το παραγόμενο εκτελέσιμο για την γραμμή εντολών.
Πρόκειται για ένα \eng{jar} αρχείο το οποίο μπορεί κάποιος να εκτελέσει σε ένα τερματικό, μέσω της εντολής \eng{\texttt{java -jar labyrinth}}.
\InsertFigure{0.9}{fig:executable_help}{images/screenshot_help.png}{
Οι διαθέσιμες επιλογές και οι τρόποι με τους οποίους μπορούμε να εκτελέσουμε το παιχνίδι.
}
Στον κώδικά μας χρησιμοποιούμε \eng{asserions} ώστε να ελέγξουμε την είσοδο και τις επιλογές του χρήστη.
Επομένως αν κάποιος θέλει να πειραματιστεί με τις επιλογές καλό θα ήταν να τα ενεργοποιήσει στην \eng{VM} της \eng{java} με την παράμετρο \eng{\texttt{-ea}}.
Σε αυτή την περίπτωση θα μπορούσε να εκτελέσει \eng{\texttt{java -ea -jar labyrinth -i -{}-norooms ...etc}}
\par Το παραγόμενο \eng{jar} παρέχει ένα αριθμό από επιλογές-ορίσματα τα οποία ελέγχουν την λειτουργία του παιχνιδιού.
Τις επιλογές αυτές μπορούμε να δούμε και από την γραμμή εντολών απλώς εκτελώντας την εντολή \eng{\texttt{java -jar labyrinth -h}}.
Στο σχήμα \ref{fig:executable_help} μπορούμε να δούμε ένα στιγμιότυπο με τις διαθέσιμες επιλογές-ορίσματα.
Αναλυτικά, εκτός από την \eng{\texttt{-h}} αυτές είναι:
\begin{itemize}
\item \eng{\textbf{-i}} ή \eng{\textbf{-{}-interactive}}\\
Αυτό το όρισμα ενεργοποιεί το \eng{\textit{“interactive mode”}}.
Πρόκειται για λειτουργία κατά την οποία το παιχνίδι μετά από κάθε γύρο σταματά και περιμένει είσοδο από τον χρήστη για να προχωρήσει στον επόμενο.
Αν αυτή η λειτουργία δεν είναι ενεργοποιημένη, τότε το παιχνίδι εκτελείται μονομιάς και στην έξοδο εκτυπώνονται όλοι οι γύροι αμέσως μετά την είσοδο της εντολής.
\textit{Αυτή η επιλογή είναι απενεργοποιημένη ως προεπιλογή}.
\item \eng{\textbf{-{}-norooms}}\\
Αυτό το όρισμα ενεργοποιεί την λειτουργία της εύρεσης και αποτροπής των κλειστών δωματίων.
Αν δεν γίνει χρήση αυτή της επιλογής, τότε η λειτουργία αυτή είναι απενεργοποιημένη ως προεπιλογή.
Η ενεργοποίησή της θεωρούμε πως είναι καλή επιλογή.
Ο λόγος που δεν την προ-επιλέγουμε είναι γιατί δεν ζητείται ρητά από την εκφώνηση.
\item \eng{\textbf{-b}} ή \eng{\textbf{-{}-board}}\\
Αυτό το όρισμα απαιτείται να ακολουθηθεί από ένα θετικό ακέραιο που περιγράφει το επιθυμητό μέγεθος της πλευράς του ταμπλό.
Πχ:\\[1.2ex]
\eng{\texttt{java -jar labyrinth -b 11}}\\[1.2ex]
Αν ο χρήστης δεν χρησιμοποιήσει την επιλογή τότε το προεπιλεγμένο μέγεθος είναι 15.
\item \eng{\textbf{-s}} ή \eng{\textbf{-{}-supplies}}\\
Αυτό το όρισμα απαιτείται να ακολουθηθεί από ένα θετικό ακέραιο που περιγράφει τον επιθυμητό αριθμό των εφοδίων για το παιχνίδι.
Πχ:\\[1.2ex]
\eng{\texttt{java -jar labyrinth -b 11 -s 7}}\\[1.2ex]
Αν ο χρήστης δεν χρησιμοποιήσει την επιλογή τότε ο προεπιλεγμένος αριθμός είναι 4.
\item \eng{\textbf{-r}} ή \eng{\textbf{-{}-rounds}}\\
Αυτό το όρισμα απαιτείται να ακολουθηθεί από ένα θετικό ακέραιο που περιγράφει τον επιθυμητό αριθμό γύρων μέχρι να ολοκληρωθεί το παιχνίδι.
Πχ:\\[1.2ex]
\eng{\texttt{java -jar labyrinth -r 200 -s 7}}\\[1.2ex]
Αν ο χρήστης δεν χρησιμοποιήσει την επιλογή τότε ο προεπιλεγμένος αριθμός είναι 100.
\end{itemize}
\InsertFigure{0.5}{fig:executable}{images/screenshot.png}{
Στιγμιότυπο από την εκτέλεση του προγράμματος σε \eng{interactive mode} και με αποφυγή κλειστών δωματίων.
Η εντολή που χρησιμοποιήθηκε είναι \eng{\texttt{java -jar labyrinth -i -{}-norooms}}
}
\section{Παρατηρήσεις}
Σε αυτό το σημείο θα θέλαμε να παρατηρήσουμε και μια σχεδιαστική αβλεψία.
Το κομμάτι του κώδικα που δημιουργεί χρήστες καθώς και ορισμένες επιλογές που τους αφορούν, όπως η θέση τους στο ταμπλό, οι κινήσεις κ.α., δεν είναι υλοποιημένο με τον καλύτερο δυνατό τρόπο.
Θα θέλαμε να ήταν πιο γενικό και παραμετροποιήσιμο.
Ο λόγος που δεν είναι, έχει να κάνει αρκετά και με την υποψία μας, πως αυτό το υποσύστημα θα χρειαστεί να αλλάξει αρκετά στις επόμενες εργασίες.
Αν εκτιμήσαμε λανθασμένα, τότε είναι απλώς κρίμα.
\par Μια τεχνική που δεν χρησιμοποιήσαμε αλλά μας προβλημάτισε αν θα το κάναμε ή όχι ήταν τα \eng{generics}.
Η αρχική μας σκέψη, καθώς ήμαστε επηρεασμένοι ξεκάθαρα από την \eng{C++}, ήταν οι τάξεις \eng{Edge, Graph, Range} και \eng{ShuffledRange} να είναι \eng{generics}.
Το γεγονός όμως ότι τα αντικείμενα αυτά θα τα χρησιμοποιούσαμε μονάχα σε αυτή την εργασία, μας απέτρεψε.
Μια απλή υλοποίηση μόνο για \eng{int} ήταν αρκετή και κάτι διαφορετικό θα εισήγαγε περιττή πολυπλοκότητα.
\par Κλείνοντας θα θέλαμε να παρατηρήσουμε πως η παρούσα υλοποίηση θεωρούμε ότι ήταν μια προσπάθεια να ισορροπήσουμε μεταξύ των ζητηθέντων και μεταξύ πρακτικών που ίσως μας βοηθήσουν στη συνέχεια για συγγραφή ευκολότερου και καθαρότερου κώδικα.
Γενικά πιστεύουμε πως \textbf{αν ο χρήστης μιας βάσης κώδικα έχει τη δυνατότητα να χρησιμοποιήσει λανθασμένα τον κώδικα, κάποια στιγμή θα το κάνει.}
Ακόμα και αν ο χρήστης είναι ο ίδιος ο αρχικός συντάκτης του κώδικα.
Για το λόγο αυτό προσπαθήσαμε να περιορίσουμε, όπου αυτό δεν ήταν αντίθετο με τα ζητούμενα, την δυνατότητα της λανθασμένης χρήσης εισάγοντας αρκετά επιπλέον αντικείμενα και δομές ως εργαλεία για την εφαρμογή.
Τέτοια για παράδειγμα είναι η τάξη \eng{Position} ή η αποφυγή των \eng{setters} κτλ.
Ελπίζουμε να μην θεωρηθούν υπερβολικά.
% References
% ============================
%\begin{thebibliography}{100}
%\bibitem{item}item...
%\end{thebibliography}
\end{document}
@@ -1,12 +1,18 @@
/** /**
* @file Board.java * @file Board.java
* *
* @author Christos Choutouridis AEM:8997 * @author
* @email cchoutou@ece.auth.gr * Anastasia Foti AEM:8959
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr>
*/ */
package net.hoo2.auth.labyrinth; package host.labyrinth;
import java.util.ArrayList;
import java.util.function.IntFunction; import java.util.function.IntFunction;
/** /**
@@ -24,26 +30,28 @@ class Board {
* The empty constructor for default initialization * The empty constructor for default initialization
*/ */
Board() { Board() {
this.N = this.S = this.W = 0; this.N = 0;
this.tiles = null; this.S = 0;
this.supplies =null; this.W = 0;
tiles = null;
supplies =null;
walls = new ArrayList<Edge>();
} }
/** /**
* The main constructor for the application * The main constructor for the application
* @param N The size of each edge of the board * @param N The size of each edge of the board
* @param S The number of supplies on the board * @param S The number of supplies on the board
* @param W The number of walls on the board
*/ */
Board(int N, int S, int W) { Board(int N, int S) {
assert (W>= 4*N-1 && W<=(3*N*N+1)/2 ) assert (N%2 != 0) : "Board's size has to be an odd number.";
: "Boards walls has to be in the range [4N-1, (3N^2+1)/2]"; assert (S <= (N*N-2)) : "At least 2 tiles has to be without supplies.";
this.N = Session.boardSize = N; this.N = Session.boardSize = N;
this.S = S; this.S = S;
this.W = W; this.W = 0;
tiles = new Tile[N*N]; tiles = new Tile[N*N];
supplies = new Supply[S]; supplies = new Supply[S];
walls = new ArrayList<Edge>();
} }
/** /**
@@ -67,6 +75,7 @@ class Board {
// Clone arrays // Clone arrays
this.tiles = b.tiles.clone(); this.tiles = b.tiles.clone();
this.supplies = b.supplies.clone(); this.supplies = b.supplies.clone();
this.walls = b.walls;
} }
/** @} */ /** @} */
@@ -79,7 +88,7 @@ class Board {
* @param theseusTile * @param theseusTile
* @param minotaurTile * @param minotaurTile
*/ */
void createBoard(int theseusTile, int minotaurTile) throws Exception { void createBoard(int theseusTile, int minotaurTile) {
createTiles(); createTiles();
createSupplies(theseusTile, minotaurTile); createSupplies(theseusTile, minotaurTile);
} }
@@ -128,7 +137,14 @@ class Board {
} }
/** /**
* Predicate to check if a direction is Walkable * Predicate to check if a direction is Walkable.
*
* A `walkable` direction is a tile direction where:
* <ul>
* <li>The wall is not the DOWN wall from tile (0, 0).
* <li>There is not already a wall in the desired direction. (Implies no sentinel tile).
* </ul>
*
* @param tileId The starting tileId. * @param tileId The starting tileId.
* @param direction The desired direction. * @param direction The desired direction.
* @return True if it is walkable. * @return True if it is walkable.
@@ -139,9 +155,16 @@ class Board {
} }
/** /**
* Predicate to check if a direction is Walkable * Predicate to check if a direction is Walkable.
*
* A `walkable` direction is a tile direction where:
* <ul>
* <li>The wall is not the DOWN wall from tile (0, 0).
* <li>There is not already a wall in the desired direction. (Implies no sentinel tile).
* </ul>
*
* @param row Row position of the starting tile. * @param row Row position of the starting tile.
* @param column Column position of the starting tile. * @param col Column position of the starting tile.
* @param direction The desired direction. * @param direction The desired direction.
* @return True if it is walkable. * @return True if it is walkable.
*/ */
@@ -172,7 +195,7 @@ class Board {
* @return A random direction; * @return A random direction;
*/ */
int dice () { int dice () {
ShuffledRange d = new ShuffledRange(Direction.Begin, Direction.End, Direction.Step); ShuffledRange d = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step);
return d.get(); return d.get();
} }
@@ -202,6 +225,13 @@ class Board {
*/ */
Supply[] getSupplies() { return supplies; } Supply[] getSupplies() { return supplies; }
/**
* @note Use it with care. Any use of this function results to what Sean Parent calls "incidental data-structure".
* <a href="https://github.com/sean-parent/sean-parent.github.io/blob/master/better-code/03-data-structures.md"> see also here</a>
* @return Reference to inner walls array.
*/
ArrayList<Edge> getWalls() { return walls; }
void setN(int N) { this.N = N; } void setN(int N) { this.N = N; }
void setS(int S) { this.S = S; } void setS(int S) { this.S = S; }
void setW(int W) { this.W = W; } void setW(int W) { this.W = W; }
@@ -218,6 +248,23 @@ class Board {
* Any call to this function will probably add memory for the garbage collector. * Any call to this function will probably add memory for the garbage collector.
*/ */
void setSupplies(Supply[] supplies) { this.supplies= supplies; } void setSupplies(Supply[] supplies) { this.supplies= supplies; }
/**
* @param walls Reference to walls that we want to act as replacement for the inner walls vector.
* @note Use with care.
* Any call to this function will probably add memory for the garbage collector.
*/
void setWalls (ArrayList<Edge> walls) { this.walls= walls; }
/** @} */
/** @name Sentinel predicates */
/** @{ */
private boolean isLeftSentinel (int tileId) { return (Position.toCol(tileId) == 0); }
private boolean isRightSentinel (int tileId) { return (Position.toCol(tileId) == N-1); }
private boolean isUpSentinel (int tileId) { return (Position.toRow(tileId) == N-1); }
private boolean isDownSentinel (int tileId) { return (Position.toRow(tileId) == 0); }
/** @} */ /** @} */
/** /**
@@ -228,11 +275,11 @@ class Board {
/** /**
* This function creates randomly all the tiles of the board * This function creates randomly all the tiles of the board
*/ */
private void createTiles() throws Exception { private void createTiles() {
int wallPool = W; int wallCount;
wallPool -= createBasicTileWalls (); // First create tiles with outer walls wallCount = createBasicTileWalls (); // First create tiles with outer walls
if (createInnerWalls(wallPool) > 0) // Create inner walls with the rest of the requested walls wallCount += createInnerWalls(); // Greedy create as many inner walls we can
throw new Exception("Can not create the requested number of walls"); W = wallCount;
} }
/** /**
@@ -254,13 +301,43 @@ class Board {
} }
} }
/** @name Sentinel predicates */ /**
/** @{ */ * Predicate to check if a wall creates a closed room.
private boolean isLeftSentinel (int tileId) { return (Position.toCol(tileId) == 0); } *
private boolean isRightSentinel (int tileId) { return (Position.toCol(tileId) == N-1); } * This algorithm has a complexity of @f$ O(N^2logN) @f$ where N represents the total
private boolean isUpSentinel (int tileId) { return (Position.toRow(tileId) == N-1); } * number of tiles.
private boolean isDownSentinel (int tileId) { return (Position.toRow(tileId) == 0); } * It should be used with care.
/** @} */ *
* @param tileId The tileId of the wall.
* @param direction The wall's relative direction.
* @return True if the wall creates a closed room, false otherwise.
*/
private boolean isRoomCreator (int tileId, int direction) {
// Clone the list of all the walls locally.
ArrayList<Edge> w = new ArrayList<Edge>();
for (Edge it : walls)
w.add(new Edge(it));
// Create the largest possible coherent graph from the list of walls(edges)
Graph g = new Graph(new Edge(tileId, direction));
int size;
do {
size = w.size(); // mark the size (before the pass)
for (int i =0, S=w.size() ; i<S ; ++i) // for each edge(wall) on the local wall list
if (g.attach(w.get(i))) { // can we attach the edge(wall) to the graph ?
w.remove(i); // if yes remove it from the local wall list
--i; --S; // decrease iterator and size to match ArrayList's new values
}
} while (size != w.size()); // If the size hasn't change(no new graph leafs) exit
// Search if a vertex is attached to the graph more than once.
// This means that there is at least 2 links to the same node
// so the graph has a closed loop
for (Edge it : walls) {
if (g.count(it.getV1()) > 1) return true;
if (g.count(it.getV2()) > 1) return true;
}
return false;
}
/** /**
* Predicate to check if a tile direction is `Wallable`. * Predicate to check if a tile direction is `Wallable`.
@@ -270,6 +347,7 @@ class Board {
* <li>The wall is not the DOWN wall from tile (0, 0). * <li>The wall is not the DOWN wall from tile (0, 0).
* <li>There is not already a wall in the desired direction. (Implies no sentinel tile). * <li>There is not already a wall in the desired direction. (Implies no sentinel tile).
* <li>The neighbor in this direction has at most `Const.maxTileWalls -1` walls. * <li>The neighbor in this direction has at most `Const.maxTileWalls -1` walls.
* <li>The wall does not create a closed room (Optional requirement).
* </ul> * </ul>
* *
* @note * @note
@@ -281,17 +359,25 @@ class Board {
*/ */
private boolean isWallableDir (int tileId, int direction) { private boolean isWallableDir (int tileId, int direction) {
// Check list // Check list
if (tileId == 0 && direction == Direction.DOWN) if (!isWalkable(tileId, direction))
return false;
if (tiles[tileId].hasWall(direction))
return false; return false;
switch (direction) { switch (direction) {
case Direction.UP: return (tiles[upTileId.apply(tileId)].hasWalls() < Const.maxTileWalls); case Direction.UP:
case Direction.DOWN: return (tiles[downTileId.apply(tileId)].hasWalls() < Const.maxTileWalls); if (tiles[upTileId.apply(tileId)].hasWalls() >= Const.maxTileWalls) return false;
case Direction.LEFT: return (tiles[leftTileId.apply(tileId)].hasWalls() < Const.maxTileWalls); break;
case Direction.RIGHT:return (tiles[rightTileId.apply(tileId)].hasWalls() < Const.maxTileWalls); case Direction.DOWN:
if (tiles[downTileId.apply(tileId)].hasWalls() >= Const.maxTileWalls) return false;
break;
case Direction.LEFT:
if (tiles[leftTileId.apply(tileId)].hasWalls() >= Const.maxTileWalls) return false;
break;
case Direction.RIGHT:
if (tiles[rightTileId.apply(tileId)].hasWalls() >= Const.maxTileWalls) return false;
break;
} }
if (Session.loopGuard && isRoomCreator(tileId, direction))
return false; return false;
return true;
} }
/** /**
@@ -311,8 +397,8 @@ class Board {
return false; return false;
if (tiles[tileId].hasWalls() >= Const.maxTileWalls) if (tiles[tileId].hasWalls() >= Const.maxTileWalls)
return false; return false;
Range dirs = new Range(Direction.Begin, Direction.End, Direction.Step); Range dirs = new Range(DirRange.Begin, DirRange.End, DirRange.Step);
for (int dir ; (dir = dirs.get()) != Const.noTileId ; ) for (int dir = dirs.get() ; dir != Const.EOR ; dir = dirs.get())
if (isWallableDir(tileId, dir)) if (isWallableDir(tileId, dir))
return true; return true;
return false; return false;
@@ -326,16 +412,23 @@ class Board {
* @return The number of walls created from the utility. * @return The number of walls created from the utility.
*/ */
private int createBasicTileWalls () { private int createBasicTileWalls () {
int tileCount =0; int wallCount =0;
for (int i =0 ; i< tiles.length ; ++i) { for (int i =0 ; i< tiles.length ; ++i) {
boolean up = isUpSentinel(i); boolean up = isUpSentinel(i);
boolean down = isDownSentinel(i) && (i != 0); boolean down = isDownSentinel(i) && (i != 0);
boolean left = isLeftSentinel(i); boolean left = isLeftSentinel(i);
boolean right = isRightSentinel(i); boolean right = isRightSentinel(i);
tileCount += ((up?1:0) + (down?1:0) + (left?1:0) + (right?1:0)); wallCount += ((up?1:0) + (down?1:0) + (left?1:0) + (right?1:0));
tiles[i] = new Tile (i, up, down, left, right); tiles[i] = new Tile (i, up, down, left, right);
// If we have loopGuard enable we populate walls also.
if (Session.loopGuard) {
if (up) walls.add(new Edge(i, Direction.UP));
if (down) walls.add(new Edge(i, Direction.DOWN));
if (left) walls.add(new Edge(i, Direction.LEFT));
if (right) walls.add(new Edge(i, Direction.RIGHT));
} }
return tileCount; }
return wallCount;
} }
/** /**
@@ -344,39 +437,42 @@ class Board {
*/ */
private void createInnerWall(int tileId) { private void createInnerWall(int tileId) {
// Randomly pick a wallable direction in that tile. // Randomly pick a wallable direction in that tile.
ShuffledRange randDirections = new ShuffledRange(Direction.Begin, Direction.End, Direction.Step); ShuffledRange randDirections = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step);
int dir; int dir;
do do
dir = randDirections.get(); dir = randDirections.get();
while (!isWallableDir(tileId, dir)); while (!isWallableDir(tileId, dir));
// Add wall to tileId and the adjacent tileId
Position neighbor = new Position(Position.toRow(tileId), Position.toCol(tileId), dir); Position neighbor = new Position(Position.toRow(tileId), Position.toCol(tileId), dir);
tiles[tileId].setWall(dir); tiles[tileId].setWall(dir);
tiles[neighbor.getId()].setWall(Direction.opposite(dir)); tiles[neighbor.getId()].setWall(Direction.opposite(dir));
// If we have loopGuard enable we populate walls also.
if (Session.loopGuard)
walls.add(new Edge(tileId, dir));
} }
/** /**
* This utility creates the inner walls of the board. * This utility creates the inner walls of the board.
* *
* @param walls The number of walls to create
* @return The number of walls failed to create. * @return The number of walls failed to create.
*/ */
private int createInnerWalls (int walls) { private int createInnerWalls () {
ShuffledRange randTiles = new ShuffledRange(0, N*N); ShuffledRange randTiles = new ShuffledRange(0, N*N);
for (int tileId, i =0, shuffleMark =0 ; i<walls ; ++i) { for (int tileId, i =0, walls =0, shuffleMark =0 ; true ; ) {
// randomly pick a wallable tile. // randomly pick a wallable tile.
do { do {
if ((tileId = randTiles.get())== Const.noTileId) { if ((tileId = randTiles.get())== Const.EOR) {
if (i == shuffleMark) // Wallable tiles exhausted. if (i == shuffleMark) // Wallable tiles exhausted.
return walls - i; return walls;
else { // Re-shuffle and continue. else { // Re-shuffle and continue.
randTiles = new ShuffledRange(0, N*N); randTiles = new ShuffledRange(0, N*N);
shuffleMark =i; shuffleMark =i;
} }
} }
} while (!isWallable(tileId)); } while (!isWallable(tileId));
++walls;
createInnerWall(tileId); createInnerWall(tileId);
} }
return 0;
} }
/** /**
@@ -395,8 +491,11 @@ class Board {
int S = tiles[tileId].hasSupply(supplies); int S = tiles[tileId].hasSupply(supplies);
if (T && !M) return " T "; if (T && !M) return " T ";
else if (M && !T) return " M ";
else if (T && M) return "T+M"; else if (T && M) return "T+M";
else if (M) {
if (S == Const.noSupply) return " M ";
else return "M+s";
}
else if (S != Const.noSupply) else if (S != Const.noSupply)
return String.format("s%02d", S+1); return String.format("s%02d", S+1);
else return " "; else return " ";
@@ -458,5 +557,9 @@ class Board {
private int W; /**< The number of walls on the board */ private int W; /**< The number of walls on the board */
private Tile[] tiles; /**< Array to hold all the tiles for the board */ private Tile[] tiles; /**< Array to hold all the tiles for the board */
private Supply[] supplies; /**< Array to hold all the supplies on the board */ private Supply[] supplies; /**< Array to hold all the supplies on the board */
private ArrayList<Edge> walls; /**<
* Array to hold all the walls using the edge representation
* required by the closed room preventing algorithm.
*/
/** @} */ /** @} */
} }
+418
View File
@@ -0,0 +1,418 @@
/**
* @file Common.java
*
* @author
* Anastasia Foti AEM:8959
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr>
*/
package host.labyrinth;
import java.util.ArrayList;
import java.util.Collections;
/**
* Class to hold constant values for entire application
*/
class Const {
static final int maxTileWalls = 2; /**< Number of maximum walls for each tile on the board */
static final int noSupply =-1; /**< Number to indicate the absent of supply */
static final int noTileId =-1; /**< Number to indicate wrong tileId */
static final int EOR =-1; /**< Number to indicate the End Of Range */
}
/**
* Application wide object to hold settings like values for the session.
*/
class Session {
static int boardSize = 15; /**< Default board's size (if no one set it via command line) */
static int supplySize = 4; /**< Default board's supply size (if no one set it via command line) */
static int maxRounds = 100; /**< Default number of rounds per game (if no one set it via command line) */
static boolean loopGuard = false; /**< When true a wall creation guard is added to prevent closed rooms inside the board */
static boolean interactive = false; /**< When true each round of the game requires user input */
}
/**
* Helper C++-like enumerator class to hold direction
*/
class Direction {
static final int UP =1; /**< North direction */
static final int RIGHT =3; /**< East direction */
static final int DOWN =5; /**< South direction */
static final int LEFT =7; /**< West direction */
/**
* Utility to get the opposite direction.
* @param direction Input direction
* @return The opposite direction
*/
static int opposite (int direction) { return (direction+4)%DirRange.End; }
}
/**
* Helper C++ like enumerator class for direction ranged loops.
*
* We can make use of this in loops like:
* <pre>
* for (int i=DirRange.Begin ; i<DirRange.End ; i += DirRange.Step) { }
*
* or
*
* Range directions = new Range(DirRange.Begin, DirRange.End, DirRange.Step);
* </pre>
*/
class DirRange {
static final int Begin =1; /**< Iterator style begin of range direction (starting north) */
static final int End =8; /**< Iterator style end of range direction (one place after the last) */
static final int Step =2; /**< Step for iterator style direction */
}
/**
* @brief
* An Application wide board position implementation holding just the id coordinate.
*
* Position is a helper class to enable us cope with the redundant position data (id and coordinates).
* This class provide both static conversion functionalities between id and coordinates
* and data representation in the coordinates system.
* For clarity we adopt a tileId convention.
*/
class Position {
/**
* Basic constructor from row-column coordinates
* @param row The row coordinate
* @param col The column coordinate
*/
Position(int row, int col) {
this.id = toID(row, col);
}
/**
* Basic constructor from Id
* @param tileId The id of tile
*/
Position(int tileId) {
this.id = tileId;
}
/**
* Constructor from row-column coordinates and a direction.
*
* This constructor creates a position relative to coordinates.
*
* @param row The row coordinate
* @param col The column coordinate
* @param direction The direction
*/
Position(int row, int col, int direction) {
switch (direction) {
case Direction.UP: this.id = toID(row+1, col); break;
case Direction.DOWN: this.id = toID(row-1, col); break;
case Direction.LEFT: this.id = toID(row, col-1); break;
case Direction.RIGHT:this.id = toID(row, col+1); break;
}
}
/** @name non-static API */
/** @{ */
int getRow() { return toRow(id); } /**< Read access to virtual row coordinate */
int getCol() { return toCol(id); } /**< Read access to virtual column coordinate */
int getId() { return id; } /**< Read access to id coordinate */
/** @} */
/** @name Static convention utilities */
/** @{ */
/**
* Takes row and column coordinates and return the calculated Id coordinate
* @param row The row coordinate
* @param col The column coordinate
* @return The converted value
*/
static int toID(int row, int col) {
return row * Session.boardSize + col;
}
/**
* Takes Id coordinate and return the corresponding row coordinate
* @param id The id coordinate
* @return The row coordinate
*/
static int toRow(int id){
return id / Session.boardSize;
}
/**
* Takes Id coordinate and return the corresponding column coordinate
* @param id The id coordinate
* @return The column coordinate
*/
static int toCol(int id) {
return id % Session.boardSize;
}
/** @} */
/** @name private data types */
/** @{ */
private int id; /**< The id coordinate of the constructed Position object */
/** @} */
}
/**
* Class to create ranges of numbers
*/
class Range {
/**
* Create the range [begin, end)
* @param begin The first item on the range
* @param end The item after the last on the range
*/
Range (int begin, int end) {
numbers = new ArrayList<Integer>();
init (begin, end, 1);
}
/**
* Create the range [begin, end) using step as interval between items.
* @param begin The first item on the range
* @param end The item after the last on the range
* @param step The interval between items
*/
Range(int begin, int end, int step) {
numbers = new ArrayList<Integer>();
init (begin, end, step);
}
/**
* Common utility to create the range for all constructors
*/
private void init (int begin, int end, int step) {
numbers.clear();
for (int i=begin ; i<end ; i+=step)
numbers.add(i);
}
/**
* Extract and return the first item from the range.
* @return The first item of the range or Const.noTileId if there is none.
*/
int get () {
if (!numbers.isEmpty())
return numbers.remove(0);
return Const.EOR;
}
/** @name protected data types */
/** @{ */
protected ArrayList<Integer> numbers; /**< handle to range */
/** @} */
}
/**
* Class to create shuffled ranges of numbers
*/
class ShuffledRange extends Range {
/**
* Create a shuffled version of range [begin, end)
* @param begin The first item on the range
* @param end The item after the last on the range
*/
ShuffledRange(int begin, int end) {
super(begin, end); // Delegate
Collections.shuffle(numbers);
}
/**
* Create a shuffled version of the range [begin, end)
* using step as interval between items.
*
* @param begin The first item on the range
* @param end The item after the last on the range
* @param step The interval between items
*/
ShuffledRange(int begin, int end, int step) {
super(begin, end, step); // Delegate
Collections.shuffle(numbers);
}
}
/**
* @brief
* A utility class used for room prevent algorithm.
*
* This class is the wall representation we use in the room preventing algorithm.
* In this algorithm we represent the crosses between tiles as nodes (V) of a graph and the
* walls as edges. So for example:
* <pre>
* 12--13--14---15
* | |
* 8 9--10 11
* | | |
* 4 5 6 7
* | | |
* 0 1---2---3
* </pre>
* In this example we have a 4x4=16 vertices board(nodes) and 14 edges(walls).
* To represent the vertices on the board we use the same trick as the tileId
*
* V = Row*(N+1) + Column, where N is the board's tile size.
*
* The edges are represented as vertices pairs. For example (0, 4) or (13, 14).
*
* @note
* Beside the fact that we prefer this kind of representation of the walls in
* the application we use the one that is suggested from the assignment. This is
* used only in room preventing algorithm.
* @note
* Using this kind of representation we don't have any more the "problem"
* of setting the wall in both neighbor tiles.
*/
class Edge {
/**
* This constructor acts as the interface between the application's wall
* representation and the one based on graph.
* @param tileId The tile id of the wall.
* @param direction The direction of the tile where the wall should be.
*/
Edge(int tileId, int direction) {
int N = Session.boardSize +1;
switch (direction) {
case Direction.UP:
v1= (Position.toRow(tileId) + 1)*N + Position.toCol(tileId);
v2= (Position.toRow(tileId) + 1)*N + Position.toCol(tileId) + 1;
break;
case Direction.DOWN:
v1= (Position.toRow(tileId))*N + Position.toCol(tileId);
v2= (Position.toRow(tileId))*N + Position.toCol(tileId) + 1;
break;
case Direction.LEFT:
v1= (Position.toRow(tileId))*N + Position.toCol(tileId);
v2= (Position.toRow(tileId) + 1)*N + Position.toCol(tileId);
break;
case Direction.RIGHT:
v1= (Position.toRow(tileId))*N + Position.toCol(tileId) + 1;
v2= (Position.toRow(tileId) + 1)*N + Position.toCol(tileId) +1;
break;
}
}
/** A deep copy contructor */
Edge(Edge e) {
v1 = e.getV1();
v2 = e.getV2();
}
/** Access of the first node of the edge */
int getV1() { return v1; }
/** Access of the second node of the edge */
int getV2() { return v2; }
private int v1; /**< First vertex of the edge */
private int v2; /**< Second vertex of the edge */
}
/**
* @brief
* Provides a graph functionality for the room preventing algorithm.
* We use a graph to represent the wall structure of the walls. This way
* its easy to find any closed loops. Using graph we transform the problem
* of the closed room into the problem of finding a non simple graph.
*
* If the board has non connected wall structure then we would need a non
* coherent graph to represent it. This class provides constroctors and
* methods to create coherent graphs
*
* An example of the biggest coherent graph we can create from the board bellow,
* starting from V=1 is:
* <pre>
* 6---7 8 (1)
* | | / \
* 3 4 5 (4) (2)
* | | | \
* 0 1---2 (5)--(8)
* </pre>
*/
class Graph {
/**
* Constructs a node of the graph using the value of a vertex(node).
* @param v The verteg to attach.
*/
Graph (int v) {
V = v;
E = new ArrayList<Graph>();
}
/**
* Constructor that transform an edge into graph.
* @param e The edge to transform.
*/
Graph (Edge e) {
V = e.getV1();
E = new ArrayList<Graph>();
E.add(new Graph(e.getV2()));
}
/** Access to the current vertex */
int getV() { return V; }
/** Access to the links of the current vertex */
ArrayList<Graph> getE() { return E; }
/**
* Attach an edge into a graph IFF the graph already has a vertex
* with the same value as one of the vertices of the edge.
* @param e The edge to attach.
* @return The status of the operation.
* @arg True on success
* @arg False on failure
*/
boolean attach (Edge e) {
return tryAttach(e, 0) > 0;
}
/**
* Counts the number of vertices on the graph with the value of `v`
* @param v The vertex to count
* @return The number of vertices with value `v`
*/
int count (int v) {
return tryCount (v, 0);
}
/**
* Recursive algorithm that tries to attach an edge into a graph
* IFF the graph already has a vertex with the same value as one
* of the vertices of the edge.
*
* @param e The edge to attach.
* @param count An initial count value to feed the algorithm.
* @return The status of the operation.
* @arg True on success
* @arg False on failure
*/
private int tryAttach (Edge e, int count) {
for (Graph n: E)
count = n.tryAttach (e, count);
if (V == e.getV1()) {
E.add(new Graph(e.getV2()));
++count;
}
if (V == e.getV2()) {
E.add(new Graph(e.getV1()));
++count;
}
return count;
}
/**
* Recursive algorithm that tries to count the number of vertices
* on the graph with the same value as `v`.
*
* @param v The vertex to count
* @param count An initial count value to feed to the algorithm.
* @return The number of vertices with value `v`
*/
private int tryCount (int v, int count) {
for (Graph n: E)
count = n.tryCount (v, count);
if (V == v)
return ++count;
return count;
}
private int V; /**< The value of the current vertex/node */
private ArrayList<Graph> E; /**< A list of all the child nodes */
}
+207
View File
@@ -0,0 +1,207 @@
/**
* @file Game.java
*
* @author
* Anastasia Foti AEM:8959
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr>
*/
/**
* @mainpage A labyrinth board game
*
* This is a console game, played by 2 players. The Theseus and Minotaur.
* The Minotaur goal is to capture Theseus. The Theseus's goal is to collect
* all the supplies of the board before Minotaur catches him and before the
* game ends.
*
* In this first assignment we deal with the board's creation and a basic
* player-game logic. The game is build around a number of classes:
* - \ref Tile
* - Supply
* - Board
* - Player
* - Game
*
* Which are the requested classes. We also provide some extra functionalities in:
* - Const
* - Session
* - Direction
* - DirRange
* - Edge
* - Graph
* - Position
* - Range
* - ShuffledRange
*/
package host.labyrinth;
import java.util.Scanner;
/**
* Main application class. This class is the only public interface of
* the entire game.
*/
public class Game {
/**< An empty constructor */
Game() {
scan = new Scanner(System.in);
}
/** @name Player main application interface */
/** @{ */
/** Utility to get current round of the game */
int round () { return round; }
/** Utility to increase and get the increased round of the game */
int nextRound() { return ++round; }
/**
* Utility to hold the execution of the program waiting for user input.
* This is true only if the user passed the interactive flag.
*/
void waitUser () {
if(Session.interactive) {
System.out.println("Press enter to continue...");
scan.nextLine();
}
}
/** @{ */
/**
* @name Accessor/Mutator interface
* @note
* Please consider not to use mutator interface. Its the abstraction killer :(
* We have added a bit of logic however, in order to make it a bit more safe.
*/
/** @{ */
int getRound() { return round; }
void setRound (int round) { this.round = round; }
/** @} */
/** @name Game's data */
/** @{ */
private int round; /**< Holds the round of the game */
private Scanner scan; /**< Input handle used in interactive mode */
/** @} */
/**
* Command line argument handler
*/
static boolean getArguments (String[] args) {
for (int i =0 ; i<args.length ; ++i) {
switch (args[i]) {
case "-b":
case "--board":
if (i+1 < args.length)
Session.boardSize = Integer.parseInt(args[++i]);
break;
case "-s":
case "--supplies":
if (i+1 < args.length)
Session.supplySize = Integer.parseInt(args[++i]);
break;
case "-r":
case "--rounds":
if (i+1 < args.length)
Session.maxRounds = Integer.parseInt(args[++i]);
break;
case "--norooms":
Session.loopGuard = true;
break;
case "-i":
case "--interactive":
Session.interactive = true;
break;
default:
case "-h":
case "--help":
System.out.println("Labyrinth Game");
System.out.println("");
System.out.println("Usage:");
System.out.println("labyrinth [-b|--board <Num>] [-s|--supplies <Num>] [-r|--rounds <Num>] [--norooms] [-i|--interactive]");
System.out.println("or");
System.out.println("labyrinth -h|--help");
System.out.println("\nOptions\n");
System.out.println("-b | --board:\n Sets the size of board's edge.\n");
System.out.println("-s | --supplies:\n Sets the number of supplies on the board.\n");
System.out.println("-r | --rounds:\n Sets the maximum number of rounds of the game.\n");
System.out.println("--norooms:\n Prevents the creation of closed rooms inside the board.\n");
System.out.println("-i | --interactive:\n Each round requires user input in order to continue.\n");
System.out.println("-h | --help:\n Print this and exits.");
return false;
}
}
return true;
}
/**
* Main game loop
*/
public static void main(String[] args) {
try {
// Get command line options
if (!Game.getArguments(args)) throw new Exception("");
// Create a game, a board and 2 players.
Game game = new Game();
Board board = new Board(Session.boardSize, Session.supplySize);
Player T = new Player(1, "Theseus", true, board, 0);
Player M = new Player(2, "Minotaur", false, board, Position.toID(Session.boardSize/2, Session.boardSize/2));
// Populate data to the board
board.createBoard(T.playerTileId(), M.playerTileId());
// Initial board printout
System.out.println("Initial board: " + (game.round()));
board.printBoard(
board.getStringRepresentation(T.playerTileId(), M.playerTileId())
);
game.waitUser ();
// Main game loop
while (true) {
int[] m;
System.out.println();
System.out.println("State after round: " + (game.round()+1));
// Player moves
m = T.move(T.playerTileId());
System.out.println(T.getName() + ":\t tileId =" + m[0] + " (" + m[1] + ", " + m[2] + ")");
m = M.move(M.playerTileId());
System.out.println(M.getName() + ":\t tileId =" + m[0] + " (" + m[1] + ", " + m[2] + ")");
board.printBoard(
board.getStringRepresentation(T.playerTileId(), M.playerTileId())
);
// Loop termination cases
if (T.getScore() == 4) {
System.out.println(T.getName() + " Wins!!! Score =" + T.getScore());
System.exit(0);
}
if (M.getScore() == 4 || M.playerTileId() == T.playerTileId()) {
System.out.println(M.getName() + " Wins!!! Score =" + M.getScore());
System.exit(0);
}
if (!(game.nextRound() < Session.maxRounds)) {
System.out.println("New day has come... Tie!!!");
System.exit(0);
}
game.waitUser ();
}
}
catch (Exception e) {
// We don't handle exceptions. Print error and exit with error status.
System.out.println(e.getMessage());
System.exit(1);
}
}
}
@@ -1,42 +1,60 @@
package net.hoo2.auth.labyrinth; /**
* @file Player.java
*
* @author
* Anastasia Foti AEM:8959
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr>
*/
package host.labyrinth;
/** /**
* @brief * @brief
* This class represents the game's player * This class represents the game's player
*/ */
class Player { class Player {
/** @name Contructors */ /** @name Constructors */
/** @{ */ /** @{ */
/** /**
* Create a new player and put him at the row-column coordinates * Create a new player and put him at the row-column coordinates
* @param id The id of the player * @param id The id of the player
* @param name The name of the player * @param name The name of the player
* @param champion Flag to indicate if a player is a `champion`
* @param board Reference to the board of the game * @param board Reference to the board of the game
* @param row The row coordinate of initial player position
* @param column The column coordinate of initial player's position
*/ */
Player(int id, String name, Board board, int row, int column) { Player(int id, String name, boolean champion, Board board, int row, int column) {
this.playerId = id; this.playerId = id;
this.name = name; this.name = name;
this.board = board; this.board = board;
this.score = 0; this.score = 0;
this.x = column; this.x = column;
this.y = row; this.y = row;
this.champion = champion;
} }
/** /**
* Create a new player and put him at the row-column coordinates * Create a new player and put him at the row-column coordinates
* @param id The id of the player * @param id The id of the player
* @param name The name of the player * @param name The name of the player
* @param champion Flag to indicate if a player is a `champion`
* @param board Reference to the board of the game * @param board Reference to the board of the game
* @param tileId The tileId coordinate of player's initial position
*/ */
Player(int id, String name, Board board, int tileId) { Player(int id, String name, boolean champion, Board board, int tileId) {
this.playerId = id; this.playerId = id;
this.name = name; this.name = name;
this.board = board; this.board = board;
this.score = 0; this.score = 0;
this.x = Position.toCol(tileId); this.x = Position.toCol(tileId);
this.y = Position.toRow(tileId); this.y = Position.toRow(tileId);
this.champion = champion;
} }
/** @} */ /** @} */
@@ -46,9 +64,9 @@ class Player {
/** /**
* Player's move. * Player's move.
* *
* A player first throw a dice to get a random direction. Then checks if the direction * A player first throws a dice to get a random direction. Then checks if the direction
* is walkable. If it is, then goes to that tile, pick up a possible supply on the tile * is walkable. If it is, then goes to that tile and update player's data.
* and update player's data. * If the player is a champion then he also picks up a possible supply from the tile.
* *
* @param id The id of the starting tile. * @param id The id of the starting tile.
* @return An array containing player's final position and possible supply of that position. * @return An array containing player's final position and possible supply of that position.
@@ -61,7 +79,7 @@ class Player {
* </ul> * </ul>
*/ */
int[] move(int id) { int[] move(int id) {
// Init return array with the current data // Initialize return array with the current data
int[] ret = {id, Position.toRow(id), Position.toCol(id), Const.noSupply}; int[] ret = {id, Position.toRow(id), Position.toCol(id), Const.noSupply};
int diceDirection = board.dice(); // throw the dice int diceDirection = board.dice(); // throw the dice
@@ -71,7 +89,8 @@ class Player {
ret[0] = next.getId(); // Update player's and return data ret[0] = next.getId(); // Update player's and return data
ret[1] = y = next.getRow(); ret[1] = y = next.getRow();
ret[2] = x = next.getCol(); ret[2] = x = next.getCol();
if ((ret[3] = board.tryPickSupply(next.getId())) != Const.noSupply) { // In case of a champion player, try also to pick a supply
if (champion && (ret[3] = board.tryPickSupply(next.getId())) != Const.noSupply) {
++score; // keep score ++score; // keep score
System.out.println(name + ":\t*Found a supply. [score: " + score + "]"); System.out.println(name + ":\t*Found a supply. [score: " + score + "]");
} }
@@ -81,8 +100,11 @@ class Player {
return ret; return ret;
} }
/** Utility to access player's tileID */
int playerTileId() { return Position.toID(y, x); } int playerTileId() { return Position.toID(y, x); }
/** Utility to access player's row position (row coordinate) */
int playerRow() { return y; } int playerRow() { return y; }
/** Utility to access player's column position (column coordinate) */
int playerCol() { return x; } int playerCol() { return x; }
/** @} */ /** @} */
@@ -99,6 +121,7 @@ class Player {
int getScore () { return score; } int getScore () { return score; }
int getX() { return x; } int getX() { return x; }
int getY() { return y; } int getY() { return y; }
boolean getChampion(){ return champion; }
void setPlayerId(int id) { playerId = id; } void setPlayerId(int id) { playerId = id; }
void setName(String name) { this.name = name; } void setName(String name) { this.name = name; }
@@ -112,6 +135,10 @@ class Player {
assert (y >= 0 && y< Session.boardSize) : "Y(row) coordinate must be in the range [0, Session.boardSize)"; assert (y >= 0 && y< Session.boardSize) : "Y(row) coordinate must be in the range [0, Session.boardSize)";
this.y = y; this.y = y;
} }
void setChampion (boolean champion) {
this.champion = champion;
}
/** @} */ /** @} */
/** @name Class data */ /** @name Class data */
@@ -122,5 +149,6 @@ class Player {
private int score; /**< The current score of the player */ private int score; /**< The current score of the player */
private int x; /**< The column coordinate of the player on the board */ private int x; /**< The column coordinate of the player on the board */
private int y; /**< The row coordinate of the player on the board */ private int y; /**< The row coordinate of the player on the board */
private boolean champion; /**< Champion indicate a player who plays against the Minotaur */
/** @} */ /** @} */
} }
@@ -1,11 +1,16 @@
/** /**
* @file Supply.java * @file Supply.java
* *
* @author Christos Choutouridis AEM:8997 * @author
* @email cchoutou@ece.auth.gr * Anastasia Foti AEM:8959
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr>
*/ */
package net.hoo2.auth.labyrinth; package host.labyrinth;
/** /**
* @brief * @brief
@@ -76,7 +81,7 @@ class Supply {
int supplyId () { return supplyId; } int supplyId () { return supplyId; }
/** /**
* Set the supplyId * Set the supplyId
* @param sId The Id to set * @param sID The Id to set
* @return The supplyId * @return The supplyId
* @note This function also returns the supplyId to help in chained expressions. * @note This function also returns the supplyId to help in chained expressions.
*/ */
@@ -1,11 +1,16 @@
/** /**
* @file Tile.java * @file Tile.java
* *
* @author Christos Choutouridis AEM:8997 * @author
* @email cchoutou@ece.auth.gr * Anastasia Foti AEM:8959
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr>
*/ */
package net.hoo2.auth.labyrinth; package host.labyrinth;
/** /**
* @brief * @brief
@@ -132,7 +137,7 @@ class Tile {
/** /**
* Sets the tile's wall in the requested direction. * Sets the tile's wall in the requested direction.
* @param up The direction for the wall. * @param direction The direction for the wall.
*/ */
void setWall (int direction) { void setWall (int direction) {
switch (direction) { switch (direction) {
@@ -145,7 +150,7 @@ class Tile {
/** /**
* Clears the tile's wall in the requested direction. * Clears the tile's wall in the requested direction.
* @param up The direction for the wall * @param direction The direction for the wall
*/ */
void clearWall (int direction) { void clearWall (int direction) {
switch (direction) { switch (direction) {
-212
View File
@@ -1,212 +0,0 @@
/**
* @file Common.java
*
* @author Christos Choutouridis AEM:8997
* @email cchoutou@ece.auth.gr
*/
package net.hoo2.auth.labyrinth;
import java.util.ArrayList;
import java.util.Collections;
/**
* Class to hold constant values for entire application
*/
class Const {
static final int maxTileWalls = 2; /**< Number of maximum walls for each tile on the board */
static final int noSupply =-1; /**< Number to indicate the absent of supply */
static final int noTileId =-1; /**< Number to indicate wrong tileId */
}
/**
* Application wide object to hold settings like values for the session.
*/
class Session {
static int boardSize = 15; /**< Default board's size (if no one set it via command line) */
}
/**
* Helper C++-like enumerator class to hold direction
*/
class Direction {
static final int UP =1; /**< North direction */
static final int RIGHT =3; /**< East direction */
static final int DOWN =5; /**< South direction */
static final int LEFT =7; /**< West direction */
static final int Begin =1; /**< Iterator style begin of range direction (starting north) */
static final int End =8; /**< Iterator style end of range direction (one place after the last) */
static final int Step =2; /**< Step for iterator style direction */
/**
* Utility to get the opposite
* @param direction Input direction
* @return The opposite direction
*/
static int opposite (int direction) { return (direction+4)%End; }
}
/**
* @brief
* An Application wide board position implementation holding just the id coordinate.
*
* Position is a helper class to enable us cope with the redundant position data (id and coordinates).
* This class provide both static conversion functionalities between id and coordinates
* and data representation in the coordinates system.
* For clarity we adopt a row-column naming convention.
*/
class Position {
/**
* Basic constructor from row-column coordinates
* @param row The row coordinate
* @param col The column coordinate
*/
Position(int row, int col) {
this.id = toID(row, col);
}
/**
* Basic constructor from Id
* @param tileId The id of tile
*/
Position(int tileId) {
this.id = tileId;
}
/**
* Constructor from row-column coordinates and a direction.
*
* This constructor creates a position relative to coordinates.
*
* @param row The row coordinate
* @param col The column coordinate
* @param direction The direction
*/
Position(int row, int col, int direction) {
switch (direction) {
case Direction.UP: this.id = toID(row+1, col); break;
case Direction.DOWN: this.id = toID(row-1, col); break;
case Direction.LEFT: this.id = toID(row, col-1); break;
case Direction.RIGHT:this.id = toID(row, col+1); break;
}
}
/** @name non-static API */
/** @{ */
int getRow() { return toRow(id); } /**< Read access to virtual row coordinate */
int getCol() { return toCol(id); } /**< Read access to virtual column coordinate */
int getId() { return id; } /**< Read access to id coordinate */
/** @} */
/** @name Static convention utilities */
/** @{ */
/**
* Takes row and column coordinates and return the calculated Id coordinate
* @param row The row coordinate
* @param col The column coordinate
* @return The converted value
*/
static int toID(int row, int col) {
return row * Session.boardSize + col;
}
/**
* Takes Id coordinate and return the corresponding row coordinate
* @param id The id coordinate
* @return The row coordinate
*/
static int toRow(int id){
return id / Session.boardSize;
}
/**
* Takes Id coordinate and return the corresponding column coordinate
* @param id The id coordinate
* @return The column coordinate
*/
static int toCol(int id) {
return id % Session.boardSize;
}
/** @} */
/** @name private data types */
/** @{ */
private int id; /**< The id coordinate of the constructed Position object */
/** @} */
}
/**
* Class to create ranges of numbers
*/
class Range {
/**
* Create the range [begin, end)
* @param begin The first item on the range
* @param end The item after the last on the range
*/
Range (int begin, int end) {
numbers = new ArrayList<Integer>();
init (begin, end, 1);
}
/**
* Create the range [begin, end) using step as interval between items.
* @param begin The first item on the range
* @param end The item after the last on the range
* @param step The interval between items
*/
Range(int begin, int end, int step) {
numbers = new ArrayList<Integer>();
init (begin, end, step);
}
/**
* Common utility to create the range for all constructors
*/
private void init (int begin, int end, int step) {
numbers.clear();
for (int i=begin ; i<end ; i+=step)
numbers.add(i);
}
/**
* Extract and return the first item from the range.
* @return The first item of the range or Const.noTileId if there is none.
*/
int get () {
try {
return numbers.remove(0);
}
catch (IndexOutOfBoundsException e) {
return Const.noTileId;
}
}
/** @name protected data types */
/** @{ */
protected ArrayList<Integer> numbers; /**< handle to range */
/** @} */
}
/**
* Class to create shuffled ranges of numbers
*/
class ShuffledRange extends Range {
/**
* Create a shuffled version of range [begin, end)
* @param begin The first item on the range
* @param end The item after the last on the range
*/
ShuffledRange(int begin, int end) {
super(begin, end); // Delegate
Collections.shuffle(numbers);
}
/**
* Create a shuffled version of the range [begin, end)
* using step as interval between items.
*
* @param begin The first item on the range
* @param end The item after the last on the range
* @param step The interval between items
*/
ShuffledRange(int begin, int end, int step) {
super(begin, end, step); // Delegate
Collections.shuffle(numbers);
}
}
-81
View File
@@ -1,81 +0,0 @@
/**
* @file Game.java
*
* @author Christos Choutouridis AEM:8997
* @email cchoutou@ece.auth.gr
*/
package net.hoo2.auth.labyrinth;
/**
* Main application class. This class is the only public interface of
* the entire game.
*/
public class Game {
Game() {} /**< An empty constructor */
/**
* @name Accessor/Mutator interface
* @note
* Please consider not to use mutator interface. Its the abstraction killer :(
* We have added a bit of logic however, in order to make it a bit more safe.
*/
/** @{ */
int getRound() { return round; }
void setRound (int round) { this.round = round; }
/** @} */
/** @name Game's data */
/** @{ */
private int round;
/** @} */
/**
* Main game loop
*/
public static void main(String[] args) {
try {
// Create a game, a board and 2 players.
Game game = new Game();
Board board = new Board(11, 4, 82);
Player T = new Player(1, "Theseus", board, 0);
Player M = new Player(2, "Minotaur", board, Position.toID(3, 3));
// Populate data to the board
board.createBoard(T.playerTileId(), M.playerTileId());
while (true) {
int[] m;
System.out.println();
System.out.println("Round: " + (game.getRound()+1));
m = T.move(T.playerTileId());
System.out.println(T.getName() + ":\t tileId =" + m[0] + " (" + m[1] + ", " + m[2] + ")");
m = M.move(M.playerTileId());
System.out.println(M.getName() + ":\t tileId =" + m[0] + " (" + m[1] + ", " + m[2] + ")");
board.printBoard(
board.getStringRepresentation(T.playerTileId(), M.playerTileId())
);
// Termination cases
if (T.getScore() == 4) {
System.out.println(T.getName() + " Wins!!! Score =" + T.getScore());
System.exit(0);
}
if (M.getScore() == 4 || M.playerTileId() == T.playerTileId()) {
System.out.println(M.getName() + " Wins!!! Score =" + M.getScore());
System.exit(0);
}
game.setRound(game.getRound()+1);
if (!(game.getRound() < 100)) {
System.out.println("New day has come... Tie!!!");
System.exit(0);
}
}
}
catch (Exception e) {
System.out.println(e.getMessage());
System.exit(1);
}
}
}