mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-12-16 15:40:12 +01:00
Preperly functional Docker instance created and working
This commit is contained in:
parent
ada2403c40
commit
661bf236d9
11 changed files with 189 additions and 4900 deletions
295
DOCKER.md
295
DOCKER.md
|
|
@ -1,8 +1,10 @@
|
||||||
# Docker Usage Guide for MTG Deckbuilder
|
# Docker Guide for MTG Python Deckbuilder
|
||||||
|
|
||||||
## Quick Start (Recommended)
|
A comprehensive guide for running the MTG Python Deckbuilder in Docker containers with full file persistence and cross-platform support.
|
||||||
|
|
||||||
### Linux/Remote Host (Interactive Applications)
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
### Linux/macOS/Remote Host
|
||||||
```bash
|
```bash
|
||||||
# Make scripts executable (one time only)
|
# Make scripts executable (one time only)
|
||||||
chmod +x quick-start.sh run-docker-linux.sh
|
chmod +x quick-start.sh run-docker-linux.sh
|
||||||
|
|
@ -10,42 +12,16 @@ chmod +x quick-start.sh run-docker-linux.sh
|
||||||
# Simplest method - just run this:
|
# Simplest method - just run this:
|
||||||
./quick-start.sh
|
./quick-start.sh
|
||||||
|
|
||||||
# Or use the full script with more options:
|
# Or use the full script with options:
|
||||||
./run-docker-linux.sh compose
|
./run-docker-linux.sh compose
|
||||||
```
|
```
|
||||||
|
|
||||||
### Windows (PowerShell)
|
### Windows (PowerShell)
|
||||||
```powershell
|
```powershell
|
||||||
# Run with Docker Compose
|
# Run with Docker Compose (recommended)
|
||||||
.\run-docker.ps1 compose
|
.\run-docker.ps1 compose
|
||||||
```
|
|
||||||
|
|
||||||
## Important: Interactive Applications & Docker Compose
|
# Or manual Docker run
|
||||||
|
|
||||||
**Your MTG Deckbuilder is an interactive application** that uses menus and requires keyboard input. This creates special requirements:
|
|
||||||
|
|
||||||
### ✅ What Works for Interactive Apps:
|
|
||||||
- `docker run -it` (manual)
|
|
||||||
- `docker-compose run` (recommended)
|
|
||||||
- `./quick-start.sh` (easiest)
|
|
||||||
|
|
||||||
### ❌ What Doesn't Work:
|
|
||||||
- `docker-compose up` (runs in background, no interaction)
|
|
||||||
- Running without `-it` flags
|
|
||||||
|
|
||||||
### Why the Difference?
|
|
||||||
|
|
||||||
- **`docker-compose up`**: Starts services in the background, doesn't attach to your terminal
|
|
||||||
- **`docker-compose run`**: Creates a new container and attaches to your terminal for interaction
|
|
||||||
|
|
||||||
## Manual Docker Commands
|
|
||||||
|
|
||||||
### Windows PowerShell
|
|
||||||
```powershell
|
|
||||||
# Build the image
|
|
||||||
docker build -t mtg-deckbuilder .
|
|
||||||
|
|
||||||
# Run with volume mounting for file persistence
|
|
||||||
docker run -it --rm `
|
docker run -it --rm `
|
||||||
-v "${PWD}/deck_files:/app/deck_files" `
|
-v "${PWD}/deck_files:/app/deck_files" `
|
||||||
-v "${PWD}/logs:/app/logs" `
|
-v "${PWD}/logs:/app/logs" `
|
||||||
|
|
@ -53,12 +29,83 @@ docker run -it --rm `
|
||||||
mtg-deckbuilder
|
mtg-deckbuilder
|
||||||
```
|
```
|
||||||
|
|
||||||
### Linux/macOS/Git Bash
|
## 📋 Prerequisites
|
||||||
```bash
|
|
||||||
# Build the image
|
|
||||||
docker build -t mtg-deckbuilder .
|
|
||||||
|
|
||||||
# Run with volume mounting for file persistence
|
- **Docker** installed and running
|
||||||
|
- **Docker Compose** (usually included with Docker)
|
||||||
|
- Basic terminal/command line knowledge
|
||||||
|
|
||||||
|
## 🔧 Available Commands
|
||||||
|
|
||||||
|
### Quick Start Scripts
|
||||||
|
|
||||||
|
| Script | Platform | Description |
|
||||||
|
|--------|----------|-------------|
|
||||||
|
| `./quick-start.sh` | Linux/macOS | Simplest way to run the application |
|
||||||
|
| `.\run-docker.ps1 compose` | Windows | PowerShell equivalent |
|
||||||
|
|
||||||
|
### Full Featured Scripts
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `./run-docker-linux.sh setup` | Create directories and check Docker installation |
|
||||||
|
| `./run-docker-linux.sh build` | Build the Docker image |
|
||||||
|
| `./run-docker-linux.sh compose` | Run with Docker Compose (recommended) |
|
||||||
|
| `./run-docker-linux.sh run` | Run with manual volume mounting |
|
||||||
|
| `./run-docker-linux.sh clean` | Remove containers and images |
|
||||||
|
|
||||||
|
## 🗂️ File Persistence
|
||||||
|
|
||||||
|
Your files are automatically saved to local directories that persist between runs:
|
||||||
|
|
||||||
|
```
|
||||||
|
mtg_python_deckbuilder/
|
||||||
|
├── deck_files/ # Your saved decks (CSV and TXT files)
|
||||||
|
├── logs/ # Application logs and debug info
|
||||||
|
├── csv_files/ # Card database and color-sorted files
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
The Docker container uses **volume mounting** to map container directories to your local filesystem:
|
||||||
|
|
||||||
|
- Container path `/app/deck_files` ↔ Host path `./deck_files`
|
||||||
|
- Container path `/app/logs` ↔ Host path `./logs`
|
||||||
|
- Container path `/app/csv_files` ↔ Host path `./csv_files`
|
||||||
|
|
||||||
|
When the application saves files, they appear in your local directories and remain there after the container stops.
|
||||||
|
|
||||||
|
## 🎮 Interactive Application Requirements
|
||||||
|
|
||||||
|
The MTG Deckbuilder is an **interactive application** that uses menus and requires keyboard input.
|
||||||
|
|
||||||
|
### ✅ Commands That Work
|
||||||
|
- `docker compose run --rm mtg-deckbuilder`
|
||||||
|
- `docker run -it --rm mtg-deckbuilder`
|
||||||
|
- `./quick-start.sh`
|
||||||
|
- Helper scripts with `compose` command
|
||||||
|
|
||||||
|
### ❌ Commands That Don't Work
|
||||||
|
- `docker compose up` (runs in background, no interaction)
|
||||||
|
- `docker run` without `-it` flags
|
||||||
|
- Any command without proper TTY allocation
|
||||||
|
|
||||||
|
### Why the Difference?
|
||||||
|
- **`docker compose run`**: Creates new container with terminal attachment
|
||||||
|
- **`docker compose up`**: Starts service in background without terminal
|
||||||
|
|
||||||
|
## 🔨 Manual Docker Commands
|
||||||
|
|
||||||
|
### Build the Image
|
||||||
|
```bash
|
||||||
|
docker build -t mtg-deckbuilder .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run with Full Volume Mounting
|
||||||
|
|
||||||
|
**Linux/macOS:**
|
||||||
|
```bash
|
||||||
docker run -it --rm \
|
docker run -it --rm \
|
||||||
-v "$(pwd)/deck_files:/app/deck_files" \
|
-v "$(pwd)/deck_files:/app/deck_files" \
|
||||||
-v "$(pwd)/logs:/app/logs" \
|
-v "$(pwd)/logs:/app/logs" \
|
||||||
|
|
@ -66,97 +113,109 @@ docker run -it --rm \
|
||||||
mtg-deckbuilder
|
mtg-deckbuilder
|
||||||
```
|
```
|
||||||
|
|
||||||
## File Persistence Explained
|
**Windows PowerShell:**
|
||||||
|
```powershell
|
||||||
The key to saving your files is **volume mounting**. Here's what happens:
|
docker run -it --rm `
|
||||||
|
-v "${PWD}/deck_files:/app/deck_files" `
|
||||||
### Without Volume Mounting (Bad)
|
-v "${PWD}/logs:/app/logs" `
|
||||||
- Files are saved inside the container
|
-v "${PWD}/csv_files:/app/csv_files" `
|
||||||
- When container stops, files are lost forever
|
mtg-deckbuilder
|
||||||
- Example: `docker run -it mtg-deckbuilder` ❌
|
|
||||||
|
|
||||||
### With Volume Mounting (Good)
|
|
||||||
- Files are saved to your local directories
|
|
||||||
- Files persist between container runs
|
|
||||||
- Local directories are "mounted" into the container
|
|
||||||
- Example: `docker run -it -v "./deck_files:/app/deck_files" mtg-deckbuilder` ✅
|
|
||||||
|
|
||||||
## Directory Structure After Running
|
|
||||||
|
|
||||||
After running the Docker container, you'll have these local directories:
|
|
||||||
|
|
||||||
```
|
|
||||||
mtg_python_deckbuilder/
|
|
||||||
├── deck_files/ # Your saved decks (CSV and TXT files)
|
|
||||||
├── logs/ # Application logs
|
|
||||||
├── csv_files/ # Card database files
|
|
||||||
└── ...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## 📁 Docker Compose Files
|
||||||
|
|
||||||
### Files Still Not Saving?
|
The project includes two Docker Compose configurations:
|
||||||
|
|
||||||
1. **Check directory creation**: The helper scripts automatically create the needed directories
|
### `docker-compose.yml` (Main)
|
||||||
2. **Verify volume mounts**: Look for `-v` flags in your docker run command
|
- Standard configuration
|
||||||
3. **Check permissions**: Make sure you have write access to the local directories
|
- Container name: `mtg-deckbuilder-main`
|
||||||
|
- Use with: `docker compose run --rm mtg-deckbuilder`
|
||||||
|
|
||||||
|
### `docker-compose.interactive.yml` (Alternative)
|
||||||
|
- Identical functionality
|
||||||
|
- Container name: `mtg-deckbuilder-interactive`
|
||||||
|
- Use with: `docker compose -f docker compose.interactive.yml run --rm mtg-deckbuilder-interactive`
|
||||||
|
|
||||||
|
Both files provide the same functionality and file persistence.
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Files Not Saving?
|
||||||
|
|
||||||
|
1. **Check volume mounts**: Ensure you see `-v` flags in your docker command
|
||||||
|
2. **Verify directories exist**: Scripts automatically create needed directories
|
||||||
|
3. **Check permissions**: Ensure you have write access to the project directory
|
||||||
|
4. **Use correct command**: Use `docker compose run`, not `docker compose up`
|
||||||
|
|
||||||
|
### Application Won't Start Interactively?
|
||||||
|
|
||||||
|
1. **Use the right command**: `docker compose run --rm mtg-deckbuilder`
|
||||||
|
2. **Check TTY allocation**: Ensure `-it` flags are present in manual commands
|
||||||
|
3. **Avoid background mode**: Don't use `docker compose up` for interactive apps
|
||||||
|
|
||||||
|
### Permission Issues?
|
||||||
|
|
||||||
|
Files created by Docker may be owned by `root`. This is normal on Linux systems.
|
||||||
|
|
||||||
|
### Container Build Fails?
|
||||||
|
|
||||||
|
1. **Update Docker**: Ensure you have a recent version
|
||||||
|
2. **Clear cache**: Run `docker system prune -f`
|
||||||
|
3. **Check network**: Ensure Docker can download dependencies
|
||||||
|
|
||||||
### Starting Fresh
|
### Starting Fresh
|
||||||
|
|
||||||
```powershell
|
**Complete cleanup:**
|
||||||
# Windows - Clean up everything
|
|
||||||
.\run-docker.ps1 clean
|
|
||||||
|
|
||||||
# Or manually
|
|
||||||
docker-compose down
|
|
||||||
docker rmi mtg-deckbuilder
|
|
||||||
```
|
|
||||||
|
|
||||||
### Container Won't Start
|
|
||||||
|
|
||||||
1. Make sure Docker Desktop is running
|
|
||||||
2. Try rebuilding: `.\run-docker.ps1 build`
|
|
||||||
3. Check for port conflicts
|
|
||||||
4. Review Docker logs: `docker logs mtg-deckbuilder`
|
|
||||||
|
|
||||||
## Helper Script Commands
|
|
||||||
|
|
||||||
### Windows PowerShell
|
|
||||||
```powershell
|
|
||||||
.\run-docker.ps1 build # Build the Docker image
|
|
||||||
.\run-docker.ps1 run # Run with manual volume mounting
|
|
||||||
.\run-docker.ps1 compose # Run with Docker Compose (recommended)
|
|
||||||
.\run-docker.ps1 clean # Clean up containers and images
|
|
||||||
.\run-docker.ps1 help # Show help
|
|
||||||
```
|
|
||||||
|
|
||||||
### Linux/macOS
|
|
||||||
```bash
|
```bash
|
||||||
./run-docker.sh build # Build the Docker image
|
# Stop all containers
|
||||||
./run-docker.sh run # Run with manual volume mounting
|
docker compose down
|
||||||
./run-docker.sh compose # Run with Docker Compose (recommended)
|
docker compose -f docker-compose.interactive.yml down
|
||||||
./run-docker.sh clean # Clean up containers and images
|
|
||||||
./run-docker.sh help # Show help
|
# Remove image
|
||||||
|
docker rmi mtg-deckbuilder
|
||||||
|
|
||||||
|
# Clean up system
|
||||||
|
docker system prune -f
|
||||||
|
|
||||||
|
# Rebuild
|
||||||
|
docker compose build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Why Docker Compose is Recommended
|
## 🔍 Verifying Everything Works
|
||||||
|
|
||||||
Docker Compose offers several advantages:
|
After running the application:
|
||||||
|
|
||||||
1. **Simpler commands**: Just `docker-compose up`
|
1. **Create or modify some data** (run setup, build a deck, etc.)
|
||||||
2. **Configuration in file**: All settings stored in `docker-compose.yml`
|
2. **Exit the container** (Ctrl+C or select Quit)
|
||||||
3. **Automatic cleanup**: Containers are removed when stopped
|
3. **Check your local directories**:
|
||||||
4. **Consistent behavior**: Same setup every time
|
```bash
|
||||||
|
ls -la deck_files/ # Should show any decks you created
|
||||||
## Verifying File Persistence
|
ls -la logs/ # Should show log files
|
||||||
|
ls -la csv_files/ # Should show card database files
|
||||||
After running the application and creating/saving files:
|
|
||||||
|
|
||||||
1. Exit the Docker container
|
|
||||||
2. Check your local directories:
|
|
||||||
```powershell
|
|
||||||
ls deck_files # Should show your saved deck files
|
|
||||||
ls logs # Should show log files
|
|
||||||
ls csv_files # Should show card database files
|
|
||||||
```
|
```
|
||||||
3. Run the container again - your files should still be there!
|
4. **Run again** - your data should still be there!
|
||||||
|
|
||||||
|
## 🎯 Best Practices
|
||||||
|
|
||||||
|
1. **Use the quick-start script** for simplest experience
|
||||||
|
2. **Always use `docker compose run`** for interactive applications
|
||||||
|
3. **Keep your project directory organized** - files persist locally
|
||||||
|
4. **Regularly backup your `deck_files/`** if you create valuable decks
|
||||||
|
5. **Use `clean` commands** to free up disk space when needed
|
||||||
|
|
||||||
|
## 🌟 Benefits of Docker Approach
|
||||||
|
|
||||||
|
- ✅ **Consistent environment** across different machines
|
||||||
|
- ✅ **No Python installation required** on host system
|
||||||
|
- ✅ **Isolated dependencies** - won't conflict with other projects
|
||||||
|
- ✅ **Easy sharing** - others can run your setup instantly
|
||||||
|
- ✅ **Cross-platform** - works on Windows, macOS, and Linux
|
||||||
|
- ✅ **File persistence** - your work is saved locally
|
||||||
|
- ✅ **Easy cleanup** - remove everything with one command
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Need help?** Check the troubleshooting section above or refer to the helper script help:
|
||||||
|
```bash
|
||||||
|
./run-docker-linux.sh help
|
||||||
|
```
|
||||||
|
|
|
||||||
16
Dockerfile
16
Dockerfile
|
|
@ -25,18 +25,22 @@ COPY csv_files/ ./csv_files/
|
||||||
COPY mypy.ini .
|
COPY mypy.ini .
|
||||||
|
|
||||||
# Create necessary directories as mount points
|
# Create necessary directories as mount points
|
||||||
RUN mkdir -p deck_files logs
|
RUN mkdir -p deck_files logs csv_files
|
||||||
|
|
||||||
# Create volumes for persistent data
|
# Create volumes for persistent data
|
||||||
VOLUME ["/app/deck_files", "/app/logs", "/app/csv_files"]
|
VOLUME ["/app/deck_files", "/app/logs", "/app/csv_files"]
|
||||||
|
|
||||||
# Set the working directory to code for proper imports
|
# Create symbolic links BEFORE changing working directory
|
||||||
WORKDIR /app/code
|
RUN cd /app/code && \
|
||||||
|
ln -sf /app/deck_files ./deck_files && \
|
||||||
# Create symbolic links so the app can find the data directories
|
|
||||||
RUN ln -sf /app/deck_files ./deck_files && \
|
|
||||||
ln -sf /app/logs ./logs && \
|
ln -sf /app/logs ./logs && \
|
||||||
ln -sf /app/csv_files ./csv_files
|
ln -sf /app/csv_files ./csv_files
|
||||||
|
|
||||||
|
# Verify symbolic links were created
|
||||||
|
RUN cd /app/code && ls -la deck_files logs csv_files
|
||||||
|
|
||||||
|
# Set the working directory to code for proper imports
|
||||||
|
WORKDIR /app/code
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
CMD ["python", "main.py"]
|
CMD ["python", "main.py"]
|
||||||
|
|
|
||||||
|
|
@ -364,7 +364,7 @@ LAND_REMOVAL_MAX_ATTEMPTS: Final[int] = 3
|
||||||
PROTECTED_LANDS: Final[List[str]] = BASIC_LANDS + [land['name'] for land in KINDRED_STAPLE_LANDS]
|
PROTECTED_LANDS: Final[List[str]] = BASIC_LANDS + [land['name'] for land in KINDRED_STAPLE_LANDS]
|
||||||
|
|
||||||
# Other defaults
|
# Other defaults
|
||||||
DEFAULT_CREATURE_COUNT: Final[int] = 35 # Default number of creatures
|
DEFAULT_CREATURE_COUNT: Final[int] = 25 # Default number of creatures
|
||||||
DEFAULT_REMOVAL_COUNT: Final[int] = 10 # Default number of spot removal spells
|
DEFAULT_REMOVAL_COUNT: Final[int] = 10 # Default number of spot removal spells
|
||||||
DEFAULT_WIPES_COUNT: Final[int] = 2 # Default number of board wipes
|
DEFAULT_WIPES_COUNT: Final[int] = 2 # Default number of board wipes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
from .builder import DeckBuilder
|
|
||||||
from .builder_utils import *
|
|
||||||
from .builder_constants import *
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'DeckBuilder',
|
|
||||||
]
|
|
||||||
|
|
@ -1,437 +0,0 @@
|
||||||
from typing import Dict, List, Final, Tuple, Union, Callable
|
|
||||||
from settings import CARD_DATA_COLUMNS as CSV_REQUIRED_COLUMNS # unified
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'CSV_REQUIRED_COLUMNS'
|
|
||||||
]
|
|
||||||
import ast
|
|
||||||
|
|
||||||
# Commander selection configuration
|
|
||||||
# Format string for displaying duplicate cards in deck lists
|
|
||||||
FUZZY_MATCH_THRESHOLD: Final[int] = 90 # Threshold for fuzzy name matching
|
|
||||||
MAX_FUZZY_CHOICES: Final[int] = 5 # Maximum number of fuzzy match choices
|
|
||||||
|
|
||||||
# Commander-related constants
|
|
||||||
DUPLICATE_CARD_FORMAT: Final[str] = '{card_name} x {count}'
|
|
||||||
COMMANDER_CSV_PATH: Final[str] = 'csv_files/commander_cards.csv'
|
|
||||||
DECK_DIRECTORY = '../deck_files'
|
|
||||||
COMMANDER_CONVERTERS: Final[Dict[str, str]] = {'themeTags': ast.literal_eval, 'creatureTypes': ast.literal_eval} # CSV loading converters
|
|
||||||
COMMANDER_POWER_DEFAULT: Final[int] = 0
|
|
||||||
COMMANDER_TOUGHNESS_DEFAULT: Final[int] = 0
|
|
||||||
COMMANDER_MANA_VALUE_DEFAULT: Final[int] = 0
|
|
||||||
COMMANDER_TYPE_DEFAULT: Final[str] = ''
|
|
||||||
COMMANDER_TEXT_DEFAULT: Final[str] = ''
|
|
||||||
COMMANDER_MANA_COST_DEFAULT: Final[str] = ''
|
|
||||||
COMMANDER_COLOR_IDENTITY_DEFAULT: Final[str] = ''
|
|
||||||
COMMANDER_COLORS_DEFAULT: Final[List[str]] = []
|
|
||||||
COMMANDER_CREATURE_TYPES_DEFAULT: Final[str] = ''
|
|
||||||
COMMANDER_TAGS_DEFAULT: Final[List[str]] = []
|
|
||||||
COMMANDER_THEMES_DEFAULT: Final[List[str]] = []
|
|
||||||
|
|
||||||
CARD_TYPES = ['Artifact','Creature', 'Enchantment', 'Instant', 'Land', 'Planeswalker', 'Sorcery',
|
|
||||||
'Kindred', 'Dungeon', 'Battle']
|
|
||||||
|
|
||||||
# Basic mana colors
|
|
||||||
MANA_COLORS: Final[List[str]] = ['W', 'U', 'B', 'R', 'G']
|
|
||||||
|
|
||||||
# Mana pip patterns for each color
|
|
||||||
MANA_PIP_PATTERNS: Final[Dict[str, str]] = {
|
|
||||||
color: f'{{{color}}}' for color in MANA_COLORS
|
|
||||||
}
|
|
||||||
|
|
||||||
MONO_COLOR_MAP: Final[Dict[str, Tuple[str, List[str]]]] = {
|
|
||||||
'COLORLESS': ('Colorless', ['colorless']),
|
|
||||||
'W': ('White', ['colorless', 'white']),
|
|
||||||
'U': ('Blue', ['colorless', 'blue']),
|
|
||||||
'B': ('Black', ['colorless', 'black']),
|
|
||||||
'R': ('Red', ['colorless', 'red']),
|
|
||||||
'G': ('Green', ['colorless', 'green'])
|
|
||||||
}
|
|
||||||
|
|
||||||
DUAL_COLOR_MAP: Final[Dict[str, Tuple[str, List[str], List[str]]]] = {
|
|
||||||
'B, G': ('Golgari: Black/Green', ['B', 'G', 'B, G'], ['colorless', 'black', 'green', 'golgari']),
|
|
||||||
'B, R': ('Rakdos: Black/Red', ['B', 'R', 'B, R'], ['colorless', 'black', 'red', 'rakdos']),
|
|
||||||
'B, U': ('Dimir: Black/Blue', ['B', 'U', 'B, U'], ['colorless', 'black', 'blue', 'dimir']),
|
|
||||||
'B, W': ('Orzhov: Black/White', ['B', 'W', 'B, W'], ['colorless', 'black', 'white', 'orzhov']),
|
|
||||||
'G, R': ('Gruul: Green/Red', ['G', 'R', 'G, R'], ['colorless', 'green', 'red', 'gruul']),
|
|
||||||
'G, U': ('Simic: Green/Blue', ['G', 'U', 'G, U'], ['colorless', 'green', 'blue', 'simic']),
|
|
||||||
'G, W': ('Selesnya: Green/White', ['G', 'W', 'G, W'], ['colorless', 'green', 'white', 'selesnya']),
|
|
||||||
'R, U': ('Izzet: Blue/Red', ['U', 'R', 'U, R'], ['colorless', 'blue', 'red', 'izzet']),
|
|
||||||
'U, W': ('Azorius: Blue/White', ['U', 'W', 'U, W'], ['colorless', 'blue', 'white', 'azorius']),
|
|
||||||
'R, W': ('Boros: Red/White', ['R', 'W', 'R, W'], ['colorless', 'red', 'white', 'boros'])
|
|
||||||
}
|
|
||||||
|
|
||||||
TRI_COLOR_MAP: Final[Dict[str, Tuple[str, List[str], List[str]]]] = {
|
|
||||||
'B, G, U': ('Sultai: Black/Blue/Green', ['B', 'G', 'U', 'B, G', 'B, U', 'G, U', 'B, G, U'],
|
|
||||||
['colorless', 'black', 'blue', 'green', 'dimir', 'golgari', 'simic', 'sultai']),
|
|
||||||
'B, G, R': ('Jund: Black/Red/Green', ['B', 'G', 'R', 'B, G', 'B, R', 'G, R', 'B, G, R'],
|
|
||||||
['colorless', 'black', 'green', 'red', 'golgari', 'rakdos', 'gruul', 'jund']),
|
|
||||||
'B, G, W': ('Abzan: Black/Green/White', ['B', 'G', 'W', 'B, G', 'B, W', 'G, W', 'B, G, W'],
|
|
||||||
['colorless', 'black', 'green', 'white', 'golgari', 'orzhov', 'selesnya', 'abzan']),
|
|
||||||
'B, R, U': ('Grixis: Black/Blue/Red', ['B', 'R', 'U', 'B, R', 'B, U', 'R, U', 'B, R, U'],
|
|
||||||
['colorless', 'black', 'blue', 'red', 'dimir', 'rakdos', 'izzet', 'grixis']),
|
|
||||||
'B, R, W': ('Mardu: Black/Red/White', ['B', 'R', 'W', 'B, R', 'B, W', 'R, W', 'B, R, W'],
|
|
||||||
['colorless', 'black', 'red', 'white', 'rakdos', 'orzhov', 'boros', 'mardu']),
|
|
||||||
'B, U, W': ('Esper: Black/Blue/White', ['B', 'U', 'W', 'B, U', 'B, W', 'U, W', 'B, U, W'],
|
|
||||||
['colorless', 'black', 'blue', 'white', 'dimir', 'orzhov', 'azorius', 'esper']),
|
|
||||||
'G, R, U': ('Temur: Blue/Green/Red', ['G', 'R', 'U', 'G, R', 'G, U', 'R, U', 'G, R, U'],
|
|
||||||
['colorless', 'green', 'red', 'blue', 'simic', 'izzet', 'gruul', 'temur']),
|
|
||||||
'G, R, W': ('Naya: Green/Red/White', ['G', 'R', 'W', 'G, R', 'G, W', 'R, W', 'G, R, W'],
|
|
||||||
['colorless', 'green', 'red', 'white', 'gruul', 'selesnya', 'boros', 'naya']),
|
|
||||||
'G, U, W': ('Bant: Blue/Green/White', ['G', 'U', 'W', 'G, U', 'G, W', 'U, W', 'G, U, W'],
|
|
||||||
['colorless', 'green', 'blue', 'white', 'simic', 'azorius', 'selesnya', 'bant']),
|
|
||||||
'R, U, W': ('Jeskai: Blue/Red/White', ['R', 'U', 'W', 'R, U', 'U, W', 'R, W', 'R, U, W'],
|
|
||||||
['colorless', 'blue', 'red', 'white', 'izzet', 'azorius', 'boros', 'jeskai'])
|
|
||||||
}
|
|
||||||
|
|
||||||
OTHER_COLOR_MAP: Final[Dict[str, Tuple[str, List[str], List[str]]]] = {
|
|
||||||
'B, G, R, U': ('Glint: Black/Blue/Green/Red',
|
|
||||||
['B', 'G', 'R', 'U', 'B, G', 'B, R', 'B, U', 'G, R', 'G, U', 'R, U', 'B, G, R',
|
|
||||||
'B, G, U', 'B, R, U', 'G, R, U', 'B, G, R, U'],
|
|
||||||
['colorless', 'black', 'blue', 'green', 'red', 'golgari', 'rakdos', 'dimir',
|
|
||||||
'gruul', 'simic', 'izzet', 'jund', 'sultai', 'grixis', 'temur', 'glint']),
|
|
||||||
'B, G, R, W': ('Dune: Black/Green/Red/White',
|
|
||||||
['B', 'G', 'R', 'W', 'B, G', 'B, R', 'B, W', 'G, R', 'G, W', 'R, W', 'B, G, R',
|
|
||||||
'B, G, W', 'B, R, W', 'G, R, W', 'B, G, R, W'],
|
|
||||||
['colorless', 'black', 'green', 'red', 'white', 'golgari', 'rakdos', 'orzhov',
|
|
||||||
'gruul', 'selesnya', 'boros', 'jund', 'abzan', 'mardu', 'naya', 'dune']),
|
|
||||||
'B, G, U, W': ('Witch: Black/Blue/Green/White',
|
|
||||||
['B', 'G', 'U', 'W', 'B, G', 'B, U', 'B, W', 'G, U', 'G, W', 'U, W', 'B, G, U',
|
|
||||||
'B, G, W', 'B, U, W', 'G, U, W', 'B, G, U, W'],
|
|
||||||
['colorless', 'black', 'blue', 'green', 'white', 'golgari', 'dimir', 'orzhov',
|
|
||||||
'simic', 'selesnya', 'azorius', 'sultai', 'abzan', 'esper', 'bant', 'witch']),
|
|
||||||
'B, R, U, W': ('Yore: Black/Blue/Red/White',
|
|
||||||
['B', 'R', 'U', 'W', 'B, R', 'B, U', 'B, W', 'R, U', 'R, W', 'U, W', 'B, R, U',
|
|
||||||
'B, R, W', 'B, U, W', 'R, U, W', 'B, R, U, W'],
|
|
||||||
['colorless', 'black', 'blue', 'red', 'white', 'rakdos', 'dimir', 'orzhov',
|
|
||||||
'izzet', 'boros', 'azorius', 'grixis', 'mardu', 'esper', 'jeskai', 'yore']),
|
|
||||||
'G, R, U, W': ('Ink: Blue/Green/Red/White',
|
|
||||||
['G', 'R', 'U', 'W', 'G, R', 'G, U', 'G, W', 'R, U', 'R, W', 'U, W', 'G, R, U',
|
|
||||||
'G, R, W', 'G, U, W', 'R, U, W', 'G, R, U, W'],
|
|
||||||
['colorless', 'blue', 'green', 'red', 'white', 'gruul', 'simic', 'selesnya',
|
|
||||||
'izzet', 'boros', 'azorius', 'temur', 'naya', 'bant', 'jeskai', 'ink']),
|
|
||||||
'B, G, R, U, W': ('WUBRG: All colors',
|
|
||||||
['B', 'G', 'R', 'U', 'W', 'B, G', 'B, R', 'B, U', 'B, W', 'G, R', 'G, U',
|
|
||||||
'G, W', 'R, U', 'R, W', 'U, W', 'B, G, R', 'B, G, U', 'B, G, W', 'B, R, U',
|
|
||||||
'B, R, W', 'B, U, W', 'G, R, U', 'G, R, W', 'G, U, W', 'R, U, W',
|
|
||||||
'B, G, R, U', 'B, G, R, W', 'B, G, U, W', 'B, R, U, W', 'G, R, U, W',
|
|
||||||
'B, G, R, U, W'],
|
|
||||||
['colorless', 'black', 'green', 'red', 'blue', 'white', 'golgari', 'rakdos',
|
|
||||||
'dimir', 'orzhov', 'gruul', 'simic', 'selesnya', 'izzet', 'boros', 'azorius',
|
|
||||||
'jund', 'sultai', 'abzan', 'grixis', 'mardu', 'esper', 'temur', 'naya',
|
|
||||||
'bant', 'jeskai', 'glint', 'dune', 'witch', 'yore', 'ink', 'wubrg'])
|
|
||||||
}
|
|
||||||
|
|
||||||
# Price checking configuration
|
|
||||||
DEFAULT_PRICE_DELAY: Final[float] = 0.1 # Delay between price checks in seconds
|
|
||||||
MAX_PRICE_CHECK_ATTEMPTS: Final[int] = 3 # Maximum attempts for price checking
|
|
||||||
PRICE_CACHE_SIZE: Final[int] = 128 # Size of price check LRU cache
|
|
||||||
PRICE_CHECK_TIMEOUT: Final[int] = 30 # Timeout for price check requests in seconds
|
|
||||||
PRICE_TOLERANCE_MULTIPLIER: Final[float] = 1.1 # Multiplier for price tolerance
|
|
||||||
DEFAULT_MAX_CARD_PRICE: Final[float] = 20.0 # Default maximum price per card
|
|
||||||
|
|
||||||
# Deck composition defaults
|
|
||||||
DEFAULT_RAMP_COUNT: Final[int] = 8 # Default number of ramp pieces
|
|
||||||
DEFAULT_LAND_COUNT: Final[int] = 35 # Default total land count
|
|
||||||
DEFAULT_BASIC_LAND_COUNT: Final[int] = 20 # Default minimum basic lands
|
|
||||||
DEFAULT_NON_BASIC_LAND_SLOTS: Final[int] = 10 # Default number of non-basic land slots to reserve
|
|
||||||
DEFAULT_BASICS_PER_COLOR: Final[int] = 5 # Default number of basic lands to add per color
|
|
||||||
|
|
||||||
# Miscellaneous land configuration
|
|
||||||
MISC_LAND_MIN_COUNT: Final[int] = 5 # Minimum number of miscellaneous lands to add
|
|
||||||
MISC_LAND_MAX_COUNT: Final[int] = 10 # Maximum number of miscellaneous lands to add
|
|
||||||
MISC_LAND_POOL_SIZE: Final[int] = 100 # Maximum size of initial land pool to select from
|
|
||||||
|
|
||||||
# Default fetch land count
|
|
||||||
FETCH_LAND_DEFAULT_COUNT: Final[int] = 3 # Default number of fetch lands to include
|
|
||||||
|
|
||||||
# Basic Lands
|
|
||||||
BASIC_LANDS = ['Plains', 'Island', 'Swamp', 'Mountain', 'Forest']
|
|
||||||
|
|
||||||
# Basic land mappings
|
|
||||||
COLOR_TO_BASIC_LAND: Final[Dict[str, str]] = {
|
|
||||||
'W': 'Plains',
|
|
||||||
'U': 'Island',
|
|
||||||
'B': 'Swamp',
|
|
||||||
'R': 'Mountain',
|
|
||||||
'G': 'Forest',
|
|
||||||
'C': 'Wastes'
|
|
||||||
}
|
|
||||||
|
|
||||||
# Dual land type mappings
|
|
||||||
DUAL_LAND_TYPE_MAP: Final[Dict[str, str]] = {
|
|
||||||
'azorius': 'Plains Island',
|
|
||||||
'dimir': 'Island Swamp',
|
|
||||||
'rakdos': 'Swamp Mountain',
|
|
||||||
'gruul': 'Mountain Forest',
|
|
||||||
'selesnya': 'Forest Plains',
|
|
||||||
'orzhov': 'Plains Swamp',
|
|
||||||
'golgari': 'Swamp Forest',
|
|
||||||
'simic': 'Forest Island',
|
|
||||||
'izzet': 'Island Mountain',
|
|
||||||
'boros': 'Mountain Plains'
|
|
||||||
}
|
|
||||||
|
|
||||||
# Triple land type mappings
|
|
||||||
TRIPLE_LAND_TYPE_MAP: Final[Dict[str, str]] = {
|
|
||||||
'bant': 'Forest Plains Island',
|
|
||||||
'esper': 'Plains Island Swamp',
|
|
||||||
'grixis': 'Island Swamp Mountain',
|
|
||||||
'jund': 'Swamp Mountain Forest',
|
|
||||||
'naya': 'Mountain Forest Plains',
|
|
||||||
'mardu': 'Mountain Plains Swamp',
|
|
||||||
'abzan': 'Plains Swamp Forest',
|
|
||||||
'sultai': 'Swamp Forest Island',
|
|
||||||
'temur': 'Forest Island Mountain',
|
|
||||||
'jeskai': 'Island Mountain Plains'
|
|
||||||
}
|
|
||||||
|
|
||||||
# Default preference for including dual lands
|
|
||||||
DEFAULT_DUAL_LAND_ENABLED: Final[bool] = True
|
|
||||||
|
|
||||||
# Default preference for including triple lands
|
|
||||||
DEFAULT_TRIPLE_LAND_ENABLED: Final[bool] = True
|
|
||||||
|
|
||||||
SNOW_COVERED_BASIC_LANDS: Final[Dict[str, str]] = {
|
|
||||||
'W': 'Snow-Covered Plains',
|
|
||||||
'U': 'Snow-Covered Island',
|
|
||||||
'B': 'Snow-Covered Swamp',
|
|
||||||
'G': 'Snow-Covered Forest'
|
|
||||||
}
|
|
||||||
|
|
||||||
SNOW_BASIC_LAND_MAPPING: Final[Dict[str, str]] = {
|
|
||||||
'W': 'Snow-Covered Plains',
|
|
||||||
'U': 'Snow-Covered Island',
|
|
||||||
'B': 'Snow-Covered Swamp',
|
|
||||||
'R': 'Snow-Covered Mountain',
|
|
||||||
'G': 'Snow-Covered Forest',
|
|
||||||
'C': 'Wastes' # Note: No snow-covered version exists for Wastes
|
|
||||||
}
|
|
||||||
|
|
||||||
# Generic fetch lands list
|
|
||||||
GENERIC_FETCH_LANDS: Final[List[str]] = [
|
|
||||||
'Evolving Wilds',
|
|
||||||
'Terramorphic Expanse',
|
|
||||||
'Shire Terrace',
|
|
||||||
'Escape Tunnel',
|
|
||||||
'Promising Vein',
|
|
||||||
'Myriad Landscape',
|
|
||||||
'Fabled Passage',
|
|
||||||
'Terminal Moraine',
|
|
||||||
'Prismatic Vista'
|
|
||||||
]
|
|
||||||
|
|
||||||
# Kindred land constants
|
|
||||||
KINDRED_STAPLE_LANDS: Final[List[Dict[str, str]]] = [
|
|
||||||
{
|
|
||||||
'name': 'Path of Ancestry',
|
|
||||||
'type': 'Land'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'Three Tree City',
|
|
||||||
'type': 'Legendary Land'
|
|
||||||
},
|
|
||||||
{'name': 'Cavern of Souls', 'type': 'Land'}
|
|
||||||
]
|
|
||||||
|
|
||||||
# Color-specific fetch land mappings
|
|
||||||
COLOR_TO_FETCH_LANDS: Final[Dict[str, List[str]]] = {
|
|
||||||
'W': [
|
|
||||||
'Flooded Strand',
|
|
||||||
'Windswept Heath',
|
|
||||||
'Marsh Flats',
|
|
||||||
'Arid Mesa',
|
|
||||||
'Brokers Hideout',
|
|
||||||
'Obscura Storefront',
|
|
||||||
'Cabaretti Courtyard'
|
|
||||||
],
|
|
||||||
'U': [
|
|
||||||
'Flooded Strand',
|
|
||||||
'Polluted Delta',
|
|
||||||
'Scalding Tarn',
|
|
||||||
'Misty Rainforest',
|
|
||||||
'Brokers Hideout',
|
|
||||||
'Obscura Storefront',
|
|
||||||
'Maestros Theater'
|
|
||||||
],
|
|
||||||
'B': [
|
|
||||||
'Polluted Delta',
|
|
||||||
'Bloodstained Mire',
|
|
||||||
'Marsh Flats',
|
|
||||||
'Verdant Catacombs',
|
|
||||||
'Obscura Storefront',
|
|
||||||
'Maestros Theater',
|
|
||||||
'Riveteers Overlook'
|
|
||||||
],
|
|
||||||
'R': [
|
|
||||||
'Bloodstained Mire',
|
|
||||||
'Wooded Foothills',
|
|
||||||
'Scalding Tarn',
|
|
||||||
'Arid Mesa',
|
|
||||||
'Maestros Theater',
|
|
||||||
'Riveteers Overlook',
|
|
||||||
'Cabaretti Courtyard'
|
|
||||||
],
|
|
||||||
'G': [
|
|
||||||
'Wooded Foothills',
|
|
||||||
'Windswept Heath',
|
|
||||||
'Verdant Catacombs',
|
|
||||||
'Misty Rainforest',
|
|
||||||
'Brokers Hideout',
|
|
||||||
'Riveteers Overlook',
|
|
||||||
'Cabaretti Courtyard'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Staple land conditions mapping
|
|
||||||
STAPLE_LAND_CONDITIONS: Final[Dict[str, Callable[[List[str], List[str], int], bool]]] = {
|
|
||||||
'Reliquary Tower': lambda commander_tags, colors, commander_power: True, # Always include
|
|
||||||
'Ash Barrens': lambda commander_tags, colors, commander_power: 'Landfall' not in commander_tags,
|
|
||||||
'Command Tower': lambda commander_tags, colors, commander_power: len(colors) > 1,
|
|
||||||
'Exotic Orchard': lambda commander_tags, colors, commander_power: len(colors) > 1,
|
|
||||||
'War Room': lambda commander_tags, colors, commander_power: len(colors) <= 2,
|
|
||||||
'Rogue\'s Passage': lambda commander_tags, colors, commander_power: commander_power >= 5
|
|
||||||
}
|
|
||||||
|
|
||||||
# Constants for land removal functionality
|
|
||||||
LAND_REMOVAL_MAX_ATTEMPTS: Final[int] = 3
|
|
||||||
|
|
||||||
# Protected lands that cannot be removed during land removal process
|
|
||||||
PROTECTED_LANDS: Final[List[str]] = BASIC_LANDS + [land['name'] for land in KINDRED_STAPLE_LANDS]
|
|
||||||
|
|
||||||
# Other defaults
|
|
||||||
DEFAULT_CREATURE_COUNT: Final[int] = 25 # Default number of creatures
|
|
||||||
DEFAULT_REMOVAL_COUNT: Final[int] = 10 # Default number of spot removal spells
|
|
||||||
DEFAULT_WIPES_COUNT: Final[int] = 2 # Default number of board wipes
|
|
||||||
|
|
||||||
DEFAULT_CARD_ADVANTAGE_COUNT: Final[int] = 10 # Default number of card advantage pieces
|
|
||||||
DEFAULT_PROTECTION_COUNT: Final[int] = 8 # Default number of protection spells
|
|
||||||
|
|
||||||
# Deck composition prompts
|
|
||||||
DECK_COMPOSITION_PROMPTS: Final[Dict[str, str]] = {
|
|
||||||
'ramp': 'Enter desired number of ramp pieces (default: 8):',
|
|
||||||
'lands': 'Enter desired number of total lands (default: 35):',
|
|
||||||
'basic_lands': 'Enter minimum number of basic lands (default: 20):',
|
|
||||||
'creatures': 'Enter desired number of creatures (default: 25):',
|
|
||||||
'removal': 'Enter desired number of spot removal spells (default: 10):',
|
|
||||||
'wipes': 'Enter desired number of board wipes (default: 2):',
|
|
||||||
'card_advantage': 'Enter desired number of card advantage pieces (default: 10):',
|
|
||||||
'protection': 'Enter desired number of protection spells (default: 8):',
|
|
||||||
'max_deck_price': 'Enter maximum total deck price in dollars (default: 400.0):',
|
|
||||||
'max_card_price': 'Enter maximum price per card in dollars (default: 20.0):'
|
|
||||||
}
|
|
||||||
DEFAULT_MAX_DECK_PRICE: Final[float] = 400.0 # Default maximum total deck price
|
|
||||||
BATCH_PRICE_CHECK_SIZE: Final[int] = 50 # Number of cards to check prices for in one batch
|
|
||||||
# Constants for input validation
|
|
||||||
|
|
||||||
# Type aliases
|
|
||||||
CardName = str
|
|
||||||
CardType = str
|
|
||||||
ThemeTag = str
|
|
||||||
ColorIdentity = str
|
|
||||||
ColorList = List[str]
|
|
||||||
ColorInfo = Tuple[str, List[str], List[str]]
|
|
||||||
|
|
||||||
INPUT_VALIDATION = {
|
|
||||||
'max_attempts': 3,
|
|
||||||
'default_text_message': 'Please enter a valid text response.',
|
|
||||||
'default_number_message': 'Please enter a valid number.',
|
|
||||||
'default_confirm_message': 'Please enter Y/N or Yes/No.',
|
|
||||||
'default_choice_message': 'Please select a valid option from the list.'
|
|
||||||
}
|
|
||||||
|
|
||||||
QUESTION_TYPES = [
|
|
||||||
'Text',
|
|
||||||
'Number',
|
|
||||||
'Confirm',
|
|
||||||
'Choice'
|
|
||||||
]
|
|
||||||
|
|
||||||
# Constants for theme weight management and selection
|
|
||||||
# Multiplier for initial card pool size during theme-based selection
|
|
||||||
THEME_POOL_SIZE_MULTIPLIER: Final[float] = 2.0
|
|
||||||
|
|
||||||
# Bonus multiplier for cards that match multiple deck themes
|
|
||||||
THEME_PRIORITY_BONUS: Final[float] = 1.2
|
|
||||||
|
|
||||||
# Safety multiplier to avoid overshooting target counts
|
|
||||||
THEME_WEIGHT_MULTIPLIER: Final[float] = 0.9
|
|
||||||
|
|
||||||
THEME_WEIGHTS_DEFAULT: Final[Dict[str, float]] = {
|
|
||||||
'primary': 1.0,
|
|
||||||
'secondary': 0.6,
|
|
||||||
'tertiary': 0.3,
|
|
||||||
'hidden': 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
WEIGHT_ADJUSTMENT_FACTORS: Final[Dict[str, float]] = {
|
|
||||||
'kindred_primary': 1.5, # Boost for Kindred themes as primary
|
|
||||||
'kindred_secondary': 1.3, # Boost for Kindred themes as secondary
|
|
||||||
'kindred_tertiary': 1.2, # Boost for Kindred themes as tertiary
|
|
||||||
'theme_synergy': 1.2 # Boost for themes that work well together
|
|
||||||
}
|
|
||||||
|
|
||||||
DEFAULT_THEME_TAGS = [
|
|
||||||
'Aggro', 'Aristocrats', 'Artifacts Matter', 'Big Mana', 'Blink',
|
|
||||||
'Board Wipes', 'Burn', 'Cantrips', 'Card Draw', 'Clones',
|
|
||||||
'Combat Matters', 'Control', 'Counters Matter', 'Energy',
|
|
||||||
'Enter the Battlefield', 'Equipment', 'Exile Matters', 'Infect',
|
|
||||||
'Interaction', 'Lands Matter', 'Leave the Battlefield', 'Legends Matter',
|
|
||||||
'Life Matters', 'Mill', 'Monarch', 'Protection', 'Ramp', 'Reanimate',
|
|
||||||
'Removal', 'Sacrifice Matters', 'Spellslinger', 'Stax', 'Super Friends',
|
|
||||||
'Theft', 'Token Creation', 'Tokens Matter', 'Voltron', 'X Spells'
|
|
||||||
]
|
|
||||||
|
|
||||||
# CSV processing configuration
|
|
||||||
CSV_READ_TIMEOUT: Final[int] = 30 # Timeout in seconds for CSV read operations
|
|
||||||
CSV_PROCESSING_BATCH_SIZE: Final[int] = 1000 # Number of rows to process in each batch
|
|
||||||
|
|
||||||
# CSV validation configuration
|
|
||||||
CSV_VALIDATION_RULES: Final[Dict[str, Dict[str, Union[str, int, float]]]] = {
|
|
||||||
'name': {'type': ('str', 'object'), 'required': True, 'unique': True},
|
|
||||||
'edhrecRank': {'type': ('str', 'int', 'float', 'object'), 'min': 0, 'max': 100000},
|
|
||||||
'manaValue': {'type': ('str', 'int', 'float', 'object'), 'min': 0, 'max': 20},
|
|
||||||
'power': {'type': ('str', 'int', 'float', 'object'), 'pattern': r'^[\d*+-]+$'},
|
|
||||||
'toughness': {'type': ('str', 'int', 'float', 'object'), 'pattern': r'^[\d*+-]+$'}
|
|
||||||
}
|
|
||||||
|
|
||||||
# (CSV_REQUIRED_COLUMNS imported from settings to avoid duplication)
|
|
||||||
|
|
||||||
# DataFrame processing configuration
|
|
||||||
BATCH_SIZE: Final[int] = 1000 # Number of records to process at once
|
|
||||||
DATAFRAME_BATCH_SIZE: Final[int] = 500 # Batch size for DataFrame operations
|
|
||||||
TRANSFORM_BATCH_SIZE: Final[int] = 250 # Batch size for data transformations
|
|
||||||
CSV_DOWNLOAD_TIMEOUT: Final[int] = 30 # Timeout in seconds for CSV downloads
|
|
||||||
PROGRESS_UPDATE_INTERVAL: Final[int] = 100 # Number of records between progress updates
|
|
||||||
|
|
||||||
# DataFrame operation timeouts
|
|
||||||
DATAFRAME_READ_TIMEOUT: Final[int] = 30 # Timeout for DataFrame read operations
|
|
||||||
DATAFRAME_WRITE_TIMEOUT: Final[int] = 30 # Timeout for DataFrame write operations
|
|
||||||
DATAFRAME_TRANSFORM_TIMEOUT: Final[int] = 45 # Timeout for DataFrame transformations
|
|
||||||
DATAFRAME_VALIDATION_TIMEOUT: Final[int] = 20 # Timeout for DataFrame validation
|
|
||||||
|
|
||||||
# Required DataFrame columns
|
|
||||||
DATAFRAME_REQUIRED_COLUMNS: Final[List[str]] = [
|
|
||||||
'name', 'type', 'colorIdentity', 'manaValue', 'text',
|
|
||||||
'edhrecRank', 'themeTags', 'keywords'
|
|
||||||
]
|
|
||||||
|
|
||||||
# DataFrame validation rules
|
|
||||||
DATAFRAME_VALIDATION_RULES: Final[Dict[str, Dict[str, Union[str, int, float, bool]]]] = {
|
|
||||||
'name': {'type': ('str', 'object'), 'required': True, 'unique': True},
|
|
||||||
'edhrecRank': {'type': ('str', 'int', 'float', 'object'), 'min': 0, 'max': 100000},
|
|
||||||
'manaValue': {'type': ('str', 'int', 'float', 'object'), 'min': 0, 'max': 20},
|
|
||||||
'power': {'type': ('str', 'int', 'float', 'object'), 'pattern': r'^[\d*+-]+$'},
|
|
||||||
'toughness': {'type': ('str', 'int', 'float', 'object'), 'pattern': r'^[\d*+-]+$'},
|
|
||||||
'colorIdentity': {'type': ('str', 'object'), 'required': True},
|
|
||||||
'text': {'type': ('str', 'object'), 'required': False}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Card type sorting order for organizing libraries
|
|
||||||
# This constant defines the order in which different card types should be sorted
|
|
||||||
# when organizing a deck library. The order is designed to group cards logically,
|
|
||||||
# starting with Planeswalkers and ending with Lands.
|
|
||||||
CARD_TYPE_SORT_ORDER: Final[List[str]] = [
|
|
||||||
'Planeswalker', 'Battle', 'Creature', 'Instant', 'Sorcery',
|
|
||||||
'Artifact', 'Enchantment', 'Land'
|
|
||||||
]
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,143 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# Diagnostic script to debug Docker volume mounting issues
|
|
||||||
|
|
||||||
echo "=== MTG Deckbuilder Volume Mount Diagnostics ==="
|
|
||||||
echo "Date: $(date)"
|
|
||||||
echo "User: $(whoami)"
|
|
||||||
echo "Working Directory: $(pwd)"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Check if Docker is working
|
|
||||||
echo "=== Docker Info ==="
|
|
||||||
docker --version
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Check host directories
|
|
||||||
echo "=== Host Directory Check ==="
|
|
||||||
echo "Current directory contents:"
|
|
||||||
ls -la
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Checking for data directories:"
|
|
||||||
for dir in deck_files logs csv_files; do
|
|
||||||
if [ -d "$dir" ]; then
|
|
||||||
echo "✓ $dir exists"
|
|
||||||
echo " Permissions: $(ls -ld $dir | awk '{print $1, $3, $4}')"
|
|
||||||
echo " Contents: $(ls -la $dir | wc -l) items"
|
|
||||||
if [ "$(ls -A $dir)" ]; then
|
|
||||||
echo " Files: $(ls -A $dir | head -3)"
|
|
||||||
else
|
|
||||||
echo " (empty)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "✗ $dir missing"
|
|
||||||
echo " Creating..."
|
|
||||||
mkdir -p "$dir"
|
|
||||||
chmod 755 "$dir"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
done
|
|
||||||
|
|
||||||
# Test basic Docker volume mounting
|
|
||||||
echo "=== Docker Volume Mount Test ==="
|
|
||||||
echo "Testing if Docker can write to host directories..."
|
|
||||||
|
|
||||||
docker run --rm \
|
|
||||||
-v "$(pwd)/deck_files:/test/deck_files" \
|
|
||||||
-v "$(pwd)/logs:/test/logs" \
|
|
||||||
-v "$(pwd)/csv_files:/test/csv_files" \
|
|
||||||
alpine:latest /bin/sh -c "
|
|
||||||
echo 'Container test started'
|
|
||||||
echo 'Working directory: \$(pwd)'
|
|
||||||
echo 'Mount points:'
|
|
||||||
ls -la /test/
|
|
||||||
echo ''
|
|
||||||
echo 'Testing file creation:'
|
|
||||||
echo 'test-$(date +%s)' > /test/deck_files/docker-test.txt
|
|
||||||
echo 'test-$(date +%s)' > /test/logs/docker-test.log
|
|
||||||
echo 'test-$(date +%s)' > /test/csv_files/docker-test.csv
|
|
||||||
echo 'Files created in container'
|
|
||||||
ls -la /test/*/docker-test.*
|
|
||||||
"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "=== Host File Check After Docker Test ==="
|
|
||||||
echo "Checking if files were created on host:"
|
|
||||||
for dir in deck_files logs csv_files; do
|
|
||||||
echo "$dir:"
|
|
||||||
if [ -f "$dir/docker-test.txt" ] || [ -f "$dir/docker-test.log" ] || [ -f "$dir/docker-test.csv" ]; then
|
|
||||||
ls -la "$dir"/docker-test.*
|
|
||||||
else
|
|
||||||
echo " No test files found"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Test with the actual MTG image
|
|
||||||
echo ""
|
|
||||||
echo "=== MTG Deckbuilder Container Test ==="
|
|
||||||
echo "Testing with actual MTG deckbuilder image..."
|
|
||||||
|
|
||||||
# First check if image exists
|
|
||||||
if docker images | grep -q mtg-deckbuilder; then
|
|
||||||
echo "MTG deckbuilder image found"
|
|
||||||
|
|
||||||
docker run --rm \
|
|
||||||
-v "$(pwd)/deck_files:/app/deck_files" \
|
|
||||||
-v "$(pwd)/logs:/app/logs" \
|
|
||||||
-v "$(pwd)/csv_files:/app/csv_files" \
|
|
||||||
mtg-deckbuilder /bin/bash -c "
|
|
||||||
echo 'MTG Container test'
|
|
||||||
echo 'Working directory: \$(pwd)'
|
|
||||||
echo 'Python path: \$(which python)'
|
|
||||||
echo 'App directory contents:'
|
|
||||||
ls -la /app/
|
|
||||||
echo ''
|
|
||||||
echo 'Mount point permissions:'
|
|
||||||
ls -la /app/deck_files /app/logs /app/csv_files
|
|
||||||
echo ''
|
|
||||||
echo 'Testing file creation:'
|
|
||||||
echo 'mtg-test-$(date +%s)' > /app/deck_files/mtg-test.txt
|
|
||||||
echo 'mtg-test-$(date +%s)' > /app/logs/mtg-test.log
|
|
||||||
echo 'mtg-test-$(date +%s)' > /app/csv_files/mtg-test.csv
|
|
||||||
echo 'MTG test files created'
|
|
||||||
|
|
||||||
# Try to run a quick Python test
|
|
||||||
cd /app/code
|
|
||||||
python -c 'import os; print(\"Python can access:\", os.listdir(\"/app\"))'
|
|
||||||
python -c '
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
print(\"Testing Path operations:\")
|
|
||||||
deck_path = Path(\"/app/deck_files\")
|
|
||||||
print(f\"Deck path exists: {deck_path.exists()}\")
|
|
||||||
print(f\"Deck path writable: {os.access(deck_path, os.W_OK)}\")
|
|
||||||
test_file = deck_path / \"python-test.txt\"
|
|
||||||
try:
|
|
||||||
test_file.write_text(\"Python test\")
|
|
||||||
print(f\"Python write successful: {test_file.read_text()}\")
|
|
||||||
except Exception as e:
|
|
||||||
print(f\"Python write failed: {e}\")
|
|
||||||
'
|
|
||||||
"
|
|
||||||
else
|
|
||||||
echo "MTG deckbuilder image not found. Building..."
|
|
||||||
docker build -t mtg-deckbuilder .
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "=== Final Host Check ==="
|
|
||||||
echo "Files in host directories after all tests:"
|
|
||||||
for dir in deck_files logs csv_files; do
|
|
||||||
echo "$dir:"
|
|
||||||
ls -la "$dir"/ 2>/dev/null || echo " Directory empty or inaccessible"
|
|
||||||
echo ""
|
|
||||||
done
|
|
||||||
|
|
||||||
# Cleanup test files
|
|
||||||
echo "=== Cleanup ==="
|
|
||||||
echo "Removing test files..."
|
|
||||||
rm -f deck_files/docker-test.* logs/docker-test.* csv_files/docker-test.*
|
|
||||||
rm -f deck_files/mtg-test.* logs/mtg-test.* csv_files/mtg-test.*
|
|
||||||
rm -f deck_files/python-test.* logs/python-test.* csv_files/python-test.*
|
|
||||||
|
|
||||||
echo "Diagnostics complete!"
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
services:
|
|
||||||
mtg-deckbuilder-interactive:
|
|
||||||
build: .
|
|
||||||
container_name: mtg-deckbuilder-interactive
|
|
||||||
stdin_open: true
|
|
||||||
tty: true
|
|
||||||
volumes:
|
|
||||||
- ${PWD}/deck_files:/app/deck_files
|
|
||||||
- ${PWD}/logs:/app/logs
|
|
||||||
- ${PWD}/csv_files:/app/csv_files
|
|
||||||
environment:
|
|
||||||
- PYTHONUNBUFFERED=1
|
|
||||||
- TERM=xterm-256color
|
|
||||||
- DEBIAN_FRONTEND=noninteractive
|
|
||||||
# Don't restart automatically
|
|
||||||
restart: "no"
|
|
||||||
|
|
@ -22,7 +22,7 @@ classifiers = [
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
]
|
]
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.13" # This is what it was built with anyway
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pandas>=1.5.0",
|
"pandas>=1.5.0",
|
||||||
"inquirer>=3.1.3",
|
"inquirer>=3.1.3",
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# Simple MTG Deckbuilder runner with proper interactivity
|
|
||||||
|
|
||||||
echo "MTG Deckbuilder - Quick Start"
|
|
||||||
echo "=============================="
|
|
||||||
|
|
||||||
# Colors
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
# Create directories if they don't exist
|
|
||||||
echo -e "${GREEN}Setting up directories...${NC}"
|
|
||||||
mkdir -p deck_files logs csv_files
|
|
||||||
|
|
||||||
# Check which compose command is available
|
|
||||||
if command -v docker-compose &> /dev/null; then
|
|
||||||
COMPOSE_CMD="docker-compose"
|
|
||||||
elif docker compose version &> /dev/null; then
|
|
||||||
COMPOSE_CMD="docker compose"
|
|
||||||
else
|
|
||||||
echo "Error: Neither docker-compose nor 'docker compose' is available"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "${GREEN}Using: $COMPOSE_CMD${NC}"
|
|
||||||
echo -e "${YELLOW}Starting MTG Deckbuilder...${NC}"
|
|
||||||
echo "Press Ctrl+C to exit when done"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Run with the interactive compose file
|
|
||||||
$COMPOSE_CMD -f docker-compose.interactive.yml run --rm mtg-deckbuilder-interactive
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue