// (c) 1997 Nicolas Bruyère //****************************************************************************** // JTRIS.java: Applet // //****************************************************************************** import java.applet.*; import java.awt.*; import java.util.Date; //****************************************************************************** // Class Piece // //****************************************************************************** class Piece { private int[] images = new int[4]; // contient les images des 4 rotations de la pièce private int[] hauteurs = new int[4]; // Contient les hauteurs des 4 rotations private int[] largeurs = new int[4]; // Contient les largeurs des 4 rotations private int x, y; // Position de la pièce private int paintx, painty, paintr; // dernières coordonnées où la pièce a été affichée (et dernière rotation) private Color couleur; // Couleur de la pièce private int idRotation; // indice de la pièce en cours public final static int tailleCarre = 15; // Largeur en pixels d'un carre // Constructeurs public Piece(int r1,int r2,int r3,int r4,Color p_couleur) { images[0] = r1; images[1] = r2; images[2] = r3; images[3] = r4; couleur = p_couleur; x = y = idRotation = 0; paintx = painty = -1; calculeHL(); } public Piece(int r1,int r2,int r3,int r4,Color p_couleur,int p_x,int p_y) { this(r1,r2,r3,r4,p_couleur); x = p_x; y = p_y; } public Piece(int[] r,Color p_couleur) { for(int i=0 ; i < r.length ; i++) images[i] = r[i]; x = y = idRotation = 0; paintx = painty = -1; idRotation = 0; couleur = p_couleur; calculeHL(); } public Piece(int[] r,Color p_couleur,int p_x,int p_y) { this(r,p_couleur); x = p_x; y = p_y; } public Piece(Piece src) { for(int i = 0 ; i < 4 ; i++) { images[i] = src.images[i]; hauteurs[i] = src.hauteurs[i]; largeurs[i] = src.largeurs[i]; } idRotation = src.idRotation; paintx = src.paintx; painty = src.painty; paintr = src.paintr; x = src.x; y = src.y; couleur = src.couleur; } // remise à 0 public void reset() { reset(0,0); } public void reset(int p_x, int p_y) { x = p_x; y = p_y; idRotation = 0; paintx = painty = -1; } public void firstRotation() { idRotation = 0; } // Calcule les hauteurs et largeurs des pièces private void calculeHL() { int cptRotations, i, j, masque; boolean ligneOK=false; // Les tableaux sont initialisés à 0 for(i=0 ; i < hauteurs.length ; i++) { hauteurs[i] = 0; largeurs[i] = 0; } for(cptRotations=0 ; cptRotations < hauteurs.length ; cptRotations++) { masque = 0x8000; for(i=0 ; i < 4 ; i++) { ligneOK = false; for(j=0 ; j < 4 ; j++, masque>>=1) if((masque & images[cptRotations])!=0) { // cette ligne avait-elle été comptabilisée ? if(!ligneOK) { // non, alors on incrémente le nombre de lignes ligneOK = true; hauteurs[cptRotations]++; } // la largeur actuelle est-elle inférieure ? if(largeurs[cptRotations] < j+1) // oui, alors mise à jour largeurs[cptRotations] = j+1; } } } } // Fonctions simples qui renvoient des valeurs de membres de la classe public void rotation() { idRotation>=3 ? idRotation=0 : idRotation++ ; } public void goDown() { y+=tailleCarre; } public void goDown(int nbl) { y+=(tailleCarre * nbl); } public void goLeft(){ x-=tailleCarre; } public void goRight() { x+=tailleCarre; } public void goUp() { y-=tailleCarre; } public void goUp(int nbl) { y-=(tailleCarre * nbl); } public int x() { return x; } public int y() { return y; } public int paintx() { return paintx; } public int painty() { return painty; } public Color getColor() { return couleur; } public int hauteur() { return hauteurs[idRotation]; } public int hauteur(int id) { return id >= 0 && id < 4 ? hauteurs[id] : 0; } public int nextHauteur() { return hauteurs[idRotation>=3 ? 0 : idRotation + 1]; } public int largeur() { return largeurs[idRotation]; } public int largeur(int id) { return id >= 0 && id < 4 ? largeurs[id] : 0; } public int nextLargeur() { return largeurs[idRotation>=3 ? 0 : idRotation + 1]; } public int getVal() { return images[idRotation]; } public int getVal(int id) { return id>=0 && id < 4 ? images[id] : 0; } public int getNextVal() { return images[idRotation>=3 ? 0 : idRotation + 1]; } public Piece getNextPiece() { Piece np = new Piece(this); np.rotation(); return np; } // Affichage public synchronized void display(Graphics g) { int masque = 0x8000, i, j; paintx = x; painty = y; paintr = idRotation; g.setColor(couleur); for(i=0 ; i < 4 ; i++) for(j=0 ; j < 4 ; j++, masque>>=1) if((masque & images[idRotation]) != 0) g.fill3DRect(x+(j*tailleCarre),y+(i*tailleCarre),tailleCarre,tailleCarre,true); } // Efface la pièce public synchronized void erase(Graphics g,Color fond) { int masque = 0x8000, i, j; // si -1, c'est que la pièce n'a pas encore été affichée if(paintx == -1 || painty == -1) return ; g.setColor(fond); for(i=0 ; i < 4 ; i++) for(j=0 ; j < 4 ; j++, masque>>=1) if((masque & images[paintr]) != 0) g.fill3DRect(paintx+(j*tailleCarre),painty+(i*tailleCarre),tailleCarre,tailleCarre,true); } } class Grille extends Canvas implements Runnable { private Thread m_Grille; // pour gérer la descente private Color[][] lesCases; // cases du tableau private Color colFond; // couleur du fond private Piece[] listePieces; // liste des pièces qui peuvent descendre private Piece currentPiece; // pièce courante public Piece nextOne; // pièce suivante private int indiceX, indiceY; // coordonnées logiques de la pièce courante private int largeur, hauteur; // largeur et hauteur de la grille en nombre de cases private int idCurrent, idNextOne; // Indice de la pièce courante et de la suivante private boolean paintAll; // indique s'il faut tout redessiner private boolean paintPiece; // indique s'il faut peindre la pièce private boolean gameRunning; // Le jeu est démarré ? private boolean gameOverPainted; // a-t-on affiché gameover ? public boolean gameFinished; // Le jeu est fini ? public int cptLignes, cptPoints; // pour les points public int bonus, level; // bonus et niveau du jeu private int direction; // +1 : vers le bas, -1 : vers le haut // double-buffering private Image offscreenImg; private Image fondImg; // image de fond d'écran private Graphics offscreenG; // la pièce nextOne devient current et on choisit une nouvelle nextOne private void selectPiece() { // Si pas de nextOne, on choisit 2 pièces if(nextOne == null) { // les indices sont tirés au hasard idCurrent = (int)(Math.random()*listePieces.length); idNextOne = (int)(Math.random()*listePieces.length); // les objets pièce currentPiece = new Piece(listePieces[idCurrent]); nextOne = new Piece(listePieces[idNextOne]); } else { // maj Piece courante currentPiece = nextOne; idCurrent = idNextOne; // choix de la suivante idNextOne = (int)(Math.random()*listePieces.length); nextOne = new Piece(listePieces[idNextOne]); } resetPiece(); bonus = 20; paintPiece = true; } // indique si la pièce p peut être affichée en x,y private boolean canDisplayAt(int x, int y, Piece p) { if(y < 0 || y + p.hauteur() > hauteur) return false; if(x < 0 || x + p.largeur() > largeur) return false; int masque = 0x8000; for(int i=0 ; i < p.hauteur() ; i++) { for(int j=0 ; j < p.largeur() ; j++, masque>>=1) if(((p.getVal() & masque)!=0) && lesCases[y+i][x+j]!=null) return false; // on positionne le masque sur la ligne suivante masque>>=4-p.largeur(); } return true; } // indique si la pièce courante peut-être affichée en x,y private boolean canDisplayAt(int x,int y) { return canDisplayAt(x,y,currentPiece); } // inverse le sens du jeu private void reverseGame() { Color buffer; int i, j; for(i=0 ; i < hauteur/2 ; i++) { for(j=0 ; j < largeur ; j++) { buffer = lesCases[i][j]; lesCases[i][j] = lesCases[hauteur-1-i][j]; lesCases[hauteur-1-i][j] = buffer; } } } private void resetPiece() { // on centre la pièce indiceX = (largeur / 2)-(currentPiece.largeur()/2); if(direction==1) { indiceY = 0; } else { indiceY = hauteur - currentPiece.hauteur(); } currentPiece.reset(indiceX*Piece.tailleCarre,indiceY*Piece.tailleCarre); } // appelée quand une pièce s'est arrêtée (en indiceX, indiceY) private void pieceStop() { int i, j, masque = 0x8000; int scr_i, scr_j; // indices utilisés pour scroller int oldCptLignes = cptLignes; // mise à jour de la grille for(i=0 ; i < currentPiece.hauteur() ; i++) { for(j=0 ; j < currentPiece.largeur() ; j++, masque>>=1) { if((currentPiece.getVal() & masque)!=0) lesCases[indiceY+i][indiceX+j] = listePieces[idCurrent].getColor(); } // on positionne le masque sur la ligne suivante masque>>=4-currentPiece.largeur(); } // 5 points quand la pièce arrive en bas cptPoints += 5; if(direction==1) { // vérification des lignes for(i=indiceY ; i < indiceY+currentPiece.hauteur() ; i++) { for(j=0 ; j < largeur ; j++) { // une case est nulle ? la ligne n'est pas complète if(lesCases[i][j] == null) break; } // j == largeur ? la ligne est complète if(j==largeur) { // on scrolle l'écran vers le bas for(scr_i=i ; scr_i > 0 ; scr_i--) { for(scr_j=0 ; scr_j < largeur ; scr_j++) { lesCases[scr_i][scr_j] = lesCases[scr_i-1][scr_j]; } } // on réinitialise la première ligne for(scr_j=0 ; scr_j < largeur ; scr_j++) { lesCases[0][scr_j] = null; } cptLignes++; } } } else { // vérification des lignes for(i=indiceY+currentPiece.hauteur()-1 ; i >= indiceY ; i--) { for(j=0 ; j < largeur ; j++) { // une case est nulle ? la ligne n'est pas complète if(lesCases[i][j] == null) break; } // j == largeur ? la ligne est complète if(j==largeur) { // on scrolle l'écran vers le haut for(scr_i=i ; scr_i < hauteur-1 ; scr_i++) { for(scr_j=0 ; scr_j < largeur ; scr_j++) { lesCases[scr_i][scr_j] = lesCases[scr_i+1][scr_j]; } } // on réinitialise la dernière ligne for(scr_j=0 ; scr_j < largeur ; scr_j++) { lesCases[hauteur-1][scr_j] = null; } cptLignes++; } } } if(oldCptLignes != cptLignes) { // 10 points par ligne cptPoints+=(cptLignes-oldCptLignes)*10; // si 4 lignes d'un coup, 100 points de bonus if(cptLignes-oldCptLignes==4) cptPoints+=100; } // Changement de niveau tous les 1000 points // 12 = niveau Max if(level < 12 && cptPoints >= 1000*level) level++; // Normalement, il n'est pas nécessaire de tout repeindre, mais // ça évite certains cas de désynchronisation. paintAll = true; paintPiece = false; repaint(); } // Constructeur public Grille(Piece[] lp) { largeur = 10; hauteur = 20; direction = 1; // vers le bas par défaut lesCases = new Color[hauteur][largeur]; listePieces = lp; selectPiece(); gameRunning = false; gameFinished = false; paintAll = false; cptLignes = cptPoints = 0; level = 1; colFond = Color.darkGray; gameOverPainted = false; } public void restartGame() { selectPiece(); gameRunning = true; gameFinished = false; paintAll = true; cptLignes = cptPoints = 0; level = 1; gameOverPainted = false; int i, j; for(i=0 ; i < hauteur ; i++) for(j=0 ; j < largeur ; j++) lesCases[i][j] = null; } public void start() { if (m_Grille == null) { m_Grille = new Thread(this); m_Grille.start(); } } public void stop() { if (m_Grille != null) { m_Grille.stop(); m_Grille = null; } } public synchronized void update(Graphics g) { paint(g); } public synchronized void paintPiece(boolean effPiece) { if(offscreenImg==null) return ; if(effPiece) currentPiece.erase(offscreenG,colFond); currentPiece.display(offscreenG); } private synchronized void paintGameOver() { Font laFont = new Font("Arial",Font.BOLD,20); FontMetrics fm = getFontMetrics(laFont); Color c1 = new Color(0,128,224); Color c2 = c1; //new Color(0,192,192); // remplissage en damier... for(int i=0 ; i < hauteur ; i++) for(int j=0 ; j < largeur ; j++) { if((i+j) % 2 == 0) offscreenG.setColor(c1); else offscreenG.setColor(c2); offscreenG.fill3DRect(j*Piece.tailleCarre,i*Piece.tailleCarre,Piece.tailleCarre,Piece.tailleCarre,true); } offscreenG.setColor(Color.yellow); offscreenG.setFont(laFont); offscreenG.drawString("GAME OVER",(this.size().width-fm.stringWidth("GAME OVER"))/2,32); String s; laFont = new Font("Arial",Font.BOLD,14); fm = getFontMetrics(laFont); offscreenG.setColor(Color.white); offscreenG.setFont(laFont); s = "e-maillez moi"; offscreenG.drawString(s,(this.size().width-fm.stringWidth(s))/2,180); s = "le code hall of fame !"; offscreenG.drawString(s,(this.size().width-fm.stringWidth(s))/2,192); s = "cliquez pour rejouer"; offscreenG.drawString(s,(this.size().width-fm.stringWidth(s))/2,220); gameOverPainted = true; } public void paint(Graphics g) { if(offscreenImg == null) { // création du buffer écran offscreenImg = createImage(largeur*Piece.tailleCarre,hauteur*Piece.tailleCarre); // création de l'image de fond fondImg = createImage(largeur*Piece.tailleCarre,hauteur*Piece.tailleCarre); // remplissage de l'image de fond offscreenG = fondImg.getGraphics(); offscreenG.setColor(colFond); for(int i=0 ; i < hauteur ; i++) for(int j=0 ; j < largeur ; j++) offscreenG.fill3DRect(j*Piece.tailleCarre,i*Piece.tailleCarre,Piece.tailleCarre,Piece.tailleCarre,true); // on transfert l'image de fond vers le buffer offscreenG = offscreenImg.getGraphics(); offscreenG.drawImage(fondImg,0,0,this); } // on redessine si le jeu n'est pas fini if(!gameFinished) { // faut-il tout refaire ? if(paintAll) { // quand on peint tout, on commence par transférer l'image de fond offscreenG.drawImage(fondImg,0,0,this); // affichage des cases occupées for(int i=0 ; i < hauteur ; i++) { for(int j=0 ; j < largeur ; j++) { if(lesCases[i][j] != null) { offscreenG.setColor(lesCases[i][j]); offscreenG.fill3DRect(j*Piece.tailleCarre,i*Piece.tailleCarre,Piece.tailleCarre,Piece.tailleCarre,true); } } } paintAll = false; // affichage de la pièce en cours // pas besoin d'effacer l'ancienne position if(paintPiece) paintPiece(false); } else { // affichage de la pièce en cours avec effacement de l'ancienne position paintPiece(true); } } else if(!gameOverPainted) { paintGameOver(); paintAll = false; // pour éviter d'écraser gameOver, on sait jamais ! } // transfert du buffer vers l'écran g.drawImage(offscreenImg,0,0,this); } // descend la pièce jusqu'à ce qu'elle soit bloquée (réponse à touche BAS) public synchronized void goDown() { int cpt = 1; // de combien de lignes va descendre la pièce ? int masque; indiceY++; // on la descend de 1 au moins for( ; /* boucle infinie */ ; ) { // en dehors des limites ? if(indiceY < 0 || indiceY + currentPiece.hauteur() > hauteur) { indiceY--; currentPiece.goDown(cpt-1); return ; } masque = 0x8000; // peut-elle être positionnée sur la grille ? for(int i=0 ; i < currentPiece.hauteur() ; i++) { for(int j=0 ; j < currentPiece.largeur() ; j++, masque>>=1) { if(((currentPiece.getVal() & masque)!=0) && lesCases[indiceY+i][indiceX+j]!=null) { indiceY--; currentPiece.goDown(cpt-1); return ; } } // on positionne le masque sur la ligne suivante masque>>=4-currentPiece.largeur(); } cpt++; indiceY++; } } // monte la pièce jusqu'à ce qu'elle soit bloquée (réponse à touche BAS) public synchronized void goUp() { int cpt = 1; // de combien de lignes va descendre la pièce ? int masque; indiceY--; // on la monte de 1 au moins for( ; /* boucle infinie */ ; ) { // en dehors des limites ? if(indiceY < 0 || indiceY + currentPiece.hauteur() > hauteur) { indiceY++; currentPiece.goUp(cpt-1); return ; } masque = 0x8000; // peut-elle être positionnée sur la grille ? for(int i=0 ; i < currentPiece.hauteur() ; i++) { for(int j=0 ; j < currentPiece.largeur() ; j++, masque>>=1) { if(((currentPiece.getVal() & masque)!=0) && lesCases[indiceY+i][indiceX+j]!=null) { indiceY++; currentPiece.goUp(cpt-1); return ; } } // on positionne le masque sur la ligne suivante masque>>=4-currentPiece.largeur(); } cpt++; indiceY--; } } public void run() { for( ; /* boucle infinie */ ; ) { if(!gameOverPainted) { repaint(); if(gameRunning && !gameFinished) { bonus--; if(direction==1) { if(canDisplayAt(indiceX,indiceY+1)) { indiceY++; currentPiece.goDown(); } else { pieceStop(); selectPiece(); if(!canDisplayAt(indiceX,indiceY)) gameFinished = true; } } else { if(canDisplayAt(indiceX,indiceY-1)) { indiceY--; currentPiece.goUp(); } else { pieceStop(); selectPiece(); if(!canDisplayAt(indiceX,indiceY)) gameFinished = true; } } } } try { Thread.sleep(300-((level-1)*20)); } catch (InterruptedException e) { stop(); } } } public boolean keyDown(Event evt, int key) { // Cas particulier de la pause if(key=='p' || key == 'P') { gameRunning = !gameRunning; } else if(gameRunning) { switch(key) { case Event.LEFT: if(canDisplayAt(indiceX-1,indiceY)) { indiceX--; currentPiece.goLeft(); } break; case Event.RIGHT: if(canDisplayAt(indiceX+1,indiceY)) { indiceX++; currentPiece.goRight(); } break; case Event.UP: if(canDisplayAt(indiceX,indiceY,currentPiece.getNextPiece())) currentPiece.rotation(); break; case Event.DOWN: if(bonus > 0) cptPoints+=bonus; if(direction == 1) goDown(); else goUp(); break; case 'i': case 'I': reverseGame(); direction = - direction; resetPiece(); bonus = 0; paintAll = true; break; default: return true; } } repaint(); return true; } public boolean lostFocus(Event evt, Object what) { gameRunning = false; return true; } public boolean mouseDown(Event evt, int x, int y) { gameRunning = true; if(gameFinished) { restartGame(); repaint(); } return true; } } //============================================================================== // classe d'affichage du score // //============================================================================== class DisplayScore extends Canvas { private int points, lignes, level; private Grille laGrille; private String codeHOF; private Font smallFont = new Font("Arial",Font.BOLD,10); private Font laFont = new Font("Arial",Font.BOLD,16); private FontMetrics fm = getFontMetrics(laFont); private FontMetrics smallFm = getFontMetrics(smallFont); // double-buffering private Image offscreenImg; private Graphics offscreenG; public DisplayScore(Grille pGrille) { setBackground(Color.black); laGrille = pGrille; codeHOF = null; } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { if(laGrille == null) return ; if(offscreenImg == null) { offscreenImg = createImage(this.size().width,this.size().height); offscreenG = offscreenImg.getGraphics(); } String s; offscreenG.setColor(Color.black); offscreenG.fillRect(0,0,this.size().width,this.size().height); offscreenG.setFont(laFont); // Affichage des Points offscreenG.setColor(new Color(0,128,224)); offscreenG.drawString("POINTS",(this.size().width-fm.stringWidth("POINTS"))/2,15); s = "" + laGrille.cptPoints; offscreenG.drawString(s,(this.size().width-fm.stringWidth(s))/2,30); // Affichage des Lignes offscreenG.setColor(new Color(0,192,0)); offscreenG.drawString("LIGNES",(this.size().width-fm.stringWidth("LIGNES"))/2,50); s = "" + laGrille.cptLignes; offscreenG.drawString(s,(this.size().width-fm.stringWidth(s))/2,65); // Affichage du niveau offscreenG.setColor(Color.yellow); offscreenG.drawString("NIVEAU",(this.size().width-fm.stringWidth("NIVEAU"))/2,85); s = "" + laGrille.level; offscreenG.drawString(s,(this.size().width-fm.stringWidth(s))/2,100); // Si le jeu est fini, affichage du code Hall Of Fame if(laGrille.gameFinished) { if(codeHOF == null) { // calcul du code HOF CodeHOF = "HOF"; } offscreenG.setColor(Color.pink); s = "Code Hall Of Fame"; offscreenG.drawString(s,(this.size().width-fm.stringWidth(s))/2,120); offscreenG.setColor(Color.white); for(int i = 0, j= 0 ; i < codeHOF.length() ; i+=8, j++) { if(codeHOF.length() > i+8) s = codeHOF.substring(i,i+8); else s = codeHOF.substring(i); offscreenG.drawString(s,(this.size().width-fm.stringWidth(s))/2,138+(j*18)); } } else { // Affichage de la prochaine pièce s = "NEXT"; offscreenG.setColor(Color.white); offscreenG.drawString(s,(this.size().width-fm.stringWidth(s))/2,140); // La pièce est créée et centrée Piece p = new Piece(laGrille.nextOne); p.firstRotation(); p.reset((this.size().width-p.largeur()*Piece.tailleCarre)/2,155); p.display(offscreenG); codeHOF = null; } // Affichage du (c) offscreenG.setColor(Color.white); offscreenG.setFont(smallFont); s = "(c) 1997 Nicolas Bruyère"; offscreenG.drawString(s,(this.size().width-smallFm.stringWidth(s))/2,this.size().height-3); // Transfert du buffer vers l'écran g.drawImage(offscreenImg,0,0,this); } } //============================================================================== // Main Class for applet JTRIS // //============================================================================== public class JTRIS extends Applet implements Runnable { // THREAD SUPPORT: // m_JTRIS is the Thread object for the applet //-------------------------------------------------------------------------- private Thread m_JTRIS = null; private Grille laGrille; private DisplayScore dspScore; private Piece[] listePieces; // JTRIS Class Constructor //-------------------------------------------------------------------------- public JTRIS() { listePieces = new Piece[7]; listePieces[0] = new Piece(0x88C0,0x2E00,0xC440,0xE800,new Color(0,192,192)); // cyan listePieces[1] = new Piece(0x44C0,0xE200,0xC880,0x8E00,new Color(255,230,0)); // jaune listePieces[2] = new Piece(0xCC00,0xCC00,0xCC00,0xCC00,new Color(0,192,0)); // vert listePieces[3] = new Piece(0x8888,0xF000,0x8888,0xF000,new Color(230,0,0)); // rouge listePieces[4] = new Piece(0x8C40,0x6C00,0x8C40,0x6C00,new Color(0,128,224)); // bleu listePieces[5] = new Piece(0x4C80,0xC600,0x4C80,0xC600,Color.orange); listePieces[6] = new Piece(0x8C80,0x4E00,0x4C40,0xE400,Color.lightGray); } public String getAppletInfo() { return "Tetris\r\n" + "Programmé par: Nicolas Bruyère\r\n" + "Créé avec Microsoft Visual J++ Version 1.1"; } public void init() { resize(330, 320); setLayout(new GridLayout(1,2,10,0)); laGrille = new Grille(listePieces); laGrille.setBackground(Color.black); laGrille.start(); add(laGrille); dspScore = new DisplayScore(laGrille); add(dspScore); } public Insets insets() { return new Insets(10,10,10,10); } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { g.setColor(Color.orange); g.fill3DRect(0,0,this.size().width,this.size().height,true); if(dspScore!=null) dspScore.repaint(); } public void start() { if (m_JTRIS == null) { m_JTRIS = new Thread(this); m_JTRIS.start(); } } public void stop() { if (m_JTRIS != null) { m_JTRIS.stop(); m_JTRIS = null; } } public void run() { for( ; ; ) { repaint(); try { Thread.sleep(300); } catch (InterruptedException e) { } } } }