1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! oxygenlance is a simple crate to allow the execution of [BF Joust] matches as a Rust library.
//!
//! It is a simple wrapper over [gearlance]'s internals, modified to be thread-safe and integrate
//! better into Rust code.
//!
//! [BF Joust]: http://esolangs.org/wiki/BF_Joust
//! [gearlance]: https://github.com/fis/chainlance
#[allow(unused)]
#[allow(nonstandard_style)]
#[rustfmt::skip]
mod interface;
mod internal_api;
mod rust_callbacks;
mod api {
use crate::{interface, internal_api, internal_api::GearlanceCompiledProgram};
use std::sync::Arc;
use thiserror::Error;
/// The minimum tape size that a round may have.
pub const MINTAPE: usize = interface::MINTAPE as usize;
/// The maximum tape size that a round may have.
pub const MAXTAPE: usize = interface::MAXTAPE as usize;
/// The number of matches that occur in a single round on a single configuration.
pub const MATCH_COUNT: usize = internal_api::MATCH_COUNT;
/// The error type for this crate.
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[error("parse failed: {0}")]
ParseFailed(&'static str),
}
/// A compiled BF Joust warrior.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Warrior {
compiled: Arc<GearlanceCompiledProgram>,
}
impl Warrior {
/// Compile a BF Joust warrior from its source code.
pub fn compile(source: impl AsRef<[u8]>) -> Result<Warrior, Error> {
Ok(Warrior { compiled: Arc::new(internal_api::compile_program(source.as_ref())?) })
}
/// Runs a match between two warriors.
///
/// This warrior is set as the left warrior, and the passed warrior is set as the right
/// warrior.
pub fn run_match(&self, right: &Warrior) -> MatchResult {
internal_api::execute_match(&self.compiled, &right.compiled, false)
}
/// Runs a match between two warriors, gathering detailed statistics during the execution.
///
/// This is slower than [`run_match`](`Warrior::run_match`), so it should only be used if
/// you need to gather the detailed statistics for some reason.
///
/// This warrior is set as the left warrior, and the passed warrior is set as the right
/// warrior.
pub fn run_match_detailed(&self, right: &Warrior) -> MatchResult {
internal_api::execute_match(&self.compiled, &right.compiled, true)
}
}
/// The result of a single round between two BF Joust warriors.
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum RoundResult {
/// The left warrior wins.
LeftWins,
/// The right warrior wins.
RightWins,
/// The round is tied.
Tie,
}
impl RoundResult {
/// Returns the score that this result would get for the left warrior.
pub fn score(&self) -> i32 {
match self {
RoundResult::LeftWins => 1,
RoundResult::RightWins => -1,
RoundResult::Tie => 0,
}
}
/// Reverses the perspective of the right and left warriors.
pub fn reverse(&self) -> RoundResult {
match self {
RoundResult::LeftWins => RoundResult::RightWins,
RoundResult::RightWins => RoundResult::LeftWins,
RoundResult::Tie => RoundResult::Tie,
}
}
}
/// How a particular round of BFJoust ended.
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum RoundEndingType {
/// The match ended in a timeout.
Timeout,
/// One or both warriors fell off the tape.
FellOffTape,
/// One or both warriors had their flag zeroed.
FlagZeroed,
}
/// The result of a match between two BF Joust warriors.
#[derive(Copy, Clone, Debug, Hash)]
#[non_exhaustive]
pub struct MatchResult {
/// The results of all matches between the warriors.
///
/// The first array is for the normal polarity configuration, and the second array is for
/// the inverted polarity configuration.
pub results: [[RoundResult; MATCH_COUNT]; 2],
/// How each match ended.
///
/// The first array is for the normal polarity configuration, and the second array is for
/// the inverted polarity configuration.
pub end_type: [[RoundEndingType; MATCH_COUNT]; 2],
/// The total number of cycles executed across all matches.
pub total_cycles: u32,
/// Detailed statistics for the match.
///
/// Only included if this was created using [`Warrior::run_match_detailed`].
pub detailed_statistics: Option<MatchDetailedStatistics>,
}
impl MatchResult {
/// Returns the score that this result would get for the left warrior.
pub fn score(&self) -> i32 {
self.results
.iter()
.map(|x| x.iter().map(|x| x.score()).sum::<i32>())
.sum()
}
/// Reverses the perspective of the right and left warriors.
///
/// This removes the detailed statistics, as those cannot be trivially reversed without
/// rerunning the match.
pub fn reverse(&self) -> MatchResult {
let mut results = [[RoundResult::Tie; MATCH_COUNT]; 2];
for configuration in 0..2 {
for i in 0..MATCH_COUNT {
results[configuration][i] = self.results[configuration][i].reverse();
}
}
MatchResult {
results,
end_type: self.end_type,
total_cycles: self.total_cycles,
detailed_statistics: self.detailed_statistics,
}
}
}
/// Detailed statistics for a match between two BF Joust warriors.
///
/// This is useful for behavior visualizations and similar programs.
#[derive(Copy, Clone, Debug, Hash)]
#[non_exhaustive]
pub struct MatchDetailedStatistics {
/// The maximum absolute distance from 0 that each cell reaches on average when the program
/// leaves a cell. This helps show its decoy layout.
///
/// The first array is the left warrior, and the second array is the right warrior.
pub tape_max: [[u8; MAXTAPE]; 2],
/// The number of cycles each program spends on each cell.
///
/// The first array is the left warrior, and the second array is the right warrior.
pub heat_position: [[u32; MAXTAPE]; 2],
}
}
pub use api::*;