// $Header: /home/pauillac/formel1/fpottier/cvs/mambo/Mambo.java,v 1.1 1998/09/23 08:34:15 fpottier Exp $

// My first Java project.
// Started June 13th, 1996.
// Moved to Linux September 8th, 1998.

// Best viewed in a 120 character window.
// This applet is called Mambo for no good reason -- this game is actually called 'morpion' in French.
// You may use this source code freely. Preferrably, credit me and let me know if you do anything interesting with it.

import java.awt.*;
import java.applet.*;
import java.util.*;

// --------------------------------------------------------------------------------------------------------------------
// The main class.

public class Mambo extends Applet {

// --------------------------------------------------------------------------------------------------------------------
// Constants.

  final int     kGridUnit = 20;		// The size of a grid element in pixels.
	
  final byte	kEmpty = 0;		// Possible values for elements of mGrid.
  final byte	kHuman = 1;
  final byte	kRobot = 2;
	
  final byte	kOpen = 0;		// Row/game status.
  final byte	kHumanWon = 1;
  final byte	kRobotWon = 2;
  final byte	kBlocked = 3;
	
  final int[][]	kStrategy = {		// Defines our strategy.
    { 1, 2, 8, 30, 200 },
    { 2, 0, 0, 0, 0 },
    { 7, 0, 0, 0, 0 },
    { 40, 0, 0, 0, 0 },
    { 400, 0, 0, 0, 0 }
  };
								
// --------------------------------------------------------------------------------------------------------------------
// Instance variables.

  int		mWidth, mHeight;	// Logical grid size.
  byte[][]	mGrid;			// Current grid state.
  int[][]	mRating;		// Ratings for the current move.
  Random	mRandom;		// Random generator.
  byte		mGameStatus;		// Current game status.
  String	mLanguage;		// Language to be used for status messages.
  Button	mButton;

// --------------------------------------------------------------------------------------------------------------------
// Applet initialization.
// This method is called when the applet starts up.

  public void init ()
  {
    Rectangle	theRectangle;
		
    theRectangle = bounds();		                // Get the applet's bounds.
    mWidth = theRectangle.width / kGridUnit;		// Compute the grid's size.
    mHeight = theRectangle.height / kGridUnit - 1;	// Leave one line for the status message.
		
    mGrid = new byte[mWidth][mHeight];			// Initialize the grid.
    mRating = new int[mWidth][mHeight];			// Allocate memory for the auxiliary variables.
    mRandom = new Random ();				// Create a new random number generator.
    mGameStatus = kOpen;
    mLanguage = getParameter("language");		// Determine which language to use.
  }

// --------------------------------------------------------------------------------------------------------------------
// Artificial Intelligence.

  // DoRow considers a row of five consecutive grid boxes. The row can be horizontal, vertical or diagonal.
  // DoRow counts how many markers of each color are in there and updates the boxes' ratings accordingly.
	
  private void DoRow (int inRowX, int inRowY, int inDeltaX, int inDeltaY)
  {
    int[]	theCount = { 0, 0, 0 };			// Counts each kind of marker in this row.
    int		theX, theY;
    int		theIndex;
	
    theX = inRowX;					// Initialize theCount.
    theY = inRowY;
    for (theIndex = 5; theIndex > 0; theIndex--) {
      theCount[mGrid[theX][theY]]++;
      theX += inDeltaX;
      theY += inDeltaY;
    }
		
    theX = inRowX;					// Increment the rating of the boxes which are still free.
    theY = inRowY;
    for (theIndex = 5; theIndex > 0; theIndex--) {
      if (mGrid[theX][theY] == kEmpty)
	mRating[theX][theY] += kStrategy[theCount[kRobot]][theCount[kHuman]];
      theX += inDeltaX;
      theY += inDeltaY;
    }
  }
	
  // Play chooses where to make the next move. It shouldn't be called when the game is over or is a draw.

  private Point Play ()
  {
    int		theX, theY, theRating, theScore, theBestScore, theBestX, theBestY;
	
    for (theX = 0; theX < mWidth; theX++)		// Non-empty spots get a negative rating -- they're illegal.
      for (theY = 0; theY < mHeight; theY++)
	mRating[theX][theY] = (mGrid[theX][theY] == kEmpty ? 0 : -1);

    for (theX = 0; theX < mWidth - 4; theX++)		// Walk the whole grid, updating ratings.
      for (theY = 0; theY < mHeight; theY++)
	DoRow(theX, theY, 1, 0);	
    for (theX = 0; theX < mWidth; theX++)
      for (theY = 0; theY < mHeight - 4; theY++)
	DoRow(theX, theY, 0, 1);
    for (theX = 0; theX < mWidth - 4; theX++)
      for (theY = 0; theY < mHeight - 4; theY++)
	DoRow(theX, theY, 1, 1);	
    for (theX = 0; theX < mWidth - 4; theX++)
      for (theY = 4; theY < mHeight; theY++)
	DoRow(theX, theY, 1, -1);
		
    theRating = -1;					// Determine the maximum rating.
    for (theX = 0; theX < mWidth; theX++)
      for (theY = 0; theY < mHeight; theY++)
	if (mRating[theX][theY] > theRating)
	  theRating = mRating[theX][theY];
				
    theBestX = 0;					// These are necessary only to make the compiler happy.
    theBestY = 0;
    theBestScore = Integer.MIN_VALUE;			// Pick a move at random among the best moves.
    for (theX = 0; theX < mWidth; theX++)
      for (theY = 0; theY < mHeight; theY++)
	if (mRating[theX][theY] == theRating) {
	  theScore = mRandom.nextInt();
	  if (theScore >= theBestScore) {
	    theBestScore = theScore;
	    theBestX = theX;
	    theBestY = theY;
	  }
	}

    return new Point (theBestX, theBestY);
  }
	
  private byte RowStatus (int inRowX, int inRowY, int inDeltaX, int inDeltaY)
  {
    int[]	theCount = { 0, 0, 0 };			// Counts each kind of marker in this row.
    int		theX, theY;
    int		theIndex;
	
    theX = inRowX;					// Initialize theCount.
    theY = inRowY;
    for (theIndex = 5; theIndex > 0; theIndex--) {
      theCount[mGrid[theX][theY]]++;
      theX += inDeltaX;
      theY += inDeltaY;
    }
		
    if (theCount[kHuman] == 5)
      return kHumanWon;
    else if (theCount[kRobot] == 5)
      return kRobotWon;
    else if (theCount[kHuman] == 0 || theCount[kRobot] == 0)
      return kOpen;
    else
      return kBlocked;
  }
	
  private byte GameStatus ()
  {
    int		theX, theY;
    byte	theGameStatus = kBlocked;
    byte	theRowStatus;
	
    for (theX = 0; theX < mWidth - 4; theX++)		// Walk the whole grid, testing each row.
      for (theY = 0; theY < mHeight; theY++)
	switch (theRowStatus = RowStatus(theX, theY, 1, 0)) {
	case kHumanWon:
	case kRobotWon:
	  return theRowStatus;
	case kOpen:
	  theGameStatus = kOpen;
	}
    for (theX = 0; theX < mWidth; theX++)
      for (theY = 0; theY < mHeight - 4; theY++)
	switch (theRowStatus = RowStatus(theX, theY, 0, 1)) {
	case kHumanWon:
	case kRobotWon:
	  return theRowStatus;
	case kOpen:
	  theGameStatus = kOpen;
	}
    for (theX = 0; theX < mWidth - 4; theX++)
      for (theY = 0; theY < mHeight - 4; theY++)
	switch (theRowStatus = RowStatus(theX, theY, 1, 1)) {
	case kHumanWon:
	case kRobotWon:
	  return theRowStatus;
	case kOpen:
	  theGameStatus = kOpen;
	}
    for (theX = 0; theX < mWidth - 4; theX++)
      for (theY = 4; theY < mHeight; theY++)
	switch (theRowStatus = RowStatus(theX, theY, 1, -1)) {
	case kHumanWon:
	case kRobotWon:
	  return theRowStatus;
	case kOpen:
	  theGameStatus = kOpen;
	}
    
    return theGameStatus;
  }

// --------------------------------------------------------------------------------------------------------------------
// Drawing.

  private void DrawGridElement (Graphics inGraphics, int inHIndex, int inVIndex)
  {
    switch (mGrid[inHIndex][inVIndex]) {
    case kEmpty:
      break;
    case kHuman:
      inGraphics.drawOval(inHIndex * kGridUnit, inVIndex * kGridUnit, kGridUnit, kGridUnit);
      break;
    case kRobot:
      inGraphics.fillOval(inHIndex * kGridUnit, inVIndex * kGridUnit, kGridUnit, kGridUnit);
      break;
    }
  }
	
  private void drawStatus (Graphics inGraphics, Rectangle inBounds)
  {
    String	theStatusString = "";
    
    switch (mGameStatus) {
    case kOpen:
      theStatusString = mLanguage.equalsIgnoreCase("en") ? "Your turn." : "À votre tour.";
      break;
    case kHumanWon:
      theStatusString = mLanguage.equalsIgnoreCase("en") ? "You won!" : "Vous m'avez battu!";
      break;
    case kRobotWon:
      theStatusString = mLanguage.equalsIgnoreCase("en") ? "I won!" : "Je vous ai battu!";
      break;
    case kBlocked:
      theStatusString = mLanguage.equalsIgnoreCase("en") ? "It's a draw." : "Match nul.";
      break;
    }
    inGraphics.clearRect(0, mHeight * kGridUnit + 1, mWidth * kGridUnit, inBounds.height - mHeight * kGridUnit - 1);
    inGraphics.drawString(theStatusString, 0, inBounds.height - 2);
  }

  public void paint (Graphics inGraphics)
  {
    Rectangle	theRectangle;
    int		theIndex, theHIndex, theVIndex;
		
    theRectangle = bounds();				// Fill the background.
    inGraphics.clearRect(theRectangle.x, theRectangle.y, theRectangle.width, theRectangle.height);
		
    for (theIndex = 0; theIndex <= mWidth; theIndex++) {// Draw the grid.
      inGraphics.drawLine(theIndex * kGridUnit, 0, theIndex * kGridUnit, mHeight * kGridUnit);
      inGraphics.drawLine(0, theIndex * kGridUnit, mWidth * kGridUnit, theIndex * kGridUnit);
    }
		
    for (theHIndex = 0; theHIndex < mWidth; theHIndex++)// Draw the grid's contents.
      for (theVIndex = 0; theVIndex < mHeight; theVIndex++)
	DrawGridElement(inGraphics, theHIndex, theVIndex);
    
    drawStatus(inGraphics, theRectangle);		// Draw the status line.
  }

// --------------------------------------------------------------------------------------------------------------------
// Event handling.
	
  public boolean mouseUp (Event inEvent, int inMouseX, int inMouseY)
  {
    int		theX, theY;
    Point	theMove;
		
    theX = inMouseX / kGridUnit;
    theY = inMouseY / kGridUnit;
		
    if (theX >= 0 && theX < mWidth && theY >= 0 && theY < mHeight && mGameStatus == kOpen) {
		
      if (mGrid[theX][theY] == kEmpty) {
	mGrid[theX][theY] = kHuman;
	DrawGridElement(getGraphics(), theX, theY);
	
	mGameStatus = GameStatus();
	if (mGameStatus == kOpen) {
				
	  theMove = Play();
	  mGrid[theMove.x][theMove.y] = kRobot;
	  DrawGridElement(getGraphics(), theMove.x, theMove.y);
	  mGameStatus = GameStatus();
	  
	}

	drawStatus(getGraphics(), bounds());

      }
			
      return true;
    }
    else
      return false;
  }
	
// --------------------------------------------------------------------------------------------------------------------
// About box.
// I don't know how the user can get this string...
	
  public String getAppletInfo()
  {
    return "Mambo ©1996 François Pottier (Francois.Pottier@inria.fr)";
  }
	
  public String[][] getParameterInfo ()
  {
    String[][]	theParamInfo = {
      { "language", "string", "Language to be used for the status messages" }
    };
		
    return theParamInfo;
  }
}


