Skip to content

Instantly share code, notes, and snippets.

@takehiko
Created June 10, 2013 16:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save takehiko/5750263 to your computer and use it in GitHub Desktop.
Save takehiko/5750263 to your computer and use it in GitHub Desktop.
A tiny combo simulator of Puzzle & Dragons
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
# A tiny combo simulator of Puzzle & Dragons
# by takehikom (http://d.hatena.ne.jp/takehikom/)
# Usage:
# ruby padsim.rb
# ruby padsim.rb 111234 511123 451116 234234 234234
module PadSim
class Table
SYMBOL = {
"火" => :fire,
"水" => :water,
"木" => :wood,
"闇" => :dark,
"光" => :light,
"回" => :recover,
"1" => :fire,
"2" => :water,
"3" => :wood,
"4" => :dark,
"5" => :light,
"6" => :recover,
}
OUTPUT_JP = {
:fire => "火",
:water => "水",
:wood => "木",
:dark => "闇",
:light => "光",
:recover => "回",
:erase => "*",
:other => "?",
}
OUTPUT_ASCII = {
:fire => "1",
:water => "2",
:wood => "3",
:dark => "4",
:light => "5",
:recover => "6",
:erase => "*",
:other => "?"
}
COMBO_JP = "・①②③④⑤⑥⑦⑧⑨⑩".split(//)
COMBO_ASCII = " abcdefghij".split(//)
# 環境変数をもとに出力文字(日本語/ASCII)を設定
if /ja/ =~ ENV["LANG"]
OUTPUT = OUTPUT_JP
COMBO = COMBO_JP
else
OUTPUT = OUTPUT_ASCII
COMBO = COMBO_ASCII
end
end
class Manager
# コンストラクタ
def initialize(param = nil)
if param.nil? || (Array === param && param.empty?)
param = <<EOS
火闇闇水木水
火火火水木水
火光光水木水
木木木木水水
回光光光光光
EOS
end
init_field(param)
@erase_h = Hash.new(0) # 地点 => 消去度合い(0なら消去されない)
@combo_h = Hash.new(0) # 地点 => コンボ番号
@combo_ha = [] # :pos, :neighbor, :cell をキーとするハッシュの配列
@erase_pos_a = nil # 消去される地点の配列.未計算時はnil
@combo_len = -1 # コンボ数.未計算時は-1
end
attr_reader :combo_len
# 消去されるドロップの数
def erase_len
if Array === @erase_pos_a
@erase_pos_a.length
else
-1
end
end
# フィールドの初期化
def init_field(field_str)
field_a = [field_str].flatten.join.gsub(/\s/m, "").split(//)
@field_h = Hash.new
5.times do |y|
6.times do |x|
@field_h[pdc_pos(x, y)] = pdc_sym(field_a.shift || nil)
end
end
end
# 文字列変換
# to_s または to_s(0) : フィールドをそのまま出力
# to_s(1) : 消去処理を加える
# to_s(2) : コンボ処理を加える
def to_s(mode_disp = 0)
s = ""
5.times do |y|
6.times do |x|
if mode_disp == 1 && @erase_h[pdc_pos(x, y)] > 0
c = pdc_char(:erase)
elsif mode_disp == 2 && @combo_h[pdc_pos(x, y)] > 0
c = pdc_combo_char(@combo_h[pdc_pos(x, y)])
else
c = pdc_char(@field_h[pdc_pos(x, y)])
end
s += c
end
s += "\n"
end
s
end
# 処理の開始(フィールドの解析)
# 出力はしない
def start
check_erasable
calc_combo
end
alias :analyze :start
private
# フィールド座標(x, y)を文字列に変換
def pdc_pos(x, y)
[y, x].pack("c*")
end
# 文字列のフィールド座標を配列に戻す(逆変換)
def pdc_depos(s)
s.unpack("c*").reverse
end
# フィールドのドロップをシンボルに変換
def pdc_sym(c)
PadSim::Table::SYMBOL[c.to_s]
end
# フィールドのドロップ(Symbol)を文字に変換
def pdc_char(sym)
PadSim::Table::OUTPUT[sym] || PadSim::Table::OUTPUT[:other]
end
# コンボ番号を文字に変換
def pdc_combo_char(n)
PadSim::Table::COMBO[n]
end
# 妥当なブロックか判定
def pdc_valid?(sym)
case sym
when :fire, :water, :wood, :dark, :light, :recover
true
else
false
end
end
# 消去可能な3つ揃いと関連情報を記録
def mark_erasable(x, y, delta_x, delta_y)
pos_a = []
neighbor_a = []
3.times do |i|
p = pdc_pos(x + delta_x * i, y + delta_y * i)
@erase_h[p] += 1
@erase_pos_a << p
pos_a << p
neighbor_a << p <<
pdc_pos(x + delta_x * i - 1, y + delta_y * i) <<
pdc_pos(x + delta_x * i + 1, y + delta_y * i) <<
pdc_pos(x + delta_x * i, y + delta_y * i - 1) <<
pdc_pos(x + delta_x * i, y + delta_y * i + 1)
end
@erase_pos_a.uniq!
@combo_ha << {:pos => pos_a, :neighbor => neighbor_a.uniq,
:cell => @field_h[pdc_pos(x + delta_x, y + delta_y)]}
end
# 消去可能な縦方向(_v)・横方向(_h)の3つ揃いと関連情報を記録
def mark_erasable_v(x, y); mark_erasable(x, y, 0, 1); end
def mark_erasable_h(x, y); mark_erasable(x, y, 1, 0); end
# 縦方向の消去判定
def check_erasable_v
(5 - 2).times do |y|
6.times do |x|
c = @field_h[pdc_pos(x, y)]
next if !pdc_valid?(c)
next if @field_h[pdc_pos(x, y + 1)] != c
next if @field_h[pdc_pos(x, y + 2)] != c
mark_erasable_v(x, y)
end
end
end
# 横方向の消去判定
def check_erasable_h
5.times do |y|
(6 - 2).times do |x|
c = @field_h[pdc_pos(x, y)]
next if !pdc_valid?(c)
next if @field_h[pdc_pos(x + 1, y)] != c
next if @field_h[pdc_pos(x + 2, y)] != c
mark_erasable_h(x, y)
end
end
end
# 消去判定
def check_erasable
@erase_h = Hash.new(0)
@combo_ha = []
@erase_pos_a = []
check_erasable_v
check_erasable_h
end
# コンボ判定
def calc_combo
# 3つ揃い領域の結合
combo_ha1 = @combo_ha.dup
combo_ha2 = []
until combo_ha1.empty?
combo1 = combo_ha1.shift
combo_ha2 << combo1
combo_ha1.each_with_index do |combo2, i|
if combo1[:cell] == combo2[:cell] &&
!(combo1[:neighbor] & combo2[:pos]).empty?
combo_ha2.pop
combo_ha1.delete_at(i)
combo_ha1 << {:pos => (combo1[:pos] | combo2[:pos]).sort.uniq,
:neighbor => (combo1[:neighbor] | combo2[:neighbor]).uniq,
:cell => combo1[:cell]}
break
end
end
end
@combo_ha = combo_ha2.sort_by {|item| item[:pos].first.to_s}
# コンボ番号の割り振り
@combo_h = Hash.new(0)
i = 1
@combo_ha.each do |p_a|
p_a[:pos].each do |pos|
@combo_h[pos] = i
end
i += 1
end
@combo_len = i - 1
end
end
end
if __FILE__ == $0
pcc = PadSim::Manager.new(ARGV)
puts "[start]"
puts pcc
pcc.analyze
puts "[erase]"
puts pcc.to_s(1)
puts "[combo]"
puts pcc.to_s(2)
puts "[info]"
puts "#{pcc.erase_len} drop(s) are erased by #{pcc.combo_len} combo(s)"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment