just_checkers 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []