<commit_before>//! Types defined by Lua thread

use hlua;
use hlua::{Lua, LuaError};
use hlua::any::AnyLuaValue;

use rustc_serialize::json::Json;

use std::fmt::{Debug, Formatter};
use std::fmt::Result as FmtResult;

use std::cmp::{PartialEq, Eq};

/// Represents an identifier for dealing with nested tables.
///
/// To access foo.bar.baz, use vec!["foo", "bar", "baz"].
///
/// To access foo[2], use vec!["foo", 2].
pub type LuaIdent = Vec<String>;

/// Methods that the Lua thread can execute.
pub type LuaFunc = fn(&mut Lua) -> AnyLuaValue;

/// Messages sent to the lua thread
pub enum LuaQuery {
    /// Pings the lua thread
    Ping,
    /// Halt the lua thread
    Terminate,
    // Restart the lua thread
    Restart,

    /// Execute a string
    Execute(String),
    /// Execute a file
    ExecFile(String),

    /// Get a variable, expecting an AnyLuaValue
    GetValue(LuaIdent),
    /// Execute some Rust using the Lua context.
    ExecWithLua(LuaFunc),
}

impl Debug for LuaQuery {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        match self {
            &LuaQuery::Ping => write!(f, "LuaQuery::Ping"),
            &LuaQuery::Terminate => write!(f, "LuaQuery::Terminate"),
            &LuaQuery::Restart => write!(f, "LuaQuery::Restart"),
            &LuaQuery::Execute(ref val) =>
                write!(f, "LuaQuery::Execute({:?})", val),
            &LuaQuery::ExecFile(ref val) =>
                write!(f, "LuaQuery::ExecFile({:?})", val),
            &LuaQuery::GetValue(ref val) =>
                write!(f, "LuaQuery::GetValue({:?})", val),
            // This is why there's no #[derive(Debug)],
            // and why we have lua/types.rs
            &LuaQuery::ExecWithLua(_) =>
                write!(f, "LuaQuery::ExecWithLua()")
        }
    }
}

unsafe impl Send for LuaQuery { }
unsafe impl Sync for LuaQuery { }

impl PartialEq for LuaQuery {
    fn eq(&self, other: &LuaQuery) -> bool {
        match (self, other) {
            (&LuaQuery::Ping, &LuaQuery::Ping) => true,
            (&LuaQuery::Terminate, &LuaQuery::Terminate) => true,
            (&LuaQuery::Restart, &LuaQuery::Restart) => true,

            (&LuaQuery::Execute(ref s1), &LuaQuery::Execute(ref s2)) =>
                s1 == s2,
            (&LuaQuery::ExecFile(ref s1), &LuaQuery::ExecFile(ref s2)) =>
                s1 == s2,
            (&LuaQuery::GetValue(ref i1), &LuaQuery::GetValue(ref i2)) =>
                i1 == i2,
            (&LuaQuery::ExecWithLua(_), &LuaQuery::ExecWithLua(_)) => true,

            _ => false
        }
    }
}

impl Eq for LuaQuery { }

/// Messages received from lua thread
pub enum LuaResponse {
    /// If the identifier had length 0
    InvalidName,
    /// Lua variable obtained
    Variable(Option<AnyLuaValue>),
    /// Lua error
    Error(hlua::LuaError),
    /// A function is returned
    Function(hlua::functions_read::LuaFunction<String>),
    /// Pong response from lua ping
    Pong,
}

impl PartialEq for LuaResponse {
    fn eq(&self, other: &LuaResponse) -> bool {
        match (self, other) {
            (&LuaResponse::InvalidName, &LuaResponse::InvalidName) => true,
            (&LuaResponse::Pong, &LuaResponse::Pong) => true,

            (&LuaResponse::Variable(ref v1), &LuaResponse::Variable(ref v2)) =>
                v1 == v2,
            (&LuaResponse::Error(ref e1), &LuaResponse::Error(ref e2)) =>
                format!("{:?}", e1) == format!("{:?}", e2),
            (&LuaResponse::Function(_), &LuaResponse::Function(_)) => true,

            _ => false
        }
    }
}

unsafe impl Send for LuaResponse { }
unsafe impl Sync for LuaResponse { }

impl Debug for LuaResponse {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        match *self {
            LuaResponse::InvalidName =>
                write!(f, "LuaReponse::InvalidName"),
            LuaResponse::Variable(ref var) =>
                write!(f, "LuaResponse::Variable({:?})", var),
            LuaResponse::Error(ref err) =>
                write!(f, "LuaResponse::Error({:?})", err),
            LuaResponse::Function(_) =>
                write!(f, "LuaResponse::Function"),
            LuaResponse::Pong =>
                write!(f, "LuaResponse::Pong")
        }
    }
}
<commit_msg>Add is_ok() and is_err() to LuaResponse<commit_after>//! Types defined by Lua thread

use hlua;
use hlua::{Lua, LuaError};
use hlua::any::AnyLuaValue;

use rustc_serialize::json::Json;

use std::fmt::{Debug, Formatter};
use std::fmt::Result as FmtResult;

use std::cmp::{PartialEq, Eq};

/// Represents an identifier for dealing with nested tables.
///
/// To access foo.bar.baz, use vec!["foo", "bar", "baz"].
///
/// To access foo[2], use vec!["foo", 2].
pub type LuaIdent = Vec<String>;

/// Methods that the Lua thread can execute.
pub type LuaFunc = fn(&mut Lua) -> AnyLuaValue;

/// Messages sent to the lua thread
pub enum LuaQuery {
    /// Pings the lua thread
    Ping,
    /// Halt the lua thread
    Terminate,
    // Restart the lua thread
    Restart,

    /// Execute a string
    Execute(String),
    /// Execute a file
    ExecFile(String),

    /// Get a variable, expecting an AnyLuaValue
    GetValue(LuaIdent),
    /// Execute some Rust using the Lua context.
    ExecWithLua(LuaFunc),
}

impl Debug for LuaQuery {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        match self {
            &LuaQuery::Ping => write!(f, "LuaQuery::Ping"),
            &LuaQuery::Terminate => write!(f, "LuaQuery::Terminate"),
            &LuaQuery::Restart => write!(f, "LuaQuery::Restart"),
            &LuaQuery::Execute(ref val) =>
                write!(f, "LuaQuery::Execute({:?})", val),
            &LuaQuery::ExecFile(ref val) =>
                write!(f, "LuaQuery::ExecFile({:?})", val),
            &LuaQuery::GetValue(ref val) =>
                write!(f, "LuaQuery::GetValue({:?})", val),
            // This is why there's no #[derive(Debug)],
            // and why we have lua/types.rs
            &LuaQuery::ExecWithLua(_) =>
                write!(f, "LuaQuery::ExecWithLua()")
        }
    }
}

unsafe impl Send for LuaQuery { }
unsafe impl Sync for LuaQuery { }

impl PartialEq for LuaQuery {
    fn eq(&self, other: &LuaQuery) -> bool {
        match (self, other) {
            (&LuaQuery::Ping, &LuaQuery::Ping) => true,
            (&LuaQuery::Terminate, &LuaQuery::Terminate) => true,
            (&LuaQuery::Restart, &LuaQuery::Restart) => true,

            (&LuaQuery::Execute(ref s1), &LuaQuery::Execute(ref s2)) =>
                s1 == s2,
            (&LuaQuery::ExecFile(ref s1), &LuaQuery::ExecFile(ref s2)) =>
                s1 == s2,
            (&LuaQuery::GetValue(ref i1), &LuaQuery::GetValue(ref i2)) =>
                i1 == i2,
            (&LuaQuery::ExecWithLua(_), &LuaQuery::ExecWithLua(_)) => true,

            _ => false
        }
    }
}

impl Eq for LuaQuery { }

/// Messages received from lua thread
pub enum LuaResponse {
    /// If the identifier had length 0
    InvalidName,
    /// Lua variable obtained
    Variable(Option<AnyLuaValue>),
    /// Lua error
    Error(hlua::LuaError),
    /// A function is returned
    Function(hlua::functions_read::LuaFunction<String>),
    /// Pong response from lua ping
    Pong,
}

impl LuaResponse {
    /// Whether this response is an InvalidName or Error
    pub fn is_err(&self) -> bool {
        match self {
            &LuaResponse::InvalidName | &LuaResponse::Error(_) => true,
            _ => false
        }
    }

    /// If this response is a Variable, Function, or Pong
    #[inline]
    pub fn is_ok(&self) -> bool {
        !self.is_err()
    }
}

impl PartialEq for LuaResponse {
    fn eq(&self, other: &LuaResponse) -> bool {
        match (self, other) {
            (&LuaResponse::InvalidName, &LuaResponse::InvalidName) => true,
            (&LuaResponse::Pong, &LuaResponse::Pong) => true,

            (&LuaResponse::Variable(ref v1), &LuaResponse::Variable(ref v2)) =>
                v1 == v2,
            (&LuaResponse::Error(ref e1), &LuaResponse::Error(ref e2)) =>
                format!("{:?}", e1) == format!("{:?}", e2),
            (&LuaResponse::Function(_), &LuaResponse::Function(_)) => true,

            _ => false
        }
    }
}

unsafe impl Send for LuaResponse { }
unsafe impl Sync for LuaResponse { }

impl Debug for LuaResponse {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        match *self {
            LuaResponse::InvalidName =>
                write!(f, "LuaReponse::InvalidName"),
            LuaResponse::Variable(ref var) =>
                write!(f, "LuaResponse::Variable({:?})", var),
            LuaResponse::Error(ref err) =>
                write!(f, "LuaResponse::Error({:?})", err),
            LuaResponse::Function(_) =>
                write!(f, "LuaResponse::Function"),
            LuaResponse::Pong =>
                write!(f, "LuaResponse::Pong")
        }
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2017-2019 Ryan Huang
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

pub mod bitboard;
mod board;
mod constants;
pub mod ttable;
mod zobrist;

use std::cell::RefCell;
use std::fmt;
use std::mem;

use glium::glutin::EventsLoopProxy;

use self::bitboard::BitBoard;
pub use self::board::Board;
use crate::ai::AI;

pub struct Model {
    pub game_type: GameType,
    pub board: Board,
    pub exchange_one_hex: RefCell<bool>,
    pub ply_count: u64,
    pub players: ColorMap<Player>,
    pub selected_piece: Option<FieldCoord>,
    pub last_move: Option<MoveAnnotated>,
    pub exchanging: bool,
    pub ai: AI,
    pub ai_search_depth: RefCell<i32>,
    pub window_states: RefCell<WindowStates>,
    pub outcome: Outcome,
    undo_stack: Vec<(Board, Option<MoveAnnotated>, Outcome)>,
    redo_stack: Vec<(Board, Option<MoveAnnotated>, Outcome)>,
    pub events_proxy: EventsLoopProxy,
}

impl Model {
    pub fn new(
        game_type: GameType,
        players: ColorMap<Player>,
        events_proxy: EventsLoopProxy,
    ) -> Self {
        Self {
            game_type,
            board: Board::new(game_type, 2),
            exchange_one_hex: RefCell::new(false),
            ply_count: 0,
            players,
            selected_piece: None,
            last_move: None,
            exchanging: false,
            ai: AI::new(),
            ai_search_depth: RefCell::new(6),
            window_states: RefCell::new(WindowStates::default()),
            outcome: Outcome::InProgress,
            undo_stack: vec![],
            redo_stack: vec![],
            events_proxy,
        }
    }
    pub fn reset(&mut self, game_type: GameType, players: ColorMap<Player>) {
        self.game_type = game_type;
        self.players = players;

        let exchange_hex_count = if *self.exchange_one_hex.borrow() {
            1
        } else {
            2
        };
        self.board = Board::new(game_type, exchange_hex_count);
        self.ply_count = 0;
        self.selected_piece = None;
        self.last_move = None;
        self.exchanging = false;
        self.ai = AI::new();
        self.outcome = Outcome::InProgress;
        self.undo_stack.clear();
        self.redo_stack.clear();
    }
    pub fn try_move(&mut self, mv: Move) -> bool {
        if self.board.can_apply_move(&mv) {
            self.ply_count += 1;
            self.push_undo_state();
            self.last_move = Some(self.board.annotated_apply_move(&mv));
            self.update_outcome();
            true
        } else {
            false
        }
    }
    pub fn can_undo(&self) -> bool {
        let comp_v_comp =
            self.players.white == Player::Computer && self.players.black == Player::Computer;
        !comp_v_comp && !self.undo_stack.is_empty()
    }
    pub fn can_redo(&self) -> bool {
        let comp_v_comp =
            self.players.white == Player::Computer && self.players.black == Player::Computer;
        !comp_v_comp && !self.redo_stack.is_empty()
    }
    pub fn push_undo_state(&mut self) {
        self.undo_stack
            .push((self.board, self.last_move.clone(), self.outcome));
        self.redo_stack.clear();
    }
    pub fn undo_move(&mut self) {
        while let Some((board, last_move, outcome)) = self.undo_stack.pop() {
            self.redo_stack.push((
                mem::replace(&mut self.board, board),
                mem::replace(&mut self.last_move, last_move),
                mem::replace(&mut self.outcome, outcome),
            ));

            self.clear_selection();
            self.exchanging = false;

            if Player::Human == self.players.get(board.turn) {
                break;
            }
        }
    }
    pub fn redo_move(&mut self) {
        while let Some((board, last_move, outcome)) = self.redo_stack.pop() {
            self.undo_stack.push((
                mem::replace(&mut self.board, board),
                mem::replace(&mut self.last_move, last_move),
                mem::replace(&mut self.outcome, outcome),
            ));

            self.clear_selection();
            self.exchanging = false;

            if Player::Human == self.players.get(board.turn) {
                break;
            }
        }
    }
    pub fn board_list(&self) -> Vec<Board> {
        let mut board_list: Vec<_> = self.undo_stack.iter().map(|ref t| t.0).collect();
        board_list.push(self.board);
        board_list
    }
    pub fn clear_selection(&mut self) {
        self.selected_piece = None;
    }
    pub fn current_player(&self) -> Player {
        self.players.get(self.board.turn)
    }
    pub fn update_outcome(&mut self) {
        if self.outcome == Outcome::InProgress {
            // Only take positions after the last irreversible move
            let board_list: Vec<_> = self
                .board_list()
                .into_iter()
                .rev()
                .skip(1)
                .take_while(|b| b.vitals == self.board.vitals)
                .collect();

            if board_list.len() >= 8 && board_list.iter().filter(|&&b| b == self.board).count() >= 2
            {
                self.outcome = Outcome::DrawThreefoldRepetition;
            } else {
                self.outcome = self.board.outcome();
            }
        }
    }
    pub fn is_game_over(&self) -> bool {
        self.outcome != Outcome::InProgress
    }
    pub fn resign(&mut self) {
        assert_eq!(self.outcome, Outcome::InProgress);
        self.outcome = Outcome::Win(self.board.turn.switch());
    }
}

#[derive(Default)]
pub struct WindowStates {
    pub about: bool,
    pub ai_debug: bool,
    pub how_to_play: bool,
}

#[derive(Copy, Clone)]
pub enum GameType {
    Laurentius,
    Ocius,
}

/// The outcome of a game. This includes being in progress; a win/loss by capturing all of an
/// opponent's pieces; and a draw by stalemate (no legal moves left), insufficient material, or
/// threefold repetition.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Outcome {
    InProgress,
    DrawStalemate,
    DrawInsufficientMaterial,
    DrawThreefoldRepetition,
    Win(Color),
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Player {
    Human,
    Computer,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Color {
    White,
    Black,
}

impl Color {
    pub fn switch(self) -> Self {
        match self {
            Color::White => Color::Black,
            Color::Black => Color::White,
        }
    }
}

/// A map to associate any two values with the variants of the Color enum. Useful for keeping
/// track of player-specific information, which almost always comes in pairs.
#[derive(Clone, Copy, PartialEq)]
pub struct ColorMap<T> {
    pub white: T,
    pub black: T,
}

impl<T> ColorMap<T> {
    pub fn new(white: T, black: T) -> Self {
        Self { white, black }
    }
    pub fn get_ref(&self, color: Color) -> &T {
        match color {
            Color::White => &self.white,
            Color::Black => &self.black,
        }
    }
    pub fn get_mut(&mut self, color: Color) -> &mut T {
        match color {
            Color::White => &mut self.white,
            Color::Black => &mut self.black,
        }
    }
}

impl<T: Copy> ColorMap<T> {
    pub fn get(&self, color: Color) -> T {
        match color {
            Color::White => self.white,
            Color::Black => self.black,
        }
    }
}

#[derive(Clone, Copy, Debug)]
pub enum Move {
    Exchange(BitBoard, Color),
    Move(BitBoard, BitBoard, Color),
}

impl Move {
    pub fn move_from_field(from: FieldCoord, to: FieldCoord) -> Self {
        Move::Move(from.to_bitboard(), to.to_bitboard(), from.color())
    }
    pub fn exchange_from_field(field: FieldCoord) -> Self {
        Move::Exchange(field.to_bitboard(), field.color())
    }
    pub fn annotate(&self, pieces: Vec<FieldCoord>, hexes: Vec<HexCoord>) -> MoveAnnotated {
        MoveAnnotated {
            mv: *self,
            removed_pieces: pieces,
            removed_hexes: hexes,
        }
    }
}

impl fmt::Display for Move {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Move::Move(from, to, color) => write!(
                f,
                "Move({}, {})",
                FieldCoord::from_bitboard(from, color).to_notation(),
                FieldCoord::from_bitboard(to, color).to_notation(),
            ),
            Move::Exchange(bb, color) => write!(
                f,
                "Exchange({})",
                FieldCoord::from_bitboard(bb, color).to_notation(),
            ),
        }
    }
}

/// A move that also holds the pieces and hexes removed by playing that move. Used by the board to
/// show the effects of the last move.
#[derive(Clone)]
pub struct MoveAnnotated {
    pub mv: Move,
    pub removed_pieces: Vec<FieldCoord>,
    pub removed_hexes: Vec<HexCoord>,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FieldCoord {
    x: i8,
    y: i8,
    f: u8,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct HexCoord {
    x: i8,
    y: i8,
}

impl FieldCoord {
    pub fn new(x: i8, y: i8, f: u8) -> Self {
        assert!(Self::is_valid_coord(x, y, f));
        Self { x, y, f }
    }
    pub fn from_bitboard(bb: BitBoard, color: Color) -> Self {
        Self::from_index(bb.trailing_zeros() as u8, color)
    }
    pub fn from_index(index: u8, color: Color) -> Self {
        assert!(index < 57);

        let f = 2 * (index % 3)
            + match color {
                Color::White => 1,
                Color::Black => 0,
            };

        Self::from_hex_f(index / 3, f)
    }
    pub fn from_hex_f(hex: u8, f: u8) -> Self {
        assert!(hex < 19);
        assert!(f < 6);

        let hex = hex as i8
            + match hex {
                0..=2 => 2,
                3..=15 => 3,
                16..=18 => 4,
                _ => unreachable!(),
            };
        Self::new(hex % 5 - 2, hex / 5 - 2, f as u8)
    }
    pub fn to_hex(self) -> HexCoord {
        HexCoord {
            x: self.x,
            y: self.y,
        }
    }
    pub fn to_bitboard(self) -> BitBoard {
        let hex = 5 * (self.y + 2) + self.x + 2;
        let hex = hex as u8
            - match hex {
                2..=4 => 2,
                6..=18 => 3,
                20..=22 => 4,
                _ => unreachable!(),
            };

        1 << (hex * 3 + self.f / 2)
    }
    pub fn to_notation(self) -> String {
        let mut notation = String::with_capacity(3);

        notation.push(match self.x {
            -2 => 'a',
            -1 => 'b',
            0 => 'c',
            1 => 'd',
            2 => 'e',
            _ => unreachable!(),
        });

        let offset = 3 + if self.x < 0 { self.x } else { 0 };
        notation.push(match self.y + offset {
            1 => '1',
            2 => '2',
            3 => '3',
            4 => '4',
            5 => '5',
            _ => unreachable!(),
        });

        notation.push(match self.f {
            5 => 'a',
            4 => 'b',
            3 => 'c',
            2 => 'd',
            1 => 'e',
            0 => 'f',
            _ => unreachable!(),
        });
        notation
    }
    pub fn f(self) -> u8 {
        self.f
    }
    pub fn color(self) -> Color {
        if self.f % 2 == 0 {
            Color::Black
        } else {
            Color::White
        }
    }
    fn is_valid_coord(x: i8, y: i8, f: u8) -> bool {
        (x + y).abs() <= 2 && x.abs() <= 2 && y.abs() <= 2 && f < 6
    }
}

impl HexCoord {
    pub fn try_new(x: i8, y: i8) -> Option<Self> {
        if Self::is_valid_coord(x, y) {
            Some(Self { x, y })
        } else {
            None
        }
    }
    pub fn x(self) -> i8 {
        self.x
    }
    pub fn y(self) -> i8 {
        self.y
    }
    pub fn to_field(self, f: u8) -> FieldCoord {
        assert!(f < 6);
        FieldCoord {
            x: self.x,
            y: self.y,
            f,
        }
    }
    pub fn from_index(index: u8) -> Self {
        let hex = index as i8
            + match index {
                0..=2 => 2,
                3..=15 => 3,
                16..=18 => 4,
                _ => unreachable!(),
            };
        Self {
            x: hex % 5 - 2,
            y: hex / 5 - 2,
        }
    }
    pub fn to_index(self) -> usize {
        let hex = 5 * (self.y + 2) + self.x + 2;
        hex as usize
            - match hex {
                2..=4 => 2,
                6..=18 => 3,
                20..=22 => 4,
                _ => unreachable!(),
            }
    }
    fn is_valid_coord(x: i8, y: i8) -> bool {
        x.abs() <= 2 && y.abs() <= 2 && (x + y).abs() <= 2
    }
}
<commit_msg>Prevent coordinate check overflow<commit_after>/*
 * Copyright (C) 2017-2019 Ryan Huang
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

pub mod bitboard;
mod board;
mod constants;
pub mod ttable;
mod zobrist;

use std::cell::RefCell;
use std::fmt;
use std::mem;
use std::ops::RangeInclusive;

use glium::glutin::EventsLoopProxy;

use self::bitboard::BitBoard;
pub use self::board::Board;
use crate::ai::AI;

pub struct Model {
    pub game_type: GameType,
    pub board: Board,
    pub exchange_one_hex: RefCell<bool>,
    pub ply_count: u64,
    pub players: ColorMap<Player>,
    pub selected_piece: Option<FieldCoord>,
    pub last_move: Option<MoveAnnotated>,
    pub exchanging: bool,
    pub ai: AI,
    pub ai_search_depth: RefCell<i32>,
    pub window_states: RefCell<WindowStates>,
    pub outcome: Outcome,
    undo_stack: Vec<(Board, Option<MoveAnnotated>, Outcome)>,
    redo_stack: Vec<(Board, Option<MoveAnnotated>, Outcome)>,
    pub events_proxy: EventsLoopProxy,
}

impl Model {
    pub fn new(
        game_type: GameType,
        players: ColorMap<Player>,
        events_proxy: EventsLoopProxy,
    ) -> Self {
        Self {
            game_type,
            board: Board::new(game_type, 2),
            exchange_one_hex: RefCell::new(false),
            ply_count: 0,
            players,
            selected_piece: None,
            last_move: None,
            exchanging: false,
            ai: AI::new(),
            ai_search_depth: RefCell::new(6),
            window_states: RefCell::new(WindowStates::default()),
            outcome: Outcome::InProgress,
            undo_stack: vec![],
            redo_stack: vec![],
            events_proxy,
        }
    }
    pub fn reset(&mut self, game_type: GameType, players: ColorMap<Player>) {
        self.game_type = game_type;
        self.players = players;

        let exchange_hex_count = if *self.exchange_one_hex.borrow() {
            1
        } else {
            2
        };
        self.board = Board::new(game_type, exchange_hex_count);
        self.ply_count = 0;
        self.selected_piece = None;
        self.last_move = None;
        self.exchanging = false;
        self.ai = AI::new();
        self.outcome = Outcome::InProgress;
        self.undo_stack.clear();
        self.redo_stack.clear();
    }
    pub fn try_move(&mut self, mv: Move) -> bool {
        if self.board.can_apply_move(&mv) {
            self.ply_count += 1;
            self.push_undo_state();
            self.last_move = Some(self.board.annotated_apply_move(&mv));
            self.update_outcome();
            true
        } else {
            false
        }
    }
    pub fn can_undo(&self) -> bool {
        let comp_v_comp =
            self.players.white == Player::Computer && self.players.black == Player::Computer;
        !comp_v_comp && !self.undo_stack.is_empty()
    }
    pub fn can_redo(&self) -> bool {
        let comp_v_comp =
            self.players.white == Player::Computer && self.players.black == Player::Computer;
        !comp_v_comp && !self.redo_stack.is_empty()
    }
    pub fn push_undo_state(&mut self) {
        self.undo_stack
            .push((self.board, self.last_move.clone(), self.outcome));
        self.redo_stack.clear();
    }
    pub fn undo_move(&mut self) {
        while let Some((board, last_move, outcome)) = self.undo_stack.pop() {
            self.redo_stack.push((
                mem::replace(&mut self.board, board),
                mem::replace(&mut self.last_move, last_move),
                mem::replace(&mut self.outcome, outcome),
            ));

            self.clear_selection();
            self.exchanging = false;

            if Player::Human == self.players.get(board.turn) {
                break;
            }
        }
    }
    pub fn redo_move(&mut self) {
        while let Some((board, last_move, outcome)) = self.redo_stack.pop() {
            self.undo_stack.push((
                mem::replace(&mut self.board, board),
                mem::replace(&mut self.last_move, last_move),
                mem::replace(&mut self.outcome, outcome),
            ));

            self.clear_selection();
            self.exchanging = false;

            if Player::Human == self.players.get(board.turn) {
                break;
            }
        }
    }
    pub fn board_list(&self) -> Vec<Board> {
        let mut board_list: Vec<_> = self.undo_stack.iter().map(|ref t| t.0).collect();
        board_list.push(self.board);
        board_list
    }
    pub fn clear_selection(&mut self) {
        self.selected_piece = None;
    }
    pub fn current_player(&self) -> Player {
        self.players.get(self.board.turn)
    }
    pub fn update_outcome(&mut self) {
        if self.outcome == Outcome::InProgress {
            // Only take positions after the last irreversible move
            let board_list: Vec<_> = self
                .board_list()
                .into_iter()
                .rev()
                .skip(1)
                .take_while(|b| b.vitals == self.board.vitals)
                .collect();

            if board_list.len() >= 8 && board_list.iter().filter(|&&b| b == self.board).count() >= 2
            {
                self.outcome = Outcome::DrawThreefoldRepetition;
            } else {
                self.outcome = self.board.outcome();
            }
        }
    }
    pub fn is_game_over(&self) -> bool {
        self.outcome != Outcome::InProgress
    }
    pub fn resign(&mut self) {
        assert_eq!(self.outcome, Outcome::InProgress);
        self.outcome = Outcome::Win(self.board.turn.switch());
    }
}

#[derive(Default)]
pub struct WindowStates {
    pub about: bool,
    pub ai_debug: bool,
    pub how_to_play: bool,
}

#[derive(Copy, Clone)]
pub enum GameType {
    Laurentius,
    Ocius,
}

/// The outcome of a game. This includes being in progress; a win/loss by capturing all of an
/// opponent's pieces; and a draw by stalemate (no legal moves left), insufficient material, or
/// threefold repetition.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Outcome {
    InProgress,
    DrawStalemate,
    DrawInsufficientMaterial,
    DrawThreefoldRepetition,
    Win(Color),
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Player {
    Human,
    Computer,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Color {
    White,
    Black,
}

impl Color {
    pub fn switch(self) -> Self {
        match self {
            Color::White => Color::Black,
            Color::Black => Color::White,
        }
    }
}

/// A map to associate any two values with the variants of the Color enum. Useful for keeping
/// track of player-specific information, which almost always comes in pairs.
#[derive(Clone, Copy, PartialEq)]
pub struct ColorMap<T> {
    pub white: T,
    pub black: T,
}

impl<T> ColorMap<T> {
    pub fn new(white: T, black: T) -> Self {
        Self { white, black }
    }
    pub fn get_ref(&self, color: Color) -> &T {
        match color {
            Color::White => &self.white,
            Color::Black => &self.black,
        }
    }
    pub fn get_mut(&mut self, color: Color) -> &mut T {
        match color {
            Color::White => &mut self.white,
            Color::Black => &mut self.black,
        }
    }
}

impl<T: Copy> ColorMap<T> {
    pub fn get(&self, color: Color) -> T {
        match color {
            Color::White => self.white,
            Color::Black => self.black,
        }
    }
}

#[derive(Clone, Copy, Debug)]
pub enum Move {
    Exchange(BitBoard, Color),
    Move(BitBoard, BitBoard, Color),
}

impl Move {
    pub fn move_from_field(from: FieldCoord, to: FieldCoord) -> Self {
        Move::Move(from.to_bitboard(), to.to_bitboard(), from.color())
    }
    pub fn exchange_from_field(field: FieldCoord) -> Self {
        Move::Exchange(field.to_bitboard(), field.color())
    }
    pub fn annotate(&self, pieces: Vec<FieldCoord>, hexes: Vec<HexCoord>) -> MoveAnnotated {
        MoveAnnotated {
            mv: *self,
            removed_pieces: pieces,
            removed_hexes: hexes,
        }
    }
}

impl fmt::Display for Move {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Move::Move(from, to, color) => write!(
                f,
                "Move({}, {})",
                FieldCoord::from_bitboard(from, color).to_notation(),
                FieldCoord::from_bitboard(to, color).to_notation(),
            ),
            Move::Exchange(bb, color) => write!(
                f,
                "Exchange({})",
                FieldCoord::from_bitboard(bb, color).to_notation(),
            ),
        }
    }
}

/// A move that also holds the pieces and hexes removed by playing that move. Used by the board to
/// show the effects of the last move.
#[derive(Clone)]
pub struct MoveAnnotated {
    pub mv: Move,
    pub removed_pieces: Vec<FieldCoord>,
    pub removed_hexes: Vec<HexCoord>,
}

const COORD_RANGE: RangeInclusive<i8> = -2..=2;

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FieldCoord {
    x: i8,
    y: i8,
    f: u8,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct HexCoord {
    x: i8,
    y: i8,
}

impl FieldCoord {
    pub fn new(x: i8, y: i8, f: u8) -> Self {
        assert!(Self::is_valid_coord(x, y, f));
        Self { x, y, f }
    }
    pub fn from_bitboard(bb: BitBoard, color: Color) -> Self {
        Self::from_index(bb.trailing_zeros() as u8, color)
    }
    pub fn from_index(index: u8, color: Color) -> Self {
        assert!(index < 57);

        let f = 2 * (index % 3)
            + match color {
                Color::White => 1,
                Color::Black => 0,
            };

        Self::from_hex_f(index / 3, f)
    }
    pub fn from_hex_f(hex: u8, f: u8) -> Self {
        assert!(hex < 19);
        assert!(f < 6);

        let hex = hex as i8
            + match hex {
                0..=2 => 2,
                3..=15 => 3,
                16..=18 => 4,
                _ => unreachable!(),
            };
        Self::new(hex % 5 - 2, hex / 5 - 2, f as u8)
    }
    pub fn to_hex(self) -> HexCoord {
        HexCoord {
            x: self.x,
            y: self.y,
        }
    }
    pub fn to_bitboard(self) -> BitBoard {
        let hex = 5 * (self.y + 2) + self.x + 2;
        let hex = hex as u8
            - match hex {
                2..=4 => 2,
                6..=18 => 3,
                20..=22 => 4,
                _ => unreachable!(),
            };

        1 << (hex * 3 + self.f / 2)
    }
    pub fn to_notation(self) -> String {
        let mut notation = String::with_capacity(3);

        notation.push(match self.x {
            -2 => 'a',
            -1 => 'b',
            0 => 'c',
            1 => 'd',
            2 => 'e',
            _ => unreachable!(),
        });

        let offset = 3 + if self.x < 0 { self.x } else { 0 };
        notation.push(match self.y + offset {
            1 => '1',
            2 => '2',
            3 => '3',
            4 => '4',
            5 => '5',
            _ => unreachable!(),
        });

        notation.push(match self.f {
            5 => 'a',
            4 => 'b',
            3 => 'c',
            2 => 'd',
            1 => 'e',
            0 => 'f',
            _ => unreachable!(),
        });
        notation
    }
    pub fn f(self) -> u8 {
        self.f
    }
    pub fn color(self) -> Color {
        if self.f % 2 == 0 {
            Color::Black
        } else {
            Color::White
        }
    }
    fn is_valid_coord(x: i8, y: i8, f: u8) -> bool {
        f < 6
            && COORD_RANGE.contains(&x)
            && COORD_RANGE.contains(&y)
            && COORD_RANGE.contains(&(x + y))
    }
}

impl HexCoord {
    pub fn try_new(x: i8, y: i8) -> Option<Self> {
        if Self::is_valid_coord(x, y) {
            Some(Self { x, y })
        } else {
            None
        }
    }
    pub fn x(self) -> i8 {
        self.x
    }
    pub fn y(self) -> i8 {
        self.y
    }
    pub fn to_field(self, f: u8) -> FieldCoord {
        assert!(f < 6);
        FieldCoord {
            x: self.x,
            y: self.y,
            f,
        }
    }
    pub fn from_index(index: u8) -> Self {
        let hex = index as i8
            + match index {
                0..=2 => 2,
                3..=15 => 3,
                16..=18 => 4,
                _ => unreachable!(),
            };
        Self {
            x: hex % 5 - 2,
            y: hex / 5 - 2,
        }
    }
    pub fn to_index(self) -> usize {
        let hex = 5 * (self.y + 2) + self.x + 2;
        hex as usize
            - match hex {
                2..=4 => 2,
                6..=18 => 3,
                20..=22 => 4,
                _ => unreachable!(),
            }
    }
    fn is_valid_coord(x: i8, y: i8) -> bool {
        COORD_RANGE.contains(&x) && COORD_RANGE.contains(&y) && COORD_RANGE.contains(&(x + y))
    }
}
<|endoftext|>


<commit_before>use rustc_serialize::Encodable;
use std::net::{TcpListener, TcpStream, ToSocketAddrs};
use std::thread;
use std::collections::HashMap;
use std;
use super::encoder::Encoder;
use super::error::Error;
use super::header::{encode, decode};
use super::Message;
use super::streamfork::{fork, TargetList, DataStream};

pub struct Publisher {
    subscriptions: DataStream,
    pub ip: String,
    pub port: u16,
    pub msg_type: String,
    pub topic: String,
}

fn header_matches<T: Message>(fields: &HashMap<String, String>, topic: &str) -> bool {
    fields.get("md5sum") == Some(&T::md5sum()) && fields.get("type") == Some(&T::msg_type()) &&
    fields.get("message_definition") == Some(&T::msg_definition()) &&
    fields.get("topic") == Some(&String::from(topic)) && fields.get("callerid") != None
}

fn read_request<T: Message, U: std::io::Read>(mut stream: &mut U,
                                              topic: &str)
                                              -> Result<(), Error> {
    if header_matches::<T>(&decode(&mut stream)?, topic) {
        Ok(())
    } else {
        Err(Error::Mismatch)
    }
}

fn write_response<T: Message, U: std::io::Write>(mut stream: &mut U) -> Result<(), Error> {
    let mut fields = HashMap::<String, String>::new();
    fields.insert(String::from("md5sum"), T::md5sum());
    fields.insert(String::from("type"), T::msg_type());
    encode(fields, &mut stream)
}

fn exchange_headers<T, U>(mut stream: &mut U, topic: &str) -> Result<(), Error>
    where T: Message,
          U: std::io::Write + std::io::Read
{
    read_request::<T, U>(&mut stream, &topic)?;
    write_response::<T, U>(&mut stream)
}

fn listen_for_subscribers<T: Message>(topic: String,
                                      listener: TcpListener,
                                      targets: TargetList<TcpStream>)
                                      -> Result<(), Error> {
    for stream in listener.incoming() {
        match stream {
            Ok(mut stream) => {
                if let Err(err) = exchange_headers::<T, _>(&mut stream, &topic) {
                    error!("Failed to exchange headers for topic '{}': {}", topic, err);
                    continue;
                }
                if targets.add(stream).is_err() {
                    // The TCP listener gets shut down when streamfork's thread deallocates
                    // This happens only when the corresponding Publisher gets deallocated,
                    // causing streamfork's data channel to shut down
                    break;
                }
            }
            Err(err) => {
                error!("TCP connection to subscriber failed on topic '{}': {}",
                       topic,
                       err);
            }
        }
    }
    Ok(())
}

impl Publisher {
    pub fn new<T, U>(address: U, topic: &str) -> Result<Publisher, Error>
        where T: Message,
              U: ToSocketAddrs
    {
        let listener = TcpListener::bind(address)?;
        let socket_address = listener.local_addr()?;
        let (targets, data) = fork();
        let topic_name = String::from(topic);
        thread::spawn(move || listen_for_subscribers::<T>(topic_name, listener, targets));
        Ok(Publisher {
            subscriptions: data,
            ip: format!("{}", socket_address.ip()),
            port: socket_address.port(),
            msg_type: T::msg_type(),
            topic: String::from(topic),
        })
    }

    pub fn send<T: Message + Encodable>(&mut self, message: T) {
        let mut encoder = Encoder::new();
        // Failure while encoding can only be caused by unsupported data types,
        // unless using deliberately bad handwritten rosmsg-s, this should never fail
        message.encode(&mut encoder).unwrap();
        // Subscriptions can only be closed from the Publisher side
        // There is no way for the streamfork thread to fail by itself
        self.subscriptions.send(encoder).unwrap();
    }
}
<commit_msg>Allow for mocks to test publisher<commit_after>use rustc_serialize::Encodable;
use std::net::{TcpListener, TcpStream, ToSocketAddrs};
use std::thread;
use std::collections::HashMap;
use std;
use super::encoder::Encoder;
use super::error::Error;
use super::header::{encode, decode};
use super::Message;
use super::streamfork::{fork, TargetList, DataStream};

pub struct Publisher {
    subscriptions: DataStream,
    pub ip: String,
    pub port: u16,
    pub msg_type: String,
    pub topic: String,
}

fn header_matches<T: Message>(fields: &HashMap<String, String>, topic: &str) -> bool {
    fields.get("md5sum") == Some(&T::md5sum()) && fields.get("type") == Some(&T::msg_type()) &&
    fields.get("message_definition") == Some(&T::msg_definition()) &&
    fields.get("topic") == Some(&String::from(topic)) && fields.get("callerid") != None
}

fn read_request<T: Message, U: std::io::Read>(mut stream: &mut U,
                                              topic: &str)
                                              -> Result<(), Error> {
    if header_matches::<T>(&decode(&mut stream)?, topic) {
        Ok(())
    } else {
        Err(Error::Mismatch)
    }
}

fn write_response<T: Message, U: std::io::Write>(mut stream: &mut U) -> Result<(), Error> {
    let mut fields = HashMap::<String, String>::new();
    fields.insert(String::from("md5sum"), T::md5sum());
    fields.insert(String::from("type"), T::msg_type());
    encode(fields, &mut stream)
}

fn exchange_headers<T, U>(mut stream: &mut U, topic: &str) -> Result<(), Error>
    where T: Message,
          U: std::io::Write + std::io::Read
{
    read_request::<T, U>(&mut stream, &topic)?;
    write_response::<T, U>(&mut stream)
}

fn listen_for_subscribers<T, U, V>(topic: String,
                                   listener: V,
                                   targets: TargetList<U>)
                                   -> Result<(), Error>
    where T: Message,
          U: std::io::Read + std::io::Write + Send,
          V: Iterator<Item = U>
{
    for mut stream in listener {
        if let Err(err) = exchange_headers::<T, _>(&mut stream, &topic) {
            error!("Failed to exchange headers for topic '{}': {}", topic, err);
            continue;
        }
        if targets.add(stream).is_err() {
            // The TCP listener gets shut down when streamfork's thread deallocates
            // This happens only when the corresponding Publisher gets deallocated,
            // causing streamfork's data channel to shut down
            break;
        }
    }

    Ok(())
}

impl Publisher {
    pub fn new<T, U>(address: U, topic: &str) -> Result<Publisher, Error>
        where T: Message,
              U: ToSocketAddrs
    {
        let listener = TcpListener::bind(address)?;
        let socket_address = listener.local_addr()?;
        Ok(Publisher::wrap_stream::<T, _, _>(topic,
                                             TcpIterator::new(listener, topic),
                                             &format!("{}", socket_address.ip()),
                                             socket_address.port()))
    }

    fn wrap_stream<T, U, V>(topic: &str, listener: V, ip: &str, port: u16) -> Publisher
        where T: Message,
              U: std::io::Read + std::io::Write + Send + 'static,
              V: Iterator<Item = U> + Send + 'static
    {
        let (targets, data) = fork();
        let topic_name = String::from(topic);
        thread::spawn(move || listen_for_subscribers::<T, _, _>(topic_name, listener, targets));
        Publisher {
            subscriptions: data,
            ip: String::from(ip),
            port: port,
            msg_type: T::msg_type(),
            topic: String::from(topic),
        }
    }

    pub fn send<T: Message + Encodable>(&mut self, message: T) {
        let mut encoder = Encoder::new();
        // Failure while encoding can only be caused by unsupported data types,
        // unless using deliberately bad handwritten rosmsg-s, this should never fail
        message.encode(&mut encoder).unwrap();
        // Subscriptions can only be closed from the Publisher side
        // There is no way for the streamfork thread to fail by itself
        self.subscriptions.send(encoder).unwrap();
    }
}

struct TcpIterator {
    listener: TcpListener,
    topic: String,
}

impl TcpIterator {
    pub fn new(listener: TcpListener, topic: &str) -> TcpIterator {
        TcpIterator {
            listener: listener,
            topic: String::from(topic),
        }
    }
}

impl Iterator for TcpIterator {
    type Item = TcpStream;

    fn next(&mut self) -> Option<Self::Item> {
        match self.listener.accept() {
            Ok((stream, _)) => Some(stream),
            Err(err) => {
                error!("TCP connection to subscriber failed on topic '{}': {}",
                       self.topic,
                       err);
                self.next()
            }
        }
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;

use executor::FieldResult;
use value::Value;
use schema::model::RootNode;

enum Sample {
    One,
    Two,
}

struct Root {}

graphql_enum!(Sample as "SampleEnum" {
    Sample::One => "ONE",
    Sample::Two => "TWO",
});

graphql_object!(Root: () as "Root" |&self| {
    field sample_enum() -> FieldResult<Sample> {
        Ok(Sample::One)
    }
});

#[test]
fn enum_introspection() {
    let doc = r#"
    {
        __type(name: "SampleEnum") {
            enumValues {
                name
                description
                isDeprecated
                deprecationReason
            }
        }
    }
    "#;
    let schema = RootNode::new(Root {}, ());

    let (result, errs) = ::execute(doc, None, &schema, &HashMap::new(), &())
        .expect("Execution failed");

    assert_eq!(errs, []);

    println!("Result: {:?}", result);

    let values = result
        .as_object_value().expect("Result is not an object")
        .get("__type").expect("__type field missing")
        .as_object_value().expect("__type field not an object value")
        .get("enumValues").expect("enumValues field missing")
        .as_list_value().expect("enumValues not a list");

    assert_eq!(values.len(), 2);

    assert!(values.contains(&Value::object(vec![
        ("name", Value::string("ONE")),
        ("description", Value::null()),
        ("isDeprecated", Value::boolean(false)),
        ("deprecationReason", Value::null()),
    ].into_iter().collect())));

    assert!(values.contains(&Value::object(vec![
        ("name", Value::string("TWO")),
        ("description", Value::null()),
        ("isDeprecated", Value::boolean(false)),
        ("deprecationReason", Value::null()),
    ].into_iter().collect())));
}
<commit_msg>Add basic interface introspection tests<commit_after>use std::collections::HashMap;

use executor::FieldResult;
use value::Value;
use schema::model::RootNode;

enum Sample {
    One,
    Two,
}

struct Interface {}

struct Root {}

graphql_enum!(Sample as "SampleEnum" {
    Sample::One => "ONE",
    Sample::Two => "TWO",
});

graphql_interface!(Interface: () as "SampleInterface" |&self| {
    description: "A sample interface"

    field sample_enum() -> FieldResult<Sample> as "A sample field in the interface" {
        Ok(Sample::One)
    }

    instance_resolvers: |&_| [
        Some(Root {}),
    ]
});

graphql_object!(Root: () as "Root" |&self| {
    interfaces: [Interface]

    field sample_enum() -> FieldResult<Sample> {
        Ok(Sample::One)
    }
});

#[test]
fn enum_introspection() {
    let doc = r#"
    {
        __type(name: "SampleEnum") {
            name
            kind
            description
            enumValues {
                name
                description
                isDeprecated
                deprecationReason
            }
        }
    }
    "#;
    let schema = RootNode::new(Root {}, ());

    let (result, errs) = ::execute(doc, None, &schema, &HashMap::new(), &())
        .expect("Execution failed");

    assert_eq!(errs, []);

    println!("Result: {:?}", result);

    let type_info = result
        .as_object_value().expect("Result is not an object")
        .get("__type").expect("__type field missing")
        .as_object_value().expect("__type field not an object value");

    assert_eq!(type_info.get("name"), Some(&Value::string("SampleEnum")));
    assert_eq!(type_info.get("kind"), Some(&Value::string("ENUM")));
    assert_eq!(type_info.get("description"), Some(&Value::null()));

    let values = type_info
        .get("enumValues").expect("enumValues field missing")
        .as_list_value().expect("enumValues not a list");

    assert_eq!(values.len(), 2);

    assert!(values.contains(&Value::object(vec![
        ("name", Value::string("ONE")),
        ("description", Value::null()),
        ("isDeprecated", Value::boolean(false)),
        ("deprecationReason", Value::null()),
    ].into_iter().collect())));

    assert!(values.contains(&Value::object(vec![
        ("name", Value::string("TWO")),
        ("description", Value::null()),
        ("isDeprecated", Value::boolean(false)),
        ("deprecationReason", Value::null()),
    ].into_iter().collect())));
}

#[test]
fn interface_introspection() {
    let doc = r#"
    {
        __type(name: "SampleInterface") {
            name
            kind
            description
            possibleTypes {
                name
            }
            fields {
                name
                description
                args {
                    name
                }
                type {
                    name
                    kind
                    ofType {
                        name
                        kind
                    }
                }
                isDeprecated
                deprecationReason
            }
        }
    }
    "#;
    let schema = RootNode::new(Root {}, ());

    let (result, errs) = ::execute(doc, None, &schema, &HashMap::new(), &())
        .expect("Execution failed");

    assert_eq!(errs, []);

    println!("Result: {:?}", result);

    let type_info = result
        .as_object_value().expect("Result is not an object")
        .get("__type").expect("__type field missing")
        .as_object_value().expect("__type field not an object value");

    assert_eq!(type_info.get("name"), Some(&Value::string("SampleInterface")));
    assert_eq!(type_info.get("kind"), Some(&Value::string("INTERFACE")));
    assert_eq!(type_info.get("description"), Some(&Value::string("A sample interface")));

    let possible_types = type_info
        .get("possibleTypes").expect("possibleTypes field missing")
        .as_list_value().expect("possibleTypes not a list");

    assert_eq!(possible_types.len(), 1);

    assert!(possible_types.contains(&Value::object(vec![
        ("name", Value::string("Root")),
    ].into_iter().collect())));

    let fields = type_info
        .get("fields").expect("fields field missing")
        .as_list_value().expect("fields field not an object value");

    assert_eq!(fields.len(), 2);

    assert!(fields.contains(&Value::object(vec![
        ("name", Value::string("sampleEnum")),
        ("description", Value::string("A sample field in the interface")),
        ("args", Value::list(vec![])),
        ("type", Value::object(vec![
            ("name", Value::null()),
            ("kind", Value::string("NON_NULL")),
            ("ofType", Value::object(vec![
                ("name", Value::string("SampleEnum")),
                ("kind", Value::string("ENUM")),
            ].into_iter().collect())),
        ].into_iter().collect())),
        ("isDeprecated", Value::boolean(false)),
        ("deprecationReason", Value::null()),
    ].into_iter().collect())));
}
<|endoftext|>


<commit_before>//! Scheduler used in Uni.rs

use core::ptr;
use core::mem;

use hal::app;
use hal::{local_irq_disable, local_irq_enable};

use alloc::boxed::Box;

use sync::spin::{InterruptSpinLock, InterruptSpinGuard};
use intrusive::queue::Queue;

use super::{Thread, Builder, ThreadImpl, State};
use super::wait_queue::InternalQueue;

static mut SCHEDULER: SchedulerImpl = SchedulerImpl::new();

pub struct Scheduler;

impl Scheduler {
    /// Mark a thread as ready, and queue it to the ready queue of the
    /// scheduler
    pub fn ready(thread: Thread) {
        unsafe {
            SCHEDULER.ready(thread);
        }
    }

    /// Run the scheduler. The current running process is put back in the
    /// ready queue. The first element of the queue is then executed
    pub fn schedule() {
        unsafe {
            SCHEDULER.schedule(true);
        }
    }

    #[doc(hidden)]
    /// Block the current thread inside a wait queue.
    pub fn block<'a>(queue: InterruptSpinGuard<'a, InternalQueue>) {
        unsafe {
            SCHEDULER.block(queue);
        }
    }

    #[doc(hidden)]
    /// Terminate the execution of the current thread
    pub fn terminate() -> ! {
        unsafe {
            SCHEDULER.terminate();
        }
    }

    /// Spawn a new thread and add it to the scheduler right away
    pub fn spawn<F>(fun: F)
        where F: FnMut() -> (), F: Send + 'static
    {
        Scheduler::ready(Builder::new().spawn(fun));
    }
}

struct SchedulerImpl {
    // This is a little hack here. We are not allowed to have boxes on static
    // mutable variables. Instead we store the raw pointer stored in the Box.
    // We have to be extra careful to rebox it properly when needed,
    // Unique is used to represent ownership
    running: Option<ptr::Unique<ThreadImpl>>,
    ready_list: InterruptSpinLock<Queue<Box<ThreadImpl>, ThreadImpl>>,
    cleanup_queue: InterruptSpinLock<Queue<Box<ThreadImpl>, ThreadImpl>>,
}

impl SchedulerImpl {
    pub const fn new() -> Self {
        SchedulerImpl {
            running: None,
            ready_list: InterruptSpinLock::new(Queue::new()),
            cleanup_queue: InterruptSpinLock::new(Queue::new()),
        }
    }

    /// Mark a thread as ready, and queue it to the ready queue of the
    /// scheduler
    pub fn ready(&self, mut thread: Thread) {
        thread.t_impl.state = State::Ready;

        self.ready_list.lock().enqueue(thread.t_impl);
    }

    unsafe fn cleanup(&mut self) {
        let mut queue = self.cleanup_queue.lock();

        while let Some(t) = queue.dequeue() {
            mem::drop(t);
        }
    }

    // Return the next thread to run. If None is returned then it means that no
    // thread is ready to be scheduled and the current thread was just blocked
    // or terminated
    unsafe fn elect_next(&mut self) -> Option<ptr::Unique<ThreadImpl>> {
        match self.running.take() {
            None => self.ready_list
                        .lock()
                        .dequeue()
                        .and_then(|t| Some(ptr::Unique::new(Box::into_raw(t)))),
            Some(mut r) => {
                let next = self.ready_list.lock().dequeue();

                match next {
                    None => {
                        if r.get().state == State::Running {
                            self.running = Some(r);
                        }

                        None
                    }
                    Some(n) => {
                        if r.get().state == State::Running {
                            r.get_mut().state = State::Ready;

                            self.ready_list
                                .lock()
                                .enqueue(Box::from_raw(r.get_mut()));
                        }

                        Some(ptr::Unique::new(Box::into_raw(n)))
                    }
                }
            }
        }
    }

    unsafe fn switch_to_next(&mut self, mut t: ptr::Unique<ThreadImpl>) -> ! {
        let t_ptr: *const _;

        {
            t_ptr = t.get();
        }

        t.get_mut().state = State::Running;

        self.running = Some(t);

        // Called with interruptions disabled
        local_irq_enable();

        (*t_ptr).context.load();
    }

    pub unsafe fn schedule(&mut self, cleanup: bool) {
        if cleanup {
            // Properly cleanup terminated threads
            self.cleanup();
        }

        if let Some(ref mut r) = self.running {
            // As explained in Context::save() this call will return twice.
            // When this function returns true that means that the thread was
            // just restored and shall continue its execution. This is why we
            // immediately return
            if r.get_mut().context.save() {
                return;
            }
        }

        loop {
            local_irq_disable();

            let next = self.elect_next();

            // The only time elect_next() leaves self.running not None is when
            // the current thread is running and there is no other thread ready
            // to be scheduled. In this case let the current thread continue
            if self.running.is_some() {
                local_irq_enable();
                break;
            }

            match next {
                None => {
                    // If no threads can be run wait for an interruption. An
                    // interruption might wake up a thread for us to run
                    app::block();
                    local_irq_enable();
                }
                Some(next) => {
                    self.switch_to_next(next);
                }
            }
        }
    }

    pub unsafe fn block<'a>(&mut self,
                            mut queue: InterruptSpinGuard<'a, InternalQueue>) {
        if let Some(ref mut r) = self.running {
            r.get_mut().state = State::Blocked;

            queue.enqueue(Box::from_raw(r.get_mut()));

            if r.get_mut().context.save() {
                // We forget the queue because it was dropped when the function
                // was actually blocking the thread. If we are here that means
                // that we were just unblocked (for more info see comment in
                // schedule())
                mem::forget(queue);

                return;
            }
        }

        let next = self.elect_next();

        // The spinlock guard is taken as parameter to avoid races on multiple
        // CPUs (even if not supported ATM). The queue is released here because
        // it is safe to use the current thread (that was just blocked) in an
        // other CPU. Indeed its context was just saved and it was removed from
        // being self.current. The current CPU has no more reference to that
        // thread so its safe to be unblocked by another CPU.
        mem::drop(queue);

        match next {
            None => {
                // See comment in schedule()
                app::block();
                local_irq_enable();
            }
            Some(next) => {
                self.switch_to_next(next);
            }
        }

        // We don't call schedule before because we need to release that lock
        // after elect_next() runs to remove all references to the blocked
        // thread in the current CPU
        self.schedule(true);
    }

    pub unsafe fn terminate(&mut self) -> ! {
        if let Some(ref mut t) = self.running {
            t.get_mut().state = State::Terminated;

            self.cleanup_queue.lock().enqueue(Box::from_raw(t.get_mut()));
        }

        // Don't cleanup terminated threads as this would mean that we release
        // the current thread, which would lead to bad things if done while
        // its still running.
        self.schedule(false);

        unreachable!()
    }
}
<commit_msg>thread/scheduler: Fix some issues detected by clippy<commit_after>//! Scheduler used in Uni.rs

use core::ptr;
use core::mem;

use hal::app;
use hal::{local_irq_disable, local_irq_enable};

use alloc::boxed::Box;

use sync::spin::{InterruptSpinLock, InterruptSpinGuard};
use intrusive::queue::Queue;

use super::{Thread, Builder, ThreadImpl, State};
use super::wait_queue::InternalQueue;

static mut SCHEDULER: SchedulerImpl = SchedulerImpl::new();

pub struct Scheduler;

impl Scheduler {
    /// Mark a thread as ready, and queue it to the ready queue of the
    /// scheduler
    pub fn ready(thread: Thread) {
        unsafe {
            SCHEDULER.ready(thread);
        }
    }

    /// Run the scheduler. The current running process is put back in the
    /// ready queue. The first element of the queue is then executed
    pub fn schedule() {
        unsafe {
            SCHEDULER.schedule(true);
        }
    }

    #[doc(hidden)]
    /// Block the current thread inside a wait queue.
    pub fn block(queue: InterruptSpinGuard<InternalQueue>) {
        unsafe {
            SCHEDULER.block(queue);
        }
    }

    #[doc(hidden)]
    /// Terminate the execution of the current thread
    pub fn terminate() -> ! {
        unsafe {
            SCHEDULER.terminate();
        }
    }

    /// Spawn a new thread and add it to the scheduler right away
    pub fn spawn<F>(fun: F)
        where F: FnMut() -> (), F: Send + 'static
    {
        Scheduler::ready(Builder::new().spawn(fun));
    }
}

struct SchedulerImpl {
    // This is a little hack here. We are not allowed to have boxes on static
    // mutable variables. Instead we store the raw pointer stored in the Box.
    // We have to be extra careful to rebox it properly when needed,
    // Unique is used to represent ownership
    running: Option<ptr::Unique<ThreadImpl>>,
    ready_list: InterruptSpinLock<Queue<Box<ThreadImpl>, ThreadImpl>>,
    cleanup_queue: InterruptSpinLock<Queue<Box<ThreadImpl>, ThreadImpl>>,
}

impl SchedulerImpl {
    pub const fn new() -> Self {
        SchedulerImpl {
            running: None,
            ready_list: InterruptSpinLock::new(Queue::new()),
            cleanup_queue: InterruptSpinLock::new(Queue::new()),
        }
    }

    /// Mark a thread as ready, and queue it to the ready queue of the
    /// scheduler
    pub fn ready(&self, mut thread: Thread) {
        thread.t_impl.state = State::Ready;

        self.ready_list.lock().enqueue(thread.t_impl);
    }

    unsafe fn cleanup(&mut self) {
        let mut queue = self.cleanup_queue.lock();

        while let Some(t) = queue.dequeue() {
            mem::drop(t);
        }
    }

    // Return the next thread to run. If None is returned then it means that no
    // thread is ready to be scheduled and the current thread was just blocked
    // or terminated
    unsafe fn elect_next(&mut self) -> Option<ptr::Unique<ThreadImpl>> {
        match self.running.take() {
            None => self.ready_list
                        .lock()
                        .dequeue()
                        .and_then(|t| Some(ptr::Unique::new(Box::into_raw(t)))),
            Some(mut r) => {
                let next = self.ready_list.lock().dequeue();

                match next {
                    None => {
                        if r.get().state == State::Running {
                            self.running = Some(r);
                        }

                        None
                    }
                    Some(n) => {
                        if r.get().state == State::Running {
                            r.get_mut().state = State::Ready;

                            self.ready_list
                                .lock()
                                .enqueue(Box::from_raw(r.get_mut()));
                        }

                        Some(ptr::Unique::new(Box::into_raw(n)))
                    }
                }
            }
        }
    }

    unsafe fn switch_to_next(&mut self, mut t: ptr::Unique<ThreadImpl>) -> ! {
        let t_ptr: *const _;

        {
            t_ptr = t.get();
        }

        t.get_mut().state = State::Running;

        self.running = Some(t);

        // Called with interruptions disabled
        local_irq_enable();

        (*t_ptr).context.load();
    }

    pub unsafe fn schedule(&mut self, cleanup: bool) {
        if cleanup {
            // Properly cleanup terminated threads
            self.cleanup();
        }

        if let Some(ref mut r) = self.running {
            // As explained in Context::save() this call will return twice.
            // When this function returns true that means that the thread was
            // just restored and shall continue its execution. This is why we
            // immediately return
            if r.get_mut().context.save() {
                return;
            }
        }

        loop {
            local_irq_disable();

            let next = self.elect_next();

            // The only time elect_next() leaves self.running not None is when
            // the current thread is running and there is no other thread ready
            // to be scheduled. In this case let the current thread continue
            if self.running.is_some() {
                local_irq_enable();
                break;
            }

            match next {
                None => {
                    // If no threads can be run wait for an interruption. An
                    // interruption might wake up a thread for us to run
                    app::block();
                    local_irq_enable();
                }
                Some(next) => {
                    self.switch_to_next(next);
                }
            }
        }
    }

    pub unsafe fn block(&mut self,
                        mut queue: InterruptSpinGuard<InternalQueue>) {
        if let Some(ref mut r) = self.running {
            r.get_mut().state = State::Blocked;

            queue.enqueue(Box::from_raw(r.get_mut()));

            if r.get_mut().context.save() {
                // We forget the queue because it was dropped when the function
                // was actually blocking the thread. If we are here that means
                // that we were just unblocked (for more info see comment in
                // schedule())
                mem::forget(queue);

                return;
            }
        }

        let next = self.elect_next();

        // The spinlock guard is taken as parameter to avoid races on multiple
        // CPUs (even if not supported ATM). The queue is released here because
        // it is safe to use the current thread (that was just blocked) in an
        // other CPU. Indeed its context was just saved and it was removed from
        // being self.current. The current CPU has no more reference to that
        // thread so its safe to be unblocked by another CPU.
        mem::drop(queue);

        match next {
            None => {
                // See comment in schedule()
                app::block();
                local_irq_enable();
            }
            Some(next) => {
                self.switch_to_next(next);
            }
        }

        // We don't call schedule before because we need to release that lock
        // after elect_next() runs to remove all references to the blocked
        // thread in the current CPU
        self.schedule(true);
    }

    pub unsafe fn terminate(&mut self) -> ! {
        if let Some(ref mut t) = self.running {
            t.get_mut().state = State::Terminated;

            self.cleanup_queue.lock().enqueue(Box::from_raw(t.get_mut()));
        }

        // Don't cleanup terminated threads as this would mean that we release
        // the current thread, which would lead to bad things if done while
        // its still running.
        self.schedule(false);

        unreachable!()
    }
}
<|endoftext|>


<commit_before>use regex::RegexBuilder;

/// Enforce that a string must match a given regex
/// Flags is a string containing all the flags that affect the regex.
/// i = ignore case
/// m = multiline
/// s = dot matches new line
/// U = swaps greedy modifiers (a* is lazy, a*? is greedy)
/// u = disable unicode support
/// x = ignore whitespace
/// Note that this doesn't test the length of the match; meaning
/// r"a..." will match "a12345678" as a match is present.
/// To force the value to match the regex exactly, use ^ and $
/// e.g: r"^a...$" will not match "a12345678"
pub fn regex(regex: &'static str, flags: &'static str) -> Box<Fn(&String) -> ::ValidatorResult> {
    let regex = RegexBuilder::new(regex)
        .case_insensitive(flags.contains("i"))
        .multi_line(flags.contains("m"))
        .dot_matches_new_line(flags.contains("s"))
        .swap_greed(flags.contains("U"))
        .unicode(!flags.contains("u"))
        .ignore_whitespace(flags.contains("x"))
        .build()
        .expect("Invalid regex in validator!");
    Box::new(move |s: &String| {
        if regex.is_match(s) {
            Ok(())
        } else {
            Err(::Invalid {
                msg: "Must match regex '/%1/%2'.".to_string(),
                // TODO: sucks to clone this, it could probably be a &'static str
                args: vec![regex.as_str().to_owned(), flags.to_owned()],
				human_readable: format!("Must match regex '/{}/{}'", regex.as_str().clone(), flags)
            })
        }
    })
}

#[cfg(test)]
mod tests
{
    use super::*;
    // regex
    #[test]
    pub fn regex_valid() {
        assert!(regex(r"^a...$", "")(&"a123".to_owned()).is_ok());
        assert!(regex(r"^a...$", "")(&"abcd".to_owned()).is_ok());
        assert!(regex(r"^a...$", "")(&"a!?£".to_owned()).is_ok());
    }
    #[test]
    pub fn regex_invalid() {
        assert!(regex(r"^a...$", "")(&"a".to_owned()).is_err());
        assert!(regex(r"^a...$", "")(&"abc".to_owned()).is_err());
        assert!(regex(r"^a...$", "")(&"a123457".to_owned()).is_err());
        assert!(regex(r"^a...$", "")(&"b".to_owned()).is_err());
        assert!(regex(r"^a...$", "")(&"baaa".to_owned()).is_err());
    }
}<commit_msg>Added URL & Email regex-based validators (#10 #9)<commit_after>use regex::RegexBuilder;

/// Enforce that a string must match a given regex
/// Flags is a string containing all the flags that affect the regex.
/// i = ignore case
/// m = multiline
/// s = dot matches new line
/// U = swaps greedy modifiers (a* is lazy, a*? is greedy)
/// u = disable unicode support
/// x = ignore whitespace
/// Note that this doesn't test the length of the match; meaning
/// r"a..." will match "a12345678" as a match is present.
/// To force the value to match the regex exactly, use ^ and $
/// e.g: r"^a...$" will not match "a12345678"
pub fn regex(regex: &'static str, flags: &'static str) -> Box<Fn(&String) -> ::ValidatorResult> {
    let regex = RegexBuilder::new(regex)
        .case_insensitive(flags.contains("i"))
        .multi_line(flags.contains("m"))
        .dot_matches_new_line(flags.contains("s"))
        .swap_greed(flags.contains("U"))
        .unicode(!flags.contains("u"))
        .ignore_whitespace(flags.contains("x"))
        .build()
        .expect("Invalid regex in validator!");
    Box::new(move |s: &String| {
        if regex.is_match(s) {
            Ok(())
        } else {
            Err(::Invalid {
                msg: "Must match regex '/%1/%2'.".to_string(),
                // TODO: sucks to clone this, it could probably be a &'static str
                args: vec![regex.as_str().to_owned(), flags.to_owned()],
				human_readable: format!("Must match regex '/{}/{}'", regex.as_str().clone(), flags)
            })
        }
    })
}

/// Convenience function for validating email addresses
pub fn email() -> Box<Fn(&String) -> ::ValidatorResult> {
    regex(r"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$", "i")
}

/// Convenience function for validating URLs.
pub fn url() -> Box<Fn(&String) -> ::ValidatorResult> {
    regex(r"^(https?://)?([A-Z]+\.)+[A-Z]{2,}(/[A-Z%0-9_]*)*?(/[A-Z%0-9_]*\.[A-Z%0-9_]*)?([#?][A-Z%0-9=&]*){0,2}$", "i")
}

#[cfg(test)]
mod tests
{
    use super::*;
    // regex
    #[test]
    pub fn regex_valid() {
        assert!(regex(r"^a...$", "")(&"a123".to_owned()).is_ok());
        assert!(regex(r"^a...$", "")(&"abcd".to_owned()).is_ok());
        assert!(regex(r"^a...$", "")(&"a!?£".to_owned()).is_ok());
    }
    #[test]
    pub fn regex_invalid() {
        assert!(regex(r"^a...$", "")(&"a".to_owned()).is_err());
        assert!(regex(r"^a...$", "")(&"abc".to_owned()).is_err());
        assert!(regex(r"^a...$", "")(&"a123457".to_owned()).is_err());
        assert!(regex(r"^a...$", "")(&"b".to_owned()).is_err());
        assert!(regex(r"^a...$", "")(&"baaa".to_owned()).is_err());
    }

    // email
    #[test]
    pub fn email_valid() {
        assert!(email()(&"asdf@asdf.com".to_owned()).is_ok());
        assert!(email()(&"amazing.email.address@super.amazing.site.tk".to_owned()).is_ok());
        assert!(email()(&"prick@legal.google".to_owned()).is_ok());
    }

    #[test]
    pub fn email_invalid() {
        assert!(email()(&"you tried.".to_owned()).is_err());
        assert!(email()(&"so close @super.amazing.site.tk".to_owned()).is_err());
        assert!(email()(&"prick@legal.g".to_owned()).is_err());
        assert!(email()(&"prick@important legal documents!.google".to_owned()).is_err());
    }

    // url
    #[test]
    pub fn url_valid() {
        assert!(url()(&"http://github.com".to_owned()).is_ok());
        assert!(url()(&"https://github.com".to_owned()).is_ok());
        assert!(url()(&"www.github.com".to_owned()).is_ok());
        assert!(url()(&"github.com".to_owned()).is_ok());
        assert!(url()(&"sub.domains.github.fr".to_owned()).is_ok());
        assert!(url()(&"neet.cool".to_owned()).is_ok());

        assert!(url()(&"github.com/org/repo".to_owned()).is_ok());
        assert!(url()(&"github.com/org/repo/logo.jpeg".to_owned()).is_ok());
        assert!(url()(&"github.com/org/repo/README#Usage".to_owned()).is_ok());
        assert!(url()(&"github.com/org/repo?folder=src&sort=desc".to_owned()).is_ok());
    }

    #[test]
    pub fn url_invalid() {
        assert!(url()(&"ftp://super.amazing.site".to_owned()).is_err());
        assert!(url()(&"http/super.amazing.site".to_owned()).is_err());
        assert!(url()(&"my awesome site!!.com".to_owned()).is_err());
        assert!(url()(&"nonon.o".to_owned()).is_err());
        assert!(url()(&"github.com/invalid file".to_owned()).is_err());
        assert!(url()(&"github.com/file? bad query params".to_owned()).is_err());
        assert!(url()(&"github.com/file# bad jump thing".to_owned()).is_err());
    }
}<|endoftext|>


<commit_before>use LalafellBot;
use config::Listener;
use listeners::ReceivesEvents;
use discord::model::{Event, Channel, ReactionEmoji};

use xivdb::error::*;

use serde_json;

use std::sync::Arc;

pub struct ReactionAuthorize {
  bot: Arc<LalafellBot>,
  config: ReactionAuthorizeConfig
}

impl ReactionAuthorize {
  pub fn new(bot: Arc<LalafellBot>, listener: &Listener) -> Result<ReactionAuthorize> {
    let config = match listener.config {
      Some(ref c) => serde_json::from_value(c.clone()).chain_err(|| "could not parse reaction_authorize configuration")?,
      None => return Err("missing configuration for reaction_authorize listener".into())
    };
    Ok(ReactionAuthorize {
      bot: bot,
      config: config
    })
  }
}

impl ReceivesEvents for ReactionAuthorize {
  fn receive(&self, event: &Event) {
    let (added, reaction) = match *event {
      Event::ReactionAdd(ref reaction) => (true, reaction),
      Event::ReactionRemove(ref reaction) => (false, reaction),
      _ => return
    };
    let channel = match self.bot.discord.get_channel(reaction.channel_id) {
      Ok(Channel::Public(c)) => c,
      Ok(_) => {
        warn!("invalid channel: {}", reaction.channel_id.0);
        return;
      },
      Err(e) => {
        warn!("couldn't get channel: {}", e);
        return;
      }
    };
    match reaction.emoji {
      ReactionEmoji::Unicode(ref emoji) if *emoji == self.config.emoji => {},
      _ => return
    }
    let roles = match self.bot.discord.get_roles(channel.server_id) {
      Ok(r) => r,
      Err(e) => {
        warn!("couldn't get roles: {}", e);
        return;
      }
    };
    let role = match roles.iter().filter(|r| r.name == self.config.role).next() {
      Some(r) => r,
      None => {
        warn!("couldn't find role for name \"{}\"", self.config.role);
        return;
      }
    };
    let result = if added {
      self.bot.discord.add_user_to_role(channel.server_id, reaction.user_id, role.id)
    } else {
      self.bot.discord.remove_user_from_role(channel.server_id, reaction.user_id, role.id)
    };
    if let Err(e) = result {
      warn!("could not change role: {}", e);
    }
  }
}

#[derive(Debug, Deserialize)]
pub struct ReactionAuthorizeConfig {
  message: u64,
  emoji: String,
  role: String
}
<commit_msg>refactor: use find instead of filter, next<commit_after>use LalafellBot;
use config::Listener;
use listeners::ReceivesEvents;
use discord::model::{Event, Channel, ReactionEmoji};

use xivdb::error::*;

use serde_json;

use std::sync::Arc;

pub struct ReactionAuthorize {
  bot: Arc<LalafellBot>,
  config: ReactionAuthorizeConfig
}

impl ReactionAuthorize {
  pub fn new(bot: Arc<LalafellBot>, listener: &Listener) -> Result<ReactionAuthorize> {
    let config = match listener.config {
      Some(ref c) => serde_json::from_value(c.clone()).chain_err(|| "could not parse reaction_authorize configuration")?,
      None => return Err("missing configuration for reaction_authorize listener".into())
    };
    Ok(ReactionAuthorize {
      bot: bot,
      config: config
    })
  }
}

impl ReceivesEvents for ReactionAuthorize {
  fn receive(&self, event: &Event) {
    let (added, reaction) = match *event {
      Event::ReactionAdd(ref reaction) => (true, reaction),
      Event::ReactionRemove(ref reaction) => (false, reaction),
      _ => return
    };
    let channel = match self.bot.discord.get_channel(reaction.channel_id) {
      Ok(Channel::Public(c)) => c,
      Ok(_) => {
        warn!("invalid channel: {}", reaction.channel_id.0);
        return;
      },
      Err(e) => {
        warn!("couldn't get channel: {}", e);
        return;
      }
    };
    match reaction.emoji {
      ReactionEmoji::Unicode(ref emoji) if *emoji == self.config.emoji => {},
      _ => return
    }
    let roles = match self.bot.discord.get_roles(channel.server_id) {
      Ok(r) => r,
      Err(e) => {
        warn!("couldn't get roles: {}", e);
        return;
      }
    };
    let role = match roles.iter().find(|r| r.name == self.config.role) {
      Some(r) => r,
      None => {
        warn!("couldn't find role for name \"{}\"", self.config.role);
        return;
      }
    };
    let result = if added {
      self.bot.discord.add_user_to_role(channel.server_id, reaction.user_id, role.id)
    } else {
      self.bot.discord.remove_user_from_role(channel.server_id, reaction.user_id, role.id)
    };
    if let Err(e) = result {
      warn!("could not change role: {}", e);
    }
  }
}

#[derive(Debug, Deserialize)]
pub struct ReactionAuthorizeConfig {
  message: u64,
  emoji: String,
  role: String
}
<|endoftext|>


<commit_before>// See LICENSE file for copyright and license details.

use core::types::{MInt};
use visualizer::types::{WorldPos, MFloat};
use visualizer::mesh::MeshId;
use std::collections::hashmap::HashMap;

#[deriving(PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct NodeId{pub id: MInt}

pub struct SceneNode {
    pub pos: WorldPos,
    pub rot: MFloat,
    pub mesh_id: Option<MeshId>, // TODO: make priv
    pub children: Vec<SceneNode>,
}

pub struct Scene {
    pub nodes: HashMap<NodeId, SceneNode>,
}

impl Scene {
    pub fn new() -> Scene {
        Scene {
            nodes: HashMap::new(),
        }
    }
}

// vim: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab:
<commit_msg>SceneNode: Removed todo-comment<commit_after>// See LICENSE file for copyright and license details.

use core::types::{MInt};
use visualizer::types::{WorldPos, MFloat};
use visualizer::mesh::MeshId;
use std::collections::hashmap::HashMap;

#[deriving(PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct NodeId{pub id: MInt}

pub struct SceneNode {
    pub pos: WorldPos,
    pub rot: MFloat,
    pub mesh_id: Option<MeshId>,
    pub children: Vec<SceneNode>,
}

pub struct Scene {
    pub nodes: HashMap<NodeId, SceneNode>,
}

impl Scene {
    pub fn new() -> Scene {
        Scene {
            nodes: HashMap::new(),
        }
    }
}

// vim: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab:
<|endoftext|>