1 /*
2 * XScrabble - X version of the popular board game, for 1 to 4 players.
3 *
4 * This software comes with NO warranty whatsoever. I therefore take no
5 * responsibility for any damages, losses or problems caused through use
6 * or misuse of this program.
7 *
8 * I hereby grant permission for this program to be freely copied and
9 * distributed by any means, provided no charge is made for it.
10 *
11 * Matthew Chapman, csuoq@csv.warwick.ac.uk
12 * Oct 1994.
13 */
14
15 /* user.c - user interaction with board */
16
17 #include "scrab.h"
18 #include "globals.h"
19
20 char selected_lett[MAXPLAYERS] = { ' ',' ',' ',' ' };
21 Widget prev[MAXPLAYERS];
22 int oldx,oldy;
23 Boolean from_bar[MAXPLAYERS];
24
Deselect(int ply)25 void Deselect(int ply)
26 {
27 /* deselect current player's tile, if required */
28 if (selected_lett[ply] != ' ')
29 {
30 /* deselect letter */
31 selected_lett[ply] = ' ';
32 XtVaSetValues(prev[ply],SETBG(app_data.brightcolor),
33 SETFG(app_data.lettercolor), NULL);
34 }
35 }
36
RemoveFromBoard()37 void RemoveFromBoard()
38 {
39 int ac,dn;
40
41 /* copy board to cboard, removing letters */
42 for (dn=0; dn<BOARDSIZE; dn++)
43 for (ac=0; ac<BOARDSIZE; ac++)
44 cboard[ac][dn]=board[ac][dn];
45 }
46
Select_sq(Widget w,XtPointer client_data,XtPointer call_data)47 void Select_sq(Widget w, XtPointer client_data, XtPointer call_data)
48 {
49 int ac,dn,x=0,y=0,i,move_ply=0;
50 char curr_tile,lett[3];
51 char cl[256];
52 Boolean is_bar=True;
53
54 /* find square widget */
55 for (i=0; i<num_players; i++)
56 for (dn=0; dn<BOARDSIZE; dn++)
57 for (ac=0; ac<BOARDSIZE; ac++)
58 if (sq[i][ac][dn]==w)
59 {
60 x=ac;
61 y=dn;
62 is_bar=False;
63 move_ply=i;
64 }
65 for (i=0; i<num_players; i++)
66 for (ac=0; ac<LONGBAR; ac++)
67 if (br[i][ac]==w)
68 {
69 x=ac;
70 is_bar=True;
71 move_ply=i;
72 }
73
74 if (is_bar)
75 curr_tile=bar[move_ply][x];
76 else
77 curr_tile=cboard[x][y];
78
79 if ((move_ply!=curr_player) && (!is_bar))
80 /* other players can only move tiles around in their bar */
81 MessageOne(move_ply,PROMPT[NOT_ON_BOARD]);
82 else
83 if (curr_tile==' ')
84 {
85 /* place tile */
86 if (selected_lett[move_ply] != ' ')
87 {
88 /* remove tile from old position */
89 if (from_bar[move_ply])
90 strcpy(cl,app_data.barcolor);
91 else
92 strcpy(cl,(char *)colours[sq_col[oldx][oldy]]);
93 XtVaSetValues(prev[move_ply],SETBG(cl),
94 SETFG(app_data.lettercolor),XtNlabel,
95 " ",NULL);
96 sprintf(lett,"%c",selected_lett[move_ply]);
97 XtVaSetValues(w,SETBG(app_data.brightcolor),
98 SETFG(app_data.lettercolor),XtNlabel,lett,NULL);
99 if (is_bar)
100 bar[move_ply][x]=selected_lett[move_ply];
101 else
102 cboard[x][y]=selected_lett[move_ply];
103 /* clear letter - can place letter only once */
104 selected_lett[move_ply] = ' ';
105 /* clear message box */
106 MessageOne(move_ply," ");
107 }
108 }
109 else
110 {
111 /* only select if not holding a letter */
112 if (selected_lett[move_ply] == ' ')
113 {
114 if ((!is_bar) && (is_perm[x][y]))
115 {
116 /* tile can't be moved */
117 MessageOne(move_ply,PROMPT[FIXED_TILE]);
118 }
119 else
120 {
121 /* store tile to be removed */
122 oldx=x;
123 oldy=y;
124 prev[move_ply]=w;
125 from_bar[move_ply]=is_bar;
126 /* mark tile as selected */
127 XtVaSetValues(w,SETBG(app_data.lettercolor),SETFG(app_data.brightcolor),NULL);
128 if (is_bar)
129 {
130 selected_lett[move_ply]=bar[move_ply][x];
131 bar[move_ply][x]=' ';
132 }
133 else
134 {
135 selected_lett[move_ply]=cboard[x][y];
136 cboard[x][y]=' ';
137 }
138 }
139 }
140 else
141 {
142 /* already holding a letter */
143 MessageOne(move_ply,PROMPT[OCCUPIED]);
144 }
145 }
146 }
147
FinishGo(Widget w,XtPointer client_data,XtPointer call_data)148 void FinishGo(Widget w, XtPointer client_data, XtPointer call_data)
149 {
150 int status;
151 int ac,dn,score_for_go,bn;
152 char mess[256],mess2[256],chlett[3];
153 Boolean TestOnly = (w==evaluate[curr_player]), all_space=True;
154 XEvent event;
155
156 if (!TestOnly)
157 {
158
159 /* check for blank tiles */
160 finished_go=True;
161
162 for (dn=0; dn<BOARDSIZE; dn++)
163 for (ac=0; ac<BOARDSIZE; ac++)
164 if (cboard[ac][dn]=='*')
165 {
166 /* query user for blank */
167 BlankPopup();
168 /* wait for user to enter letter */
169 while (waiting_for_blank==True)
170 {
171 XtAppNextEvent(app_context,&event);
172 XtDispatchEvent(&event);
173 }
174 cboard[ac][dn]=blank_letter;
175 sprintf(chlett,"%c",blank_letter);
176 XtVaSetValues(sq[curr_player][ac][dn],XtNlabel,chlett,NULL);
177 }
178 }
179
180 status=validate(cboard,TestOnly,&score_for_go);
181
182 switch (status)
183 {
184 case HORLICKS: MessageOne(curr_player,PROMPT[ARRANGEMENT]); break;
185 case INVALID:
186 {
187 if (TestOnly)
188 /* shouldn't happen as dict is not checked when TestOnly */
189 MessageAll(PROMPT[SPAM]);
190 else
191 {
192 sprintf(mess,PROMPT[LOST_GO_ALL],player[curr_player].name);
193 Message(curr_player,PROMPT[LOST_GO],mess);
194 RemoveFromBoard();
195 ShowTiles();
196 ShowBar(curr_player);
197 num_passed=0;
198 GotoNextPlayer();
199 }
200 break;
201 }
202 case NOTCENTRE: MessageOne(curr_player,PROMPT[USE_CENTRE]); break;
203 case VALID:
204 {
205 if (TestOnly)
206 {
207 sprintf(mess,PROMPT[WOULD_SCORE],score_for_go,
208 PROMPT[WDPOINT+(score_for_go>1)]);
209 MessageOne(curr_player,mess);
210 }
211 else
212 {
213 sprintf(mess,PROMPT[GO_ACCEPTED],score_for_go,
214 PROMPT[WDPOINT+(score_for_go>1)]);
215 sprintf(mess2,PROMPT[GO_ALL],player[curr_player].name,score_for_go,
216 PROMPT[WDPOINT+(score_for_go>1)]);
217 Message(curr_player,mess,mess2);
218
219 /* copy cboard to board */
220 for (dn=0; dn<BOARDSIZE; dn++)
221 for (ac=0; ac<BOARDSIZE; ac++)
222 board[ac][dn]=cboard[ac][dn];
223 player[curr_player].score += score_for_go;
224 ShowScores();
225
226 /* show board to everyone */
227 ShowBoard(True);
228
229 /* fix letters on board, and remove from bar */
230 for (dn=0; dn<BOARDSIZE; dn++)
231 for (ac=0; ac<BOARDSIZE; ac++)
232 if ((board[ac][dn] != ' ') && (is_perm[ac][dn] == False))
233 {
234 is_perm[ac][dn] = True;
235 for (bn=0; bn<BARLEN; bn++)
236 if ((player[curr_player].bar[bn]==board[ac][dn])
237 || (islower(board[ac][dn]) &&
238 player[curr_player].bar[bn]=='*'))
239 {
240 player[curr_player].bar[bn]=' ';
241 break;
242 }
243 }
244
245 /* replenish bar */
246 fillbar(curr_player);
247 TilesLeft();
248
249 ShowBar(curr_player);
250
251 /* check for player going out */
252 all_space=True;
253 for (bn=0; bn<BARLEN; bn++)
254 if (player[curr_player].bar[bn]!=' ')
255 all_space=False;
256 if (all_space==True)
257 GameOver(curr_player);
258
259 num_passed=0;
260 GotoNextPlayer();
261 break;
262 }
263 }
264 }
265 }
266
GotoNextPlayer()267 void GotoNextPlayer()
268 {
269 char mess[256],mess2[256];
270
271 finished_go=True;
272
273 /* make player's buttons insensitive */
274 if (type[curr_player]==0)
275 {
276 XtSetSensitive(change[curr_player],False);
277 XtSetSensitive(finish[curr_player],False);
278 XtSetSensitive(pass[curr_player],False);
279 XtSetSensitive(evaluate[curr_player],False);
280 XtSetSensitive(revert[curr_player],False);
281 XtVaSetValues(pauseButton[curr_player],XtNstate,False,NULL);
282 XtSetSensitive(pauseButton[curr_player],False);
283
284 if (time_limit>0)
285 {
286 XtVaSetValues(timeleft[curr_player],
287 XtNpercentageCompleted,0,NULL);
288 XtVaSetValues(pauseButton[curr_player],
289 XtNmappedWhenManaged,False,NULL);
290 }
291 }
292
293 /* move to next player */
294 curr_player = (curr_player+1) % num_players;
295 Wait(MESS_DELAY);
296 ShowBoard(False);
297
298 if (num_passed==num_players)
299 GameOver(-1);
300
301 sprintf(mess,PROMPT[YOUR_GO],player[curr_player].name);
302 sprintf(mess2,PROMPT[START_GO_ALL],player[curr_player].name);
303 Message(curr_player,mess,mess2);
304 ShowScores();
305 /* beep the player */
306 XBell(dpy[curr_player],2*bell_level-100);
307 savegame();
308
309 if (type[curr_player]!=0)
310 ComputerGo();
311 else
312 {
313 /* make new player's buttons sensitive */
314 XtSetSensitive(change[curr_player],True);
315 XtSetSensitive(finish[curr_player],True);
316 XtSetSensitive(pass[curr_player],True);
317 XtSetSensitive(evaluate[curr_player],True);
318 XtSetSensitive(revert[curr_player],True);
319 XtSetSensitive(pauseButton[curr_player],True);
320
321 /* start the timer */
322 finished_go=False;
323 comp=0;
324 if (time_limit>0)
325 {
326 XtVaSetValues(pauseButton[curr_player],
327 XtNmappedWhenManaged,True,NULL);
328 Click();
329 }
330 }
331 }
332
333
ComputerGo()334 void ComputerGo()
335 {
336 int score_for_go,ac,dn,bn;
337 char mess[256];
338 Boolean all_space;
339
340 XtVaSetValues(compw[curr_player],XtNlabel,PROMPT[THINKING],NULL);
341 comp_move(&score_for_go,type[curr_player]);
342 UpdateComp(0);
343 XtVaSetValues(compw[curr_player],XtNlabel," ",NULL);
344
345 if (score_for_go==0)
346 {
347 sprintf(mess,PROMPT[PASS],player[curr_player].name);
348 MessageAll(mess);
349 num_passed++;
350 }
351 else
352 {
353 num_passed=0;
354 sprintf(mess,PROMPT[COMP_SCORE],player[curr_player].name,score_for_go,
355 PROMPT[WDPOINT+(score_for_go>1)]);
356 MessageAll(mess);
357 ShowBoard(True);
358 ShowBar(curr_player);
359 ShowScores();
360
361 /* copy board to cboard */
362 for (dn=0; dn<BOARDSIZE; dn++)
363 for (ac=0; ac<BOARDSIZE; ac++)
364 {
365 cboard[ac][dn]=board[ac][dn];
366 if (board[ac][dn] != ' ')
367 is_perm[ac][dn] = True;
368 }
369
370 /* check for player going out */
371 all_space=True;
372 for (bn=0; bn<BARLEN; bn++)
373 if (player[curr_player].bar[bn]!=' ')
374 all_space=False;
375 if (all_space==True)
376 GameOver(curr_player);
377 }
378 TilesLeft();
379 GotoNextPlayer();
380 }
381
382
ChangePopdown(Widget w,XtPointer client_data,XtPointer call_data)383 void ChangePopdown(Widget w, XtPointer client_data, XtPointer call_data)
384 {
385 int i,changedptr=0;
386 Boolean state;
387 char changed[8],mess[256];
388
389 waiting_for_change=False;
390 if (changeconfirm==w)
391 {
392 /* user clicked on confirm, not cancel */
393 for (i=0; i<BARLEN; i++)
394 {
395 XtVaGetValues(changeletts[i],XtNstate,&state,NULL);
396 if (state)
397 changed[changedptr++]=player[curr_player].bar[i];
398 }
399
400 if (changedptr==0) return; /* no selected letters ! */
401
402 Deselect(curr_player);
403 changed[changedptr]='\0';
404 if (changedptr>bagptr+1)
405 {
406 XtPopdown(changeshell);
407 XtDestroyWidget(changeshell);
408 MessageOne(curr_player,PROMPT[CANNOT_CHANGE]);
409 }
410 else
411 {
412 /* remove changed letters from bar */
413 for (i=0; i<BARLEN; i++)
414 {
415 XtVaGetValues(changeletts[i],XtNstate,&state,NULL);
416 if (state)
417 player[curr_player].bar[i]=' ';
418 }
419
420 fillbar(curr_player);
421 addtobag(changed);
422 sprintf(mess,PROMPT[PLAYER_CHANGE],player[curr_player].name);
423
424 XtPopdown(changeshell);
425 XtDestroyWidget(changeshell);
426
427 MessageAll(mess);
428
429 RemoveFromBoard();
430 ShowTiles();
431
432 ShowBar(curr_player);
433 num_passed=0;
434 GotoNextPlayer();
435 }
436 }
437 else
438 {
439 XtPopdown(changeshell);
440 XtDestroyWidget(changeshell);
441 }
442 }
443
BlankPopdown(Widget w,XtPointer client_data,XtPointer call_data)444 void BlankPopdown(Widget w, XtPointer client_data, XtPointer call_data)
445 {
446 int i;
447
448 for (i=0; i<NUMLETTERS; i++)
449 if (a2z[i]==w)
450 blank_letter=i+'a';
451 XtPopdown(blankshell);
452 XtDestroyWidget(blankshell);
453 waiting_for_blank=False;
454 }
455
PassGo()456 void PassGo()
457 {
458 char mess[256];
459
460 XSync(dpy[curr_player],True);
461 XtSetSensitive(pass[curr_player],False);
462 /* make player's buttons insensitive */
463 XtSetSensitive(change[curr_player],False);
464 XtSetSensitive(finish[curr_player],False);
465 XtSetSensitive(evaluate[curr_player],False);
466 XtSetSensitive(revert[curr_player],False);
467 XtVaSetValues(pauseButton[curr_player],XtNstate,False,NULL);
468 XtSetSensitive(pauseButton[curr_player],False);
469
470 RemoveFromBoard();
471 ShowTiles();
472 ShowBar(curr_player);
473 sprintf(mess,PROMPT[PASS],player[curr_player].name);
474 MessageAll(mess);
475 num_passed++;
476 GotoNextPlayer();
477 }
478
GameOver(int ply)479 void GameOver(int ply)
480 {
481 /* end of game: ply is player who went out, or -1 if everyone passed */
482 char mess[256];
483 int i,points=0,totalpoints=0,winner=0,hiscore=-10000;
484 int drawn=0,dr[MAXPLAYERS];
485 XEvent event;
486
487 if (ply==-1)
488 {
489 /* everyone passed */
490 for (i=0; i<num_players; i++)
491 {
492 points=bartotal(i);
493 player[i].score-=points;
494 sprintf(mess,PROMPT[GAME_OVER_LOSE],points,PROMPT[WDPOINT+(points>1)]);
495 MessageOne(i,mess);
496 }
497 }
498 else
499 {
500 /* ply went out, by using all his tiles */
501 for (i=0; i<num_players; i++)
502 if (i!=ply)
503 {
504 points=bartotal(i);
505 player[i].score-=points;
506 sprintf(mess,PROMPT[GAME_OVER_LOSE],points,PROMPT[WDPOINT+(points>1)]);
507 MessageOne(i,mess);
508 totalpoints+=points;
509 }
510 player[ply].score+=totalpoints;
511 sprintf(mess,PROMPT[GAME_OVER_WIN],totalpoints,PROMPT[WDPOINT+(points>1)]);
512 MessageOne(ply,mess);
513 }
514 ShowScores();
515 Wait(MESS_DELAY);
516
517 for (i=0; i<num_players; i++)
518 {
519 if (player[i].score == hiscore)
520 {
521 drawn++;
522 dr[drawn]=i;
523 }
524 if (player[i].score > hiscore)
525 {
526 winner=i;
527 hiscore=player[i].score;
528 drawn=0;
529 dr[drawn]=i;
530 }
531 }
532
533 switch (drawn)
534 {
535 case 0:
536 sprintf(mess,PROMPT[WINNER],player[winner].name);
537 MessageAll(mess);
538 break;
539 case 1:
540 if (num_players==2)
541 MessageAll(PROMPT[DRAW_ALL]);
542 else
543 {
544 sprintf(mess,PROMPT[DRAWN2],player[dr[0]].name,player[dr[1]].name);
545 MessageAll(mess);
546 }
547 break;
548 case 2:
549 if (num_players==3)
550 MessageAll(PROMPT[DRAW_ALL]);
551 else
552 {
553 sprintf(mess,PROMPT[DRAWN3],player[dr[0]].name,player[dr[1]].name,
554 player[dr[2]].name);
555 MessageAll(mess);
556 }
557 break;
558 case 3:
559 MessageAll(PROMPT[DRAW_ALL]);
560 break;
561 default:
562 MessageAll(PROMPT[SPAM]);
563 break;
564 }
565
566 loadscores();
567 alterscores();
568 savescores();
569
570 for (i=0; i<num_players; i++)
571 {
572 menu_status[i][2]=True;
573 HiScoresPopup(i);
574 }
575 Wait(MESS_DELAY);
576 MessageAll(PROMPT[TO_END]);
577
578 for (i=0; i<num_players; i++)
579 {
580 if (type[i]==0)
581 {
582 XtSetSensitive(change[i],False);
583 XtSetSensitive(finish[i],False);
584 XtSetSensitive(pass[i],False);
585 XtSetSensitive(evaluate[i],False);
586 XtSetSensitive(revert[i],False);
587 }
588 XtVaSetValues(pauseButton[curr_player],XtNstate,False,NULL);
589 XtSetSensitive(pauseButton[curr_player],False);
590 }
591
592 while (1)
593 {
594 XtAppNextEvent(app_context,&event);
595 XtDispatchEvent(&event);
596 }
597
598 }
599
RevertToBar(Widget w,XtPointer client_data,XtPointer call_data)600 void RevertToBar(Widget w, XtPointer client_data, XtPointer call_data)
601 {
602 int i;
603
604 for (i=0; w!=revert[i]; i++)
605 ;
606
607 Deselect(i);
608 if (i==curr_player)
609 {
610 RemoveFromBoard();
611 ShowTiles();
612 }
613 ShowBar(i);
614 }
615
Juggle(Widget w,XtPointer client_data,XtPointer call_data)616 void Juggle(Widget w, XtPointer client_data, XtPointer call_data)
617 {
618 int i,rep=5;
619 int swap1,swap2;
620 char tmp;
621
622 /* identify current player */
623 for (i=0; w!=juggle[i]; i++)
624 ;
625
626 Deselect(i);
627 /* revert letters to bar */
628 if (i==curr_player)
629 {
630 RemoveFromBoard();
631 ShowTiles();
632 }
633
634 for ( ; rep>0; rep--)
635 {
636 swap1 = rand() % BARLEN;
637 swap2 = rand() % BARLEN;
638 while (swap2 == swap1)
639 swap2 = rand() % BARLEN;
640 tmp = player[i].bar[swap1];
641 player[i].bar[swap1] = player[i].bar[swap2];
642 player[i].bar[swap2] = tmp;
643 }
644 ShowBar(i);
645 }
646
CompShowBar(Widget w,XtPointer client_data,XtPointer call_data)647 void CompShowBar(Widget w, XtPointer client_data, XtPointer call_data)
648 {
649 int i;
650
651 for (i=0; i<num_players; i++)
652 {
653 if ((type[i]>0) && (w==compshowbar[i]))
654 XtVaSetValues(barw[i],XtNmappedWhenManaged,True,NULL);
655 }
656 }
657
CompHideBar(Widget w,XtPointer client_data,XtPointer call_data)658 void CompHideBar(Widget w, XtPointer client_data, XtPointer call_data)
659 {
660 int i;
661
662 for (i=0; i<num_players; i++)
663 {
664 if ((type[i]>0) && (w==comphidebar[i]))
665 XtVaSetValues(barw[i],XtNmappedWhenManaged,False,NULL);
666 }
667 }
668
669
670
671