Board3D.m   [plain text]


//#import <AppKit/AppKit.h>

#import <AppKit/NSImage.h>
#import <AppKit/NSPrintInfo.h>
#import <AppKit/NSBitmapImageRep.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSEvent.h>
#import <AppKit/NSGraphics.h>		// NSBeep
#import <Foundation/NSBundle.h>
#import <Foundation/NSGeometry.h>	// NSMakeSize, NSZeroPoint
#import <Foundation/NSUtilities.h>	// MIN, MAX, ABS
#import <Foundation/NSException.h>

#include <math.h>

// own interface
#import "Board3D.h"

// messaging objects
#import "Square3D.h"
#import "Chess.h"			// NSApp

// portability layer
#import "gnuglue.h"			// floor_value, sleep_microsecs, ...

// piece size
#define PIECE_WIDTH_3D		(float)55.0
#define PIECE_HEIGHT_3D		(float)95.0

// back store size
#define BACK_STORE_WIDTH	(float)80.0
#define BACK_STORE_HEIGHT	(float)110.0

/*  Each set of points describes the vertical lines along the board. */
struct NXLine {
  NSPoint a, b;
};

#define BASE_X 89

static struct NXLine vertical[] = {
  {{BASE_X,122},    {BASE_X+61, 467}},
  {{BASE_X+71,122}, {BASE_X+111,467}},
  {{BASE_X+135,122},{BASE_X+162,467}},
  {{BASE_X+200,122},{BASE_X+212,467}},
  {{BASE_X+265,122},{BASE_X+265,467}},
  {{BASE_X+330,122},{BASE_X+316,467}},
  {{BASE_X+393,122},{BASE_X+367,467}},
  {{BASE_X+459,122},{BASE_X+419,467}},
  {{BASE_X+524,122},{BASE_X+469,467}}
};

/* Each coordinate describes the y value of each line of the board. */
#define BASE_Y  132

static float horizontal[] = {
  BASE_Y, BASE_Y+54, BASE_Y+106, BASE_Y+153,
  BASE_Y+196, BASE_Y+237, BASE_Y+277, BASE_Y+312, BASE_Y+345
};


// private functions

static void squareOrigin( int r, int c, float *x, float *y )
{
    float dx, m, b;

    dx = (vertical[c].a.x -  vertical[c].b.x);
    m  = (vertical[c].a.y -  vertical[c].b.y) / dx;
    b  =  vertical[c].b.y - (vertical[c].b.x  * m);
    *x = (dx) ? ((horizontal[r] - b) / m) : vertical[c].a.x;
    *y = horizontal[r];
    return;
}

static void squareBounds( int r, int c, NSPoint *p1, NSPoint *p2, NSPoint *p3, NSPoint *p4 )
/*
   (p2)----(p4)
    |        |
    |        |
   (p1)----(p3)
*/
{
    float dx, m, b;

    dx = (vertical[c].a.x -  vertical[c].b.x);
    m  = (vertical[c].a.y -  vertical[c].b.y) / dx;
    b  =  vertical[c].b.y - (vertical[c].b.x  * m);
    p1->x = (dx) ? ((horizontal[r] - b) / m) : vertical[c].a.x;
    p1->x = (float) floor_value( (double)p1->x );
    p1->y = (float) floor_value( (double)horizontal[r] );

    p2->x = (dx) ? ((horizontal[r+1] - b) / m) : vertical[c].a.x;
    p2->x = (float) floor_value( (double)p2->x );
    p2->y = (float) floor_value( (double)horizontal[r+1] );

    dx = (vertical[c+1].a.x -  vertical[c+1].b.x);
    m  = (vertical[c+1].a.y -  vertical[c+1].b.y) / dx;
    b  =  vertical[c+1].b.y - (vertical[c+1].b.x  * m);
    p3->x = (dx) ? ((horizontal[r] - b) / m) : vertical[c+1].a.x;
    p3->x = (float) floor_value( (double)p3->x );
    p3->y = (float) floor_value( (double)horizontal[r] );

    p4->x = (dx) ? ((horizontal[r+1] - b) / m) : vertical[c+1].a.x;
    p4->x = (float) floor_value( (double)p4->x );
    p4->y = (float) floor_value( (double)horizontal[r+1] );

    return;
}

static float check_point( struct NXLine *l, NSPoint *p )
{
    float dx  = l->a.x - l->b.x;
    float dy  = l->a.y - l->b.y;
    float dx1 = p->x   - l->a.x;
    float dy1 = p->y   - l->a.y;
    return( dx*dy1 - dy*dx1 );
}

static void convert_point( NSPoint *p, int *r, int *c )
{
    int i;
    for( i = 0; i < 8; i++ ) {
        if( p->y >= horizontal[i] && p->y <= horizontal[i+1] ) {
            *r = i;
            break;
        }
    }
    for( i = 0; i < 8; i++ ) {
        float m1 = check_point( &vertical[i], p );
        float m2 = check_point( &vertical[i+1], p );
        if( m1 > 0 && m2 < 0 ) {
            *c = i;
            break;
        }
    }
    return;
}

// Board3D implementations

@implementation Board3D

- (id)initWithFrame: (NSRect)f
{
    self = [super initWithFrame: f];
    if( self ) {
        NSBundle  *bundle;
        NSString  *path1, *path2;
        NSSize  size;
        int r, c;

        [self allocateGState];
        bundle = [NSBundle mainBundle];
        path1 = [bundle pathForImageResource: @"3d_board"];
        _background = [[NSImage alloc] initWithContentsOfFile: path1];
        path2 = [bundle pathForImageResource: @"3d_pieces"];
        _pieces = [[NSImage alloc] initWithContentsOfFile: path2];
        size = NSMakeSize( BACK_STORE_WIDTH, BACK_STORE_HEIGHT );
        backBitmap = [[NSImage alloc] initWithSize: size];

        for( r = 0; r < 8; r++ ) {
            for( c = 0; c < 8; c++ )
                square[r][c] = [[Square3D alloc] init];
        }
        [self setupPieces];
        return self;
    }
    return nil;
}

- (void)setBackgroundBitmap: (NSImage *) bitmap
{
    if( _background )
        [_background release];
    _background = [bitmap retain];
    return;
}

- (id) backgroundBitmap
{
    return _background;
}

- (void)setPiecesBitmap: (NSImage *) bitmap
{
    if( _pieces )
        [_pieces release];
    _pieces = [bitmap retain];
    return;
}

- (NSImage *)piecesBitmap
{
    return _pieces;
}

- (void)setupPieces
{
    short  *pieces = default_pieces();
    short  *colors = default_colors();
    [self layoutBoard: pieces color: colors];
    return;
}

- (void)layoutBoard: (short *)p color: (short *)c
{
    int  sq;
    PSgsave();
    PSrotate( (float)10.0 );
    for( sq = 0; sq < SQUARE_COUNT; sq++ ) {
        int  row = sq / 8;
        int  col = sq % 8;
        [self placePiece: p[sq] at: row: col color: c[sq]];
    }
    PSgrestore();
    return;
}

- (void)placePiece:  (short)p at: (int)row : (int)col color: (short)c
{
    int  col2;
    float  m, b, dx, x;
    NSRect  loc;
    Square3D  *theSquare = square[row][col];

    [theSquare setPieceType: p color: c];
    [theSquare setRow: row];
    dx = (vertical[col].a.x -  vertical[col].b.x);
    m  = (vertical[col].a.y -  vertical[col].b.y) / dx;
    b  =  vertical[col].b.y - (vertical[col].b.x  * m);
    x  = (dx) ? ((horizontal[row] - b) / m) : vertical[col].a.x;
    loc.origin.x = x;
    loc.origin.y = horizontal[row];

    col2 = col + 1;
    dx = (vertical[col2].a.x -  vertical[col2].b.x);
    m  = (vertical[col2].a.y -  vertical[col2].b.y) / dx;
    b  =  vertical[col2].b.y - (vertical[col2].b.x  * m);
    x  = (dx) ? ((horizontal[row] - b) / m) : vertical[col2].a.x;
    loc.size.width  = x - loc.origin.x;
    loc.size.height = 99999;

    [theSquare setLocation: loc];
    return;
}

- (void)slidePieceFrom: (int)row1 : (int)col1 to: (int)row2 : (int)col2
{
    Square3D  *theSquare;
    int  pieceType, color;
    NSRect  oldLocation;
    NSPoint  backP, endP, roundedBackP;
    int  controlGState;
    float  incX, incY;
    int  increments, i;

    theSquare = square[row1][col1];
    pieceType = [theSquare pieceType];
    if( ! pieceType )
        return;
    color = [theSquare colorVal];
    oldLocation = [theSquare location];

    squareOrigin( row2, col2, &endP.x, &endP.y );

    /* Remove piece and then save background */
    [theSquare setPieceType: NO_PIECE color: NEUTRAL];
    [self drawRect: [self frame]];

    squareOrigin( row1, col1, &backP.x, &backP.y );
    controlGState = [self gState];

    [backBitmap lockFocus];
    PSgsave();
    PScomposite( roundedBackP.x = floor(backP.x), roundedBackP.y = floor(backP.y), 
				 BACK_STORE_WIDTH, BACK_STORE_HEIGHT, controlGState, 
				 (float)0.0, (float)0.0, NSCompositeCopy );
    PSgrestore();
    [backBitmap unlockFocus];   

    [self lockFocus];
    [theSquare setPieceType: pieceType color: color];
    [theSquare drawInteriorWithFrame: [self frame] inView: self];
    [theSquare setMoving: YES];
    [[self window] flushWindow];

    incX = endP.x - backP.x;
    incY = endP.y - backP.y;
    increments = (int) MAX( ABS(incX), ABS(incY) ) / 7;	// was 5 gcr
    incX = incX / increments;
    incY = incY / increments;

    for( i = 0; i < increments; i++ ) {
        int  dr, dc;
        NSRect  newLocation;

        /* Restore old background */
        [self lockFocus];
        [backBitmap compositeToPoint: roundedBackP operation: NSCompositeCopy];
        [self unlockFocus];   

        backP.x += incX;
        backP.y += incY;
        convert_point( &backP, &dr, &dc );

        /* Save new background */
        [backBitmap lockFocus];
        PSgsave();
		PScomposite( roundedBackP.x = floor(backP.x), roundedBackP.y = floor(backP.y), 
					 BACK_STORE_WIDTH, BACK_STORE_HEIGHT, controlGState, 
					 (float)0.0, (float)0.0, NSCompositeCopy );
        PSgrestore();
        [backBitmap unlockFocus];     

        /* Draw piece at new location. */
        [theSquare setRow: dr];
        newLocation.origin = backP;
        newLocation.size   = NSMakeSize( PIECE_WIDTH_3D, PIECE_HEIGHT_3D );
        [theSquare setLocation: newLocation];
        [theSquare drawInteriorWithFrame: [self frame] inView: self];
        [[self window] flushWindow];
    }

    [theSquare setMoving: NO];
    [self unlockFocus];
    return;
}

- (int) pieceAt: (int)row : (int)col
{
    if( row >= 0 && col >= 0 ) {
        Square3D  *theSquare = square[row][col];
        return [theSquare pieceType];
    }
    return (int)NO_PIECE;
}

- (int) colorAt: (int)row : (int)col
{
    if( row >= 0 && col >= 0 ) {
        Square3D  *theSquare = square[row][col];
        return [theSquare colorVal];
    }
    return (int)NEUTRAL;
}

- (void) highlightSquareAt: (int)row : (int)col
{
    NSPoint  p1, p2, p3, p4;
    int  idx;

    squareBounds( row, col, &p1, &p2, &p3, &p4 );
    [self lockFocus];

    PSgsave();
    PSsetlinewidth( (float)3.0 );
    PSmoveto( p1.x, p1.y );
    PSlineto( p2.x, p2.y );
    PSlineto( p4.x, p4.y );
    PSlineto( p3.x, p3.y );
    PSlineto( p1.x, p1.y );
    PSclosepath();

    /* flash 2 times */
    for( idx = 1; idx <= 3; idx++ ) {
        float  color = NSWhite;
        //	float  color = NSWhite - [self colorAt: row : col];	// ??
        PSsetgray( color );
        PSgsave();
        PSstroke();
        PSgrestore();
        if( [self pieceAt: row : col]
            || (row > 0 && [self pieceAt: row-1 : col]) )
            [self drawRows: row from: col];
        [[self window] flushWindow];
        if( ! [square[row][col] isMoving] )
            sleep_microsecs( (unsigned)15000 );
    }
    PSgrestore();
    [self unhighlightSquareAt: row : col];

    [self unlockFocus];
    return;
}

- (void) unhighlightSquareAt: (int)row : (int)col
{
    NSPoint p1, p2, p3, p4, to;
    NSRect backR;

    squareBounds( row, col, &p1, &p2, &p3, &p4 );
    p1.x = p1.x - 3;
    p1.y = p1.y - 3;
    p2.x = p2.x - 3;
    p2.y = p2.y + 3;
    p3.x = p3.x + 3;
    p3.y = p3.y - 3;
    p4.x = p4.x + 3;
    p4.y = p4.y + 3;

    to.x = MIN( p1.x, p2.x );
    to.y = p1.y;

    backR.origin = to;
    backR.size.width  = MAX( p3.x, p4.x ) - to.x;
    backR.size.height = p2.y - p1.y;

    [self lockFocus];
    PSgsave();
    PSsetlinewidth( (float)3.0 );
    PSsetgray( NSWhite );
    PSnewpath();
    PSmoveto( p1.x, p1.y );
    PSlineto( p2.x, p2.y );
    PSlineto( p4.x, p4.y );
    PSlineto( p3.x, p3.y );
    PSlineto( p1.x, p1.y );
    PSclosepath();
    PSclip();

    [_background compositeToPoint:to fromRect:backR operation:NSCompositeCopy];
    PSgrestore();
    if( [self pieceAt: row : col] || (row > 0 && [self pieceAt: row-1 : col]) )
        [self drawRows: row from: col];
    [[self window] flushWindow];

    [self unlockFocus];
    return;
}

- (void) flashSquareAt: (int)row : (int)col
{
    NSPoint p1, p2, p3, p4;

    squareBounds( row, col, &p1, &p2, &p3, &p4 );
    [self lockFocus];

    PSgsave();
    PSsetlinewidth( (float)3.0 );
    PSsetgray( NSWhite );
    PSnewpath();
    PSmoveto( p1.x, p1.y );
    PSlineto( p2.x, p2.y );
    PSlineto( p4.x, p4.y );
    PSlineto( p3.x, p3.y );
    PSlineto( p1.x, p1.y );
    PSclosepath();
    PSstroke();
    PSgrestore();

    if( [self pieceAt: row : col] || (row > 0 && [self pieceAt: row-1 : col]) )
        [self drawRows: row from: col];

    [self unlockFocus];
    return;
}

- (void) drawRows: (int)row from: (int)col
{
    while( row >= 0 ) {
        Square3D  *theSquare = square[row][col];
        if( [self pieceAt: row : col] && ! [theSquare isMoving] )
            [theSquare drawInteriorWithFrame: [self frame] inView: self];
        row--;
    }
    return;
}

- (void) print: (id)sender
{
    NSPrintInfo	*pi = [NSPrintInfo sharedPrintInfo];
    NSSize	ps  = [pi paperSize];
    NSSize	fs  = [self frame].size;
    float	hm  = (ps.width  - fs.width)  / 2.0;
    float	vm  = (ps.height - fs.height) / 2.0;

    [pi setLeftMargin: hm];
    [pi setRightMargin: hm];
    [pi setTopMargin: vm];
    [pi setBottomMargin: vm];

    [self lockFocus];
    printImage = [[NSBitmapImageRep alloc] initWithFocusedViewRect: [self bounds]];
    [self unlockFocus];

    [super print: sender];
    [printImage release];
    printImage = nil;
    return;
}

- (void) drawRect: (NSRect)f
{
    if( ! printImage ) {
        int  r, c;
        NSPoint  p = NSZeroPoint;

        PSgsave();
        [_background compositeToPoint: p operation: NSCompositeCopy];
        for( r = 7; r >= 0; r-- ) {
            for( c = 7; c >= 0; c-- ) {
                Square3D  *theSquare = square[r][c];
                [theSquare drawWithFrame: f inView: self];
            }
        }
        PSgrestore();
    }
    else {
        [printImage draw];
    }
    return;
}

- (void) mouseDown: (NSEvent *)event
{
    NSException	 *exception = nil;

    if ( [NSApp bothsides] ) {
        NSBeep();
    }
    else if( [NSApp finished] ) {
        [NSApp finishedAlert];
  /*      [[self window] setAcceptsMouseMovedEvents: YES];

        NS_DURING
            while( [event type] != NSLeftMouseUp ) {
                unsigned int mask = (NSLeftMouseUpMask | NSLeftMouseDraggedMask);
                event = [[self window] nextEventMatchingMask: mask];
            }
            NS_HANDLER
                exception = localException;
            NS_ENDHANDLER    */
    }
    else if ([self isEnabled]) {
	    NSPoint  pickedP, backP, roundedBackP;
	    Square3D  *theSquare;
	    int  t, clr;
	    NSRect  oldLocation;
	    float  x, y;
	    int  controlGState;
	    int  r2, c2;
	    int  r = -1, c = -1;
	    int  hi_r = -1, hi_c = -1;

            pickedP = [event locationInWindow];
            pickedP = [self convertPoint: pickedP fromView: nil];
            backP   = pickedP;

            convert_point( &pickedP, &r, &c );
            if( r == -1 || c == -1 )
                return;

            theSquare = square[r][c];
            t   = [theSquare pieceType];
            clr = [theSquare colorVal];
            oldLocation = [theSquare location];

            [self lockFocus];
            PSgsave();

            if( t ) {
                [theSquare setPieceType: 0 color: 0];
                [self drawRect: [self frame]];
                [self flashSquareAt: r : c];
                hi_r = r;
                hi_c = c;

                /* Save background */
                squareOrigin( r, c, &x, &y );
                backP.x = x;
                backP.y = y;
				controlGState = [self gState];

                [backBitmap lockFocus];
                PSgsave();
                PScomposite( roundedBackP.x = floor(backP.x), roundedBackP.y = floor(backP.y), 
							 BACK_STORE_WIDTH, BACK_STORE_HEIGHT,
                             controlGState, (float)0.0, (float)0.0, NSCompositeCopy );
                PSgrestore();
                [backBitmap unlockFocus];    

                [theSquare setPieceType: t color: clr];
                [theSquare drawInteriorWithFrame: [self frame] inView: self];
                [theSquare setMoving: YES];
                [[self window] flushWindow];

                pickedP.x = (float) floor_value( (double)(pickedP.x - x) );
                pickedP.y = (float) floor_value( (double)(pickedP.y - y) );
            }

            r2 = 0;
            c2 = 0;
            [[self window] setAcceptsMouseMovedEvents: YES];

            NS_DURING
                while( [event type] != NSLeftMouseUp ) {
                    NSPoint  p, centerP;
                    NSRect  newLocation;
                    unsigned int mask = (NSLeftMouseUpMask | NSLeftMouseDraggedMask);
                    event = [[self window] nextEventMatchingMask: mask];
                    if( ! t )
                        continue;

                    p = [event locationInWindow];
                    p = [self convertPoint: p fromView: nil];

                    /* Restore old background */
			        [self lockFocus];
                    [backBitmap compositeToPoint: roundedBackP operation: NSCompositeCopy];
                    [self unlockFocus];   

                    backP.x = p.x - pickedP.x;
                    backP.y = p.y - pickedP.y;

                    /* Unhighlight square */
                    centerP.y = backP.y + PIECE_HEIGHT_3D / 4.0;
                    centerP.x = backP.x + PIECE_WIDTH_3D  / 2.0;
                    convert_point( &centerP, &r2, &c2 );

                    if( r2 != hi_r || c2 != hi_c ) {
                        if( hi_r != -1 && hi_c != -1 )
                            [self unhighlightSquareAt: hi_r : hi_c];
                        hi_r = r2;
                        hi_c = c2;
                        [self flashSquareAt: r2 : c2];
                    }

                    /* Save new background */
                    [backBitmap lockFocus];
                    PSgsave();
					PScomposite( roundedBackP.x = floor(backP.x), roundedBackP.y = floor(backP.y), 
							 BACK_STORE_WIDTH, BACK_STORE_HEIGHT,
                             controlGState, (float)0.0, (float)0.0, NSCompositeCopy );
                    PSgrestore();
                    [backBitmap unlockFocus];    

                    /* Draw piece at new location. */
                    [theSquare setRow: r2];
                    newLocation.origin.x = p.x - pickedP.x;
                    newLocation.origin.y = p.y - pickedP.y;
                    newLocation.size.width  = PIECE_WIDTH_3D;
                    newLocation.size.height = PIECE_HEIGHT_3D;
                    [theSquare setLocation: newLocation];
                    [theSquare drawInteriorWithFrame: [self frame] inView: self];
                    [self setNeedsDisplay:YES];   // THIS WAS A PROBLEM!
                    [[self window] flushWindow];

                }
                NS_HANDLER
                    exception = localException;
                NS_ENDHANDLER

                if( t ) {
                    [theSquare setMoving: NO];
                    if( r2 != r || c2 != c ) {
                        if( ! [NSApp makeMoveFrom: r : c to: r2 : c2] ) {
                            [theSquare setLocation: oldLocation];
                            [theSquare setPieceType: t color: clr];
                            [theSquare setRow: r];
                        }
                    }
                    else {
                        [theSquare setLocation: oldLocation];
                        [theSquare setPieceType: t color: clr];
                        [theSquare setRow: r];
                    }
                    [self display];
                    [[self window] flushWindow];
               }

                PSgrestore();
                [self unlockFocus];
        }

            if( exception )
                [exception raise];
            return;
}

@end

// Local Variables:
// tab-width: 8
// End: