doom 0.2.0 → 0.3.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.
data/lib/doom/game.rb DELETED
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Doom
4
- # Game is the main class that ties everything together
5
- class Game
6
- attr_reader :window, :loaders, :renderers
7
-
8
- # Initialize a new Game with the given WAD file
9
- # @param wad_file [String] the path to the WAD file
10
- # @param width [Integer] the width of the window
11
- # @param height [Integer] the height of the window
12
- # @param fullscreen [Boolean] whether the window should be fullscreen
13
- def initialize(wad_file, width = 640, height = 480, fullscreen = false)
14
- @wad_file = wad_file
15
- @width = width
16
- @height = height
17
- @fullscreen = fullscreen
18
- @loaders = {}
19
- @renderers = {}
20
- @current_map = nil
21
- @player_data = {
22
- health: 100,
23
- armor: 0,
24
- ammo: 50,
25
- weapon: "Pistol"
26
- }
27
- end
28
-
29
- # Start the game
30
- # @param map_name [String] the name of the map to start on
31
- def start(map_name = nil)
32
- load_wad
33
- setup_window
34
- setup_renderers
35
- load_map(map_name) if map_name
36
- @window.show
37
- end
38
-
39
- # Load the WAD file
40
- def load_wad
41
- @loaders = Doom.load_wad(@wad_file)
42
- end
43
-
44
- # Setup the window
45
- def setup_window
46
- @window = Window.new(@width, @height, @fullscreen)
47
- @window.game = self
48
- end
49
-
50
- # Setup the renderers
51
- def setup_renderers
52
- @renderers[:bsp] = BSPRenderer.new(@window, @loaders[:maps])
53
- @renderers[:texture] = TextureMapper.new(@loaders[:textures])
54
- @renderers[:sprite] = SpriteRenderer.new(@loaders[:sprites])
55
- @renderers[:hud] = HUD.new(@window)
56
- end
57
-
58
- # Load a specific map
59
- # @param map_name [String] the name of the map to load
60
- def load_map(map_name)
61
- @current_map = map_name
62
- @renderers[:bsp].load_map(map_name)
63
- end
64
-
65
- # Update the game state
66
- def update
67
- # Will be implemented in Phase 3
68
- end
69
-
70
- # Draw the game
71
- def draw
72
- @renderers[:bsp].render if @current_map
73
- @renderers[:hud].render(@player_data)
74
- end
75
-
76
- # Handle button press events
77
- def button_down(id)
78
- case id
79
- when Gosu::KB_ESCAPE
80
- @window.close
81
- end
82
- end
83
- end
84
- end
data/lib/doom/hud.rb DELETED
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Doom
4
- # HUD is responsible for rendering the Heads-Up Display
5
- class HUD
6
- attr_reader :window
7
-
8
- # Initialize a new HUD with the given window
9
- # @param window [Doom::Window] the window to render to
10
- def initialize(window)
11
- @window = window
12
- @font = Gosu::Font.new(20)
13
- end
14
-
15
- # Render the HUD
16
- # @param player_data [Hash] the player data to display
17
- def render(player_data = {})
18
- # In a real implementation, this would render:
19
- # - Health
20
- # - Armor
21
- # - Ammo
22
- # - Weapons
23
- # - Face
24
- # - Keys
25
- # - etc.
26
-
27
- # For now, we'll just render some placeholder text
28
- health = player_data[:health] || 100
29
- armor = player_data[:armor] || 0
30
- ammo = player_data[:ammo] || 50
31
- weapon = player_data[:weapon] || "Pistol"
32
-
33
- # Draw the HUD background
34
- draw_background
35
-
36
- # Draw the HUD elements
37
- draw_health(health)
38
- draw_armor(armor)
39
- draw_ammo(ammo)
40
- draw_weapon(weapon)
41
- end
42
-
43
- private
44
-
45
- # Draw the HUD background
46
- def draw_background
47
- color = Gosu::Color.new(128, 0, 0, 0) # Semi-transparent black
48
- @window.draw_quad(
49
- 0, @window.height - 80, color,
50
- @window.width, @window.height - 80, color,
51
- @window.width, @window.height, color,
52
- 0, @window.height, color
53
- )
54
- end
55
-
56
- # Draw the health display
57
- # @param health [Integer] the player's health
58
- def draw_health(health)
59
- @font.draw_text("Health: #{health}", 20, @window.height - 70, 0, 1, 1, Gosu::Color::RED)
60
- end
61
-
62
- # Draw the armor display
63
- # @param armor [Integer] the player's armor
64
- def draw_armor(armor)
65
- @font.draw_text("Armor: #{armor}", 20, @window.height - 50, 0, 1, 1, Gosu::Color::GREEN)
66
- end
67
-
68
- # Draw the ammo display
69
- # @param ammo [Integer] the player's ammo
70
- def draw_ammo(ammo)
71
- @font.draw_text("Ammo: #{ammo}", @window.width - 120, @window.height - 70, 0, 1, 1, Gosu::Color::YELLOW)
72
- end
73
-
74
- # Draw the weapon display
75
- # @param weapon [String] the player's current weapon
76
- def draw_weapon(weapon)
77
- @font.draw_text("Weapon: #{weapon}", @window.width - 120, @window.height - 50, 0, 1, 1, Gosu::Color::WHITE)
78
- end
79
- end
80
- end
@@ -1,255 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Doom
4
- # MapLoader is responsible for loading and parsing map data from a WAD file
5
- class MapLoader
6
- # Map lumps that make up a complete map
7
- MAP_LUMPS = %w[THINGS LINEDEFS SIDEDEFS VERTEXES SEGS SSECTORS NODES SECTORS REJECT BLOCKMAP].freeze
8
-
9
- attr_reader :maps, :current_map
10
-
11
- # Initialize a new MapLoader with the given WADLoader
12
- # @param wad_loader [WADLoader] the WAD loader to use
13
- def initialize(wad_loader)
14
- @wad_loader = wad_loader
15
- @maps = find_maps
16
- @current_map = nil
17
- end
18
-
19
- # Find all maps in the WAD file
20
- # @return [Array<String>] array of map names
21
- def find_maps
22
- map_names = []
23
-
24
- # In Doom, maps are identified by their name (e.g., E1M1, MAP01)
25
- # followed by the map lumps (THINGS, LINEDEFS, etc.)
26
- @wad_loader.directory.each_with_index do |entry, index|
27
- # Check if this entry is followed by map lumps
28
- if is_map_start?(index)
29
- map_names << entry[:name]
30
- end
31
- end
32
-
33
- map_names
34
- end
35
-
36
- # Check if the entry at the given index is the start of a map
37
- # @param index [Integer] the index in the directory
38
- # @return [Boolean] true if the entry is the start of a map
39
- def is_map_start?(index)
40
- # Check if there are enough entries left
41
- return false if index + MAP_LUMPS.size >= @wad_loader.directory.size
42
-
43
- # Check if the next entries match the map lumps
44
- MAP_LUMPS.each_with_index do |lump_name, i|
45
- return false unless @wad_loader.directory[index + i + 1][:name] == lump_name
46
- end
47
-
48
- true
49
- end
50
-
51
- # Load a specific map
52
- # @param map_name [String] the name of the map to load
53
- # @return [Hash, nil] the map data or nil if not found
54
- def load_map(map_name)
55
- return nil unless @maps.include?(map_name)
56
-
57
- @current_map = {
58
- name: map_name,
59
- things: load_things(map_name),
60
- linedefs: load_linedefs(map_name),
61
- sidedefs: load_sidedefs(map_name),
62
- vertexes: load_vertexes(map_name),
63
- sectors: load_sectors(map_name)
64
- # Other map components can be added as needed
65
- }
66
- end
67
-
68
- # Load the THINGS lump for a map
69
- # @param map_name [String] the name of the map
70
- # @return [Array<Hash>] array of things
71
- def load_things(map_name)
72
- lump_data = get_map_lump(map_name, "THINGS")
73
- return [] unless lump_data
74
-
75
- things = []
76
- offset = 0
77
-
78
- # Each thing is 10 bytes
79
- while offset < lump_data.size
80
- thing_data = lump_data[offset..offset + 9]
81
- x_pos = thing_data[0..1].unpack("s<")[0]
82
- y_pos = thing_data[2..3].unpack("s<")[0]
83
- angle = thing_data[4..5].unpack("s<")[0]
84
- type = thing_data[6..7].unpack("s<")[0]
85
- flags = thing_data[8..9].unpack("s<")[0]
86
-
87
- things << {
88
- x: x_pos,
89
- y: y_pos,
90
- angle: angle,
91
- type: type,
92
- flags: flags
93
- }
94
-
95
- offset += 10
96
- end
97
-
98
- things
99
- end
100
-
101
- # Load the LINEDEFS lump for a map
102
- # @param map_name [String] the name of the map
103
- # @return [Array<Hash>] array of linedefs
104
- def load_linedefs(map_name)
105
- lump_data = get_map_lump(map_name, "LINEDEFS")
106
- return [] unless lump_data
107
-
108
- linedefs = []
109
- offset = 0
110
-
111
- # Each linedef is 14 bytes
112
- while offset < lump_data.size
113
- linedef_data = lump_data[offset..offset + 13]
114
- start_vertex = linedef_data[0..1].unpack("s<")[0]
115
- end_vertex = linedef_data[2..3].unpack("s<")[0]
116
- flags = linedef_data[4..5].unpack("s<")[0]
117
- special_type = linedef_data[6..7].unpack("s<")[0]
118
- sector_tag = linedef_data[8..9].unpack("s<")[0]
119
- right_sidedef = linedef_data[10..11].unpack("s<")[0]
120
- left_sidedef = linedef_data[12..13].unpack("s<")[0]
121
-
122
- linedefs << {
123
- start_vertex: start_vertex,
124
- end_vertex: end_vertex,
125
- flags: flags,
126
- special_type: special_type,
127
- sector_tag: sector_tag,
128
- right_sidedef: right_sidedef,
129
- left_sidedef: left_sidedef
130
- }
131
-
132
- offset += 14
133
- end
134
-
135
- linedefs
136
- end
137
-
138
- # Load the SIDEDEFS lump for a map
139
- # @param map_name [String] the name of the map
140
- # @return [Array<Hash>] array of sidedefs
141
- def load_sidedefs(map_name)
142
- lump_data = get_map_lump(map_name, "SIDEDEFS")
143
- return [] unless lump_data
144
-
145
- sidedefs = []
146
- offset = 0
147
-
148
- # Each sidedef is 30 bytes
149
- while offset < lump_data.size
150
- sidedef_data = lump_data[offset..offset + 29]
151
- x_offset = sidedef_data[0..1].unpack("s<")[0]
152
- y_offset = sidedef_data[2..3].unpack("s<")[0]
153
- upper_texture = sidedef_data[4..11].unpack("Z*")[0]
154
- lower_texture = sidedef_data[12..19].unpack("Z*")[0]
155
- middle_texture = sidedef_data[20..27].unpack("Z*")[0]
156
- sector = sidedef_data[28..29].unpack("s<")[0]
157
-
158
- sidedefs << {
159
- x_offset: x_offset,
160
- y_offset: y_offset,
161
- upper_texture: upper_texture,
162
- lower_texture: lower_texture,
163
- middle_texture: middle_texture,
164
- sector: sector
165
- }
166
-
167
- offset += 30
168
- end
169
-
170
- sidedefs
171
- end
172
-
173
- # Load the VERTEXES lump for a map
174
- # @param map_name [String] the name of the map
175
- # @return [Array<Hash>] array of vertexes
176
- def load_vertexes(map_name)
177
- lump_data = get_map_lump(map_name, "VERTEXES")
178
- return [] unless lump_data
179
-
180
- vertexes = []
181
- offset = 0
182
-
183
- # Each vertex is 4 bytes
184
- while offset < lump_data.size
185
- vertex_data = lump_data[offset..offset + 3]
186
- x_pos = vertex_data[0..1].unpack("s<")[0]
187
- y_pos = vertex_data[2..3].unpack("s<")[0]
188
-
189
- vertexes << {
190
- x: x_pos,
191
- y: y_pos
192
- }
193
-
194
- offset += 4
195
- end
196
-
197
- vertexes
198
- end
199
-
200
- # Load the SECTORS lump for a map
201
- # @param map_name [String] the name of the map
202
- # @return [Array<Hash>] array of sectors
203
- def load_sectors(map_name)
204
- lump_data = get_map_lump(map_name, "SECTORS")
205
- return [] unless lump_data
206
-
207
- sectors = []
208
- offset = 0
209
-
210
- # Each sector is 26 bytes
211
- while offset < lump_data.size
212
- sector_data = lump_data[offset..offset + 25]
213
- floor_height = sector_data[0..1].unpack("s<")[0]
214
- ceiling_height = sector_data[2..3].unpack("s<")[0]
215
- floor_texture = sector_data[4..11].unpack("Z*")[0]
216
- ceiling_texture = sector_data[12..19].unpack("Z*")[0]
217
- light_level = sector_data[20..21].unpack("s<")[0]
218
- special_type = sector_data[22..23].unpack("s<")[0]
219
- tag = sector_data[24..25].unpack("s<")[0]
220
-
221
- sectors << {
222
- floor_height: floor_height,
223
- ceiling_height: ceiling_height,
224
- floor_texture: floor_texture,
225
- ceiling_texture: ceiling_texture,
226
- light_level: light_level,
227
- special_type: special_type,
228
- tag: tag
229
- }
230
-
231
- offset += 26
232
- end
233
-
234
- sectors
235
- end
236
-
237
- # Get a specific map lump
238
- # @param map_name [String] the name of the map
239
- # @param lump_name [String] the name of the lump
240
- # @return [String, nil] the lump data or nil if not found
241
- def get_map_lump(map_name, lump_name)
242
- map_index = @wad_loader.directory.find_index { |entry| entry[:name] == map_name }
243
- return nil unless map_index
244
-
245
- lump_index = MAP_LUMPS.find_index(lump_name)
246
- return nil unless lump_index
247
-
248
- # The lump is at map_index + lump_index + 1
249
- entry = @wad_loader.directory[map_index + lump_index + 1]
250
- return nil unless entry && entry[:name] == lump_name
251
-
252
- @wad_loader.get_lump(lump_name)
253
- end
254
- end
255
- end
data/lib/doom/renderer.rb DELETED
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Doom
4
- # Base class for all renderers
5
- class Renderer
6
- attr_reader :window
7
-
8
- # Initialize a new Renderer with the given window
9
- # @param window [Doom::Window] the window to render to
10
- def initialize(window)
11
- @window = window
12
- end
13
-
14
- # Render the current frame
15
- # This is an abstract method that should be overridden by subclasses
16
- def render
17
- raise NotImplementedError, "Subclasses must implement render"
18
- end
19
-
20
- # Clear the screen
21
- def clear
22
- # In Gosu, drawing is done directly to the window
23
- # This method is a placeholder for potential buffer clearing operations
24
- end
25
-
26
- # Flush the rendered frame to the screen
27
- def flush
28
- # In Gosu, drawing is done directly to the window
29
- # This method is a placeholder for potential buffer flushing operations
30
- end
31
- end
32
- end
@@ -1,88 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Doom
4
- # SpriteLoader is responsible for loading and parsing sprite data from a WAD file
5
- class SpriteLoader
6
- attr_reader :sprites
7
-
8
- # Initialize a new SpriteLoader with the given WADLoader
9
- # @param wad_loader [WADLoader] the WAD loader to use
10
- def initialize(wad_loader)
11
- @wad_loader = wad_loader
12
- @sprites = {}
13
- load_sprites
14
- end
15
-
16
- # Load all sprites from the WAD file
17
- def load_sprites
18
- # In Doom, sprites are stored between S_START and S_END markers
19
- start_index = @wad_loader.directory.find_index { |entry| entry[:name] == "S_START" }
20
- end_index = @wad_loader.directory.find_index { |entry| entry[:name] == "S_END" }
21
-
22
- return unless start_index && end_index && start_index < end_index
23
-
24
- # Extract all sprite lumps between S_START and S_END
25
- (start_index + 1...end_index).each do |i|
26
- entry = @wad_loader.directory[i]
27
- next if entry[:size] == 0 # Skip markers
28
-
29
- sprite_name = entry[:name]
30
- sprite_data = @wad_loader.get_lump(sprite_name)
31
-
32
- if sprite_data
33
- @sprites[sprite_name] = parse_sprite(sprite_data)
34
- end
35
- end
36
- end
37
-
38
- # Parse a sprite lump
39
- # @param sprite_data [String] the sprite lump data
40
- # @return [Hash] the parsed sprite data
41
- def parse_sprite(sprite_data)
42
- # Doom sprites are stored in the Picture format
43
- # Picture format header:
44
- # - 2 bytes: width
45
- # - 2 bytes: height
46
- # - 2 bytes: left offset
47
- # - 2 bytes: top offset
48
- # - variable: column data
49
-
50
- width = sprite_data[0..1].unpack("v")[0]
51
- height = sprite_data[2..3].unpack("v")[0]
52
- left_offset = sprite_data[4..5].unpack("s<")[0]
53
- top_offset = sprite_data[6..7].unpack("s<")[0]
54
-
55
- # The rest of the data is column data, which we'll store as raw bytes for now
56
- # In a full implementation, we would parse this into a usable format for rendering
57
- column_data = sprite_data[8..-1]
58
-
59
- {
60
- width: width,
61
- height: height,
62
- left_offset: left_offset,
63
- top_offset: top_offset,
64
- column_data: column_data
65
- }
66
- end
67
-
68
- # Get a specific sprite by name
69
- # @param name [String] the name of the sprite
70
- # @return [Hash, nil] the sprite data or nil if not found
71
- def get_sprite(name)
72
- @sprites[name]
73
- end
74
-
75
- # List all sprite names
76
- # @return [Array<String>] array of sprite names
77
- def sprite_names
78
- @sprites.keys
79
- end
80
-
81
- # Check if a sprite exists
82
- # @param name [String] the name of the sprite
83
- # @return [Boolean] true if the sprite exists
84
- def sprite_exists?(name)
85
- @sprites.key?(name)
86
- end
87
- end
88
- end
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Doom
4
- # SpriteRenderer is responsible for rendering sprites (enemies, items, etc.)
5
- class SpriteRenderer
6
- attr_reader :sprite_loader
7
-
8
- # Initialize a new SpriteRenderer with the given sprite loader
9
- # @param sprite_loader [Doom::SpriteLoader] the sprite loader to use
10
- def initialize(sprite_loader)
11
- @sprite_loader = sprite_loader
12
- @sprites = {}
13
- end
14
-
15
- # Load a specific sprite
16
- # @param sprite_name [String] the name of the sprite to load
17
- # @return [Hash, nil] the sprite data or nil if not found
18
- def load_sprite(sprite_name)
19
- return @sprites[sprite_name] if @sprites.key?(sprite_name)
20
-
21
- sprite_data = @sprite_loader.get_sprite(sprite_name)
22
- return nil unless sprite_data
23
-
24
- # In a real implementation, this would convert the sprite data
25
- # into a format that can be efficiently rendered
26
- @sprites[sprite_name] = sprite_data
27
- end
28
-
29
- # Render a sprite
30
- # @param sprite_name [String] the name of the sprite to render
31
- # @param x [Integer] the x coordinate to render at
32
- # @param y [Integer] the y coordinate to render at
33
- # @param scale [Float] the scale to render at
34
- # @param window [Doom::Window] the window to render to
35
- def render_sprite(sprite_name, x, y, scale, window)
36
- sprite = load_sprite(sprite_name)
37
- return unless sprite
38
-
39
- # In a real implementation, this would:
40
- # 1. Calculate the screen coordinates for the sprite
41
- # 2. Apply any necessary transformations (scaling, rotation)
42
- # 3. Draw the sprite with proper transparency
43
-
44
- # For now, we'll just draw a placeholder rectangle
45
- width = sprite[:width] * scale
46
- height = sprite[:height] * scale
47
- color = Gosu::Color.new(255, 255, 0, 0) # Semi-transparent red
48
- window.draw_quad(
49
- x - width / 2, y - height, color,
50
- x + width / 2, y - height, color,
51
- x + width / 2, y, color,
52
- x - width / 2, y, color
53
- )
54
- end
55
- end
56
- end