OSDN Git Service

Merge pull request #3532 from sikabane-works/release/3.0.0.87-alpha
[hengbandforosx/hengbandosx.git] / src / term / z-term.cpp
1 /*
2  * @brief Purpose: a generic, efficient, terminal window package -BEN-
3  * Copyright (c) 1997 Ben Harrison
4  *
5  * This software may be copied and distributed for educational, research,
6  * and not for profit purposes provided that this copyright and statement
7  * are included in all such copies.
8  */
9
10 #include "term/z-term.h"
11 #include "game-option/map-screen-options.h"
12 #include "game-option/runtime-arguments.h"
13 #include "game-option/special-options.h"
14 #include "term/gameterm.h"
15 #include "term/term-color-types.h"
16 #include "term/z-virt.h"
17
18 /* Special flags in the attr data */
19 #define AF_BIGTILE2 0xf0
20 #define AF_TILE1 0x80
21
22 #ifdef JP
23 /*
24  * 全角文字対応。
25  * 属性に全角文字の1バイト目、2バイト目も記憶。
26  * By FIRST
27  */
28 #define AF_KANJI1 0x10
29 #define AF_KANJI2 0x20
30 #define AF_KANJIC 0x0f
31 #endif
32
33 /* The current "term" */
34 term_type *game_term = nullptr;
35
36 /*** Local routines ***/
37
38 /*!
39  * @brief オブジェクトが生存している間、画面表示関数の座標をずらす。
40  *
41  * 引数でずらすX座標オフセット、Y座標オフセットをそれぞれ指定する。
42  * 正方向のオフセットのみ有効。負の値が指定された場合、オフセット位置は 0 とする。
43  * 指定された座標が std::nullopt の場合、現在のオフセットを維持する。
44  *
45  * @param x X座標オフセット
46  * @param y Y座標オフセット
47  */
48 TermOffsetSetter::TermOffsetSetter(std::optional<TERM_LEN> x, std::optional<TERM_LEN> y)
49     : term(game_term)
50     , orig_offset_x(game_term != nullptr ? game_term->offset_x : 0)
51     , orig_offset_y(game_term != nullptr ? game_term->offset_y : 0)
52 {
53     if (this->term == nullptr) {
54         return;
55     }
56
57     if (x.has_value()) {
58         this->term->offset_x = (x.value() > 0) ? x.value() : 0;
59     }
60     if (y.has_value()) {
61         this->term->offset_y = (y.value() > 0) ? y.value() : 0;
62     }
63 }
64
65 TermOffsetSetter::~TermOffsetSetter()
66 {
67     if (this->term == nullptr) {
68         return;
69     }
70
71     this->term->offset_x = this->orig_offset_x;
72     this->term->offset_y = this->orig_offset_y;
73 }
74
75 /*!
76  * @brief オブジェクトが生存している間、画面表示関数の座標をずらす。
77  *
78  * 表示に使用する領域の大きさを指定し、その領域が画面中央に表示されるように座標をずらす。
79  * 引数で領域の横幅、縦幅をそれぞれ指定する。
80  * 画面の幅より大きな値が指定された場合はオフセット 0 になる。
81  * 指定された幅が std::nullopt の場合、画面の幅全体を使用する(オフセット 0 になる)。
82  *
83  * @param width 表示に使用する領域の横幅
84  * @param height 表示に使用する領域の縦幅
85  */
86 TermCenteredOffsetSetter::TermCenteredOffsetSetter(std::optional<TERM_LEN> width, std::optional<TERM_LEN> height)
87     : term(game_term)
88     , orig_centered_wid(game_term != nullptr ? game_term->centered_wid : std::nullopt)
89     , orig_centered_hgt(game_term != nullptr ? game_term->centered_hgt : std::nullopt)
90 {
91     if (game_term == nullptr) {
92         return;
93     }
94
95     const auto offset_x = width.has_value() ? (game_term->wid - width.value()) / 2 : 0;
96     const auto offset_y = height.has_value() ? (game_term->hgt - height.value()) / 2 : 0;
97     this->tos.emplace(offset_x, offset_y);
98
99     game_term->centered_wid = (width < game_term->wid) ? width : std::nullopt;
100     game_term->centered_hgt = (height < game_term->hgt) ? height : std::nullopt;
101 }
102
103 TermCenteredOffsetSetter::~TermCenteredOffsetSetter()
104 {
105     if (this->term == nullptr) {
106         return;
107     }
108
109     this->term->centered_wid = this->orig_centered_wid;
110     this->term->centered_hgt = this->orig_centered_hgt;
111 }
112
113 /*
114  * Initialize a "term_win" (using the given window size)
115  */
116 term_win::term_win(TERM_LEN w, TERM_LEN h)
117     : a(h, std::vector<TERM_COLOR>(w))
118     , c(h, std::vector<char>(w))
119     , ta(h, std::vector<TERM_COLOR>(w))
120     , tc(h, std::vector<char>(w))
121 {
122 }
123
124 std::unique_ptr<term_win> term_win::create(TERM_LEN w, TERM_LEN h)
125 {
126     // privateコンストラクタを呼び出すための補助クラス
127     struct impl : term_win {
128         impl(TERM_LEN w, TERM_LEN h)
129             : term_win(w, h)
130         {
131         }
132     };
133     return std::make_unique<impl>(w, h);
134 }
135
136 std::unique_ptr<term_win> term_win::clone() const
137 {
138     return std::make_unique<term_win>(*this);
139 }
140
141 void term_win::resize(TERM_LEN w, TERM_LEN h)
142 {
143     /* Ignore non-changes */
144     if (this->a.size() == static_cast<size_t>(h) && this->a[0].size() == static_cast<size_t>(w)) {
145         return;
146     }
147
148     this->a.resize(h, std::vector<TERM_COLOR>(w));
149     this->c.resize(h, std::vector<char>(w));
150     this->ta.resize(h, std::vector<TERM_COLOR>(w));
151     this->tc.resize(h, std::vector<char>(w));
152
153     for (TERM_LEN y = 0; y < h; y++) {
154         this->a[y].resize(w);
155         this->c[y].resize(w);
156         this->ta[y].resize(w);
157         this->tc[y].resize(w);
158     }
159
160     /* Illegal cursor */
161     if (this->cx >= w) {
162         this->cu = 1;
163     }
164     if (this->cy >= h) {
165         this->cu = 1;
166     }
167 }
168
169 /*** External hooks ***/
170
171 /*
172  * Execute the "Term->user_hook" hook, if available (see above).
173  */
174 errr term_user(int n)
175 {
176     /* Verify the hook */
177     if (!game_term->user_hook) {
178         return -1;
179     }
180
181     /* Call the hook */
182     return (*game_term->user_hook)(n);
183 }
184
185 /*
186  * Execute the "Term->xtra_hook" hook, if available (see above).
187  */
188 errr term_xtra(int n, int v)
189 {
190     /* Verify the hook */
191     if (!game_term->xtra_hook) {
192         return -1;
193     }
194
195     /* Call the hook */
196     return (*game_term->xtra_hook)(n, v);
197 }
198
199 /*** Fake hooks ***/
200
201 /*
202  * Fake hook for "term_curs()" (see above)
203  */
204 static errr term_curs_hack(TERM_LEN x, TERM_LEN y)
205 {
206     /* Unused */
207     (void)x;
208     (void)y;
209
210     return -1;
211 }
212
213 /*
214  * Fake hook for "term_bigcurs()" (see above)
215  */
216 static errr term_bigcurs_hack(TERM_LEN x, TERM_LEN y)
217 {
218     return (*game_term->curs_hook)(x, y);
219 }
220
221 /*
222  * Fake hook for "term_wipe()" (see above)
223  */
224 static errr term_wipe_hack(TERM_LEN x, TERM_LEN y, int n)
225 {
226     /* Unused */
227     (void)x;
228     (void)y;
229     (void)n;
230
231     return -1;
232 }
233
234 /*
235  * Fake hook for "term_text()" (see above)
236  */
237 static errr term_text_hack(TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, concptr cp)
238 {
239     /* Unused */
240     (void)x;
241     (void)y;
242     (void)n;
243     (void)a;
244     (void)cp;
245
246     return -1;
247 }
248
249 /*
250  * Fake hook for "term_pict()" (see above)
251  */
252 static errr term_pict_hack(TERM_LEN x, TERM_LEN y, int n, const TERM_COLOR *ap, concptr cp, const TERM_COLOR *tap, concptr tcp)
253 {
254     /* Unused */
255     (void)x;
256     (void)y;
257     (void)n;
258     (void)ap;
259     (void)cp;
260     (void)tap;
261     (void)tcp;
262
263     return -1;
264 }
265
266 /*** Efficient routines ***/
267
268 /*
269  * Mentally draw an attr/char at a given location
270  * Assumes given location and values are valid.
271  */
272 static void term_queue_char_aux(TERM_LEN x, TERM_LEN y, TERM_COLOR a, char c, TERM_COLOR ta, char tc)
273 {
274     if ((x < 0) || (x >= game_term->wid)) {
275         return;
276     }
277     if ((y < 0) || (y >= game_term->hgt)) {
278         return;
279     }
280
281     const auto &scrn = game_term->scr;
282
283     TERM_COLOR *scr_aa = &scrn->a[y][x];
284     char *scr_cc = &scrn->c[y][x];
285
286     TERM_COLOR *scr_taa = &scrn->ta[y][x];
287     char *scr_tcc = &scrn->tc[y][x];
288
289     /* Ignore non-changes */
290     if ((*scr_aa == a) && (*scr_cc == c) && (*scr_taa == ta) && (*scr_tcc == tc)) {
291         return;
292     }
293
294     /* Save the "literal" information */
295     *scr_aa = a;
296     *scr_cc = c;
297
298     *scr_taa = ta;
299     *scr_tcc = tc;
300
301     /* Check for new min/max row info */
302     if (y < game_term->y1) {
303         game_term->y1 = y;
304     }
305     if (y > game_term->y2) {
306         game_term->y2 = y;
307     }
308
309     /* Check for new min/max col info for this row */
310     if (x < game_term->x1[y]) {
311         game_term->x1[y] = x;
312     }
313     if (x > game_term->x2[y]) {
314         game_term->x2[y] = x;
315     }
316
317 #ifdef JP
318     if (((scrn->a[y][x] & AF_BIGTILE2) == AF_BIGTILE2) || (scrn->a[y][x] & AF_KANJI2))
319 #else
320     if ((scrn->a[y][x] & AF_BIGTILE2) == AF_BIGTILE2)
321 #endif
322         if ((x - 1) < game_term->x1[y]) {
323             game_term->x1[y]--;
324         }
325 }
326
327 void term_queue_char(TERM_LEN x, TERM_LEN y, TERM_COLOR a, char c, TERM_COLOR ta, char tc)
328 {
329     term_queue_char_aux(x + game_term->offset_x, y + game_term->offset_y, a, c, ta, tc);
330 }
331
332 /*
333  * Bigtile version of term_queue_char().
334  * If use_bigtile is FALSE, simply call term_queue_char().
335  * Otherwise, mentally draw a pair of attr/char at a given location.
336  * Assumes given location and values are valid.
337  */
338 void term_queue_bigchar(TERM_LEN x, TERM_LEN y, TERM_COLOR a, char c, TERM_COLOR ta, char tc)
339 {
340 #ifdef JP
341     /*
342      * A table which relates each ascii character to a multibyte
343      * character.
344      *
345      * 「■」は二倍幅豆腐の内部コードに使用。
346      */
347     static char ascii_to_zenkaku[] = " !”#$%&’()*+,-./"
348                                      "0123456789:;<=>?"
349                                      "@ABCDEFGHIJKLMNO"
350                                      "PQRSTUVWXYZ[\]^_"
351                                      "‘abcdefghijklmno"
352                                      "pqrstuvwxyz{|}~■";
353 #endif
354
355     byte a2;
356     char c2;
357
358     const auto ch_x = x + game_term->offset_x;
359     const auto ch_y = y + game_term->offset_y;
360
361     /* If non bigtile mode, call orginal function */
362     if (!use_bigtile) {
363         term_queue_char_aux(ch_x, ch_y, a, c, ta, tc);
364         return;
365     }
366
367     /* A tile becomes a Bigtile */
368     if ((a & AF_TILE1) && (c & 0x80)) {
369         /* Mark it as a Bigtile */
370         a2 = AF_BIGTILE2;
371
372         c2 = -1;
373
374         /* Ignore non-tile background */
375         if (!((ta & AF_TILE1) && (tc & 0x80))) {
376             ta = 0;
377             tc = 0;
378         }
379     }
380
381 #ifdef JP
382     /*
383      * Use a multibyte character instead of a dirty pair of ASCII
384      * characters.
385      */
386     else if (' ' <= c) /* isprint(c) */
387     {
388         c2 = ascii_to_zenkaku[2 * (c - ' ') + 1];
389         c = ascii_to_zenkaku[2 * (c - ' ')];
390
391         /* Mark it as a Kanji */
392         a2 = a | AF_KANJI2;
393         a |= AF_KANJI1;
394     }
395 #endif
396
397     else {
398         /* Dirty pair of ASCII characters */
399         a2 = TERM_WHITE;
400         c2 = ' ';
401     }
402
403     /* Display pair of attr/char */
404     term_queue_char_aux(ch_x, ch_y, a, c, ta, tc);
405     term_queue_char_aux(ch_x + 1, ch_y, a2, c2, 0, 0);
406 }
407
408 /*
409  * Mentally draw a string of attr/chars at a given location
410  * Assumes given location and values are valid.
411  * This function is designed to be fast, with no consistancy checking.
412  * It is used to update the map in the game.
413  */
414 void term_queue_line(TERM_LEN x, TERM_LEN y, int n, TERM_COLOR *a, char *c, TERM_COLOR *ta, char *tc)
415 {
416     const auto &scrn = game_term->scr;
417
418     TERM_LEN x1 = -1;
419     TERM_LEN x2 = -1;
420
421     TERM_COLOR *scr_aa = &scrn->a[y][x];
422     char *scr_cc = &scrn->c[y][x];
423
424     TERM_COLOR *scr_taa = &scrn->ta[y][x];
425     char *scr_tcc = &scrn->tc[y][x];
426
427     while (n--) {
428         /* Ignore non-changes */
429         if ((*scr_aa == *a) && (*scr_cc == *c) && (*scr_taa == *ta) && (*scr_tcc == *tc)) {
430             x++;
431             a++;
432             c++;
433             ta++;
434             tc++;
435             scr_aa++;
436             scr_cc++;
437             scr_taa++;
438             scr_tcc++;
439             continue;
440         }
441
442         /* Save the "literal" information */
443         *scr_taa++ = *ta++;
444         *scr_tcc++ = *tc++;
445
446         /* Save the "literal" information */
447         *scr_aa++ = *a++;
448         *scr_cc++ = *c++;
449
450         /* Track minimum changed column */
451         if (x1 < 0) {
452             x1 = x;
453         }
454
455         /* Track maximum changed column */
456         x2 = x;
457
458         x++;
459     }
460
461     /* Expand the "change area" as needed */
462     if (x1 >= 0) {
463         /* Check for new min/max row info */
464         if (y < game_term->y1) {
465             game_term->y1 = y;
466         }
467         if (y > game_term->y2) {
468             game_term->y2 = y;
469         }
470
471         /* Check for new min/max col info in this row */
472         if (x1 < game_term->x1[y]) {
473             game_term->x1[y] = x1;
474         }
475         if (x2 > game_term->x2[y]) {
476             game_term->x2[y] = x2;
477         }
478     }
479 }
480
481 /*
482  * Mentally draw some attr/chars at a given location
483  *
484  * Assumes that (x,y) is a valid location, that the first "n" characters
485  * of the string "s" are all valid (non-zero), and that (x+n-1,y) is also
486  * a valid location, so the first "n" characters of "s" can all be added
487  * starting at (x,y) without causing any illegal operations.
488  */
489 static void term_queue_chars(TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, std::string_view sv)
490 {
491     TERM_LEN x1 = -1, x2 = -1;
492
493     auto &scr_aa = game_term->scr->a[y];
494 #ifdef JP
495     auto &scr_cc = game_term->scr->c[y];
496
497     auto &scr_taa = game_term->scr->ta[y];
498     auto &scr_tcc = game_term->scr->tc[y];
499 #else
500     auto &scr_cc = game_term->scr->c[y];
501
502     auto &scr_taa = game_term->scr->ta[y];
503     auto &scr_tcc = game_term->scr->tc[y];
504 #endif
505
506 #ifdef JP
507     /* 表示文字なし */
508     if (n == 0 || sv.empty()) {
509         return;
510     }
511     /*
512      * 全角文字の右半分から文字を表示する場合、
513      * 重なった文字の左部分を消去。
514      * 表示開始位置が左端でないと仮定。
515      */
516     if ((scr_aa[x] & AF_KANJI2) && (scr_aa[x] & AF_BIGTILE2) != AF_BIGTILE2) {
517         scr_cc[x - 1] = ' ';
518         scr_aa[x - 1] &= AF_KANJIC;
519         x1 = x2 = x - 1;
520     }
521 #endif
522     /* Queue the attr/chars */
523     for (auto s = sv.begin(); (n > 0) && (s != sv.end()); x++, s++, n--) {
524 #ifdef JP
525         /* 特殊文字としてMSBが立っている可能性がある */
526         /* その場合attrのMSBも立っているのでこれで識別する */
527         /* check */
528         if (!(a & AF_TILE1) && iskanji(*s)) {
529             char nc1 = *s++;
530             char nc2 = *s;
531
532             byte na1 = (a | AF_KANJI1);
533             byte na2 = (a | AF_KANJI2);
534
535             if ((--n == 0) || !nc2) {
536                 break;
537             }
538
539             if (scr_aa[x++] == na1 && scr_aa[x] == na2 && scr_cc[x - 1] == nc1 && scr_cc[x] == nc2 && (scr_taa[x - 1] == 0) && (scr_taa[x] == 0) && (scr_tcc[x - 1] == 0) && (scr_tcc[x] == 0)) {
540                 continue;
541             }
542
543             scr_aa[x - 1] = na1;
544             scr_aa[x] = na2;
545             scr_cc[x - 1] = nc1;
546             scr_cc[x] = nc2;
547
548             if (x1 < 0) {
549                 x1 = x - 1;
550             }
551             x2 = x;
552         } else {
553 #endif
554             TERM_COLOR oa = scr_aa[x];
555             char oc = scr_cc[x];
556
557             TERM_COLOR ota = scr_taa[x];
558             char otc = scr_tcc[x];
559
560             /* Ignore non-changes */
561             if ((oa == a) && (oc == *s) && (ota == 0) && (otc == 0)) {
562                 continue;
563             }
564
565             /* Save the "literal" information */
566             scr_aa[x] = a;
567             scr_cc[x] = *s;
568
569             scr_taa[x] = 0;
570             scr_tcc[x] = 0;
571
572             /* Note the "range" of window updates */
573             if (x1 < 0) {
574                 x1 = x;
575             }
576             x2 = x;
577 #ifdef JP
578         }
579 #endif
580     }
581
582 #ifdef JP
583     /*
584      * 全角文字の左半分で表示を終了する場合、
585      * 重なった文字の右部分を消去。
586      * (条件追加:タイルの1文字目でない事を確かめるように。)
587      */
588     {
589         if ((x < game_term->wid) && !(scr_aa[x] & AF_TILE1) && (scr_aa[x] & AF_KANJI2)) {
590             scr_cc[x] = ' ';
591             scr_aa[x] &= AF_KANJIC;
592             if (x1 < 0) {
593                 x1 = x;
594             }
595             x2 = x;
596         }
597     }
598 #endif
599     /* Expand the "change area" as needed */
600     if (x1 >= 0) {
601         /* Check for new min/max row info */
602         if (y < game_term->y1) {
603             game_term->y1 = y;
604         }
605         if (y > game_term->y2) {
606             game_term->y2 = y;
607         }
608
609         /* Check for new min/max col info in this row */
610         if (x1 < game_term->x1[y]) {
611             game_term->x1[y] = x1;
612         }
613         if (x2 > game_term->x2[y]) {
614             game_term->x2[y] = x2;
615         }
616     }
617 }
618
619 /*** Refresh routines ***/
620
621 /*
622  * Flush a row of the current window (see "term_fresh")
623  * Display text using "term_pict()"
624  */
625 static void term_fresh_row_pict(TERM_LEN y, TERM_LEN x1, TERM_LEN x2)
626 {
627     auto &old_aa = game_term->old->a[y];
628     auto &old_cc = game_term->old->c[y];
629
630     const auto &scr_aa = game_term->scr->a[y];
631     const auto &scr_cc = game_term->scr->c[y];
632
633     auto &old_taa = game_term->old->ta[y];
634     auto &old_tcc = game_term->old->tc[y];
635
636     const auto &scr_taa = game_term->scr->ta[y];
637     const auto &scr_tcc = game_term->scr->tc[y];
638
639     TERM_COLOR ota;
640     char otc;
641
642     TERM_COLOR nta;
643     char ntc;
644
645     /* Pending length */
646     TERM_LEN fn = 0;
647
648     /* Pending start */
649     TERM_LEN fx = 0;
650
651     TERM_COLOR oa;
652     char oc;
653
654     TERM_COLOR na;
655     char nc;
656
657 #ifdef JP
658     /* 全角文字の2バイト目かどうか */
659     int kanji = 0;
660 #endif
661     /* Scan "modified" columns */
662     for (TERM_LEN x = x1; x <= x2; x++) {
663         /* See what is currently here */
664         oa = old_aa[x];
665         oc = old_cc[x];
666
667         /* See what is desired there */
668         na = scr_aa[x];
669         nc = scr_cc[x];
670
671 #ifdef JP
672         if (kanji) {
673             /* 全角文字2バイト目 */
674             kanji = 0;
675             old_aa[x] = na;
676             old_cc[x] = nc;
677             fn++;
678             continue;
679         }
680         /* 特殊文字としてMSBが立っている可能性がある */
681         /* その場合attrのMSBも立っているのでこれで識別する */
682         /* check */
683         kanji = (iskanji(nc) && !(na & AF_TILE1));
684 #endif
685
686         ota = old_taa[x];
687         otc = old_tcc[x];
688
689         nta = scr_taa[x];
690         ntc = scr_tcc[x];
691
692         /* Handle unchanged grids */
693 #ifdef JP
694         if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc) && (!kanji || (scr_aa[x + 1] == old_aa[x + 1] && scr_cc[x + 1] == old_cc[x + 1] && scr_taa[x + 1] == old_taa[x + 1] && scr_tcc[x + 1] == old_tcc[x + 1])))
695 #else
696         if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc))
697 #endif
698         {
699             /* Flush */
700             if (fn) {
701                 /* Draw pending attr/char pairs */
702                 (void)((*game_term->pict_hook)(fx, y, fn, &scr_aa[fx], &scr_cc[fx], &scr_taa[fx], &scr_tcc[fx]));
703
704                 /* Forget */
705                 fn = 0;
706             }
707
708 #ifdef JP
709             /* 全角文字の時は再開位置は+1 */
710             if (kanji) {
711                 x++;
712                 fx++;
713                 kanji = 0;
714             }
715 #endif
716             /* Skip */
717             continue;
718         }
719         /* Save new contents */
720         old_aa[x] = na;
721         old_cc[x] = nc;
722
723         old_taa[x] = nta;
724         old_tcc[x] = ntc;
725
726         /* Restart and Advance */
727         if (fn++ == 0) {
728             fx = x;
729         }
730     }
731
732     /* Flush */
733     if (fn) {
734         /* Draw pending attr/char pairs */
735         (void)((*game_term->pict_hook)(fx, y, fn, &scr_aa[fx], &scr_cc[fx], &scr_taa[fx], &scr_tcc[fx]));
736     }
737 }
738
739 /*
740  * Flush a row of the current window (see "term_fresh")
741  *
742  * Display text using "term_text()" and "term_wipe()",
743  * but use "term_pict()" for high-bit attr/char pairs
744  */
745 static void term_fresh_row_both(TERM_LEN y, int x1, int x2)
746 {
747     auto &old_aa = game_term->old->a[y];
748     auto &old_cc = game_term->old->c[y];
749
750     const auto &scr_aa = game_term->scr->a[y];
751     const auto &scr_cc = game_term->scr->c[y];
752
753     auto &old_taa = game_term->old->ta[y];
754     auto &old_tcc = game_term->old->tc[y];
755     const auto &scr_taa = game_term->scr->ta[y];
756     const auto &scr_tcc = game_term->scr->tc[y];
757
758     TERM_COLOR ota;
759     char otc;
760     TERM_COLOR nta;
761     char ntc;
762
763     /* The "always_text" flag */
764     int always_text = game_term->always_text;
765
766     /* Pending length */
767     int fn = 0;
768
769     /* Pending start */
770     int fx = 0;
771
772     /* Pending attr */
773     byte fa = game_term->attr_blank;
774
775     TERM_COLOR oa;
776     char oc;
777
778     TERM_COLOR na;
779     char nc;
780
781 #ifdef JP
782     /* 全角文字の2バイト目かどうか */
783     int kanji = 0;
784 #endif
785     /* Scan "modified" columns */
786     for (TERM_LEN x = x1; x <= x2; x++) {
787         /* See what is currently here */
788         oa = old_aa[x];
789         oc = old_cc[x];
790
791         /* See what is desired there */
792         na = scr_aa[x];
793         nc = scr_cc[x];
794
795 #ifdef JP
796         if (kanji) {
797             /* 全角文字2バイト目 */
798             kanji = 0;
799             old_aa[x] = na;
800             old_cc[x] = nc;
801             fn++;
802             continue;
803         }
804         /* 特殊文字としてMSBが立っている可能性がある */
805         /* その場合attrのMSBも立っているのでこれで識別する */
806         /* check */
807         /*              kanji = (iskanji(nc));  */
808         kanji = (iskanji(nc) && !(na & AF_TILE1));
809 #endif
810
811         ota = old_taa[x];
812         otc = old_tcc[x];
813
814         nta = scr_taa[x];
815         ntc = scr_tcc[x];
816
817         /* Handle unchanged grids */
818 #ifdef JP
819         if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc) && (!kanji || (scr_aa[x + 1] == old_aa[x + 1] && scr_cc[x + 1] == old_cc[x + 1] && scr_taa[x + 1] == old_taa[x + 1] && scr_tcc[x + 1] == old_tcc[x + 1])))
820 #else
821         if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc))
822 #endif
823         {
824             /* Flush */
825             if (fn) {
826                 /* Draw pending chars (normal) */
827                 if (fa || always_text) {
828                     (void)((*game_term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
829                 }
830
831                 /* Draw pending chars (black) */
832                 else {
833                     (void)((*game_term->wipe_hook)(fx, y, fn));
834                 }
835
836                 /* Forget */
837                 fn = 0;
838             }
839
840 #ifdef JP
841             /* 全角文字の時は再開位置は+1 */
842             if (kanji) {
843                 x++;
844                 fx++;
845                 kanji = 0;
846             }
847 #endif
848             /* Skip */
849             continue;
850         }
851
852         /* Save new contents */
853         old_aa[x] = na;
854         old_cc[x] = nc;
855
856         old_taa[x] = nta;
857         old_tcc[x] = ntc;
858
859         /* 2nd byte of bigtile */
860         if ((na & AF_BIGTILE2) == AF_BIGTILE2) {
861             continue;
862         }
863
864         /* Handle high-bit attr/chars */
865         if ((na & AF_TILE1) && (nc & 0x80)) {
866             /* Flush */
867             if (fn) {
868                 /* Draw pending chars (normal) */
869                 if (fa || always_text) {
870                     (void)((*game_term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
871                 }
872
873                 /* Draw pending chars (black) */
874                 else {
875                     (void)((*game_term->wipe_hook)(fx, y, fn));
876                 }
877
878                 /* Forget */
879                 fn = 0;
880             }
881
882             /* Draw the special attr/char pair */
883             (void)((*game_term->pict_hook)(x, y, 1, &na, &nc, &nta, &ntc));
884
885             /* Skip */
886             continue;
887         }
888
889         /* Notice new color */
890 #ifdef JP
891         if (fa != (na & AF_KANJIC))
892 #else
893         if (fa != na)
894 #endif
895
896         {
897             /* Flush */
898             if (fn) {
899                 /* Draw the pending chars */
900                 if (fa || always_text) {
901                     (void)((*game_term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
902                 }
903
904                 /* Erase "leading" spaces */
905                 else {
906                     (void)((*game_term->wipe_hook)(fx, y, fn));
907                 }
908
909                 /* Forget */
910                 fn = 0;
911             }
912
913             /* Save the new color */
914 #ifdef JP
915             fa = (na & AF_KANJIC);
916 #else
917             fa = na;
918 #endif
919         }
920
921         /* Restart and Advance */
922         if (fn++ == 0) {
923             fx = x;
924         }
925     }
926
927     /* Flush */
928     if (fn) {
929         /* Draw pending chars (normal) */
930         if (fa || always_text) {
931             (void)((*game_term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
932         }
933
934         /* Draw pending chars (black) */
935         else {
936             (void)((*game_term->wipe_hook)(fx, y, fn));
937         }
938     }
939 }
940
941 /*
942  * Flush a row of the current window (see "term_fresh")
943  *
944  * Display text using "term_text()" and "term_wipe()"
945  */
946 static void term_fresh_row_text(TERM_LEN y, TERM_LEN x1, TERM_LEN x2)
947 {
948     auto &old_aa = game_term->old->a[y];
949     auto &old_cc = game_term->old->c[y];
950
951     const auto &scr_aa = game_term->scr->a[y];
952     const auto &scr_cc = game_term->scr->c[y];
953
954     /* The "always_text" flag */
955     int always_text = game_term->always_text;
956
957     /* Pending length */
958     int fn = 0;
959
960     /* Pending start */
961     int fx = 0;
962
963     /* Pending attr */
964     byte fa = game_term->attr_blank;
965
966     TERM_COLOR oa;
967     char oc;
968
969     TERM_COLOR na;
970     char nc;
971
972 #ifdef JP
973     /* 全角文字の2バイト目かどうか */
974     int kanji = 0;
975
976     for (TERM_LEN x = 0; x < x1; x++) {
977         if (!(old_aa[x] & AF_TILE1) && iskanji(old_cc[x])) {
978             if (x == x1 - 1) {
979                 x1--;
980                 break;
981             } else {
982                 x++;
983             }
984         }
985     }
986 #endif
987     /* Scan "modified" columns */
988     for (TERM_LEN x = x1; x <= x2; x++) {
989         /* See what is currently here */
990         oa = old_aa[x];
991         oc = old_cc[x];
992
993         /* See what is desired there */
994         na = scr_aa[x];
995         nc = scr_cc[x];
996
997 #ifdef JP
998         if (kanji) {
999             /* 全角文字2バイト目 */
1000             kanji = 0;
1001             old_aa[x] = na;
1002             old_cc[x] = nc;
1003             fn++;
1004             continue;
1005         }
1006         /* 特殊文字としてMSBが立っている可能性がある */
1007         /* その場合attrのMSBも立っているのでこれで識別する */
1008         /* check */
1009         kanji = (iskanji(nc) && !(na & AF_TILE1));
1010 #endif
1011         /* Handle unchanged grids */
1012 #ifdef JP
1013         if ((na == oa) && (nc == oc) && (!kanji || (scr_aa[x + 1] == old_aa[x + 1] && scr_cc[x + 1] == old_cc[x + 1])))
1014 #else
1015         if ((na == oa) && (nc == oc))
1016 #endif
1017
1018         {
1019             /* Flush */
1020             if (fn) {
1021                 /* Draw pending chars (normal) */
1022                 if (fa || always_text) {
1023                     (void)((*game_term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
1024                 }
1025
1026                 /* Draw pending chars (black) */
1027                 else {
1028                     (void)((*game_term->wipe_hook)(fx, y, fn));
1029                 }
1030
1031                 /* Forget */
1032                 fn = 0;
1033             }
1034
1035 #ifdef JP
1036             /* 全角文字の時は再開位置は+1 */
1037             if (kanji) {
1038                 x++;
1039                 fx++;
1040                 kanji = 0;
1041             }
1042 #endif
1043             /* Skip */
1044             continue;
1045         }
1046
1047         /* Save new contents */
1048         old_aa[x] = na;
1049         old_cc[x] = nc;
1050
1051         /* Notice new color */
1052 #ifdef JP
1053         if (fa != (na & AF_KANJIC))
1054 #else
1055         if (fa != na)
1056 #endif
1057
1058         {
1059             /* Flush */
1060             if (fn) {
1061                 /* Draw the pending chars */
1062                 if (fa || always_text) {
1063                     (void)((*game_term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
1064                 }
1065
1066                 /* Erase "leading" spaces */
1067                 else {
1068                     (void)((*game_term->wipe_hook)(fx, y, fn));
1069                 }
1070
1071                 /* Forget */
1072                 fn = 0;
1073             }
1074
1075             /* Save the new color */
1076 #ifdef JP
1077             fa = (na & AF_KANJIC);
1078 #else
1079             fa = na;
1080 #endif
1081         }
1082
1083         /* Restart and Advance */
1084         if (fn++ == 0) {
1085             fx = x;
1086         }
1087     }
1088
1089     /* Flush */
1090     if (fn) {
1091         /* Draw pending chars (normal) */
1092         if (fa || always_text) {
1093             (void)((*game_term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
1094         }
1095
1096         /* Draw pending chars (black) */
1097         else {
1098             (void)((*game_term->wipe_hook)(fx, y, fn));
1099         }
1100     }
1101 }
1102
1103 /*
1104  * @brief Actually perform all requested changes to the window
1105  */
1106 errr term_fresh(void)
1107 {
1108     int w = game_term->wid;
1109     int h = game_term->hgt;
1110
1111     int y1 = game_term->y1;
1112     int y2 = game_term->y2;
1113
1114     const auto &old = game_term->old;
1115     const auto &scr = game_term->scr;
1116
1117     /* Before initialize (Advice from Mr.shimitei)*/
1118     if (!old || !scr) {
1119         return 1;
1120     }
1121
1122     if (game_term->never_fresh) {
1123         return 1;
1124     }
1125
1126     /* Do nothing unless "mapped" */
1127     if (!game_term->mapped_flag) {
1128         return 1;
1129     }
1130
1131     /* Trivial Refresh */
1132     if ((y1 > y2) && (scr->cu == old->cu) && (scr->cv == old->cv) && (scr->cx == old->cx) && (scr->cy == old->cy) && !(game_term->total_erase)) {
1133         /* Nothing */
1134         return 1;
1135     }
1136
1137     /* Handle "total erase" */
1138     if (game_term->total_erase) {
1139         byte na = game_term->attr_blank;
1140         char nc = game_term->char_blank;
1141
1142         /* Physically erase the entire window */
1143         term_xtra(TERM_XTRA_CLEAR, 0);
1144
1145         /* clear all "cursor" data */
1146         old->cv = old->cu = false;
1147         old->cx = old->cy = 0;
1148
1149         /* Wipe each row */
1150         for (TERM_LEN y = 0; y < h; y++) {
1151             auto &aa = old->a[y];
1152             auto &cc = old->c[y];
1153
1154             auto &taa = old->ta[y];
1155             auto &tcc = old->tc[y];
1156
1157             /* Wipe each column */
1158             for (TERM_LEN x = 0; x < w; x++) {
1159                 /* Wipe each grid */
1160                 aa[x] = na;
1161                 cc[x] = nc;
1162
1163                 taa[x] = na;
1164                 tcc[x] = nc;
1165             }
1166         }
1167
1168         /* Redraw every row */
1169         game_term->y1 = y1 = 0;
1170         game_term->y2 = y2 = h - 1;
1171
1172         /* Redraw every column */
1173         for (TERM_LEN y = 0; y < h; y++) {
1174             game_term->x1[y] = 0;
1175             game_term->x2[y] = w - 1;
1176         }
1177
1178         /* Forget "total erase" */
1179         game_term->total_erase = false;
1180     }
1181
1182     /* Cursor update -- Erase old Cursor */
1183     if (game_term->soft_cursor) {
1184         /* Cursor was visible */
1185         if (!old->cu && old->cv) {
1186             int csize = 1;
1187             TERM_LEN tx = old->cx;
1188             TERM_LEN ty = old->cy;
1189
1190             const auto &old_aa = old->a[ty];
1191             const auto &old_cc = old->c[ty];
1192
1193             const auto &old_taa = old->ta[ty];
1194             const auto &old_tcc = old->tc[ty];
1195
1196             TERM_COLOR ota = old_taa[tx];
1197             char otc = old_tcc[tx];
1198
1199 #ifdef JP
1200             if (tx + 1 < game_term->wid && !(old_aa[tx] & AF_TILE1) && iskanji(old_cc[tx])) {
1201                 csize = 2;
1202             }
1203 #endif
1204             /* Use "term_pict()" always */
1205             if (game_term->always_pict) {
1206                 (void)((*game_term->pict_hook)(tx, ty, csize, &old_aa[tx], &old_cc[tx], &ota, &otc));
1207             }
1208
1209             /* Use "term_pict()" sometimes */
1210             else if (game_term->higher_pict && (old_aa[tx] & AF_TILE1) && (old_cc[tx] & 0x80)) {
1211                 (void)((*game_term->pict_hook)(tx, ty, 1, &old_aa[tx], &old_cc[tx], &ota, &otc));
1212             }
1213
1214             /*
1215              * Restore the actual character
1216              * 元の文字の描画範囲がカーソルより小さいと、
1217              * 上書きされなかった部分がゴミとして残る。
1218              * wipe_hook でカーソルを消去して text_hook で書き直す。
1219              */
1220             else if (old_aa[tx] || game_term->always_text) {
1221                 (void)((*game_term->wipe_hook)(tx, ty, 1));
1222                 (void)((*game_term->text_hook)(tx, ty, csize, (unsigned char)(old_aa[tx] & 0xf), &old_cc[tx]));
1223             }
1224
1225             /* Erase the grid */
1226             else {
1227                 (void)((*game_term->wipe_hook)(tx, ty, 1));
1228             }
1229         }
1230     }
1231
1232     /* Hide the hardware cursor while drawing */
1233     else {
1234         /* Cursor will be invisible */
1235         term_xtra(TERM_XTRA_SHAPE, 0);
1236     }
1237
1238     /* Something to update */
1239     if (y1 <= y2) {
1240         /* Handle "icky corner" */
1241         if (game_term->icky_corner) {
1242             /* Avoid the corner */
1243             if (y2 >= h - 1) {
1244                 /* Avoid the corner */
1245                 if (game_term->x2[h - 1] > w - 2) {
1246                     /* Avoid the corner */
1247                     game_term->x2[h - 1] = w - 2;
1248                 }
1249             }
1250         }
1251
1252         /* Scan the "modified" rows */
1253         for (TERM_LEN y = y1; y <= y2; ++y) {
1254             TERM_LEN x1 = game_term->x1[y];
1255             TERM_LEN x2 = game_term->x2[y];
1256
1257             /* Flush each "modified" row */
1258             if (x1 <= x2) {
1259                 /* Always use "term_pict()" */
1260                 if (game_term->always_pict) {
1261                     /* Flush the row */
1262                     term_fresh_row_pict(y, x1, x2);
1263                 }
1264
1265                 /* Sometimes use "term_pict()" */
1266                 else if (game_term->higher_pict) {
1267                     /* Flush the row */
1268                     term_fresh_row_both(y, x1, x2);
1269                 }
1270
1271                 /* Never use "term_pict()" */
1272                 else {
1273                     /* Flush the row */
1274                     term_fresh_row_text(y, x1, x2);
1275                 }
1276
1277                 /* This row is all done */
1278                 game_term->x1[y] = w;
1279                 game_term->x2[y] = 0;
1280
1281                 /* Flush that row (if allowed) */
1282                 if (!game_term->never_frosh) {
1283                     term_xtra(TERM_XTRA_FROSH, y);
1284                 }
1285             }
1286         }
1287
1288         /* No rows are invalid */
1289         game_term->y1 = h;
1290         game_term->y2 = 0;
1291     }
1292
1293     /* Cursor update -- Show new Cursor */
1294     if (game_term->soft_cursor) {
1295         /* Draw the cursor */
1296         if (!scr->cu && scr->cv) {
1297 #ifdef JP
1298             if ((scr->cx + 1 < w) && ((old->a[scr->cy][scr->cx + 1] & AF_BIGTILE2) == AF_BIGTILE2 || (!(old->a[scr->cy][scr->cx] & AF_TILE1) && iskanji(old->c[scr->cy][scr->cx]))))
1299 #else
1300             if ((scr->cx + 1 < w) && (old->a[scr->cy][scr->cx + 1] & AF_BIGTILE2) == AF_BIGTILE2)
1301 #endif
1302             {
1303                 /* Double width cursor for the Bigtile mode */
1304                 (void)((*game_term->bigcurs_hook)(scr->cx, scr->cy));
1305             } else {
1306                 /* Call the cursor display routine */
1307                 (void)((*game_term->curs_hook)(scr->cx, scr->cy));
1308             }
1309         }
1310     }
1311
1312     /* Cursor Update -- Show new Cursor */
1313     else {
1314         if (scr->cu) {
1315             /* Paranoia -- Put the cursor NEAR where it belongs */
1316             (void)((*game_term->curs_hook)(w - 1, scr->cy));
1317         } else {
1318             /* Put the cursor where it belongs */
1319             (void)((*game_term->curs_hook)(scr->cx, scr->cy));
1320         }
1321     }
1322
1323     /* Save the "cursor state" */
1324     old->cu = scr->cu;
1325     old->cv = scr->cv;
1326     old->cx = scr->cx;
1327     old->cy = scr->cy;
1328
1329     /* Actually flush the output */
1330     term_xtra(TERM_XTRA_FRESH, 0);
1331
1332     if (!game_term->soft_cursor && !scr->cu && scr->cv) {
1333         /* The cursor is visible, display it correctly */
1334         term_xtra(TERM_XTRA_SHAPE, 1);
1335     }
1336
1337     return 0;
1338 }
1339
1340 /*
1341  * @brief never_freshの値を無視して強制的にterm_freshを行う。
1342  */
1343 errr term_fresh_force(void)
1344 {
1345     bool old = game_term->never_fresh;
1346     game_term->never_fresh = false;
1347     errr err = term_fresh();
1348     game_term->never_fresh = old;
1349     return err;
1350 }
1351
1352 /*** Output routines ***/
1353
1354 /*
1355  * Set the cursor visibility
1356  */
1357 errr term_set_cursor(int v)
1358 {
1359     /* Already done */
1360     if (game_term->scr->cv == (bool)v) {
1361         return 1;
1362     }
1363
1364     /* Change */
1365     game_term->scr->cv = (bool)v;
1366     return 0;
1367 }
1368
1369 /*
1370  * Place the cursor at a given location
1371  *
1372  * Note -- "illegal" requests do not move the cursor.
1373  */
1374 errr term_gotoxy(TERM_LEN x, TERM_LEN y)
1375 {
1376     int w = game_term->wid;
1377     int h = game_term->hgt;
1378
1379     x += game_term->offset_x;
1380     y += game_term->offset_y;
1381
1382     /* Verify */
1383     if ((x < 0) || (x >= w)) {
1384         return -1;
1385     }
1386     if ((y < 0) || (y >= h)) {
1387         return -1;
1388     }
1389
1390     /* Remember the cursor */
1391     game_term->scr->cx = x;
1392     game_term->scr->cy = y;
1393
1394     /* The cursor is not useless */
1395     game_term->scr->cu = 0;
1396     return 0;
1397 }
1398
1399 /*
1400  * At a given location, place an attr/char
1401  * Do not change the cursor position
1402  * No visual changes until "term_fresh()".
1403  */
1404 errr term_draw(TERM_LEN x, TERM_LEN y, TERM_COLOR a, char c)
1405 {
1406     if (auto res = term_gotoxy(x, y); res != 0) {
1407         return -1;
1408     }
1409
1410     /* Paranoia -- illegal char */
1411     if (!c) {
1412         return -2;
1413     }
1414
1415     /* Queue it for later */
1416     term_queue_char_aux(game_term->scr->cx, game_term->scr->cy, a, c, 0, 0);
1417     return 0;
1418 }
1419
1420 /*
1421  * Using the given attr, add the given char at the cursor.
1422  *
1423  * We return "-2" if the character is "illegal". XXX XXX
1424  *
1425  * We return "-1" if the cursor is currently unusable.
1426  *
1427  * We queue the given attr/char for display at the current
1428  * cursor location, and advance the cursor to the right,
1429  * marking it as unuable and returning "1" if it leaves
1430  * the screen, and otherwise returning "0".
1431  *
1432  * So when this function, or the following one, return a
1433  * positive value, future calls to either function will
1434  * return negative ones.
1435  */
1436 errr term_addch(TERM_COLOR a, char c)
1437 {
1438     TERM_LEN w = game_term->wid;
1439
1440     /* Handle "unusable" cursor */
1441     if (game_term->scr->cu) {
1442         return -1;
1443     }
1444
1445     /* Paranoia -- no illegal chars */
1446     if (!c) {
1447         return -2;
1448     }
1449
1450     /* Queue the given character for display */
1451     term_queue_char_aux(game_term->scr->cx, game_term->scr->cy, a, c, 0, 0);
1452
1453     /* Advance the cursor */
1454     game_term->scr->cx++;
1455
1456     /* Success */
1457     if (game_term->scr->cx < w) {
1458         return 0;
1459     }
1460
1461     /* Note "Useless" cursor */
1462     game_term->scr->cu = 1;
1463
1464     /* Note "Useless" cursor */
1465     return 1;
1466 }
1467
1468 /*
1469  * Bigtile version of term_addch().
1470  *
1471  * If use_bigtile is FALSE, simply call term_addch() .
1472  *
1473  * Otherwise, queue a pair of attr/char for display at the current
1474  * cursor location, and advance the cursor to the right by two.
1475  */
1476 errr term_add_bigch(TERM_COLOR a, char c)
1477 {
1478     if (!use_bigtile) {
1479         return term_addch(a, c);
1480     }
1481
1482     /* Handle "unusable" cursor */
1483     if (game_term->scr->cu) {
1484         return -1;
1485     }
1486
1487     /* Paranoia -- no illegal chars */
1488     if (!c) {
1489         return -2;
1490     }
1491
1492     /* Queue the given character for display */
1493     term_queue_bigchar(game_term->scr->cx, game_term->scr->cy, a, c, 0, 0);
1494
1495     /* Advance the cursor */
1496     game_term->scr->cx += 2;
1497
1498     /* Success */
1499     if (game_term->scr->cx < game_term->wid) {
1500         return 0;
1501     }
1502
1503     /* Note "Useless" cursor */
1504     game_term->scr->cu = 1;
1505
1506     /* Note "Useless" cursor */
1507     return 1;
1508 }
1509
1510 /*
1511  * At the current location, using an attr, add a string
1512  *
1513  * We also take a length "n", using negative values to imply
1514  * the largest possible value, and then we use the minimum of
1515  * this length and the "actual" length of the string as the
1516  * actual number of characters to attempt to display, never
1517  * displaying more characters than will actually fit, since
1518  * we do NOT attempt to "wrap" the cursor at the screen edge.
1519  *
1520  * We return "-1" if the cursor is currently unusable.
1521  * We return "N" if we were "only" able to write "N" chars,
1522  * even if all of the given characters fit on the screen,
1523  * and mark the cursor as unusable for future attempts.
1524  *
1525  * So when this function, or the preceding one, return a
1526  * positive value, future calls to either function will
1527  * return negative ones.
1528  */
1529 errr term_addstr(int n, TERM_COLOR a, std::string_view sv)
1530 {
1531     TERM_LEN w = game_term->wid;
1532     errr res = 0;
1533
1534     /* Handle "unusable" cursor */
1535     if (game_term->scr->cu) {
1536         return -1;
1537     }
1538
1539     /* Obtain maximal length */
1540     const auto max_len = (n < 0) ? (w + 1) : n;
1541
1542     /* Obtain the usable string length */
1543     auto len = std::min<int>(max_len, sv.length());
1544
1545     /* React to reaching the edge of the screen */
1546     if (game_term->scr->cx + len >= w) {
1547         res = len = w - game_term->scr->cx;
1548     }
1549
1550     /* Queue the first "n" characters for display */
1551     term_queue_chars(game_term->scr->cx, game_term->scr->cy, len, a, sv);
1552
1553     /* Advance the cursor */
1554     game_term->scr->cx += len;
1555
1556     /* Notice "Useless" cursor */
1557     if (res) {
1558         game_term->scr->cu = 1;
1559     }
1560
1561     return res;
1562 }
1563
1564 /*
1565  * Move to a location and, using an attr, add a char
1566  */
1567 errr term_putch(TERM_LEN x, TERM_LEN y, TERM_COLOR a, char c)
1568 {
1569     errr res;
1570
1571     /* Move first */
1572     if ((res = term_gotoxy(x, y)) != 0) {
1573         return res;
1574     }
1575
1576     /* Then add the char */
1577     if ((res = term_addch(a, c)) != 0) {
1578         return res;
1579     }
1580
1581     return 0;
1582 }
1583
1584 /*
1585  * Move to a location and, using an attr, add a string
1586  */
1587 errr term_putstr(TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, std::string_view sv)
1588 {
1589     errr res;
1590
1591     /* Move first */
1592     if ((res = term_gotoxy(x, y)) != 0) {
1593         return res;
1594     }
1595
1596     /* Then add the string */
1597     if ((res = term_addstr(n, a, sv)) != 0) {
1598         return res;
1599     }
1600
1601     return 0;
1602 }
1603
1604 /*
1605  * Place cursor at (x,y), and clear the next "n" chars
1606  */
1607 errr term_erase(TERM_LEN x, TERM_LEN y, int n)
1608 {
1609     TERM_LEN w = game_term->wid;
1610     /* int h = Term->hgt; */
1611
1612     TERM_LEN x1 = -1;
1613     TERM_LEN x2 = -1;
1614
1615     int na = game_term->attr_blank;
1616     int nc = game_term->char_blank;
1617
1618     /* Place cursor */
1619     if (term_gotoxy(x, y)) {
1620         return -1;
1621     }
1622
1623     x = game_term->scr->cx;
1624     y = game_term->scr->cy;
1625
1626     /* Force legal size */
1627     if (x + n > w) {
1628         n = w - x;
1629     }
1630
1631     /* Fast access */
1632     auto &scr_aa = game_term->scr->a[y];
1633     auto &scr_cc = game_term->scr->c[y];
1634
1635     auto &scr_taa = game_term->scr->ta[y];
1636     auto &scr_tcc = game_term->scr->tc[y];
1637
1638 #ifdef JP
1639     /*
1640      * 全角文字の右半分から文字を表示する場合、
1641      * 重なった文字の左部分を消去。
1642      */
1643     if (n > 0 && (((scr_aa[x] & AF_KANJI2) && !(scr_aa[x] & AF_TILE1)) || (scr_aa[x] & AF_BIGTILE2) == AF_BIGTILE2))
1644 #else
1645     if (n > 0 && (scr_aa[x] & AF_BIGTILE2) == AF_BIGTILE2)
1646 #endif
1647     {
1648         x--;
1649         n++;
1650     }
1651
1652     /* Scan every column */
1653     for (int i = 0; i < n; i++, x++) {
1654         int oa = scr_aa[x];
1655         int oc = scr_cc[x];
1656
1657         /* Ignore "non-changes" */
1658         if ((oa == na) && (oc == nc)) {
1659             continue;
1660         }
1661
1662 #ifdef JP
1663         /*
1664          * 全角文字の左半分で表示を終了する場合、
1665          * 重なった文字の右部分を消去。
1666          *
1667          * 2001/04/29 -- Habu
1668          * 行の右端の場合はこの処理をしないように修正。
1669          */
1670         if ((oa & AF_KANJI1) && (i + 1) == n && x != w - 1) {
1671             n++;
1672         }
1673 #endif
1674         /* Save the "literal" information */
1675         scr_aa[x] = (byte)na;
1676         scr_cc[x] = (char)nc;
1677
1678         scr_taa[x] = 0;
1679         scr_tcc[x] = 0;
1680
1681         /* Track minimum changed column */
1682         if (x1 < 0) {
1683             x1 = x;
1684         }
1685
1686         /* Track maximum changed column */
1687         x2 = x;
1688     }
1689
1690     /* Expand the "change area" as needed */
1691     if (x1 >= 0) {
1692         /* Check for new min/max row info */
1693         if (y < game_term->y1) {
1694             game_term->y1 = y;
1695         }
1696         if (y > game_term->y2) {
1697             game_term->y2 = y;
1698         }
1699
1700         /* Check for new min/max col info in this row */
1701         if (x1 < game_term->x1[y]) {
1702             game_term->x1[y] = x1;
1703         }
1704         if (x2 > game_term->x2[y]) {
1705             game_term->x2[y] = x2;
1706         }
1707     }
1708
1709     return 0;
1710 }
1711
1712 /*
1713  * Clear the entire window, and move to the top left corner
1714  *
1715  * Note the use of the special "total_erase" code
1716  */
1717 errr term_clear(void)
1718 {
1719     TERM_LEN w = game_term->wid;
1720     TERM_LEN h = game_term->hgt;
1721
1722     TERM_COLOR na = game_term->attr_blank;
1723     char nc = game_term->char_blank;
1724
1725     /* Cursor usable */
1726     game_term->scr->cu = 0;
1727
1728     /* Cursor to the top left */
1729     game_term->scr->cx = game_term->scr->cy = 0;
1730
1731     /* Wipe each row */
1732     for (TERM_LEN y = 0; y < h; y++) {
1733         auto &scr_aa = game_term->scr->a[y];
1734         auto &scr_cc = game_term->scr->c[y];
1735
1736         auto &scr_taa = game_term->scr->ta[y];
1737         auto &scr_tcc = game_term->scr->tc[y];
1738
1739         /* Wipe each column */
1740         for (TERM_LEN x = 0; x < w; x++) {
1741             scr_aa[x] = na;
1742             scr_cc[x] = nc;
1743
1744             scr_taa[x] = 0;
1745             scr_tcc[x] = 0;
1746         }
1747
1748         /* This row has changed */
1749         game_term->x1[y] = 0;
1750         game_term->x2[y] = w - 1;
1751     }
1752
1753     /* Every row has changed */
1754     game_term->y1 = 0;
1755     game_term->y2 = h - 1;
1756
1757     /* Force "total erase" */
1758     game_term->total_erase = true;
1759     return 0;
1760 }
1761
1762 /*
1763  * Redraw (and refresh) the whole window.
1764  */
1765 errr term_redraw(void)
1766 {
1767     /* Force "total erase" */
1768     game_term->total_erase = true;
1769     term_fresh();
1770     return 0;
1771 }
1772
1773 /*
1774  * Redraw part of a window.
1775  */
1776 errr term_redraw_section(TERM_LEN x1, TERM_LEN y1, TERM_LEN x2, TERM_LEN y2)
1777 {
1778     /* Bounds checking */
1779     if (y2 >= game_term->hgt) {
1780         y2 = game_term->hgt - 1;
1781     }
1782     if (x2 >= game_term->wid) {
1783         x2 = game_term->wid - 1;
1784     }
1785     if (y1 < 0) {
1786         y1 = 0;
1787     }
1788     if (x1 < 0) {
1789         x1 = 0;
1790     }
1791
1792     /* Set y limits */
1793     game_term->y1 = y1;
1794     game_term->y2 = y2;
1795
1796     /* Set the x limits */
1797     for (int i = game_term->y1; i <= game_term->y2; i++) {
1798 #ifdef JP
1799         TERM_LEN x1j = x1;
1800         TERM_LEN x2j = x2;
1801
1802         if (x1j > 0) {
1803             if (game_term->scr->a[i][x1j] & AF_KANJI2) {
1804                 x1j--;
1805             }
1806         }
1807
1808         if (x2j < game_term->wid - 1) {
1809             if (game_term->scr->a[i][x2j] & AF_KANJI1) {
1810                 x2j++;
1811             }
1812         }
1813
1814         game_term->x1[i] = x1j;
1815         game_term->x2[i] = x2j;
1816
1817         auto &g_ptr = game_term->old->c[i];
1818
1819         /* Clear the section so it is redrawn */
1820         for (int j = x1j; j <= x2j; j++) {
1821             /* Hack - set the old character to "none" */
1822             g_ptr[j] = 0;
1823         }
1824 #else
1825         game_term->x1[i] = x1;
1826         game_term->x2[i] = x2;
1827
1828         auto &g_ptr = game_term->old->c[i];
1829
1830         /* Clear the section so it is redrawn */
1831         for (int j = x1; j <= x2; j++) {
1832             /* Hack - set the old character to "none" */
1833             g_ptr[j] = 0;
1834         }
1835 #endif
1836     }
1837
1838     term_fresh();
1839     return 0;
1840 }
1841
1842 /*** Access routines ***/
1843
1844 /*
1845  * Extract the cursor visibility
1846  */
1847 errr term_get_cursor(int *v)
1848 {
1849     /* Extract visibility */
1850     (*v) = game_term->scr->cv;
1851     return 0;
1852 }
1853
1854 /*
1855  * Extract the current window size
1856  */
1857 errr term_get_size(TERM_LEN *w, TERM_LEN *h)
1858 {
1859     (*w) = game_term->centered_wid.value_or(game_term->wid);
1860     (*h) = game_term->centered_hgt.value_or(game_term->hgt);
1861     return 0;
1862 }
1863
1864 /*
1865  * Extract the current cursor location
1866  */
1867 errr term_locate(TERM_LEN *x, TERM_LEN *y)
1868 {
1869     /* Access the cursor */
1870     *x = game_term->scr->cx - game_term->offset_x;
1871     *y = game_term->scr->cy - game_term->offset_y;
1872
1873     /* Warn about "useless" cursor */
1874     if (game_term->scr->cu) {
1875         return 1;
1876     }
1877
1878     return 0;
1879 }
1880
1881 /*
1882  * At a given location, determine the "current" attr and char
1883  * Note that this refers to what will be on the window after the
1884  * next call to "term_fresh()".  It may or may not already be there.
1885  */
1886 errr term_what(TERM_LEN x, TERM_LEN y, TERM_COLOR *a, char *c)
1887 {
1888     TERM_LEN w = game_term->wid;
1889     TERM_LEN h = game_term->hgt;
1890
1891     x += game_term->offset_x;
1892     y += game_term->offset_y;
1893
1894     if ((x < 0) || (x >= w)) {
1895         return -1;
1896     }
1897     if ((y < 0) || (y >= h)) {
1898         return -1;
1899     }
1900
1901     /* Direct access */
1902     (*a) = game_term->scr->a[y][x];
1903     (*c) = game_term->scr->c[y][x];
1904     return 0;
1905 }
1906
1907 /*** Input routines ***/
1908
1909 /*
1910  * Flush and forget the input
1911  */
1912 errr term_flush(void)
1913 {
1914     /* Flush all events */
1915     term_xtra(TERM_XTRA_FLUSH, 0);
1916
1917     /* Forget all keypresses */
1918     game_term->key_head = game_term->key_tail = 0;
1919     return 0;
1920 }
1921
1922 /*
1923  * Add a keypress to the FRONT of the "queue"
1924  */
1925 errr term_key_push(int k)
1926 {
1927     /* Refuse to enqueue non-keys */
1928     if (!k) {
1929         return -1;
1930     }
1931
1932     /* Overflow may induce circular queue */
1933     if (game_term->key_tail == 0) {
1934         game_term->key_tail = game_term->key_size;
1935     }
1936
1937     /* Back up, Store the char */
1938     game_term->key_queue[--game_term->key_tail] = (char)k;
1939
1940     if (game_term->key_head != game_term->key_tail) {
1941         return 0;
1942     }
1943
1944     return 1;
1945 }
1946
1947 /*
1948  * Check for a pending keypress on the key queue.
1949  *
1950  * Store the keypress, if any, in "ch", and return "0".
1951  * Otherwise store "zero" in "ch", and return "1".
1952  *
1953  * Wait for a keypress if "wait" is true.
1954  *
1955  * Remove the keypress if "take" is true.
1956  */
1957 errr term_inkey(char *ch, bool wait, bool take)
1958 {
1959     /* Assume no key */
1960     (*ch) = '\0';
1961
1962     /* get bored */
1963     if (!game_term->never_bored) {
1964         /* Process random events */
1965         term_xtra(TERM_XTRA_BORED, 0);
1966     }
1967
1968     /* Wait */
1969     if (wait) {
1970         /* Process pending events while necessary */
1971         while (game_term->key_head == game_term->key_tail) {
1972             /* Process events (wait for one) */
1973             term_xtra(TERM_XTRA_EVENT, true);
1974         }
1975     }
1976
1977     /* Do not Wait */
1978     else {
1979         /* Process pending events if necessary */
1980         if (game_term->key_head == game_term->key_tail) {
1981             /* Process events (do not wait) */
1982             term_xtra(TERM_XTRA_EVENT, false);
1983         }
1984     }
1985
1986     /* No keys are ready */
1987     if (game_term->key_head == game_term->key_tail) {
1988         return 1;
1989     }
1990
1991     /* Extract the next keypress */
1992     (*ch) = game_term->key_queue[game_term->key_tail];
1993
1994     /* If requested, advance the queue, wrap around if necessary */
1995     if (take && (++game_term->key_tail == game_term->key_size)) {
1996         game_term->key_tail = 0;
1997     }
1998
1999     return 0;
2000 }
2001
2002 /*** Extra routines ***/
2003
2004 /*
2005  * Save the "requested" screen into the "memorized" screen
2006  *
2007  * Every "term_save()" should match exactly one "term_load()"
2008  */
2009 errr term_save(void)
2010 {
2011     /* Push stack */
2012     game_term->mem_stack.push(game_term->scr->clone());
2013
2014     return 0;
2015 }
2016
2017 /*
2018  * Restore the "requested" contents (see above).
2019  *
2020  * Every "term_save()" should match exactly one "term_load()"
2021  */
2022 errr term_load(bool load_all)
2023 {
2024     TERM_LEN w = game_term->wid;
2025     TERM_LEN h = game_term->hgt;
2026
2027     if (game_term->mem_stack.empty()) {
2028         return 0;
2029     }
2030
2031     if (load_all) {
2032         // 残り1つを残して読み捨てる
2033         while (game_term->mem_stack.size() > 1) {
2034             game_term->mem_stack.pop();
2035         }
2036     }
2037
2038     /* Load */
2039     game_term->scr.swap(game_term->mem_stack.top());
2040     game_term->scr->resize(w, h);
2041
2042     /* Pop stack */
2043     game_term->mem_stack.pop();
2044
2045     /* Assume change */
2046     for (TERM_LEN y = 0; y < h; y++) {
2047         /* Assume change */
2048         game_term->x1[y] = 0;
2049         game_term->x2[y] = w - 1;
2050     }
2051
2052     /* Assume change */
2053     game_term->y1 = 0;
2054     game_term->y2 = h - 1;
2055     return 0;
2056 }
2057
2058 /*
2059  * Exchange the "requested" screen with the "tmp" screen
2060  */
2061 errr term_exchange(void)
2062 {
2063     TERM_LEN w = game_term->wid;
2064     TERM_LEN h = game_term->hgt;
2065
2066     /* Create */
2067     if (!game_term->tmp) {
2068         /* Allocate window */
2069         game_term->tmp = term_win::create(w, h);
2070     }
2071
2072     /* Swap */
2073     game_term->scr.swap(game_term->tmp);
2074
2075     /* Assume change */
2076     for (TERM_LEN y = 0; y < h; y++) {
2077         /* Assume change */
2078         game_term->x1[y] = 0;
2079         game_term->x2[y] = w - 1;
2080     }
2081
2082     /* Assume change */
2083     game_term->y1 = 0;
2084     game_term->y2 = h - 1;
2085     return 0;
2086 }
2087
2088 /*
2089  * React to a new physical window size.
2090  */
2091 errr term_resize(TERM_LEN w, TERM_LEN h)
2092 {
2093     /* Resizing is forbidden */
2094     if (game_term->fixed_shape) {
2095         return -1;
2096     }
2097
2098     /* Ignore illegal changes */
2099     if ((w < 1) || (h < 1)) {
2100         return -1;
2101     }
2102
2103     /* Ignore non-changes */
2104     if ((game_term->wid == w) && (game_term->hgt == h) && (arg_bigtile == use_bigtile)) {
2105         return 1;
2106     }
2107
2108     use_bigtile = arg_bigtile;
2109
2110     /* Resize windows */
2111     game_term->old->resize(w, h);
2112     game_term->scr->resize(w, h);
2113     if (game_term->tmp) {
2114         game_term->tmp->resize(w, h);
2115     }
2116
2117     /* Resize scanners */
2118     game_term->x1.resize(h);
2119     game_term->x2.resize(h);
2120
2121     /* Save new size */
2122     game_term->wid = w;
2123     game_term->hgt = h;
2124
2125     /* Force "total erase" */
2126     game_term->total_erase = true;
2127
2128     /* Assume change */
2129     for (int i = 0; i < h; i++) {
2130         /* Assume change */
2131         game_term->x1[i] = 0;
2132         game_term->x2[i] = w - 1;
2133     }
2134
2135     /* Assume change */
2136     game_term->y1 = 0;
2137     game_term->y2 = h - 1;
2138
2139     /* Execute the "resize_hook" hook, if available */
2140     if (game_term->resize_hook) {
2141         game_term->resize_hook();
2142     }
2143
2144     return 0;
2145 }
2146
2147 /*
2148  * Activate a new Term (and deactivate the current Term)
2149  *
2150  * This function is extremely important, and also somewhat bizarre.
2151  * It is the only function that should "modify" the value of "Term".
2152  *
2153  * To "create" a valid "term", one should do "term_init(t)", then
2154  * set the various flags and hooks, and then do "term_activate(t)".
2155  */
2156 errr term_activate(term_type *t)
2157 {
2158     /* already done */
2159     if (game_term == t) {
2160         return 1;
2161     }
2162
2163     /* Deactivate the old Term */
2164     if (game_term) {
2165         term_xtra(TERM_XTRA_LEVEL, 0);
2166     }
2167
2168     /* Call the special "init" hook */
2169     if (t && !t->active_flag) {
2170         /* Call the "init" hook */
2171         if (t->init_hook) {
2172             (*t->init_hook)(t);
2173         }
2174
2175         /* Remember */
2176         t->active_flag = true;
2177
2178         /* Assume mapped */
2179         t->mapped_flag = true;
2180     }
2181
2182     /* Remember the Term */
2183     game_term = t;
2184
2185     /* Activate the new Term */
2186     if (game_term) {
2187         term_xtra(TERM_XTRA_LEVEL, 1);
2188     }
2189
2190     return 0;
2191 }
2192
2193 /*
2194  * Initialize a term, using a window of the given size.
2195  * Also prepare the "input queue" for "k" keypresses
2196  * By default, the cursor starts out "invisible"
2197  * By default, we "erase" using "black spaces"
2198  */
2199 errr term_init(term_type *t, TERM_LEN w, TERM_LEN h, int k)
2200 {
2201     /* Wipe it */
2202     *t = term_type{};
2203
2204     /* Prepare the input queue */
2205     t->key_head = t->key_tail = 0;
2206
2207     /* Determine the input queue size */
2208     t->key_size = (uint16_t)k;
2209
2210     /* Allocate the input queue */
2211     t->key_queue.resize(t->key_size);
2212
2213     /* Save the size */
2214     t->wid = w;
2215     t->hgt = h;
2216
2217     /* Allocate change arrays */
2218     t->x1.resize(h);
2219     t->x2.resize(h);
2220
2221     /* Allocate "displayed" */
2222     t->old = term_win::create(w, h);
2223
2224     /* Allocate "requested" */
2225     t->scr = term_win::create(w, h);
2226
2227     /* Assume change */
2228     for (TERM_LEN y = 0; y < h; y++) {
2229         /* Assume change */
2230         t->x1[y] = 0;
2231         t->x2[y] = w - 1;
2232     }
2233
2234     /* Assume change */
2235     t->y1 = 0;
2236     t->y2 = h - 1;
2237
2238     /* Force "total erase" */
2239     t->total_erase = true;
2240
2241     /* Default "blank" */
2242     t->attr_blank = 0;
2243     t->char_blank = ' ';
2244
2245     /* Prepare "fake" hooks to prevent core dumps */
2246     t->curs_hook = term_curs_hack;
2247     t->bigcurs_hook = term_bigcurs_hack;
2248     t->wipe_hook = term_wipe_hack;
2249     t->text_hook = term_text_hack;
2250     t->pict_hook = term_pict_hack;
2251     return 0;
2252 }
2253
2254 #ifdef JP
2255 /*
2256  * Move to a location and, using an attr, add a string vertically
2257  */
2258 errr term_putstr_v(TERM_LEN x, TERM_LEN y, int n, byte a, concptr s)
2259 {
2260     errr res;
2261     int y0 = y;
2262
2263     for (int i = 0; i < n && s[i] != 0; i++) {
2264         /* Move first */
2265         if ((res = term_gotoxy(x, y0)) != 0) {
2266             return res;
2267         }
2268
2269         if (iskanji(s[i])) {
2270             if ((res = term_addstr(2, a, &s[i])) != 0) {
2271                 return res;
2272             }
2273             i++;
2274             y0++;
2275             if (s[i] == 0) {
2276                 break;
2277             }
2278         } else {
2279             if ((res = term_addstr(1, a, &s[i])) != 0) {
2280                 return res;
2281             }
2282             y0++;
2283         }
2284     }
2285
2286     return 0;
2287 }
2288 #endif
2289
2290 #ifndef WINDOWS
2291 errr term_nuke(term_type *t)
2292 {
2293     if (t->active_flag) {
2294         if (t->nuke_hook) {
2295             (*t->nuke_hook)(t);
2296         }
2297
2298         t->active_flag = false;
2299         t->mapped_flag = false;
2300     }
2301
2302     t->old.reset();
2303     t->scr.reset();
2304     t->tmp.reset();
2305     while (!t->mem_stack.empty()) {
2306         t->mem_stack.pop();
2307     }
2308
2309     t->x1.clear();
2310     t->x2.clear();
2311     t->key_queue.clear();
2312     return 0;
2313 }
2314 #endif