Skip to content

Commit

Permalink
migrate project from private repo (#7)
Browse files Browse the repository at this point in the history
* add osrlib, osrgame and pyproject.toml files

* migrate from public repo

* status update + fix Py version

* terminate bullets
  • Loading branch information
mmacy committed Oct 16, 2023
1 parent 5c4282c commit 54ee7b9
Show file tree
Hide file tree
Showing 36 changed files with 5,956 additions and 13 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,4 @@ cython_debug/

# Miscellaneous
.DS_Store
test_db.json
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
27 changes: 16 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,44 @@ OSR Console is a turn-based dungeon crawler RPG in the Old-School Renaissance (O

## Project status

As of 14 OCT 2023,development has been in progress for about six weeks.
Character creation isn't quite in, and there are no monsters or spells or combat or anything fun like that. This isn't yet a game you can actually play; more like early-stage hackin' stuff together-type stuff.

👉 = In progress

- [x] Build partial proof-of-concept in private repo.
- [x] Create public repo (this one).
- [x] Init `mkdocs` project (sans actual docs).
- [ ] 👉 Move `osrlib`, `osr_ui`, and `tests` projects to public repo.
- [x] Move `osrlib`, `osrgame`, and `tests` projects to this repo.
- [x] Move to [Poetry](https://python-poetry.org/).
- [ ] 👉 Character save/load in [TinyDB](https://tinydb.readthedocs.io/).
- [ ] ...and everything else it takes to make a turn-based RPG.
- [ ] Party and character creation workflow in UI.
- [ ] ...and everything else you need for a turn-based dungeon crawler fantasty RPG.

## Prerequisites

- Python 3.10+
- Poetry
- Python 3.11+
- Poetry 1.6+

## Installation

TBD
This is a monorepo housing two projects. The game's library, `osrlib`, and its user interface, `osrgame`. For more information about each, see their respective `README.md` files.

- [osrgame: Textual TUI for an OSR-style turn-based RPG](osrgame/README.md)
- [osrlib: Python library for OSR-style turn-based RPGs](osrlib/README.md)

## Usage

TBD
TODO

## Contribute

TBD
TODO

## License

[MIT License](LICENSE)
[MIT License](LICENSE) for now.

## Credits

- Project owner: @mmacy
- Game rules and mechanics inspired by the TSR's 1981 versions of the Dungeons & Dragons Basic and Expert sets, or *D&D B/X*.
- Project owner: [@mmacy](https://github.com/mmacy)
- Game rules and mechanics heavily inspired by the TSR's 1981 versions of the Dungeons & Dragons Basic and Expert sets, or *D&D B/X*.
28 changes: 28 additions & 0 deletions osrgame/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# osrgame: Textual TUI for an OSR-style turn-based RPG

The `osrgame` package provides user interface for the OSR Console application--it's the thing you run to play the game.

## Prerequisites

- Python 3.11+
- Poetry 1.6+

## Installation

Install the application's dependencies and its virtual environment by using Poetry. This command will create a Python virtual environment and install `osrlib` and other dependencies for you.

```sh
# Run from within the <repo-root>/osrgame directory
poetry install
```

## Usage

Launch the OSR Console game application by using Poetry:

```sh
# Run from within the <repo-root>/osrgame directory (same dir as install command)
poetry run python ./osrgame/osrgame.py
```

By starting the game with `poetry run`, you don't have to worry about manually entering a virtual environment because Poetry handles it for you.
3 changes: 3 additions & 0 deletions osrgame/osrgame/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .osrgame import OSRConsole
from .screen import CharacterScreen, WelcomeScreen, ModuleScreen, ExploreScreen
from .widgets import CharacterScreenButtons, CharacterStats, AbilityTable, ItemTable, SavingThrows
57 changes: 57 additions & 0 deletions osrgame/osrgame/osrgame.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from textual.app import App, ComposeResult

from osrlib import CharacterClassType, PlayerCharacter, Armor, Item, Weapon
from screen import CharacterScreen, WelcomeScreen


class OSRConsole(App):
CSS_PATH = "screen.tcss"

BINDINGS = [
("c", "character", "Character"),
("q", "quit", "Quit"),
]

player_character = PlayerCharacter(
"Sckricko", CharacterClassType.FIGHTER
)
armor = Armor("Chain Mail", -5, usable_by_classes = {CharacterClassType.FIGHTER}, max_equipped = 2, gp_value = 40)
shield = Armor("Shield", -1, usable_by_classes = {CharacterClassType.FIGHTER}, max_equipped = 2, gp_value = 10)
sword = Weapon("Sword", "1d8", usable_by_classes = {CharacterClassType.FIGHTER}, max_equipped = 1, gp_value = 10)
player_character.add_item_to_inventory(armor)
player_character.add_item_to_inventory(shield)
player_character.add_item_to_inventory(sword)
player_character.inventory.equip_item(armor)
player_character.inventory.equip_item(shield)
player_character.inventory.equip_item(sword)

backpack = Item("Backpack", gp_value = 5)
wineskin = Item("Wineskin", gp_value = 1)
iron_rations = Item("Iron Rations", gp_value = 15)
torch = Item("Torch", gp_value = 1)
player_character.add_item_to_inventory(backpack)
player_character.add_item_to_inventory(wineskin)
player_character.add_item_to_inventory(iron_rations)
player_character.add_item_to_inventory(torch)

SCREENS = {"scr_character": CharacterScreen()}

def compose(self) -> ComposeResult:
yield WelcomeScreen()

def on_mount(self) -> None:
self.title = "OSR Console"
self.sub_title = "Adventures in turn-based text"

def action_character(self) -> None:
"""Show the character screen."""
self.push_screen("scr_character")

def action_quit(self) -> None:
"""Quit the application."""
self.exit()


app = OSRConsole()
if __name__ == "__main__":
app.run()
230 changes: 230 additions & 0 deletions osrgame/osrgame/screen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
from textual.app import ComposeResult
from textual.containers import Container
from textual.screen import Screen
from textual.widgets import Button, Header, Footer, Log, Placeholder

from osrlib import CharacterClassType, Armor, Weapon
from widgets import CharacterStats, AbilityTable, ItemTable, SavingThrows, CharacterScreenButtons


####################
# Welcome Screen
class WelcomeScreen(Screen):
BINDINGS = [
("escape", "app.pop_screen", "Pop screen"),
("q", "quit", "Quit"),
]

def compose(self) -> ComposeResult:
yield Header(show_clock=True, id="header")

yield Footer()

def on_mount(self) -> None:
pass

def on_button_pressed(self, event: Button.Pressed) -> None:
pass

def action_quit(self) -> None:
"""An action to quit the application."""
self.exit()


####################
# Main Screen
class MainScreen(Screen):
BINDINGS = [
("escape", "app.pop_screen", "Pop screen"),
("q", "quit", "Quit"),
]

def compose(self) -> ComposeResult:
yield Header(show_clock=True, id="header")
yield Placeholder("Main")
yield Footer()

def on_mount(self) -> None:
pass

def on_button_pressed(self, event: Button.Pressed) -> None:
pass

def action_quit(self) -> None:
"""An action to quit the application."""
self.exit()


####################
# Character Screen


class CharacterScreen(Screen):
BINDINGS = [
("k", "clear_log", "Clear log"),
("escape", "app.pop_screen", "Pop screen"),
]

def compose(self) -> ComposeResult:
yield Header(id="header", show_clock=True, classes="header-footer")
yield CharacterStats(id="stat-block", classes="box")
yield Log(id="log", auto_scroll=True, classes="box")
yield AbilityTable(id="ability-block")
yield SavingThrows(id="saving-throw-block")
yield ItemTable(id="item-block", classes="box")
yield CharacterScreenButtons(id="char-buttons", classes="char-buttons-class")
yield Footer()

def on_mount(self) -> None:
"""Perform actions when the widget is mounted."""
self.query_one(Log).border_subtitle = "LOG"
self.query_one(CharacterStats).pc_name = self.app.player_character.name
self.query_one(CharacterStats).pc_class = self.app.player_character.character_class
self.query_one(CharacterStats).pc_level = self.app.player_character.character_class.current_level
self.query_one(CharacterStats).pc_hp = self.app.player_character.character_class.hp
self.query_one(CharacterStats).pc_ac = self.app.player_character.armor_class
self.query_one(AbilityTable).update_table()
self.query_one(SavingThrows).update_table()
self.query_one(ItemTable).items = self.app.player_character.inventory.all_items

def on_button_pressed(self, event: Button.Pressed) -> None:
pc = self.app.player_character
if event.button.id == "btn_roll_abilities":
self.reroll()
self.query_one(CharacterStats).pc_ac = pc.armor_class

elif event.button.id == "btn_roll_hp":
hp_roll = pc.roll_hp()
pc.character_class.hp = max(hp_roll.total_with_modifier, 1)
roll_string = hp_roll.pretty_print()
self.query_one(Log).write_line(roll_string)
self.query_one(CharacterStats).pc_hp = pc.character_class.hp

def action_clear_log(self) -> None:
"""An action to clear the log."""
self.query_one(Log).clear()

def reroll(self):
"""Rolls the ability scores of the active character."""
self.app.player_character.roll_abilities()
self.query_one(AbilityTable).update_table()


####################
# Party Screen
class PartyScreen(Screen):
BINDINGS = [
("escape", "app.pop_screen", "Pop screen"),
("q", "quit", "Quit"),
]

def compose(self) -> ComposeResult:
yield Header(show_clock=True, id="header")
yield Placeholder("Party Manager")
yield Footer()

def on_mount(self) -> None:
pass

def on_button_pressed(self, event: Button.Pressed) -> None:
pass

def action_quit(self) -> None:
"""An action to quit the application."""
self.exit()


####################
# Module Screen
class ModuleScreen(Screen):
BINDINGS = [
("escape", "app.pop_screen", "Pop screen"),
("q", "quit", "Quit"),
]

def compose(self) -> ComposeResult:
yield Header(show_clock=True, id="header")
yield Placeholder("Module Manager")
yield Footer()

def on_mount(self) -> None:
pass

def on_button_pressed(self, event: Button.Pressed) -> None:
pass

def action_quit(self) -> None:
"""An action to quit the application."""
self.exit()


####################
# Explore Screen
class ExploreScreen(Screen):
BINDINGS = [
("escape", "app.pop_screen", "Pop screen"),
("q", "quit", "Quit"),
]

def compose(self) -> ComposeResult:
yield Header(show_clock=True, id="header")
yield Placeholder("Explore")
yield Footer()

def on_mount(self) -> None:
pass

def on_button_pressed(self, event: Button.Pressed) -> None:
pass

def action_quit(self) -> None:
"""An action to quit the application."""
self.exit()


####################
# Combat Screen
class CombatScreen(Screen):
BINDINGS = [
("escape", "app.pop_screen", "Pop screen"),
("q", "quit", "Quit"),
]

def compose(self) -> ComposeResult:
yield Header(show_clock=True, id="header")
yield Placeholder("Adventure")
yield Footer()

def on_mount(self) -> None:
pass

def on_button_pressed(self, event: Button.Pressed) -> None:
pass

def action_quit(self) -> None:
"""An action to quit the application."""
self.exit()


####################
# Exit Screen
class ExitScreen(Screen):
BINDINGS = [
("escape", "app.pop_screen", "Pop screen"),
("q", "quit", "Quit"),
]

def compose(self) -> ComposeResult:
yield Header(show_clock=True, id="header")
yield Placeholder("Goodbye")
yield Footer()

def on_mount(self) -> None:
pass

def on_button_pressed(self, event: Button.Pressed) -> None:
pass

def action_quit(self) -> None:
"""An action to quit the application."""
self.exit()
Loading

0 comments on commit 54ee7b9

Please sign in to comment.