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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
use std::error::Error;
use std::fs::File;
use std::env::Args;
use std::io::Write;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use rand::prelude::*;
use rand_pcg::{Pcg64, Lcg128Xsl64};

#[derive(PartialEq)]
pub enum HelpType {NoHelp, Short, Long}

pub struct Config
{
	pub seed: u64,
	pub num_variants: u32,
	pub help: HelpType
}

impl Config
{
	pub fn new(args: Args) -> Result<Config, &'static str>
	{
		let mut seed: u64 = 0;
		let mut num_variants: u32 = 0;
		let mut help: HelpType = HelpType::NoHelp;

		let mut args = args.peekable();

		let num_args = args.len();
		if num_args < 2
		{
			help = HelpType::Short;
		}
		else
		{
			// Ignore the first argument because it is the executable path
			args.next();
			while let Some(arg) = args.next()
			{
				match arg.as_str()
				{
					"-h" => help = HelpType::Long,

					"-s" => seed = match args.next()
					{
						Some(arg_string) => match arg_string.parse::<u64>()
						{
							Ok(num) => num,
							Err(_) => return Err("Invalid numeric seed! Use -S for non-numeric seeds."),
						},
						None => return Err("Please provide a numeric seed."),
					},

					"-S" => seed = match args.next()
					{
						Some(arg_string) =>
						{
							let mut hasher = DefaultHasher::new();
							arg_string.hash(&mut hasher);
							hasher.finish()
						}
						None => return Err("Please provide a seed."),
					},

					_ => if let Some(_) = args.peek()
					{
						return Err("Invalid option provided! Type vmks-exam-generator -h for help.");
					}
					else
					{
						num_variants = match arg.parse::<u32>()
						{
							Ok(num) => num,
							Err(_) => return Err("Invalid number of variants!"),
						}
					},
				}
			};

			if num_variants == 0 && help == HelpType::NoHelp
			{
				return Err("Please provide a number of variants greater than 0.");
			}
		}

		Ok(Config
		{
			seed,
			num_variants,
			help
		})
	}
}

trait PushRandom
{
	fn push_random(&mut self, list: &[&str], rng: &mut Lcg128Xsl64);
}

impl PushRandom for String
{
	fn push_random(&mut self, list: &[&str], rng: &mut Lcg128Xsl64)
	{
		//self.push_str(&list[rand::thread_rng().gen_range(0, list.len())]);
		self.push_str(&list[rng.gen_range(0, list.len())]);
	}
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>>
{
	match config.help
	{
		HelpType::Short =>
		{
			println!("Usage: vmks-exam-generator [OPTIONS] NUM\nType vmks-exam-generator -h for more information.");
			Ok(())
		},
		HelpType::Long =>
		{
			println!("Usage: vmks-exam-generator [OPTIONS] NUM\n\
				Pseudo-randomly generates NUM variants of an embedded programming exam. If no seed is provided, 0 is used as seed.\n\
				\t-h\t    Show this help\n\
				\t-s SEED\t    Use the number SEED to seed the random number generator\n\
				\t-S SEED\t    Use the hashsum of the string SEED to seed the random number generator");
			Ok(())
		},
		HelpType::NoHelp =>
		{
			let mut rng: Lcg128Xsl64 = Pcg64::seed_from_u64(config.seed);
			generate_variants(config.num_variants, &mut rng)
		},
	}
}

fn generate_variants(num_variants: u32, rng: &mut Lcg128Xsl64) -> Result<(), Box<dyn Error>>
{
	const TASKS: [&str; 13] =
	[
		"Контролно по \"Програмиране на вградени микроконтролерни системи\"\n\nЗадача 1:\nСвържете ",
		" между пин ",
		" и ",
		". При ",
		" на веригата да се изпълни прекъсване, което да ",
		"\n\nЗадача 2:\nСвържете потенциометър, чрез който да се задава напрежение между 0V и 5V на пин ",
		" на Arduino Uno. ",
		" на зададеното чрез потенциометъра напрежение.\n\nЗадача 3:\nКонфигурирайте канал ",
		" на таймер 1 на Arduino Uno със следните параметри:\n\tчестота - ",
		"\n\tкоефициент на запълване - ",
		"\n\tрежим 14 - Fast PWM (виж таблица 15-5)\n\t",
		" режим на пиновете (виж таблица 15-3)\nКонфигурирайте прекъсване при ",
		", което да инкрементира една 16-битова целочислена променлива.\n"
	];
	let task_variants: [Vec<&str>; 12] =
	[
		vec!["ключ", "бутон"],
		vec!["2", "3"],
		vec!["захранване", "земя"],
		vec!["затваряне", "отваряне"],
		vec!
		[
			"сменя цвета на 7-мия пиксел на 12-пикселова светодиодна лента между жълто и синьо. Лентата да е свързана към пин 12 на Arduino Uno.",
			"сменя цвета на 3-тия пиксел на 10-пикселова светодиодна лента между зелено и бяло. Лентата да е свързана към пин 12 на Arduino Uno.",
			"обръща посоката на въртене на постояннотоков четков мотор управляван с H-bridge L293. Сигналите за управление на мотора да са \
			свързани към пинове 7 и 8 на Arduino Uno, а разрешаващият вход на L293 да е винаги активен.",
			"включва и изключва постояннотоков четков мотор управляван с H-bridge L293. Сигналите за управление на мотора да са \
			свързани към пинове 7 и 8 на Arduino Uno, а разрешаващият вход на L293 да е винаги активен.",
			//"пуска и спира таймера, конфигуриран според условието на задача 3.",
			//"разрешава и забранява прекъсването от таймера, конфигуриран според условието на задача 3.",
			"увеличава ъгъла на завъртане на сервомотор с 10 градуса. Когато ъгълът стане по-голям от 120 градуса да се върне на 0 градуса. \
			Сервомоторът да е свързан към пин 11 на Arduino Uno.",
			"изпраща по серийната връзка времето в милисекунди от стартирането на програмата. Параметрите на серийната връзка да бъдат\n\
			\tскорост - 38400 b/s\n\t8 бита за данни\n\tпроверка по четност - EVEN\n\t2 стоп бита",
			"изпраща по серийната връзка времето в милисекунди от стартирането на програмата. Параметрите на серийната връзка да бъдат\n\
			\tскорост - 57600 b/s\n\t8 бита за данни\n\tпроверка по четност - ODD\n\t1 стоп бит",
		],
		vec!["A0", "A1", "A2", "A3", "A4", "A5"],
		vec!
		[
			"Към пин 6 на Arduino Uno да се свърже сервомотор. Ъгълът на завъртане на сервомотора да се поддържа правопропорционален",
			"Към пин 4 на Arduino Uno да се свърже 20-пикселова светодиодна лента. Да светят в цвят по избор брой пиксели правопропорционален",
			//"Честотата на таймера в задача 3 да се регулира между половината от зададената и зададената честота правопропорционално",
			//"Коефициентът на запълване на таймера в задача 3 да се регулира между 0% и зададената стойност правопропорционално",
			"Към пинове 5 и 6 на Arduino Uno да се свържат два от управляващите сигнали на драйвер L293 и съответния разрешаващ вход да се свърже \
			към пин 13. Към съответните изходи на драйвера да се свърже постояннотоков четков мотор, чиято скорост да се поддържа правопропорционална",
			"Към пин 4 на Arduino Uno да се свърже червен светодиод, който да мига с коефициент на запълване 25% и честота, правопропорционална",
			"Към пин 4 на Arduino Uno да се свърже зелен светодиод, който да мига с честота 10 Hz и коефициент на запълване, правопропорционален"
		],
		vec!["A", "B"],
		vec!
		[
			"1 MHz", "500 kHz", "250 kHz", "100 kHz", "50 kHz", "20 kHz", "10 kHz",
			"5 kHz","1 kHz", "500 Hz", "200 Hz", "100 Hz", "50 Hz", "20 Hz"
		],
		vec!["50%", "25%", "75%", "12.5%", "37.5%", "62.5%", "87.5%"],
		vec!["инвертиращ", "неинвертиращ"],
		vec!["Compare match", "Overflow"]
	];

	let mut buf = String::new();
	buf.reserve(2000);

	for i in 0..num_variants
	{
		let mut task_variants_iter = task_variants.iter();
		for task in TASKS.iter()
		{
			buf.push_str(task);
			if let Some(vars) = task_variants_iter.next()
			{
				buf.push_random(vars, rng);
			};
		}

		let mut file = File::create(format!("variant_{}.txt", i + 1))?;
		file.write_all(buf.as_bytes())?;
		buf.truncate(0);
	}

	Ok(())
}