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 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
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.DS_Store
@@ -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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in just_checkers.gemspec
4
+ gemspec
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
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ t.test_files = FileList['test/just_checkers/*_test.rb']
6
+ end
7
+
8
+ desc 'run tests'
9
+ task :default => :test
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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,7 @@
1
+ require "just_checkers/version"
2
+
3
+ module JustCheckers
4
+
5
+ end
6
+
7
+ require 'just_checkers/game_state'
@@ -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
@@ -0,0 +1,3 @@
1
+ module JustCheckers
2
+ VERSION = "0.1.0"
3
+ 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: []