Introduction to Motif Application Development

Part 7: Application One, tic-tac-toe

by Marshall Brain , brain@iftech.com
Interface Technologies, Inc.
(800) 224-4965
© Copyright 1994 by Marshall Brain. All rights reserved.
Version 2.01, 6/27/95

These tutorials are excerpted from the book "Motif Programming: The Essentials and More" , by Marshall Brain.


This application, although simple, is important because it demonstrates the placement and use of multiple pushbuttons in an application. It 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 <Xm/Xm.h>  #include <Xm/PushB.h>  #include <Xm/Form.h>  #include <Xm/Label.h>  #include <Xm/MessageB.h>  #include <Xm/RowColumn.h>  #include <Xm/CascadeB.h>    #define OK        1      #define CANCEL    2   #define RESTART   1 #define QUIT      2   XtAppContext context; XmStringCharSet char_set=XmSTRING_DEFAULT_CHARSET;   Widget file_menu; Widget restart_item; Widget quit_item; Widget menu_bar; Widget toplevel; Widget dialog; Widget button[3][3]; Widget form; Widget label;   int board[3][3]={0,0,0,0,0,0,0,0,0}; int rand_seed=10;   Widget make_menu_item();   /* adds an item into the menu            */ Widget make_menu();        /* creates a menu on the menu bar        */ void create_menus();       /* creates all menus for this program    */ void init_board();         /* resets the state of the game to       */                            /* the beginning                         */ void menuCB();             /* callback routine used for all menus   */ void dialogCB();           /* callback function for the dialog box  */                            /* called whenever the user clicks on    */                            /* the OK or Cancel buttons on the       */                            /* dialog box.                           */ Boolean check_win();       /* checks for a winner or a draw. If a   */                            /* win or a draw is detected, the        */                            /* dialog box is activated.              */ int rand();                /* random number generator from K&R       */ void do_computers_move();  /* determines the computer's next move   */                            /* and places it on the grid.            */ void buttonCB();           /* callback function for the 9 grid      */                            /* buttons called when one of the grid   */                            /* buttons is clicked.                   */     void main(argc,argv)     int argc;      char *argv[]; {     Arg al[10];     int ac;     int x,y;       /* create the toplevel shell */     toplevel = XtAppInitialize(&context,"",NULL,0,&argc,argv,         NULL,NULL,0);       /* 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=XmCreateForm(toplevel,"form",al,ac);     XtManageChild(form);       /* create the menu bar and attach it to the form. */     ac=0;     XtSetArg(al[ac],XmNtopAttachment,XmATTACH_FORM); ac++;     XtSetArg(al[ac],XmNrightAttachment,XmATTACH_FORM); ac++;     XtSetArg(al[ac],XmNleftAttachment,XmATTACH_FORM); ac++;     menu_bar=XmCreateMenuBar(form,"menu_bar",al,ac);     XtManageChild(menu_bar);       /* set up the buttons for the board. Attach them to the form. */     for (x=0; x<3; x++)     {          for (y=0; y<3; y++)          {               ac=0;               XtSetArg(al[ac],XmNlabelString,                   XmStringCreate("-",char_set)); 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]=XmCreatePushButton(form,"label",al,ac);               XtManageChild(button[x][y]);               XtAddCallback(button[x][y],XmNactivateCallback,                   buttonCB,x*3+y);          }     }       /* create a label widget and attach it to the form. */     ac=0;     XtSetArg(al[ac],XmNlabelString,         XmStringCreate("Please click on your choice",char_set)); ac++;     XtSetArg(al[ac],XmNrightAttachment,XmATTACH_FORM); ac++;     XtSetArg(al[ac],XmNrightOffset,5); ac++;     XtSetArg(al[ac],XmNleftOffset,5); ac++;     XtSetArg(al[ac],XmNleftAttachment,XmATTACH_FORM); ac++;     XtSetArg(al[ac],XmNtopAttachment,XmATTACH_POSITION); ac++;     XtSetArg(al[ac],XmNtopPosition,85); ac++;     label=XmCreateLabel(form,"label",al,ac);     XtManageChild(label);       /* 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));       create_menus(menu_bar);       XtRealizeWidget(toplevel);     XtAppMainLoop(context); }   Widget make_menu_item(item_name,client_data,menu)     char *item_name;     int client_data;     Widget menu; /* adds an item into the menu. */      {     int ac;     Arg al[10];     Widget item;       ac = 0;     XtSetArg(al[ac], XmNlabelString,         XmStringCreateLtoR(item_name,char_set)); ac++;           item=XmCreatePushButton(menu,item_name,al,ac);     XtManageChild(item);     XtAddCallback (item,XmNactivateCallback,menuCB,client_data);           XtSetSensitive(item,True);     return(item); }   Widget make_menu(menu_name,menu_bar)     char *menu_name;      Widget menu_bar; /* creates a menu on the menu bar */ {     int ac;     Arg al[10];     Widget menu, cascade;       menu=XmCreatePulldownMenu(menu_bar,menu_name,NULL,0);     ac=0;     XtSetArg(al[ac],XmNsubMenuId,menu);  ac++;     XtSetArg(al[ac],XmNlabelString,         XmStringCreateLtoR(menu_name,char_set)); ac++;     cascade=XmCreateCascadeButton(menu_bar,menu_name,al,ac);           XtManageChild (cascade);      return(menu); }   void create_menus(menu_bar)     Widget menu_bar; /* creates all the menus for this program */ {     /* create the file menu */     file_menu=make_menu("File",menu_bar);     restart_item=make_menu_item("Restart",RESTART,file_menu);     quit_item=make_menu_item("Quit",QUIT,file_menu); }   void init_board() /* Resets the state of the game to the beginning. */ {     int x,y;     int ac;     Arg al[10];       for(x=0; x<3; x++)         for (y=0; y<3; y++)         {             board[x][y]=0;             ac=0;             XtSetArg(al[ac],XmNlabelString,                  XmStringCreate("-",char_set)); ac++;             XtSetValues(button[x][y],al,ac);         } }   void menuCB(w,client_data,call_data)     Widget w;     int client_data;     XmAnyCallbackStruct *call_data; /* callback routine used for all menus */ {     if (client_data==QUIT) /* if quit seen, then exit */         exit(0);     if (client_data==RESTART)         init_board(); }   void dialogCB(w,client_data,call_data)     Widget w;     int client_data;     XmAnyCallbackStruct *call_data; /* callback function for the dialog box. Called whenever the      user clicks on the OK or Cancel buttons on the dialog box. */ {     /* after someone wins, restart. */     XtUnmanageChild(w);     init_board(); }   Boolean check_win() /* checks for a winner or a draw. If a win or a draw is detected,     the dialog box is activated. */ {     Arg al[10];     int ac;     char *s=NULL;     int x,y;     int sum1,sum2,tot=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) s="You won.";         else if (sum1 == -3 || sum2 == -3) 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) s="You won.";     else if (sum1 == -3 || sum2 == -3) 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 && !s) s="It's a draw.";       /* announce winner in dialog box */     if (s)     {         ac=0;         XtSetArg(al[ac], XmNmessageString,             XmStringCreateLtoR(s,char_set));  ac++;         XtSetValues(dialog,al,ac);         XtManageChild(dialog);         return(True);     } return(False); }   int rand() /* randum number generator from K&R */ {     rand_seed = rand_seed * 1103515245 +12345;     return (unsigned int)(rand_seed / 65536) % 32768; }     void do_computers_move() /* determines the computers next move and places it on the grid. */ {     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",char_set)); ac++;         XtSetValues(button[x][y],al,ac);         check_win();     } }   void buttonCB(w,client_data,call_data)     Widget w;     int client_data;     XmAnyCallbackStruct *call_data; /* callback function for the 9 grid buttons. Called when one of     the grid buttons is clicked. */ {    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",char_set)); ac++;         XtSetValues(button[x][y],al,ac);         do_computers_move();     }  } 

It takes suprisingly little code to create this game, and all of the code that you see here should be familiar from the previous exercises. The main procedure starts by creating a form widget, and then attaches the menu bar and the nine buttons onto the form using attachments. 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_win procedure 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 may be used to replace the form widget. A rowColumn widget lets you create a collection 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. The advantage of the RowColumn widget is the fact that you do not have to worry about widget placement. The disadvantage is that the buttons in a RowColumn container have a fixed size.