1extern crate rand;
2
3use std::ops::Index;
4use std::ops::IndexMut;
5
6#[derive(Eq, PartialEq, Debug, Copy, Clone)]
23pub struct TilePos(usize, usize);
24
25impl From<(usize, usize)> for TilePos {
26 fn from(p: (usize, usize)) -> Self {
27 TilePos(p.0, p.1)
28 }
29}
30
31impl TilePos {
32 pub fn to_usize_index(&self, size: TilePos) -> usize {
41 self.0 * size.1 + self.1
42 }
43
44 fn from_usize_index(index: usize, size: TilePos) -> TilePos {
45 TilePos::from((index / size.1, index % size.1))
46 }
47
48 fn size_to_max_index(&self) -> usize {
49 self.0 * self.1 - 1
50 }
51
52 pub fn size_as_usize(&self) -> usize {
53 self.0 * self.1
54 }
55}
56
57impl Index<usize> for Board {
58 type Output = u8;
59
60 fn index(&self, index: usize) -> &u8 {
61 &self.content[index]
62 }
63}
64
65impl IndexMut<usize> for Board {
66
67 fn index_mut(&mut self, index: usize) -> &mut u8 {
68 &mut self.content[index]
69 }
70}
71
72#[derive(Copy, Clone, Debug)]
73pub enum Control { Up, Down, Left, Right }
74
75#[derive(Copy, Clone, Debug)]
76pub enum Display {
77 Create { pos: TilePos, value: u8 },
78 CombineInto { a: TilePos, b: TilePos, target: TilePos },
79 Move { from: TilePos, to: TilePos },
80 ScoreAdd { add: usize },
81 GameOver { score: usize }
82}
83
84#[derive(Debug, Eq, PartialEq)]
87pub struct Board {
88 content: Vec<u8>,
89 size: TilePos,
90 score: usize,
91}
92
93impl Board {
94 pub fn new(size: impl Into<TilePos>) -> Board {
95 let size = size.into();
96 Board::from_raw_board(size, Vec::new())
97 }
98
99 fn from_raw_board(size: impl Into<TilePos>, mut content: Vec<u8>) -> Board {
100 let size = size.into();
101 content.resize(size.size_as_usize(), 0);
102 Board {
103 content,
104 size,
105 score: 0
106 }
107 }
108
109 #[must_use]
110 pub fn start_game(&mut self) -> Vec<Display> {
111 gen(self, 4)
112 }
113
114 #[must_use]
115 pub fn control_and_generate(&mut self, ctrl: Control) -> Vec<Display> {
116 let mut vec = Vec::new();
117 vec.append(&mut control_move(self, &ctrl).clone());
118 vec.append(&mut gen(self, 2).clone());
119 if is_game_over(self) {
120 vec.push(Display::GameOver { score: self.score });
121 };
122 vec
123 }
124}
125
126fn next_index(ct: &Control, ind: usize, &TilePos(rows, columns): &TilePos) -> Option<usize> {
129 use Control::{Up, Down, Left, Right};
130 match *ct {
131 Up => if ind >= columns * (rows - 1) { None } else { Some(ind + columns) },
132 Down => if ind < columns { None } else { Some(ind - columns) },
133 Left => if ind % columns == columns - 1 { None } else { Some(ind + 1) },
134 Right => if ind % columns == 0 { None } else { Some(ind - 1) },
135 }
136}
137
138fn each_start(ct: &Control, &TilePos(rows, columns): &TilePos) -> impl Iterator<Item = usize> {
139 use Control::{Up, Down, Left, Right};
140 let (begin, end, multiplier, minus) = match *ct {
141 Up => (0, columns, 1, 0),
142 Down => (columns * (rows - 1), columns * rows, 1, 0),
143 Left => (0, rows, columns, 0),
144 Right => (1, rows + 1, columns, 1),
145 };
146 (begin .. end).map( move |k| k * multiplier).map(move |k| k - minus)
147}
148
149#[must_use]
150fn control_move(board: &mut Board, ctrl: &Control) -> Vec<Display> {
151 let mut ans = Vec::new();
152 for start_ind in each_start(ctrl, &board.size) {
153 let mut last_ind = start_ind;
154 let mut ind = Vec::new();
155 ind.push(last_ind);
156 while let Some(next_ind) = next_index(ctrl, last_ind, &board.size) {
157 ind.push(next_ind);
158 last_ind = next_ind;
159 }
160 let target_ind = ind.clone();
161 ind.retain(|&e| board[e] != 0);
164 let mut ptr = 0;
166 for i in 0..ind.len() {
167 if board[ind[i]] == 0 {
168 continue;
169 }
170 if i != ind.len() - 1 && board[ind[i]] == board[ind[i + 1]] {
171 let val = board[ind[i]]; board[ind[i]] = 0;
174 board[ind[i + 1]] = 0;
175 board[target_ind[ptr]] = val + 1;
176 display_combine_into(&mut ans, ind[i + 1], ind[i], target_ind[ptr],&board);
177 let add = 2usize << val as usize;
178 ans.push(Display::ScoreAdd { add });
179 board.score += add;
180 } else if ind[i] != target_ind[ptr] { let val = board[ind[i]];
183 board[ind[i]] = 0;
184 board[target_ind[ptr]] = val;
185 display_move(&mut ans, ind[i], target_ind[ptr], &board);
186 }
187 ptr += 1;
188 }
189 };
190 ans
191}
192
193fn display_combine_into(v: &mut Vec<Display>, a: usize, b: usize, target: usize, bo: &Board) {
194 let r = Display::CombineInto {
195 a: TilePos::from_usize_index(a, bo.size),
196 b: TilePos::from_usize_index(b, bo.size),
197 target: TilePos::from_usize_index(target, bo.size)
198 };
199 v.push(r);
200}
201
202fn display_move(v: &mut Vec<Display>, f: usize, t: usize, b: &Board) {
203 let r = Display::Move {
204 from: TilePos::from_usize_index(f, b.size),
205 to: TilePos::from_usize_index( t, b.size),
206 };
207 v.push(r);
208}
209
210use rand::Rng;
213
214fn gen_tile_value() -> u8 {
215 if rand::thread_rng().gen_bool(0.9) { 1 } else { 2 }
216}
217
218fn gen_pos(board: &Board) -> Option<TilePos> {
219 let mut available = Vec::new();
220 for i in 0..=board.size.size_to_max_index() {
221 if board[i] == 0 {
222 available.push(TilePos::from_usize_index(i, board.size));
223 }
224 }
225 rand::thread_rng().choose(&available).map(|a| *a)
226}
227
228#[must_use]
229fn gen(board: &mut Board, count: usize) -> Vec<Display> {
230 let size = board.size;
231 let mut ans = Vec::new();
232 for _ in 0..count {
233 if let Some(pos) = gen_pos(&board) {
234 let value = gen_tile_value();
235 board[pos.to_usize_index(size)] = value;
236 ans.push(Display::Create { pos, value })
237 }
238 }
239 ans
240}
241
242fn is_game_over(board: &Board) -> bool {
245 for i in 0..=board.size.size_to_max_index() {
246 if board[i] == 0 {
247 return false;
248 }
249 for ct in [Control::Right, Control::Down].iter() {
250 if let Some(next) = next_index(ct, i, &board.size) {
251 if board[i] == board[next] {
252 return false;
253 }
254 }
255 }
256 }
257 true
258}
259
260#[cfg(test)]
263mod tests {
264 use Board;
265 use control_move;
266 use Control;
267 use TilePos;
268 use is_game_over;
269 use gen_tile_value;
270 use gen_pos;
271 use gen;
272 use Display;
273
274 #[test]
275 fn index_usize_convert() {
276 let cond = [
277 ((300, 500), (0, 0), 0),
278 ((300, 500), (0, 10), 10),
279 ((300, 500), (0, 499), 499),
280 ((300, 500), (1, 0), 500),
281 ((300, 500), (1, 499), 999),
282 ((300, 500), (299, 499), 300 * 500 - 1)
283 ];
284 for ((sx, sy), (x, y), i) in cond.iter() {
285 let size = TilePos::from((*sx, *sy));
286 let pos = TilePos::from((*x, *y));
287 let i1 = pos.to_usize_index(size);
288 assert_eq!(*i, i1);
289 let i2 = TilePos::from_usize_index(i1, size);
290 assert_eq!(pos, i2);
291 }
292 }
293
294 #[test]
295 fn index_new() {
296 let cond = [
297 ((10, 5), 10 * 5 - 1),
298 ((300, 500), 300 * 500 - 1),
299 ];
300 for ((sx, sy), s) in cond.iter() {
301 let size = TilePos::from((*sx, *sy));
302 let i = size.size_to_max_index();
303 assert_eq!(i, *s);
304 }
305 }
306
307 #[test]
308 fn board_new() {
309 let g = Board::new((5, 10));
310 assert_eq!(g.content.len(), 50);
311 assert_eq!(g.size, TilePos::from((5, 10)));
312 }
313
314 #[test]
315 fn control_move_output() {
316 let mut g = Board::from_raw_board((2, 4), vec![
317 1, 1, 0, 2,
318 0, 4, 4, 2,
319 ]);
320 let a = control_move(&mut g, &Control::Left);
321 let ans = String::from("[\
322 CombineInto { a: TilePos(0, 1), b: TilePos(0, 0), target: TilePos(0, 0) }, \
323 Move { from: TilePos(0, 3), to: TilePos(0, 1) }, \
324 CombineInto { a: TilePos(1, 2), b: TilePos(1, 1), target: TilePos(1, 0) }, \
325 Move { from: TilePos(1, 3), to: TilePos(1, 1) }]");
326 assert_eq!(ans, format!("{:?}", a));
327 }
328
329 #[test]
330 fn control_move_path() {
331 let cond = [
332 (Control::Left, Board::from_raw_board((8, 7), vec![
333 2, 0, 0, 0, 0, 0, 0,
334 7, 3, 0, 0, 0, 0, 0,
335 3, 0, 0, 0, 0, 0, 0,
336 4, 3, 5, 2, 1, 0, 0,
337 3, 4, 3, 2, 0, 0, 0,
338 3, 3, 2, 0, 0, 0, 0,
339 2, 5, 2, 0, 0, 0, 0,
340 2, 1, 2, 1, 2, 1, 2,
341 ])),
342 (Control::Right, Board::from_raw_board((8, 7), vec![
343 0, 0, 0, 0, 0, 0, 2,
344 0, 0, 0, 0, 0, 7, 3,
345 0, 0, 0, 0, 0, 0, 3,
346 0, 0, 4, 3, 5, 2, 1,
347 0, 0, 0, 3, 4, 2, 3,
348 0, 0, 0, 0, 2, 3, 3,
349 0, 0, 0, 0, 2, 5, 2,
350 2, 1, 2, 1, 2, 1, 2,
351 ])),
352 (Control::Up, Board::from_raw_board((8, 7), vec![
353 3, 6, 6, 5, 3, 3, 3,
354 2, 2, 4, 3, 3, 2, 2,
355 0, 4, 2, 3, 2, 1, 0,
356 0, 3, 4, 1, 0, 2, 0,
357 0, 4, 2, 0, 0, 1, 0,
358 0, 1, 0, 0, 0, 0, 0,
359 0, 0, 0, 0, 0, 0, 0,
360 0, 0, 0, 0, 0, 0, 0,
361 ])),
362 (Control::Down, Board::from_raw_board((8, 7), vec![
363 0, 0, 0, 0, 0, 0, 0,
364 0, 0, 0, 0, 0, 0, 0,
365 0, 6, 0, 0, 0, 0, 0,
366 0, 2, 6, 0, 0, 2, 0,
367 0, 4, 4, 5, 0, 3, 0,
368 0, 3, 2, 3, 2, 1, 0,
369 2, 4, 4, 3, 3, 2, 2,
370 3, 1, 2, 1, 3, 1, 3
371 ]))
372 ];
373 for (dir, target) in cond.iter() {
374 let mut g = Board::from_raw_board((8, 7), vec![
375 0, 0, 0, 0, 0, 2, 0,
376 0, 6, 6, 0, 2, 2, 0,
377 0, 2, 0, 0, 0, 2, 0,
378 0, 4, 3, 5, 2, 1, 0,
379 2, 2, 3, 3, 2, 2, 2,
380 0, 2, 2, 2, 2, 0, 2,
381 2, 4, 4, 2, 0, 0, 0,
382 2, 1, 2, 1, 2, 1, 2,
383 ]);
384 let _ = control_move(&mut g, dir);
385 assert_eq!(g, *target);
386 }
387 }
388
389 #[test]
390 fn board_game_over() {
391 let cond = [
392 (false, Board::from_raw_board((2, 2), vec![0, 0, 0, 0])),
393 (false, Board::from_raw_board((2, 2), vec![1, 0, 0, 0])),
394 (false, Board::from_raw_board((2, 2), vec![0, 1, 0, 0])),
395 (false, Board::from_raw_board((2, 2), vec![0, 0, 1, 0])),
396 (false, Board::from_raw_board((2, 2), vec![0, 0, 0, 1])),
397 (false, Board::from_raw_board((2, 2), vec![
398 0, 1,
399 0, 1]
400 )),
401 (false, Board::from_raw_board((2, 2), vec![
402 2, 2,
403 0, 0]
404 )),
405 (false, Board::from_raw_board((2, 2), vec![
406 1, 2,
407 1, 3]
408 )),
409 (false, Board::from_raw_board((2, 2), vec![
410 1, 2,
411 0, 0]
412 )),
413 (true, Board::from_raw_board((2, 2), vec![1, 2, 3, 4])),
414 (false, Board::from_raw_board((8, 7), vec![
415 0, 0, 0, 0, 0, 2, 0,
416 0, 6, 6, 0, 2, 2, 0,
417 0, 2, 0, 0, 0, 2, 0,
418 0, 4, 3, 5, 2, 1, 0,
419 2, 2, 3, 3, 2, 2, 2,
420 0, 2, 2, 2, 2, 0, 2,
421 2, 4, 4, 2, 0, 0, 0,
422 2, 1, 2, 1, 2, 1, 2,
423 ]))
424 ];
425 for (res, b) in cond.iter() {
426 let i = is_game_over(b);
427 assert_eq!(*res, i);
428 }
429 }
430
431 #[test]
432 fn generate_tile_value() {
433 for _ in 0..1000 {
434 let a = gen_tile_value();
435 assert!(a == 1 || a == 2);
436 }
437 }
438
439 #[test]
440 fn generate_pos() {
441 let cond = [
442 (Board::from_raw_board((2, 2), vec![
443 0, 0,
444 1, 0
445 ]), Some(vec![(0, 0), (0, 1), (1, 1)])),
446 (Board::from_raw_board((2, 3), vec![
447 0, 3, 0,
448 1, 2, 1
449 ]), Some(vec![(0, 0), (0, 2)])),
450 (Board::from_raw_board((2, 2), vec![
451 2, 3,
452 1, 4
453 ]), None),
454 ];
455 fn check_contains(range: &Vec<(usize, usize)>, pos: TilePos) -> bool {
456 let a = &(pos.0, pos.1);
457 range.contains(a)
458 }
459 for (board, within) in cond.iter() {
460 for _ in 0..1000 {
461 let pos = gen_pos(board);
462 match (within, pos) {
463 (None, Some(_)) => panic!("pos generated with no space left in board"),
464 (None, None) => continue,
465 (Some(_), None) => panic!("space in board not detected"),
466 (Some(range), Some(pos)) if !check_contains(range, pos) =>
467 panic!("pos generated outside bound"),
468 (Some(_), Some(_)) => continue,
469 }
470 }
471 }
472 }
473
474 #[test]
475 fn generate() {
476 let mut board = Board::from_raw_board((2, 2), vec![
477 0, 8,
478 9, 0
479 ]);
480 let display = gen(&mut board, 2);
481 for d in &display {
482 if let Display::Create { pos, value } = d {
483 assert!(vec![(0, 0), (1, 1)].contains(&(pos.0, pos.1)));
484 assert!(vec![1, 2].contains(value));
485 }
486 }
487 }
488}
489