From f19fb93e5b4802f36fe73be1aeb4c6614d5c93b3 Mon Sep 17 00:00:00 2001 From: Alex Clink Date: Thu, 2 Sep 2021 23:20:21 -0400 Subject: [PATCH] Initial commit --- .editorconfig | 9 +++++ .gitignore | 5 +++ .travis.yml | 6 ++++ LICENSE | 21 +++++++++++ README.md | 38 ++++++++++++++++++++ shard.yml | 13 +++++++ spec/lx_chess_spec.cr | 7 ++++ spec/spec_helper.cr | 2 ++ src/lx_chess.cr | 42 ++++++++++++++++++++++ src/lx_chess/board.cr | 43 ++++++++++++++++++++++ src/lx_chess/fen.cr | 50 ++++++++++++++++++++++++++ src/lx_chess/game.cr | 12 +++++++ src/lx_chess/piece.cr | 50 ++++++++++++++++++++++++++ src/lx_chess/player.cr | 4 +++ src/lx_chess/term_board.cr | 74 ++++++++++++++++++++++++++++++++++++++ src/lx_chess/terminal.cr | 29 +++++++++++++++ src/lx_chess/version.cr | 3 ++ 17 files changed, 408 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 shard.yml create mode 100644 spec/lx_chess_spec.cr create mode 100644 spec/spec_helper.cr create mode 100644 src/lx_chess.cr create mode 100644 src/lx_chess/board.cr create mode 100644 src/lx_chess/fen.cr create mode 100644 src/lx_chess/game.cr create mode 100644 src/lx_chess/piece.cr create mode 100644 src/lx_chess/player.cr create mode 100644 src/lx_chess/term_board.cr create mode 100644 src/lx_chess/terminal.cr create mode 100644 src/lx_chess/version.cr diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..163eb75 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*.cr] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0bb75ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/docs/ +/lib/ +/bin/ +/.shards/ +*.dwarf diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..765f0e9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: crystal + +# Uncomment the following if you'd like Travis to run specs and check code formatting +# script: +# - crystal spec +# - crystal tool format --check diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ab4c30b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 Alex Clink + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0feaf89 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# lx_chess + +A chess program written in crystal + +## Installation + +`shards build src/lx_chess.cr` + +## Usage + +run `lx_chess --help` for usage information + +Usage example: + +```sh +╰─$ ./bin/lx_chess --fen "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" +8: ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ +7: ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ +6: +5: +4: +3: +2: ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ +1: ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ + a b c d e f g h +``` + +## Contributing + +1. Fork it () +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request + +## Contributors + +- [Alex Clink](https://github.com/sleepinginsomniac) - creator and maintainer diff --git a/shard.yml b/shard.yml new file mode 100644 index 0000000..1155bfb --- /dev/null +++ b/shard.yml @@ -0,0 +1,13 @@ +name: lx_chess +version: 0.1.0 + +authors: + - Alex Clink + +targets: + lx_chess: + main: src/lx_chess.cr + +crystal: 1.0.0 + +license: MIT diff --git a/spec/lx_chess_spec.cr b/spec/lx_chess_spec.cr new file mode 100644 index 0000000..9490343 --- /dev/null +++ b/spec/lx_chess_spec.cr @@ -0,0 +1,7 @@ +require "./spec_helper" + +describe LxChess do + it "works" do + expect(false).to eq(true) + end +end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr new file mode 100644 index 0000000..e57f392 --- /dev/null +++ b/spec/spec_helper.cr @@ -0,0 +1,2 @@ +require "spec" +require "../src/lx_chess" diff --git a/src/lx_chess.cr b/src/lx_chess.cr new file mode 100644 index 0000000..ca0c335 --- /dev/null +++ b/src/lx_chess.cr @@ -0,0 +1,42 @@ +require "./lx_chess/version" +require "./lx_chess/board" +require "./lx_chess/terminal" +require "./lx_chess/term_board" +require "./lx_chess/game" +require "./lx_chess/fen" + +require "option_parser" + +options = { + "fen_string" => "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", +} + +OptionParser.parse do |parser| + parser.banner = "Usage: lx_chess [fen]" + + parser.on("--fen=FEN", "use fen string for board") do |fen_string| + options["fen_string"] = fen_string + end + + parser.on("-h", "--help", "Show this help") do + puts parser + exit + end + + parser.invalid_option do |flag| + STDERR.puts "ERROR: #{flag} is not a valid option." + STDERR.puts parser + exit(1) + end +end + +fen = LxChess::Fen.parse(options["fen_string"]) +game = LxChess::Game.new(board: fen.board) +gb = LxChess::TermBoard.new(game.board) + +gb.draw +puts + +# gb.flip! +# gb.draw +# puts diff --git a/src/lx_chess/board.cr b/src/lx_chess/board.cr new file mode 100644 index 0000000..a39e52e --- /dev/null +++ b/src/lx_chess/board.cr @@ -0,0 +1,43 @@ +require "./piece" + +module LxChess + class Board + LETTERS = ('a'..'z').to_a + + property :width, :height, :squares + + def initialize(@width : Int = 8, @height : Int = 8) + @squares = Array(Piece | Nil).new(@width * @height) { nil } + end + + def [](index : Int) + @squares[index] + end + + def [](cord : String) + @squares[cord_index(cord)] + end + + def at(x : Int, y : Int) + @squares[index(x, y)] + end + + def []=(index : Int, value : Piece | Nil) + @squares[index] = value + end + + def []=(cord : String, value : Piece | Nil) + @squares[cord_index(cord)] = value + end + + def index(x : Int, y : Int) + (y * @width) + x + end + + def cord_index(cord : String) + x = LETTERS.index(cord[0]) || 0 + y = cord[1].to_i - 1 + index(x, y) + end + end +end diff --git a/src/lx_chess/fen.cr b/src/lx_chess/fen.cr new file mode 100644 index 0000000..fdc1baf --- /dev/null +++ b/src/lx_chess/fen.cr @@ -0,0 +1,50 @@ +require "./board" +require "./piece" + +module LxChess + class Fen + def self.parse(fen : String) + placement, turn, castling, en_passant, halfmove_clock, fullmove_counter = fen.split(/\s+/) + + ranks = placement.split('/') + width = ranks.first.size + height = ranks.size + rank = height - 1 + + board = Board.new(width, height) + ranks.map { |r| r.chars }.each do |pieces| + file = 0 + + pieces.each do |symbol| + case symbol + when .in_set? "PKQRBNpkqrbn" + piece = Piece.from_fen(symbol) + index = rank * width + file + board[index] = piece + file += 1 + when .number? + file += symbol.to_i + else + raise "#{symbol} is not a valid fen symbol" + end + end + + rank -= 1 + end + + Fen.new(board: board) + end + + property board : Board + + def initialize( + @board : Board + # @turn : Int, + # @castling : String, + # @en_passant : String, + # @halfmove_clock : Int, + # @fullmove_counter : Int + ) + end + end +end diff --git a/src/lx_chess/game.cr b/src/lx_chess/game.cr new file mode 100644 index 0000000..ca85a1b --- /dev/null +++ b/src/lx_chess/game.cr @@ -0,0 +1,12 @@ +require "./player" +require "./board" + +module LxChess + class Game + property turn : Int8, board : Board + + def initialize(@board : Board, @players = [] of Player) + @turn = 0 + end + end +end diff --git a/src/lx_chess/piece.cr b/src/lx_chess/piece.cr new file mode 100644 index 0000000..138dd0a --- /dev/null +++ b/src/lx_chess/piece.cr @@ -0,0 +1,50 @@ +module LxChess + class Piece + NAMES = ["Pawn", "King", "Queen", "Rook", "Bishop", "Knight"] + SYMBOLS = "♙♔♕♖♗♘--♟♚♛♜♝♞" + FEN_SYMBOLS = "PKQRBN--pkqrbn" + + PAWN = 0 + KING = 1 + QUEEN = 2 + ROOK = 3 + BISHOP = 4 + KNIGHT = 5 + + def self.from_fen(fen : Char) + id = FEN_SYMBOLS.index(fen).as(Int32).to_i8 + Piece.new(id) + end + + def initialize(@id : Int8 = 0) + end + + def white? + @id & 0b1000 == 0 + end + + def black? + @id & 0b1000 == 1 + end + + def color + white? ? :white : :black + end + + def name + NAMES[@id & 0b1000] + end + + def symbol(force_black = false) + if force_black + SYMBOLS[@id | 0b1000] + else + SYMBOLS[@id] + end + end + + def fen_symbol + FEN_SYMBOLS[@id] + end + end +end diff --git a/src/lx_chess/player.cr b/src/lx_chess/player.cr new file mode 100644 index 0000000..b1a0feb --- /dev/null +++ b/src/lx_chess/player.cr @@ -0,0 +1,4 @@ +module LxChess + class Player + end +end diff --git a/src/lx_chess/term_board.cr b/src/lx_chess/term_board.cr new file mode 100644 index 0000000..7e331b9 --- /dev/null +++ b/src/lx_chess/term_board.cr @@ -0,0 +1,74 @@ +require "colorize" + +module LxChess + class TermBoard + LETTERS = ('a'..'z').to_a + + property :bg_dark, :bg_light, :fg_dark, :fg_light + + def initialize(@board : Board) + @flipped = false + @bg_dark = :green + @bg_light = :light_green + @fg_dark = :black + @fg_light = :white + end + + def set_scheme(bg_dark : Symbol, bg_light : Symbol, fg_dark : Symbol, fg_light : Symbol) + @bg_dark = bg_dark + @bg_light = bg_light + @fg_dark = fg_dark + @fg_light = fg_light + end + + def flip! + @flipped = !@flipped + end + + def width + @board.width + end + + def height + @board.height + end + + def ranks + @flipped ? 0.upto(height - 1) : (height - 1).downto(0) + end + + def files + @flipped ? (width - 1).downto(0) : 0.upto(width - 1) + end + + def draw(io = STDOUT) + ranks.each do |y| + io << (y + 1) << ": " + + files.each do |x| + index = @board.index(x, y) + piece = @board[index] + + background = (index + y) % 2 == 0 ? @bg_dark : @bg_light + + if piece + foreground = piece.white? ? @fg_light : @fg_dark + io << piece.symbol(true).colorize.back(background).fore(foreground) << " ".colorize.back(background) + else + io << " ".colorize.back(background) + end + end + + io << "\n" + end + + io << " " + + files.each do |x| + io << LETTERS[x] << " " + end + + io + end + end +end diff --git a/src/lx_chess/terminal.cr b/src/lx_chess/terminal.cr new file mode 100644 index 0000000..9890425 --- /dev/null +++ b/src/lx_chess/terminal.cr @@ -0,0 +1,29 @@ +module LxChess + class Terminal + # ttycom.h : _IOR('t', 104, struct winsize) + # ioccom.h : + # #define _IOC(inout, group, num, len) \ + # (inout | ((len & IOCPARM_MASK) << 16) | ((group) << 8) | (num)) + # TODO : Figure out how to define this dynamically per system + TIOCGWINSZ = 1074295912 + + lib C + struct Winsize + rows : UInt16 # rows, in characters + cols : UInt16 # columns, in characters + width : UInt16 # horizontal size, pixels + height : UInt16 # vertical size, pixels + end + + fun ioctl(fd : Int32, request : UInt32, winsize : C::Winsize*) : Int32 + end + + def self.size + C.ioctl(0, TIOCGWINSZ, out screen_size) + screen_size + end + + def initialize(@io = STDOUT) + end + end +end diff --git a/src/lx_chess/version.cr b/src/lx_chess/version.cr new file mode 100644 index 0000000..41473e8 --- /dev/null +++ b/src/lx_chess/version.cr @@ -0,0 +1,3 @@ +module LxChess + VERSION = "0.1.0" +end