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