├── LICENSE.txt ├── README.md ├── expanded ├── .DS_Store ├── ai.rb ├── board.rb ├── data.rb ├── pieces.rb ├── play.rb └── test │ ├── .DS_Store │ ├── setup.rb │ ├── test_ai.rb │ ├── test_all.rb │ ├── test_board.rb │ ├── test_data.rb │ ├── test_pieces.rb │ └── test_play.rb └── lambda_chess.rb /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Colin Fulton 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or 5 | without modification, are permitted provided that the 6 | following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above 9 | copyright notice, this list of conditions and the 10 | following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the 13 | above copyright notice, this list of conditions and 14 | the following disclaimer in the documentation and/or 15 | other materials provided with the distribution. 16 | 17 | 3. Neither the name of the copyright holder nor the 18 | names of its contributors may be used to endorse or 19 | promote products derived from this software without 20 | specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 23 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 24 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 25 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 29 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 33 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 34 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lambda Chess! 2 | A playable game of chess and an AI opponent written in untyped lambda calculus... and it's a quine. 3 | 4 | [Here is a demo](https://youtu.be/DC-bjR6WeaM?t=39m7s) of it in action from a talk about how to write quines and other useless programs. 5 | 6 | **Note:** the bottom part of the code—which is written in "normal" `Ruby`—just handles re-rendering the program and IO. The actual rules of chess and the AI are entirely done in untyped lambda syntax using `->` lambdas. Also, the expanded version in this repo is a little out of date and contains some bugs which were fixed later. 7 | 8 | 1. First look at the program: 9 | 10 | `cat lambda_chess.rb` 11 | 12 | Make your text reeeeeeal small (at least 195 characters per line) so you can see what the code looks like. 13 | 14 | **Hint:** you may want to turn off anti-aliasing and decrease the spacing between lines to make it look a little better. 15 | 16 | 2. Now run the program: 17 | 18 | `ruby lambda_chess.rb` 19 | 20 | Make the text reeeeeeal small again to see how the program changed. 21 | 22 | 3. To prove it is a quine, run: 23 | 24 | `ruby lambda_chess.rb | ruby` 25 | 26 | OR: 27 | 28 | `ruby lambda_chess.rb | ruby | ruby` 29 | 30 | OR: 31 | 32 | `ruby lambda_chess.rb | ruby | ruby | ruby` 33 | 34 | etc. 35 | 36 | 4. You play as white (on the bottom). To play a move, pass your move in as a command line argument. Use the format `b1:c3`, or `b1-c3`, or `b1 c3`, etc (the argument parser is very forgiving). The first position (`b1` in this example) is the position you are moving from, while the second position (`c3` in this example) is the position you are moving to. 37 | 38 | `ruby lambda_chess.rb b1:c3` 39 | 40 | 5. To view your move AND to save the resulting program to a file you can use pipe the result into `tee`: 41 | 42 | `ruby lambda_chess.rb b1:c3 | tee move_1.rb` 43 | 44 | **Note:** if you run the same program multiple times with the same move, the black AI may respond differently each time. 45 | 46 | 6. To play another move, run the program the last move generated, passing in your next move: 47 | 48 | `ruby move_1.rb d2:d4 | tee move_2.rb` 49 | 50 | Try playing a full game of chess! Try playing invalid moves! Castling is done by giving the from and to positions for the king you are castling, such as `e1:c1`. Pawns will automatically be promoted to queens, unless you pass in what piece you want to promote to such as `e7:e8 k` to promote to a knight, `b` for bishop, etc. 51 | 52 | For chess nerds out there: yes, en passant captures are valid, and the AI may perform them. 53 | 54 | ## Find a bug? 55 | 56 | Report it! I will fix it. It may take a while... but I'll do it. :-) 57 | -------------------------------------------------------------------------------- /expanded/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/front-endian/lambda_chess/7dd23c1544e559e6efc4ce12362a8520d3238034/expanded/.DS_Store -------------------------------------------------------------------------------- /expanded/ai.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Colin Fulton 2 | # All rights reserved. 3 | # 4 | # This software may be modified and distributed under the 5 | # terms of the three-clause BSD license. See LICENSE.txt 6 | 7 | # AI Functions 8 | 9 | MAX_PIECE_TOTAL = ADD[ 10 | MULTIPLY[PAWN_VALUE, EIGHT], 11 | ADD[ 12 | MULTIPLY[KNIGHT_VALUE, TWO], 13 | ADD[ 14 | MULTIPLY[BISHOP_VALUE, TWO], 15 | ADD[ 16 | MULTIPLY[ROOK_VALUE, TWO], 17 | ADD[ 18 | QUEEN_VALUE, 19 | KING_VALUE 20 | ] 21 | ] 22 | ] 23 | ] 24 | ] 25 | 26 | SCORE = ->(board, last_moved) { 27 | BOARD_REDUCE[ 28 | board, 29 | ->(memo, piece, position) { 30 | ADD[ 31 | IS_BLACK[piece][ 32 | ADD, 33 | SUBTRACT 34 | ][ 35 | memo, 36 | GET_VALUE[piece] 37 | ], 38 | IF[IS_WHITE[piece]][ 39 | -> { 40 | IF[IS_EQUAL[KING_VALUE, GET_VALUE[piece]]][ 41 | -> { 42 | ISNT_INVALID[ 43 | GET_RULE[GET_POSITION[board, last_moved]][ 44 | CREATE_STATE[ 45 | last_moved, 46 | position, 47 | last_moved, 48 | last_moved, 49 | board, 50 | ZERO, 51 | BLACK_QUEEN 52 | ] 53 | ] 54 | ][ 55 | MAX_PIECE_TOTAL, 56 | ZERO 57 | ] 58 | }, 59 | -> { ZERO } 60 | ] 61 | }, 62 | -> { ZERO } 63 | ] 64 | ] 65 | }, 66 | MAX_PIECE_TOTAL 67 | ] 68 | } 69 | 70 | FROM_TO_REDUCE = ->(possible_froms, possible_tos, func, initial) { 71 | VECTOR_REDUCE[ 72 | possible_froms, 73 | ->(memo, from_position) { 74 | VECTOR_REDUCE[ 75 | possible_tos, 76 | ->(inner_memo, to_position) { 77 | func[inner_memo, from_position, to_position] 78 | }, 79 | memo 80 | ] 81 | }, 82 | initial 83 | ] 84 | } 85 | 86 | POSSIBLE_MOVES = ->(state, color, possible_tos) { 87 | ->(board) { 88 | FROM_TO_REDUCE[ 89 | POSITION_SELECT[board, color[IS_BLACK, IS_WHITE]], 90 | possible_tos, 91 | ->(possible_moves, from, to) { 92 | ADVANCE_STATE[ 93 | CREATE_STATE[ 94 | from, 95 | to, 96 | GET_LAST_FROM[state], 97 | GET_LAST_TO[state], 98 | board, 99 | ZERO, 100 | color[BLACK_QUEEN, WHITE_QUEEN] 101 | ] 102 | ][ 103 | ->(new_state) { VECTOR_APPEND[possible_moves, new_state] }, 104 | -> { possible_moves } 105 | ] 106 | }, 107 | EMPTY_VECTOR 108 | ] 109 | }[ 110 | # "board" 111 | GET_BOARD[state] 112 | ] 113 | } 114 | 115 | POSSIBLE_BLACK_RESPONSES = ->(state) { 116 | POSSIBLE_MOVES[ 117 | state, 118 | BLACK, 119 | POSITION_SELECT[ 120 | GET_BOARD[state], 121 | ->(piece) { NOT[IS_BLACK[piece]] } 122 | ] 123 | ] 124 | } 125 | 126 | COUNTER_RESPONSES = ->(reponses, color) { 127 | VECTOR_REDUCE[ 128 | reponses, 129 | ->(memo, old_state) { 130 | # Find the highest scoring response 131 | VECTOR_APPEND[ 132 | memo, 133 | VECTOR_REDUCE[ 134 | # Find all possble responses 135 | POSSIBLE_MOVES[ 136 | old_state, 137 | color, 138 | VECTOR_APPEND[EMPTY_VECTOR, GET_TO[old_state]] 139 | ], 140 | ->(memo, new_state) { 141 | IS_GREATER_OR_EQUAL[GET_SCORE[new_state], GET_SCORE[memo]][ 142 | color[UPDATE_ALL_BUT_FROM_TO_PROMOTION[memo, new_state], memo], 143 | color[memo, UPDATE_ALL_BUT_FROM_TO_PROMOTION[memo, new_state]] 144 | ] 145 | }, 146 | old_state 147 | ] 148 | ] 149 | }, 150 | EMPTY_VECTOR 151 | ] 152 | } 153 | 154 | BEST_SET_OF_STATES = ->(states) { 155 | VECTOR_REDUCE[ 156 | states, 157 | ->(memo, state) { 158 | IF[IS_ZERO[VECTOR_SIZE[memo]]][ 159 | -> { VECTOR_APPEND[memo, state] }, 160 | -> { 161 | IS_EQUAL[GET_SCORE[state], GET_SCORE[VECTOR_FIRST[memo]]][ 162 | VECTOR_APPEND[memo, state], 163 | IS_GREATER_OR_EQUAL[ 164 | GET_SCORE[state], 165 | GET_SCORE[VECTOR_FIRST[memo]] 166 | ][ 167 | VECTOR_APPEND[EMPTY_VECTOR, state], 168 | memo 169 | ] 170 | ] 171 | } 172 | ] 173 | }, 174 | EMPTY_VECTOR 175 | ] 176 | } 177 | 178 | BEST_MOVE = ->(states, seed) { 179 | IF[IS_ZERO[VECTOR_SIZE[states]]][ 180 | -> { PAIR[SECOND, ZERO] }, 181 | -> { 182 | ->(best_vector) { 183 | PAIR[ 184 | FIRST, 185 | NTH[ 186 | VECTOR_LIST[best_vector], 187 | MODULUS[seed, VECTOR_SIZE[best_vector]] 188 | ] 189 | ] 190 | }[ 191 | # "best_vector" 192 | BEST_SET_OF_STATES[states] 193 | ] 194 | } 195 | ] 196 | } 197 | 198 | BLACK_AI = ->(state, seed) { 199 | ->(result) { 200 | IF[LEFT[result]][ 201 | -> { 202 | ADVANCE_STATE[UPDATE_ALL_BUT_FROM_TO_PROMOTION[RIGHT[result], state]][ 203 | ->(new_state) { PAIR[FIRST, new_state] }, 204 | -> { PAIR[SECOND, ZERO] } 205 | ] 206 | }, 207 | -> { result } 208 | ] 209 | }[ 210 | # "result" 211 | BEST_MOVE[ 212 | COUNTER_RESPONSES[ 213 | POSSIBLE_BLACK_RESPONSES[state], 214 | WHITE 215 | ], 216 | seed 217 | ] 218 | ] 219 | } 220 | -------------------------------------------------------------------------------- /expanded/board.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Colin Fulton 2 | # All rights reserved. 3 | # 4 | # This software may be modified and distributed under the 5 | # terms of the three-clause BSD license. See LICENSE.txt 6 | 7 | # Board Functions 8 | 9 | BOARD_MAP = ->(board, func) { 10 | LIST_MAP[ 11 | board, 12 | SIDE_LENGTH, 13 | ->(row, y) { 14 | LIST_MAP[ 15 | row, 16 | SIDE_LENGTH, 17 | ->(piece, x) { 18 | func[piece, PAIR[x, y]] 19 | } 20 | ] 21 | } 22 | ] 23 | } 24 | 25 | BOARD_REDUCE = ->(board, func, initial) { 26 | LIST_REDUCE[ 27 | board, 28 | SIDE_LENGTH, 29 | ->(memo, row, y) { 30 | LIST_REDUCE[ 31 | row, 32 | SIDE_LENGTH, 33 | ->(memo, piece, x) { 34 | func[memo, piece, PAIR[x, y]] 35 | }, 36 | memo 37 | ] 38 | }, 39 | initial 40 | ] 41 | } 42 | 43 | SAME_POSITION = ->(a, b) { 44 | AND[ 45 | IS_EQUAL[LEFT[a], LEFT[b]], 46 | IS_EQUAL[RIGHT[a], RIGHT[b]] 47 | ] 48 | } 49 | 50 | GET_POSITION = ->(board, position) { 51 | NTH[NTH[board, RIGHT[position]], LEFT[position]] 52 | } 53 | 54 | CHANGE_FUNC = ->(from, to, coordinate) { 55 | ->(a, b) { 56 | IS_GREATER_OR_EQUAL[a, b][ 57 | IS_EQUAL[a, b][ 58 | IDENTITY, 59 | DECREMENT 60 | ], 61 | INCREMENT 62 | ] 63 | }[ 64 | coordinate[from], 65 | coordinate[to] 66 | ] 67 | } 68 | 69 | FREE_PATH = ->(board, from, to, alter_length) { 70 | ->(delta_x, delta_y) { 71 | IF[ 72 | OR[ 73 | OR[ 74 | IS_ZERO[delta_x], 75 | IS_ZERO[delta_y], 76 | ], 77 | IS_EQUAL[delta_x, delta_y] 78 | ] 79 | ][ 80 | -> { 81 | RIGHT[ 82 | # Get the number of positions that have to be checked 83 | alter_length[ 84 | IS_ZERO[delta_x][ 85 | DELTA[from, to, RIGHT], 86 | delta_x 87 | ] 88 | ][ 89 | # For each position inbetween.... 90 | ->(memo) { 91 | ->(new_postion) { 92 | PAIR[ 93 | new_postion, 94 | # If a filled position hasn't been found, check for a piece 95 | RIGHT[memo][ 96 | IS_EMPTY[GET_POSITION[board, new_postion]], 97 | SECOND 98 | ] 99 | ] 100 | }[ 101 | # Calculate next postion to check 102 | PAIR[ 103 | CHANGE_FUNC[from, to, LEFT][LEFT[LEFT[memo]]], 104 | CHANGE_FUNC[from, to, RIGHT][RIGHT[LEFT[memo]]] 105 | ] 106 | ] 107 | }, 108 | PAIR[from, FIRST] 109 | ] 110 | ] 111 | }, 112 | -> { SECOND } 113 | ] 114 | }[ 115 | # "delta_x" 116 | DELTA[from, to, LEFT], 117 | # "delta_y" 118 | DELTA[from, to, RIGHT] 119 | ] 120 | } 121 | 122 | POSITION_SELECT = ->(board, condition) { 123 | BOARD_REDUCE[ 124 | board, 125 | ->(memo, piece, position) { 126 | condition[piece][ 127 | VECTOR_APPEND[memo, position], 128 | memo 129 | ] 130 | }, 131 | EMPTY_VECTOR 132 | ] 133 | } 134 | 135 | # Move Functions 136 | 137 | NORMAL_MOVE = ->(board, from, to, new_piece) { 138 | CHANGE_MOVE[board, from, to, GET_POSITION[board, from]] 139 | } 140 | 141 | CASTLING_MOVE = ->(board, from, to, new_piece) { 142 | ->(is_moving_left) { 143 | NORMAL_MOVE[ 144 | NORMAL_MOVE[board, from, to, new_piece], 145 | # Rook positions 146 | PAIR[is_moving_left[ZERO, SEVEN], RIGHT[from]], 147 | PAIR[is_moving_left[THREE, FIVE], RIGHT[from]], 148 | new_piece 149 | ] 150 | }[ 151 | # "is_moving_left" 152 | IS_GREATER_OR_EQUAL[LEFT[from], LEFT[to]] 153 | ] 154 | } 155 | 156 | CHANGE_MOVE = ->(board, from, to, new_piece) { 157 | BOARD_MAP[ 158 | board, 159 | ->(old_piece, position) { 160 | IF[SAME_POSITION[position, to]][ 161 | -> { TO_MOVED_PIECE[new_piece] }, 162 | -> { SAME_POSITION[position, from][EMPTY_SPACE, old_piece] } 163 | ] 164 | } 165 | ] 166 | } 167 | 168 | EN_PASSANT_MOVE = ->(board, from, to, new_piece) { 169 | ->(captured) { 170 | CHANGE_MOVE[ 171 | NORMAL_MOVE[board, from, to, new_piece], 172 | captured, 173 | captured, 174 | EMPTY_SPACE 175 | ] 176 | }[ 177 | # "captured" 178 | PAIR[LEFT[to], RIGHT[from]] 179 | ] 180 | } 181 | 182 | # Initial Board 183 | 184 | P = PAIR 185 | 186 | INITIAL_BOARD = 187 | P[P[BLACK_ROOK, P[BLACK_KNIGHT, P[BLACK_BISHOP, P[BLACK_QUEEN, P[BLACK_KING, P[BLACK_BISHOP, P[BLACK_KNIGHT, P[BLACK_ROOK, ZERO]]]]]]]], 188 | P[P[BLACK_PAWN, P[BLACK_PAWN, P[BLACK_PAWN, P[BLACK_PAWN, P[BLACK_PAWN, P[BLACK_PAWN, P[BLACK_PAWN, P[BLACK_PAWN, ZERO]]]]]]]], 189 | P[P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, ZERO]]]]]]]], 190 | P[P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, ZERO]]]]]]]], 191 | P[P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, ZERO]]]]]]]], 192 | P[P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, P[EMPTY_SPACE, ZERO]]]]]]]], 193 | P[P[WHITE_PAWN, P[WHITE_PAWN, P[WHITE_PAWN, P[WHITE_PAWN, P[WHITE_PAWN, P[WHITE_PAWN, P[WHITE_PAWN, P[WHITE_PAWN, ZERO]]]]]]]], 194 | P[P[WHITE_ROOK, P[WHITE_KNIGHT, P[WHITE_BISHOP, P[WHITE_QUEEN, P[WHITE_KING, P[WHITE_BISHOP, P[WHITE_KNIGHT, P[WHITE_ROOK, ZERO]]]]]]]], 195 | ZERO]]]]]]]] 196 | -------------------------------------------------------------------------------- /expanded/data.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Colin Fulton 2 | # All rights reserved. 3 | # 4 | # This software may be modified and distributed under the 5 | # terms of the three-clause BSD license. See LICENSE.txt 6 | 7 | # Choice Functions 8 | 9 | FIRST = ->(first, second) { first } 10 | SECOND = ->(first, second) { second } 11 | 12 | AND = ->(a, b) { 13 | ->(first, second) { 14 | a[b[first, second], second] 15 | } 16 | } 17 | 18 | OR = ->(a, b) { 19 | ->(first, second) { 20 | a[first, b[first, second]] 21 | } 22 | } 23 | 24 | NOT = ->(choice) { 25 | ->(first, second) { 26 | choice[second, first] 27 | } 28 | } 29 | 30 | FIVE_CONDITIONS_MET = ->(cond_1, cond_2, cond_3, cond_4, cond_5) { 31 | ->(first, second) { 32 | cond_1[cond_2[cond_3[cond_4[cond_5[ 33 | first, 34 | second], 35 | second], 36 | second], 37 | second], 38 | second] 39 | } 40 | } 41 | 42 | IF = ->(condition) { 43 | ->(first, second) { 44 | condition[first, second][] 45 | } 46 | } 47 | 48 | ALWAYS_FIRST = ->(condition) { FIRST } 49 | 50 | # Math Functions 51 | 52 | IDENTITY = ->(x) { x } 53 | 54 | INCREMENT = ->(a) { ADD[ONE, a] } 55 | 56 | ADD = ->(a, b) { 57 | ->(func, zero) { 58 | b[func, a[func, zero]] 59 | } 60 | } 61 | 62 | DECREMENT = ->(a) { 63 | RIGHT[ 64 | a[ 65 | ->(memo) { 66 | PAIR[ 67 | INCREMENT[LEFT[memo]], 68 | LEFT[memo] 69 | ] 70 | }, 71 | PAIR[ZERO, ZERO] 72 | ] 73 | ] 74 | } 75 | 76 | SUBTRACT = ->(a, b) { 77 | ->(func, zero) { 78 | b[DECREMENT, a][func, zero] 79 | } 80 | } 81 | 82 | MULTIPLY = ->(a, b) { 83 | ->(func, zero) { 84 | a[ 85 | ->(value) { b[func, value] }, 86 | zero 87 | ] 88 | } 89 | } 90 | 91 | DELTA = ->(position_1, position_2, coordinate) { 92 | ->(a, b) { 93 | IS_GREATER_OR_EQUAL[a, b][ 94 | SUBTRACT[a, b], 95 | SUBTRACT[b, a] 96 | ] 97 | }[ 98 | coordinate[position_1], 99 | coordinate[position_2] 100 | ] 101 | } 102 | 103 | MODULUS = ->(a, b) { 104 | RIGHT[ 105 | a[ 106 | ->(memo) { 107 | IS_GREATER_OR_EQUAL[LEFT[memo], b][ 108 | PAIR[ 109 | SUBTRACT[LEFT[memo], b], 110 | ZERO 111 | ], 112 | PAIR[ 113 | LEFT[memo], 114 | LEFT[memo] 115 | ] 116 | ] 117 | }, 118 | PAIR[a, ZERO] 119 | ] 120 | ] 121 | } 122 | 123 | # Numbers 124 | 125 | ZERO = ->(succ, zero) { zero } 126 | ONE = ->(succ, zero) { succ[zero] } 127 | TWO = ->(succ, zero) { succ[succ[zero]] } 128 | THREE = ->(succ, zero) { succ[succ[succ[zero]]] } 129 | FOUR = ->(succ, zero) { succ[succ[succ[succ[zero]]]] } 130 | FIVE = ADD[TWO, THREE] 131 | SIX = MULTIPLY[TWO, THREE] 132 | SEVEN = ADD[THREE, FOUR] 133 | EIGHT = MULTIPLY[TWO, FOUR] 134 | 135 | # Pair Functions 136 | 137 | PAIR = ->(left, right) { 138 | ->(select) { select[left, right] } 139 | } 140 | 141 | LEFT = ->(pair) { pair[FIRST] } 142 | RIGHT = ->(pair) { pair[SECOND] } 143 | 144 | # Lists Functions 145 | 146 | NTH = ->(list, index) { LEFT[index[RIGHT, list]] } 147 | 148 | LIST_MAP = ->(list, size, func) { 149 | LEFT[ 150 | size[ 151 | ->(memo) { 152 | PAIR[ 153 | PAIR[ 154 | func[ 155 | NTH[list, RIGHT[memo]], 156 | RIGHT[memo] 157 | ], 158 | LEFT[memo] 159 | ], 160 | DECREMENT[RIGHT[memo]] 161 | ] 162 | }, 163 | PAIR[ZERO, DECREMENT[size]] 164 | ] 165 | ] 166 | } 167 | 168 | LIST_REDUCE = ->(list, size, func, initial) { 169 | LEFT[ 170 | size[ 171 | ->(memo) { 172 | PAIR[ 173 | func[ 174 | # previous 175 | LEFT[memo], 176 | # next 177 | NTH[list, RIGHT[memo]], 178 | # index 179 | RIGHT[memo], 180 | ], 181 | INCREMENT[RIGHT[memo]] 182 | ] 183 | }, 184 | PAIR[initial, ZERO] 185 | ] 186 | ] 187 | } 188 | 189 | # Vector Functions 190 | 191 | EMPTY_VECTOR = PAIR[ZERO, ZERO] 192 | 193 | VECTOR_SIZE = RIGHT 194 | VECTOR_LIST = LEFT 195 | 196 | VECTOR_APPEND = ->(vector, item) { 197 | PAIR[ 198 | PAIR[item, VECTOR_LIST[vector]], 199 | INCREMENT[VECTOR_SIZE[vector]] 200 | ] 201 | } 202 | 203 | VECTOR_FIRST = ->(vector) { 204 | NTH[VECTOR_LIST[vector], ZERO] 205 | } 206 | 207 | VECTOR_MAP = ->(vector, func) { 208 | PAIR[ 209 | LIST_MAP[ 210 | VECTOR_LIST[vector], 211 | VECTOR_SIZE[vector], 212 | ->(item, index) { func[item] } 213 | ], 214 | VECTOR_SIZE[vector] 215 | ] 216 | } 217 | 218 | VECTOR_REDUCE = ->(vector, func, initial) { 219 | LIST_REDUCE[ 220 | VECTOR_LIST[vector], 221 | VECTOR_SIZE[vector], 222 | ->(memo, item, index) { func[memo, item] }, 223 | initial 224 | ] 225 | } 226 | 227 | # Magic Numbers 228 | 229 | SIDE_LENGTH = EIGHT 230 | 231 | BLACK_PAWN_ROW = ONE 232 | WHITE_PAWN_ROW = SIX 233 | BLACK_HOME_ROW = ZERO 234 | WHITE_HOME_ROW = SEVEN 235 | KING_COLUMN = FOUR 236 | 237 | PAWN_VALUE = ONE 238 | KNIGHT_VALUE = TWO 239 | BISHOP_VALUE = THREE 240 | ROOK_VALUE = FOUR 241 | QUEEN_VALUE = FIVE 242 | KING_VALUE = SIX 243 | 244 | # Piece data 245 | 246 | GET_COLOR = ->(piece) { LEFT[LEFT[piece]] } 247 | GET_VALUE = ->(piece) { RIGHT[LEFT[piece]] } 248 | GET_OCCUPIED = ->(piece) { LEFT[RIGHT[piece]] } 249 | GET_MOVED = ->(piece) { RIGHT[RIGHT[piece]] } 250 | 251 | OCCUPIED = FIRST 252 | EMPTY = SECOND 253 | 254 | UNMOVED = SECOND 255 | MOVED = FIRST 256 | 257 | BLACK = FIRST 258 | WHITE = SECOND 259 | 260 | MAKE_PIECE = ->(color, value, occupied, moved) { 261 | PAIR[PAIR[color, value], PAIR[occupied, moved]] 262 | } 263 | 264 | INITIAL_PIECE = ->(color, value) { 265 | MAKE_PIECE[color, value, OCCUPIED, UNMOVED] 266 | } 267 | 268 | EMPTY_SPACE = MAKE_PIECE[BLACK, ZERO, EMPTY, UNMOVED] 269 | 270 | BLACK_PAWN = INITIAL_PIECE[BLACK, PAWN_VALUE] 271 | BLACK_KNIGHT = INITIAL_PIECE[BLACK, KNIGHT_VALUE] 272 | BLACK_BISHOP = INITIAL_PIECE[BLACK, BISHOP_VALUE] 273 | BLACK_ROOK = INITIAL_PIECE[BLACK, ROOK_VALUE] 274 | BLACK_QUEEN = INITIAL_PIECE[BLACK, QUEEN_VALUE] 275 | BLACK_KING = INITIAL_PIECE[BLACK, KING_VALUE] 276 | 277 | WHITE_PAWN = INITIAL_PIECE[WHITE, PAWN_VALUE] 278 | WHITE_ROOK = INITIAL_PIECE[WHITE, ROOK_VALUE] 279 | WHITE_KNIGHT = INITIAL_PIECE[WHITE, KNIGHT_VALUE] 280 | WHITE_BISHOP = INITIAL_PIECE[WHITE, BISHOP_VALUE] 281 | WHITE_QUEEN = INITIAL_PIECE[WHITE, QUEEN_VALUE] 282 | WHITE_KING = INITIAL_PIECE[WHITE, KING_VALUE] 283 | 284 | IS_EMPTY = ->(piece) { 285 | NOT[GET_OCCUPIED[piece]] 286 | } 287 | 288 | IS_BLACK = ->(piece) { 289 | COLOR_SWITCH[piece][FIRST, SECOND, SECOND] 290 | } 291 | 292 | IS_WHITE = ->(piece) { 293 | COLOR_SWITCH[piece][SECOND, FIRST, SECOND] 294 | } 295 | 296 | COLOR_SWITCH = ->(piece) { 297 | ->(black, white, empty) { 298 | GET_OCCUPIED[piece][ 299 | GET_COLOR[piece][ 300 | black, 301 | white 302 | ], 303 | empty 304 | ] 305 | } 306 | } 307 | 308 | TO_MOVED_PIECE = ->(piece) { 309 | MAKE_PIECE[GET_COLOR[piece], GET_VALUE[piece], GET_OCCUPIED[piece], MOVED] 310 | } 311 | 312 | IS_MOVED = ->(piece) { 313 | GET_MOVED[piece] 314 | } 315 | 316 | HAS_VALUE = ->(piece, value) { 317 | IS_EQUAL[value, GET_VALUE[piece]] 318 | } 319 | 320 | # Comparisons 321 | 322 | IS_ZERO = ->(number) { 323 | number[->(_) { SECOND }, FIRST] 324 | } 325 | 326 | IS_GREATER_OR_EQUAL = ->(a, b) { 327 | IS_ZERO[SUBTRACT[b, a]] 328 | } 329 | 330 | IS_EQUAL = ->(a, b) { 331 | IF[IS_GREATER_OR_EQUAL[a, b]][ 332 | -> { IS_GREATER_OR_EQUAL[b, a] }, 333 | -> { SECOND } 334 | ] 335 | } 336 | 337 | # Board State 338 | 339 | CREATE_STATE = ->(from, to, last_from, last_to, board, score, promotion) { 340 | PAIR[ 341 | PAIR[ 342 | PAIR[from, to], 343 | PAIR[board, score] 344 | ], 345 | PAIR[ 346 | promotion, 347 | PAIR[last_from, last_to] 348 | ] 349 | ] 350 | } 351 | 352 | GET_PROMOTION = ->(state) { 353 | LEFT[RIGHT[state]] 354 | } 355 | 356 | GET_LAST_FROM = ->(state) { 357 | LEFT[RIGHT[RIGHT[state]]] 358 | } 359 | 360 | GET_LAST_TO = ->(state) { 361 | RIGHT[RIGHT[RIGHT[state]]] 362 | } 363 | 364 | GET_FROM = ->(state) { 365 | LEFT[LEFT[LEFT[state]]] 366 | } 367 | 368 | GET_TO = ->(state) { 369 | RIGHT[LEFT[LEFT[state]]] 370 | } 371 | 372 | GET_BOARD = ->(state) { 373 | LEFT[RIGHT[LEFT[state]]] 374 | } 375 | 376 | GET_SCORE = ->(state) { 377 | RIGHT[RIGHT[LEFT[state]]] 378 | } 379 | 380 | UPDATE_ALL_BUT_FROM_TO_PROMOTION = ->(older, newer) { 381 | CREATE_STATE[ 382 | GET_FROM[older], 383 | GET_TO[older], 384 | GET_FROM[newer], 385 | GET_TO[newer], 386 | GET_BOARD[newer], 387 | GET_SCORE[newer], 388 | GET_PROMOTION[older] 389 | ] 390 | } 391 | 392 | UPDATE_LAST_FROM_TO = ->(older, newer) { 393 | CREATE_STATE[ 394 | GET_FROM[older], 395 | GET_TO[older], 396 | GET_FROM[newer], 397 | GET_TO[newer], 398 | GET_BOARD[older], 399 | GET_SCORE[older], 400 | GET_PROMOTION[older] 401 | ] 402 | } 403 | 404 | UPDATE_AFTER_MOVE = ->(older, board) { 405 | CREATE_STATE[ 406 | GET_FROM[older], 407 | GET_TO[older], 408 | GET_FROM[older], 409 | GET_TO[older], 410 | board, 411 | SCORE[board, GET_TO[older]], 412 | GET_PROMOTION[older] 413 | ] 414 | } 415 | 416 | WITH_BASIC_INFO = ->(state, func) { 417 | func[GET_BOARD[state], GET_FROM[state], GET_TO[state]] 418 | } 419 | 420 | # Z Combinator 421 | 422 | Z = ->(f) { ->(a) { a[a] }[ ->(x) { f[->(v) { x[x][v] }] } ] } 423 | -------------------------------------------------------------------------------- /expanded/pieces.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Colin Fulton 2 | # All rights reserved. 3 | # 4 | # This software may be modified and distributed under the 5 | # terms of the three-clause BSD license. See LICENSE.txt 6 | 7 | # Piece Helper Functions 8 | 9 | VALID = ->(valid, invalid, en_passant, castle, promotion) { valid } 10 | INVALID = ->(valid, invalid, en_passant, castle, promotion) { invalid } 11 | EN_PASSANT = ->(valid, invalid, en_passant, castle, promotion) { en_passant } 12 | CASTLE = ->(valid, invalid, en_passant, castle, promotion) { castle } 13 | PROMOTION = ->(valid, invalid, en_passant, castle, promotion) { promotion } 14 | 15 | ISNT_INVALID = ->(move_result) { move_result[FIRST, SECOND, FIRST, FIRST, FIRST] } 16 | 17 | ADVANCE_STATE = ->(state) { 18 | ->(move_type) { 19 | ->(if_valid, if_invalid) { 20 | IF[ISNT_INVALID[move_type]][ 21 | -> { 22 | if_valid[ 23 | UPDATE_AFTER_MOVE[ 24 | state, 25 | move_type[ 26 | NORMAL_MOVE, 27 | ZERO, 28 | EN_PASSANT_MOVE, 29 | CASTLING_MOVE, 30 | CHANGE_MOVE 31 | ][ 32 | GET_BOARD[state], 33 | GET_FROM[state], 34 | GET_TO[state], 35 | GET_PROMOTION[state] 36 | ] 37 | ] 38 | ] 39 | }, 40 | -> { if_invalid[] } 41 | ] 42 | } 43 | }[ 44 | # "move_type" 45 | GET_RULE[GET_POSITION[GET_BOARD[state], GET_FROM[state]]][state] 46 | ] 47 | } 48 | 49 | BASIC_CHECKS = ->(rule) { 50 | ->(get_rule) { 51 | ->(state) { 52 | WITH_BASIC_INFO[ 53 | state, 54 | ->(board, from, to) { 55 | IF[ 56 | # Cannot capture own color 57 | COLOR_SWITCH[GET_POSITION[board, from]][ 58 | IS_BLACK[GET_POSITION[board, to]], 59 | IS_WHITE[GET_POSITION[board, to]], 60 | SECOND 61 | ] 62 | ][ 63 | -> { INVALID }, 64 | -> { 65 | ->(move_type) { 66 | IF[ISNT_INVALID[move_type]][ 67 | -> { 68 | ->(moved_piece, after_move) { 69 | ->(my_kings_data) { 70 | VECTOR_REDUCE[ 71 | my_kings_data, 72 | ->(memo, king_position) { 73 | IF[memo][ 74 | -> { 75 | IS_NOT_IN_CHECK[ 76 | after_move, 77 | king_position, 78 | get_rule 79 | ][ 80 | FIRST, 81 | SECOND 82 | ] 83 | }, 84 | -> { SECOND } 85 | ] 86 | }, 87 | FIRST 88 | ] 89 | }[ 90 | # "my_kings_data" 91 | POSITION_SELECT[ 92 | after_move, 93 | ->(possible) { 94 | AND[ 95 | HAS_VALUE[possible, KING_VALUE], 96 | IS_BLACK[possible][ 97 | IS_BLACK[moved_piece], 98 | IS_WHITE[moved_piece] 99 | ] 100 | ] 101 | } 102 | ] 103 | ] 104 | }[ 105 | # "moved_piece" 106 | GET_POSITION[board, from], 107 | # "after_move" 108 | NORMAL_MOVE[board, from, to, ZERO] 109 | ][ 110 | move_type, 111 | INVALID 112 | ] 113 | }, 114 | -> { move_type } 115 | ] 116 | }[ 117 | # "move_type" 118 | rule[state, get_rule] 119 | ] 120 | } 121 | ] 122 | } 123 | ] 124 | } 125 | } 126 | } 127 | 128 | STRAIGHT_LINE_RULE = ->(rule) { 129 | BASIC_CHECKS[ 130 | ->(state, _) { 131 | WITH_BASIC_INFO[ 132 | state, 133 | ->(board, from, to) { 134 | IF[rule[DELTA[from, to, LEFT], DELTA[from, to, RIGHT]]][ 135 | -> { 136 | FREE_PATH[board, from, to, DECREMENT][ 137 | VALID, 138 | INVALID 139 | ] 140 | }, 141 | -> { INVALID } 142 | ] 143 | } 144 | ] 145 | } 146 | ] 147 | } 148 | 149 | # Piece Rules 150 | 151 | NULL_RULE = ->(_) { ->(_) { INVALID } } 152 | 153 | ROOK_RULE = STRAIGHT_LINE_RULE[ 154 | ->(delta_x, delta_y) { 155 | OR[ 156 | IS_ZERO[delta_x], 157 | IS_ZERO[delta_y] 158 | ] 159 | } 160 | ] 161 | 162 | BISHOP_RULE = STRAIGHT_LINE_RULE[ 163 | ->(delta_x, delta_y) { 164 | IS_EQUAL[delta_x, delta_y] 165 | } 166 | ] 167 | 168 | QUEEN_RULE = ->(get_rule) { 169 | ->(state) { 170 | ->(follows_rule) { 171 | OR[ 172 | follows_rule[ROOK_RULE[get_rule]], 173 | follows_rule[BISHOP_RULE[get_rule]] 174 | ][ 175 | VALID, 176 | INVALID 177 | ] 178 | }[ 179 | # "follows_rule" 180 | ->(rule) { ISNT_INVALID[rule[state]] } 181 | ] 182 | } 183 | } 184 | 185 | VALID_CASTLE = ->(state, get_rule) { 186 | WITH_BASIC_INFO[ 187 | state, 188 | ->(board, from, to) { 189 | IF[ 190 | AND[ 191 | IS_EQUAL[TWO, DELTA[from, to, LEFT]], 192 | IS_ZERO[DELTA[from, to, RIGHT]] 193 | ] 194 | ][ 195 | -> { 196 | ->(is_moving_left) { 197 | ->(rook_from, invalid, mid_to) { 198 | IF[ 199 | ->(king, rook) { 200 | FIVE_CONDITIONS_MET[ 201 | # Moving a king 202 | HAS_VALUE[king, KING_VALUE], 203 | # King is unmoved 204 | NOT[GET_MOVED[king]], 205 | # Moving a rook 206 | HAS_VALUE[rook, ROOK_VALUE], 207 | # Rook is unmoved 208 | NOT[GET_MOVED[rook]], 209 | # Path is free 210 | FREE_PATH[board, from, rook_from, DECREMENT], 211 | ] 212 | }[ 213 | # "king" 214 | GET_POSITION[board, from], 215 | # "rook" 216 | GET_POSITION[board, rook_from] 217 | ] 218 | ][ 219 | -> { 220 | IF[IS_NOT_IN_CHECK[NORMAL_MOVE[board, from, from, ZERO], from, get_rule]][ 221 | -> { 222 | IF[IS_NOT_IN_CHECK[NORMAL_MOVE[board, from, mid_to, ZERO], mid_to, get_rule]][ 223 | # Don't check IS_NOT_IN_CHECK[board, from, to] since 224 | # BASIC_CHECKS does that. 225 | -> { FIRST }, 226 | invalid 227 | ] 228 | }, 229 | invalid 230 | ] 231 | }, 232 | invalid 233 | ] 234 | }[ 235 | # "rook_from" 236 | PAIR[is_moving_left[ZERO, SEVEN], RIGHT[from]], 237 | # "invalid" 238 | -> { SECOND }, 239 | # "mid_to" 240 | PAIR[ 241 | is_moving_left[DECREMENT, INCREMENT][LEFT[from]], 242 | RIGHT[from] 243 | ] 244 | ] 245 | }[ 246 | # "is_moving_left" 247 | IS_GREATER_OR_EQUAL[LEFT[from], LEFT[to]] 248 | ] 249 | }, 250 | -> { SECOND } 251 | ] 252 | } 253 | ] 254 | } 255 | 256 | IS_NOT_IN_CHECK = ->(board, to, get_rule) { 257 | FROM_TO_REDUCE[ 258 | POSITION_SELECT[ 259 | board, 260 | ->(piece) { 261 | IS_BLACK[GET_POSITION[board, to]][ 262 | IS_WHITE[piece], 263 | IS_BLACK[piece] 264 | ] 265 | } 266 | ], 267 | VECTOR_APPEND[EMPTY_VECTOR, to], 268 | ->(memo, from, to) { 269 | IF[memo][ 270 | -> { 271 | NOT[ 272 | ISNT_INVALID[ 273 | get_rule[GET_POSITION[board, from]][ 274 | CREATE_STATE[from, to, from, to, board, ZERO, ZERO] 275 | ] 276 | ] 277 | ] 278 | }, 279 | -> { SECOND } 280 | ] 281 | }, 282 | FIRST 283 | ] 284 | } 285 | 286 | KING_RULE = BASIC_CHECKS[ 287 | ->(state, get_rule) { 288 | IF[ 289 | AND[ 290 | IS_GREATER_OR_EQUAL[ONE, DELTA[GET_FROM[state], GET_TO[state], LEFT]], 291 | IS_GREATER_OR_EQUAL[ONE, DELTA[GET_FROM[state], GET_TO[state], RIGHT]] 292 | ] 293 | ][ 294 | -> { VALID }, 295 | -> { 296 | VALID_CASTLE[state, get_rule][ 297 | CASTLE, 298 | INVALID 299 | ] 300 | } 301 | ] 302 | } 303 | ] 304 | 305 | KNIGHT_RULE = BASIC_CHECKS[ 306 | ->(state, _) { 307 | ->(delta_x, delta_y) { 308 | OR[ 309 | AND[ 310 | IS_EQUAL[TWO, delta_x], 311 | IS_EQUAL[ONE, delta_y] 312 | ], 313 | AND[ 314 | IS_EQUAL[ONE, delta_x], 315 | IS_EQUAL[TWO, delta_y] 316 | ] 317 | ][ 318 | VALID, 319 | INVALID 320 | ] 321 | }[ 322 | # "delta_x" 323 | DELTA[GET_FROM[state], GET_TO[state], LEFT], 324 | # "delta_y" 325 | DELTA[GET_FROM[state], GET_TO[state], RIGHT] 326 | ] 327 | } 328 | ] 329 | 330 | PAWN_RULE = BASIC_CHECKS[ 331 | ->(state, _) { 332 | WITH_BASIC_INFO[ 333 | state, 334 | ->(board, from, to) { 335 | ->(pawn_is_black, from_y, to_y) { 336 | IF[ 337 | pawn_is_black[ 338 | IS_ZERO[SUBTRACT[from_y, to_y]], 339 | IS_ZERO[SUBTRACT[to_y, from_y]] 340 | ] 341 | ][ 342 | # If moving forward 343 | -> { 344 | ->(vertical_movement) { 345 | IF[IS_EQUAL[ONE, vertical_movement]][ 346 | # If moving vertically one 347 | -> { 348 | ->(horizontal_movement, last_to) { 349 | IF[IS_ZERO[horizontal_movement]][ 350 | # If not moving horizontally 351 | -> { 352 | # Performing a normal move 353 | IS_EMPTY[GET_POSITION[board, to]][ 354 | IS_EQUAL[ 355 | to_y, 356 | pawn_is_black[WHITE_HOME_ROW, BLACK_HOME_ROW] 357 | ][ 358 | PROMOTION, 359 | VALID 360 | ], 361 | INVALID 362 | ] 363 | }, 364 | # If moving horizontally 365 | -> { 366 | IF[IS_EQUAL[ONE, horizontal_movement]][ 367 | # If moving horizontally one 368 | -> { 369 | IF[IS_EMPTY[GET_POSITION[board, to]]][ 370 | # Not performing a normal capture 371 | -> { 372 | ->(last_moved) { 373 | FIVE_CONDITIONS_MET[ 374 | FIRST, 375 | # Position behind "to" is "last_to" 376 | SAME_POSITION[ 377 | last_to, 378 | PAIR[LEFT[to], from_y] 379 | ], 380 | # "last_moved" is a pawn 381 | HAS_VALUE[last_moved, PAWN_VALUE], 382 | # "last_moved" is the opposite color 383 | pawn_is_black[IS_WHITE, IS_BLACK][last_moved], 384 | # "last_moved" moved forward two 385 | IS_EQUAL[ 386 | DELTA[GET_LAST_FROM[state], last_to, RIGHT], 387 | TWO 388 | ] 389 | ][ 390 | EN_PASSANT, 391 | INVALID 392 | ] 393 | }[ 394 | # "last_moved" 395 | GET_POSITION[board, last_to] 396 | ] 397 | }, 398 | # Performing a normal capture 399 | -> { VALID } 400 | ] 401 | }, 402 | # If moving horizontally more than 403 | -> { INVALID } 404 | ] 405 | } 406 | ] 407 | }[ 408 | # "horizontal_movement" 409 | DELTA[from, to, LEFT], 410 | # "last_to" 411 | GET_LAST_TO[state] 412 | ] 413 | }, 414 | # If not moving vertically one 415 | -> { 416 | IF[IS_EQUAL[TWO, vertical_movement]][ 417 | # If moving vertically two 418 | -> { 419 | IF[IS_ZERO[DELTA[from, to, LEFT]]][ 420 | # If not moving horizontally 421 | -> { 422 | AND[ 423 | IS_EMPTY[GET_POSITION[board, to]], 424 | AND[ 425 | # One space ahead of pawn is free 426 | IS_EMPTY[ 427 | GET_POSITION[ 428 | board, 429 | PAIR[ 430 | LEFT[to], 431 | CHANGE_FUNC[from, to, RIGHT][from_y] 432 | ] 433 | ] 434 | ], 435 | # Pawn has not moved yet 436 | NOT[IS_MOVED[GET_POSITION[board, from]]] 437 | ] 438 | ][ 439 | VALID, 440 | INVALID 441 | ] 442 | }, 443 | # If moving horizontally 444 | -> { INVALID } 445 | ] 446 | }, 447 | # If not moving vertically two 448 | -> { INVALID } 449 | ] 450 | } 451 | ] 452 | }[ 453 | # "vertical_movement" 454 | DELTA[from, to, RIGHT] 455 | ] 456 | }, 457 | # If not moving forward 458 | -> { INVALID } 459 | ] 460 | }[ 461 | # "pawn_is_black" 462 | IS_BLACK[GET_POSITION[board, from]], 463 | # "from_y" 464 | RIGHT[from], 465 | # "to_y" 466 | RIGHT[to] 467 | ] 468 | } 469 | ] 470 | } 471 | ] 472 | 473 | GET_RULE = Z[->(get_rule) { 474 | ->(piece) { 475 | ->(piece_value) { 476 | IS_EQUAL[piece_value, PAWN_VALUE][ 477 | PAWN_RULE, 478 | IS_EQUAL[piece_value, ROOK_VALUE][ 479 | ROOK_RULE, 480 | IS_EQUAL[piece_value, KNIGHT_VALUE][ 481 | KNIGHT_RULE, 482 | IS_EQUAL[piece_value, BISHOP_VALUE][ 483 | BISHOP_RULE, 484 | IS_EQUAL[piece_value, QUEEN_VALUE][ 485 | QUEEN_RULE, 486 | IS_EQUAL[piece_value, KING_VALUE][ 487 | KING_RULE, 488 | NULL_RULE 489 | ]]]]]][get_rule] 490 | }[ 491 | # "piece_value" 492 | GET_VALUE[piece] 493 | ] 494 | } 495 | }] 496 | -------------------------------------------------------------------------------- /expanded/play.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Colin Fulton 2 | # All rights reserved. 3 | # 4 | # This software may be modified and distributed under the 5 | # terms of the three-clause BSD license. See LICENSE.txt 6 | 7 | PLAY = ->(state, accept, reject, loss, forfit, seed) { 8 | IF[IS_BLACK[GET_POSITION[GET_BOARD[state], GET_FROM[state]]]][ 9 | -> { reject[state] }, 10 | -> { 11 | ADVANCE_STATE[state][ 12 | ->(new_state) { 13 | ->(response) { 14 | IF[LEFT[response]][ 15 | -> { 16 | ->(response_state) { 17 | IF[ISNT_WHITE_CHECKMATE[GET_BOARD[response_state]]][ 18 | -> { accept[response_state] }, 19 | -> { loss[response_state] } 20 | ] 21 | }[ 22 | # "response_state" 23 | UPDATE_LAST_FROM_TO[RIGHT[response], state] 24 | ] 25 | }, 26 | -> { forfit[new_state] } 27 | ] 28 | }[ 29 | # "response" 30 | BLACK_AI[new_state, seed] 31 | ] 32 | }, 33 | -> { reject[state] } 34 | ] 35 | } 36 | ] 37 | } 38 | 39 | ISNT_WHITE_CHECKMATE = ->(board) { 40 | ->(king_position_vector) { 41 | ->(king_position) { 42 | IF[ 43 | FROM_TO_REDUCE[ 44 | king_position_vector, 45 | POSITION_SELECT[board, ALWAYS_FIRST], 46 | ->(memo, from, to) { 47 | IF[memo][ 48 | -> { FIRST }, 49 | -> { 50 | ISNT_INVALID[ 51 | KING_RULE[GET_RULE][ 52 | CREATE_STATE[from, to, from, to, board, ZERO, WHITE_QUEEN] 53 | ] 54 | ] 55 | } 56 | ] 57 | }, 58 | SECOND 59 | ] 60 | ][ 61 | # King can move 62 | -> { FIRST }, 63 | # King can't move 64 | -> { 65 | IS_NOT_IN_CHECK[board, king_position, GET_RULE] 66 | } 67 | ] 68 | }[ 69 | # "king_position" 70 | VECTOR_FIRST[king_position_vector] 71 | ] 72 | }[ 73 | # "king_position_vector" 74 | POSITION_SELECT[ 75 | board, 76 | ->(piece) { 77 | AND[ 78 | IS_WHITE[piece], 79 | IS_EQUAL[KING_VALUE, GET_VALUE[piece]] 80 | ] 81 | } 82 | ] 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /expanded/test/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/front-endian/lambda_chess/7dd23c1544e559e6efc4ce12362a8520d3238034/expanded/test/.DS_Store -------------------------------------------------------------------------------- /expanded/test/setup.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Colin Fulton 2 | # All rights reserved. 3 | # 4 | # This software may be modified and distributed under the 5 | # terms of the three-clause BSD license. See LICENSE.txt 6 | 7 | require 'tet' 8 | require_relative './../data' 9 | require_relative './../board' 10 | require_relative './../pieces' 11 | require_relative './../ai' 12 | require_relative './../play' 13 | 14 | class Proc 15 | def to_i 16 | call proc { |x| x + 1 }, 0 17 | end 18 | 19 | def list_to_a length 20 | result = [] 21 | part = self 22 | 23 | length.times do 24 | result.push(LEFT[part]) 25 | part = RIGHT[part] 26 | end 27 | 28 | result 29 | end 30 | 31 | def vector_to_a 32 | VECTOR_LIST[self].list_to_a(VECTOR_SIZE[self].to_i) 33 | end 34 | 35 | def board_to_a 36 | self.list_to_a(8) 37 | .map { |row| 38 | row.list_to_a(8).map { |piece| 39 | GET_VALUE[piece].to_i + IS_BLACK[piece][KING_VALUE.to_i, 0] 40 | } 41 | }.flatten 42 | end 43 | 44 | def position_to_a 45 | [LEFT[self].to_i, RIGHT[self].to_i] 46 | end 47 | end 48 | 49 | class Fixnum 50 | def to_peano 51 | proc do |func, result| 52 | times { result = func.call(result) } 53 | result 54 | end 55 | end 56 | end 57 | 58 | class Array 59 | def to_linked_list 60 | reverse.inject(ZERO) do |previous, element| 61 | PAIR[element, previous] 62 | end 63 | end 64 | 65 | def to_vector 66 | reverse.inject(EMPTY_VECTOR) do |previous, element| 67 | VECTOR_APPEND[previous, element] 68 | end 69 | end 70 | 71 | def to_board 72 | map do |row| 73 | row.map do |piece| 74 | piece.is_a?(Proc) ? piece : EMPTY_SPACE 75 | end.to_linked_list 76 | end.to_linked_list 77 | end 78 | end 79 | 80 | def position x, y 81 | PAIR[x.to_peano, y.to_peano] 82 | end 83 | 84 | def shift_position position, delta_x, delta_y 85 | PAIR[ 86 | (LEFT[position].to_i + delta_x).to_peano, 87 | (RIGHT[position].to_i + delta_y).to_peano 88 | ] 89 | end 90 | 91 | def test_castling_to_one_side board:, 92 | home_row:, 93 | king:, 94 | rook:, 95 | king_to_column:, 96 | rook_to_column:, 97 | perform:, 98 | expect: 99 | 100 | king_from = PAIR[FOUR, home_row] 101 | king_to = PAIR[king_to_column, home_row] 102 | rook_from = PAIR[ 103 | (rook_to_column == THREE) ? ZERO : SEVEN, 104 | home_row 105 | ] 106 | rook_to = PAIR[rook_to_column, home_row] 107 | result = perform.call(board, king_from, king_to) 108 | 109 | expect.call(result, king_to, rook_to, rook_from) 110 | end 111 | 112 | def test_castling black_board, white_board, perform:, expect: 113 | null_pos = PAIR[ZERO, ZERO] 114 | 115 | group 'with a white king' do 116 | assert 'castling to the left' do 117 | test_castling_to_one_side board: white_board, 118 | home_row: WHITE_HOME_ROW, 119 | king: WHITE_KING, 120 | rook: WHITE_ROOK, 121 | king_to_column: TWO, 122 | rook_to_column: THREE, 123 | perform: perform, 124 | expect: expect 125 | end 126 | 127 | assert 'castling to the right' do 128 | test_castling_to_one_side board: white_board, 129 | home_row: WHITE_HOME_ROW, 130 | king: WHITE_KING, 131 | rook: WHITE_ROOK, 132 | king_to_column: SIX, 133 | rook_to_column: FIVE, 134 | perform: perform, 135 | expect: expect 136 | end 137 | end 138 | 139 | group 'with a black king' do 140 | assert 'can castle to the left' do 141 | test_castling_to_one_side board: black_board, 142 | home_row: BLACK_HOME_ROW, 143 | king: BLACK_KING, 144 | rook: BLACK_ROOK, 145 | king_to_column: TWO, 146 | rook_to_column: THREE, 147 | perform: perform, 148 | expect: expect 149 | end 150 | 151 | assert 'can castle to the right' do 152 | test_castling_to_one_side board: black_board, 153 | home_row: BLACK_HOME_ROW, 154 | king: BLACK_KING, 155 | rook: BLACK_ROOK, 156 | king_to_column: SIX, 157 | rook_to_column: FIVE, 158 | perform: perform, 159 | expect: expect 160 | end 161 | end 162 | end 163 | 164 | def expect_truthy func 165 | func[true, false] 166 | end 167 | 168 | def expect_falsy func 169 | func[false, true] 170 | end 171 | 172 | def expect_valid result 173 | result[true, false, false, false, false] 174 | end 175 | 176 | def expect_invalid result 177 | result[false, true, false, false, false] 178 | end 179 | 180 | def expect_en_passant result 181 | result[false, false, true, false, false] 182 | end 183 | 184 | def expect_castle result 185 | result[false, false, false, true, false] 186 | end 187 | 188 | NULL_POS = PAIR[ZERO, ZERO] 189 | 190 | INDEX_ARRAY = [0, 1, 2, 3, 4, 5, 6, 7, 191 | 8, 9, 10, 11, 12, 13, 14, 15, 192 | 16, 17, 18, 19, 20, 21, 22, 23, 193 | 24, 25, 26, 27, 28, 29, 30, 31, 194 | 32, 33, 34, 35, 36, 37, 38, 39, 195 | 40, 41, 42, 43, 44, 45, 46, 47, 196 | 48, 49, 50, 51, 52, 53, 54, 55, 197 | 56, 57, 58, 59, 60, 61, 62, 63] 198 | 199 | BP = BLACK_PAWN 200 | BR = BLACK_ROOK 201 | BN = BLACK_KNIGHT 202 | BB = BLACK_BISHOP 203 | BQ = BLACK_QUEEN 204 | BK = BLACK_KING 205 | MBP = TO_MOVED_PIECE[BLACK_PAWN] 206 | MBR = TO_MOVED_PIECE[BLACK_ROOK] 207 | MBN = TO_MOVED_PIECE[BLACK_KNIGHT] 208 | MBB = TO_MOVED_PIECE[BLACK_BISHOP] 209 | MBQ = TO_MOVED_PIECE[BLACK_QUEEN] 210 | MBK = TO_MOVED_PIECE[BLACK_KING] 211 | 212 | WP = WHITE_PAWN 213 | WR = WHITE_ROOK 214 | WN = WHITE_KNIGHT 215 | WB = WHITE_BISHOP 216 | WQ = WHITE_QUEEN 217 | WK = WHITE_KING 218 | MWP = TO_MOVED_PIECE[WHITE_PAWN] 219 | MWR = TO_MOVED_PIECE[WHITE_ROOK] 220 | MWN = TO_MOVED_PIECE[WHITE_KNIGHT] 221 | MWB = TO_MOVED_PIECE[WHITE_BISHOP] 222 | MWQ = TO_MOVED_PIECE[WHITE_QUEEN] 223 | MWK = TO_MOVED_PIECE[WHITE_KING] 224 | -------------------------------------------------------------------------------- /expanded/test/test_ai.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Colin Fulton 2 | # All rights reserved. 3 | # 4 | # This software may be modified and distributed under the 5 | # terms of the three-clause BSD license. See LICENSE.txt 6 | 7 | require_relative './setup' 8 | 9 | group 'AI Functions' do 10 | group 'MAX_PIECE_TOTAL' do 11 | assert 'has the correct value' do 12 | MAX_PIECE_TOTAL.to_i == (GET_VALUE[BP].to_i * 8) + 13 | (GET_VALUE[BN].to_i * 2) + 14 | (GET_VALUE[BB].to_i * 2) + 15 | (GET_VALUE[BR].to_i * 2) + 16 | GET_VALUE[BQ].to_i + 17 | GET_VALUE[BK].to_i 18 | end 19 | end 20 | 21 | group 'SCORE' do 22 | group 'INITIAL_BOARD has MAX_PIECE_TOTAL' do 23 | assert 'for black' do 24 | MAX_PIECE_TOTAL.to_i == SCORE[INITIAL_BOARD, NULL_POS].to_i 25 | end 26 | end 27 | 28 | group 'if a black piece is taken' do 29 | board = [[BR,BN,0, BQ,BK,BB,BN,BR], 30 | [BP,BP,BP,BP,BP,BP,BP,BP], 31 | [0, 0, 0, 0, 0, 0, 0, 0], 32 | [0, 0, 0, 0, 0, 0, 0, 0], 33 | [0, 0, 0, 0, 0, 0, 0, 0], 34 | [0, 0, 0, 0, 0, 0, 0, 0], 35 | [WP,WP,WP,WP,WP,WP,WP,WP], 36 | [WR,WN,WB,WQ,WK,WB,WN,WR]] 37 | .to_board 38 | 39 | assert 'the score for black goes down by that amount' do 40 | expected = MAX_PIECE_TOTAL.to_i - GET_VALUE[BB].to_i 41 | expected == SCORE[board, NULL_POS].to_i 42 | end 43 | end 44 | 45 | group 'if a white piece is taken' do 46 | board = [[BR,BN,BB,BQ,BK,BB,BN,BR], 47 | [BP,BP,BP,BP,BP,BP,BP,BP], 48 | [0, 0, 0, 0, 0, 0, 0, 0], 49 | [0, 0, 0, 0, 0, 0, 0, 0], 50 | [0, 0, 0, 0, 0, 0, 0, 0], 51 | [0, 0, 0, 0, 0, 0, 0, 0], 52 | [WP,WP,WP,WP,WP,WP,WP,WP], 53 | [WR,WN,WB,0, WK,WB,WN,WR]] 54 | .to_board 55 | 56 | assert 'the score for black goes up by the black equivolent' do 57 | expected = MAX_PIECE_TOTAL.to_i + GET_VALUE[BQ].to_i 58 | expected == SCORE[board, NULL_POS].to_i 59 | end 60 | end 61 | 62 | assert 'does not bottom out' do 63 | one_left = [[0, 0, 0, 0, 0, 0, 0, 0], 64 | [BP,0, 0, 0, 0, 0, 0, 0], 65 | [0, 0, 0, 0, 0, 0, 0, 0], 66 | [0, 0, 0, 0, 0, 0, 0, 0], 67 | [0, 0, 0, 0, 0, 0, 0, 0], 68 | [0, 0, 0, 0, 0, 0, 0, 0], 69 | [WP,WP,WP,WP,WP,WP,WP,WP], 70 | [WR,WN,WB,WQ,WK,WB,WN,WR]] 71 | .to_board 72 | 73 | none_left = [[0, 0, 0, 0, 0, 0, 0, 0], 74 | [0, 0, 0, 0, 0, 0, 0, 0], 75 | [0, 0, 0, 0, 0, 0, 0, 0], 76 | [0, 0, 0, 0, 0, 0, 0, 0], 77 | [0, 0, 0, 0, 0, 0, 0, 0], 78 | [0, 0, 0, 0, 0, 0, 0, 0], 79 | [WP,WP,WP,WP,WP,WP,WP,WP], 80 | [WR,WN,WB,WQ,WK,WB,WN,WR]] 81 | .to_board 82 | 83 | SCORE[one_left, NULL_POS].to_i > SCORE[none_left, NULL_POS].to_i 84 | end 85 | end 86 | 87 | group 'BLACK_AI' do 88 | def run_black_ai board, seed 89 | BLACK_AI[ 90 | CREATE_STATE[ 91 | NULL_POS, 92 | NULL_POS, 93 | NULL_POS, 94 | NULL_POS, 95 | board, 96 | 0.to_peano, 97 | 0.to_peano 98 | ], 99 | seed.to_peano 100 | ] 101 | end 102 | 103 | assert 'flags when no moves are valid' do 104 | board = [[0, 0, 0, 0, 0, 0, 0, 0], 105 | [0, 0, 0, 0, 0, 0, 0, 0], 106 | [0, 0, 0, 0, 0, 0, 0, 0], 107 | [0, 0, BR,0, 0, 0, 0, 0], 108 | [0, 0, 0, 0, 0, 0, 0, 0], 109 | [0, 0, 0, 0, 0, WQ,0, WR], 110 | [0, 0, 0, 0, 0, 0, 0, 0], 111 | [0, 0, 0, 0, 0, WR,0, BK]] 112 | .to_board 113 | 114 | expect_falsy LEFT[run_black_ai(board, 1)] 115 | end 116 | 117 | group 'give multiple valid moves' do 118 | board = [[0, 0, 0, 0, 0, 0, 0, 0], 119 | [0, 0, 0, 0, 0, 0, 0, 0], 120 | [0, 0, 0, 0, 0, 0, 0, 0], 121 | [0, 0, BR,0, 0, 0, 0, 0], 122 | [0, 0, 0, 0, 0, 0, 0, 0], 123 | [0, 0, 0, 0, 0, WQ,0, 0], 124 | [0, 0, 0, 0, 0, 0, WR,0], 125 | [0, 0, 0, 0, 0, 0, 0, BK]] 126 | .to_board 127 | 128 | result_1 = run_black_ai(board, 1) 129 | result_2 = run_black_ai(board, 2) 130 | 131 | 132 | assert 'flags when moves are valid' do 133 | expect_truthy LEFT[result_1] 134 | end 135 | 136 | assert 'only moves a valid piece' do 137 | expect_truthy SAME_POSITION[GET_FROM[RIGHT[result_1]], position(2, 3)] 138 | end 139 | 140 | assert 'finds multiple moves' do 141 | expect_falsy SAME_POSITION[GET_TO[RIGHT[result_1]], GET_TO[RIGHT[result_2]]] 142 | end 143 | end 144 | 145 | assert 'selects "to" based on "seed" parameter' do 146 | board = [[0, 0, 0, 0, 0, 0, 0, 0], 147 | [0, 0, 0, 0, 0, 0, 0, 0], 148 | [0, 0, 0, 0, 0, 0, 0, 0], 149 | [0, 0, 0, 0, 0, 0, 0, 0], 150 | [0, 0, 0, 0, 0, 0, 0, 0], 151 | [0, 0, 0, 0, 0, WQ,0, 0], 152 | [0, 0, 0, 0, 0, 0, WR,0], 153 | [BN,0, 0, 0, 0, 0, 0, BK]] 154 | .to_board 155 | 156 | result_1 = run_black_ai(board, 1) 157 | result_2 = run_black_ai(board, 2) 158 | 159 | possible_tos = [GET_TO[RIGHT[result_1]], GET_TO[RIGHT[result_2]]] 160 | 161 | expect_truthy(SAME_POSITION[GET_FROM[RIGHT[result_1]], position(0, 7)]) && 162 | expect_truthy(SAME_POSITION[GET_FROM[RIGHT[result_2]], position(0, 7)]) && 163 | possible_tos.any? { |pos| expect_truthy SAME_POSITION[pos, position(1, 5)] } && 164 | possible_tos.any? { |pos| expect_truthy SAME_POSITION[pos, position(2, 6)] } 165 | end 166 | 167 | assert 'moves king out of check when needed' do 168 | board = [[0, 0, 0, 0, 0, 0, 0, 0], 169 | [0, 0, 0, 0, 0, 0, 0, 0], 170 | [0, 0, 0, 0, 0, 0, 0, 0], 171 | [0, 0, BN,0, 0, 0, 0, 0], 172 | [0, 0, 0, 0, 0, 0, 0, 0], 173 | [0, 0, 0, 0, 0, 0, WR,0], 174 | [0, 0, 0, 0, 0, 0, 0 ,0], 175 | [0, 0, 0, 0, 0, WR,0, BK]] 176 | .to_board 177 | 178 | result_1 = RIGHT[run_black_ai(board, 1)] 179 | result_2 = RIGHT[run_black_ai(board, 2)] 180 | 181 | expect_truthy(SAME_POSITION[GET_TO[result_1], position(7, 6)]) && 182 | expect_truthy(SAME_POSITION[GET_TO[result_2], position(7, 6)]) 183 | end 184 | 185 | assert 'finds the best move' do 186 | board = [[0, 0, 0, 0, 0, 0, 0, 0], 187 | [0, 0, 0, 0, 0, 0, 0, 0], 188 | [0, 0, 0, 0, 0, BP,0, 0], 189 | [0, 0, BN,0, 0, 0, 0, 0], 190 | [0, 0, 0, 0, BP,0, 0, 0], 191 | [0, 0, 0, WP,0, 0, 0, 0], 192 | [0, 0, 0, 0, 0, 0, WQ,0], 193 | [0, 0, 0, 0, 0, 0, WN,BK]] 194 | .to_board 195 | 196 | result_1 = RIGHT[run_black_ai(board, 1)] 197 | result_2 = RIGHT[run_black_ai(board, 2)] 198 | 199 | expect_truthy(SAME_POSITION[GET_TO[result_1], position(6, 6)]) && 200 | expect_truthy(SAME_POSITION[GET_TO[result_2], position(6, 6)]) 201 | end 202 | 203 | assert 'prefers putting white into check' do 204 | board = [[WK,0, 0, 0, 0, 0, 0, 0], 205 | [0, 0, 0, 0, 0, 0, 0, 0], 206 | [0, 0, 0, 0, 0, 0, 0, 0], 207 | [0, 0, 0, 0, 0, 0, 0, 0], 208 | [0, BP,0, 0, 0, 0, 0, 0], 209 | [0, BR,0, 0, 0, 0, 0, 0], 210 | [0, 0, 0, 0, 0, 0, 0, 0], 211 | [0, 0, 0, 0, 0, 0, 0, 0]] 212 | .to_board 213 | 214 | result = RIGHT[run_black_ai(board, 1)] 215 | 216 | expect_truthy SAME_POSITION[GET_TO[result], position(0, 5)] 217 | end 218 | end 219 | end 220 | -------------------------------------------------------------------------------- /expanded/test/test_all.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Colin Fulton 2 | # All rights reserved. 3 | # 4 | # This software may be modified and distributed under the 5 | # terms of the three-clause BSD license. See LICENSE.txt 6 | 7 | require_relative './test_data' 8 | require_relative './test_board' 9 | require_relative './test_pieces' 10 | require_relative './test_ai' 11 | require_relative './test_play' 12 | -------------------------------------------------------------------------------- /expanded/test/test_board.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Colin Fulton 2 | # All rights reserved. 3 | # 4 | # This software may be modified and distributed under the 5 | # terms of the three-clause BSD license. See LICENSE.txt 6 | 7 | require_relative './setup' 8 | 9 | group 'Board Functions' do 10 | group 'GET_POSITION' do 11 | assert 'gets data at the given position' do 12 | piece = GET_POSITION[INITIAL_BOARD, PAIR[KING_COLUMN, BLACK_HOME_ROW]] 13 | 14 | GET_VALUE[piece].to_i == KING_VALUE.to_i 15 | end 16 | end 17 | 18 | group 'FREE_PATH' do 19 | center = position(3, 3) 20 | 21 | group 'returns FIRST if there are no pieces in the way' do 22 | example_board = [[0, 0, 0, 0, 0, 0, 0, 0], 23 | [0, 0, 0, 0, 0, 0, 0, 0], 24 | [0, 0, 0, 0, 0, 0, 0, 0], 25 | [0, 0, 0, BP,0, 0, 0, 0], 26 | [0, 0, 0, 0, 0, 0, 0, 0], 27 | [0, 0, 0, 0, 0, 0, 0, 0], 28 | [0, 0, 0, 0, 0, 0, 0, 0], 29 | [0, 0, 0, 0, 0, 0, 0, 0]] 30 | .to_board 31 | 32 | assert 'horizontally' do 33 | expect_truthy( 34 | FREE_PATH[ 35 | example_board, 36 | center, 37 | shift_position(center, 3, 0), 38 | DECREMENT 39 | ] 40 | ) 41 | end 42 | 43 | assert 'vertically' do 44 | expect_truthy( 45 | FREE_PATH[ 46 | example_board, 47 | center, 48 | shift_position(center, 0, -3), 49 | DECREMENT 50 | ] 51 | ) 52 | end 53 | 54 | assert 'diagonally' do 55 | expect_truthy( 56 | FREE_PATH[ 57 | example_board, 58 | center, 59 | shift_position(center, -3, 3), 60 | DECREMENT 61 | ] 62 | ) 63 | end 64 | end 65 | 66 | group 'if the only piece in the way is at the TO location' do 67 | group 'returns FIRST if told to DECREMENT length' do 68 | assert 'horizontally' do 69 | example_board = [[0, 0, 0, 0, 0, 0, 0, 0], 70 | [0, 0, 0, 0, 0, 0, 0, 0], 71 | [0, 0, 0, 0, 0, 0, 0, 0], 72 | [WQ,0, 0, BQ,0, 0, 0, 0], 73 | [0, 0, 0, 0, 0, 0, 0, 0], 74 | [0, 0, 0, 0, 0, 0, 0, 0], 75 | [0, 0, 0, 0, 0, 0, 0, 0], 76 | [0, 0, 0, 0, 0, 0, 0, 0]] 77 | .to_board 78 | 79 | expect_truthy( 80 | FREE_PATH[ 81 | example_board, 82 | center, 83 | shift_position(center, -3, 0), 84 | DECREMENT 85 | ] 86 | ) 87 | end 88 | 89 | assert 'vertically' do 90 | example_board = [[0, 0, 0, 0, 0, 0, 0, 0], 91 | [0, 0, 0, 0, 0, 0, 0, 0], 92 | [0, 0, 0, 0, 0, 0, 0, 0], 93 | [0, 0, 0, BQ,0, 0, 0, 0], 94 | [0, 0, 0, 0, 0, 0, 0, 0], 95 | [0, 0, 0, 0, 0, 0, 0, 0], 96 | [0, 0, 0, 0, 0, 0, 0, 0], 97 | [0, 0, 0, WQ,0, 0, 0, 0]] 98 | .to_board 99 | 100 | expect_truthy( 101 | FREE_PATH[ 102 | example_board, 103 | center, 104 | shift_position(center, 0, 4), 105 | DECREMENT 106 | ] 107 | ) 108 | end 109 | 110 | assert 'diagonally' do 111 | example_board = [[0, 0, 0, 0, 0, 0, WQ,0], 112 | [0, 0, 0, 0, 0, 0, 0, 0], 113 | [0, 0, 0, 0, 0, 0, 0, 0], 114 | [0, 0, 0, BQ,0, 0, 0, 0], 115 | [0, 0, 0, 0, 0, 0, 0, 0], 116 | [0, 0, 0, 0, 0, 0, 0, 0], 117 | [0, 0, 0, 0, 0, 0, 0, 0], 118 | [0, 0, 0, 0, 0, 0, 0, 0]] 119 | .to_board 120 | 121 | expect_truthy( 122 | FREE_PATH[ 123 | example_board, 124 | center, 125 | shift_position(center, 3, -3), 126 | DECREMENT 127 | ] 128 | ) 129 | end 130 | end 131 | 132 | group 'returns SECOND if told to not alter the length' do 133 | assert 'horizontally' do 134 | example_board = [[0, 0, 0, 0, 0, 0, 0, 0], 135 | [0, 0, 0, 0, 0, 0, 0, 0], 136 | [0, 0, 0, 0, 0, 0, 0, 0], 137 | [WQ,0, 0, BQ,0, 0, 0, 0], 138 | [0, 0, 0, 0, 0, 0, 0, 0], 139 | [0, 0, 0, 0, 0, 0, 0, 0], 140 | [0, 0, 0, 0, 0, 0, 0, 0], 141 | [0, 0, 0, 0, 0, 0, 0, 0]] 142 | .to_board 143 | 144 | expect_falsy( 145 | FREE_PATH[ 146 | example_board, 147 | center, 148 | shift_position(center, -3, 0), 149 | IDENTITY 150 | ] 151 | ) 152 | end 153 | 154 | assert 'vertically' do 155 | example_board = [[0, 0, 0, 0, 0, 0, 0, 0], 156 | [0, 0, 0, 0, 0, 0, 0, 0], 157 | [0, 0, 0, 0, 0, 0, 0, 0], 158 | [0, 0, 0, BQ,0, 0, 0, 0], 159 | [0, 0, 0, 0, 0, 0, 0, 0], 160 | [0, 0, 0, 0, 0, 0, 0, 0], 161 | [0, 0, 0, 0, 0, 0, 0, 0], 162 | [0, 0, 0, WQ,0, 0, 0, 0]] 163 | .to_board 164 | 165 | expect_falsy( 166 | FREE_PATH[ 167 | example_board, 168 | center, 169 | shift_position(center, 0, 4), 170 | IDENTITY 171 | ] 172 | ) 173 | end 174 | 175 | assert 'diagonally' do 176 | example_board = [[0, 0, 0, 0, 0, 0, WQ,0], 177 | [0, 0, 0, 0, 0, 0, 0, 0], 178 | [0, 0, 0, 0, 0, 0, 0, 0], 179 | [0, 0, 0, BQ,0, 0, 0, 0], 180 | [0, 0, 0, 0, 0, 0, 0, 0], 181 | [0, 0, 0, 0, 0, 0, 0, 0], 182 | [0, 0, 0, 0, 0, 0, 0, 0], 183 | [0, 0, 0, 0, 0, 0, 0, 0]] 184 | .to_board 185 | 186 | expect_falsy( 187 | FREE_PATH[ 188 | example_board, 189 | center, 190 | shift_position(center, 3, -3), 191 | IDENTITY 192 | ] 193 | ) 194 | end 195 | end 196 | end 197 | 198 | group 'returns SECOND if there is a piece in the way' do 199 | assert 'horizontally' do 200 | example_board = [[0, 0, 0, 0, 0, 0, 0, 0], 201 | [0, 0, 0, 0, 0, 0, 0, 0], 202 | [0, 0, 0, 0, 0, 0, 0, 0], 203 | [0, 0, 0, BQ,WQ,0, 0, 0], 204 | [0, 0, 0, 0, 0, 0, 0, 0], 205 | [0, 0, 0, 0, 0, 0, 0, 0], 206 | [0, 0, 0, 0, 0, 0, 0, 0], 207 | [0, 0, 0, 0, 0, 0, 0, 0]] 208 | .to_board 209 | 210 | expect_falsy( 211 | FREE_PATH[ 212 | example_board, 213 | center, 214 | shift_position(center, 4, 0), 215 | DECREMENT 216 | ] 217 | ) 218 | end 219 | 220 | assert 'vertically' do 221 | example_board = [[0, 0, 0, 0, 0, 0, 0, 0], 222 | [0, 0, 0, WQ,0, 0, 0, 0], 223 | [0, 0, 0, 0, 0, 0, 0, 0], 224 | [0, 0, 0, BQ,0, 0, 0, 0], 225 | [0, 0, 0, 0, 0, 0, 0, 0], 226 | [0, 0, 0, 0, 0, 0, 0, 0], 227 | [0, 0, 0, 0, 0, 0, 0, 0], 228 | [0, 0, 0, 0, 0, 0, 0, 0]] 229 | .to_board 230 | 231 | expect_falsy( 232 | FREE_PATH[ 233 | example_board, 234 | center, 235 | shift_position(center, 0, -3), 236 | DECREMENT 237 | ] 238 | ) 239 | end 240 | 241 | assert 'diagonally' do 242 | example_board = [[0, 0, 0, 0, 0, 0, 0, 0], 243 | [0, 0, 0, 0, 0, 0, 0, 0], 244 | [0, 0, 0, 0, 0, 0, 0, 0], 245 | [0, 0, 0, BQ,0, 0, 0, 0], 246 | [0, 0, 0, 0, 0, 0, 0, 0], 247 | [0, WQ,0, 0, 0, 0, 0, 0], 248 | [0, 0, 0, 0, 0, 0, 0, 0], 249 | [0, 0, 0, 0, 0, 0, 0, 0]] 250 | .to_board 251 | 252 | expect_falsy( 253 | FREE_PATH[ 254 | example_board, 255 | center, 256 | shift_position(center, -3, 3), 257 | DECREMENT 258 | ] 259 | ) 260 | end 261 | end 262 | end 263 | 264 | group 'NORMAL_MOVE' do 265 | example_board = INITIAL_BOARD 266 | 267 | from = position(0, 0) 268 | to = position(2, 2) 269 | moved = NORMAL_MOVE[example_board, from, to, nil] 270 | 271 | assert 'moves the piece at the "from" position to the "to" position' do 272 | expected = GET_VALUE[GET_POSITION[example_board, from]].to_i 273 | 274 | expected == GET_VALUE[GET_POSITION[moved, to]].to_i 275 | end 276 | 277 | assert 'marks the moved piece as moved' do 278 | expect_falsy(GET_MOVED[GET_POSITION[moved, from]]) && 279 | expect_truthy(GET_MOVED[GET_POSITION[moved, to]]) 280 | end 281 | 282 | assert 'puts an empty piece in the "from" position' do 283 | EMPTY_SPACE == GET_POSITION[moved, from] 284 | end 285 | 286 | assert 'works when moving to same position' do 287 | null_move = NORMAL_MOVE[example_board, from, from, nil] 288 | 289 | expected = GET_VALUE[GET_POSITION[example_board, from]].to_i 290 | 291 | expected == GET_VALUE[GET_POSITION[null_move, from]].to_i 292 | end 293 | end 294 | 295 | group 'CHANGE_MOVE' do 296 | example_board = INITIAL_BOARD 297 | 298 | from = position(0, 0) 299 | to = position(2, 2) 300 | new_piece = BLACK_PAWN 301 | moved = CHANGE_MOVE[example_board, from, to, new_piece] 302 | 303 | assert 'puts the "new_piece" in the "to" position' do 304 | expected = GET_VALUE[new_piece].to_i 305 | 306 | expected == GET_VALUE[GET_POSITION[moved, to]].to_i 307 | end 308 | 309 | assert 'marks the moved piece as moved' do 310 | expect_falsy(GET_MOVED[GET_POSITION[moved, from]]) && 311 | expect_truthy(GET_MOVED[GET_POSITION[moved, to]]) 312 | end 313 | 314 | assert 'puts an empty piece in the "from" position' do 315 | EMPTY_SPACE == GET_POSITION[moved, from] 316 | end 317 | 318 | assert 'works when moving to same position' do 319 | null_move = CHANGE_MOVE[example_board, from, from, new_piece] 320 | 321 | expected = GET_VALUE[new_piece].to_i 322 | 323 | expected == GET_VALUE[GET_POSITION[null_move, from]].to_i 324 | end 325 | end 326 | 327 | group 'CASTLING_MOVE' do 328 | board = [[BR,0, 0, 0, BK,0, 0, BR], 329 | [0, 0, 0, 0, 0, 0, 0, 0], 330 | [0, 0, 0, 0, 0, 0, 0, 0], 331 | [0, 0, 0, 0, 0, 0, 0, 0], 332 | [0, 0, 0, 0, 0, 0, 0, 0], 333 | [0, 0, 0, 0, 0, 0, 0, 0], 334 | [0, 0, 0, 0, 0, 0, 0, 0], 335 | [WR,0, 0, 0, WK,0, 0, WR]] 336 | .to_board 337 | 338 | test_castling board, board, 339 | perform: proc { |board, from, to| CASTLING_MOVE[board, from, to, nil] }, 340 | expect: proc { |result, king_to, rook_to, rook_from| 341 | assert "king was moved" do 342 | piece_in_position = KING_VALUE == GET_VALUE[GET_POSITION[result, king_to]] 343 | 344 | piece_in_position 345 | end 346 | 347 | assert "rook was moved" do 348 | piece_in_position = ROOK_VALUE == GET_VALUE[GET_POSITION[result, rook_to]] 349 | 350 | piece_in_position 351 | end 352 | 353 | assert "correct rook was moved" do 354 | piece_in_position = EMPTY_SPACE == GET_POSITION[result, rook_from] 355 | 356 | piece_in_position 357 | end 358 | } 359 | end 360 | 361 | group 'EN_PASSANT_MOVE' do 362 | example_board = [[0, 0, 0, 0, 0, 0, 0, 0], 363 | [0, 0, 0, 0, 0, 0, 0, 0], 364 | [0, 0, 0, 0, 0, 0, 0, 0], 365 | [0, 0, 0, 0, 0, 0, 0, 0], 366 | [0, 0, 0, BP,MWP,0, 0, 0], 367 | [0, 0, 0, 0, 0, 0, 0, 0], 368 | [0, 0, 0, 0, 0, 0, 0, 0], 369 | [0, 0, 0, 0, 0, 0, 0, 0]] 370 | .to_board 371 | 372 | from = position(3, 4) 373 | to = position(4, 5) 374 | moved = EN_PASSANT_MOVE[example_board, from, to, nil] 375 | 376 | assert 'moves the piece at the "from" position to the "to" position' do 377 | expected = GET_VALUE[GET_POSITION[example_board, from]].to_i 378 | 379 | expected == GET_VALUE[GET_POSITION[moved, to]].to_i 380 | end 381 | 382 | assert 'marks the moved piece as moved' do 383 | expect_falsy(GET_MOVED[GET_POSITION[moved, from]]) && 384 | expect_truthy(GET_MOVED[GET_POSITION[moved, to]]) 385 | end 386 | 387 | group 'puts an empty piece' do 388 | assert 'in the "from" position' do 389 | expect_truthy IS_EMPTY[GET_POSITION[moved, from]] 390 | end 391 | 392 | assert 'in space behind "to"' do 393 | expect_truthy IS_EMPTY[GET_POSITION[moved, position(4, 4)]] 394 | end 395 | end 396 | end 397 | end 398 | -------------------------------------------------------------------------------- /expanded/test/test_data.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Colin Fulton 2 | # All rights reserved. 3 | # 4 | # This software may be modified and distributed under the 5 | # terms of the three-clause BSD license. See LICENSE.txt 6 | 7 | require_relative './setup' 8 | 9 | group 'Choice Functions' do 10 | group 'AND' do 11 | assert 'returns a FIRST when given two FIRSTs' do 12 | expect_truthy AND[FIRST, FIRST] 13 | end 14 | 15 | assert 'returns a SECOND when given a FIRST and a SECOND' do 16 | expect_falsy AND[FIRST, SECOND] 17 | end 18 | 19 | assert 'returns a SECOND when given two SECONDs' do 20 | expect_falsy AND[SECOND, SECOND] 21 | end 22 | end 23 | 24 | group 'OR' do 25 | assert 'returns a FIRST when given two FIRSTs' do 26 | expect_truthy OR[FIRST, FIRST] 27 | end 28 | 29 | assert 'returns a FIRST when given a FIRST and a SECOND' do 30 | expect_truthy OR[FIRST, SECOND] 31 | end 32 | 33 | assert 'returns a SECOND when given two SECONDs' do 34 | expect_falsy OR[SECOND, SECOND] 35 | end 36 | end 37 | end 38 | 39 | group 'List Functions' do 40 | group 'NTH' do 41 | example = [:A, :B, :C].to_linked_list 42 | 43 | assert 'gets the first element when given 0' do 44 | :A == NTH[example, 0.to_peano] 45 | end 46 | 47 | assert 'gets the third element when given 2' do 48 | :C == NTH[example, 2.to_peano] 49 | end 50 | end 51 | 52 | group 'LIST_MAP' do 53 | assert 'maps function across list and returns a new list' do 54 | incremented = LIST_MAP[ 55 | INDEX_ARRAY.to_linked_list, 56 | 64.to_peano, 57 | ->(x, _) { x + 1 } 58 | ].list_to_a(64) 59 | 60 | incremented == INDEX_ARRAY.map { |x| x + 1 } 61 | end 62 | 63 | assert 'second argument gives current position index' do 64 | empty_board = ([nil] * 64).to_linked_list 65 | given_indexes = LIST_MAP[ 66 | empty_board, 67 | 64.to_peano, 68 | ->(_, i) { i.to_i } 69 | ].list_to_a(64) 70 | 71 | given_indexes == INDEX_ARRAY 72 | end 73 | end 74 | 75 | group 'LIST_REDUCE' do 76 | assert 'passes memo between iterations' do 77 | reduction = LIST_REDUCE[ 78 | INDEX_ARRAY.to_linked_list, 79 | 64.to_peano, 80 | ->(x, _, _) { x + 1 }, 81 | 0 82 | ] 83 | 84 | reduction == INDEX_ARRAY.size 85 | end 86 | 87 | assert 'third argument gives current index' do 88 | empty_board = ([nil] * 64).to_linked_list 89 | reduction = LIST_REDUCE[ 90 | empty_board, 91 | 64.to_peano, 92 | ->(memo, _, i) { memo << i.to_i }, 93 | [] 94 | ] 95 | 96 | reduction == INDEX_ARRAY 97 | end 98 | 99 | assert 'second argument gives current value' do 100 | reduction = LIST_REDUCE[ 101 | INDEX_ARRAY.map(&:succ).to_linked_list, 102 | 64.to_peano, 103 | ->(memo, value, _) { memo << value }, 104 | [] 105 | ] 106 | 107 | reduction == INDEX_ARRAY.map(&:succ) 108 | end 109 | end 110 | end 111 | 112 | group 'Math Functions' do 113 | group 'ADD' do 114 | assert 'adds two numbers together' do 115 | 11 == ADD[8.to_peano, 3.to_peano].to_i 116 | end 117 | 118 | assert 'works with zero' do 119 | 36 == ADD[0.to_peano, 36.to_peano].to_i 120 | end 121 | end 122 | 123 | group 'DECREMENT' do 124 | assert 'subtracts one from the given number' do 125 | 99 == DECREMENT[100.to_peano].to_i 126 | end 127 | 128 | assert 'given zero returns zero' do 129 | 0 == DECREMENT[0.to_peano].to_i 130 | end 131 | end 132 | 133 | group 'SUBTRACT' do 134 | assert 'subtracts the second number from the first number' do 135 | 10 == SUBTRACT[20.to_peano, 10.to_peano].to_i 136 | end 137 | 138 | assert 'floors at zero' do 139 | 0 == SUBTRACT[3.to_peano, 5.to_peano].to_i 140 | end 141 | end 142 | 143 | group 'MULTIPLY' do 144 | assert 'multiplies two numbers together' do 145 | 8 == MULTIPLY[2.to_peano, 4.to_peano].to_i 146 | end 147 | 148 | assert 'works with zero' do 149 | 0 == MULTIPLY[0.to_peano, 23.to_peano].to_i 150 | end 151 | end 152 | end 153 | 154 | group 'Comparison Functions' do 155 | group 'IS_ZERO' do 156 | assert 'returns FIRST when given zero' do 157 | expect_truthy IS_ZERO[0.to_peano] 158 | end 159 | 160 | assert 'returns SECOND when given a non-zero number' do 161 | expect_falsy IS_ZERO[9.to_peano] 162 | end 163 | end 164 | 165 | group 'IS_GREATER_OR_EQUAL' do 166 | assert 'returns FIRST when greater' do 167 | expect_truthy IS_GREATER_OR_EQUAL[2.to_peano, 1.to_peano] 168 | end 169 | 170 | assert 'returns FIRST when equal' do 171 | expect_truthy IS_GREATER_OR_EQUAL[5.to_peano, 5.to_peano] 172 | end 173 | 174 | assert 'returns SECOND when less' do 175 | expect_falsy IS_GREATER_OR_EQUAL[4.to_peano, 9.to_peano] 176 | end 177 | end 178 | 179 | group 'IS_EQUAL' do 180 | assert 'returns FIRST when equal' do 181 | expect_truthy IS_EQUAL[6.to_peano, 6.to_peano] 182 | end 183 | 184 | assert 'returns SECOND when greater' do 185 | expect_falsy IS_EQUAL[7.to_peano, 2.to_peano] 186 | end 187 | 188 | assert 'returns SECOND when less' do 189 | expect_falsy IS_EQUAL[1.to_peano, 4.to_peano] 190 | end 191 | end 192 | end 193 | -------------------------------------------------------------------------------- /expanded/test/test_pieces.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Colin Fulton 2 | # All rights reserved. 3 | # 4 | # This software may be modified and distributed under the 5 | # terms of the three-clause BSD license. See LICENSE.txt 6 | 7 | require_relative './setup' 8 | 9 | group 'Piece Functions' do 10 | REAL_ROOK_RULE = ROOK_RULE[GET_RULE] 11 | REAL_BISHOP_RULE = BISHOP_RULE[GET_RULE] 12 | REAL_QUEEN_RULE = QUEEN_RULE[GET_RULE] 13 | REAL_KING_RULE = KING_RULE[GET_RULE] 14 | REAL_KNIGHT_RULE = KNIGHT_RULE[GET_RULE] 15 | REAL_PAWN_RULE = PAWN_RULE[GET_RULE] 16 | 17 | FROM_POSITION = position(4, 4) 18 | 19 | NOTHING_SURROUNDING_BLACK = [[0, 0, 0, 0, 0, 0, 0, 0], 20 | [0, 0, 0, 0, 0, 0, 0, 0], 21 | [0, 0, 0, 0, 0, 0, 0, 0], 22 | [0, 0, 0, 0, 0, 0, 0, 0], 23 | [0, 0, 0, 0, BQ,0, 0, 0], 24 | [0, 0, 0, 0, 0, 0, 0, 0], 25 | [0, 0, 0, 0, 0, 0, 0, 0], 26 | [0, 0, 0, 0, 0, 0, 0, 0]] 27 | .to_board 28 | 29 | NOTHING_SURROUNDING_WHITE = [[0, 0, 0, 0, 0, 0, 0, 0], 30 | [0, 0, 0, 0, 0, 0, 0, 0], 31 | [0, 0, 0, 0, 0, 0, 0, 0], 32 | [0, 0, 0, 0, 0, 0, 0, 0], 33 | [0, 0, 0, 0, WQ,0, 0, 0], 34 | [0, 0, 0, 0, 0, 0, 0, 0], 35 | [0, 0, 0, 0, 0, 0, 0, 0], 36 | [0, 0, 0, 0, 0, 0, 0, 0]] 37 | .to_board 38 | 39 | BLACK_KING_IN_DANGER = [[0, 0, 0, 0, 0, 0, 0, 0], 40 | [0, 0, 0, 0, 0, 0, 0, 0], 41 | [0, 0, 0, 0, 0, 0, 0, 0], 42 | [0, 0, 0, 0, 0, 0, 0, 0], 43 | [WR,0, 0, 0, BQ,0, 0,BK], 44 | [0, 0, 0, 0, 0, 0, 0, 0], 45 | [0, 0, 0, 0, 0, 0, 0, 0], 46 | [0, 0, 0, 0, 0, 0, 0, 0]] 47 | .to_board 48 | 49 | WHITE_KING_IN_DANGER = [[0, 0, 0, 0, 0, 0, 0, 0], 50 | [0, 0, 0, 0, 0, 0, 0, 0], 51 | [0, 0, 0, 0, 0, 0, 0, 0], 52 | [0, 0, 0, 0, 0, 0, 0, 0], 53 | [BR,0, 0, 0, WQ,0, 0,WK], 54 | [0, 0, 0, 0, 0, 0, 0, 0], 55 | [0, 0, 0, 0, 0, 0, 0, 0], 56 | [0, 0, 0, 0, 0, 0, 0, 0]] 57 | .to_board 58 | 59 | SURROUNDED = [[0, 0, 0, 0, 0, 0, 0, 0], 60 | [0, 0, 0, 0, 0, 0, 0, 0], 61 | [0, 0, 0, 0, 0, 0, 0, 0], 62 | [0, 0, 0, WQ,WQ,WQ,0, 0], 63 | [0, 0, 0, WQ,BQ,WQ,0, 0], 64 | [0, 0, 0, WQ,WQ,WQ,0, 0], 65 | [0, 0, 0, 0, 0, 0, 0, 0], 66 | [0, 0, 0, 0, 0, 0, 0, 0]] 67 | .to_board 68 | 69 | ALL_WHITE = Array.new(8, Array.new(8, MWQ)).to_board 70 | ALL_BLACK = Array.new(8, Array.new(8, MBQ)).to_board 71 | 72 | def run_rule rule, board, from, to, last_from = NULL_POS, last_to = NULL_POS 73 | rule[CREATE_STATE[from, to, last_from, last_to, board, ZERO, ZERO]] 74 | end 75 | 76 | def test_movement board, is_valid, rule, delta_x, delta_y 77 | result = run_rule( 78 | rule, 79 | board, 80 | FROM_POSITION, 81 | shift_position(FROM_POSITION, delta_y, delta_x) 82 | ) 83 | 84 | if is_valid 85 | expect_valid result 86 | else 87 | expect_invalid result 88 | end 89 | end 90 | 91 | def horizontal_movement board, delta, is_valid, rule 92 | group "can#{' not' unless is_valid}" do 93 | assert 'left' do 94 | test_movement board, is_valid, rule, -delta, 0 95 | end 96 | 97 | assert 'right' do 98 | test_movement board, is_valid, rule, delta, 0 99 | end 100 | 101 | assert 'up' do 102 | test_movement board, is_valid, rule, 0, delta 103 | end 104 | 105 | assert 'down' do 106 | test_movement board, is_valid, rule, 0, -delta 107 | end 108 | end 109 | end 110 | 111 | def diagonal_movement board, delta, is_valid, rule 112 | group "can#{' not' unless is_valid}" do 113 | assert 'up + left' do 114 | test_movement board, is_valid, rule, -delta, delta 115 | end 116 | 117 | assert 'up + right' do 118 | test_movement board, is_valid, rule, delta, delta 119 | end 120 | 121 | assert 'down + left' do 122 | test_movement board, is_valid, rule, -delta, -delta 123 | end 124 | 125 | assert 'down + right' do 126 | test_movement board, is_valid, rule, -delta, -delta 127 | end 128 | end 129 | end 130 | 131 | def cannot_cause_check rule, delta_x, delta_y 132 | group 'cannot move putting own king in check' do 133 | assert 'white' do 134 | expect_invalid( 135 | run_rule( 136 | rule, 137 | WHITE_KING_IN_DANGER, 138 | FROM_POSITION, 139 | shift_position(FROM_POSITION, delta_x, -delta_y) 140 | ) 141 | ) 142 | end 143 | 144 | assert 'black' do 145 | expect_invalid( 146 | run_rule( 147 | rule, 148 | BLACK_KING_IN_DANGER, 149 | FROM_POSITION, 150 | shift_position(FROM_POSITION, delta_x, delta_y) 151 | ) 152 | ) 153 | end 154 | end 155 | end 156 | 157 | def capturing_basics rule, delta_x, delta_y 158 | group 'can not capture self' do 159 | assert 'white' do 160 | expect_invalid( 161 | run_rule( 162 | rule, 163 | NOTHING_SURROUNDING_WHITE, 164 | FROM_POSITION, 165 | FROM_POSITION 166 | ) 167 | ) 168 | end 169 | 170 | assert 'black' do 171 | expect_invalid( 172 | run_rule( 173 | rule, 174 | NOTHING_SURROUNDING_BLACK, 175 | FROM_POSITION, 176 | FROM_POSITION 177 | ) 178 | ) 179 | end 180 | end 181 | 182 | group 'can not capture own color' do 183 | def test_own_capture rule, color, delta_y, delta_x 184 | direction, piece, board = case color 185 | when :black 186 | [1, BP, NOTHING_SURROUNDING_BLACK] 187 | when :white 188 | [-1, WP, NOTHING_SURROUNDING_WHITE] 189 | end 190 | 191 | to = shift_position(FROM_POSITION, delta_y, direction * delta_x) 192 | board = board.list_to_a(8).map { |row| row.list_to_a(8) } 193 | 194 | board[RIGHT[to].to_i][LEFT[to].to_i] = piece 195 | board = board.to_board 196 | 197 | expect_invalid( 198 | run_rule( 199 | rule, 200 | board, 201 | FROM_POSITION, 202 | to 203 | ) 204 | ) 205 | end 206 | 207 | assert('black') { test_own_capture rule, :black, delta_y, delta_x } 208 | assert('white') { test_own_capture rule, :white, delta_y, delta_x } 209 | end 210 | end 211 | 212 | def check_check 213 | assert 'returns FIRST with nothing around' do 214 | example_board = [[0, 0, 0, 0, 0, 0, 0, 0], 215 | [0, 0, 0, 0, 0, 0, 0, 0], 216 | [0, 0, 0, 0, 0, 0, 0, 0], 217 | [0, 0, 0, 0, 0, 0, 0, 0], 218 | [0, 0, 0, 0, BK,0, 0, 0], 219 | [0, 0, 0, 0, 0, 0, 0, 0], 220 | [0, 0, 0, 0, 0, 0, 0, 0], 221 | [0, 0, 0, 0, 0, 0, 0, 0]] 222 | .to_board 223 | 224 | expect_truthy yield( 225 | example_board, 226 | position(4, 4), 227 | position(4, 5) 228 | ) 229 | end 230 | 231 | assert 'returns SECOND when moving into check' do 232 | example_board = [[0, 0, 0, 0, 0, 0, 0, 0], 233 | [0, 0, 0, 0, 0, 0, 0, 0], 234 | [0, 0, 0, 0, 0, 0, 0, 0], 235 | [0, 0, 0, 0, 0, 0, 0, 0], 236 | [0, 0, 0, 0, MBK,0, 0, 0], 237 | [0, 0, MWP,0, 0, 0, 0, 0], 238 | [0, 0, 0, 0, 0, 0, 0, 0], 239 | [0, 0, 0, 0, 0, 0, 0, 0]] 240 | .to_board 241 | 242 | expect_falsy yield( 243 | example_board, 244 | position(4, 4), 245 | position(3, 4) 246 | ) 247 | end 248 | 249 | assert 'returns FIRST when moving out of check' do 250 | example_board = [[0, 0, 0, 0, 0, 0, 0, 0], 251 | [0, 0, 0, 0, 0, 0, 0, 0], 252 | [0, 0, 0, 0, 0, 0, 0, 0], 253 | [0, 0, 0, 0, 0, 0, 0, 0], 254 | [0, 0, 0, 0, BK,0, 0, 0], 255 | [0, 0, 0, 0, WR,0, 0, 0], 256 | [0, 0, 0, 0, 0, 0, 0, 0], 257 | [0, 0, 0, 0, 0, 0, 0, 0]] 258 | .to_board 259 | 260 | expect_truthy yield( 261 | example_board, 262 | position(4, 4), 263 | position(3, 4) 264 | ) 265 | end 266 | 267 | assert 'check can not come from own color' do 268 | example_board = [[0, 0, 0, 0, 0, 0, 0, 0], 269 | [0, 0, 0, 0, 0, 0, 0, 0], 270 | [0, 0, 0, 0, 0, 0, 0, 0], 271 | [0, 0, 0, 0, 0, 0, 0, 0], 272 | [0, 0, 0, 0, BK,0, 0, 0], 273 | [0, 0, 0, BR,0, 0, 0, 0], 274 | [0, 0, 0, 0, 0, 0, 0, 0], 275 | [0, 0, 0, 0, 0, 0, 0, 0]] 276 | .to_board 277 | 278 | expect_truthy yield( 279 | example_board, 280 | position(4, 4), 281 | position(3, 4) 282 | ) 283 | end 284 | 285 | assert 'returns FIRST when moving near but not into check' do 286 | example_board = [[0, 0, 0, 0, 0, 0, 0, 0], 287 | [0, 0, 0, 0, 0, 0, 0, 0], 288 | [0, 0, 0, 0, 0, 0, 0, 0], 289 | [0, 0, 0, 0, 0, 0, 0, 0], 290 | [0, 0, 0, 0, BK,0, 0, 0], 291 | [0, WR,0, 0, 0, 0, 0, 0], 292 | [0, 0, 0, 0, 0, 0, 0, 0], 293 | [0, 0, 0, 0, 0, 0, 0, 0]] 294 | .to_board 295 | 296 | expect_truthy yield( 297 | example_board, 298 | position(4, 4), 299 | position(3, 4) 300 | ) 301 | end 302 | end 303 | 304 | group 'ROOK_RULE' do 305 | capturing_basics REAL_ROOK_RULE, 0, 1 306 | cannot_cause_check REAL_ROOK_RULE, 0, 1 307 | horizontal_movement NOTHING_SURROUNDING_BLACK, 3, true, REAL_ROOK_RULE 308 | diagonal_movement NOTHING_SURROUNDING_BLACK, 3, false, REAL_ROOK_RULE 309 | 310 | group 'if a piece is in the way' do 311 | horizontal_movement SURROUNDED, 3, false, REAL_ROOK_RULE 312 | end 313 | end 314 | 315 | group 'BISHOP_RULE' do 316 | capturing_basics REAL_BISHOP_RULE, 1, 1 317 | cannot_cause_check REAL_BISHOP_RULE, 1, 1 318 | horizontal_movement NOTHING_SURROUNDING_BLACK, 3, false, REAL_BISHOP_RULE 319 | diagonal_movement NOTHING_SURROUNDING_BLACK, 3, true, REAL_BISHOP_RULE 320 | 321 | group 'if a piece is in the way' do 322 | diagonal_movement SURROUNDED, 3, false, REAL_BISHOP_RULE 323 | end 324 | end 325 | 326 | group 'QUEEN_RULE' do 327 | capturing_basics REAL_QUEEN_RULE, 1, 1 328 | cannot_cause_check REAL_QUEEN_RULE, 1, 1 329 | horizontal_movement NOTHING_SURROUNDING_BLACK, 3, true, REAL_QUEEN_RULE 330 | diagonal_movement NOTHING_SURROUNDING_BLACK, 3, true, REAL_QUEEN_RULE 331 | 332 | group 'if a piece is in the way' do 333 | diagonal_movement SURROUNDED, 3, false, REAL_QUEEN_RULE 334 | horizontal_movement SURROUNDED, 3, false, REAL_QUEEN_RULE 335 | end 336 | 337 | assert 'cannot move arbitrarily' do 338 | expect_invalid( 339 | run_rule( 340 | REAL_QUEEN_RULE, 341 | NOTHING_SURROUNDING_BLACK, 342 | FROM_POSITION, 343 | shift_position(FROM_POSITION, -1, 3), 344 | NULL_POS, 345 | NULL_POS 346 | ) 347 | ) 348 | end 349 | end 350 | 351 | group 'IS_NOT_IN_CHECK' do 352 | check_check do |board, from, to| 353 | IS_NOT_IN_CHECK[NORMAL_MOVE[board, from, to, ZERO], to, GET_RULE] 354 | end 355 | 356 | assert 'allows moving into same position with own pieces nearby' do 357 | to = position(4, 2) 358 | board = [[0, 0, 0, 0, 0, 0, 0, 0], 359 | [0, 0, 0, 0, 0, 0, 0, 0], 360 | [BR,0, 0, 0, BK,0, 0, 0], 361 | [0, 0, 0, 0, 0, 0, 0, 0], 362 | [0, 0, 0, 0, 0, 0, 0, 0], 363 | [0, 0, 0, 0, 0, 0, 0, 0], 364 | [0, 0, 0, 0, 0, 0, 0, 0], 365 | [0, 0, 0, 0, 0, 0, 0, 0]] 366 | .to_board 367 | 368 | expect_truthy IS_NOT_IN_CHECK[NORMAL_MOVE[board, position(4, 2), to, ZERO], to, GET_RULE] 369 | end 370 | end 371 | 372 | group 'KING_RULE' do 373 | capturing_basics REAL_KING_RULE, 1, 0 374 | horizontal_movement NOTHING_SURROUNDING_BLACK, 1, true, REAL_KING_RULE 375 | diagonal_movement NOTHING_SURROUNDING_BLACK, 1, true, REAL_KING_RULE 376 | 377 | check_check do |board, from, to| 378 | run_rule(REAL_KING_RULE, board, from, to)[ 379 | FIRST, 380 | SECOND, 381 | SECOND, 382 | SECOND, 383 | SECOND 384 | ] 385 | end 386 | 387 | assert 'cannot move more than one' do 388 | expect_invalid( 389 | run_rule( 390 | REAL_KING_RULE, 391 | NOTHING_SURROUNDING_BLACK, 392 | FROM_POSITION, 393 | shift_position(FROM_POSITION, 2, 0) 394 | ) 395 | ) 396 | end 397 | 398 | assert 'cannot move arbitrarily' do 399 | expect_invalid( 400 | run_rule( 401 | REAL_KING_RULE, 402 | NOTHING_SURROUNDING_BLACK, 403 | FROM_POSITION, 404 | shift_position(FROM_POSITION, 2, 1) 405 | ) 406 | ) 407 | end 408 | 409 | assert 'cannot move into check' do 410 | near_check = [[0, 0, 0, 0, 0, 0, 0, 0], 411 | [0, 0, 0, 0, 0, 0, 0, 0], 412 | [0, 0, 0, 0, 0, 0, 0, 0], 413 | [0, 0, 0, 0, 0, 0, 0, 0], 414 | [0, 0, 0, 0, BK,0, 0, 0], 415 | [0, 0, WQ,0, 0, 0, 0, 0], 416 | [0, 0, 0, 0, 0, 0, 0, 0], 417 | [0, 0, 0, 0, 0, 0, 0, 0]] 418 | .to_board 419 | 420 | expect_invalid( 421 | run_rule( 422 | REAL_KING_RULE, 423 | near_check, 424 | FROM_POSITION, 425 | shift_position(FROM_POSITION, -1, 0) 426 | ) 427 | ) 428 | end 429 | 430 | group 'castling' do 431 | castle_result = proc { |result| result == CASTLE } 432 | invalid_result = proc { |result| result == INVALID } 433 | rule_proc = proc { |board, from, to| 434 | run_rule(REAL_KING_RULE, board, from, to, NULL_POS, NULL_POS) 435 | } 436 | 437 | group 'is valid' do 438 | board = [[BR,0, 0, 0, BK,0, 0, BR], 439 | [0, 0, 0, 0, 0, 0, 0, 0], 440 | [0, 0, 0, 0, 0, 0, 0, 0], 441 | [0, 0, 0, 0, 0, 0, 0, 0], 442 | [0, 0, 0, 0, 0, 0, 0, 0], 443 | [0, 0, 0, 0, 0, 0, 0, 0], 444 | [0, 0, 0, 0, 0, 0, 0, 0], 445 | [WR,0, 0, 0, WK,0, 0, WR]] 446 | .to_board 447 | 448 | test_castling board, board, perform: rule_proc, expect: castle_result 449 | end 450 | 451 | group 'is invalid when' do 452 | group 'when positions are off' do 453 | board = [[BR,0, 0, 0, BK,0, 0, BR], 454 | [0, 0, 0, 0, 0, 0, 0, 0], 455 | [0, 0, 0, 0, 0, 0, 0, 0], 456 | [0, 0, 0, 0, 0, 0, 0, 0], 457 | [0, 0, 0, 0, 0, 0, 0, 0], 458 | [0, 0, 0, 0, 0, 0, 0, 0], 459 | [0, 0, 0, 0, 0, 0, 0, 0], 460 | [WR,0, 0, 0, WK,0, 0, WR]] 461 | .to_board 462 | 463 | group 'horizontally' do 464 | test_castling board, board, 465 | perform: proc { |board, from, to| 466 | amount = LEFT[from].to_i > LEFT[to].to_i ? 1 : -1 467 | 468 | run_rule( 469 | REAL_KING_RULE, 470 | board, 471 | shift_position(from, amount, 0), 472 | to 473 | ) 474 | }, 475 | expect: invalid_result 476 | end 477 | 478 | group 'vertically' do 479 | test_castling board, board, 480 | perform: proc { |board, from, to| 481 | amount = IS_BLACK[GET_POSITION[board, from]][1, -1] 482 | 483 | run_rule( 484 | REAL_KING_RULE, 485 | board, 486 | shift_position(from, 0, amount), 487 | to 488 | ) 489 | }, 490 | expect: invalid_result 491 | end 492 | end 493 | 494 | group 'path is blocked' do 495 | board = [[BR,0, BP,0, BK,0, BP,BR], 496 | [0, 0, 0, 0, 0, 0, 0, 0], 497 | [0, 0, 0, 0, 0, 0, 0, 0], 498 | [0, 0, 0, 0, 0, 0, 0, 0], 499 | [0, 0, 0, 0, 0, 0, 0, 0], 500 | [0, 0, 0, 0, 0, 0, 0, 0], 501 | [0, 0, 0, 0, 0, 0, 0, 0], 502 | [WR,0, WP,0, WK,0, WP,WR]] 503 | .to_board 504 | 505 | test_castling board, board, perform: rule_proc, expect: invalid_result 506 | end 507 | 508 | group 'king has moved' do 509 | board = [[BR,0, 0, 0, MBK,0, 0, BR], 510 | [0, 0, 0, 0, 0, 0, 0, 0], 511 | [0, 0, 0, 0, 0, 0, 0, 0], 512 | [0, 0, 0, 0, 0, 0, 0, 0], 513 | [0, 0, 0, 0, 0, 0, 0, 0], 514 | [0, 0, 0, 0, 0, 0, 0, 0], 515 | [0, 0, 0, 0, 0, 0, 0, 0], 516 | [WR,0, 0, 0, MWK,0, 0, WR]] 517 | .to_board 518 | 519 | test_castling board, board, perform: rule_proc, expect: invalid_result 520 | end 521 | 522 | group 'rook has moved' do 523 | board = [[MBR,0, 0, 0, BK,0, 0, MBR], 524 | [0, 0, 0, 0, 0, 0, 0, 0], 525 | [0, 0, 0, 0, 0, 0, 0, 0], 526 | [0, 0, 0, 0, 0, 0, 0, 0], 527 | [0, 0, 0, 0, 0, 0, 0, 0], 528 | [0, 0, 0, 0, 0, 0, 0, 0], 529 | [0, 0, 0, 0, 0, 0, 0, 0], 530 | [MWR,0, 0, 0, WK,0, 0, MWR]] 531 | .to_board 532 | 533 | test_castling board, board, perform: rule_proc, expect: invalid_result 534 | end 535 | 536 | group 'king is in check' do 537 | black_board = [[BR,0, 0, 0, BK,0, 0, BR], 538 | [0, 0, 0, 0, WR,0, 0, 0], 539 | [0, 0, 0, 0, 0, 0, 0, 0], 540 | [0, 0, 0, 0, 0, 0, 0, 0], 541 | [0, 0, 0, 0, 0, 0, 0, 0], 542 | [0, 0, 0, 0, 0, 0, 0, 0], 543 | [0, 0, 0, 0, 0, 0, 0, 0], 544 | [0, 0, 0, 0, 0, 0, 0, 0] ] 545 | .to_board 546 | 547 | white_board = [[0, 0, 0, 0, 0, 0, 0, 0], 548 | [0, 0, 0, 0, 0, 0, 0, 0], 549 | [0, 0, 0, 0, 0, 0, 0, 0], 550 | [0, 0, 0, 0, 0, 0, 0, 0], 551 | [0, 0, 0, 0, 0, 0, 0, 0], 552 | [0, 0, 0, 0, 0, 0, 0, 0], 553 | [0, 0, 0, 0, BR,0, 0, 0], 554 | [WR,0, 0, 0, WK,0, 0, WR]] 555 | .to_board 556 | 557 | test_castling black_board, white_board, perform: rule_proc, expect: invalid_result 558 | end 559 | 560 | group 'king is moving into check' do 561 | board = [[BR,0, 0, 0, BK,0, 0, BR], 562 | [0, 0, WR,0, 0, 0, WR,0], 563 | [0, 0, 0, 0, 0, 0, 0, 0], 564 | [0, 0, 0, 0, 0, 0, 0, 0], 565 | [0, 0, 0, 0, 0, 0, 0, 0], 566 | [0, 0, 0, 0, 0, 0, 0, 0], 567 | [0, 0, BR,0, 0, 0, BR,0], 568 | [WR,0, 0, 0, WK,0, 0, WR]] 569 | .to_board 570 | 571 | test_castling board, board, perform: rule_proc, expect: invalid_result 572 | end 573 | 574 | group 'king is moving past check' do 575 | board = [[BR,0, 0, 0, BK,0, 0, BR], 576 | [0, 0, 0, WR,0, WR,0, 0], 577 | [0, 0, 0, 0, 0, 0, 0, 0], 578 | [0, 0, 0, 0, 0, 0, 0, 0], 579 | [0, 0, 0, 0, 0, 0, 0, 0], 580 | [0, 0, 0, 0, 0, 0, 0, 0], 581 | [0, 0, 0, BR,0, BR,0, 0], 582 | [WR,0, 0, 0, WK,0, 0, WR]] 583 | .to_board 584 | 585 | test_castling board, board, perform: rule_proc, expect: invalid_result 586 | end 587 | 588 | assert 'moving wildly' do 589 | board = [[BR,0, 0, 0, BK,0, 0, BR], 590 | [0, 0, 0, 0, 0, 0, 0, 0], 591 | [0, 0, 0, 0, 0, 0, 0, 0], 592 | [BP,BP,BP,BP,BP,BP,BP,BP], 593 | [0, 0, 0, 0, 0, 0, 0, 0], 594 | [0, 0, 0, 0, 0, 0, 0, 0], 595 | [0, 0, 0, 0, 0, 0, 0, 0], 596 | [WR,0, 0, 0, 0, 0, WK, WR]] 597 | .to_board 598 | 599 | expect_invalid run_rule(REAL_KING_RULE, board, position(4, 0), position(0, 7)) 600 | end 601 | end 602 | end 603 | end 604 | 605 | group 'KNIGHT_RULE' do 606 | def knights_moves board 607 | assert 'can make knights moves' do 608 | [ 609 | [ 2, 1], [ 1, 2], 610 | [-2, 1], [-1, 2], 611 | [ 2, -1], [ 1, -2], 612 | [-2, -1], [-1, -2] 613 | ].map do |shifts| 614 | shift_position(FROM_POSITION, *shifts) 615 | end 616 | .all? do |valid_move| 617 | expect_valid( 618 | run_rule( 619 | REAL_KNIGHT_RULE, 620 | board, 621 | FROM_POSITION, 622 | valid_move 623 | ) 624 | ) 625 | end 626 | end 627 | end 628 | 629 | capturing_basics REAL_KNIGHT_RULE, 1, 2 630 | cannot_cause_check REAL_KNIGHT_RULE, 1, 2 631 | knights_moves NOTHING_SURROUNDING_BLACK 632 | 633 | group 'if a piece is in the way' do 634 | knights_moves SURROUNDED 635 | end 636 | 637 | assert 'cannot move arbitrarily' do 638 | expect_invalid( 639 | run_rule( 640 | REAL_KNIGHT_RULE, 641 | NOTHING_SURROUNDING_BLACK, 642 | FROM_POSITION, 643 | shift_position(FROM_POSITION, -1, 1) 644 | ) 645 | ) 646 | end 647 | end 648 | 649 | group 'PAWN_RULE' do 650 | starting_board = [[0, 0, 0, 0, 0, 0, 0, 0], 651 | [BP,BP,BP,BP,BP,BP,BP,BP], 652 | [0, 0, 0, 0, 0, 0, 0, 0], 653 | [0, 0, 0, 0, 0, 0, 0, 0], 654 | [0, 0, 0, 0, 0, 0, 0, 0], 655 | [0, 0, 0, 0, 0, 0, 0, 0], 656 | [WP,WP,WP,WP,WP,WP,WP,WP], 657 | [0, 0, 0, 0, 0, 0, 0, 0]] 658 | .to_board 659 | 660 | capturing_basics REAL_PAWN_RULE, 1, 1 661 | cannot_cause_check REAL_PAWN_RULE, 0, 1 662 | 663 | group 'can move forward by one' do 664 | assert 'white' do 665 | expect_valid( 666 | run_rule( 667 | REAL_PAWN_RULE, 668 | starting_board, 669 | position(4, 6), 670 | position(4, 5) 671 | ) 672 | ) 673 | end 674 | 675 | assert 'black' do 676 | expect_valid( 677 | run_rule( 678 | REAL_PAWN_RULE, 679 | starting_board, 680 | position(1, 1), 681 | position(1, 2) 682 | ) 683 | ) 684 | end 685 | end 686 | 687 | group 'can move forward by two on the first move' do 688 | assert 'white' do 689 | expect_valid( 690 | run_rule( 691 | REAL_PAWN_RULE, 692 | starting_board, 693 | position(4, 6), 694 | position(4, 4) 695 | ) 696 | ) 697 | end 698 | 699 | assert 'black' do 700 | expect_valid( 701 | run_rule( 702 | REAL_PAWN_RULE, 703 | starting_board, 704 | position(1, 1), 705 | position(1, 3) 706 | ) 707 | ) 708 | end 709 | end 710 | 711 | group 'cannot move forward by two on subsequent moves' do 712 | later_board = [[0, 0, 0, 0, 0, 0, 0, 0], 713 | [0, 0, 0, 0, 0, 0, 0, 0], 714 | [0, MBP,0, 0, 0, 0, 0, 0], 715 | [0, 0, 0, 0, 0, 0, 0, 0], 716 | [0, 0, 0, 0, 0, 0, 0, 0], 717 | [0, 0, 0, 0, MWP,0, 0, 0], 718 | [0, 0, 0, 0, 0, 0, 0, 0], 719 | [0, 0, 0, 0, 0, 0, 0, 0]] 720 | .to_board 721 | 722 | assert 'white' do 723 | expect_invalid( 724 | run_rule( 725 | REAL_PAWN_RULE, 726 | later_board, 727 | position(4, 5), 728 | position(4, 3) 729 | ) 730 | ) 731 | end 732 | 733 | assert 'black' do 734 | expect_invalid( 735 | run_rule( 736 | REAL_PAWN_RULE, 737 | later_board, 738 | position(1, 2), 739 | position(1, 4) 740 | ) 741 | ) 742 | end 743 | end 744 | 745 | group 'cannot move backwards' do 746 | assert 'white' do 747 | expect_invalid( 748 | run_rule( 749 | REAL_PAWN_RULE, 750 | starting_board, 751 | position(4, 6), 752 | position(4, 7) 753 | ) 754 | ) 755 | end 756 | 757 | assert 'black' do 758 | expect_invalid( 759 | run_rule( 760 | REAL_PAWN_RULE, 761 | starting_board, 762 | position(1, 1), 763 | position(1, 0) 764 | ) 765 | ) 766 | end 767 | end 768 | 769 | group 'cannot move diagonally without capturing' do 770 | assert 'white' do 771 | expect_invalid( 772 | run_rule( 773 | REAL_PAWN_RULE, 774 | starting_board, 775 | position(4, 6), 776 | position(5, 5) 777 | ) 778 | ) 779 | end 780 | 781 | assert 'black' do 782 | expect_invalid( 783 | run_rule( 784 | REAL_PAWN_RULE, 785 | starting_board, 786 | position(1, 1), 787 | position(0, 2) 788 | ) 789 | ) 790 | end 791 | end 792 | 793 | group 'cannot move sideways' do 794 | assert 'white' do 795 | expect_invalid( 796 | run_rule( 797 | REAL_PAWN_RULE, 798 | starting_board, 799 | position(4, 6), 800 | position(5, 6) 801 | ) 802 | ) 803 | end 804 | 805 | assert 'black' do 806 | expect_invalid( 807 | run_rule( 808 | REAL_PAWN_RULE, 809 | starting_board, 810 | position(1, 1), 811 | position(0, 1) 812 | ) 813 | ) 814 | end 815 | end 816 | 817 | group 'cannot capture sideways' do 818 | sideways_capture_board = [[0, 0, 0, 0, 0, 0, 0, 0], 819 | [WP,BP,0, 0, 0, 0, 0, 0], 820 | [0, 0, 0, 0, 0, 0, 0, 0], 821 | [0, 0, 0, 0, 0, 0, 0, 0], 822 | [0, 0, 0, 0, 0, 0, 0, 0], 823 | [0, 0, 0, 0, 0, 0, 0, 0], 824 | [0, 0, 0, 0, WP,BP,0, 0], 825 | [0, 0, 0, 0, 0, 0, 0, 0]] 826 | .to_board 827 | 828 | assert 'white' do 829 | expect_invalid( 830 | run_rule( 831 | REAL_PAWN_RULE, 832 | sideways_capture_board, 833 | position(4, 6), 834 | position(5, 6) 835 | ) 836 | ) 837 | end 838 | 839 | assert 'black' do 840 | expect_invalid( 841 | run_rule( 842 | REAL_PAWN_RULE, 843 | sideways_capture_board, 844 | position(1, 1), 845 | position(0, 1) 846 | ) 847 | ) 848 | end 849 | end 850 | 851 | group 'can capture forward diagonally' do 852 | capture_board = [[0, 0, 0, 0, 0, 0, 0, 0], 853 | [0, 0, 0, 0, 0, 0, 0, 0], 854 | [0, MBP,0, 0, 0, 0, 0, 0], 855 | [MWP,0, 0, 0, 0, 0, 0, 0], 856 | [0, 0, 0, 0, 0, MBP,0, 0], 857 | [0, 0, 0, 0, MWP,0, 0, 0], 858 | [0, 0, 0, 0, 0, 0, 0, 0], 859 | [0, 0, 0, 0, 0, 0, 0, 0]] 860 | .to_board 861 | 862 | assert 'white' do 863 | expect_valid( 864 | run_rule( 865 | REAL_PAWN_RULE, 866 | capture_board, 867 | position(4, 5), 868 | position(5, 4) 869 | ) 870 | ) 871 | end 872 | 873 | assert 'black' do 874 | expect_valid( 875 | run_rule( 876 | REAL_PAWN_RULE, 877 | capture_board, 878 | position(1, 2), 879 | position(0, 3) 880 | ) 881 | ) 882 | end 883 | end 884 | 885 | group 'cannot capture backwards diagonally' do 886 | capture_board = [[0, 0, 0, 0, 0, 0, 0, 0], 887 | [MWP,0, 0, 0, 0, 0, 0, 0], 888 | [0, MBP,0, 0, 0, 0, 0, 0], 889 | [0, 0, 0, 0, 0, 0, 0, 0], 890 | [0, 0, 0, 0, 0, 0, 0, 0], 891 | [0, 0, 0, 0, MWP,0, 0, 0], 892 | [0, 0, 0, 0, 0, MBP,0, 0], 893 | [0, 0, 0, 0, 0, 0, 0, 0]] 894 | .to_board 895 | 896 | assert 'white' do 897 | expect_invalid( 898 | run_rule( 899 | REAL_PAWN_RULE, 900 | capture_board, 901 | position(4, 5), 902 | position(5, 6) 903 | ) 904 | ) 905 | end 906 | 907 | assert 'black' do 908 | expect_invalid( 909 | run_rule( 910 | REAL_PAWN_RULE, 911 | capture_board, 912 | position(1, 2), 913 | position(0, 1) 914 | ) 915 | ) 916 | end 917 | end 918 | 919 | en_passant_board = [[0, 0, 0, 0, 0, 0, 0, 0], 920 | [0, 0, 0, 0, 0, 0, 0, 0], 921 | [0, 0, 0, 0, 0, 0, 0, 0], 922 | [0, MBP,WP,0, 0, 0, 0, 0], 923 | [0, 0, 0, BP,MWP,0, 0, 0], 924 | [0, 0, 0, 0, 0, 0, 0, 0], 925 | [0, 0, 0, 0, 0, 0, 0, 0], 926 | [0, 0, 0, 0, 0, 0, 0, 0]] 927 | .to_board 928 | 929 | group 'can capture en passant' do 930 | assert 'white' do 931 | expect_en_passant( 932 | run_rule( 933 | REAL_PAWN_RULE, 934 | en_passant_board, 935 | position(2, 3), 936 | position(1, 2), 937 | position(1, 1), 938 | position(1, 3) 939 | ) 940 | ) 941 | end 942 | 943 | assert 'black' do 944 | expect_en_passant( 945 | run_rule( 946 | REAL_PAWN_RULE, 947 | en_passant_board, 948 | position(3, 4), 949 | position(4, 5), 950 | position(4, 6), 951 | position(4, 4) 952 | ) 953 | ) 954 | end 955 | 956 | group 'only if last moved was a pawn' do 957 | non_passant_board = [[0, 0, 0, 0, 0, 0, 0, 0], 958 | [0, 0, 0, 0, 0, 0, 0, 0], 959 | [0, 0, 0, 0, 0, 0, 0, 0], 960 | [0, BQ,WP,0, 0, 0, 0, 0], 961 | [0, 0, 0, BP,WQ,0, 0, 0], 962 | [0, 0, 0, 0, 0, 0, 0, 0], 963 | [0, 0, 0, 0, 0, 0, 0, 0], 964 | [0, 0, 0, 0, 0, 0, 0, 0]] 965 | .to_board 966 | 967 | assert 'white' do 968 | expect_invalid( 969 | run_rule( 970 | REAL_PAWN_RULE, 971 | non_passant_board, 972 | position(2, 3), 973 | position(1, 2), 974 | position(1, 1), 975 | position(1, 3) 976 | ) 977 | ) 978 | end 979 | 980 | assert 'black' do 981 | expect_invalid( 982 | run_rule( 983 | REAL_PAWN_RULE, 984 | non_passant_board, 985 | position(3, 4), 986 | position(4, 5), 987 | position(4, 6), 988 | position(4, 4) 989 | ) 990 | ) 991 | end 992 | end 993 | 994 | group 'only if the last moved pawn moved two' do 995 | assert 'white' do 996 | expect_invalid( 997 | run_rule( 998 | REAL_PAWN_RULE, 999 | en_passant_board, 1000 | position(2, 3), 1001 | position(1, 2), 1002 | position(1, 2), 1003 | position(1, 3) 1004 | ) 1005 | ) 1006 | end 1007 | 1008 | assert 'black' do 1009 | expect_invalid( 1010 | run_rule( 1011 | REAL_PAWN_RULE, 1012 | en_passant_board, 1013 | position(3, 4), 1014 | position(4, 5), 1015 | position(4, 5), 1016 | position(4, 4) 1017 | ) 1018 | ) 1019 | end 1020 | end 1021 | end 1022 | end 1023 | end 1024 | -------------------------------------------------------------------------------- /expanded/test/test_play.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Colin Fulton 2 | # All rights reserved. 3 | # 4 | # This software may be modified and distributed under the 5 | # terms of the three-clause BSD license. See LICENSE.txt 6 | 7 | require_relative './setup' 8 | 9 | group 'Play' do 10 | def same_board board_a, board_b 11 | board_a.board_to_a == board_b.board_to_a 12 | end 13 | 14 | def safe_position_to_a object 15 | object.is_a?(Array) ? object : object.position_to_a 16 | end 17 | 18 | def same_position position_a, position_b 19 | safe_position_to_a(position_a) == safe_position_to_a(position_b) 20 | end 21 | 22 | def return_type type 23 | proc { |new_state| { state: new_state, type: type } } 24 | end 25 | 26 | def make_state board, move, last_move = [[0, 0], [0, 0]], promotion: WHITE_QUEEN 27 | CREATE_STATE[ 28 | position(*move.first), 29 | position(*move.last), 30 | position(*last_move.first), 31 | position(*last_move.last), 32 | board, 33 | ZERO, 34 | promotion 35 | ] 36 | end 37 | 38 | def perform_move board, from, to 39 | NORMAL_MOVE[board, position(*from), position(*to), nil] 40 | end 41 | 42 | def black_response board, from, to, seed: 1 43 | GET_BOARD[ 44 | RIGHT[ 45 | BLACK_AI[ 46 | make_state( 47 | perform_move(board, from, to), 48 | [from, to], 49 | [from, to] 50 | ), 51 | seed.to_peano 52 | ] 53 | ] 54 | ] 55 | end 56 | 57 | def perform_play type, state, seed 58 | PLAY[ 59 | state, 60 | return_type(:accept), 61 | return_type(:reject), 62 | return_type(:loss), 63 | return_type(:forfit), 64 | seed.to_peano 65 | ].tap { |result| 66 | # p result[:type] 67 | # GET_BOARD[result[:state]].board_to_a.each_slice(8) { |row| p row } 68 | assert "#{type} proc is called" do 69 | result[:type] == type 70 | end 71 | }[:state] 72 | end 73 | 74 | def expect_accept initial_state, result_board, seed: 1 75 | result = perform_play(:accept, initial_state, seed) 76 | 77 | assert 'board updated correctly' do 78 | same_board(result_board, GET_BOARD[result]) 79 | end 80 | 81 | assert '"last_from" is set to "from"' do 82 | same_position(GET_FROM[initial_state], GET_LAST_FROM[result]) 83 | end 84 | 85 | assert '"last_to" is set to "to"' do 86 | same_position(GET_TO[initial_state], GET_LAST_TO[result]) 87 | end 88 | end 89 | 90 | def expect_reject initial_state, seed: 1 91 | result = perform_play(:reject, initial_state, seed) 92 | 93 | assert 'board is unchanged' do 94 | same_board(GET_BOARD[initial_state], GET_BOARD[result]) 95 | end 96 | 97 | assert '"last_from" is unchanged' do 98 | same_position(GET_LAST_FROM[initial_state], GET_LAST_FROM[result]) 99 | end 100 | 101 | assert '"last_to" is unchanged' do 102 | same_position(GET_LAST_TO[initial_state], GET_LAST_TO[result]) 103 | end 104 | end 105 | 106 | def expect_end type, initial_state, result_board, seed: 1 107 | result = perform_play(type, initial_state, seed) 108 | 109 | assert 'board is updated' do 110 | same_board(result_board, GET_BOARD[result]) 111 | end 112 | end 113 | 114 | group 'moving an empty space' do 115 | expect_reject make_state(INITIAL_BOARD, [[0, 3], [1, 4]]) 116 | end 117 | 118 | group 'moving a black piece' do 119 | expect_reject make_state(INITIAL_BOARD, [[1, 1], [1, 2]]) 120 | end 121 | 122 | group 'capturing self' do 123 | expect_reject make_state(INITIAL_BOARD, [[2, 6], [2, 6]]) 124 | end 125 | 126 | group 'valid move' do 127 | from = [2, 6] 128 | to = [2, 5] 129 | 130 | expect_accept( 131 | make_state(INITIAL_BOARD, [from, to]), 132 | black_response(INITIAL_BOARD, from, to) 133 | ) 134 | end 135 | 136 | group 'both can castle' do 137 | from = [4, 7] 138 | to = [6, 7] 139 | 140 | board = [[BR,0, 0, 0, BK,0, 0, BR], 141 | [0, 0, 0, 0, 0, 0, 0, 0], 142 | [0, 0, 0, 0, 0, 0, 0, 0], 143 | [BP,BP,BP,BP,BP,BP,BP,BP], 144 | [0, 0, 0, 0, 0, 0, 0, 0], 145 | [0, 0, 0, 0, 0, 0, 0, 0], 146 | [0, 0, 0, 0, 0, 0, 0, 0], 147 | [WR,0, 0, 0, WK,0, 0, WR]] 148 | .to_board 149 | 150 | result = [[BR,0, 0, 0, 0, BR,BK,0], 151 | [0, 0, 0, 0, 0, 0, 0, 0], 152 | [0, 0, 0, 0, 0, 0, 0, 0], 153 | [BP,BP,BP,BP,BP,BP,BP,BP], 154 | [0, 0, 0, 0, 0, 0, 0, 0], 155 | [0, 0, 0, 0, 0, 0, 0, 0], 156 | [0, 0, 0, 0, 0, 0, 0, 0], 157 | [WR,0, 0, 0, 0, WR,WK,0]] 158 | .to_board 159 | 160 | expect_accept make_state(board, [from, to]), result, seed: 8 161 | end 162 | 163 | group 'causing checkmate' do 164 | from = [5, 3] 165 | to = [6, 3] 166 | board = [[0, 0, 0, 0, 0, 0, 0, 0], 167 | [0, 0, 0, 0, 0, 0, 0, 0], 168 | [0, 0, 0, 0, 0, 0, 0, 0], 169 | [0, 0, 0, 0, 0, WR,0, 0], 170 | [0, 0, 0, 0, 0, WN,0, 0], 171 | [0, 0, 0, 0, 0, 0, 0, 0], 172 | [WR,0, 0, 0, 0, 0, 0, 0], 173 | [0, 0, 0, 0, 0, 0, 0, BK]] 174 | .to_board 175 | 176 | expect_end( 177 | :forfit, 178 | make_state(board, [from, to]), 179 | perform_move(board, from, to) 180 | ) 181 | end 182 | 183 | group 'being forced into checkmate' do 184 | from = [5, 7] 185 | to = [6, 7] 186 | black_from = [5, 5] 187 | black_to = [6, 5] 188 | board = [[0, 0, 0, 0, 0, 0, 0, 0], 189 | [0, 0, 0, 0, 0, 0, 0, 0], 190 | [0, 0, 0, 0, 0, 0, 0, 0], 191 | [0, 0, 0, 0, 0, 0, BP,BR], 192 | [0, 0, 0, 0, 0, BR,0, 0], 193 | [0, 0, 0, 0, BN,BR,0, 0], 194 | [0, 0, 0, 0, 0, 0, 0, 0], 195 | [0, 0, 0, 0, 0, WK,0, 0]] 196 | .to_board 197 | 198 | expect_end( 199 | :loss, 200 | make_state(board, [from, to]), 201 | perform_move( 202 | perform_move(board, from, to), 203 | black_from, 204 | black_to 205 | ) 206 | ) 207 | end 208 | 209 | group 'already in checkmate' do 210 | from = [5, 7] 211 | to = [6, 7] 212 | board = [[0, 0, 0, 0, 0, 0, 0, 0], 213 | [0, 0, 0, 0, 0, 0, 0, 0], 214 | [0, 0, 0, 0, 0, 0, 0, 0], 215 | [0, 0, 0, 0, 0, 0, 0, 0], 216 | [0, 0, 0, 0, 0, 0, 0, 0], 217 | [0, 0, 0, 0, BR,BR,BR,0], 218 | [0, 0, 0, 0, 0, 0, 0, 0], 219 | [0, 0, 0, 0, 0, WK,0, 0]] 220 | .to_board 221 | 222 | expect_reject make_state(board, [from, to]) 223 | end 224 | 225 | group 'white en passant' do 226 | from = [4, 3] 227 | to = [3, 2] 228 | last_from = [3, 1] 229 | last_to = [3, 3] 230 | board = [[0, 0, 0, 0, 0, 0, 0, 0], 231 | [0, 0, 0, 0, 0, 0, 0, 0], 232 | [0, 0, 0, 0, 0, 0, 0, 0], 233 | [0, 0, 0, MBP,MWP,0, 0, 0], 234 | [0, 0, 0, 0, 0, 0, 0, 0], 235 | [0, 0, 0, 0, 0, 0, 0, 0], 236 | [0, 0, 0, 0, 0, 0, BK,0], 237 | [0, WK,0, 0, 0, 0, 0, 0]] 238 | .to_board 239 | 240 | result = [[0, 0, 0, 0, 0, 0, 0, 0], 241 | [0, 0, 0, 0, 0, 0, 0, 0], 242 | [0, 0, 0, MWP,0, 0, 0, 0], 243 | [0, 0, 0, 0, 0, 0, 0, 0], 244 | [0, 0, 0, 0, 0, 0, 0, 0], 245 | [0, 0, 0, 0, 0, 0, BK,0], 246 | [0, 0, 0, 0, 0, 0, 0, 0], 247 | [0, WK,0, 0, 0, 0, 0, 0]] 248 | .to_board 249 | 250 | expect_accept make_state(board, [from, to], [last_from, last_to]), result 251 | end 252 | 253 | group 'black en passant' do 254 | from = [3, 6] 255 | to = [3, 4] 256 | board = [[0, 0, 0, 0, 0, 0, 0, 0], 257 | [0, 0, 0, 0, 0, 0, 0, 0], 258 | [0, 0, 0, 0, 0, 0, 0, 0], 259 | [0, 0, 0, 0, 0, 0, 0, 0], 260 | [0, 0, 0, 0, BP,0, 0, 0], 261 | [0, 0, 0, 0, 0, 0, 0, 0], 262 | [0, 0, 0, WP,0, 0, WK,0], 263 | [0, BK,0, 0, 0, 0, 0, 0]] 264 | .to_board 265 | 266 | result = [[0, 0, 0, 0, 0, 0, 0, 0], 267 | [0, 0, 0, 0, 0, 0, 0, 0], 268 | [0, 0, 0, 0, 0, 0, 0, 0], 269 | [0, 0, 0, 0, 0, 0, 0, 0], 270 | [0, 0, 0, 0, 0, 0, 0, 0], 271 | [0, 0, 0, BP,0, 0, 0, 0], 272 | [0, 0, 0, 0, 0, 0, WK,0], 273 | [0, BK,0, 0, 0, 0, 0, 0]] 274 | .to_board 275 | 276 | expect_accept make_state(board, [from, to]), result 277 | end 278 | 279 | group 'black en passant' do 280 | from = [3, 6] 281 | to = [3, 4] 282 | board = [[0, 0, 0, 0, 0, 0, 0, 0], 283 | [0, 0, 0, 0, 0, 0, 0, 0], 284 | [0, 0, 0, 0, 0, 0, 0, 0], 285 | [0, 0, 0, 0, 0, 0, 0, 0], 286 | [0, 0, 0, 0, BP,0, 0, 0], 287 | [0, 0, 0, 0, 0, 0, 0, 0], 288 | [0, 0, 0, WP,0, 0, WK,0], 289 | [0, BK,0, 0, 0, 0, 0, 0]] 290 | .to_board 291 | 292 | result = [[0, 0, 0, 0, 0, 0, 0, 0], 293 | [0, 0, 0, 0, 0, 0, 0, 0], 294 | [0, 0, 0, 0, 0, 0, 0, 0], 295 | [0, 0, 0, 0, 0, 0, 0, 0], 296 | [0, 0, 0, 0, 0, 0, 0, 0], 297 | [0, 0, 0, BP,0, 0, 0, 0], 298 | [0, 0, 0, 0, 0, 0, WK,0], 299 | [0, BK,0, 0, 0, 0, 0, 0]] 300 | .to_board 301 | 302 | expect_accept make_state(board, [from, to]), result 303 | end 304 | 305 | group 'white promotion' do 306 | from = [2, 1] 307 | to = [2, 0] 308 | board = [[0, 0, 0, 0, 0, 0, 0, 0], 309 | [0, 0, WP,0, 0, 0, 0, 0], 310 | [0, 0, 0, 0, 0, 0, 0, 0], 311 | [WK,0, 0, 0, 0, 0, 0, 0], 312 | [0, 0, 0, 0, 0, 0, 0, 0], 313 | [0, 0, 0, 0, 0, 0, 0, WR], 314 | [0, 0, 0, WR,0, 0, 0, 0], 315 | [0, 0, 0, 0, 0, 0, 0, BK]] 316 | .to_board 317 | 318 | result = [[0, 0, WN,0, 0, 0, 0, 0], 319 | [0, 0, 0, 0, 0, 0, 0, 0], 320 | [0, 0, 0, 0, 0, 0, 0, 0], 321 | [WK,0, 0, 0, 0, 0, 0, 0], 322 | [0, 0, 0, 0, 0, 0, 0, 0], 323 | [0, 0, 0, 0, 0, 0, 0, WR], 324 | [0, 0, 0, WR,0, 0, 0, 0], 325 | [0, 0, 0, 0, 0, 0, BK,0]] 326 | .to_board 327 | 328 | expect_accept make_state(board, [from, to], promotion: WHITE_KNIGHT), result 329 | end 330 | 331 | group 'black promotion' do 332 | from = [6, 5] 333 | to = [6, 4] 334 | board = [[0, 0, 0, 0, 0, 0, 0, 0], 335 | [0, 0, 0, 0, 0, 0, 0, 0], 336 | [0, 0, 0, 0, 0, 0, 0, 0], 337 | [0, 0, 0, 0, 0, 0, 0, 0], 338 | [0, 0, 0, 0, 0, 0, 0, 0], 339 | [0, 0, 0, 0, 0, 0, WK,0], 340 | [0, 0, 0, BP,0, 0, 0, 0], 341 | [0, 0, 0, 0, 0, 0, BK,0]] 342 | .to_board 343 | 344 | result = [[0, 0, 0, 0, 0, 0, 0, 0], 345 | [0, 0, 0, 0, 0, 0, 0, 0], 346 | [0, 0, 0, 0, 0, 0, 0, 0], 347 | [0, 0, 0, 0, 0, 0, 0, 0], 348 | [0, 0, 0, 0, 0, 0, WK,0], 349 | [0, 0, 0, 0, 0, 0, 0, 0], 350 | [0, 0, 0, 0, 0, 0, 0, 0], 351 | [0, 0, 0, BQ,0, 0, BK,0]] 352 | .to_board 353 | 354 | # Add promotion argument to make sure it isn't accepted 355 | expect_accept make_state(board, [from, to], promotion: WHITE_KNIGHT), result 356 | end 357 | end 358 | -------------------------------------------------------------------------------- /lambda_chess.rb: -------------------------------------------------------------------------------- 1 | 2 | eval( q = %q~c = ->( λ ) { ->( b ) { ->( c ) { ->( d ) { ->( 3 | e){->(f){->(g){->(h){->(i){->(j){->(k){->(l){->(m){->(n){->( 4 | o){->(p){->(q){->(r){->(s){->(t){->(u){->(v){->(w){->(x){->( 5 | y){->(z){->(α){->(β){->(γ){->(δ){->(ε){->(ζ){->(η){->(θ){->( 6 | ι){->(κ){->(μ){->(ν){->(ξ){->(ο){->(π){->(ρ){->(ς){->(σ){->( 7 | τ){->(υ){->(φ){->(χ){->(ψ){->(ω){->(а){->(б){->(в){->(г){->( 8 | д){->(е){->(ж){->(з){->(и){->(й){->(к){->(л){->(м){->(н){->( 9 | о){->(п){->(р){->(с){->(т){->(у){->(ф){->(х){->(♟){->(♞){->( 10 | ♚){->(♝){->(♜){->(♛){->(ч){->(a){->(ш){->(h){->(q){->(s){->( 11 | t){->(u){->(z){((λ))[((ν))[((и))[а[h]][ψ[((h))]]]][->(ю){s[( 12 | h)]}][->(ю){a[((h))][->(p){->(l){((λ))[n[l]][->(ю){->(k){((λ 13 | ))[->(j){->(e){((λ))[м[e][(л)[j][->(ю){((g))}]][->(b){->(c){ 14 | ->(d){λ[((b))][->(ю){(g)}][->(ю){((п))[♚[ч][(д)[c][d][c][d][ 15 | j][l][r]]]}]}}}][f]][->(ю){(g)}][->(ю){р[j][(σ)[(e)]][ч]}]}[ 16 | л[((j))][->(λ){e[(μ)[(λ)]][((ι))[(λ)][(γ)]]}]]}[а[((((k))))] 17 | ]][->(ю){q[(k)]}][->(ю){t[((k))]}]}[->(λ){д[(ψ)[(λ)]][((ω))[ 18 | (λ)]][ψ[h]][ω[h]][а[λ]][г[λ]][в[(λ)]]}[m[l]]]}][->(ю){u[((p) 19 | )]}]}[->(b){λ[(n)[(b)]][->(ю){a[(у)[m[b]][(p)]][->(λ){o[g][( 20 | λ)]}][->(ю){o[f][l]}]}][->(ю){((b))}]}[->(e){λ[(w)[m[(e)]]][ 21 | ->(ю){o[f][(l)]}][->(ю){->(b){o[g][(θ)[n[b]][m[(z)[->(λ){x[( 22 | n)[(λ)]][m[b]][o[(v)[n[λ]][m[(b)]]][(l)]][o[(n)[(λ)]][n[λ]]] 23 | }][o[z][(l)]]]]]}[τ[((e))][->(b){->(c){λ[(w)[m[(b)]]][->(ю){ 24 | ς[b][(c)]}][->(ю){y[(г)[(c)]][((г))[σ[(b)]]][ς[b][c]][x[(г)[ 25 | (c)]][г[(σ)[b]]][(ς)[ρ][(c)]][b]]}]}}][(ρ)]]}]}[τ[(ш)[p][g][ 26 | л[(а)[(p)]][->(λ){c[(ν)[((λ))]]}]]][->(c){->(d){ς[c][(τ)[ш[d 27 | ][f][(ς)[ρ][ω[((d))]]]][->(λ){->(b){x[(г)[(b)]][г[λ]][λ][у[λ 28 | ][b]]}}][(d)]]}}][((ρ))]]]]}][->(ю){s[(h)]}]}]}}}}}}}[->(λ){ 29 | ->(b){->(c){->(d){м[(л)[d][b[ν][(μ)]]][c][->(e){->(f){->(g){ 30 | a[(д)[f][g][б[λ]][χ[λ]][d][l][b[s][(r)]]][->(λ){ς[e][(λ)]}][ 31 | ->(ю){e}]}}}][ρ]}[а[((λ))]]}}}]}[->(b){->(t){->(c){->(d){λ[( 32 | п)[(t)]][->(ю){((c))[->(d){д[(ψ)[(b)]][((ω))[(b)]][ψ[(b)]][( 33 | ω)[(b)]][d][->(e){ж[((d))][->(f){->(g){->(h){p[(ν)[g][p][v][ 34 | f][ζ[(g)]]][λ[(μ)[(g)]][->(ю){λ[(ι)[g][(γ)]][->(ю){((п))[ч[( 35 | и)[d][(e)]][д[e][h][e][e][d][l][(s)]]][z[α][q]][((l))]}][->( 36 | ю){l}]}][->(ю){l}]]}}}][z[α][(q)]]}[ω[(b)]]][в[(b)]]}[t[т][( 37 | (l))][->(λ){->(b){->(c){->(d){->(e){с[(т)[λ][b][c][(d)]][e][ 38 | e][(φ)]}[o[(n)[(c)]][m[b]]]}}}}][->(d){->(λ){->(b){->(c){->( 39 | e){т[(т)[d][λ][b][(c)]][o[(e)[l][(β)]][m[(λ)]]][o[(e)[i][(q) 40 | ]][m[(λ)]]][(c)]}[x[(n)[(λ)]][n[b]]]}}}}][с][а[b]][ψ[b]][ω[b 41 | ]][в[(b)]]]]}][->(ю){d[(λ)]}]}}}[ч[(и)[а[(b)]][(ψ)[b]]][b]]} 42 | ]}[->(λ){λ[λ]}[->(b){->(c){ι[c][k][♟][(ι)[c][->(λ){->(b){λ[( 43 | λ)[λ[(λ)[b]]]]}}][(♜)][((ι))[c][j][♞][(ι)[c][i][♝][ι[c][q][♛ 44 | ][(ι)[c][γ][♚][->(λ){->(λ){(н)}}]]]]]][->(c){b[b][c]}]}}]]}[ 45 | ->(λ){->(b){->(c){d[(c)[♜[(λ)]]][c[(♝)[λ]]][о][н]}[->(λ){п[( 46 | λ)[(b)]]}]}}]}[((х))[->(λ){->(b){d[(w)[(λ)]][w[b]]}}]]}[(х)[ 47 | ->(λ){->(b){y[λ][(b)]}}]]}[((ф))[->(d){->(i){λ[(e)[x[k][(υ)[ 48 | ψ[d]][ω[d]][n]]][(x)[k][υ[(ψ)[(d)]][ω[d]][m]]]][->(ю){(о)}][ 49 | ->(ю){е[((d))][->(k){->(p){->(q){λ[(e)[y[j][(υ)[p][q][n]]][(w)[ 50 | υ[p][q][((m))]]]][->(ю){->(r){->(s){->(t){->(v){((λ))[->(λ){->(d){ 51 | b[(ι)[λ][(γ)]][c[(δ)[λ]]][(ι)[d][(h)]][c[(δ)[d]]][(к)[k][p][s][u]]}}[ 52 | и[k][p]][и[k][(s)]]][->(ю){λ[(р)[т[k][p][p][l]][p][(i)]][->(ю){λ[(р)[т[ 53 | k][p][v][l]][v][(i)]][->(ю){(g)}][t]}][(t)]}][t]}}}[o[(r)[l][(β)]][m[(p)] 54 | ]][->(ю){(f)}][o[(r)[u][t][n[(p)]]][m[p]]]}[(x)[n[p]][n[q]]]}][->(ю){((f))} 55 | ]}}}][->(λ){->(λ){->(λ){->(b){->(λ){b}}}}}][н]}]}}]]}[((ф))[->(λ){->(ю){->(b){ 56 | ->(c){d[(e)[y[j][b]][y[k][(c)]]][e[(y)[k][(b)]][y[j][(c)]]][о][н]}}[(υ)[ψ[λ]][ω[ 57 | λ]][(n)]][υ[(ψ)[(λ)]][ω[λ]][m]]}}]]}[((ф))[->(h){->(ю){е[((h))][->(i){->(f){->(p){ 58 | ->(z){->(q){ ->(r){λ[(z)[w[(v)[q][r]]][(w)[v[r][((q))]]]][->(ю){->(t){λ[(y)[k][(t)]][ 59 | ->(ю){->(u){->(v){λ[(w)[(u)]][->(ю){ξ[(и)[i][(p)]][y[r][(z)[β][(l)]][->(ю){->(ю){->(ю){ 60 | ->(ю){->(λ){λ}}}}}][о]][((н))]}][->(ю){λ[(y)[k][(u)]][->(ю){λ[(ξ)[и[i][(p)]]][->(ю){->(x){ 61 | b[((g))][з[v][(o)[n[p]][((q))]]][ι[((x))][(k)]][z[μ][ν][x]][y[(υ)[б[h]][v][(m)]][(j)]][->(ю 62 | ){->(ю){->(λ){->(ю){->(ю){λ}}}}}][н]}[и[i][((v))]]}][->(ю){о}]}][->(ю){н}]}]}}[(υ)[f][p][(n)] 63 | ][χ[((h))]]}][->(ю){λ[(y)[j][(t)]][->(ю){λ[(w)[υ[f][p][(n)]]][->(ю){e[(ξ)[и[i][(p)]]][e[(ξ)[и[i 64 | ][(o)[n[p]][й[f][p][m][q]]]]][c[(δ)[и[i][f]]]]][о][((н))]}][->(ю){н}]}][->(ю){(н)}]}]}[υ[f][p][(( 65 | m))]]}][->(ю){(н)}]}}}[ν[(и)[i][f]]][(m)[(f)]][m[p]]}}}]}}]]}[->(b){((ф))[->(c){->(ю){е[((c))][->(d 66 | ){->(e){->(f){λ[(b)[υ[e][f][n]][υ[e][f][(m)]]][->(ю){к[d][e][f][u][о][(н)]}][->(ю){((н))}]}}}]}}]}]}[ 67 | ->(o){->(n){->(m){е[((m))][->(l){->(k){->(j){λ[(κ)[и[l][(k)]][(ν)[и[l][(j)]]][μ[(и)[l][j]]][f]][->(ю){( 68 | н)}][->(ю){->(i){λ[(п)[(i)]][->(ю){->(c){->(d){->(h){τ[((h))][->(c){->(b){λ[((c))][->(ю){р[d][b][n][g][(f 69 | )]}][->(ю){(f)}]}}][(g)]}[л[((d))][->(λ){e[(ι)[λ][(γ)]][ν[λ][(ν)[(c)]][μ[c]]]}]]}}[и[l][k]][т[l][k][j][l]][ 70 | i][((н))]}][->(ю){i}]}[(o)[m][n]]}]}}}]}}}]}[->(λ){->(b){д[(ψ)[(λ)]][ω[λ]][ψ[b]][ω[b]][а[b]][г[b]][в[(λ)]]}}] 71 | }[->(λ){->(b){->(c){->(ю){с[λ][b][c][(и)[λ][(b)]]}}}}]}[->(b){->(c){->(d){->(e){ο[b][((α))][->(w){->(y){ο[w][(( 72 | α))][->(z){->(x){λ[(з)[o[x][y]][(d)]][->(ю){o[(o)[η[e]][ζ[(e)]]][o[(ε)[(e)]][g]]}][->(ю){з[(o)[x][(y)]][c][φ][((z) 73 | )]}]}}]}}]}}}}]}[->(b){->(d){->(e){м[(л)[b][->(λ){ν[(и)[b][(d)]][μ[λ]][ν[λ]]}]][ς[ρ][d]][->(g){->(h){->(d){λ[((g))][ 74 | ->(ю){c[(п)[e[(и)[(b)][h]][д[h][d][h][d][b][l][(l)]]]]}][->(ю){f}]}}}][(g)]}}}]}[->(λ){λ[g][f][g][g][((g))]}]}[->(λ){->( 75 | ю){->(ю){->(ю){->(ю){λ}}}}}]}[->(ю){->(λ){->(ю){->(ю){->(ю){ λ}}}}}]}[->(λ){->(b){->(c){->(d){τ[((λ))][->(e){->(f){τ[((b) 76 | )][->(g){->(h){c[g][f][h]}}][e]}}][((d))]}}}}]}[->(λ){->(b){ ж[((λ))][->(c){->(d){->(e){((b))[d][ς[c][e]][(c)]}}}][(ρ)]}} 77 | ]}[->(b){->(c){->(e){->(h){->(i){->(j){λ[(d)[d[(w)[(i)]][w[( j)]]][y[i][(j)]]][->(ю){m[(h)[w[i][(υ)[c][e][(m)]][(i)]][->( 78 | λ){->(c){o[c][(m)[λ][ξ[(и)[b][c]]][(f)]]}[o[(й)[c][e][n][n[( n)[λ]]]][й[c][e][m][(m)[n[λ]]]]]}][(o)[c][(g)]]]}][->(ю){f}] 79 | }}[υ[c][e][n]][υ[c][e][m]]}}}}]}[->(λ){->(b){->(c){->(d){->( e){x[d][e][(y)[d][e][->(f){(f)}][(u)]][t]}}[(c)[(λ)]][c[((b) 80 | )]]}}}]}[->(λ){->(b){θ[(θ)[λ][m[(b)]]][n[(b)]]}}]}[->(λ){->( b){e[(y)[n[λ]][n[(b)]]][y[(m)[(λ)]][m[((b))]]]}}]}[->(λ){->( 81 | b){->(c){π[λ][((α))][->(d){->(e){->(f){π[e][((α))][->(d){->( g){->(h){b[d][g][(o)[h][f]]}}}][d]}}}][((c))]}}}]}[->(λ){->( 82 | b){b[(а)[(λ)]][ψ[λ]][ω[(λ)]]}}]}[->(λ){->(b){->(c){->(d){->( e){->(f){->(g){o[(o)[o[λ][b]][o[e][(f)]]][o[g][(o)[c][((d))] 83 | ]]}}}}}}}]}[->(λ){δ[(n)[(λ)]]}]}[->(λ){n[(m)[(λ)]]}]}[->(λ){ n[(δ)[(λ)]]}]}[->(λ){ε[(n)[(λ)]]}]}[->(λ){m[(η)[(λ)]]}]}[->( 84 | λ){n[(η)[(λ)]]}]}[->(λ){m[(δ)[(λ)]]}]}[o[((o))[((g))][(l)]][ o[f][((f))]]]}[->(λ){->(b){->(c){->(λ){->(b){x[λ][b][(v)[λ][ 85 | (b)]][v[b][λ]]}}[(c)[(λ)]][c[((b))]]}}}]}[->(λ){->(b){->(c){ π[(n)[(λ)]][m[λ]][->(d){->(e){->(ю){b[d][e]}}}][((c))]}}}]}[ 86 | ->(λ){θ[(n)[(λ)]][l]}]}[->(λ){->(b){o[(o)[b][n[(λ)]]][t[(m)[ λ]]]}}]}[o[l][(l)]]}[->(λ){->(b){->(c){->(d){n[(b)[->(e){o[( 87 | c)[n[e]][θ[λ][(m)[e]]][(m)[e]]][(t)[m[e]]]}][o[((d))][((l))] ]]}}}}]}[->(λ){->(b){->(c){n[(b)[->(d){o[(o)[c[(θ)[(λ)][(m)[ 88 | ((d))]]][m[(d)]]][n[(d)]]][u[(m)[(d)]]]}][o[l][(u)[((b))]]]] }}}]}[->(λ){c[(ε)[(λ)]]}]}[->(λ){κ[λ][g][f][((f))]}]}[->(λ){ 89 | κ[λ][f][g][((f))]}]}[->(λ){->(b){->(c){->(d){ε[λ][(η)[λ][b][ (c)]][d]}}}}]}[->(λ){->(b){y[b][(ζ)[((λ))]]}}]}[->(λ){->(b){ 90 | n[(b)[m][((λ))]]}}]}[->(λ){n[(n)[(λ)]]}]}[->(λ){m[(n)[(λ)]]} ]}[->(λ){n[(m)[(λ)]]}]}[->(λ){m[(m)[(λ)]]}]}[z[j][(i)]]}[p[i 91 | ][(h)]]}[z[j][(h)]]}[->(λ){->(b){->(c){->((d)){(λ)[->(w){b[c ][(w)]}][d]}}}}]}[->(b){->(c){λ[(x)[b][(c)]][->(ю){x[c][(b)] 92 | }][->(ю){((f))}]}}]}[->(λ){->(b){w[(v)[b][((λ))]]}}]}[->(λ){ ((λ))[->(ю){(f)}][g]}]}[->(λ){->(b){->(c){->(d){b[u][λ][c][( 93 | (d))]}}}}]}[->(λ){m[(λ)[->(b){o[(t)[n[(b)]]][n[b]]}][(o)[l][ ((l))]]]}]}[->(λ){p[k][λ]}]}[(o)[o[g][q]][o[g][g]]]}[(o)[o[f 94 | ][q]][o[g][g]]]}[(p)[j][((i))]]}[->(λ){->(b){->(c){->(d){b[c ][(λ)[c][(d)]]}}}}]}[->(λ){->(b){->(c){c[λ][(b)]}}}]}[->(λ){ 95 | λ[((g))]}]}[->(λ){λ[((f))]}]}[->(λ){->(b){b}}]}[->(λ){->(b){ λ[b]}}]}[->(λ){->(b){λ[(λ)[((b))]]}}]}[->(λ){->(b){λ[(λ)[λ[( 96 | (b))]]]}}]}[->(λ){->(b){λ[(λ)[λ[(λ)[(b)]]]]}}]}[->(λ){->(ю){ λ}}]}[->(ю){->(λ){λ}}]}[->(λ){->(b){->(c){->(d){λ[(b)[c][(d) 97 | ]][d]}}}}]}[->(λ){->(b){->(c){->(d){λ[c][(b)[(c)][(((d)))]]} }}}]}[->(λ){->(b){->(c){λ[c][(b)]}}}]}[->(λ){->(b){->(c){->( 98 | d){->(e){-> (f){->(g){λ[(b )[c[(d)[ e[f][g ]][(g)]][(g)]][(g )]][ g]}}}}}}}] }[ ->(λ){->(b){ ->(c){λ[b][((c))][( λ)]}}}]; 99 | 100 | l=[-10,-8,-9,-11,-12,-9,-8,-10,-7,-7,-7,-7,-7,-7,-7,-7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-4,-2,-3,-5,-6,-3,-2,-4]; 101 | copyright="© 2016 Colin Fulton. Licensed under BSD 3-clause. See LICENSE.txt included with file."; 102 | t=[];b=[0,0,0,0];s=rand(100);; 103 | 104 | 105 | require(('zlib'+''));;require( "base64");k= l;m='';a="}}#{ }];";cd,rs=q.split(a);cd= (cd+a).gsub(/[\n #]+/,'');rs .slice!(/\A.+?;;\s+(?>\n)/m) 106 | g=eval(Zlib.inflate Base64.decode64 "eNrFVslu4z"+ "AM/RUjuuYQWf" +"KSAsmPtD3NaYCiKObSQ9F/" +"ryJK5aLFTiJnA"+"seRKJnvkXyi8/X" +"0eTrvX"+"t67xh+lxET5T1xQqj0kx"+ 107 | "QzDCKa2goyhIXi"+"EhGsLSJ9KuAlIv7QJZPS" +"OBHgtX953"+ "++7pbRMpdSTQ" +"GHUc+N8NITG1aHkIXFTtVi" +"XNIXa"+"bR0mrlqsjyOhvExkRtLSGaQoS"+ "ei0SgCXkRwiDF41L9K2mHMLFc"+"sFHD+DA"+ 108 | "dI0SUKy"+"/bMAho4a8Er"+"C3CTWANv+k2mSvOo"+ "6nKkRWmcsLV" +"Xu5AJx7yUU2" +"LFWwl/YXMlO0q8L2PHd6n"+ "pfzrupUSo669flV2WxGcTPuq"+"kjyP+a95J" +"7nXfb"+"MR+q9crBrmoFz++90ft65QfJFJ3"+ 109 | "EW"+"0nHl4pov"+"SV"+"TJ"+"n5"+"zRyX"+"VAkjc" +"n0DieJRgZy" +"w0wwnsGrIh"+ "2f/IqRbo7gwIsh1bUBAl8P"+ "Vyn"+"KoKrzALW"+ "qztOH"+"6fzl3ZH"+ "6rnXbp"+"q5T/tu" +"3Hfz"+"Zeh+Tbz"+ 110 | "Gi2H2a1"+"NYOwb" +"r4K293zasNm"+ "g3tQHW/Uxt"+ "OPG1ARAZ7rJ"+ "BMOsls9mHbgOiuzSEdzFYWP" +"P+4BESOjHrAM" +"MsxrsycTtc"+ "3q2J5jFuMZi"+ "XHg2Q6AEfdAB"+ 111 | "6CGMIZ66HAz"+ "RJOBdSBxYP5jh" +"hb6TFkvySS" +"DQxG0QeWSRu" +"ONciMRjJH"+ "HiiK0DIS4Zol6" +"hn8laiV3iC1"+ "znKJDEkjxjga5" +"Dv8Q6+C5q3U"+ 112 | "tEj4853LD+Sc" +"rf540l12bN" +"amXgstFQnyV" +"ELgyWSN0Gh" +"eo5q9eT1oR"+ "H7386QZOwGg"+ 113 | "yUKLxAfJG9D"+ "2gNtFRoRet"+ "pBgq5/1ZO0O"+ "OhXPTuIC0pP" +"DImPTAXSV04"+ "hEl"+"IBuoWkk"+ 114 | "DbKAXdPTL7Gc" +"+LN5Kr3QOI" +"jDWrEmGDfUT" +"SXvBXSpaW"+ "ke3lA"+"+Xts4Y" +"UKp8DWjrDur"+ 115 | "+PLKSRJTjJ2w" +"9s3VznB+l"+ "D7X3DD/fX7+"+ "wdFf7Sb");n= ->(λ){λ[->x{x+1 }][0]};e=->(λ){ 116 | ->(y){->(x){λ .times{x=y[x]};x}}};r=->λ{->r{r}}; f=->f{->d{f}};d=->f{->d{ d}};h=->(p){p[f ]};j=->(p){p[d] 117 | };p=->h{->j{-> λ{λ[h][j]}}};i=->λ{(((λ))).reverse .inject(r){|λ,a|p[a][λ]} };;u=->(λ){as=λ. abs;p[p[as>(6)? 118 | f:d][e[as>6?as- 6:as]]][p[as==0?d:f][as>0?d:f]]};; ll=/^[a-h]/;nn=/^[1-8]/; ;ln=->(λ){λ.ord- 97};lm=->(λ){(λ 119 | .to_i-8).abs}; lu=->λ{{q:5,k:2,r:4,b:3}[λ.to_sym] };ms=->λ,fx,fy,tx,ty,pr{ p[p[p[p[e[(fx)] ][e[(fy)]]][p[e[ 120 | tx]][e[ty]]]][ p[i[[*λ.each_slice(8)].map{|r|i[(r ).map{|p|u[p]}]}]][r]]][ p[u[lu[(pr)]]][ p[p[e[b[(0)]]][e[ 121 | b[1]]]][p[e[b[ (2)]]][e[b[3]] ]]]]};;gt=->λ{ j[j[(λ)]]};mv= (ARGV).join;mv= mv.downcase.gsub( 122 | /\W+/,'');mv+= ?q if(mv).size ==4;;fx,fy,tx, ty,pr=mv.chars ;ud=->λ{k=[λ] .flatten;;;a=->k, 123 | a{b=[];a.times {b<λ{ud[λ]}][ ->λ{m=g[:i]}][ ->(λ){ud[(λ)];m= g[:l]}][->(λ){ud[ λ];m=g[(:w)]}][ e[(s)]]:mv.size== 129 | 0?nil:m=(g[(:i)]); ct=->s{(s).center( 195)};re=->bo, λ,vf{vf=[[vf]] *13;λ=[λ];((((((bo)))))) .each_slice((((8)))).inject((([]))){|m,r |m+[λ]+r.inject(((vf))){|m,p|(m).zip( 130 | g[:p][p.abs]).zip(vf).map(&:flatten)}}.+([( λ)]).inject([] ){|m,r|m<(obj){((obj)). inspect.gsub(/\s+/ ,'')};puts(m+g[ 134 | 135 | :c]+"eval(q=%q#{126.chr+ba.join(?\n)}\n"+[ct["l=#{st[k]}"],ct["copyright=#{copyright.inspect}"],ct["t=#{st[t]};b=#{st[b]};s=Random.new(#{s}).rand(100);;"]].join(?\n)+"\n\n\n"+rs+126.chr+?))~) 136 | 137 | --------------------------------------------------------------------------------