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 +7 -0
- data/lib/alex_butirskiy_sea_battle.rb +3 -0
- data/lib/board.rb +189 -0
- data/lib/cell.rb +30 -0
- data/lib/game.rb +134 -0
- data/lib/ship.rb +27 -0
- metadata +49 -0
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
|
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: []
|