/*
                GNU GO - the game of Go (Wei-Chi)
                Version 1.1   last revised 3-1-89
           Copyright (C) Free Software Foundation, Inc.
                      written by Man L. Li
                      modified by Wayne Iba
                    documented by Bob Webber
                    NeXT version by John Neil
*/
/*
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation - version 1.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License in file COPYING for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

Please report any bug/fix, modification, suggestion to

mail address:   Man L. Li
                Dept. of Computer Science
                University of Houston
                4800 Calhoun Road
                Houston, TX 77004

e-mail address: manli@cs.uh.edu         (Internet)
                coscgbn@uhvax1.bitnet   (BITNET)
                70070,404               (CompuServe)

For the NeXT version, please report any bug/fix, modification, suggestion to

mail address:   John Neil
                Mathematics Department
                Portland State University
                PO Box 751
                Portland, OR  97207

e-mail address: neil@math.mth.pdx.edu  (Internet)
                neil@psuorvm.bitnet    (BITNET)
*/

#import "Board.h"
#import "gnugo.h"

#import <libc.h>
#import <math.h>
#import <dpsclient/wraps.h>	// PSxxx functions
#import <appkit/color.h>	// For background colors of images
#import <defaults/defaults.h>	// For writing/reading high score
#import <appkit/Application.h>	// For NXApp
#import <appkit/Button.h>
#import <appkit/Control.h>
#import <appkit/NXImage.h>
#import <appkit/OpenPanel.h>
#import <appkit/Panel.h>
#import <appkit/Window.h>
#import <appkit/Menu.h>
#import <appkit/Matrix.h>

#define EMPTY		0
#define WHITESTONE	1
#define BLACKSTONE	2
#define KOMI            5.5

// The following values are the default sizes for the various pieces. 
  
#define RADIUS		14.5 			// Stone radius
#define STONEWIDTH	29.0			// Stone width
#define STONEHEIGHT	29.0			// Stone height
  
  // SHADOWOFFSET defines the amount the shadow is offset from the piece. 
  
#define SHADOWOFFSET 2.0
  
#define BASEBOARDX 19.0
#define BASEBOARDY 19.0
#define WINDOWOFFSETX 12.0
#define WINDOWOFFSETY 100.0
  
#define gameSize  bounds.size

#define PSLine(a, b, x, y)	PSmoveto(a, b); PSlineto(x, y)

float stoneX, stoneY;

void setStoneLoc(int x, int y)
{
  stoneX = ((19.0 - MAXX)/2.0)*STONEWIDTH + BASEBOARDX - RADIUS + (x*STONEWIDTH);
  stoneY = BASEBOARDY - RADIUS + ((18 - y)*STONEHEIGHT) - ((19.0 - MAXY)/2.0)*STONEHEIGHT;
}  
  
  
@implementation GoView
  
  - initFrame:(const NXRect *)frm
{
  NXSize stoneSize;
  
  stoneSize.width = STONEWIDTH;
  stoneSize.height = STONEHEIGHT;
  
  [super initFrame:frm];
  
  [self allocateGState];	// For faster lock/unlockFocus
    
  [(blackStone = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
  [blackStone useDrawMethod:@selector(drawBlackStone:) inObject:self];
  [blackStone setSize:&stoneSize];
  
  [(whiteStone = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
  [whiteStone useDrawMethod:@selector(drawWhiteStone:) inObject:self];
  [whiteStone setSize:&stoneSize];
  
  [(grayStone = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
  [grayStone useDrawMethod:@selector(drawGrayStone:) inObject:self];
  [grayStone setSize:&stoneSize];
  
  [(upperLeft = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
  [upperLeft useDrawMethod:@selector(drawUpperLeft:) inObject:self];
  [upperLeft setSize:&stoneSize];
  
  [(upperRight = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
  [upperRight useDrawMethod:@selector(drawUpperRight:) inObject:self];
  [upperRight setSize:&stoneSize];
  
  [(lowerLeft = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
  [lowerLeft useDrawMethod:@selector(drawLowerLeft:) inObject:self];
  [lowerLeft setSize:&stoneSize];
  
  [(lowerRight = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
  [lowerRight useDrawMethod:@selector(drawLowerRight:) inObject:self];
  [lowerRight setSize:&stoneSize];
  
  [(midLeft = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
  [midLeft useDrawMethod:@selector(drawMidLeft:) inObject:self];
  [midLeft setSize:&stoneSize];
  
  [(midRight = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
  [midRight useDrawMethod:@selector(drawMidRight:) inObject:self];
  [midRight setSize:&stoneSize];
  
  [(midTop = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
  [midTop useDrawMethod:@selector(drawMidTop:) inObject:self];
  [midTop setSize:&stoneSize];
  
  [(midBottom = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
  [midBottom useDrawMethod:@selector(drawMidBottom:) inObject:self];
  [midBottom setSize:&stoneSize];
  
  [(innerSquare = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
  [innerSquare useDrawMethod:@selector(drawInnerSquare:) inObject:self];
  [innerSquare setSize:&stoneSize];
  
  [(innerHandicap = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
  [innerHandicap useDrawMethod:@selector(drawInnerHandicap:) inObject:self];
  [innerHandicap setSize:&stoneSize];
  
  [self setBackgroundFile:NXGetDefaultValue([NXApp appName], "BackGround") 
 andRemember:NO];
  
  [self startNewGame];
  
  return self;
}

// free simply gets rid of everything we created for MainGoView, including
  // the instance of MainGoView itself. This is how nice objects clean up.
  
  - free
{
  [backGround free];
  return [super free];
}



// This methods allows changing the file used to paint the background of the
  // playing field. Set fileName to NULL to revert to the default. Set
  // remember to YES if you wish the write the value out in the defaults.
  
  - setBackgroundFile:(const char *)fileName andRemember:(BOOL)remember
{
  [backGround free];
  backGround = [[NXImage allocFromZone:[self zone]] initSize:&gameSize];
  if (fileName) {
    [backGround useFromFile:fileName];
    if (remember) {
      NXWriteDefault ([NXApp appName], "Background", fileName);
    }
  } else {
    [backGround useFromSection:"Background.tiff"];
    if (remember) {
      NXRemoveDefault ([NXApp appName], "Background");
    }
  }
  [backGround setBackgroundColor:NX_COLORWHITE];
  [backGround setScalable:NO];
  [self display];
  
  return self;   
}

// The following two methods allow changing the background image from
  // menu items or buttons.
  
  - changeBackground:sender
{
  const char *const types[] = {"tiff", "eps", NULL};  
  
  if ([[OpenPanel new] runModalForTypes:types]) {
    [self setBackgroundFile:[[OpenPanel new] filename] andRemember:YES];
    [self display];
  }
  
  return self;
}

- revertBackground:sender
{
  [self setBackgroundFile:NULL andRemember:YES];
  [self display];
  return self;
}

// The following method will initialize all the variables for a new game.
  
  - startNewGame
{
  int i, j;
  
  gameRunning = NO;
  finished = NO;
  
  blackCaptured = whiteCaptured = 0;
  
  seed(&rd);
  
  for (i = 0; i < MAXX; i++)
    for (j = 0; j < MAXY; j++)
      p[i][j] = 0;
  
  for (i = 0; i < 9; i++)
    opn[i] = 1;
  opn[4] = 0;
  
  sethand(handicap);
  currentStone = (handicap == 0)?BLACKSTONE:WHITESTONE;
  opposingStone = (currentStone == BLACKSTONE)?WHITESTONE:BLACKSTONE;
  
  if (currentStone == BLACKSTONE)
    [gameMessage setStringValue:"Black's Turn"];
  else
    [gameMessage setStringValue:"White's Turn"];
    
  if (bothSides)
    [passButton setEnabled: NO];
  else
    [passButton setEnabled: YES];
  [passButton display];
  
  if (neitherSide) {
    [startButton setEnabled: NO];
    [stopButton setEnabled: NO];
    [startButton display];
    [stopButton display];
  } else {
    [startButton setEnabled: YES];
    [stopButton setEnabled: YES];
    [startButton display];
    [stopButton display];
  }
  
  if (((currentStone == BLACKSTONE) && (blackSide == 1)) ||
      ((currentStone == WHITESTONE) && (whiteSide == 1)))  
    [gameMessage2 setStringValue:"Press START to begin..."];
  else
    [gameMessage2 setStringValue:"You move first..."];
  
  return self;
}

// The stop method will pause a running game. The go method will start it up
  // again.
  
  - go:sender
{
  if ((gameRunning == 0) && (finished == 0)) {
    gameRunning = YES;
    [self step];
  }
  return self;
}

- stop:sender
{
  id menuList = [mainMenu itemList];
  int i, r, c;
  
  if (gameRunning) {
    [menuList getNumRows: &r numCols: &c];
    for( i = 0; i < r; i++ )
      [[menuList cellAt: i :0] setEnabled: YES];
    [mainMenu display];
    gameRunning = NO;
  }
  return self;
}

- mouseDown:(NXEvent *)event
{
  NXPoint pickedP;
  
  if (((currentStone == BLACKSTONE) && (blackSide == 1)) ||
      ((currentStone == WHITESTONE) && (whiteSide == 1)))
    return self;
    
  if ((!gameRunning) && (!finished))
    gameRunning = YES;
  
  if (!finished) {
    int x, y;
    pickedP = event->location;
    
    x = floor((pickedP.x - ((19.0 - MAXX)/2.0)*STONEWIDTH - BASEBOARDX - WINDOWOFFSETX + RADIUS)/STONEWIDTH);
    y = 18 - floor((pickedP.y - BASEBOARDY - WINDOWOFFSETY + RADIUS + ((19.0 - MAXY)/2.0)*STONEHEIGHT)/STONEHEIGHT);
    
    if (x < 0) x = 0;
    if (x > MAXX - 1) x = MAXX - 1;
    if (y < 0) y = 0;
    if (y > MAXY - 1) y = MAXY - 1;
    
    if ((p[x][y] == 0) && (!suicide(x,y))) {
      p[x][y] = currentStone;
      if (currentStone == BLACKSTONE)
	blackPassed = 0;
      else
	whitePassed = 0;
      
      setStoneLoc(x,y);
  //    stoneX = BASEBOARDX - RADIUS + (x*STONEWIDTH);
  //    stoneY = BASEBOARDY - RADIUS + ((18 - y)*STONEHEIGHT);
      
      //	    sprintf(s,"%d %d: %f %f",x,y,pickedP.x,pickedP.y);
      //	    [gameMessage setStringValue:s];
      
      [self lockFocus];
      
      switch (p[x][y]) {
      case WHITESTONE: [self showWhiteStone];
	break;
      case BLACKSTONE: [self showBlackStone];
	break;
      default: break;
      }
      
      [self unlockFocus];
      [self updateInfo];
      if (!neitherSide)
        [self step];
      
    } 
  } else {
    [self go:self];
  }
  
  return self;
}

- passMove
{
  if (((currentStone == BLACKSTONE) && (blackSide == 1)) ||
      ((currentStone == WHITESTONE) && (whiteSide == 1)))
    return self;

  if (currentStone == BLACKSTONE)
    blackPassed = 1;
  else
    whitePassed = 1;
    
  [self updateInfo];
  if (!neitherSide)
    [self step];
    
  return self;
}

- updateInfo
{
  char s[35], s2[150];
  int oldBlackPrisoners, oldWhitePrisoners;
  
  oldBlackPrisoners = blackCaptured;
  oldWhitePrisoners = whiteCaptured;
  
  examboard(opposingStone);
  
  if (currentStone == BLACKSTONE) {
    opposingStone = BLACKSTONE;
    currentStone = WHITESTONE;
    [gameMessage setStringValue:"White's Turn"];
  } else {
    opposingStone = WHITESTONE;
    currentStone = BLACKSTONE;
    [gameMessage setStringValue:"Black's Turn"];
  }
  
  sprintf(s,"%d",blackCaptured);
  [blackPrisoners setStringValue:s];
  
  sprintf(s,"%d",whiteCaptured);
  [whitePrisoners setStringValue:s];
  
  if ((oldBlackPrisoners != blackCaptured) ||
      (oldWhitePrisoners != whiteCaptured))
    {
      [self lockFocus];
      [[self window] flushWindow];
      [self drawSelf:&bounds :0];
      [self display];
      [self unlockFocus];
    }
  
  if ((blackPassed) && (opposingStone == BLACKSTONE))
    [gameMessage2 setStringValue:"Black has Passed."];
    
  if ((whitePassed) && (opposingStone == WHITESTONE))
    [gameMessage2 setStringValue:"White has Passed."];
    
  if ((!blackPassed) && (!whitePassed))
    [gameMessage2 setStringValue:""];
  
  if ((blackPassed) && (whitePassed)) {
    [self lockFocus];
    [[self window] flushWindow];
    [gameMessage setStringValue:"Scoring Game, Please Wait"];
    [gameMessage2 setStringValue:"Removing Dead Groups..."];
    [self display];
    [self unlockFocus];
    finished = 1;
    [self scoreGame];
    black_Score = (float)blackTerritory - (float)blackCaptured;
    white_Score = (float)whiteTerritory - (float)whiteCaptured;
    white_Score += (handicap == 0)?KOMI:0.5;
    if (black_Score > white_Score)
      sprintf(s, "Black has won by %3.1f points.", black_Score - white_Score);
    if (white_Score > black_Score)
      sprintf(s, "White has won by %3.1f points.", white_Score - black_Score);
    if (black_Score == white_Score)
      sprintf(s, "The game was a tie.");
    [gameMessage2 setStringValue:s];
    [gameMessage setStringValue:"Game Over"];
    [self lockFocus];
    [self display];
    [self unlockFocus];
    sprintf(s2, "Scoring:  Territory - Captured + Komi = Score\nBlack          %5d           %5d          %3.1f        %3.1f\nWhite          %5d          %5d           %3.1f        %3.1f", blackTerritory, blackCaptured, 0.0, black_Score, whiteTerritory, whiteCaptured, (handicap == 0)?KOMI:0.5, white_Score);
    NXRunAlertPanel("Scoring Summary", s2, "OK", NULL, NULL);
  }
  
  return self;
}

- scoreGame
{
  int i, j, k, l, changes = 1, num_in_pattern;

  for (i = 0; i < MAXX; i++)
    for (j = 0; j < MAXY; j++)
      scoringmat[i][j] = 0;
      
  while (changes)
    {
      changes = 0;
      find_owner();

      for (i = 0; i < MAXX; i++)
	for (j = 0; j < MAXY; j++)
	  if ((p[i][j] != 0) && (scoringmat[i][j] == 0))
	    {
	      if (surrounds_territory(i, j))
		{
		  find_pattern_in_board(i, j);

		  for (k = 0; k < MAXX; k++)
		    for (l = 0; l < MAXY; l++)
		      if (patternmat[k][l])
			scoringmat[k][l] = p[k][l];
		}
	      else
		{
		  find_pattern_in_board(i, j);
		  set_temp_to_p();
		  num_in_pattern = 0;

		  for (k = 0; k < MAXX; k++)
		    for (l = 0; l < MAXY; l++)
		      if (patternmat[k][l])
			{
			  p[k][l] = 0;
			  [self flashStone:k:l];
			  num_in_pattern++;
			}

		  find_owner();

		  if ((ownermat[i][j] != -1) && (ownermat[i][j] != tempmat[i][j]))
		    {
		      if (tempmat[i][j] == 2)
			blackCaptured += num_in_pattern;
		      else
			whiteCaptured += num_in_pattern;
		      changes++;
		      [self lockFocus];
		      [[self window] flushWindow];
		      [self drawSelf:&bounds :0];
		      [self display];
		      [self unlockFocus];
		    }
		  else
		    {
		      set_p_to_temp();
		      find_owner();
		    }
		}
	    }
    }

  blackTerritory = 0;
  whiteTerritory = 0;

  [self lockFocus];
  for (i = 0; i < MAXX; i++)
    for (j = 0; j < MAXY; j++)
      {
	if (ownermat[i][j] == BLACKSTONE)
	  blackTerritory++;
	if (ownermat[i][j] == WHITESTONE)
	  whiteTerritory++;
	if (ownermat[i][j] == -1)
	  [self flashStone:i:j];
      }
  [self unlockFocus];

  return self;
}

// The following methods draw the pieces.
  
  - drawBlackStone:imageRep 
{
  //    PSscale (1.0, 1.0);
  
  // First draw the shadow under the stone.
    
//    PSarc (RADIUS+SHADOWOFFSET/2, RADIUS-SHADOWOFFSET/2, 
//	   RADIUS-SHADOWOFFSET, 0.0, 360.0);
//  PSsetgray (NX_DKGRAY);
//  if (NXDrawingStatus == NX_DRAWING) {
//    PSsetalpha (0.666);
//  }
//  PSfill ();
  if (NXDrawingStatus == NX_DRAWING) {
    PSsetalpha (1.0);
  }
  
  // Draw the stone.
    
    PSarc (RADIUS, RADIUS, 
	   RADIUS, 0.0, 360.0);
  PSsetgray (NX_BLACK);
  PSfill ();
  
  // And the lighter & darker spots on the stone...
    
    PSarcn (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
	    RADIUS-SHADOWOFFSET-3.0, 170.0, 100.0);
  PSarc (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
	 RADIUS-SHADOWOFFSET-2.0, 100.0, 170.0);
  PSsetgray (NX_DKGRAY);
  PSfill ();
  PSarcn (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
	  RADIUS-SHADOWOFFSET-2.0, 350.0, 280.0);
  PSarc (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
	 RADIUS-SHADOWOFFSET-2.0, 280.0, 350.0);
  PSsetgray (NX_LTGRAY);
  PSfill ();
  
  return self;
}

- drawWhiteStone:imageRep 
{
  //    PSscale (1.0, 1.0);
  
  // First draw the shadow under the stone.
    
//    PSarc (RADIUS+SHADOWOFFSET/2, RADIUS-SHADOWOFFSET/2, 
//	   RADIUS-SHADOWOFFSET, 0.0, 360.0);
//  PSsetgray (NX_DKGRAY);
//  if (NXDrawingStatus == NX_DRAWING) {
//    PSsetalpha (0.666);
//  }
//  PSfill ();
  if (NXDrawingStatus == NX_DRAWING) {
    PSsetalpha (1.0);
  }
  
  // Draw the stone.
    
    PSarc (RADIUS, RADIUS, 
	   RADIUS, 0.0, 360.0);
  PSsetgray (NX_WHITE);
  PSfill ();
  
  // And the lighter & darker spots on the stone...
    
    PSarcn (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
	    RADIUS-SHADOWOFFSET-3.0, 170.0, 100.0);
  PSarc (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
	 RADIUS-SHADOWOFFSET-2.0, 100.0, 170.0);
  PSsetgray (NX_LTGRAY);
  PSfill ();
  PSarcn (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
	  RADIUS-SHADOWOFFSET-2.0, 350.0, 280.0);
  PSarc (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
	 RADIUS-SHADOWOFFSET-2.0, 280.0, 350.0);
  PSsetgray (NX_DKGRAY);
  PSfill ();
  
  return self;
}

- drawGrayStone:imageRep 
{
  //    PSscale (1.0, 1.0);
  
  // First draw the shadow under the stone.
    
//    PSarc (RADIUS+SHADOWOFFSET/2, RADIUS-SHADOWOFFSET/2, 
//	   RADIUS-SHADOWOFFSET, 0.0, 360.0);
//  PSsetgray (NX_DKGRAY);
//  if (NXDrawingStatus == NX_DRAWING) {
//    PSsetalpha (0.666);
//  }
//  PSfill ();
  if (NXDrawingStatus == NX_DRAWING) {
    PSsetalpha (1.0);
  }
  
  // Draw the stone.
    
//    PSarc (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
//	   RADIUS-SHADOWOFFSET, 0.0, 360.0);
    PSarc (RADIUS, RADIUS, 
	   RADIUS, 0.0, 360.0);
  PSsetgray (NX_DKGRAY);
  PSfill ();
  
  // And the lighter & darker spots on the stone...
    
    PSarcn (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
	    RADIUS-SHADOWOFFSET-3.0, 170.0, 100.0);
  PSarc (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
	 RADIUS-SHADOWOFFSET-2.0, 100.0, 170.0);
  PSsetgray (NX_LTGRAY);
  PSfill ();
  PSarcn (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
	  RADIUS-SHADOWOFFSET-2.0, 350.0, 280.0);
  PSarc (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
	 RADIUS-SHADOWOFFSET-2.0, 280.0, 350.0);
  PSsetgray (NX_WHITE);
  PSfill ();
  
  return self;
}

- drawUpperLeft:imageRep
{
  PSsetgray(NX_BLACK);
  PSsetlinewidth(0.0);
  if (NXDrawingStatus == NX_DRAWING) {
    PSsetalpha (1.0);
  }

  PSnewpath();
  PSmoveto(RADIUS, RADIUS);
  PSlineto(bounds.size.width,RADIUS);
  PSmoveto(RADIUS, RADIUS);
  PSlineto(RADIUS, 0.0);
  PSmoveto(RADIUS-SHADOWOFFSET, RADIUS+SHADOWOFFSET);
  PSlineto(bounds.size.width, RADIUS+SHADOWOFFSET);
  PSmoveto(RADIUS-SHADOWOFFSET, RADIUS+SHADOWOFFSET);
  PSlineto(RADIUS-SHADOWOFFSET, 0.0);
  PSstroke();

  return self;
}

- drawUpperRight:imageRep
{
  PSsetgray(NX_BLACK);
  PSsetlinewidth(0.0);
  if (NXDrawingStatus == NX_DRAWING) {
    PSsetalpha (1.0);
  }

  PSnewpath();
  PSmoveto(RADIUS, RADIUS);
  PSlineto(0.0,RADIUS);
  PSmoveto(RADIUS, RADIUS);
  PSlineto(RADIUS, 0.0);
  PSmoveto(RADIUS+SHADOWOFFSET, RADIUS+SHADOWOFFSET);
  PSlineto(0.0, RADIUS+SHADOWOFFSET);
  PSmoveto(RADIUS+SHADOWOFFSET, RADIUS+SHADOWOFFSET);
  PSlineto(RADIUS+SHADOWOFFSET, 0.0);
  PSstroke();

  return self;
}

- drawLowerLeft:imageRep
{
  PSsetgray(NX_BLACK);
  PSsetlinewidth(0.0);
  if (NXDrawingStatus == NX_DRAWING) {
    PSsetalpha (1.0);
  }

  PSnewpath();
  PSmoveto(RADIUS, RADIUS);
  PSlineto(bounds.size.width,RADIUS);
  PSmoveto(RADIUS, RADIUS);
  PSlineto(RADIUS, bounds.size.height);
  PSmoveto(RADIUS-SHADOWOFFSET, RADIUS-SHADOWOFFSET);
  PSlineto(bounds.size.width, RADIUS-SHADOWOFFSET);
  PSmoveto(RADIUS-SHADOWOFFSET, RADIUS-SHADOWOFFSET);
  PSlineto(RADIUS-SHADOWOFFSET, bounds.size.height);
  PSstroke();

  return self;
}

- drawLowerRight:imageRep
{
  PSsetgray(NX_BLACK);
  PSsetlinewidth(0.0);
  if (NXDrawingStatus == NX_DRAWING) {
    PSsetalpha (1.0);
  }

  PSnewpath();
  PSmoveto(RADIUS, RADIUS);
  PSlineto(0.0,RADIUS);
  PSmoveto(RADIUS, RADIUS);
  PSlineto(RADIUS, bounds.size.height);
  PSmoveto(RADIUS+SHADOWOFFSET, RADIUS-SHADOWOFFSET);
  PSlineto(0.0, RADIUS-SHADOWOFFSET);
  PSmoveto(RADIUS+SHADOWOFFSET, RADIUS-SHADOWOFFSET);
  PSlineto(RADIUS+SHADOWOFFSET, bounds.size.height);
  PSstroke();

  return self;
}

- drawMidLeft:imageRep
{
  PSsetgray(NX_BLACK);
  PSsetlinewidth(0.0);
  if (NXDrawingStatus == NX_DRAWING) {
    PSsetalpha (1.0);
  }

  PSnewpath();
  PSmoveto(RADIUS, RADIUS);
  PSlineto(bounds.size.width,RADIUS);
  PSmoveto(RADIUS, bounds.size.height);
  PSlineto(RADIUS, 0.0);
  PSmoveto(RADIUS-SHADOWOFFSET, bounds.size.height);
  PSlineto(RADIUS-SHADOWOFFSET, 0.0);
  PSstroke();

  return self;
}

- drawMidRight:imageRep
{
  PSsetgray(NX_BLACK);
  PSsetlinewidth(0.0);
  if (NXDrawingStatus == NX_DRAWING) {
    PSsetalpha (1.0);
  }

  PSnewpath();
  PSmoveto(RADIUS, RADIUS);
  PSlineto(0.0,RADIUS);
  PSmoveto(RADIUS, bounds.size.height);
  PSlineto(RADIUS, 0.0);
  PSmoveto(RADIUS+SHADOWOFFSET, bounds.size.height);
  PSlineto(RADIUS+SHADOWOFFSET, 0.0);
  PSstroke();

  return self;
}

- drawMidTop:imageRep
{
  PSsetgray(NX_BLACK);
  PSsetlinewidth(0.0);
  if (NXDrawingStatus == NX_DRAWING) {
    PSsetalpha (1.0);
  }

  PSnewpath();
  PSmoveto(RADIUS, RADIUS);
  PSlineto(RADIUS,0.0);
  PSmoveto(0.0, RADIUS);
  PSlineto(bounds.size.width, RADIUS);
  PSmoveto(0.0, RADIUS+SHADOWOFFSET);
  PSlineto(bounds.size.width, RADIUS+SHADOWOFFSET);
  PSstroke();

  return self;
}

- drawMidBottom:imageRep
{
  PSsetgray(NX_BLACK);
  PSsetlinewidth(0.0);
  if (NXDrawingStatus == NX_DRAWING) {
    PSsetalpha (1.0);
  }

  PSnewpath();
  PSmoveto(RADIUS, RADIUS);
  PSlineto(RADIUS,bounds.size.height);
  PSmoveto(0.0, RADIUS);
  PSlineto(bounds.size.width, RADIUS);
  PSmoveto(0.0, RADIUS-SHADOWOFFSET);
  PSlineto(bounds.size.width, RADIUS-SHADOWOFFSET);
  PSstroke();

  return self;
}

- drawInnerSquare:imageRep
{
  PSsetgray(NX_BLACK);
  PSsetlinewidth(0.0);
  if (NXDrawingStatus == NX_DRAWING) {
    PSsetalpha (1.0);
  }

  PSnewpath();
  PSmoveto(0.0, RADIUS);
  PSlineto(bounds.size.width,RADIUS);
  PSmoveto(RADIUS, bounds.size.height);
  PSlineto(RADIUS, 0.0);
  PSstroke();

  return self;
}

- drawInnerHandicap:imageRep
{
  PSsetgray(NX_BLACK);
  PSsetlinewidth(0.0);
  if (NXDrawingStatus == NX_DRAWING) {
    PSsetalpha (1.0);
  }

  PSnewpath();
  PSmoveto(0.0, RADIUS);
  PSlineto(bounds.size.width,RADIUS);
  PSmoveto(RADIUS, bounds.size.height);
  PSlineto(RADIUS, 0.0);
  PSstroke();
  
  PSarc(RADIUS, RADIUS, SHADOWOFFSET, 0.0, 360.0);
  PSfill();

  return self;
}

// The following methods show or erase the stones from the board.
  
  - showBlackStone 
{
  NXRect tmpRect = {{floor(stoneX), floor(stoneY)},
		      {floor(STONEWIDTH), floor(STONEHEIGHT)}};
  [blackStone composite:NX_SOVER toPoint:&tmpRect.origin];
  return self;
}

- showWhiteStone
{
  NXRect tmpRect = {{floor(stoneX), floor(stoneY)},
		      {floor(STONEWIDTH), floor(STONEHEIGHT)}};
  [whiteStone composite:NX_SOVER toPoint:&tmpRect.origin];
  return self;
}

- showGrayStone
{
  NXRect tmpRect = {{floor(stoneX), floor(stoneY)},
		      {floor(STONEWIDTH), floor(STONEHEIGHT)}};
  [grayStone composite:NX_SOVER toPoint:&tmpRect.origin];
  return self;
}

- eraseStone
{
  NXRect tmpRect = {{floor(stoneX), floor(stoneY)}, {floor(STONEWIDTH), floor(STONEHEIGHT)}};
  return [self drawBackground:&tmpRect];
}

// drawBackground: just draws the specified piece of the background by
  // compositing from the background image.

  - showBackgroundPiece: (int)x: (int)y
{
  int q;
  NXRect tmpRect = {{floor(stoneX), floor(stoneY)}, {floor(STONEWIDTH), floor(STONEHEIGHT)}};

  if ((x == 0) && (y == 0))
    [upperLeft composite:NX_SOVER toPoint:&tmpRect.origin];
  
  if ((x == 0) && (y == MAXY - 1))
    [lowerLeft composite:NX_SOVER toPoint:&tmpRect.origin];
  
  if ((x == MAXX - 1) && (y == 0))
    [upperRight composite:NX_SOVER toPoint:&tmpRect.origin];
  
  if ((x == MAXX - 1) && (y == MAXY - 1))
    [lowerRight composite:NX_SOVER toPoint:&tmpRect.origin];
  
  if ((x == 0) && (y > 0) && (y < MAXY - 1))
    [midLeft composite:NX_SOVER toPoint:&tmpRect.origin];
  
  if ((x == MAXX - 1) && (y > 0) && (y < MAXY - 1))
    [midRight composite:NX_SOVER toPoint:&tmpRect.origin];
  
  if ((x > 0) && (x < MAXX - 1) && (y == 0))
    [midTop composite:NX_SOVER toPoint:&tmpRect.origin];
  
  if ((x > 0) && (x < MAXX - 1) && (y == MAXY - 1))
    [midBottom composite:NX_SOVER toPoint:&tmpRect.origin];
  
  if ((x > 0) && (x < MAXX - 1) && (y > 0) && (y < MAXY - 1))
    [innerSquare composite:NX_SOVER toPoint:&tmpRect.origin];
    
  if (MAXX < 13)
    q = 2;
  else
    q = 3;
  
  if (((x == q) && (y == q)) || ((x == q) && (y == MAXY/2)) ||
      ((x == q) && (y == MAXY-q-1)) || ((x == MAXX/2) && (y == q)) ||
      ((x == MAXX/2) && (y == MAXY/2)) || ((x == MAXX/2) && (y == MAXY-q-1)) ||
      ((x == MAXX-q-1) && (y == q)) || ((x == MAXX-q-1) && (y == MAXY/2)) ||
      ((x == MAXX-q-1) && (y == MAXY-q-1)))
    [innerHandicap composite:NX_SOVER toPoint:&tmpRect.origin];

  return self;
}
  
  - drawBackground:(NXRect *)rect
{
  NXRect tmpRect = *rect;
  
  NX_X(&tmpRect) = floor(NX_X(&tmpRect));
  NX_Y(&tmpRect) = floor(NX_Y(&tmpRect));
  if (NXDrawingStatus == NX_DRAWING) {
    PSsetgray (NX_WHITE);
    PScompositerect (NX_X(&tmpRect), NX_Y(&tmpRect),
		     NX_WIDTH(&tmpRect), NX_HEIGHT(&tmpRect), NX_COPY);
  }
  [backGround composite:NX_SOVER fromRect:&tmpRect toPoint:&tmpRect.origin];
  return self;
}

// drawSelf::, a method every decent View should have, redraws the game
  // in its current state. This allows us to print the game very easily.
  
  - drawSelf:(NXRect *)rects :(int)rectCount 
{
  int xcnt, ycnt;
  
  [self drawBackground:(rects ? rects : &bounds)];
  
  for (xcnt = 0; xcnt < MAXX; xcnt++) { 
    for (ycnt = 0; ycnt < MAXY; ycnt++) {
      setStoneLoc(xcnt, ycnt);
 //     stoneX = BASEBOARDX - RADIUS + (xcnt*STONEWIDTH);
 //     stoneY = BASEBOARDY - RADIUS + ((18 - ycnt)*STONEHEIGHT);
      switch (p[xcnt][ycnt]) {
      case EMPTY: [self showBackgroundPiece: xcnt: ycnt];
	break;
      case WHITESTONE: [self showWhiteStone];
	break;
      case BLACKSTONE: [self showBlackStone];
	break;
      default: break;
      }
    }
  }
  
  return self;
}

- print:sender
{
  return [self printPSCode:sender];
}

- step
{
  id menuList = [mainMenu itemList];
  int i, r, c;
  
  if (neitherSide)
    return self;
    
  if (((currentStone == BLACKSTONE) && (blackSide == 0)) ||
      ((currentStone == WHITESTONE) && (whiteSide == 0)))
    return self;
  
  if (bothSides) {
    while ((gameRunning) && (!finished)) {
      [self selectMove];
      NXPing();
      [self updateInfo];
    }
  
    NXPing();

    [menuList getNumRows: &r numCols: &c];
    for( i = 0; i < r; i++ )
      [[menuList cellAt: i :0] setEnabled: YES];
    [mainMenu display];
  } else {
    [passButton setEnabled: NO];
    [passButton display];
    [self selectMove];
    [self updateInfo];
    [passButton setEnabled: YES];
    [passButton display];
  }
  return self;
}

- selectMove
{
  id menuList = [mainMenu itemList];
  int i, j, r, c;
  
  NXPing();
  
  [menuList getNumRows: &r numCols: &c];
  for( i = 0; i < r-1; i++ )
    [[menuList cellAt: i :0] setEnabled: NO];
  [mainMenu display];
  if( !bothSides )
    [stopButton setEnabled: NO];
  else
    [stopButton setEnabled: YES];
  
  genmove( &i, &j );
  if (i >= 0)
    {
      p[i][j] = currentStone;
      [self flashStone: i: j];
    }

  [self selectMoveEnd];
  
  if (i >= 0)
    {
      [self lockFocus];
      if (currentStone == BLACKSTONE)
        [self showBlackStone];
      else
        [self showWhiteStone];
      [self unlockFocus];
    }
  
  NXPing();
  return self;
}

- selectMoveEnd
{
  id menuList = [mainMenu itemList];
  int i, r, c;
  NXEvent peek_ev, *get_ev;
  
  NXPing();

  [menuList getNumRows: &r numCols: &c];
  if( !bothSides )
    for( i = 0; i < r; i++ )
      [[menuList cellAt: i :0] setEnabled: YES];
  [mainMenu display];
  [startButton setEnabled: YES];
  [stopButton setEnabled: YES];
  if( [NXApp peekNextEvent: NX_MOUSEDOWNMASK into: &peek_ev] ){
    get_ev = [NXApp getNextEvent: NX_MOUSEDOWNMASK];
    [NXApp sendEvent: get_ev];
  }
  NXPing();

  return self;
}

- flashStone: (int)x :(int)y
{
  
  setStoneLoc(x, y);
//  stoneX = BASEBOARDX - RADIUS + (x*STONEWIDTH);
//  stoneY = BASEBOARDY - RADIUS + ((18 - y)*STONEHEIGHT);
  
  [self lockFocus];
  [self showGrayStone];
  [self unlockFocus];
  
  return self;
}


@end

