From ff05587350f7a817eb13a83007865624a55311d7 Mon Sep 17 00:00:00 2001 From: Jakub Suchenek Date: Mon, 20 Nov 2023 16:46:25 +0100 Subject: [PATCH 01/10] Change `fails` to private attribute --- FTE/world.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FTE/world.py b/FTE/world.py index d17542b..1f8551b 100644 --- a/FTE/world.py +++ b/FTE/world.py @@ -75,7 +75,7 @@ def __init__( self._all_locations: tuple[Location] = all_locations self._all_characters: tuple[Character] = all_characters self._location: Location = starting_location - self.fails = 0 + self._fails = 0 @property def location(self) -> Location: @@ -218,14 +218,14 @@ def interaction(self) -> Character | Location | None: [command, argument] = query.split(' ', 1) command = commands.get(command) if not command: - self.fails += 1 + self._fails += 1 self._prefix() - if self.fails >= 3: + if self._fails >= 3: console.print('Psst, you can use `help`.') else: console.print('I\'m not sure what do you mean.') return None - self.fails = 0 + self._fails = 0 match command.name: case 'exit': self._prefix() From 5d871f7199d413e7c2e3ef78a679cb7833bd4510 Mon Sep 17 00:00:00 2001 From: Jakub Suchenek Date: Mon, 20 Nov 2023 17:46:07 +0100 Subject: [PATCH 02/10] Improve user first contact and help --- FTE/chapters/one.py | 3 +- FTE/world.py | 130 +++++++++++++++++++++++++++++++++----------- 2 files changed, 101 insertions(+), 32 deletions(-) diff --git a/FTE/chapters/one.py b/FTE/chapters/one.py index d309507..be87245 100644 --- a/FTE/chapters/one.py +++ b/FTE/chapters/one.py @@ -24,7 +24,8 @@ def chapter_one() -> None: world = World( all_locations=(bridge, capsules, engine_deck, quarters), all_characters=(roommate, capitan, pilot, engineer), - starting_location=quarters + starting_location=quarters, + first_interaction=True ) console.clear() diff --git a/FTE/world.py b/FTE/world.py index 1f8551b..2be0a54 100644 --- a/FTE/world.py +++ b/FTE/world.py @@ -1,10 +1,14 @@ # -*- coding: utf-8 -*- +from time import sleep + from rich.style import Style +from rich.table import Table from rich.text import Text from FTE.console import console from FTE.characters import Character from FTE.locations import Location +from FTE.settings import DEBUG class UnknownCommand(BaseException): @@ -23,16 +27,19 @@ class Command: def __init__( self, name: str, - brief: str, - description: str = None + description: str = None, + *, + usage: str = None ) -> None: self.name: str = name - self.brief: str = brief - self._description: str = description + self.description: str = description + self._usage: str = usage or '' @property - def description(self) -> str: - return self._description or self._brief + def usage(self) -> str: + if not self._usage: + return self.name + return f'{self.name} {self._usage}' commands: dict[str, Command] = dict( @@ -42,25 +49,27 @@ def description(self) -> str: ), help = Command( 'help', - 'Shows help.', - 'Shows how to use specific command. What more did you expect?' + 'Shows help. Optionally only "commands" or "arguments".', + usage='("commands" | "arguments")' ), talk = Command( 'talk', - 'Talk to someone.', - 'Pass character name to start conversation with them.' + - ' You can talk only to characters in your location.' + 'Talk to someone.' + + ' Pass character name to start conversation with them.' + + ' You can talk only to characters in your location.', + usage='' ), go = Command( 'go', - 'Go somewhere.', - 'Pass location name to go there.' + 'Go somewhere.' + + ' Pass location name to go there.', + usage='' ), info = Command( 'info', - 'Get information about a character or a place.', - 'Pass character or location name to get information about what' + - 'do you know about the subject.' + 'Get information about a character or a location.' + + ' Do not pass anything to show info about current location.', + usage='(charcter name | location name)' ) ) @@ -70,12 +79,16 @@ def __init__( self, all_locations: tuple[Location], all_characters: tuple[Character], - starting_location: Location + starting_location: Location, + first_interaction: bool = False, + assistant: bool = False ) -> None: self._all_locations: tuple[Location] = all_locations self._all_characters: tuple[Character] = all_characters self._location: Location = starting_location self._fails = 0 + self._first_interaction = first_interaction + self._assistant: bool = assistant @property def location(self) -> Location: @@ -90,6 +103,9 @@ def characters(self) -> tuple[Character]: def _prefix(self) -> None: console.print(Text.assemble('[ ', self.location.display_name, ' ] '), end='') + def _prefix_help(self) -> None: + console.print('[', Text.assemble( 'Help', style=Style(color='blue') ), ']', end=' ') + def find_location(self, name: str) -> Location | None: try: location = tuple(filter( @@ -197,6 +213,43 @@ def go_to( # return (True, character.hook) def interaction(self) -> Character | Location | None: + if self._first_interaction: + self._prefix_help() + console.print( + 'This is your first interaction with the World.', + 'Would you like some assitance?' + ) + expect = ('yes', 'no') + self._prefix_help() + query = console.input('') + while query.lower() not in expect: + self._prefix_help() + query = console.input('"yes" or "no"? ') + self._first_interaction = False + if query == 'no': + self._prefix_help() + console.print('OK! I won\'t ask you again. Have fun!') + return None + for line in ( + Text.assemble( + Text.assemble('Fix The Engines', style=Style(bold=True)), + ' is text-based, paragraph game. There is no mouse control,', + ' you operate only with commands.' + ), + 'You can show them by typing "help" during interaction' + + ' with the World.', + 'All commands are single words. For example "help" or' + + ' "go" insted of "go to".', + # 'You can type "help " (eg. "help go")' + + # ' to see it\'s usage and description.', + 'Have fun! :smile:' + ): + self._prefix_help() + console.print(line) + if not DEBUG: + sleep(2.0) + self._assistant = True + return None query = '' while not query: try: @@ -232,21 +285,36 @@ def interaction(self) -> Character | Location | None: console.print('Goodbye!') exit() case 'help': - self._prefix() - if not argument: - console.print(Text.assemble( - 'Available commands', - style=Style(bold=True) - )) - for cmd in commands.keys(): - console.print( - '{0.name} - {0.brief}'.format(commands[cmd]) - ) + show_commands, show_arguments = False, False + if argument == 'commands': + show_commands = True + elif argument == 'arguments': + show_arguments = True else: - if not (cmd := commands.get(argument)): - console.print(f'Command `{argument}` does not exist.') - else: - console.print('{0.name}: {0.description}'.format(cmd)) + show_commands, show_arguments = True, True + if show_commands: + commands_table = Table( + title='Available commands', + show_lines=True + ) + commands_table.add_column('Command') + commands_table.add_column('Description') + commands_table.add_column('Usage') + for cmd in commands.values(): + commands_table.add_row(cmd.name, cmd.description, cmd.usage) + console.print(commands_table) + if show_arguments: + arguments_table = Table(title='Arguments description') + arguments_table.add_column('Representation') + arguments_table.add_column('Description') + for line in ( + ('< ... >', 'Required argument.'), + ('( ... )', 'Optional argument.'), + ('( a | b )', 'Optional argument, but only "a" or "b".') + ): + arguments_table.add_row(*line) + console.print(arguments_table) + return None case 'talk': if not argument: self._prefix() From 42928e03274d97387f6847645c39836b7368feea Mon Sep 17 00:00:00 2001 From: Jakub Suchenek Date: Mon, 20 Nov 2023 18:01:28 +0100 Subject: [PATCH 03/10] Add proper `assistant` method `assistant` method will be executed only if user allowed for it. --- FTE/world.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/FTE/world.py b/FTE/world.py index 2be0a54..66f4e09 100644 --- a/FTE/world.py +++ b/FTE/world.py @@ -212,12 +212,16 @@ def go_to( # return (False, 'This character does not want to talk with you.') # return (True, character.hook) + def assistant(self, text: str| Text) -> None: + if self._assistant: + console.print(text) + def interaction(self) -> Character | Location | None: if self._first_interaction: self._prefix_help() console.print( 'This is your first interaction with the World.', - 'Would you like some assitance?' + 'Would you like to enable assitant?' ) expect = ('yes', 'no') self._prefix_help() From 64ab9da97b9bd1d81d503b554ed23b0e625ecb51 Mon Sep 17 00:00:00 2001 From: Jakub Suchenek Date: Mon, 20 Nov 2023 18:01:59 +0100 Subject: [PATCH 04/10] Fix standing info Add missing spaces. --- FTE/world.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FTE/world.py b/FTE/world.py index 66f4e09..bd5b31a 100644 --- a/FTE/world.py +++ b/FTE/world.py @@ -360,9 +360,9 @@ def interaction(self) -> Character | Location | None: self._prefix() console.print(Text.assemble( char.display_name, - 'has', + ' has ', char.standing.color_text, - 'standing towards you.' + ' standing towards you.' )) if (i := char.info): self._prefix() From 210d4a1fd5e8c832d0241091c4ddbda149e06ea2 Mon Sep 17 00:00:00 2001 From: Jakub Suchenek Date: Mon, 20 Nov 2023 18:02:34 +0100 Subject: [PATCH 05/10] Add `ne` check for `Location` --- FTE/locations.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/FTE/locations.py b/FTE/locations.py index 264bfe5..010e779 100644 --- a/FTE/locations.py +++ b/FTE/locations.py @@ -29,3 +29,8 @@ def __eq__(self, other) -> bool: if isinstance(other, Location): return self.name == other.name return False + + def __ne__(self, other) -> bool: + if isinstance(other, Location): + return self.name != other.name + return True From f9382912de365cc359958f9fc0f6eac0fe171aff Mon Sep 17 00:00:00 2001 From: Jakub Suchenek Date: Mon, 20 Nov 2023 18:03:25 +0100 Subject: [PATCH 06/10] Update location info - Game is now aware if users checks current location. - Fix proper displaying other locations. --- FTE/world.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/FTE/world.py b/FTE/world.py index bd5b31a..64367d7 100644 --- a/FTE/world.py +++ b/FTE/world.py @@ -168,7 +168,7 @@ def _show_location_characters(self) -> None: location_characters = location_characters[:-1] console.print(*location_characters, '.', sep='') - def _show_all_locations(self) -> None: + def _show_other_locations(self) -> None: self._prefix() other_locations = tuple(filter(lambda l: l != self.location, self._all_locations)) if (l := len(other_locations)) == 0: @@ -179,7 +179,7 @@ def _show_all_locations(self) -> None: else: console.print(f'There are {l} other locations you can go to: ', end='') locations: list[str | Text] = [] - for loc in self._all_locations: + for loc in other_locations: locations.append(loc.display_name) locations.append(', ') locations = locations[:-1] @@ -352,9 +352,24 @@ def interaction(self) -> Character | Location | None: return None return self._location case 'info': + if (loc := self.find_location(argument)): + if loc == self.location: + argument = None + else: + self._prefix() + if (i := loc.info): + console.print(i) + else: + console.print('You don\'t know anything about that location.') + return None if not argument: + self._prefix() + if (i := loc.info): + console.print(i) + else: + console.print('You don\'t know anything about this location.') self._show_location_characters() - self._show_all_locations() + self._show_other_locations() return None if (char := self.find_character(argument)): self._prefix() @@ -368,12 +383,6 @@ def interaction(self) -> Character | Location | None: self._prefix() console.print(Text.assemble(char.display_name, '-', i)) return None - if (loc := self.find_location(argument)): - self._prefix() - if (i := loc.info): - console.print(i) - else: - console.print('You don\'t know anything about tat location.') - return None + self._prefix() console.print('I don\'t know what do you mean.') From 66723d80d55a551467480acc98bc049d413c8b49 Mon Sep 17 00:00:00 2001 From: Jakub Suchenek Date: Mon, 20 Nov 2023 18:06:11 +0100 Subject: [PATCH 07/10] Remove unused `help` message --- FTE/world.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/FTE/world.py b/FTE/world.py index 64367d7..17907f9 100644 --- a/FTE/world.py +++ b/FTE/world.py @@ -244,8 +244,6 @@ def interaction(self) -> Character | Location | None: ' with the World.', 'All commands are single words. For example "help" or' + ' "go" insted of "go to".', - # 'You can type "help " (eg. "help go")' + - # ' to see it\'s usage and description.', 'Have fun! :smile:' ): self._prefix_help() From fe9c4a5fd911108c9b6193dee4fa4fa1e4718a06 Mon Sep 17 00:00:00 2001 From: Jakub Suchenek Date: Mon, 20 Nov 2023 18:31:05 +0100 Subject: [PATCH 08/10] Add locations' descriptions --- FTE/chapters/one.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/FTE/chapters/one.py b/FTE/chapters/one.py index be87245..472f664 100644 --- a/FTE/chapters/one.py +++ b/FTE/chapters/one.py @@ -11,10 +11,22 @@ from FTE.world import World def chapter_one() -> None: - bridge = Location('Bridge') - capsules = Location('Capsules') - engine_deck = Location('Engine Deck') - quarters = Location('Quarters') + bridge = Location( + 'Bridge', + 'Big fishes spend time here.' + ) + capsules = Location( + 'Capsules', + 'You can escape the ship here during an emergency' + ) + engine_deck = Location( + 'Engine Deck', + 'Here engineers make sure the ship is working properly.' + ) + quarters = Location( + 'Quarters', + 'All crewmen spend night and freetime here.' + ) roommate = Character('Hevy', quarters, poke='Good to see you.', standing=Standing.GOOD) capitan = Character('Rex', bridge, poke='Yes sergant? Oh, wait.') From 262cea9f2796edc3232cd8d81f067abc5e40ed71 Mon Sep 17 00:00:00 2001 From: Jakub Suchenek Date: Mon, 20 Nov 2023 18:38:06 +0100 Subject: [PATCH 09/10] Update `World.interaction` - Handle cases with methods. - Simplify handlers. - All commands' handlers start with `_command_`. --- FTE/world.py | 295 ++++++++++++++++++++++++++------------------------- 1 file changed, 148 insertions(+), 147 deletions(-) diff --git a/FTE/world.py b/FTE/world.py index 17907f9..805c4de 100644 --- a/FTE/world.py +++ b/FTE/world.py @@ -185,32 +185,155 @@ def _show_other_locations(self) -> None: locations = locations[:-1] console.print(*locations, '.', sep='') - def go_to( + def _do_first_interaction(self) -> None: + self._prefix_help() + console.print( + 'This is your first interaction with the World.', + 'Would you like to enable assitant?' + ) + expect = ('yes', 'no') + self._prefix_help() + query = console.input('') + while query.lower() not in expect: + self._prefix_help() + query = console.input('"yes" or "no"? ') + self._first_interaction = False + if query == 'no': + self._prefix_help() + console.print('OK! I won\'t ask you again. Have fun!') + return + for line in ( + Text.assemble( + Text.assemble('Fix The Engines', style=Style(bold=True)), + ' is text-based, paragraph game. There is no mouse control,', + ' you operate only with commands.' + ), + 'You can show them by typing "help" during interaction' + + ' with the World.', + 'All commands are single words. For example "help" or' + + ' "go" insted of "go to".', + 'Have fun! :smile:' + ): + self._prefix_help() + console.print(line) + if not DEBUG: + sleep(2.0) + self._assistant = True + + def _command_exit(self) -> None: + self._prefix() + console.print('Goodbye!') + exit() + + def _command_help(self, menu: str = None) -> None: + show_commands, show_arguments = False, False + if menu == 'commands': + show_commands = True + elif menu == 'arguments': + show_arguments = True + else: + show_commands, show_arguments = True, True + if show_commands: + commands_table = Table( + title='Available commands', + show_lines=True + ) + commands_table.add_column('Command') + commands_table.add_column('Description') + commands_table.add_column('Usage') + for cmd in commands.values(): + commands_table.add_row(cmd.name, cmd.description, cmd.usage) + console.print(commands_table) + if show_arguments: + arguments_table = Table(title='Arguments description') + arguments_table.add_column('Representation') + arguments_table.add_column('Description') + for line in ( + ('< ... >', 'Required argument.'), + ('( ... )', 'Optional argument.'), + ('( a | b )', 'Optional argument, but only "a" or "b".') + ): + arguments_table.add_row(*line) + console.print(arguments_table) + + def _command_talk(self, character_name: str) -> Character | None: + """Tries to go specifiec `Character` by `character_name`. + If character exists, is in currect `Location` and is `pokable` + it returns it, otherwise `None`. + """ + if not character_name: + self._prefix() + console.print('You speak to everyone, but no one hears you.') + return None + if not self.character_in_global(character_name): + self._prefix() + console.print('You don\'t know this character.') + return None + if not self.character_in_location(character_name): + self._prefix() + console.print('This chracter is not here.') + return None + char = self.find_character(character_name) + if not char.pokable: + self._prefix() + console.print(Text.assemble(char.display_name, ' does not want to talk with you.')) + return None + char.monologue(char.poke) + return char + + def _command_go( self, - location: Location, - ) -> bool: - """Tries to go specifiec `Location`. - If location didn't change it returns `False`, otherwise `True`. + location_name: str = None, + ) -> Location | None: + """Tries to go specifiec `Location` by `location_name`. + If location changed it returns it, otherwise `None`. """ + if not location_name: + self._prefix() + console.print('After running in circle for a while you find it worthless.') + return None + if not (location := self.find_location(location_name)): + self._prefix() + console.print('You don\'t know this location.') + return None if self._location == location: console.print('You\'re currently here.') - return False + return None self._location = location self._prefix() console.print(Text.assemble('You\'re now in ', self.location.display_name, '.')) self._show_location_characters() - return True - - # def talk_to( - # self, - # character: Character - # ) -> tuple(bool, str): - # """Checks if you can talk to the character.""" - # if character not in self._characters: - # return (False, 'You can\'t talk to this character.') - # if not character.hookable: - # return (False, 'This character does not want to talk with you.') - # return (True, character.hook) + return location + + def _command_info(self, name: str = None) -> None: + """Shows info about `Location` by `Location.name` + or `Character` by `Character.name`. + """ + if not name: + name = self._location.name + if (loc := self.find_location(name)): + self._prefix() + if (i := loc.info): + console.print(i) + else: + console.print('You don\'t know anything about this location.') + if loc == self._location: + self._show_location_characters() + self._show_other_locations() + elif (char := self.find_character(name)): + self._prefix() + console.print(Text.assemble( + char.display_name, + ' has ', + char.standing.color_text, + ' standing towards you.' + )) + if (i := char.info): + self._prefix() + console.print(Text.assemble(char.display_name, '-', i)) + else: + self._prefix() + console.print('I don\'t know what do you mean.') def assistant(self, text: str| Text) -> None: if self._assistant: @@ -218,39 +341,7 @@ def assistant(self, text: str| Text) -> None: def interaction(self) -> Character | Location | None: if self._first_interaction: - self._prefix_help() - console.print( - 'This is your first interaction with the World.', - 'Would you like to enable assitant?' - ) - expect = ('yes', 'no') - self._prefix_help() - query = console.input('') - while query.lower() not in expect: - self._prefix_help() - query = console.input('"yes" or "no"? ') - self._first_interaction = False - if query == 'no': - self._prefix_help() - console.print('OK! I won\'t ask you again. Have fun!') - return None - for line in ( - Text.assemble( - Text.assemble('Fix The Engines', style=Style(bold=True)), - ' is text-based, paragraph game. There is no mouse control,', - ' you operate only with commands.' - ), - 'You can show them by typing "help" during interaction' + - ' with the World.', - 'All commands are single words. For example "help" or' + - ' "go" insted of "go to".', - 'Have fun! :smile:' - ): - self._prefix_help() - console.print(line) - if not DEBUG: - sleep(2.0) - self._assistant = True + self._do_first_interaction() return None query = '' while not query: @@ -283,104 +374,14 @@ def interaction(self) -> Character | Location | None: self._fails = 0 match command.name: case 'exit': - self._prefix() - console.print('Goodbye!') - exit() + self._command_exit() case 'help': - show_commands, show_arguments = False, False - if argument == 'commands': - show_commands = True - elif argument == 'arguments': - show_arguments = True - else: - show_commands, show_arguments = True, True - if show_commands: - commands_table = Table( - title='Available commands', - show_lines=True - ) - commands_table.add_column('Command') - commands_table.add_column('Description') - commands_table.add_column('Usage') - for cmd in commands.values(): - commands_table.add_row(cmd.name, cmd.description, cmd.usage) - console.print(commands_table) - if show_arguments: - arguments_table = Table(title='Arguments description') - arguments_table.add_column('Representation') - arguments_table.add_column('Description') - for line in ( - ('< ... >', 'Required argument.'), - ('( ... )', 'Optional argument.'), - ('( a | b )', 'Optional argument, but only "a" or "b".') - ): - arguments_table.add_row(*line) - console.print(arguments_table) + self._command_help(argument) return None case 'talk': - if not argument: - self._prefix() - console.print('You speak to everyone, but no one hears you.') - return None - if not self.character_in_global(argument): - self._prefix() - console.print('You don\'t know this character.') - return None - if not self.character_in_location(argument): - self._prefix() - console.print('This chracter is not here.') - return None - char = self.find_character(argument) - if not char.pokable: - self._prefix() - console.print(Text.assemble(char.display_name, ' does not want to talk with you.')) - return None - char.monologue(char.poke) - return char + return self._command_talk(argument) case 'go': - if not argument: - self._prefix() - console.print('After running in circle for a while you find it worthless.') - return None - if not (loc := self.find_location(argument)): - self._prefix() - console.print('You don\'t know this location.') - return None - if not self.go_to(loc): - return None - return self._location + return self._command_go(argument) case 'info': - if (loc := self.find_location(argument)): - if loc == self.location: - argument = None - else: - self._prefix() - if (i := loc.info): - console.print(i) - else: - console.print('You don\'t know anything about that location.') - return None - if not argument: - self._prefix() - if (i := loc.info): - console.print(i) - else: - console.print('You don\'t know anything about this location.') - self._show_location_characters() - self._show_other_locations() - return None - if (char := self.find_character(argument)): - self._prefix() - console.print(Text.assemble( - char.display_name, - ' has ', - char.standing.color_text, - ' standing towards you.' - )) - if (i := char.info): - self._prefix() - console.print(Text.assemble(char.display_name, '-', i)) - return None - - self._prefix() - console.print('I don\'t know what do you mean.') + self._command_info(argument) + return None From 9e356ab6143a8b8a9f50d9f38b88d86da813ee83 Mon Sep 17 00:00:00 2001 From: Jakub Suchenek Date: Mon, 20 Nov 2023 18:38:27 +0100 Subject: [PATCH 10/10] Add missing dot --- FTE/chapters/one.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FTE/chapters/one.py b/FTE/chapters/one.py index 472f664..a85ef73 100644 --- a/FTE/chapters/one.py +++ b/FTE/chapters/one.py @@ -17,7 +17,7 @@ def chapter_one() -> None: ) capsules = Location( 'Capsules', - 'You can escape the ship here during an emergency' + 'You can escape the ship here during an emergency.' ) engine_deck = Location( 'Engine Deck',