just_checkers 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +58 -0
- data/Rakefile +9 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/just_checkers.gemspec +25 -0
- data/lib/just_checkers.rb +7 -0
- data/lib/just_checkers/direction.rb +30 -0
- data/lib/just_checkers/game_state.rb +181 -0
- data/lib/just_checkers/piece.rb +39 -0
- data/lib/just_checkers/point.rb +35 -0
- data/lib/just_checkers/square.rb +85 -0
- data/lib/just_checkers/square_set.rb +145 -0
- data/lib/just_checkers/vector.rb +78 -0
- data/lib/just_checkers/version.rb +3 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9b222d98791ecb7fbeaccbbc243a5700804beccd
|
4
|
+
data.tar.gz: 52f55d6e55e7d1217a1b761b89c7bd692c8d97ad
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7fcf4df66998b315fdb28bdf6c84b9a379244c73da60edb87f91f05f9595c2458077aabff90d1766d13dbbde28a7bb28f18151dea8aec3ad1a5db5108638b0a9
|
7
|
+
data.tar.gz: 331229a35d08ee92e1dbd454efab6f18a5a637fd88dd697071e3b4467f0d9f3e9f3741928c04509bac5e1d38351e3613d42debbf992516a320d346cc1e30507d
|
data/.gitignore
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at mark@mrlhumphreys.com. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Mark Humphreys
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# JustCheckers
|
2
|
+
|
3
|
+
A checkers engine written in ruby. It provides a representation of a checkers game complete with rules enforcement and serialisation.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'just_checkers'
|
11
|
+
```
|
12
|
+
|
13
|
+
Or install it yourself as:
|
14
|
+
|
15
|
+
$ gem install just_checkers
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
To start, a new game state can be instantiated with the default state:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
game_state = JustCheckers::GameState.default
|
23
|
+
```
|
24
|
+
|
25
|
+
Moves can be made by passing in the player number, from co-ordinates and an array of to co-ordinates. It will return true if the move is valid, otherwise it will return false.
|
26
|
+
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
game_state.move!(1, {x: 1, y: 2}, [{x: 2, y: 3}])
|
30
|
+
```
|
31
|
+
|
32
|
+
The Winner can be found by calling winner on the object.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
game_state.winner
|
36
|
+
```
|
37
|
+
|
38
|
+
Also, the game can be serialized into a hash.
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
game_state.as_json
|
42
|
+
```
|
43
|
+
|
44
|
+
## Development
|
45
|
+
|
46
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
47
|
+
|
48
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
49
|
+
|
50
|
+
## Contributing
|
51
|
+
|
52
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mrlhumphreys/just_checkers. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
53
|
+
|
54
|
+
|
55
|
+
## License
|
56
|
+
|
57
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
58
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "just_checkers"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'just_checkers/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "just_checkers"
|
8
|
+
spec.version = JustCheckers::VERSION
|
9
|
+
spec.authors = ["Mark Humphreys"]
|
10
|
+
spec.email = ["mark@mrlhumphreys.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A checkers engine written in ruby}
|
13
|
+
spec.description = %q{Provides a representation of a checkers game complete with rules enforcement and serialisation.}
|
14
|
+
spec.homepage = "https://github.com/mrlhumphreys/just_checkers"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency 'minitest', '~> 5.8'
|
25
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module JustCheckers
|
2
|
+
|
3
|
+
# = Direction
|
4
|
+
#
|
5
|
+
# The Direction that something is moving on a 2d plane
|
6
|
+
class Direction
|
7
|
+
|
8
|
+
# New objects can be instantiated with
|
9
|
+
#
|
10
|
+
# * +x+ - x magnitude
|
11
|
+
# * +y+ - y magnitude
|
12
|
+
#
|
13
|
+
# ==== Example:
|
14
|
+
# # Instantiates a new Direction
|
15
|
+
# JustCheckers::Direction.new({
|
16
|
+
# x: 1,
|
17
|
+
# y: 1
|
18
|
+
# })
|
19
|
+
def initialize(x, y)
|
20
|
+
@x, @y = x, y
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :x, :y
|
24
|
+
|
25
|
+
# check if directions are equal by seeing if their magnitudes are equal.
|
26
|
+
def ==(other)
|
27
|
+
self.x == other.x && self.y == other.y
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'just_checkers/square_set'
|
2
|
+
|
3
|
+
module JustCheckers
|
4
|
+
|
5
|
+
# = Game State
|
6
|
+
#
|
7
|
+
# Represents a game of Checkers in progress.
|
8
|
+
class GameState
|
9
|
+
|
10
|
+
# New objects can be instantiated by passing in a hash with
|
11
|
+
#
|
12
|
+
# * +current_player_number+ - Who's turn it is, 1 or 2
|
13
|
+
# * +squares+ - An array of squares, each with x and y co-ordinates and a piece.
|
14
|
+
#
|
15
|
+
# ==== Example:
|
16
|
+
# # Instantiates a new Game of Checkers
|
17
|
+
# JustCheckers::GameState.new({
|
18
|
+
# current_player_number: 1,
|
19
|
+
# squares: [
|
20
|
+
# { x: 1, y: 0, piece: { player_number: 1, direction: 1, king: false }}
|
21
|
+
# ]
|
22
|
+
# })
|
23
|
+
def initialize(args = {})
|
24
|
+
@current_player_number = args[:current_player_number]
|
25
|
+
@squares = SquareSet.new(squares: args[:squares])
|
26
|
+
end
|
27
|
+
|
28
|
+
# Instantiates a new GameState object in the starting position
|
29
|
+
def self.default
|
30
|
+
new({
|
31
|
+
current_player_number: 1,
|
32
|
+
squares: [
|
33
|
+
{ x: 1, y: 0, piece: { player_number: 1, direction: 1, king: false }},
|
34
|
+
{ x: 3, y: 0, piece: { player_number: 1, direction: 1, king: false }},
|
35
|
+
{ x: 5, y: 0, piece: { player_number: 1, direction: 1, king: false }},
|
36
|
+
{ x: 7, y: 0, piece: { player_number: 1, direction: 1, king: false }},
|
37
|
+
|
38
|
+
{ x: 0, y: 1, piece: { player_number: 1, direction: 1, king: false }},
|
39
|
+
{ x: 2, y: 1, piece: { player_number: 1, direction: 1, king: false }},
|
40
|
+
{ x: 4, y: 1, piece: { player_number: 1, direction: 1, king: false }},
|
41
|
+
{ x: 6, y: 1, piece: { player_number: 1, direction: 1, king: false }},
|
42
|
+
|
43
|
+
{ x: 1, y: 2, piece: { player_number: 1, direction: 1, king: false }},
|
44
|
+
{ x: 3, y: 2, piece: { player_number: 1, direction: 1, king: false }},
|
45
|
+
{ x: 5, y: 2, piece: { player_number: 1, direction: 1, king: false }},
|
46
|
+
{ x: 7, y: 2, piece: { player_number: 1, direction: 1, king: false }},
|
47
|
+
|
48
|
+
{ x: 0, y: 3, piece: nil },
|
49
|
+
{ x: 2, y: 3, piece: nil },
|
50
|
+
{ x: 4, y: 3, piece: nil },
|
51
|
+
{ x: 6, y: 3, piece: nil },
|
52
|
+
|
53
|
+
{ x: 1, y: 4, piece: nil },
|
54
|
+
{ x: 3, y: 4, piece: nil },
|
55
|
+
{ x: 5, y: 4, piece: nil },
|
56
|
+
{ x: 7, y: 4, piece: nil },
|
57
|
+
|
58
|
+
{ x: 0, y: 5, piece: { player_number: 2, direction: -1, king: false }},
|
59
|
+
{ x: 2, y: 5, piece: { player_number: 2, direction: -1, king: false }},
|
60
|
+
{ x: 4, y: 5, piece: { player_number: 2, direction: -1, king: false }},
|
61
|
+
{ x: 6, y: 5, piece: { player_number: 2, direction: -1, king: false }},
|
62
|
+
|
63
|
+
{ x: 1, y: 6, piece: { player_number: 2, direction: -1, king: false }},
|
64
|
+
{ x: 3, y: 6, piece: { player_number: 2, direction: -1, king: false }},
|
65
|
+
{ x: 5, y: 6, piece: { player_number: 2, direction: -1, king: false }},
|
66
|
+
{ x: 7, y: 6, piece: { player_number: 2, direction: -1, king: false }},
|
67
|
+
|
68
|
+
{ x: 0, y: 7, piece: { player_number: 2, direction: -1, king: false }},
|
69
|
+
{ x: 2, y: 7, piece: { player_number: 2, direction: -1, king: false }},
|
70
|
+
{ x: 4, y: 7, piece: { player_number: 2, direction: -1, king: false }},
|
71
|
+
{ x: 6, y: 7, piece: { player_number: 2, direction: -1, king: false }},
|
72
|
+
]
|
73
|
+
})
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :current_player_number, :squares
|
77
|
+
|
78
|
+
# Returns a hash serialized representation of the game state
|
79
|
+
def as_json
|
80
|
+
{ current_player_number: current_player_number, squares: squares.as_json, winner: winner }
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns the json serialized representation of the game state.
|
84
|
+
def to_json
|
85
|
+
as_json.to_json
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns the player number of the winner. It returns nil if there is no winner
|
89
|
+
def winner
|
90
|
+
if squares.occupied_by(1).none? { |s| s.possible_jumps(s.piece, squares).any? || s.possible_moves(s.piece, squares).any? }
|
91
|
+
2
|
92
|
+
elsif squares.occupied_by(2).none? { |s| s.possible_jumps(s.piece, squares).any? || s.possible_moves(s.piece, squares).any? }
|
93
|
+
1
|
94
|
+
else
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Moves a piece owned by the player, from one square, to another
|
100
|
+
#
|
101
|
+
# * +player_number+ - the player number, 1 or 2
|
102
|
+
# * +from+ - A hash containing x and y co-ordinates.
|
103
|
+
# * +to+ - An array of hashes containing x and y co-ordinates.
|
104
|
+
#
|
105
|
+
# It moves the piece and returns true if the move is valid and it's the player's turn.
|
106
|
+
# It returns false otherwise.
|
107
|
+
#
|
108
|
+
# ==== Example:
|
109
|
+
# # Moves a piece from a square to perform a double jump
|
110
|
+
# game_state.move!(1, {x: 0, y: 1}, [{x: 1, y: 2}, {x: 3, y: 4}])
|
111
|
+
def move!(player_number, from, to)
|
112
|
+
from_square = squares.find_by_x_and_y(from[:x].to_i, from[:y].to_i)
|
113
|
+
to_squares = to.map { |p| squares.find_by_x_and_y(p[:x].to_i, p[:y].to_i) }
|
114
|
+
|
115
|
+
if player_number != current_player_number
|
116
|
+
false
|
117
|
+
else
|
118
|
+
if move_valid?(from_square, to_squares)
|
119
|
+
perform_move!(from_square, to_squares)
|
120
|
+
promote!(to_squares.last) if promotable?(to_squares.last)
|
121
|
+
turn!
|
122
|
+
true
|
123
|
+
else
|
124
|
+
false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def move_valid?(from, to) # :nodoc:
|
132
|
+
legs = to.unshift(from)
|
133
|
+
if from && from.piece
|
134
|
+
legs.each_cons(2).map do |a, b|
|
135
|
+
if squares.occupied_by(from.piece.player_number).any? { |s| s.possible_jumps(s.piece, squares).any? }
|
136
|
+
a.possible_jumps(from.piece, squares).include?(b)
|
137
|
+
else
|
138
|
+
a.possible_moves(from.piece, squares).include?(b)
|
139
|
+
end
|
140
|
+
end.all?
|
141
|
+
else
|
142
|
+
false
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def promote!(square) # :nodoc:
|
147
|
+
square.piece.promote!
|
148
|
+
end
|
149
|
+
|
150
|
+
def perform_move!(from, to) # :nodoc:
|
151
|
+
legs = to.unshift(from)
|
152
|
+
legs.each_cons(2) do |a, b|
|
153
|
+
between_square = squares.between(a, b).first
|
154
|
+
between_square.piece = nil if between_square
|
155
|
+
end
|
156
|
+
|
157
|
+
to.last.piece = from.piece
|
158
|
+
from.piece = nil
|
159
|
+
end
|
160
|
+
|
161
|
+
def turn! # :nodoc:
|
162
|
+
@current_player_number = other_player_number
|
163
|
+
end
|
164
|
+
|
165
|
+
def other_player_number # :nodoc:
|
166
|
+
current_player_number == 1 ? 2 : 1
|
167
|
+
end
|
168
|
+
|
169
|
+
def promotable?(square) # :nodoc:
|
170
|
+
case square.piece.direction
|
171
|
+
when 1
|
172
|
+
square.y == 7
|
173
|
+
when -1
|
174
|
+
square.y == 0
|
175
|
+
else
|
176
|
+
false
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module JustCheckers
|
2
|
+
# = Game State
|
3
|
+
#
|
4
|
+
# A piece that can move on a checkers board
|
5
|
+
class Piece
|
6
|
+
|
7
|
+
# New objects can be instantiated by passing in a hash with
|
8
|
+
#
|
9
|
+
# * +player_number+ - The player who owns the piece, 1 or 2
|
10
|
+
# * +direction+ - The direction forward on the board, 1 for moving down, -1 for moving up.
|
11
|
+
# * +king+ - Set to true if the piece has been crowned
|
12
|
+
#
|
13
|
+
# ==== Example:
|
14
|
+
# # Instantiates a new Piece
|
15
|
+
# JustCheckers::Piece.new({
|
16
|
+
# player_number: 1,
|
17
|
+
# direction: 1,
|
18
|
+
# king: false
|
19
|
+
# })
|
20
|
+
def initialize(args = {})
|
21
|
+
@player_number = args[:player_number]
|
22
|
+
@direction = args[:direction]
|
23
|
+
@king = args[:king]
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :player_number, :direction, :king
|
27
|
+
alias_method :king?, :king
|
28
|
+
|
29
|
+
# promotes the piece by setting the +king+ attribute to true.
|
30
|
+
def promote!
|
31
|
+
@king = true
|
32
|
+
end
|
33
|
+
|
34
|
+
# returns a serialized piece as a hash
|
35
|
+
def as_json
|
36
|
+
{ player_number: player_number, direction: direction, king: king }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module JustCheckers
|
2
|
+
|
3
|
+
# = Point
|
4
|
+
#
|
5
|
+
# A point with an x and y co-ordinates
|
6
|
+
class Point
|
7
|
+
|
8
|
+
# New objects can be instantiated with
|
9
|
+
#
|
10
|
+
# * +x+ - x co-ordinate
|
11
|
+
# * +y+ - y co-ordinate
|
12
|
+
#
|
13
|
+
# ==== Example:
|
14
|
+
# # Instantiates a new Point
|
15
|
+
# JustCheckers::Point.new({
|
16
|
+
# x: 1,
|
17
|
+
# y: 1
|
18
|
+
# })
|
19
|
+
def initialize(x, y)
|
20
|
+
@x, @y = x, y
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :x, :y
|
24
|
+
|
25
|
+
# add a point to another point by adding their co-ordinates and returning a new point.
|
26
|
+
def +(other)
|
27
|
+
self.class.new(self.x + other.x, self.y + other.y)
|
28
|
+
end
|
29
|
+
|
30
|
+
# check if points are equal by seeing if their co-ordinates are equal.
|
31
|
+
def ==(other)
|
32
|
+
self.x == other.x && self.y == other.y
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'just_checkers/piece'
|
2
|
+
require 'just_checkers/point'
|
3
|
+
|
4
|
+
module JustCheckers
|
5
|
+
|
6
|
+
# = Square
|
7
|
+
#
|
8
|
+
# A Square on a board of checkers
|
9
|
+
class Square
|
10
|
+
|
11
|
+
# New objects can be instantiated by passing in a hash with
|
12
|
+
#
|
13
|
+
# * +x+ - The x co-ordinate of the square.
|
14
|
+
# * +y+ - The y co-ordinate of the square.
|
15
|
+
# * +piece+ - The piece on the square, can be a piece object or hash or nil.
|
16
|
+
#
|
17
|
+
# ==== Example:
|
18
|
+
# # Instantiates a new Square
|
19
|
+
# JustCheckers::Square.new({
|
20
|
+
# x: 1,
|
21
|
+
# y: 0,
|
22
|
+
# piece: { player_number: 1, direction: 1, king: false }
|
23
|
+
# })
|
24
|
+
def initialize(args = {})
|
25
|
+
@x = args[:x]
|
26
|
+
@y = args[:y]
|
27
|
+
if args[:piece].is_a?(Hash)
|
28
|
+
@piece = Piece.new(args[:piece])
|
29
|
+
else
|
30
|
+
@piece = args[:piece]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :x, :y
|
35
|
+
attr_accessor :piece
|
36
|
+
|
37
|
+
# checks if the square matches the attributes passed.
|
38
|
+
#
|
39
|
+
# * +attribute+ - a symbol for the squares attribute
|
40
|
+
# * +value+ - a value to match on. Can be a hash of attribute/value pairs for deep matching
|
41
|
+
#
|
42
|
+
# ==== Example:
|
43
|
+
# # Check if square has a piece owned by player 1
|
44
|
+
# square.attribute_match?(:piece, player_number: 1)
|
45
|
+
def attribute_match?(attribute, value)
|
46
|
+
hash_obj_matcher = lambda do |obj, k, v|
|
47
|
+
value = obj.send(k)
|
48
|
+
if !value.nil? && v.is_a?(Hash)
|
49
|
+
v.all? { |k2,v2| hash_obj_matcher.call(value, k2, v2) }
|
50
|
+
else
|
51
|
+
value == v
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
hash_obj_matcher.call(self, attribute, value)
|
56
|
+
end
|
57
|
+
|
58
|
+
# returns true if the square has no piece on it.
|
59
|
+
def unoccupied?
|
60
|
+
piece.nil?
|
61
|
+
end
|
62
|
+
|
63
|
+
# returns a point object with the square's co-ordinates.
|
64
|
+
def point
|
65
|
+
Point.new(x, y)
|
66
|
+
end
|
67
|
+
|
68
|
+
# returns all squares that a piece on this square could jump to, given the board.
|
69
|
+
def possible_jumps(piece, squares)
|
70
|
+
squares.two_squares_away_from(self).in_direction_of(piece, self).unoccupied.select do |s|
|
71
|
+
squares.between(self, s).occupied_by_opponent_of(piece.player_number).any?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# returns all squares that a piece on this square could jump to, given the board.
|
76
|
+
def possible_moves(piece, squares)
|
77
|
+
squares.one_square_away_from(self).in_direction_of(piece, self).unoccupied
|
78
|
+
end
|
79
|
+
|
80
|
+
# returns a serialized version of the square as a hash
|
81
|
+
def as_json
|
82
|
+
{ x: x, y: y, piece: piece && piece.as_json }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'just_checkers/square'
|
3
|
+
require 'just_checkers/vector'
|
4
|
+
|
5
|
+
module JustCheckers
|
6
|
+
|
7
|
+
# = Square Set
|
8
|
+
#
|
9
|
+
# A collection of Squares with useful filtering functions
|
10
|
+
class SquareSet
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
# New objects can be instantiated by passing in a hash with squares.
|
14
|
+
# They can be square objects or hashes.
|
15
|
+
#
|
16
|
+
# * +squares+ - An array of squares, each with x and y co-ordinates and a piece.
|
17
|
+
#
|
18
|
+
# ==== Example:
|
19
|
+
# # Instantiates a new Square Set
|
20
|
+
# JustCheckers::SquareSet.new({
|
21
|
+
# squares: [
|
22
|
+
# { x: 1, y: 0, piece: { player_number: 1, direction: 1, king: false }}
|
23
|
+
# ]
|
24
|
+
# })
|
25
|
+
def initialize(args = {})
|
26
|
+
if args[:squares].first.class == Square
|
27
|
+
@squares = args[:squares]
|
28
|
+
else
|
29
|
+
@squares = args[:squares].map { |s| Square.new(s) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :squares
|
34
|
+
|
35
|
+
def_delegator :squares, :first
|
36
|
+
def_delegator :squares, :size
|
37
|
+
def_delegator :squares, :include?
|
38
|
+
def_delegator :squares, :any?
|
39
|
+
def_delegator :squares, :none?
|
40
|
+
def_delegator :squares, :empty?
|
41
|
+
|
42
|
+
# Iterate over the squares with a block and behaves like Enumerable#each.
|
43
|
+
def each(&block)
|
44
|
+
squares.each(&block)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Filter the squares with a block and behaves like Enumerable#select.
|
48
|
+
# It returns a SquareSet with the filtered squares.
|
49
|
+
def select(&block)
|
50
|
+
self.class.new(squares: squares.select(&block))
|
51
|
+
end
|
52
|
+
|
53
|
+
# Filter the squares with a hash of attribute and matching values.
|
54
|
+
#
|
55
|
+
# ==== Example:
|
56
|
+
# # Find all squares where piece is nil
|
57
|
+
# square_set.where(piece: nil)
|
58
|
+
def where(hash)
|
59
|
+
res = hash.inject(squares) do |memo, (k, v)|
|
60
|
+
memo.select { |s| s.attribute_match?(k, v) }
|
61
|
+
end
|
62
|
+
self.class.new(squares: res)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Find the square with the matching x and y co-ordinates
|
66
|
+
#
|
67
|
+
# ==== Example:
|
68
|
+
# # Find the square at 4,2
|
69
|
+
# square_set.find_by_x_and_y(4, 2)
|
70
|
+
def find_by_x_and_y(x, y)
|
71
|
+
select { |s| s.x == x && s.y == y }.first
|
72
|
+
end
|
73
|
+
|
74
|
+
# Return all squares that are one square away from the passed square.
|
75
|
+
def one_square_away_from(square)
|
76
|
+
select { |s| Vector.new(square, s).magnitude == 1 }
|
77
|
+
end
|
78
|
+
|
79
|
+
# Return all squares that are two squares away from the passed square.
|
80
|
+
def two_squares_away_from(square)
|
81
|
+
select { |s| Vector.new(square, s).magnitude == 2 }
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns squares in the direction of the piece from the square
|
85
|
+
# If the piece is normal, it returns squares in front of it.
|
86
|
+
# If the piece is king, it returns all squares.
|
87
|
+
#
|
88
|
+
# ==== Example:
|
89
|
+
# # Get all squares in the direction of the piece.
|
90
|
+
# square_set.in_direction_of(piece, square)
|
91
|
+
def in_direction_of(piece, square)
|
92
|
+
select do |s|
|
93
|
+
piece.king? || Vector.new(square, s).direction_y == piece.direction
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns all squares without pieces on them.
|
98
|
+
def unoccupied
|
99
|
+
select { |s| s.piece.nil? }
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns squares between a and b.
|
103
|
+
# Only squares that are in the same diagonal will return squares.
|
104
|
+
#
|
105
|
+
# ==== Example:
|
106
|
+
# # Get all squares between square_a and square_b
|
107
|
+
# square_set.between(square_a, square_b)
|
108
|
+
def between(a, b)
|
109
|
+
vector = Vector.new(a, b)
|
110
|
+
if vector.diagonal
|
111
|
+
point_counter = a.point
|
112
|
+
direction = vector.direction
|
113
|
+
squares = []
|
114
|
+
|
115
|
+
while point_counter != b.point
|
116
|
+
point_counter = point_counter + direction
|
117
|
+
square = find_by_x_and_y(point_counter.x, point_counter.y)
|
118
|
+
if square && square.point != b.point
|
119
|
+
squares.push(square)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
else
|
123
|
+
squares = []
|
124
|
+
end
|
125
|
+
self.class.new(squares: squares)
|
126
|
+
end
|
127
|
+
|
128
|
+
# takes a player number and returns all squares occupied by the opponent of the player
|
129
|
+
def occupied_by_opponent_of(player_number)
|
130
|
+
select { |s| s.piece && s.piece.player_number != player_number }
|
131
|
+
end
|
132
|
+
|
133
|
+
# takes a player number and returns all squares occupied by the player
|
134
|
+
def occupied_by(player_number)
|
135
|
+
select { |s| s.piece && s.piece.player_number == player_number }
|
136
|
+
end
|
137
|
+
|
138
|
+
attr_reader :squares
|
139
|
+
|
140
|
+
# serializes the squares as a hash
|
141
|
+
def as_json
|
142
|
+
squares.map(&:as_json)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'just_checkers/direction'
|
2
|
+
|
3
|
+
module JustCheckers
|
4
|
+
|
5
|
+
# = Vector
|
6
|
+
#
|
7
|
+
# An element of Vector space
|
8
|
+
class Vector
|
9
|
+
|
10
|
+
# New objects can be instantiated by passing in a two points with x and y co-ordinates
|
11
|
+
#
|
12
|
+
# * +a+ - A point with an x and y co-ordinates.
|
13
|
+
# * +b+ - A point with an x and y co-ordinates.
|
14
|
+
#
|
15
|
+
# ==== Example:
|
16
|
+
# # Instantiates a new Vector
|
17
|
+
# JustCheckers::Vector.new({
|
18
|
+
# a: JustCheckers::Point.new(x: 1, y: 1),
|
19
|
+
# b: JustCheckers::Point.new(x: 3, y: 3)
|
20
|
+
# })
|
21
|
+
def initialize(a, b)
|
22
|
+
@a, @b = a, b
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :a, :b
|
26
|
+
|
27
|
+
# returns how big the vector is if it's diagonal, otherwise, nil.
|
28
|
+
def magnitude
|
29
|
+
if diagonal
|
30
|
+
dx.abs
|
31
|
+
else
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# returns a direction of the vector as a object
|
37
|
+
def direction
|
38
|
+
Direction.new(direction_x, direction_y)
|
39
|
+
end
|
40
|
+
|
41
|
+
# returns the x component of the direction, 1 if moving down, -1 if moving up, 0 otherwise
|
42
|
+
def direction_x
|
43
|
+
if dx > 0
|
44
|
+
1
|
45
|
+
elsif dx == 0
|
46
|
+
0
|
47
|
+
else
|
48
|
+
-1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# returns the y component of the direction, 1 if moving right, -1 if moving left, 0 otherwise
|
53
|
+
def direction_y
|
54
|
+
if dy > 0
|
55
|
+
1
|
56
|
+
elsif dy == 0
|
57
|
+
0
|
58
|
+
else
|
59
|
+
-1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# returns true if the vector is diagonal.
|
64
|
+
def diagonal
|
65
|
+
dx.abs == dy.abs
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def dx
|
71
|
+
b.x - a.x
|
72
|
+
end
|
73
|
+
|
74
|
+
def dy
|
75
|
+
b.y - a.y
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: just_checkers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mark Humphreys
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.11'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.11'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.8'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.8'
|
55
|
+
description: Provides a representation of a checkers game complete with rules enforcement
|
56
|
+
and serialisation.
|
57
|
+
email:
|
58
|
+
- mark@mrlhumphreys.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- CODE_OF_CONDUCT.md
|
65
|
+
- Gemfile
|
66
|
+
- LICENSE.txt
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- bin/console
|
70
|
+
- bin/setup
|
71
|
+
- just_checkers.gemspec
|
72
|
+
- lib/just_checkers.rb
|
73
|
+
- lib/just_checkers/direction.rb
|
74
|
+
- lib/just_checkers/game_state.rb
|
75
|
+
- lib/just_checkers/piece.rb
|
76
|
+
- lib/just_checkers/point.rb
|
77
|
+
- lib/just_checkers/square.rb
|
78
|
+
- lib/just_checkers/square_set.rb
|
79
|
+
- lib/just_checkers/vector.rb
|
80
|
+
- lib/just_checkers/version.rb
|
81
|
+
homepage: https://github.com/mrlhumphreys/just_checkers
|
82
|
+
licenses:
|
83
|
+
- MIT
|
84
|
+
metadata: {}
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 2.4.5
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: A checkers engine written in ruby
|
105
|
+
test_files: []
|