Inspired by #17, compared our Nitfol source to the source from its last
[rodin/chimara.git] / interpreters / frotz / glkscreen.c
1 /******************************************************************************
2  *                                                                            *
3  * Copyright (C) 2006-2009 by Tor Andersson.                                  *
4  *                                                                            *
5  * This file is part of Gargoyle.                                             *
6  *                                                                            *
7  * Gargoyle is free software; you can redistribute it and/or modify           *
8  * it under the terms of the GNU General Public License as published by       *
9  * the Free Software Foundation; either version 2 of the License, or          *
10  * (at your option) any later version.                                        *
11  *                                                                            *
12  * Gargoyle is distributed in the hope that it will be useful,                *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
15  * GNU General Public License for more details.                               *
16  *                                                                            *
17  * You should have received a copy of the GNU General Public License          *
18  * along with Gargoyle; if not, write to the Free Software                    *
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA *
20  *                                                                            *
21  *****************************************************************************/
22
23 /* screen.c - Generic screen manipulation
24  *
25  *  Portions copyright (c) 1995-1997 Stefan Jokisch.
26  */
27
28 #include "glkfrotz.h"
29
30 static unsigned char statusline[256];
31 static int oldstyle = 0;
32 static int curstyle = 0;
33 static int upperstyle = 0;
34 static int lowerstyle = 0;
35 static int cury = 1;
36 static int curx = 1;
37
38 int curr_fg = 0;
39 int curr_bg = 0;
40
41 /* To make the common code happy */
42
43 int os_char_width (zchar z)
44 {
45         return 1;
46 }
47
48 int os_string_width (const zchar *s)
49 {
50         int width = 0;
51         zchar c;
52         while ((c = *s++) != 0)
53                 if (c == ZC_NEW_STYLE || c == ZC_NEW_FONT)
54                         s++;
55                 else
56                         width += os_char_width(c);
57         return width;
58 }
59
60 void os_prepare_sample (int a)
61 {
62         glk_sound_load_hint(a, 1);
63 }
64
65 void os_finish_with_sample (int a)
66 {
67         glk_sound_load_hint(a, 0);
68 }
69
70 /*
71  * os_start_sample
72  *
73  * Play the given sample at the given volume (ranging from 1 to 8 and
74  * 255 meaning a default volume). The sound is played once or several
75  * times in the background (255 meaning forever). In Z-code 3 the
76  * repeats value is always 0 and the number of repeats is taken from
77  * the sound file itself. The end_of_sound function is called as soon
78  * as the sound finishes.
79  *
80  */
81
82 void os_start_sample (int number, int volume, int repeats, zword eos)
83 {
84         int vol;
85
86         if (!gos_channel)
87         {
88                 gos_channel = glk_schannel_create(0);
89                 if (!gos_channel)
90                         return;
91         }
92
93         switch (volume)
94         {
95         case   1: vol = 0x02000; break;
96         case   2: vol = 0x04000; break;
97         case   3: vol = 0x06000; break;
98         case   4: vol = 0x08000; break;
99         case   5: vol = 0x0a000; break;
100         case   6: vol = 0x0c000; break;
101         case   7: vol = 0x0e000; break;
102         case   8: vol = 0x10000; break;
103         default:  vol = 0x20000; break;
104         }
105
106         /* we dont do repeating or eos-callback for now... */
107         glk_schannel_play_ext(gos_channel, number, 1, 0);
108         glk_schannel_set_volume(gos_channel, vol);
109 }
110
111 void os_stop_sample (int a)
112 {
113         if (!gos_channel)
114                 return;
115         glk_schannel_stop(gos_channel);
116 }
117
118 void os_beep (int volume)
119 {
120 }
121
122 void gos_update_width(void)
123 {
124         glui32 width;
125         if (gos_upper)
126         {
127                 glk_window_get_size(gos_upper, &width, NULL);
128                 h_screen_cols = width;
129                 SET_BYTE(H_SCREEN_COLS, width);
130                 if (curx > width)
131                 {
132                         glk_window_move_cursor(gos_upper, 0, cury-1);
133                         curx = 1;
134                 }
135         }
136 }
137
138 void gos_update_height(void)
139 {
140         glui32 height_upper;
141         glui32 height_lower;
142         if (gos_curwin)
143         {
144                 glk_window_get_size(gos_upper, NULL, &height_upper);
145                 glk_window_get_size(gos_lower, NULL, &height_lower);
146                 h_screen_rows = height_upper + height_lower + 1;
147                 SET_BYTE(H_SCREEN_ROWS, h_screen_rows);
148         }
149 }
150
151 void reset_status_ht(void)
152 {
153         glui32 height;
154         if (gos_upper)
155         {
156                 glk_window_get_size(gos_upper, NULL, &height);
157                 if (mach_status_ht != height)
158                         glk_window_set_arrangement(
159                                 glk_window_get_parent(gos_upper),
160                                 winmethod_Above | winmethod_Fixed,
161                                 mach_status_ht, NULL);
162         }
163 }
164
165 void erase_window (int w)
166 {
167         if (w == 0)
168                 glk_window_clear(gos_lower);
169         else if (gos_upper)
170         {
171                 memset(statusline, ' ', sizeof statusline);
172                 glk_window_clear(gos_upper);
173                 reset_status_ht();
174                 curr_status_ht = 0;
175         }
176 }
177
178 void split_window (int lines)
179 {
180         if (!gos_upper)
181                 return;
182         /* The top line is always set for V1 to V3 games */
183         if (h_version < V4)
184                 lines++;
185
186         if (!lines || lines > curr_status_ht)
187         {
188                 glui32 height;
189
190                 glk_window_get_size(gos_upper, NULL, &height);
191                 if (lines != height)
192                         glk_window_set_arrangement(
193                                 glk_window_get_parent(gos_upper),
194                                 winmethod_Above | winmethod_Fixed,
195                                 lines, NULL);
196                 curr_status_ht = lines;
197         }
198         mach_status_ht = lines;
199         if (cury > lines)
200         {
201                 glk_window_move_cursor(gos_upper, 0, 0);
202                 curx = cury = 1;
203         }
204         gos_update_width();
205
206         if (h_version == V3)
207                 glk_window_clear(gos_upper);
208 }
209
210 void restart_screen (void)
211 {
212         erase_window(0);
213         erase_window(1);
214         split_window(0);
215 }
216
217 /*
218  * statusline overflowed the window size ... bad game!
219  * so ... split status text into regions, reformat and print anew.
220  */
221
222 void packspaces(unsigned char *src, unsigned char *dst)
223 {
224         int killing = 0;
225         while (*src)
226         {
227                 if (*src == ' ')
228                         killing++;
229                 else
230                         killing = 0;
231                 if (killing > 2)
232                         src++;
233                 else
234                         *dst++ = *src++;
235         }
236         *dst = 0;
237 }
238
239 void smartstatusline (void)
240 {
241         unsigned char packed[256];
242         unsigned char buf[256];
243         unsigned char *a, *b, *c, *d;
244         int roomlen, scorelen, scoreofs;
245         int len;
246
247         statusline[curx - 1] = 0; /* terminate! */
248
249         packspaces(statusline, packed);
250         //strcpy(packed, statusline);
251         len = strlen(packed);
252
253         a = packed;
254         while (a[0] == ' ')
255                 a ++;
256
257         b = a;
258         while (b[0] != 0 && !(b[0] == ' ' && b[1] == ' '))
259                 b ++;
260
261         c = b;
262         while (c[0] == ' ')
263                 c ++;
264
265         d = packed + len - 1;
266         while (d[0] == ' ' && d > c)
267                 d --;
268         if (d[0] != ' ' && d[0] != 0)
269                 d ++;
270         if (d < c)
271                 d = c;
272
273         //printf("smart '%s'\n", packed);
274         //printf("smart %d %d %d %d\n",a-packed,b-packed,c-packed,d-packed);
275
276         roomlen = b - a;
277         scorelen = d - c;
278         scoreofs = h_screen_cols - scorelen - 2;
279         if (scoreofs <= roomlen)
280                 scoreofs = roomlen + 2;
281
282         memset(buf, ' ', h_screen_cols);
283         memcpy(buf + 1 + scoreofs, c, scorelen);
284         memcpy(buf + 1, a, roomlen);
285         //if (roomlen >= scoreofs)
286         //      buf[roomlen + 1] = '|';
287
288         glk_window_move_cursor(gos_upper, 0, 0);
289         glk_set_style(style_User1);
290         glk_put_buffer(buf, h_screen_cols);
291         glk_window_move_cursor(gos_upper, cury - 1, curx - 1);
292 }
293
294 void screen_char (zchar c)
295 {
296         if (gos_linepending && (gos_curwin == gos_linewin))
297         {
298                 gos_cancel_pending_line();
299                 if (gos_curwin == gos_upper)
300                 {
301                         curx = 1;
302                         cury ++;
303                 }
304                 if (c == '\n')
305                         return;
306         }
307
308         if (gos_upper && gos_curwin == gos_upper) {
309                 if (cury > mach_status_ht) {
310                         mach_status_ht = cury;
311                         reset_status_ht();
312                 }
313         }
314
315         /* check fixed flag in header, game can change it at whim */
316         if (gos_curwin == gos_lower)
317         {
318                 static int forcefix = -1;
319                 int curfix = h_flags & FIXED_FONT_FLAG;
320                 if (forcefix != curfix)
321                 {
322                         forcefix = curfix;
323                         zargs[0] = 0xf000;      /* tickle tickle! */
324                         z_set_text_style();
325                 }
326         }
327
328         if (gos_upper && gos_curwin == gos_upper)
329         {
330                 if (c == '\n' || c == ZC_RETURN) {
331                         glk_put_char('\n');
332                         curx = 1;
333                         cury ++;
334                 }
335                 else {
336                         if (cury == 1)
337                         {
338                                 if (curx < sizeof statusline)
339                                         statusline[curx - 1] = c;
340                                 curx++;
341                                 if (curx <= h_screen_cols)
342                                         glk_put_char(c);
343                                 else
344                                         smartstatusline();
345                         }
346                         else
347                         {
348                                 glk_put_char(c);
349                                 curx++;
350                                 if (curx > h_screen_cols) {
351                                         curx = 1;
352                                         cury++;
353                                 }
354                         }
355                 }
356         }
357         else if (gos_curwin == gos_lower)
358         {
359                 if (c == ZC_RETURN)
360                         glk_put_char('\n');
361                 else glk_put_char(c);
362         }
363 }
364
365 void screen_new_line (void)
366 {
367         screen_char('\n');
368 }
369
370 void screen_word (const zchar *s)
371 {
372         zchar c;
373         while ((c = *s++) != 0)
374                 if (c == ZC_NEW_FONT)
375                         s++;
376                 else if (c == ZC_NEW_STYLE)
377                         s++;
378                 else
379                         screen_char (c); 
380 }
381
382 void screen_mssg_on (void)
383 {
384         if (gos_curwin == gos_lower)
385         {
386                 oldstyle = curstyle;
387                 glk_set_style(style_Preformatted);
388                 glk_put_string("\n    ");
389         }
390 }
391
392 void screen_mssg_off (void)
393 {
394         if (gos_curwin == gos_lower)
395         {
396                 glk_put_char('\n');
397                 zargs[0] = 0;
398                 z_set_text_style();
399                 zargs[0] = oldstyle;
400                 z_set_text_style();
401         }
402 }
403
404 /*
405  * z_buffer_mode, turn text buffering on/off.
406  *
407  *              zargs[0] = new text buffering flag (0 or 1)
408  *
409  */
410
411 void z_buffer_mode (void)
412 {
413 }
414
415 /*
416  * z_erase_line, erase the line starting at the cursor position.
417  *
418  *              zargs[0] = 1 + #units to erase (1 clears to the end of the line)
419  *
420  */
421
422 void z_erase_line (void)
423 {
424         int i;
425
426         if (gos_upper && gos_curwin == gos_upper)
427         {
428                 for (i = 0; i < h_screen_cols + 1 - curx; i++)
429                         glk_put_char(' ');
430                 glk_window_move_cursor(gos_curwin, curx - 1, cury - 1);
431         }
432 }
433
434 /*
435  * z_erase_window, erase a window or the screen to background colour.
436  *
437  *              zargs[0] = window (-3 current, -2 screen, -1 screen & unsplit)
438  *
439  */
440
441 void z_erase_window (void)
442 {
443         short w = zargs[0];
444         if (w == -2)
445         {
446                 if (gos_upper) {
447                         glk_set_window(gos_upper);
448 #ifdef GARGLK
449                         garglk_set_zcolors(curr_fg, curr_bg);
450 #endif /* GARGLK */
451                         glk_window_clear(gos_upper);
452                         glk_set_window(gos_curwin);
453                 }
454                 glk_window_clear(gos_lower);
455         }
456         if (w == -1)
457         {
458                 if (gos_upper) {
459                         glk_set_window(gos_upper);
460 #ifdef GARGLK
461                         garglk_set_zcolors(curr_fg, curr_bg);
462 #endif /* GARGLK */
463                         glk_window_clear(gos_upper);
464                 }
465                 glk_window_clear(gos_lower);
466                 split_window(0);
467                 glk_set_window(gos_lower);
468                 gos_curwin = gos_lower;
469         }
470         if (w == 0)
471                 glk_window_clear(gos_lower);
472         if (w == 1 && gos_upper)
473                 glk_window_clear(gos_upper);
474 }
475
476 /*
477  * z_get_cursor, write the cursor coordinates into a table.
478  *
479  *              zargs[0] = address to write information to
480  *
481  */
482
483 void z_get_cursor (void)
484 {
485         storew ((zword) (zargs[0] + 0), cury);
486         storew ((zword) (zargs[0] + 2), curx);
487 }
488
489 /*
490  * z_print_table, print ASCII text in a rectangular area.
491  *
492  *              zargs[0] = address of text to be printed
493  *              zargs[1] = width of rectangular area
494  *              zargs[2] = height of rectangular area (optional)
495  *              zargs[3] = number of char's to skip between lines (optional)
496  *
497  */
498
499 void z_print_table (void)
500 {
501         zword addr = zargs[0];
502         zword x;
503         int i, j;
504
505         /* Supply default arguments */
506
507         if (zargc < 3)
508                 zargs[2] = 1;
509         if (zargc < 4)
510                 zargs[3] = 0;
511
512         /* Write text in width x height rectangle */
513
514         x = curx;
515
516         for (i = 0; i < zargs[2]; i++) {
517
518                 if (i != 0) {
519                         cury += 1;
520                         curx = x;
521                 }
522
523                 for (j = 0; j < zargs[1]; j++) {
524
525                         zbyte c;
526
527                         LOW_BYTE (addr, c)
528                         addr++;
529
530                         print_char (c);
531                 }
532
533                 addr += zargs[3];
534         }
535 }
536
537 /*
538  * z_set_colour, set the foreground and background colours.
539  *
540  *              zargs[0] = foreground colour
541  *              zargs[1] = background colour
542  *              zargs[2] = window (-3 is the current one, optional)
543  *
544  */
545
546 void z_set_colour (void)
547 {
548         int zfore = zargs[0];
549         int zback = zargs[1];
550
551
552         if (!(zfore == 0 && zback == 0)) {
553 #ifdef GARGLK
554                 garglk_set_zcolors(zfore, zback);
555 #endif /* GARGLK */
556         }
557
558         curr_fg = zfore;
559         curr_bg = zback;
560 }
561
562 /*
563  * z_set_font, set the font for text output and store the previous font.
564  *
565  *               zargs[0] = number of font or 0 to keep current font
566  *
567  */
568
569 void z_set_font (void)
570 {
571 }
572
573 /*
574  * z_set_cursor, set the cursor position or turn the cursor on/off.
575  *
576  *              zargs[0] = y-coordinate or -2/-1 for cursor on/off
577  *              zargs[1] = x-coordinate
578  *              zargs[2] = window (-3 is the current one, optional)
579  *
580  */
581
582 void z_set_cursor (void)
583 {
584         cury = zargs[0];
585         curx = zargs[1];
586         if (gos_upper)
587                 glk_window_move_cursor(gos_upper, curx - 1, cury - 1);
588 }
589
590 /*
591  * z_set_text_style, set the style for text output.
592  *
593  *               zargs[0] = style flags to set or 0 to reset text style
594  *
595  */
596
597 void z_set_text_style (void)
598 {
599         int style;
600
601         if (zargs[0] == 0)
602                 curstyle = 0;
603         else if (zargs[0] != 0xf000) /* not tickle time */
604                 curstyle |= zargs[0];
605
606         if (h_flags & FIXED_FONT_FLAG)
607                 style = curstyle | FIXED_WIDTH_STYLE;
608         else
609                 style = curstyle;
610
611         if (gos_linepending && gos_curwin == gos_linewin)
612                 return;
613
614         if (style & REVERSE_STYLE)
615         {
616                 if (gos_curwin == gos_upper && gos_upper) {
617                         glk_set_style(style_User1);
618                 }
619 #ifdef GARGLK
620                 garglk_set_reversevideo(TRUE);
621 #endif /* GARGLK */
622         }
623         else if (style & FIXED_WIDTH_STYLE)
624                 glk_set_style(style_Preformatted);
625         else if (style & BOLDFACE_STYLE && style & EMPHASIS_STYLE)
626                 glk_set_style(style_Alert);
627         else if (style & BOLDFACE_STYLE)
628                 glk_set_style(style_Subheader);
629         else if (style & EMPHASIS_STYLE)
630                 glk_set_style(style_Emphasized);
631         else
632                 glk_set_style(style_Normal);
633
634         if (curstyle == 0) {
635 #ifdef GARGLK
636                 garglk_set_reversevideo(FALSE);
637 #endif /* GARGLK */
638         }
639 }
640
641 /*
642  * z_set_window, select the current window.
643  *
644  *              zargs[0] = window to be selected (-3 is the current one)
645  *
646  */
647
648 void z_set_window (void)
649 {
650         int win = zargs[0];
651
652         if (gos_curwin == gos_lower)
653                 lowerstyle = curstyle;
654         else
655                 upperstyle = curstyle;
656
657         if (win == 0)
658         {
659                 glk_set_window(gos_lower);
660                 gos_curwin = gos_lower;
661                 curstyle = lowerstyle;
662         }
663         else
664         {
665                 if (gos_upper)
666                         glk_set_window(gos_upper);
667                 gos_curwin = gos_upper;
668                 curstyle = upperstyle;
669         }
670
671         if (win == 0)
672             enable_scripting = TRUE;
673         else
674             enable_scripting = FALSE;
675 }
676
677 /*
678  * z_show_status, display the status line for V1 to V3 games.
679  *
680  *              no zargs used
681  *
682  */
683
684 static void pad_status_line (int column)
685 {
686         int spaces;
687         spaces = (h_screen_cols + 1 - curx) - column;
688         while (spaces-- > 0)
689                 print_char(' ');
690 }
691
692 void z_show_status (void)
693 {
694         zword global0;
695         zword global1;
696         zword global2;
697         zword addr;
698
699         bool brief = FALSE;
700
701         if (!gos_upper)
702                 return;
703
704         /* One V5 game (Wishbringer Solid Gold) contains this opcode by
705            accident, so just return if the version number does not fit */
706
707         if (h_version >= V4)
708                 return;
709
710         /* Read all relevant global variables from the memory of the
711            Z-machine into local variables */
712
713         addr = h_globals;
714         LOW_WORD (addr, global0)
715         addr += 2;
716         LOW_WORD (addr, global1)
717         addr += 2;
718         LOW_WORD (addr, global2)
719
720         /* Move to top of the status window, and print in reverse style. */
721
722         glk_set_window(gos_upper);
723         gos_curwin = gos_upper;
724
725         curx = cury = 1;
726         glk_window_move_cursor(gos_upper, 0, 0);
727         glk_set_style(style_User1);
728
729         /* If the screen width is below 55 characters then we have to use
730            the brief status line format */
731
732         if (h_screen_cols < 55)
733                 brief = TRUE;
734
735         /* Print the object description for the global variable 0 */
736
737         print_char (' ');
738         print_object (global0);
739
740         /* A header flag tells us whether we have to display the current
741            time or the score/moves information */
742
743         if (h_config & CONFIG_TIME) {           /* print hours and minutes */
744
745                 zword hours = (global1 + 11) % 12 + 1;
746
747                 pad_status_line (brief ? 15 : 20);
748
749                 print_string ("Time: ");
750
751                 if (hours < 10)
752                         print_char (' ');
753                 print_num (hours);
754
755                 print_char (':');
756
757                 if (global2 < 10)
758                         print_char ('0');
759                 print_num (global2);
760
761                 print_char (' ');
762
763                 print_char ((global1 >= 12) ? 'p' : 'a');
764                 print_char ('m');
765
766         } else {                                                                /* print score and moves */
767
768                 pad_status_line (brief ? 15 : 30);
769
770                 print_string (brief ? "S: " : "Score: ");
771                 print_num (global1);
772
773                 pad_status_line (brief ? 8 : 14);
774
775                 print_string (brief ? "M: " : "Moves: ");
776                 print_num (global2);
777
778         }
779
780         /* Pad the end of the status line with spaces */
781
782         pad_status_line (0);
783
784         /* Return to the lower window */
785
786         glk_set_window(gos_lower);
787         gos_curwin = gos_lower;
788 }
789
790 /*
791  * z_split_window, split the screen into an upper (1) and lower (0) window.
792  *
793  *              zargs[0] = height of upper window in screen units (V6) or #lines
794  *
795  */
796
797 void z_split_window (void)
798 {
799         split_window(zargs[0]);
800 }
801