eighty-one 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
+ SHA256:
3
+ metadata.gz: 608d79543717c3d25a2c22590db7e807d153a3b9c3cce49026d40d70dd0a0d00
4
+ data.tar.gz: 3aa116facf776eca0c8b9229c11864fdb5c4731225d86cfc9390fc83dfecdd89
5
+ SHA512:
6
+ metadata.gz: a0885c724c76d86b541d7cc66e1f89ec3fe79fefed1a47268eeca021dee20de5ff8f03841f2aa3e9f8e8930f2b1486c9ac96133d3d290ee28d15bf3fd2633606
7
+ data.tar.gz: 4403cf8c809dfdbb12f5a105a4ed26f7bf30d9041dbab42bfd0b58c082516c742787988a70720079b5e1fa37120ebbdddc42ef0ac99017e0300d715f35618d2f
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in eighty-one.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,24 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ eighty-one (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ power_assert (1.1.1)
10
+ rake (10.5.0)
11
+ test-unit (3.2.7)
12
+ power_assert
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ bundler (~> 1.16)
19
+ eighty-one!
20
+ rake (~> 10.0)
21
+ test-unit
22
+
23
+ BUNDLED WITH
24
+ 1.16.1
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 youchan
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,27 @@
1
+ # EightyOne
2
+
3
+ A game engine of Shogi.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'eighty-one'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install eighty-one
20
+
21
+ ## Contributing
22
+
23
+ Bug reports and pull requests are welcome on GitHub at https://github.com/youchan/eighty-one.
24
+
25
+ ## License
26
+
27
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc "Run tests"
4
+ task :test do
5
+ ruby("test/run_test.rb")
6
+ end
7
+
8
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "eighty/one"
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(__FILE__)
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,27 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "eighty_one/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "eighty-one"
8
+ spec.version = EightyOne::VERSION
9
+ spec.authors = ["youchan"]
10
+ spec.email = ["youchan01@gmail.com"]
11
+
12
+ spec.summary = %q{A game engine of Shogi.}
13
+ spec.description = %q{A game engine of Shogi.}
14
+ spec.homepage = "https://github.com/youchan/eighty-one"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.16"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "test-unit"
27
+ end
@@ -0,0 +1,183 @@
1
+ module EightyOne
2
+ class Board
3
+ include Helper
4
+
5
+ def initialize
6
+ @board = Array.new(81)
7
+ @hands = Struct.new(:sente, :gote).new([], [])
8
+ end
9
+
10
+ def initial_state
11
+ (1..9).each do |i|
12
+ self[i, 7] = Pieces::Fu.new(:sente)
13
+ self[i, 3] = Pieces::Fu.new(:gote)
14
+ end
15
+ [1, 9].each do |i|
16
+ self[i, 9] = Pieces::Ky.new(:sente)
17
+ self[i, 1] = Pieces::Ky.new(:gote)
18
+ end
19
+ [2, 8].each do |i|
20
+ self[i, 9] = Pieces::Ke.new(:sente)
21
+ self[i, 1] = Pieces::Ke.new(:gote)
22
+ end
23
+ [3, 7].each do |i|
24
+ self[i, 9] = Pieces::Gi.new(:sente)
25
+ self[i, 1] = Pieces::Gi.new(:gote)
26
+ end
27
+ [4, 6].each do |i|
28
+ self[i, 9] = Pieces::Ki.new(:sente)
29
+ self[i, 1] = Pieces::Ki.new(:gote)
30
+ end
31
+ self[2, 8] = Pieces::Ka.new(:sente)
32
+ self[8, 2] = Pieces::Ka.new(:gote)
33
+ self[8, 8] = Pieces::Hi.new(:sente)
34
+ self[2, 2] = Pieces::Hi.new(:gote)
35
+ self[5, 9] = Pieces::Ou.new(:sente)
36
+ self[5, 1] = Pieces::Ou.new(:gote)
37
+ end
38
+
39
+ def inside?(col, row)
40
+ 1 <= row && 9 >= row && 1 <= col && 9 >= col
41
+ end
42
+
43
+ def double_fu?(piece, col, row)
44
+ piece.face.symbol == :FU && (1..9).any?{|i| self[i, row]&.yield_self{|p| p.face.symbol == :FU && p.turn == piece.turn } }
45
+ end
46
+
47
+ def can_move_to_any_place?(piece, col, row)
48
+ !([:FU, :KY].include?(piece.face.symbol) && (piece.sente? ? col == 1 : col == 9))
49
+ end
50
+
51
+ def placeable?(piece, col, row, from_hand = false)
52
+ res = inside?(col, row) && !(self[col, row]&.turn == piece.turn)
53
+ res &&= can_move_to_any_place?(piece, col, row) && !double_fu?(piece, col, row) if from_hand
54
+ res
55
+ end
56
+
57
+ def []=(col, row, value)
58
+ assert(inside?(col, row))
59
+ assert(value.nil? || Piece === value)
60
+ @board[(row - 1) * 9 + 9 - col] = value
61
+ end
62
+
63
+ def [](col, row)
64
+ @board[(row - 1) * 9 + 9 - col]
65
+ end
66
+
67
+ def sente_hands
68
+ @hands.sente
69
+ end
70
+
71
+ def gote_hands
72
+ @hands.gote
73
+ end
74
+
75
+ alias :at :[]
76
+
77
+ def row(row)
78
+ @board[(row - 1) * 9, 9].reverse
79
+ end
80
+
81
+ def dests_from(col, row)
82
+ piece = self.at(col, row)
83
+ assert(Piece === piece)
84
+ dest = Proc.new{|(c, r)| [col + c, piece.sente? ? row - r : row + r] }
85
+ piece.face.movements.map do |m|
86
+ if EightyOne::Faces::Direction === m
87
+ captured = false
88
+ m.map{|p| dest[p] }.take_while do |p|
89
+ (!captured && placeable?(piece, *p)).tap do |x|
90
+ captured = x && self.at(*p)
91
+ end
92
+ end
93
+ else
94
+ dest[m].yield_self{|p| placeable?(piece, *p) ? [p] : [] }
95
+ end
96
+ end.flatten(1)
97
+ end
98
+
99
+ def move_from(col, row)
100
+ Movement.new(self, col, row)
101
+ end
102
+
103
+ def capture(piece)
104
+ @hands[piece.turn] << piece
105
+ end
106
+
107
+ def place(piece, col, row)
108
+ dest = self.at(col, row)
109
+ if dest
110
+ assert(Piece === dest)
111
+ assert(dest.turn != piece.turn)
112
+ capture(dest.reset(piece.turn))
113
+ end
114
+ self[col, row] = piece
115
+ end
116
+
117
+ def encode
118
+ board_map = (0..81).step(4).map do |i|
119
+ @board.slice(i, 4).map.with_index{|piece, i| (piece ? 1 : 0) << (3 - i) }.sum.to_s(16)
120
+ end.join
121
+
122
+ on_board = (1..9).map do |row|
123
+ row(row).compact.map(&:to_s).join
124
+ end.join
125
+
126
+ hands = @hands.sente.map(&:to_s).join + @hands.gote.map(&:to_s).join
127
+
128
+ board_map + on_board + ?/ + hands
129
+ end
130
+
131
+ def self.decode(code)
132
+ board = Board.new
133
+ (board_map, pieces) = code.unpack('a21a*')
134
+ (on_board, hands) = pieces.split(?/).map{|x| x.chars.each_slice(3).map(&:join) }
135
+
136
+ code[0,21].to_i(16).to_s(2).chars.each_slice(9).with_index do |row, y|
137
+ row.each_with_index do |cell, x|
138
+ board[9 - x, y + 1] = Piece.decode(on_board.shift) if cell == '1'
139
+ end
140
+ end
141
+
142
+ hands.map{|s| Piece.decode(s) }.each do |piece|
143
+ case piece.turn
144
+ when :sente
145
+ board.sente_hands << piece
146
+ when :gote
147
+ board.gote_hands << piece
148
+ end
149
+ end
150
+ board
151
+ end
152
+
153
+ def to_csi
154
+ (1..9).map do |i|
155
+ "P#{i}" + row(i).map{|c| c ? c.to_s : ' * '}.join
156
+ end.join(?\n)
157
+ end
158
+
159
+ def to_s
160
+ self.to_csi
161
+ end
162
+
163
+ class Movement
164
+ include Helper
165
+
166
+ def initialize(board, col, row)
167
+ @from = [col, row]
168
+ @board = board
169
+ @piece = board.at(col, row)
170
+ assert(Piece === @piece)
171
+ end
172
+
173
+ def to(col, row)
174
+ if @board.dests_from(*@from).include?([col, row])
175
+ @board[*@from] = nil
176
+ @board.place(@piece, col, row)
177
+ else
178
+ raise CantGetMovement.new
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,49 @@
1
+ module EightyOne
2
+ class Face
3
+ attr_reader :symbol, :movements
4
+
5
+ def initialize(symbol, movements)
6
+ @symbol = symbol
7
+ @movements = movements
8
+ end
9
+ end
10
+
11
+ module Faces
12
+ class Direction
13
+ include Enumerable
14
+
15
+ attr_reader :dir
16
+
17
+ def initialize(col, row)
18
+ @dir = [col, row]
19
+ end
20
+
21
+ def each
22
+ (1..8).each do |i|
23
+ yield [@dir[0] * i, @dir[1] * i]
24
+ end
25
+ end
26
+ end
27
+
28
+ def self.dir(col, row)
29
+ Direction.new(col, row)
30
+ end
31
+
32
+ FU = Face.new(:FU, [[0, 1]])
33
+ KY = Face.new(:KY, [dir(0, 1)])
34
+ KE = Face.new(:KE, [[-1, 2], [1, 2]])
35
+ GI = Face.new(:GI, [[-1, 1], [0, 1], [1, 1], [-1, -1], [1, -1]])
36
+ KI = Face.new(:KI, [[-1, 1], [0, 1], [1, 1], [-1, 0], [-1, 0], [0, -1]])
37
+ KA = Face.new(:KA, [dir(-1, 1), dir(1, 1), dir(-1, -1), dir(1, -1)])
38
+ HI = Face.new(:HI, [dir(0, 1), dir(0, -1), dir(-1, 0), dir(1, 0)])
39
+ OU = Face.new(:OU, [[-1, 1], [0, 1], [1, 1], [-1, 0], [1, 0], [-1, -1], [0, -1], [1, -1]])
40
+ TO = Face.new(:TO, KI.movements)
41
+ NY = Face.new(:NY, KI.movements)
42
+ NK = Face.new(:NK, KI.movements)
43
+ NG = Face.new(:NG, KI.movements)
44
+ UM = Face.new(:UM, KA.movements + [[0, 1], [-1, 0], [1, 0], [0, -1]])
45
+ RY = Face.new(:RY, HI.movements + [[-1, 1], [1, 1], [-1, -1], [1, -1]])
46
+
47
+ ALL = [ FU, KY, KE, GI, KI, KA, HI, OU, TO, NY, NK, NG, UM, RY ]
48
+ end
49
+ end
@@ -0,0 +1,94 @@
1
+ module EightyOne
2
+ class Piece
3
+ include Helper
4
+
5
+ def self.new_class(forward, backword)
6
+ Class.new(self) do |c|
7
+ c.instance_eval do
8
+ define_method(:forward) { forward }
9
+ define_singleton_method(:forward) { forward }
10
+ define_method(:backward) { backward }
11
+ define_singleton_method(:backward) { backward }
12
+ end
13
+ end
14
+ end
15
+
16
+ attr_reader :turn
17
+
18
+ def initialize(turn)
19
+ assert(turn == :sente || turn == :gote)
20
+ @turn = turn
21
+ @promoted = false
22
+ end
23
+
24
+ def face
25
+ @promoted ? backward : forward
26
+ end
27
+
28
+ def reset(turn)
29
+ @turn = turn
30
+ @promote = false
31
+ self
32
+ end
33
+
34
+ def promote
35
+ @promoted = true
36
+ self
37
+ end
38
+
39
+ def promoted?
40
+ @promoted
41
+ end
42
+
43
+ def forward?
44
+ !@promoted
45
+ end
46
+
47
+ def opposite
48
+ @turn.sente? ? :gote : :sente
49
+ end
50
+
51
+ def sente?
52
+ @turn == :sente
53
+ end
54
+
55
+ def gote?
56
+ @turn == :gote
57
+ end
58
+
59
+ def encode
60
+ (@turn == :sente ? ?+ : ?-) + face.symbol.to_s
61
+ end
62
+
63
+ def to_s
64
+ self.encode
65
+ end
66
+
67
+ def self.decode(code)
68
+ turn = code[0] == ?+ ? :sente : :gote
69
+ symbol = code[1, 2].to_sym
70
+ piece_class = Pieces::ALL.find{|p| p.forward.symbol == symbol }
71
+ if piece_class
72
+ piece_class.new(turn)
73
+ else
74
+ piece_class = ALL.find{|p| p.backword.symbol == symbol }
75
+ if piece_class
76
+ piece_class.new(turn).promote
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ module Pieces
83
+ Fu = Piece.new_class(Faces::FU, Faces::TO)
84
+ Ky = Piece.new_class(Faces::KY, Faces::NY)
85
+ Ke = Piece.new_class(Faces::KE, Faces::NK)
86
+ Gi = Piece.new_class(Faces::GI, Faces::NG)
87
+ Ki = Piece.new_class(Faces::KI, nil)
88
+ Ka = Piece.new_class(Faces::KA, Faces::UM)
89
+ Hi = Piece.new_class(Faces::HI, Faces::RY)
90
+ Ou = Piece.new_class(Faces::OU, nil)
91
+
92
+ ALL = [Fu, Ky, Ke, Gi, Ki, Ka, Hi, Ou]
93
+ end
94
+ end
@@ -0,0 +1,3 @@
1
+ module EightyOne
2
+ VERSION = "0.1.0"
3
+ end
data/lib/eighty_one.rb ADDED
@@ -0,0 +1,18 @@
1
+ require "eighty_one/version"
2
+
3
+ module EightyOne
4
+ class CantGetMovement < RuntimeError; end
5
+ module Helper
6
+ def assert(*conds)
7
+ unless conds.all?
8
+ e = RuntimeError.new 'assert error'
9
+ e.set_backtrace(caller)
10
+ raise e
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ require 'eighty_one/face'
17
+ require 'eighty_one/piece'
18
+ require 'eighty_one/board'
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eighty-one
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - youchan
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-02-17 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.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
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: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: A game engine of Shogi.
56
+ email:
57
+ - youchan01@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - Gemfile.lock
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - bin/console
69
+ - bin/setup
70
+ - eighty-one.gemspec
71
+ - lib/eighty_one.rb
72
+ - lib/eighty_one/board.rb
73
+ - lib/eighty_one/face.rb
74
+ - lib/eighty_one/piece.rb
75
+ - lib/eighty_one/version.rb
76
+ homepage: https://github.com/youchan/eighty-one
77
+ licenses:
78
+ - MIT
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.7.3
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: A game engine of Shogi.
100
+ test_files: []