/// Active gameplay screen for the Flutter host. library; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:wolf_3d_dart/wolf_3d_engine.dart'; import 'package:wolf_3d_flutter/wolf_3d_flutter.dart'; import 'package:wolf_3d_renderer/wolf_3d_ascii_renderer.dart'; import 'package:wolf_3d_renderer/wolf_3d_flutter_renderer.dart'; /// Launches a [WolfEngine] via [Wolf3d] and exposes renderer/input integrations. class GameScreen extends StatefulWidget { /// Shared application facade owning the engine, audio, and input. final Wolf3d wolf3d; /// Creates a gameplay screen driven by [wolf3d]. const GameScreen({ required this.wolf3d, super.key, }); @override State createState() => _GameScreenState(); } class _GameScreenState extends State { late final WolfEngine _engine; bool _useAsciiMode = false; @override void initState() { super.initState(); _engine = widget.wolf3d.launchEngine( onGameWon: () { _engine.difficulty = null; widget.wolf3d.clearActiveDifficulty(); Navigator.of(context).pop(); }, ); } @override Widget build(BuildContext context) { return PopScope( canPop: _engine.difficulty != null, onPopInvokedWithResult: (didPop, _) { if (!didPop && _engine.difficulty == null) { widget.wolf3d.input.queueBackAction(); } }, child: Scaffold( body: LayoutBuilder( builder: (context, constraints) { return Listener( onPointerDown: (event) { widget.wolf3d.input.onPointerDown(event); }, onPointerUp: widget.wolf3d.input.onPointerUp, onPointerMove: widget.wolf3d.input.onPointerMove, onPointerHover: widget.wolf3d.input.onPointerMove, child: Stack( children: [ // Keep both renderers behind the same engine so mode switching does // not reset level state or audio playback. _useAsciiMode ? WolfAsciiRenderer(engine: _engine) : WolfFlutterRenderer(engine: _engine), if (!_engine.isInitialized) Container( color: Colors.black, child: const Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(color: Colors.teal), SizedBox(height: 20), Text( "GET PSYCHED!", style: TextStyle( color: Colors.teal, fontFamily: 'monospace', ), ), ], ), ), ), // Tab toggles the renderer implementation for quick visual debugging. Focus( autofocus: true, onKeyEvent: (node, event) { if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.tab) { setState(() => _useAsciiMode = !_useAsciiMode); return KeyEventResult.handled; } return KeyEventResult.ignored; }, child: const SizedBox.shrink(), ), // A second full-screen overlay keeps the presentation simple while // the engine is still warming up or decoding the first frame. if (!_engine.isInitialized) Container( color: Colors.black, child: const Center( child: CircularProgressIndicator(color: Colors.teal), ), ), Positioned( top: 16, right: 16, child: Text( 'TAB: Swap Renderer', style: TextStyle( color: Colors.white.withValues(alpha: 0.5), ), ), ), ], ), ); }, ), ), ); } }