btetris_kp 0.0.1
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/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/bin/btetris_kp +5 -0
- data/btetris_kp.gemspec +25 -0
- data/lib/btetris_kp/btetris.rb +37 -0
- data/lib/btetris_kp/constants.rb +197 -0
- data/lib/btetris_kp/core/board.rb +223 -0
- data/lib/btetris_kp/core/piece.rb +79 -0
- data/lib/btetris_kp/game.rb +118 -0
- data/lib/btetris_kp/gui/menuitem.rb +44 -0
- data/lib/btetris_kp/gui/textfield.rb +66 -0
- data/lib/btetris_kp/menu.rb +83 -0
- data/lib/btetris_kp/netgame.rb +152 -0
- data/lib/btetris_kp/netjoin.rb +115 -0
- data/lib/btetris_kp/netsetup.rb +79 -0
- data/lib/btetris_kp/version.rb +3 -0
- data/lib/btetris_kp.rb +5 -0
- data/media/drop.ogg +0 -0
- data/media/pop.ogg +0 -0
- data/media/rotate.ogg +0 -0
- data/media/title.png +0 -0
- data/spec/btetris_spec.rb +15 -0
- data/spec/core/board_spec.rb +94 -0
- data/spec/core/piece_spec.rb +114 -0
- data/spec/game_spec.rb +29 -0
- data/spec/gui/menuitem_spec.rb +39 -0
- data/spec/gui/textfield_spec.rb +35 -0
- data/spec/menu_spec.rb +24 -0
- data/spec/netgame_spec.rb +75 -0
- data/spec/netjoin_spec.rb +15 -0
- data/spec/netsetup_spec.rb +16 -0
- data/spec/spec_helper.rb +5 -0
- metadata +147 -0
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'btetris_kp/menu'
|
2
|
+
require 'btetris_kp/core/board'
|
3
|
+
require 'btetris_kp/constants'
|
4
|
+
|
5
|
+
module BTetrisKp
|
6
|
+
# class representing game window state
|
7
|
+
class GameState
|
8
|
+
attr_reader :game_over, :paused
|
9
|
+
attr_accessor :rows_cleared
|
10
|
+
|
11
|
+
def initialize(window, x, y)
|
12
|
+
@window = window
|
13
|
+
@font = Gosu::Font.new(@window, Gosu.default_font_name, 80)
|
14
|
+
@board = Board.new(@window, x, y)
|
15
|
+
@paused, @game_over = false, false
|
16
|
+
# press time, set 0 when left, right or down key is pressed
|
17
|
+
# used for smoother controls in update method
|
18
|
+
@counter, @press_time, @rows_cleared = 0, 0, 0
|
19
|
+
initialize_sounds
|
20
|
+
end
|
21
|
+
|
22
|
+
# initializes ingame sounds
|
23
|
+
def initialize_sounds
|
24
|
+
@drop_snd = Gosu::Sample.new(@window, Const::PATH_SND_DROP)
|
25
|
+
@clear_snd = Gosu::Sample.new(@window, Const::PATH_SND_POP)
|
26
|
+
@rotate_snd = Gosu::Sample.new(@window, Const::PATH_SND_ROTATE)
|
27
|
+
end
|
28
|
+
|
29
|
+
# inserts garbage in board depending on count of cleared rows
|
30
|
+
def insert_garbage!(cnt)
|
31
|
+
@board.insert_garbage!(cnt)
|
32
|
+
end
|
33
|
+
|
34
|
+
# returns game board in a string (current piece included)
|
35
|
+
def get_board_s
|
36
|
+
@board.get_board_s
|
37
|
+
end
|
38
|
+
|
39
|
+
# switches pause boolean
|
40
|
+
def pause!
|
41
|
+
@paused = !@paused
|
42
|
+
end
|
43
|
+
|
44
|
+
# updates game
|
45
|
+
def update
|
46
|
+
unless @paused || @game_over
|
47
|
+
@press_time += 1
|
48
|
+
@counter += 1
|
49
|
+
|
50
|
+
if @press_time % Const::DROP_SPEED == 0
|
51
|
+
# check if KbDown is pressed, move piece down in faster interval
|
52
|
+
@board.piece_down! if @window.button_down?(Gosu::KbDown)
|
53
|
+
end
|
54
|
+
if @press_time % Const::TURN_SPEED == 0
|
55
|
+
# check if KbLeft is pressed, move piece left in faster interval
|
56
|
+
@board.piece_left! if @window.button_down?(Gosu::KbLeft)
|
57
|
+
# check if KbRight is pressed, move piece right in faster interval
|
58
|
+
@board.piece_right! if @window.button_down?(Gosu::KbRight)
|
59
|
+
end
|
60
|
+
|
61
|
+
# checks if it's time for next step in game
|
62
|
+
if @counter % Const::GAME_SPEED == 0
|
63
|
+
@rows_cleared = @board.next_step!
|
64
|
+
@clear_snd.play if @rows_cleared > 0
|
65
|
+
# checks if it's game over
|
66
|
+
@game_over = @board.game_over?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# draws board and texts if paused or game over
|
72
|
+
def draw
|
73
|
+
@board.draw
|
74
|
+
x = @window.width / 2
|
75
|
+
y = @window.height / 2 - 30
|
76
|
+
@font.draw(Const::PAUSE_CAPTION, x - 130, y, 0) if @paused
|
77
|
+
@font.draw(Const::GAME_OVER_CAPTION, x - 170, y, 0) if @game_over
|
78
|
+
end
|
79
|
+
|
80
|
+
# button handler
|
81
|
+
def button_down(id)
|
82
|
+
if @game_over || @paused
|
83
|
+
pause! if @paused && id == Gosu::KbP
|
84
|
+
if @game_over && id == Gosu::KbEscape
|
85
|
+
@window.state = MenuState.new(@window)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
# handling specific events
|
89
|
+
case id
|
90
|
+
when Gosu::KbEscape
|
91
|
+
@window.state = MenuState.new(@window)
|
92
|
+
when Gosu::KbP
|
93
|
+
pause!
|
94
|
+
when Gosu::KbLeft
|
95
|
+
@board.piece_left!
|
96
|
+
@press_time = 0
|
97
|
+
when Gosu::KbRight
|
98
|
+
@board.piece_right!
|
99
|
+
@press_time = 0
|
100
|
+
when Gosu::KbDown
|
101
|
+
@board.piece_down!
|
102
|
+
@press_time = 0
|
103
|
+
when Gosu::KbUp
|
104
|
+
@rotate_snd.play if @board.piece_rotate!
|
105
|
+
when Gosu::KbSpace
|
106
|
+
@board.piece_drop!
|
107
|
+
@drop_snd.play
|
108
|
+
@counter = Const::GAME_SPEED - 10
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# hides default system cursor
|
114
|
+
def needs_cursor?
|
115
|
+
true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'btetris_kp/constants'
|
2
|
+
|
3
|
+
module BTetrisKp
|
4
|
+
# class representing menu item
|
5
|
+
class MenuItem
|
6
|
+
def initialize(window, text, id, callback, font, x, y)
|
7
|
+
@window = window
|
8
|
+
@text = text
|
9
|
+
@callback = callback
|
10
|
+
@font = font
|
11
|
+
@color = Const::MENU_ITEM_CLR
|
12
|
+
@x = x
|
13
|
+
@y = y + id * (@font.height + 5)
|
14
|
+
end
|
15
|
+
|
16
|
+
# updates menu item, changes item color depending on mouse_over?
|
17
|
+
def update
|
18
|
+
if mouse_over?
|
19
|
+
@color = Const::MENU_ITEM_MO_CLR
|
20
|
+
else
|
21
|
+
@color = Const::MENU_ITEM_CLR
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# returns true if mouse is over menu item
|
26
|
+
def mouse_over?
|
27
|
+
mx = @window.mouse_x
|
28
|
+
my = @window.mouse_y
|
29
|
+
(mx >= @x && my >= @y) &&
|
30
|
+
(mx <= @x + @font.text_width(@text)) &&
|
31
|
+
(my <= @y + @font.height)
|
32
|
+
end
|
33
|
+
|
34
|
+
# returns true menu item is clicked (click + mouse_over)
|
35
|
+
def clicked
|
36
|
+
@callback.call if mouse_over?
|
37
|
+
end
|
38
|
+
|
39
|
+
# draws menuitem on window (gosu)
|
40
|
+
def draw
|
41
|
+
@font.draw(@text, @x, @y, 0, 1, 1, @color)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'gosu'
|
2
|
+
require 'btetris_kp/constants'
|
3
|
+
|
4
|
+
module BTetrisKp
|
5
|
+
# class representing input box for IP adress / port / text
|
6
|
+
class TextField < Gosu::TextInput
|
7
|
+
attr_reader :x, :y
|
8
|
+
|
9
|
+
def initialize(window, font, x, y, text, filter, width, maxlen)
|
10
|
+
super()
|
11
|
+
@window = window
|
12
|
+
@font = font
|
13
|
+
@x = x
|
14
|
+
@y = y
|
15
|
+
@width = width
|
16
|
+
@filter = filter
|
17
|
+
@maxlen = maxlen
|
18
|
+
self.text = text
|
19
|
+
end
|
20
|
+
|
21
|
+
# filters text of characters specified in regexp
|
22
|
+
def filter(text)
|
23
|
+
text.upcase.gsub(@filter, '')
|
24
|
+
end
|
25
|
+
|
26
|
+
# updates textfield, limits length of text to @maxlen
|
27
|
+
def update
|
28
|
+
self.text = text.slice(0...@maxlen) if text.size > @maxlen
|
29
|
+
end
|
30
|
+
|
31
|
+
# draws textfield on window (gosu)
|
32
|
+
def draw
|
33
|
+
@window.draw_line(@x - Const::BORDER_GAP, @y + @font.height, Const::CARET_CLR,
|
34
|
+
@x + @width + 2 * Const::BORDER_GAP, @y + @font.height, Const::CARET_CLR)
|
35
|
+
unless text.nil?
|
36
|
+
# draws the caret if textfield is selected
|
37
|
+
pos_x = @x + @font.text_width(text[0...caret_pos])
|
38
|
+
if @window.text_input == self
|
39
|
+
@window.draw_line(pos_x, @y, Const::CARET_CLR,
|
40
|
+
pos_x, @y + @font.height, Const::CARET_CLR, 0)
|
41
|
+
end
|
42
|
+
|
43
|
+
@font.draw(text, @x, @y, 0)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# returns true if mouse is over textfield
|
48
|
+
def mouse_over?(mouse_x, mouse_y)
|
49
|
+
mouse_x >= x - Const::BORDER_GAP &&
|
50
|
+
mouse_x <= x + @width + Const::BORDER_GAP &&
|
51
|
+
mouse_y >= y - Const::BORDER_GAP &&
|
52
|
+
mouse_y <= y + @font.height + Const::BORDER_GAP
|
53
|
+
end
|
54
|
+
|
55
|
+
# moves the caret to the position specified by mouse
|
56
|
+
def move_caret(mouse_x)
|
57
|
+
1.upto(text.length) do |i|
|
58
|
+
if mouse_x < x + @font.text_width(text[0...i])
|
59
|
+
self.caret_pos = self.selection_start = i - 1
|
60
|
+
return
|
61
|
+
end
|
62
|
+
end
|
63
|
+
self.caret_pos = self.selection_start = text.length
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'btetris_kp/game'
|
2
|
+
require 'btetris_kp/core/board'
|
3
|
+
require 'btetris_kp/gui/menuitem'
|
4
|
+
require 'btetris_kp/netsetup'
|
5
|
+
require 'btetris_kp/netjoin'
|
6
|
+
require 'btetris_kp/constants'
|
7
|
+
|
8
|
+
module BTetrisKp
|
9
|
+
# class representing main menu window state
|
10
|
+
class MenuState
|
11
|
+
def initialize(window)
|
12
|
+
@window = window
|
13
|
+
@font = Gosu::Font.new(@window, Gosu.default_font_name, 30)
|
14
|
+
initialize_title_image
|
15
|
+
generate_back_board
|
16
|
+
generate_menu
|
17
|
+
end
|
18
|
+
|
19
|
+
# loads title image and calculates position, size variables
|
20
|
+
def initialize_title_image
|
21
|
+
@title_image = Gosu::Image.new(@window, Const::PATH_IMAGE_TITLE, false)
|
22
|
+
@img_size_factor = (@window.width - 50.0) / @title_image.width
|
23
|
+
@img_x = (@window.width - @title_image.width * @img_size_factor) / 2
|
24
|
+
@img_y = 20
|
25
|
+
end
|
26
|
+
|
27
|
+
# generates background board
|
28
|
+
def generate_back_board
|
29
|
+
@board = Board.new(@window, @window.width / 3, 40)
|
30
|
+
nice_board = Array.new(Const::PNR_VER) { Array.new(Const::PNR_HOR, 0) }
|
31
|
+
nice_board.each_with_index do |row, x|
|
32
|
+
if x > Const::PNR_VER / 2
|
33
|
+
row.each_with_index do |val, y|
|
34
|
+
nice_board[x][y] = rand(Const::TILE_COLORS_NR + 1)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
@board.board = nice_board
|
39
|
+
end
|
40
|
+
|
41
|
+
# generates menu, menu items and callback procedures for menu items
|
42
|
+
def generate_menu
|
43
|
+
@items = []
|
44
|
+
@x = @window.width / 3 + 30
|
45
|
+
@y = @title_image.height
|
46
|
+
n_g = proc { @window.state = GameState.new(@window, @window.width / 3, 40) }
|
47
|
+
cr_n = proc { @window.state = NetSetupState.new(@window) }
|
48
|
+
j_n = proc { @window.state = NetJoinState.new(@window) }
|
49
|
+
exit = proc { @window.close }
|
50
|
+
@items << MenuItem.new(@window, Const::MENU_NEW, 0, n_g, @font, @x, @y)
|
51
|
+
@items << MenuItem.new(@window, Const::MENU_CREATE, 1, cr_n, @font, @x, @y)
|
52
|
+
@items << MenuItem.new(@window, Const::MENU_JOIN, 2, j_n, @font, @x, @y)
|
53
|
+
@items << MenuItem.new(@window, Const::MENU_QUIT, 3, exit, @font, @x, @y)
|
54
|
+
end
|
55
|
+
|
56
|
+
# updates menu
|
57
|
+
def update
|
58
|
+
@items.each { |i| i.update }
|
59
|
+
end
|
60
|
+
|
61
|
+
# draws menu
|
62
|
+
def draw
|
63
|
+
@board.draw
|
64
|
+
@items.each { |i| i.draw }
|
65
|
+
@title_image.draw(@img_x, @img_y, 1, @img_size_factor, @img_size_factor)
|
66
|
+
end
|
67
|
+
|
68
|
+
# button handler
|
69
|
+
def button_down(id)
|
70
|
+
case id
|
71
|
+
when Gosu::KbEscape
|
72
|
+
@window.close
|
73
|
+
when Gosu::MsLeft then
|
74
|
+
@items.each { |i| i.clicked }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# shows default system cursor
|
79
|
+
def needs_cursor?
|
80
|
+
true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'btetris_kp/menu'
|
2
|
+
require 'btetris_kp/core/board'
|
3
|
+
require 'btetris_kp/game'
|
4
|
+
require 'btetris_kp/constants'
|
5
|
+
|
6
|
+
module BTetrisKp
|
7
|
+
# class representing net game window state
|
8
|
+
# used for playin game over network (no matter if you are client or server)
|
9
|
+
class NetGameState
|
10
|
+
def initialize(window, socket)
|
11
|
+
@window = window
|
12
|
+
@socket = socket
|
13
|
+
@font = Gosu::Font.new(@window, Gosu.default_font_name, 80)
|
14
|
+
@game = GameState.new(@window, 10, 40)
|
15
|
+
@o_board = Board.new(@window, 2 * @window.width / 3 - 10, 40)
|
16
|
+
@winner = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
# sends message to socket
|
20
|
+
def send_msg(msg)
|
21
|
+
s = ''
|
22
|
+
case msg
|
23
|
+
when Const::MSG_PAUSE
|
24
|
+
s = "#{Const::MSG_PAUSE}"
|
25
|
+
when Const::MSG_GAME_OVER
|
26
|
+
s = "#{Const::MSG_GAME_OVER}"
|
27
|
+
when Const::MSG_BOARD
|
28
|
+
s = "#{Const::MSG_BOARD}:#{@game.get_board_s}"
|
29
|
+
when Const::MSG_GARBAGE
|
30
|
+
s = "#{Const::MSG_GARBAGE}:#{@game.rows_cleared}"
|
31
|
+
end
|
32
|
+
begin
|
33
|
+
@socket.sendmsg_nonblock(s)
|
34
|
+
rescue
|
35
|
+
# problem s pripojenim -- game over
|
36
|
+
@socket.close
|
37
|
+
@window.state = MenuState.new(@window)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# blocking recv for cnt of bytes
|
42
|
+
def recv_block(cnt)
|
43
|
+
block = nil
|
44
|
+
begin
|
45
|
+
block = @socket.recv(cnt, Socket::MSG_WAITALL)
|
46
|
+
rescue
|
47
|
+
# problem s pripojenim -- game over
|
48
|
+
@socket.close
|
49
|
+
@window.state = MenuState.new(@window)
|
50
|
+
end
|
51
|
+
block
|
52
|
+
end
|
53
|
+
|
54
|
+
# recieves and manages game updates
|
55
|
+
def recv_msg(id)
|
56
|
+
case id
|
57
|
+
when Const::MSG_PAUSE
|
58
|
+
@game.pause!
|
59
|
+
when Const::MSG_GAME_OVER
|
60
|
+
@winner = Const::GAME_WON
|
61
|
+
when Const::MSG_BOARD
|
62
|
+
recv_block(1)
|
63
|
+
board = recv_block(Const::PNR_HOR * Const::PNR_VER)
|
64
|
+
@o_board.from_s!(board) unless board.nil?
|
65
|
+
when Const::MSG_GARBAGE
|
66
|
+
recv_block(1)
|
67
|
+
garbage = recv_block(1).to_i
|
68
|
+
@game.insert_garbage!(garbage)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# checks if there is incomming message
|
73
|
+
# returns a string with message id, or GOT_NO_MESSAGE
|
74
|
+
def check_msg
|
75
|
+
begin
|
76
|
+
s = @socket.recv_nonblock(1)
|
77
|
+
return s
|
78
|
+
rescue Errno::EAGAIN
|
79
|
+
# zadna zprava, hrajeme dal
|
80
|
+
rescue
|
81
|
+
# problem s pripojenim -- game over
|
82
|
+
@socket.close
|
83
|
+
@window.state = MenuState.new(@window)
|
84
|
+
rescue
|
85
|
+
end
|
86
|
+
Const::GOT_NO_MESSAGE
|
87
|
+
end
|
88
|
+
|
89
|
+
# updates net game state
|
90
|
+
def update
|
91
|
+
if @winner == Const::GAME_ON
|
92
|
+
until (m = check_msg) == Const::GOT_NO_MESSAGE
|
93
|
+
recv_msg(m)
|
94
|
+
end
|
95
|
+
unless @game.paused
|
96
|
+
@game.update
|
97
|
+
send_msg(Const::MSG_BOARD)
|
98
|
+
if @game.game_over
|
99
|
+
send_msg(Const::MSG_GAME_OVER)
|
100
|
+
@winner = Const::GAME_LOST
|
101
|
+
end
|
102
|
+
if @game.rows_cleared > 0
|
103
|
+
send_msg(Const::MSG_GARBAGE)
|
104
|
+
@game.rows_cleared = 0
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# draws net game window (contains normal game + opponents board)
|
111
|
+
def draw
|
112
|
+
if @winner == Const::GAME_ON
|
113
|
+
@game.draw
|
114
|
+
@o_board.draw
|
115
|
+
elsif @winner == Const::GAME_LOST
|
116
|
+
@font.draw(Const::GAME_LOST_CAPTION,
|
117
|
+
@window.width / 2 - 170, @window.height / 2 - 30, 0)
|
118
|
+
else
|
119
|
+
@font.draw(Const::GAME_WON_CAPTION,
|
120
|
+
@window.width / 2 - 170, @window.height / 2 - 30, 0)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# button handler
|
125
|
+
def button_down(id)
|
126
|
+
if @winner == Const::GAME_ON
|
127
|
+
case id
|
128
|
+
when Gosu::KbP
|
129
|
+
send_msg(Const::MSG_PAUSE)
|
130
|
+
@game.pause!
|
131
|
+
when Gosu::KbEscape
|
132
|
+
send_msg(Const::MSG_GAME_OVER)
|
133
|
+
sleep(0.2)
|
134
|
+
@socket.close
|
135
|
+
@window.state = MenuState.new(@window)
|
136
|
+
else
|
137
|
+
@game.button_down(id)
|
138
|
+
end
|
139
|
+
else
|
140
|
+
if id == Gosu::KbEscape
|
141
|
+
@socket.close
|
142
|
+
@window.state = MenuState.new(@window)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# hides default system cursor
|
148
|
+
def needs_cursor?
|
149
|
+
true
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'btetris_kp/netgame'
|
3
|
+
require 'btetris_kp/gui/textfield'
|
4
|
+
require 'btetris_kp/constants'
|
5
|
+
|
6
|
+
module BTetrisKp
|
7
|
+
# class representing a window for joining a game over network (CLIENT SIDE)
|
8
|
+
class NetJoinState
|
9
|
+
def initialize(window)
|
10
|
+
@window = window
|
11
|
+
@font = Gosu::Font.new(@window, Gosu.default_font_name, 40)
|
12
|
+
@connecting = false
|
13
|
+
initialize_title_image
|
14
|
+
initialize_textfields
|
15
|
+
@socket = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# loads title image and calculates position, size variables
|
19
|
+
def initialize_title_image
|
20
|
+
@title_image = Gosu::Image.new(@window, Const::PATH_IMAGE_TITLE, false)
|
21
|
+
@img_size_factor = (@window.width - 50.0) / @title_image.width
|
22
|
+
@img_x = (@window.width - @title_image.width * @img_size_factor) / 2
|
23
|
+
@img_y = 20
|
24
|
+
end
|
25
|
+
|
26
|
+
# initializes text fields for reading ip and port
|
27
|
+
# initializes positions for texts and textfields
|
28
|
+
def initialize_textfields
|
29
|
+
@text_x = @window.width / 3 + 10
|
30
|
+
@text_y = @window.height / 3
|
31
|
+
@ip_x = @text_x - @font.text_width(Const::IP_CAPTION) - 15
|
32
|
+
@port_x = @text_x - @font.text_width(Const::PORT_CAPTION) - 15
|
33
|
+
@text_fields = []
|
34
|
+
@text_fields << TextField.new(@window, @font, @text_x,
|
35
|
+
@text_y, Const::DEF_IP, /[^0-9.]/,
|
36
|
+
@font.text_width('000.000.000.000'), 15)
|
37
|
+
@text_fields << TextField.new(@window, @font, @text_x,
|
38
|
+
@text_y + @font.height + 10,
|
39
|
+
Const::DEF_PORT, /[^0-9]/,
|
40
|
+
@font.text_width('00000'), 5)
|
41
|
+
end
|
42
|
+
|
43
|
+
# tries to connect to server
|
44
|
+
def try_connect
|
45
|
+
begin
|
46
|
+
@socket = TCPSocket.new(@text_fields[0].text, @text_fields[1].text)
|
47
|
+
@socket.sendmsg_nonblock(Const::MSG_WELCOME)
|
48
|
+
msg = @socket.recv(1)
|
49
|
+
unless msg == Const::MSG_WELCOME
|
50
|
+
@socket.close
|
51
|
+
@socket = nil
|
52
|
+
end
|
53
|
+
rescue
|
54
|
+
# error while connecting
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# updates window state, tries to connect to server, starts game when ready
|
59
|
+
def update
|
60
|
+
@text_fields.each { |t| t.update }
|
61
|
+
if @connecting
|
62
|
+
if @socket.nil?
|
63
|
+
try_connect
|
64
|
+
else
|
65
|
+
# socket created and connected, start net game
|
66
|
+
# windows text_input needs to be set on nil
|
67
|
+
# otherwise gosu wont register some keys
|
68
|
+
@window.text_input = nil
|
69
|
+
@window.state = NetGameState.new(@window, @socket)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# draws net join window
|
75
|
+
def draw
|
76
|
+
@text_fields.each { |t| t.draw }
|
77
|
+
@font.draw(Const::IP_CAPTION, @ip_x,
|
78
|
+
@text_y, 0)
|
79
|
+
@font.draw(Const::PORT_CAPTION, @port_x,
|
80
|
+
@text_y + @font.height + 10, 0)
|
81
|
+
@font.draw(Const::CONNECTING, @text_x,
|
82
|
+
@text_y + 3 * (@font.height + 10), 0) if @connecting
|
83
|
+
@title_image.draw(@img_x, @img_y, 1, @img_size_factor, @img_size_factor)
|
84
|
+
end
|
85
|
+
|
86
|
+
# button handler
|
87
|
+
def button_down(id)
|
88
|
+
case id
|
89
|
+
when Gosu::KbEscape
|
90
|
+
if @connecting
|
91
|
+
@connecting = false
|
92
|
+
else
|
93
|
+
# windows text_input needs to be set on nil
|
94
|
+
# otherwise gosu wont register some keys
|
95
|
+
@window.text_input = nil
|
96
|
+
@window.state = MenuState.new(@window)
|
97
|
+
end
|
98
|
+
when Gosu::KbReturn
|
99
|
+
@connecting = true
|
100
|
+
when Gosu::MsLeft
|
101
|
+
@window.text_input = @text_fields.find do |tf|
|
102
|
+
tf.mouse_over?(@window.mouse_x, @window.mouse_y)
|
103
|
+
end
|
104
|
+
unless @window.text_input.nil?
|
105
|
+
@window.text_input.move_caret(@window.mouse_x)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# shows default system cursor
|
111
|
+
def needs_cursor?
|
112
|
+
true
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'btetris_kp/menu'
|
3
|
+
require 'btetris_kp/netgame'
|
4
|
+
require 'btetris_kp/constants'
|
5
|
+
|
6
|
+
module BTetrisKp
|
7
|
+
# class representing a window for creating a game over network (SERVER SIDE)
|
8
|
+
class NetSetupState
|
9
|
+
def initialize(window)
|
10
|
+
@window = window
|
11
|
+
@font = Gosu::Font.new(@window, Gosu.default_font_name, 40)
|
12
|
+
initialize_title_image
|
13
|
+
setup_server
|
14
|
+
@connected = false
|
15
|
+
end
|
16
|
+
|
17
|
+
# setups a server with random port
|
18
|
+
def setup_server
|
19
|
+
@port = 0
|
20
|
+
begin
|
21
|
+
@port = rand(40_000)
|
22
|
+
@server = TCPServer.new(@port)
|
23
|
+
rescue
|
24
|
+
retry
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# loads title image and calculates position, size variables
|
29
|
+
def initialize_title_image
|
30
|
+
@title_image = Gosu::Image.new(@window, Const::PATH_IMAGE_TITLE, false)
|
31
|
+
@img_size_factor = (@window.width - 50.0) / @title_image.width
|
32
|
+
@img_x = (@window.width - @title_image.width * @img_size_factor) / 2
|
33
|
+
@img_y = 20
|
34
|
+
end
|
35
|
+
|
36
|
+
# tries to accept client connection, sets @connected true when accepted
|
37
|
+
def try_accept
|
38
|
+
begin
|
39
|
+
# tries to accept client connection
|
40
|
+
@socket = @server.accept_nonblock
|
41
|
+
@socket.sendmsg_nonblock(Const::MSG_WELCOME)
|
42
|
+
# if welcome msg is recvd, start game, else close socket
|
43
|
+
msg = @socket.recv(1)
|
44
|
+
msg == Const::MSG_WELCOME ? @connected = true : @socket.close
|
45
|
+
rescue
|
46
|
+
# no one connected, try again next time
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# updates window state, checks for incomming connections
|
51
|
+
# starts game when ready
|
52
|
+
def update
|
53
|
+
if @connected
|
54
|
+
@window.state = NetGameState.new(@window, @socket)
|
55
|
+
else
|
56
|
+
try_accept
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# draws net setup window
|
61
|
+
def draw
|
62
|
+
@font.draw(Const::SERVER_WAIT, @window.width / 5,
|
63
|
+
@window.height / 2 - 60, 0)
|
64
|
+
@font.draw("#{Const::SERVER_PORT}#{@port}", @window.width / 3.5,
|
65
|
+
@window.height / 2, 0)
|
66
|
+
@title_image.draw(@img_x, @img_y, 1, @img_size_factor, @img_size_factor)
|
67
|
+
end
|
68
|
+
|
69
|
+
# button handler
|
70
|
+
def button_down(id)
|
71
|
+
@window.state = MenuState.new(@window) if id == Gosu::KbEscape
|
72
|
+
end
|
73
|
+
|
74
|
+
# shows default system cursor
|
75
|
+
def needs_cursor?
|
76
|
+
true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/btetris_kp.rb
ADDED
data/media/drop.ogg
ADDED
Binary file
|
data/media/pop.ogg
ADDED
Binary file
|
data/media/rotate.ogg
ADDED
Binary file
|
data/media/title.png
ADDED
Binary file
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'gosu'
|
5
|
+
require "btetris_kp/btetris"
|
6
|
+
|
7
|
+
module BTetrisKp
|
8
|
+
describe BTetrisWindow do
|
9
|
+
|
10
|
+
it 'can initialize BTetrisWindow' do
|
11
|
+
@bt = BTetrisWindow.new
|
12
|
+
@bt.should(be_an_instance_of BTetrisWindow)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|