Skip to content

Commit d5579dd

Browse files
committed
Sample code for the article on OpenCode
1 parent 3209d8a commit d5579dd

File tree

4 files changed

+445
-0
lines changed

4 files changed

+445
-0
lines changed

opencode/AGENTS.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Agent Instructions: Dice Project
2+
3+
This document provides essential information for AI agents working on the Dice Project. Follow these guidelines to maintain consistency and ensure high-quality contributions.
4+
5+
## 1. Project Overview
6+
7+
The Dice Project is a lightweight Python CLI tool that simulates rolling one to six dice and displays the results using ASCII art.
8+
9+
- **Primary Language:** Python 3.x
10+
- **Main Entry Point:** `dice.py`
11+
- **Architecture:** Procedural script with functional decomposition.
12+
13+
## 2. Build, Run, and Test Commands
14+
15+
### Running the Application
16+
To run the interactive CLI:
17+
```bash
18+
python dice.py
19+
```
20+
21+
### Testing
22+
There is currently no formal test suite. When adding tests:
23+
- **Framework:** Use `pytest` (standard for this project).
24+
- **Running all tests:** `pytest`
25+
- **Running a single test file:** `pytest path/to/test_file.py`
26+
- **Running a specific test:** `pytest path/to/test_file.py::test_function_name`
27+
28+
### Linting and Formatting
29+
Maintain PEP 8 compliance. Recommended tools:
30+
- **Linting:** `flake8 dice.py` or `ruff check dice.py`
31+
- **Formatting:** `black dice.py`
32+
33+
## 3. Code Style Guidelines
34+
35+
### General Principles
36+
- **Simplicity:** Keep the logic straightforward. Avoid over-engineering for a single-file utility.
37+
- **Readability:** Prioritize clear, descriptive names over brevity.
38+
39+
### Naming Conventions
40+
- **Functions:** Use `snake_case` (e.g., `generate_dice_faces_diagram`).
41+
- **Variables:** Use `snake_case` (e.g., `roll_results`).
42+
- **Constants:** Use `SCREAMING_SNAKE_CASE` (e.g., `DICE_ART`, `DIE_HEIGHT`).
43+
- **Internal Helpers:** Prefix with a single underscore (e.g., `_get_dice_faces`).
44+
45+
### Imports
46+
- Place standard library imports at the top of the file.
47+
- Keep them alphabetized within their group.
48+
- Example:
49+
```python
50+
import random
51+
import sys
52+
```
53+
54+
### Documentation
55+
- Use triple-quoted docstrings for all functions.
56+
- The first line should be a concise summary.
57+
- Follow with a more detailed explanation if the function's logic or parameters aren't trivial.
58+
- Example:
59+
```python
60+
def roll_dice(num_dice):
61+
"""Return a list of random integers between 1 and 6."""
62+
# implementation
63+
```
64+
65+
### Formatting
66+
- **Indentation:** 4 spaces.
67+
- **Line Length:** Max 79-88 characters (standard PEP 8 / Black).
68+
- **Vertical Spacing:** Two blank lines between top-level functions.
69+
70+
### Error Handling
71+
- Use `parse_input` to validate user input.
72+
- For fatal CLI errors, print a user-friendly message and use `raise SystemExit(1)`.
73+
- Avoid broad `except Exception:` blocks; catch specific exceptions.
74+
75+
## 4. Project Conventions & Architecture
76+
77+
### ASCII Art Management
78+
- All dice representations are stored in the `DICE_ART` dictionary.
79+
- Each entry is a tuple of strings representing the rows of the die.
80+
- Maintain the alignment and character usage (┌, ┐, └, ┘, │, ●) when modifying art.
81+
82+
### Main Execution Block
83+
- Currently, the script executes its main logic at the bottom of the file.
84+
- **Refactoring Target:** When modifying `dice.py`, consider wrapping the execution logic in an `if __name__ == "__main__":` block to allow for better testability and modularity.
85+
86+
### Input Validation
87+
- All user inputs must be stripped and validated against expected values before processing.
88+
- The `parse_input` function is the gatekeeper for valid dice counts.
89+
90+
## 5. External Rules
91+
92+
- **Cursor Rules:** No specific `.cursorrules` or `.cursor/rules/` detected.
93+
- **Copilot Instructions:** No `.github/copilot-instructions.md` detected.
94+
- **Standard Guidelines:** Default to standard Python best practices (PEP 8, PEP 257).
95+
96+
## 6. Task-Specific Instructions
97+
98+
### When Adding Features
99+
1. **Check for side effects:** Ensure changes to `DICE_ART` don't break `generate_dice_faces_diagram`.
100+
2. **Maintain ASCII alignment:** Use `DIE_HEIGHT` and `DIE_WIDTH` constants to ensure the diagram remains rectangular.
101+
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.
102+
103+
### When Refactoring
104+
- Ensure that helper functions remain "private" (prefixed with `_`) if they are not intended for external use.
105+
- Maintain the docstring style and clarity.
106+
107+
## Python Guidelines
108+
109+
- Follow PEP 8 style conventions
110+
- Add type hints to all new and modified functions
111+
- Prefer specific exception types over bare `except` clauses
112+
- Use context managers for file I/O and database connections
113+
- Write descriptive error messages that include the offending value
114+
115+
---
116+
*Note: This file is intended for AI agents. If you are a human, feel free to update these guidelines as the project evolves.*

opencode/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# How to Use OpenCode for AI-Assisted Python Coding
2+
3+
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/).

opencode/dice_improved.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
"""Simulate a six-sided dice roll.
2+
3+
Usage:
4+
5+
$ python dice.py
6+
How many dice do you want to roll? [1-6] 5
7+
8+
~~~~~~~~~~~~~~~~~~~~~~~~~ RESULTS ~~~~~~~~~~~~~~~~~~~~~~~~~
9+
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
10+
│ ● ● │ │ ● │ │ ● ● │ │ ● ● │ │ │
11+
│ ● │ │ │ │ ● │ │ ● │ │ ● │
12+
│ ● ● │ │ ● │ │ ● ● │ │ ● ● │ │ │
13+
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
14+
"""
15+
16+
import random
17+
from typing import List, Tuple
18+
19+
# Constants
20+
MIN_DICE = 1
21+
MAX_DICE = 6
22+
23+
DICE_ART = {
24+
1: (
25+
"┌─────────┐",
26+
"│ │",
27+
"│ ● │",
28+
"│ │",
29+
"└─────────┘",
30+
),
31+
2: (
32+
"┌─────────┐",
33+
"│ ● │",
34+
"│ │",
35+
"│ ● │",
36+
"└─────────┘",
37+
),
38+
3: (
39+
"┌─────────┐",
40+
"│ ● │",
41+
"│ ● │",
42+
"│ ● │",
43+
"└─────────┘",
44+
),
45+
4: (
46+
"┌─────────┐",
47+
"│ ● ● │",
48+
"│ │",
49+
"│ ● ● │",
50+
"└─────────┘",
51+
),
52+
5: (
53+
"┌─────────┐",
54+
"│ ● ● │",
55+
"│ ● │",
56+
"│ ● ● │",
57+
"└─────────┘",
58+
),
59+
6: (
60+
"┌─────────┐",
61+
"│ ● ● │",
62+
"│ ● ● │",
63+
"│ ● ● │",
64+
"└─────────┘",
65+
),
66+
}
67+
DIE_HEIGHT = len(DICE_ART[1])
68+
DIE_WIDTH = len(DICE_ART[1][0])
69+
DIE_FACE_SEPARATOR = " "
70+
71+
72+
def parse_input(input_string: str) -> int:
73+
"""Validate input string and return as an integer.
74+
75+
Check if `input_string` is an integer number between MIN_DICE and MAX_DICE.
76+
If so, return the integer value. Otherwise, raise a ValueError.
77+
"""
78+
try:
79+
val = int(input_string.strip())
80+
if MIN_DICE <= val <= MAX_DICE:
81+
return val
82+
except ValueError:
83+
pass
84+
85+
raise ValueError(f"Please enter a number from {MIN_DICE} to {MAX_DICE}.")
86+
87+
88+
def roll_dice(num_dice: int) -> List[int]:
89+
"""Return a list of random integers between 1 and 6 with length `num_dice`.
90+
91+
Args:
92+
num_dice: The number of dice to roll.
93+
94+
Returns:
95+
A list of random integers.
96+
"""
97+
roll_results = []
98+
for _ in range(num_dice):
99+
roll = random.randint(1, 6)
100+
roll_results.append(roll)
101+
return roll_results
102+
103+
104+
def generate_dice_faces_diagram(dice_values: List[int]) -> str:
105+
"""Return an ASCII diagram of dice faces from `dice_values`.
106+
107+
The string returned contains an ASCII representation of each die.
108+
109+
Args:
110+
dice_values: A list of integers representing the rolled values.
111+
112+
Returns:
113+
A formatted string with the ASCII diagram.
114+
"""
115+
dice_faces = _get_dice_faces(dice_values)
116+
dice_faces_rows = _generate_dice_faces_rows(dice_faces)
117+
118+
# Generate header with the word "RESULTS" centered
119+
width = len(dice_faces_rows[0])
120+
diagram_header = " RESULTS ".center(width, "~")
121+
122+
dice_faces_diagram = "\n".join([diagram_header] + dice_faces_rows)
123+
return dice_faces_diagram
124+
125+
126+
def _get_dice_faces(dice_values: List[int]) -> List[Tuple[str, str, str, str, str]]:
127+
"""Retrieve ASCII art representations for given dice values.
128+
129+
Args:
130+
dice_values: A list of integers representing the rolled values.
131+
132+
Returns:
133+
A list of tuples, where each tuple is the ASCII art for a die.
134+
"""
135+
dice_faces = []
136+
for value in dice_values:
137+
dice_faces.append(DICE_ART[value])
138+
return dice_faces
139+
140+
141+
def _generate_dice_faces_rows(
142+
dice_faces: List[Tuple[str, str, str, str, str]],
143+
) -> List[str]:
144+
"""Combine dice face tuples into horizontal rows of strings.
145+
146+
Args:
147+
dice_faces: A list of tuples, where each tuple is the ASCII art for a die.
148+
149+
Returns:
150+
A list of strings, each representing a horizontal row of the dice diagram.
151+
"""
152+
dice_faces_rows = []
153+
for row_idx in range(DIE_HEIGHT):
154+
row_components = []
155+
for die in dice_faces:
156+
row_components.append(die[row_idx])
157+
row_string = DIE_FACE_SEPARATOR.join(row_components)
158+
dice_faces_rows.append(row_string)
159+
return dice_faces_rows
160+
161+
162+
def main() -> None:
163+
"""Main entry point for the Dice CLI application."""
164+
while True:
165+
num_dice_input = input(
166+
f"How many dice do you want to roll? [{MIN_DICE}-{MAX_DICE}] "
167+
)
168+
try:
169+
num_dice = parse_input(num_dice_input)
170+
break
171+
except ValueError as e:
172+
print(e)
173+
174+
roll_results = roll_dice(num_dice)
175+
dice_face_diagram = generate_dice_faces_diagram(roll_results)
176+
print(f"\n{dice_face_diagram}")
177+
178+
179+
if __name__ == "__main__":
180+
main()

0 commit comments

Comments
 (0)