lib2048/
lib.rs

1extern crate rand;
2
3use std::ops::Index;
4use std::ops::IndexMut;
5
6/*
7    Example frontend-backend communication
8
9    > go right
10    < display move: tile (1, 1) -> (1, 4), ...
11      display combine: tile (1, 2) into tile (1, 1)
12      display create: tile (3, 3) -> 2, ...
13    > go left
14    < display ...
15    > go up
16    < game over
17
18 */
19
20//////////////////////////////////// STRUCTURES ////////////////////////////////////
21
22#[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    /* When size = (300, 500), maximum pos is (299, 499)
33       (0, 0) => 0
34       (0, 10) => 10
35       (0, 499) => 500
36       (1, 0) => 501
37       (1, 499) => 1000,
38       (299, 499) => 300 * 500 - 1
39    */
40    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// For value n as u8, the shown number is 2^(n-1). Specially, if n==0, there isn't a tile.
85// That means, 0 => nothing, 1 => 2, 2 => 4, 3 => 8, ..., 11 => 2048.
86#[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
126//////////////////////////////////// CONTROLLING ////////////////////////////////////
127
128fn 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        // 从左往右扫ab,忽略0,找到a!=b就将a取出,找到a==b就将ab合并
162        // 先忽略0
163        ind.retain(|&e| board[e] != 0);
164        // 找!
165        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                //合并i和i+1
172                let val = board[ind[i]]; // in case that i or i+1 equals target_ind[ptr]
173                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] { // filter unnecessary moves
181                //取出i
182                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
210//////////////////////////////////// GENERATE TILES ////////////////////////////////////
211
212use 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
242//////////////////////////////////// GAME OVER LOGIC ////////////////////////////////////
243
244fn 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//////////////////////////////////// UNIT TESTS ////////////////////////////////////
261
262#[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