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
use std::path::PathBuf;
pub const NUMERIC_DATA_FILENAME: &str = "numbers.csv";
pub const COLOR_DATA_FILENAME: &str = "colors.csv";
pub const DIFFICULTY_DATA_FILENAME: &str = "difficulty.txt";
/// Returns the path of the hidden directory where game data is stored for `tsudoku`.
pub fn game_dir () -> PathBuf {
PathBuf::from(env!("HOME")).join(".tsudoku")
}
pub mod csv {
use std::{
fs,
io::Write,
};
use crate::{
terminal::display,
common,
};
/**
* Reads saved game data. Exact functionality is different depending on whether numeric or
* color code data are being parsed. The parsed data is returned as a 2D vector of bytes.
*
* filename -> The name to use to search for the save game data. This should be
* equivalent to either `common::NUMERIC_DATA_FILENAME` or
* `common::COLOR_DATA_FILENAME`.
*/
pub fn read (filename: &str) -> Result<Vec<Vec<u8>>, std::io::Error> {
let data_vec: Vec<u8> =
if filename.ends_with(common::COLOR_DATA_FILENAME) {
//NOTE: If reading color data, simply read and strip commas from vector
let mut data: Vec<u8> = std::fs::read(filename)?;
data.retain(|&b| b != ',' as u8);
data
}
else {
let data_string: String = std::fs::read_to_string(filename).expect(
format!("Couldn't find file {} to read from...", filename).as_str()
);
let mut data: Vec<u8> = Vec::new();
for line in data_string.split('\n') {
if !line.is_empty() {
let mut line: Vec<u8> = line.split(',').map(
|s| s.parse().expect(
format!("Expected to parse a number from {}", filename).as_str()
)
).collect();
/* NOTE: Newlines are still needed to properly form the data matrix in
* the for loop at the end of this function, so they need to be
* added back once each line is parsed like this.
*/
line.push('\n' as u8);
data = [data, line].concat();
}
}
data
};
let mut data: Vec<Vec<u8>> = Vec::new();
let mut row: Vec<u8> = Vec::new();
for b in data_vec {
if b == '\n' as u8 {
data.push(row);
row = Vec::new();
}
else {
row.push(b);
}
}
Ok(data)
}
/**
* Writes game data to a file. Functionality is the same whether writing numeric or color
* code data.
*
* save_game_name -> The name to save the game under. This will internally create a
* directory that stores the numeric and color data.
* data_file_name -> The file data is being saved to. This should be equivalent to
* either `common::NUMERIC_DATA_FILENAME` or
* `common::COLOR_DATA_FILENAME`.
* data -> The game data being saved. This will be either numeric or color data.
*/
pub fn write<T: ToString> (
save_game_name: &str,
data_file_name: &str,
data: &Vec<[T; display::DISPLAY_MATRIX_ROWS]>) {
let save_dir = crate::game_dir().join(&save_game_name);
let _ = fs::create_dir(&save_dir);
let mut outfile: fs::File = fs::OpenOptions::new().create(true)
.truncate(true)
.write(true)
.open(save_dir.join(data_file_name))
.expect(format!("Unable to create or open {}", data_file_name).as_str());
for d in data {
let d: String = d.iter()
.map(|item| item.to_string())
.collect::<Vec<String>>()
.join(",");
outfile.write_all(d.as_bytes())
.expect(format!("Unable to write to {}...", data_file_name).as_str());
outfile.write_all(b"\n")
.expect(
format!("Unable to write newline for {}...", data_file_name
).as_str());
}
}
}