Next: Application Threeeditors Up: Motif Tutorial Previous: Application Onemkill

Application Two, tic-tac-toe

This second application, although simple, is important because it demonstrates the placement and use of multiple pushbuttons in an application. The following code implements a simple tic-tac-toe game. To try it out, type it in, compile and run it. You have the first move, so click on one of the buttons. The computer will counter, and then it's your move again. This proceeds until someone wins.


        #include <X11/Intrinsic.h>
        #include <Xm/Xm.h>
        #include <Xm/PushB.h>
        #include <Xm/Form.h>
        #include <Xm/MessageB.h>

        #define OK      1
        #define CANCEL  2

        Widget toplevel, dialog;
        Widget button[3][3], form;
        Boolean first_time=True;
        int board[3][3]={0,0,0,0,0,0,0,0,0};
        int rand_seed=10;

        int rand()
        /* from K&R */
        {
          rand_seed = rand_seed * 1103515245 +12345;
          return (unsigned int)(rand_seed / 65536) % 32768;
        }

        void dialogCB(Widget w,
                   int client_data,
                   XmAnyCallbackStruct *call_data)
        {
          /* after someone wins, quit. */
          XtUnmanageChild(w);
          exit(0);
        }

        Boolean check_win()
        {
          Arg al[10];
          int ac;
          char s[100];
          int x,y;
          int sum1,sum2,tot=0;

          /* decide if someone has won. If yes, announce it */
          s[0]='\0';
          /* check all rows and columns for a win */
          for (x=0; x<3; x++)
          {
            sum1=sum2=0;
            for (y=0; y<3; y++)
            {
              sum1 += board[x][y];
              sum2 += board[y][x];
            }
            if (sum1 == 3 || sum2 == 3)
              strcpy(s,"You won.");
            else if (sum1 == -3 || sum2 == -3)
              strcpy(s,"I won!");
          }

          /* check diagonals for a win */
          sum1=sum2=0;
          for (x=0; x<3; x++)
          {
            sum1 += board[x][x];
            sum2 += board[2-x][x];
          }
          if (sum1 == 3 || sum2 == 3)
            strcpy(s,"You won.");
          else if (sum1 == -3 || sum2 == -3)
            strcpy(s,"I won!");
          
          /* check for draw. */
          for (x=0; x<3; x++)
            for (y=0; y<3; y++)
              if (board[x][y] != 0) tot++;
          if (tot==9) strcpy(s,"It's a draw.");

          /* announce winner in dialog box */
          if (strlen(s)>0)
          {
            ac=0;
            XtSetArg(al[ac], XmNmessageString,
                XmStringCreateLtoR(s,
                XmSTRING_DEFAULT_CHARSET));  ac++;
            XtSetValues(dialog,al,ac);
            XtManageChild(dialog);
            return(True);
          }
          return(False);
        }

        void do_computers_move()
        {
          Arg al[10];
          int ac;
          int x,y;

          if (!check_win())
          {
            /* computer's move is random. Wait until a valid move is chosen.*/
            do
            {
              x=rand()%3;
              y=rand()%3;
            } while (board[x][y]!=0);
            board[x][y] = -1;
            /* update the screen. */
            ac=0;
            XtSetArg(al[ac],XmNlabelString, XmStringCreate("O",
                XmSTRING_DEFAULT_CHARSET)); ac++;
            XtSetValues(button[x][y],al,ac);
            check_win();
          }
        }

        void buttonCB(Widget w,
                   int client_data,
                   XmAnyCallbackStruct *call_data)
        {
          Arg al[10];
          int ac;
          int x,y;

          /* make sure the move is valid. If it is update the screen. */
          x=client_data/3;
          y=client_data%3;
          if (board[x][y]==0)
          {
            board[x][y]=1;
            ac=0;
            XtSetArg(al[ac],XmNlabelString, XmStringCreate("X",
                XmSTRING_DEFAULT_CHARSET)); ac++;
            XtSetValues(button[x][y],al,ac);
            do_computers_move();
          } 
        }

        main(int argc, char *argv[])
        {
          Arg al[20];
          int ac;
          int x,y;

          toplevel=XtInitialize(argv[0],"",NULL,0,&argc,argv);

          /* set the default size of the window. */ 
          ac=0;
          XtSetArg(al[ac],XmNwidth,200); ac++;
          XtSetArg(al[ac],XmNheight,200); ac++;
          XtSetValues(toplevel,al,ac);
          
          /* create a form widget */
          ac=0;
          form=XtCreateManagedWidget("form",xmFormWidgetClass,
                toplevel,al,ac);

          /* set up the buttons for the board. Attach them so they look nice. */
          for (x=0; x<3; x++)
          {
            for (y=0; y<3; y++)
            {
              ac=0;
              XtSetArg(al[ac],XmNlabelString, XmStringCreate("-",
                        XmSTRING_DEFAULT_CHARSET)); ac++;
              XtSetArg(al[ac], XmNleftAttachment,    XmATTACH_POSITION); ac++;
              XtSetArg(al[ac], XmNleftPosition, 20+x*20); ac++;
              XtSetArg(al[ac], XmNrightAttachment,    XmATTACH_POSITION); ac++;
              XtSetArg(al[ac], XmNrightPosition, 40+x*20); ac++;
              XtSetArg(al[ac], XmNtopAttachment,    XmATTACH_POSITION); ac++;
              XtSetArg(al[ac], XmNtopPosition, 20+y*20); ac++;
              XtSetArg(al[ac], XmNbottomAttachment,    XmATTACH_POSITION); ac++;
              XtSetArg(al[ac], XmNbottomPosition, 40+y*20); ac++;
              button[x][y]=XtCreateManagedWidget("label",
                        xmPushButtonWidgetClass, form,al,ac);
              XtAddCallback (button[x][y], XmNactivateCallback, 
                        buttonCB, x*3+y);
            }
          }

          /* create a dialog that will announce the winner. */
          ac=0;
          dialog = XmCreateMessageDialog(toplevel,"dialog", al, ac);
          XtAddCallback (dialog, XmNokCallback, dialogCB, OK);
          XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
          XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));

          XtRealizeWidget(toplevel);
          XtMainLoop();
        }

It takes suprisingly little code to create this game. The main procedure starts by creating a form widget, and placing the nine buttons onto the form using attached positions. It also creates a dialog box that will later be managed to announce the winner.

The user now has to press a button. When he/she does, the buttonCB callback is activated. It checks that the move is valid, updates the button to display an "X" if it is, and then does the computer's move. The computer's move consists of a check to make sure the user hasn't won, followed by a series of calls to the rand function to find a valid place to move to. Once a valid place is found, it is marked and displayed on the screen.

The check_winprocedure simply checks all rows and columns, and displays a dialog box if someone wins.

The important thing to notice in this program is that it is easy in motif to create and respond to a lot of buttons without creating that much code. For example, you could scale the board up to chess-board-size, with 64 buttons, without creating any more code.

Sometimes an application has a lot of buttons, but they aren't homogeneous. That is, they are all different sizes, contain different strings, etc. In this case, a rowColumn container widget should be used to replace the form widget in the above code. A rowColumn widget lets you create a bunch of objects, and then it manages their placement for you. There are all sorts of options so that you can control the spacing in different ways, without having to worry about the actual X,Y coordinates of each object.

Young's book goes into some detail about rowColumn widgets, and you will probably want to refer to it or one of the OSF books for more information. If you want to play around though, replace the main program of the tic-tac-toe program with the following code - it won't look as nice initially, but you can change resources to get a feel for how rowColumn widgets behave.


       #include <Xm/RowColumn.h>

        main(int argc, char *argv[])
        {
          Arg al[20];
          int ac;
          int x,y;

          toplevel=XtInitialize(argv[0],"",NULL,0,&argc,argv);

          /* set the default size of the window. */ 
          ac=0;
          XtSetArg(al[ac],XmNwidth,200); ac++;
          XtSetArg(al[ac],XmNheight,200); ac++;
          XtSetValues(toplevel,al,ac);
          
          /* create a rowColumn widget to manage the buttons */
          ac=0;
          XtSetArg(al[ac], XmNnumColumns, 3); ac++;
          XtSetArg(al[ac], XmNpacking, XmPACK_COLUMN ); ac++;
          XtSetArg(al[ac], XmNadjustLast, False ); ac++;
          rowcol=XtCreateManagedWidget("rowcol",xmRowColumnWidgetClass,
                toplevel,al,ac);

          /* set up the buttons for the board. Attach them so they look nice. */
          for (x=0; x<3; x++)
          {
            for (y=0; y<3; y++)
            {
              ac=0;
              XtSetArg(al[ac],XmNlabelString, XmStringCreate("-",
                        XmSTRING_DEFAULT_CHARSET)); ac++;
              button[x][y]=XtCreateManagedWidget("label",
                        xmPushButtonWidgetClass, rowcol,al,ac);
              XtAddCallback (button[x][y], XmNactivateCallback, 
                        buttonCB, x*3+y);
            }
          }

          /* create a dialog that will announce the winner. */
          ac=0;
          dialog = XmCreateMessageDialog(toplevel,"dialog", al, ac);
          XtAddCallback (dialog, XmNokCallback, dialogCB, OK);
          XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
          XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));

          XtRealizeWidget(toplevel);
          XtMainLoop();
        }

In this code, the form widget (and the code used to attach buttons to the form) has been lost, and replaced with a rowColumn widget. Because they are now in a rowColumn container, the buttons will default to their "natural" size, based on the size of their labels. Their positions will be controlled by the rowColumn container. In this particular case, a form container is the better choice, but the rowColumn container is better in other situations.



Next: Application Threeeditors Up: Motif Tutorial Previous: Application Onemkill


morbe@enstb.enst-bretagne.fr