MBCInteractivePlayer.mm [plain text]
/*
File: MBCInteractivePlayer.mm
Contains: An agent representing a local human player
Version: 1.0
Copyright: © 2002 by Apple Computer, Inc., all rights reserved.
File Ownership:
DRI: Matthias Neeracher x43683
Writers:
(MN) Matthias Neeracher
Change History (most recent first):
$Log: MBCInteractivePlayer.mm,v $
Revision 1.12 2004/08/16 07:48:48 neerache
Support flexible voices, accessibility
Revision 1.11 2003/07/17 23:30:38 neerache
Add Speech recognition help
Revision 1.10 2003/07/14 23:22:50 neerache
Move to much smarter speech recognition model
Revision 1.9 2003/07/07 08:49:01 neerache
Improve startup time
Revision 1.8 2003/06/30 05:02:32 neerache
Use proper move generator instead of engine
Revision 1.7 2003/05/24 20:25:25 neerache
Eliminate compact moves for most purposes
Revision 1.6 2003/04/24 23:20:35 neeri
Support pawn promotions
Revision 1.5 2002/10/08 22:12:38 neeri
Beep on rejected move
Revision 1.4 2002/09/13 23:57:06 neeri
Support for Crazyhouse display and mouse
Revision 1.3 2002/09/12 17:46:46 neeri
Introduce dual board representation, in-hand pieces
Revision 1.2 2002/08/26 23:14:40 neeri
Weed out non-moves
Revision 1.1 2002/08/22 23:47:06 neeri
Initial Checkin
*/
#import "MBCInteractivePlayer.h"
#import "MBCBoardView.h"
#import "MBCController.h"
#import "MBCLanguageModel.h"
#import <ApplicationServices/ApplicationServices.h>
//
// Private selector to set the help text in the speech feedback window
//
#ifndef kSRCommandsDisplayCFPropListRef
#define kSRCommandsDisplayCFPropListRef 'cdpl'
#endif
pascal OSErr HandleSpeechDoneAppleEvent (const AppleEvent *theAEevt, AppleEvent* reply, long refcon)
{
long actualSize;
DescType actualType;
OSErr status = 0;
OSErr recStatus = 0;
SRRecognitionResult recResult = 0;
status = AEGetParamPtr(theAEevt,keySRSpeechStatus,typeShortInteger,
&actualType, (Ptr)&recStatus, sizeof(status), &actualSize);
if (!status)
status = recStatus;
if (!status)
status = AEGetParamPtr(theAEevt,keySRSpeechResult,
typeSRSpeechResult, &actualType,
(Ptr)&recResult,
sizeof(SRRecognitionResult), &actualSize);
if (!status) {
[reinterpret_cast<MBCInteractivePlayer *>(refcon)
recognized:recResult];
SRReleaseObject(recResult);
}
return status;
}
@implementation MBCInteractivePlayer
- (id) initWithController:(MBCController *)controller
{
[super init];
fController = controller;
fRecSystem = 0;
fRecognizer = 0;
fSpeechHelp = 0;
fStartingSR = false;
return self;
}
- (void) makeSpeechHelp
{
NSPropertyListFormat format;
NSString * path =
[[NSBundle mainBundle] pathForResource: @"SpeechHelp" ofType: @"xml"];
NSData * help =
[NSData dataWithContentsOfFile:path];
NSMutableDictionary * prop =
[NSPropertyListSerialization
propertyListFromData: help
mutabilityOption: NSPropertyListMutableContainers
format: &format
errorDescription:nil];
ProcessSerialNumber psn;
GetCurrentProcess(&psn);
[prop setObject:[NSNumber numberWithLong:psn.highLongOfPSN]
forKey:@"ProcessPSNHigh"];
[prop setObject:[NSNumber numberWithLong:psn.lowLongOfPSN]
forKey:@"ProcessPSNLow"];
fSpeechHelp =
[[NSPropertyListSerialization
dataFromPropertyList:prop
format: NSPropertyListXMLFormat_v1_0
errorDescription:nil]
retain];
}
- (void) updateNeedMouse:(id)arg
{
BOOL wantMouse;
if (fLastSide == kBlackSide)
wantMouse = fSide == kWhiteSide || fSide == kBothSides;
else
wantMouse = fSide == kBlackSide || fSide == kBothSides;
[[fController view] wantMouse:wantMouse];
if ([fController listenForMoves]) {
//
// Work with speech recognition
//
if (wantMouse) {
if (fStartingSR) {
; // Current starting, will update later
} else if (!fRecSystem) {
fStartingSR = true;
[NSThread detachNewThreadSelector:@selector(initSR:)
toTarget:self withObject:nil];
} else {
if (!fSpeechHelp) {
[self makeSpeechHelp];
SRSetProperty(fRecognizer, kSRCommandsDisplayCFPropListRef,
[fSpeechHelp bytes], [fSpeechHelp length]);
}
SRStopListening(fRecognizer);
MBCMoveCollector * moves = [MBCMoveCollector new];
MBCMoveGenerator generateMoves(moves, fVariant, 0);
generateMoves.Generate(fLastSide==kBlackSide,
*[[fController board] curPos]);
[fLanguageModel buildLanguageModel:fModel
fromMoves:[moves collection]
takeback:[[fController board] canUndo]];
SRSetLanguageModel(fRecognizer, fModel);
SRStartListening(fRecognizer);
[moves release];
}
} else if (fRecSystem)
SRStopListening(fRecognizer);
} else if (fRecSystem && !fStartingSR) {
//
// Time to take the recognition system down
//
SRStopListening(fRecognizer);
[fLanguageModel release];
SRReleaseObject(fRecognizer);
SRCloseRecognitionSystem(fRecSystem);
fRecSystem = 0;
}
}
- (void) initSR:(id)arg
{
if (!fRecognizer) // very first time
AEInstallEventHandler(kAESpeechSuite, kAESpeechDone,
NewAEEventHandlerUPP(HandleSpeechDoneAppleEvent),
reinterpret_cast<long>(self), false);
if (SROpenRecognitionSystem(&fRecSystem, kSRDefaultRecognitionSystemID))
return;
SRNewRecognizer(fRecSystem, &fRecognizer, kSRDefaultSpeechSource);
short modes = kSRHasFeedbackHasListenModes;
SRSetProperty(fRecognizer, kSRFeedbackAndListeningModes, &modes, sizeof(short));
SRNewLanguageModel(fRecSystem, &fModel, "<moves>", 7);
fLanguageModel =
[[MBCLanguageModel alloc] initWithRecognitionSystem:fRecSystem];
if (fSpeechHelp)
SRSetProperty(fRecognizer, kSRCommandsDisplayCFPropListRef,
[fSpeechHelp bytes], [fSpeechHelp length]);
fStartingSR = false;
[self performSelectorOnMainThread:@selector(updateNeedMouse:)
withObject:self waitUntilDone:NO];
}
- (void) startGame:(MBCVariant)variant playing:(MBCSide)sideToPlay
{
fVariant = variant;
fLastSide =
([[[MBCController controller] board] numMoves] & 1)
? kWhiteSide : kBlackSide;
[[NSNotificationCenter defaultCenter] removeObserver:self];
switch (fSide = sideToPlay) {
case kWhiteSide:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(switchSides:)
name:MBCWhiteMoveNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(opponentMoved:)
name:MBCBlackMoveNotification
object:nil];
break;
case kBlackSide:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(opponentMoved:)
name:MBCWhiteMoveNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(switchSides:)
name:MBCBlackMoveNotification
object:nil];
break;
case kBothSides:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(switchSides:)
name:MBCWhiteMoveNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(switchSides:)
name:MBCBlackMoveNotification
object:nil];
break;
case kNeitherSide:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(opponentMoved:)
name:MBCWhiteMoveNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(opponentMoved:)
name:MBCBlackMoveNotification
object:nil];
break;
}
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(reject:)
name:MBCIllegalMoveNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(takeback:)
name:MBCTakebackNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(speakMove:)
name:MBCGameEndNotification
object:nil];
[self updateNeedMouse:self];
}
- (void) reject:(NSNotification *)n
{
NSBeep();
[[fController view] unselectPiece];
}
- (void) takeback:(NSNotification *)n
{
[self updateNeedMouse:self];
}
- (void) switchSides:(NSNotification *)n
{
fLastSide = fLastSide==kBlackSide ? kWhiteSide : kBlackSide;
[self updateNeedMouse:self];
}
const char * sPieceName[] = {
"", "king", "queen", "bishop", "knight", "rook", "pawn"
};
- (NSString *)stringFromMove:(MBCMove *)move
{
switch (move->fCommand) {
case kCmdDrop:
return [NSString stringWithFormat:@"Drop %s at %c%d.",
sPieceName[Piece(move->fPiece)],
Col(move->fToSquare), Row(move->fToSquare)];
case kCmdMove: {
MBCBoard * board = [fController board];
MBCPiece piece;
MBCPiece victim;
MBCPiece promo;
if (!move->fCastling)
[board tryCastling:move];
switch (move->fCastling) {
case kCastleQueenside:
return @"Castle [[emph +]]queen side.";
case kCastleKingside:
return @"Castle [[emph +]]king side.";
default:
piece = [board oldContents:move->fFromSquare];
victim = [board oldContents:move->fToSquare];
promo = move->fPromotion;
if (promo)
return [NSString stringWithFormat:@"%s %c%d %s %c%d promoting to %s.",
sPieceName[Piece(piece)],
Col(move->fFromSquare), Row(move->fFromSquare),
(victim ? "takes" : "to"),
Col(move->fToSquare), Row(move->fToSquare),
sPieceName[Piece(promo)]];
else
return [NSString stringWithFormat:@"%s %c%d %s %c%d.",
sPieceName[Piece(piece)],
Col(move->fFromSquare), Row(move->fFromSquare),
(victim ? "takes" : "to"),
Col(move->fToSquare), Row(move->fToSquare)];
}}
case kCmdWhiteWins:
return @"[[emph +]]Check mate!";
case kCmdBlackWins:
return @"[[emph +]]Check mate!";
case kCmdDraw:
return @"The game is a draw!";
default:
return @"";
}
}
- (void) speakMove:(NSNotification *)notification
{
if ([fController speakMoves]) {
MBCMove * move = reinterpret_cast<MBCMove *>([notification object]);
NSString * text = [self stringFromMove:move];
//
// We only wait for speech to end before speaking the next move
// to allow a maximum in concurrency.
//
while (SpeechBusy() > 0)
;
NSSpeechSynthesizer * synth;
if (fSide == kNeitherSide && Color(move->fPiece)==kBlackPiece)
synth = [fController alternateSynth];
else
synth = [fController defaultSynth];
[synth startSpeakingString:text];
}
}
- (void) opponentMoved:(NSNotification *)notification
{
[self speakMove:notification];
[self switchSides:notification];
}
- (void) startSelection:(MBCSquare)square
{
MBCPiece piece;
if (square > kInHandSquare) {
piece = square-kInHandSquare;
if (fVariant!=kVarCrazyhouse || ![[fController board] curInHand:piece])
return;
} else if (square == kWhitePromoSquare || square == kBlackPromoSquare)
return;
else
piece = [[fController board] oldContents:square];
if (!piece)
return;
if (Color(piece) == (fLastSide==kBlackSide ? kWhitePiece : kBlackPiece)) {
fFromSquare = square;
[[fController view] selectPiece:piece at:square];
}
}
- (void) endSelection:(MBCSquare)square animate:(BOOL)animate
{
if (fFromSquare == square) {
[[fController view] clickPiece];
return;
} else if (square > kSyntheticSquare) {
[[fController view] unselectPiece];
return;
}
MBCMove * move = [MBCMove moveWithCommand:kCmdMove];
if (fFromSquare > kInHandSquare) {
move->fCommand = kCmdDrop;
move->fPiece = fFromSquare-kInHandSquare;
} else {
move->fFromSquare = fFromSquare;
}
move->fToSquare = square;
move->fAnimate = animate;
//
// Fill in promotion info
//
[[fController board] tryPromotion:move];
[[NSNotificationCenter defaultCenter]
postNotificationName:
(fLastSide==kBlackSide
? MBCUncheckedWhiteMoveNotification
: MBCUncheckedBlackMoveNotification)
object:move];
}
- (void) recognized:(SRRecognitionResult)result
{
if (MBCMove * move = [fLanguageModel recognizedMove:result]) {
if (move->fCommand == kCmdUndo) {
[fController takeback:self];
} else {
//
// Fill in promotion info if missing
//
[[fController board] tryPromotion:move];
NSString * notification;
if (fLastSide==kBlackSide)
notification = MBCUncheckedWhiteMoveNotification;
else
notification = MBCUncheckedBlackMoveNotification;
[[NSNotificationCenter defaultCenter]
postNotificationName:notification
object:move];
}
}
}
@end
// Local Variables:
// mode:ObjC
// End: