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 |