mirror of
https://github.com/rr-/pq-cli.git
synced 2026-09-28 22:33:59 -06:00
ui: sketch game view
This commit is contained in:
+35
-7
@@ -4,6 +4,8 @@ import logging
|
||||
import typing as T
|
||||
from dataclasses import dataclass
|
||||
|
||||
import urwid
|
||||
|
||||
from pqcli import random
|
||||
from pqcli.config import *
|
||||
from pqcli.lingo import *
|
||||
@@ -11,19 +13,37 @@ from pqcli.lingo import *
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SignalMixin:
|
||||
def __init_subclass__(cls: T.Any, **kwargs: T.Any) -> None:
|
||||
urwid.signals.register_signal(cls, cls.signals)
|
||||
super().__init_subclass__(**kwargs)
|
||||
|
||||
def emit(self, signal_name: str, user_data: T.Any = None) -> None:
|
||||
urwid.signals.emit_signal(self, signal_name, user_data)
|
||||
|
||||
def connect(self, signal_name: str, callback: T.Callable) -> None:
|
||||
urwid.signals.connect_signal(
|
||||
self, signal_name, lambda _sender, *args: callback(*args)
|
||||
)
|
||||
|
||||
|
||||
def level_up_time(level: int) -> int:
|
||||
# seconds
|
||||
return 20 * level * 60
|
||||
|
||||
|
||||
class Bar:
|
||||
class Bar(SignalMixin):
|
||||
signals = ["change"]
|
||||
|
||||
def __init__(self, max_: int, position: float = 0.0) -> None:
|
||||
self.position = position
|
||||
self.max_ = max_
|
||||
self.emit("change")
|
||||
|
||||
def reset(self, new_max: int, position: float = 0.0) -> None:
|
||||
self.max_ = new_max
|
||||
self.position = position
|
||||
self.emit("change")
|
||||
|
||||
def increment(self, inc: float) -> None:
|
||||
self.reposition(self.position + inc)
|
||||
@@ -34,9 +54,12 @@ class Bar:
|
||||
|
||||
def reposition(self, new_pos: float) -> None:
|
||||
self.position = float(min(new_pos, self.max_))
|
||||
self.emit("change")
|
||||
|
||||
|
||||
class Stats:
|
||||
class Stats(SignalMixin):
|
||||
signals = ["change"]
|
||||
|
||||
def __init__(self, values: T.Dict[StatType, int]) -> None:
|
||||
self.values = values
|
||||
|
||||
@@ -49,6 +72,7 @@ class Stats:
|
||||
def increment(self, stat: StatType, qty: int = 1) -> None:
|
||||
self.values[stat] += qty
|
||||
logger.info("Increased %s to %d", stat.value, self[stat])
|
||||
self.emit("change")
|
||||
|
||||
|
||||
class QuestBook:
|
||||
@@ -184,7 +208,9 @@ class PlotTask(RegularTask):
|
||||
pass
|
||||
|
||||
|
||||
class Player:
|
||||
class Player(SignalMixin):
|
||||
signals = ["new_task", "level_up"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
@@ -244,6 +270,7 @@ class Player:
|
||||
self.task = task
|
||||
self.task_bar.reset(task.duration)
|
||||
logger.info("%s...", task.description)
|
||||
self.emit("new_task")
|
||||
|
||||
def equip_price(self) -> int:
|
||||
return 5 * (self.level ** 2) + 10 * self.level + 20
|
||||
@@ -263,6 +290,7 @@ class Player:
|
||||
self.win_stat()
|
||||
self.win_spell()
|
||||
self.exp_bar.reset(level_up_time(self.level))
|
||||
self.emit("level_up")
|
||||
|
||||
def win_stat(self) -> bool:
|
||||
chosen_stat: T.Optional[StatType] = None
|
||||
@@ -346,12 +374,12 @@ class Simulation:
|
||||
def __init__(self, player: Player) -> None:
|
||||
self.player = player
|
||||
self.last_tick = datetime.datetime.now()
|
||||
self.elapsed = 0
|
||||
self.elapsed = 0.0
|
||||
|
||||
def tick(self) -> None:
|
||||
def tick(self, elapsed: float = 100.0) -> None:
|
||||
if not self.player.task_bar.done:
|
||||
self.elapsed = 100
|
||||
self.player.task_bar.increment(100)
|
||||
self.elapsed += elapsed
|
||||
self.player.task_bar.increment(elapsed)
|
||||
return
|
||||
|
||||
# gain XP / level up
|
||||
|
||||
@@ -6,6 +6,7 @@ from pqcli import random
|
||||
from pqcli.mechanic import Player
|
||||
from pqcli.roster import Roster
|
||||
from pqcli.ui.confirm_dialog import ConfirmDialog
|
||||
from pqcli.ui.game_view import GameView
|
||||
from pqcli.ui.new_game_view import NewGameView
|
||||
from pqcli.ui.roster_view import RosterView
|
||||
|
||||
@@ -17,6 +18,8 @@ PALETTE: T.List[T.Tuple[str, str, str]] = [
|
||||
("linebox-focus", "light red", "black"),
|
||||
("linebox-content", "", ""),
|
||||
("linebox-content-focus", "", ""),
|
||||
("progressbar-done", "black", "light red"),
|
||||
("progressbar-incomplete", "", ""),
|
||||
]
|
||||
|
||||
|
||||
@@ -93,7 +96,9 @@ class Ui:
|
||||
self.switch_to_roster_view()
|
||||
|
||||
def switch_to_game_view(self, player_idx: int) -> None:
|
||||
raise NotImplementedError("not implemented")
|
||||
player = self.roster.players[player_idx]
|
||||
self.loop.widget = GameView(self.loop, player)
|
||||
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"])
|
||||
|
||||
@@ -2,7 +2,7 @@ import typing as T
|
||||
|
||||
import urwid
|
||||
|
||||
from pqcli.ui.button import MenuButton
|
||||
from pqcli.ui.custom_button import CustomButton
|
||||
|
||||
|
||||
class ConfirmDialog(urwid.Overlay):
|
||||
@@ -10,10 +10,10 @@ class ConfirmDialog(urwid.Overlay):
|
||||
|
||||
def __init__(self, label: str, parent: urwid.Widget) -> None:
|
||||
question = urwid.Text(("bold", label), "center")
|
||||
yes_btn = MenuButton(
|
||||
yes_btn = CustomButton(
|
||||
"Yes", hint="Y", on_press=lambda _user_data: self.confirm()
|
||||
)
|
||||
no_btn = MenuButton(
|
||||
no_btn = CustomButton(
|
||||
"No", hint="N", on_press=lambda _user_data: self.cancel()
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import typing as T
|
||||
import urwid
|
||||
|
||||
|
||||
class MenuButton(urwid.Button):
|
||||
class CustomButton(urwid.Button):
|
||||
def __init__(
|
||||
self,
|
||||
label: str,
|
||||
@@ -3,7 +3,7 @@ import typing as T
|
||||
import urwid
|
||||
|
||||
|
||||
class LineBox(urwid.AttrWrap):
|
||||
class CustomLineBox(urwid.AttrWrap):
|
||||
def __init__(
|
||||
self, widget: urwid.Widget, *args: T.Any, **kwargs: T.Any
|
||||
) -> None:
|
||||
@@ -0,0 +1,9 @@
|
||||
import urwid
|
||||
|
||||
|
||||
class CustomProgressBar(urwid.ProgressBar):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("progressbar-incomplete", "progressbar-done", 0, 1)
|
||||
|
||||
def set_max(self, value: int) -> None:
|
||||
self._done = value
|
||||
@@ -0,0 +1,115 @@
|
||||
import typing as T
|
||||
|
||||
import urwid
|
||||
|
||||
from pqcli.config import StatType
|
||||
from pqcli.mechanic import Player, Simulation
|
||||
from pqcli.ui.custom_line_box import CustomLineBox
|
||||
from pqcli.ui.custom_progress_bar import CustomProgressBar
|
||||
|
||||
|
||||
class CharacterSheetView(CustomLineBox):
|
||||
def __init__(self, player: Player) -> None:
|
||||
self.player = player
|
||||
|
||||
self.level_text = urwid.Text("")
|
||||
self.stat_texts = {stat: urwid.Text("") for stat in list(StatType)}
|
||||
|
||||
self.list_box = urwid.ListBox(
|
||||
[
|
||||
urwid.Columns([urwid.Text("Name"), urwid.Text(player.name)]),
|
||||
urwid.Columns(
|
||||
[urwid.Text("Race"), urwid.Text(player.race.name)]
|
||||
),
|
||||
urwid.Columns(
|
||||
[urwid.Text("Class"), urwid.Text(player.class_.name)]
|
||||
),
|
||||
urwid.Columns([urwid.Text("Level"), self.level_text]),
|
||||
urwid.Divider(),
|
||||
]
|
||||
+ [
|
||||
urwid.Columns([urwid.Text(stat.value), self.stat_texts[stat]])
|
||||
for stat in StatType
|
||||
]
|
||||
)
|
||||
|
||||
self.player.connect("level_up", self.sync_level)
|
||||
self.player.stats.connect("change", self.sync_stats)
|
||||
self.sync_level()
|
||||
self.sync_stats()
|
||||
|
||||
super().__init__(self.list_box, title="Character Sheet")
|
||||
|
||||
def sync_level(self) -> None:
|
||||
self.level_text.set_text(str(self.player.level))
|
||||
|
||||
def sync_stats(self) -> None:
|
||||
for stat in StatType:
|
||||
self.stat_texts[stat].set_text(str(self.player.stats[stat]))
|
||||
|
||||
|
||||
class TaskView(urwid.Pile):
|
||||
def __init__(self, player: Player) -> None:
|
||||
self.player = player
|
||||
|
||||
self.current_task_text = urwid.Text("...")
|
||||
self.current_task_bar = CustomProgressBar()
|
||||
|
||||
self.player.task_bar.connect("change", self.sync_position)
|
||||
self.player.connect("new_task", self.sync_task_name)
|
||||
self.sync_position()
|
||||
self.sync_task_name()
|
||||
|
||||
super().__init__([self.current_task_text, self.current_task_bar])
|
||||
|
||||
def sync_position(self) -> None:
|
||||
self.current_task_bar.set_completion(self.player.task_bar.position)
|
||||
self.current_task_bar.set_max(self.player.task_bar.max_)
|
||||
|
||||
def sync_task_name(self) -> None:
|
||||
self.current_task_text.set_text(
|
||||
f"{self.player.task.description}..." if self.player.task else "?"
|
||||
)
|
||||
|
||||
def pack(self, size: T.Tuple[int, int]) -> T.Tuple[int, int]:
|
||||
return (size[0], 2)
|
||||
|
||||
|
||||
class GameView(urwid.Pile):
|
||||
signals = ["cancel"]
|
||||
|
||||
def __init__(self, loop: urwid.MainLoop, player: Player) -> None:
|
||||
self.loop = loop
|
||||
self.player = player
|
||||
self.simulation = Simulation(player)
|
||||
|
||||
self.character_sheet_view = CharacterSheetView(player)
|
||||
self.task_view = TaskView(player)
|
||||
|
||||
super().__init__(
|
||||
[
|
||||
urwid.Columns(
|
||||
[
|
||||
urwid.Pile([self.character_sheet_view]),
|
||||
]
|
||||
),
|
||||
("pack", self.task_view),
|
||||
]
|
||||
)
|
||||
|
||||
self.tick()
|
||||
|
||||
def cancel(self) -> None:
|
||||
self._emit("cancel")
|
||||
|
||||
def tick(self) -> None:
|
||||
elapsed = 100 # TODO
|
||||
self.simulation.tick(elapsed)
|
||||
self.loop.set_alarm_in(0.1, lambda _loop, _user_data: self.tick())
|
||||
|
||||
def unhandled_input(self, key: str) -> bool:
|
||||
if key == "esc":
|
||||
self.cancel()
|
||||
return True
|
||||
|
||||
return False
|
||||
+10
-10
@@ -6,11 +6,11 @@ import urwid_readline
|
||||
|
||||
from pqcli.config import CLASSES, PRIME_STATS, RACES
|
||||
from pqcli.mechanic import StatsBuilder, create_player, generate_name
|
||||
from pqcli.ui.button import MenuButton
|
||||
from pqcli.ui.line_box import LineBox
|
||||
from pqcli.ui.custom_button import CustomButton
|
||||
from pqcli.ui.custom_line_box import CustomLineBox
|
||||
|
||||
|
||||
class StatsBox(LineBox):
|
||||
class StatsBox(CustomLineBox):
|
||||
def __init__(self) -> None:
|
||||
self.stats_builder = StatsBuilder()
|
||||
self.stats = self.stats_builder.roll()
|
||||
@@ -18,10 +18,10 @@ class StatsBox(LineBox):
|
||||
self.total_label = urwid.Text("0")
|
||||
self.update_values()
|
||||
|
||||
roll_button = MenuButton(
|
||||
roll_button = CustomButton(
|
||||
"Roll", hint="F5", on_press=self.on_roll_press
|
||||
)
|
||||
unroll_button = MenuButton(
|
||||
unroll_button = CustomButton(
|
||||
"Unroll", hint="F6", on_press=self.on_unroll_press
|
||||
)
|
||||
|
||||
@@ -69,7 +69,7 @@ class StatsBox(LineBox):
|
||||
)
|
||||
|
||||
|
||||
class RaceBox(LineBox):
|
||||
class RaceBox(CustomLineBox):
|
||||
def __init__(self) -> None:
|
||||
self.race = random.choice(RACES)
|
||||
|
||||
@@ -92,7 +92,7 @@ class RaceBox(LineBox):
|
||||
self.race = user_data
|
||||
|
||||
|
||||
class ClassBox(LineBox):
|
||||
class ClassBox(CustomLineBox):
|
||||
def __init__(self) -> None:
|
||||
self.class_ = random.choice(CLASSES)
|
||||
|
||||
@@ -126,7 +126,7 @@ class NewGameView(urwid.Pile):
|
||||
self.char_name_edit = urwid_readline.ReadlineEdit(
|
||||
"Name: ", generate_name()
|
||||
)
|
||||
generate_char_name_btn = MenuButton(
|
||||
generate_char_name_btn = CustomButton(
|
||||
"Generate random name",
|
||||
hint="F7",
|
||||
on_press=self.on_generate_char_name_press,
|
||||
@@ -136,10 +136,10 @@ class NewGameView(urwid.Pile):
|
||||
urwid.Padding(
|
||||
urwid.Pile(
|
||||
[
|
||||
MenuButton(
|
||||
CustomButton(
|
||||
"Sold!", hint="F10", on_press=self.on_confirm_press
|
||||
),
|
||||
MenuButton(
|
||||
CustomButton(
|
||||
"Cancel", hint="Esc", on_press=self.on_cancel_press
|
||||
),
|
||||
]
|
||||
|
||||
@@ -3,7 +3,7 @@ import typing as T
|
||||
import urwid
|
||||
|
||||
from pqcli.roster import Roster
|
||||
from pqcli.ui.button import MenuButton
|
||||
from pqcli.ui.custom_button import CustomButton
|
||||
|
||||
|
||||
class RosterView(urwid.Filler):
|
||||
@@ -19,14 +19,14 @@ class RosterView(urwid.Filler):
|
||||
buttons.append(
|
||||
urwid.Columns(
|
||||
[
|
||||
MenuButton(
|
||||
CustomButton(
|
||||
label=player.name,
|
||||
on_press=self.on_resume_game_press,
|
||||
user_data=player_idx,
|
||||
),
|
||||
(
|
||||
10,
|
||||
MenuButton(
|
||||
CustomButton(
|
||||
label="Delete",
|
||||
on_press=self.on_delete_game_press,
|
||||
user_data=player_idx,
|
||||
@@ -37,7 +37,7 @@ class RosterView(urwid.Filler):
|
||||
)
|
||||
|
||||
buttons.append(
|
||||
MenuButton(
|
||||
CustomButton(
|
||||
label="Create a new character",
|
||||
hint="F1",
|
||||
on_press=self.on_new_game_press,
|
||||
@@ -45,7 +45,7 @@ class RosterView(urwid.Filler):
|
||||
)
|
||||
|
||||
buttons.append(
|
||||
MenuButton(label="Exit", hint="Esc", on_press=self.on_exit_press)
|
||||
CustomButton(label="Exit", hint="Esc", on_press=self.on_exit_press)
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
|
||||
Reference in New Issue
Block a user