Merge branch 'fix/all'
This commit is contained in:
commit
020603d173
@ -1,40 +1,9 @@
|
|||||||
# Version control
|
__pycache__
|
||||||
.git/
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.git
|
||||||
.gitignore
|
.gitignore
|
||||||
|
tests/
|
||||||
# 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)
|
|
||||||
*.md
|
*.md
|
||||||
prompt.md
|
uv.lock
|
||||||
|
.venv
|
||||||
|
|||||||
2
data.py
2
data.py
@ -58,7 +58,7 @@ def normalize_text(text: str) -> str:
|
|||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
for char in text.lower():
|
for char in text.lower():
|
||||||
if char.isalnum() or char.isspace():
|
if char.isalpha() or char.isdigit() or char.isspace():
|
||||||
result.append(char)
|
result.append(char)
|
||||||
normalized = "".join(result)
|
normalized = "".join(result)
|
||||||
return " ".join(normalized.split())
|
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 main.py ./
|
||||||
COPY --chown=app:app data.py ./
|
COPY --chown=app:app data.py ./
|
||||||
COPY --chown=app:app verses.json ./
|
COPY --chown=app:app verses.json ./
|
||||||
COPY --chown=app:app tests/ ./tests/
|
|
||||||
|
|
||||||
USER app
|
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
|
working_dir: /app
|
||||||
environment:
|
environment:
|
||||||
- PYTHONUNBUFFERED=1
|
- 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."""
|
"""Arcane Purge - A text-based exorcist game using Bible verses as spell mechanics."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
import time
|
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
|
from data import load_verses, normalize_text
|
||||||
|
|
||||||
# Demon encounter definitions with escalating difficulty
|
# Demon encounter definitions with escalating difficulty
|
||||||
@ -40,7 +47,8 @@ DEMONS = [
|
|||||||
|
|
||||||
def clear_screen() -> None:
|
def clear_screen() -> None:
|
||||||
"""Clear the terminal screen."""
|
"""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:
|
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:
|
if not text1 or not text2:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
words1 = text1.split()
|
words1 = set(text1.split())
|
||||||
words2 = text2.split()
|
words2 = set(text2.split())
|
||||||
matches = sum(1 for w in words1 if w in words2)
|
matches = len(words1 & words2)
|
||||||
max_len = max(len(words1), len(words2))
|
max_len = max(len(words1), len(words2))
|
||||||
return matches / max_len if max_len > 0 else 0.0
|
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)")
|
print("(Press H for hint if needed)")
|
||||||
|
|
||||||
attempts = 3
|
attempts = 3
|
||||||
while attempts > 0:
|
max_iterations = 10
|
||||||
|
iteration = 0
|
||||||
|
while attempts > 0 and iteration < max_iterations:
|
||||||
|
iteration += 1
|
||||||
typed = input("> ")
|
typed = input("> ")
|
||||||
if typed.upper() == "H":
|
if typed.upper() == "H":
|
||||||
print(f"\nHint: {get_hint(verse['text'])}")
|
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)
|
similarity = calculate_similarity(typed_normalized, verse_normalized)
|
||||||
if similarity >= 0.8:
|
if similarity >= 0.8:
|
||||||
|
attempts -= 1
|
||||||
print(f"\nClose ({similarity:.0%} correct). One more retry.")
|
print(f"\nClose ({similarity:.0%} correct). One more retry.")
|
||||||
print("Try again:")
|
print("Try again:")
|
||||||
continue
|
continue
|
||||||
@ -286,12 +298,14 @@ def run_encounter(player: dict, all_verses: list, unlocked: list, demon_index: i
|
|||||||
Updated list of unlocked verse IDs.
|
Updated list of unlocked verse IDs.
|
||||||
"""
|
"""
|
||||||
demon_data = DEMONS[demon_index % len(DEMONS)]
|
demon_data = DEMONS[demon_index % len(DEMONS)]
|
||||||
|
cycle = demon_index // len(DEMONS)
|
||||||
|
scale = 1 + cycle * 0.25
|
||||||
demon = {
|
demon = {
|
||||||
"name": demon_data["name"],
|
"name": demon_data["name"],
|
||||||
"health": demon_data["health"],
|
"health": int(demon_data["health"] * scale),
|
||||||
"max_health": demon_data["health"],
|
"max_health": int(demon_data["health"] * scale),
|
||||||
"weaknesses": demon_data["weaknesses"],
|
"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]
|
available = [v for v in all_verses if v["id"] in unlocked]
|
||||||
|
|||||||
@ -3,13 +3,13 @@ name = "exorcist-game"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "A text-based exorcist game using Bible verses as spell mechanics"
|
description = "A text-based exorcist game using Bible verses as spell mechanics"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = ["pytest>=7.0"]
|
dependencies = []
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
exorcist = "main:main"
|
exorcist = "main:main"
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
test = ["pytest>=7.0"]
|
dev = ["pytest>=7.0"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
|
|||||||
@ -78,20 +78,24 @@ class TestGameInitialization:
|
|||||||
class TestDemonData:
|
class TestDemonData:
|
||||||
"""Tests for demon encounter data."""
|
"""Tests for demon encounter data."""
|
||||||
|
|
||||||
from main import DEMONS
|
|
||||||
|
|
||||||
def test_demons_exist(self) -> None:
|
def test_demons_exist(self) -> None:
|
||||||
"""Demon list is populated."""
|
"""Demon list is populated."""
|
||||||
assert len(self.DEMONS) > 0
|
from main import DEMONS
|
||||||
|
|
||||||
|
assert len(DEMONS) > 0
|
||||||
|
|
||||||
def test_demon_structure(self) -> None:
|
def test_demon_structure(self) -> None:
|
||||||
"""Each demon has required fields."""
|
"""Each demon has required fields."""
|
||||||
|
from main import DEMONS
|
||||||
|
|
||||||
required = {"name", "health", "weaknesses", "damage"}
|
required = {"name", "health", "weaknesses", "damage"}
|
||||||
for demon in self.DEMONS:
|
for demon in DEMONS:
|
||||||
assert required.issubset(demon.keys())
|
assert required.issubset(demon.keys())
|
||||||
|
|
||||||
def test_escalating_difficulty(self) -> None:
|
def test_escalating_difficulty(self) -> None:
|
||||||
"""Demons have escalating health values."""
|
"""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)):
|
for i in range(1, len(healths)):
|
||||||
assert healths[i] > healths[i - 1]
|
assert healths[i] > healths[i - 1]
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "exorcism_001",
|
"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",
|
"category": "Exorcism",
|
||||||
"difficulty": 2,
|
"difficulty": 2,
|
||||||
"reference": "Mark 16:17"
|
"reference": "Mark 16:17"
|
||||||
@ -65,7 +65,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "judgment_001",
|
"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",
|
"category": "Judgment",
|
||||||
"difficulty": 4,
|
"difficulty": 4,
|
||||||
"reference": "Psalm 21:9"
|
"reference": "Psalm 21:9"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user