Wargame Engine — Best Fit

Repo: maximinus/wargame Stack: Python 100%, Pygame Status: Active (52 commits, last updated Jul 2024, 27 stars) License: GPL-3.0 Pip installable: Yes

Architectural alignment with LangGraph:

Wargame Feature LangGraph Mapping
Node-tree viewport LangGraph nodes = viewport nodes. Each graph node can render as a viewport node
Scene system Discrete game phases (planning, execution, resolution). Switch scenes per phase
Messaging system Decoupled state update propagation. Engine sends “clicked hex_17” message, LangGraph processes, state updates push back
Save/load/replay Complements LangGraph checkpointer. Engine handles file serialization, LangGraph handles state persistence
GUI system (built-in) interrupt() prompts rendered as GUI dialogs. Intel suggestions as sidebar/overlay
Tween animations Unit movement animations between nodes, state transition visual feedback
Terminal (runtime REPL) Debug interface for inspecting LangGraph state during development
Automatic config loading Unit configs, node access lists loaded from config files
Logging system Trace LangGraph node execution alongside engine events
Board wargame domain Turn-based, tile/hex-based, tactical — exactly the game paradigm

Key quote from README: > “A tree of nodes is used as a viewport, with each node usually representing either a view of sprites, or the sprites themselves.”

This is the engine explicitly designed around a node-tree viewport. The LangGraph graph IS a node tree. The mapping is architectural, not metaphorical.

Example integration sketch:

# LangGraph owns state, Wargame Engine owns the view
def game_loop():
    controller = wargame.engine.init(os.cwd())
    
    # Build scene from LangGraph state
    scene = build_scene_from_langgraph_state(graph.get_state(config))
    controller.add_scene('game', scene)
    
    # Override input handler to fire LangGraph invocations
    controller.on_click = lambda pos: graph.invoke({
        "action": "move", 
        "target": screen_to_hex(pos)
    }, config)
    
    # Post-invoke: rebuild scene from updated state
    controller.on_post_update = lambda: rebuild_scene_from_state(...)
    
    controller.run('game')

Risk: Opinionated engine — owns the main loop. If LangGraph’s invoke/stream pattern conflicts with the engine’s internal loop, integration may require fighting the engine’s assumptions. The “not a library” design means less flexibility. Mitigation: the messaging system is designed for decoupling — the engine may be more hackable than it appears.