ui: sketch game view

This commit is contained in:
rr-
2018-11-05 21:04:39 +01:00
parent ad59b4db2b
commit 2dbbdbe708
9 changed files with 185 additions and 28 deletions
+35 -7
View File
@@ -4,6 +4,8 @@ import logging
import typing as T import typing as T
from dataclasses import dataclass from dataclasses import dataclass
import urwid
from pqcli import random from pqcli import random
from pqcli.config import * from pqcli.config import *
from pqcli.lingo import * from pqcli.lingo import *
@@ -11,19 +13,37 @@ from pqcli.lingo import *
logger = logging.getLogger(__name__) 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: def level_up_time(level: int) -> int:
# seconds # seconds
return 20 * level * 60 return 20 * level * 60
class Bar: class Bar(SignalMixin):
signals = ["change"]
def __init__(self, max_: int, position: float = 0.0) -> None: def __init__(self, max_: int, position: float = 0.0) -> None:
self.position = position self.position = position
self.max_ = max_ self.max_ = max_
self.emit("change")
def reset(self, new_max: int, position: float = 0.0) -> None: def reset(self, new_max: int, position: float = 0.0) -> None:
self.max_ = new_max self.max_ = new_max
self.position = position self.position = position
self.emit("change")
def increment(self, inc: float) -> None: def increment(self, inc: float) -> None:
self.reposition(self.position + inc) self.reposition(self.position + inc)
@@ -34,9 +54,12 @@ class Bar:
def reposition(self, new_pos: float) -> None: def reposition(self, new_pos: float) -> None:
self.position = float(min(new_pos, self.max_)) 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: def __init__(self, values: T.Dict[StatType, int]) -> None:
self.values = values self.values = values
@@ -49,6 +72,7 @@ class Stats:
def increment(self, stat: StatType, qty: int = 1) -> None: def increment(self, stat: StatType, qty: int = 1) -> None:
self.values[stat] += qty self.values[stat] += qty
logger.info("Increased %s to %d", stat.value, self[stat]) logger.info("Increased %s to %d", stat.value, self[stat])
self.emit("change")
class QuestBook: class QuestBook:
@@ -184,7 +208,9 @@ class PlotTask(RegularTask):
pass pass
class Player: class Player(SignalMixin):
signals = ["new_task", "level_up"]
def __init__( def __init__(
self, self,
name: str, name: str,
@@ -244,6 +270,7 @@ class Player:
self.task = task self.task = task
self.task_bar.reset(task.duration) self.task_bar.reset(task.duration)
logger.info("%s...", task.description) logger.info("%s...", task.description)
self.emit("new_task")
def equip_price(self) -> int: def equip_price(self) -> int:
return 5 * (self.level ** 2) + 10 * self.level + 20 return 5 * (self.level ** 2) + 10 * self.level + 20
@@ -263,6 +290,7 @@ class Player:
self.win_stat() self.win_stat()
self.win_spell() self.win_spell()
self.exp_bar.reset(level_up_time(self.level)) self.exp_bar.reset(level_up_time(self.level))
self.emit("level_up")
def win_stat(self) -> bool: def win_stat(self) -> bool:
chosen_stat: T.Optional[StatType] = None chosen_stat: T.Optional[StatType] = None
@@ -346,12 +374,12 @@ class Simulation:
def __init__(self, player: Player) -> None: def __init__(self, player: Player) -> None:
self.player = player self.player = player
self.last_tick = datetime.datetime.now() 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: if not self.player.task_bar.done:
self.elapsed = 100 self.elapsed += elapsed
self.player.task_bar.increment(100) self.player.task_bar.increment(elapsed)
return return
# gain XP / level up # gain XP / level up
+6 -1
View File
@@ -6,6 +6,7 @@ from pqcli import random
from pqcli.mechanic import Player from pqcli.mechanic import Player
from pqcli.roster import Roster from pqcli.roster import Roster
from pqcli.ui.confirm_dialog import ConfirmDialog 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.new_game_view import NewGameView
from pqcli.ui.roster_view import RosterView 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-focus", "light red", "black"),
("linebox-content", "", ""), ("linebox-content", "", ""),
("linebox-content-focus", "", ""), ("linebox-content-focus", "", ""),
("progressbar-done", "black", "light red"),
("progressbar-incomplete", "", ""),
] ]
@@ -93,7 +96,9 @@ class Ui:
self.switch_to_roster_view() self.switch_to_roster_view()
def switch_to_game_view(self, player_idx: int) -> None: 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: def switch_to_delete_player_view(self, player_idx: int) -> None:
adjective = random.choice(["faithful", "noble", "loyal", "brave"]) adjective = random.choice(["faithful", "noble", "loyal", "brave"])
+3 -3
View File
@@ -2,7 +2,7 @@ import typing as T
import urwid import urwid
from pqcli.ui.button import MenuButton from pqcli.ui.custom_button import CustomButton
class ConfirmDialog(urwid.Overlay): class ConfirmDialog(urwid.Overlay):
@@ -10,10 +10,10 @@ class ConfirmDialog(urwid.Overlay):
def __init__(self, label: str, parent: urwid.Widget) -> None: def __init__(self, label: str, parent: urwid.Widget) -> None:
question = urwid.Text(("bold", label), "center") question = urwid.Text(("bold", label), "center")
yes_btn = MenuButton( yes_btn = CustomButton(
"Yes", hint="Y", on_press=lambda _user_data: self.confirm() "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() "No", hint="N", on_press=lambda _user_data: self.cancel()
) )
@@ -3,7 +3,7 @@ import typing as T
import urwid import urwid
class MenuButton(urwid.Button): class CustomButton(urwid.Button):
def __init__( def __init__(
self, self,
label: str, label: str,
@@ -3,7 +3,7 @@ import typing as T
import urwid import urwid
class LineBox(urwid.AttrWrap): class CustomLineBox(urwid.AttrWrap):
def __init__( def __init__(
self, widget: urwid.Widget, *args: T.Any, **kwargs: T.Any self, widget: urwid.Widget, *args: T.Any, **kwargs: T.Any
) -> None: ) -> None:
+9
View File
@@ -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
+115
View File
@@ -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
View File
@@ -6,11 +6,11 @@ import urwid_readline
from pqcli.config import CLASSES, PRIME_STATS, RACES from pqcli.config import CLASSES, PRIME_STATS, RACES
from pqcli.mechanic import StatsBuilder, create_player, generate_name from pqcli.mechanic import StatsBuilder, create_player, generate_name
from pqcli.ui.button import MenuButton from pqcli.ui.custom_button import CustomButton
from pqcli.ui.line_box import LineBox from pqcli.ui.custom_line_box import CustomLineBox
class StatsBox(LineBox): class StatsBox(CustomLineBox):
def __init__(self) -> None: def __init__(self) -> None:
self.stats_builder = StatsBuilder() self.stats_builder = StatsBuilder()
self.stats = self.stats_builder.roll() self.stats = self.stats_builder.roll()
@@ -18,10 +18,10 @@ class StatsBox(LineBox):
self.total_label = urwid.Text("0") self.total_label = urwid.Text("0")
self.update_values() self.update_values()
roll_button = MenuButton( roll_button = CustomButton(
"Roll", hint="F5", on_press=self.on_roll_press "Roll", hint="F5", on_press=self.on_roll_press
) )
unroll_button = MenuButton( unroll_button = CustomButton(
"Unroll", hint="F6", on_press=self.on_unroll_press "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: def __init__(self) -> None:
self.race = random.choice(RACES) self.race = random.choice(RACES)
@@ -92,7 +92,7 @@ class RaceBox(LineBox):
self.race = user_data self.race = user_data
class ClassBox(LineBox): class ClassBox(CustomLineBox):
def __init__(self) -> None: def __init__(self) -> None:
self.class_ = random.choice(CLASSES) self.class_ = random.choice(CLASSES)
@@ -126,7 +126,7 @@ class NewGameView(urwid.Pile):
self.char_name_edit = urwid_readline.ReadlineEdit( self.char_name_edit = urwid_readline.ReadlineEdit(
"Name: ", generate_name() "Name: ", generate_name()
) )
generate_char_name_btn = MenuButton( generate_char_name_btn = CustomButton(
"Generate random name", "Generate random name",
hint="F7", hint="F7",
on_press=self.on_generate_char_name_press, on_press=self.on_generate_char_name_press,
@@ -136,10 +136,10 @@ class NewGameView(urwid.Pile):
urwid.Padding( urwid.Padding(
urwid.Pile( urwid.Pile(
[ [
MenuButton( CustomButton(
"Sold!", hint="F10", on_press=self.on_confirm_press "Sold!", hint="F10", on_press=self.on_confirm_press
), ),
MenuButton( CustomButton(
"Cancel", hint="Esc", on_press=self.on_cancel_press "Cancel", hint="Esc", on_press=self.on_cancel_press
), ),
] ]
+5 -5
View File
@@ -3,7 +3,7 @@ import typing as T
import urwid import urwid
from pqcli.roster import Roster from pqcli.roster import Roster
from pqcli.ui.button import MenuButton from pqcli.ui.custom_button import CustomButton
class RosterView(urwid.Filler): class RosterView(urwid.Filler):
@@ -19,14 +19,14 @@ class RosterView(urwid.Filler):
buttons.append( buttons.append(
urwid.Columns( urwid.Columns(
[ [
MenuButton( CustomButton(
label=player.name, label=player.name,
on_press=self.on_resume_game_press, on_press=self.on_resume_game_press,
user_data=player_idx, user_data=player_idx,
), ),
( (
10, 10,
MenuButton( CustomButton(
label="Delete", label="Delete",
on_press=self.on_delete_game_press, on_press=self.on_delete_game_press,
user_data=player_idx, user_data=player_idx,
@@ -37,7 +37,7 @@ class RosterView(urwid.Filler):
) )
buttons.append( buttons.append(
MenuButton( CustomButton(
label="Create a new character", label="Create a new character",
hint="F1", hint="F1",
on_press=self.on_new_game_press, on_press=self.on_new_game_press,
@@ -45,7 +45,7 @@ class RosterView(urwid.Filler):
) )
buttons.append( 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__( super().__init__(