Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .replit
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
modules = ["nodejs-20", "python-3.12", "web"]

[nix]
channel = "stable-25_05"

[agent]
expertMode = true

[workflows]
runButton = "Project"

[[workflows.workflow]]
name = "Project"
mode = "parallel"
author = "agent"

[[workflows.workflow.tasks]]
task = "workflow.run"
args = "Backend API"

[[workflows.workflow.tasks]]
task = "workflow.run"
args = "Start application"

[[workflows.workflow]]
name = "Backend API"
author = "agent"

[[workflows.workflow.tasks]]
task = "shell.exec"
args = "cd backend && python main.py"
waitForPort = 8000

[workflows.workflow.metadata]
outputType = "console"

[[workflows.workflow]]
name = "Start application"
author = "agent"

[[workflows.workflow.tasks]]
task = "shell.exec"
args = "cd frontend && npm run dev"
waitForPort = 5000

[workflows.workflow.metadata]
outputType = "webview"

[[ports]]
localPort = 5000
externalPort = 5000

[[ports]]
localPort = 8000
externalPort = 80
92 changes: 87 additions & 5 deletions backend/db/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from sqlalchemy import Column, String, Integer, DateTime, ForeignKey, Boolean
from sqlalchemy import Column, String, Integer, DateTime, ForeignKey, Boolean, JSON
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func

Expand Down Expand Up @@ -28,22 +28,22 @@ class State(TimestampMixin, Base):

id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
date = Column(String, nullable=False) # Format: YYYY-MM
date = Column(String, nullable=False)
flag_svg = Column(String, nullable=False)
description = Column(String, nullable=False)
turn_in_progress = Column(Boolean, nullable=False, default=False)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)

# Relationships
user = relationship("User", back_populates="states")
snapshots = relationship("StateSnapshot", back_populates="state")
game_state = relationship("GameState", back_populates="state", uselist=False)


class StateSnapshot(TimestampMixin, Base):
__tablename__ = "state_snapshots"

id = Column(Integer, primary_key=True, index=True)
date = Column(String, nullable=False) # Format: YYYY-MM
date = Column(String, nullable=False)
state_id = Column(Integer, ForeignKey("states.id"), nullable=False)

markdown_state = Column(String, nullable=False)
Expand All @@ -52,5 +52,87 @@ class StateSnapshot(TimestampMixin, Base):
markdown_delta = Column(String, nullable=True)
markdown_delta_report = Column(String, nullable=True)

# Relationship to State
state = relationship("State", back_populates="snapshots")


class GameState(TimestampMixin, Base):
"""Extended game state — world map, resources, turn counter."""
__tablename__ = "game_states"

id = Column(Integer, primary_key=True, index=True)
state_id = Column(Integer, ForeignKey("states.id"), unique=True, nullable=False)
map_seed = Column(Integer, nullable=False, default=0)
map_data = Column(JSON, nullable=False, default=dict)
game_turn = Column(Integer, nullable=False, default=0)
gold = Column(Integer, nullable=False, default=5000)
production = Column(Integer, nullable=False, default=100)

state = relationship("State", back_populates="game_state")
military_units = relationship("MilitaryUnit", back_populates="game_state", cascade="all, delete-orphan")
cities = relationship("GameCity", back_populates="game_state", cascade="all, delete-orphan")
researched_techs = relationship("ResearchedTech", back_populates="game_state", cascade="all, delete-orphan")
diplomacy = relationship("DiplomacyRelation", back_populates="game_state", cascade="all, delete-orphan")


class MilitaryUnit(TimestampMixin, Base):
"""A military unit on the world map."""
__tablename__ = "military_units"

id = Column(Integer, primary_key=True, index=True)
game_state_id = Column(Integer, ForeignKey("game_states.id"), nullable=False)
unit_type = Column(String, nullable=False)
name = Column(String, nullable=True)
tile_col = Column(Integer, nullable=False, default=0)
tile_row = Column(Integer, nullable=False, default=0)
health = Column(Integer, nullable=False, default=100)
moves_remaining = Column(Integer, nullable=False, default=1)
experience = Column(Integer, nullable=False, default=0)
status = Column(String, nullable=False, default="active")

game_state = relationship("GameState", back_populates="military_units")


class GameCity(TimestampMixin, Base):
"""A city managed by the player (city builder)."""
__tablename__ = "game_cities"

id = Column(Integer, primary_key=True, index=True)
game_state_id = Column(Integer, ForeignKey("game_states.id"), nullable=False)
name = Column(String, nullable=False)
tile_col = Column(Integer, nullable=False)
tile_row = Column(Integer, nullable=False)
population = Column(Integer, nullable=False, default=10000)
is_capital = Column(Boolean, nullable=False, default=False)
city_grid = Column(JSON, nullable=False, default=list)
budget = Column(Integer, nullable=False, default=500000)
happiness = Column(Integer, nullable=False, default=50)

game_state = relationship("GameState", back_populates="cities")


class ResearchedTech(TimestampMixin, Base):
"""Technology researched by a game state."""
__tablename__ = "researched_techs"

id = Column(Integer, primary_key=True, index=True)
game_state_id = Column(Integer, ForeignKey("game_states.id"), nullable=False)
tech_id = Column(String, nullable=False)
progress = Column(Integer, nullable=False, default=0)
researched = Column(Boolean, nullable=False, default=False)

game_state = relationship("GameState", back_populates="researched_techs")


class DiplomacyRelation(TimestampMixin, Base):
"""Diplomatic relations between player and AI countries."""
__tablename__ = "diplomacy_relations"

id = Column(Integer, primary_key=True, index=True)
game_state_id = Column(Integer, ForeignKey("game_states.id"), nullable=False)
ai_country_id = Column(String, nullable=False)
ai_country_name = Column(String, nullable=False)
relation_score = Column(Integer, nullable=False, default=50)
status = Column(String, nullable=False, default="neutral")
trade_active = Column(Boolean, nullable=False, default=False)

game_state = relationship("GameState", back_populates="diplomacy")
26 changes: 18 additions & 8 deletions backend/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
Expand All @@ -7,7 +8,7 @@

from db.database import init_db
from tasks.tasks import reset_stuck_states
from routers import auth, states, stripe
from routers import auth, states, stripe, game


@asynccontextmanager
Expand All @@ -31,15 +32,23 @@ async def lifespan(app: FastAPI):
app = FastAPI(lifespan=lifespan)

# Configure CORS
_frontend_url = os.getenv("FRONTEND_URL", "")
_allowed_origins = [
"http://localhost:3000",
"http://localhost:5000",
"http://localhost:8000",
"https://*.up.railway.app",
"https://*.replit.dev",
"https://*.replit.app",
"https://*.repl.co",
]
if _frontend_url and _frontend_url not in _allowed_origins:
_allowed_origins.append(_frontend_url)

app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:3000",
"http://localhost:3001",
"http://localhost:8000",
"http://localhost:8080",
"https://*.up.railway.app",
],
allow_origins=_allowed_origins,
allow_origin_regex=r"https://.*\.(replit\.dev|replit\.app|repl\.co)$",
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
Expand All @@ -48,6 +57,7 @@ async def lifespan(app: FastAPI):
app.include_router(auth.router)
app.include_router(states.router)
app.include_router(stripe.router)
app.include_router(game.router)

if __name__ == "__main__":
import uvicorn
Expand Down
Loading