Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Quantum RPG] allow action selections to be modified #182

Merged
merged 8 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ docs/generated

# Notebook formatting script.
nbformat

build
19 changes: 11 additions & 8 deletions unitary/examples/quantum_rpg/battle.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
30 changes: 23 additions & 7 deletions unitary/examples/quantum_rpg/battle_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -39,6 +39,8 @@ def test_battle():
m) Measure enemy qubit.
q) Read Quantopedia.
?) Help.
[enter]) Confirm selection.
r) Select again.
""".strip()
)

Expand All @@ -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()
Expand All @@ -63,6 +65,8 @@ def test_bad_monster():
q) Read Quantopedia.
?) Help.
Invalid number selected.
[enter]) Confirm selection.
r) Select again.
""".strip()
)

Expand All @@ -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()
Expand All @@ -96,6 +100,8 @@ def test_higher_level_players():
s) Sample enemy qubit.
q) Read Quantopedia.
?) Help.
[enter]) Confirm selection.
r) Select again.
------------------------------------------------------------
Aaronson Analyst 1) watcher Observer
3QP (0|1> 0|0> 3?) 1QP (0|1> 1|0> 0?) *DOWN*
Expand All @@ -107,6 +113,8 @@ def test_higher_level_players():
x) Attack with X gate.
q) Read Quantopedia.
?) Help.
[enter]) Confirm selection.
r) Select again.
""".strip()
)

Expand Down Expand Up @@ -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
Expand All @@ -153,6 +161,8 @@ def test_battle_loop():
m) Measure enemy qubit.
q) Read Quantopedia.
?) Help.
[enter]) Confirm selection.
r) Select again.
------------------------------------------------------------
Battle Summary

Expand All @@ -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
Expand All @@ -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) Select again.
------------------------------------------------------------
Battle Summary

Expand All @@ -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
Expand All @@ -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) Select again.
------------------------------------------------------------
Battle Summary

Expand All @@ -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])
Expand All @@ -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) Select again.
------------------------------------------------------------
Battle Summary

Expand Down
7 changes: 5 additions & 2 deletions unitary/examples/quantum_rpg/encounter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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])

Expand All @@ -56,6 +57,8 @@ def test_encounter():
m) Measure enemy qubit.
q) Read Quantopedia.
?) Help.
[enter]) Confirm selection.
r) Select again.
""".strip()
)

Expand Down
34 changes: 29 additions & 5 deletions unitary/examples/quantum_rpg/input_helpers.py
Original file line number Diff line number Diff line change
@@ -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."
Expand All @@ -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.
Expand All @@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am conflicted about making the user do an extra prompt at the end for every action. Let's discuss at the next unitary meeting.

print("r) Select again.", file=file)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could change this to "Redo selection" so that the choice of 'r' is more obvious?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

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:
Expand Down
33 changes: 31 additions & 2 deletions unitary/examples/quantum_rpg/input_helpers_test.py
Original file line number Diff line number Diff line change
@@ -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():
Expand Down Expand Up @@ -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]
22 changes: 17 additions & 5 deletions unitary/examples/quantum_rpg/main_loop_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -366,6 +368,8 @@ def test_battle() -> None:
m) Measure enemy qubit.
q) Read Quantopedia.
?) Help.
[enter]) Confirm selection.
r) Select again.
------------------------------------------------------------
Battle Summary

Expand All @@ -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
Expand Down Expand Up @@ -427,6 +433,8 @@ def test_lost_battle() -> None:
x) Attack with X gate.
q) Read Quantopedia.
?) Help.
[enter]) Confirm selection.
r) Select again.
Observer watcher measures Mensing_1 as HURT.
------------------------------------------------------------
Battle Summary
Expand All @@ -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
Expand Down Expand Up @@ -492,6 +502,8 @@ def test_escaped_battle():
x) Attack with X gate.
q) Read Quantopedia.
?) Help.
[enter]) Confirm selection.
r) Select again.
Observer watcher measures Mensing_1 as HEALTHY.
------------------------------------------------------------
Battle Summary
Expand Down
Loading