ui/basic: add basic user interface

This commit is contained in:
rr-
2018-12-14 00:32:21 +01:00
parent e27f3470a4
commit 595538cdca
5 changed files with 242 additions and 4 deletions
+10 -1
View File
@@ -4,6 +4,7 @@ from pathlib import Path
import xdg
from pqcli.roster import Roster
from pqcli.ui.basic import BasicUserInterface
from pqcli.ui.urwid import UrwidUserInterface
SAVE_PATH = Path(xdg.XDG_CONFIG_HOME) / "pqcli" / "save.dat"
@@ -11,6 +12,9 @@ SAVE_PATH = Path(xdg.XDG_CONFIG_HOME) / "pqcli" / "save.dat"
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(prog="pqcli")
parser.add_argument(
"--basic", action="store_true", help="Use basic user interface"
)
parser.add_argument(
"--no-config",
dest="use_config",
@@ -35,7 +39,12 @@ def main() -> None:
roster = Roster.load(SAVE_PATH)
try:
UrwidUserInterface(roster, args).run()
if args.basic:
ui = BasicUserInterface(roster, args)
else:
ui = UrwidUserInterface(roster, args)
ui.run()
finally:
if args.use_saves:
roster.save()
+5
View File
@@ -122,3 +122,8 @@ def special(m: int, subject: str) -> str:
return prefix(
["Battle-", "cursed ", "Were-", "undead ", "demon "], m, subject, ""
)
def terminate_message(player_name: str) -> None:
adjective = random.choice(["faithful", "noble", "loyal", "brave"])
return f"Terminate {adjective} {player_name}?"
+7 -1
View File
@@ -170,7 +170,7 @@ class Inventory(SignalMixin):
def pop(self, index: int) -> None:
item = self._items[index]
logger.info("Lost %s (qty=%d)", item.name, item.quantity)
logger.info("Lost %s", indefinite(item.name, item.quantity))
self._items.pop(index)
self.sync_encumbrance()
self.emit("item_del", item)
@@ -237,6 +237,12 @@ class SpellBook(SignalMixin):
def __iter__(self) -> T.Iterator[Spell]:
return iter(self._spells)
def __bool__(self) -> bool:
return bool(self._spells)
def __len__(self) -> int:
return len(self._spells)
def add(self, spell_name: str, level: int) -> None:
for spell in self._spells:
if spell.name == spell_name:
+218
View File
@@ -0,0 +1,218 @@
import argparse
import enum
import logging
import signal
import sys
import time
import typing as T
from datetime import datetime, timedelta
from pqcli import lingo
from pqcli.config import CLASSES, PRIME_STATS, RACES
from pqcli.mechanic import Player, Simulation, StatsBuilder, create_player
from pqcli.roster import Roster
LOGO = """
____ ___ _
| _ \ _ __ ___ __ _ _ __ ___ ___ ___ / _ \ _ _ ___ ___| |_
| |_) | '__/ _ \ / _` | '__/ _ \/ __/ __| | | | | | |/ _ \/ __| __|
| __/| | | (_) | (_| | | | __/\__ \__ \ |_| | |_| | __/\__ \ |_
|_| |_| \___/ \__, |_| \___||___/___/\__\_\\\\__,_|\___||___/\__|
|___/
"""
class MainMenu(enum.IntEnum):
create = 1
play = 2
info = 3
delete = 4
quit = 5
class BasicUserInterface:
def __init__(self, roster: Roster, args: argparse.Namespace) -> None:
self.roster = roster
self.args = args
def signal_handler(sig, frame):
print("Quitting")
if self.args.use_saves:
self.roster.save()
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
def run(self) -> None:
logging.basicConfig(
stream=sys.stdout,
level=logging.INFO,
format="[%(asctime)s] %(message)s",
)
self.logo()
self.main_menu()
def logo(self) -> None:
print(LOGO)
def main_menu(self):
while True:
choice = self.menu(
[
(MainMenu.create, "Create a new character"),
(MainMenu.play, "Play as character"),
(MainMenu.info, "Query character info"),
(MainMenu.delete, "Delete a character"),
(MainMenu.quit, "Quit"),
]
)
if choice == MainMenu.create:
player = self.create_player()
elif choice == MainMenu.play:
player = self.choose_player()
if not player:
continue
self.play(player)
elif choice == MainMenu.info:
player = self.choose_player()
if not player:
continue
self.print_player_info(player)
elif choice == MainMenu.delete:
player = self.choose_player()
if not player:
continue
self.delete_player(player)
elif choice == MainMenu.quit:
self.quit()
break
def create_player(self) -> T.Optional[Player]:
name = input("Name your new character: ")
if not name:
print("Cancelled.")
return None
race = self.menu([(race, race.name) for race in RACES])
class_ = self.menu([(class_, class_.name) for class_ in CLASSES])
stats_builder = StatsBuilder()
while True:
stats = stats_builder.roll()
for stat in PRIME_STATS:
print(f"{stat.value}: {stats[stat]}")
print(f"Total: {sum(stats[stat] for stat in PRIME_STATS)}")
if self.confirm("Is this okay?"):
break
player = create_player(
name=name, race=race, class_=class_, stats=stats
)
self.roster.players.append(player)
if self.confirm("Do you want to play as your new character?"):
self.play(player)
return player
def play(self, player: Player) -> None:
print(f"Playing as {player.name}")
simulation = Simulation(player)
last_tick = datetime.now()
last_level = 0
while True:
elapsed = datetime.now() - last_tick
simulation.tick(elapsed.total_seconds() * 1000)
last_tick = datetime.now()
wait = (timedelta(milliseconds=100) - elapsed).total_seconds()
wait = max(0, wait)
time.sleep(wait)
if player.level != last_level:
last_level = player.level
self.print_player_info(player)
def print_player_info(self, player: Player) -> None:
print("--- Character Sheet ---")
print(f"Name: {player.name}")
print(f"Race: {player.race.name}")
print(f"Class: {player.class_.name}")
print(f"Level: {player.level}")
print()
for stat, value in player.stats:
print(f"{stat.value}: {value}")
print()
print("--- Spell Book ---")
if not player.spell_book:
print("No spells memorized yet.")
else:
for spell in player.spell_book:
print(f"{spell.name} {lingo.to_roman(spell.level)}")
print()
print("--- Equipment ---")
for equipment_type, name in player.equipment:
print(f"{equipment_type.value}: {name}")
print()
print("--- Inventory ---")
print(f"Gold: {player.inventory.gold}")
for item in player.inventory:
print(f"{item.name}: {item.quantity}")
print()
print("--- Plot ---")
print(f"Current act: {lingo.to_roman(player.quest_book.act)}")
print(f"Current quest: {player.quest_book.current_quest or '?'}")
print(f"Current task: {player.task.description}")
def delete_player(self, player: Player) -> None:
if self.confirm(lingo.terminate_message(player.name)):
del self.roster.players[self.roster.players.index(player)]
def quit(self) -> None:
if self.args.use_saves:
self.roster.save()
def choose_player(self) -> T.Optional[Player]:
if not self.roster.players:
print("No characters to choose from!")
return None
return self.menu(
[(player, player.name) for player in self.roster.players],
title="Choose your character",
)
def confirm(self, message: str) -> bool:
while True:
choice = input(f"{message} [y/n] ")
if choice.lower() in {"y", "yes", "1"}:
return True
if choice.lower() in {"n", "no", "0"}:
return False
def menu(
self,
options: T.Sequence[T.Tuple[T.Any, str]],
title: T.Optional[str] = None,
) -> T.Any:
print()
if title:
print(f"{title}:")
for i, item in enumerate(options, 1):
_, description = item
print(f"{i}) {description}")
print()
while True:
try:
num = int(input("Your choice: "))
except ValueError:
print("Not a number")
continue
if num not in range(1, len(options) + 1):
print(f"Expected a number betwen 1..{len(options)}")
continue
return options[num - 1][0]
+2 -2
View File
@@ -7,6 +7,7 @@ import urwid
import xdg
from pqcli import random
from pqcli import lingo
from pqcli.mechanic import Player
from pqcli.roster import Roster
from pqcli.ui.urwid.confirm_dialog import ConfirmDialog
@@ -132,12 +133,11 @@ class UrwidUserInterface:
self._connect("cancel", self.switch_to_roster_view)
def switch_to_delete_player_view(self, player_idx: int) -> None:
adjective = random.choice(["faithful", "noble", "loyal", "brave"])
player_name = self.roster.players[player_idx].name
self.old_views.append(self.loop.widget)
self.loop.widget = ConfirmDialog(
f"Terminate {adjective} {player_name}?", self.old_views[-1]
lingo.terminate_message(player_name), self.old_views[-1]
)
self._connect("confirm", lambda: self.delete_player(player_idx))
self._connect("cancel", self.cancel_dialog)