├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "2048" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "rand 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "rustbox 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "advapi32-sys" 11 | version = "0.1.2" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | dependencies = [ 14 | "winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 15 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 16 | ] 17 | 18 | [[package]] 19 | name = "bitflags" 20 | version = "0.2.1" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | 23 | [[package]] 24 | name = "gag" 25 | version = "0.1.6" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | dependencies = [ 28 | "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 29 | "tempfile 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 30 | ] 31 | 32 | [[package]] 33 | name = "kernel32-sys" 34 | version = "0.1.4" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | dependencies = [ 37 | "winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 38 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 39 | ] 40 | 41 | [[package]] 42 | name = "libc" 43 | version = "0.1.10" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | 46 | [[package]] 47 | name = "num" 48 | version = "0.1.27" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | dependencies = [ 51 | "rand 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", 52 | "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", 53 | ] 54 | 55 | [[package]] 56 | name = "rand" 57 | version = "0.3.11" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | dependencies = [ 60 | "advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 62 | "winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 63 | ] 64 | 65 | [[package]] 66 | name = "rustbox" 67 | version = "0.7.1" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | dependencies = [ 70 | "bitflags 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 71 | "gag 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 72 | "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 73 | "num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 74 | "termbox-sys 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 75 | "time 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 76 | ] 77 | 78 | [[package]] 79 | name = "rustc-serialize" 80 | version = "0.3.16" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | 83 | [[package]] 84 | name = "tempfile" 85 | version = "1.1.1" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | dependencies = [ 88 | "kernel32-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 89 | "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 90 | "rand 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 92 | ] 93 | 94 | [[package]] 95 | name = "termbox-sys" 96 | version = "0.2.8" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | 99 | [[package]] 100 | name = "time" 101 | version = "0.1.32" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | dependencies = [ 104 | "kernel32-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 105 | "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 107 | ] 108 | 109 | [[package]] 110 | name = "winapi" 111 | version = "0.2.4" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | 114 | [[package]] 115 | name = "winapi-build" 116 | version = "0.1.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | 119 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "2048" 3 | version = "0.1.0" 4 | authors = ["Kevin Darlington "] 5 | 6 | [dependencies] 7 | rustbox = "*" 8 | rand = "0.3" 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rust-2048 2 | ========= 3 | 4 | The 2048 game programmed in rust using [rustbox](https://github.com/gchp/rustbox). 5 | 6 | ![2048](https://cloud.githubusercontent.com/assets/119919/9983806/b692b8de-5fd8-11e5-81c1-1ee823cf8283.png) 7 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Implements http://rosettacode.org/wiki/2048 2 | // 3 | // Based on the C++ version: http://rosettacode.org/wiki/2048#C.2B.2B 4 | // Uses rustbox (termbox) to draw the board. 5 | 6 | extern crate rustbox; 7 | extern crate rand; 8 | 9 | use std::default::Default; 10 | use std::fmt; 11 | use rand::distributions::{IndependentSample, Range}; 12 | use rustbox::{Color, RustBox}; 13 | use rustbox::Key as RKey; 14 | 15 | #[derive(PartialEq, Clone)] 16 | enum Direction { 17 | Up, 18 | Down, 19 | Left, 20 | Right, 21 | } 22 | 23 | impl Direction { 24 | fn offset(self) -> i32 { 25 | match self { 26 | Direction::Up => -1, 27 | Direction::Down => 1, 28 | Direction::Left => -1, 29 | Direction::Right => 1, 30 | } 31 | } 32 | } 33 | 34 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 35 | pub enum Key { 36 | Right, 37 | Left, 38 | Up, 39 | Down, 40 | Char(char), 41 | } 42 | 43 | trait UI { 44 | fn wait_key(&self) -> Option; 45 | fn draw_grid(&self, grid: [[Tile; 4]; 4], rows: usize, cols: usize); 46 | fn present(&self); 47 | fn draw_lost(&self); 48 | fn draw_won(&self); 49 | fn draw_score(&self, text: String); 50 | fn draw_instructions(&self, text: String); 51 | } 52 | 53 | struct TermboxUI<'a> { 54 | rustbox: &'a RustBox, 55 | } 56 | 57 | impl<'a> UI for TermboxUI<'a> { 58 | fn wait_key(&self) -> Option { 59 | match self.rustbox.poll_event(false) { 60 | Ok(rustbox::Event::KeyEvent(key)) => { 61 | match key { 62 | Some(RKey::Char('q')) => Some(Key::Char('q')), 63 | Some(RKey::Up) => Some(Key::Up), 64 | Some(RKey::Down) => Some(Key::Down), 65 | Some(RKey::Left) => Some(Key::Left), 66 | Some(RKey::Right) => Some(Key::Right), 67 | _ => None, 68 | } 69 | }, 70 | Err(e) => panic!("{}", e), 71 | _ => None 72 | } 73 | } 74 | 75 | fn draw_grid(&self, grid: [[Tile; 4]; 4], rows: usize, cols: usize) { 76 | let x = 0; 77 | let y = 2; 78 | let width = 30; 79 | let height = 18; 80 | let cell_width = (width + 2*rows) / cols; 81 | let cell_height = height / rows; 82 | 83 | for i in 0..rows { 84 | let x_coord = x + i*cell_width + i; 85 | 86 | for j in 0..cols { 87 | let y_coord = y + j*cell_height + j; 88 | 89 | let x_text_offset = (cell_width as f64 / 2 as f64).floor() as usize; 90 | let y_text_offset = (cell_height as f64 / 2 as f64).floor() as usize; 91 | 92 | let num: String = format!("{}", grid[i][j]); 93 | let x_text_offset = x_text_offset - num.len() / 4; 94 | self.draw_rectangle(x_coord, y_coord, cell_width, cell_height, Color::Black, Color::White, Color::Black); 95 | if num != "0" { 96 | self.rustbox.print(x_coord + x_text_offset, y_coord + y_text_offset, rustbox::RB_NORMAL, Color::White, Color::Black, &num); 97 | } 98 | } 99 | } 100 | } 101 | 102 | fn present(&self) { 103 | self.rustbox.present(); 104 | } 105 | 106 | fn draw_lost(&self) { 107 | self.draw_text(16, 12, "You lost!".to_string(), Color::Red, Color::Default); 108 | } 109 | 110 | fn draw_won(&self) { 111 | self.draw_text(16, 12, "You won!".to_string(), Color::Green, Color::Default); 112 | } 113 | 114 | fn draw_score(&self, text: String) { 115 | self.draw_text(16, 1, text, Color::White, Color::Default); 116 | } 117 | 118 | fn draw_instructions(&self, text: String) { 119 | self.draw_text(14, 22, text, Color::White, Color::Default); 120 | } 121 | } 122 | 123 | impl<'a> TermboxUI<'a> { 124 | fn new(rustbox: &'a rustbox::RustBox) -> TermboxUI<'a> { 125 | TermboxUI{rustbox: rustbox} 126 | } 127 | 128 | fn fill_area(&self, x: usize, y: usize, w: usize, h: usize, fg: Color, bg: Color) { 129 | for row in 0..h { 130 | for column in 0..w { 131 | self.rustbox.print_char(x+column, y+row, rustbox::RB_NORMAL, fg, bg, ' '); 132 | } 133 | } 134 | } 135 | 136 | fn draw_horizontal_line(&self, x: usize, y: usize, w: usize, fg: Color, bg: Color) { 137 | for i in 0..w+1 { 138 | self.rustbox.print_char(x+i, y, rustbox::RB_NORMAL, fg, bg, '─'); 139 | } 140 | } 141 | 142 | fn draw_vertical_line(&self, x: usize, y: usize, h: usize, fg: Color, bg: Color) { 143 | for i in 0..h+1 { 144 | self.rustbox.print_char(x, y+i, rustbox::RB_NORMAL, fg, bg, '│'); 145 | } 146 | } 147 | 148 | fn draw_rectangle(&self, x: usize, y: usize, w: usize, h: usize, fill: Color, fg: Color, bg: Color) { 149 | self.fill_area(x, y, w, h, fill, fill); 150 | self.draw_horizontal_line(x, y, w, fg, bg); // top 151 | self.draw_horizontal_line(x, h+y, w, fg, bg); // bottom 152 | self.draw_vertical_line(x, y, h, fg, bg); // left 153 | self.draw_vertical_line(x+w, y, h, fg, bg); // right 154 | self.rustbox.print_char(x, y, rustbox::RB_NORMAL, fg, bg, '┌'); 155 | self.rustbox.print_char(x+w, y, rustbox::RB_NORMAL, fg, bg, '┐'); 156 | self.rustbox.print_char(x, y+h, rustbox::RB_NORMAL, fg, bg, '└'); 157 | self.rustbox.print_char(x+w, y+h, rustbox::RB_NORMAL, fg, bg, '┘'); 158 | } 159 | 160 | fn draw_text(&self, x: usize, y: usize, line: String, fg: Color, bg: Color) -> (usize, usize) { 161 | for (i, ch) in line.chars().enumerate() { 162 | self.rustbox.print_char(x+i, y, rustbox::RB_NORMAL, fg, bg, ch); 163 | } 164 | (x + line.len(), y) 165 | } 166 | } 167 | 168 | #[derive(Copy, Clone)] 169 | struct Tile { 170 | _value: usize, 171 | _blocked: bool, 172 | } 173 | 174 | impl Tile { 175 | fn new() -> Tile { 176 | Tile{_value: 0, _blocked: false} 177 | } 178 | 179 | fn set(&mut self, val: usize) { 180 | self._value = val; 181 | } 182 | 183 | fn get(&self) -> usize { 184 | self._value 185 | } 186 | 187 | fn is_empty(&self) -> bool { 188 | self._value == 0 189 | } 190 | 191 | fn blocked(&mut self, b: bool) { 192 | self._blocked = b; 193 | } 194 | 195 | fn is_blocked(&self) -> bool { 196 | return self._blocked; 197 | } 198 | } 199 | 200 | impl fmt::Display for Tile { 201 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 202 | write!(f, "{}", self._value) 203 | } 204 | } 205 | 206 | impl PartialEq for Tile { 207 | fn eq(&self, other: &Tile) -> bool { 208 | self._value == other._value 209 | } 210 | 211 | fn ne(&self, other: &Tile) -> bool { 212 | self._value != other._value 213 | } 214 | } 215 | 216 | #[derive(PartialEq, Debug)] 217 | enum State { 218 | Playing, 219 | Won, 220 | Lost, 221 | } 222 | 223 | struct Game<'a> { 224 | ui: &'a UI, 225 | grid: [[Tile; 4]; 4], 226 | state: State, 227 | score: usize, 228 | moved: bool, 229 | } 230 | 231 | impl<'a> Game<'a> { 232 | fn new(ui: &'a UI) -> Game<'a> { 233 | let mut g = Game{ui: ui, grid: [[Tile::new(); 4]; 4], state: State::Playing, score: 0, moved: false}; 234 | for _ in 0..2 { 235 | g.add_tile(); 236 | } 237 | 238 | g 239 | } 240 | 241 | fn run(&mut self) { 242 | loop { 243 | self.draw(); 244 | 245 | self.moved = false; 246 | 247 | let key = self.ui.wait_key(); 248 | if key == Some(Key::Char('q')) { 249 | break; 250 | } 251 | 252 | if self.state != State::Lost && self.state != State::Won { 253 | match key { 254 | Some(Key::Up) => { self.move_up(); } 255 | Some(Key::Down) => { self.move_down(); } 256 | Some(Key::Left) => { self.move_left(); } 257 | Some(Key::Right) => { self.move_right(); } 258 | _ => {} 259 | } 260 | } 261 | 262 | for i in 0..4 { 263 | for j in 0..4 { 264 | self.grid[i][j].blocked(false); 265 | } 266 | } 267 | 268 | if self.moved { 269 | self.add_tile(); 270 | } else if !self.can_move() { 271 | self.state = State::Lost; 272 | } 273 | } 274 | } 275 | 276 | fn add_tile(&mut self) { 277 | let mut cantadd = true; 278 | 'OUTER: for i in 0..4 { 279 | for j in 0..4 { 280 | if self.grid[i][j].is_empty() { 281 | cantadd = false; 282 | break 'OUTER; 283 | } 284 | } 285 | } 286 | 287 | let cantmove = !self.can_move(); 288 | if cantadd || cantmove { 289 | return; 290 | } 291 | 292 | let between = Range::new(0f64, 1.); 293 | let mut rng = rand::thread_rng(); 294 | let a = between.ind_sample(&mut rng); 295 | 296 | let mut cell1 = rand::random::<(usize, usize)>(); 297 | while !self.grid[cell1.0 % 4][cell1.1 % 4].is_empty() { 298 | cell1 = rand::random::<(usize, usize)>(); 299 | } 300 | self.grid[cell1.0 % 4][cell1.1 % 4].set(if a > 0.9 { 4 } else { 2 }); 301 | } 302 | 303 | fn can_move(&self) -> bool { 304 | for i in 0..4 { 305 | for j in 0..4 { 306 | if self.grid[i][j].is_empty() { 307 | return true; 308 | } 309 | 310 | if self.test_add(i + 1, j, self.grid[i][j]) {return true}; 311 | if i > 0 && self.test_add(i - 1, j, self.grid[i][j]) {return true}; 312 | if self.test_add(i, j + 1, self.grid[i][j]) {return true}; 313 | if j > 0 && self.test_add(i, j - 1, self.grid[i][j]) {return true}; 314 | } 315 | } 316 | 317 | return false; 318 | } 319 | 320 | fn test_add(&self, x: usize, y: usize, v: Tile) -> bool { 321 | if x > 3 || y > 3 { 322 | return false; 323 | } 324 | return self.grid[x][y] == v; 325 | } 326 | 327 | fn add_score(&mut self, score: usize) { 328 | self.score += score; 329 | 330 | if score == 2048 { 331 | self.state = State::Won; 332 | } 333 | } 334 | 335 | fn draw(&self) { 336 | self.ui.draw_score(format!("Score: {}", self.score)); 337 | self.ui.draw_grid(self.grid, 4, 4); 338 | self.ui.draw_instructions("←,↑,→,↓ or q".to_string()); 339 | 340 | if self.state == State::Lost { 341 | self.ui.draw_lost(); 342 | } else if self.state == State::Won { 343 | self.ui.draw_won(); 344 | } 345 | 346 | self.ui.present(); 347 | } 348 | 349 | fn move_direction(&mut self, x: usize, y: usize, d: Direction) { 350 | let o = d.clone().offset(); 351 | 352 | if d == Direction::Up || d == Direction::Down { 353 | if y as i32+o < 0 || y as i32+o > 3 { 354 | return; 355 | } 356 | 357 | let yo: usize = (y as i32+o) as usize; 358 | 359 | if !self.grid[x][yo].is_empty() && self.grid[x][yo] == self.grid[x][y] && !self.grid[x][y].is_blocked() && !self.grid[x][yo].is_blocked() { 360 | self.grid[x][y].set(0); 361 | let val = self.grid[x][yo].get(); 362 | self.grid[x][yo].set(val * 2); 363 | self.add_score(val * 2); 364 | self.grid[x][yo].blocked(true); 365 | self.moved = true; 366 | } else if self.grid[x][yo].is_empty() && !self.grid[x][y].is_empty() { 367 | let val = self.grid[x][y].get(); 368 | self.grid[x][yo].set(val); 369 | self.grid[x][y].set(0); 370 | self.moved = true; 371 | } 372 | 373 | self.move_direction(x, yo, d); 374 | } else if d == Direction::Left || d == Direction::Right { 375 | if x as i32+o < 0 || x as i32+o > 3 { 376 | return; 377 | } 378 | 379 | let xo: usize = (x as i32+o) as usize; 380 | 381 | if !self.grid[xo][y].is_empty() && self.grid[xo][y] == self.grid[x][y] && !self.grid[x][y].is_blocked() && !self.grid[xo][y].is_blocked() { 382 | self.grid[x][y].set(0); 383 | let val = self.grid[xo][y].get(); 384 | self.grid[xo][y].set(val * 2); 385 | self.add_score(val * 2); 386 | self.grid[xo][y].blocked(true); 387 | self.moved = true; 388 | } else if self.grid[xo][y].is_empty() && !self.grid[x][y].is_empty() { 389 | let val = self.grid[x][y].get(); 390 | self.grid[xo][y].set(val); 391 | self.grid[x][y].set(0); 392 | self.moved = true; 393 | } 394 | 395 | self.move_direction(xo, y, d); 396 | } 397 | } 398 | 399 | fn move_up(&mut self) { 400 | for i in 0..4 { 401 | for j in 1..4 { 402 | if !self.grid[i][j].is_empty() { 403 | self.move_direction(i, j, Direction::Up); 404 | } 405 | } 406 | } 407 | } 408 | 409 | fn move_down(&mut self) { 410 | for i in 0..4 { 411 | for j in (0..3).rev() { 412 | if !self.grid[i][j].is_empty() { 413 | self.move_direction(i, j, Direction::Down); 414 | } 415 | } 416 | } 417 | } 418 | 419 | fn move_left(&mut self) { 420 | for j in 0..4 { 421 | for i in 1..4 { 422 | if !self.grid[i][j].is_empty() { 423 | self.move_direction(i, j, Direction::Left); 424 | } 425 | } 426 | } 427 | } 428 | 429 | fn move_right(&mut self) { 430 | for j in 0..4 { 431 | for i in (0..3).rev() { 432 | if !self.grid[i][j].is_empty() { 433 | self.move_direction(i, j, Direction::Right); 434 | } 435 | } 436 | } 437 | } 438 | } 439 | 440 | fn main() { 441 | let rustbox = match RustBox::init(Default::default()) { 442 | Result::Ok(v) => v, 443 | Result::Err(e) => panic!("{}", e), 444 | }; 445 | 446 | let ui = TermboxUI::new(&rustbox); 447 | let mut game = Game::new(&ui); 448 | game.run(); 449 | } 450 | --------------------------------------------------------------------------------