I found source codes of the famous and my favorite game.
https://computerarcheology.com/Arcade/SpaceInvaders/Code.html
I'm trying to move it by Ruby on DXOpal.
I found source codes of the famous and my favorite game.
https://computerarcheology.com/Arcade/SpaceInvaders/Code.html
I'm trying to move it by Ruby on DXOpal.
| class Charactor | |
| attr_reader :pattern | |
| attr_reader :vsize | |
| attr_reader :image | |
| def initialize pat, vsize = 1 | |
| @pattern = pat.each_line.map do |l| | |
| l.split(" ")[1..-1].map{|e| e.to_i(16)} | |
| end.flatten | |
| @vsize = vsize | |
| @width = @pattern.size / @vsize | |
| @height = vsize * 8 | |
| @image = Image.new(@width, @height, [255, 0, 0, 0]) | |
| idx = 0 | |
| @width.times do |ix| | |
| x = ix | |
| y = @height - 1 | |
| @vsize.times do |iy| | |
| v = @pattern[idx] | |
| 8.times do |b| | |
| if v & 1 == 0 | |
| @image[x, y] = [0, 0, 0, 0] | |
| else | |
| @image[x, y] = [255, 255, 255, 255] | |
| end | |
| v >>= 1 | |
| y -= 1 | |
| end | |
| idx += 1 | |
| end | |
| end | |
| end | |
| def sub_charactor offset, length | |
| pat = pattern[offset, length] | |
| Charactor.new("0000: " + pat.map{|c| c.to_s(16).upcase.rjust(2, '0') }.join(" ")) | |
| end | |
| def self.shield | |
| @shield ||= begin | |
| pat = <<EOS | |
| 1D20: FF 0F FF 1F FF 3F FF 7F FF FF FC FF F8 FF F0 FF F0 FF F0 FF F0 FF | |
| 1D36: F0 FF F0 FF F0 FF F8 FF FC FF FF FF FF FF FF 7F FF 3F FF 1F FF 0F | |
| EOS | |
| self.new pat, 2 | |
| end | |
| end | |
| def self.flying_saucer | |
| @flying_saucer ||= begin | |
| pat = <<EOS | |
| 1D64: 00 00 00 00 04 0C 1E 37 3E 7C 74 7E 7E 74 7C 3E 37 1E 0C 04 00 00 00 00 | |
| EOS | |
| self.new pat | |
| end | |
| end | |
| def self.flying_saucer_explosion | |
| @flying_saucer_explosion ||= begin | |
| pat = <<EOS | |
| 1D7C: 00 22 00 A5 40 08 98 3D B6 3C 36 1D 10 48 62 B6 1D 98 08 42 90 08 00 00 | |
| EOS | |
| self.new pat | |
| end | |
| end | |
| def self.alians | |
| @alians ||= begin | |
| pat = <<EOS | |
| 1C00: 00 00 39 79 7A 6E EC FA FA EC 6E 7A 79 39 00 00 | |
| 1C10: 00 00 00 78 1D BE 6C 3C 3C 3C 6C BE 1D 78 00 00 | |
| 1C20: 00 00 00 00 19 3A 6D FA FA 6D 3A 19 00 00 00 00 | |
| 1C30: 00 00 38 7A 7F 6D EC FA FA EC 6D 7F 7A 38 00 00 | |
| 1C40: 00 00 00 0E 18 BE 6D 3D 3C 3D 6D BE 18 0E 00 00 | |
| 1C50: 00 00 00 00 1A 3D 68 FC FC 68 3D 1A 00 00 00 00 | |
| EOS | |
| self.new pat | |
| end | |
| end | |
| def self.alian point, pat | |
| x = [10, 20, 30].index point | |
| y = [:a, :b].index pat | |
| return nil unless x && y | |
| alians.sub_charactor(16 * (y * 3 + x), 16) | |
| end | |
| def self.charactor code | |
| @fonts ||= begin | |
| pat ||= <<EOS | |
| 1E00: 00 1F 24 44 24 1F 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1E08: 00 7F 49 49 49 36 00 00 ; *****... *******. .*****.. *******. *******. *******. .*****.. *******. | |
| 1E10: 00 3E 41 41 41 22 00 00 ; ..*..*.. *..*..*. *.....*. *.....*. *..*..*. ...*..*. *.....*. ...*.... | |
| 1E18: 00 7F 41 41 41 3E 00 00 ; ..*...*. *..*..*. *.....*. *.....*. *..*..*. ...*..*. *.....*. ...*.... | |
| 1E20: 00 7F 49 49 49 41 00 00 ; ..*..*.. *..*..*. *.....*. *.....*. *..*..*. ...*..*. *.*...*. ...*.... | |
| 1E28: 00 7F 48 48 48 40 00 00 ; *****... .**.**.. .*...*.. .*****.. *.....*. ......*. ***...*. *******. | |
| 1E30: 00 3E 41 41 45 47 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1E38: 00 7F 08 08 08 7F 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1E40: 00 00 41 7F 41 00 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1E48: 00 02 01 01 01 7E 00 00 ; ........ .*...... *******. *******. *******. *******. .*****.. *******. | |
| 1E50: 00 7F 08 14 22 41 00 00 ; *.....*. *....... ...*.... *....... .....*.. ....*... *.....*. ...*..*. | |
| 1E58: 00 7F 01 01 01 01 00 00 ; *******. *....... ..*.*... *....... ...**... ...*.... *.....*. ...*..*. | |
| 1E60: 00 7F 20 18 20 7F 00 00 ; *.....*. *....... .*...*.. *....... .....*.. ..*..... *.....*. ...*..*. | |
| 1E68: 00 7F 10 08 04 7F 00 00 ; ........ .******. *.....*. *....... *******. *******. .*****.. ....**.. | |
| 1E70: 00 3E 41 41 41 3E 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1E78: 00 7F 48 48 48 30 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1E80: 00 3E 41 45 42 3D 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1E88: 00 7F 48 4C 4A 31 00 00 ; .*****.. *******. .*..**.. ......*. .******. ..*****. *******. **...**. | |
| 1E90: 00 32 49 49 49 26 00 00 ; *.....*. ...*..*. *..*..*. ......*. *....... .*...... .*...... ..*.*... | |
| 1E98: 00 40 40 7F 40 40 00 00 ; *.*...*. ..**..*. *..*..*. *******. *....... *....... ..**.... ...*.... | |
| 1EA0: 00 7E 01 01 01 7E 00 00 ; .*....*. .*.*..*. *..*..*. ......*. *....... .*...... .*...... ..*.*... | |
| 1EA8: 00 7C 02 01 02 7C 00 00 ; *.****.. *...**.. .**..*.. ......*. .******. ..*****. *******. **...**. | |
| 1EB0: 00 7F 02 0C 02 7F 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1EB8: 00 63 14 08 14 63 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1EC0: 00 60 10 0F 10 60 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1EC8: 00 43 45 49 51 61 00 00 ; .....**. **....*. .*****.. ........ **...*.. .*....*. ..**.... .*..***. | |
| 1ED0: 00 3E 45 49 51 3E 00 00 ; ....*... *.*...*. *.*...*. *....*.. *.*...*. *.....*. ..*.*... *...*.*. | |
| 1ED8: 00 00 21 7F 01 00 00 00 ; ****.... *..*..*. *..*..*. *******. *..*..*. *..*..*. ..*..*.. *...*.*. | |
| 1EE0: 00 23 45 49 49 31 00 00 ; ....*... *...*.*. *...*.*. *....... *..*..*. *..**.*. *******. *...*.*. | |
| 1EE8: 00 42 41 49 59 66 00 00 ; .....**. *....**. .*****.. ........ *...**.. .**..**. ..*..... .***..*. | |
| 1EF0: 00 0C 14 24 7F 04 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1EF8: 00 72 51 51 51 4E 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1F00: 00 1E 29 49 49 46 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1F08: 00 40 47 48 50 60 00 00 ; .****... ......*. .**.**.. *...**.. ...*.... ........ ........ ..*.*... | |
| 1F10: 00 36 49 49 49 36 00 00 ; *..*.*.. ***...*. *..*..*. *..*..*. ..*.*... *.....*. ........ ..*.*... | |
| 1F18: 00 31 49 49 4A 3C 00 00 ; *..*..*. ...*..*. *..*..*. *..*..*. .*...*.. .*...*.. ........ ..*.*... | |
| 1F20: 00 08 14 22 41 00 00 00 ; *..*..*. ....*.*. *..*..*. .*.*..*. *.....*. ..*.*... ........ ..*.*... | |
| 1F28: 00 00 41 22 14 08 00 00 ; .**...*. .....**. .**.**.. ..****.. ........ ...*.... ........ ..*.*... | |
| 1F30: 00 00 00 00 00 00 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1F38: 00 14 14 14 14 14 00 00 ; ........ ........ ........ ........ ........ ........ ........ ........ | |
| 1F40: 00 22 14 7F 14 22 00 00 ; ........ ........ | |
| 1F48: 00 03 04 78 04 03 00 00 ; .*...*.. **...... | |
| ; ..*.*... ..*..... | |
| ; *******. ...****. | |
| ; ..*.*... ..*..... | |
| ; .*...*.. **...... | |
| ; ........ ........ | |
| ; ........ ........ | |
| ; ........ ........ | |
| ; ........ ........ | |
| ; ........ ........ | |
| ; ........ ........ | |
| ; ........ ........ | |
| ; ........ ........ | |
| ; ........ ........ | |
| ; ........ ........ | |
| 1FC0: 00 20 40 4D 50 20 00 00 ; 38:"?" | |
| ; ........ ........ | |
| ; ........ ........ | |
| ; ........ ........ | |
| ; ........ ........ | |
| ; ........ ........ | |
| ; ........ ........ | |
| 1FF8: 00 08 08 08 08 08 00 00 ; 3F:"-" | |
| EOS | |
| pat.each_line.select{|l| l.include?(";")}.map do |l| | |
| self.new l.split(/;/).first | |
| end | |
| end | |
| @fonts[code] | |
| end | |
| end |
| require_remote 'vram.rb' | |
| require_remote 'charactor.rb' | |
| require_remote 'message.rb' | |
| class Uses | |
| attr_reader :inv | |
| MODE_BEGIN_WAIT_1 = 0 | |
| MODE_WAIT_1 = 1 | |
| MODE_BEGIN_PLAY = 2 | |
| MODE_PLAY = 3 | |
| MODE_BEGIN_SPACE_INVADERS = 4 | |
| MODE_SPACE_INVADERS = 5 | |
| MODE_BEGIN_WAIT_2 = 6 | |
| MODE_WAIT_2 = 7 | |
| MODE_BEGIN_SCORE_ADVANCE_TABLE = 8 | |
| MODE_SCORE_ADVANCE_TABLE = 9 | |
| MODE_BEGIN_WAIT_3 = 10 | |
| MODE_WAIT_3 = 11 | |
| MODE_BEGIN_MYSTERY_SCORE = 12 | |
| MODE_MYSTERY_SCORE = 13 | |
| MODE_BEGIN_30_SCORE = 14 | |
| MODE_30_SCORE = 15 | |
| MODE_BEGIN_20_SCORE = 16 | |
| MODE_20_SCORE = 17 | |
| MODE_BEGIN_10_SCORE = 18 | |
| MODE_10_SCORE = 19 | |
| MODE_BEGIN_WAIT_4 = 20 | |
| MODE_WAIT_4 = 21 | |
| MODE_END = 22 | |
| def initialize | |
| @inv = false | |
| reset | |
| end | |
| def step game | |
| now = Time.now | |
| return true unless @duration == 0 || now - @start_at >= @duration | |
| case @mode | |
| when MODE_BEGIN_WAIT_1, MODE_BEGIN_WAIT_2, MODE_BEGIN_WAIT_3, MODE_BEGIN_WAIT_4 | |
| begin_wait 0x40 | |
| @mode += 1 | |
| when MODE_BEGIN_WAIT_4 | |
| begin_wait 0x80 | |
| @mode += 1 | |
| when MODE_WAIT_1, MODE_WAIT_2, MODE_WAIT_3, MODE_WAIT_4 | |
| begin_wait 0.0 | |
| @mode += 1 | |
| when MODE_BEGIN_PLAY | |
| mes = inv ? Message.play_inv : Message.play | |
| begin_to_show_message game, mes, 0x3017 | |
| @mode += 1 | |
| when MODE_BEGIN_SPACE_INVADERS | |
| mes = Message.space_invaders | |
| begin_to_show_message game, mes, 0x2B14 | |
| @mode += 1 | |
| when MODE_BEGIN_SCORE_ADVANCE_TABLE | |
| mes = Message.score_advance_table | |
| mes.draw *game.conv_xy(0x2810), game.vram.image | |
| [ | |
| [Charactor.flying_saucer.sub_charactor(4, 16), 0x2C0E], | |
| [Charactor.alian(30, :a), 0x2C0C], | |
| [Charactor.alian(20, :b), 0x2C0A], | |
| [Charactor.alian(10, :a), 0x2C08], | |
| ].each do |char, address| | |
| x, y = game.conv_xy(address) | |
| game.vram.image.draw(x, y, char.image) | |
| end | |
| begin_wait 0.0 | |
| @mode += 1 | |
| when MODE_PLAY, MODE_SPACE_INVADERS, MODE_SCORE_ADVANCE_TABLE, | |
| MODE_MYSTERY_SCORE, MODE_10_SCORE, MODE_20_SCORE, MODE_30_SCORE | |
| c = @codes[@code_ptr] | |
| if c == nil | |
| @mode += 1 | |
| else | |
| @code_ptr += 1 | |
| img = Charactor.charactor(c).image | |
| game.vram.image.draw(@x, @y, img) | |
| @x += 8 | |
| begin_wait 7 | |
| end | |
| when MODE_BEGIN_MYSTERY_SCORE | |
| mes = Message.mystery | |
| begin_to_show_message game, mes, 0x2E0E | |
| @mode += 1 | |
| when MODE_BEGIN_30_SCORE | |
| mes = Message.thirty_points | |
| begin_to_show_message game, mes, 0x2E0C | |
| @mode += 1 | |
| when MODE_BEGIN_20_SCORE | |
| mes = Message.twenty_points | |
| begin_to_show_message game, mes, 0x2E0A | |
| @mode += 1 | |
| when MODE_BEGIN_10_SCORE | |
| mes = Message.ten_points | |
| begin_to_show_message game, mes, 0x2E08 | |
| @mode += 1 | |
| when MODE_END | |
| @inv = !@inv | |
| @mode = MODE_BEGIN_WAIT_1 | |
| return false | |
| else | |
| return false | |
| end | |
| true | |
| end | |
| private | |
| def reset | |
| @mode = MODE_BEGIN_WAIT_1 | |
| begin_wait 0.0 | |
| end | |
| def begin_wait count | |
| duration = count.to_f / 0x40.to_f | |
| @start_at = Time.now | |
| @duration = duration | |
| end | |
| def begin_to_show_message game, mes, address | |
| @codes = mes.codes | |
| @code_ptr = 0 | |
| @x, @y = game.conv_xy(address) | |
| end | |
| end | |
| class Game | |
| attr_reader :width, :height | |
| attr_reader :scores, :credit | |
| attr_reader :mode | |
| attr_reader :uses | |
| attr_reader :vram | |
| PLAYER_1 = 0 | |
| PLAYER_2 = 1 | |
| HIGH_SCORE = 2 | |
| MODE_INIT = 0 | |
| MODE_USES = 1 | |
| def initialize | |
| @width = 244 | |
| @height = 256 | |
| @scores = [0] * 3 | |
| @credit = 0 | |
| @mode = MODE_INIT | |
| @uses = Uses.new | |
| reset | |
| end | |
| def image | |
| @vram.image | |
| end | |
| def step | |
| case mode | |
| when MODE_INIT | |
| draw_score | |
| draw_credit | |
| @mode = MODE_USES | |
| when MODE_USES | |
| r = @uses.step self | |
| unless r | |
| reset | |
| @mode = MODE_INIT unless r | |
| end | |
| end | |
| end | |
| def conv_xy address | |
| y = 32 * 8 - ((address - 0x2400) % 32) * 8# + 8 | |
| x = ((address - 0x2400) / (32 * 8)).to_i * 8 + 8 | |
| [x, y] | |
| end | |
| private | |
| def draw_score | |
| Message.score_titles.draw(*conv_xy(0x241E), @vram.image) | |
| draw_number 0x2F1C, scores[PLAYER_1] | |
| draw_number 0x391C, scores[PLAYER_2] | |
| draw_number 0x271C, scores[HIGH_SCORE] | |
| end | |
| def draw_credit | |
| Message.credit.draw(*conv_xy(0x3501), @vram.image) | |
| draw_number 0x3C01, credit, 2 | |
| end | |
| def draw_number address, number, digits=5 | |
| codes = number.to_s.rjust(digits, '0').split(//).map{|e| e.to_i + 0x1a} | |
| x, y = conv_xy address | |
| codes.each do |c| | |
| img = Charactor.charactor(c).image | |
| @vram.image.draw(x, y, img) | |
| x += 8 | |
| end | |
| end | |
| def reset | |
| @vram = VRam.new(@width, @height) | |
| reset_shields | |
| end | |
| def reset_shields | |
| @shields = 4.times.map{|i| Charactor.shield.dup } | |
| end | |
| end |
| require 'dxopal' | |
| require_remote 'game.rb' | |
| include DXOpal | |
| class FpsMeasure | |
| attr_reader :fps, :game | |
| def initialize | |
| @from = Time.new | |
| @fps = 0 | |
| end | |
| def measure | |
| now = Time.new | |
| @fps = 1.0 / (now - @from) | |
| @from = now | |
| end | |
| end | |
| @game = Game.new | |
| Window.load_resources do | |
| @fps_meas = FpsMeasure.new | |
| Window.bgcolor = C_BLACK | |
| Window.width = @game.width | |
| Window.height = @game.height + 8 * 2 + 20 | |
| @fps_meas.measure | |
| Window.loop do | |
| @game.step | |
| Window.draw(0, 0, @game.image) | |
| @fps_meas.measure | |
| Window.draw_font(0, @game.height + 8, "FPS #{"%.1f" % @fps_meas.fps}", Font.default, color: C_WHITE) | |
| end | |
| end | |
| require_remote 'charactor.rb' | |
| class Message | |
| attr_reader :codes | |
| def initialize codes | |
| @codes = codes | |
| end | |
| def self.score_titles | |
| @score_titles ||= begin | |
| pat = <<EOS | |
| 1AE4: 26 12 02 0E 11 04 24 1B 25 26 07 08 | |
| 1AF0: 3F 12 02 0E 11 04 26 12 02 0E 11 04 | |
| 1AFC: 24 1C 25 26 | |
| EOS | |
| codes = pat.each_line.map do |l| | |
| l.strip.split(/ /)[1..-1].map{|l| l.to_i(16)} | |
| end.flatten | |
| self.new codes | |
| end | |
| end | |
| def self.credit | |
| @credit ||= begin | |
| pat = <<EOS | |
| 1FA9: 02 11 04 03 08 13 26 | |
| EOS | |
| codes = pat.each_line.map do |l| | |
| l.strip.split(/ /)[1..-1].map{|l| l.to_i(16)} | |
| end.flatten | |
| self.new codes | |
| end | |
| end | |
| def self.play | |
| @play ||= begin | |
| pat = <<EOS | |
| 1DAB: 0F 0B 00 18 ; "PLAY" with normal Y | |
| EOS | |
| codes = pat.each_line.map do |l| | |
| l.strip.split(";").first.split(/ /)[1..-1].map{|l| l.to_i(16)} | |
| end.flatten | |
| self.new codes | |
| end | |
| end | |
| def self.play_inv | |
| @play_inv ||= begin | |
| pat = <<EOS | |
| 1CFA: 0F 0B 00 29 ; "PLAy" with an upside down 'Y' for splash screen | |
| EOS | |
| codes = pat.each_line.map do |l| | |
| l.strip.split(";").first.split(/ /)[1..-1].map{|l| l.to_i(16)} | |
| end.flatten | |
| self.new codes | |
| end | |
| end | |
| def self.space_invaders | |
| @space_invaders ||= begin | |
| pat = <<EOS | |
| 1DAF: 12 0F 00 02 04 26 26 08 0D 15 00 03 04 11 12 | |
| EOS | |
| codes = pat.each_line.map do |l| | |
| l.strip.split(";").first.split(/ /)[1..-1].map{|l| l.to_i(16)} | |
| end.flatten | |
| self.new codes | |
| end | |
| end | |
| def self.score_advance_table | |
| @score_advance_table ||= begin | |
| pat = <<EOS | |
| 1CA3: 28 12 02 0E 11 04 26 00 | |
| 1CAB: 03 15 00 0D 02 04 26 13 | |
| 1CB3: 00 01 0B 04 28 | |
| EOS | |
| codes = pat.each_line.map do |l| | |
| l.strip.split(";").first.split(/ /)[1..-1].map{|l| l.to_i(16)} | |
| end.flatten | |
| self.new codes | |
| end | |
| end | |
| def self.mystery | |
| @mystery ||= begin | |
| pat = <<EOS | |
| 1DE0: 27 38 26 0C 18 12 13 04 11 18 ; "=? MYSTERY" | |
| EOS | |
| codes = pat.each_line.map do |l| | |
| l.strip.split(";").first.split(/ /)[1..-1].map{|l| l.to_i(16)} | |
| end.flatten | |
| self.new codes | |
| end | |
| end | |
| def self.thirty_points | |
| @thirty_points ||= begin | |
| pat = <<EOS | |
| 1DEA: 27 1D 1A 26 0F 0E 08 0D 13 12 ; "=30 POINTS" | |
| EOS | |
| codes = pat.each_line.map do |l| | |
| l.strip.split(";").first.split(/ /)[1..-1].map{|l| l.to_i(16)} | |
| end.flatten | |
| self.new codes | |
| end | |
| end | |
| def self.twenty_points | |
| @twenty_points ||= begin | |
| pat = <<EOS | |
| 1DF4: 27 1C 1A 26 0F 0E 08 0D 13 12 ; "=20 POINTS" | |
| EOS | |
| codes = pat.each_line.map do |l| | |
| l.strip.split(";").first.split(/ /)[1..-1].map{|l| l.to_i(16)} | |
| end.flatten | |
| self.new codes | |
| end | |
| end | |
| def self.ten_points | |
| @ten_points ||= begin | |
| pat = <<EOS | |
| 1C99: 27 1B 1A 26 0F 0E 08 0D 13 12 ; "=10 POINTS" | |
| EOS | |
| codes = pat.each_line.map do |l| | |
| l.strip.split(";").first.split(/ /)[1..-1].map{|l| l.to_i(16)} | |
| end.flatten | |
| self.new codes | |
| end | |
| end | |
| def draw x, y, image | |
| draw_with_duration x, y, image | |
| end | |
| def draw_with_duration x, y, image, duration = 0 | |
| codes.each do |c| | |
| img = Charactor.charactor(c).image | |
| image.draw(x, y, img) | |
| x += 8 | |
| sleep(duration) unless duration == 0 | |
| end | |
| end | |
| end | |
| class VRam | |
| attr_reader :width, :height, :image | |
| def initialize(width, height) | |
| @width = width | |
| @height = height | |
| @vram_size = @width / 8 * @height | |
| @vram = Array.new(@vram_size, 0) | |
| @image = Image.new(@width, @height, [255, 0, 0, 0]) | |
| end | |
| def draw(w) | |
| w.draw(0, 0, @image) | |
| end | |
| end |