feat: add of an MVC base architecture for Tetris

This commit is contained in:
Morph01
2025-05-15 16:17:50 +02:00
parent 275d761f24
commit eb7a013543
11 changed files with 553 additions and 40 deletions

View File

@@ -3,7 +3,9 @@
*/ */
package org; package org;
import org.Controllers.IO;
import org.Models.Grille; import org.Models.Grille;
import org.Models.Jeu;
import org.Views.VueGrille; import org.Views.VueGrille;
public class App { public class App {
@@ -13,7 +15,16 @@ public class App {
public static void main(String[] args) { public static void main(String[] args) {
System.out.println(new App().getGreeting()); System.out.println(new App().getGreeting());
Grille grille = new Grille(10, 10);
VueGrille vueGrille = new VueGrille(grille); // Models
Grille grille = new Grille(20, 10);
Jeu jeu = new Jeu(grille);
// Views
VueGrille vueGrille = new VueGrille(grille, jeu);
// Controllers
IO io = new IO(jeu);
vueGrille.addKeyListener(io);
} }
} }

View File

@@ -0,0 +1,47 @@
package org.Controllers;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import org.Models.Direction;
import org.Models.Jeu;
public class IO implements KeyListener {
private Jeu jeu;
public IO(Jeu jeu) {
this.jeu = jeu;
}
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_DOWN:
jeu.getGrille().deplacerPiece(Direction.BAS);
break;
case KeyEvent.VK_LEFT:
jeu.getGrille().deplacerPiece(Direction.GAUCHE);
break;
case KeyEvent.VK_RIGHT:
jeu.getGrille().deplacerPiece(Direction.DROITE);
break;
case KeyEvent.VK_SPACE:
jeu.getGrille().deplacerPiece(Direction.TOUTENBAS);
default:
break;
}
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
}

View File

@@ -0,0 +1,8 @@
package org.Models;
public enum Direction {
BAS,
GAUCHE,
DROITE,
TOUTENBAS,
}

View File

@@ -1,16 +1,144 @@
package org.Models; package org.Models;
public class Grille { import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
@SuppressWarnings("deprecation")
public class Grille extends Observable { // TODO: ?? implements Runnable {
private boolean[][] grille; private boolean[][] grille;
public int nbLignes; public int nbLignes;
public int nbColonnes; public int nbColonnes;
private PieceCourante pieceCourante;
private int pieceCouranteX;
private int pieceCouranteY;
public Grille(int nbLignes, int nbColonnes) { public Grille(int nbLignes, int nbColonnes) {
this.nbLignes = nbLignes; this.nbLignes = nbLignes;
this.nbColonnes = nbColonnes; this.nbColonnes = nbColonnes;
this.grille = new boolean[nbLignes][nbColonnes]; this.grille = new boolean[nbLignes][nbColonnes];
for (int i = 0; i < nbLignes; i++) {
for (int j = 0; j < nbColonnes; j++) {
this.grille[i][j] = false;
}
}
} }
public void setCase(int i, int j, boolean value) {
if (i >= 0 && i < nbLignes && j >= 0 && j < nbColonnes) {
this.grille[i][j] = value;
setChanged();
notifyObservers();
}
}
public boolean getCase(int i, int j) {
if (i >= 0 && i < nbLignes && j >= 0 && j < nbColonnes) {
return this.grille[i][j];
}
return false;
}
public PieceCourante getPieceCourante() {
return pieceCourante;
}
public void setPieceCourante(PieceCourante pieceCourante) {
this.pieceCourante = pieceCourante;
this.pieceCouranteX = 3;
this.pieceCouranteY = 0;
setChanged();
notifyObservers();
}
public int getPieceCouranteX() {
return pieceCouranteX;
}
public int getPieceCouranteY() {
return pieceCouranteY;
}
public void deplacerPiece(Direction direction) {
switch (direction) {
case BAS:
if (peuxBouger(direction)) {
deplacerPieceBas();
}
break;
case GAUCHE:
if (peuxBouger(direction)) {
deplacerPieceGauche();
}
break;
case DROITE:
if (peuxBouger(direction)) {
deplacerPieceDroite();
}
break;
case TOUTENBAS:
if (peuxBouger(direction)) {
deplacerPieceToutEnBas();
}
default:
break;
}
}
private void deplacerPieceBas() {
pieceCouranteY++;
setChanged();
notifyObservers();
}
private void deplacerPieceGauche() {
pieceCouranteX--;
setChanged();
notifyObservers();
}
private void deplacerPieceDroite() {
pieceCouranteX++;
setChanged();
notifyObservers();
}
private void deplacerPieceToutEnBas() {
while (peuxBouger(Direction.BAS)) {
deplacerPieceBas();
}
}
// TODO : ENLEVER ?
// public int getMaxYPieceCouranteColoree(List<Point> motifPieceColoree) {
// int maxY = 0;
// for (Point caseColoree : motifPieceColoree) {
// if (caseColoree.y > maxY) {
// maxY = caseColoree.y;
// }
// }
// return maxY;
// }
// public int getMaxXPieceCouranteColoree(List<Point> motifPieceColoree) {
// int maxX = 0;
// for (Point caseColoree : motifPieceColoree) {
// if (caseColoree.x > maxX) {
// maxX = caseColoree.x;
// }
// }
// return maxX;
// }
public int getNbLignes() { public int getNbLignes() {
return nbLignes; return nbLignes;
} }
@@ -18,4 +146,74 @@ public class Grille {
public int getNbColonnes() { public int getNbColonnes() {
return nbColonnes; return nbColonnes;
} }
}
public List<Point> motifPieceCouranteColoriee() {
List<Point> casesColores = new ArrayList<>();
boolean[][] motif = pieceCourante.getMotif();
for (int i = 0; i < motif.length; i++) {
for (int j = 0; j < motif[i].length; j++) {
if (motif[i][j]) {
casesColores.add(new Point(pieceCouranteX + j, pieceCouranteY + i));
}
}
}
return casesColores;
}
public boolean peuxBouger(Direction direction) {
int deltaX = 0;
int deltaY = 0;
switch (direction) {
case GAUCHE:
deltaX = -1;
break;
case DROITE:
deltaX = 1;
break;
case BAS:
deltaY = 1;
break;
case TOUTENBAS:
break;
default:
break;
}
List<Point> motifPieceColoree = motifPieceCouranteColoriee();
for (Point caseColoree : motifPieceColoree) {
int newX = caseColoree.x + deltaX;
int newY = caseColoree.y + deltaY;
if (newX < 0 || newX >= nbColonnes || newY < 0 || newY >= nbLignes) {
return false;
}
if (grille[newY][newX]) {
return false;
}
}
return true;
}
public boolean[][] getGrille() {
return grille;
}
public void fixerPiece() {
for (Point caseColoree : motifPieceCouranteColoriee()) {
setCase(caseColoree.y, caseColoree.x, true);
}
}
public boolean doitFixerPiece() {
if (!peuxBouger(Direction.BAS)) {
return true;
}
return false;
}
}

View File

@@ -1,5 +1,92 @@
package org.Models; package org.Models;
public class Jeu { import java.awt.Point;
import java.util.Observable;
import org.Models.Pieces.PieceCarre;
import org.Models.Pieces.PieceL;
@SuppressWarnings("deprecation")
public class Jeu extends Observable implements Runnable {
private Grille grille;
private Ordonnanceur ordonnanceur;
private PieceCourante pieceSuivante;
private int pieceSuivanteX;
private int pieceSuivanteY;
// TODO : remove test variable
public int test = 0;
public boolean jeuEnCours = true;
public Jeu(Grille grille) {
this.grille = grille;
this.grille.setPieceCourante(getNouvellePiece());
this.pieceSuivante = getNouvellePiece();
this.ordonnanceur = new Ordonnanceur(this, 1000);
this.ordonnanceur.start();
}
private PieceCourante getNouvellePiece() {
this.pieceSuivante = new PieceL();
this.pieceSuivanteX = 3;
this.pieceSuivanteY = 0;
return pieceSuivante;
}
public PieceCourante getPieceSuivante() {
return pieceSuivante;
}
public int getPieceSuivanteX() {
return pieceSuivanteX;
}
public int getPieceSuivanteY() {
return pieceSuivanteY;
}
public Grille getGrille() {
return grille;
}
public boolean estFinPartie() {
for (Point caseColoree : this.grille.motifPieceCouranteColoriee()) {
if (this.grille.getCase(caseColoree.y, caseColoree.x)) {
return true;
}
}
return false;
}
public void finPartie() {
this.jeuEnCours = false;
ordonnanceur.interrupt();
setChanged();
notifyObservers();
}
@Override
public void run() {
// TODO: game logic here
if (estFinPartie()) {
finPartie();
return;
}
if (!this.grille.doitFixerPiece()) {
this.grille.deplacerPiece(Direction.BAS);
} else {
this.grille.fixerPiece();
this.grille.setPieceCourante(getNouvellePiece());
}
setChanged();
notifyObservers();
}
} }

View File

@@ -0,0 +1,32 @@
package org.Models;
public class Ordonnanceur extends Thread {
Runnable runnable;
long pause;
private boolean estActif = true;
public Ordonnanceur(Runnable runnable, long pause) {
this.runnable = runnable;
this.pause = pause;
}
public void stopOrdonnanceur() {
estActif = false;
interrupt();
}
@Override
public void run() {
while (estActif) {
try {
Thread.sleep(pause);
runnable.run();
} catch (InterruptedException e) {
if (!estActif) {
break;
}
}
}
}
}

View File

@@ -1,17 +1,6 @@
package org.Models; package org.Models;
public class PieceCourante { public interface PieceCourante {
protected boolean[][] motif = new boolean[4][4]; boolean[][] motif = new boolean[4][4];
abstract public boolean[][] getMotif();
PieceCourante() {
initialiserPieceCourante();
}
public void initialiserPieceCourante() {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
this.motif[i][j] = false;
}
}
}
} }

View File

@@ -1,13 +0,0 @@
package org.Models;
public class PieceL extends PieceCourante {
PieceL(){
super();
this.motif[1][0] = true;
this.motif[2][0] = true;
this.motif[3][0] = true;
this.motif[3][1] = true;
this.motif[3][2] = true;
}
}

View File

@@ -0,0 +1,17 @@
package org.Models.Pieces;
import org.Models.PieceCourante;
public class PieceCarre implements PieceCourante {
public PieceCarre() {
motif[1][1] = true;
motif[1][2] = true;
motif[2][1] = true;
motif[2][2] = true;
}
public boolean[][] getMotif() {
return motif;
}
}

View File

@@ -0,0 +1,19 @@
package org.Models.Pieces;
import org.Models.PieceCourante;
public class PieceL implements PieceCourante {
public PieceL(){
motif[1][0] = true;
motif[2][0] = true;
motif[3][0] = true;
motif[3][1] = true;
motif[3][2] = true;
}
@Override
public boolean[][] getMotif() {
return motif;
}
}

View File

@@ -1,34 +1,58 @@
package org.Views; package org.Views;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.border.Border;
import org.Models.Grille; import org.Models.Grille;
import org.Models.Jeu;
import org.Models.Ordonnanceur;
import org.Models.PieceCourante;
import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout; import java.awt.GridLayout;
import java.awt.Toolkit;
import java.util.Observable;
import java.util.Observer;
public class VueGrille extends JFrame { @SuppressWarnings("deprecation")
public class VueGrille extends JFrame implements Observer, Runnable {
private JPanel grillePanel; private JPanel grillePanel;
private int tailleJPanel = 1000; private Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
private Grille grille; private double tailleJPanelX = screenSize.getHeight() / 4;
private double tailleJPanelY = screenSize.getHeight() / 2;
public VueGrille(Grille grille) { private Grille grille;
private Jeu jeu;
private Ordonnanceur ordonnanceur;
private boolean afficherFenetreFinPartie = false;
public VueGrille(Grille grille, Jeu jeu) {
this.grille = grille; this.grille = grille;
this.jeu = jeu;
grillePanel = new JPanel(new GridLayout(grille.getNbLignes(), grille.getNbColonnes())); grillePanel = new JPanel(new GridLayout(grille.getNbLignes(), grille.getNbColonnes()));
setSize(tailleJPanel, tailleJPanel); setSize((int) tailleJPanelX, (int) tailleJPanelY);
setContentPane(grillePanel); setContentPane(grillePanel);
initialiserVueGrille(); initialiserVueGrille();
grille.addObserver(this);
jeu.addObserver(this);
this.ordonnanceur = new Ordonnanceur(this, 1000);
ordonnanceur.start();
} }
private void initialiserVueGrille() { private void initialiserVueGrille() {
Border border = BorderFactory.createLineBorder(Color.BLACK);
for (int i = 0; i < grille.getNbLignes(); i++) { for (int i = 0; i < grille.getNbLignes(); i++) {
for (int j = 0; j < grille.getNbColonnes(); j++) { for (int j = 0; j < grille.getNbColonnes(); j++) {
JPanel caseG = new JPanel(); JPanel caseG = new JPanel();
caseG.setBorder(border);
caseG.setBackground(Color.WHITE); caseG.setBackground(Color.WHITE);
grillePanel.add(caseG); grillePanel.add(caseG);
} }
@@ -38,4 +62,98 @@ public class VueGrille extends JFrame {
setDefaultCloseOperation(EXIT_ON_CLOSE); setDefaultCloseOperation(EXIT_ON_CLOSE);
setTitle("TETRIS"); setTitle("TETRIS");
} }
public synchronized void updateGrille() {
for (int i = 0; i < grille.getNbLignes(); i++) {
for (int j = 0; j < grille.getNbColonnes(); j++) {
JPanel caseG = (JPanel) grillePanel.getComponent(i * grille.getNbColonnes() + j);
if (grille.getGrille()[i][j]) {
caseG.setBackground(Color.BLUE);
} else {
caseG.setBackground(Color.WHITE);
}
}
}
// dessiner la pièce courante
if (jeu != null) {
PieceCourante piece = this.grille.getPieceCourante();
int posX = this.grille.getPieceCouranteX();
int posY = this.grille.getPieceCouranteY();
boolean[][] motif = piece.getMotif();
for (int i = 0; i < motif.length; i++) {
for (int j = 0; j < motif[i].length; j++) {
if (motif[i][j]) {
int grilleX = posX + j;
int grilleY = posY + i;
if (grilleY >= 0 && grilleY < grille.getNbLignes() &&
grilleX >= 0 && grilleX < grille.getNbColonnes()) {
JPanel caseG = (JPanel) grillePanel
.getComponent(grilleY * grille.getNbColonnes() + grilleX);
caseG.setBackground(Color.RED);
}
}
}
}
}
repaint();
}
@SuppressWarnings("unused")
private void afficherFinPartie() {
JDialog gameOverDialog = new JDialog(this, "Partie terminée", true);
gameOverDialog.setLayout(new BorderLayout());
gameOverDialog.setUndecorated(true);
gameOverDialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
JLabel gameOverLabel = new JLabel("GAME OVER", JLabel.CENTER);
gameOverLabel.setFont(new Font("Arial", Font.BOLD, 50));
gameOverLabel.setForeground(Color.RED);
gameOverLabel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
gameOverDialog.add(gameOverLabel, BorderLayout.CENTER);
JButton quitButton = new JButton("Quitter");
quitButton.addActionListener(e -> System.exit(0));
JPanel buttonPanel = new JPanel();
buttonPanel.add(quitButton);
gameOverDialog.add(buttonPanel, BorderLayout.SOUTH);
gameOverDialog.pack();
gameOverDialog.setLocationRelativeTo(this);
gameOverDialog.setVisible(true);
}
/**
* * Met à jour la vue de la grille lorsque l'état de la grille change.
*/
@Override
public void update(Observable o, Object arg) {
if (o instanceof Grille) {
updateGrille();
}
if (o instanceof Jeu && !afficherFenetreFinPartie) {
if (!this.jeu.jeuEnCours) {
afficherFenetreFinPartie = true;
afficherFinPartie();
}
}
}
/**
* * Met à jour la vue de la grille périodiquement selon l'ordonnaceur.
*/
@Override
public void run() {
updateGrille();
}
} }