Compare commits
5
Commits
cebed68105
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f1fb7189d | ||
|
|
6a8ab289c6 | ||
|
|
8d13657773 | ||
|
|
058c2451e0 | ||
|
|
e73218d9f4 |
@@ -1,2 +1,94 @@
|
||||
# 009-the_secret_auction_program
|
||||
|
||||
# 🚀 Secret Auction Program
|
||||
|
||||
A beginner-friendly Python program that runs a blind auction: bidders enter their name and bid one after another, the screen is cleared between bidders, and the highest bidder wins.
|
||||
|
||||
## 📚 Table of Contents
|
||||
|
||||
- [Overview](#-overview)
|
||||
- [Requirements](#-requirements)
|
||||
- [Setup](#-setup)
|
||||
- [Run](#-run)
|
||||
- [Tests](#-tests)
|
||||
- [License](#-license)
|
||||
- [Links](#-links)
|
||||
|
||||
## 🧭 Overview
|
||||
|
||||
This is the Day 9 project of Udemy's *100 Days of Code: The Complete Python Pro Bootcamp*. Each bidder enters a name and a bid, which are stored in a dictionary (name → bid). After each bidder the program asks whether there are more bidders and, if so, clears the screen so the next bidder cannot see earlier bids. When everyone has bid, the program announces the winner.
|
||||
|
||||
See the [program flowchart](docs/flowchart.md) and the [plan and milestones](docs/plan.md).
|
||||
|
||||
### 🖼️ Screenshots
|
||||
|
||||
Entering a bid:
|
||||
|
||||

|
||||
|
||||
Announcing the winner:
|
||||
|
||||

|
||||
|
||||
## 📋 Requirements
|
||||
|
||||
- Python 3.13 or newer
|
||||
- No runtime dependencies
|
||||
- `pytest` for the tests (installed by the `dev` extra)
|
||||
- [Doxygen](https://www.doxygen.nl/) (optional, to generate the API documentation)
|
||||
|
||||
## 🛠️ Setup
|
||||
|
||||
Create and activate a local virtual environment, upgrade pip, then install the project with its development tools.
|
||||
|
||||
Windows (PowerShell):
|
||||
|
||||
```powershell
|
||||
python -m venv .venv
|
||||
.venv\Scripts\Activate.ps1
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
Linux / macOS:
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
## ▶️ Run
|
||||
|
||||
With the virtual environment active:
|
||||
|
||||
```bash
|
||||
python -m secret_auction.main
|
||||
```
|
||||
|
||||
or use the installed command:
|
||||
|
||||
```bash
|
||||
secret-auction
|
||||
```
|
||||
|
||||
## 🧪 Tests
|
||||
|
||||
```bash
|
||||
python -m pytest
|
||||
```
|
||||
|
||||
To generate the API documentation (output in `docs/doxygen/`):
|
||||
|
||||
```bash
|
||||
doxygen Doxyfile
|
||||
```
|
||||
|
||||
## 📄 License
|
||||
|
||||
Licensed under the GNU Affero General Public License v3.0 — see [LICENSE](LICENSE).
|
||||
|
||||
## 🔗 Links
|
||||
|
||||
- [Repository](https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/009-the_secret_auction_program)
|
||||
- [Documentation](docs/)
|
||||
- [Issue tracker](https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/009-the_secret_auction_program/issues)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Blind Auction Flowchart
|
||||
|
||||
Original chart: [diagrams.net](https://viewer.diagrams.net/?highlight=0000ff&edit=_blank&layers=1&nav=1&title=Blind%20Auction%20Flow%20Chart)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([Start]) --> B[Print welcome message]
|
||||
B --> C[bids = empty dictionary]
|
||||
C --> D[/Ask name/]
|
||||
D --> E[/Ask bid, repeat until valid/]
|
||||
E --> F[Store bids name = bid]
|
||||
F --> G{Other bidders?}
|
||||
G -- yes --> H[Clear screen] --> D
|
||||
G -- no --> I[Find highest bid]
|
||||
I --> J[/Print winner/]
|
||||
J --> K([End])
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
+40
-40
@@ -1,40 +1,40 @@
|
||||
# Plan: Secret Auction Program (Day 9)
|
||||
|
||||
## Context
|
||||
|
||||
Udemy 100 Days of Code blind-auction CLI. The repository uses a Python `.gitignore`, an AGPL `LICENSE` and a git-ignored `.env` (tokens, never printed or committed). Python 3.13 or newer is required.
|
||||
|
||||
## Working method
|
||||
|
||||
Work on ONE milestone at a time. Each milestone is one branch (`milestone/NN-name`) and one pull request. When a milestone is finished, stop, verify it and review before starting the next. Milestones and issues are also tracked on the Gitea repository (via API, token from `.env`).
|
||||
|
||||
## Milestone 1 - Project scaffold and repo metadata (`milestone/01-scaffold`)
|
||||
|
||||
- [x] Create `src/secret_auction/__init__.py`, `tests/`, `docs/`
|
||||
- [x] `pyproject.toml`: `requires-python >=3.13`, src layout, no runtime deps, `dev` extra (`pytest`), pytest config, console script
|
||||
- [x] `.env.example`; `.env` and `.venv/` ignored; add `docs/doxygen/` to `.gitignore`
|
||||
- [x] `constants.py`
|
||||
- [x] `Doxyfile`
|
||||
- [x] Set repository description and topics (`python`, `udemy`, `100-days-of-code`, `blind-auction`, `beginner`) via API
|
||||
- [x] Create milestones and issues on the host (milestones M1-M4, issues #1-#12)
|
||||
- [x] Verify: create `.venv`, upgrade pip, `pip install -e ".[dev]"`
|
||||
- [x] Save plan and milestones in `docs/plan.md`
|
||||
|
||||
## Milestone 2 - Core auction logic (`milestone/02-core-logic`)
|
||||
|
||||
- [x] `auction.py` (Doxygen comments): `add_bid`, `find_winner` (uses `max(bids, key=bids.get)`, `ValueError` on empty)
|
||||
- [x] `tests/test_auction.py`
|
||||
- Verify: `pytest` passes
|
||||
|
||||
## Milestone 3 - CLI program (`milestone/03-cli`)
|
||||
|
||||
- [ ] `main.py`: prompt name and numeric bid (re-prompt on invalid), bids dict kept outside the loop, "other bidders? yes/no", clear screen with newlines between bidders, announce the winner
|
||||
- [ ] `tests/test_main.py` with monkeypatched `input`
|
||||
- Verify: `pytest`; scripted run `python -m secret_auction.main` with two bidders
|
||||
|
||||
## Milestone 4 - Documentation (`milestone/04-docs`)
|
||||
|
||||
- [ ] `README.md` from the template (Overview, Requirements, Setup incl. creating/activating `.venv` and `python -m pip install --upgrade pip`, Run, Tests, License AGPL, Links)
|
||||
- [ ] `docs/flowchart.md` (Mermaid plus link to the diagrams.net chart)
|
||||
- [ ] Generate Doxygen docs if `doxygen` is installed
|
||||
- Verify: README commands work verbatim; `.env` stays untracked
|
||||
# Plan: Secret Auction Program (Day 9)
|
||||
|
||||
## Context
|
||||
|
||||
Udemy 100 Days of Code blind-auction CLI. The repository uses a Python `.gitignore`, an AGPL `LICENSE` and a git-ignored `.env` (tokens, never printed or committed). Python 3.13 or newer is required.
|
||||
|
||||
## Working method
|
||||
|
||||
Work on ONE milestone at a time. Each milestone is one branch (`milestone/NN-name`) and one pull request. When a milestone is finished, stop, verify it and review before starting the next. Milestones and issues are also tracked on the Gitea repository (via API, token from `.env`).
|
||||
|
||||
## Milestone 1 - Project scaffold and repo metadata (`milestone/01-scaffold`)
|
||||
|
||||
- [x] Create `src/secret_auction/__init__.py`, `tests/`, `docs/`
|
||||
- [x] `pyproject.toml`: `requires-python >=3.13`, src layout, no runtime deps, `dev` extra (`pytest`), pytest config, console script
|
||||
- [x] `.env.example`; `.env` and `.venv/` ignored; add `docs/doxygen/` to `.gitignore`
|
||||
- [x] `constants.py`
|
||||
- [x] `Doxyfile`
|
||||
- [x] Set repository description and topics (`python`, `udemy`, `100-days-of-code`, `blind-auction`, `beginner`) via API
|
||||
- [x] Create milestones and issues on the host (milestones M1-M4, issues #1-#12)
|
||||
- [x] Verify: create `.venv`, upgrade pip, `pip install -e ".[dev]"`
|
||||
- [x] Save plan and milestones in `docs/plan.md`
|
||||
|
||||
## Milestone 2 - Core auction logic (`milestone/02-core-logic`)
|
||||
|
||||
- [x] `auction.py` (Doxygen comments): `add_bid`, `find_winner` (uses `max(bids, key=bids.get)`, `ValueError` on empty)
|
||||
- [x] `tests/test_auction.py`
|
||||
- Verify: `pytest` passes
|
||||
|
||||
## Milestone 3 - CLI program (`milestone/03-cli`)
|
||||
|
||||
- [x] `main.py`: prompt name and numeric bid (re-prompt on invalid), bids dict kept outside the loop, "other bidders? yes/no", clear screen with newlines between bidders, announce the winner
|
||||
- [x] `tests/test_main.py` with monkeypatched `input`
|
||||
- Verify: `pytest`; scripted run `python -m secret_auction.main` with two bidders
|
||||
|
||||
## Milestone 4 - Documentation (`milestone/04-docs`)
|
||||
|
||||
- [x] `README.md` from the template (Overview, Requirements, Setup incl. creating/activating `.venv` and `python -m pip install --upgrade pip`, Run, Tests, License AGPL, Links)
|
||||
- [x] `docs/flowchart.md` (Mermaid plus link to the diagrams.net chart)
|
||||
- [x] Generate Doxygen docs if `doxygen` is installed
|
||||
- Verify: README commands work verbatim; `.env` stays untracked
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
"""! @file main.py
|
||||
@brief Command line interface for the secret auction program.
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
from secret_auction import constants
|
||||
from secret_auction.auction import add_bid, find_winner
|
||||
|
||||
|
||||
def clear_screen() -> None:
|
||||
"""! @brief Hide previous entries by printing blank lines."""
|
||||
print("\n" * constants.CLEAR_SCREEN_LINES)
|
||||
|
||||
|
||||
def ask_bid() -> float:
|
||||
"""! @brief Ask for a bid until a valid, non-negative number is entered.
|
||||
@return The bid amount.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
amount = float(input(constants.PROMPT_BID))
|
||||
except ValueError:
|
||||
print(constants.MSG_INVALID_BID)
|
||||
continue
|
||||
if math.isfinite(amount) and amount >= 0:
|
||||
return amount
|
||||
print(constants.MSG_INVALID_BID)
|
||||
|
||||
|
||||
def ask_more_bidders() -> bool:
|
||||
"""! @brief Ask whether there are more bidders until yes/no is answered.
|
||||
@return True if more bidders exist, False otherwise.
|
||||
"""
|
||||
while True:
|
||||
answer = input(constants.PROMPT_MORE_BIDDERS).strip().lower()
|
||||
if answer in constants.YES_ANSWERS:
|
||||
return True
|
||||
if answer in constants.NO_ANSWERS:
|
||||
return False
|
||||
print(constants.MSG_INVALID_ANSWER)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""! @brief Run the blind auction: collect bids, then announce the winner."""
|
||||
print(constants.MSG_WELCOME)
|
||||
bids: dict[str, float] = {} # kept outside the loop so bids are not lost
|
||||
more_bidders = True
|
||||
while more_bidders:
|
||||
name = input(constants.PROMPT_NAME).strip()
|
||||
add_bid(bids, name, ask_bid())
|
||||
more_bidders = ask_more_bidders()
|
||||
if more_bidders:
|
||||
clear_screen()
|
||||
winner, amount = find_winner(bids)
|
||||
print(constants.MSG_WINNER.format(name=winner, amount=amount))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,35 @@
|
||||
from secret_auction import constants
|
||||
from secret_auction.main import ask_bid, ask_more_bidders, main
|
||||
|
||||
|
||||
def feed(monkeypatch, answers):
|
||||
it = iter(answers)
|
||||
monkeypatch.setattr("builtins.input", lambda _prompt="": next(it))
|
||||
|
||||
|
||||
def test_ask_bid_reprompts_on_invalid(monkeypatch, capsys):
|
||||
feed(monkeypatch, ["abc", "-5", "nan", "12.5"])
|
||||
assert ask_bid() == 12.5
|
||||
assert capsys.readouterr().out.count(constants.MSG_INVALID_BID) == 3
|
||||
|
||||
|
||||
def test_ask_more_bidders(monkeypatch, capsys):
|
||||
feed(monkeypatch, ["maybe", "Y"])
|
||||
assert ask_more_bidders() is True
|
||||
assert constants.MSG_INVALID_ANSWER in capsys.readouterr().out
|
||||
feed(monkeypatch, ["no"])
|
||||
assert ask_more_bidders() is False
|
||||
|
||||
|
||||
def test_main_announces_highest_bidder_and_clears_screen(monkeypatch, capsys):
|
||||
feed(monkeypatch, ["Ann", "10", "yes", "Bob", "25.5", "yes", "Cy", "3", "no"])
|
||||
main()
|
||||
out = capsys.readouterr().out
|
||||
assert "The winner is Bob with a bid of $25.50." in out
|
||||
assert out.count("\n" * constants.CLEAR_SCREEN_LINES) == 2
|
||||
|
||||
|
||||
def test_main_single_bidder(monkeypatch, capsys):
|
||||
feed(monkeypatch, ["Ann", "7", "n"])
|
||||
main()
|
||||
assert "The winner is Ann with a bid of $7.00." in capsys.readouterr().out
|
||||
Reference in New Issue
Block a user