alex_butirskiy_sea_battle 0.0.1

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: adb9cdd2b8492bed6cdb7e86b34fe3af60f42267
4
+ data.tar.gz: 59c47a4619e0ca90d2014d3fc0ffd66d32e3c330
5
+ SHA512:
6
+ metadata.gz: 239e51424133bf7838a5920de34b49897b3b844e52fe37fb59b4f7f0a562ecc178ae80bfda0c3f2d2fb896275cc75040114f85d8599767f32cda9c179cf62634
7
+ data.tar.gz: 79b6477356c70df0ceda1e05c87b7b8883b9c4ca6fc1c8c2f65c9dc91b711d18e084c7246030db477a84a6295807b3e6fed3971454d86d3041c46ae0beb3b4ea
@@ -0,0 +1,3 @@
1
+ require_relative './game.rb'
2
+
3
+ Game.new.run
data/lib/board.rb ADDED
@@ -0,0 +1,189 @@
1
+ require_relative 'cell'
2
+ require_relative 'ship'
3
+
4
+ class BoardSizeError < RuntimeError; end
5
+ class OutOfBoardError < RuntimeError; end
6
+ class ShipSizeError < RuntimeError; end
7
+ class NotRoomError < RuntimeError; end
8
+
9
+ # class Board describes game board
10
+ class Board
11
+ include Direction
12
+ include State
13
+
14
+ def initialize(size_x = 10, size_y = 10, name = '')
15
+ unless (1..20).include?(size_x) && (1..20).include?(size_y)
16
+ fail BoardSizeError, 'size should be in (1..20) range'
17
+ end
18
+
19
+ @size_x, @size_y, @player_name = size_x, size_y, name
20
+
21
+ @cells, @ships = [], []
22
+
23
+ @alphabet = ('A'..'Z').map { |a| a }
24
+
25
+ 0.upto(size_x - 1) do |x|
26
+ @cells[x] = []
27
+ 0.upto(size_y - 1) { |y| @cells[x][y] = Cell.new(x, y) }
28
+ end
29
+ end
30
+
31
+ def cell(col, row)
32
+ @cells[col][row]
33
+ end
34
+
35
+ def put_ship(head_x, head_y, size, dir)
36
+ head_x, head_y = x_y_normalize head_x, head_y
37
+
38
+ unless (1..5).include? size
39
+ fail ShipSizeError, 'Ship size should be in range (1..5)'
40
+ end
41
+
42
+ # it makes list of cells which will be occupied by ship
43
+ # and checks for board border crossing
44
+ cell_list = []
45
+ x = head_x
46
+ y = head_y
47
+ size.times do |i|
48
+ case dir
49
+ when NORTH
50
+ fail NotRoomError if y == 0 && i != size - 1
51
+ cell_list[i] = [x, y]
52
+ y -= 1
53
+ when EAST
54
+ fail NotRoomError if x == 0 && i != size - 1
55
+ cell_list[i] = [x, y]
56
+ x -= 1
57
+ when SOUTH
58
+ fail NotRoomError if y == @size_y - 1 && i != size - 1
59
+ cell_list[i] = [x, y]
60
+ y += 1
61
+ when WEST
62
+ fail NotRoomError if x == @size_x - 1 && i != size - 1
63
+ cell_list[i] = [x, y]
64
+ x += 1
65
+ end
66
+ end
67
+
68
+ # it checks if the nearest cells have already been occupied
69
+ # algorithm isn't optimal because some cells are being checked many times
70
+ cell_list.each do |x, y|
71
+ fail NotRoomError, 'Other ship is here' if cell(x, y).state != EMPTY
72
+ if x != 0
73
+ if (cell(x - 1, y).state != EMPTY) ||
74
+ (y != 0 && cell(x - 1, y - 1).state != EMPTY) ||
75
+ (y != @size_y - 1 && cell(x - 1, y + 1).state != EMPTY)
76
+ fail NotRoomError, "Other ship is near [#{x}:#{y}]"
77
+ end
78
+ end
79
+ if (y != 0 && cell(x, y - 1).state != EMPTY) ||
80
+ (y != @size_y - 1 && cell(x, y + 1).state != EMPTY)
81
+ fail NotRoomError, "Other ship is near [#{x}:#{y}]"
82
+ end
83
+ if x != @size_x - 1
84
+ if (cell(x + 1, y).state != EMPTY) ||
85
+ (y != 0 && cell(x + 1, y - 1).state != EMPTY) ||
86
+ (y != @size_y - 1 && cell(x + 1, y + 1).state != EMPTY)
87
+ fail NotRoomError, 'Other ship is near [#{x}:#{y}]'
88
+ end
89
+ end
90
+ end
91
+
92
+ @ships << Ship.new(cell_list.map { |x, y| cell x, y })
93
+ end
94
+
95
+ def shoot(x, y)
96
+ # there is a posibility of using letters as X coordinate
97
+ x, y = x_y_normalize x, y
98
+ c = cell(x, y)
99
+ case c.state
100
+ when EMPTY
101
+ c.state = MISSED
102
+ return 'MISSED'
103
+ when SHIP
104
+ ship = @ships.find { |sh| sh.include? c }
105
+ fail FatalError, 'Mismatch between cell and ship tables' if ship.nil?
106
+ c. state = BROKEN
107
+ ship.alive? ? (return 'INJURED') : (return 'KILLED')
108
+ when MISSED, BROKEN
109
+ return 'ALREADY SHOT'
110
+ end
111
+ end
112
+
113
+ def is_ship_alive?
114
+ @ships.each { |sh| return true if sh.alive? }
115
+ false
116
+ end
117
+
118
+ def display(str)
119
+ fail TypeError, 'str should be Fixnum' unless str.class == Fixnum
120
+ offset = 2
121
+ res = ''
122
+ case str
123
+ when 0
124
+ res << @player_name.center(@size_x + 4)
125
+ res << ' '
126
+ when 1, 1 + @size_y + 1
127
+ res << ' ' * 2
128
+ (@size_x).times { |i| res << @alphabet[i] }
129
+ res << ' ' * 3
130
+ when (offset..@size_y - 1 + offset)
131
+ res << (@size_y - 1 - (str - offset)).to_s
132
+ res << '|'
133
+ @size_x.times do |x|
134
+ case @cells[x][@size_y - 1 - (str - offset)].state
135
+ when SHIP then res << 'O'
136
+ when BROKEN then res << 'X'
137
+ when MISSED then res << '*'
138
+ else res << ' '
139
+ end
140
+ end
141
+ res << '|'
142
+ res << (@size_y - 1 - (str - offset)).to_s
143
+ res << ' '
144
+ end
145
+ res
146
+ end
147
+
148
+ def generate(*list)
149
+ 5.downto(1).each do |size|
150
+ count = list[size - 1]
151
+ count.times do
152
+ 100.times do |try|
153
+ begin
154
+ head_x = rand(@size_x)
155
+ head_y = rand(@size_y)
156
+ dir = rand(4)
157
+ put_ship head_x, head_y, size, dir
158
+ break
159
+ rescue NotRoomError
160
+ raise NotRoomError, 'Too much ships' if try == 100 - 1
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ private
168
+
169
+ # it converts x to number if it is a letter
170
+ # then validates them
171
+ # returns x, y
172
+ def x_y_normalize(x, y)
173
+ x = @alphabet.index(x.upcase) if x.is_a?(String)
174
+ p = x_y_normalize_params
175
+ fail OutOfBoardError, "x should be in range #{p[:x_n]} or #{p[:x_l]}"\
176
+ unless (0..@size_x - 1).include? x
177
+ fail OutOfBoardError, "y should be in range #{p[:y]}"\
178
+ unless (0..@size_y - 1).include? y
179
+ [x, y]
180
+ end
181
+
182
+ def x_y_normalize_params
183
+ {
184
+ x_n: "(0..#{@size_x - 1})",
185
+ x_l: "(\"A\"..\"#{@alphabet[@size_x - 1]}\"))",
186
+ y: "(0..#{@size_x - 1})"
187
+ }
188
+ end
189
+ end
data/lib/cell.rb ADDED
@@ -0,0 +1,30 @@
1
+ # Describes cell states
2
+ module State
3
+ EMPTY = 0 # Nothing except sea water
4
+ SHIP = 1 # Occupied by ship
5
+ BROKEN = 2 # Occupied by broken ship
6
+ MISSED = 3 # There was a mishit here
7
+ end
8
+
9
+ # class Cell describes cell state
10
+ class Cell
11
+ include State
12
+ attr_accessor :state
13
+ def initialize(col, row, st = EMPTY)
14
+ @state = st
15
+ @col = col
16
+ @row = row
17
+ end
18
+
19
+ def state=(new_st)
20
+ if (State.constants.collect { |sym| State.const_get sym }).include? new_st
21
+ @state = new_st
22
+ else
23
+ fail TypeError
24
+ end
25
+ end
26
+
27
+ def to_s
28
+ "Cell [#{@col}:#{@row}] State = #{@state}"
29
+ end
30
+ end
data/lib/game.rb ADDED
@@ -0,0 +1,134 @@
1
+ require_relative 'board.rb'
2
+
3
+ ## Default setting constants
4
+ module Defaults
5
+ SIZE_X = 20
6
+ SIZE_Y = 10
7
+ PLAYER_1_NAME = 'Player 1'
8
+ PLAYER_2_NAME = 'Player 2'
9
+ PLAYER_NAME = 'Human'
10
+ COMPUTER_NAME = 'Computer'
11
+ COMPUTER_1_NAME = 'Computer 1'
12
+ COMPUTER_2_NAME = 'Computer 2'
13
+
14
+ SHIP_1_COUNT = 5
15
+ SHIP_2_COUNT = 4
16
+ SHIP_3_COUNT = 3
17
+ SHIP_4_COUNT = 2
18
+ SHIP_5_COUNT = 1
19
+
20
+ SHIPS_COUNT = [SHIP_1_COUNT, SHIP_2_COUNT, SHIP_3_COUNT,
21
+ SHIP_4_COUNT, SHIP_5_COUNT]
22
+ end
23
+
24
+ # Main game class
25
+ # Use Game.new.run
26
+ class Game
27
+ include Defaults
28
+ def initialize
29
+ @alphabet = ''
30
+ ('A'..'Z').each { |a| @alphabet << a }
31
+ end
32
+
33
+ def display(b1, b2)
34
+ system('clear')
35
+ (SIZE_Y + 5).times do |i|
36
+ puts b1.display(i) << ' ' * 3 << b2.display(i)
37
+ end
38
+ end
39
+
40
+ def comp_vs_comp
41
+ board = [Board.new(SIZE_X, SIZE_Y, COMPUTER_1_NAME),
42
+ Board.new(SIZE_X, SIZE_Y, COMPUTER_2_NAME)]
43
+
44
+ board[0].generate(*SHIPS_COUNT)
45
+ board[1].generate(*SHIPS_COUNT)
46
+
47
+ player_name = [COMPUTER_1_NAME, COMPUTER_2_NAME]
48
+
49
+ current_player = 0
50
+ result = ' '
51
+ auto = :OFF
52
+ delay = 1
53
+ loop do
54
+ if auto == :OFF
55
+ display(*board)
56
+ puts "Turn: #{player_name[current_player]}\n"
57
+ puts "Press:Enter to continue\tQ+Enter - exit\tA+Enter - demo mode"
58
+ inp = gets.chop
59
+ case inp
60
+ when 'Q', 'q' then return
61
+ when 'A', 'a' then auto = :ON
62
+ when 'AF', 'af'
63
+ auto = :ON
64
+ delay = 0.2
65
+ end
66
+ end
67
+
68
+ hit = loop do
69
+ hit = [rand(SIZE_X), rand(SIZE_Y)]
70
+ result = board[current_player - 1].shoot(*hit)
71
+ break hit if result != 'ALREADY SHOT'
72
+ end
73
+
74
+ display(*board)
75
+ puts "Turn: #{player_name[current_player]}"
76
+ puts "Hit: #{@alphabet[hit[0]]}#{hit[1]} - #{result}"
77
+
78
+ case result
79
+ when 'MISSED'
80
+ current_player == 0 ? (current_player = 1) : (current_player = 0)
81
+ when 'INJURED'
82
+ sleep(delay)
83
+ next
84
+ when 'KILLED'
85
+ if board[current_player - 1].is_ship_alive? == false
86
+ congratulation player_name[current_player]
87
+ return
88
+ end
89
+ sleep(delay)
90
+ end
91
+ sleep(delay)
92
+ end
93
+ end
94
+
95
+ def congratulation(name)
96
+ puts "\nCONGRATULATION!!!"
97
+ puts "#{name} has won"
98
+ puts 'GAME OVER'
99
+ sleep(2)
100
+ end
101
+
102
+ def human_vs_human
103
+ puts "\nUnder construction. Try later..."
104
+ end
105
+
106
+ def human_vs_comp
107
+ puts "\nUnder construction. Try later..."
108
+ end
109
+
110
+ def run
111
+ system('clear')
112
+
113
+ greeting
114
+ input = gets.chop
115
+
116
+ case input
117
+ when '1' then comp_vs_comp
118
+ when '2' then human_vs_comp
119
+ when '3' then human_vs_human
120
+ when 'q', 'Q' then
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ def greeting
127
+ puts "\n\n\n************** NAVAL BATTLE****************"
128
+ puts 'Choose game mode:'
129
+ puts '1 - Computer vs Computer'
130
+ puts '2 - Human vs Computer'
131
+ puts '3 - Human vs Human'
132
+ puts
133
+ end
134
+ end
data/lib/ship.rb ADDED
@@ -0,0 +1,27 @@
1
+ require_relative 'cell'
2
+
3
+ # Ship direction constants
4
+ module Direction
5
+ NORTH = 0
6
+ EAST = 1
7
+ SOUTH = 2
8
+ WEST = 3
9
+ end
10
+
11
+ # class Ship describes ship state
12
+ class Ship
13
+ include State
14
+ def initialize(cells)
15
+ @cells = cells
16
+ @cells.each { |c| c.state = SHIP }
17
+ end
18
+
19
+ def alive?
20
+ @cells.each { |c| return true if c.state == SHIP }
21
+ false
22
+ end
23
+
24
+ def include?(cell)
25
+ @cells.include? cell
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: alex_butirskiy_sea_battle
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alex Butirskiy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-12 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: "A simple computr game. \n There are three playing modes:\n\tHuman vs
14
+ Human\n\tHuman vs Computer\n\t Computer vs Computer"
15
+ email: butirskiy@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/alex_butirskiy_sea_battle.rb
21
+ - lib/board.rb
22
+ - lib/cell.rb
23
+ - lib/game.rb
24
+ - lib/ship.rb
25
+ homepage: http://rubygems.org/gems/alex_butirskiy_sea_battle
26
+ licenses:
27
+ - MIT
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 2.4.8
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: Sea battle game
49
+ test_files: []