Chess program

Import

  • Luxor for graphical interface
  • Colors for board color
  • Crayons for command line interface
  • Base.Test for unittesting

In [1]:
using Luxor
using Colors
using Base.Test

Define struct Chess


In [2]:
mutable struct Chess
    board :: Array{Char, 2}
    whitemove :: Bool
    
    function Chess()
        board = Array{Char, 2}(8, 8)
        fill!(board, ' ')
        board[1,1] = '♖'
        board[2,1] = '♘'
        board[3,1] = '♗'
        board[4,1] = '♕'
        board[5,1] = '♔'
        board[6,1] = '♗'
        board[7,1] = '♘'
        board[8,1] = '♖'
        for i in 1:8
            board[i, 2] = '♙'
            board[i, 7] = '♟'
        end
        board[1,8] = '♜'
        board[2,8] = '♞'
        board[3,8] = '♝'
        board[4,8] = '♛'
        board[5,8] = '♚'
        board[6,8] = '♝'
        board[7,8] = '♞'
        board[8,8] = '♜'
        new(board, true)
    end
end

Add Base.show method and a draw function for a chessboard object


In [3]:
function Base.show(io::IO, chess::Chess)
    for i in 8:-1:1
        print(io, i)
        for j in 1:8
            print(io, " ", chess.board[j, i])
        end
        println(io)
    end
    print(io, "  a b c d e f g h")
end

function draw(chess::Chess)
    t = Table(10, 10, 50, 50)
    setcolor("gray")
    for i in 1:8
        for j in 1:8
            if i % 2 == 0 && j % 2 == 1 || i % 2 == 1 && j % 2 == 0
                setcolor("navajowhite")
            else
                setcolor("sandybrown")
            end
            n = 10*(9-i)+j+1 
            box(t[n], t.colwidths[t.currentcol], t.rowheights[t.currentrow], :fill)
        end
    end
    setcolor("black")
    for i in 1:8
        for j in 1:8
            n = 10*(9-i)+j+1
            fontsize(t.rowheights[t.currentrow]*2/3)
            text(string(chess.board[j, i]), t[n]-(5,0), halign=:center, valign=:middle)
        end
    end
    for i in 1:8
        n = 10*(9-i)+1
        fontsize(t.rowheights[t.currentrow]/2)
        text(string(i), t[n], halign=:center, valign=:middle)
    end
    for (j, c) in enumerate("abcdefgh")
        n = 91+j
        fontsize(t.rowheights[t.currentrow]/2)
        text(string(c), t[n], halign=:center, valign=:middle)
    end
end


Out[3]:
draw (generic function with 1 method)

Game logic


In [4]:
function domove(chess::Chess, move::String)
    x₀, y₀, x₁, y₁, capture = parsemove(move)
    if !isvalidmove(chess, x₀, y₀, x₁, y₁, capture)
        error("Move is not valid!")
    end
    chess.board[x₁, y₁] = chess.board[x₀, y₀]
    chess.board[x₀, y₀] = ' '
    chess.whitemove = !chess.whitemove
    nothing
end


Out[4]:
domove (generic function with 1 method)

Parse input


In [5]:
function parsemove(move::String)
    x₀ = Int(move[1])-96
    y₀ = parse(Int, move[2])
    x₁ = Int(move[3])-96
    y₁ = parse(Int, move[4])
    capture = ' '
    if length(move) == 5
        capture = move[5]
    end
    @assert(1  x₀  8, "Movement can not be parsed!")
    @assert(1  y₀  8, "Movement can not be parsed!")
    @assert(1  x₁  8, "Movement can not be parsed!")
    @assert(1  y₁  8, "Movement can not be parsed!")
    x₀, y₀, x₁, y₁, capture
end

@test parsemove("e2e4") == (5, 2, 5, 4, ' ')
@test parsemove("g7g8") == (7, 7, 7, 8, ' ')


Out[5]:
Test Passed

Validate move


In [6]:
function isvalidmove(chess::Chess, x₀::Int64, y₀::Int64, x₁::Int64, y₁::Int64, capture::Char)
    piece = chess.board[x₀, y₀]
    if chess.whitemove
        if piece == '♙'
            if isverticalmove(x₀, y₀, x₁, y₁) && movedistance(x₀, y₀, x₁, y₁, true) == 1
                return chess.board[x₁, y₁] == ' '
            elseif isverticalmove(x₀, y₀, x₁, y₁) && y₀ == 2 && movedistance(x₀, y₀, x₁, y₁, true) == 2
                if nopiecesinbetween(chess, x₀, y₀, x₁, y₁)
                    return chess.board[x₁, y₁] == ' '
                end
            elseif isdiagonalmove(x₀, y₀, x₁, y₁) && movedistance(x₀, y₀, x₁, y₁, true) == 2
                return chess.board[x₁, y₁]  "♜♞♝♛♚♟"
            end
        elseif piece == '♗'
            if isdiagonalmove(x₀, y₀, x₁, y₁)
                if nopiecesinbetween(chess, x₀, y₀, x₁, y₁)
                    return chess.board[x₁, y₁]  " ♜♞♝♛♚♟"
                end
            end
        elseif piece == '♖'
            if isverticalmove(x₀, y₀, x₁, y₁) || ishorizontalmove(x₀, y₀, x₁, y₁)
                if nopiecesinbetween(chess, x₀, y₀, x₁, y₁)
                    return chess.board[x₁, y₁]  " ♜♞♝♛♚♟"
                end
            end
        elseif piece == '♘'
            if movedistance(x₀, y₀, x₁, y₁) == 3 && !ishorizontalmove(x₀, y₀, x₁, y₁) &&
                !isverticalmove(x₀, y₀, x₁, y₁) && !isdiagonalmove(x₀, y₀, x₁, y₁)
                return chess.board[x₁, y₁]  " ♜♞♝♛♚♟"
            end
        elseif piece == '♕'
            if isverticalmove(x₀, y₀, x₁, y₁) || ishorizontalmove(x₀, y₀, x₁, y₁) || 
                isdiagonalmove(x₀, y₀, x₁, y₁)
                if nopiecesinbetween(chess, x₀, y₀, x₁, y₁)
                    return chess.board[x₁, y₁]  " ♜♞♝♛♚♟"
                end
            end
        elseif piece == '♔'
            if 1  movedistance(x₀, y₀, x₁, y₁)  2
                return chess.board[x₁, y₁]  " ♜♞♝♛♚♟"
            end
        end
    else
        if piece == '♟'
            if isverticalmove(x₀, y₀, x₁, y₁) && movedistance(x₀, y₀, x₁, y₁, true) == -1
                return chess.board[x₁, y₁] == ' '
            elseif isverticalmove(x₀, y₀, x₁, y₁) && y₀ == 7 && movedistance(x₀, y₀, x₁, y₁, true) == -2
                if nopiecesinbetween(chess, x₀, y₀, x₁, y₁)
                    return chess.board[x₁, y₁] == ' '
                end
            elseif isdiagonalmove(x₀, y₀, x₁, y₁) && movedistance(x₀, y₀, x₁, y₁, true) == 2
                return chess.board[x₁, y₁]  "♖♘♗♕♔♙"
            end
        elseif piece == '♝'
            if isdiagonalmove(x₀, y₀, x₁, y₁)
                if nopiecesinbetween(chess, x₀, y₀, x₁, y₁)
                    return chess.board[x₁, y₁]  " ♖♘♗♕♔♙"
                end
            end
        elseif piece == '♜'
            if isverticalmove(x₀, y₀, x₁, y₁) || ishorizontalmove(x₀, y₀, x₁, y₁)
                if nopiecesinbetween(chess, x₀, y₀, x₁, y₁)
                    return chess.board[x₁, y₁]  " ♖♘♗♕♔♙"
                end
            end
        elseif piece == '♞'
            if movedistance(x₀, y₀, x₁, y₁) == 3 && !ishorizontalmove(x₀, y₀, x₁, y₁) &&
                !isverticalmove(x₀, y₀, x₁, y₁) && !isdiagonalmove(x₀, y₀, x₁, y₁)
                    return chess.board[x₁, y₁]  " ♖♘♗♕♔♙"
            end
        elseif piece == '♛'
            if isverticalmove(x₀, y₀, x₁, y₁) || ishorizontalmove(x₀, y₀, x₁, y₁) || 
                isdiagonalmove(x₀, y₀, x₁, y₁)
                if nopiecesinbetween(chess, x₀, y₀, x₁, y₁)
                    return chess.board[x₁, y₁]  " ♖♘♗♕♔♙"
                end
            end
        elseif piece == '♚'
            if 1  movedistance(x₀, y₀, x₁, y₁)  2
                return chess.board[x₁, y₁]  " ♖♘♗♕♔♙"
            end
        end
    end
    false
end

function movedistance(x₀::Int64, y₀::Int64, x₁::Int64, y₁::Int64, oriented::Bool=false)
    if oriented
        return x₁-x₀ + y₁-y₀
    end
    abs(x₁-x₀) + abs(y₁-y₀)
end

function isverticalmove(x₀::Int64, y₀::Int64, x₁::Int64, y₁::Int64)
   x₀ == x₁ && y₀  y₁
end

function ishorizontalmove(x₀::Int64, y₀::Int64, x₁::Int64, y₁::Int64)
   x₀  x₁ && y₀ == y₁
end

function isdiagonalmove(x₀::Int64, y₀::Int64, x₁::Int64, y₁::Int64)
    x₀  x₁ && y₀  y₁ && max(x₀, x₁) - min(x₀, x₁) == max(y₀, y₁) - min(y₀, y₁)
end

function nopiecesinbetween(chess::Chess, x₀::Int64, y₀::Int64, x₁::Int64, y₁::Int64)
    δx = sign(x₁ - x₀)
    δy = sign(y₁ - y₀)
    n = max(abs(x₁ - x₀), abs(y₁ - y₀))
    x = x₀
    y = y₀
    println(δx, ", ", δy, ": ", n)
    for i in 1:n-1
        x = x + δx
        y = y + δy
        if chess.board[x, y]  ' '
            return false
        end
    end
    true
end


Out[6]:
nopiecesinbetween (generic function with 1 method)

Frontend that displays board between moves


In [7]:
function play(gui::Bool=false)
        chess = Chess()
        msg = ""
        str = ""
        while true
            if chess.whitemove
                msg = "White move: $msg"
            else
                msg = "Black move: $msg"
            end
            if gui
                @svg begin
                    draw(chess)
                    text(msg, Point(-230, -220))
                end 480 480
                print(" ")       # Hack to always display the 
                flush(STDOUT)    # input box
                str = readline()
            else
                println(chess)
                print(msg)
                str = readline()
                println()
            end
            if str == "done"
                break
            end
            try
                domove(chess, str)
                msg = ""
            catch
                msg = "$str is not valid! "
            end
        end
    end


Out[7]:
play (generic function with 2 methods)

Example game


In [ ]:
play(true)


 

In [ ]:
@svg begin draw(Chess()) end

In [ ]:
Chess()

In [ ]:
draw(Chess())

In [ ]: