TP-systemes-concurrents/TP3/IHMPhilo.java
2023-06-21 20:19:26 +02:00

467 lines
18 KiB
Java

// Time-stamp: <10 jui 2013 16:51 queinnec@enseeiht.fr>
// V0.1 9/13 (PM, commentaires)
// V0.2 10/14 (PM, aide)
// V0.3 10/15 (PM, correction placement fourchettes)
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Hashtable;
import Synchro.Simulateur;
/** La partie graphique des philosophes.
* Les seules méthodes visibles sont IHMPhilo.changerEtat et IHMPhilo.poser.
*
* Note : le code est crade et mériterait une réécriture complète.
*/
public class IHMPhilo {
/* Attention: les variables d'état partagées de l'affichage
* (lesFourchettes[].etat et lesPhilos[].etat) ne sont pas protégées.
* Il ne doit pas y avoir de problème avec des appels concurrents entre
* poser et/ou changer_etat (a priori, ils concernent des objets
* différents). Il ne devrait pas y avoir de surprise lors d'une
* exécution concurrente avec repaint() car cette fonction est
* thread-safe.
*/
private final static boolean verbose = false;
private static int nbPhilosophes;
private Simulateur simu;
class Point {
public int x = 0;
public int y = 0;
/** Changement de position, coordonnées cartésiennes. */
public void setPosition (int cardx, int cardy) {
x = cardx;
y = cardy;
}
/** Changement de position, coordonnées angulaires. */
public void setPosition (double rayon, double unAngle)
{
x = centreTable.x + (int) (rayon * Math.cos (unAngle));
y = centreTable.y + (int) (rayon * Math.sin (unAngle));
}
}
/* Le rayon d'une assiette et de la table. */
private double rayonAss, rayonTable;
/* Le centre de la table. */
private Point centreTable = new Point();
/* Les 3 placements d'une fourchette, selon son état. */
class Fourchette {
EtatFourchette etat;
Point table[];
Point assGau[];
Point assDro[];
public Fourchette () {
etat = EtatFourchette.Table;
table = new Point[2];
assGau = new Point[2];
assDro = new Point[2];
for(int i = 0; i < 2; i++) {
table[i] = new Point();
assGau[i] = new Point();
assDro[i] = new Point();
}
}
}
/* Le placement d'un philosophe, et son état à l'écran. */
class Philosophe {
int no;
Point position;
EtatPhilosophe etat;
public Philosophe (int i) {
no = i;
position = new Point();
etat = EtatPhilosophe.Pense;
}
}
/* Le centre d'une assiette. */
class Assiette {
Point position;
public Assiette () {
position = new Point();
}
}
private static Philosophe[] lesPhilos; // tableau [nbPhilosophes]
private static Fourchette[] lesFourchettes; // tableau [nbPhilosophes]
private Assiette[] lesAssiettes; // tableau [nbPhilosophes]
/****************************************************************/
static double distance (int x1, int y1, int x2, int y2)
{
return Math.sqrt((double) (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
}
/****************************************************************/
class FenetreDessin extends JPanel {
/* La police utilisée pour les numéros des philosophes. */
private final Font font = new Font("Helvetica", Font.BOLD, 14); // def = ("Times",Font.PLAIN,12);
private final FontMetrics fontMetrics = getFontMetrics(font);
FenetreDessin ()
{
super();
setFont (font);
}
/* appelé lors de la création et le redimensionnement de la fenêtre */
void computePlacement ()
{
int taille_fenetre_x, taille_fenetre_y, taille_fenetre;
double unAngle, unAngleFour, deplAngle;
double distTableAss, distTablePhilo;
double centreAss, centrePhilo;
double posFour[] = new double[2];
taille_fenetre_x = getWidth();
taille_fenetre_y = getHeight();
taille_fenetre = (taille_fenetre_x < taille_fenetre_y ?
taille_fenetre_x : taille_fenetre_y);
centreTable.setPosition(taille_fenetre_x / 2, taille_fenetre_y / 2);
rayonTable = taille_fenetre * 6.0 / (8.0 * 2.0);
distTableAss = taille_fenetre / 15.0;
distTablePhilo = taille_fenetre / 20.0;
centreAss = rayonTable - distTableAss;
centrePhilo = rayonTable + distTablePhilo;
unAngle = 0.0;
unAngleFour = 0.0;
deplAngle = (2.0 * Math.PI) / nbPhilosophes;
rayonAss = (rayonTable - centreAss) / 2.0;
posFour[0] = centreAss + rayonAss;
posFour[1] = centreAss - rayonAss;
for (int i = 0; i < nbPhilosophes; i++) {
lesPhilos[i].position.setPosition (centrePhilo, unAngle);
lesAssiettes[i].position.setPosition (centreAss, unAngle);
unAngleFour = unAngle - deplAngle / 2.0;
lesFourchettes[i].table[0].setPosition (posFour[0], unAngleFour);
lesFourchettes[i].table[1].setPosition (posFour[1], unAngleFour);
unAngleFour = unAngle - deplAngle + deplAngle / 12.0;
lesFourchettes[i].assDro[0].setPosition (posFour[0], unAngleFour);
lesFourchettes[i].assDro[1].setPosition (posFour[1], unAngleFour);
unAngleFour = unAngle - deplAngle / 12.0;
lesFourchettes[i].assGau[0].setPosition (posFour[0], unAngleFour);
lesFourchettes[i].assGau[1].setPosition (posFour[1], unAngleFour);
unAngle = unAngle + deplAngle;
}
repaint();
}
void tracerCercle (Graphics g, boolean fill, Point unPt, int rayon)
{
if (fill)
g.fillOval(unPt.x - rayon, unPt.y - rayon, rayon*2, rayon*2);
else
g.drawOval(unPt.x - rayon, unPt.y - rayon, rayon*2, rayon*2);
}
void tracerFourchette (Graphics g, Point pos[])
{
g.drawLine (pos[0].x, pos[0].y, pos[1].x, pos[1].y);
}
void tracerNumero (Graphics g, int num, Point pos)
{
String str = Integer.toString(num);
g.drawString (str, pos.x - fontMetrics.stringWidth(str) / 2,
(int) (pos.y + fontMetrics.getLineMetrics(str,g).getHeight() / 2));
}
public void paintComponent (Graphics g)
{
super.paintComponent(g);
g.setColor (Color.black);
tracerCercle (g, false, centreTable,(int) rayonTable);
for (int i = 0; i < nbPhilosophes; i++) {
tracerCercle (g, false, lesAssiettes[i].position,(int) rayonAss);
tracerNumero (g, i, lesAssiettes[i].position);
EtatFourchette etat = lesFourchettes[i].etat;
if (etat == EtatFourchette.Table)
tracerFourchette (g, lesFourchettes[i].table);
else if (etat == EtatFourchette.AssietteDroite)
tracerFourchette (g, lesFourchettes[i].assDro);
else if (etat == EtatFourchette.AssietteGauche)
tracerFourchette (g, lesFourchettes[i].assGau);
if (isInvalidState(i))
g.setColor(Color.red);
if (lesPhilos[i].etat == EtatPhilosophe.Mange)
tracerCercle (g, true, lesPhilos[i].position, 7);
else if (lesPhilos[i].etat != EtatPhilosophe.Pense)
tracerCercle (g, false, lesPhilos[i].position, 7);
g.setColor(Color.black);
}
}
} // class FenetreDessin
/* Le panel où l'on dessine les philosophes. */
private static FenetreDessin jp_fen;
/****************************************************************/
private static void afficher ()
{
jp_fen.repaint();
}
/****************************************************************/
/* Renvoie vrai si le philosophe i mange et l'un de ses voisins mange aussi. */
private boolean isInvalidState(int i)
{
return ((lesPhilos[i].etat == EtatPhilosophe.Mange)
&& ((lesPhilos[Main.PhiloGauche(i)].etat == EtatPhilosophe.Mange) || lesPhilos[Main.PhiloDroite(i)].etat == EtatPhilosophe.Mange));
}
/** Indique à l'affichage que la fourchette noFourch passe dans l'état spécifié (assiette de droite, assiette de gauche, table). */
public static void poser (int noFourch, EtatFourchette pos)
{
lesFourchettes[noFourch].etat = pos;
if (verbose)
System.out.println("Fourchette "+noFourch+" posée "+pos);
afficher();
}
/** Indique à l'affichage que le philosophe noPhilo passe dans l'état spécifié (penseur, demandeur, mangeur). */
public static void changerEtat (int noPhilo, EtatPhilosophe etat)
{
lesPhilos[noPhilo].etat = etat;
if (verbose)
System.out.println("Philosophe "+noPhilo+" change d'état : "+etat);
afficher();
}
/****************************************************************/
/* Initialise la fenêtre d'affichage
*/
public IHMPhilo (String nomstrategie, int _nbPhilosophes, Simulateur _simu)
{
simu = _simu;
nbPhilosophes = _nbPhilosophes;
lesPhilos = new Philosophe[nbPhilosophes];
lesFourchettes = new Fourchette[nbPhilosophes];
lesAssiettes = new Assiette[nbPhilosophes];
for (int i = 0; i < nbPhilosophes; i++) {
lesPhilos[i] = new Philosophe(i);
lesFourchettes[i] = new Fourchette();
lesAssiettes[i] = new Assiette();
}
// Fenetre
final JFrame fenetre = new JFrame("Philosophes");
// Listener Fermeture de la fenetre
fenetre.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
// Fermeture de la fenetre graphique
System.exit(0);
}
});
// Listener Redimensionnement de la fenetre
fenetre.addComponentListener(new ComponentAdapter()
{
public void componentResized(ComponentEvent e)
{
jp_fen.computePlacement ();
}
});
// Listener touche 'q'
fenetre.addKeyListener (new KeyAdapter()
{
public void keyTyped (KeyEvent e)
{
if (e.getKeyChar() == 'q') {
fenetre.dispatchEvent (new WindowEvent(fenetre,WindowEvent.WINDOW_CLOSING));
//System.exit (0);
}
}
});
/* ===== boutons ===== */
JPanel jp_boutons = new JPanel(new GridLayout(0,4,5,10));
// Quitter
JButton jb_quitter = new JButton("Quitter");
jb_quitter.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt)
{
System.exit(0);
}
});
jp_boutons.add(jb_quitter);
// Parametres
final JDialog dialogParam = new IHMParametres (fenetre);
JButton jb_parametres = new JButton("Paramètres");
jb_parametres.setToolTipText("Paramétrage fin du comportement");
jb_parametres.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt)
{
dialogParam.setVisible (true);
}
});
jp_boutons.add(jb_parametres);
// Pause
final JButton jb_pause = new JButton("Pause");
jb_pause.setToolTipText("Suspension/reprise du temps");
/* Event clicked on button "pause". */
jb_pause.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt)
{
simu.swapRunning();
if (simu.getRunning())
jb_pause.setText("Pause");
else
jb_pause.setText("Temps suspendu");
}
});
jp_boutons.add(jb_pause);
// Aide
JEditorPane jep_aide =
new JEditorPane("text/html",
"<html><head></head><body><br>"+
"Les Philosophes et les Spaghettis"+
"<br>------------------------<br><br>"+
"Problème : un philosophe ne peut manger des spaghettis qu'avec deux fourchettes. Il doit donc utiliser les deux fourchettes adjacentes à son assiette pour pouvoir manger. Ce faisant, il empêche ses deux voisins de manger.<br><br>"+
"Symboles :"+
"<ul>"+
"<li>un disque plein indique un philosophe mangeant ;"+
"<ul>"+
"<li>un disque noir indique une situation normale ;"+
"<li>un disque rouge signale une incohérence <i>possible</i> ;"+
"</ul>"+
"<li>un cercle indique un philosophe demandeur ;"+
"<li>aucun symbole indique un philosophe penseur ;"+
"</ul>"+
"Actions :"+
"<ul>"+
"<li>en cliquant dans l'assiette d'un philosophe, vous pouvez forcer une transition: penser -&gt; demander, ou manger -&gt; penser (la transition demander -&gt; manger reste du ressort de la synchronisation);"+
"<li>pause permet de suspendre le temps de la simulation. Les actions forcées sont par contre toujours possibles;"+
"<li>vous pouvez régler la vitesse de la simulation avec l'échelle du bas."+
"</ul>"+
"</body></html>");
jep_aide.setEditable (false);
JOptionPane pane = new JOptionPane(new JScrollPane (jep_aide));
final JDialog dialogAide = pane.createDialog(fenetre,"Aide");
dialogAide.setModal (false);
dialogAide.setSize(500,500);
JButton jb_aide = new JButton(" Aide ");
jb_aide.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt)
{
dialogAide.setVisible(true);
}});
jp_boutons.add(jb_aide);
/* ===== Le nom de la stratégie ===== */
JPanel jp_strategie = new JPanel();
jp_strategie.add(new JLabel(nomstrategie));
jp_strategie.setBorder(BorderFactory.createEtchedBorder());
/* ===== Fenêtre de dessin ===== */
jp_fen = new FenetreDessin();
/* Event button release on drawingarea. */
jp_fen.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
int mx, my;
mx = e.getX();
my = e.getY();
/* Les assiettes */
for (int i = 0; i < nbPhilosophes; i++) {
if (distance (mx, my, lesAssiettes[i].position.x,
lesAssiettes[i].position.y) <= rayonAss)
simu.wakeup (i);
}
}
});
/* ===== Le réglage de vitesse du temps ===== */
JPanel jp_vitesse = new JPanel();
jp_vitesse.setToolTipText("Vitesse d'écoulement du temps simulé");
jp_vitesse.setBorder(BorderFactory.createEtchedBorder());
jp_vitesse.add(new JLabel(" Vitesse du temps : "));
JSlider js_vitesseTemps = new JSlider(JSlider.HORIZONTAL,1,100,1);
// Event "value_changed" de l'ajustement de l'échelle de vitesse du temps.
js_vitesseTemps.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
simu.setTimespeed(source.getValue());
}
});
js_vitesseTemps.setMajorTickSpacing(10);
js_vitesseTemps.setPaintTicks(true);
// crée table de labels
Hashtable<Integer,JLabel> labelTable = new Hashtable<Integer,JLabel>();
labelTable.put( new Integer( 1 ), new JLabel("1") );
labelTable.put( new Integer( 100 ), new JLabel("100") );
js_vitesseTemps.setLabelTable( labelTable );
js_vitesseTemps.setPaintLabels(true);
jp_vitesse.add(js_vitesseTemps);
/* ===== Assemblage ===== */
Container contentPane = fenetre.getContentPane();
contentPane.add(jp_boutons);
contentPane.add(jp_strategie);
contentPane.add(jp_fen);
contentPane.add(jp_vitesse);
/* ==== Contraintes ==== */
GridBagLayout gridbag = new GridBagLayout();
contentPane.setLayout(gridbag);
// Contraintes pour la fenêtre de dessin
GridBagConstraints c_fen = new GridBagConstraints();
c_fen.fill = GridBagConstraints.BOTH;
c_fen.gridwidth = GridBagConstraints.REMAINDER;
c_fen.weightx = 1.0;
c_fen.weighty = 1.0;
gridbag.setConstraints(jp_fen, c_fen);
// Contraintes pour les autres éléments
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(jp_boutons, c);
gridbag.setConstraints(jp_strategie, c);
gridbag.setConstraints(jp_vitesse, c);
//jp_fen.setDebugGraphicsOptions(DebugGraphics.LOG_OPTION);
fenetre.pack();
int taille_fen_x = 600;
int taille_fen_y = 500;
fenetre.setSize(taille_fen_x,taille_fen_y);
jp_fen.computePlacement ();
fenetre.setVisible(true);
}
}