diff --git a/.gitignore b/.gitignore index 00b93bc6..b8d0da42 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,5 @@ docs/generated # Notebook formatting script. nbformat + +build \ No newline at end of file diff --git a/unitary/examples/quantum_rpg/battle.py b/unitary/examples/quantum_rpg/battle.py index e859bc18..2aca9391 100644 --- a/unitary/examples/quantum_rpg/battle.py +++ b/unitary/examples/quantum_rpg/battle.py @@ -143,19 +143,22 @@ def take_player_turn(self): else: break if action in current_player.actions(): - monster = ( - input_helpers.get_user_input_number( + monster, qubit = input_helpers.get_multiple_user_inputs( + self.get_user_input, + lambda: input_helpers.get_user_input_number( self.get_user_input, "Which enemy number: ", max_number=len(self.enemy_side), file=self.file, - ) - - 1 - ) - selected_monster = self.enemy_side[monster] - qubit = input_helpers.get_user_input_number( - self.get_user_input, "Which enemy qubit number: ", file=self.file + ), + lambda: input_helpers.get_user_input_number( + self.get_user_input, + "Which enemy qubit number: ", + file=self.file, + ), + file=self.file, ) + selected_monster = self.enemy_side[monster - 1] qubit_name = selected_monster.quantum_object_name(qubit) if qubit_name in selected_monster.active_qubits(): res = actions[action](selected_monster, qubit) diff --git a/unitary/examples/quantum_rpg/battle_test.py b/unitary/examples/quantum_rpg/battle_test.py index 98f2f084..4edfb253 100644 --- a/unitary/examples/quantum_rpg/battle_test.py +++ b/unitary/examples/quantum_rpg/battle_test.py @@ -23,7 +23,7 @@ def test_battle(): c = classes.Analyst("Aaronson") e = npcs.Observer("watcher") state = game_state.GameState( - party=[c], user_input=["m", "1", "1"], file=io.StringIO() + party=[c], user_input=["m", "1", "1", ""], file=io.StringIO() ) b = battle.Battle(state, [e]) b.take_player_turn() @@ -39,6 +39,8 @@ def test_battle(): m) Measure enemy qubit. q) Read Quantopedia. ?) Help. +[enter]) Confirm selection. +r) Redo selection. """.strip() ) @@ -47,7 +49,7 @@ def test_bad_monster(): c = classes.Analyst("Aaronson") e = npcs.Observer("watcher") state = game_state.GameState( - party=[c], user_input=["m", "2", "1", "1"], file=io.StringIO() + party=[c], user_input=["m", "2", "1", "1", ""], file=io.StringIO() ) b = battle.Battle(state, [e]) b.take_player_turn() @@ -63,6 +65,8 @@ def test_bad_monster(): q) Read Quantopedia. ?) Help. Invalid number selected. +[enter]) Confirm selection. +r) Redo selection. """.strip() ) @@ -78,7 +82,7 @@ def test_higher_level_players(): w = npcs.Observer("watcher") w2 = npcs.Observer("watcher") state = game_state.GameState( - [c, e], user_input=["m", "1", "1", "h", "2", "1"], file=io.StringIO() + [c, e], user_input=["m", "1", "1", "", "h", "2", "1", ""], file=io.StringIO() ) b = battle.Battle(state, [w, w2]) b.take_player_turn() @@ -96,6 +100,8 @@ def test_higher_level_players(): s) Sample enemy qubit. q) Read Quantopedia. ?) Help. +[enter]) Confirm selection. +r) Redo selection. ------------------------------------------------------------ Aaronson Analyst 1) watcher Observer 3QP (0|1> 0|0> 3?) 1QP (0|1> 1|0> 0?) *DOWN* @@ -107,6 +113,8 @@ def test_higher_level_players(): x) Attack with X gate. q) Read Quantopedia. ?) Help. +[enter]) Confirm selection. +r) Redo selection. """.strip() ) @@ -138,7 +146,7 @@ def test_battle_loop(): c = classes.Analyst("Aaronson") e = npcs.Observer("watcher") state = game_state.GameState( - party=[c], user_input=["m", "1", "1"], file=io.StringIO() + party=[c], user_input=["m", "1", "1", ""], file=io.StringIO() ) b = battle.Battle(state, [e]) assert b.loop() == battle.BattleResult.PLAYERS_WON @@ -153,6 +161,8 @@ def test_battle_loop(): m) Measure enemy qubit. q) Read Quantopedia. ?) Help. +[enter]) Confirm selection. +r) Redo selection. ------------------------------------------------------------ Battle Summary @@ -167,7 +177,7 @@ def test_battle_help(): c = classes.Analyst("Aaronson") e = npcs.Observer("watcher") state = game_state.GameState( - party=[c], user_input=["?", "m", "1", "1"], file=io.StringIO() + party=[c], user_input=["?", "m", "1", "1", ""], file=io.StringIO() ) b = battle.Battle(state, [e]) assert b.loop() == battle.BattleResult.PLAYERS_WON @@ -186,6 +196,8 @@ def test_battle_help(): into the |0> state or |1> state with a probability based on its amplitude. Try to measure the enemy qubits as |0> to defeat them. +[enter]) Confirm selection. +r) Redo selection. ------------------------------------------------------------ Battle Summary @@ -200,7 +212,7 @@ def test_read_quantopedia_not_known(): c = classes.Analyst("Aaronson") e = npcs.Observer("watcher") state = game_state.GameState( - party=[c], user_input=["q", "m", "1", "1"], file=io.StringIO() + party=[c], user_input=["q", "m", "1", "1", ""], file=io.StringIO() ) b = battle.Battle(state, [e]) assert b.loop() == battle.BattleResult.PLAYERS_WON @@ -216,6 +228,8 @@ def test_read_quantopedia_not_known(): q) Read Quantopedia. ?) Help. You do not have information on Observer yet. +[enter]) Confirm selection. +r) Redo selection. ------------------------------------------------------------ Battle Summary @@ -230,7 +244,7 @@ def test_read_quantopedia(): c = classes.Analyst("Aaronson") e = npcs.Observer("watcher") state = game_state.GameState( - party=[c], user_input=["q", "m", "1", "1"], file=io.StringIO() + party=[c], user_input=["q", "m", "1", "1", ""], file=io.StringIO() ) state.set_quantopedia(1) b = battle.Battle(state, [e]) @@ -248,6 +262,8 @@ def test_read_quantopedia(): ?) Help. Observers are known to frequent quantum events. They will measure qubits in order to find out their values. +[enter]) Confirm selection. +r) Redo selection. ------------------------------------------------------------ Battle Summary diff --git a/unitary/examples/quantum_rpg/encounter_test.py b/unitary/examples/quantum_rpg/encounter_test.py index 25687d8b..a36fc881 100644 --- a/unitary/examples/quantum_rpg/encounter_test.py +++ b/unitary/examples/quantum_rpg/encounter_test.py @@ -13,11 +13,12 @@ # limitations under the License. import io + import unitary.alpha as alpha import unitary.examples.quantum_rpg.battle as battle import unitary.examples.quantum_rpg.classes as classes -import unitary.examples.quantum_rpg.game_state as game_state import unitary.examples.quantum_rpg.encounter as encounter +import unitary.examples.quantum_rpg.game_state as game_state import unitary.examples.quantum_rpg.npcs as npcs import unitary.examples.quantum_rpg.xp_utils as xp_utils @@ -38,7 +39,7 @@ def test_encounter(): c = classes.Analyst("Aaronson") o = npcs.Observer("watcher") state = game_state.GameState( - party=[c], user_input=["m", "1", "1"], file=io.StringIO() + party=[c], user_input=["m", "1", "1", ""], file=io.StringIO() ) e = encounter.Encounter([o]) @@ -56,6 +57,8 @@ def test_encounter(): m) Measure enemy qubit. q) Read Quantopedia. ?) Help. +[enter]) Confirm selection. +r) Redo selection. """.strip() ) diff --git a/unitary/examples/quantum_rpg/input_helpers.py b/unitary/examples/quantum_rpg/input_helpers.py index 6e1b44c6..f4649624 100644 --- a/unitary/examples/quantum_rpg/input_helpers.py +++ b/unitary/examples/quantum_rpg/input_helpers.py @@ -1,9 +1,10 @@ """Functions for safe and user-friendly input.""" -from typing import Callable, Optional, Sequence, TextIO +from typing import Callable, Optional, Sequence, TextIO, Union import sys -import unitary.examples.quantum_rpg.qaracter as qaracter + +from unitary.examples.quantum_rpg import qaracter _USER_INPUT = Callable[[str], str] _INVALID_MESSAGE = "Invalid number selected." @@ -27,9 +28,9 @@ def get_user_input_number( get_user_input: _USER_INPUT, message: str = "", max_number: Optional[int] = None, - invalid_message: Optional[str] = _INVALID_MESSAGE, + invalid_message: str = _INVALID_MESSAGE, file: TextIO = sys.stdout, -): +) -> int: """Helper to get a valid number from the user. This will only accept valid numbers from the user from 1 to max_number. @@ -54,9 +55,32 @@ def get_user_input_number( print("number out of range", file=file) +def get_multiple_user_inputs( + get_user_input: _USER_INPUT, + *prompts: Sequence[Union[Callable[[], int], Callable[[], str]]], + file: TextIO = sys.stdout, +) -> Sequence[Union[int, str]]: + """Runs multiple number or string prompts and returns their results. + + After all inputs have been provided the last prompt asks for confirmation if + user happy with inputs, and allows to restart the sequence and change + inputs. + """ + while True: + inputs = [p() for p in prompts] + print("[enter]) Confirm selection.", file=file) + print("r) Redo selection.", file=file) + while True: + a = get_user_input("Choose your action: ") + if a == "r": + break + if a == "": + return inputs + + def get_user_input_qaracter_name( get_user_input: _USER_INPUT, - qaracter_type: Optional[str] = "a new qaracter", + qaracter_type: str = "a new qaracter", file: TextIO = sys.stdout, ): while True: diff --git a/unitary/examples/quantum_rpg/input_helpers_test.py b/unitary/examples/quantum_rpg/input_helpers_test.py index 01ebd015..86339245 100644 --- a/unitary/examples/quantum_rpg/input_helpers_test.py +++ b/unitary/examples/quantum_rpg/input_helpers_test.py @@ -1,7 +1,8 @@ -import pytest import io -import unitary.examples.quantum_rpg.input_helpers as input_helpers +import pytest + +from unitary.examples.quantum_rpg import input_helpers def test_get_user_input_function(): @@ -43,3 +44,31 @@ def test_get_user_input_number_max(): assert ( input_helpers.get_user_input_number(get_input, file=output, max_number=4) == 3 ) + + +def test_get_multiple_user_inputs(): + get_input = input_helpers.get_user_input_function( + ["1", "2", "r", "3", "1", ""], + ) + output = io.StringIO() + + def p1(): + return input_helpers.get_user_input_number( + get_input, + max_number=4, + file=output, + ) + + def p2(): + return input_helpers.get_user_input_number( + get_input, + max_number=4, + file=output, + ) + + assert input_helpers.get_multiple_user_inputs( + get_input, + p1, + p2, + file=output, + ) == [3, 1] diff --git a/unitary/examples/quantum_rpg/main_loop_test.py b/unitary/examples/quantum_rpg/main_loop_test.py index 321eb6a2..4cbbe870 100644 --- a/unitary/examples/quantum_rpg/main_loop_test.py +++ b/unitary/examples/quantum_rpg/main_loop_test.py @@ -11,10 +11,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from typing import cast - import copy import io +from typing import cast + import unitary.alpha as alpha import unitary.examples.quantum_rpg.ascii_art as ascii_art import unitary.examples.quantum_rpg.classes as classes @@ -328,7 +328,9 @@ def test_bad_load() -> None: def test_battle() -> None: c = classes.Analyst("Mensing") state = game_state.GameState( - party=[c], user_input=["e", "south", "m", "1", "1", "quit"], file=io.StringIO() + party=[c], + user_input=["e", "south", "m", "1", "1", "", "quit"], + file=io.StringIO(), ) loop = main_loop.MainLoop(state=state, world=world.World(example_world())) loop.loop() @@ -366,6 +368,8 @@ def test_battle() -> None: m) Measure enemy qubit. q) Read Quantopedia. ?) Help. +[enter]) Confirm selection. +r) Redo selection. ------------------------------------------------------------ Battle Summary @@ -385,7 +389,9 @@ def test_battle() -> None: def test_lost_battle() -> None: c = classes.Engineer("Mensing") state = game_state.GameState( - party=[c], user_input=["e", "south", "x", "1", "1", "quit"], file=io.StringIO() + party=[c], + user_input=["e", "south", "x", "1", "1", "", "quit"], + file=io.StringIO(), ) assert state.party[0].name == "Mensing" assert len(state.party[0].active_qubits()) == 1 @@ -427,6 +433,8 @@ def test_lost_battle() -> None: x) Attack with X gate. q) Read Quantopedia. ?) Help. +[enter]) Confirm selection. +r) Redo selection. Observer watcher measures Mensing_1 as HURT. ------------------------------------------------------------ Battle Summary @@ -450,7 +458,9 @@ def test_escaped_battle(): c = classes.Engineer("Mensing") c.add_quantum_effect(alpha.Flip(), 1) state = game_state.GameState( - party=[c], user_input=["e", "south", "x", "1", "1", "quit"], file=io.StringIO() + party=[c], + user_input=["e", "south", "x", "1", "1", "", "quit"], + file=io.StringIO(), ) assert state.party[0].name == "Mensing" assert len(state.party[0].active_qubits()) == 1 @@ -492,6 +502,8 @@ def test_escaped_battle(): x) Attack with X gate. q) Read Quantopedia. ?) Help. +[enter]) Confirm selection. +r) Redo selection. Observer watcher measures Mensing_1 as HEALTHY. ------------------------------------------------------------ Battle Summary