From 394d2d698de4f7f24853af499fdfe9a339d7a832 Mon Sep 17 00:00:00 2001 From: H.G. Muller Date: Thu, 4 Feb 2010 22:42:15 +0100 Subject: [PATCH] Implement SeekGraph in XBoard For now only controlled by left mouse clicks on board. New options -seekGraph true|false and -sg to switch this feature on. Facilitate selection of coinciding seek ads Seek-ads that are viewed but not answered get a distance penalty on the up-click that makes their selection more difficult next time, so that the ad they covered gets a (better) change. The penalty ages away in the course of 5 clicks. Slightly offset Seek-Graph dots of different color Dots of different color are now displaced horizontally be 3 pixels, to prevent them from completely covering each other. (Required the axis labels to be moved a little too, to not be covered by the 1-min ads.) Auto-show seek-ad texts in WinBoard Hovering the mouse above a Seek-Graph dot will make the text belonging to it appear in the message field without the need to press a button. Required SeekGraphClick() to be called from the WB mouse event handler, with an extra argument 'moving' to make the distinction with a real click. Show seek-ad text on mouse hovering (XBoard) The Eventproc is also set to trigger on PointerMotion, and this event then calls SeekGraphClick with motion=true to handle it. Put poetic name of wilds in seek-ad text Use XBoard variant names, except for 'loadable' and 'fischerandom' (becomes 'setup' and 'chess960'), but leave number (to distinguish wilds that transate to same XBoard variant). --- args.h | 2 + backend.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++- backend.h | 2 + common.h | 1 + frontend.h | 4 + winboard/winboard.c | 55 +++++++++++++++- xboard.c | 28 ++++++++- 7 files changed, 266 insertions(+), 4 deletions(-) diff --git a/args.h b/args.h index e94c16b..305906e 100644 --- a/args.h +++ b/args.h @@ -241,6 +241,8 @@ ArgDescriptor argDescriptors[] = { { "internetChessserverHelper", ArgFilename, (void *) &appData.icsHelper, FALSE, INVALID }, // for XB { "icshelper", ArgFilename, (void *) &appData.icsHelper, FALSE, (ArgIniType) "" }, + { "seekGraph", ArgBoolean, (void *) &appData.seekGraph, TRUE, (ArgIniType) FALSE }, + { "sg", ArgTrue, (void *) &appData.seekGraph, FALSE, INVALID }, { "gateway", ArgString, (void *) &appData.gateway, FALSE, (ArgIniType) "" }, { "loadGameFile", ArgFilename, (void *) &appData.loadGameFile, FALSE, (ArgIniType) "" }, { "lgf", ArgFilename, (void *) &appData.loadGameFile, FALSE, INVALID }, diff --git a/backend.c b/backend.c index 593e056..caa8e19 100644 --- a/backend.c +++ b/backend.c @@ -2064,6 +2064,150 @@ static int player2Rating = -1; ColorClass curColor = ColorNormal; int suppressKibitz = 0; +// [HGM] seekgraph +Boolean soughtPending = FALSE; +Boolean seekGraphUp; +#define MAX_SEEK_ADS 200 +char *seekAdList[MAX_SEEK_ADS]; +int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS]; +float tcList[MAX_SEEK_ADS]; +int nrOfSeekAds = 0; +int minRating = 1010, maxRating = 2800; +int hMargin = 10, vMargin = 20, h, w; +extern int squareSize, lineGap; + +void +PlotSeekAd(int i) +{ + int x, y, color = 0, r = ratingList[i]; float tc = tcList[i]; + xList[i] = yList[i] = -100; // outside graph, so cannot be clicked + zList[i] = 0; + if(r < minRating+100 && r >=0 ) r = minRating+100; + if(r > maxRating) r = maxRating; + if(tc < 1.) tc = 1.; + if(tc > 95.) tc = 95.; + x = (w-hMargin)* log(tc)/log(100.) + hMargin; + y = ((double)r - minRating)/(maxRating - minRating) + * (h-vMargin-squareSize/8) + vMargin; + if(ratingList[i] < 0) y = vMargin + squareSize/4; + if(strstr(seekAdList[i], " u ")) color = 1; + if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color + !strstr(seekAdList[i], "bullet") && + !strstr(seekAdList[i], "blitz") && + !strstr(seekAdList[i], "standard") ) color = 2; + DrawSeekDot(xList[i]=x+3*color, yList[i]=h-1-y, color); +} + +void +AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot) +{ + char buf[MSG_SIZ], *ext = ""; + VariantClass v = StringToVariant(type); + if(strstr(type, "wild")) { + ext = type + 4; // append wild number + if(v == VariantFischeRandom) type = "chess960"; else + if(v == VariantLoadable) type = "setup"; else + type = VariantName(v); + } + sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext); + if(nrOfSeekAds < MAX_SEEK_ADS-1) { + if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]); + ratingList[nrOfSeekAds] = -1; // for if seeker has no rating + sscanf(rating, "%d", &ratingList[nrOfSeekAds]); + tcList[nrOfSeekAds] = base + (2./3.)*inc; + seekNrList[nrOfSeekAds] = nr; + seekAdList[nrOfSeekAds++] = StrSave(buf); + if(plot) PlotSeekAd(nrOfSeekAds-1); + } +} + +Boolean +MatchSoughtLine(char *line) +{ + char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ]; + int nr, base, inc, u=0; char dummy; + + if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 || + sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 || + (u=1) && + (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 || + sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) { + // match: compact and save the line + AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE); + return TRUE; + } + return FALSE; +} + +int +DrawSeekGraph() +{ + if(!seekGraphUp) return FALSE; + int i; + h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap; + w = BOARD_WIDTH * (squareSize + lineGap) + lineGap; + + DrawSeekBackground(0, 0, w, h); + DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin); + DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5); + for(i=0; i<4000; i+= 100) if(i>=minRating && i40 ? i%20 : i%10) == 0) { + char buf[MSG_SIZ]; + sprintf(buf, "%d", i); + DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2); + } + } + for(i=0; i0) zList[i] *= 0.8; // age priority + } + if(dist < 300) { + char buf[MSG_SIZ]; + if(lastDown != closest) DisplayMessage(seekAdList[closest], ""); + sprintf(buf, "play %d\n", seekNrList[closest]); + if(click == Press) { lastDown = closest; return TRUE; } // on press 'hit', only show info + SendToICS(ics_prefix); + SendToICS(buf); // should this be "sought all"? + } else if(click == Release) { // release 'miss' is ignored + zList[lastDown] = 200; // make future selection of the rejected ad more difficult + return TRUE; + } else if(moving) { if(lastDown >= 0) DisplayMessage("", ""); lastDown = -1; return TRUE; } + // press miss or release hit 'pop down' seek graph + seekGraphUp = FALSE; + DrawPosition(TRUE, NULL); + } + return TRUE; +} + void read_from_ics(isr, closure, data, count, error) InputSourceRef isr; @@ -2439,6 +2583,23 @@ read_from_ics(isr, closure, data, count, error) continue; } + // [HGM] seekgraph: recognize sought lines and end-of-sought message + if(appData.seekGraph) { + if(soughtPending && MatchSoughtLine(buf+i)) { + i = strstr(buf+i, "rated") - buf; + next_out = leftover_start = i; + started = STARTED_CHATTER; + suppressKibitz = TRUE; + } + if((gameMode == IcsIdle || gameMode == BeginningOfGame) + && looking_at(buf, &i, "* ads displayed")) { + soughtPending = FALSE; + seekGraphUp = TRUE; + DrawSeekGraph(); + continue; + } + } + /* skip formula vars */ if (started == STARTED_NONE && buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') { @@ -2878,6 +3039,11 @@ read_from_ics(isr, closure, data, count, error) if (looking_at(buf, &i, "% ") || ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE) && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book + if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line + soughtPending = FALSE; + seekGraphUp = TRUE; + DrawSeekGraph(); + } if(suppressKibitz) next_out = i; savingComment = FALSE; suppressKibitz = 0; @@ -4039,7 +4205,7 @@ ParseBoard12(string) } } - + /* Display the board */ if (!pausing && !appData.noGUI) { @@ -4049,7 +4215,9 @@ ParseBoard12(string) ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove)))) ClearPremoveHighlights(); - DrawPosition(FALSE, boards[currentMove]); + j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph + DrawPosition(j, boards[currentMove]); + DisplayMove(moveNum - 1); if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) || @@ -5745,6 +5913,12 @@ void LeftClick(ClickType clickType, int xPix, int yPix) static int second = 0, promotionChoice = 0; char promoChoice = NULLCHAR; + if(appData.seekGraph && appData.icsActive && + (gameMode == BeginningOfGame || gameMode == IcsIdle)) { + SeekGraphClick(clickType, xPix, yPix, FALSE); + return; + } + if (clickType == Press) ErrorPopDown(); MarkTargetSquares(1); diff --git a/backend.h b/backend.h index a07e423..dd0bec0 100644 --- a/backend.h +++ b/backend.h @@ -138,6 +138,8 @@ int SaveGameToFile P((char *filename, int append)); int LoadPosition P((FILE *f, int n, char *title)); int ReloadPosition P((int offset)); int SavePosition P((FILE *f, int dummy, char *dummy2)); +int DrawSeekGraph P(()); +int SeekGraphClick P((ClickType click, int x, int y, Boolean moving)); void EditPositionEvent P((void)); void FlipViewEvent P((void)); void MachineWhiteEvent P((void)); diff --git a/common.h b/common.h index b6b727f..9474c88 100644 --- a/common.h +++ b/common.h @@ -413,6 +413,7 @@ typedef struct { char *icsHelper; Boolean icsInputBox; Boolean useTelnet; + Boolean seekGraph; char *telnetProgram; char *gateway; char *loadGameFile; diff --git a/frontend.h b/frontend.h index b75ca7c..5f45f31 100644 --- a/frontend.h +++ b/frontend.h @@ -90,6 +90,10 @@ void CommentPopDown P((void)); void EditCommentPopUp P((int index, String title, String text)); void ErrorPopDown P((void)); int EventToSquare P((int x, int limit)); +void DrawSeekAxis P(( int x, int y, int xTo, int yTo )); +void DrawSeekBackground P(( int left, int top, int right, int bottom )); +void DrawSeekText P((char *buf, int x, int y)); +void DrawSeekDot P((int x, int y, int color)); void RingBell P((void)); void PlayIcsWinSound P((void)); diff --git a/winboard/winboard.c b/winboard/winboard.c index 0742e89..0958d00 100644 --- a/winboard/winboard.c +++ b/winboard/winboard.c @@ -159,7 +159,7 @@ BoardSize boardSize; Boolean chessProgram; //static int boardX, boardY; int minX, minY; // [HGM] placement: volatile limits on upper-left corner -static int squareSize, lineGap, minorSize; +int squareSize, lineGap, minorSize; static int winW, winH; static RECT messageRect, whiteRect, blackRect, leftLogoRect, rightLogoRect; // [HGM] logo static int logoHeight = 0; @@ -3084,6 +3084,56 @@ DrawLogoOnDC(HDC hdc, RECT logoRect, HBITMAP logo) DeleteDC(tmphdc); } +static HDC hdcSeek; + +// [HGM] seekgraph +void DrawSeekAxis( int x, int y, int xTo, int yTo ) +{ + POINT stPt; + HPEN hp = SelectObject( hdcSeek, gridPen ); + MoveToEx( hdcSeek, boardRect.left+x, boardRect.top+y, &stPt ); + LineTo( hdcSeek, boardRect.left+xTo, boardRect.top+yTo ); + SelectObject( hdcSeek, hp ); +} + +// front-end wrapper for drawing functions to do rectangles +void DrawSeekBackground( int left, int top, int right, int bottom ) +{ + HPEN hp; + RECT rc; + + if (hdcSeek == NULL) { + hdcSeek = GetDC(hwndMain); + if (!appData.monoMode) { + SelectPalette(hdcSeek, hPal, FALSE); + RealizePalette(hdcSeek); + } + } + hp = SelectObject( hdcSeek, gridPen ); + rc.top = boardRect.top+top; rc.left = boardRect.left+left; + rc.bottom = boardRect.top+bottom; rc.right = boardRect.left+right; + FillRect( hdcSeek, &rc, lightSquareBrush ); + SelectObject( hdcSeek, hp ); +} + +// front-end wrapper for putting text in graph +void DrawSeekText(char *buf, int x, int y) +{ + SIZE stSize; + SetBkMode( hdcSeek, TRANSPARENT ); + GetTextExtentPoint32( hdcSeek, buf, strlen(buf), &stSize ); + TextOut( hdcSeek, boardRect.left+x-3, boardRect.top+y-stSize.cy/2, buf, strlen(buf) ); +} + +void DrawSeekDot(int x, int y, int color) +{ + HBRUSH oldBrush = SelectObject(hdcSeek, + color == 0 ? markerBrush : color == 1 ? darkSquareBrush : explodeBrush); + Ellipse(hdcSeek, boardRect.left+x - squareSize/8, boardRect.top+y - squareSize/8, + boardRect.left+x + squareSize/8, boardRect.top+y + squareSize/8); + SelectObject(hdcSeek, oldBrush); +} + VOID HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board) { @@ -3110,6 +3160,8 @@ HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board) */ Boolean fullrepaint = repaint; + if(DrawSeekGraph()) return; // [HG} seekgraph: suppress printing board if seek graph up + if( DrawPositionNeedsFullRepaint() ) { fullrepaint = TRUE; } @@ -3721,6 +3773,7 @@ MouseEvent(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) break; case WM_MOUSEMOVE: + if(SeekGraphClick(Press, pt.x - boardRect.left, pt.y - boardRect.top, TRUE)) break; MovePV(pt.x - boardRect.left, pt.y - boardRect.top, boardRect.bottom - boardRect.top); if ((appData.animateDragging || appData.highlightDragging) && (wParam & MK_LBUTTON) diff --git a/xboard.c b/xboard.c index 715a7b3..2102837 100644 --- a/xboard.c +++ b/xboard.c @@ -2469,7 +2469,7 @@ XBoard square size (hint): %d\n\ /* Why is the following needed on some versions of X instead * of a translation? */ - XtAddEventHandler(boardWidget, ExposureMask, False, + XtAddEventHandler(boardWidget, ExposureMask|PointerMotionMask, False, (XtEventHandler) EventProc, NULL); /* end why */ @@ -4223,6 +4223,8 @@ void EventProc(widget, unused, event) if (event->xexpose.count > 0) return; /* no clipping is done */ XDrawPosition(widget, True, NULL); break; + case MotionNotify: + if(SeekGraphClick(Press, event->xbutton.x, event->xbutton.y, TRUE)) break; default: return; } @@ -4303,6 +4305,28 @@ static int check_castle_draw(newb, oldb, rrow, rcol) return 0; } +// [HGM] seekgraph: some low-level drawing routines cloned from xevalgraph +void DrawSeekAxis( int x, int y, int xTo, int yTo ) +{ + XDrawLine(xDisplay, xBoardWindow, lineGC, x, y, xTo, yTo); +} + +void DrawSeekBackground( int left, int top, int right, int bottom ) +{ + XFillRectangle(xDisplay, xBoardWindow, lightSquareGC, left, top, right-left, bottom-top); +} + +void DrawSeekText(char *buf, int x, int y) +{ + XDrawString(xDisplay, xBoardWindow, coordGC, x, y+4, buf, strlen(buf)); +} + +void DrawSeekDot(int x, int y, int color) +{ + XFillArc(xDisplay, xBoardWindow, color == 0 ? prelineGC : color == 1 ? darkSquareGC : highlineGC, + x-squareSize/8, y-squareSize/8, squareSize/4, squareSize/4, 0, 64*360); +} + static int damage[BOARD_RANKS][BOARD_FILES]; /* @@ -4320,6 +4344,8 @@ void XDrawPosition(w, repaint, board) Arg args[16]; int rrow, rcol; + if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up + if (board == NULL) { if (!lastBoardValid) return; board = lastBoard; -- 1.7.0.4