Ruby 学习经历:业余学习编程,挺喜欢 Ruby 的语法,学习了一段时间
1、听说在 Windows 上 Ruby on Rails 非常不好整,尝试了安装 Ruby on Rails,也能跑起来,小问题 baidu 一下还是能解决的,个人认为应该 Ruby2.3+ 版本应该对 Windows 不是哪么排斥了吧,毕竟看到说建议不要在 Windows 上玩 Ruby on Rails 的贴子也是几年前了吧。
2、也关注了 Ruby 的另一个轻量框架 Sinatra,在 Windows 上玩了一把,好像也没什么大问题,简单的连接 MySql,启动服务器,都是可以的,就是有一点迷惑,启动服务器要在 Windows 的 cmd 下启动,cmd 窗口不能关闭,看着哪个 cmd 窗口,总有一种不踏实的感觉,觉得服务器进程会不会宕掉啊 。关于 Ruby 的服务器,例如:puma,thin 一类的服务器,如果只单开一个进程,到底能支持多大的并发数呢?还有这些服务器都说最好用 Nginx 反向代理,不用代理可不可以跑啊?这些个迷惑一直在找答案,听说 Ruby China 社区很多热心的前辈,希望有经验的前辈能给个明示,先谢谢!
3、进入主题,Ruby 的 gui 界面开发,没看到说 Ruby 在 Windows 上难整,高兴一个,一直关注 Ruby China 社区,但在社区没有发现有使用 Ruby 进行界面开发的好例子,因为工作一直在 Windows 上,就试着在 Windows 上玩。也装过双系统 Windows7+Ubuntu Gnome16.04 玩过,不适应啊,还有一个最重要的问题就是这个坦克大战在 ubuntu 上跑居然会卡死,一直没想通,在 Windows 上啥事没有 和儿子玩得挺欢,这是什么情况啊?.
游戏是参照马士兵的 Java 坦克大战的思想,图片来自韩顺平的坦克大战。Ruby 的实现,废话不多说,以下目录结构及图片代码。
1、tank_game.rb(游戏入口,主类)
# -*- coding: UTF-8 -*-
require 'gtk3'
require 'thread'
require 'rexml/document'
Dir["./*.rb"].each{|f| require f if f!="./tank_game.rb"}
module TankGame
# 坦克大战主类
class TankGame < Gtk::Window
# 常量
WIDTH, HEIGHT = 800, 600
MENU_HEIGHT = 25
TANK_TYPE = {enemy: 0, my1p: 1, my2p: 2}
# setter and getter
attr_reader :missiles, :tanks, :bombs, :walls, :tank_image, :bomb_image, :wall_image
attr_accessor :run_flag
# 构造器方法
def initialize
super
source = Source.new
@tank_image = source.tank_image # 获取坦克图片资源
@bomb_image = source.bomb_image # 获取爆炸图片资源
@wall_image = source.wall_image # 获取墙类图片资源
@run_flag = true # 线程启动标记
# xml解析地图
@maps = REXML::Document.new(File.new "map.xml").root
init # 初始化变量
init_ui # def初始化ui界面
listeners # def事件监听
show_all # 显示所有部件
run # def启动线程
end
# 初始化变量
def init
@my1p_num = @my2p_num = 3 # 我方坦克数量
@tanks = Array.new # 初始化敌人坦克数组
@missiles = Array.new # 初始化子弹数组
@bombs = Array.new # 初始化爆炸数组
@walls = Array.new # 初始化墙类数组
@tank_1p = Tank.new self, 325, 570, TANK_TYPE[:my1p], true, Tank::DIR[:stop] # 生成1P坦克
@tank_2p = Tank.new self, 445, 570, TANK_TYPE[:my2p], true, Tank::DIR[:stop] # 生成2P坦克
init_tanks # def初始化创建坦克
init_walls 1 # def初始化创建墙,并指定关数
end
# 重置1P坦克
def reset_my1p_tank
@my1p_num -= 1
if @my1p_num > 0 && @tank_1p != nil
@tank_1p.x, @tank_1p.y = 325, 570
@tank_1p.dir, @tank_1p.ptdir = Tank::DIR[:stop], Tank::DIR[:up]
else
@tank_1p = nil
end
end
# 重置2P坦克
def reset_my2p_tank
@my2p_num -= 1
if @my2p_num > 0 && @tank_2p != nil
@tank_2p.x, @tank_2p.y = 445, 570
@tank_2p.dir, @tank_2p.ptdir = Tank::DIR[:stop], Tank::DIR[:up]
else
@tank_2p = nil
end
end
# 初始化ui界面
def init_ui
set_title "坦克世界" # 设置窗口标题
set_resizable false # 禁止改变窗口大小,ubuntu下要注释掉,不明原因
set_icon @tank_image[TANK_TYPE[:my2p]][Tank::DIR[:up]] # 设置任务栏窗口图标,使用2p的坦克作为图标
# set_icon_from_file "./img/1P/Up.png" # 设置任务栏窗口图标
override_background_color :normal, Gdk::RGBA::new(0.6, 0.6, 0.6, 1) # 设置窗口背景色
set_default_size WIDTH, HEIGHT + MENU_HEIGHT # 设置窗口宽高
set_window_position :center # 设置窗口位置
@darea = Gtk::DrawingArea.new # 创建画布
@box = Gtk::Box.new :vertical, 0 # 创建盒子 :vertical垂直, :horizontal水平
@menu = Menu.new(self)
@menubar = @menu.menubar
@box.pack_start @menubar, :expand => false, :fill => false, :padding => 0 # 添加菜单到box盒子
@box.pack_start @darea, :expand => true, :fill => true, :padding => 0 # 添加画布到box盒子
# widget:@darea对象, cr: 是@darea.window.create_cairo_context创建的对象
@darea.signal_connect "draw" do |widget, cr| # 监听draw动作
draw cr
end
add @box # 画面box盒子到窗口
# add @darea # 画面添加到窗口
end
# 事件监听
def listeners
# 窗口销毁事件
signal_connect "destroy" do
Gtk.main_quit
end
# 键盘按下事件
signal_connect "key_press_event" do |widget, event|
@tank_1p.key_press event unless @tank_1p == nil
@tank_2p.key_press event unless @tank_2p == nil
end
# 键盘松开事件
signal_connect "key_release_event" do |widget, event|
@tank_1p.key_release event if @tank_1p != nil
@tank_2p.key_release event if @tank_2p != nil
end
end
# 画出所有东西
def draw cr
# 写字
cr.save do # 等同于cr.save和cr.restore同时使用
cr.set_source_rgb 1, 1, 1
cr.select_font_face "宋体", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_NORMAL
cr.set_font_size 13
cr.move_to 20, 15
cr.show_text "子弹数量: %d" % @missiles.size
cr.move_to 20, 30
cr.show_text "坦克数量: %d" % @tanks.size
cr.move_to 120, 15
cr.show_text "炸弹数量: %d" % @bombs.size
cr.move_to 120, 30
cr.show_text "墙的数量: %d" % @walls.size
cr.move_to 220, 15
cr.show_text "1P的数量: %d" % @my1p_num
cr.move_to 220, 30
cr.show_text "2P的数量: %d" % @my2p_num
cr.stroke # 写字也要结尾
end
# 画草墙以外所有墙
@walls.each {|w| w.draw cr unless w.type == 1}
# 画所有子弹
@missiles.each do |m|
m.hit_tanks @tanks
m.hit_walls @walls
m.hit_tank @tank_1p if @tank_1p != nil
m.hit_tank @tank_2p if @tank_2p != nil
m.draw cr
end
# 画我方坦克
if @tank_1p != nil
@tank_1p.collide_walls @walls
@tank_1p.collide_tank @tank_2p if @tank_2p != nil
@tank_1p.collide_tanks @tanks
@tank_1p.draw cr
end
if @tank_2p != nil
@tank_2p.collide_walls @walls
@tank_2p.collide_tank @tank_1p if @tank_1p != nil
@tank_2p.collide_tanks @tanks
@tank_2p.draw cr
end
# 画敌人坦克
@tanks.each do |t|
t.collide_tank @tank_1p if @tank_1p != nil
t.collide_tank @tank_2p if @tank_2p != nil
t.collide_tanks @tanks
t.collide_walls @walls
t.draw cr
end
# 画所有草墙
@walls.each {|w| w.draw cr if w.type == 1}
# 画所有爆炸
@bombs.each {|b| b.draw cr}
# 我方坦克数量为0,调用菜单的重新游戏
if @my1p_num + @my2p_num == 0
@menu.again
end
end
# 初始化坦克
def init_tanks
3.times do |i|
@tanks << Tank.new(self, i*385, 0, TANK_TYPE[:enemy], false, Tank::DIR[:down]) # 默认间隔385
end
end
# 初始化墙
def init_walls num
map = @maps.elements[num].text.split ";"
map.each do |m|
n = m.split(",").map{|i| i.to_i}
@walls << Wall.new(self, n[0]*20, n[1]*20, n[2])
end
@walls << Wall.new(self, 380, 560, 4) # 总部
end
# 重画函数
def redraw
@darea.queue_draw #部件重画函数,会自动调用draw事件
end
# 开启线程自动重画
def run
t = Thread.new { # 线程创建
while true # 无限循环
sleep(0.05) # 线程阻塞
redraw if @run_flag # 调用重画
end
}
end
end
end
tank = TankGame::TankGame.new # 实例化窗口对象
Gtk.main # 启动窗口程序
2、tank.rb(坦克类)
# -*- coding: UTF-8 -*-
module TankGame
# 坦克类
class Tank
# 常量
SPEED = 3
WIDTH = HEIGHT = 30
DIR = {up: 0, down: 1, left: 2, right: 3, stop: 4}
# setter and getter
attr_reader :good, :type
attr_accessor :x, :y, :dir, :ptdir
# 构造器方法
def initialize tg, x, y, type, good, dir
@tg, @x, @y, @type, @good, @dir = tg, x, y, type, good, dir
@image = @tg.tank_image
@bU = @bD = @bL = @bR = false
@ptdir = DIR[:up]
@step = rand(4..15)
@old_x, @old_y = @x, @y
@missile_max_number = 5
@interval = 0
end
# 坦克画自己
def draw cr
cr.save do
img = @image[@type][@ptdir].scale Tank::WIDTH, Tank::HEIGHT # 获取图片并设置宽高
# img = img.rotate 90 # 图片旋转
cr.set_source_pixbuf(img, @x, @y).paint # 设置来源并画出图片
end
move # 移动坦克
end
# 生成碰撞检测类
def get_rect
Rectangle.new @x, @y, WIDTH, HEIGHT
end
# 坦克撞墙
def collide_wall wall
if wall.type != 1 && get_rect.intersects?(wall.get_rect)
stay #unless wall.type == 1
return true
end
false
end
# 坦克撞墙集合
def collide_walls walls
walls.each {|w| return true if collide_wall w}
false
end
# 坦克撞坦克
def collide_tank tank
if get_rect.intersects?(tank.get_rect) && tank != self
stay
return true
end
false
end
# 坦克撞坦克集合
def collide_tanks tanks
tanks.each {|t| return true if collide_tank t}
false
end
# 键盘按下事件
def key_press event
key_event event, true
end
# 键盘松开事件
def key_release event
key_event event, false
end
# 以下是私有方法
private
# 键盘事件
def key_event event, b
key = event.keyval
if @type == 1
case key
when Gdk::Keyval::KEY_KP_4 then fire if !b
when Gdk::Keyval::KEY_Up then @bU = b
when Gdk::Keyval::KEY_Down then @bD = b
when Gdk::Keyval::KEY_Left then @bL = b
when Gdk::Keyval::KEY_Right then @bR = b
end
else
case key
when Gdk::Keyval::KEY_j then fire if !b
when Gdk::Keyval::KEY_w then @bU = b
when Gdk::Keyval::KEY_s then @bD = b
when Gdk::Keyval::KEY_a then @bL = b
when Gdk::Keyval::KEY_d then @bR = b
end
end
locate_direction
end
# 根据键盘事件改变坦克方向
def locate_direction
@dir = DIR[:up] if @bU && !@bD && !@bL && !@bR
@dir = DIR[:down] if !@bU && @bD && !@bL && !@bR
@dir = DIR[:left] if !@bU && !@bD && @bL && !@bR
@dir = DIR[:right] if !@bU && !@bD && !@bL && @bR
@dir = DIR[:stop] if !@bU && !@bD && !@bL && !@bR
end
# 我方坦克自动挂载子弹
def load_missile
@interval > 20 ? @interval = 0 : @interval += 1
@missile_max_number += 1 if @missile_max_number < 5 && @interval == 20
end
# 坦克移动
def move
@old_x, @old_y = @x, @y
case @dir
when DIR[:up] then @y -= SPEED
when DIR[:down] then @y += SPEED
when DIR[:left] then @x -= SPEED
when DIR[:right] then @x += SPEED
when DIR[:stop]
end
@ptdir = @dir unless @dir == DIR[:stop] # 根据坦克方向改变炮桶方向,运用Ruby的unless判断
out
turn_dir
load_missile
end
# 敌人坦克自动改变方向,行走,发炮弹
def turn_dir
if !@good
if @step == 0
@step = rand(4..15)
@dir = rand(DIR.size)
end
@step -= 1
fire if rand(40) > 38
end
end
# 判断坦克出界
def out
if @x > TankGame::WIDTH - WIDTH
@x = TankGame::WIDTH - WIDTH
elsif @x < 0
@x = 0
end
if @y > TankGame::HEIGHT - HEIGHT
@y = TankGame::HEIGHT - HEIGHT
elsif @y < 0
@y = 0
end
end
# 发射子弹
def fire
x = @x + WIDTH/2 - Missile::WIDTH/2
y = @y + HEIGHT/2 - Missile::HEIGHT/2
if @missile_max_number > 0
m = Missile.new @tg, x, y, @type, @good, @ptdir
@tg.missiles << m
@missile_max_number -= 1
end
end
# 返回上一步
def stay
@x, @y = @old_x, @old_y
end
end
end
3、missile.rb(子弹类)
# -*- coding: UTF-8 -*-
module TankGame
# 子弹类
class Missile
# 常量
SPEED = 6 # 子弹速度
WIDTH = HEIGHT = 3 # 子弹宽高
# 构造器方法
# @tg: TankGame主类实例
# @x, @y: 子弹坐标
# @type: 子弹类型,区分敌,我[1P,2P]
# @good: 区分子弹好坏
# @dir, @color: 子弹方向,颜色
def initialize tg, x, y, type, good, dir
@tg, @x, @y, @type, @good, @dir = tg, x, y, type, good, dir
@color = [[0,1,1],[1,1,0],[0,0.5,0]]
end
# 画出子弹
def draw cr
cr.save do
cr.set_source_rgb @color[@type]
cr.rectangle @x, @y, WIDTH, HEIGHT # 设置坐标,宽高
cr.fill
end
move
end
# 生成碰撞检测类
def get_rect
Rectangle.new @x, @y, WIDTH, HEIGHT
end
# 子弹打中墙
def hit_wall wall
if get_rect.intersects?(wall.get_rect)
@tg.missiles.delete self unless wall.type == 1 || wall.type == 2
@tg.walls.delete wall if wall.type == 0 || wall.type == 4
return true
end
false
end
# 子弹打中墙集合
def hit_walls walls
walls.each {|w| return true if hit_wall w}
false
end
# 子弹打中坦克
def hit_tank tank
if get_rect.intersects?(tank.get_rect) && @good != tank.good
@tg.bombs << Bomb.new(@tg, tank.x, tank.y)
@tg.tanks.delete tank
@tg.reset_my1p_tank if tank.type == TankGame::TANK_TYPE[:my1p]
@tg.reset_my2p_tank if tank.type == TankGame::TANK_TYPE[:my2p]
@tg.missiles.delete self
return true
end
false
end
# 子弹打中全部坦克
def hit_tanks tanks
tanks.each {|t| return true if hit_tank t}
false
end
# private以下都属于私有方法
private
# 子弹移动
def move
case @dir
when Tank::DIR[:up] then @y -= SPEED
when Tank::DIR[:down] then @y += SPEED
when Tank::DIR[:left] then @x -= SPEED
when Tank::DIR[:right] then @x += SPEED
end
if @y > TankGame::HEIGHT || @y < 0 || @x > TankGame::WIDTH || @x < 0
@tg.missiles.delete self # 子弹出界移除
end
end
end
end
4、wall.rb(墙类)
# -*- coding: UTF-8 -*-
module TankGame
# 墙类,包括总部
class Wall
# setter and getter
attr_reader :type
# 构造器方法
def initialize tg, x, y, type
@tg, @x, @y, @type = tg, x, y, type
@img = @tg.wall_image[@type]
@w, @h = @img.width, @img.height
end
# 画墙
def draw cr
cr.set_source_pixbuf(@img, @x, @y).paint # 设置来源,并画出图片
end
# 生成碰撞检测类
def get_rect
Rectangle.new @x, @y, @w, @h
end
end
end
5、bomb.rb(爆炸类)
# -*- coding: UTF-8 -*-
module TankGame
# 爆炸类
class Bomb
# 构造器方法
def initialize tg, x, y
@tg, @x, @y = tg, x, y
@step = 0
end
# 画爆炸
def draw cr
img = @tg.bomb_image[@step/4].scale Tank::WIDTH, Tank::HEIGHT # 设置图片宽高
cr.set_source_pixbuf(img, @x, @y).paint # 设置来源,并画出图片
@step == 11 ? @tg.bombs.delete(self) : @step += 1 # 以动画形式切换图片
end
end
end
6、source.rb(图片资源类)
# -*- coding: UTF-8 -*-
module TankGame
# 资源类
class Source
# getter and setter
attr_reader :tank_image, :bomb_image, :wall_image
# 构造器方法
def initialize
load_img # def引入图片
end
# 私有方法,load_img引入图片资源
private
# 引入图片
def load_img
@tank_image = Array.new 3 do |i|
["Up", "Down", "Left", "Right"].map! do |n|
# Cairo::ImageSurface.from_png "./img/%dp/%s.png" % [i, n] # 引入图片资源
GdkPixbuf::Pixbuf.new :file => "./img/%dp/%s.png" % [i, n] # 引入图片资源
end
end
@bomb_image = Array.new 3 do |i|
GdkPixbuf::Pixbuf.new :file => "./img/bomb_%d.gif" % i
end
@wall_image = Array.new 5 do |i|
GdkPixbuf::Pixbuf.new :file => "./img/wall_%d.png" % i
end
end
end
end
7、rectangle.rb(检测碰撞类)
# -*- coding: UTF-8 -*-
module TankGame
# 碰撞检测类
class Rectangle
# getter and setter
attr_reader :x, :y, :w, :h
# 构造器方法
def initialize x, y, w, h
@x, @y, @w, @h = x, y, w, h
end
# 矩形碰撞检测方法
def intersects? other_rectangle
r = other_rectangle
# 根据两个矩形左上角x,y计算出中心点x,y
x, y, x1, y1 = @x + @w/2, @y + @h/2, r.x + r.w/2, r.y + r.h/2
# 当两个矩形横向中心点的距离小于两个矩形宽的和的一半
# 且两个矩形纵向中心点的距离小于两个矩形高的和的一半,即碰上
(x - x1).abs < (@w + r.w)/2 && (y - y1).abs < (@h + r.h)/2
end
end
end
8、menu.rb(菜单类)
# -*- coding: UTF-8 -*-
module TankGame
# 菜单类
class Menu
# setter and getter
attr_reader :menubar
# 构造器方法
def initialize tg
@tg = tg
@menubar = Gtk::MenuBar.new # 创建根菜单
init_menu # 初始化菜单
end
# 菜单初始化
def init_menu
menu = REXML::Document.new(File.new "menu.xml").root # 解析菜单menu.xml文件
menu.elements.each "Menu" do |m| # 遍历xml文件根目录下Menu一级目录
menu_name = m.elements["Name"].text # 获取一级目录名称
menu_obj = Gtk::Menu.new # 创建一级菜单容器
menu_item_obj = Gtk::MenuItem.new :label => menu_name # 创建一级菜单
menu_item_obj.set_submenu menu_obj # 一级菜单绑定一级菜单容器
@menubar.append menu_item_obj # 一级菜单添加到根菜单@menubar
m.elements.each "MenuItem" do |mi| # 遍历一级目录下的MenuItem二级目录
item_name = mi.elements["Name"].text # 获取二级目录的名称
item_acti = mi.elements["Activate"].text # 获取二级目录的事件绑定方法名称
item_obj = Gtk::MenuItem.new :label => item_name # 创建二级子菜单
menu_obj.append item_obj # 二级子菜单添加到一级菜单容器
item_obj.signal_connect "activate" do # 创建二级子菜单激活事件
send item_acti # 利用方法名称绑定子菜单事件到指定方法
end
end
end
end
# 以下为菜单事件绑定方法,方法名与nemu.xml文件中定义一样
def start
p @tg.tanks
end
def again
@tg.run_flag = false # 线程暂停
md = Gtk::MessageDialog.new :parent => @tg, :flags => :destroy_with_parent, :type => :info,
:buttons_type => :ok, :message => "游戏结束!点击确定重新开始游戏"
md.override_font Pango::FontDescription.new "DFKai-SB 12" # 设置消息框字体
md_msg = md.run
md.destroy
@tg.init if md_msg.name == "GTK_RESPONSE_OK" # 点击确定重新开始游戏,初始化所有变量
@tg.run_flag = true # 线程启动
end
def pause
@tg.run_flag = false # 线程暂停
md = Gtk::MessageDialog.new :parent => @tg, :flags => :destroy_with_parent, :type => :info,
:buttons_type => :ok, :message => "游戏已暂停!点击确定继续游戏"
md.override_font Pango::FontDescription.new "DFKai-SB 12" # 设置消息框字体
md.run
md.destroy
@tg.run_flag = true # 线程启动
end
def read
p "read"
end
def save
p "save"
end
def exit
Gtk.main_quit
end
def empty
p "empty"
end
end
end
9、menu.xml(菜单的名称及响应的方法名都记录在此文件中)
<?xml version="1.0" encoding="UTF-8"?>
<MenuBar>
<Menu>
<Name>游戏</Name>
<MenuItem><Name>开始游戏</Name><Activate>start</Activate></MenuItem>
<MenuItem><Name>重新游戏</Name><Activate>again</Activate></MenuItem>
<MenuItem><Name>暂停游戏</Name><Activate>pause</Activate></MenuItem>
<MenuItem><Name>读取游戏</Name><Activate>read</Activate></MenuItem>
<MenuItem><Name>保存游戏</Name><Activate>save</Activate></MenuItem>
<MenuItem><Name>退出游戏</Name><Activate>exit</Activate></MenuItem>
</Menu>
<Menu>
<Name>设置</Name>
<MenuItem><Name>游戏人数</Name><Activate>empty</Activate></MenuItem>
<MenuItem><Name>游戏难度</Name><Activate>empty</Activate></MenuItem>
<MenuItem><Name>游戏设置</Name><Activate>empty</Activate></MenuItem>
</Menu>
<Menu>
<Name>地图</Name>
<MenuItem><Name>创建地图</Name><Activate>empty</Activate></MenuItem>
<MenuItem><Name>修改地图</Name><Activate>empty</Activate></MenuItem>
<MenuItem><Name>删除地图</Name><Activate>empty</Activate></MenuItem>
</Menu>
<Menu>
<Name>关于</Name>
<MenuItem><Name>游戏帮助</Name><Activate>empty</Activate></MenuItem>
<MenuItem><Name>关于游戏</Name><Activate>empty</Activate></MenuItem>
</Menu>
</MenuBar>
10、map.xml(地图)
<?xml version="1.0" encoding="UTF-8"?>
<Maps>
<Map id='1'>2,2,0;3,2,0;6,2,0;7,2,0;10,2,0;11,2,0;12,2,0;15,2,0;16,2,0;19,2,0;20,2,0;23,2,0;24,2,0;27,2,0;28,2,0;29,2,0;32,2,0;33,2,0;36,2,0;37,2,0;2,3,0;3,3,0;6,3,0;7,3,0;10,3,0;11,3,0;12,3,0;15,3,0;16,3,0;19,3,0;20,3,0;23,3,0;24,3,0;27,3,0;28,3,0;29,3,0;32,3,0;33,3,0;36,3,0;37,3,0;2,4,0;3,4,0;6,4,0;7,4,0;10,4,0;11,4,0;12,4,0;15,4,0;16,4,0;19,4,0;20,4,0;23,4,0;24,4,0;27,4,0;28,4,0;29,4,0;32,4,0;33,4,0;36,4,0;37,4,0;2,5,0;3,5,0;6,5,0;7,5,0;10,5,0;11,5,0;12,5,0;15,5,0;16,5,0;19,5,0;20,5,0;23,5,0;24,5,0;27,5,0;28,5,0;29,5,0;32,5,0;33,5,0;36,5,0;37,5,0;2,6,0;3,6,0;6,6,0;7,6,0;8,6,3;9,6,3;10,6,0;11,6,0;12,6,0;15,6,0;16,6,0;17,6,3;18,6,3;19,6,0;20,6,0;21,6,3;22,6,3;23,6,0;24,6,0;27,6,0;28,6,0;29,6,0;30,6,3;31,6,3;32,6,0;33,6,0;36,6,0;37,6,0;2,7,0;3,7,0;6,7,0;7,7,0;8,7,3;9,7,3;10,7,0;11,7,0;12,7,0;15,7,0;16,7,0;17,7,1;18,7,1;19,7,0;20,7,0;21,7,1;22,7,1;23,7,0;24,7,0;27,7,0;28,7,0;29,7,0;30,7,3;31,7,3;32,7,0;33,7,0;36,7,0;37,7,0;2,8,0;3,8,0;6,8,0;7,8,0;10,8,0;11,8,0;12,8,0;15,8,0;16,8,0;17,8,1;18,8,1;19,8,0;20,8,0;21,8,1;22,8,1;23,8,0;24,8,0;27,8,0;28,8,0;29,8,0;32,8,0;33,8,0;36,8,0;37,8,0;2,9,0;3,9,0;6,9,0;7,9,0;10,9,0;11,9,0;12,9,0;15,9,0;16,9,0;19,9,0;20,9,0;23,9,0;24,9,0;27,9,0;28,9,0;29,9,0;32,9,0;33,9,0;36,9,0;37,9,0;2,10,0;3,10,0;6,10,0;7,10,0;10,10,0;11,10,0;12,10,0;15,10,0;16,10,0;19,10,0;20,10,0;23,10,0;24,10,0;27,10,0;28,10,0;29,10,0;32,10,0;33,10,0;36,10,0;37,10,0;2,11,0;3,11,0;6,11,0;7,11,0;10,11,0;11,11,0;12,11,0;27,11,0;28,11,0;29,11,0;32,11,0;33,11,0;36,11,0;37,11,0;14,12,0;15,12,0;24,12,0;25,12,0;0,13,3;1,13,3;4,13,0;5,13,0;6,13,0;7,13,0;8,13,0;14,13,0;15,13,0;24,13,0;25,13,0;31,13,0;32,13,0;33,13,0;34,13,0;35,13,0;38,13,3;39,13,3;0,14,3;1,14,3;4,14,0;5,14,0;6,14,0;7,14,0;8,14,0;10,14,1;11,14,1;12,14,1;13,14,1;16,14,2;17,14,2;18,14,2;19,14,2;20,14,2;21,14,2;22,14,2;23,14,2;26,14,1;27,14,1;28,14,1;29,14,1;31,14,0;32,14,0;33,14,0;34,14,0;35,14,0;38,14,3;39,14,3;10,15,1;11,15,1;12,15,1;13,15,1;16,15,2;17,15,2;18,15,2;19,15,2;20,15,2;21,15,2;22,15,2;23,15,2;26,15,1;27,15,1;28,15,1;29,15,1;2,16,0;3,16,0;6,16,0;7,16,0;10,16,0;11,16,0;28,16,0;29,16,0;32,16,0;33,16,0;36,16,0;37,16,0;2,17,0;3,17,0;6,17,0;7,17,0;10,17,0;11,17,0;14,17,0;15,17,0;18,17,0;21,17,0;24,17,0;25,17,0;28,17,0;29,17,0;32,17,0;33,17,0;36,17,0;37,17,0;0,18,1;1,18,1;2,18,0;3,18,0;6,18,0;7,18,0;10,18,0;11,18,0;14,18,0;15,18,0;18,18,0;21,18,0;24,18,0;25,18,0;28,18,0;29,18,0;32,18,0;33,18,0;36,18,0;37,18,0;0,19,1;1,19,1;2,19,0;3,19,0;6,19,0;7,19,0;8,19,3;9,19,3;10,19,0;11,19,0;14,19,0;15,19,0;18,19,0;21,19,0;24,19,0;25,19,0;28,19,0;29,19,0;30,19,3;31,19,3;32,19,0;33,19,0;36,19,0;37,19,0;38,19,1;39,19,1;0,20,1;1,20,1;2,20,0;3,20,0;6,20,0;7,20,0;8,20,3;9,20,3;10,20,0;11,20,0;14,20,0;15,20,0;18,20,0;19,20,3;20,20,3;21,20,0;24,20,0;25,20,0;28,20,0;29,20,0;30,20,3;31,20,3;32,20,0;33,20,0;36,20,0;37,20,0;38,20,1;39,20,1;0,21,1;1,21,1;2,21,0;3,21,0;6,21,0;7,21,0;10,21,0;11,21,0;14,21,0;15,21,0;18,21,0;19,21,3;20,21,3;21,21,0;24,21,0;25,21,0;28,21,0;29,21,0;32,21,0;33,21,0;36,21,0;37,21,0;38,21,1;39,21,1;2,22,0;3,22,0;6,22,0;7,22,0;10,22,0;11,22,0;14,22,0;15,22,0;18,22,0;21,22,0;24,22,0;25,22,0;28,22,0;29,22,0;32,22,0;33,22,0;36,22,0;37,22,0;38,22,1;39,22,1;2,23,0;3,23,0;6,23,0;7,23,0;10,23,0;11,23,0;14,23,0;15,23,0;18,23,0;21,23,0;24,23,0;25,23,0;28,23,0;29,23,0;32,23,0;33,23,0;36,23,0;37,23,0;2,24,0;3,24,0;12,24,1;13,24,1;18,24,0;21,24,0;26,24,1;27,24,1;36,24,0;37,24,0;12,25,1;13,25,1;26,25,1;27,25,1;3,26,0;4,26,0;5,26,0;6,26,0;7,26,0;8,26,0;9,26,0;10,26,0;11,26,0;12,26,0;13,26,0;14,26,0;15,26,0;24,26,0;25,26,0;26,26,0;27,26,0;28,26,0;29,26,0;30,26,0;31,26,0;32,26,0;33,26,0;34,26,0;35,26,0;36,26,0;3,27,0;4,27,0;5,27,0;6,27,0;7,27,0;8,27,0;9,27,0;10,27,0;11,27,0;12,27,0;13,27,0;14,27,0;15,27,0;18,27,0;19,27,0;20,27,0;21,27,0;24,27,0;25,27,0;26,27,0;27,27,0;28,27,0;29,27,0;30,27,0;31,27,0;32,27,0;33,27,0;34,27,0;35,27,0;36,27,0;18,28,0;21,28,0;18,29,0;21,29,0;</Map>
</Maps>
游戏说明:1P 控制键:方向键 + 小键盘 4 发弹,2P 控制键:ASDW+J 键发弹,
游戏还有很多地方未完善,分享出来主要是互相学习,看看有没有高手实现网络对战版 ,并解决 ubuntu 下为什么卡死
补充一下:Ruby 版本 2.3.3,gem 版本 2.5.2,gtk3 版本 3.1.6
第一次发贴,不会排版,也不会直接上传文件,只好这样了。多多包含,谢谢!
最后,上一张游戏画面