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