mirror of
https://github.com/rr-/pq-cli.git
synced 2026-09-29 06:44:00 -06:00
initial commit
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import typing as T
|
||||
|
||||
import urwid
|
||||
|
||||
from pqcli.game_state import Roster
|
||||
from pqcli.ui.exit_view import ExitView
|
||||
from pqcli.ui.new_game_view import NewGameView
|
||||
from pqcli.ui.roster_view import RosterView
|
||||
|
||||
|
||||
urwid.command_map["k"] = "cursor up"
|
||||
urwid.command_map["K"] = "cursor up"
|
||||
urwid.command_map["j"] = "cursor down"
|
||||
urwid.command_map["J"] = "cursor down"
|
||||
urwid.command_map["h"] = "cursor left"
|
||||
urwid.command_map["H"] = "cursor left"
|
||||
urwid.command_map["l"] = "cursor right"
|
||||
urwid.command_map["L"] = "cursor right"
|
||||
|
||||
|
||||
class Ui:
|
||||
def __init__(self, roster: Roster) -> None:
|
||||
self.roster = roster
|
||||
self.loop = urwid.MainLoop(None, unhandled_input=self.unhandled_input)
|
||||
self.old_view: T.Optional[urwid.Widget] = None
|
||||
|
||||
self.switch_to_roster_view()
|
||||
|
||||
def run(self) -> None:
|
||||
self.loop.run()
|
||||
|
||||
def unhandled_input(self, key: str) -> bool:
|
||||
callback = getattr(self.loop.widget, "unhandled_input", None)
|
||||
if callback and callback(key):
|
||||
return True
|
||||
|
||||
if key == "ctrl q":
|
||||
self.old_view = self.loop.widget
|
||||
self.switch_to_exit_view()
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def switch_to_roster_view(self) -> None:
|
||||
self.loop.widget = RosterView(
|
||||
self.roster,
|
||||
self.loop,
|
||||
on_exit=self.switch_to_exit_view,
|
||||
on_new_game=self.switch_to_new_game_view,
|
||||
on_resume_game=self.switch_to_game_view,
|
||||
)
|
||||
|
||||
def switch_to_exit_view(self) -> None:
|
||||
self.old_view = self.loop.widget
|
||||
self.loop.widget = ExitView(
|
||||
self.old_view, on_exit=self.exit, on_cancel=self.cancel_exit
|
||||
)
|
||||
|
||||
def switch_to_new_game_view(self) -> None:
|
||||
self.loop.widget = NewGameView(
|
||||
on_confirm=self.switch_to_game_view,
|
||||
on_cancel=self.switch_to_roster_view,
|
||||
)
|
||||
|
||||
def switch_to_game_view(self, player_name: str) -> None:
|
||||
...
|
||||
|
||||
def exit(self) -> None:
|
||||
raise urwid.ExitMainLoop()
|
||||
|
||||
def cancel_exit(self) -> None:
|
||||
assert self.old_view is not None
|
||||
self.loop.widget = self.old_view
|
||||
self.old_view = None
|
||||
@@ -0,0 +1,34 @@
|
||||
import typing as T
|
||||
|
||||
import urwid
|
||||
|
||||
from pqcli.game_state import Roster
|
||||
|
||||
|
||||
class ExitView(urwid.Overlay):
|
||||
def __init__(
|
||||
self, parent: urwid.Widget, on_exit: T.Callable, on_cancel: T.Callable
|
||||
) -> None:
|
||||
self.on_exit = on_exit
|
||||
self.on_cancel = on_cancel
|
||||
|
||||
question = urwid.Text(("bold", "Really quit?"), "center")
|
||||
yes_btn = urwid.Button("Yes", lambda _user_data: self.on_exit())
|
||||
no_btn = urwid.Button("No", lambda _user_data: self.on_cancel())
|
||||
|
||||
line_box = urwid.LineBox(
|
||||
urwid.ListBox(
|
||||
urwid.SimpleFocusListWalker([question, no_btn, yes_btn])
|
||||
)
|
||||
)
|
||||
|
||||
super().__init__(line_box, parent, "center", 20, "middle", 5)
|
||||
|
||||
def keypress(self, size: T.Any, key: str) -> bool:
|
||||
if key in {"y", "Y"}:
|
||||
self.on_exit()
|
||||
return True
|
||||
if key in {"n", "N"}:
|
||||
self.on_cancel()
|
||||
return True
|
||||
return super().keypress(size, key)
|
||||
@@ -0,0 +1,141 @@
|
||||
import random
|
||||
import typing as T
|
||||
|
||||
import urwid
|
||||
import urwid_readline
|
||||
|
||||
from pqcli.game_state import Player, StatsBuilder, generate_name, create_player
|
||||
from pqcli.config import RACES, CLASSES, PRIME_STATS
|
||||
|
||||
|
||||
class StatsGroupBox(urwid.Pile):
|
||||
def __init__(self, player: Player, stats_builder: StatsBuilder) -> None:
|
||||
self.player = player
|
||||
self.stats_builder = stats_builder
|
||||
self.stat_labels = {stat: urwid.Text("0") for stat in PRIME_STATS}
|
||||
self.total_label = urwid.Text("0")
|
||||
self.update_values()
|
||||
|
||||
roll_button = urwid.Button("Roll", on_press=self.on_roll_press)
|
||||
unroll_button = urwid.Button("Unroll", on_press=self.on_unroll_press)
|
||||
|
||||
value_texts = list(self.stat_labels.values()) + [self.total_label]
|
||||
label_texts = [
|
||||
urwid.Text(f"{stat.value}: ") for stat in PRIME_STATS
|
||||
] + [urwid.Text("Total: ")]
|
||||
|
||||
super().__init__(
|
||||
[
|
||||
urwid.Columns(
|
||||
[
|
||||
(8, urwid.ListBox(label_texts)),
|
||||
urwid.ListBox(value_texts),
|
||||
]
|
||||
),
|
||||
urwid.Filler(roll_button),
|
||||
urwid.Filler(unroll_button),
|
||||
]
|
||||
)
|
||||
|
||||
def on_roll_press(self, _user_data: T.Any) -> None:
|
||||
self.player.stats = self.stats_builder.roll()
|
||||
self.update_values()
|
||||
|
||||
def on_unroll_press(self, _user_data: T.Any) -> None:
|
||||
try:
|
||||
self.player.stats = self.stats_builder.unroll()
|
||||
except IndexError:
|
||||
return
|
||||
self.update_values()
|
||||
|
||||
def update_values(self) -> None:
|
||||
for stat, edit in self.stat_labels.items():
|
||||
edit.set_text(str(self.player.stats[stat]))
|
||||
self.total_label.set_text(
|
||||
str(
|
||||
sum(
|
||||
self.player.stats[stat] for stat in self.stat_labels.keys()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class NewGameView(urwid.Pile):
|
||||
def __init__(self, on_confirm: T.Callable, on_cancel: T.Callable) -> None:
|
||||
self.on_confirm = on_confirm
|
||||
self.on_cancel = on_cancel
|
||||
|
||||
stats_builder = StatsBuilder()
|
||||
self.player = create_player(stats_builder)
|
||||
|
||||
race_checkboxes = []
|
||||
for race in RACES:
|
||||
urwid.RadioButton(
|
||||
group=race_checkboxes,
|
||||
label=race.name,
|
||||
state=race.name == self.player.race.name,
|
||||
)
|
||||
|
||||
class_checkboxes = []
|
||||
for class_ in CLASSES:
|
||||
urwid.RadioButton(
|
||||
group=class_checkboxes,
|
||||
label=class_.name,
|
||||
state=class_.name == self.player.class_.name,
|
||||
)
|
||||
|
||||
self.char_name_edit = urwid_readline.ReadlineEdit(
|
||||
"Name: ", self.player.name
|
||||
)
|
||||
generate_char_name_btn = urwid.Button(
|
||||
"?", on_press=self.on_generate_char_name_press
|
||||
)
|
||||
|
||||
urwid.connect_signal(
|
||||
self.char_name_edit, "postchange", self.on_char_name_change
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
[
|
||||
(
|
||||
1,
|
||||
urwid.Columns(
|
||||
[
|
||||
urwid.Filler(self.char_name_edit),
|
||||
urwid.Filler(generate_char_name_btn),
|
||||
]
|
||||
),
|
||||
),
|
||||
urwid.Columns(
|
||||
[
|
||||
urwid.LineBox(
|
||||
urwid.ListBox(race_checkboxes), title="Race"
|
||||
),
|
||||
urwid.LineBox(
|
||||
urwid.ListBox(class_checkboxes), title="Class"
|
||||
),
|
||||
urwid.LineBox(
|
||||
StatsGroupBox(self.player, stats_builder),
|
||||
title="Stats",
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
def generate_random_char_name(self) -> None:
|
||||
self.char_name_edit.edit_text = generate_name()
|
||||
self.char_name_edit.edit_pos = len(self.char_name_edit.edit_text)
|
||||
|
||||
def on_char_name_change(self, widget: urwid.Text, old_text: str) -> None:
|
||||
self.player.name = widget.edit_text
|
||||
|
||||
def on_generate_char_name_press(self, _user_data: T.Any) -> None:
|
||||
self.generate_random_char_name()
|
||||
|
||||
def unhandled_input(self, key: str) -> bool:
|
||||
if key == "esc":
|
||||
self.on_cancel()
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -0,0 +1,65 @@
|
||||
import typing as T
|
||||
|
||||
import urwid
|
||||
|
||||
from pqcli.game_state import Roster
|
||||
|
||||
|
||||
class RosterView(urwid.ListBox):
|
||||
def __init__(
|
||||
self,
|
||||
roster: Roster,
|
||||
loop: urwid.MainLoop,
|
||||
on_new_game: T.Callable,
|
||||
on_resume_game: T.Callable,
|
||||
on_exit: T.Callable,
|
||||
) -> None:
|
||||
self.roster = roster
|
||||
self.on_new_game = on_new_game
|
||||
self.on_resume_game = on_resume_game
|
||||
self.on_exit = on_exit
|
||||
|
||||
logo = urwid.BigText("ProgressQuest", urwid.HalfBlock5x4Font())
|
||||
|
||||
buttons = []
|
||||
for player in self.roster.players:
|
||||
buttons.append(
|
||||
urwid.Button(
|
||||
label=player.name,
|
||||
on_press=self.on_resume_game_press,
|
||||
user_data=player.name,
|
||||
)
|
||||
)
|
||||
|
||||
buttons.append(
|
||||
urwid.Button(
|
||||
label="Create a new character", on_press=self.on_new_game_press
|
||||
)
|
||||
)
|
||||
|
||||
buttons.append(urwid.Button(label="Exit", on_press=self.on_exit_press))
|
||||
|
||||
super().__init__(
|
||||
[
|
||||
urwid.Padding(logo, width="clip"),
|
||||
urwid.Divider(),
|
||||
urwid.Pile(buttons),
|
||||
]
|
||||
)
|
||||
|
||||
def on_resume_game_press(self, user_data: T.Any) -> None:
|
||||
player_name = T.cast(str, user_data)
|
||||
self.on_resume_game(player_name)
|
||||
|
||||
def on_new_game_press(self, _user_data: T.Any) -> None:
|
||||
self.on_new_game()
|
||||
|
||||
def on_exit_press(self, _user_data: T.Any) -> None:
|
||||
self.on_exit()
|
||||
|
||||
def unhandled_input(self, key: str) -> bool:
|
||||
if key == "esc":
|
||||
self.on_exit()
|
||||
return True
|
||||
|
||||
return False
|
||||
Reference in New Issue
Block a user