## Nathan Hinton ## Main entry point for the game. Sets up the world and holds the main loop require 'gosu' require 'yaml' require './gamegrid.rb' require './extractor.rb' require './producer.rb' require './consumer.rb' require './dialouge_box.rb' require './ui.rb' FONT_PATH = 'assets/font/Cousine-Regular.ttf' # Viewport resolution WIDTH = Gosu::screen_width HEIGHT = Gosu::screen_height BOX_SIZE = 64 # Default game settings GAME_SAVE_DIRECTORY = 'game_save_data' GAME_WIDTH = 201 # Odd numbers look best, in squares GAME_HEIGHT = 201 GAME_PRODUCER_CHANCE = 0.01 GAME_PRODUCER_VALUE_MULT = 0.1 GAME_PRODUCER_BASE_SPEED = 1 # Camera settings CAMERA_MOVE_RANGE = 10 CAMERA_MOVE_SPEED = 1 CAMERA_MOVE_MAX_SPEED = 5 CAMERA_MIN_ZOOM = 16 # Boxes across CAMERA_MAX_ZOOM = 80 # Boxes across class NumFactory < Gosu::Window attr_accessor :current_tool def initialize super WIDTH, HEIGHT, :fullscreen => true self.caption = "Number factory" @background_color = Gosu::Color::BLUE @cursor_image = Gosu::Image.new('assets/images/cursor.png') @cursor = @cursor_image @cursor_offset = 16 @camera_x = 0 @camera_y = 0 @camera_boxes = 30 # Default zoom @camera_zoom = 1 @camera_x_zoom_offset = WIDTH.to_f / (@camera_boxes * BOX_SIZE * 2).to_f @camera_y_zoom_offset = HEIGHT.to_f / (@camera_boxes * BOX_SIZE * 2).to_f @ui = UI.new(self, WIDTH, HEIGHT) @current_tool = nil @extractor_image = Gosu::Image.new('assets/images/extractor.png') @belt_image = Gosu::Image.new('assets/images/belt.png') @adder_image = Gosu::Image.new('assets/images/adder.png') @delete_image = Gosu::Image.new('assets/images/delete.png') @dialouges = [] @extractors = [] # Setup the game grid @game_grid = GameGrid.new(GAME_WIDTH, GAME_HEIGHT) # Load from file or initialize a new game begin game_save_data = YAML.load_file("#{GAME_SAVE_DIRECTORY}/positions.yaml") puts game_save_data # Load from file OR generate new save file @producers = [ Producer.new(0, 0, 1, 1), Producer.new(1000, 1000, 1, 1), Producer.new(2000, 2000, 1, 1), ] @consumers = [] @belts = [] rescue Errno::ENOENT # File does not exist puts "Generating new game..." @game_width = GAME_WIDTH @game_height = GAME_HEIGHT @producers = [] @consumers = [] # Consumer in center @belts = [] # Loop through and place producers center_x = ((@game_width - 1) / 2).to_i center_y = ((@game_height - 1) / 2).to_i consumer = Consumer.new(self, center_x, center_y, BOX_SIZE, WIDTH, HEIGHT) if @game_grid.add_item(center_x, center_y, consumer) @consumers.append(consumer) end # The value of the producer is proportional to the distance from the center ((@game_width).to_i).times do |xx| (@game_height).to_i.times do |yy| if rand < GAME_PRODUCER_CHANCE # Place a producer: producer = Producer.new( self, xx, yy, (Math.sqrt((xx - center_x)**2 + (yy - center_y) ** 2) * GAME_PRODUCER_VALUE_MULT).to_i, GAME_PRODUCER_BASE_SPEED, BOX_SIZE, WIDTH, HEIGHT, ) # Add an item to the game with checking that the internal game state is good if @game_grid.add_item(xx, yy, producer) @producers.append(producer) end end end end end # End file loading # Move the camera to the center of the map: @camera_x = @game_width / 2 @camera_y = @game_height / 2 # Render the background @background = Gosu.render(@game_width * BOX_SIZE, @game_height * BOX_SIZE) { im = Gosu::Image.new('assets/images/background_square.png', {:tileable => true}) zoom = BOX_SIZE / 30.0 (@game_width).to_i.times do |ww| puts ww (@game_height).to_i.times do |hh| im.draw(ww * BOX_SIZE, hh * BOX_SIZE, 0, zoom, zoom) end end } @tick_time = Time.now() @delta_time = 0 end # Disable cursor in the window def needs_cursor? false end def button_down(id) ### Process mouse ### mouse_used = @ui.update(self.mouse_x, self.mouse_y) # They clicked in the game screen, attempt to place an item if !mouse_used && id == Gosu::MS_LEFT case @current_tool when 'delete' @cursor = @delete_image @cursor_offset = @delete_image.height #break when 'belt' @cursor = @belt_image @cursor_offset = @belt_image.height #break when 'adder' @cursor = @adder_image @cursor_offset = @adder_image.height #break when 'extractor' #xx = @camera_x + ((((self.mouse_x) - (WIDTH/2)) / BOX_SIZE - 1) * @camera_zoom).to_i #yy = @camera_y + ((((self.mouse_y) - (HEIGHT/2)) / BOX_SIZE - 1) * @camera_zoom).to_i xx = @camera_x + ((self.mouse_x - @cursor_offset + (BOX_SIZE * @camera_zoom)) / (BOX_SIZE * @camera_zoom)).to_i yy = @camera_y + ((self.mouse_y - @cursor_offset + (BOX_SIZE * @camera_zoom)) / (BOX_SIZE * @camera_zoom)) extractor = Extractor.new(xx, yy, BOX_SIZE) if @game_grid.add_item(xx, yy, extractor) @extractors.append(extractor) end #break end end ### Process keyboard ### if id == Gosu::KB_ESCAPE close elsif [Gosu::MS_WHEEL_UP, Gosu::KB_Z].include? id @camera_boxes -= 2 if @camera_boxes < CAMERA_MIN_ZOOM @camera_boxes = CAMERA_MIN_ZOOM end @camera_zoom = WIDTH / (@camera_boxes * BOX_SIZE).to_f @camera_x_zoom_offset = WIDTH.to_f / (@camera_boxes * BOX_SIZE * 2).to_f @camera_y_zoom_offset = HEIGHT.to_f / (@camera_boxes * BOX_SIZE * 2).to_f elsif [Gosu::MS_WHEEL_DOWN, Gosu::KB_X].include? id @camera_boxes += 2 if @camera_boxes > CAMERA_MAX_ZOOM @camera_boxes = CAMERA_MAX_ZOOM end @camera_zoom = WIDTH.to_f / (@camera_boxes * BOX_SIZE).to_f @camera_x_zoom_offset = WIDTH.to_f / (@camera_boxes * BOX_SIZE * 2).to_f @camera_y_zoom_offset = HEIGHT.to_f / (@camera_boxes * BOX_SIZE * 2).to_f elsif id == Gosu::KB_DOWN @camera_y += 1 elsif id == Gosu::KB_UP @camera_y -= 1 elsif id == Gosu::KB_LEFT @camera_x -= 1 elsif id == Gosu::KB_RIGHT @camera_x += 1 elsif id == Gosu::KB_SPACE @camera_boxes = 30 @camera_zoom = WIDTH.to_f / (@camera_boxes * BOX_SIZE).to_f @camera_x_zoom_offset = WIDTH.to_f / (@camera_boxes * BOX_SIZE * 2).to_f @camera_y_zoom_offset = HEIGHT.to_f / (@camera_boxes * BOX_SIZE * 2).to_f elsif id == Gosu::KB_G @cursor = @cursor_image @cursor_offset = 16 @current_tool = nil else super end end ## Register a dialouge into the game # @param x [Integer] The x position that the dialouge should be drawn at. # @param y [Integer] The y position that the dialouge should be drawn at. # @param size_x [Integer] The pixel width # @param size_y [Integer] The pixel width # @param text [String] The text for the dialouge box def register_dialouge(x, y, size_x, size_y, text, popup_time) dialouge = DialougeBox.new(x, y, size_x, size_y, WIDTH, HEIGHT, text, BOX_SIZE, popup_time) @dialouges.append(dialouge) return dialouge end ## Remove a dialouge from the game # @param dialouge [DialougeBox] The dialouge to remove def deregister_dialouge(dialouge) @dialouges.delete(dialouge) end ## Called every frame def update @delta_time = Time.now() - @tick_time delta_mult = @delta_time * 30 #puts "Time: #{@delta_time}, Mult: #{delta_mult}" # Timing debug @tick_time = Time.now() ### CAMERA ### # Move the camera if needed if self.mouse_x < CAMERA_MOVE_RANGE @camera_x -= ([(CAMERA_MOVE_SPEED * ((CAMERA_MOVE_RANGE - self.mouse_x ) / 5)), CAMERA_MOVE_MAX_SPEED].min * delta_mult) elsif self.mouse_x > WIDTH - CAMERA_MOVE_RANGE @camera_x += ([(CAMERA_MOVE_SPEED * ((CAMERA_MOVE_RANGE - (WIDTH - self.mouse_x + 1)) / 5)), CAMERA_MOVE_MAX_SPEED].min * delta_mult) end if self.mouse_y < CAMERA_MOVE_RANGE @camera_y -= ([(CAMERA_MOVE_SPEED * ((CAMERA_MOVE_RANGE - self.mouse_y ) / 5)), CAMERA_MOVE_MAX_SPEED].min * delta_mult) elsif self.mouse_y > HEIGHT - CAMERA_MOVE_RANGE @camera_y += ([(CAMERA_MOVE_SPEED * ((CAMERA_MOVE_RANGE - (HEIGHT - (self.mouse_y + 1)) ) / 5)), CAMERA_MOVE_MAX_SPEED].min * delta_mult) end # Prevent camera from leaving the screen @camera_x = [@camera_x.to_i, 0].max @camera_x = [@camera_x.to_i, @game_width].min @camera_y = [@camera_y.to_i, 0].max @camera_y = [@camera_y.to_i, @game_width].min ### ITEMS ### @producers.each do |producer| producer.update(delta_mult, @camera_x, @camera_y, @camera_zoom) end @extractors.each do |extractor| extractor.update(delta_mult, @camera_x, @camera_y, @camera_zoom) end @consumers.each do |consumer| consumer.update(delta_mult, @camera_x, @camera_y, @camera_zoom, self.mouse_x, self.mouse_y) end # Finally process the dialouges @dialouges.each do |dialouge| dialouge.update(delta_mult, @camera_zoom, self.mouse_x, self.mouse_y) end case @current_tool when 'delete' @cursor = @delete_image @cursor_offset = @delete_image.height #break when 'belt' @cursor = @belt_image @cursor_offset = @belt_image.height #break when 'adder' @cursor = @adder_image @cursor_offset = @adder_image.height #break when 'extractor' @cursor = @extractor_image @cursor_offset = @extractor_image.height #break end end ## Called when OS wants to update window. def draw # Draw mouse coords: onscreen_items = 0 # TODO: The background does *not* actually need to move. It can just stay # in one spot and scale... This would make the program load so much faster # as well as allow for larger maps. @background.draw( (((((WIDTH / BOX_SIZE.to_f) / 2.0) - (@camera_x * @camera_zoom)) * (BOX_SIZE))), ((((HEIGHT / BOX_SIZE.to_f) / 2.0) - (@camera_y * @camera_zoom)) * (BOX_SIZE)), 0, @camera_zoom, @camera_zoom ) @producers.each do |producer| if producer.draw_x <= WIDTH && producer.draw_x >= 0 if producer.draw_y <= HEIGHT && producer.draw_y >= 0 producer.draw() onscreen_items += 1 end end end @extractors.each do |extractor| if extractor.draw_x <= WIDTH && extractor.draw_x >= 0 if extractor.draw_y <= HEIGHT && extractor.draw_y >= 0 extractor.draw() onscreen_items += 1 end end end @consumers.each do |consumer| if consumer.draw_x <= WIDTH && consumer.draw_x >= 0 if consumer.draw_y <= HEIGHT && consumer.draw_y >= 0 consumer.draw() onscreen_items += 1 end end end @dialouges.each do |dialouge| dialouge.draw() end @ui.draw() Gosu::Image.from_text("#{width}, #{height}", 20, font: FONT_PATH).draw(1600, 0, 1) Gosu::Image.from_text("#{mouse_x}, #{mouse_y}", 20, font: FONT_PATH).draw(1600, 100, 1) Gosu::Image.from_text("#{@camera_x + ((self.mouse_x - @cursor_offset + (BOX_SIZE * @camera_zoom)) / (BOX_SIZE * @camera_zoom)).to_i}, #{@camera_y + ((self.mouse_y - (HEIGHT/2)) / BOX_SIZE * @camera_zoom).to_i}", 20, font: FONT_PATH).draw(1600, 125, 1) Gosu::Image.from_text("Tick time: #{((Time.now() - @tick_time).to_f * 1000).to_i}", 20, font: FONT_PATH).draw(1600, 25, 1) Gosu::Image.from_text("FPS: #{Gosu.fps}", 20, font: FONT_PATH).draw(1600, 50, 1) Gosu::Image.from_text("items on screen: #{onscreen_items}", 20, font: FONT_PATH).draw(1600, 75, 1) Gosu::Image.from_text("Camera boxes: #{@camera_boxes}", 20, font: FONT_PATH).draw(1600, 175, 1) Gosu::Image.from_text("Camera location: #{@camera_x}, #{@camera_y}", 20, font: FONT_PATH).draw(1600, 200, 1) Gosu::Image.from_text("Camera zoom: #{@camera_zoom}, #{@camera_x_zoom_offset}", 20, font: FONT_PATH).draw(1600, 225, 1) Gosu::Image.from_text("Camera viewport: (#{@camera_min_x}, #{@camera_min_y}) to (#{@camera_max_x}, #{@camera_max_y})", 20, font: FONT_PATH).draw(1600, 250, 1) if @cursor == @cursor_image @cursor.draw(self.mouse_x - @cursor_offset, self.mouse_y - @cursor_offset, 10) else @cursor.draw(self.mouse_x - @cursor_offset, self.mouse_y - @cursor_offset, 10, BOX_SIZE / 32.0 * @camera_zoom, BOX_SIZE / 32.0 * @camera_zoom, 0x88_888888) end Gosu.draw_rect(self.mouse_x - 2, self.mouse_y - 2, 4, 4, Gosu::Color::RED) end end NumFactory.new.show