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

public class WW {
	public static void main(String [] args){
		WorldPanel panel=new WorldPanel();
		JFrame j=new JFrame();
		j.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		j.getContentPane().add(panel, BorderLayout.CENTER);
		j.pack();
		j.setVisible(true);
		panel.start();
	}
}

class WorldPanel extends JPanel implements ActionListener,KeyListener {
	private javax.swing.Timer timer; // used to send ticks to this, so that this can simulate

	private Vector actors=new Vector(); // the objects contained in this world
	private Player player; // The player of this game

	private int worldWidth, worldHeight;  // the logical dimensions of this
	private int displayWidth, displayHeight;  // the dimensions of the display of this

	private Actor [][] grid;

	/**
	 * @param worldWidth the logical width of this
	 * @param worldHeight the logical height of this
	 * @param displayWidth the displayed width (in pixels) of this
	 * @param displayHeight the displayed height (in pixels) of this
	 * @param tickDelay the minimum number of milliseconds between ticks for simulation 
	 */
	public WorldPanel(int worldWidth, int worldHeight, int displayWidth, int displayHeight, int tickDelay){ 
		addKeyListener(this);
		setDisplay(displayWidth,displayHeight);
		setWorld(worldWidth, worldHeight);
		setTimer(tickDelay);
	}
	public WorldPanel(){ 
		this(20,20,32*20,32*20,100); 
		// Add players
		setPlayer(new Player(this));
		// Add monsters
		for(int i=0;i<10;i++){
			int x=WWUtil.rand(getWorldWidth()/4, 3*getWorldWidth()/4);
			int y=WWUtil.rand(getWorldHeight()/4, 3*getWorldHeight()/4);
			int dx=WWUtil.rand(-1,1);
			int dy=WWUtil.rand(-1,1);
			int slowDown=WWUtil.rand(1,10);
			//String iconFile="../icons/00"+WWUtil.rand(0,9)+".gif";
			String iconFile="firstfour.gif";
			if(grid[x][y]==null)addActor(new Monster(this,x,y,slowDown,iconFile,dx,dy));
		}
		// Add boxes
		for(int i=0;i<50;i++){
			int x=WWUtil.rand(getWorldWidth()/4, 3*getWorldWidth()/4);
			int y=WWUtil.rand(getWorldHeight()/4, 3*getWorldHeight()/4);
			int slowDown=1;

			String iconFile="../icons/c00"+WWUtil.rand(0,4)+".jpg";
			if(grid[x][y]==null)addActor(new Box(this,x,y,iconFile));
		}
	}
	
	// World methods -------------------------------------------
	public void addActor(Actor a){ 
		actors.add(a); 
		grid[a.getX()][a.getY()]=a;
	}
	public void setPlayer(Player p){ 
		addActor(p);
		player=p;
	}
	public void removeActor(Actor a){ actors.remove(a); }

	private void setWorld(int worldWidth, int worldHeight){
		this.worldWidth=worldWidth; 
		this.worldHeight=worldHeight; 
		grid=new Actor[worldWidth][worldHeight];
	}
	public int getWorldWidth(){ return worldWidth; }
	public int getWorldHeight(){ return worldHeight; }
	public void moveActor(int x, int y, Actor actor){
		grid[actor.getX()][actor.getY()]=null;
		grid[x][y]=actor;
	}
	public Actor[][]  getGrid(){ return grid; }
	public Actor getActor(int x, int y){ 
		if(x>=0 && x<worldWidth && y>=0 && y<worldWidth)return grid[x][y]; 
		else return null;
	}

	// For the player
	public void keyPressed(KeyEvent e){ }
	public void keyReleased(KeyEvent e){ }
	public void keyTyped(KeyEvent e){
		if(player!=null)switch(e.getKeyChar()){
			case 'i': 
			case '7': player.setMove(-1,-1); break;

			case 'o': 
			case '8': player.setMove(0,-1); break;
			
			case 'p': 
			case '9': player.setMove(1,-1); break;

			case 'k': 
			case '4': player.setMove(-1,0); break;

			case 'l': 
			case '5': player.setMove(0,0); break;

			case ';': 
			case '6': player.setMove(1,0); break;

			case ',': 
			case '1': player.setMove(-1,1); break;

			case '.': 
			case '2': player.setMove(0,1); break;

			case '/': 
			case '3': player.setMove(1,1); break;
			default:
		}
		//repaint();
	}

	// Timer methods------------------------------------
	private void setTimer(int tickDelay){
		timer=new javax.swing.Timer(tickDelay,this);
	}
	// take one step in the simulation of this
	private void tick(){
		for (Enumeration e = actors.elements() ; e.hasMoreElements() ;) {
			Actor a=(Actor)e.nextElement();
			a.tick();
		}
	}

	// Timer (start and stop ticks and so the simulation)
	public void start(){ timer.start(); }
	public void stop(){ timer.stop(); }

	// Each time a timer event fires, this method is called
	public void actionPerformed(ActionEvent e){
		tick(); 
		repaint();
		requestFocus();
	}

	// Display methods ---------------------------------------
	public int getDisplayWidth(){ return displayWidth; }
	public int getDisplayHeight(){ return displayHeight; }
	public void setDisplay(int displayWidth, int displayHeight){
		setBackground(Color.black); 
		this.displayWidth=displayWidth; 
		this.displayHeight=displayHeight; 
		Dimension size=new Dimension(displayWidth,displayHeight);
		setMinimumSize(size); setPreferredSize(size);
	}

	// Panel (draw a representation of this, by drawing all members of the world)
	public void paintComponent(Graphics g){
		super.paintComponent(g);
		// Draw all objects in the world
		for (Enumeration e = actors.elements() ; e.hasMoreElements() ;) {
			Actor a=(Actor)e.nextElement();

			// If I just want to draw a white ball...
			//g.setColor(Color.WHITE);
			//g.fillOval(x,y,10,10);
	
			// If I want to draw an image...
			g.drawImage(a.getIcon(),a.getX()*32,a.getY()*32,null);
		}
		// Draw the boundary of the world
		g.setColor(Color.white);
		g.drawLine(0,0,displayWidth-1,0);
		g.drawLine(displayWidth-1,0,displayWidth-1,displayHeight-1);
		g.drawLine(displayWidth-1,displayHeight-1,0,displayHeight-1);
		g.drawLine(0,displayHeight-1,0,0);
	}
}
class WWUtil {
	public static Random r=new Random();
	public static int rand(int lower, int upper){
		return (Math.abs(r.nextInt())%(1+upper-lower))+lower;
	}
}
class Actor {
	protected int x, y; // position of this
	protected WorldPanel world; // world in which this lives
	protected BufferedImage icon; // image for display of this
	protected int slowDown, tickCount; // used to determine speed of this. That is, every slowDown ticks, this moves
	protected int width, height; // the width and height of this, used to determine when this collides with something

	// Construct a new Actor inside world
	public Actor(WorldPanel world, int x, int y, int slowDown, String iconFile){
		this.world=world;
		this.x=x; this.y=y; 
		this.slowDown=slowDown;
		setIcon(iconFile);
	}
	protected void setIcon(String fileName){ 
		icon=ImageLoader.loadImage(fileName); 
		//width=icon.getWidth(null); 
		//height=icon.getHeight(null);
		width=icon.getWidth(null);
		height=icon.getHeight(null);
	}

	public int getX(){ return x; }
	public int getY(){ return y; }
	public WorldPanel getWorld(){ return world; }
	public BufferedImage getIcon(){ return icon; }
	public int getSlowDown(){ return slowDown; }
	public int getTickCount(){ return tickCount; }
	public int getWidth(){ return width; }
	public int getHeight(){ return height; }

	protected void tick(){
		tickCount=(tickCount+1)%slowDown;
		if(tickCount==0)move();
	}

	protected void move(){
	// See what happens if we make this public, private, protected
		// Random move
		int xNew=x+WWUtil.rand(-1,1), yNew=y+WWUtil.rand(-1,1);
		if(xNew>=0 && yNew>=0 
			&& xNew<=world.getWorldWidth()-1
			&& yNew<=world.getWorldHeight()-1){
				move(xNew,yNew);
		}
	}
	protected void move(int x, int y){
		world.moveActor(x, y, this); // tell the world that we are going to move
		this.x=x; this.y=y; // change our location
	}
	// What we do when we are pushed
	protected boolean push(int dx, int dy){ return false; }
}

class Monster extends Actor {
	protected int dx, dy; // direction of this

	public Monster(WorldPanel world, int x, int y, int slowDown, String iconFile, int dx, int dy){
		super(world, x, y, slowDown, iconFile);
		this.dx=dx;
		this.dy=dy;
	}

	public void move(){
		int xNew=x+dx, yNew=y+dy;
		// Determine if we should bounce
		if( xNew<0 || xNew>=world.getWorldWidth() || 
		    yNew<0 || yNew>=world.getWorldHeight() ||
		    world.getActor(xNew,yNew)!= null) 
		{
			bounce();
		} else {
			move(xNew,yNew);
		}
	}
	private void bounce(){ dx=-dx; dy=-dy; }
}

class Box extends Actor {
	public Box(WorldPanel world, int x, int y, String iconFile){
		super(world, x, y, 1, iconFile);
	}
	protected void move(){ }
	protected void tick(){ }
	protected boolean push(int dx, int dy){ 
		int xNew=x+dx, yNew=y+dy;
		Actor a=world.getActor(xNew,yNew);
		if( xNew<0 || xNew>=world.getWorldWidth() || yNew<0 || yNew>=world.getWorldHeight()) {
			return false;
		} else if(a==null || a.push(dx, dy)){ 
			move(xNew,yNew); 
			return true;
		}
		return false;
	}
}

class Player extends Actor {
	protected int dx, dy; // direction of this

	public Player(WorldPanel world, int x, int y, int slowDown, String iconFile){
		super(world, x, y, 1, iconFile);
		dx=0; dy=0;
	}
	public Player(WorldPanel world){
		this(world, 0,0,1, "../icons/Tractor.gif");
	}
	public void setMove(int dx, int dy){ this.dx=dx; this.dy=dy; }
	protected void move(){
		int xNew=x+dx, yNew=y+dy;
		Actor a=world.getActor(xNew,yNew);
		if( xNew<0 || xNew>=world.getWorldWidth() || yNew<0 || yNew>=world.getWorldHeight()) {

		} else if(a==null || a.push(dx, dy)){ 
			move(xNew,yNew); 
		}
		dx=0; dy=0;
	}
}
