Qi (Chinese: 棋; pinyin: qí) is a lightweight, flexible, and adaptable tool for representing board game positions, built in Ruby. It is designed to be game-agnostic and can be used with a variety of board games such as Chess, Four-Player Chess, Go, Makruk, Shogi, and Xiangqi.
Qi uses a unique approach where the state of a game is represented through capturing the pieces in play, the arrangement of pieces on the board, the sequence of turns, and other possible states that a game can have.
- Game Agnostic: Qi can be used to represent board game positions for a wide variety of games. Whether you are playing Chess, Makruk, Shogi, or Xiangqi, Qi's flexible structure allows you to accurately capture the state of your game.
- Flexible Position Representation: Qi captures the state of the game by recording the pieces in play, their arrangement on the board, the sequence of turns, and other additional states of the game. This enables a comprehensive view of the game at any given point.
- State Manipulation: Qi allows for manipulation and update of game states through the
commit
method, allowing transitions between game states. - Equality Checks: With the
eql?
method, Qi allows for comparisons between different game states, which can be useful for tracking game progress, detecting repeats, or even in creating AI for your games. - Turn Management: Qi keeps track of the sequence of turns allowing users to identify whose turn it is currently.
- Access to Game Data: Qi provides methods to access the current arrangement of pieces on the board (
squares_hash
) and the pieces captured by each player (captures_hash
), helping users understand the current status of the game. It also allows access to a list of captured pieces (captures_array
). - Customizability: Qi is flexible and allows for customization as per your needs. The keys and values of the
captures_hash
andsquares_hash
can be any kind of object, as well as the items fromturns
and values fromstate
.
While Qi
does not generate game moves itself, it serves as a solid foundation upon which game engines can be built. Its design is focused on providing a robust and adaptable representation of game states, paving the way for the development of diverse board game applications.
Add this line to your application's Gemfile:
gem "qi"
And then execute:
bundle install
Or install it yourself as:
gem install qi
The following usage example is derived from a classic tsume shogi (詰将棋) problem, which translates to mate shogi - a popular genre of shogi problems where the goal is to checkmate the opponent's king. In the provided setup, the attacking side is in possession of a silver general (S), a promoted bishop (+B) positioned on square 43, and a promoted pawn (+P) on square 22.
On the defending side, there is a king (k) situated on square 4, surrounded by two silver generals (s) on squares 3 and 5 respectively.
In this scenario, Qi
allows us to represent the state of the game and apply changes as moves are made. Please follow the given example to understand how to create such a representation and how to update it:
require "qi"
# Initialize an array for each player's captured pieces
north_captures = %w[r r b g g g g s n n n n p p p p p p p p p p p p p p p p p]
south_captures = %w[S]
# Combine and count each player's captured pieces
captures = Hash.new(0)
(north_captures + south_captures).each { |piece| captures[piece] += 1 }
# Define the squares occupied by each piece on the board
squares = { 3 => "s", 4 => "k", 5 => "s", 22 => "+P", 43 => "+B" }
# Create a new game position
qi0 = Qi.new(captures, squares, [0, 1])
# Verify the properties of the game position
qi0.captures_array # => ["S", "b", "g", "g", "g", "g", "n", "n", "n", "n", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "r", "r", "s"]
qi0.captures_hash # => {"r"=>2, "b"=>1, "g"=>4, "s"=>1, "n"=>4, "p"=>17, "S"=>1}
qi0.squares_hash # => {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 43=>"+B"}
qi0.state # => {}
qi0.turn # => 0
qi0.turns # => [0, 1]
qi0.eql?(Qi.new(captures, squares, [0, 1])) # => true
qi0.eql?(Qi.new(captures, squares, [1, 0])) # => false
# Move a piece on the board and check the game state
qi1 = qi0.commit([], [], { 43 => nil, 13 => "+B" }, in_check: true)
qi1.captures_array # => ["S", "b", "g", "g", "g", "g", "n", "n", "n", "n", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "r", "r", "s"]
qi1.captures_hash # => {"r"=>2, "b"=>1, "g"=>4, "s"=>1, "n"=>4, "p"=>17, "S"=>1}
qi1.squares_hash # => {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}
qi1.state # => {:in_check=>true}
qi1.turn # => 1
qi1.turns # => [1, 0]
qi1.eql?(Qi.new(captures, squares, [0, 1])) # => false
qi1.eql?(Qi.new(captures, squares, [1, 0])) # => false
In this example, we first create a Qi
object to represent a game position with Qi.new
. Then, we check various aspects of the game state using the methods provided by Qi
. After that, we create a new game state qi1
by committing changes to the existing state qi0
. Finally, we again check various aspects of the new game state.
The gem is available as open source under the terms of the MIT License.
This project is maintained by Sashité — promoting chess variants and sharing the beauty of Chinese, Japanese, and Western chess cultures.