Skip to content

Instantly share code, notes, and snippets.

@Veedrac
Created November 23, 2015 04:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Veedrac/7e009819107633833bd8 to your computer and use it in GitHub Desktop.
Save Veedrac/7e009819107633833bd8 to your computer and use it in GitHub Desktop.
pub const WIDTH: usize = 7;
pub const HEIGHT: usize = 6;
pub const MAX_DEPTH: i32 = 7;
pub const ORANGE_WINS: i32 = 1000000;
pub const YELLOW_WINS: i32 = -ORANGE_WINS;
// Couldn't resist changing the type of this
pub type Board = [[i8; WIDTH]; HEIGHT];
pub fn other_color(color:i8) -> i8 {
-color
}
// += makes this noticably nicer,
// as does using correct types for
// dimensions. I also inlined counts[...] += 1.
// That's about it for this one, though.
pub fn score_board(board:&Board) -> i32 {
let mut counts = [0; 9];
// Horizontal spans
for y in 0..HEIGHT {
let mut score: i8 = board[y][0] + board[y][1] + board[y][2];
for x in 3..WIDTH {
score += board[y][x];
counts[(score+4) as usize] += 1;
score -= board[y][x - 3];
}
}
// Vertical spans
for x in 0..WIDTH {
let mut score: i8 = board[0][x] + board[1][x] + board[2][x];
for y in 3..HEIGHT {
score += board[y][x];
counts[(score+4) as usize] += 1;
score -= board[y - 3][x];
}
}
// Down-right (and up-left) diagonals
for y in 0..HEIGHT-3 {
for x in 0 .. WIDTH-3 {
let mut score: i8 = 0;
for idx in 0..4 {
score += board[y+idx][x+idx];
}
counts[(score+4) as usize] += 1;
}
}
// up-right (and down-left) diagonals
for y in 3..HEIGHT {
for x in 0..WIDTH-3 {
let mut score: i8 = 0;
for idx in 0..4 {
score += board[y-idx][x+idx];
}
counts[(score+4) as usize] += 1;
}
}
if counts[0] != 0 {
YELLOW_WINS
} else if counts[8] != 0 {
ORANGE_WINS
} else {
counts[5] + 2*counts[6] + 5*counts[7] -
counts[3] - 2*counts[2] - 5*counts[1]
}
}
/* vim: set expandtab ts=8 sts=4 shiftwidth=4 */
use std::env;
use std::process;
mod common;
use common::Board;
use common::{score_board, other_color};
use common::{WIDTH, HEIGHT, ORANGE_WINS, YELLOW_WINS, MAX_DEPTH};
// Parse cmdline specs to create initial board
fn load_board(args: Vec<String>) -> Board {
let mut board = [[0; WIDTH]; HEIGHT];
for y in 0..HEIGHT {
for x in 0..WIDTH {
let orange = format!("o{}{}", y, x);
let yellow = format!("y{}{}", y, x);
if args.iter().any(|x| *x == orange) {
board[y][x] = 1;
} else if args.iter().any(|x| *x == yellow) {
board[y][x] = -1;
} else {
board[y][x] = 0;
}
}
}
board
}
// Drop a chip, return the new board
fn drop_disk(board: &Board, column:usize, color:i8) -> Board {
// Clone!
let mut board_new = board.clone();
for y in (0..HEIGHT).rev() {
if board_new[y][column] == 0 {
board_new[y][column] = color;
break;
}
}
board_new
}
// TODO: Write new comment.
fn ab_minimax(max_or_min:bool, color:i8, depth:i32, board:&Board, debug:bool) -> (Option<usize>, i32) {
// Cheap, so just recalculate as needed
// It would be nicer to clone this, but I don't
// think the closure wants to let me do that. :(
let moves = || (0..WIDTH).filter(|&column| board[0][column] == 0);
// I ❤ early returns
if moves().next().is_none() {
return (None, score_board(board));
}
// Expensive, so store in vectors.
let boards: Vec<_> = moves().map(|column| drop_disk(board, column, color)).collect();
let scores: Vec<_> = boards.iter().map(score_board).collect();
let target_score = if max_or_min { ORANGE_WINS } else { YELLOW_WINS };
// Extra scope to stop killer_move stealing scores for ages,
// although it's not a massive deal if it is as we can just
// use .iter().cloned() in best_scores.
{
let killer_move = moves().zip(&scores).find(|&(_, &score)| score == target_score);
if let Some((index, &score)) = killer_move {
return (Some(index), score); // ❤ ❤ ❤
}
}
// Boxing iterators allows using multiple iterator types
// generically using dynamic dispatch. It's normally cheaper
// than storing them in vectors and tons more convenient than
// using an actual enum type implementing Iterator.
//
// In this case we can be even craftier by using a pointer
// into stack variables. We don't save much (the allocation is
// tiny), but it's nice to know about this option if you need it.
let mut shallow_iter;
let mut deep_iter;
let best_scores: &mut Iterator<Item=_> = match depth {
if depth == 1 {
shallow_iter = scores.into_iter();
&mut shallow_iter
}
else {
// Single maps tend to be prettier,
// unless using predefined functions.
deep_iter = boards.into_iter().map(|board| {
let (_, best_score) = ab_minimax(!max_or_min, other_color(color), depth-1, &board, debug);
// when loss is certain, avoid forfeiting the match, by shifting scores by depth...
match best_score {
1000000 | -1000000 => best_score - depth*(color as i32),
_ => best_score
}
});
&mut deep_iter
};
// The Box method follows, for if you want something simpler but
// with a tiny bit more overhead
/*
// Note the use of match here, which I personally
// think is prettier for single-line statments
let best_scores: Box<Iterator<Item=_>> = match depth {
1 => Box::new(scores.into_iter()),
_ => Box::new(boards.into_iter().map(|board| {
...
}))
};
*/
let all_data = best_scores.zip(moves())
.inspect(|&(column, score)| if debug && depth == MAX_DEPTH {
println!("Depth {}, placing on {}, Score:{}\n", depth, column, score)
});
let best = if max_or_min { all_data.max() } else { all_data.min() };
// Not sure if this is better than a match. Whatever.
best.map(|(score, best_move)| (Some(best_move), score)).or_else((None, 0))
}
fn run_game() -> i32 {
let board = load_board(env::args().collect());
let score_orig = score_board(&board);
let debug = env::args().any(|x| x == "-debug");
if debug {
println!("Starting score: {}", score_orig);
}
if score_orig == ORANGE_WINS {
println!("I win");
return -1;
} else if score_orig == YELLOW_WINS {
println!("You win");
return -1;
} else {
match ab_minimax(true, 1, MAX_DEPTH, &board, debug) {
(Some(column), _) => println!("{}", column),
_ => println!("No move possible"),
}
return 0;
}
}
// I don't trust process::exit... :P
fn main() {
process::exit(run_game());
}
/* vim: set expandtab ts=8 sts=4 shiftwidth=4 */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment