Nicolas Winkler 3 lat temu
rodzic
commit
1a4fdae4e2
9 zmienionych plików z 498 dodań i 52 usunięć
  1. 251 0
      Cargo.lock
  2. 6 1
      Cargo.toml
  3. 11 5
      src/engine.rs
  4. 11 2
      src/evaluate.rs
  5. 27 4
      src/game.rs
  6. 24 15
      src/interface.rs
  7. 19 1
      src/main.rs
  8. 70 5
      src/movegen.rs
  9. 79 19
      src/search.rs

+ 251 - 0
Cargo.lock

@@ -1,5 +1,256 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
 [[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
 name = "bishop"
 version = "0.1.0"
+dependencies = [
+ "env_logger",
+ "log",
+ "rand",
+ "simplelog",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "libc"
+version = "0.2.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "simplelog"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1348164456f72ca0116e4538bdaabb0ddb622c7d9f16387c725af3e96d6001c"
+dependencies = [
+ "chrono",
+ "log",
+ "termcolor",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi",
+ "winapi",
+]
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

+ 6 - 1
Cargo.toml

@@ -3,4 +3,9 @@ name = "bishop"
 version = "0.1.0"
 authors = ["Nicolas Winkler <nicolas.winkler@gmx.ch>"]
 
-[dependencies]
+[dependencies]
+
+log = "*"
+env_logger = "*"
+simplelog = "*"
+rand = "*"

+ 11 - 5
src/engine.rs

@@ -2,7 +2,9 @@ use search::apply_move;
 use bitboard::Bitboard;
 use game::Game;
 use search::*;
+use std::io::Write;
 use movegen::*;
+use log::{info};
 
 use std::sync::mpsc::{Receiver, Sender};
 
@@ -39,23 +41,27 @@ pub fn run_engine(r: Receiver<EngineMsg>, s: Sender<InterfaceMsg>) {
 
         match msg {
             EngineMsg::SetBoard(g) => {
-                println!("SetBoard");
+                //println!("SetBoard");
                 game = g;
             },
             EngineMsg::SetPiece(_) => { println!("SetPiece") },
             EngineMsg::Search(depth) => {
-                println!("Search {}", depth);
+                info!("searching in pos\n {}", game.beautiful_print());
+                //println!("Search {}", depth);
                 let best_move = search(&game, depth);
-                println!("bestmove {:?}", best_move);
+                println!("bestmove {}", best_move.to_string());
+                info!("bestmove {}", best_move.to_string());
             },
             EngineMsg::Ping => { println!("Ping") },
             EngineMsg::Stop => { println!("Stop") },
-            EngineMsg::NewGame => { println!("NewGame") },
+            EngineMsg::NewGame => {
+                //println!("NewGame")
+            },
             EngineMsg::GetState => { println!("GetState") },
             EngineMsg::GetInfo => { println!("GetInfo") },
         }
 
-        println!("{}", game.beautiful_print());
+        //println!("{}", game.beautiful_print());
 
         //let moves = generate_moves(&game, WHITE);
     }

+ 11 - 2
src/evaluate.rs

@@ -1,7 +1,16 @@
 use game::*;
+use bitboard::*;
+use movegen::*;
 
 
+fn side_value(game: &Game, side: Side) -> u32 {
+    game.get_piece(PAWN, game.turn).count_ones() * 100
+    + (game.get_piece(KNIGHT, game.turn) | game.get_piece(BISHOP, game.turn)).count_ones() * 300
+    + game.get_piece(ROOK, game.turn).count_ones() * 400
+    + game.get_piece(QUEEN, game.turn).count_ones() * 600
+    + game.get_piece(KING, game.turn).count_ones() * 100000
+}
+
 pub fn evaluate(game: &Game) -> i32 {
-    return game.get_all_side(game.turn).count_ones() as i32
-        - game.get_all_side(!game.turn).count_ones() as i32;
+    return side_value(game, game.turn) as i32 - side_value(game, !game.turn) as i32;
 }

+ 27 - 4
src/game.rs

@@ -44,9 +44,19 @@ impl Default for Game {
 }
 
 impl Game {
+    pub fn empty() -> Game {
+        Game {
+            pieces: [0; 12],
+            turn: WHITE,
+            en_passant: None,
+            castling_rights: [true; 4],
+            halfmoves_since_last_event: 0,
+            turn_number: 0
+        }
+    }
 
     pub fn from_fen(fen: &[&str]) -> Option<Game> {
-        let mut game: Game = Game::default();
+        let mut game: Game = Game::empty();
         let example = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
 
         let position = fen[0];
@@ -85,16 +95,22 @@ impl Game {
                         'Q' => { *game.queens_mut(WHITE) |= bit_to_set },
                         'K' => { *game.kings_mut(WHITE) |= bit_to_set },
                         num => {
-                            col_index += match num.to_digit(10) { Some(x) => x, None => {
-                                return None;
-                            }} as u8 - 1;
+                            col_index += match num.to_digit(10) {
+                                Some(x) => x,
+                                None => { return None; }
+                            } as u8 - 1;
                         }
                     }
                     col_index += 1;
                 }
+                row_index += 1;
             }
         }
 
+        if turn == "b" {
+            game.turn = BLACK
+        }
+
         // parse castling rights
         game.castling_rights[0] = castling_rights.contains('K');
         game.castling_rights[1] = castling_rights.contains('Q');
@@ -124,8 +140,14 @@ impl Game {
             let origin = Move::parse_square(&mov[0..2]);
             let target = Move::parse_square(&mov[2..4]);
 
+
             if let (Some(from), Some(to)) = (origin, target) {
                 let (piece_type, side) = self.get_square(from);
+                //println!("from: {}", from);
+
+                if piece_type == NO_PIECE {
+                    return Err(());
+                }
 
                 if side != self.turn { // wrong side to move
                     return Err(());
@@ -135,6 +157,7 @@ impl Game {
                     Err(())
                 }
                 else {
+                    //println!("pt: {}", piece_type);
                     Ok(Move::Default{ mov: SimpleMove{ from, to }, piece_type })
                 }
             }

+ 24 - 15
src/interface.rs

@@ -1,3 +1,5 @@
+
+use search::apply_move;
 use std::io::{self, BufRead};
 use std::sync::mpsc::{Receiver, Sender};
 
@@ -8,26 +10,27 @@ use game::{Game};
 
 use std::fs::File;
 use std::io::Write;
+use std::sync::Mutex;
 use std::cell::RefCell;
+use std::sync::Arc;
 
-
-thread_local!(static debug_log: RefCell<File> = RefCell::new(File::create("debug.log").unwrap()));
-
+use std::thread::sleep_ms;
+use log::info;
 
 
 pub fn run(r: Receiver<InterfaceMsg>, s: Sender<EngineMsg>) {
     //unsafe { debug_log = Box::new(File::open("debug.log").unwrap()); }
+    //unsafe { debug_log = Arc::new(Some(Mutex::new(File::create("~/debug.log").unwrap()))) };
     let stdin = io::stdin();
     for line_m in stdin.lock().lines() {
         let line = line_m.unwrap();
 
-        debug_log.with(|fc| 
-            writeln!(fc.borrow_mut(), "received line: {}", line)
-        );
+        info!("received line: {}", line);
 
         let split = line.split_whitespace().collect::<Vec<&str>>();
         run_command(split, &r, &s);
     }
+    sleep_ms(2000);
 }
 
 fn run_command(mut cmd: Vec<&str>, r: &Receiver<InterfaceMsg>, s: &Sender<EngineMsg>) {
@@ -62,22 +65,28 @@ fn cmd_position(mut args: Vec<&str>, r: &Receiver<InterfaceMsg>, s: &Sender<Engi
 
     let mut game = Game::default();
     if position == "startpos" {
-
     }
     else if position == "fen" {
-        let fen_parts: Vec<String> = Vec::from(&args[0..6]).into_iter().map(|x| x.to_owned() + " ").collect::<Vec<String>>();
-
-        debug_log.with(|fc| 
-            writeln!(fc.borrow_mut(), "fen string is: {:?}", fen_parts)
-        );
-        
-        // "3N4/5P2/2K1P1pP/p2PNn2/1p3r2/2P4P/4k2B/8 w - - 0 1"
+        let fen_parts: Vec<&str> = Vec::from(&args[0..6]).into_iter().collect::<Vec<&str>>();
+        let moves = &args[6..];
+
+        game = Game::from_fen(fen_parts.as_slice()).unwrap();
+
+        for mov in moves {
+            let m = game.parse_move(mov);
+            if let Ok(mm) = m {
+                game = apply_move(&game, mm);
+            }
+            else {
+                println!("error");
+            }
+        }
     }
     s.send(EngineMsg::SetBoard(game)).unwrap();
 }
 
 fn cmd_go(_args: Vec<&str>, _r: &Receiver<InterfaceMsg>, s: &Sender<EngineMsg>) {
-    s.send(EngineMsg::Search(3)).unwrap();
+    s.send(EngineMsg::Search(7)).unwrap();
 }
 
 fn cmd_newgame(_args: Vec<&str>, _r: &Receiver<InterfaceMsg>, s: &Sender<EngineMsg>) {

+ 19 - 1
src/main.rs

@@ -6,10 +6,28 @@ mod game;
 mod evaluate;
 mod search;
 
+extern crate log;
+extern crate env_logger;
+extern crate simplelog;
+extern crate rand;
+
+use simplelog::*;
+
 use std::sync::mpsc;
-use std::thread;
+use std::{thread, fs::File};
+use log::*;
+use env_logger::{Target, Builder};
 
 fn main() {
+    /*let mut builder = Builder::from_default_env();
+    builder
+        .filter(None, LevelFilter::Info)
+        .target(Target::Pipe(Box::new(File::create("debug.log").unwrap())))
+        //.target(Target::Stderr)
+        .init();
+        */
+    simplelog::WriteLogger::init(LevelFilter::Info, Config::default(), File::create("C:\\\\Users\\Nicolas\\debug.log").unwrap()).unwrap();
+
     let (esend, erecv) = mpsc::channel();
     let (isend, irecv) = mpsc::channel();
 

+ 70 - 5
src/movegen.rs

@@ -44,7 +44,38 @@ impl Move {
         let col = sq.chars().nth(0)?;
         let row = sq.chars().nth(1)?;
 
-        Some((row as u8 - '1' as u8) * 8 + (col as u8 - 'a' as u8))
+        Some((row as u8 - '1' as u8) * 8 + 7 - (col as u8 - 'a' as u8))
+    }
+
+
+    pub fn square_to_string(sq: Square) -> String {
+        let col =  7 - (sq % 8);
+        let row = sq / 8;
+        let colchar = (('a' as u8) + col) as char;
+        let rowchar = (('1' as u8) + row) as char;
+
+        let chars = [colchar, rowchar];
+        //println!("{} {}", col, row);
+
+        chars.iter().collect()
+    }
+
+    pub fn to_string(&self) -> String {
+        match self {
+            Move::Default{ mov, piece_type } => {
+                Self::square_to_string(mov.from) + &Self::square_to_string(mov.to)
+            },
+            Move::Castling{ side, left } => { "castling not implemented".to_owned() },
+            Move::EnPassant{ side, column } => { "en passant not implemented".to_owned() },
+            Move::Promotion{ mov, promote_to } => {
+                Self::square_to_string(mov.from) + &Self::square_to_string(mov.to) + 
+                if *promote_to == QUEEN { "Q" }
+                else if *promote_to == ROOK { "R" }
+                else if *promote_to == KNIGHT { "N" }
+                else if *promote_to == BISHOP { "B" }
+                else { "" }
+            },
+        }
     }
 }
 
@@ -91,6 +122,16 @@ pub fn generate_moves(game: &Game, side: Side) -> Vec<Move> {
 }
 
 
+pub fn generate_legal_moves(game: &Game, side: Side) -> Vec<Move> {
+    let mut moves = generate_moves(game, side);
+
+    moves.into_iter().filter(|mov| {
+        let tryout = super::search::apply_move(game, *mov);
+        !is_check(&tryout, side)
+    }).collect::<Vec<Move>>()
+}
+
+
 /*
 pub fn generate_pawn_moves(game: &Game, side: Side) -> Bitboard {
     let pushed = generate_pawn_pushes(game, side);
@@ -105,11 +146,12 @@ pub fn generate_pawn_moves(game: &Game, side: Side) -> Bitboard {
 fn generate_pawn_pushes(game: &Game, side: Side, move_list: &mut Vec<Move>) {
     let pawns = game.pawns(side);
     let others = game.get_all_side(!side);
+    let pieces = game.get_all_side(side);
     let moved_pawns =
         match side {
             WHITE => north_one(pawns),
             BLACK => south_one(pawns)
-        } & !(ROW_1 | ROW_8);
+        } & !(ROW_1 | ROW_8) & !(others | pieces);
 
     let iter = BitboardIterator { board: moved_pawns & !others };
     for p in iter {
@@ -124,12 +166,15 @@ fn generate_pawn_pushes(game: &Game, side: Side, move_list: &mut Vec<Move>) {
 fn generate_pawn_doublepushes(game: &Game, side: Side, move_list: &mut Vec<Move>) {
     let pawns = game.pawns(side) & match side { WHITE => ROW_2, BLACK => ROW_7 };
     let others = game.get_all_side(!side);
+    let pieces = game.get_all_side(side);
+
+    let all_pieces = others | pieces;
 
     let moved_pawns =
         match side {
-            WHITE => north_one(north_one(pawns)),
-            BLACK => south_one(south_one(pawns))
-        };
+            WHITE => north_one(north_one(pawns) & !all_pieces),
+            BLACK => south_one(south_one(pawns) & !all_pieces)
+        } & !all_pieces;
 
     let iter = BitboardIterator { board: moved_pawns & !others };
     for p in iter {
@@ -308,6 +353,26 @@ fn generate_king_moves(game: &Game, side: Side, move_list: &mut Vec<Move>) {
     }
 }
 
+/**
+ * checks if side's king is in check
+ */
+pub fn is_check(game: &Game, side: Side) -> bool {
+    let king_square = square(game.get_piece(KING, side));
+    let possible_attacks = generate_moves(game, !side);
+    for mov in possible_attacks {
+        if let Move::Default{ mov, piece_type: _ } = mov {
+            if mov.to == king_square {
+                return true;
+            }
+        }
+        else if let Move::Promotion{ mov, promote_to: _ } = mov {
+            if mov.to == king_square {
+                return true;
+            }
+        }
+    }
+    false
+}
 /*
 #[cfg(test)]
 mod tests {

+ 79 - 19
src/search.rs

@@ -2,6 +2,8 @@ use bitboard::*;
 use movegen::*;
 use game::Game;
 use evaluate::*;
+use log::info;
+use rand::prelude::*;
 
 enum MoveUndo {
     Default {
@@ -35,36 +37,80 @@ pub fn search(game: &Game, depth: i32) -> Move {
         return best_move;
     }
 
-    let moves = generate_moves(game, game.turn);
-    for mov in moves {
-        let new_game = apply_move(game, mov);
-        let val = -negamax(&new_game, depth - 1);
-        if val > best {
-            best = val;
-            best_move = mov;
-        }
+    let mut moves = generate_legal_moves(game, game.turn);
+    //info!("mov list: {:?}", moves.iter().map(|x| x.to_string()).collect::<Vec<String>>());
+
+    let mut rng = rand::thread_rng();
+
+    /*moves.shuffle(&mut rng);
+
+    // return random move
+    if moves.len() >= 1 {
+        return moves[0];
+    }*/
+
+    let mut valued_moves = moves.iter().map(|mov| {
+        let new_game = apply_move(game, *mov);
+        (*mov, negamax(&new_game, i32::min_value(), i32::max_value() - 1, depth - 1))
+    }).collect::<Vec<(Move, i32)>>();
+
+    valued_moves.sort_by_key(|mv| mv.1);
+
+    if valued_moves.len() > 0 {
+        let min_val = valued_moves[0].1;
+        let best_moves = valued_moves.iter().filter(|mv| mv.1 <= min_val).map(|(mov, _)| *mov).collect::<Vec<Move>>();
+
+        return best_moves[(rng.next_u64() % best_moves.len() as u64) as usize];
+    }
+    else {
+        return Move::Default{ mov: SimpleMove{ from: 0, to: 0 }, piece_type: PAWN };
     }
+
+    //info!("best is {}", best_move.to_string());
     return best_move;
 }
 
-fn negamax(game: &Game, depth: i32) -> i32 {
+fn negamax(game: &Game, mut alpha: i32, beta: i32, depth: i32) -> i32 {
     if depth == 0 {
         return evaluate(game);
     }
 
-    let mut best = i32::min_value();
-    let mut best_move = Move::default();
+    const MIN_VALUE: i32 = i32::min_value() + 1;
+
+    let mut best = MIN_VALUE;
+    //let mut best_move = Move::default();
+    //info!(" -> generate_legal_moves");
     let moves = generate_moves(game, game.turn);
+
+    if moves.len() == 0 {
+        if is_check(game, game.turn) {
+            // mate
+            return MIN_VALUE;
+        }
+        else {
+            // stalemate
+            return 0;
+        }
+    }
+    //info!(" -> generated_legal_moves {:?}", moves);
     for mov in moves {
         let new_game = apply_move(game, mov);
-        let val = -negamax(&new_game, depth - 1);
+        //info!(" -> applied {} -> {}", mov.to_string(), depth - 1);
+        let val = -negamax(&new_game, -beta, -alpha, depth - 1);
+        if val >= beta {
+            return beta;
+        }
+        if val > alpha {
+            alpha = val;
+        }
+        //info!(" -> negamaxed {} -> {}", mov.to_string(), depth - 1);
         if val > best {
             best = val;
-            best_move = mov;
+            //best_move = mov;
         }
     }
-    println!("bestmove {:?}", best_move);
-    return 0;
+    //println!("best {}", best);
+    return best;
 }
 
 pub fn apply_move(game: &Game, mov: Move) -> Game {
@@ -74,7 +120,7 @@ pub fn apply_move(game: &Game, mov: Move) -> Game {
     let friends = game.get_all_side(side);
     let others = game.get_all_side(!side);
     match mov {
-        Move::Default { mov: mov, piece_type: pt } => {
+        Move::Default { mov, piece_type: pt } => {
             if from_square(mov.to) | others != 0 {
                 new_game.apply_mask(!from_square(mov.to));
             }
@@ -96,8 +142,7 @@ pub fn apply_move(game: &Game, mov: Move) -> Game {
             new_game.set_piece(KING, side, new_king);
             new_game.set_piece(ROOK, side, new_rook);
         },
-        Move::EnPassant { side: side, column: col } => {
-            
+        Move::EnPassant { side, column: _ } => {
             let pawns = game.pawns(side);
             if let Some(ep) = game.en_passant {
                 if pawns & A_FILE >> ep & (ROW_4 | ROW_5) != 0 {
@@ -108,7 +153,22 @@ pub fn apply_move(game: &Game, mov: Move) -> Game {
             }
             panic!("oh no");
         },
-        Move::Promotion { mov: mov, promote_to: pt } => { panic!("oh no"); },
+        Move::Promotion { mov, promote_to } => {
+            if from_square(mov.to) | others != 0 {
+                new_game.apply_mask(!from_square(mov.to));
+            }
+            new_game.apply_mask(!from_square(mov.from));
+
+            match promote_to {
+                QUEEN => { *new_game.queens_mut(side) |= from_square(mov.to); }
+                ROOK => { *new_game.rooks_mut(side) |= from_square(mov.to); }
+                BISHOP => { *new_game.bishops_mut(side) |= from_square(mov.to); }
+                KNIGHT => { *new_game.knights_mut(side) |= from_square(mov.to); }
+                _ => {
+                    info!("internal error");
+                }
+            }
+        },
     }
 
     new_game.turn = !new_game.turn;