Fixes pushwalls and a bunch of ASCII/sixel rasterizer issues

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-18 02:02:39 +01:00
parent d7692ea325
commit 7ee1d0704d
23 changed files with 3781 additions and 143 deletions

View File

@@ -1,6 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'package:wolf_3d_cli/cli_game_loop.dart';
import 'package:wolf_3d_dart/wolf_3d_data.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
@@ -15,13 +15,7 @@ void exitCleanly(int code) {
}
void main() async {
stdin.echoMode = false;
stdin.lineMode = false;
// HIDE the blinking cursor and clear the screen to prep the canvas
stdout.write('\x1b[?25l\x1b[2J');
print("Discovering game data...");
stdout.write("Discovering game data...");
// 1. Get the absolute URI of where this exact script lives
final scriptUri = Platform.script;
@@ -36,76 +30,31 @@ void main() async {
recursive: true,
);
final AsciiRasterizer asciiRasterizer = AsciiRasterizer(isTerminal: true);
final SixelRasterizer sixelRasterizer = SixelRasterizer();
CliRasterizer rasterizer = sixelRasterizer;
CliGameLoop? gameLoop;
final FrameBuffer initialFrameBuffer = FrameBuffer(
stdout.terminalColumns,
stdout.terminalLines,
);
void stopAndExit(int code) {
gameLoop?.stop();
exitCleanly(code);
}
final engine = WolfEngine(
data: availableGames.values.first,
difficulty: Difficulty.medium,
startingEpisode: 0,
frameBuffer: initialFrameBuffer,
audio: CliSilentAudio(),
frameBuffer: FrameBuffer(
stdout.terminalColumns,
stdout.terminalLines,
),
input: CliInput(),
onGameWon: () {
exitCleanly(0);
print("YOU WON!");
},
onGameWon: () => stopAndExit(0),
);
engine.init();
stdin.listen((List<int> bytes) {
if (bytes.contains(113) || bytes.contains(27)) {
exitCleanly(0);
}
if (bytes.contains(9)) {
rasterizer = identical(rasterizer, sixelRasterizer)
? asciiRasterizer
: sixelRasterizer;
stdout.write('\x1b[2J\x1b[H');
return;
}
(engine.input as CliInput).handleKey(bytes);
});
Stopwatch stopwatch = Stopwatch()..start();
Duration lastTick = Duration.zero;
Timer.periodic(const Duration(milliseconds: 33), (timer) {
// 1. Terminal Size Safety Check
if (stdout.hasTerminal) {
int cols = stdout.terminalColumns;
int rows = stdout.terminalLines;
if (!rasterizer.prepareTerminalFrame(engine, columns: cols, rows: rows)) {
// Clear the screen and print the warning at the top left
stdout.write('\x1b[2J\x1b[H');
stdout.write(
rasterizer.buildTerminalSizeWarning(columns: cols, rows: rows),
);
// Prevent the engine from simulating a massive time jump when resumed
lastTick = stopwatch.elapsed;
return;
}
}
// 2. Normal Game Loop
Duration currentTick = stopwatch.elapsed;
Duration elapsed = currentTick - lastTick;
lastTick = currentTick;
// Move cursor to top-left (0,0) before drawing the frame
stdout.write('\x1b[H');
engine.tick(elapsed);
stdout.write(rasterizer.render(engine));
});
gameLoop = CliGameLoop(
engine: engine,
input: engine.input as CliInput,
onExit: stopAndExit,
);
gameLoop.start();
}

View File

@@ -0,0 +1,123 @@
import 'dart:async';
import 'dart:io';
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
import 'package:wolf_3d_dart/wolf_3d_input.dart';
import 'package:wolf_3d_dart/wolf_3d_rasterizer.dart';
class CliGameLoop {
CliGameLoop({
required this.engine,
required this.input,
required this.onExit,
}) : primaryRasterizer = SixelRasterizer(),
secondaryRasterizer = AsciiRasterizer(isTerminal: true) {
_rasterizer = primaryRasterizer;
}
final WolfEngine engine;
final CliRasterizer primaryRasterizer;
final CliRasterizer secondaryRasterizer;
final CliInput input;
final void Function(int code) onExit;
final Stopwatch _stopwatch = Stopwatch();
late CliRasterizer _rasterizer;
StreamSubscription<List<int>>? _stdinSubscription;
Timer? _timer;
bool _isRunning = false;
Duration _lastTick = Duration.zero;
void start() {
if (_isRunning) {
return;
}
stdin.echoMode = false;
stdin.lineMode = false;
stdout.write('\x1b[?25l\x1b[2J');
_stdinSubscription = stdin.listen(_handleInput);
_stopwatch.start();
_timer = Timer.periodic(const Duration(milliseconds: 33), _tick);
_isRunning = true;
}
void stop() {
if (!_isRunning) {
return;
}
_timer?.cancel();
_timer = null;
_stdinSubscription?.cancel();
_stdinSubscription = null;
if (_stopwatch.isRunning) {
_stopwatch.stop();
}
if (stdin.hasTerminal) {
stdin.echoMode = true;
stdin.lineMode = true;
}
if (stdout.hasTerminal) {
stdout.write('\x1b[0m\x1b[?25h');
}
_isRunning = false;
}
void _handleInput(List<int> bytes) {
if (bytes.contains(113) || bytes.contains(27)) {
stop();
onExit(0);
return;
}
if (bytes.contains(9)) {
_rasterizer = identical(_rasterizer, secondaryRasterizer)
? primaryRasterizer
: secondaryRasterizer;
stdout.write('\x1b[2J\x1b[H');
return;
}
input.handleKey(bytes);
}
void _tick(Timer timer) {
if (!_isRunning) {
return;
}
if (stdout.hasTerminal) {
final int cols = stdout.terminalColumns;
final int rows = stdout.terminalLines;
if (!_rasterizer.prepareTerminalFrame(
engine,
columns: cols,
rows: rows,
)) {
stdout.write('\x1b[2J\x1b[H');
stdout.write(
_rasterizer.buildTerminalSizeWarning(columns: cols, rows: rows),
);
_lastTick = _stopwatch.elapsed;
return;
}
}
final Duration currentTick = _stopwatch.elapsed;
final Duration elapsed = currentTick - _lastTick;
_lastTick = currentTick;
stdout.write('\x1b[H');
engine.tick(elapsed);
stdout.write(_rasterizer.render(engine));
}
}