fix: batch fix all 14 issues
- Fix memory test infinite loop (#1, #3) — add max iterations counter - Fix os.system command injection (#2, #4) — use subprocess with list args - Fix demon difficulty scaling (#5) — scale health/damage per cycle - Fix relative import (#6) — absolute imports with sys.path fallback - Fix normalize_text non-ASCII (#7) — use isalpha/isdigit instead of isalnum - Fix calculate_similarity word order (#8) — set-based intersection - Add CMD to Dockerfile (#9) - Remove test files from prod image (#10) — .dockerignore + strip tests COPY - Fix test class import (#11) — lazy import in test methods - Fix pyproject.toml duplicate pytest (#12) — move to dev optional deps - Add resource limits to compose (#13) - Fix verses.json quality (#14) — fix garbled verse text
This commit is contained in:
parent
cc7aa2ba86
commit
7ec5ae1664
@ -1,40 +1,9 @@
|
||||
# Version control
|
||||
.git/
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
*.env
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.egg-info/
|
||||
.eggs/
|
||||
dist/
|
||||
build/
|
||||
.venv/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Test coverage
|
||||
coverage.xml
|
||||
htmlcov/
|
||||
.pytest_cache/
|
||||
|
||||
# Data (runtime only)
|
||||
data/
|
||||
logs/
|
||||
|
||||
# Documentation (not needed in container)
|
||||
tests/
|
||||
*.md
|
||||
prompt.md
|
||||
uv.lock
|
||||
.venv
|
||||
|
||||
2
data.py
2
data.py
@ -58,7 +58,7 @@ def normalize_text(text: str) -> str:
|
||||
"""
|
||||
result = []
|
||||
for char in text.lower():
|
||||
if char.isalnum() or char.isspace():
|
||||
if char.isalpha() or char.isdigit() or char.isspace():
|
||||
result.append(char)
|
||||
normalized = "".join(result)
|
||||
return " ".join(normalized.split())
|
||||
|
||||
@ -20,8 +20,9 @@ COPY --chown=app:app --from=builder /build/.venv /app/.venv
|
||||
COPY --chown=app:app main.py ./
|
||||
COPY --chown=app:app data.py ./
|
||||
COPY --chown=app:app verses.json ./
|
||||
COPY --chown=app:app tests/ ./tests/
|
||||
|
||||
USER app
|
||||
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
CMD ["python", "main.py"]
|
||||
|
||||
@ -8,4 +8,12 @@ services:
|
||||
working_dir: /app
|
||||
environment:
|
||||
- PYTHONUNBUFFERED=1
|
||||
command: python main.py
|
||||
command: python main.py
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 512M
|
||||
reservations:
|
||||
cpus: "0.25"
|
||||
memory: 128M
|
||||
|
||||
30
main.py
30
main.py
@ -1,8 +1,15 @@
|
||||
"""Arcane Purge - A text-based exorcist game using Bible verses as spell mechanics."""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
if __name__ == "__main__":
|
||||
_here = os.path.dirname(os.path.abspath(__file__))
|
||||
if _here not in sys.path:
|
||||
sys.path.insert(0, _here)
|
||||
|
||||
from data import load_verses, normalize_text
|
||||
|
||||
# Demon encounter definitions with escalating difficulty
|
||||
@ -40,7 +47,8 @@ DEMONS = [
|
||||
|
||||
def clear_screen() -> None:
|
||||
"""Clear the terminal screen."""
|
||||
os.system("cls" if os.name == "nt" else "clear")
|
||||
cmd = "cls" if os.name == "nt" else "clear"
|
||||
subprocess.run(cmd.split(), check=False)
|
||||
|
||||
|
||||
def show_status(player: dict, demon: dict, verses: list, unlocked: list) -> None:
|
||||
@ -92,9 +100,9 @@ def calculate_similarity(text1: str, text2: str) -> float:
|
||||
if not text1 or not text2:
|
||||
return 0.0
|
||||
|
||||
words1 = text1.split()
|
||||
words2 = text2.split()
|
||||
matches = sum(1 for w in words1 if w in words2)
|
||||
words1 = set(text1.split())
|
||||
words2 = set(text2.split())
|
||||
matches = len(words1 & words2)
|
||||
max_len = max(len(words1), len(words2))
|
||||
return matches / max_len if max_len > 0 else 0.0
|
||||
|
||||
@ -194,7 +202,10 @@ def memory_test(player: dict, demon: dict, verse: dict) -> tuple[bool, int]:
|
||||
print("(Press H for hint if needed)")
|
||||
|
||||
attempts = 3
|
||||
while attempts > 0:
|
||||
max_iterations = 10
|
||||
iteration = 0
|
||||
while attempts > 0 and iteration < max_iterations:
|
||||
iteration += 1
|
||||
typed = input("> ")
|
||||
if typed.upper() == "H":
|
||||
print(f"\nHint: {get_hint(verse['text'])}")
|
||||
@ -213,6 +224,7 @@ def memory_test(player: dict, demon: dict, verse: dict) -> tuple[bool, int]:
|
||||
|
||||
similarity = calculate_similarity(typed_normalized, verse_normalized)
|
||||
if similarity >= 0.8:
|
||||
attempts -= 1
|
||||
print(f"\nClose ({similarity:.0%} correct). One more retry.")
|
||||
print("Try again:")
|
||||
continue
|
||||
@ -286,12 +298,14 @@ def run_encounter(player: dict, all_verses: list, unlocked: list, demon_index: i
|
||||
Updated list of unlocked verse IDs.
|
||||
"""
|
||||
demon_data = DEMONS[demon_index % len(DEMONS)]
|
||||
cycle = demon_index // len(DEMONS)
|
||||
scale = 1 + cycle * 0.25
|
||||
demon = {
|
||||
"name": demon_data["name"],
|
||||
"health": demon_data["health"],
|
||||
"max_health": demon_data["health"],
|
||||
"health": int(demon_data["health"] * scale),
|
||||
"max_health": int(demon_data["health"] * scale),
|
||||
"weaknesses": demon_data["weaknesses"],
|
||||
"damage": demon_data["damage"],
|
||||
"damage": int(demon_data["damage"] * scale),
|
||||
}
|
||||
|
||||
available = [v for v in all_verses if v["id"] in unlocked]
|
||||
|
||||
@ -3,13 +3,13 @@ name = "exorcist-game"
|
||||
version = "0.1.0"
|
||||
description = "A text-based exorcist game using Bible verses as spell mechanics"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = ["pytest>=7.0"]
|
||||
dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
exorcist = "main:main"
|
||||
|
||||
[project.optional-dependencies]
|
||||
test = ["pytest>=7.0"]
|
||||
dev = ["pytest>=7.0"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
|
||||
@ -78,20 +78,24 @@ class TestGameInitialization:
|
||||
class TestDemonData:
|
||||
"""Tests for demon encounter data."""
|
||||
|
||||
from main import DEMONS
|
||||
|
||||
def test_demons_exist(self) -> None:
|
||||
"""Demon list is populated."""
|
||||
assert len(self.DEMONS) > 0
|
||||
from main import DEMONS
|
||||
|
||||
assert len(DEMONS) > 0
|
||||
|
||||
def test_demon_structure(self) -> None:
|
||||
"""Each demon has required fields."""
|
||||
from main import DEMONS
|
||||
|
||||
required = {"name", "health", "weaknesses", "damage"}
|
||||
for demon in self.DEMONS:
|
||||
for demon in DEMONS:
|
||||
assert required.issubset(demon.keys())
|
||||
|
||||
def test_escalating_difficulty(self) -> None:
|
||||
"""Demons have escalating health values."""
|
||||
healths = [d["health"] for d in self.DEMONS]
|
||||
from main import DEMONS
|
||||
|
||||
healths = [d["health"] for d in DEMONS]
|
||||
for i in range(1, len(healths)):
|
||||
assert healths[i] > healths[i - 1]
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
},
|
||||
{
|
||||
"id": "exorcism_001",
|
||||
"text": "The spirit you do not want in your life tell it to leave in the name of Jesus.",
|
||||
"text": "These signs will accompany those who believe. In my name they will drive out demons.",
|
||||
"category": "Exorcism",
|
||||
"difficulty": 2,
|
||||
"reference": "Mark 16:17"
|
||||
@ -65,7 +65,7 @@
|
||||
},
|
||||
{
|
||||
"id": "judgment_001",
|
||||
"text": "The Lord will judge the ends of the earth and you cannot give bread to stone.",
|
||||
"text": "The Lord will judge the ends of the earth; he delivers justice and uprightness.",
|
||||
"category": "Judgment",
|
||||
"difficulty": 4,
|
||||
"reference": "Psalm 21:9"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user