IFT1020 - Automne 2004
TP2 - Jeu de Combat

 

But: pratiquer la création d'une programe complexe qui utilise héritage, les interfaces et l'animation
Date limite: 17 Novembre, 12h00 (midi)

Il faut créer un applet Java qui copie le vieux jeu vidéo Combat par Atari. Combat était le premier "tank game".

Étape 1

Créer Terrain.java qui contient class Terrain extends JPanel et mettre un Terrain dans un class TankApplet extends JApplet

Terrain est un paneau qui montre un terrain lu d'un fichier. Vous pouvez utiliser le fichier suivant:


combat.gif

Au dessus du terrain, vous devez imprimer les tanks. Vous pouvez utiliser tank_blue.gif et tank_red.gif ou vous pouvez créer vous-même un graphique.


blue_tank.gif red_tank.gif

Il doit être possible de redimensionner le terrain et les tanks. Donc votre Terrain devrait implanter l'interface ComponentListener pour savoir quand la taille de l'applet est changée. Voir le code suivant pour un exemple (ResizableApplet.java). L'exemple s'occupe du JPanel; nous allons fournir des details pour redimensionner les tanks plus tard dans un des démos.

Une nouvelle version qui utilise java.net.URL pour lire le fichier. Cette version va marcher même si on utilise
appletviewer http://www.iro.umontreal.ca/~pift1020/tp2/ResizableApplet.html. Aussi cette version utilise un MediaTracker pour lire l'image.

import java.awt.*;
import javax.swing.*;
import java.awt.image.*;
import java.awt.event.*;
import java.net.URL;
import java.net.MalformedURLException;

public class ResizableApplet extends JApplet {
    public ResizablePanel p;
    private MediaTracker tracker=null;
    public void init() {
	Image combatImg = loadImageCompletely("combat.gif");
	p = new ResizablePanel(combatImg);
	this.getContentPane().add(p);
    }

    private Image loadImageCompletely(String imgName) {
	//load image using URL. wait for load using MediaTracker
	Image img;
	if (tracker==null) {
	    tracker = new MediaTracker(this);
	}
	try {
	    img = Toolkit.getDefaultToolkit().getImage(new URL(this.getDocumentBase(),imgName));
	    tracker.addImage(img,0);
	    try {
		tracker.waitForID(0);
	    } catch (InterruptedException e) {
		System.out.println("Cannot load image "+ img);
		System.out.println(e);
	    }
	    return img;
	} catch (MalformedURLException e) {
	    System.out.println(e);
	}
	return null;
    }

}

class ResizablePanel extends JPanel implements ComponentListener {
    private Image bImg=null;
    private Image bScaledImg=null;
    public ResizablePanel( Image bImg) {
	this.bImg = bImg;
	addComponentListener(this);
    }

    public void paintComponent(Graphics g) {
	super.paintComponent(g);
	if (bScaledImg==null) {
	    resizeTerrainImage();
	}
	g.drawImage(bScaledImg,0,0,this);
    }

    public void componentHidden(ComponentEvent e) {}
    public void componentMoved(ComponentEvent e) {}
    public void componentShown(ComponentEvent e) {}
    public void componentResized(ComponentEvent e) {
	resizeTerrainImage();
    }
    private void resizeTerrainImage() {
	bScaledImg = bImg.getScaledInstance(this.getSize().width, this.getSize().height,Image.SCALE_DEFAULT);
    }

}

La vielle version:
import java.awt.*;
import javax.swing.*;
import java.awt.image.*;
import java.awt.event.*;

public class ResizableApplet extends JApplet {
    public ResizablePanel p;
    public void init() {
	p = new ResizablePanel("combat.gif");
	this.getContentPane().add(p);
    }    
}

class ResizablePanel extends JPanel implements ComponentListener {
    private Image bImg=null;
    private Image bScaledImg=null;

    public ResizablePanel( String bImageName) {
	bImg = Toolkit.getDefaultToolkit().getImage(bImageName);
	addComponentListener(this);
    }

    public void paintComponent(Graphics g) {
	super.paintComponent(g);
	if (bScaledImg==null) {
	    resizeTerrainImage();
	}
	g.drawImage(bScaledImg,0,0,this);
    }

    public void componentHidden(ComponentEvent e) {}
    public void componentMoved(ComponentEvent e) {}
    public void componentShown(ComponentEvent e) {}
    public void componentResized(ComponentEvent e) {
	resizeTerrainImage();
    }
    private void resizeTerrainImage() {
	bScaledImg = bImg.getScaledInstance(this.getSize().width, this.getSize().height,Image.SCALE_DEFAULT);
    }
}

Étape 2

Implanter abstract class Tank qui contient:
public Tank(String tankName, Image image, TerrainInterface terrain);
public String toString();
public Image getImage();   
public void start();
TankInterface est un interface pour Les classes qui heritent de Tank peuvent utiliser le TerrainInterface.java pour naviguer dans le jeu. Dans étape 2 vous pouvez avoir un implementation de TerrainInterface dans Terrain très simple, par exemple:
public TankList getTankList() { return (TankList)null; } 
public boolean  canTankMakeMove(Tank t, int dir) { return false; }
public double   rotateDelta() { return 0;}
public double   moveDelta() { return 0;}
Dans étape 9 vous pouvez implementer les "vrai" méthodes.

Implanter class RandomTank extends Tank. RandomTank doit utilser un Timer pour se déplacer au hasard dans le jeu. Pour commmuniquer avec le jeu, le RandomTank doit generer les TankEvents. Voir en bas.

Étape 3

Implanter class Terrain qui accepte TankEvents des Tanks en utilisant TankListener. Terrain doit aussi monter les tanks sur le JPanel.

Voici le code pour TankEvent (TankEvent.java):
import java.util.EventObject;
class TankEvent extends EventObject {
    private int move=0;
    private int rotate=0;
    private boolean fire=false;
    TankEvent(Object source, int move, int rotate, boolean fire) {
	super(source);
	this.move=move;
	this.rotate=rotate;
	this.fire=fire;
    }
    public int getMove() {
	return move;
    }
    public int getRotate() {
	return rotate;
    }
    public boolean getFire() {
	return fire;
    }

    public String toString() {
	return "Move=" + move + " rotate=" + rotate + "fire=" + fire;
    }
}
Ajouter une méthode addTankListener(TankListener tl) dans class Tank. Puis, quand vous créez votre Terrain et Tanks, vous pouvez failre:
Terrain terrain = new Terrain();
Tank t = new RandomTank("redTank",redImg, terrain);
t.addTankListener(terrain);
Maintentant vous devriez instantier deux RandomTanks dans votre Terrain et les voir se deplacer au hasard dans le jeu.

Étape 4

Implanter les obstacles pour que les tanks ne peuvent pas passer par des murs, obstacles etc. Norman va vous fournir une stratégie pour ça dans un des démos.

Voir l'exemple suivant (ObstacleApplet.java et ObstacleApplet.html):

import java.awt.*;
import javax.swing.*;
import java.awt.image.*;
import java.awt.event.*;
import java.net.URL;
import java.net.MalformedURLException;

public class ObstacleApplet extends JApplet {
    public ObstaclePanel p;
    private MediaTracker tracker=null;
    public void init() {
	Image combatImg = loadImageCompletely("combat.gif");
	p = new ObstaclePanel(combatImg);
	this.getContentPane().add(p);
    }    
    private Image loadImageCompletely(String imgName) {
	//load image using URL. wait for load using MediaTracker
	Image img;
	if (tracker==null) {
	    tracker = new MediaTracker(this);
	}
	try {
	    img = Toolkit.getDefaultToolkit().getImage(new URL(this.getDocumentBase(),imgName));
	    tracker.addImage(img,0);
	    try {
		tracker.waitForID(0);
	    } catch (InterruptedException e) {
		System.out.println("Cannot load image "+ img);
		System.out.println(e);
	    }
	    return img;
	} catch (MalformedURLException e) {
	    System.out.println(e);
	}
	return null;
    }

}

class ObstaclePanel extends JPanel implements ComponentListener, MouseListener {
    private Image bImg=null;
    private BufferedImage bScaledBufferedImg=null;
    public ObstaclePanel( Image bImg) {
	this.bImg = bImg;
	addComponentListener(this);
	addMouseListener(this);
    }

    public void paintComponent(Graphics g) {
	super.paintComponent(g);
	if (bScaledBufferedImg==null) {
	    resizeTerrainImage();
	}
	g.drawImage(bScaledBufferedImg,0,0,this);
    }

    public void componentHidden(ComponentEvent e) {}
    public void componentMoved(ComponentEvent e) {}
    public void componentShown(ComponentEvent e) {}
    public void componentResized(ComponentEvent e) {
	resizeTerrainImage();
    }
    private void resizeTerrainImage() {
        int w = this.getSize().width;
        int h = this.getSize().height;
        bScaledBufferedImg = (BufferedImage)this.createImage(w,h);
	
        if ( bScaledBufferedImg == null ) {
            return;
        }
	
        Graphics2D g = bScaledBufferedImg.createGraphics();
        g.drawImage(bImg, 0, 0, w, h, this);
        g.dispose();
    }
   
    public void mouseClicked(MouseEvent e) {	
        int obsRGBColor = -8041230;
	int pixel = bScaledBufferedImg.getRGB(e.getX(),e.getY());
	System.out.print("Color="+pixel);
	if (pixel==obsRGBColor) {
	    System.out.println(" obstacle!");
	} else {
	    System.out.println(" OK");
	}
    }

    public void mouseEntered(MouseEvent e) {}
    public void mouseExited(MouseEvent e) {}    
    public void mousePressed(MouseEvent e) {}    
    public void mouseReleased(MouseEvent e) {}

}

Étape 5

Implanter class UserTank extends Tank qui utilse le clavier pour controller le tank. Utilser les flèches pour naviguer et le barre d'espacement pour tirer.

Voir l'exemple suivant (KeyTrackApplet.java et KeyTrackApplet.html):

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class KeyTrackApplet extends JApplet implements KeyEventDispatcher{
    
    int vPress=0;
    public void init() {
	setFocusable(true);
    }

    public void paint(Graphics g) {
	super.paint(g);
	if (vPress==1) {
	    g.drawString("Tracking key v... pressed",10,20);
	} else if (vPress==-1) {	    
	    g.drawString("Tracking key v... released",10,20);
	} else {
	    g.drawString("Try pressing key v",10,20);
	}
    }
    
    public void start() {
	KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this);
    }

    public boolean dispatchKeyEvent(KeyEvent e) {
        // Nous traitons seulement VK_V ("v").  
	// Voir documentation pour KeyEventDispatcher et KeyEvent (par exemple "google KeyEventDispatcher") 
	// pour voir tous les "keycodes" (comme KEY_PRESSED et VK_V)
	if (e.getKeyCode()==KeyEvent.VK_V) { 
	    if (e.getID() == KeyEvent.KEY_PRESSED) 
		vPress=1;
	    else
		vPress=-1;
	    repaint();
	}  

	return false;
    }
}


Maintentant vous devriez instantier un RandomTank et un UserTank dans votre Terrain et voir le RandomTank se deplacer au hasard en même temps que vous pilotez le UserTank.

Étape 6

Implanter les balles. Chaque tank peut avoir seulement une balle à la fois. Donc si un tank tire en même temps que une balle est déjà lancée, la première balle doit disparaître. Les balles ne peuvent pas passer par les murs et les obstacles.

Étape 7

Implanter l'habilité pour les tanks d'être atteint par des balles. Il faut compter les atteintes. Quand un tank est touché 5 fois, il est "mort". Le tank devrait disparaître et réapparaître dans une place choisi au hasard, mais loin des autres tanks et pas à l'interieur "un obstacle.

Étape 8

Monter le pointage. Le jeu est fin quand un tank arrive à 10 points (10 tués). Ce n'est pas nécessaire de copier les chiffres de l'interface de Atari 2600.

Étape 9

Implanter class SmartTank extends RandomTank qui utilise votre meilleur algorithme pour gagner.

Notre implanatation

Vous pouvez voir un exemple en faisant les commandes suivantes. Il est normal que vous ne puissiez lire le contenu de ce répertoire mais vous pouvez accéder quand même au programme en tapant ces commandes telles quelles (dans une machine linux):

appletviewer http://www.iro.umontreal.ca/~pift1020/tp2/Tank.html

Détail de soumission

Il faut ensuite envoyer, par la procédure de remise électronique habituelle, les fichiers nécessaires à l'éxécution de votre programme avant le vendredi 17 novembre 2004 à 12:00 (midi).

Vous remettrez également une copie papier un court rapport et votre programme Java lors de la séance du cours du 1 octobre.

Ce TP devra être fait seul. Vous pouvez aider vos collègues avec les questions générales de java, mais vous ne pouvez pas fournir le code.

TP1 sera noté sur 30:

étape 13
étape 22
étape 32
étape 42
étape 52
étape 62
étape 72
étape 81
étape 94
résultats corrects 3
interface usager (GUI) 3
lisibilité du code 2
rapport 2
total 30

Bonne chance!

Doug Eck

Retour à la page principale d'ift1020
Last modified: Wed Nov 10 14:43:38 EST 2004 par Douglas Eck [douglas D0T eck AT umontreal D0T ca]