console-shogi 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: add4a4e51f593521d761a1afe000dfe184f8effe9d4f41d4822807465036a3cb
4
- data.tar.gz: dbde9af4607ec797e9bdbea94d1a7ef64d7eb0cdbfbca8c9d5c601696e1f0785
3
+ metadata.gz: bee75f732b33e177d7b2c15939508663a391bc2d593692273a1a1f47ae654543
4
+ data.tar.gz: d78846e34e4afbeea51dfd4558e9c076aee6625c6ad43129fb01cff9c36d41a7
5
5
  SHA512:
6
- metadata.gz: 85e7e5b136cfda4c6a31fa6e1cb9b380b0adfb3162b2f9c2ae154d831a1ee3e7302275c8e94f2da2ce7a2c6695280eedf26fb4df2f0a24b9775b606030d2d0a5
7
- data.tar.gz: 5ea91665e1498809b9b59cc282e2ea22ac7c8dbd1ed2b5770f02bd29afbc18cb08c0ab074ef2a8a5b8aaa4e006513ced0f62a4f9b67bff31866a341b46d810b6
6
+ metadata.gz: 2b0aeed55b0fcaedbe879703c3d7e20fc38a6fa5e2a0617d627ff6f873766aff6f4963dc117f032b64911021a46888ebb2229ed6d4a4231bd91d9ecbcf5a868a
7
+ data.tar.gz: 926eeda23c9e565c45ec8290c0ecf5ca607adfaea24c29f1a95c858f952af948b7e371ae8408f32512c3acd720bb3af5873dce62aa8ff435e0801d1cf2b1cc35
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- console-shogi (0.2.0)
4
+ console-shogi (0.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  You can play the Shogi in your terminal ☗☖
4
4
 
5
+ Maybe, you’ll hard to see if you don’t increase the font size of the terminal.
6
+
5
7
  ## Installation
6
8
 
7
9
  ```
Binary file
Binary file
Binary file
@@ -12,8 +12,8 @@ module ConsoleShogi
12
12
  @pieces
13
13
  end
14
14
 
15
- def copy
16
- Board.new(pieces: pieces.to_a)
15
+ def deep_copy
16
+ Board.new(pieces: pieces.map(&:dup).to_a)
17
17
  end
18
18
 
19
19
  def within_range?(x:, y:)
@@ -9,19 +9,21 @@ module ConsoleShogi
9
9
  @gote_player = Player.new(teban: Teban::GOTE)
10
10
  @board = NewBoardBuilder.build
11
11
 
12
- @previous_board = Board.new(pieces: [])
13
- @from_cursor = nil
14
- @selected_piece = false
12
+ @selected_cursor = nil
15
13
  @teban_player = @sente_player
16
- end
17
14
 
18
- def start
19
- Terminal::Operator.clear_scrren
15
+ @game_histories = [{board: board.deep_copy, sente_komadai: sente_player.komadai.deep_copy, gote_komadai: gote_player.komadai.deep_copy}]
20
16
 
21
- Terminal::Operator.print_diff_board(previous_board: previous_board, board: board, sente_komadai: sente_player.komadai, gote_komadai: gote_player.komadai)
17
+ Terminal::Operator.clear_scrren
18
+ Terminal::Operator.print_board(board: board, sente_komadai: sente_player.komadai, gote_komadai: gote_player.komadai)
19
+ Terminal::Operator.print_history_button
22
20
  Terminal::Operator.print_teban(teban_player.teban)
21
+ end
23
22
 
23
+ def start
24
24
  while key = STDIN.getch
25
+ cursor = Terminal::Operator.cursor
26
+
25
27
  # NOTE Ctrl-C を押したら終了
26
28
  if key == "\C-c"
27
29
  exit
@@ -29,81 +31,54 @@ module ConsoleShogi
29
31
  elsif key == "\e" && STDIN.getch == "["
30
32
  key = STDIN.getch
31
33
 
32
- cursor = Terminal::Operator.cursor
33
- @previous_cursor_on_square = cursor.dup unless cursor.squares_position.location == :none
34
+ @last_cursor_on_grid = cursor.dup unless cursor.grid_position.location.outside?
34
35
 
35
36
  cursor.move(key)
36
37
 
37
38
  # TODO 選択したピースは色を変えないようにしている。状態の持ち方を見直したい
38
- deactive_piece(previous_cursor_on_square) unless selected_piece && from_cursor.squares_position == previous_cursor_on_square.squares_position
39
- focus_piece(cursor) unless selected_piece && from_cursor.squares_position == cursor.squares_position
39
+ deactive_piece(last_cursor_on_grid) if selected_cursor&.grid_position != last_cursor_on_grid.grid_position
40
+ focus_piece(cursor) if selected_cursor&.grid_position != cursor.grid_position
40
41
  # NOTE Enter を押したら駒を移動
41
42
  elsif key == "\r"
42
- # TODO このまま Hash にするかは要検討
43
- cursor = Terminal::Operator.cursor
44
-
45
- if selected_piece
46
- next if cursor.squares_position.location != :board
47
-
48
- piece_mover =
49
- case from_cursor.squares_position.location
50
- when :board
51
- # TODO 後で index を統一的にどう扱うか整理する
52
- PieceMover.new(
53
- board: board,
54
- player: teban_player,
55
- from: from_cursor.squares_position.to_h,
56
- to: cursor.squares_position.to_h
57
- )
58
- when :sente_komadai
59
- PieceMoverOnKomadai.new(
60
- board: board,
61
- komadai: sente_player.komadai,
62
- from: from_cursor.squares_position.to_h,
63
- to: cursor.squares_position.to_h
64
- )
65
- when :gote_komadai
66
- PieceMoverOnKomadai.new(
67
- board: board,
68
- komadai: gote_player.komadai,
69
- from: from_cursor.squares_position.to_h,
70
- to: cursor.squares_position.to_h
71
- )
72
- end
43
+ if cursor.grid_position.location.history?
44
+ GameHistory.new(game_histories: game_histories).start
73
45
 
74
- piece_mover.move!
75
-
76
- if piece_mover.moved_piece?
77
- Terminal::Operator.print_diff_board(previous_board: previous_board, board: board, sente_komadai: sente_player.komadai, gote_komadai: gote_player.komadai)
46
+ next
47
+ end
78
48
 
79
- @previous_board = board.copy
80
- change_teban!
49
+ if selected_cursor.nil?
50
+ # TODO PieceMover の can_move? と分散してしまっている気もする
51
+ next if cursor.grid_position.location.outside?
81
52
 
82
- Terminal::Operator.print_teban(teban_player.teban)
83
- else
84
- deactive_piece(from_cursor)
85
- end
53
+ active_piece(cursor)
86
54
 
87
- @from_cursor = nil
88
- @selected_piece = false
55
+ @selected_cursor = cursor.dup
89
56
  else
90
- # TODO PieceMover の can_move? と分散してしまっている気もする
91
- next if cursor.squares_position.location == :none
92
- next if teban_player.sente? && cursor.squares_position.location == :gote_komadai
93
- next if teban_player.gote? && cursor.squares_position.location == :sente_komadai
57
+ next unless cursor.grid_position.location.board?
94
58
 
95
- active_piece(cursor)
59
+ piece_mover = PieceMover.build(board: board, player: teban_player, from_cursor: selected_cursor, to_cursor: cursor)
96
60
 
97
- @from_cursor = cursor.dup
98
- @selected_piece = true
99
- end
61
+ piece_mover.move!
62
+
63
+ if piece_mover.moved_piece?
64
+ Terminal::Operator.print_diff_board(previous_board: game_histories.last[:board], board: board, sente_komadai: sente_player.komadai, gote_komadai: gote_player.komadai)
100
65
 
101
- # TODO ひとまず動く物を作った。リファクタリングする
102
- [sente_player, gote_player].each do |player|
103
- next unless player.win?
66
+ @game_histories << {board: board.deep_copy, sente_komadai: sente_player.komadai.deep_copy, gote_komadai: gote_player.komadai.deep_copy}
104
67
 
105
- Terminal::Operator.print_winner(player)
106
- exit
68
+ if teban_player.win?
69
+ Terminal::Operator.print_winner(teban_player)
70
+
71
+ exit
72
+ else
73
+ change_teban!
74
+
75
+ Terminal::Operator.print_teban(teban_player.teban)
76
+ end
77
+ else
78
+ deactive_piece(selected_cursor)
79
+ end
80
+
81
+ @selected_cursor = nil
107
82
  end
108
83
  end
109
84
  end
@@ -111,45 +86,45 @@ module ConsoleShogi
111
86
 
112
87
  private
113
88
 
114
- attr_reader :board, :sente_player, :gote_player, :selected_piece, :from_cursor, :teban_player, :previous_board, :previous_cursor_on_square
89
+ attr_reader :board, :sente_player, :gote_player, :selected_cursor, :teban_player, :game_histories, :last_cursor_on_grid
115
90
 
116
91
  def change_teban!
117
92
  @teban_player = teban_player == sente_player ? gote_player : sente_player
118
93
  end
119
94
 
95
+ # TODO ここらへんイケてないのでリファクタしたい
120
96
  def active_piece(cursor)
121
- # TODO case で指定しないといけないのイケてないのでリファクタしたい
122
- case cursor.squares_position.location
123
- when :board
124
- Terminal::Operator.active_piece(location: board, cursor: cursor)
125
- when :sente_komadai
126
- Terminal::Operator.active_piece(location: sente_player.komadai, cursor: cursor)
127
- when :gote_komadai
128
- Terminal::Operator.active_piece(location: gote_player.komadai, cursor: cursor)
129
- end
97
+ location = fetch_piece_location_object(cursor.grid_position.location)
98
+
99
+ return if location.nil?
100
+
101
+ Terminal::Operator.active_piece(board: location, cursor: cursor)
130
102
  end
131
103
 
132
104
  def deactive_piece(cursor)
133
- # TODO case で指定しないといけないのイケてないのでリファクタしたい
134
- case cursor.squares_position.location
135
- when :board
136
- Terminal::Operator.deactive_piece(location: board, previous_cursor: cursor)
137
- when :sente_komadai
138
- Terminal::Operator.deactive_piece(location: sente_player.komadai, previous_cursor: cursor)
139
- when :gote_komadai
140
- Terminal::Operator.deactive_piece(location: gote_player.komadai, previous_cursor: cursor)
141
- end
105
+ location = fetch_piece_location_object(cursor.grid_position.location)
106
+
107
+ return if location.nil?
108
+
109
+ Terminal::Operator.deactive_piece(board: location, previous_cursor: cursor)
142
110
  end
143
111
 
144
112
  def focus_piece(cursor)
145
- # TODO case で指定しないといけないのイケてないのでリファクタしたい
146
- case cursor.squares_position.location
113
+ location = fetch_piece_location_object(cursor.grid_position.location)
114
+
115
+ return if location.nil?
116
+
117
+ Terminal::Operator.focus_piece(board: location, cursor: cursor)
118
+ end
119
+
120
+ def fetch_piece_location_object(cursor_location)
121
+ case cursor_location.name
147
122
  when :board
148
- Terminal::Operator.focus_piece(location: board, cursor: cursor)
123
+ board
149
124
  when :sente_komadai
150
- Terminal::Operator.focus_piece(location: sente_player.komadai, cursor: cursor)
125
+ sente_player.komadai
151
126
  when :gote_komadai
152
- Terminal::Operator.focus_piece(location: gote_player.komadai, cursor: cursor)
127
+ gote_player.komadai
153
128
  end
154
129
  end
155
130
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/console'
4
+
5
+ module ConsoleShogi
6
+ class GameHistory
7
+ def initialize(game_histories:)
8
+ @game_histories = game_histories
9
+ # TODO game_count という名前は分かりづらいので変える
10
+ @game_count = game_histories.count
11
+
12
+ Terminal::Operator.print_start_history
13
+ end
14
+
15
+ def start
16
+ while key = STDIN.getch
17
+ cursor = Terminal::Operator.cursor
18
+
19
+ # NOTE Ctrl-C を押したら終了
20
+ if key == "\C-c"
21
+ exit
22
+ # NOTE 矢印キーを押したらカーソルを移動
23
+ elsif key == "\e" && STDIN.getch == "["
24
+ key = STDIN.getch
25
+
26
+ cursor.move(key)
27
+ elsif key == "\r"
28
+ location = cursor.grid_position.location
29
+
30
+ next unless location.history?
31
+
32
+ if location.resume?
33
+ Terminal::Operator.clear_scrren
34
+
35
+ Terminal::Operator.print_board(board: game_histories.last[:board], sente_komadai: game_histories.last[:sente_komadai], gote_komadai: game_histories.last[:gote_komadai])
36
+ Terminal::Operator.print_history_button
37
+
38
+ return
39
+ elsif location.back?
40
+ next if game_count == 1
41
+
42
+ @game_count -= 1
43
+
44
+ Terminal::Operator.print_board(board: game_histories[game_count - 1][:board], sente_komadai: game_histories[game_count - 1][:sente_komadai], gote_komadai: game_histories[game_count - 1][:gote_komadai])
45
+ elsif location.forward?
46
+ next if game_count == game_histories.count
47
+
48
+ @game_count += 1
49
+
50
+ Terminal::Operator.print_board(board: game_histories[game_count - 1][:board], sente_komadai: game_histories[game_count - 1][:sente_komadai], gote_komadai: game_histories[game_count - 1][:gote_komadai])
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ attr_reader :game_histories, :game_count
59
+ end
60
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConsoleShogi
4
+ class GridPosition < Struct.new(:x, :y, :location)
5
+ end
6
+ end
@@ -8,8 +8,12 @@ module ConsoleShogi
8
8
 
9
9
  attr_reader :pieces
10
10
 
11
- def initialize
12
- @pieces = empty_pieces
11
+ def initialize(pieces: empty_pieces)
12
+ @pieces = Matrix.rows(pieces)
13
+ end
14
+
15
+ def deep_copy
16
+ Komadai.new(pieces: pieces.map(&:dup).to_a)
13
17
  end
14
18
 
15
19
  def fetch_piece(x:, y:)
@@ -28,8 +32,8 @@ module ConsoleShogi
28
32
  @pieces = sort_pieces
29
33
  end
30
34
 
31
- def pick_up_piece!(from:)
32
- @pieces[from[:y], from[:x]] = NonePiece.new
35
+ def pick_up_piece!(x:, y:)
36
+ @pieces[y, x] = NonePiece.new
33
37
  @pieces = sort_pieces
34
38
  end
35
39
 
@@ -49,13 +53,11 @@ module ConsoleShogi
49
53
  end
50
54
 
51
55
  def empty_pieces
52
- Matrix.rows(
53
- [
54
- [NonePiece.new, NonePiece.new, NonePiece.new],
55
- [NonePiece.new, NonePiece.new, NonePiece.new],
56
- [NonePiece.new, NonePiece.new, NonePiece.new]
57
- ]
58
- )
56
+ [
57
+ [NonePiece.new, NonePiece.new, NonePiece.new],
58
+ [NonePiece.new, NonePiece.new, NonePiece.new],
59
+ [NonePiece.new, NonePiece.new, NonePiece.new]
60
+ ]
59
61
  end
60
62
  end
61
63
  end
@@ -63,6 +63,11 @@ module ConsoleShogi
63
63
  def can_move_long_distance?
64
64
  [Kaku, Hisha, Kyosha, PromotedPiece::Uma, PromotedPiece::Ryu].include?(self::class)
65
65
  end
66
+
67
+ def same?(piece)
68
+ self::class == piece::class &&
69
+ self.teban == piece.teban
70
+ end
66
71
  end
67
72
 
68
73
  class NonePiece < Piece
@@ -2,12 +2,26 @@
2
2
 
3
3
  module ConsoleShogi
4
4
  class PieceMover
5
- def initialize(board:, player:, from:, to:)
6
- @board = board
7
- @player = player
8
- @from = from
9
- @to = to
10
- @moved_piece = false
5
+ class << self
6
+ def build(board:, player:, from_cursor:, to_cursor:)
7
+ location = from_cursor.grid_position.location
8
+
9
+ if location.board?
10
+ PieceMoverFromBoard.new(
11
+ board: board,
12
+ player: player,
13
+ from: from_cursor.grid_position,
14
+ to: to_cursor.grid_position
15
+ )
16
+ elsif location.komadai?
17
+ PieceMoverFromKomadai.new(
18
+ board: board,
19
+ player: player,
20
+ from: from_cursor.grid_position,
21
+ to: to_cursor.grid_position
22
+ )
23
+ end
24
+ end
11
25
  end
12
26
 
13
27
  def move!
@@ -17,49 +31,5 @@ module ConsoleShogi
17
31
  def moved_piece?
18
32
  @moved_piece
19
33
  end
20
-
21
- private
22
-
23
- attr_reader :board, :player, :from, :to
24
-
25
- def from_piece
26
- @from_piece ||= board.fetch_piece(x: from[:x], y: from[:y])
27
- end
28
-
29
- def move_piece!
30
- return false if from_piece.nil? || from_piece.none?
31
- return false if from_piece.teban != player.teban
32
-
33
- return false unless piece_movement_checker.can_move?
34
-
35
- to_piece = board.fetch_piece(x: to[:x], y: to[:y])
36
- player.capture_piece!(to_piece) unless to_piece.none?
37
-
38
- board.move_piece!(
39
- from: {x: from[:x], y: from[:y]},
40
- to: {x: to[:x], y: to[:y]}
41
- )
42
-
43
- # TODO とりあえずここに実装してしまっている。整理したい
44
- return true unless can_promote?(from_piece, from, to)
45
-
46
- board.promote_piece!(x: to[:x], y: to[:y]) if Terminal::Operator.select_promotion
47
-
48
- true
49
- end
50
-
51
- def piece_movement_checker
52
- PieceMovementChecker.new(board: board, from: from, to: to)
53
- end
54
-
55
- def can_promote?(piece, from, to)
56
- return false unless piece.can_promote?
57
-
58
- if piece.teban == Teban::SENTE
59
- from[:y].between?(0, 2) || to[:y].between?(0, 2)
60
- elsif piece.teban == Teban::GOTE
61
- from[:y].between?(6, 8) || to[:y].between?(6, 8)
62
- end
63
- end
64
34
  end
65
35
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConsoleShogi
4
+ class PieceMoverFromBoard < PieceMover
5
+ def initialize(board:, player:, from:, to:)
6
+ @board = board
7
+ @player = player
8
+ @from = from
9
+ @to = to
10
+ @moved_piece = false
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :board, :player, :from, :to
16
+
17
+ def from_piece
18
+ @from_piece ||= board.fetch_piece(x: from.x, y: from.y)
19
+ end
20
+
21
+ def move_piece!
22
+ return false if from_piece.nil? || from_piece.none?
23
+ return false if from_piece.teban != player.teban
24
+
25
+ return false unless piece_movement_checker.can_move?
26
+
27
+ to_piece = board.fetch_piece(x: to.x, y: to.y)
28
+ player.capture_piece!(to_piece) unless to_piece.none?
29
+
30
+ board.move_piece!(
31
+ from: {x: from.x, y: from.y},
32
+ to: {x: to.x, y: to.y}
33
+ )
34
+
35
+ # TODO とりあえずここに実装してしまっている。整理したい
36
+ return true unless can_promote?(from_piece, from, to)
37
+
38
+ board.promote_piece!(x: to.x, y: to.y) if Terminal::Operator.select_promotion
39
+
40
+ true
41
+ end
42
+
43
+ def piece_movement_checker
44
+ PieceMovementChecker.new(board: board, from: from, to: to)
45
+ end
46
+
47
+ def can_promote?(piece, from, to)
48
+ return false unless piece.can_promote?
49
+
50
+ if piece.teban == Teban::SENTE
51
+ from.y.between?(0, 2) || to.y.between?(0, 2)
52
+ elsif piece.teban == Teban::GOTE
53
+ from.y.between?(6, 8) || to.y.between?(6, 8)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConsoleShogi
4
+ class PieceMoverFromKomadai < PieceMover
5
+ def initialize(board:, player:, from:, to:)
6
+ @board = board
7
+ @komadai = player.komadai
8
+ @player = player
9
+ @from = from
10
+ @to = to
11
+ @moved_piece = false
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :board, :komadai, :player, :from, :to
17
+
18
+ def move_piece!
19
+ return false if from_piece.nil? || from_piece.none?
20
+ return false if from_piece.teban != player.teban
21
+
22
+ return false unless to.location.board?
23
+ return false unless can_drop?(piece: from_piece, to: to)
24
+
25
+ komadai.pick_up_piece!(x: from.x, y: from.y)
26
+ board.put_piece!(piece: from_piece, to: to)
27
+
28
+ true
29
+ end
30
+
31
+ def from_piece
32
+ @from_piece ||= komadai.fetch_piece(x: from.x, y: from.y)
33
+ end
34
+
35
+ def can_drop?(piece:, to:)
36
+ to_piece = board.fetch_piece(x: to.x, y: to.y)
37
+
38
+ return false unless to_piece.none?
39
+
40
+ return false if nifu?(piece, to)
41
+
42
+ # TODO 動かせない場所に打つことはできないようにする
43
+ true
44
+ end
45
+
46
+ def nifu?(piece, to)
47
+ piece.fu? &&
48
+ board.matrix.column(to.x).any? {|p| p.fu? && piece.teban == p.teban }
49
+ end
50
+ end
51
+ end
@@ -1,25 +1,17 @@
1
1
  module ConsoleShogi
2
2
  module Terminal
3
3
  class Cursor
4
- module Location
5
- NONE = :none
6
- BOARD = :board
7
- SENTE_KOMADAI = :sente_komadai
8
- GOTE_KOMADAI = :gote_komadai
9
- end
10
-
11
4
  TerminalPosition = Struct.new(:x, :y)
12
- SquaresPosition = Struct.new(:x, :y, :location)
13
5
 
14
6
  VERTICAL_DISTANCE = 1
15
7
  HORIZONTAL_DISTANCE = 2
16
8
  CURSOR_DIRECTIONS = %w(A B C D)
17
9
 
18
- attr_reader :terminal_position, :squares_position
10
+ attr_reader :terminal_position, :grid_position
19
11
 
20
12
  def initialize(terminal_index_x: 1, terminal_index_y: 1)
21
13
  @terminal_position = TerminalPosition.new(x: 1, y: 1)
22
- @squares_position = SquaresPosition.new(**calculate_squares_position)
14
+ @grid_position = GridPosition.new(**calculate_grid_position)
23
15
  end
24
16
 
25
17
  def move(direction)
@@ -44,19 +36,21 @@ module ConsoleShogi
44
36
  positions = fetch_cursor_position_in_stdin
45
37
 
46
38
  @terminal_position = TerminalPosition.new(x: positions[2].to_i, y: positions[1].to_i)
47
- @squares_position = SquaresPosition.new(**calculate_squares_position)
39
+ @grid_position = GridPosition.new(**calculate_grid_position)
48
40
  end
49
41
 
50
- def calculate_squares_position
51
- case terminal_position.to_h
52
- in x: 1..18, y: 1..9
53
- {x: (terminal_position.x - 1) / HORIZONTAL_DISTANCE, y: terminal_position.y - 1, location: Location::BOARD}
54
- in x: 21..38, y: 1..3
55
- {x: (terminal_position.x - 21) / HORIZONTAL_DISTANCE, y: terminal_position.y - 1, location: Location::GOTE_KOMADAI}
56
- in x: 21..38, y: 7..9
57
- {x: (terminal_position.x - 21) / HORIZONTAL_DISTANCE, y: terminal_position.y - 7, location: Location::SENTE_KOMADAI}
42
+ def calculate_grid_position
43
+ area = DisplayArea.fetch_area(x: terminal_position.x, y: terminal_position.y)
44
+
45
+ # TODO history infomation の扱いを整理する
46
+ if area.outside?
47
+ {x: nil, y: nil, location: area}
58
48
  else
59
- {x: (terminal_position.x - 1) / HORIZONTAL_DISTANCE, y: terminal_position.y - 1, location: Location::NONE}
49
+ {
50
+ x: (terminal_position.x - area.start_position.x) / HORIZONTAL_DISTANCE,
51
+ y: terminal_position.y - area.start_position.y,
52
+ location: area
53
+ }
60
54
  end
61
55
  end
62
56
 
@@ -1,29 +1,121 @@
1
1
  module ConsoleShogi
2
2
  module Terminal
3
- module DisplayArea
4
- module Board
5
- START_INDEX = {x: 1, y: 1}
6
- END_INDEX = {x: 18, y: 9}
7
- RANGE = {x: Range.new(START_INDEX[:x], END_INDEX[:x]), y: Range.new(START_INDEX[:y], END_INDEX[:y])}
3
+ class DisplayArea
4
+ OTHERS = :others
5
+ BOARD = :board
6
+ SENTE_KOMADAI = :sente_komadai
7
+ GOTE_KOMADAI = :gote_komadai
8
+
9
+ Position = Struct.new(:x, :y)
10
+
11
+ class << self
12
+ def fetch_area(x:, y:)
13
+ [Board, Komadai::Gote, Komadai::Sente, History::Back, History::Forward, History::Resume, Infomation].each do |klass|
14
+ return klass if klass.in?(x: x, y: y)
15
+ end
16
+
17
+ Others
18
+ end
19
+
20
+ def start_position
21
+ self::START_POSITION
22
+ end
23
+
24
+ def end_position
25
+ self::END_POSITION
26
+ end
27
+
28
+ def name
29
+ self::NAME
30
+ end
31
+
32
+ def board?
33
+ Board == self
34
+ end
35
+
36
+ def komadai?
37
+ [Komadai::Gote, Komadai::Sente].include?(self)
38
+ end
39
+
40
+ def history?
41
+ [History::Back, History::Forward, History::Resume].include?(self)
42
+ end
43
+
44
+ def outside?
45
+ [History::Back, History::Forward, History::Resume, Infomation, Others].include?(self)
46
+ end
47
+
48
+ def in?(x:, y:)
49
+ Range.new(start_position.x, end_position.x).include?(x) &&
50
+ Range.new(start_position.y, end_position.y).include?(y)
51
+ end
52
+ end
53
+
54
+ class Board < DisplayArea
55
+ START_POSITION = Position.new(x: 1, y: 1)
56
+ END_POSITION = Position.new(x: 18, y: 9)
57
+ NAME = :board
8
58
  end
9
59
 
10
60
  module Komadai
11
- module Gote
12
- START_INDEX = {x: 21, y: 1}
13
- END_INDEX = {x: 38, y: 3}
14
- RANGE = {x: Range.new(START_INDEX[:x], END_INDEX[:x]), y: Range.new(START_INDEX[:y], END_INDEX[:y])}
61
+ class Gote < DisplayArea
62
+ START_POSITION = Position.new(x: 21, y: 1)
63
+ END_POSITION = Position.new(x: 38, y: 3)
64
+ NAME = :gote_komadai
65
+ end
66
+
67
+ class Sente < DisplayArea
68
+ START_POSITION = Position.new(x: 21, y: 7)
69
+ END_POSITION = Position.new(x: 38, y: 9)
70
+ NAME = :sente_komadai
71
+ end
72
+ end
73
+
74
+ class History < DisplayArea
75
+ START_POSITION = Position.new(x: 1, y: 10)
76
+ END_POSITION = Position.new(x: 6, y: 10)
77
+ NAME = :history
78
+
79
+ class << self
80
+ def back?
81
+ self == Back
82
+ end
83
+
84
+ def forward?
85
+ self == Forward
86
+ end
87
+
88
+ def resume?
89
+ self == Resume
90
+ end
91
+ end
92
+
93
+ class Back < History
94
+ START_POSITION = Position.new(x: 1, y: 10)
95
+ END_POSITION = Position.new(x: 2, y: 10)
15
96
  end
16
97
 
17
- module Sente
18
- START_INDEX = {x: 21, y: 7}
19
- END_INDEX = {x: 38, y: 9}
20
- RANGE = {x: Range.new(START_INDEX[:x], END_INDEX[:x]), y: Range.new(START_INDEX[:y], END_INDEX[:y])}
98
+ class Forward < History
99
+ START_POSITION = Position.new(x: 3, y: 10)
100
+ END_POSITION = Position.new(x: 4, y: 10)
21
101
  end
102
+
103
+ class Resume < History
104
+ START_POSITION = Position.new(x: 5, y: 10)
105
+ END_POSITION = Position.new(x: 6, y: 10)
106
+ end
107
+ end
108
+
109
+ class Infomation < DisplayArea
110
+ START_POSITION = Position.new(x: 1, y: 11)
111
+ END_POSITION = Position.new(x: 18, y: 15)
112
+ NAME = :infomation
22
113
  end
23
114
 
24
- module OutSide
25
- START_INDEX = {x: 10, y: 1}
26
- RANGE = {x: Range.new(START_INDEX[:x], nil), y: Range.new(START_INDEX[:y], nil)}
115
+ class Others < DisplayArea
116
+ START_POSITION = Position.new(x: nil, y: nil)
117
+ END_POSITION = Position.new(x: nil, y: nil)
118
+ NAME = :others
27
119
  end
28
120
  end
29
121
  end
@@ -14,10 +14,12 @@ module ConsoleShogi
14
14
 
15
15
  module EscapeSequence
16
16
  RESET = "\e[0m"
17
- RESET_CURSOR = "\e[1;1H"
18
17
  SCREEN_CLEAR = "\e[2J"
19
18
  SCREEN_CLEAR_AFTER_CURSOR = "\e[0J"
20
- OUTSIDE_BOARD = "\e[10;1H"
19
+ SCREEN_CLEAR_NOW_LINE = "\e[2K"
20
+ MOVE_START_POINT = "\e[1;1H"
21
+ MOVE_HISTORY_AREA = "\e[#{DisplayArea::History.start_position.y};#{DisplayArea::History.start_position.x}H"
22
+ MOVE_INFOMATION_AREA = "\e[#{DisplayArea::Infomation.start_position.y};#{DisplayArea::Infomation.start_position.x}H"
21
23
  CURRENT_POSITION = "\e[6n"
22
24
  MOVE_RIGHT_2 = "\e[2C"
23
25
  end
@@ -33,16 +35,34 @@ module ConsoleShogi
33
35
  def clear_scrren
34
36
  print EscapeSequence::SCREEN_CLEAR
35
37
 
36
- print EscapeSequence::RESET_CURSOR
38
+ print EscapeSequence::MOVE_START_POINT
39
+ end
40
+
41
+ def print_board(board:, sente_komadai:, gote_komadai:)
42
+ print EscapeSequence::MOVE_START_POINT
43
+
44
+ board.matrix.row_vectors.each_with_index do |vector, i|
45
+ vector.each_with_index do |piece, j|
46
+ print_image(image: piece.image, height: image_height)
47
+ end
48
+
49
+ print "#{EscapeSequence::RESET}\n"
50
+ end
51
+
52
+ # NOTE 駒台を表示
53
+ print_komadai(komadai: sente_komadai, start_position: DisplayArea::Komadai::Sente.start_position)
54
+ print_komadai(komadai: gote_komadai, start_position: DisplayArea::Komadai::Gote.start_position)
55
+
56
+ back_to_cursor
37
57
  end
38
58
 
39
59
  # TODO 駒台の表示方法も考える
40
60
  def print_diff_board(previous_board:, board:, sente_komadai:, gote_komadai:)
41
- print EscapeSequence::RESET_CURSOR
61
+ print EscapeSequence::MOVE_START_POINT
42
62
 
43
63
  board.matrix.row_vectors.each_with_index do |vector, i|
44
64
  vector.each_with_index do |piece, j|
45
- if previous_board.matrix[i, j] == piece
65
+ if previous_board.matrix[i, j].nil? || previous_board.matrix[i, j].same?(piece)
46
66
  print EscapeSequence::MOVE_RIGHT_2
47
67
  else
48
68
  print_image(image: piece.image, height: image_height)
@@ -53,15 +73,23 @@ module ConsoleShogi
53
73
  end
54
74
 
55
75
  # NOTE 駒台を表示
56
- print_komadai(komadai: sente_komadai, **DisplayArea::Komadai::Sente::START_INDEX)
57
- print_komadai(komadai: gote_komadai, **DisplayArea::Komadai::Gote::START_INDEX)
76
+ print_komadai(komadai: sente_komadai, start_position: DisplayArea::Komadai::Sente.start_position)
77
+ print_komadai(komadai: gote_komadai, start_position: DisplayArea::Komadai::Gote.start_position)
78
+
79
+ back_to_cursor
80
+ end
81
+
82
+ def print_start_history
83
+ print EscapeSequence::MOVE_INFOMATION_AREA
84
+ print EscapeSequence::SCREEN_CLEAR_AFTER_CURSOR
85
+
86
+ print '履歴モード'
58
87
 
59
- # NOTE back a cursor
60
88
  back_to_cursor
61
89
  end
62
90
 
63
- def focus_piece(location:, cursor:)
64
- piece = location.fetch_piece(x: cursor.squares_position.x, y: cursor.squares_position.y)
91
+ def focus_piece(board:, cursor:)
92
+ piece = board.fetch_piece(x: cursor.grid_position.x, y: cursor.grid_position.y)
65
93
 
66
94
  return if piece.nil?
67
95
 
@@ -70,18 +98,18 @@ module ConsoleShogi
70
98
  back_to_cursor
71
99
  end
72
100
 
73
- def deactive_piece(location:, previous_cursor:)
101
+ def deactive_piece(board:, previous_cursor:)
74
102
  print "\e[#{previous_cursor.terminal_position.y};#{previous_cursor.terminal_position.x}H"
75
103
 
76
- piece = location.fetch_piece(x: previous_cursor.squares_position.x, y: previous_cursor.squares_position.y)
104
+ piece = board.fetch_piece(x: previous_cursor.grid_position.x, y: previous_cursor.grid_position.y)
77
105
 
78
106
  print_image(image: piece.image, height: image_height) unless piece.nil?
79
107
 
80
108
  back_to_cursor
81
109
  end
82
110
 
83
- def active_piece(location:, cursor:)
84
- piece = location.fetch_piece(x: cursor.squares_position.x, y: cursor.squares_position.y)
111
+ def active_piece(board:, cursor:)
112
+ piece = board.fetch_piece(x: cursor.grid_position.x, y: cursor.grid_position.y)
85
113
 
86
114
  return if piece.nil?
87
115
 
@@ -91,7 +119,7 @@ module ConsoleShogi
91
119
  end
92
120
 
93
121
  def select_promotion
94
- print EscapeSequence::OUTSIDE_BOARD
122
+ print EscapeSequence::MOVE_INFOMATION_AREA
95
123
 
96
124
  prompt = TTY::Prompt.new
97
125
 
@@ -106,12 +134,12 @@ module ConsoleShogi
106
134
 
107
135
  print_image(image: player.win_image, height: image_height * 3)
108
136
 
109
- print EscapeSequence::OUTSIDE_BOARD
137
+ print EscapeSequence::MOVE_INFOMATION_AREA
110
138
  end
111
139
 
112
140
  # TODO 見た目は後で直す
113
141
  def print_teban(teban)
114
- print EscapeSequence::OUTSIDE_BOARD
142
+ print EscapeSequence::MOVE_INFOMATION_AREA
115
143
  print EscapeSequence::SCREEN_CLEAR_AFTER_CURSOR
116
144
 
117
145
  print "手番 : #{TEBAN[teban.to_sym]}"
@@ -119,6 +147,18 @@ module ConsoleShogi
119
147
  back_to_cursor
120
148
  end
121
149
 
150
+ # TODO データの指定の仕方は後で整理する
151
+ def print_history_button
152
+ print EscapeSequence::MOVE_HISTORY_AREA
153
+ print EscapeSequence::SCREEN_CLEAR_NOW_LINE
154
+
155
+ print_image(image: File.read("images/history_button/left.png"), height: image_height)
156
+ print_image(image: File.read("images/history_button/right.png"), height: image_height)
157
+ print_image(image: File.read("images/history_button/play.png"), height: image_height)
158
+
159
+ back_to_cursor
160
+ end
161
+
122
162
  private
123
163
 
124
164
  IMAGE_HEIGHT_PIXEL = 240
@@ -134,15 +174,15 @@ module ConsoleShogi
134
174
 
135
175
  print EscapeSequence::SCREEN_CLEAR
136
176
  # NOTE カーソルを1行1列に移動
137
- print EscapeSequence::RESET_CURSOR
177
+ print EscapeSequence::MOVE_START_POINT
138
178
 
139
179
  fixed_height
140
180
  end
141
181
 
142
182
  # TODO view 用の Player 作って整理する
143
- def print_komadai(komadai:, x:, y:)
183
+ def print_komadai(komadai:, start_position:)
144
184
  komadai.pieces.row_vectors.each_with_index do |row_pieces, i|
145
- print "\e[#{y + i};#{x}H"
185
+ print "\e[#{start_position.y + i};#{start_position.x}H"
146
186
 
147
187
  row_pieces.each do |p|
148
188
  print_image(image: p.image, height: image_height)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ConsoleShogi
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/console_shogi.rb CHANGED
@@ -1,20 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'console_shogi/version'
4
- require_relative 'console_shogi/game'
5
4
  require_relative 'console_shogi/player'
6
5
  require_relative 'console_shogi/board'
7
6
  require_relative 'console_shogi/piece'
8
7
  require_relative 'console_shogi/komadai'
9
8
  require_relative 'console_shogi/teban'
9
+ require_relative 'console_shogi/grid_position'
10
10
 
11
11
  require_relative 'console_shogi/terminal/display_area'
12
12
  require_relative 'console_shogi/terminal/operator'
13
13
  require_relative 'console_shogi/terminal/cursor'
14
14
 
15
+ require_relative 'console_shogi/game'
16
+ require_relative 'console_shogi/game_history'
15
17
  require_relative 'console_shogi/new_board_builder'
16
18
  require_relative 'console_shogi/piece_mover'
17
- require_relative 'console_shogi/piece_mover_on_komadai'
19
+ require_relative 'console_shogi/piece_mover_from_board'
20
+ require_relative 'console_shogi/piece_mover_from_komadai'
18
21
  require_relative 'console_shogi/piece_movement_checker'
19
22
 
20
23
  module ConsoleShogi
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: console-shogi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - pekepek
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-06-02 00:00:00.000000000 Z
11
+ date: 2023-06-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: You can play the Shogi in terminal
14
14
  email:
@@ -87,6 +87,9 @@ files:
87
87
  - images/focused/sente/tokin.png
88
88
  - images/focused/sente/uma.png
89
89
  - images/gote_shori.png
90
+ - images/history_button/left.png
91
+ - images/history_button/play.png
92
+ - images/history_button/right.png
90
93
  - images/nomal/gote/fu.png
91
94
  - images/nomal/gote/gin.png
92
95
  - images/nomal/gote/gyoku.png
@@ -122,12 +125,15 @@ files:
122
125
  - lib/console_shogi.rb
123
126
  - lib/console_shogi/board.rb
124
127
  - lib/console_shogi/game.rb
128
+ - lib/console_shogi/game_history.rb
129
+ - lib/console_shogi/grid_position.rb
125
130
  - lib/console_shogi/komadai.rb
126
131
  - lib/console_shogi/new_board_builder.rb
127
132
  - lib/console_shogi/piece.rb
128
133
  - lib/console_shogi/piece_movement_checker.rb
129
134
  - lib/console_shogi/piece_mover.rb
130
- - lib/console_shogi/piece_mover_on_komadai.rb
135
+ - lib/console_shogi/piece_mover_from_board.rb
136
+ - lib/console_shogi/piece_mover_from_komadai.rb
131
137
  - lib/console_shogi/player.rb
132
138
  - lib/console_shogi/teban.rb
133
139
  - lib/console_shogi/terminal/cursor.rb
@@ -156,7 +162,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
162
  - !ruby/object:Gem::Version
157
163
  version: '0'
158
164
  requirements: []
159
- rubygems_version: 3.4.10
165
+ rubygems_version: 3.4.13
160
166
  signing_key:
161
167
  specification_version: 4
162
168
  summary: You can play the Shogi in terminal
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ConsoleShogi
4
- class PieceMoverOnKomadai
5
- def initialize(board:, komadai:, from:, to:)
6
- @board = board
7
- @komadai = komadai
8
- # 駒台の index に直す必要がある
9
- @from = from
10
- @to = to
11
- @moved_piece = false
12
- end
13
-
14
- def move!
15
- @moved_piece = move_piece!
16
- end
17
-
18
- def moved_piece?
19
- @moved_piece
20
- end
21
-
22
- private
23
-
24
- attr_reader :board, :komadai, :from, :to
25
-
26
- def move_piece!
27
- return false if from_piece.nil? || from_piece.none?
28
-
29
- # TODO ここで location の key もってるの期待してるの酷い、修正する
30
- return false if to[:location] != :board
31
- return false unless can_drop?(piece: from_piece, to: to)
32
-
33
- komadai.pick_up_piece!(from: from)
34
- board.put_piece!(piece: from_piece, to: to)
35
-
36
- true
37
- end
38
-
39
- def from_piece
40
- @from_piece ||= komadai.fetch_piece(x: from[:x], y: from[:y])
41
- end
42
-
43
- def can_drop?(piece:, to:)
44
- to_piece = board.fetch_piece(x: to[:x], y: to[:y])
45
-
46
- return false unless to_piece.none?
47
-
48
- return false if nifu?(piece, to)
49
-
50
- # TODO 動かせない場所に打つことはできないようにする
51
- true
52
- end
53
-
54
- def nifu?(piece, to)
55
- piece.fu? &&
56
- board.matrix.column(to[:x]).any? {|p| p.fu? && piece.teban == p.teban }
57
- end
58
- end
59
- end