diff --git a/opencode/AGENTS.md b/opencode/AGENTS.md new file mode 100644 index 0000000000..033a8f0ea8 --- /dev/null +++ b/opencode/AGENTS.md @@ -0,0 +1,116 @@ +# Agent Instructions: Dice Project + +This document provides essential information for AI agents working on the Dice Project. Follow these guidelines to maintain consistency and ensure high-quality contributions. + +## 1. Project Overview + +The Dice Project is a lightweight Python CLI tool that simulates rolling one to six dice and displays the results using ASCII art. + +- **Primary Language:** Python 3.x +- **Main Entry Point:** `dice.py` +- **Architecture:** Procedural script with functional decomposition. + +## 2. Build, Run, and Test Commands + +### Running the Application +To run the interactive CLI: +```bash +python dice.py +``` + +### Testing +There is currently no formal test suite. When adding tests: +- **Framework:** Use `pytest` (standard for this project). +- **Running all tests:** `pytest` +- **Running a single test file:** `pytest path/to/test_file.py` +- **Running a specific test:** `pytest path/to/test_file.py::test_function_name` + +### Linting and Formatting +Maintain PEP 8 compliance. Recommended tools: +- **Linting:** `flake8 dice.py` or `ruff check dice.py` +- **Formatting:** `black dice.py` + +## 3. Code Style Guidelines + +### General Principles +- **Simplicity:** Keep the logic straightforward. Avoid over-engineering for a single-file utility. +- **Readability:** Prioritize clear, descriptive names over brevity. + +### Naming Conventions +- **Functions:** Use `snake_case` (e.g., `generate_dice_faces_diagram`). +- **Variables:** Use `snake_case` (e.g., `roll_results`). +- **Constants:** Use `SCREAMING_SNAKE_CASE` (e.g., `DICE_ART`, `DIE_HEIGHT`). +- **Internal Helpers:** Prefix with a single underscore (e.g., `_get_dice_faces`). + +### Imports +- Place standard library imports at the top of the file. +- Keep them alphabetized within their group. +- Example: + ```python + import random + import sys + ``` + +### Documentation +- Use triple-quoted docstrings for all functions. +- The first line should be a concise summary. +- Follow with a more detailed explanation if the function's logic or parameters aren't trivial. +- Example: + ```python + def roll_dice(num_dice): + """Return a list of random integers between 1 and 6.""" + # implementation + ``` + +### Formatting +- **Indentation:** 4 spaces. +- **Line Length:** Max 79-88 characters (standard PEP 8 / Black). +- **Vertical Spacing:** Two blank lines between top-level functions. + +### Error Handling +- Use `parse_input` to validate user input. +- For fatal CLI errors, print a user-friendly message and use `raise SystemExit(1)`. +- Avoid broad `except Exception:` blocks; catch specific exceptions. + +## 4. Project Conventions & Architecture + +### ASCII Art Management +- All dice representations are stored in the `DICE_ART` dictionary. +- Each entry is a tuple of strings representing the rows of the die. +- Maintain the alignment and character usage (┌, ┐, └, ┘, │, ●) when modifying art. + +### Main Execution Block +- Currently, the script executes its main logic at the bottom of the file. +- **Refactoring Target:** When modifying `dice.py`, consider wrapping the execution logic in an `if __name__ == "__main__":` block to allow for better testability and modularity. + +### Input Validation +- All user inputs must be stripped and validated against expected values before processing. +- The `parse_input` function is the gatekeeper for valid dice counts. + +## 5. External Rules + +- **Cursor Rules:** No specific `.cursorrules` or `.cursor/rules/` detected. +- **Copilot Instructions:** No `.github/copilot-instructions.md` detected. +- **Standard Guidelines:** Default to standard Python best practices (PEP 8, PEP 257). + +## 6. Task-Specific Instructions + +### When Adding Features +1. **Check for side effects:** Ensure changes to `DICE_ART` don't break `generate_dice_faces_diagram`. +2. **Maintain ASCII alignment:** Use `DIE_HEIGHT` and `DIE_WIDTH` constants to ensure the diagram remains rectangular. +3. **Self-Verification:** After making changes, run `python dice.py` and test with various inputs (1, 6, and invalid strings) to ensure the CLI still behaves as expected. + +### When Refactoring +- Ensure that helper functions remain "private" (prefixed with `_`) if they are not intended for external use. +- Maintain the docstring style and clarity. + +## Python Guidelines + +- Follow PEP 8 style conventions +- Add type hints to all new and modified functions +- Prefer specific exception types over bare `except` clauses +- Use context managers for file I/O and database connections +- Write descriptive error messages that include the offending value + +--- +*Note: This file is intended for AI agents. If you are a human, feel free to update these guidelines as the project evolves.* diff --git a/opencode/README.md b/opencode/README.md new file mode 100644 index 0000000000..9db2aabc96 --- /dev/null +++ b/opencode/README.md @@ -0,0 +1,3 @@ +# How to Use OpenCode for AI-Assisted Python Coding + +This folder provides the code examples for the Real Python tutorial [How to Use OpenCode for AI-Assisted Python Coding](https://realpython.com/opencode-guide/). diff --git a/opencode/dice_improved.py b/opencode/dice_improved.py new file mode 100644 index 0000000000..7bf3b7e265 --- /dev/null +++ b/opencode/dice_improved.py @@ -0,0 +1,180 @@ +"""Simulate a six-sided dice roll. + +Usage: + + $ python dice.py + How many dice do you want to roll? [1-6] 5 + +~~~~~~~~~~~~~~~~~~~~~~~~~ RESULTS ~~~~~~~~~~~~~~~~~~~~~~~~~ +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ ● ● │ │ ● │ │ ● ● │ │ ● ● │ │ │ +│ ● │ │ │ │ ● │ │ ● │ │ ● │ +│ ● ● │ │ ● │ │ ● ● │ │ ● ● │ │ │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ +""" + +import random +from typing import List, Tuple + +# Constants +MIN_DICE = 1 +MAX_DICE = 6 + +DICE_ART = { + 1: ( + "┌─────────┐", + "│ │", + "│ ● │", + "│ │", + "└─────────┘", + ), + 2: ( + "┌─────────┐", + "│ ● │", + "│ │", + "│ ● │", + "└─────────┘", + ), + 3: ( + "┌─────────┐", + "│ ● │", + "│ ● │", + "│ ● │", + "└─────────┘", + ), + 4: ( + "┌─────────┐", + "│ ● ● │", + "│ │", + "│ ● ● │", + "└─────────┘", + ), + 5: ( + "┌─────────┐", + "│ ● ● │", + "│ ● │", + "│ ● ● │", + "└─────────┘", + ), + 6: ( + "┌─────────┐", + "│ ● ● │", + "│ ● ● │", + "│ ● ● │", + "└─────────┘", + ), +} +DIE_HEIGHT = len(DICE_ART[1]) +DIE_WIDTH = len(DICE_ART[1][0]) +DIE_FACE_SEPARATOR = " " + + +def parse_input(input_string: str) -> int: + """Validate input string and return as an integer. + + Check if `input_string` is an integer number between MIN_DICE and MAX_DICE. + If so, return the integer value. Otherwise, raise a ValueError. + """ + try: + val = int(input_string.strip()) + if MIN_DICE <= val <= MAX_DICE: + return val + except ValueError: + pass + + raise ValueError(f"Please enter a number from {MIN_DICE} to {MAX_DICE}.") + + +def roll_dice(num_dice: int) -> List[int]: + """Return a list of random integers between 1 and 6 with length `num_dice`. + + Args: + num_dice: The number of dice to roll. + + Returns: + A list of random integers. + """ + roll_results = [] + for _ in range(num_dice): + roll = random.randint(1, 6) + roll_results.append(roll) + return roll_results + + +def generate_dice_faces_diagram(dice_values: List[int]) -> str: + """Return an ASCII diagram of dice faces from `dice_values`. + + The string returned contains an ASCII representation of each die. + + Args: + dice_values: A list of integers representing the rolled values. + + Returns: + A formatted string with the ASCII diagram. + """ + dice_faces = _get_dice_faces(dice_values) + dice_faces_rows = _generate_dice_faces_rows(dice_faces) + + # Generate header with the word "RESULTS" centered + width = len(dice_faces_rows[0]) + diagram_header = " RESULTS ".center(width, "~") + + dice_faces_diagram = "\n".join([diagram_header] + dice_faces_rows) + return dice_faces_diagram + + +def _get_dice_faces(dice_values: List[int]) -> List[Tuple[str, ...]]: + """Retrieve ASCII art representations for given dice values. + + Args: + dice_values: A list of integers representing the rolled values. + + Returns: + A list of tuples, where each tuple is the ASCII art for a die. + """ + dice_faces = [] + for value in dice_values: + dice_faces.append(DICE_ART[value]) + return dice_faces + + +def _generate_dice_faces_rows(dice_faces: List[Tuple[str, ...]]) -> List[str]: + """Combine dice face tuples into horizontal rows of strings. + + Args: + dice_faces: A list of tuples, where each tuple is the ASCII art for a + die. + + Returns: + A list of strings, each representing a horizontal row of the dice + diagram. + """ + dice_faces_rows = [] + for row_idx in range(DIE_HEIGHT): + row_components = [] + for die in dice_faces: + row_components.append(die[row_idx]) + row_string = DIE_FACE_SEPARATOR.join(row_components) + dice_faces_rows.append(row_string) + return dice_faces_rows + + +def main() -> None: + """Main entry point for the Dice CLI application.""" + while True: + num_dice_input = input( + f"How many dice do you want to roll? [{MIN_DICE}-{MAX_DICE}] " + ) + try: + num_dice = parse_input(num_dice_input) + break + except ValueError as e: + print(e) + + roll_results = roll_dice(num_dice) + dice_face_diagram = generate_dice_faces_diagram(roll_results) + print(f"\n{dice_face_diagram}") + + +if __name__ == "__main__": + main() diff --git a/opencode/dice_original.py b/opencode/dice_original.py new file mode 100644 index 0000000000..2198ce88a8 --- /dev/null +++ b/opencode/dice_original.py @@ -0,0 +1,146 @@ +"""Simulate a six-sided dice roll. + +Usage: + + $ python dice.py + How many dice do you want to roll? [1-6] 5 + +~~~~~~~~~~~~~~~~~~~~~~~~~ RESULTS ~~~~~~~~~~~~~~~~~~~~~~~~~ +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ ● ● │ │ ● │ │ ● ● │ │ ● ● │ │ │ +│ ● │ │ │ │ ● │ │ ● │ │ ● │ +│ ● ● │ │ ● │ │ ● ● │ │ ● ● │ │ │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ +""" + +import random + +DICE_ART = { + 1: ( + "┌─────────┐", + "│ │", + "│ ● │", + "│ │", + "└─────────┘", + ), + 2: ( + "┌─────────┐", + "│ ● │", + "│ │", + "│ ● │", + "└─────────┘", + ), + 3: ( + "┌─────────┐", + "│ ● │", + "│ ● │", + "│ ● │", + "└─────────┘", + ), + 4: ( + "┌─────────┐", + "│ ● ● │", + "│ │", + "│ ● ● │", + "└─────────┘", + ), + 5: ( + "┌─────────┐", + "│ ● ● │", + "│ ● │", + "│ ● ● │", + "└─────────┘", + ), + 6: ( + "┌─────────┐", + "│ ● ● │", + "│ ● ● │", + "│ ● ● │", + "└─────────┘", + ), +} +DIE_HEIGHT = len(DICE_ART[1]) +DIE_WIDTH = len(DICE_ART[1][0]) +DIE_FACE_SEPARATOR = " " + + +def parse_input(input_string): + """Return `input_string` as an integer between 1 and 6. + + Check if `input_string` is an integer number between 1 and 6. + If so, return an integer with the same value. Otherwise, tell + the user to enter a valid number and quit the program. + """ + if input_string.strip() in {"1", "2", "3", "4", "5", "6"}: + return int(input_string) + else: + print("Please enter a number from 1 to 6.") + raise SystemExit(1) + + +def roll_dice(num_dice): + """Return a list of integers with length `num_dice`. + + Each integer in the returned list is a random number between + 1 and 6, inclusive. + """ + roll_results = [] + for _ in range(num_dice): + roll = random.randint(1, 6) + roll_results.append(roll) + return roll_results + + +def generate_dice_faces_diagram(dice_values): + """Return an ASCII diagram of dice faces from `dice_values`. + + The string returned contains an ASCII representation of each die. + For example, if `dice_values = [4, 1, 3, 2]` then the string + returned looks like this: + + ~~~~~~~~~~~~~~~~~~~ RESULTS ~~~~~~~~~~~~~~~~~~~ + ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ + │ ● ● │ │ │ │ ● │ │ ● │ + │ │ │ ● │ │ ● │ │ │ + │ ● ● │ │ │ │ ● │ │ ● │ + └─────────┘ └─────────┘ └─────────┘ └─────────┘ + """ + dice_faces = _get_dice_faces(dice_values) + dice_faces_rows = _generate_dice_faces_rows(dice_faces) + + # Generate header with the word "RESULTS" centered + width = len(dice_faces_rows[0]) + diagram_header = " RESULTS ".center(width, "~") + + dice_faces_diagram = "\n".join([diagram_header] + dice_faces_rows) + return dice_faces_diagram + + +def _get_dice_faces(dice_values): + dice_faces = [] + for value in dice_values: + dice_faces.append(DICE_ART[value]) + return dice_faces + + +def _generate_dice_faces_rows(dice_faces): + dice_faces_rows = [] + for row_idx in range(DIE_HEIGHT): + row_components = [] + for die in dice_faces: + row_components.append(die[row_idx]) + row_string = DIE_FACE_SEPARATOR.join(row_components) + dice_faces_rows.append(row_string) + return dice_faces_rows + + +# ~~~ App's main code block ~~~ +# 1. Get and validate user's input +num_dice_input = input("How many dice do you want to roll? [1-6] ") +num_dice = parse_input(num_dice_input) +# 2. Roll the dice +roll_results = roll_dice(num_dice) +# 3. Generate the ASCII diagram of dice faces +dice_face_diagram = generate_dice_faces_diagram(roll_results) +# 4. Display the diagram +print(f"\n{dice_face_diagram}")