Compare commits

..

No commits in common. "master" and "v1.0b1" have entirely different histories.

20 changed files with 238 additions and 1314 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
release/8997_PartA.zip Normal file

Binary file not shown.

@ -1 +1 @@
Subproject commit 5523c571a9dc02b432d539428307f756e63c4c0c Subproject commit 3612ad227e11b056579910354c58c6973652f7ef

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

View File

@ -1,41 +1,28 @@
% %
% Report for data structures 2020-2021 assignment part A. % Report for data structures 2020-2021 assignment part 1.
% %
% authors: % authors:
% Αναστασία Φώτη ΑΕΜ 8959
% anastaskf@ece.auth.gr
%
% Χρήστος Χουτουρίδης ΑΕΜ 8997 % Χρήστος Χουτουρίδης ΑΕΜ 8997
% cchoutou@ece.auth.gr % cchoutou@ece.auth.gr
% AuthReportConfig requirements
% ===============================================================
\newcommand{\AuthorName}{Αναστασία Φώτη}
\newcommand{\AuthorMail}{anastaskf@ece.auth.gr}
\newcommand{\AuthorAEM}{8959}
\newcommand{\CoAuthorName}{Χρήστος Χουτουρίδης} % Document configuration
\newcommand{\CoAuthorMail}{cchoutou@ece.auth.gr}
\newcommand{\CoAuthorAEM}{8997}
\newcommand{\WorkGroup}{Ομάδα 182}
\newcommand{\DocTitle}{Λαβύρινθος: Ο Θησέας και ο Μινώταυρος III}
\newcommand{\Department}{Τμημα ΗΜΜΥ. Τομεάς Ηλεκτρονικής}
\newcommand{\ClassName}{Δομές δεδομένων} \newcommand{\ClassName}{Δομές δεδομένων}
\newcommand{\DocTitle}{Λαβύρινθος: Ο Θησέας και ο Μινώταυρος 1}
\newcommand{\InstructorName}{Σταυρούλα Σιάχαλου} \newcommand{\InstructorName}{Σταυρούλα Σιάχαλου}
\newcommand{\InstructorMail}{ssiachal@auth.gr} \newcommand{\InstructorMail}{ssiachal@auth.gr}
\newcommand{\CurrentDate}{\today} \newcommand{\CurrentDate}{\today}
\input{config/AuthReportConfig.tex} \input{config/AuthReportConfig.tex}
%\renewcommand{\AuthorName}{Χρήστος Χουτουρίδης}
%\renewcommand{\AuthorMail}{cchoutou@ece.auth.gr}
%\renewcommand{\AuthorAEM}{8997}
\setFancyHeadLR{\ClassName}{\DocTitle} \setFancyHeadLR{\ClassName}{\DocTitle}
%\setFancyHeadLERO{\ClassName}{\DocTitle} %\setFancyHeadLERO{\ClassName}{\DocTitle}
%\BottomTitleSpace{8em} %\BottomTitleSpace{8em}
\renewcommand{\bottomtitlespace}{0.12\textheight}
% Document % Document
% ================= % =================
\begin{document} \begin{document}
@ -47,68 +34,74 @@
%\listoftables %\listoftables
\section{Εισαγωγή} \section{Εισαγωγή}
Η παρούσα εργασία αφορά την δημιουργία ενός παιχνιδιού με τίτλο “Μία νύχτα στο μουσείο”. Η παρούσα εργασία αφορά τη δημιουργία ενός παιχνιδιού λαβυρίνθου με θέμα \textit{“Μια νύχτα στο μουσείο”}.
Στο συγκεκριμένο παιχνίδι καλούμαστε να υλοποιήσουμε ένα λαβύρινθο μέσα στον οποίο κινούνται δύο παίκτες, ο Μινώταυρος και ο Θησέας. Στο συγκεκριμένο παιχνίδι καλούμαστε να δημιουργήσουμε ένα λαβύρινθο μέσα στον οποίο κινούνται με τυχαίο τρόπο δύο παίχτες, ο Θησέας και ο Μινώταυρος.
Στόχος του Μινώταυρου είναι να “πιάσει” τον Θησέα και στόχος του Θησέα είναι να βρει όλα τα εφόδια πού βρίσκονται κατανεμημένα στον ταμπλό, πριν ξημερώσει και πριν τον “πιάσει” ο Μινώταυρος. Στόχος του Μινώταυρου είναι να “πιάσει” τον Θησέα και στόχος του Θησέα είναι να βρει όλα τα εφόδια που είναι τυχαία κατανεμημένα στο ταμπλό, πριν ξημερώσει και πριν τον πιάσει ο Μινώταυρος.
Στο παιχνίδι αυτό υπάρχουν δύο βασικά προβλήματα τα οποία χρειάζεται να λύσουμε.
Το πρόβλημα της δημιουργίας του λαβύρινθου και το πρόβλημα της λειτουργίας των παιχτών και του υπόλοιπου παιχνιδιού.
\par Υπενθυμίζουμε πως στις πρώτες εργασίες κληθήκαμε να υλοποιήσουμε το ταμπλό και τα στοιχεία που το απαρτίζουν καθώς και τις βασικές λειτουργίες του παιχνιδιού. \par Κατά την άποψή μας, σε αυτό το πρώτο μέρος της εργασίας, η δημιουργία του ταμπλό είναι το κυριότερο από τα δύο προβλήματα.
Στη δεύτερη εργασία κληθήκαμε επιπρόσθετα να υλοποιήσουμε ένα παίκτη ο οποίος χειρίζεται το ζάρι προς όφελός του. Το ταμπλό αποτελείται από πλακίδια και τοίχους.
Στην παρούσα εργασία ασχοληθήκαμε με τη δημιουργία ενός παίκτη που κάνει χρήση του αλγόριθμου \eng{minimax}, με σκοπό να προβλέψει καλύτερα την κίνησή του, αναλύοντας τις πιθανές κινήσεις του αντιπάλου. Τα πλακίδια είναι διατεταγμένα σε τετραγωνικό σχήμα και ανάμεσά τους τοποθετούνται οι τοίχοι.
Φυσικά όλα τα βασικά συστατικά του παιχνιδιού που αναπτύξαμε στις δύο πρώτες εργασίες είναι παρόντα. Το πρόβλημα έγκειται στην επιλογή και τοποθέτηση τοίχων με τέτοιο τρόπο ώστε να πληρούνται οι προδιαγραφές του παιχνιδιού όπως πχ κάθε πλακίδιο να έχει το πολύ δύο τοίχους ή τα εξωτερικά πλακίδια να έχουν τοίχο από την έξω μεριά.
Οι τροποποιήσεις σε αυτά είναι ελάχιστες και αφορούν κυρίως την συμβατότητα με τις νέες λειτουργίες, όπως αυτές περιγράφονται στην εκφώνηση. Μετά από μια πιο λεπτομερή ανάλυση του προβλήματος, διαπιστώσαμε πως οι δοθείσες προδιαγραφές δεν αποτρέπουν τη δημιουργία κλειστών δωματίων, κάτι που θεωρήσαμε άδικο, με αποτέλεσμα, όπως περιγράφουμε και αναλυτικά παρακάτω, \textbf{να προσθέσουμε έναν ακόμη περιορισμό}.
\textbf{Την αποτροπή κλειστών δωματίων στο ταμπλό}.
\par Το πρόβλημα της λειτουργίας του υπόλοιπου παιχνιδιού έχει να κάνει με τη δημιουργία των παιχτών καθώς και τις κινήσεις τους.
Οι προδιαγραφές αφορούν περιορισμούς στην κίνηση των παιχτών και τον τρόπο με τον οποίο λειτουργεί το παιχνίδι.
Για παράδειγμα οι παίχτες δεν μπορούν να περάσουν μέσα από τοίχους, ή οι παίχτες κινούνται κατά ένα πλακίδιο τη φορά κλτ.
Σε αντίθεση με το πρόβλημα του ταμπλό εδώ η λύση ήταν τετριμμένη.
\section{Παραδοτέα} \section{Παραδοτέα}
Τα επισυναπτόμενα παραδοτέα αποτελούνται από: Τα επισυναπτόμενα παραδοτέα αποτελούνται από:
\begin{itemize} \begin{itemize}
\item Τον \eng{\textbf{root}} κατάλογο στον οποίο υπάρχει και το \eng{project} του \eng{eclipse}. \item Τον \eng{\textbf{root}}κατάλογο στον οποίο υπάρχει και το \eng{project}του\eng{eclipse.}
\item Ένας υποκατάλογος \eng{\textbf{src/}} με τον κώδικα της \eng{java}, αποτελούμενο από ένα αριθμό αντικειμένων ενσωματωμένο στο πακέτο \eng{host.labyrinth}. \item Ένας υποκατάλογος \eng{\textbf{src/}}με τον κώδικα της \eng{java,}αποτελούμενο από ένα αριθμό αντικειμένων ενσωματωμένο στο πακέτο \eng{host.labyrinth.}
\item Ένας υποκατάλογος \eng{\textbf{out/}} που περιέχει το παραγόμενο \eng{command line jar} του παιχνιδιού. \item Ένας υποκατάλογος \eng{\textbf{out/}} που περιέχει το παραγόμενο \eng{command line jar}του παιχνιδιού.
\item Ένας υποκατάλογος \eng{\textbf{doc/}} με την τεκμηρίωση του κώδικα όπως αυτή έχει παραχθεί από τα σχόλια, με το εργαλείο \eng{doxygen}. \item Ένας υποκατάλογος \eng{\textbf{doc/}}με την τεκμηρίωση του κώδικα όπως αυτή έχει παραχθεί από τα σχόλια, με το εργαλείο \eng{doxygen.}
Το αρχείο ρυθμίσεων του \eng{doxygen} είναι στον \eng{root} με το όνομα \eng{Doxyfile}. Το αρχείο ρυθμίσεων του \eng{doxygen}είναι στον \eng{root} με το όνομα \eng{Doxyfile.}
Η πλοήγηση στην τεκμηρίωση μπορεί να γίνει ανοίγοντας το αρχείο \eng{doc/html/index.html} Η πλοήγηση στην τεκμηρίωση μπορεί να γίνει ανοίγοντας το αρχείο \eng{doc/index.html}
\item Το αρχείο \eng{\textbf{report.pdf}} που αποτελεί την \textbf{παρούσα αναφορά}. \item Ένας υποκατάλογος \eng{\textbf{report/}} που περιέχει την \textbf{παρούσα αναφορά}.
\end{itemize} \end{itemize}
Εκτός από τα επισυναπτόμενα αρχεία διαθέσιμο υπάρχει και το \textbf{\eng{git} αποθετήριο} ολόκληρης της εργασίας \href{https://git.hoo2.net/hoo2/Labyrinth}{εδώ}. Εκτός από τα επισυναπτόμενα αρχεία διαθέσιμο υπάρχει και το \textbf{\eng{git}αποθετήριο} ολόκληρης της εργασίας \href{https://git.hoo2.net/hoo2/Labyrinth}{εδώ}.
Αυτό περιέχει τόσο τον κώδικα της εφαρμογής όσο και τον κώδικα της αναφοράς. Αυτό περιέχει τόσο τον κώδικα της εφαρμογής όσο και τον κώδικα της αναφοράς.
\section{Σχεδιαστικές επιλογές} \section{Σχεδιαστικές επιλογές}
Πριν ασχοληθούμε όμως με τα ζητηθέντα αντικείμενα του προγράμματος, θα πρέπει να αναφερθούμε σε ορισμένες δομές που προστέθηκαν, αλλά και κάποιες σχεδιαστικές επιλογές που έγιναν για να απλοποιήσουν τον κώδικα. Πριν ασχοληθούμε όμως με τα ζητηθέντα αντικείμενα του προγράμματος, θα πρέπει να αναφερθούμε σε ορισμένες δομές που προστέθηκαν, αλλά και κάποιες σχεδιαστικές επιλογές που έγιναν για να απλοποιήσουν τον κώδικα.
Αρκετές από αυτές προέρχονται από τις προηγούμενες εργασίες.
Τις παραθέτουμε όμως και εδώ καθώς παίζουν σημαντικό ρόλο, τόσο στην λειτουργία του προγράμματος όσο και στην καλύτερη κατανόηση της τρέχουσας εργασίας.
\subsection{\eng{Accessor - mutator idiom}} \subsection{\eng{Accessor - mutator idiom}}
Στις προδιαγραφές της εργασίας αφήνεται να εννοηθεί πως ζητείται η χρήση του \eng{\textit{accessor - mutator idiom}}. Στις προδιαγραφές της εργασίας αφήνεται να εννοηθεί πως ζητείται η χρήση του \eng{\textit{accessor - mutator idiom.}}
Θα πρέπει να παραδεχτούμε όμως, πως \textbf{θεωρούμε το συγκεκριμένο ιδίωμα ιδιαίτερα προβληματικό}. Θα πρέπει να παραδεχτούμε όμως, πως \textbf{θεωρούμε το συγκεκριμένο ιδίωμα ιδιαίτερα προβληματικό}.
Ο κύριος λόγος είναι πως παραβιάζει θεμελιακά τις αφαιρέσεις προδίδοντας τον εσωτερικό σχεδιασμό του αντικειμένου. Ο κύριος λόγος είναι πως παραβιάζει θεμελιακά τις αφαιρέσεις προδίδοντας τον εσωτερικό σχεδιασμό του αντικειμένου.
Ακόμα δίνει πρόσβαση στην εσωτερική δομή του, “πίσω από την πλάτη” του αντικειμένου. Ακόμα δίνει πρόσβαση στην εσωτερική δομή του “πίσω από την πλάτη” του αντικειμένου.
Εν αντιθέτως με το ιδίωμα αυτό, \textbf{τα αντικείμενα που υλοποιούνται ως αφαιρέσεις θα μπορούσαν να προσφέρουν μεθόδους που εκτελούν κάποια λειτουργία, κρύβοντας τελείως τις εσωτερικές λεπτομέρειες της υλοποίησης}. Εν αντιθέτως με το ιδίωμα αυτό, \textbf{τα αντικείμενα που υλοποιούνται ως αφαιρέσεις μπορούν να προσφέρουν μεθόδους που εκτελούν κάποια λειτουργία, κρύβοντας τελείως τις εσωτερικές λεπτομέρειες της υλοποίησης}.
Αυτός είναι και ο δρόμος που διαλέξαμε για το σχεδιασμό του προγράμματος. Αυτός είναι και ο δρόμος που διαλέξαμε για το σχεδιασμό του προγράμματος.
Η κάθε τάξη του προγράμματός μας προσφέρει δημόσια ένα αριθμό από μεθόδους που είναι απαραίτητες για την εκάστοτε απαιτούμενη λειτουργικότητα και κρύβει όσο καλύτερα γίνεται την εσωτερική υλοποίηση. Η κάθε τάξη του προγράμματός μας προσφέρει δημόσια ένα αριθμό από μεθόδους που είναι απαραίτητες για την εκάστοτε απαιτούμενη λειτουργικότητα και κρύβει όσο καλύτερα γίνεται την εσωτερική υλοποίηση.
Ενώ λοιπόν υλοποιήσαμε το ζητηθέν \eng{get-set} ζευγάρι για την κάθε μεταβλητή των τάξεων, δεν το χρησιμοποιήσαμε σχεδόν καθόλου μέσα στο πρόγραμμα. Ενώ λοιπόν υλοποιήσαμε το ζητηθέν \eng{get-set}ζευγάρι για την κάθε μεταβλητή των τάξεων, δεν το χρησιμοποιήσαμε πουθενά μέσα στο πρόγραμμα.
\subsection{Ενοποιημένο σύστημα συντεταγμένων} \subsection{Ενοποιημένο σύστημα συντεταγμένων}
Στις προδιαγραφές της 1ης εργασίας περιγράφεται επίσης ένα διπλό σύστημα συντεταγμένων, τόσο για τα πλακίδια όσο και για τα εφόδια. Στις προδιαγραφές της εργασίας περιγράφεται επίσης ένα διπλό σύστημα συντεταγμένων, τόσο για τα πλακίδια όσο και για τα εφόδια.
Ένα καρτεσιανό που διευθυνσιοδοτεί ως προς δύο άξονες και περιέχει ένα ζευγάρι γραμμής και στήλης και ένα μονοδιάστατο που αποτελείται από τον γραμμικό συνδυασμό των προηγουμένων. Ένα καρτεσιανό που διευθυνσιοδοτεί ως προς δύο άξονες και περιέχει ένα ζευγάρι γραμμής και στήλης και ένα μονοδιάστατο που αποτελείται από τον γραμμικό συνδυασμό των προηγουμένων.
Το μονοδιάστατο αντικατοπτρίζει και την απεικόνιση στη μνήμη ενός πίνακα 2 διαστάσεων σε \eng{row major order}. Το μονοδιάστατο αντικατοπτρίζει και την απεικόνιση στη μνήμη ενός πίνακα 2 διαστάσεων σε \eng{row major order.}
\par Γενικά θεωρούμε πως κάτι τέτοιο δημιουργεί πλεονασμό δεδομένων και επομένως είναι κακή πρακτική. \par Γενικά θεωρούμε πως κάτι τέτοιο δημιουργεί πλεονασμό δεδομένων και επομένως είναι κακή πρακτική.
Αυτό γιατί μεταξύ άλλων μειονεκτημάτων, που κυρίως αφορά τον πολυνηματικό προγραμματισμό, οδηγεί και σε προγραμματιστικά λάθη που πιθανώς θα αφήνουν τα δύο συστήματα ασυγχρόνιστα. Αυτό γιατί μεταξύ άλλων μειονεκτημάτων, που κυρίως αφορά τον πολυνηματικό προγραμματισμό, οδηγεί και σε προγραμματιστικά λάθη που πιθανώς θα αφήνουν τα δύο συστήματα ασυγχρόνιστα.
Για να λύσουμε αυτό το πρόβλημα \textbf{δημιουργήσαμε την τάξη \eng{Position}}, στην οποία εσωτερικά χρησιμοποιούμε μόνο το ένα από τα δύο συστήματα, για την ακρίβεια το μονοδιάστατο και ταυτόχρονα παρέχουμε μεθόδους για την πρόσβαση στη θέση και από τα δύο συστήματα. Για να λύσουμε αυτό το πρόβλημα \textbf{δημιουργήσαμε την τάξη \eng{Position,}}στην οποία εσωτερικά χρησιμοποιούμε μόνο το ένα από τα δύο συστήματα, για την ακρίβεια το μονοδιάστατο και ταυτόχρονα παρέχουμε μεθόδους για την πρόσβαση στη θέση και από τα δύο συστήματα.
Η τάξη μεταξύ άλλων προσφέρει και \textbf{στατικές μεθόδους για τις μετατροπές} προσφέροντας έτσι μια είδους εργαλειοθήκη για την εφαρμογή. Η τάξη μεταξύ άλλων προσφέρει και \textbf{στατικές μεθόδους για τις μετατροπές} προσφέροντας έτσι μια είδους εργαλειοθήκη για την εφαρμογή.
Για την παρούσα εργασία χρησιμοποιήσαμε την \eng{Position} όπου ήταν δυνατό. Για την παρούσα εργασία χρησιμοποιήσαμε την \eng{Position}όπου ήταν δυνατό.
\subsection{Αναβάθμιση της τάξης \eng{Tile}} \subsection{Αναβάθμιση της τάξης \eng{Tile}}
Κατά τον προγραμματισμό του παιχνιδιού παρατηρήσαμε πως τόσο η τάξη \eng{Tile} όσο και η \eng{Supply} έχουν πληροφορίες για τη θέση τους στο ταμπλό. Κατά τον προγραμματισμό του παιχνιδιού παρατηρήσαμε πως τόσο η τάξη \eng{Tile}όσο και η \eng{Supply}έχουν πληροφορίες για τη θέση τους στο ταμπλό.
Αυτό σημαίνει πως τόσο τα πλακίδια όσο και τα εφόδια ανήκουν στο ταμπλό. Αυτό σημαίνει πως τόσο τα πλακίδια όσο και τα εφόδια ανήκουν στο ταμπλό.
Ακόμα σημαίνει πως δημιουργούν επιπλέον πλεονασμό σε δεδομένα, καθώς απαιτείται οι συντεταγμένες του πλακιδίου που βρίσκεται το εφόδιο να επαναληφθούν μέσα στην \eng{Supply}. Ακόμα σημαίνει πως δημιουργούν επιπλέον πλεονασμό σε δεδομένα, καθώς απαιτείται οι συντεταγμένες των εφοδίων να επαναληφθούν μέσα στην \eng{Supply.}
Κάτι τέτοιο γίνεται αντιληπτό και από τις προδιαγραφές της \eng{Board} η οποία είναι αυτή που περιέχει τους πίνακες αναφορών τόσο των πλακιδίων όσο και των εφοδίων. Κάτι τέτοιο γίνεται αντιληπτό και από τις προδιαγραφές της \eng{Board}η οποία είναι αυτή που περιέχει τους πίνακες αναφορών τόσο των πλακιδίων όσο και των εφοδίων.
\textbf{Μια πιο διαισθητική προσέγγιση βέβαια θα ήθελε τα εφόδια να ανήκουν στα πλακίδια} και όχι στο ταμπλό. \textbf{Μια πιο διαισθητική προσέγγιση βέβαια θα ήθελε τα εφόδια να ανήκουν στα πλακίδια} και όχι στο ταμπλό.
Με αυτό τον τρόπο η τάξη \eng{Supply} δεν θα είχε τις επαναλαμβανόμενες πληροφορίες θέσης, αλλά αντίθετα η τάξη \eng{Tile} θα είχε απλώς μια επιπρόσθετη πληροφορία για το αν φιλοξενεί κάποιο εφόδιο ή όχι. Με αυτό τον τρόπο η τάξη \eng{Supply}δεν θα είχε τις επαναλαμβανόμενες πληροφορίες θέσης, αλλά αντίθετα η τάξη \eng{Tile}θα είχε μια επιπρόσθετη πληροφορία για το αν υπάρχει εφόδιο ή όχι.
\par Όπως είναι φυσικό θελήσαμε να υλοποιήσουμε αυτή την προσέγγιση. \par Όπως είναι φυσικό θελήσαμε να υλοποιήσουμε αυτή την προσέγγιση.
Αν όμως μετακινούσαμε τις αναφορές των εφοδίων στην \eng{Tile} θα αλλοιώναμε τις προδιαγραφές της εκφώνησης. Αν όμως μετακινούσαμε τις αναφορές των εφοδίων στην \eng{Tile}θα αλλοιώναμε τις προδιαγραφές της εκφώνησης.
Επομένως επιλέξαμε μια μέση οδό. Επομένως επιλέξαμε μια μέση οδό.
Εμπλουτίσαμε την \eng{Tile} με μεθόδους που αφορούν τα εφόδια. Εμπλουτίσαμε την \eng{Tile}με μεθόδους που αφορούν τα εφόδια.
Αυτές είναι οι: Αυτές είναι οι:
\begin{itemize} \begin{itemize}
\item \eng{\textbf{\textit{int hasSupply (Supply[] supplies)}}}\\ \item \eng{\textbf{\textit{int hasSupply (Supply[] supplies)}}}\\
@ -116,55 +109,36 @@
\item \eng{\textbf{\textit{void pickSupply (Supply[] supplies, int supplyId)}}}\\ \item \eng{\textbf{\textit{void pickSupply (Supply[] supplies, int supplyId)}}}\\
που δίνει την δυνατότητα σε κάποιο παίκτη να “σηκώσει” το εφόδιο. που δίνει την δυνατότητα σε κάποιο παίκτη να “σηκώσει” το εφόδιο.
\end{itemize} \end{itemize}
Έτσι έχουμε ομοιομορφία με τις αντίστοιχες μεθόδους \eng{\textit{boolean hasWall(int direction)}} και \eng{\textit{int hasWalls()}} της \eng{Tile}. Έτσι έχουμε ομοιομορφία με τις αντίστοιχες μεθόδους \eng{\textit{boolean hasWall(int direction)}}και \eng{\textit{int hasWalls()}}της \eng{Tile.}
\par Ένας παρατηρητικός αναγνώστης θα διαπιστώσει πως οι συναρτήσεις για τα εφόδια είναι αναγκασμένες να πάρουν τον πίνακα αναφορών στα εφόδια ως όρισμα. \par Ένας παρατηρητικός αναγνώστης θα διαπιστώσει πως οι συναρτήσεις για τα εφόδια είναι αναγκασμένες να πάρουν τον πίνακα αναφορών στα εφόδια ως όρισμα.
Αυτό είναι το τίμημα που πρέπει να πληρώσουμε προωθώντας τις μεθόδους αυτές στην \eng{Tile}. Αυτό είναι το τίμημα που πρέπει να πληρώσουμε προωθώντας τις μεθόδους αυτές στην \eng{Tile.}
\subsection{Αναβάθμιση της τάξης \eng{Board}}
Στην διάρκεια του σχεδιασμού θεωρήσαμε ωφέλιμη την εισαγωγή του πίνακα \eng{\textit{int[][] moves}} στην τάξη \eng{Board}.
Σε αυτόν καταχωρούμε τις πληροφορίες της τελευταίας κίνησης του κάθε παίκτη, όπως το \eng{id} του πλακιδίου που βρίσκεται ο παίκτης, οι συντεταγμένες του και η “ζαριά” της κίνησης.
Στην προσθήκη αυτή οδηγηθήκαμε αφού παρατηρήσαμε πως για την τάξη \eng{HeuristicPlayer} είναι απαραίτητη η πρόσβαση στην θέση και στο \eng{id} του αντιπάλου.
Φυσικά την ίδια απαίτηση έχουμε και με την τάξη \eng{MinMaxPlayer}.
Η πρόσβαση στις κινήσεις πραγματοποιείται μέσω της μεταβλητής \eng{moves} και της συνάρτησης \eng{\textit{getOpponentMove()}}.
Έτσι ο κάθε \eng{HeuristicPlayer} ή \eng{MinMaxPlayer} μπορεί να διαπιστώσει αν στο οπτικό του πεδίο βρίσκεται κάποιος αντίπαλος και να κινηθεί κατάλληλα.
\par Ακόμα μέσω του ίδιου μηχανισμού δίνουμε την δυνατότητα στους παίκτες να μαθαίνουν το \eng{ID} του αντιπάλου.
Αυτό είναι χρήσιμο για τον \eng{MinMaxPlayer} όταν προσπαθεί να σκηνοθετήσει τις κινήσεις του αντιπάλου.
Θα αναλύσουμε περισσότερα όμως γύρω από αυτό στην παράγραφο \ref{minimax}.
\subsection{Αναβάθμιση της τάξης \eng{Player}}
Για την επέμβαση σε αυτή την τάξη κινηθήκαμε με γνώμονα την αντιμετώπιση δύο προβλημάτων που μας παρουσιάστηκαν.
Αρχικά για την σωστή λειτουργία τη κλάσης \eng{HeuristicPlayer}, όπως αναφέραμε και παραπάνω, είναι αναγκαία η πρόσβαση στο \eng{id} του αντιπάλου.
Για τον λόγο αυτό επιλέξαμε να δημιουργείται ένα \eng{id} για κάθε παίκτη μέσω συνάρτησης της τάξης \eng{Board}, στο οποίο θα έχουμε πρόσβαση μέσω του πίνακα \eng{\textit{int[][] moves}}.
Τη λειτουργία αυτή αναλαμβάνει η συνάρτηση \eng{\textit{int generatePlayerId()}}.
\par
Επιπρόσθετα, θελήσαμε να ενοποιήσουμε τις υλοποιήσεις των τάξεων \eng{Player}, \eng{HeuristicPlayer} και \eng{MinMaxPlayer}, ώστε να τις κάνουμε συμβατές με την αρχή του \eng{Liskov} \footnote{\eng{\url{https://en.wikipedia.org/wiki/Liskov_substitution_principle}}}.
Για το λόγο αυτό οι συναρτήσεις \eng{\textit{move(), statistics()}} και \eng{\textit{final\_statistice()}} είναι πλέον υλοποιημένες και σε όλα τα αντικείμενα.
Αυτό μας ανάγκασε να μετακινήσουμε την μεταβλητή \eng{path} στο \eng{base object} δηλαδή στην \eng{Player}.
\section{\eng{Concepts}} \section{\eng{Concepts}}
Αν και τα \eng{concepts} υλοποιήθηκαν για την εκπόνηση του πρώτου μέρους, τα παραθέτουμε συνοπτικά και σε αυτή την αναφορά, καθώς τα θεωρούμε ουσιαστικό κομμάτι για όλα τα μέρη της εργασίας. Η δημιουργία της \eng{Board}αποτέλεσε τον μεγαλύτερο όγκο του κώδικα της παρούσας εργασίας.
Τα \eng{”concepts”} μας έχουν τη μορφή συναρτήσεων και αφορούν έννοιες σχετικές με την εφαρμογή. Για να κάνουμε τον κώδικα καθαρότερο αλλά και ευκολότερο στην κατανόηση επινοήσαμε κάποια \eng{\textbf{concepts}.}
Μας δίνεται έτσι η δυνατότητα να ελέγχουμε αν κάποια είσοδός ενός \eng{predicate-concept} πληροί τις προδιαγραφές του ή όχι. Η ιδέα των \eng{concepts}προέρχεται από την \eng{C++}όπου τα \eng{concepts}είναι ένα είδους \eng{compile time predicate}και εφαρμόζεται στους τύπους δεδομένων.
Εμείς για την εργασία υλοποιήσαμε κάποια \eng{concepts} σε μορφή συναρτήσεων κατά την εκτέλεση του προγράμματος.
Τα \eng{concepts}αυτά αφορούν έννοιες σχετικές με την εφαρμογή και έχουν την μορφή \eng{predicate.}
Μας δίνεται έτσι η δυνατότητα να ελέγχουμε αν κάποια είσοδός ενός \eng{concept}πληροί τις προδιαγραφές του ή όχι.
Στο σχήμα \ref{fig:concepts} φαίνεται μια οπτικοποιημένη έκδοση τους. Στο σχήμα \ref{fig:concepts} φαίνεται μια οπτικοποιημένη έκδοση τους.
\subsection{Πλακίδιο φρουρός - \textit{\eng{isSentinel()}}} \subsection{Πλακίδιο φρουρός - \textit{\eng{isSentinel()}}}
\WrapFigure{0.45}{r}{fig:concepts}{images/concepts.png}{ \WrapFigure{0.45}{r}{fig:concepts}{images/concepts.png}{
Οπτική αναπαράσταση των \eng{concepts} που χρησιμοποιούμε σε ένα ταμπλό $7x7$. Οπτική αναπαράσταση των \eng{concepts}που χρησιμοποιούμε σε ένα ταμπλό $7x7$.
Με πράσινο αναπαρίστανται όσα πληρούν κάποιο \eng{concept} και με κόκκινο όσα όχι. Με πράσινο αναπαρίστανται όσα πληρούν κάποιο \eng{concept}και με κόκκινο όσα όχι.
Τα πλακίδια με το γκρι χρώμα, είναι τα πλακίδια φρουροί. Τα πλακίδια με το γκρι χρώμα, είναι τα πλακίδια φρουροί.
} }
Πρόκειται για \eng{concept} που μας επιτρέπει να ελέγξουμε αν το πλακίδιο είναι \textit{”πλακίδιο φρουρός”}. Πρόκειται για \eng{concept}που μας επιτρέπει να ελέγξουμε αν το πλακίδιο είναι \textit{”πλακίδιο φρουρός”}.
Αν δηλαδή βρίσκεται στα εξωτερικά άκρα του ταμπλό. Αν δηλαδή βρίσκεται στα εξωτερικά άκρα του ταμπλό.
Η υλοποίηση αυτού του \eng{concept} γίνεται μέσω τεσσάρων συναρτήσεων που ελέγχουν χωριστά τις τέσσερεις διευθύνσεις του ταμπλό. Η υλοποίηση αυτού του \eng{concept}γίνεται μέσω τεσσάρων συναρτήσεων που ελέγχουν χωριστά τις τέσσερεις διευθύνσεις του ταμπλό.
\par Για την παράδειγμα η \eng{\textit{boolean isLeftSentinel (int tileId)}} μας δίνει την δυνατότητα να ελέγξουμε αν το πλακίδιο είναι \textit{πλακίδιο φρουρού} αριστερά του ταμπλό. \par Για την παράδειγμα η \eng{\textit{boolean isLeftSentinel (int tileId)}}μας δίνει την δυνατότητα να ελέγξουμε αν το πλακίδιο είναι \textit{πλακίδιο φρουρού} αριστερά του ταμπλό.
Αυτό είναι χρήσιμο για λειτουργίες όπως για παράδειγμα αν θέλουμε να δούμε μήπως χρειάζεται να τοποθετηθεί εξωτερικός τοίχος αριστερά του πλακιδίου. Αυτό είναι χρήσιμο για λειτουργίες όπως για παράδειγμα αν θέλουμε να δούμε μήπως χρειάζεται να τοποθετηθεί εξωτερικός τοίχος αριστερά του πλακιδίου.
Αντίστοιχα υπάρχουν και οι υπόλοιπες συναρτήσεις για τις υπόλοιπες διευθύνσεις. Αντίστοιχα υπάρχουν και οι υπόλοιπες συναρτήσεις για τις υπόλοιπες διευθύνσεις.
\subsection{Διασχίσιμη διεύθυνση - \textit{\eng{isWalkable()}}} \subsection{Διασχίσιμη διεύθυνση - \textit{\eng{isWalkable()}}}
Πρόκειται για \eng{predicate} που μας επιτρέπει να ελέγξουμε αν μια διεύθυνση σε κάποιο πλακίδιο είναι \textit{”διασχίσημη διεύθυνση”}. Πρόκειται για \eng{predicate}που μας επιτρέπει να ελέγξουμε αν μια διεύθυνση σε κάποιο πλακίδιο είναι \textit{”διασχίσημη διεύθυνση”}.
Αν δηλαδή κάποιος παίκτης μπορεί να κινηθεί σε αυτή. Αν δηλαδή κάποιος παίκτης μπορεί να κινηθεί σε αυτή.
Για να ισχύει κάτι τέτοιο θα πρέπει: Για να ισχύει κάτι τέτοιο θα πρέπει:
\begin{itemize} \begin{itemize}
@ -173,7 +147,7 @@
\end{itemize} \end{itemize}
\subsection{Χτίσιμη διεύθυνση - \textit{\eng{isWallableDir()}}} \subsection{Χτίσιμη διεύθυνση - \textit{\eng{isWallableDir()}}}
Πρόκειται για \eng{predicate} που μας επιτρέπει να ελέγξουμε αν μια διεύθυνση κάποιου πλακιδίου είναι \textit{”χτίσιμη διεύθυνση”}. Πρόκειται για \eng{predicate}που μας επιτρέπει να ελέγξουμε αν μια διεύθυνση κάποιου πλακιδίου είναι \textit{”χτίσιμη διεύθυνση”}.
Αν δηλαδή μπορούμε να τοποθετήσουμε τοίχο στη διεύθυνση αυτή. Αν δηλαδή μπορούμε να τοποθετήσουμε τοίχο στη διεύθυνση αυτή.
Για να είναι μια διεύθυνση χτίσιμη θα πρέπει: Για να είναι μια διεύθυνση χτίσιμη θα πρέπει:
\begin{itemize} \begin{itemize}
@ -181,23 +155,22 @@
\item Το γειτονικό πλακίδιο σε αυτή τη διεύθυνση να μην περιέχει ήδη τον μέγιστο επιτρεπτό αριθμό τοίχων. \item Το γειτονικό πλακίδιο σε αυτή τη διεύθυνση να μην περιέχει ήδη τον μέγιστο επιτρεπτό αριθμό τοίχων.
\item Ο τοίχος να μην δημιουργεί κάποιο κλειστό δωμάτιο.\\ \item Ο τοίχος να μην δημιουργεί κάποιο κλειστό δωμάτιο.\\
\end{itemize} \end{itemize}
Αυτή η τελευταία \textbf{απαίτηση δεν υπάρχει στις προδιαγραφές} και την παίρνουμε υπόψιν, όπως θα δούμε και στην ενότητα \ref{sec:excecutable}, μόνο αν ο χρήστης την έχει ζητήσει από την γραμμή εντολών. Αυτή η τελευταία \textbf{απαίτηση δεν υπάρχει στις προδιαγραφές} και την παίρνουμε υπόψιν μόνο αν ο χρήστης την έχει ζητήσει από την γραμμή εντολών.
Ο λόγος είναι γιατί ο υπολογισμός της κοστίζει και αυτό μπορεί να μην παίζει ρόλο για ταμπλό μεγέθους $15x15$, αλλά αν ζητηθεί κάποιο πολύ μεγαλύτερο τότε ο χρόνος είναι υπολογίσιμος. Ο λόγος είναι γιατί ο υπολογισμός της κοστίζει και αυτό μπορεί να μην παίζει ρόλο για ταμπλό μεγέθους $15x15$, αλλά αν ζητηθεί κάποιο πολύ μεγαλύτερο τότε ο χρόνος είναι υπολογίσιμος.
Φυσικά στον κώδικα κάνουμε χρήση αυτού του \eng{concept} μόνο κατά τη δημιουργία του ταμπλό, με αποτέλεσμα να μην επιβαρύνεται καθόλου η λειτουργία του προγράμματος κατά τη διάρκεια του παιχνιδιού. Φυσικά στον κώδικα κάνουμε χρήση αυτού του \eng{concept}μόνο κατά τη δημιουργία του ταμπλό, με αποτέλεσμα να μην επιβαρύνεται καθόλου η λειτουργία του προγράμματος κατά τη διάρκεια του παιχνιδιού.
Έτσι προτείνουμε στον αναγνώστη και χρήστη της εφαρμογής μας να κάνει χρήση αυτής της επιλογής.
Αναφερόμαστε αναλυτικά σε αυτό τον αλγόριθμο στην ενότητα \ref{sec:isRoomCreator} Αναφερόμαστε αναλυτικά σε αυτό τον αλγόριθμο στην ενότητα \ref{sec:isRoomCreator}
\subsection{Χτίσιμο πλακίδιο - \textit{\eng{isWallable()}}} \subsection{Χτίσιμο πλακίδιο - \textit{\eng{isWallable()}}}
Πρόκειται για \eng{predicate} που μας επιτρέπει να ελέγξουμε αν κάποιο πλακίδιο είναι \textit{”χτίσιμo πλακίδιο”}. Πρόκειται για \eng{predicate}που μας επιτρέπει να ελέγξουμε αν κάποιο πλακίδιο είναι \textit{”χτίσιμo πλακίδιο”}.
Αν δηλαδή υπάρχει κάποια πλευρά του πλακιδίου στην οποία μπορούμε να τοποθετήσουμε τοίχο. Αν δηλαδή υπάρχει κάποια πλευρά του πλακιδίου στην οποία μπορούμε να τοποθετήσουμε τοίχο.
Για να ισχύει αυτό θα πρέπει: Για να ισχύει αυτό θα πρέπει:
\begin{itemize} \begin{itemize}
\item Να υπάρχει τουλάχιστον μία \textit{χτίσιμη διεύθυνση} στο πλακίδιο.
\item Το πλακίδιο να μην έχει ήδη τον μέγιστο επιτρεπτό αριθμό τοίχων. \item Το πλακίδιο να μην έχει ήδη τον μέγιστο επιτρεπτό αριθμό τοίχων.
\item Να υπάρχει τουλάχιστον μία \textit{χτίσιμη διεύθυνση} στο πλακίδιο.
\end{itemize} \end{itemize}
\subsection{Δημιουργός κλειστού δωματίου - \textit{\eng{isRoomCreator()}}} \label{sec:isRoomCreator} \subsection{Δημιουργός κλειστού δωματίου - \textit{\eng{isRoomCreator()}}} \label{sec:isRoomCreator}
Οι προδιαγραφές της εργασίας που αφορούν τη δημιουργία του ταμπλό, δεν αποτρέπουν την εμφάνιση κλειστών δωματίων. Όπως αναφέραμε και παραπάνω οι προδιαγραφές της εργασίας δεν αποτρέπουν τη δημιουργία κλειστών δωματίων.
Για το λόγο αυτό υλοποιήσαμε ένα αλγόριθμο που ανιχνεύει την πιθανότητα δημιουργίας κλειστών δωματίων του οποίου η ενεργοποίηση γίνεται κατόπιν επιλογής του χρήστη από τη γραμμή εντολών. Για το λόγο αυτό υλοποιήσαμε ένα αλγόριθμο που ανιχνεύει την πιθανότητα δημιουργίας κλειστών δωματίων του οποίου η ενεργοποίηση γίνεται κατόπιν επιλογής του χρήστη από τη γραμμή εντολών.
\par Ένας αλγόριθμος για να είναι λειτουργικός χρειάζεται δεδομένα. \par Ένας αλγόριθμος για να είναι λειτουργικός χρειάζεται δεδομένα.
@ -205,31 +178,29 @@
Θέλαμε λοιπόν ένα τρόπο αναπαράστασης των τοίχων που να βολεύει για το συγκεκριμένο πρόβλημα. Θέλαμε λοιπόν ένα τρόπο αναπαράστασης των τοίχων που να βολεύει για το συγκεκριμένο πρόβλημα.
Η λύση που χρησιμοποιήσαμε συνοψίζεται στα εξής: Η λύση που χρησιμοποιήσαμε συνοψίζεται στα εξής:
\begin{itemize} \begin{itemize}
\item Οι γωνίες των πλακιδίων ονομάζονται κόμβοι και η θέση τους αναπαρίστανται με έναν αύξοντα αριθμό που ισοδυναμεί με την αναπαράσταση δισδιάστατου πίνακα σε \eng{row major order}. \item Οι γωνίες των πλακιδίων ονομάζονται κόμβοι και η θέση τους αναπαρίστανται μονοδιάστατα σαν διεύθυνση σε \eng{row major order.}
\item Ο κάθε κόμβος αποτελεί τον κόμβο-γωνία \eng{(vertex)} ενός γράφου. \item Ο κάθε κόμβος αποτελεί τον κόμβο-γωνία \eng{(vertex)}ενός γράφου.
\item Ο κάθε τοίχος αναπαρίσταται ως ακμή \eng{(edge)} στον γράφο. \item Ο κάθε τοίχος αναπαρίσταται ως ακμή στον γράφο.
\end{itemize} \end{itemize}
Για παράδειγμα στο σχήμα \ref{fig:graph}, που φαίνεται και ένα παράδειγμα αναπαράστασης, ο τοίχος αριστερά του πλακιδίου `3` αναπαρίσταται ως η ακμή `(4,8)`. Για παράδειγμα στο σχήμα \ref{fig:graph}, που φαίνεται και ένα παράδειγμα αναπαράστασης, ο τοίχος αριστερά του πλακιδίου `3` αναπαρίσταται ως η ακμή `(4,8)`.
\InsertFigure{0.6}{fig:graph}{images/graph.png}{ \InsertFigure{0.6}{fig:graph}{images/graph.png}{
\\(α) Διευθυνσιοδότηση πλακιδίων(τετραγωνάκια) και κόμβων(σφαίρες) ενός ταμπλό $3x3$.\\ \\(α) Διευθυνσιοδότηση πλακιδίων(τετραγωνάκια) και κόμβων(σφαίρες) ενός ταμπλό $3x3$.\\
(β) Ένας συνεκτικός γράφος που προκύπτει από το (α) ξεκινώντας από τον τοίχο (7, 11). (β) Ένας συνεκτικός γράφος που προκύπτει από το (α) ξεκινώντας από τον τοίχο (7, 11).
Για τον εν λόγο τοίχο θέλουμε να διαπιστώσουμε αν δημιουργεί κλειστό δωμάτιο. Ο τοίχος είναι αυτός για τον οποίο θέλουμε να διαπιστώσουμε αν δημιουργεί κλειστό δωμάτιο.
Κάτι που σε αυτή την περίπτωση δεν συμβαίνει.
} }
\par Για τον αλγόριθμο υλοποιήσαμε δύο τάξεις. \par Για τον αλγόριθμο υλοποιήσαμε δύο τάξεις.
Την \eng{\textbf{Edge}} που δημιουργεί ζευγάρια κόμβων ώστε να μπορεί να αποθηκεύσει τον κάθε τοίχο(ακμή) και την \eng{\textbf{Graph}} που προσφέρει λειτουργίες δημιουργίας συνεκτικού γράφου λαμβάνοντας ως είσοδο τοίχους(ακμές). Την \eng{\textbf{Edge}}που δημιουργεί ζευγάρια κόμβων ώστε να μπορεί να αποθηκεύσει τον κάθε τοίχο(ακμή) και την \eng{\textbf{Graph}}που προσφέρει λειτουργίες δημιουργίας συνεκτικού γράφου λαμβάνοντας ως είσοδο τοίχους(ακμές).
\par Η λειτουργία του είναι απλή. \par Η λειτουργία του είναι απλή.
Κάθε φορά που ελέγχουμε αν μία διεύθυνση πλακιδίου είναι \textit{χτίσιμη}, \textbf{δημιουργούμε το μεγαλύτερο δυνατό συνεκτικό γράφο που περιέχει τον εν λόγο τοίχο} και όλους τους ήδη τοποθετημένους τοίχους. Κάθε φορά που ελέγχουμε αν μία διεύθυνση πλακιδίου είναι \textit{χτίσιμη}, \textbf{δημιουργούμε το μεγαλύτερο δυνατό συνεκτικό γράφο που περιέχει τον εν λόγο τοίχο} και όλους τους ήδη τοποθετημένους τοίχους.
Αν στον γράφο που προκύπτει υπάρχει κάποιος κόμβος περισσότερες από μία φορές, δηλαδή ο γράφος δεν είναι απλός, τότε αυτό σημαίνει πως στον εν λόγο κόμβο μπορούμε να πάμε ακολουθώντας τοίχους από τουλάχιστον δύο κατευθύνσεις. Αν στον γράφο που προκύπτει υπάρχει κάποιος κόμβος περισσότερες από μία φορές, δηλαδή ο γράφος δεν είναι απλός, τότε αυτό σημαίνει πως στον εν λόγο κόμβο μπορούμε να πάμε ακολουθώντας τοίχους από τουλάχιστον δύο κατευθύνσεις.
Άρα το ταμπλό περιέχει κάποιο κλειστό δωμάτιο. Άρα το ταμπλό περιέχει κάποιο κλειστό δωμάτιο.
Αφού τον αλγόριθμο τον εκτελούμε για κάθε πιθανή \textit{χτίσιμη διεύθυνση}, τότε ο τοίχος που προκαλεί το κλειστό δωμάτιο είναι ο τρέχον. Αφού τον αλγόριθμο τον εκτελούμε για κάθε πιθανή \textit{χτίσιμη διεύθυνση}, τότε ο τοίχος που προκαλεί το κλειστό δωμάτιο είναι αυτός που ελέγχουμε την εκάστοτε στιγμή.
\par Για την λειτουργία του αλγόριθμου χρειαζόμαστε όλους τους τοίχους που είναι ήδη τοποθετημένοι στο ταμπλό στη μορφή \eng{Edge}. \par Για την λειτουργία του αλγόριθμου χρειαζόμαστε όλους τους τοίχους που είναι ήδη τοποθετημένοι στο ταμπλό στη μορφή \eng{Edge.}
Γιαυτό προσθέσαμε στην τάξη \eng{Board} μια λίστα αναφορών \eng{(ArrayList)} και σε αυτήν αποθηκεύουμε κάθε τοίχο που δημιουργούμε. Γιαυτό προσθέσαμε στην τάξη \eng{Board} μια λίστα αναφορών \eng{(ArrayList)} και σε αυτήν αποθηκεύουμε κάθε τοίχο που δημιουργούμε.
Τον αλγόριθμο μπορούμε να τον ενεργοποιήσουμε αν περάσουμε στο πρόγραμμα ως επιλογή το όρισμα \eng{\texttt{-{}-norooms}} από τη γραμμή εντολών Τον αλγόριθμο μπορούμε να τον ενεργοποιήσουμε αν περάσουμε στο πρόγραμμα ως επιλογή το όρισμα \eng{\texttt{-{}-norooms}} από τη γραμμή εντολών.
\footnote{Συνίσταται ανεπιφύλακτα η χρήση αυτής της επιλογής}.
\par Δυστυχώς η κωδικοποίηση που χρησιμοποιούμε εδώ δεν ταιριάζει με αυτή της υπόλοιπης εφαρμογής. \par Δυστυχώς η κωδικοποίηση που χρησιμοποιούμε εδώ δεν ταιριάζει με αυτή της υπόλοιπης εφαρμογής.
Αυτό έχει σαν αποτέλεσμα να πρέπει να δημιουργούμε τον γράφο κάθε φορά. Αυτό έχει σαν αποτέλεσμα να πρέπει να δημιουργούμε τον γράφο κάθε φορά.
@ -240,16 +211,17 @@
Φυσικά θα μπορούσαμε να χρησιμοποιήσουμε υπομνηματισμό και να αποθηκεύουμε τους γράφους Φυσικά θα μπορούσαμε να χρησιμοποιήσουμε υπομνηματισμό και να αποθηκεύουμε τους γράφους
Όμως λόγο του ότι η επιβάρυνση λαμβάνει χώρα μόνο μία φορά κατά την εκκίνηση, σε συνδυασμό με το μικρό μέγεθος του ταμπλό, αποφασίσαμε να μην προχωρήσουμε σε περαιτέρω βελτιστοποίηση. Όμως λόγο του ότι η επιβάρυνση λαμβάνει χώρα μόνο μία φορά κατά την εκκίνηση, σε συνδυασμό με το μικρό μέγεθος του ταμπλό, αποφασίσαμε να μην προχωρήσουμε σε περαιτέρω βελτιστοποίηση.
\section{Υλοποίηση} \section{Υλοποίηση}
Για την μεταγλώττιση της εφαρμογής, απαιτείται \eng{java} έκδοση 8 ή και μεταγενέστερη καθώς έχουμε κάνει χρήση \eng{lambdas}. Για την μεταγλώττιση της εφαρμογής, απαιτείται \eng{java} έκδοση 8 ή και μεταγενέστερη καθώς έχουμε κάνει χρήση \eng{lambdas.}
Για υπενθύμιση, αναφέρουμε συνοπτικά τη λειτουργία ορισμένων αντικειμένων που προσθέσαμε κατά την πρώτη και δεύτερη εργασία. Όσο αφορά την υλοποίηση, εκτός από τα ζητηθέντα αντικείμενα υλοποιήσαμε και τα παρακάτω.
\begin{itemize} \begin{itemize}
\item \eng{\textbf{Const}}\\ \item \eng{\textbf{Const}}\\
Το αντικείμενο αυτό περιέχει σταθερές για όλη την εφαρμογή. Το αντικείμενο αυτό περιέχει σταθερές για όλη την εφαρμογή.
\item \eng{\textbf{Session}}\\ \item \eng{\textbf{Session}}\\
Το αντικείμενο αυτό περιέχει όλες τις τιμές της εφαρμογής που αποτελούν ρυθμίσεις ή επιλογές, όπως πχ το μέγεθος του ταμπλό, τον αριθμό των εφοδίων κτλ. Το αντικείμενο αυτό περιέχει όλες τις τιμές της εφαρμογής που αποτελούν ρυθμίσεις ή επιλογές, όπως πχ το μέγεθος του ταμπλό, τον αριθμό των εφοδίων κτλ.
\item \eng{\textbf{Direction}}\\ \item \eng{\textbf{Direction}}\\
Το αντικείμενο αυτό λειτουργεί σαν \eng{C++ enumerator} και παρέχει ονοματολογία στις διευθύνσεις που χρησιμοποιούμε στην εφαρμογή. Το αντικείμενο αυτό λειτουργεί σαν \eng{C++ enumerator}και παρέχει ονοματολογία στις διευθύνσεις που χρησιμοποιούμε στην εφαρμογή.
\item \eng{\textbf{DirRange}}\\ \item \eng{\textbf{DirRange}}\\
Ομοίως ένα βοηθητικό αντικείμενο αυτή τη φορά για την αυτόματη δημιουργία διευθύνσεων σε βρόχους επανάληψης. Ομοίως ένα βοηθητικό αντικείμενο αυτή τη φορά για την αυτόματη δημιουργία διευθύνσεων σε βρόχους επανάληψης.
\item \eng{\textbf{Edge}}\\ \item \eng{\textbf{Edge}}\\
@ -258,135 +230,108 @@
Με αυτό τον τρόπο λειτουργεί ως διεπαφή ανάμεσα στην κωδικοποίηση που χρησιμοποιείται στην υπόλοιπη εφαρμογή και στην κωδικοποίηση που χρησιμοποιείται για την εύρεση κλειστών δωματίων. Με αυτό τον τρόπο λειτουργεί ως διεπαφή ανάμεσα στην κωδικοποίηση που χρησιμοποιείται στην υπόλοιπη εφαρμογή και στην κωδικοποίηση που χρησιμοποιείται για την εύρεση κλειστών δωματίων.
\item \eng{\textbf{Graph}}\\ \item \eng{\textbf{Graph}}\\
Το αντικείμενο αυτό υλοποιεί λειτουργίες ενός συνεκτικού γράφου. Το αντικείμενο αυτό υλοποιεί λειτουργίες ενός συνεκτικού γράφου.
Ο \eng{constructor} της τάξης δέχεται ως όρισμα μια ακμή και στην ουσία δημιουργεί τους 2 πρώτους κόμβους. Ο \eng{constructor}της τάξης δέχεται ως όρισμα μια ακμή και στην ουσία δημιουργεί τους 2 πρώτους κόμβους.
Οι βασικές λειτουργίες είναι η \eng{\textit{attach()}} η οποία δέχεται μια ακμή και προσπαθεί να τοποθετήσει τους κόμβους της στο γράφο με συνεκτικό τρόπο. Οι βασικές λειτουργίες είναι η \eng{\textit{attach()}}η οποία δέχεται μια ακμή και αν κάποιος κόμβος της ακμής ανήκει ήδη στο γράφο τότε τοποθετεί και τον άλλο.
Και η \eng{\textit{count()}} η οποία δέχεται ένα κόμβο και μετράει πόσες φορές ο κόμβος αυτός περιέχεται στον γράφο. Και η \eng{\textit{count()}}η οποία δέχεται ένα κόμβο και μετράει πόσες φορές ο κόμβος αυτός περιέχεται στον γράφο.
\item \eng{\textbf{Position}}\\ \item \eng{\textbf{Position}}\\
Το αντικείμενο αυτό χρησιμοποιείται ως ένα κοινό σύστημα αναπαράστασης συντεταγμένων για την εφαρμογή. Το αντικείμενο αυτό χρησιμοποιείται ως ένα κοινό σύστημα συντεταγμένων για την εφαρμογή.
Ακόμα προσφέρει μεθόδους μετατροπής της μιας αναπαράστασης στην άλλη.
Οι αναπαραστάσεις αυτές όπως αναφερθήκαμε και παραπάνω είναι η καρτεσιανή που περιέχει δύο μεταβλητές για την γραμμή και τη στήλη και η μονοδιάστατη \eng{(id)}.
\item \eng{\textbf{Range}}\\ \item \eng{\textbf{Range}}\\
Το αντικείμενο χρησιμοποιείται για να δημιουργεί εύρη τιμών. Το αντικείμενο χρησιμοποιείται για να δημιουργεί εύρη τιμών.
Για παράδειγμα ο κώδικας παρακάτω δημιουργεί ένα \eng{range} με όλες τις διευθύνσεις.
\selectlanguage{english}
\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}
\selectlanguage{greek}
\item \eng{\textbf{ShuffledRange}}\\ \item \eng{\textbf{ShuffledRange}}\\
Το αντικείμενο αυτό χρησιμοποιείται για να δημιουργεί \textit{“τυχαίως διατεταγμένα”} εύρη τιμών. Το αντικείμενο αυτό χρησιμοποιείται για να δημιουργεί “τυχαίως ανακατεμένα” εύρη τιμών.
Η τάξη αυτή κληρονομεί την \eng{Range} και προσθέτει τη λειτουργία του τυχαίου ανακατέματος των τιμών. Η τάξη αυτή κληρονομεί την \eng{Range}και προσθέτει τη λειτουργία του τυχαίου ανακατέματος των τιμών.
Για παράδειγμα παρακάτω δημιουργούμε μια τυχαία σειρά από όλα τα πλακίδια του ταμπλό.
\selectlanguage{english} \begin{verbatim}
ShuffledRange rand = new ShuffledRange(0, N*N);
for (int tileId =rand.get(); tileId!=Const.EOR ; tileId=rand.get()){
// use tileId
}
\end{verbatim} \selectlanguage{greek}
\end{itemize} \end{itemize}
Τόσο η \eng{Range}όσο και η \eng{ShuffledRange}έχουν μια μέθοδο \eng{\textit{get()}}η οποία επιστρέφει και αφαιρεί το πρώτο στοιχείο από το \eng{range}.
Όταν το \eng{range}είναι άδειο τότε επιστρέφει την τιμή φρουρό \eng{EOR - End of Range.}
\subsection{Αλγόριθμος \eng{minimax}} \label{minimax} \par Τα υπόλοιπα αντικείμενα είναι τα ζητηθέντα.
\WrapFigure{0.45}{r}{fig:maxValue}{images/maxValue.png}{ Σε αυτά δεν έχουμε αλλάξει τις προδιαγραφές με εξαίρεση τις μεθόδους \eng{\textit{createBoard()}}και \eng{\textit{createSupply()}}στις οποίες προσθέσαμε σαν ορίσματα τα πλακίδια του Θησέα και του Μινώταυρου.
Διάγραμμα ροής της \eng{\textit{maxValue()}}, όπου γίνεται αναδρομική κλήση της \eng{\textit{minValue()}}. Ο λόγος είναι γιατί για τα εφόδια, θέλουμε να γνωρίζουμε σε ποια πλακίδια είναι οι παίχτες, ώστε να μην τοποθετήσουμε εκεί κάποιο εφόδιο.
} Δε για το ταμπλό, γιατί θέλουμε να ξέρουμε σε ποια πλακίδια θα τοποθετήσουμε τους παίχτες.
Ο σκοπός της παρούσας εργασίας θεωρούμε πως ήταν η υλοποίηση του αλγόριθμου \eng{minimax}.
Για τον αλγόριθμο αυτό αρχικά απαιτείται η δημιουργία ενός δέντρου.
Το κάθε επίπεδο του δέντρου έχει για κόμβους όλες τις δυνατές κινήσεις των παικτών εναλλάξ.
Έτσι ο κάθε κόμβος αναπαριστά μια κίνηση και τα παιδιά του κάθε κόμβου αναπαριστούν όλες τις δυνατές κινήσεις-απαντήσεις του αντίπαλου παίκτη στην κίνηση αυτή.
Καθώς εκτελείται ο αλγόριθμος η αξιολόγηση της κάθε κίνησης από τους κατώτερους κόμβους ανεβαίνει προς τα πάνω.
Η επιλογή γίνεται εναλλάξ από τον κάθε παίκτη.
Έτσι ο αντίπαλος επιλέγει για τιμή του κάθε κόμβου την τιμή από τα παιδιά με την μικρότερη αξιολόγηση, ενώ ο παίκτης μας την τιμή με την μεγαλύτερη.
Στο τέλος ο βασικός κόμβος περιέχει την μέγιστη δυνατή τιμή λαμβάνοντας υπόψιν την καλύτερη δυνατή κίνηση-απάντηση του αντιπάλου.
\par Για τον κάθε κόμβο υλοποιήσαμε την τάξη \eng{Node} ακολουθώντας τις οδηγίες της εκφώνησης.
Πέρα από αυτές όμως χρειαστήκαμε και μια επιπλέον πληροφορία, που αφορά το μονοπάτι της επιλογής των κόμβων από τον αλγόριθμο.
Δηλαδή, σε κάθε κόμβο αποθηκεύουμε επιπλέον ένα δείκτη προς τον κόμβο από τον οποίο έγινε η επιλογή της τιμής του.
Με αυτό τον τρόπο ήμαστε σε θέση, μετά την εκτέλεση του αλγόριθμου, να γνωρίζουμε τόσο την τιμής της αξιολόγησης, όσο και την κίνηση από την οποία προήλθε.
\par Η υλοποίηση του \eng{minimax} αλγόριθμου έγινε στην τάξη \eng{MinMaxPlayer}. \section{Εκτελέσιμο}
Η τάξη αυτή κληρονομεί την \eng{Player} και μαζί και τις βασικές λειτουργίες όπως τους \eng{Constructors} και το \eng{accessor - mutator idiom}.
Ακολουθώντας τον σχεδιασμό της \eng{HeuristicPlayer}, υλοποιήσαμε την \eng{MinMaxPlayer}, δίχως κανένα \eng{data member}.
Αυτά ανήκουν όλα στην \eng{Player}.
Έτσι όλη η επιπρόσθετη λειτουργικότητα της τάξης αφορά τον αλγόριθμο \eng{nimimax}.
Οι κύριες μέθοδοι που υλοποιήθηκαν για την δημιουργία του δέντρου είναι:\\
\begin{itemize}
\item \eng{\textbf{\textit{int[] dryMove()}}}\\
Η συνάρτηση αυτή προσομοιώνει την κίνηση κάποιου παίκτη χρησιμοποιώντας για ταμπλό αυτό από τα ορίσματά της.
Έτσι μπορούμε να πραγματοποιήσουμε διαφορετικές κινήσεις σε διαφορετικούς κλώνους του ταμπλό χωρίς να αλλοιώσουμε το βασικό ταμπλό του παιχνιδιού.
\item \eng{\textbf{\textit{void createMySubtree()}}}\\
Η συνάρτηση αυτή είναι η μία από τις δύο μεθόδους που υλοποιούν το δέντρο με τις δυνατές κινήσεις των παικτών.
Ξεκινώντας από τη τρέχουσα θέση του παίκτη και για κάθε “διασχίσιμη διεύθυνση” δημιουργούμε και ένα αντίγραφο του ταμπλό και σε αυτό εκτελούμε την κίνηση του παίκτη εικονικά μέσω της συνάρτησης \eng{\textit{dryMove()}}.
Έπειτα αποθηκεύουμε στο δέντρο την κίνηση αυτή ώς κόμβο και καλούμε την συνάρτηση \eng{\textit{createOppSubtree()}} με αυτόν σαν όρισμα.
\item \eng{\textbf{\textit{void createOppSubtree()}}}\\
Η συνάρτηση εκτελεί την ίδια διαδικασία με την \eng{\textit{createMySubtree()}} αλλά για τον αντίπαλο αυτή τη φορά.
\item \eng{\textbf{\textit{double evaluation()}}}\\
Η συνάρτηση αυτή είναι υπεύθυνη για την αξιολόγηση της κάθε κίνησης.
Τόσο στην περίπτωση της του παίκτη, όσο και στην περίπτωση του αντιπάλου.
Για να είναι συμβατή με τα ζητούμενα, αξιολογεί την κίνηση “κοιτώντας” το ταμπλό στην κατεύθυνση της κίνησης και μόνο.
Αυτή η συμπεριφορά είναι αρκετά περιοριστική και οδηγεί σε πολύ φτωχά αποτελέσματα.
Για παράδειγμα ενώ μια κίνηση προς τον αντίπαλο αξιολογείται αρνητικά καθώς ο παίκτης μπορεί να τον “δει”, η κατεύθυνση μακριά από τον αντίπαλο δεν αξιολογείται θετικά.
Ακόμα στην περίπτωση που η αξιολόγηση αφορά κίνηση του αντιπάλου, δεν έχουμε την δυνατότητα να αξιολογήσουμε την κατάσταση της πίστας συνολικά.
Για να αντιμετωπίσουμε αυτόν τον τελευταίο περιορισμό, μετά την προσομοίωση της κίνησης του αντιπάλου, αξιολογούμε ξανά την τελευταία κίνηση του παίκτη.
Μόνο που πλέον ο αντίπαλος είναι σε νέα θέση.
\end{itemize}
\par Τόσο η \eng{\textit{createMySubtree()}}, όσο και η \eng{\textit{createOppSubtree()}} εσωτερικά αξιολογούν την κάθε κίνηση.
Αυτό είναι περιττό καθώς οι μόνοι κόμβοι που απαιτείται να αξιολογηθούν είναι τα φύλλα.
Παρόλα αυτά αποφασίσαμε να εκτελέσουμε την \eng{\textit{evaluate()}} που αξιολογεί την κίνηση σε κάθε κόμβο ώστε να συμβαδίζουμε με τις οδηγίες της εκφώνησης.
\par Ο αλγόριθμος ολοκληρώνεται με την υλοποίηση των συναρτήσεων που ανάγουν τις αξιολογήσεις των κόμβων προς τα πάνω.
Αυτές είναι οι \eng{\textit{maxValue()}} και \eng{\textit{minValue()}} και εκτελούνται αναδρομικά εναλλάξ από τον παίκτη και τον αντίπαλο.
Στο σχήμα \ref{fig:maxValue} φαίνεται το διάγραμμα ροής της \eng{\textit{maxValue()}}.
Η \eng{\textit{minValue()}} είναι αντίστοιχη.
Ο παίκτης προσπαθεί μέσω της \eng{\textit{maxValue()}} να επιλέξει τη μεγαλύτερη αξιολόγηση ενώ ο αντίπαλος, μέσω της \eng{\textit{minValue()}}, επιλέγει την μικρότερη.
\section{Εκτελέσιμο} \label{sec:excecutable}
Όπως αναφέραμε και στην παράγραφο με τα παραδοτέα, σε αυτά υπάρχει και το παραγόμενο εκτελέσιμο για την γραμμή εντολών. Όπως αναφέραμε και στην παράγραφο με τα παραδοτέα, σε αυτά υπάρχει και το παραγόμενο εκτελέσιμο για την γραμμή εντολών.
Πρόκειται για ένα \eng{jar} αρχείο το οποίο μπορεί κάποιος να εκτελέσει σε ένα τερματικό, μέσω της εντολής \eng{\texttt{java -jar labyrinth}}. Πρόκειται για ένα \eng{jar}αρχείο το οποίο μπορεί κάποιος να εκτελέσει σε ένα τερματικό, μέσω της εντολής \eng{\texttt{java -jar labyrinth}}.
\InsertFigure{0.9}{fig:executable_help}{images/screenshot_help.png}{ \InsertFigure{0.5}{fig:executable}{images/screenshot.png}{
Οι διαθέσιμες επιλογές και οι τρόποι με τους οποίους μπορούμε να εκτελέσουμε το παιχνίδι. Στιγμιότυπο από την εκτέλεση του προγράμματος σε \eng{interactive mode}και με αποφυγή κλειστών δωματίων.
Η εντολή που χρησιμοποιήθηκε είναι \eng{\texttt{java -jar labyrinth -i -{}-norooms}}
} }
Στον κώδικά μας χρησιμοποιούμε \eng{asserions} ώστε να ελέγξουμε την είσοδο και τις επιλογές του χρήστη. Στον κώδικά μας χρησιμοποιούμε \eng{asserions}ώστε να ελέγξουμε την είσοδο και τις επιλογές του χρήστη.
Επομένως αν κάποιος θέλει να πειραματιστεί με τις επιλογές καλό θα ήταν να τα ενεργοποιήσει στην \eng{VM} της \eng{java} με την παράμετρο \eng{\texttt{-ea}}. Επομένως αν κάποιος θέλει να πειραματιστεί με τις επιλογές καλό θα ήταν να τα ενεργοποιήσει στην \eng{VM}της \eng{java}με την παράμετρο \eng{\texttt{-ea}.}
Σε αυτή την περίπτωση θα μπορούσε να εκτελέσει \eng{\texttt{java -ea -jar labyrinth -i -{}-norooms ...etc}} Σε αυτή την περίπτωση θα μπορούσε να εκτελέσει \eng{\texttt{java -ea -jar labyrinth -i --norooms ...etc}}
\par Το παραγόμενο \eng{jar} παρέχει ένα αριθμό από επιλογές-ορίσματα τα οποία ελέγχουν την λειτουργία του παιχνιδιού. \par Το παραγόμενο \eng{jar}παρέχει ένα αριθμό από επιλογές-ορίσματα τα οποία ελέγχουν την λειτουργία του παιχνιδιού.
Τις επιλογές αυτές μπορούμε να δούμε και από την γραμμή εντολών απλώς εκτελώντας την εντολή \eng{\texttt{java -jar labyrinth -h}}. Τις επιλογές αυτές μπορούμε να δούμε και από την γραμμή εντολών απλώς εκτελώντας την εντολή \eng{\texttt{java -jar labyrinth -h}}.
Στο σχήμα \ref{fig:executable_help} μπορούμε να δούμε ένα στιγμιότυπο με τις διαθέσιμες επιλογές-ορίσματα. Στο σχήμα \ref{fig:executable_help} μπορούμε να δούμε ένα στιγμιότυπο με τις διαθέσιμες επιλογές-ορίσματα.
Αναλυτικά, εκτός από την \eng{\texttt{-h}} αυτές είναι: Αναλυτικά, εκτός από την \eng{\texttt{-h}}αυτές είναι:
\begin{itemize} \begin{itemize}
\item \eng{\textbf{-i}} ή \eng{\textbf{-{}-interactive}}\\ \item \eng{\textbf{-i}}ή \eng{\textbf{-{}-interactive}}\\
Αυτό το όρισμα ενεργοποιεί το \eng{\textit{“interactive mode”}}. Αυτό το όρισμα ενεργοποιεί το \eng{\textit{“interactive mode”.}}
Πρόκειται για λειτουργία κατά την οποία το παιχνίδι μετά από κάθε γύρο σταματά και περιμένει είσοδο από τον χρήστη για να προχωρήσει στον επόμενο. Πρόκειται για λειτουργία κατά την οποία το παιχνίδι μετά από κάθε γύρο σταματά και περιμένει είσοδο από τον χρήστη για να προχωρήσει στον επόμενο.
Αν αυτή η λειτουργία δεν είναι ενεργοποιημένη, τότε το παιχνίδι εκτελείται μονομιάς και στην έξοδο εκτυπώνονται όλοι οι γύροι αμέσως μετά την είσοδο της εντολής. Αν αυτή η λειτουργία δεν είναι ενεργοποιημένη, τότε το παιχνίδι εκτελείται μονομιάς και στην έξοδο εκτυπώνονται όλοι οι γύροι αμέσως μετά την είσοδο της εντολής.
\textit{Αυτή η επιλογή είναι απενεργοποιημένη ως προεπιλογή}. \textit{Αυτή η επιλογή είναι απενεργοποιημένη ως προεπιλογή}.
\item \eng{\textbf{-{}-norooms}}\\ \item \eng{\textbf{-{}-norooms}}\\
Αυτό το όρισμα ενεργοποιεί την λειτουργία της εύρεσης και αποτροπής των κλειστών δωματίων. Αυτό το όρισμα ενεργοποιεί την λειτουργία της εύρεσης και αποτροπής των κλειστών δωματίων.
Αν δεν γίνει χρήση αυτή της επιλογής, τότε η λειτουργία αυτή είναι απενεργοποιημένη ως προεπιλογή. Αν δεν γίνει χρήση αυτή της επιλογής, τότε η λειτουργία αυτή είναι απενεργοποιημένη ως προεπιλογή\footnote{Συνίσταται ανεπιφύλακτα η χρήση αυτής της επιλογής}.
Η ενεργοποίησή της θεωρούμε πως είναι καλή επιλογή. \InsertFigure{0.9}{fig:executable_help}{images/screenshot_help.png}{
Ο λόγος που δεν την προ-επιλέγουμε είναι γιατί δεν ζητείται ρητά από την εκφώνηση. Οι διαθέσιμες επιλογές και οι τρόποι με τους οποίους μπορούμε να εκτελέσουμε το παιχνίδι
}
\item \eng{\textbf{-b}} ή \eng{\textbf{-{}-board}}\\ \item \eng{\textbf{-b}}ή \eng{\textbf{-{}-board}}\\
Αυτό το όρισμα απαιτείται να ακολουθηθεί από ένα θετικό ακέραιο που περιγράφει το επιθυμητό μέγεθος της πλευράς του ταμπλό. Αυτό το όρισμα απαιτείται να ακολουθηθεί από ένα θετικό ακέραιο που περιγράφει το επιθυμητό μέγεθος της πλευράς του ταμπλό.
Πχ:\\[1.2ex] Πχ:\\[1.2ex]
\eng{\texttt{java -jar labyrinth -b 11}}\\[1.2ex] \eng{\texttt{java -jar labyrinth -b 11}}\\[1.2ex]
Αν ο χρήστης δεν χρησιμοποιήσει την επιλογή τότε το προεπιλεγμένο μέγεθος είναι 15. Αν ο χρήστης δεν χρησιμοποιήσει την επιλογή τότε το προεπιλεγμένο μέγεθος είναι 15.
\item \eng{\textbf{-s}} ή \eng{\textbf{-{}-supplies}}\\ \item \eng{\textbf{-s}}ή \eng{\textbf{-{}-supplies}}\\
Αυτό το όρισμα απαιτείται να ακολουθηθεί από ένα θετικό ακέραιο που περιγράφει τον επιθυμητό αριθμό των εφοδίων για το παιχνίδι. Αυτό το όρισμα απαιτείται να ακολουθηθεί από ένα θετικό ακέραιο που περιγράφει τον επιθυμητό αριθμό των εφοδίων για το παιχνίδι.
Πχ:\\[1.2ex] Πχ:\\[1.2ex]
\eng{\texttt{java -jar labyrinth -b 11 -s 7}}\\[1.2ex] \eng{\texttt{java -jar labyrinth -b 11 -s 7}}\\[1.2ex]
Αν ο χρήστης δεν χρησιμοποιήσει την επιλογή τότε ο προεπιλεγμένος αριθμός είναι 4. Αν ο χρήστης δεν χρησιμοποιήσει την επιλογή τότε ο προεπιλεγμένος αριθμός είναι 4.
\item \eng{\textbf{-r}} ή \eng{\textbf{-{}-rounds}}\\ \item \eng{\textbf{-r}}ή \eng{\textbf{-{}-rounds}}\\
Αυτό το όρισμα απαιτείται να ακολουθηθεί από ένα θετικό ακέραιο που περιγράφει τον επιθυμητό αριθμό γύρων μέχρι να ολοκληρωθεί το παιχνίδι. Αυτό το όρισμα απαιτείται να ακολουθηθεί από ένα θετικό ακέραιο που περιγράφει τον επιθυμητό αριθμό γύρων μέχρι να ολοκληρωθεί το παιχνίδι.
Πχ:\\[1.2ex] Πχ:\\[1.2ex]
\eng{\texttt{java -jar labyrinth -r 200 -s 7}}\\[1.2ex] \eng{\texttt{java -jar labyrinth -r 200 -s 7}}\\[1.2ex]
Αν ο χρήστης δεν χρησιμοποιήσει την επιλογή τότε ο προεπιλεγμένος αριθμός είναι 100. Αν ο χρήστης δεν χρησιμοποιήσει την επιλογή τότε ο προεπιλεγμένος αριθμός είναι 100.
\end{itemize} \end{itemize}
\InsertFigure{0.6}{fig:executable}{images/screenshot.png}{
Στιγμιότυπο από την εκτέλεση του προγράμματος σε \eng{interactive mode} και με αποφυγή κλειστών δωματίων.
Η εντολή που χρησιμοποιήθηκε είναι \eng{\texttt{java -jar labyrinth -i -{}-norooms}}
}
\section{Παρατηρήσεις} \section{Παρατηρήσεις}
Κλείνοντας θα θέλαμε να πούμε πως η παρούσα εργασία ήταν μια προσπάθεια να υλοποιήσουμε τα ζητούμενα χρησιμοποιώντας όμως πρακτικές που θεωρούμε αναγκαίες για την συγγραφή κώδικα. Σε αυτό το σημείο θα θέλαμε να παρατηρήσουμε και μια σχεδιαστική αβλεψία.
Πρακτικές που στόχο έχουν να αποτρέψουν την πιθανότητα λανθασμένης χρήσης σε βάθος χρόνου. Το κομμάτι του κώδικα που δημιουργεί χρήστες καθώς και ορισμένες επιλογές που τους αφορούν, όπως η θέση τους στο ταμπλό, οι κινήσεις κ.α., δεν είναι υλοποιημένο με τον καλύτερο δυνατό τρόπο.
Για το λόγο αυτό προσπαθήσαμε να περιορίσουμε, όπου αυτό δεν ήταν αντίθετο με τα ζητούμενα, την δυνατότητα της λανθασμένης χρήσης εισάγοντας αρκετά επιπλέον εργαλεία για την εφαρμογή. Θα θέλαμε να ήταν πιο γενικό και παραμετροποιήσιμο.
Τέτοια για παράδειγμα είναι η τάξη \eng{Position} ή η αποφυγή των \eng{setters} κτλ. Ο λόγος που δεν είναι, έχει να κάνει αρκετά και με την υποψία μας, πως αυτό το υποσύστημα θα χρειαστεί να αλλάξει αρκετά στις επόμενες εργασίες.
Ακόμα προσπαθήσαμε να οργανώσουμε τις δομές δεδομένων με τέτοιο τρόπο ώστε να περιορίσουμε τον απαιτούμενο κώδικά όσο το δυνατό περισσότερο. Αν εκτιμήσαμε λανθασμένα, τότε είναι απλώς κρίμα.
Αυτό έχει ως αποτέλεσμα ένα κάπως πιο μακροσκελές κώδικα, με πολλά αντικείμενα και συναρτήσεις, που ίσως είναι δυσανάλογος των μικρών απαιτήσεων της εργασίας.
\par Μια τεχνική που δεν χρησιμοποιήσαμε αλλά μας προβλημάτισε αν θα το κάναμε ή όχι ήταν τα \eng{generics.}
Η αρχική μας σκέψη, καθώς ήμαστε επηρεασμένοι ξεκάθαρα από την \eng{C++,}ήταν οι τάξεις \eng{Edge, Graph, Range}και \eng{ShuffledRange}να είναι \eng{generics.}
Το γεγονός όμως ότι τα αντικείμενα αυτά θα τα χρησιμοποιούσαμε μονάχα σε αυτή την εργασία, μας απέτρεψε.
Μια απλή υλοποίηση μόνο για \eng{int}ήταν αρκετή και κάτι διαφορετικό θα εισήγαγε περιττή πολυπλοκότητα.
\par Κλείνοντας θα θέλαμε να παρατηρήσουμε πως η παρούσα υλοποίηση θεωρούμε ότι ήταν μια προσπάθεια να ισορροπήσουμε μεταξύ των ζητηθέντων και μεταξύ πρακτικών που ίσως μας βοηθήσουν στη συνέχεια για συγγραφή ευκολότερου και καθαρότερου κώδικα.
Γενικά πιστεύουμε πως \textbf{αν ο χρήστης μιας βάσης κώδικα έχει τη δυνατότητα να χρησιμοποιήσει λανθασμένα τον κώδικα, κάποια στιγμή θα το κάνει.}
Ακόμα και αν ο χρήστης είναι ο ίδιος ο αρχικός συντάκτης του κώδικα.
Για το λόγο αυτό προσπαθήσαμε να περιορίσουμε, όπου αυτό δεν ήταν αντίθετο με τα ζητούμενα, την δυνατότητα της λανθασμένης χρήσης εισάγοντας αρκετά επιπλέον αντικείμενα και δομές ως εργαλεία για την εφαρμογή.
Τέτοια για παράδειγμα είναι η τάξη \eng{Position}ή η αποφυγή των \eng{setters}κτλ.
Ελπίζουμε να μην θεωρηθούν υπερβολικά. Ελπίζουμε να μην θεωρηθούν υπερβολικά.
% References % References

View File

@ -2,12 +2,9 @@
* @file Board.java * @file Board.java
* *
* @author * @author
* Anastasia Foti AEM:8959 * Christos Choutouridis
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr> * <cchoutou@ece.auth.gr>
* AEM:8997
*/ */
package host.labyrinth; package host.labyrinth;
@ -34,10 +31,8 @@ class Board {
this.S = 0; this.S = 0;
this.W = 0; this.W = 0;
tiles = null; tiles = null;
supplies = null; supplies =null;
walls = new ArrayList<Edge>(); walls = new ArrayList<Edge>();
moves = new int[Const.numOfPlayers][Player.MOVE_DATA_SIZE];
playerCount =0;
} }
/** /**
@ -54,8 +49,6 @@ class Board {
tiles = new Tile[N*N]; tiles = new Tile[N*N];
supplies = new Supply[S]; supplies = new Supply[S];
walls = new ArrayList<Edge>(); walls = new ArrayList<Edge>();
moves = new int[Const.numOfPlayers][Player.MOVE_DATA_SIZE];
playerCount =0;
} }
/** /**
@ -76,25 +69,10 @@ class Board {
this.N = b.N; this.N = b.N;
this.S = b.S; this.S = b.S;
this.W = b.W; this.W = b.W;
tiles = new Tile[b.tiles.length]; // Clone arrays
supplies = new Supply[b.supplies.length]; this.tiles = b.tiles.clone();
walls = new ArrayList<Edge>(); this.supplies = b.supplies.clone();
moves = new int[Const.numOfPlayers][Player.MOVE_DATA_SIZE]; this.walls = b.walls;
playerCount =b.playerCount;
// clone moves array of array of primitives
for (int i=0 ; i<b.moves.length ; ++i)
this.moves[i] = b.moves[i].clone();
// Clone arrays of objects
for (int i=0 ; i<b.tiles.length ; ++i)
this.tiles[i] = new Tile(b.tiles[i]);
for (int i=0 ; i<b.supplies.length ; ++i)
this.supplies[i] = new Supply(b.supplies[i]);
// clone vectors
for (Edge it: b.walls)
this.walls.add(new Edge(it));
} }
/** @} */ /** @} */
@ -192,15 +170,6 @@ class Board {
&& !(Position.toID(row, col) == 0 && direction == Direction.DOWN); && !(Position.toID(row, col) == 0 && direction == Direction.DOWN);
} }
/**
* Utility function to check if there is a supply on the tile or not
* @param tileId The tile to check
* @return Yes/no
*/
boolean hasSupply (int tileId) {
return (Const.noSupply != tiles[tileId].hasSupply(supplies)) ? true : false;
}
/** /**
* Try to pick supply from a tile. If succeed it also erases the * Try to pick supply from a tile. If succeed it also erases the
* supply from the board. * supply from the board.
@ -218,7 +187,6 @@ class Board {
return supplyId; return supplyId;
} }
/** /**
* A plain fair dice functionality provided by the board. * A plain fair dice functionality provided by the board.
* @return A random direction; * @return A random direction;
@ -230,53 +198,6 @@ class Board {
/** @return the size of each site of the board. */ /** @return the size of each site of the board. */
int size () { return N; } int size () { return N; }
/**
* Utility function to create player IDs
* @return The generated player id.
*/
int generatePlayerId () throws Exception {
if (playerCount < Const.numOfPlayers)
return playerCount++;
else
throw new Exception("Maximum number of players exceeded");
}
/**
* Boards utility to give access to other player Id.
*
* @param playerId The id of player who asks
* @return The other player's Id.
*/
int getOpponentId(int playerId) {
return Const.numOfPlayers - (playerId +1);
}
/**
* Boards utility to give access to other player moves.
*
* @param playerId The id of player who asks
* @return The moves data of other player
*/
int[] getOpponentMove (int playerId) {
return moves[getOpponentId(playerId)];
}
/**
* Utility to update the moves of each player.
*
* This function is used by the players to update their position on the board.
* After that a player can read other player positions using getOpponentMoves()
* @see getOpponentMoves()
*
* @param m Reference to new move data
* @param playerId The id of the player who update his/her data.
*/
void updateMove(int[] m, int playerId) {
//moves.set(playerId, Arrays.stream(m).boxed().toArray(Integer[]::new));
moves[playerId] = m;
}
/** @} */ /** @} */
/** /**
@ -308,13 +229,6 @@ class Board {
*/ */
ArrayList<Edge> getWalls() { return walls; } ArrayList<Edge> getWalls() { return walls; }
/**
* @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.
*/
int[][] getMoves() { return moves; }
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; }
@ -339,13 +253,6 @@ class Board {
*/ */
void setWalls (ArrayList<Edge> walls) { this.walls= walls; } void setWalls (ArrayList<Edge> walls) { this.walls= walls; }
/**
* @param moves Reference to moves that we want to act as replacement for the inner moves vector.
* @note Use with care.
* Any call to this function will probably add memory for the garbage collector.
*/
void setMoves(int[][] moves) { this.moves =moves; }
/** @} */ /** @} */
@ -548,15 +455,15 @@ class Board {
*/ */
private int createInnerWalls () { private int createInnerWalls () {
ShuffledRange randTiles = new ShuffledRange(0, N*N); ShuffledRange randTiles = new ShuffledRange(0, N*N);
for (int tileId, walls =0, shuffleMark =0 ; true ; ) { 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.EOR) { if ((tileId = randTiles.get())== Const.EOR) {
if (walls == shuffleMark) // Wallable tiles exhausted. if (i == shuffleMark) // Wallable tiles exhausted.
return walls; 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 =walls; shuffleMark =i;
} }
} }
} while (!isWallable(tileId)); } while (!isWallable(tileId));
@ -651,7 +558,5 @@ class Board {
* Array to hold all the walls using the edge representation * Array to hold all the walls using the edge representation
* required by the closed room preventing algorithm. * required by the closed room preventing algorithm.
*/ */
private int[][] moves;
private int playerCount;
/** @} */ /** @} */
} }

View File

@ -2,40 +2,23 @@
* @file Common.java * @file Common.java
* *
* @author * @author
* Anastasia Foti AEM:8959 * Christos Choutouridis
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr> * <cchoutou@ece.auth.gr>
* AEM:8997
*/ */
package host.labyrinth; package host.labyrinth;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
//import java.util.function.IntFunction;
/** /**
* Class to hold constant values for entire application * Class to hold constant values for entire application
*/ */
class Const { class Const {
static final int numOfPlayers = 2;
static final int maxTileWalls = 2; /**< Number of maximum walls for each tile on the board */ 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 noSupply =-1; /**< Number to indicate the absent of supply */
static final int noOpponent =-1; /**< Number to indicate the absent of supply */
static final int noTileId =-1; /**< Number to indicate wrong tileId */ static final int noTileId =-1; /**< Number to indicate wrong tileId */
static final int EOR =-1; /**< Number to indicate the End Of Range */ static final int EOR =-1; /**< Number to indicate the End Of Range */
static final int viewDistance =3; /**< The max distance of the Heuristic player's ability to see */
static final int noView = viewDistance+1;
/** Parameters to control move evaluation */
/** @{ */
static final double opponentFactor = 1.0; /**< opponent distance factor */
static final double supplyFactor = 0.65; /**< supply distance factor */
static final double preMoveFactor = 0.65; /**< pre move distances factor */
static final double postMoveFactor = 0.35; /**< post move distances factor */
static final int minimaxTreeDepth = 4; /**< The maximum depth of the minimax tree */
/** @} */
} }
/** /**
* Application wide object to hold settings like values for the session. * Application wide object to hold settings like values for the session.
@ -48,6 +31,23 @@ class Session {
static boolean interactive = false; /**< When true each round of the game requires user input */ 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. * Helper C++ like enumerator class for direction ranged loops.
* *
@ -64,40 +64,6 @@ class DirRange {
static final int Begin =1; /**< Iterator style begin of range direction (starting north) */ 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 End =8; /**< Iterator style end of range direction (one place after the last) */
static final int Step =2; /**< Step for iterator style direction */ static final int Step =2; /**< Step for iterator style direction */
static final int numOfDirections =4;
}
/**
* 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 NONE =8; /**< No direction */
/**
* Utility to get the opposite direction.
* @param direction Input direction
* @return The opposite direction
*/
static int opposite (int direction) {
return (direction != NONE) ? (direction+4)%DirRange.End : NONE;
}
static int get (int fromId, int toId) {
if (Position.toID(Position.toRow(fromId), Position.toCol(fromId)-1) == toId)
return Direction.LEFT;
else if (Position.toID(Position.toRow(fromId), Position.toCol(fromId)+1) == toId)
return Direction.RIGHT;
else if (Position.toID(Position.toRow(fromId)+1, Position.toCol(fromId) ) == toId)
return Direction.UP;
else if (Position.toID(Position.toRow(fromId)-1, Position.toCol(fromId) ) == toId)
return Direction.DOWN;
else
return DirRange.End;
}
} }
/** /**
@ -143,7 +109,6 @@ class Position {
case Direction.DOWN: 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.LEFT: this.id = toID(row, col-1); break;
case Direction.RIGHT:this.id = toID(row, col+1); break; case Direction.RIGHT:this.id = toID(row, col+1); break;
case Direction.NONE: this.id = toID(row, col); break;
} }
} }
@ -232,13 +197,6 @@ class Range {
return Const.EOR; return Const.EOR;
} }
/**
* @return The size of the underline structure
*/
int size () {
return numbers.size();
}
/** @name protected data types */ /** @name protected data types */
/** @{ */ /** @{ */
protected ArrayList<Integer> numbers; /**< handle to range */ protected ArrayList<Integer> numbers; /**< handle to range */
@ -353,7 +311,7 @@ class Edge {
* of the closed room into the problem of finding a non simple graph. * 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 * If the board has non connected wall structure then we would need a non
* coherent graph to represent it. This class provides constructors and * coherent graph to represent it. This class provides constroctors and
* methods to create coherent graphs * methods to create coherent graphs
* *
* An example of the biggest coherent graph we can create from the board bellow, * An example of the biggest coherent graph we can create from the board bellow,
@ -369,7 +327,7 @@ class Edge {
class Graph { class Graph {
/** /**
* Constructs a node of the graph using the value of a vertex(node). * Constructs a node of the graph using the value of a vertex(node).
* @param v The vertex to attach. * @param v The verteg to attach.
*/ */
Graph (int v) { Graph (int v) {
V = v; V = v;

View File

@ -2,12 +2,9 @@
* @file Game.java * @file Game.java
* *
* @author * @author
* Anastasia Foti AEM:8959 * Christos Choutouridis
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr> * <cchoutou@ece.auth.gr>
* AEM:8997
*/ */
/** /**
@ -18,14 +15,12 @@
* all the supplies of the board before Minotaur catches him and before the * all the supplies of the board before Minotaur catches him and before the
* game ends. * game ends.
* *
* In this 3rd assignment we deal with the creation of a new minimax player * In this first assignment we deal with the board's creation and a basic
* who can cheat and manipulate the dice. Documented classes: * player-game logic. The game is build around a number of classes:
* - Tile * - \ref Tile
* - Supply * - Supply
* - Board * - Board
* - Player * - Player
* - HeuristicPlayer
* - MinMaxPlayer
* - Game * - Game
* *
* Which are the requested classes. We also provide some extra functionalities in: * Which are the requested classes. We also provide some extra functionalities in:
@ -157,9 +152,8 @@ public class Game {
// Create a game, a board and 2 players. // Create a game, a board and 2 players.
Game game = new Game(); Game game = new Game();
Board board = new Board(Session.boardSize, Session.supplySize); Board board = new Board(Session.boardSize, Session.supplySize);
Player T = new MinMaxPlayer("Theseus", true, board, 0); Player T = new Player(1, "Theseus", true, board, 0);
Player M = new Player("Minotaur", false, board, Position.toID(Session.boardSize/2, Session.boardSize/2)); Player M = new Player(2, "Minotaur", false, board, Position.toID(Session.boardSize/2, Session.boardSize/2));
Player players [] = {M, T};
// Populate data to the board // Populate data to the board
board.createBoard(T.playerTileId(), M.playerTileId()); board.createBoard(T.playerTileId(), M.playerTileId());
@ -172,36 +166,34 @@ public class Game {
game.waitUser (); game.waitUser ();
// Main game loop // Main game loop
while (true) { while (true) {
int[] m;
System.out.println(); System.out.println();
System.out.println("Round: " + (game.round()+1)); System.out.println("State after round: " + (game.round()+1));
// Players moves // Player moves
for (Player p : players) { m = T.move(T.playerTileId());
p.move(p.playerTileId()); System.out.println(T.getName() + ":\t tileId =" + m[0] + " (" + m[1] + ", " + m[2] + ")");
p.statistics(); m = M.move(M.playerTileId());
} System.out.println(M.getName() + ":\t tileId =" + m[0] + " (" + m[1] + ", " + m[2] + ")");
board.printBoard( board.printBoard(
board.getStringRepresentation(T.playerTileId(), M.playerTileId()) board.getStringRepresentation(T.playerTileId(), M.playerTileId())
); );
// Loop termination cases // Loop termination cases
if (T.getScore() == Session.supplySize) { if (T.getScore() == 4) {
System.out.println("**** " + T.getName() + " Wins!!! Score =" + T.getScore() + " ****"); System.out.println(T.getName() + " Wins!!! Score =" + T.getScore());
break; System.exit(0);
} }
if (M.playerTileId() == T.playerTileId()) { if (M.getScore() == 4 || M.playerTileId() == T.playerTileId()) {
System.out.println("**** " + M.getName() + " Wins!!!"); System.out.println(M.getName() + " Wins!!! Score =" + M.getScore());
break; System.exit(0);
} }
if (!(game.nextRound() < Session.maxRounds)) { if (!(game.nextRound() < Session.maxRounds)) {
System.out.println("**** New day has come... Tie! ****"); System.out.println("New day has come... Tie!!!");
break; System.exit(0);
} }
game.waitUser (); game.waitUser ();
} }
for (Player p : players)
p.final_statistics();
System.exit(0);
} }
catch (Exception e) { catch (Exception e) {
// We don't handle exceptions. Print error and exit with error status. // We don't handle exceptions. Print error and exit with error status.

View File

@ -1,270 +0,0 @@
/**
* @file HeuristicPlayer.java
*
* @author
* Anastasia Foti AEM:8959
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr>
*/
package host.labyrinth;
/**
* @brief
* This class represents the game's player who cheats.
*/
class HeuristicPlayer extends Player {
/** @name Constructors */
/** @{ */
/**
* Create a new player and put him at the row-column coordinates
* @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 row The row coordinate of initial player position
* @param column The column coordinate of initial player's position
*/
public HeuristicPlayer(String name, boolean champion, Board board, int row, int column) throws Exception {
super(name, champion, board, row, column);
}
/**
* Create a new player and put him at the row-column coordinates
* @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 tileId The tileId coordinate of player's initial position
*/
public HeuristicPlayer(String name, boolean champion, Board board, int tileId) throws Exception {
super(name, champion, board, tileId);
}
/** @} */
/** @name Board's main application interface */
/** @{ */
/**
* Utility to get the distance of a possible supply in some direction
* @param currentPos The current position of the player
* @param direction The direction to check
* @return The distance or Const.noSupply
*/
int supplyInDirection(int currentPos, int direction) {
Position pos = new Position(Position.toRow(currentPos), Position.toCol(currentPos));
for (int i=0 ; board.isWalkable(pos.getId(), direction) && i<Const.viewDistance ; ++i) {
pos = new Position(pos.getRow(), pos.getCol(), direction);
if (board.hasSupply(pos.getId()))
return i+1;
}
return Const.noSupply;
}
/**
* Utility to get the distance of a possible opponent in some direction
* @param currentPos The current position of the player
* @param direction The direction to check
* @return The distance or Const.noOpponent
*/
int opponetInDirection(int currentPos, int direction) {
Position pos = new Position(Position.toRow(currentPos), Position.toCol(currentPos));
int [] opp = board.getOpponentMove(playerId);
for (int i=0 ; board.isWalkable(pos.getId(), direction) && i<Const.viewDistance ; ++i) {
pos = new Position(pos.getRow(), pos.getCol(), direction);
if (opp[MOVE_TILE_ID] == pos.getId())
return i+1;
}
return Const.noOpponent;
}
/**
* This is the main move evaluation function.
* @param currentPos The current position of the player (before the move to evaluate)
* @param direction The direction (a.k.a. the move) to evaluate
* @return A signed real number. The higher the output, the higher the evaluation.
*/
double evaluate (int currentPos, int direction) {
int opDist = opponetInDirection (currentPos, direction);
int supDist = supplyInDirection(currentPos, direction);
// saturate
opDist = (opDist == Const.noOpponent) ? 0: opDist;
supDist = (supDist == Const.noSupply) ? 0 : supDist;
return ((supDist != 0)? (1.0/supDist * Const.supplyFactor) : 0)
- ((opDist != 0) ? (1.0/opDist * Const.opponentFactor) : 0);
}
/**
* Selects the best possible move to return
* @param currentPos Player's current position to the board
* @return The move array
*
* @note
* This function always return a new move.
*/
int[] getNextMove(int currentPos) {
Range dirs = new Range(DirRange.Begin, DirRange.End, DirRange.Step);
int N = dirs.size();
double[] eval = new double[N];
int [] eval_dir = new int[N];
for (int i =0, dir = dirs.get() ; dir != Const.EOR ; dir = dirs.get(), ++i) {
if (board.isWalkable(currentPos, dir))
eval[i] = evaluate(currentPos, dir);
else
eval[i] = Double.NEGATIVE_INFINITY;
eval_dir[i] = dir;
}
int dir;
if (isUnevaluated(eval, N)) {
ShuffledRange r = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step);
do
dir = r.get();
while (!board.isWalkable(currentPos, dir));
}
else {
dir = directionOfMax (eval, eval_dir, N);
}
Position new_pos = new Position( Position.toRow(currentPos), Position.toCol(currentPos), dir );
int [] ret = {new_pos.getId(), new_pos.getRow(), new_pos.getCol(), dir};
return ret;
}
/**
* HeuristicPlayer's move.
*
* A player of this kind cheats. He does not throw a dice to get a direction. In contrary he
* calculates his next move very carefully.
* 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.
* @return An array containing player's final position and possible supply of that position.
* The array format is:
* <ul>
* <li> int[0]: The tileId of the final player's position.
* <li> int[1]: The row of the final player's position.
* <li> int[2]: The column of the final player's position.
* <li> int[3]: The dice/direction of the move.
* </ul>
*/
@Override
int[] move(int id) {
// Initialize return array with the current data
int[] ret = getNextMove(id);
y = Position.toRow(ret[MOVE_TILE_ID]);
x = Position.toCol(ret[MOVE_TILE_ID]);
int supplyFlag =0, moveFlag =1;
// In case of a champion player, try also to pick a supply
if (champion && (board.tryPickSupply(ret[MOVE_TILE_ID]) != Const.noSupply)) {
++score; // keep score
++supplyFlag;
}
++dirCounter[ret[MOVE_DICE]]; // update direction counters
board.updateMove(ret, playerId);
// Update supply and opponent distance
int smin =DirRange.End, omin =DirRange.End;
for (int d = DirRange.Begin ; d<DirRange.End ; d += DirRange.Step) {
int s = supplyInDirection (ret[MOVE_TILE_ID], d);
int o = opponetInDirection(ret[MOVE_TILE_ID], d);
if (s >= 0 && s < smin) smin = s;
if (o >= 0 && o < omin) omin = o;
}
// update path
Integer[] p = {
ret[MOVE_TILE_ID], ret[MOVE_DICE], moveFlag, supplyFlag,
dirCounter[Direction.UP], dirCounter[Direction.RIGHT], dirCounter[Direction.DOWN], dirCounter[Direction.LEFT],
(smin != DirRange.End)? smin:Const.noSupply, (omin != DirRange.End)? omin:Const.noOpponent
};
path.add(p);
return ret;
}
/**
* Prints round information for the player
*/
void statistics() {
if (!path.isEmpty()) {
Integer[] last = path.get(path.size()-1);
String who = String.format("%12s", name);
System.out.print(who + ": score[" + score + "]" + ", dice =" + last[1] + ", tileId =" + last[0] + " (" + Position.toRow(last[0]) + ", " + Position.toCol(last[0]) + ")");
if (last[2] == 0)
System.out.println(" *Can not move.");
else if (last[3] != 0)
System.out.println(" *Found a supply.");
else
System.out.println("");
// extra prints for heuristic
if (last[8] != Const.noSupply) System.out.println(" supply =" + last[8]);
else System.out.println(" supply = blind");
if (last[9] != Const.noOpponent) System.out.println(" opponent =" + last[9]);
else System.out.println(" opponent = blind");
}
}
/**
* Prints final statistics for the player
*/
void final_statistics () {
String who = String.format("%12s", name);
System.out.println();
System.out.println(who + ": score[" + score + "]");
System.out.println(" Moves up: " + dirCounter[Direction.UP]);
System.out.println(" Moves right: " + dirCounter[Direction.RIGHT]);
System.out.println(" Moves down: " + dirCounter[Direction.DOWN]);
System.out.println(" Moves left: " + dirCounter[Direction.LEFT]);
}
/** @} */
/**
* A small utility to extract the direction of maximum evaluation result.
*
* We search into the \c eval results and find the index of the maximum evaluation.
* Then we return the direction of \c eval_dir matrix at the same index we found.
*
* @param eval Array with evaluation results for each direction
* @param eval_dir Array with the matching direction to \c eval array
* @param N The size of both arrays
* @return The direction of maximum evaluation. If \c eval is empty returns the first item \c eval[0]
* @note
* This function should not be called if there is at least one evaluation result in \c eval
*/
private int directionOfMax (double[] eval, int[] eval_dir, int N) {
double M = Double.NEGATIVE_INFINITY;
int M_idx = 0;
for (int i =0; i < N ; ++i) {
if (eval[i] > M) {
M = eval[i];
M_idx = i;
}
}
return eval_dir[M_idx];
}
/**
* A small utility to check if there is at least one evaluation result in the \c eval array
* @param eval The array to check
* @param N The size of the array
* @return True if there is none, false otherwise
*/
private boolean isUnevaluated (double[] eval, int N) {
for (int i =0 ; i<N ; ++i)
if (eval[i] != 0 && eval[i] != Double.NEGATIVE_INFINITY)
return false;
return true;
}
/** @name Class data */
/** @{ */
/** @} */
}

View File

@ -1,388 +0,0 @@
/**
* @file MinMaxPlayer.java
*
* @author
* Anastasia Foti AEM:8959
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr>
*/
package host.labyrinth;
/**
* @brief
* This class represents the game's minimax player.
*/
class MinMaxPlayer extends Player {
/** @name Constructors */
/** @{ */
/**
* Create a new player and put him at the row-column coordinates
* @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 row The row coordinate of initial player position
* @param column The column coordinate of initial player's position
*/
public MinMaxPlayer(String name, boolean champion, Board board, int row, int column) throws Exception {
super(name, champion, board, row, column);
}
/**
* Create a new player and put him at the row-column coordinates
* @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 tileId The tileId coordinate of player's initial position
*/
public MinMaxPlayer(String name, boolean champion, Board board, int tileId) throws Exception {
super(name, champion, board, tileId);
}
/** @} */
/** @name Board's main application interface */
/** @{ */
/**
* Utility to get the distance of a possible supply in some direction
* @param currentPos The current position of the player
* @param direction The direction to check
* @param board Reference to the Board object to use
*
* @return The distance or Const.noView
*/
int supplyInDirection(int currentPos, int direction, Board board) {
Position pos = new Position(Position.toRow(currentPos), Position.toCol(currentPos));
for (int i=0 ; i<=Const.viewDistance ; ++i) {
if (board.hasSupply(pos.getId()))
return i;
if (board.isWalkable(pos.getId(), direction))
pos = new Position(pos.getRow(), pos.getCol(), direction);
else
break;
}
return Const.noView;
}
/**
* Utility to get the distance of a possible opponent in some direction
* @param currentPos The current position of the player
* @param direction The direction to check
* @param board Reference to the Board object to use
*
* @return The distance or Const.noView
*/
int opponetInDirection(int currentPos, int direction, Board board) {
Position pos = new Position(Position.toRow(currentPos), Position.toCol(currentPos));
int[] opp = board.getOpponentMove(playerId);
for (int i=0 ; i<=Const.viewDistance ; ++i) {
if (opp[MOVE_TILE_ID] == pos.getId())
return i;
if (board.isWalkable(pos.getId(), direction))
pos = new Position(pos.getRow(), pos.getCol(), direction);
else
break;
}
return Const.noView;
}
/**
* This is the main move evaluation function.
*
* @param currentPos The current position of the player (before the move to evaluate)
* @param direction The direction (a.k.a. the move) to evaluate
* @param board Reference to the Board object to use
* @return A signed real number. The higher the output, the higher the evaluation.
*/
double evaluate (int currentPos, int direction, Board board) {
Position next = new Position (Position.toRow(currentPos), Position.toCol(currentPos), direction);
int preOpDist = opponetInDirection (currentPos, direction, board);
int preSupDist = supplyInDirection(currentPos, direction, board);
int postOpDist = opponetInDirection (next.getId(), direction, board);
int postSupDist = supplyInDirection(next.getId(), direction, board);
return ((preSupDist != Const.noView) ? Const.preMoveFactor *(1.0/(preSupDist+1) * Const.supplyFactor) : 0)
- ((preOpDist != Const.noView) ? Const.preMoveFactor *(1.0/(preOpDist+1) * Const.opponentFactor) : 0)
+ ((postSupDist != Const.noView)? Const.postMoveFactor*(1.0/(preSupDist+1) * Const.supplyFactor) : 0)
- ((postOpDist != Const.noView) ? Const.postMoveFactor*(1.0/(preOpDist+1) * Const.opponentFactor) : 0);
}
/**
* Executes the minimax algorithm and return a reference to selected move
* @param node The root node to start
* @return Reference to the selected move
*/
Node chooseMinMaxMove(Node node) {
node.setNodeEvaluation(maxValue(node));
return node.getPath();
}
/**
* Selects the best possible move to return
* @param currentPos Player's current position to the board
* @return The move array
*
* @note
* This function always return a new move.
*/
int[] getNextMove(int currentPos) {
Node root = new Node (board);
int [] opp = board.getOpponentMove(playerId);
createMySubtree(currentPos, opp[MOVE_TILE_ID], root, root.getNodeDepth()+1);
return chooseMinMaxMove(root).getNodeMove();
}
/**
* MinMaxPlayer's move.
*
* A player of this kind cheats. He does not throw a dice to get a direction. In contrary he
* calculates his next move very carefully.
* 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.
* @return An array containing player's final position and possible supply of that position.
* The array format is:
* <ul>
* <li> int[0]: The tileId of the final player's position.
* <li> int[1]: The row of the final player's position.
* <li> int[2]: The column of the final player's position.
* <li> int[3]: The dice/direction of the move.
* </ul>
*/
@Override
int[] move(int id) {
// Initialize return array with the current data
int[] ret = getNextMove(id);
y = Position.toRow(ret[MOVE_TILE_ID]);
x = Position.toCol(ret[MOVE_TILE_ID]);
int supplyFlag =0, moveFlag =1;
// In case of a champion player, try also to pick a supply
if (champion && (board.tryPickSupply(ret[MOVE_TILE_ID]) != Const.noSupply)) {
++score; // keep score
++supplyFlag;
}
++dirCounter[ret[MOVE_DICE]]; // update direction counters
board.updateMove(ret, playerId);
// Update supply and opponent distance
int smin =Const.noView, omin =Const.noView;
for (int d = DirRange.Begin ; d<DirRange.End ; d += DirRange.Step) {
int s = supplyInDirection (ret[MOVE_TILE_ID], d, board);
int o = opponetInDirection(ret[MOVE_TILE_ID], d, board);
if (s < smin) smin = s;
if (o < omin) omin = o;
}
// update path
Integer[] p = {
ret[MOVE_TILE_ID], ret[MOVE_DICE], moveFlag, supplyFlag,
dirCounter[Direction.UP], dirCounter[Direction.RIGHT], dirCounter[Direction.DOWN], dirCounter[Direction.LEFT],
smin, omin
};
path.add(p);
return ret;
}
/**
* Prints round information for the player
*/
void statistics() {
if (!path.isEmpty()) {
Integer[] last = path.get(path.size()-1);
String who = String.format("%12s", name);
System.out.print(who + ": score[" + score + "]" + ", dice =" + last[1] + ", tileId =" + last[0] + " (" + Position.toRow(last[0]) + ", " + Position.toCol(last[0]) + ")");
if (last[2] == 0)
System.out.println(" *Can not move.");
else if (last[3] != 0)
System.out.println(" *Found a supply.");
else
System.out.println("");
// extra prints for minimax player
if (last[8] != Const.noView) System.out.println(" supply =" + last[8]);
else System.out.println(" supply = blind");
if (last[9] != Const.noView) System.out.println(" opponent =" + last[9]);
else System.out.println(" opponent = blind");
}
}
/**
* Prints final statistics for the player
*/
void final_statistics () {
String who = String.format("%12s", name);
System.out.println();
System.out.println(who + ": score[" + score + "]");
System.out.println(" Moves up: " + dirCounter[Direction.UP]);
System.out.println(" Moves right: " + dirCounter[Direction.RIGHT]);
System.out.println(" Moves down: " + dirCounter[Direction.DOWN]);
System.out.println(" Moves left: " + dirCounter[Direction.LEFT]);
}
/** @} */
/** @name Minimax algorithm related part */
/** @{ */
/**
* Get the previous direction of the player
* @param parent Reference to previous nNode
* @return The previous direction if exist, else return Direction.NONE
*/
private int prevDirection(Node parent) {
if (parent != null && parent.getParent() != null)
return parent.getParent().getNodeMove()[MOVE_DICE];
return Direction.NONE;
}
/**
* A simulated move in a copy of the bard.
*
* @param board The board on witch we simulate the move
* @param currentPos The current position of the player to the @c board
* @param dir The direction of the move
* @param champion Flag to indicate if the player is champion or not
* @return The move array
*/
private int[] dryMove (Board board, int currentPos, int dir, boolean champion) {
int[] ret = new int[MOVE_DATA_SIZE];
Position p = new Position(Position.toRow(currentPos), Position.toCol(currentPos), dir);
ret[MOVE_TILE_ID] = p.getId();
ret[MOVE_ROW] = p.getRow();
ret[MOVE_COLUMN] = p.getCol();
ret[MOVE_DICE] = dir;
board.updateMove(ret, (champion) ? playerId : board.getOpponentId(playerId));
return ret;
}
/**
* One of the 2 recursive functions for creating the minimax tree. This one
* creates children for the MinMax player.
*
* @param parent The parent Node
* @param depth The current depth for the children
* @param currentPos The tile of MinMax player
* @param oppCurrentPos The tile of the opponent
*
* @note
* Even though unnecessary we calculate the evaluation for every node and not only for the leafs.
* This follows the exercise instructions. We could also rely on lazy evaluation of "evaluation()"
* and use AB pruning but the depth of the tree is not worth the try.
*/
private void createMySubtree (int currentPos, int oppCurrentPos, Node parent, int depth) {
ShuffledRange dirs = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step);
int [] nodeMove;
for (int dir = dirs.get() ; dir != Const.EOR ; dir = dirs.get()) {
if ((dir != Direction.opposite(prevDirection(parent)))
&& parent.getNodeBoard().isWalkable(currentPos, dir)) {
Board nodeBoard = new Board (parent.getNodeBoard()); // clone board
double eval = evaluate (currentPos, dir, nodeBoard); // evaluate the move
nodeMove = dryMove (nodeBoard, currentPos, dir, true); // simulate the move
// make child Node
Node child = new Node (parent, depth, nodeMove, nodeBoard, eval);
parent.addChild(child); // add child to tree
createOppSubtree (nodeMove[MOVE_TILE_ID], oppCurrentPos, child, depth+1);
}
}
}
/**
* One of the 2 recursive functions for creating the minimax tree. This one
* creates children for the opponent player.
*
* @param parent The parent Node
* @param depth The current depth for the children
* @param currentPos The tile of MinMax player
* @param oppCurrentPos The tile of the opponent
*
* @note
* Even though unnecessary we calculate the evaluation for every node and not only for the leafs.
* This follows the exercise instructions. We could also rely on lazy evaluation of "evaluation()"
* and use AB pruning but the depth of the tree is not worth the try.
*/
private void createOppSubtree (int currentPos, int oppCurrentPos, Node parent, int depth) {
ShuffledRange dirs = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step);
int [] nodeMove;
for (int dir = dirs.get() ; dir != Const.EOR ; dir = dirs.get()) {
if ((dir != Direction.opposite(prevDirection(parent)))
&& parent.getNodeBoard().isWalkable(oppCurrentPos, dir)) {
Board nodeBoard = new Board(parent.getNodeBoard()); // clone board
nodeMove = dryMove (nodeBoard, oppCurrentPos, dir, false); // simulate move
Position init = new Position( // evaluate from "My" perspective the move
parent.getNodeMove()[MOVE_ROW],
parent.getNodeMove()[MOVE_COLUMN],
Direction.opposite(parent.getNodeMove()[MOVE_DICE])
);
double eval = evaluate(init.getId(), parent.getNodeMove()[MOVE_DICE], nodeBoard);
// make child Node
Node child = new Node (parent, depth, nodeMove, nodeBoard, eval);
parent.addChild(child); // add child to tree
if (depth < Const.minimaxTreeDepth) {
createMySubtree (currentPos, nodeMove[MOVE_TILE_ID], child, depth+1);
}
}
}
}
/**
* The Minimax recursive function for the maximizing part.
*
* @param node The current Node
* @return The selected Node
*/
private double maxValue (Node node) {
if (node.getChildren() == null) {
//node.setPath(node);
return node.getNodeEvaluation();
}
else {
double M = Double.NEGATIVE_INFINITY;
for (Node n : node.getChildren()) {
n.setNodeEvaluation(minValue(n)); // evaluation propagation
if (M < n.getNodeEvaluation()) {
M = n.getNodeEvaluation();
node.setPath(n); // path propagation
}
}
return M;
}
}
/**
* The Minimax recursive function for the minimizing part.
*
* @param node The current Node
* @return The selected Node
*/
private double minValue (Node node) {
if (node.getChildren() == null) {
//node.setPath(node);
return node.getNodeEvaluation();
}
else {
double m = Double.POSITIVE_INFINITY;
for (Node n : node.getChildren()) {
n.setNodeEvaluation(maxValue(n)); // evaluation propagation
if (m > n.getNodeEvaluation()) {
m = n.getNodeEvaluation();
node.setPath(n); // path propagation
}
}
return m;
}
}
/** @} */
}

View File

@ -1,119 +0,0 @@
/**
* @file Node.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;
/**
* Node object for minimax tree.
*/
class Node {
/** @name Constructors */
/** @{ */
/** Null initialize constructor */
Node () { }
/** The main constructor for the Node */
Node (Node parent, int nodeDepth, int [] nodeMove, Board nodeBoard, double nodeEvaluation) {
this.parent = parent;
this.children = null;
this.nodeDepth = nodeDepth;
this.nodeMove = nodeMove;
this.nodeBoard = nodeBoard;
this.nodeEvaluation = nodeEvaluation;
this.path = null;
}
/** A special constructor for creating a root Node */
Node (Board nodeBoard) {
this.parent = null;
this.children = null;
this.nodeDepth = 0;
this.nodeMove = new int [4];
this.nodeBoard = nodeBoard;
this.nodeEvaluation = 0;
this.path = null;
}
/**@} */
/** @name Get/Set interface */
/** @{ */
/** Get parent */
Node getParent() { return parent; }
/** get children */
ArrayList<Node>
getChildren() { return children; }
/** get nodeDepth */
int getNodeDepth() { return nodeDepth; }
/** get nodeMove */
int[] getNodeMove() { return nodeMove; }
/** get nodeBoard */
Board getNodeBoard() { return nodeBoard; }
/** get nodeEvluation */
double getNodeEvaluation (){ return nodeEvaluation; }
/** get path */
Node getPath() { return path; }
/** set parent */
void setParent(Node parent) { this.parent = parent; }
/** set children */
void setChildren(ArrayList<Node> children) {
this.children = children;
}
/** set nodeDepth */
void setNodeDepth(int nodeDepth) {
this.nodeDepth = nodeDepth;
}
/** set nodeMove */
void setNodeMove(int[] nodeMove) {
this.nodeMove = nodeMove;
}
/** set nodeBoard */
void setNodeBoard(Board nodeBoard) {
this.nodeBoard = nodeBoard;
}
/** set nodeEvaluation */
void setNodeEvaluation(double nodeEvaluation) {
this.nodeEvaluation = nodeEvaluation;
}
/** set path */
void setPath (Node path) {
this.path = path;
}
/**@}*/
/** @name Public API */
/** @{ */
/**
* Add a child to the tree
* @param child The child to add
* @return the status of the operation
*/
boolean addChild (Node child) {
if (children == null)
children = new ArrayList<>();
return children.add(child);
}
/**@}*/
/** @name Data members */
/** @{ */
private Node parent; /**< Back reference to parent Node */
private ArrayList<Node> children; /**< Fwd reference to leaf Nodes */
private int nodeDepth; /**< The Node's depth */
private int[] nodeMove; /**< The Node's move data [tile, initTile, points, roll]*/
private Board nodeBoard; /**< Reference to Board's copy of the current node*/
private double nodeEvaluation; /**< The Node's evaluation result */
private Node path; /**< The minimax evaluation path */
/**@}*/
}

View File

@ -2,78 +2,56 @@
* @file Player.java * @file Player.java
* *
* @author * @author
* Anastasia Foti AEM:8959 * Christos Choutouridis
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr> * <cchoutou@ece.auth.gr>
* AEM:8997
*/ */
package host.labyrinth; package host.labyrinth;
import java.util.ArrayList;
/** /**
* @brief * @brief
* This class represents the game's player * This class represents the game's player
*/ */
class Player { class Player {
/** Helper variables to keep track of the move() return values @see move() */
static final int MOVE_DATA_SIZE = 4; /**< The move return data array size */
static final int MOVE_TILE_ID = 0; /**< Index of the tileId information of the move */
static final int MOVE_ROW = 1; /**< The index of row information */
static final int MOVE_COLUMN = 2; /**< The index of column information */
static final int MOVE_DICE = 3; /**< The index of dice information */
/** @name Constructors */ /** @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 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 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 row The row coordinate of initial player position
* @param column The column coordinate of initial player's position * @param column The column coordinate of initial player's position
*/ */
Player(String name, boolean champion, Board board, int row, int column) throws Exception { Player(int id, String name, boolean champion, Board board, int row, int column) {
this.playerId = board.generatePlayerId(); 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; this.champion = champion;
this.dirCounter= new int[DirRange.End]; // yes we spoil some memory. Java is the worst.
this.path = new ArrayList<Integer[]>();
int[] m = {
Position.toID(row, column), row, column, Const.noSupply
};
board.updateMove(m, playerId);
} }
/** /**
* 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 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 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 * @param tileId The tileId coordinate of player's initial position
*/ */
Player(String name, boolean champion, Board board, int tileId) throws Exception { Player(int id, String name, boolean champion, Board board, int tileId) {
this.playerId = board.generatePlayerId(); 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; this.champion = champion;
this.dirCounter= new int[DirRange.End]; // yes we spoil some memory. Java is the worst.
this.path = new ArrayList<Integer[]>();
int[] m = {
tileId, Position.toRow(tileId), Position.toCol(tileId), Const.noSupply
};
board.updateMove(m, playerId);
} }
/** @} */ /** @} */
@ -94,73 +72,31 @@ class Player {
* <li> int[0]: The tileId of the final player's position. * <li> int[0]: The tileId of the final player's position.
* <li> int[1]: The row of the final player's position. * <li> int[1]: The row of the final player's position.
* <li> int[2]: The column of the final player's position. * <li> int[2]: The column of the final player's position.
* <li> int[3]: The dice/direction of the move. * <li> int[1]: The supplyId in case player picked one (Const.noSupply otherwise).
* </ul> * </ul>
*/ */
int[] move(int id) { int[] move(int id) {
// Initialize return array with the current data // Initialize return array with the current data
int[] ret = new int[MOVE_DATA_SIZE]; int[] ret = {id, Position.toRow(id), Position.toCol(id), Const.noSupply};
ret[MOVE_TILE_ID] = id;
ret[MOVE_ROW] = Position.toRow(id);
ret[MOVE_COLUMN] = Position.toCol(id);
ret[MOVE_DICE] = Direction.NONE;
int supplyFlag =0, moveFlag =0;
int diceDirection; int diceDirection = board.dice(); // throw the dice
do if (board.isWalkable(id, diceDirection)) { // The result is walkable
diceDirection = board.dice(); // throw the dice
while (!board.isWalkable(id, diceDirection));
moveFlag =1; // mark the successful move
// Get next tile // Get next tile
Position next = new Position(Position.toRow(id), Position.toCol(id), diceDirection); Position next = new Position(Position.toRow(id), Position.toCol(id), diceDirection);
ret[MOVE_TILE_ID] = next.getId(); // Update move's return data ret[0] = next.getId(); // Update player's and return data
ret[MOVE_ROW] = y = next.getRow(); ret[1] = y = next.getRow();
ret[MOVE_COLUMN] = x = next.getCol(); ret[2] = x = next.getCol();
ret[MOVE_DICE] = diceDirection;
// In case of a champion player, try also to pick a supply // In case of a champion player, try also to pick a supply
if (champion && (board.tryPickSupply(next.getId()) != Const.noSupply)) { if (champion && (ret[3] = board.tryPickSupply(next.getId())) != Const.noSupply) {
supplyFlag =1; // mark the successful supply pickup
++score; // keep score ++score; // keep score
System.out.println(name + ":\t*Found a supply. [score: " + score + "]");
} }
++dirCounter[diceDirection]; // update direction counters }
board.updateMove(ret, playerId); else
// update path System.out.println(name + ":\t*Can not move.");
Integer[] p = {
ret[MOVE_TILE_ID], diceDirection, moveFlag, supplyFlag,
dirCounter[Direction.UP], dirCounter[Direction.RIGHT], dirCounter[Direction.DOWN], dirCounter[Direction.LEFT],
Const.noSupply, Const.noOpponent
};
path.add(p);
return ret; return ret;
} }
/**
* Prints round information for the player
*/
void statistics() {
if (!path.isEmpty()) {
Integer[] last = path.get(path.size()-1);
String who = String.format("%12s", name);
System.out.print(who + ": score[" + score + "]" + ", dice =" + last[1] + ", tileId =" + last[0] + " (" + Position.toRow(last[0]) + ", " + Position.toCol(last[0]) + ")");
if (last[2] == 0)
System.out.println(" *Can not move.");
else if (last[3] != 0)
System.out.println(" *Found a supply.");
else
System.out.println("");
}
}
/**
* Prints final statistics for the player
* @note
* We add this final_statistics() wrapper in order to provide polymorphism to
* Player class hierarchy and to be sure that we are not braking the
* Liskov substitution principle
* @see https://en.wikipedia.org/wiki/Liskov_substitution_principle
*/
void final_statistics () { }
/** Utility to access player's tileID */ /** 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) */ /** Utility to access player's row position (row coordinate) */
@ -183,11 +119,6 @@ class Player {
int getX() { return x; } int getX() { return x; }
int getY() { return y; } int getY() { return y; }
boolean getChampion(){ return champion; } boolean getChampion(){ return champion; }
int[] getDirCounter(){ return dirCounter; }
ArrayList<Integer[]> getPath() {
return path;
}
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; }
@ -204,41 +135,17 @@ class Player {
void setChampion (boolean champion) { void setChampion (boolean champion) {
this.champion = champion; this.champion = champion;
} }
void setDirCounter(int[] dirCounter) {
this.dirCounter = dirCounter;
}
void setPath(ArrayList<Integer[]> path) {
this.path = path;
}
/** @} */ /** @} */
/** @name Class data */ /** @name Class data */
/** @{ */ /** @{ */
protected int playerId; /**< The unique identifier of the player */ private int playerId; /**< The unique identifier of the player */
protected String name; /**< The name of the player */ private String name; /**< The name of the player */
protected Board board; /**< Reference to the session's boards */ private Board board; /**< Reference to the session's boards */
protected int score; /**< The current score of the player */ private int score; /**< The current score of the player */
protected int x; /**< The column coordinate of the player on the board */ private int x; /**< The column coordinate of the player on the board */
protected int y; /**< The row coordinate of the player on the board */ private int y; /**< The row coordinate of the player on the board */
protected boolean champion; /**< Champion indicate a player who plays against the Minotaur */ private boolean champion; /**< Champion indicate a player who plays against the Minotaur */
protected int dirCounter[];
protected ArrayList<Integer[]> path;
/**<
* our history. The integer[] format is:
* <ul>
* <li> Integer[0]: tileId - The tile id we choose for the move
* <li> Integer[1]: dice - The dice (a.k.a direction) of move
* <li> Integer[2]: moveStatus - True if it was successful (we can move in that direction)
* <li> Integer[3]: tookSupply - True if we took supply
* <li> Integer[4]: upCounter - Accumulator to count all the up moves
* <li> Integer[5]: righrCounter - Accumulator to count all the right moves
* <li> Integer[6]: downCounter - Accumulator to count all the down moves
* <li> Integer[7]: leftCounter - Accumulator to count all the left moves
* <li> Integer[8]: SupDistance - The distance of the nearest supply (only for heuristic players)
* <li> Integer[9]: OppDistance - The distance of the nearest opponent (only for heuristic players)
* </ul>
* }
*/
/** @} */ /** @} */
} }

View File

@ -2,12 +2,9 @@
* @file Supply.java * @file Supply.java
* *
* @author * @author
* Anastasia Foti AEM:8959 * Christos Choutouridis
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr> * <cchoutou@ece.auth.gr>
* AEM:8997
*/ */
package host.labyrinth; package host.labyrinth;

View File

@ -2,12 +2,9 @@
* @file Tile.java * @file Tile.java
* *
* @author * @author
* Anastasia Foti AEM:8959 * Christos Choutouridis
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr> * <cchoutou@ece.auth.gr>
* AEM:8997
*/ */
package host.labyrinth; package host.labyrinth;