-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #75 from dstrain115/qrpg3
Quantum RPG Battle Engine Add class to actually run a Quantum RPG battle. This pits players versus NPCs in a battle. Player actions and NPC actions can be triggered via functions. Also adds a simple test NPC.
- Loading branch information
Showing
5 changed files
with
224 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import io | ||
import sys | ||
from typing import List, Optional | ||
|
||
from unitary.examples.quantum_rpg.qaracter import Qaracter | ||
|
||
|
||
class Battle: | ||
"""Class representing a battle between players and NPCs. | ||
This class encapsulates a list of players and enemies (NPCs), | ||
each of which are `QuantumWorld` objects. | ||
This class has functions for each side to take a turn using | ||
rules of the Quantum RPG, as well as a function to print out | ||
the status. | ||
Args: | ||
player_side: a list of player QuantumWorld objects representing | ||
their character sheets (initial state). | ||
enemy_side: a list of NPC QuantumWorld objects. | ||
file: Optional IOBase file object to write output to. | ||
This enables the battle to write status to a file or string | ||
for testing. | ||
""" | ||
|
||
def __init__(self, | ||
player_side: List[Qaracter], | ||
enemy_side: List[Qaracter], | ||
file: io.IOBase = sys.stdout): | ||
self.player_side = player_side | ||
self.enemy_side = enemy_side | ||
self.file = file | ||
|
||
def print_screen(self): | ||
"""Prints a two-column output of the battle status. | ||
Left side includes the players and their qubits. Right | ||
side includes the NPCs and their qubits. | ||
Output will be written to the `file` attribute. | ||
""" | ||
print('-----------------------------------------------', file=self.file) | ||
for i in range(max(len(self.player_side), len(self.enemy_side))): | ||
status = '' | ||
if i < len(self.player_side): | ||
status += f'{self.player_side[i].name} {type(self.player_side[i]).__name__}' | ||
else: | ||
status += '\t\t' | ||
status += '\t\t\t' | ||
if i < len(self.enemy_side): | ||
status += f'{self.enemy_side[i].name} {type(self.enemy_side[i]).__name__}' | ||
|
||
status += '\n' | ||
|
||
if i < len(self.player_side): | ||
status += self.player_side[i].status_line() | ||
else: | ||
status += '\t\t' | ||
status += '\t\t\t' | ||
if i < len(self.enemy_side): | ||
status += self.enemy_side[i].status_line() | ||
print(status, file=self.file) | ||
print('-----------------------------------------------', file=self.file) | ||
|
||
def take_player_turn(self, user_input: Optional[List[str]] = None): | ||
"""Take a player's turn and record results in the battle. | ||
1) Retrieve the possible actions from the player. | ||
2) Prompt the player for which action to use. | ||
3) Prompt the player which NPC and qubit to target. | ||
4) Call the player's action to perform the action. | ||
Args: | ||
user_input: List of strings that substitute for the user's | ||
raw input. | ||
""" | ||
|
||
# If user input is provided as an argument, then use that. | ||
# Otherwise, prompt from raw input. | ||
if user_input is not None: | ||
user_input = iter(user_input) | ||
get_user_input = lambda _: next(user_input) | ||
else: | ||
get_user_input = input | ||
|
||
for current_player in self.player_side: | ||
self.print_screen() | ||
print(f'{current_player.name} turn:', file=self.file) | ||
if not current_player.is_active(): | ||
print(f'{current_player.name} is DOWN!', file=self.file) | ||
continue | ||
actions = current_player.actions() | ||
for key in actions: | ||
print(key, file=self.file) | ||
action = get_user_input('Choose your action: ') | ||
if action in current_player.actions(): | ||
monster = int(get_user_input('Which enemy number: ')) - 1 | ||
if monster < len(self.enemy_side): | ||
qubit = int(get_user_input('Which enemy qubit number: ')) | ||
selected_monster = self.enemy_side[monster] | ||
qubit_name = selected_monster.quantum_object_name(qubit) | ||
if qubit_name in selected_monster.active_qubits(): | ||
res = actions[action](selected_monster, qubit) | ||
if isinstance(res, str): | ||
print(res, file=self.file) | ||
else: | ||
print(f'{qubit_name} is not an active qubit', | ||
file=self.file) | ||
else: | ||
print(f'{monster + 1} is not a valid monster', | ||
file=self.file) | ||
|
||
def take_npc_turn(self): | ||
"""Take all NPC turns. | ||
Loop through all NPCs and call each function. | ||
""" | ||
for npc in self.enemy_side: | ||
if not npc.is_active(): | ||
print(f'{npc.name} is DOWN!', file=self.file) | ||
continue | ||
result = npc.npc_action(self) | ||
print(result, file=self.file) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import io | ||
import unitary.examples.quantum_rpg.battle as battle | ||
import unitary.examples.quantum_rpg.classes as classes | ||
import unitary.examples.quantum_rpg.npcs as npcs | ||
|
||
|
||
def test_battle(): | ||
output = io.StringIO() | ||
c = classes.Analyst('Aaronson') | ||
e = npcs.Observer('watcher') | ||
b = battle.Battle([c], [e], file=output) | ||
b.take_player_turn(user_input=['s', '1', '1']) | ||
b.take_npc_turn() | ||
assert output.getvalue().replace('\t', ' ').strip() == r""" | ||
----------------------------------------------- | ||
Aaronson Analyst watcher Observer | ||
1QP (0|1> 0|0> 1?) 1QP (0|1> 0|0> 1?) | ||
----------------------------------------------- | ||
Aaronson turn: | ||
s | ||
m | ||
Sample result HealthPoint.HURT | ||
Observer watcher measures Aaronson at qubit Aaronson_1 | ||
""".strip() | ||
|
||
|
||
def test_bad_monster(): | ||
output = io.StringIO() | ||
c = classes.Analyst('Aaronson') | ||
e = npcs.Observer('watcher') | ||
b = battle.Battle([c], [e], file=output) | ||
b.take_player_turn(user_input=['s', '2', '1']) | ||
assert output.getvalue().replace('\t', ' ').strip() == r""" | ||
----------------------------------------------- | ||
Aaronson Analyst watcher Observer | ||
1QP (0|1> 0|0> 1?) 1QP (0|1> 0|0> 1?) | ||
----------------------------------------------- | ||
Aaronson turn: | ||
s | ||
m | ||
2 is not a valid monster | ||
""".strip() | ||
|
||
|
||
def test_bad_qubit(): | ||
output = io.StringIO() | ||
c = classes.Analyst('Aaronson') | ||
e = npcs.Observer('watcher') | ||
b = battle.Battle([c], [e], file=output) | ||
b.take_player_turn(user_input=['s', '1', '2']) | ||
assert output.getvalue().replace('\t', ' ').strip() == r""" | ||
----------------------------------------------- | ||
Aaronson Analyst watcher Observer | ||
1QP (0|1> 0|0> 1?) 1QP (0|1> 0|0> 1?) | ||
----------------------------------------------- | ||
Aaronson turn: | ||
s | ||
m | ||
watcher_2 is not an active qubit | ||
""".strip() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import random | ||
|
||
import unitary.alpha as alpha | ||
from unitary.examples.quantum_rpg import qaracter | ||
|
||
|
||
class Npc(qaracter.Qaracter): | ||
"""Base class for non-player character `Qaracter` objects. | ||
""" | ||
|
||
def is_npc(self): | ||
return True | ||
|
||
|
||
class Observer(Npc): | ||
"""Simple test NPC that measures a random qubit each turn.""" | ||
|
||
def npc_action(self, battle) -> str: | ||
enemy_target = random.randint(0, len(battle.player_side) - 1) | ||
enemy_name = battle.player_side[enemy_target].name | ||
enemy_qubit = random.choice( | ||
battle.player_side[enemy_target].active_qubits()) | ||
|
||
battle.player_side[enemy_target].sample(enemy_qubit, True) | ||
return f'Observer {self.name} measures {enemy_name} at qubit {enemy_qubit}' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
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.npcs as npcs | ||
|
||
|
||
def test_observer(): | ||
qar = npcs.Observer(name='glasses') | ||
c = classes.Analyst('cat') | ||
b = battle.Battle([c], [qar]) | ||
assert qar.is_npc() | ||
assert qar.npc_action(b) == "Observer glasses measures cat at qubit cat_1" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters