This commit is contained in:
TimonPost 2018-08-19 23:13:21 +02:00
parent a6149f75f3
commit 685bc5e961
11 changed files with 460 additions and 34 deletions

View File

@ -28,6 +28,8 @@ use crossterm::cursor::cursor;
use std::io::Read; use std::io::Read;
fn main() { fn main() {
use crossterm::color;
let input = CROSSTERM.input(); let input = CROSSTERM.input();
let mut stdin = input.read_async().bytes(); let mut stdin = input.read_async().bytes();
CROSSTERM.cursor().hide(); CROSSTERM.cursor().hide();

View File

@ -0,0 +1,11 @@
[package]
name = "first_depth_search"
version = "0.1.0"
authors = ["TimonPost <timonpost@hotmail.nl>"]
[dependencies]
rand = "0.4.2"
[dependencies.crossterm]
path = "../../../"
branch = "development"

View File

@ -0,0 +1,141 @@
extern crate crossterm;
extern crate rand;
mod map;
mod snake;
mod variables;
mod messages;
use self::crossterm::input::input;
use self::crossterm::terminal::{terminal, ClearType};
use self::crossterm::style::Color;
use self::crossterm::{Screen, Crossterm};
use map::Map;
use variables::{Size, Direction, Position};
use snake::Snake;
use std::collections::HashMap;
use std::{thread, time};
use std::iter::Iterator;
use std::io::Read;
use std::io::Write;
fn main() {
let map_size = title_screen();
{
let mut screen = Screen::new(true);
let crossterm = Crossterm::new(&screen);
let cursor = crossterm.cursor();
let mut input = crossterm.input();
cursor.hide();
let mut stdin = input.read_async().bytes();
let mut free_positions: HashMap<String, Position> = HashMap::with_capacity((map_size.width * map_size.height) as usize);
let mut map = Map::new(map_size.clone());
map.render_map(&screen, &mut free_positions);
let mut direction = Direction::Right;
let mut snake = Snake::new(map_size.clone());
for part in snake.get_parts().iter() {
free_positions.remove_entry(format!("{},{}", part.position.x, part.position.y).as_str());
}
map.spawn_food(&free_positions, &screen);
loop {
thread::sleep(time::Duration::from_millis(500));
let pressed_key = stdin.next();
if let Some(Ok(key)) = pressed_key {
match key as char {
'w' => direction = Direction::Up,
'a' => direction = Direction::Left,
's' => direction = Direction::Down,
'd' => direction = Direction::Right,
_ => {}
}
}
snake.move_snake(&direction, &screen, &mut free_positions);
if map.is_out_of_bounds(snake.snake_parts[0].position)
{
break;
}
snake.draw_snake(&screen);
if snake.has_eaten_food(map.foot_pos)
{
map.spawn_food(&free_positions, &screen);
}
}
}
game_over_screen();
}
fn title_screen() -> Size
{
let crossterm = Crossterm::new(&Screen::default());
let cursor = crossterm.cursor();
let terminal = crossterm.terminal().clear(ClearType::All);
println!("{}",messages::SNAKERS.join("\n\r"));
cursor.goto(0, 15);
println!("Enter map width:");
cursor.goto(17, 15);
let width = crossterm.input().read_line().unwrap();
println!("\n\rEnter map height:");
cursor.goto(17, 16);
let height = crossterm.input().read_line().unwrap();
let parsed_width = width.parse::<usize>().unwrap();
let parsed_height = height.parse::<usize>().unwrap();
let terminal = crossterm.terminal().clear(ClearType::All);
return Size::new(parsed_width, parsed_height);
}
fn print_game_stats(map_size: Size, snake_lenght: usize, food_aten: usize, screen: &mut Screen)
{
let crossterm = Crossterm::new(&Screen::default());
let cursor = crossterm.cursor();
let terminal = crossterm.terminal().clear(ClearType::All);
screen.write(format!("Snake Lenght: {}\n\r", snake_lenght).as_ref());
screen.write(format!("Food aten: {}\n\r", snake_lenght).as_ref());
cursor.goto(0,map_size.height as u16);
cursor.goto(0,map_size.height as u16);
}
fn game_over_screen()
{
let crossterm = Crossterm::new(&Screen::default());
let cursor = crossterm.cursor();
let terminal = crossterm.terminal();
terminal.clear(ClearType::All);
println!("{}",messages::END_MESSAGE.join("\n\r"));
// cursor.goto()
cursor.show();
}

View File

@ -0,0 +1,80 @@
use super::variables::{Position, Size, Direction };
use super::snake::Snake;
use crossterm::{Crossterm, Screen};
use crossterm::cursor::cursor;
use crossterm::style::{ObjectStyle, StyledObject, Color, style};
use rand::distributions::{IndependentSample, Range};
use std::hash::Hash;
use std::collections::HashMap;
use std::fmt::Display;
use std::ops::Index;
use rand;
pub struct Map
{
pub size: Size,
pub foot_pos: Position,
}
impl Map
{
pub fn new(size: Size) -> Self
{
Map { size: size, foot_pos: Position::new(0,0) }
}
// render the map on the screen.
pub fn render_map(&mut self, screen: &Screen, free_positions: &mut HashMap<String, Position>)
{
let crossterm = Crossterm::new(screen);
let mut cursor = crossterm.cursor();
let mut terminal = crossterm.terminal();
for y in 0..self.size.height
{
for x in 0..self.size.height
{
if (y == 0 || y == self.size.height - 1) || (x == 0 || x == self.size.width - 1)
{
cursor.goto(x as u16, y as u16);
terminal.write("")
}else {
free_positions.insert(format!("{},{}",x,y), Position::new(x,y));
}
}
}
}
pub fn is_out_of_bounds(&self, new_pos: Position) -> bool
{
if (new_pos.x == 0 || new_pos.x == self.size.width) || (new_pos.y == 0 || new_pos.y == self.size.height)
{
return true;
}
return false;
}
pub fn is_food_eaten(&self, snake_head: Position) -> bool
{
snake_head.x == self.foot_pos.x && snake_head.y == self.foot_pos.y
}
pub fn spawn_food(&mut self, free_positions: &HashMap<String, Position>, screen: &Screen)
{
let index = Range::new(0, free_positions.len()).ind_sample(&mut rand::thread_rng());
self.foot_pos = free_positions.values().skip(index).next().unwrap().clone();
self.draw_food(screen);
}
fn draw_food(&self, screen: &Screen)
{
cursor(screen).goto(self.foot_pos.x as u16, self.foot_pos.y as u16);
style("$").with(Color::Green).paint(screen);
screen.stdout.flush();
}
}

View File

@ -0,0 +1,30 @@
pub const SNAKERS: [&str; 11] =
[
" ▄▄▄▄▄▄▄▄▄▄▄ ▄▄ ▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄ ▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ",
"▐░░░░░░░░░░░▌▐░░▌ ▐░▌▐░░░░░░░░░░░▌▐░▌ ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌",
"▐░█▀▀▀▀▀▀▀▀▀ ▐░▌░▌ ▐░▌▐░█▀▀▀▀▀▀▀█░▌▐░▌ ▐░▌ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀▀▀",
"▐░▌ ▐░▌▐░▌ ▐░▌▐░▌ ▐░▌▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌▐░▌",
"▐░█▄▄▄▄▄▄▄▄▄ ▐░▌ ▐░▌ ▐░▌▐░█▄▄▄▄▄▄▄█░▌▐░▌░▌ ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄▄▄",
"▐░░░░░░░░░░░▌▐░▌ ▐░▌ ▐░▌▐░░░░░░░░░░░▌▐░░▌ ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌",
" ▀▀▀▀▀▀▀▀▀█░▌▐░▌ ▐░▌ ▐░▌▐░█▀▀▀▀▀▀▀█░▌▐░▌░▌ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀█░█▀▀ ▀▀▀▀▀▀▀▀▀█░▌",
" ▐░▌▐░▌ ▐░▌▐░▌▐░▌ ▐░▌▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌",
" ▄▄▄▄▄▄▄▄▄█░▌▐░▌ ▐░▐░▌▐░▌ ▐░▌▐░▌ ▐░▌ ▐░█▄▄▄▄▄▄▄▄▄ ▐░▌ ▐░▌ ▄▄▄▄▄▄▄▄▄█░▌",
"▐░░░░░░░░░░░▌▐░▌ ▐░░▌▐░▌ ▐░▌▐░▌ ▐░▌▐░░░░░░░░░░░▌▐░▌ ▐░▌▐░░░░░░░░░░░▌",
" ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀▀ ▀ ▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀"
];
pub const END_MESSAGE: [&str; 11] =
[
" ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄ ▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄ ▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ",
" ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░▌ ▐░░▌▐░░░░░░░░░░░▌ ▐░░░░░░░░░░░▌▐░▌ ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌ ",
" ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░▌░▌ ▐░▐░▌▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌ ▐░▌ ▐░▌ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌ ",
" ▐░▌ ▐░▌ ▐░▌▐░▌▐░▌ ▐░▌▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ",
" ▐░▌ ▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄█░▌▐░▌ ▐░▐░▌ ▐░▌▐░█▄▄▄▄▄▄▄▄▄ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄█░▌ ",
" ▐░▌▐░░░░░░░░▌▐░░░░░░░░░░░▌▐░▌ ▐░▌ ▐░▌▐░░░░░░░░░░░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌ ",
" ▐░▌ ▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀█░▌▐░▌ ▀ ▐░▌▐░█▀▀▀▀▀▀▀▀▀ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀█░█▀▀ ",
" ▐░▌ ▐░▌▐░▌ ▐░▌▐░▌ ▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ",
" ▐░█▄▄▄▄▄▄▄█░▌▐░▌ ▐░▌▐░▌ ▐░▌▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄█░▌ ▐░▐░▌ ▐░█▄▄▄▄▄▄▄▄▄ ▐░▌ ▐░▌ ",
" ▐░░░░░░░░░░░▌▐░▌ ▐░▌▐░▌ ▐░▌▐░░░░░░░░░░░▌ ▐░░░░░░░░░░░▌ ▐░▌ ▐░░░░░░░░░░░▌▐░▌ ▐░▌ ",
" ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀ ",
];

View File

@ -0,0 +1,100 @@
use crossterm::{Screen, Crossterm};
use crossterm::style::Color;
use super::variables::{Position, Direction, Size};
use std::collections::HashMap;
pub struct Part
{
pub position: Position,
}
impl Part
{
pub fn new(x: usize, y: usize) -> Part
{
Part { position: Position::new(x,y) }
}
}
pub struct Snake
{
pub snake_parts: Vec<Part>,
pub parent_pos: Position,
map_size: Size
}
impl Snake
{
pub fn new(map_size: Size) -> Snake
{
return Snake { map_size, snake_parts: vec![Part::new(9, 10), Part::new(8, 10)], parent_pos: Position::new(0,0) }
}
pub fn move_snake(&mut self, direction: &Direction, screen: &Screen, free_positions: &mut HashMap<String, Position> )
{
let crossterm = Crossterm::new(screen);
let cursor = crossterm.cursor();
let terminal = crossterm.terminal();
let count = self.snake_parts.len();
let is_food_eaten = false;
for (index, ref mut snake_part) in self.snake_parts.iter_mut().enumerate() {
if index == count - 1
{
snake_part.position.remove(screen);
free_positions.insert(format!("{},{}",snake_part.position.x,snake_part.position.y), snake_part.position);
}
if index == 0
{
self.parent_pos = snake_part.position.clone();
match direction {
&Direction::Up => { snake_part.position.y -= 1 },
&Direction::Down => { snake_part.position.y += 1 },
&Direction::Left => { snake_part.position.x -= 1 },
&Direction::Right => { snake_part.position.x += 1 },
}
free_positions.remove_entry(format!("{},{}",snake_part.position.x,snake_part.position.y).as_str());
} else {
let new_pos = self.parent_pos.clone();
self.parent_pos = snake_part.position.clone();
snake_part.position = new_pos;
}
}
}
pub fn draw_snake(&mut self, screen: &Screen)
{
for (index, ref mut snake_part) in self.snake_parts.iter_mut().enumerate() {
snake_part.position.draw("", screen);
}
}
pub fn has_eaten_food(&mut self, food_pos: Position) -> bool
{
if self.snake_parts[0].position.x == food_pos.x && self.snake_parts[0].position.y == food_pos.y
{
self.snake_parts.push(Part::new(1,1));
return true;
}
return false;
}
pub fn get_parts(&self) -> &Vec<Part>
{
return &self.snake_parts;
}
}
pub enum SnakeState
{
MovedOutMap,
Moved,
AteFood
}

View File

@ -0,0 +1,61 @@
extern crate crossterm;
use self::crossterm::terminal::{terminal, ClearType};
use self::crossterm::style::{Color, StyledObject, ObjectStyle, style };
use self::crossterm::cursor::cursor;
use self::crossterm::Screen;
use std::fmt::Debug;
use std::fmt;
#[derive(Copy, Clone,Debug)]
pub enum Direction
{
Up = 0,
Down = 1,
Left = 2,
Right = 3
}
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
pub struct Position
{
pub x: usize,
pub y: usize
}
impl Position
{
pub fn new(x: usize, y: usize) -> Position
{
Position { x, y }
}
pub fn draw(&self, val: &str, screen: &Screen)
{
cursor(screen).goto(self.x as u16, self.y as u16);
style(val).with(Color::Red).paint(&screen);
screen.stdout.flush();
}
pub fn remove(&self, screen: &Screen)
{
cursor(screen).goto(self.x as u16, self.y as u16);
terminal(&screen).write(" ");
}
}
#[derive(Copy, Clone)]
pub struct Size
{
pub width: usize,
pub height: usize
}
impl Size
{
pub fn new(width: usize, height: usize) -> Size
{
Size {width,height}
}
}

View File

@ -11,11 +11,11 @@ use std::io::{self, Read, Write,Stdout, stdout};
use std::str::from_utf8; use std::str::from_utf8;
/// This struct is a wrapper for `Stdout` /// This struct is a wrapper for `Stdout`
pub struct AnsiStdout { pub struct AnsiOutput {
pub handle: Stdout, pub handle: Stdout,
} }
impl IStdout for AnsiStdout { impl IStdout for AnsiOutput {
fn write_str(&self, string: &str) -> io::Result<usize> { fn write_str(&self, string: &str) -> io::Result<usize> {
let out = &self.handle; let out = &self.handle;
let mut handle = out.lock(); let mut handle = out.lock();
@ -47,8 +47,8 @@ impl IStdout for AnsiStdout {
} }
} }
impl AnsiStdout { impl AnsiOutput {
pub fn new() -> Self { pub fn new() -> Self {
AnsiStdout { handle: stdout() } AnsiOutput { handle: stdout() }
} }
} }

View File

@ -1,16 +1,16 @@
//! This module provides a way to work with an handle to an screen on different platforms. //! This module provides a way to work with an handle to an screen on different platforms.
mod stdout; mod output;
mod ansi_stdout; mod ansi_output;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod winapi_stdout; mod winapi_output;
pub use self::ansi_stdout::AnsiStdout; pub use self::ansi_output::AnsiOutput;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use self::winapi_stdout::WinApiStdout; pub use self::winapi_output::WinApiOutput;
pub use self::stdout::Stdout; pub use self::output::TerminalOutput;
use std::any::Any; use std::any::Any;
use std::io; use std::io;

View File

@ -34,67 +34,67 @@ use std::sync::Arc;
/// This handle could be used to write to the current screen /// This handle could be used to write to the current screen
/// ///
/// For unix and windows 10 `stdout()` will be used for handle when on windows systems with versions lower than 10 WinApi `HANDLE` will be used. /// For unix and windows 10 `stdout()` will be used for handle when on windows systems with versions lower than 10 WinApi `HANDLE` will be used.
pub struct Stdout { pub struct TerminalOutput {
screen_manager: Box<IStdout + Send + Sync>, stdout: Box<IStdout + Send + Sync>,
pub is_in_raw_mode:bool, pub is_in_raw_mode:bool,
} }
impl Stdout { impl TerminalOutput {
/// Create new screen write instance whereon screen related actions can be performed. /// Create new screen write instance whereon screen related actions can be performed.
pub fn new(is_in_raw_mode: bool) -> Self { pub fn new(is_in_raw_mode: bool) -> Self {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
let screen_manager: Box<IStdout + Send + Sync> = functions::get_module::<Box<IStdout + Send + Sync>>( let stdout: Box<IStdout + Send + Sync> = functions::get_module::<Box<IStdout + Send + Sync>>(
Box::from(WinApiStdout::new()), Box::from(WinApiStdout::new()),
Box::from(AnsiStdout::new()), Box::from(AnsiStdout::new()),
).unwrap(); ).unwrap();
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
let screen_manager = Box::from(AnsiStdout::new()) as Box<IStdout + Send + Sync>; let stdout = Box::from(AnsiStdout::new()) as Box<IStdout + Send + Sync>;
Stdout { screen_manager , is_in_raw_mode} TerminalOutput { stdout , is_in_raw_mode}
} }
/// Write String to the current screen. /// Write String to the current screen.
pub fn write_string(&self, string: String) -> io::Result<usize> { pub fn write_string(&self, string: String) -> io::Result<usize> {
self.screen_manager.write_str(string.as_str()) self.stdout.write_str(string.as_str())
} }
/// Flush the current screen. /// Flush the current screen.
pub fn flush(&self) -> io::Result<()> { pub fn flush(&self) -> io::Result<()> {
self.screen_manager.flush() self.stdout.flush()
} }
/// Write &str to the current screen. /// Write &str to the current screen.
pub fn write_str(&self, string: &str) -> io::Result<usize> { pub fn write_str(&self, string: &str) -> io::Result<usize> {
self.screen_manager.write_str(string) self.stdout.write_str(string)
} }
/// Write buffer to the screen /// Write buffer to the screen
pub fn write_buf(&self, buf: &[u8]) -> io::Result<usize> { pub fn write_buf(&self, buf: &[u8]) -> io::Result<usize> {
self.screen_manager.write(buf) self.stdout.write(buf)
} }
pub fn as_any(&self) -> &Any { pub fn as_any(&self) -> &Any {
self.screen_manager.as_any() self.stdout.as_any()
} }
pub fn as_any_mut(&mut self) -> &mut Any { pub fn as_any_mut(&mut self) -> &mut Any {
self.screen_manager.as_any_mut() self.stdout.as_any_mut()
} }
} }
impl Default for Stdout impl Default for TerminalOutput
{ {
/// Get the default handle to the current screen. /// Get the default handle to the current screen.
fn default() -> Self { fn default() -> Self {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
let screen_manager = functions::get_module::<Box<IStdout + Send + Sync>>( let stdout = functions::get_module::<Box<IStdout + Send + Sync>>(
Box::from(WinApiStdout::new()), Box::from(WinApiStdout::new()),
Box::from(AnsiStdout::new()), Box::from(AnsiStdout::new()),
).unwrap(); ).unwrap();
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
let screen_manager = Box::from(AnsiStdout::new()) as Box<IStdout + Send + Sync>; let stdout = Box::from(AnsiStdout::new()) as Box<IStdout + Send + Sync>;
Stdout { screen_manager , is_in_raw_mode: false} TerminalOutput { stdout , is_in_raw_mode: false}
} }
} }

View File

@ -3,17 +3,18 @@ use kernel::windows_kernel::{handle, kernel, writing};
use winapi::um::wincon::ENABLE_PROCESSED_OUTPUT; use winapi::um::wincon::ENABLE_PROCESSED_OUTPUT;
use winapi::um::winnt::HANDLE; use winapi::um::winnt::HANDLE;
use std::sync::Mutex;
use std::ptr::NonNull; use std::ptr::NonNull;
use std::any::Any; use std::any::Any;
use std::io::{self, Write}; use std::io;
use std::sync::{Mutex,Arc, };
/// This struct is a wrapper for WINAPI `HANDLE` /// This struct is a wrapper for WINAPI `HANDLE`
pub struct WinApiStdout { pub struct WinApiOutput {
pub handle: Mutex<HANDLE>, pub handle: Mutex<HANDLE>,
} }
impl IStdout for WinApiStdout { impl IStdout for WinApiOutput {
fn write_str(&self, string: &str) -> io::Result<usize> { fn write_str(&self, string: &str) -> io::Result<usize> {
self.write(string.as_bytes()) self.write(string.as_bytes())
@ -36,9 +37,9 @@ impl IStdout for WinApiStdout {
} }
} }
impl WinApiStdout { impl WinApiOutput {
pub fn new() -> Self { pub fn new() -> Self {
WinApiStdout { handle: Mutex::new(handle::get_output_handle().unwrap()) } WinApiOutput { handle: Mutex::new(handle::get_output_handle().unwrap()) }
} }
pub fn set(&mut self, handle: HANDLE) pub fn set(&mut self, handle: HANDLE)
@ -53,6 +54,6 @@ impl WinApiStdout {
} }
} }
unsafe impl Send for WinApiStdout {} unsafe impl Send for WinApiOutput {}
unsafe impl Sync for WinApiStdout {} unsafe impl Sync for WinApiOutput {}