mirror of
https://github.com/SleepingInsomniac/lx_chess_cr
synced 2024-12-25 09:58:58 +01:00
Initial commit
This commit is contained in:
commit
f19fb93e5b
17 changed files with 408 additions and 0 deletions
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -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
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/docs/
|
||||||
|
/lib/
|
||||||
|
/bin/
|
||||||
|
/.shards/
|
||||||
|
*.dwarf
|
6
.travis.yml
Normal file
6
.travis.yml
Normal file
|
@ -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
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2021 Alex Clink <alexclink@gmail.com>
|
||||||
|
|
||||||
|
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.
|
38
README.md
Normal file
38
README.md
Normal file
|
@ -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 (<https://github.com/your-github-user/cr_lx_chess/fork>)
|
||||||
|
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
|
13
shard.yml
Normal file
13
shard.yml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
name: lx_chess
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
authors:
|
||||||
|
- Alex Clink <alexclink@gmail.com>
|
||||||
|
|
||||||
|
targets:
|
||||||
|
lx_chess:
|
||||||
|
main: src/lx_chess.cr
|
||||||
|
|
||||||
|
crystal: 1.0.0
|
||||||
|
|
||||||
|
license: MIT
|
7
spec/lx_chess_spec.cr
Normal file
7
spec/lx_chess_spec.cr
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
require "./spec_helper"
|
||||||
|
|
||||||
|
describe LxChess do
|
||||||
|
it "works" do
|
||||||
|
expect(false).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
2
spec/spec_helper.cr
Normal file
2
spec/spec_helper.cr
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
require "spec"
|
||||||
|
require "../src/lx_chess"
|
42
src/lx_chess.cr
Normal file
42
src/lx_chess.cr
Normal file
|
@ -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
|
43
src/lx_chess/board.cr
Normal file
43
src/lx_chess/board.cr
Normal file
|
@ -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
|
50
src/lx_chess/fen.cr
Normal file
50
src/lx_chess/fen.cr
Normal file
|
@ -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
|
12
src/lx_chess/game.cr
Normal file
12
src/lx_chess/game.cr
Normal file
|
@ -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
|
50
src/lx_chess/piece.cr
Normal file
50
src/lx_chess/piece.cr
Normal file
|
@ -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
|
4
src/lx_chess/player.cr
Normal file
4
src/lx_chess/player.cr
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module LxChess
|
||||||
|
class Player
|
||||||
|
end
|
||||||
|
end
|
74
src/lx_chess/term_board.cr
Normal file
74
src/lx_chess/term_board.cr
Normal file
|
@ -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
|
29
src/lx_chess/terminal.cr
Normal file
29
src/lx_chess/terminal.cr
Normal file
|
@ -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
|
3
src/lx_chess/version.cr
Normal file
3
src/lx_chess/version.cr
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module LxChess
|
||||||
|
VERSION = "0.1.0"
|
||||||
|
end
|
Loading…
Reference in a new issue