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