ソースコード:2021年度受賞作品「Titly」

2021年度受賞作品「Titly」を元に、ブラウザ上でRubyグラフィックプログラミングができる「rbCanvas/p5」(https://rbcanvas.net/p5/)に移植しました。そのソースコードの紹介をしています。

main

# 初期設定用のコード (your setup code here)
C_START    = [255, 255,   0, 255]   # 色(スタート地点)
C_GOAL     = [255, 128,   0, 255]   # 色(ゴール地点)
C_STREET   = [255, 255, 255, 255]   # 色(通路)
C_WALL     = [  0, 128,   0, 255]   # 色(壁)
C_OBSTACLE = [  0,   0,   0, 255]   # 色(障害物)

def setup
  createCanvas(400, 400)
  @status     = :READY
  @course     = CourseStatic.new
  angleMode   = DEGREES
  selectCourse
end

# 画面描画用のコード (your draw code here)
def draw
  @course.draw
  check
  showStatus
  showClear    if @status == :CLEAR
  showGameOver if @status == :OVER
end

# コースの選択
def selectCourse
  caption = [
    'コース1(初級)',
    'コース2(中級)',
    'コース3(上級)',
    'コース4(おまけ)'
  ]
  sel = createSelect
  sel.position(4, 4)
  sel.option(caption[0])
  sel.option(caption[1])
  sel.option(caption[2])
  sel.option(caption[3])
  sel.selected(caption[0])
  mySelectEvent = Proc.new {
    @status = :READY
    loop
    case sel.value
    when caption[0]
      @course = CourseStatic.new
    when caption[1]
      @course = CourseRolling.new
    when caption[2]
      @course = CourseMoving.new
    when caption[3]
      @course = CourseCrossing.new
    end
  }
  sel.changed(mySelectEvent)
end

# ゲームオーバーのチェック
def check
  return if @status != :GO!
  mx = mouseX
  my = mouseY

  # キャンバスの外側
  if mx < 0 or mx > width or
     my < 0 or my > height
    @status = :OVER
  end

  # 壁または障害物
  c = get(mx, my)
  if c == C_WALL or c == C_OBSTACLE
    @status = :OVER
  end
end

# マウスクリック
def mouseClicked
  c = get(mouseX, mouseY)
  if c == C_START and @status == :READY  # ゲーム開始
    @status = :GO!
  elsif c == C_GOAL and @status == :GO!  # ゴール到達
    @status = :CLEAR
  elsif @status == :OVER or @status == :CLEAR  # リプレイ
    @status = :READY
    loop
  end
end

course1

# 追加のコード (your supplementary code here)
class CourseStatic
  def draw
    Landmark.wall(0, 0, width, height)
    Landmark.street(  0, 200, 100,  30)
    Landmark.street( 70,  50,  30, 150)
    Landmark.street( 70,  50, 100,  30)
    Landmark.street(150,  50,  30, 300)
    Landmark.street(150, 320, 100,  30)
    Landmark.street(220, 120,  30, 200)
    Landmark.street(220, 100, 100,  30)
    Landmark.street(300, 100,  30, 200)
    Landmark.street(300, 300, 100,  30)
    Landmark.startPoint(0, 200, 30, 30)
    Landmark.goalPoint(370, 300, 30, 30)
  end
end

course2

# 追加のコード (your supplementary code here)
class CourseRolling
  def initialize
    @rolling1 = Rolling.new(140, 215, 120, :R)
    @rolling2 = Rolling.new(280, 215,  80, :L)
  end
  def draw
    Landmark.wall(0, 0, width, height)
    Landmark.street(0, 200, width,  30)
    @rolling1.role(0.01)
    @rolling2.role
    Landmark.startPoint(0, 200, 30, 30)
    Landmark.goalPoint(370, 200, 30, 30)
  end
end

course3

# 追加のコード (your supplementary code here)
class CourseMoving
  def initialize
    @moving1 = Moving.new( 70, 200, 50, :V)
    @moving2 = Moving.new(250, 200, 50, :H)
  end
  def draw
    Landmark.wall(0, 0, width, height)
    Landmark.street(  0, 200, 400, 30)
    Landmark.street(200, 185,  30, 15)
    Landmark.street(260, 230,  20, 15)
    Landmark.wall(100, 220, 30, 10)
    Landmark.wall(140, 200, 20, 15)
    Landmark.wall(170, 220, 30, 10)
    Landmark.wall(330, 210, 30, 10)
    @moving1.move
    @moving2.move(0.1)
    Landmark.startPoint(  0, 200, 30, 30)
    Landmark.goalPoint(370, 200, 30, 30)
  end
end

course4

# 追加のコード (your supplementary code here)
class CourseCrossing
  def initialize
    @movings = []
    @movings << Moving.new( 35, 150, 50, :V)
    @movings << Moving.new( 65, 260, 50, :V, 180)
    @movings << Moving.new( 95, 150, 50, :V)
    @movings << Moving.new(125, 260, 50, :V, 180)
    @movings << Moving.new(155, 150, 50, :V)
    @movings << Moving.new(185, 260, 50, :V, 180)
    @movings << Moving.new(215, 150, 50, :V)
    @movings << Moving.new(245, 260, 50, :V, 180)
    @movings << Moving.new(275, 150, 50, :V)
    @movings << Moving.new(305, 260, 50, :V, 180)
    @movings << Moving.new(335, 150, 50, :V)

    @movings << Moving.new( 35, 250, 50, :V, 270)
    @movings << Moving.new( 65, 150, 80, :V,  90)
    @movings << Moving.new( 95, 250, 50, :V, 270)
    @movings << Moving.new(125, 150, 80, :V,  90)
    @movings << Moving.new(155, 250, 50, :V, 270)
    @movings << Moving.new(185, 150, 80, :V,  90)
    @movings << Moving.new(215, 250, 50, :V, 270)
    @movings << Moving.new(245, 150, 80, :V,  90)
    @movings << Moving.new(275, 250, 50, :V, 270)
    @movings << Moving.new(305, 150, 80, :V,  90)
    @movings << Moving.new(335, 250, 50, :V, 270)
  end
  def draw
    Landmark.wall(0, 0, width, height)
    Landmark.street(  0, 200, width, 30)
    Landmark.street( 35,  60, width - (30 + 5) * 2, height - 60 * 2)
    @movings.each do |m|
      m.move
    end
    Landmark.startPoint(  0, 200, 30, 30)
    Landmark.goalPoint(370, 200, 30, 30)
  end
end

landmark

# 追加のコード (your supplementary code here)
module Landmark

  # スタート地点の表示
  def self.startPoint(x, y, w, h)
    noStroke
    fill(C_START)
    rect(x, y, w, h)
  end

  # ゴール地点の表示
  def self.goalPoint(x, y, w, h)
    noStroke
    fill(C_GOAL)
    rect(x, y, w, h)
  end

  # 壁の表示
  def self.wall(x, y, w, h)
    noStroke
    fill(C_WALL)
    rect(x, y, w, h)
  end

  # 通路の表示
  def self.street(x, y, w, h)
    noStroke
    fill(C_STREET)
    rect(x, y, w, h)
  end
end

message

# 追加のコード (your supplementary code here)
# ステータスの表示
def showStatus
  textSize(30)
  noStroke
  fill("white")
  text(@status, 280, 40)
end

# クリアしたときのメッセージ
def showClear
  textSize(60)
  noStroke
  fill("gold")
  textAlign(CENTER)
  text("Clear!", width / 2, 100)
  if @course.class == CourseCrossing
    fill(255, 255, 255, 128)
    rect(0, 0, width, height)
    a = createElement('a', 'つぎのコンテンツに進もう!');
    a.style('color', '#ff0000');
    a.elt.href = 'https://www.mitaka.ne.jp/'
    a.elt.target = '_parent'
#   a.elt.target = '_blank'
    a.position(70, 420);
    a.style('font-size', '20px');
    %x{
      console.log(a)
      console.log(a.native.elt)
    }
  end
  noLoop
end

# ゲームオーバーのときのメッセージ
def showGameOver
  textSize(60)
  noStroke
  fill("black")
  textAlign(CENTER)
  text("GameOver", width / 2, 100)
  noLoop
end

obstacle

# 追加のコード (your supplementary code here)
class Rolling
  def initialize(x, y, d, dir)
    @x = x
    @y = y
    @d = d
    @dir = dir
    @th = 0
  end
  def role(delta = 0.02)
    push do
      translate(@x, @y)
      if @dir == :R
        @th -= delta
      else
        @th += delta
      end
      rotate(-@th)
      noStroke
      fill(C_STREET)
      ellipse(0, 0, @d, @d)
      fill(C_OBSTACLE)
      rect(-@d / 2, -8, @d, 16)
      rect(-8, -@d / 2, 16, @d)
    end
  end
end

# 移動する障害物の表示
class Moving
  def initialize(x, y, d, dir, th = 0)
    @x = x
    @y = y
    @d = d
    @dir = dir
    @th = th
  end
  def move(delta = 0.06)
    @th += delta
    push do
      if @dir == :V
        x = @x
        y = @y + @d * sin(@th)
      else
        x = @x + @d * cos(@th)
        y = @y
      end
      translate(x, y)
      noStroke
      fill(C_OBSTACLE)
      rect(0, 0, 30, 30)
    end
  end
end