コリドール

(2018/01/20 公開)

 ボードゲームです。正しくは "Quoridor" で、Gigamic 社が開発・販売しているゲームです。

 2人で交代しながらプレイしてください。コンピュータとの対戦は(まだ)できません。対戦プログラムを BASIC で書ける気はあまりしませんが、誰かチャレンジしてみます?

プログラムリスト

'  Corridor for Smile Basic on Pasocom Mini
'  2018.1.20. Toshi Nagata
'  UTF-8 encoding
'

dim board[9, 9]   '  bit0-1:下の壁、1=左半分、2=右半分;bit2-3:右の壁、1=上半分、2=下半分
dim cx[2], cy[2]  '  プレイヤー n のコマの現在位置
dim walls[2]      '  プレイヤー n が持っている壁の残り数(初期値は10)

tile = 25     '  コマが乗るタイルの大きさ(正方形)
groove = 5    '  ミゾの幅
posx = -1     '  現在のカーソル位置 (x)
posy = -1     '  現在のカーソル位置 (y)
posn = -1     '  0: コマ,1: 水平壁、2: 垂直壁
cando = 0     '  この位置にコマ/壁を置けるか

'  盤面のサイズ
wid = tile * 9 + groove * 8

'  盤面の左上の座標
xbase = floor((640 - wid) / 2)
ybase = floor((360 - wid) / 2)

'  盤面の初期化
def init_board
  var i, j
  for i = 0 to 8
    for j = 0 to 8
      board[i, j] = 0
    next
  next
  cx[0] = 0
  cy[0] = 4
  cx[1] = 8
  cy[1] = 4
  walls[0] = 10
  walls[1] = 10
end

'  太めの四角形を描く
def gbox2 x1, y1, x2, y2, col
  gbox x1, y1, x2, y2, col
  gbox x1 + 1, y1 + 1, x2 - 1, y2 - 1, col
end

'  (x, y) と (x + dx, y + dy) の間に壁があるか
def has_wall(x, y, dx, dy)
  if x < 0 || x >= 9 || y < 0 || y >= 9 then return 0
  if dx == 1 then
    ' (x, y) の右に壁があるか
    if x < 8 && (board[x, y] and 12) != 0 then return 1 else return 0
  elseif dx == -1 then
    ' (x - 1, y) の右に壁があるか
    if x > 0 && (board[x - 1, y] and 12) != 0 then return 1 else return 0
  elseif dy == 1 then
    ' (x, y) の下に壁があるか
    if y < 8 && (board[x, y] and 3) != 0 then return 1 else return 0
  elseif dy == -1 then
    ' (x, y - 1) の下に壁があるか
    if y > 0 && (board[x, y - 1] and 3) != 0 then return 1 else return 0
  else
    return 0
  endif
end

'  プレーヤー turn が位置 (nx, ny) に動けるか
def is_movable(turn, nx, ny)
  var x, y, dx, dy, ox, oy, b, s
  x = cx[turn]
  y = cy[turn]
  dx = nx - x
  dy = ny - y
  ox = cx[1 - turn]
  oy = cy[1 - turn]
  '  盤面の外?
  if nx < 0 || nx >= 9 || ny < 0 || ny >= 9 then return 0
  '  相手と同じ位置?
  if nx == ox && ny == oy then return 0
  '  今と同じ位置?
  if dx == 0 && dy == 0 then return 0
  if dx != 0 && dy != 0 then
    '  (dx, dy) のどちらも0でない場合
    '  斜め1マスの移動のみ可能
    if abs(dx) != 1 || abs(dy) != 1 then return 0
    ' (x + dx, y) に相手がいて、(x + dx, y) と (x + 2*dx, y) の間に壁がある
    if x + dx == ox && y == oy && has_wall(ox, oy, ox + dx, oy) then return 1
    ' (x, y + dy) に相手がいて、(x, y + dy) と (x, y + dy) の間に壁がある
    if x == ox && y + dy == oy && has_wall(ox, oy, ox, oy + dy) then return 1
    return 0
  elseif dx == 0 then
    ' 3マス以上の移動は不可
    if abs(dy) >= 3 then return 0
    ' 隣に壁があれば不可
    s = sgn(dy)
    if has_wall(x, y, 0, s) then return 0
    ' 1マスの移動は OK
    if abs(dy) == 1 then return 1
    ' 2マスの移動は (x, y + s) に相手がいて、そこと (x, y + dy) の間に壁がなければ OK
    if x == ox && y + s == oy && !has_wall(x, oy, 0, s) then return 1
    return 0
  else   '  dy == 0
    ' 3マス以上の移動は不可
    if abs(dx) >= 3 then return 0
    ' 隣に壁があれば不可
    s = sgn(dx)
    if has_wall(x, y, s, 0) then return 0
    ' 1マスの移動は OK
    if abs(dx) == 1 then return 1
    ' 2マスの移動は (x + s, y) に相手がいて、そこと (x + dx, y) の間に壁がなければ OK
    if x + s == ox && y == oy && !has_wall(ox, y, s, 0) then return 1
    return 0
  endif
end

'  (x, y) の下に横壁を置けるか
def can_place_hwall(x, y)
  var b
  if x < 0 || x >= 8 || y < 0 || y >= 8 then return 0
  b = board[x, y]
  '  すでに壁がある
  if (b and 3) != 0 then return 0
  '  右タイルの下に壁がある
  if (board[x + 1, y] and 3) != 0 then return 0
  '  右に縦壁の上半分がある
  if (b and 12) == 4 then return 0
  return 1
end

'  (x, y) の右に縦壁を置けるか
def can_place_vwall(x, y)
  var b
  if x < 0 || x >= 8 || y < 0 || y >= 8 then return 0
  b = board[x, y]
  '  すでに壁がある
  if (b and 12) != 0 then return 0
  '  下タイルの右に壁がある
  if (board[x, y + 1] and 12) != 0 then return 0
  '  下に横壁の左半分がある
  if (b and 3) == 1 then return 0
  return 1
end

'  横壁を置く/消す
def place_hwall x, y, flag
  if flag then
    board[x, y] = board[x, y] or 1
    board[x + 1, y] = board[x + 1, y] or 2
  else
    board[x, y] = board[x, y] and 254
    board[x + 1, y] = board[x + 1, y] and 253
  endif
end

'  縦壁を置く/消す
def place_vwall x, y, flag
  if flag then
    board[x, y] = board[x, y] or 4
    board[x, y + 1] = board[x, y + 1] or 8
  else
    board[x, y] = board[x, y] and 251
    board[x, y + 1] = board[x, y + 1] and 247
  endif
end

'  
def clear_marks
  var x, y
  for x = 0 to 8
    for y = 0 to 8
      board[x, y] = board[x, y] and 15
    next
  next
end

'  ゴールインは可能か
'  (現在のコマの位置からシード・フィル法で塗りつぶしを行い、ゴールラインに達するかどうか調べる)
dim bufx[45], bufy[45]
def can_goal(turn)
  var x, y, st, ed, xl, xr, b, ret, yy, bb
  '  いったんマークをすべて消す
  clear_marks
  x = cx[turn]
  y = cy[turn]
  bufx[0] = x
  bufy[0] = y
  st = 0
  ed = 1
  ret = 0
  while st != ed
    '  シードを取り出す
    x = bufx[st]
    y = bufy[st]
    st = (st + 1) mod 45
    '  マーク済みならスキップ
    b = board[x, y]
    if (b and 16) != 0 then
      continue
    endif
    '  現在位置をマーク
    board[x, y] = b or 16
    '  左に行けるところまで行く
    xl = x
    while xl > 0
      xl = xl - 1
      b = board[xl, y]
      if (b and 12) != 0 then
        xl = xl + 1
        break
      endif
      board[xl, y] = b or 16
    wend
    '  右に行けるところまで行く
    xr = x
    b = board[xr, y]
    while xr < 8
      if (b and 12) != 0 then break
      xr = xr + 1
      b = board[xr, y]
      board[xr, y] = (b or 16)
    wend
    if (turn == 0 && xr == 8) || (turn == 1 && xl == 0) then
      ret = 1   '  ゴール可能
      break     '  これ以上処理する必要なし
    endif
    ' 一つ上/一つ下のラインで、xl..xr から行けるところを探す
    for dy = -1 to 1 step 2
      yy = y + dy
      if yy < 0 || yy >= 9 then continue
      x = xl
      while x <= xr
        b = board[x, yy]
        if (b and 16) != 0 then @next  '  処理済み
        if dy == -1 then
          if (b and 3) != 0 then @next  '  上に行けない
        else
          if (board[x, y] and 3) != 0 then @next  '  下に行けない
        endif
        ' 1つ上/下に行けて、かつ未処理
        ' この位置から右に行けるところまで行く(ただし xr が限度)
        while x < xr
          if (b and 12) != 0 then break
          x = x + 1
          b = board[x, yy]
        wend
        ' (x, yy) をシードとして登録
        bufx[ed] = x
        bufy[ed] = yy
        ed = (ed + 1) mod 45
        '  次の x はここの右隣から探す
    @next: x = x + 1
      wend
    next
  wend
  '  ret == 1 ならマークを消す
  if ret == 1 then
    for x = 0 to 8
      for y = 0 to 8
        board[x, y] = board[x, y] and 15
      next
    next
  endif
  return ret
end

'  画面を更新する
def display turn
  var i, j, k, x, y, n, col, ccol, tcol
  gfill 0, ybase, 639, ybase + wid - 1, rgb(0, 0, 0)
  '  手持ちの壁の数を表示
  for i = 0 to 1
    for n = 0 to walls[i] - 1
      if i == 0 then
        x = xbase - (tile * 3 + groove * 2 - 2)
      else
        x = xbase + wid + (tile + groove)
      endif
      y = ybase + 10 + (tile + groove - 3) * n
      gfill x, y, x + tile * 2 + groove - 3, y + groove - 1, #yellow
    next
  next
  clear_marks
  '  「次の手」が打てるかどうかを判断
  if posn == 0 then
    '  コマが動かせるか
    cando = is_movable(turn, posx, posy)
  elseif posn == 1 then
    '  横壁が置けるか
    cando = can_place_hwall(posx, posy)
    if cando then
      '  ゴールを妨げないか
      place_hwall posx, posy, 1
      cando = can_goal(turn) && can_goal(1 - turn)
      place_hwall posx, posy, 0
    endif
  else
    '  縦壁が置けるか
    cando = can_place_vwall(posx, posy)
    if cando then
      '  ゴールを妨げないか
      place_vwall posx, posy, 1
      cando = can_goal(turn) && can_goal(1 - turn)
      place_vwall posx, posy, 0
    endif
  endif
  if cando then
    if turn == 0 then ccol = #red else ccol = #blue
  else
    ccol = rgb(180, 180, 180)
  endif
  for i = 0 to 8
    for j = 0 to 8
      x = xbase + (tile + groove) * i
      y = ybase + (tile + groove) * j
      if posn != 0 && (board[i, j] and 16) != 0 then
        tcol = rgb(0, 84, 12)    '  ゴールに到達できないタイルは色を変える
      else
        tcol = rgb(144, 84, 12)
      endif
      gfill x, y, x + tile - 1, y + tile - 1, tcol
      if posn == 0 && posx == i && posy == j then
        gbox2 x, y, x + tile - 1, y + tile - 1, ccol
      endif
      n = board[i, j] and 3
      if n == 1 then
        gfill x + 2, y + tile, x + tile + groove - 1, y + tile + groove - 1, rgb(255, 255, 0)
      elseif n == 2 then
        gfill x, y + tile, x + tile - 3, y + tile + groove - 1, rgb(255, 255, 0)
      endif
      if posn == 1 && posx == i && posy == j then
        gbox2 x + 2, y + tile, x + tile * 2 + groove - 3, y + tile + groove - 1, ccol
      endif
      n = board[i, j] and 12
      if n == 4 then
        gfill x + tile, y + 2, x + tile + groove - 1, y + tile + groove - 1, rgb(255, 255, 0)
      elseif n == 8 then
        gfill x + tile, y, x + tile + groove - 1, y + tile - 3, rgb(255, 255, 0)
      endif
      if posn == 2 && posx == i && posy == j then
        gbox2 x + tile, y + 2, x + tile + groove - 1, y + tile * 2 + groove - 3, ccol
      endif
      x = x + floor(tile / 2)
      y = y + floor(tile / 2)
      for k = 0 to 1
        if k == 0 then col = #red else col = #blue
        if i == cx[k] && j == cy[k] then
          gcircle x, y, 7, col
          gpaint x, y, col
        endif
      next
    next
  next
end

def move(turn)
  var col1, col2, lim
  if turn == 0 then
    col1 = rgb(200, 0, 0)
    col2 = rgb(0, 0, 0)
  else
    col1 = rgb(0, 0, 0)
    col2 = rgb(0, 0, 200)
  endif
  gbox2 0, ybase, xbase - 1, ybase + wid - 1, col1
  gbox2 xbase + wid, ybase, 639, ybase + wid - 1, col2
  posn = 0
  posx = cx[turn]
  posy = cy[turn]
  while 1
    display turn
    repeat : in$ = inkey$() : until len(in$) > 0
    ch = asc(in$)
    if posn == 0 then lim = 9 else lim = 8
    if ch == 32 then
      if walls[turn] > 0 then
        posn = (posn + 1) mod 3
        if posn != 0 then
          '  壁は (0, 0)-(7, 7) の範囲にしか置けない
          if posx == 8 then posx = 7
          if posy == 8 then posy = 7
        endif
      endif
    elseif ch == 28 then
      posx = (posx + 1) mod lim
    elseif ch == 29 then
      posx = (posx - 1 + lim) mod lim
    elseif ch == 30 then
      posy = (posy - 1 + lim) mod lim
    elseif ch == 31 then
      posy = (posy + 1) mod lim
    elseif ch == 13 && cando then
      if posn == 0 then
        '  コマを動かす
        cx[turn] = posx
        cy[turn] = posy
        if (turn == 0 && posx == 8) || (turn == 1 && posx == 0) then
          return 1   '  ゴールイン
        endif
      elseif posn == 1 then
        '  横壁を置く
        place_hwall posx, posy, 1
        inc walls[turn], -1
      else
        '  縦壁を置く
        place_vwall posx, posy, 1
        inc walls[turn], -1
      endif
      return 0
    endif
  wend
end

cls : gcls
init_board
this_turn = 0
while move(this_turn) == 0
  this_turn = 1 - this_turn
wend
if this_turn == 0 then s$ = "赤" else s$ = "青"
print s$; "がゴールしました!"