console-shogi 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/Gemfile +14 -0
- data/Gemfile.lock +44 -0
- data/LICENSE.txt +21 -0
- data/README.md +1 -0
- data/Rakefile +4 -0
- data/images/gote/fu.png +0 -0
- data/images/gote/gin.png +0 -0
- data/images/gote/gyoku.png +0 -0
- data/images/gote/hisha.png +0 -0
- data/images/gote/kaku.png +0 -0
- data/images/gote/keima.png +0 -0
- data/images/gote/kin.png +0 -0
- data/images/gote/kyosha.png +0 -0
- data/images/gote/narigin.png +0 -0
- data/images/gote/narikei.png +0 -0
- data/images/gote/narikyo.png +0 -0
- data/images/gote/nonepiece.png +0 -0
- data/images/gote/ohsho.png +0 -0
- data/images/gote/ryu.png +0 -0
- data/images/gote/shori.png +0 -0
- data/images/gote/tokin.png +0 -0
- data/images/gote/uma.png +0 -0
- data/images/sente/fu.png +0 -0
- data/images/sente/gin.png +0 -0
- data/images/sente/gyoku.png +0 -0
- data/images/sente/hisha.png +0 -0
- data/images/sente/kaku.png +0 -0
- data/images/sente/keima.png +0 -0
- data/images/sente/kin.png +0 -0
- data/images/sente/kyosha.png +0 -0
- data/images/sente/narigin.png +0 -0
- data/images/sente/narikei.png +0 -0
- data/images/sente/narikyo.png +0 -0
- data/images/sente/nonepiece.png +0 -0
- data/images/sente/ohsho.png +0 -0
- data/images/sente/ryu.png +0 -0
- data/images/sente/shori.png +0 -0
- data/images/sente/tokin.png +0 -0
- data/images/sente/uma.png +0 -0
- data/lib/console_shogi/board.rb +53 -0
- data/lib/console_shogi/game.rb +57 -0
- data/lib/console_shogi/komadai.rb +59 -0
- data/lib/console_shogi/new_board_builder.rb +113 -0
- data/lib/console_shogi/piece.rb +327 -0
- data/lib/console_shogi/piece_mover.rb +82 -0
- data/lib/console_shogi/piece_mover_on_komadai.rb +65 -0
- data/lib/console_shogi/player.rb +42 -0
- data/lib/console_shogi/terminal_operator.rb +211 -0
- data/lib/console_shogi/version.rb +5 -0
- data/lib/console_shogi.rb +21 -0
- metadata +96 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3a6623ab23cf26496870d6849e0e3433035756ad6d0a4d17475f4afa4619c13a
|
|
4
|
+
data.tar.gz: d2ef4c45e1f79a20a7d7105a21d5b2b2d33a1373e6ef918c13471dd111478640
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: eea768b6b1d02aedf1be04c06691b9ed5ba7e4ac4cce6e8da479cbf8ffc7ab1b3e7411018640f751b72556485d8edafbb1ecd9e4d116552050226152982a73af
|
|
7
|
+
data.tar.gz: 2559ca6fa74b5154ac626eb6c0de61dd15b0d48fe6758cce573672360f5d6eea0c980be9112017fefb9907a44046d0529eca66ae8622fca784aaacae537f60df
|
data/Gemfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
# Specify your gem's dependencies in console-shogi.gemspec
|
|
6
|
+
gemspec
|
|
7
|
+
|
|
8
|
+
ruby "~> 3.2.0"
|
|
9
|
+
|
|
10
|
+
gem "rake", "~> 13.0"
|
|
11
|
+
gem "matrix", "~> 0.4.2"
|
|
12
|
+
gem "pry", "~> 0.14.2"
|
|
13
|
+
|
|
14
|
+
gem "tty-prompt", "~> 0.23.1"
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
console-shogi (0.1.0)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
coderay (1.1.3)
|
|
10
|
+
matrix (0.4.2)
|
|
11
|
+
method_source (1.0.0)
|
|
12
|
+
pastel (0.8.0)
|
|
13
|
+
tty-color (~> 0.5)
|
|
14
|
+
pry (0.14.2)
|
|
15
|
+
coderay (~> 1.1)
|
|
16
|
+
method_source (~> 1.0)
|
|
17
|
+
rake (13.0.6)
|
|
18
|
+
tty-color (0.6.0)
|
|
19
|
+
tty-cursor (0.7.1)
|
|
20
|
+
tty-prompt (0.23.1)
|
|
21
|
+
pastel (~> 0.8)
|
|
22
|
+
tty-reader (~> 0.8)
|
|
23
|
+
tty-reader (0.9.0)
|
|
24
|
+
tty-cursor (~> 0.7)
|
|
25
|
+
tty-screen (~> 0.8)
|
|
26
|
+
wisper (~> 2.0)
|
|
27
|
+
tty-screen (0.8.1)
|
|
28
|
+
wisper (2.0.1)
|
|
29
|
+
|
|
30
|
+
PLATFORMS
|
|
31
|
+
arm64-darwin-21
|
|
32
|
+
|
|
33
|
+
DEPENDENCIES
|
|
34
|
+
console-shogi!
|
|
35
|
+
matrix (~> 0.4.2)
|
|
36
|
+
pry (~> 0.14.2)
|
|
37
|
+
rake (~> 13.0)
|
|
38
|
+
tty-prompt (~> 0.23.1)
|
|
39
|
+
|
|
40
|
+
RUBY VERSION
|
|
41
|
+
ruby 3.2.2p53
|
|
42
|
+
|
|
43
|
+
BUNDLED WITH
|
|
44
|
+
2.4.12
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 pekepek
|
|
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 @@
|
|
|
1
|
+
# console-shogi
|
data/Rakefile
ADDED
data/images/gote/fu.png
ADDED
|
Binary file
|
data/images/gote/gin.png
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/images/gote/kin.png
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/images/gote/ryu.png
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/images/gote/uma.png
ADDED
|
Binary file
|
data/images/sente/fu.png
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'matrix'
|
|
4
|
+
|
|
5
|
+
module ConsoleShogi
|
|
6
|
+
class Board
|
|
7
|
+
def initialize(pieces:)
|
|
8
|
+
@pieces = Matrix.rows(pieces)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def matrix
|
|
12
|
+
@pieces
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def within_range?(x:, y:)
|
|
16
|
+
case {x: x, y: y}
|
|
17
|
+
in x: 0..8, y: 0..8
|
|
18
|
+
true
|
|
19
|
+
else
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def fetch_piece(x:, y:)
|
|
25
|
+
pieces[y, x]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def promote_piece!(x:, y:)
|
|
29
|
+
@pieces[y, x] = pieces[y, x].promote
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def put_piece!(piece:, to:)
|
|
33
|
+
@pieces[to[:y], to[:x]] = piece
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def move_piece!(from:, to:)
|
|
37
|
+
from_piece = @pieces[from[:y], from[:x]]
|
|
38
|
+
@pieces[from[:y], from[:x]] = NonePiece.new
|
|
39
|
+
|
|
40
|
+
to_piece = @pieces[to[:y], to[:x]]
|
|
41
|
+
|
|
42
|
+
# NOTE 駒を取る、リファクタする
|
|
43
|
+
# from_piece.player で player 取るの違和感
|
|
44
|
+
from_piece.player.capture_piece!(to_piece) unless to_piece.none?
|
|
45
|
+
|
|
46
|
+
@pieces[to[:y], to[:x]] = from_piece
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
attr_reader :pieces
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'io/console'
|
|
4
|
+
|
|
5
|
+
module ConsoleShogi
|
|
6
|
+
class Game
|
|
7
|
+
def initialize
|
|
8
|
+
@sente_player = Player.new(teban: Player::Teban::SENTE)
|
|
9
|
+
@gote_player = Player.new(teban: Player::Teban::GOTE)
|
|
10
|
+
@board = NewBoardBuilder.build(sente_player: @sente_player, gote_player: @gote_player)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def start
|
|
14
|
+
TerminalOperator.print_board(board: board, sente_komadai: sente_player.komadai, gote_komadai: gote_player.komadai)
|
|
15
|
+
|
|
16
|
+
while key = STDIN.getch
|
|
17
|
+
# NOTE Ctrl-C を押したら終了
|
|
18
|
+
if key == "\C-c"
|
|
19
|
+
exit
|
|
20
|
+
# NOTE 矢印キーを押したらカーソルを移動
|
|
21
|
+
elsif key == "\e" && STDIN.getch == "["
|
|
22
|
+
key = STDIN.getch
|
|
23
|
+
|
|
24
|
+
TerminalOperator.move_cursor(key)
|
|
25
|
+
# NOTE Enter を押したら駒を移動
|
|
26
|
+
elsif key == "\r"
|
|
27
|
+
# TODO このまま Hash にするかは要検討
|
|
28
|
+
index = TerminalOperator.squares_index
|
|
29
|
+
|
|
30
|
+
case index[:location]
|
|
31
|
+
when :board
|
|
32
|
+
PieceMover.new(board: board, from: index).move!
|
|
33
|
+
when :sente_komadai
|
|
34
|
+
PieceMoverOnKomadai.new(board: board, komadai: sente_player.komadai, from: index).drop!
|
|
35
|
+
when :gote_komadai
|
|
36
|
+
PieceMoverOnKomadai.new(board: board, komadai: gote_player.komadai, from: index).drop!
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# TODO 描写に時間がかかるので、差分のみ表示できる様にする
|
|
40
|
+
TerminalOperator.print_board(board: board, sente_komadai: sente_player.komadai, gote_komadai: gote_player.komadai)
|
|
41
|
+
|
|
42
|
+
# TODO ひとまず動く物を作った。リファクタリングする
|
|
43
|
+
[sente_player, gote_player].each do |player|
|
|
44
|
+
next unless player.win?
|
|
45
|
+
|
|
46
|
+
TerminalOperator.print_winner(player.teban)
|
|
47
|
+
exit
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
attr_reader :board, :sente_player, :gote_player
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'matrix'
|
|
4
|
+
|
|
5
|
+
module ConsoleShogi
|
|
6
|
+
class Komadai
|
|
7
|
+
class NoSpaceError < StandardError; end
|
|
8
|
+
|
|
9
|
+
attr_reader :pieces
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@pieces = empty_pieces
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def expand!
|
|
16
|
+
@pieces = pieces.hstack(empty_pieces)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def have_space?
|
|
20
|
+
pieces.any?(&:none?)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def fetch_piece(x:, y:)
|
|
24
|
+
pieces[y, x]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def put(piece:)
|
|
28
|
+
index = pieces.index(&:none?)
|
|
29
|
+
|
|
30
|
+
raise NoSpaceError if index.nil?
|
|
31
|
+
|
|
32
|
+
@pieces[*index] = piece
|
|
33
|
+
|
|
34
|
+
@pieces = sort_pieces
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def pick_up_piece!(from:)
|
|
38
|
+
@pieces[from[:y], from[:x]] = NonePiece.new
|
|
39
|
+
@pieces = sort_pieces
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def sort_pieces
|
|
45
|
+
column_num = pieces.count / 3
|
|
46
|
+
Matrix.rows(pieces.sort_by {|p| -p.number }.each_slice(column_num).to_a)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def empty_pieces
|
|
50
|
+
Matrix.rows(
|
|
51
|
+
[
|
|
52
|
+
[NonePiece.new, NonePiece.new, NonePiece.new],
|
|
53
|
+
[NonePiece.new, NonePiece.new, NonePiece.new],
|
|
54
|
+
[NonePiece.new, NonePiece.new, NonePiece.new]
|
|
55
|
+
]
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConsoleShogi
|
|
4
|
+
class NewBoardBuilder
|
|
5
|
+
class << self
|
|
6
|
+
def build(sente_player: , gote_player:)
|
|
7
|
+
Board.new(
|
|
8
|
+
pieces: [
|
|
9
|
+
[
|
|
10
|
+
Kyosha.new(player: gote_player),
|
|
11
|
+
Keima.new(player: gote_player),
|
|
12
|
+
Gin.new(player: gote_player),
|
|
13
|
+
Kin.new(player: gote_player),
|
|
14
|
+
Ohsho.new(player: gote_player),
|
|
15
|
+
Kin.new(player: gote_player),
|
|
16
|
+
Gin.new(player: gote_player),
|
|
17
|
+
Keima.new(player: gote_player),
|
|
18
|
+
Kyosha.new(player: gote_player)
|
|
19
|
+
],
|
|
20
|
+
[
|
|
21
|
+
NonePiece.new,
|
|
22
|
+
Hisha.new(player: gote_player),
|
|
23
|
+
NonePiece.new,
|
|
24
|
+
NonePiece.new,
|
|
25
|
+
NonePiece.new,
|
|
26
|
+
NonePiece.new,
|
|
27
|
+
NonePiece.new,
|
|
28
|
+
Kaku.new(player: gote_player),
|
|
29
|
+
NonePiece.new
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
Fu.new(player: gote_player),
|
|
33
|
+
Fu.new(player: gote_player),
|
|
34
|
+
Fu.new(player: gote_player),
|
|
35
|
+
Fu.new(player: gote_player),
|
|
36
|
+
Fu.new(player: gote_player),
|
|
37
|
+
Fu.new(player: gote_player),
|
|
38
|
+
Fu.new(player: gote_player),
|
|
39
|
+
Fu.new(player: gote_player),
|
|
40
|
+
Fu.new(player: gote_player)
|
|
41
|
+
],
|
|
42
|
+
[
|
|
43
|
+
NonePiece.new,
|
|
44
|
+
NonePiece.new,
|
|
45
|
+
NonePiece.new,
|
|
46
|
+
NonePiece.new,
|
|
47
|
+
NonePiece.new,
|
|
48
|
+
NonePiece.new,
|
|
49
|
+
NonePiece.new,
|
|
50
|
+
NonePiece.new,
|
|
51
|
+
NonePiece.new
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
NonePiece.new,
|
|
55
|
+
NonePiece.new,
|
|
56
|
+
NonePiece.new,
|
|
57
|
+
NonePiece.new,
|
|
58
|
+
NonePiece.new,
|
|
59
|
+
NonePiece.new,
|
|
60
|
+
NonePiece.new,
|
|
61
|
+
NonePiece.new,
|
|
62
|
+
NonePiece.new
|
|
63
|
+
],
|
|
64
|
+
[
|
|
65
|
+
NonePiece.new,
|
|
66
|
+
NonePiece.new,
|
|
67
|
+
NonePiece.new,
|
|
68
|
+
NonePiece.new,
|
|
69
|
+
NonePiece.new,
|
|
70
|
+
NonePiece.new,
|
|
71
|
+
NonePiece.new,
|
|
72
|
+
NonePiece.new,
|
|
73
|
+
NonePiece.new
|
|
74
|
+
],
|
|
75
|
+
[
|
|
76
|
+
Fu.new(player: sente_player),
|
|
77
|
+
Fu.new(player: sente_player),
|
|
78
|
+
Fu.new(player: sente_player),
|
|
79
|
+
Fu.new(player: sente_player),
|
|
80
|
+
Fu.new(player: sente_player),
|
|
81
|
+
Fu.new(player: sente_player),
|
|
82
|
+
Fu.new(player: sente_player),
|
|
83
|
+
Fu.new(player: sente_player),
|
|
84
|
+
Fu.new(player: sente_player)
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
NonePiece.new,
|
|
88
|
+
Kaku.new(player: sente_player),
|
|
89
|
+
NonePiece.new,
|
|
90
|
+
NonePiece.new,
|
|
91
|
+
NonePiece.new,
|
|
92
|
+
NonePiece.new,
|
|
93
|
+
NonePiece.new,
|
|
94
|
+
Hisha.new(player: sente_player),
|
|
95
|
+
NonePiece.new
|
|
96
|
+
],
|
|
97
|
+
[
|
|
98
|
+
Kyosha.new(player: sente_player),
|
|
99
|
+
Keima.new(player: sente_player),
|
|
100
|
+
Gin.new(player: sente_player),
|
|
101
|
+
Kin.new(player: sente_player),
|
|
102
|
+
Ohsho.new(player: sente_player),
|
|
103
|
+
Kin.new(player: sente_player),
|
|
104
|
+
Gin.new(player: sente_player),
|
|
105
|
+
Keima.new(player: sente_player),
|
|
106
|
+
Kyosha.new(player: sente_player)
|
|
107
|
+
]
|
|
108
|
+
]
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConsoleShogi
|
|
4
|
+
class Piece
|
|
5
|
+
attr_reader :player
|
|
6
|
+
|
|
7
|
+
def initialize(player: nil)
|
|
8
|
+
@player = player
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def move
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def promote
|
|
16
|
+
return self unless can_promote?
|
|
17
|
+
|
|
18
|
+
promoted
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def promoted?
|
|
22
|
+
false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def number
|
|
26
|
+
self::class::NUMBER
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def image
|
|
30
|
+
if player.sente?
|
|
31
|
+
File.read("images/sente/#{self::class.name.split('::').last.downcase}.png")
|
|
32
|
+
else
|
|
33
|
+
File.read("images/gote/#{self::class.name.split('::').last.downcase}.png")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def none?
|
|
38
|
+
self::class == NonePiece
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def kaku?
|
|
42
|
+
self::class == Kaku
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def hisha?
|
|
46
|
+
self::class == Hisha
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def kyosha?
|
|
50
|
+
self::class == Kyosha
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def fu?
|
|
54
|
+
self::class == Fu
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def moves
|
|
58
|
+
ms = base_moves
|
|
59
|
+
|
|
60
|
+
if player.sente?
|
|
61
|
+
ms
|
|
62
|
+
else
|
|
63
|
+
ms.map {|m| m.transform_values {|v| v * -1 } }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def change_player!(player)
|
|
68
|
+
@player = player
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class NonePiece < Piece
|
|
73
|
+
NUMBER = 0
|
|
74
|
+
|
|
75
|
+
def moves
|
|
76
|
+
[]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def player
|
|
80
|
+
NonPlayer.new
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def can_promote?
|
|
84
|
+
false
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class Fu < Piece
|
|
89
|
+
NUMBER = 1
|
|
90
|
+
|
|
91
|
+
def base_moves
|
|
92
|
+
[
|
|
93
|
+
{x: 0, y: -1}
|
|
94
|
+
]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def can_promote?
|
|
98
|
+
true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def promoted
|
|
102
|
+
PromotedPiece::Tokin.new(original: self)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class Kyosha < Piece
|
|
107
|
+
NUMBER = 2
|
|
108
|
+
|
|
109
|
+
def base_moves
|
|
110
|
+
(1..8).map {|n|
|
|
111
|
+
{x: 0, y: -1 * n}
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def can_promote?
|
|
116
|
+
true
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def promoted
|
|
120
|
+
PromotedPiece::Narikyo.new(original: self)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
class Keima < Piece
|
|
125
|
+
NUMBER = 3
|
|
126
|
+
|
|
127
|
+
def base_moves
|
|
128
|
+
[
|
|
129
|
+
{x: 1, y: -2},
|
|
130
|
+
{x: -1, y: -2},
|
|
131
|
+
]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def can_promote?
|
|
135
|
+
true
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def promoted
|
|
139
|
+
PromotedPiece::Narikei.new(original: self)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class Gin < Piece
|
|
144
|
+
NUMBER = 4
|
|
145
|
+
|
|
146
|
+
def base_moves
|
|
147
|
+
[
|
|
148
|
+
{x: -1, y: -1},
|
|
149
|
+
{x: 0, y: -1},
|
|
150
|
+
{x: 1, y: -1},
|
|
151
|
+
{x: 1, y: 1},
|
|
152
|
+
{x: -1, y: 1}
|
|
153
|
+
]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def can_promote?
|
|
157
|
+
true
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def promoted
|
|
161
|
+
PromotedPiece::Narigin.new(original: self)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
class Kin < Piece
|
|
166
|
+
NUMBER = 5
|
|
167
|
+
|
|
168
|
+
def base_moves
|
|
169
|
+
[
|
|
170
|
+
{x: -1, y: -1},
|
|
171
|
+
{x: 0, y: -1},
|
|
172
|
+
{x: 1, y: -1},
|
|
173
|
+
{x: 1, y: 0},
|
|
174
|
+
{x: -1, y: 0},
|
|
175
|
+
{x: 0, y: 1}
|
|
176
|
+
]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def can_promote?
|
|
180
|
+
false
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
class Kaku < Piece
|
|
185
|
+
NUMBER = 6
|
|
186
|
+
|
|
187
|
+
def base_moves
|
|
188
|
+
(1..8).flat_map {|n|
|
|
189
|
+
[
|
|
190
|
+
{x: -1 * n, y: -1 * n},
|
|
191
|
+
{x: 1 * n, y: -1 * n},
|
|
192
|
+
{x: -1 * n, y: 1 * n},
|
|
193
|
+
{x: 1 * n, y: 1 * n}
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def can_promote?
|
|
199
|
+
true
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def promoted
|
|
203
|
+
PromotedPiece::Uma.new(original: self)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
class Hisha < Piece
|
|
208
|
+
NUMBER = 7
|
|
209
|
+
|
|
210
|
+
def base_moves
|
|
211
|
+
(1..8).flat_map {|n|
|
|
212
|
+
[
|
|
213
|
+
{x: 0, y: -1 * n},
|
|
214
|
+
{x: 0, y: 1 * n},
|
|
215
|
+
{x: -1 * n, y: 0},
|
|
216
|
+
{x: 1 * n, y: 0}
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def can_promote?
|
|
222
|
+
true
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def promoted
|
|
226
|
+
PromotedPiece::Ryu.new(original: self)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
class Ohsho < Piece
|
|
231
|
+
NUMBER = 8
|
|
232
|
+
|
|
233
|
+
def base_moves
|
|
234
|
+
[
|
|
235
|
+
{x: -1, y: -1},
|
|
236
|
+
{x: 0, y: -1},
|
|
237
|
+
{x: 1, y: -1},
|
|
238
|
+
{x: 1, y: 0},
|
|
239
|
+
{x: -1, y: 0},
|
|
240
|
+
{x: -1, y: 1},
|
|
241
|
+
{x: 0, y: 1},
|
|
242
|
+
{x: 1, y: 1}
|
|
243
|
+
]
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def can_promote?
|
|
247
|
+
false
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
class PromotedPiece < Piece
|
|
252
|
+
attr_reader :player, :original
|
|
253
|
+
|
|
254
|
+
def initialize(original: nil)
|
|
255
|
+
@player = original.player
|
|
256
|
+
@original = original
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def can_promote?
|
|
260
|
+
false
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def promoted?
|
|
264
|
+
true
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
class NariKin < PromotedPiece
|
|
268
|
+
NUMBER = 9
|
|
269
|
+
|
|
270
|
+
def base_moves
|
|
271
|
+
[
|
|
272
|
+
{x: -1, y: -1},
|
|
273
|
+
{x: 0, y: -1},
|
|
274
|
+
{x: 1, y: -1},
|
|
275
|
+
{x: 1, y: 0},
|
|
276
|
+
{x: -1, y: 0},
|
|
277
|
+
{x: 0, y: 1}
|
|
278
|
+
]
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
class Narigin < NariKin; end
|
|
283
|
+
class Narikei < NariKin; end
|
|
284
|
+
class Narikyo < NariKin; end
|
|
285
|
+
class Tokin < NariKin; end
|
|
286
|
+
|
|
287
|
+
class Uma < PromotedPiece
|
|
288
|
+
NUMBER = 10
|
|
289
|
+
|
|
290
|
+
def base_moves
|
|
291
|
+
(1..8).flat_map {|n|
|
|
292
|
+
[
|
|
293
|
+
{x: -1 * n, y: -1 * n},
|
|
294
|
+
{x: 1 * n, y: -1 * n},
|
|
295
|
+
{x: -1 * n, y: 1 * n},
|
|
296
|
+
{x: 1 * n, y: 1 * n}
|
|
297
|
+
]
|
|
298
|
+
} + [
|
|
299
|
+
{x: 0, y: -1},
|
|
300
|
+
{x: 1, y: 0},
|
|
301
|
+
{x: -1, y: 0},
|
|
302
|
+
{x: 0, y: 1}
|
|
303
|
+
]
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
class Ryu < PromotedPiece
|
|
308
|
+
NUMBER = 11
|
|
309
|
+
|
|
310
|
+
def base_moves
|
|
311
|
+
(1..8).flat_map {|n|
|
|
312
|
+
[
|
|
313
|
+
{x: 0, y: -1 * n},
|
|
314
|
+
{x: 0, y: 1 * n},
|
|
315
|
+
{x: -1 * n, y: 0},
|
|
316
|
+
{x: 1 * n, y: 0}
|
|
317
|
+
]
|
|
318
|
+
} + [
|
|
319
|
+
{x: -1, y: -1},
|
|
320
|
+
{x: 1, y: -1},
|
|
321
|
+
{x: -1, y: 1},
|
|
322
|
+
{x: 1, y: 1}
|
|
323
|
+
]
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConsoleShogi
|
|
4
|
+
class PieceMover
|
|
5
|
+
def initialize(board:, from:)
|
|
6
|
+
@board = board
|
|
7
|
+
@from_piece_index = from
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def move!
|
|
11
|
+
return if target_piece.nil? || target_piece.none?
|
|
12
|
+
|
|
13
|
+
while key = STDIN.getch
|
|
14
|
+
if key == "\e" && STDIN.getch == "["
|
|
15
|
+
key = STDIN.getch
|
|
16
|
+
|
|
17
|
+
TerminalOperator.move_cursor(key)
|
|
18
|
+
elsif key == "\r"
|
|
19
|
+
to_piece_index = TerminalOperator.squares_index
|
|
20
|
+
|
|
21
|
+
return unless can_move?(piece: target_piece, to_piece_index: to_piece_index)
|
|
22
|
+
|
|
23
|
+
board.move_piece!(
|
|
24
|
+
from: {x: from_piece_index[:x], y: from_piece_index[:y]},
|
|
25
|
+
to: {x: to_piece_index[:x], y: to_piece_index[:y]}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# TODO とりあえずここに実装してしまっている。整理したい
|
|
29
|
+
piece = board.fetch_piece(x: to_piece_index[:x], y: to_piece_index[:y])
|
|
30
|
+
return unless can_promote?(piece, from_piece_index, to_piece_index)
|
|
31
|
+
|
|
32
|
+
board.promote_piece!(x: to_piece_index[:x], y: to_piece_index[:y]) if TerminalOperator.select_promotion
|
|
33
|
+
|
|
34
|
+
return
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def can_move?(piece:, to_piece_index:)
|
|
40
|
+
return false unless board.within_range?(x: to_piece_index[:x], y: to_piece_index[:y])
|
|
41
|
+
|
|
42
|
+
diff = {x: to_piece_index[:x] - from_piece_index[:x], y: to_piece_index[:y] - from_piece_index[:y]}
|
|
43
|
+
|
|
44
|
+
return false if piece.moves.none? {|m| m[:x] == diff[:x] && m[:y] == diff[:y] }
|
|
45
|
+
|
|
46
|
+
to_piece = board.fetch_piece(x: to_piece_index[:x], y: to_piece_index[:y])
|
|
47
|
+
|
|
48
|
+
return false if piece.player.teban == to_piece.player.teban
|
|
49
|
+
|
|
50
|
+
return true unless piece.kaku? || piece.hisha? || piece.kyosha?
|
|
51
|
+
|
|
52
|
+
distance = (diff[:x].nonzero? || diff[:y]).abs
|
|
53
|
+
element = [diff[:x] / distance, diff[:y] / distance]
|
|
54
|
+
|
|
55
|
+
1.upto(distance - 1) do |d|
|
|
56
|
+
piece = board.fetch_piece(x: from_piece_index[:x] + element[0] * d, y: from_piece_index[:y] + element[1] * d)
|
|
57
|
+
|
|
58
|
+
return false unless piece.none?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
attr_reader :board, :from_piece_index
|
|
67
|
+
|
|
68
|
+
def target_piece
|
|
69
|
+
@target_piece ||= board.fetch_piece(x: from_piece_index[:x], y: from_piece_index[:y])
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def can_promote?(piece, from, to)
|
|
73
|
+
return false unless piece.can_promote?
|
|
74
|
+
|
|
75
|
+
if piece.player.sente?
|
|
76
|
+
from[:y].between?(0, 2) || to[:y].between?(0, 2)
|
|
77
|
+
elsif piece.player.gote?
|
|
78
|
+
from[:y].between?(6, 8) || to[:y].between?(6, 8)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConsoleShogi
|
|
4
|
+
class PieceMoverOnKomadai
|
|
5
|
+
def initialize(board:, komadai:, from:)
|
|
6
|
+
@board = board
|
|
7
|
+
@komadai = komadai
|
|
8
|
+
# 駒台の index に直す必要がある
|
|
9
|
+
@from_piece_index = from
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def drop!
|
|
13
|
+
return if target_piece.nil? || target_piece.none?
|
|
14
|
+
|
|
15
|
+
while key = STDIN.getch
|
|
16
|
+
if key == "\e" && STDIN.getch == "["
|
|
17
|
+
key = STDIN.getch
|
|
18
|
+
|
|
19
|
+
TerminalOperator.move_cursor(key)
|
|
20
|
+
elsif key == "\r"
|
|
21
|
+
to_piece_index = TerminalOperator.squares_index
|
|
22
|
+
|
|
23
|
+
return if to_piece_index[:location] != :board
|
|
24
|
+
return unless can_drop?(piece: target_piece, to_piece_index: to_piece_index)
|
|
25
|
+
|
|
26
|
+
komadai.pick_up_piece!(from: from_piece_index)
|
|
27
|
+
board.put_piece!(piece: target_piece, to: to_piece_index)
|
|
28
|
+
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
attr_reader :board, :komadai, :from_piece_index
|
|
37
|
+
|
|
38
|
+
def target_piece
|
|
39
|
+
@target_piece ||= komadai.fetch_piece(x: from_piece_index[:x], y: from_piece_index[:y])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def can_drop?(piece:, to_piece_index:)
|
|
43
|
+
to_piece = board.fetch_piece(x: to_piece_index[:x], y: to_piece_index[:y])
|
|
44
|
+
|
|
45
|
+
return false unless to_piece.none?
|
|
46
|
+
|
|
47
|
+
return false if nifu?(piece, to_piece_index)
|
|
48
|
+
|
|
49
|
+
can_move_next_turn?(piece, to_piece_index)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def can_move_next_turn?(piece, to_piece_index)
|
|
53
|
+
piece_mover = PieceMover.new(board: board, from: to_piece_index)
|
|
54
|
+
|
|
55
|
+
piece.moves.any? {|m|
|
|
56
|
+
piece_mover.can_move?(piece: piece, to_piece_index: {x: to_piece_index[:x] + m[:x], y: to_piece_index[:y] + m[:y]})
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def nifu?(piece, to_piece_index)
|
|
61
|
+
piece.fu? &&
|
|
62
|
+
board.matrix.column(to_piece_index[:x]).any? {|p| p.fu? && piece.player == p.player }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConsoleShogi
|
|
4
|
+
class Player
|
|
5
|
+
module Teban
|
|
6
|
+
SENTE = :sente
|
|
7
|
+
GOTE = :gote
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :teban, :komadai
|
|
11
|
+
|
|
12
|
+
def initialize(teban: nil)
|
|
13
|
+
@teban = teban
|
|
14
|
+
# TODO Komadai, Piece, Player が循環してる
|
|
15
|
+
@komadai = Komadai.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def sente?
|
|
19
|
+
teban == Teban::SENTE
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def gote?
|
|
23
|
+
teban == Teban::GOTE
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def win?
|
|
27
|
+
komadai.pieces.any? {|p| p.class == Ohsho }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def capture_piece!(piece)
|
|
31
|
+
@komadai.expand! unless komadai.have_space?
|
|
32
|
+
|
|
33
|
+
piece = piece.original if piece.promoted?
|
|
34
|
+
|
|
35
|
+
piece.change_player!(self)
|
|
36
|
+
komadai.put(piece: piece)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class NonPlayer < Player
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
require 'tty-prompt'
|
|
5
|
+
|
|
6
|
+
module ConsoleShogi
|
|
7
|
+
class TerminalOperator
|
|
8
|
+
class << self
|
|
9
|
+
CursorPosition = Struct.new(:x, :y)
|
|
10
|
+
|
|
11
|
+
HORIZONTAL_DISTANCE = 2
|
|
12
|
+
|
|
13
|
+
KOMADAI_START_X = 21
|
|
14
|
+
KOMADAI_GOTE_START_Y = 1
|
|
15
|
+
KOMADAI_SENTE_START_Y = 7
|
|
16
|
+
|
|
17
|
+
module EscapeSequence
|
|
18
|
+
RESET = "\e[0m"
|
|
19
|
+
RESET_CURSOR = "\e[1;1H"
|
|
20
|
+
BACKGROUND_COLOR_YELLOW = "\e[43m"
|
|
21
|
+
BACKGROUND_COLOR_GREEN = "\e[42m"
|
|
22
|
+
TEXT_COLOR_SENTE = "\e[37m"
|
|
23
|
+
TEXT_COLOR_GOTE = "\e[30m"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
module Location
|
|
27
|
+
NONE = :none
|
|
28
|
+
BOARD = :board
|
|
29
|
+
SENTE_KOMADAI = :sente_komadai
|
|
30
|
+
GOTE_KOMADAI = :gote_komadai
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def cursor_position
|
|
34
|
+
@cursor_position ||= CursorPosition.new(x: 1, y: 1)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def image_height
|
|
38
|
+
@image_height ||= calculate_fit_image_height
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def print_board(board:, sente_komadai:, gote_komadai:)
|
|
42
|
+
# NOTE 画面をクリア
|
|
43
|
+
print "\e[2J"
|
|
44
|
+
|
|
45
|
+
# NOTE カーソルを1行1列に移動
|
|
46
|
+
print EscapeSequence::RESET_CURSOR
|
|
47
|
+
|
|
48
|
+
board.matrix.row_vectors.each_with_index do |vector, i|
|
|
49
|
+
vector.each_with_index do |piece, j|
|
|
50
|
+
print_image(image: piece.image, height: image_height)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
print "#{EscapeSequence::RESET}\n"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# NOTE 駒台を表示
|
|
57
|
+
print_komadai(sente_komadai, KOMADAI_SENTE_START_Y, EscapeSequence::TEXT_COLOR_SENTE)
|
|
58
|
+
print_komadai(gote_komadai, KOMADAI_GOTE_START_Y, EscapeSequence::TEXT_COLOR_GOTE)
|
|
59
|
+
|
|
60
|
+
# NOTE back a cursor
|
|
61
|
+
back_to_cursor
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def active_piece(board:, piece_index:)
|
|
65
|
+
back_to_cursor
|
|
66
|
+
|
|
67
|
+
board.fetch_piece(x: piece_index[:x], y: piece_index[:y])
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def move_cursor(key)
|
|
71
|
+
return unless %w(A B C D).include?(key)
|
|
72
|
+
|
|
73
|
+
direction =
|
|
74
|
+
case key
|
|
75
|
+
when 'A', 'B'
|
|
76
|
+
key
|
|
77
|
+
when 'C', 'D'
|
|
78
|
+
"#{HORIZONTAL_DISTANCE}#{key}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
print "\e[#{direction}"
|
|
82
|
+
|
|
83
|
+
reload_cursor_position_in_stdin!
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# TODO 駒台の大きさが変わるのをどうするか考える、メソッド名考える
|
|
87
|
+
def squares_index
|
|
88
|
+
case cursor_position
|
|
89
|
+
in x: 1..18, y: 1..9
|
|
90
|
+
{x: (cursor_position.x - 1) / HORIZONTAL_DISTANCE, y: cursor_position.y - 1, location: Location::BOARD}
|
|
91
|
+
in x: 21..38, y: 1..3
|
|
92
|
+
{x: (cursor_position.x - 21) / HORIZONTAL_DISTANCE, y: cursor_position.y - 1, location: Location::GOTE_KOMADAI}
|
|
93
|
+
in x: 21..38, y: 7..9
|
|
94
|
+
{x: (cursor_position.x - 21) / HORIZONTAL_DISTANCE, y: cursor_position.y - 7, location: Location::SENTE_KOMADAI}
|
|
95
|
+
else
|
|
96
|
+
{x: (cursor_position.x - 1) / HORIZONTAL_DISTANCE, y: cursor_position.y - 1, location: Location::NONE}
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def select_promotion
|
|
101
|
+
print "\e[10;1H"
|
|
102
|
+
|
|
103
|
+
prompt = TTY::Prompt.new
|
|
104
|
+
|
|
105
|
+
prompt.select('成りますか?', show_help: :never) {|menu|
|
|
106
|
+
menu.choice '成る', true
|
|
107
|
+
menu.choice '成らない', false
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def print_winner(teban)
|
|
112
|
+
print "\e[4;7H"
|
|
113
|
+
|
|
114
|
+
print_image(image: File.read("images/#{teban}/shori.png"), height: image_height * 3)
|
|
115
|
+
|
|
116
|
+
print "\e[10;1H"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
IMAGE_HEIGHT_PIXEL = 240
|
|
122
|
+
|
|
123
|
+
# TODO 無理やりなので、実現方法を再考する
|
|
124
|
+
def calculate_fit_image_height
|
|
125
|
+
start_position = fetch_cursor_position_in_stdin
|
|
126
|
+
# NOTE NonePiece を選んでいることに意味はない
|
|
127
|
+
print_image(image: NonePiece.new.image)
|
|
128
|
+
end_position = fetch_cursor_position_in_stdin
|
|
129
|
+
|
|
130
|
+
fixed_height = IMAGE_HEIGHT_PIXEL / (end_position[1].to_i - start_position[1].to_i)
|
|
131
|
+
|
|
132
|
+
print "\e[2J"
|
|
133
|
+
# NOTE カーソルを1行1列に移動
|
|
134
|
+
print EscapeSequence::RESET_CURSOR
|
|
135
|
+
|
|
136
|
+
fixed_height
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# TODO view 用の Player 作って整理する
|
|
140
|
+
def print_komadai(komadai, start_y, text_color)
|
|
141
|
+
komadai.pieces.row_vectors.each_with_index do |row_pieces, i|
|
|
142
|
+
print "\e[#{start_y + i};#{KOMADAI_START_X}H"
|
|
143
|
+
print EscapeSequence::BACKGROUND_COLOR_YELLOW
|
|
144
|
+
print text_color
|
|
145
|
+
|
|
146
|
+
row_pieces.each do |p|
|
|
147
|
+
print_image(image: p.image, height: image_height)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
print EscapeSequence::RESET
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def fetch_cursor_position_in_stdin
|
|
155
|
+
stdout = ''
|
|
156
|
+
|
|
157
|
+
$stdin.raw do |stdin|
|
|
158
|
+
$stdout << "\e[6n"
|
|
159
|
+
$stdout.flush
|
|
160
|
+
|
|
161
|
+
while (c = stdin.getc) != 'R'
|
|
162
|
+
stdout += c
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
stdout.match /(\d+);(\d+)/
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def reload_cursor_position_in_stdin!
|
|
170
|
+
positions = fetch_cursor_position_in_stdin
|
|
171
|
+
|
|
172
|
+
@cursor_position = CursorPosition.new(x: positions[2].to_i, y: positions[1].to_i)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def back_to_cursor
|
|
176
|
+
print "\e[#{cursor_position.y};#{cursor_position.x}H"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# TODO 画像を表示するためのメソッド、場所検討
|
|
180
|
+
def print_image(image:, width: nil, height: nil)
|
|
181
|
+
encoded_image = Base64.strict_encode64(image)
|
|
182
|
+
|
|
183
|
+
print_osc
|
|
184
|
+
print "1337;File=inline=1"
|
|
185
|
+
print ";size=#{image.size}"
|
|
186
|
+
print ";name=#{encoded_image}"
|
|
187
|
+
print ";width=#{width}px" unless width.nil?
|
|
188
|
+
print ";height=#{height}px" unless height.nil?
|
|
189
|
+
print ":#{encoded_image}"
|
|
190
|
+
print_st
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def print_osc
|
|
194
|
+
# NOTE \e] で始まるシーケンスは全てのターミナルで扱えるわけでは無い
|
|
195
|
+
if ENV['TERM'].start_with?('screen') || ENV['TERM'].start_with?('tmux')
|
|
196
|
+
print "\ePtmux;\e\e]"
|
|
197
|
+
else
|
|
198
|
+
print "\e]"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def print_st
|
|
203
|
+
if ENV['TERM'].start_with?('screen') || ENV['TERM'].start_with?('tmux')
|
|
204
|
+
print "\a\e\\"
|
|
205
|
+
else
|
|
206
|
+
print "\a"
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'console_shogi/version'
|
|
4
|
+
require_relative 'console_shogi/game'
|
|
5
|
+
require_relative 'console_shogi/player'
|
|
6
|
+
require_relative 'console_shogi/board'
|
|
7
|
+
require_relative 'console_shogi/piece'
|
|
8
|
+
require_relative 'console_shogi/komadai'
|
|
9
|
+
|
|
10
|
+
require_relative 'console_shogi/terminal_operator'
|
|
11
|
+
require_relative 'console_shogi/new_board_builder'
|
|
12
|
+
require_relative 'console_shogi/piece_mover'
|
|
13
|
+
require_relative 'console_shogi/piece_mover_on_komadai'
|
|
14
|
+
|
|
15
|
+
module ConsoleShogi
|
|
16
|
+
class Error < StandardError; end
|
|
17
|
+
|
|
18
|
+
def self.game_start
|
|
19
|
+
Game.new.start
|
|
20
|
+
end
|
|
21
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: console-shogi
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- pekepek
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2023-05-24 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: You can play the Shogi in terminal
|
|
14
|
+
email:
|
|
15
|
+
- ishihata@sansan.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- Gemfile
|
|
21
|
+
- Gemfile.lock
|
|
22
|
+
- LICENSE.txt
|
|
23
|
+
- README.md
|
|
24
|
+
- Rakefile
|
|
25
|
+
- images/gote/fu.png
|
|
26
|
+
- images/gote/gin.png
|
|
27
|
+
- images/gote/gyoku.png
|
|
28
|
+
- images/gote/hisha.png
|
|
29
|
+
- images/gote/kaku.png
|
|
30
|
+
- images/gote/keima.png
|
|
31
|
+
- images/gote/kin.png
|
|
32
|
+
- images/gote/kyosha.png
|
|
33
|
+
- images/gote/narigin.png
|
|
34
|
+
- images/gote/narikei.png
|
|
35
|
+
- images/gote/narikyo.png
|
|
36
|
+
- images/gote/nonepiece.png
|
|
37
|
+
- images/gote/ohsho.png
|
|
38
|
+
- images/gote/ryu.png
|
|
39
|
+
- images/gote/shori.png
|
|
40
|
+
- images/gote/tokin.png
|
|
41
|
+
- images/gote/uma.png
|
|
42
|
+
- images/sente/fu.png
|
|
43
|
+
- images/sente/gin.png
|
|
44
|
+
- images/sente/gyoku.png
|
|
45
|
+
- images/sente/hisha.png
|
|
46
|
+
- images/sente/kaku.png
|
|
47
|
+
- images/sente/keima.png
|
|
48
|
+
- images/sente/kin.png
|
|
49
|
+
- images/sente/kyosha.png
|
|
50
|
+
- images/sente/narigin.png
|
|
51
|
+
- images/sente/narikei.png
|
|
52
|
+
- images/sente/narikyo.png
|
|
53
|
+
- images/sente/nonepiece.png
|
|
54
|
+
- images/sente/ohsho.png
|
|
55
|
+
- images/sente/ryu.png
|
|
56
|
+
- images/sente/shori.png
|
|
57
|
+
- images/sente/tokin.png
|
|
58
|
+
- images/sente/uma.png
|
|
59
|
+
- lib/console_shogi.rb
|
|
60
|
+
- lib/console_shogi/board.rb
|
|
61
|
+
- lib/console_shogi/game.rb
|
|
62
|
+
- lib/console_shogi/komadai.rb
|
|
63
|
+
- lib/console_shogi/new_board_builder.rb
|
|
64
|
+
- lib/console_shogi/piece.rb
|
|
65
|
+
- lib/console_shogi/piece_mover.rb
|
|
66
|
+
- lib/console_shogi/piece_mover_on_komadai.rb
|
|
67
|
+
- lib/console_shogi/player.rb
|
|
68
|
+
- lib/console_shogi/terminal_operator.rb
|
|
69
|
+
- lib/console_shogi/version.rb
|
|
70
|
+
homepage: https://github.com/pekepek/console-shogi
|
|
71
|
+
licenses:
|
|
72
|
+
- MIT
|
|
73
|
+
metadata:
|
|
74
|
+
homepage_uri: https://github.com/pekepek/console-shogi
|
|
75
|
+
source_code_uri: https://github.com/pekepek/console-shogi
|
|
76
|
+
changelog_uri: https://github.com/pekepek/console-shogi/releases
|
|
77
|
+
post_install_message:
|
|
78
|
+
rdoc_options: []
|
|
79
|
+
require_paths:
|
|
80
|
+
- lib
|
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
82
|
+
requirements:
|
|
83
|
+
- - ">="
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: 2.6.0
|
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '0'
|
|
91
|
+
requirements: []
|
|
92
|
+
rubygems_version: 3.4.10
|
|
93
|
+
signing_key:
|
|
94
|
+
specification_version: 4
|
|
95
|
+
summary: You can play the Shogi in terminal
|
|
96
|
+
test_files: []
|