Compare commits
2 Commits
5424772424
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f9930694ec | |||
| 9c2793324a |
2881
input/day18.txt
Normal file
2881
input/day18.txt
Normal file
File diff suppressed because it is too large
Load Diff
13
input/day18_example.txt
Normal file
13
input/day18_example.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
2,2,2
|
||||||
|
1,2,2
|
||||||
|
3,2,2
|
||||||
|
2,1,2
|
||||||
|
2,3,2
|
||||||
|
2,2,1
|
||||||
|
2,2,3
|
||||||
|
2,2,4
|
||||||
|
2,2,6
|
||||||
|
1,2,5
|
||||||
|
3,2,5
|
||||||
|
2,1,5
|
||||||
|
2,3,5
|
||||||
64
src/day16.rs
64
src/day16.rs
@@ -7,7 +7,7 @@ use super::util;
|
|||||||
pub struct Day16 {
|
pub struct Day16 {
|
||||||
valves: Vec<Valve>,
|
valves: Vec<Valve>,
|
||||||
initial_valve_idx: usize,
|
initial_valve_idx: usize,
|
||||||
valve_names: Vec<String>
|
// valve_names: Vec<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Day16 {
|
impl Day16 {
|
||||||
@@ -18,23 +18,24 @@ impl Day16 {
|
|||||||
|
|
||||||
// We first map all the valve names to indexes for performance reasons:
|
// We first map all the valve names to indexes for performance reasons:
|
||||||
let valve_names = lines.iter()
|
let valve_names = lines.iter()
|
||||||
.map(|s| s.split_whitespace().skip(1).next().unwrap().to_string())
|
.map(|s| s.split_whitespace().nth(1).unwrap().to_string())
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
// Put the input into the day struct
|
// Put the input into the day struct
|
||||||
return Day16 {
|
return Day16 {
|
||||||
valves: lines.iter().enumerate().map(|(i, s)| {
|
valves: lines.iter().enumerate().map(|(_, s)| {
|
||||||
let mut s_split = s.split(";");
|
let mut s_split = s.split(';');
|
||||||
let flow_rate = s_split.next().unwrap().split("rate=").skip(1).next().unwrap().parse::<u32>().unwrap();
|
let flow_rate = s_split.next().unwrap().split("rate=").nth(1).unwrap().parse::<u32>().unwrap();
|
||||||
let connections = (&s_split.next().unwrap()["tunnels lead to valves ".len()..].trim()).split(", ")
|
let connections = s_split.next().unwrap()["tunnels lead to valves ".len()..].trim().split(", ")
|
||||||
.map(|n| valve_names.iter().position(|nn| n.eq(nn)).unwrap())
|
.map(|n| valve_names.iter().position(|nn| n.eq(nn)).unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
Valve {
|
Valve {
|
||||||
id: i, flow_rate, connections
|
// id: i,
|
||||||
|
flow_rate, connections
|
||||||
}
|
}
|
||||||
}).collect(),
|
}).collect(),
|
||||||
initial_valve_idx: valve_names.iter().position(|n| n == "AA").unwrap(),
|
initial_valve_idx: valve_names.iter().position(|n| n == "AA").unwrap(),
|
||||||
valve_names
|
// valve_names
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,19 +47,19 @@ impl Day16 {
|
|||||||
open_valves_mask & (1u64 << valve_idx) != 0
|
open_valves_mask & (1u64 << valve_idx) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_closed(valve_idx: &usize, open_valves_mask: &u64) -> bool {
|
// fn is_closed(valve_idx: &usize, open_valves_mask: &u64) -> bool {
|
||||||
!Self::is_open(valve_idx, open_valves_mask)
|
// !Self::is_open(valve_idx, open_valves_mask)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fn calc_flow_rate(&self, open_valves_mask: &u64) -> u32 {
|
// fn calc_flow_rate(&self, open_valves_mask: &u64) -> u32 {
|
||||||
let mut res = 0;
|
// let mut res = 0;
|
||||||
for i in 0..self.valves.len() {
|
// for i in 0..self.valves.len() {
|
||||||
if Self::is_open(&i, open_valves_mask) {
|
// if Self::is_open(&i, open_valves_mask) {
|
||||||
res += self.valves[i].flow_rate
|
// res += self.valves[i].flow_rate
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
res
|
// res
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn should_valve_be_opened(&self, valve_idx: &usize, open_valves_mask: &u64) -> bool {
|
fn should_valve_be_opened(&self, valve_idx: &usize, open_valves_mask: &u64) -> bool {
|
||||||
if Self::is_open(valve_idx, open_valves_mask) { false }
|
if Self::is_open(valve_idx, open_valves_mask) { false }
|
||||||
@@ -107,8 +108,8 @@ impl Day16 {
|
|||||||
|
|
||||||
// Now we try all combinations of my and elephants options:
|
// Now we try all combinations of my and elephants options:
|
||||||
let mut best: u32 = 0;
|
let mut best: u32 = 0;
|
||||||
let mut my_best_step = None;
|
// let mut my_best_step = None;
|
||||||
let mut elephant_best_step = None;
|
// let mut elephant_best_step = None;
|
||||||
let next_minutes_left = valve_state.minutes_left - 1;
|
let next_minutes_left = valve_state.minutes_left - 1;
|
||||||
for my_option in my_options {
|
for my_option in my_options {
|
||||||
|
|
||||||
@@ -122,7 +123,6 @@ impl Day16 {
|
|||||||
next_valve_mask_for_me = Self::set_open(&idx, &next_valve_mask_for_me);
|
next_valve_mask_for_me = Self::set_open(&idx, &next_valve_mask_for_me);
|
||||||
next_flow_rate_for_me += self.valves[idx].flow_rate
|
next_flow_rate_for_me += self.valves[idx].flow_rate
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
for elephant_option in &elephant_options {
|
for elephant_option in &elephant_options {
|
||||||
|
|
||||||
@@ -133,17 +133,16 @@ impl Day16 {
|
|||||||
match elephant_option {
|
match elephant_option {
|
||||||
MoveTo(idx) => elephant_next_position = *idx,
|
MoveTo(idx) => elephant_next_position = *idx,
|
||||||
OpenValve(idx) => {
|
OpenValve(idx) => {
|
||||||
next_valve_mask = Self::set_open(&idx, &next_valve_mask);
|
next_valve_mask = Self::set_open(idx, &next_valve_mask);
|
||||||
next_flow_rate += self.valves[*idx].flow_rate
|
next_flow_rate += self.valves[*idx].flow_rate
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = self.find_best_option_pt2(ValveStatePt2::new(my_next_position, elephant_next_position, next_valve_mask, next_minutes_left), next_flow_rate, cache);
|
let res = self.find_best_option_pt2(ValveStatePt2::new(my_next_position, elephant_next_position, next_valve_mask, next_minutes_left), next_flow_rate, cache);
|
||||||
if best == 0 || res.total_flow > best {
|
if best == 0 || res.total_flow > best {
|
||||||
best = res.total_flow;
|
best = res.total_flow;
|
||||||
my_best_step = Some(my_option);
|
// my_best_step = Some(my_option);
|
||||||
elephant_best_step = Some(elephant_option.to_owned());
|
// elephant_best_step = Some(elephant_option.to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,14 +172,14 @@ impl Day16 {
|
|||||||
// One option is to open the valve at the current position
|
// One option is to open the valve at the current position
|
||||||
let new_open_valves_mask = Self::set_open(&valve_state.my_position, &valve_state.open_valves_mask);
|
let new_open_valves_mask = Self::set_open(&valve_state.my_position, &valve_state.open_valves_mask);
|
||||||
best_step_result_option = self.find_best_option(ValveState::new(valve_state.my_position, new_open_valves_mask, valve_state.minutes_left - 1), cur_flow_rate + cur_valve.flow_rate, cache);
|
best_step_result_option = self.find_best_option(ValveState::new(valve_state.my_position, new_open_valves_mask, valve_state.minutes_left - 1), cur_flow_rate + cur_valve.flow_rate, cache);
|
||||||
best_step_option = Some(ValveStep::OpenValve(valve_state.my_position));
|
best_step_option = Some(OpenValve(valve_state.my_position));
|
||||||
}
|
}
|
||||||
// We try and move to an adjacent valve, see if we can be more useful there
|
// We try and move to an adjacent valve, see if we can be more useful there
|
||||||
for connection in cur_valve.connections.iter().rev() {
|
for connection in cur_valve.connections.iter().rev() {
|
||||||
let next_step_result = self.find_best_option(ValveState::new(connection.to_owned(), valve_state.open_valves_mask, valve_state.minutes_left - 1), cur_flow_rate, cache);
|
let next_step_result = self.find_best_option(ValveState::new(connection.to_owned(), valve_state.open_valves_mask, valve_state.minutes_left - 1), cur_flow_rate, cache);
|
||||||
if best_step_result_option.is_none() || next_step_result.as_ref().unwrap().total_flow > best_step_result_option.as_ref().unwrap().total_flow {
|
if best_step_result_option.is_none() || next_step_result.as_ref().unwrap().total_flow > best_step_result_option.as_ref().unwrap().total_flow {
|
||||||
best_step_result_option = next_step_result;
|
best_step_result_option = next_step_result;
|
||||||
best_step_option = Some(ValveStep::MoveTo(connection.to_owned()));
|
best_step_option = Some(MoveTo(connection.to_owned()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,13 +241,13 @@ impl DaySolver for Day16 {
|
|||||||
let mut cache= HashMap::new();
|
let mut cache= HashMap::new();
|
||||||
let best = self.find_best_option_pt2(ValveStatePt2::new(self.initial_valve_idx, self.initial_valve_idx, 0, 26), 0, &mut cache);
|
let best = self.find_best_option_pt2(ValveStatePt2::new(self.initial_valve_idx, self.initial_valve_idx, 0, 26), 0, &mut cache);
|
||||||
|
|
||||||
return best.total_flow.to_string();
|
best.total_flow.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Valve {
|
struct Valve {
|
||||||
id: usize,
|
// id: usize,
|
||||||
flow_rate: u32,
|
flow_rate: u32,
|
||||||
connections: Vec<usize>
|
connections: Vec<usize>
|
||||||
}
|
}
|
||||||
@@ -299,5 +298,4 @@ struct BestOptionResult {
|
|||||||
enum ValveStep {
|
enum ValveStep {
|
||||||
OpenValve(usize),
|
OpenValve(usize),
|
||||||
MoveTo(usize),
|
MoveTo(usize),
|
||||||
None
|
|
||||||
}
|
}
|
||||||
205
src/day18.rs
Normal file
205
src/day18.rs
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
use std::collections::{HashSet};
|
||||||
|
use crate::day18::VoxelState::{Air, Unknown};
|
||||||
|
|
||||||
|
use crate::day_solver::DaySolver;
|
||||||
|
|
||||||
|
use super::util;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Day18 {
|
||||||
|
voxels: HashSet<Vec3>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Day18 {
|
||||||
|
|
||||||
|
pub fn create() -> Self {
|
||||||
|
// let lines = util::read_file("input/day18_example.txt");
|
||||||
|
let lines = util::read_file("input/day18.txt");
|
||||||
|
|
||||||
|
let voxels = lines.iter().map(|l| {
|
||||||
|
let mut c_iter = l.split(",").map(|c| c.parse().unwrap());
|
||||||
|
Vec3 {
|
||||||
|
x: c_iter.next().unwrap(),
|
||||||
|
y: c_iter.next().unwrap(),
|
||||||
|
z: c_iter.next().unwrap()
|
||||||
|
}
|
||||||
|
}).collect::<HashSet<Vec3>>();
|
||||||
|
|
||||||
|
// Put the input into the day struct
|
||||||
|
return Day18 { voxels }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DaySolver for Day18 {
|
||||||
|
|
||||||
|
|
||||||
|
fn solve_part1(&mut self) -> String {
|
||||||
|
|
||||||
|
let mut sides = 0;
|
||||||
|
for voxel in &self.voxels {
|
||||||
|
// We just scan all sides if there are voxels there
|
||||||
|
if !self.voxels.contains(&voxel.moved(-1, 0, 0)) { sides += 1 };
|
||||||
|
if !self.voxels.contains(&voxel.moved(1, 0, 0)) { sides += 1 };
|
||||||
|
if !self.voxels.contains(&voxel.moved(0, -1, 0)) { sides += 1 };
|
||||||
|
if !self.voxels.contains(&voxel.moved(0, 1, 0)) { sides += 1 };
|
||||||
|
if !self.voxels.contains(&voxel.moved(0, 0, -1)) { sides += 1 };
|
||||||
|
if !self.voxels.contains(&voxel.moved(0, 0, 1)) { sides += 1 };
|
||||||
|
}
|
||||||
|
return sides.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solve_part2(&mut self) -> String {
|
||||||
|
|
||||||
|
// We need to find the cubes that can't be reached
|
||||||
|
let x_min = self.voxels.iter().map(|v| v.x).min().unwrap();
|
||||||
|
let x_max = self.voxels.iter().map(|v| v.x).max().unwrap();
|
||||||
|
let y_min = self.voxels.iter().map(|v| v.y).min().unwrap();
|
||||||
|
let y_max = self.voxels.iter().map(|v| v.y).max().unwrap();
|
||||||
|
let z_min = self.voxels.iter().map(|v| v.z).min().unwrap();
|
||||||
|
let z_max = self.voxels.iter().map(|v| v.z).max().unwrap();
|
||||||
|
|
||||||
|
let width = (x_max + 1 - x_min) as usize;
|
||||||
|
let height = (y_max + 1 - y_min) as usize;
|
||||||
|
let depth = (z_max + 1 - z_min) as usize;
|
||||||
|
|
||||||
|
|
||||||
|
let mut grid = Grid3D {
|
||||||
|
data: vec![Unknown; width * depth * height],
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
depth,
|
||||||
|
offset: Vec3 { x: x_min, y: y_min, z: z_min },
|
||||||
|
max: Vec3{ x: x_max, y: y_max, z: z_max}
|
||||||
|
};
|
||||||
|
|
||||||
|
for vox in &self.voxels {
|
||||||
|
grid.set(&vox.x, &vox.y, &vox.z, VoxelState::Rock);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut queue = Vec::new();
|
||||||
|
// Now we mark all voxels on the edge as air (if they're not rock)
|
||||||
|
for x in grid.offset.x..grid.max.x+1 {
|
||||||
|
for y in grid.offset.y..grid.max.y+1 {
|
||||||
|
queue_if_unknown(&mut grid, &mut queue, &x, &y, &z_min);
|
||||||
|
queue_if_unknown(&mut grid, &mut queue, &x, &y, &z_max);
|
||||||
|
}
|
||||||
|
for z in grid.offset.z..grid.max.z+1 {
|
||||||
|
queue_if_unknown(&mut grid, &mut queue, &x, &y_min, &z);
|
||||||
|
queue_if_unknown(&mut grid, &mut queue, &x, &y_max, &z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for z in z_min..z_max + 1 {
|
||||||
|
for y in y_min..y_max + 1 {
|
||||||
|
queue_if_unknown(&mut grid, &mut queue, &x_min, &y, &z);
|
||||||
|
queue_if_unknown(&mut grid, &mut queue, &x_max, &y, &z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We search one extra cube around the actual grid, so that the search doesn't get blocked
|
||||||
|
// by rocks hitting the outer edge:
|
||||||
|
queue.push(grid.offset.moved(-1, 0, 0));
|
||||||
|
while let Some(pos) = queue.pop() {
|
||||||
|
// If we got here, it means that "pos" is in air, and we need to check if the neighbors are as well
|
||||||
|
try_pos(&mut grid, &mut queue, &pos, 1, 0, 0);
|
||||||
|
try_pos(&mut grid, &mut queue, &pos, -1, 0, 0);
|
||||||
|
try_pos(&mut grid, &mut queue, &pos, 0, 1, 0);
|
||||||
|
try_pos(&mut grid, &mut queue, &pos, 0, -1, 0);
|
||||||
|
try_pos(&mut grid, &mut queue, &pos, 0, 0, 1);
|
||||||
|
try_pos(&mut grid, &mut queue, &pos, 0, 0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// and finally, we do the same check (basically) as in pt 1:
|
||||||
|
let mut sides = 0;
|
||||||
|
for voxel in &self.voxels {
|
||||||
|
// We just scan all sides if there are voxels there
|
||||||
|
if grid.get(&(voxel.x + 1), &voxel.y, &voxel.z) == &Air { sides += 1 };
|
||||||
|
if grid.get(&(voxel.x - 1), &voxel.y, &voxel.z) == &Air { sides += 1 };
|
||||||
|
if grid.get(&voxel.x, &(voxel.y + 1), &voxel.z) == &Air { sides += 1 };
|
||||||
|
if grid.get(&voxel.x, &(voxel.y - 1), &voxel.z) == &Air { sides += 1 };
|
||||||
|
if grid.get(&voxel.x, &voxel.y, &(voxel.z + 1)) == &Air { sides += 1 };
|
||||||
|
if grid.get(&voxel.x, &voxel.y, &(voxel.z - 1)) == &Air { sides += 1 };
|
||||||
|
}
|
||||||
|
// println!("{:?}", grid.data);
|
||||||
|
// return grid.data.iter().filter(|v| v == &&Unknown).count().to_string();
|
||||||
|
sides.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_pos(grid: &mut Grid3D, queue: &mut Vec<Vec3>, pos: &Vec3, offset_x: i32, offset_y: i32, offset_z: i32) {
|
||||||
|
let x = pos.x + offset_x;
|
||||||
|
let y = pos.y + offset_y;
|
||||||
|
let z = pos.z + offset_z;
|
||||||
|
queue_if_unknown(grid, queue, &x, &y, &z);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queue_if_unknown(grid: &mut Grid3D, queue: &mut Vec<Vec3>, x: &i32, y: &i32, z: &i32) {
|
||||||
|
if grid.get(x, y, z) == &Unknown {
|
||||||
|
grid.set(x, y, z, Air);
|
||||||
|
queue.push(Vec3::new(*x, *y, *z))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||||
|
struct Vec3 {
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
z: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vec3 {
|
||||||
|
fn moved(&self, x: i32, y: i32, z: i32) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: self.x + x,
|
||||||
|
y: self.y + y,
|
||||||
|
z: self.z + z
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(x: i32, y: i32, z: i32) -> Vec3 {
|
||||||
|
return Vec3 {
|
||||||
|
x, y, z
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
struct Grid3D {
|
||||||
|
data: Vec<VoxelState>,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
depth: usize,
|
||||||
|
offset: Vec3,
|
||||||
|
max: Vec3
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Grid3D {
|
||||||
|
fn set(&mut self, x: &i32, y: &i32, z: &i32, value: VoxelState) {
|
||||||
|
|
||||||
|
if x < &self.offset.x || y < &self.offset.y || z < &self.offset.z {
|
||||||
|
panic!("Invalid position to update the grid: ({}, {}, {})", x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
let coord = self.grid_coord(&x, &y, &z);
|
||||||
|
self.data[coord] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, x: &i32, y: &i32, z: &i32) -> &VoxelState {
|
||||||
|
if x < &self.offset.x || y < &self.offset.y || z < &self.offset.z ||
|
||||||
|
x > &self.max.x || y > &self.max.y || z > &self.max.z {
|
||||||
|
&VoxelState::Air
|
||||||
|
} else {
|
||||||
|
&self.data[self.grid_coord(x, y, z)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn grid_coord(&self, x: &i32, y: &i32, z: &i32) -> usize {
|
||||||
|
(x - self.offset.x) as usize + ((y - self.offset.y) as usize * self.width) + ((z - self.offset.z) as usize * self.width * self.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
enum VoxelState {
|
||||||
|
Air, Rock, Unknown
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ use crate::day14::Day14;
|
|||||||
use crate::day15::Day15;
|
use crate::day15::Day15;
|
||||||
use crate::day16::Day16;
|
use crate::day16::Day16;
|
||||||
use crate::day17::Day17;
|
use crate::day17::Day17;
|
||||||
|
use crate::day18::Day18;
|
||||||
use crate::day_solver::DaySolver;
|
use crate::day_solver::DaySolver;
|
||||||
|
|
||||||
mod util;
|
mod util;
|
||||||
@@ -39,6 +40,7 @@ mod day14;
|
|||||||
mod day15;
|
mod day15;
|
||||||
mod day16;
|
mod day16;
|
||||||
mod day17;
|
mod day17;
|
||||||
|
mod day18;
|
||||||
|
|
||||||
const MAX_DAY: u8 = 17;
|
const MAX_DAY: u8 = 17;
|
||||||
const DEFAULT_BENCHMARK_AMOUNT: u32 = 100;
|
const DEFAULT_BENCHMARK_AMOUNT: u32 = 100;
|
||||||
@@ -121,6 +123,7 @@ fn build_day_solver(day: u8) -> Option<Box<dyn DaySolver>> {
|
|||||||
15 => Some(Box::new(Day15::create())),
|
15 => Some(Box::new(Day15::create())),
|
||||||
16 => Some(Box::new(Day16::create())),
|
16 => Some(Box::new(Day16::create())),
|
||||||
17 => Some(Box::new(Day17::create())),
|
17 => Some(Box::new(Day17::create())),
|
||||||
|
18 => Some(Box::new(Day18::create())),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user