Implement SeekGraph in XBoard
authorH.G. Muller <h.g.muller@hccnet.nl>
Thu, 4 Feb 2010 21:42:15 +0000 (22:42 +0100)
committerArun Persaud <arun@nubati.net>
Sat, 6 Feb 2010 21:04:17 +0000 (13:04 -0800)
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
backend.c
backend.h
common.h
frontend.h
winboard/winboard.c
xboard.c

diff --git a/args.h b/args.h
index e94c16b..305906e 100644 (file)
--- 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 },
index 593e056..caa8e19 100644 (file)
--- 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 && i<maxRating) {
+       int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8) + vMargin;
+       yy = h-1-yy;
+       DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
+       if(i%500 == 0) {
+           char buf[MSG_SIZ];
+           sprintf(buf, "%d", i);
+           DrawSeekText(buf, hMargin+squareSize/8+7, yy);
+       }
+    }
+    DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
+    for(i=1; i<100; i+=(i<10?1:5)) {
+       int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
+       DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
+       if(i<=5 || (i>40 ? 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; i<nrOfSeekAds; i++) PlotSeekAd(i);
+    return TRUE;
+}
+
+int SeekGraphClick(ClickType click, int x, int y, Boolean moving)
+{
+    static int lastDown = 0;
+    if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
+       if(click == Release || moving) return FALSE;
+       nrOfSeekAds = 0;
+       soughtPending = TRUE;
+       SendToICS(ics_prefix);
+       SendToICS("sought\n"); // should this be "sought all"?
+    } else { // issue challenge based on clicked ad
+       int dist = 10000; int i, closest = 0;
+       for(i=0; i<nrOfSeekAds; i++) {
+           int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
+           if(d < dist) { dist = d; closest = i; }
+           if(click == Press && zList[i]>0) 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);
 
index a07e423..dd0bec0 100644 (file)
--- 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));
index b6b727f..9474c88 100644 (file)
--- 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;
index b75ca7c..5f45f31 100644 (file)
@@ -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));
index 0742e89..0958d00 100644 (file)
@@ -159,7 +159,7 @@ BoardSize boardSize;
 Boolean chessProgram;\r
 //static int boardX, boardY;\r
 int  minX, minY; // [HGM] placement: volatile limits on upper-left corner\r
-static int squareSize, lineGap, minorSize;\r
+int squareSize, lineGap, minorSize;\r
 static int winW, winH;\r
 static RECT messageRect, whiteRect, blackRect, leftLogoRect, rightLogoRect; // [HGM] logo\r
 static int logoHeight = 0;\r
@@ -3084,6 +3084,56 @@ DrawLogoOnDC(HDC hdc, RECT logoRect, HBITMAP logo)
   DeleteDC(tmphdc);\r
 }\r
 \r
+static HDC hdcSeek;\r
+\r
+// [HGM] seekgraph\r
+void DrawSeekAxis( int x, int y, int xTo, int yTo )\r
+{\r
+    POINT stPt;\r
+    HPEN hp = SelectObject( hdcSeek, gridPen );\r
+    MoveToEx( hdcSeek, boardRect.left+x, boardRect.top+y, &stPt );\r
+    LineTo( hdcSeek, boardRect.left+xTo, boardRect.top+yTo );\r
+    SelectObject( hdcSeek, hp );\r
+}\r
+\r
+// front-end wrapper for drawing functions to do rectangles\r
+void DrawSeekBackground( int left, int top, int right, int bottom )\r
+{\r
+    HPEN hp;\r
+    RECT rc;\r
+\r
+    if (hdcSeek == NULL) {\r
+    hdcSeek = GetDC(hwndMain);\r
+      if (!appData.monoMode) {\r
+        SelectPalette(hdcSeek, hPal, FALSE);\r
+        RealizePalette(hdcSeek);\r
+      }\r
+    }\r
+    hp = SelectObject( hdcSeek, gridPen );\r
+    rc.top = boardRect.top+top; rc.left = boardRect.left+left; \r
+    rc.bottom = boardRect.top+bottom; rc.right = boardRect.left+right;\r
+    FillRect( hdcSeek, &rc, lightSquareBrush );\r
+    SelectObject( hdcSeek, hp );\r
+}\r
+\r
+// front-end wrapper for putting text in graph\r
+void DrawSeekText(char *buf, int x, int y)\r
+{\r
+        SIZE stSize;\r
+       SetBkMode( hdcSeek, TRANSPARENT );\r
+        GetTextExtentPoint32( hdcSeek, buf, strlen(buf), &stSize );\r
+        TextOut( hdcSeek, boardRect.left+x-3, boardRect.top+y-stSize.cy/2, buf, strlen(buf) );\r
+}\r
+\r
+void DrawSeekDot(int x, int y, int color)\r
+{\r
+           HBRUSH oldBrush = SelectObject(hdcSeek, \r
+                       color == 0 ? markerBrush : color == 1 ? darkSquareBrush : explodeBrush);\r
+           Ellipse(hdcSeek, boardRect.left+x - squareSize/8, boardRect.top+y - squareSize/8,\r
+                            boardRect.left+x + squareSize/8, boardRect.top+y + squareSize/8);\r
+           SelectObject(hdcSeek, oldBrush);\r
+}\r
+\r
 VOID\r
 HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board)\r
 {\r
@@ -3110,6 +3160,8 @@ HDCDrawPosition(HDC hdc, BOOLEAN repaint, Board board)
    */\r
   Boolean fullrepaint = repaint;\r
 \r
+  if(DrawSeekGraph()) return; // [HG} seekgraph: suppress printing board if seek graph up\r
+\r
   if( DrawPositionNeedsFullRepaint() ) {\r
       fullrepaint = TRUE;\r
   }\r
@@ -3721,6 +3773,7 @@ MouseEvent(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
     break;\r
 \r
   case WM_MOUSEMOVE:\r
+    if(SeekGraphClick(Press, pt.x - boardRect.left, pt.y - boardRect.top, TRUE)) break;\r
     MovePV(pt.x - boardRect.left, pt.y - boardRect.top, boardRect.bottom - boardRect.top);\r
     if ((appData.animateDragging || appData.highlightDragging)\r
        && (wParam & MK_LBUTTON)\r
index 715a7b3..2102837 100644 (file)
--- 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;\r
       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;