1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program 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 this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "glk/comprehend/game.h"
24 #include "common/debug-channels.h"
25 #include "common/translation.h"
26 #include "glk/comprehend/comprehend.h"
27 #include "glk/comprehend/debugger.h"
28 #include "glk/comprehend/dictionary.h"
29 #include "glk/comprehend/draw_surface.h"
30 #include "glk/comprehend/game_data.h"
31
32 namespace Glk {
33 namespace Comprehend {
34
clear()35 void Sentence::clear() {
36 for (uint idx = 0; idx < 4; ++idx)
37 _words[idx].clear();
38 for (uint idx = 0; idx < 6; ++idx)
39 _formattedWords[idx] = 0;
40
41 _nr_words = 0;
42 _specialOpcodeVal2 = 0;
43 }
44
copyFrom(const Sentence & src,bool copyNoun)45 void Sentence::copyFrom(const Sentence &src, bool copyNoun) {
46 for (uint idx = (copyNoun ? 0 : 1); idx < 6; ++idx)
47 _formattedWords[idx] = src._formattedWords[idx];
48 }
49
format()50 void Sentence::format() {
51 for (uint idx = 0; idx < 6; ++idx)
52 _formattedWords[idx] = 0;
53 byte wordTypes[5] = { 0, 0, 0, 0, 0 };
54
55 for (uint idx = 0; idx < _nr_words; ++idx) {
56 const Word &w = _words[idx];
57
58 if (w._type & 8) {
59 if (w._type < 24) {
60 int index, type;
61
62 if (w._type & 0xf0 & wordTypes[2]) {
63 index = _formattedWords[2];
64 type = wordTypes[2];
65 } else if (w._type & 0xf0 & wordTypes[3]) {
66 index = _formattedWords[3];
67 type = wordTypes[3];
68 } else {
69 continue;
70 }
71
72 if (!_formattedWords[2]) {
73 _formattedWords[2] = index;
74 wordTypes[2] = type;
75 } else if (!_formattedWords[3]) {
76 _formattedWords[3] = index;
77 wordTypes[3] = type;
78 }
79 } else {
80 if (w._type == 8)
81 _specialOpcodeVal2 = 1;
82 else if (w._type == 9)
83 _specialOpcodeVal2 = 2;
84 }
85 } else {
86 int val = w._type & 0xf0;
87
88 if (val) {
89 if ((w._type & 1) && !_formattedWords[0]) {
90 _formattedWords[0] = w._index;
91 } else if (!_formattedWords[2]) {
92 _formattedWords[2] = w._index;
93 wordTypes[2] = val;
94 } else if (!_formattedWords[3]) {
95 _formattedWords[3] = w._index;
96 wordTypes[3] = val;
97 }
98 } else if (w._type & 1) {
99 if (!_formattedWords[0]) {
100 _formattedWords[0] = w._index;
101 } else if (!_formattedWords[1]) {
102 _formattedWords[1] = w._index;
103 }
104 } else if (w._type == 2) {
105 if (!_formattedWords[4])
106 _formattedWords[4] = w._index;
107 } else if (w._type == 4) {
108 if (!_formattedWords[5])
109 _formattedWords[5] = w._index;
110 }
111 }
112 }
113 }
114
115 /*-------------------------------------------------------*/
116
117
ComprehendGame()118 ComprehendGame::ComprehendGame() : _gameStrings(nullptr), _ended(false),
119 _functionNum(0), _specialOpcode(0), _nounState(NOUNSTATE_INITIAL),
120 _inputLineIndex(0), _currentRoomCopy(-1), _redoLine(REDO_NONE) {
121 Common::fill(&_inputLine[0], &_inputLine[INPUT_LINE_SIZE], 0);
122 }
123
~ComprehendGame()124 ComprehendGame::~ComprehendGame() {
125 }
126
synchronizeSave(Common::Serializer & s)127 void ComprehendGame::synchronizeSave(Common::Serializer &s) {
128 uint dir, i;
129 size_t nr_rooms, nr_items;
130
131 s.syncAsUint16LE(_currentRoom);
132
133 // Variables
134 for (i = 0; i < ARRAY_SIZE(_variables); i++)
135 s.syncAsUint16LE(_variables[i]);
136
137 // Flags
138 for (i = 0; i < ARRAY_SIZE(_flags); i++)
139 s.syncAsByte(_flags[i]);
140
141 // Rooms. Note that index 0 is the player's inventory
142 nr_rooms = _rooms.size();
143 s.syncAsByte(nr_rooms);
144 assert(nr_rooms == _rooms.size());
145
146 for (i = 1; i < _rooms.size(); ++i) {
147 s.syncAsUint16LE(_rooms[i]._stringDesc);
148 for (dir = 0; dir < NR_DIRECTIONS; dir++)
149 s.syncAsByte(_rooms[i]._direction[dir]);
150
151 s.syncAsByte(_rooms[i]._flags);
152 s.syncAsByte(_rooms[i]._graphic);
153 }
154
155 // Objects
156 nr_items = _items.size();
157 s.syncAsByte(nr_items);
158 assert(nr_items == _items.size());
159
160 for (i = 0; i < _items.size(); ++i)
161 _items[i].synchronize(s);
162
163 _redoLine = REDO_NONE;
164 }
165
stringLookup(uint16 index)166 Common::String ComprehendGame::stringLookup(uint16 index) {
167 uint16 string;
168 uint8 table;
169
170 /*
171 * There are two tables of strings. The first is stored in the main
172 * game data file, and the second is stored in multiple string files.
173 *
174 * In instructions string indexes are split into a table and index
175 * value. In other places such as the save files strings from the
176 * main table are occasionally just a straight 16-bit index. We
177 * convert all string indexes to the former case so that we can handle
178 * them the same everywhere.
179 */
180 table = (index >> 8) & 0xff;
181 string = index & 0xff;
182
183 switch (table) {
184 case 0x81:
185 case 0x01:
186 string += 0x100;
187 /* Fall-through */
188 case 0x00:
189 case 0x80:
190 if (string < _strings.size())
191 return _strings[string];
192 break;
193
194 case 0x83:
195 string += 0x100;
196 /* Fall-through */
197 case 0x02:
198 case 0x82:
199 if (string < _strings2.size())
200 return _strings2[string];
201 break;
202 }
203
204 return Common::String::format("BAD_STRING(%.4x)", index);
205 }
206
instrStringLookup(uint8 index,uint8 table)207 Common::String ComprehendGame::instrStringLookup(uint8 index, uint8 table) {
208 return stringLookup(table << 8 | index);
209 }
210
console_get_key()211 int ComprehendGame::console_get_key() {
212 return g_comprehend->readChar();
213 }
214
console_println(const char * text)215 void ComprehendGame::console_println(const char *text) {
216 const char *replace, *word = nullptr, *p = text;
217 char bad_word[64];
218 int word_len = 0;
219
220 if (!text) {
221 g_comprehend->print("\n");
222 return;
223 }
224
225 while (*p) {
226 switch (*p) {
227 case '\n':
228 word = NULL;
229 word_len = 0;
230 g_comprehend->print("\n");
231 p++;
232 break;
233
234 case '@':
235 /* Replace word */
236 if (_currentReplaceWord >= _replaceWords.size()) {
237 snprintf(bad_word, sizeof(bad_word),
238 "[BAD_REPLACE_WORD(%.2x)]",
239 _currentReplaceWord);
240 word = bad_word;
241 } else {
242 word = _replaceWords[_currentReplaceWord].c_str();
243 }
244 word_len = strlen(word);
245 p++;
246 break;
247
248 default:
249 /* Find next space */
250 word_len = strcspn(p, " \n");
251 if (word_len == 0)
252 break;
253
254 /*
255 * If this word contains a replacement symbol, then
256 * print everything before the symbol.
257 */
258 replace = strchr(p, '@');
259 if (replace)
260 word_len = replace - p;
261
262 word = p;
263 p += word_len;
264 break;
265 }
266
267 if (!word || !word_len)
268 continue;
269
270 Common::String wordStr(word, word_len);
271 g_comprehend->print("%s", wordStr.c_str());
272
273 if (*p == ' ') {
274 g_comprehend->print(" ");
275 p++;
276
277 /* Skip any double spaces */
278 while (*p == ' ')
279 p++;
280 }
281 }
282
283 g_comprehend->print("\n");
284 }
285
get_room(uint16 index)286 Room *ComprehendGame::get_room(uint16 index) {
287 /* Room zero is reserved for the players inventory */
288 if (index == 0)
289 error("Room index 0 (player inventory) is invalid");
290
291 if (index >= (int)_rooms.size())
292 error("Room index %d is invalid", index);
293
294 return &_rooms[index];
295 }
296
get_item(uint16 index)297 Item *ComprehendGame::get_item(uint16 index) {
298 if (index >= _items.size())
299 error("Bad item %d\n", index);
300
301 return &_items[index];
302 }
303
game_save()304 void ComprehendGame::game_save() {
305 int c;
306
307 console_println(_strings[STRING_SAVE_GAME].c_str());
308
309 c = console_get_key();
310 if (g_comprehend->shouldQuit())
311 return;
312
313 if (c < '1' || c > '3') {
314 /*
315 * The original Comprehend games just silently ignore any
316 * invalid selection.
317 */
318 console_println("Invalid save game number");
319 return;
320 }
321
322 g_comprehend->saveGameState(c - '0', _("Savegame"));
323 }
324
game_restore()325 void ComprehendGame::game_restore() {
326 int c;
327
328 console_println(_strings[STRING_RESTORE_GAME].c_str());
329
330 c = console_get_key();
331 if (g_comprehend->shouldQuit())
332 return;
333
334 if (c < '1' || c > '3') {
335 /*
336 * The original Comprehend games just silently ignore any
337 * invalid selection.
338 */
339 console_println("Invalid save game number");
340 return;
341 }
342
343 (void)g_comprehend->loadGameState(c - '0');
344 }
345
handle_restart()346 bool ComprehendGame::handle_restart() {
347 console_println(stringLookup(_gameStrings->game_restart).c_str());
348 _ended = false;
349
350 if (tolower(console_get_key()) == 'r') {
351 loadGame();
352 _updateFlags = UPDATE_ALL;
353 return true;
354 } else {
355 g_comprehend->quitGame();
356 return false;
357 }
358 }
359
get_item_by_noun(byte noun)360 Item *ComprehendGame::get_item_by_noun(byte noun) {
361 uint i;
362
363 if (!noun)
364 return nullptr;
365
366 /*
367 * FIXME - in oo-topos the word 'box' matches more than one object
368 * (the box and the snarl-in-a-box). The player is unable
369 * to drop the latter because this will match the former.
370 */
371 for (i = 0; i < _items.size(); i++)
372 if (_items[i]._word == noun)
373 return &_items[i];
374
375 return NULL;
376 }
377
get_item_id(byte noun)378 int ComprehendGame::get_item_id(byte noun) {
379 for (int i = 0; i < (int)_items.size(); i++)
380 if (_items[i]._word == noun)
381 return i;
382
383 return -1;
384 }
385
update_graphics()386 void ComprehendGame::update_graphics() {
387 Item *item;
388 Room *room;
389 int type;
390 uint i;
391
392 if (!g_comprehend->isGraphicsEnabled())
393 return;
394
395 type = roomIsSpecial(_currentRoomCopy, NULL);
396
397 switch (type) {
398 case ROOM_IS_DARK:
399 if (_updateFlags & UPDATE_GRAPHICS)
400 g_comprehend->clearScreen(false);
401 break;
402
403 case ROOM_IS_TOO_BRIGHT:
404 if (_updateFlags & UPDATE_GRAPHICS)
405 g_comprehend->clearScreen(true);
406 break;
407
408 default:
409 if (_updateFlags & UPDATE_GRAPHICS) {
410 room = get_room(_currentRoom);
411 g_comprehend->drawLocationPicture(room->_graphic - 1);
412 }
413
414 if ((_updateFlags & UPDATE_GRAPHICS) ||
415 (_updateFlags & UPDATE_GRAPHICS_ITEMS)) {
416 for (i = 0; i < _items.size(); i++) {
417 item = &_items[i];
418
419 if (item->_room == _currentRoom &&
420 item->_graphic != 0)
421 g_comprehend->drawItemPicture(item->_graphic - 1);
422 }
423 }
424 break;
425 }
426 }
427
describe_objects_in_current_room()428 void ComprehendGame::describe_objects_in_current_room() {
429 Item *item;
430 size_t count = 0;
431 uint i;
432
433 for (i = 0; i < _items.size(); i++) {
434 item = &_items[i];
435
436 if (item->_room == _currentRoom && item->_stringDesc != 0
437 && !(item->_flags & ITEMF_INVISIBLE))
438 count++;
439 }
440
441 if (count > 0) {
442 console_println(stringLookup(STRING_YOU_SEE).c_str());
443
444 for (i = 0; i < _items.size(); i++) {
445 item = &_items[i];
446
447 if (item->_room == _currentRoom && item->_stringDesc != 0
448 && !(item->_flags & ITEMF_INVISIBLE))
449 console_println(stringLookup(item->_stringDesc).c_str());
450 }
451 }
452 }
453
updateRoomDesc()454 void ComprehendGame::updateRoomDesc() {
455 Room *room = get_room(_currentRoom);
456 uint room_desc_string = room->_stringDesc;
457 roomIsSpecial(_currentRoom, &room_desc_string);
458
459 Common::String desc = stringLookup(room_desc_string);
460 g_comprehend->printRoomDesc(desc);
461 }
462
update()463 void ComprehendGame::update() {
464 Room *room = get_room(_currentRoom);
465 unsigned room_type, room_desc_string;
466
467 update_graphics();
468
469 /* Check if the room is special (dark, too bright, etc) */
470 room_desc_string = room->_stringDesc;
471 room_type = roomIsSpecial(_currentRoom,
472 &room_desc_string);
473
474 if (_updateFlags & UPDATE_ROOM_DESC) {
475 Common::String desc = stringLookup(room_desc_string);
476 console_println(desc.c_str());
477 g_comprehend->printRoomDesc(desc.c_str());
478 }
479
480 if ((_updateFlags & UPDATE_ITEM_LIST) && room_type == ROOM_IS_NORMAL)
481 describe_objects_in_current_room();
482
483 _updateFlags = 0;
484 }
485
move_to(uint8 room)486 void ComprehendGame::move_to(uint8 room) {
487 if (room >= (int)_rooms.size())
488 error("Attempted to move to invalid room %.2x\n", room);
489
490 _currentRoom = _currentRoomCopy = room;
491 _updateFlags = (UPDATE_GRAPHICS | UPDATE_ROOM_DESC |
492 UPDATE_ITEM_LIST);
493 }
494
num_objects_in_room(int room)495 size_t ComprehendGame::num_objects_in_room(int room) {
496 size_t count = 0, i;
497
498 for (i = 0; i < _items.size(); i++)
499 if (_items[i]._room == room)
500 count++;
501
502 return count;
503 }
504
move_object(Item * item,int new_room)505 void ComprehendGame::move_object(Item *item, int new_room) {
506 unsigned obj_weight = item->_flags & ITEMF_WEIGHT_MASK;
507
508 if (item->_room == new_room)
509 return;
510
511 if (item->_room == ROOM_INVENTORY) {
512 /* Removed from player's inventory */
513 _variables[VAR_INVENTORY_WEIGHT] -= obj_weight;
514 }
515 if (new_room == ROOM_INVENTORY) {
516 /* Moving to the player's inventory */
517 _variables[VAR_INVENTORY_WEIGHT] += obj_weight;
518 }
519
520 if (item->_room == _currentRoom) {
521 /* Item moved away from the current room */
522 _updateFlags |= UPDATE_GRAPHICS;
523
524 } else if (new_room == _currentRoom) {
525 /*
526 * Item moved into the current room. Only the item needs a
527 * redraw, not the whole room.
528 */
529 _updateFlags |= (UPDATE_GRAPHICS_ITEMS |
530 UPDATE_ITEM_LIST);
531 }
532
533 item->_room = new_room;
534 }
535
eval_instruction(FunctionState * func_state,const Function & func,uint functionOffset,const Sentence * sentence)536 void ComprehendGame::eval_instruction(FunctionState *func_state,
537 const Function &func, uint functionOffset, const Sentence *sentence) {
538
539 const Instruction *instr = &func[functionOffset];
540
541 if (DebugMan.isDebugChannelEnabled(kDebugScripts)) {
542 Common::String line;
543 if (!instr->_isCommand) {
544 line += "? ";
545 } else {
546 if (func_state->_testResult)
547 line += "+ ";
548 else
549 line += "- ";
550 }
551
552 line += Common::String::format("%.2x ", functionOffset);
553 line += g_debugger->dumpInstruction(this, func_state, instr);
554 debugC(kDebugScripts, "%s", line.c_str());
555 }
556
557 if (func_state->_orCount)
558 func_state->_orCount--;
559
560 if (instr->_isCommand) {
561 bool do_command;
562
563 func_state->_inCommand = true;
564 do_command = func_state->_testResult;
565
566 if (func_state->_orCount != 0)
567 g_comprehend->print("Warning: or_count == %d\n",
568 func_state->_orCount);
569 func_state->_orCount = 0;
570
571 if (!do_command)
572 return;
573
574 func_state->_elseResult = false;
575 func_state->_executed = true;
576
577 } else {
578 if (func_state->_inCommand) {
579 /* Finished command sequence - clear test result */
580 func_state->_inCommand = false;
581 func_state->_testResult = false;
582 func_state->_and = false;
583 }
584 }
585
586 execute_opcode(instr, sentence, func_state);
587 }
588
eval_function(uint functionNum,const Sentence * sentence)589 void ComprehendGame::eval_function(uint functionNum, const Sentence *sentence) {
590 FunctionState func_state;
591 uint i;
592
593 const Function &func = _functions[functionNum];
594 func_state._elseResult = true;
595 func_state._executed = false;
596
597 debugC(kDebugScripts, "Start of function %.4x", functionNum);
598
599 for (i = 0; i < func.size(); i++) {
600 if (func_state._executed && !func[i]._isCommand) {
601 /*
602 * At least one command has been executed and the
603 * current instruction is a test. Exit the function.
604 */
605 break;
606 }
607
608 eval_instruction(&func_state, func, i, sentence);
609 }
610
611 debugC(kDebugScripts, "End of function %.4x\n", functionNum);
612 }
613
skip_whitespace(const char ** p)614 void ComprehendGame::skip_whitespace(const char **p) {
615 while (**p && Common::isSpace(**p))
616 (*p)++;
617 }
618
skip_non_whitespace(const char ** p)619 void ComprehendGame::skip_non_whitespace(const char **p) {
620 while (**p && !Common::isSpace(**p) && **p != ',' && **p != '\n')
621 (*p)++;
622 }
623
handle_sentence(Sentence * sentence)624 bool ComprehendGame::handle_sentence(Sentence *sentence) {
625 if (sentence->_nr_words == 1 && !strcmp(sentence->_words[0]._word, "quit")) {
626 g_comprehend->quitGame();
627 return true;
628 }
629
630 // Set up default sentence
631 Common::Array<byte> words;
632 const byte *src = &sentence->_formattedWords[0];
633
634 if (src[1] && src[3]) {
635 words.clear();
636
637 for (int idx = 0; idx < 4; ++idx)
638 words.push_back(src[idx]);
639
640 if (handle_sentence(0, sentence, words))
641 return true;
642 }
643
644 if (src[1]) {
645 words.clear();
646
647 for (int idx = 0; idx < 3; ++idx)
648 words.push_back(src[idx]);
649
650 if (handle_sentence(1, sentence, words))
651 return true;
652 }
653
654 if (src[3] && src[4]) {
655 words.clear();
656
657 words.push_back(src[4]);
658 words.push_back(src[0]);
659 words.push_back(src[2]);
660 words.push_back(src[3]);
661
662 if (handle_sentence(2, sentence, words))
663 return true;
664 }
665
666 if (src[4]) {
667 words.clear();
668
669 words.push_back(src[4]);
670 words.push_back(src[0]);
671 words.push_back(src[2]);
672
673 if (handle_sentence(3, sentence, words))
674 return true;
675 }
676
677 if (src[3]) {
678 words.clear();
679
680 words.push_back(src[0]);
681 words.push_back(src[2]);
682 words.push_back(src[3]);
683
684 if (handle_sentence(4, sentence, words))
685 return true;
686 }
687
688 if (src[2]) {
689 words.clear();
690
691 words.push_back(src[0]);
692 words.push_back(src[2]);
693
694 if (handle_sentence(5, sentence, words))
695 return true;
696 }
697
698 if (src[0]) {
699 words.clear();
700 words.push_back(src[0]);
701
702 if (handle_sentence(6, sentence, words))
703 return true;
704 }
705
706 return false;
707 }
708
handle_sentence(uint tableNum,Sentence * sentence,Common::Array<byte> & words)709 bool ComprehendGame::handle_sentence(uint tableNum, Sentence *sentence, Common::Array<byte> &words) {
710 const ActionTable &table = _actions[tableNum];
711
712 for (uint i = 0; i < table.size(); i++) {
713 const Action &action = table[i];
714
715 // Check for a match on the words of the action
716 bool isMatch = true;
717 for (uint idx = 0; idx < action._nr_words && isMatch; ++idx)
718 isMatch = action._words[idx] == words[idx];
719
720 if (isMatch) {
721 // Match
722 _functionNum = action._function;
723 return true;
724 }
725 }
726
727 // No matching action
728 return false;
729 }
730
handleAction(Sentence * sentence)731 void ComprehendGame::handleAction(Sentence *sentence) {
732 _specialOpcode = 0;
733
734 if (_functionNum == 0) {
735 console_println(stringLookup(STRING_DONT_UNDERSTAND).c_str());
736 } else {
737 eval_function(_functionNum, sentence);
738 _functionNum = 0;
739 eval_function(0, nullptr);
740 }
741
742 handleSpecialOpcode();
743 }
744
read_sentence(Sentence * sentence)745 void ComprehendGame::read_sentence(Sentence *sentence) {
746 bool sentence_end = false;
747 const char *word_string, *p = &_inputLine[_inputLineIndex];
748 Word *word;
749
750 sentence->clear();
751 for (;;) {
752 // Get the next word
753 skip_whitespace(&p);
754 word_string = p;
755 skip_non_whitespace(&p);
756
757 Common::String wordStr(word_string, p);
758
759 // Check for end of sentence
760 // FIXME: The below is a hacked simplified version of how the
761 // original handles cases like "get item1, item2"
762 if (*p == ',' || *p == '\n' || wordStr.equalsIgnoreCase("and")) {
763 // Sentence separator
764 ++p;
765 sentence_end = true;
766 } else if (*p == '\0') {
767 sentence_end = true;
768 }
769
770 /* Find the dictionary word for this */
771 word = dict_find_word_by_string(this, wordStr.c_str());
772 if (!word)
773 sentence->_words[sentence->_nr_words].clear();
774 else
775 sentence->_words[sentence->_nr_words] = *word;
776
777 sentence->_nr_words++;
778
779 if (sentence->_nr_words >= ARRAY_SIZE(sentence->_words) ||
780 sentence_end)
781 break;
782 }
783
784 parse_sentence_word_pairs(sentence);
785 sentence->format();
786
787 _inputLineIndex = p - _inputLine;
788 }
789
parse_sentence_word_pairs(Sentence * sentence)790 void ComprehendGame::parse_sentence_word_pairs(Sentence *sentence) {
791 if (sentence->_nr_words < 2)
792 return;
793
794 // Iterate through the pairs
795 for (uint idx = 0; idx < _wordMaps.size(); ++idx) {
796 for (int firstWord = 0; firstWord < (int)sentence->_nr_words - 1; ++firstWord) {
797 for (int secondWord = firstWord + 1; secondWord < (int)sentence->_nr_words; ) {
798 if (sentence->_words[firstWord] == _wordMaps[idx]._word[0] &&
799 sentence->_words[secondWord] == _wordMaps[idx]._word[1]) {
800 // Found a word pair match
801 // Delete the second word
802 for (; secondWord < (int)sentence->_nr_words - 1; ++secondWord)
803 sentence->_words[secondWord] = sentence->_words[secondWord + 1];
804
805 sentence->_words[sentence->_nr_words - 1].clear();
806 sentence->_nr_words--;
807
808 // Replace the first word with the target
809 sentence->_words[firstWord] = _wordMaps[idx]._word[2];
810 } else {
811 // Move to next word
812 ++secondWord;
813 }
814 }
815 }
816 }
817 }
818
doBeforeTurn()819 void ComprehendGame::doBeforeTurn() {
820 // Make a copy of the current room
821 _currentRoomCopy = _currentRoom;
822
823 beforeTurn();
824
825 if (!_ended)
826 update();
827 }
828
beforeTurn()829 void ComprehendGame::beforeTurn() {
830 // Run the each turn functions
831 eval_function(0, nullptr);
832 }
833
read_input()834 void ComprehendGame::read_input() {
835 Sentence tempSentence;
836 bool handled;
837
838 turn:
839 doBeforeTurn();
840 if (_ended)
841 return;
842
843 // If we're in full screen text, we can afford a blank row between
844 // any game response and the next line of text
845 if (!g_comprehend->isGraphicsEnabled())
846 g_comprehend->print("\n");
847
848 beforePrompt();
849
850 for (;;) {
851 _redoLine = REDO_NONE;
852 g_comprehend->print("> ");
853 g_comprehend->readLine(_inputLine, INPUT_LINE_SIZE);
854 if (g_comprehend->shouldQuit())
855 return;
856
857 _inputLineIndex = 0;
858 if (strlen(_inputLine) == 0) {
859 // Empty line, so toggle picture window visibility
860 if (!g_comprehend->toggleGraphics())
861 updateRoomDesc();
862 g_comprehend->print(_("Picture window toggled\n"));
863
864 _updateFlags |= UPDATE_GRAPHICS;
865 update_graphics();
866 continue;
867 }
868
869 afterPrompt();
870
871 if (_redoLine == REDO_NONE)
872 break;
873 else if (_redoLine == REDO_TURN)
874 goto turn;
875 }
876
877 for (;;) {
878 NounState prevNounState = _nounState;
879 _nounState = NOUNSTATE_STANDARD;
880
881 read_sentence(&tempSentence);
882 _sentence.copyFrom(tempSentence, tempSentence._formattedWords[0] || prevNounState != NOUNSTATE_STANDARD);
883
884 handled = handle_sentence(&_sentence);
885 handleAction(&_sentence);
886
887 if (!handled)
888 return;
889
890 /* FIXME - handle the 'before you can continue' case */
891 if (_inputLine[_inputLineIndex] == '\0')
892 break;
893 }
894
895 afterTurn();
896 }
897
playGame()898 void ComprehendGame::playGame() {
899 if (!g_comprehend->loadLauncherSavegameIfNeeded())
900 beforeGame();
901
902 _updateFlags = (uint)UPDATE_ALL;
903 while (!g_comprehend->shouldQuit()) {
904 read_input();
905
906 if (_ended && !handle_restart())
907 break;
908 }
909 }
910
getRandomNumber(uint max) const911 uint ComprehendGame::getRandomNumber(uint max) const {
912 return g_comprehend->getRandomNumber(max);
913 }
914
doMovementVerb(uint verbNum)915 void ComprehendGame::doMovementVerb(uint verbNum) {
916 assert(verbNum >= 1 && verbNum <= NR_DIRECTIONS);
917 Room *room = get_room(_currentRoom);
918 byte newRoom = room->_direction[verbNum - 1];
919
920 if (newRoom)
921 move_to(newRoom);
922 else
923 console_println(_strings[0].c_str());
924 }
925
weighInventory()926 void ComprehendGame::weighInventory() {
927 _totalInventoryWeight = 0;
928 if (!g_debugger->_invLimit)
929 // Allow for an unlimited number of items in inventory
930 return;
931
932 for (int idx = _itemCount - 1; idx > 0; --idx) {
933 Item *item = get_item(idx);
934 if (item->_room == ROOM_INVENTORY)
935 _totalInventoryWeight += item->_flags & ITEMF_WEIGHT_MASK;
936 }
937 }
938
939 } // namespace Comprehend
940 } // namespace Glk
941