← 都来码 / Dolaima← 例程索引fortran/app/sokoban.f90原文

fortran/app/sokoban.f90 · 11601 字节 · 下载原文

! 推箱子 —— 用 **Fortran** 写的手机游戏

! Sokoban -- a mobile game written in **Fortran**

!

! 玩法:方向键推箱子,把两个箱子都推到目标点上即过关(回车重开、ESC 退出)。

! Gameplay: push the boxes with the arrow keys; get both boxes onto the target cells to win (Enter restarts, ESC exits).

! 箱子只能推、不能拉;推到墙或另一个箱子上就推不动。

! Boxes can only be pushed, never pulled; pushing into a wall or another box does not move them.

!

! 与前面几份刻意不同:这份是**双数组状态 + 移动规则 + 过关判定**,

! Deliberately different from the earlier files: this one is **two-array state + movement rules + win detection**,

! 一帧里没有任何连续位移 —— 照的是「离散格子 + 规则表」这条路径。

! with no continuous motion within a frame -- it exercises the "discrete cells + rule table" path.

! 也刻意不画数字:`ui_2d_text` 要字符串,而 Fortran 没有现成的数字转字符串路径,

! It also deliberately draws no digits: `ui_2d_text` needs a string, and Fortran has no ready-made number-to-string path,

! 所以状态一律用**颜色与形状**表达(目标=空心框、箱子=实心方块、箱子归位=变绿)。

! so state is expressed purely with **color and shape** (target = hollow frame, box = solid square, box in place = turns green).

!

! ◆ 手机那套 UI

! ◆ The mobile UI set

!

! 开窗 / 绘图 / 输入 / 定时器是 C 写的(`Lib/shared/src/vmlui.c` → `vmlui.vml`),

! Window opening / drawing / input / timers are written in C (`Lib/shared/src/vmlui.c` -> `vmlui.vml`),

! 由 `vmltool.config.xml` 的 `<Language Name="fortran" Libs="vmlui.vml">` 挂上来。

! pulled in by `<Language Name="fortran" Libs="vmlui.vml">` in `vmltool.config.xml`.

!

! ◆ 写法要求(沿用 corpus/fortran/skel.f90 的两条)

! ◆ Style requirements (the two from corpus/fortran/skel.f90)

!

!   · `call ui_2d_rect(...)` 会被编成 `CALL sub_ui_rect`,而链接器只剥

!   · `call ui_2d_rect(...)` compiles to `CALL sub_ui_rect`, and the linker strips only

!     func_/word_/method_/var_ 四种前缀、**不含 sub_** ⇒ 碰不到 UI 库。

!     the four prefixes func_/word_/method_/var_, **not sub_** => it never reaches the UI library.

!     所以一律写成**函数调用表达式** `k = ui_2d_rect(...)`(编成 func_ui_rect)。

!     So always write a **function-call expression** `k = ui_2d_rect(...)` (compiles to func_ui_rect).

!   · Fortran 数组 **1 基**:扁平下标 = (行-1)*8 + 列 + 1。

!   · Fortran arrays are **1-based**: flat index = (row-1)*8 + column + 1.


program sokoban
  implicit none
  integer :: map(64)     ! 0=地板 1=墙 2=目标点

  ! 0=floor 1=wall 2=target cell

  integer :: obj(64)     ! 0=空 1=箱子 2=玩家

  ! 0=empty 1=box 2=player

  integer :: i
  integer :: k
  integer :: w
  integer :: h
  integer :: cell
  integer :: ox
  integer :: oy
  integer :: pr
  integer :: pc
  integer :: done
  integer :: t
  integer :: key
  integer :: tid
  integer :: nr
  integer :: nc
  integer :: ni
  integer :: over
  integer :: goals
  integer :: lang

  ! 界面语言:开局问一次宿主要中文还是英文(0=中文 1=英文),之后整局按它分支。

  ! UI language: ask the host once at start whether Chinese or English (0=Chinese 1=English), then branch on it for the whole round.

  ! ⚠ 别在每帧里调 —— 那是一次 syscall。

  ! ⚠ Do not call it every frame -- that is a syscall.

  ! ⚠ 本前端**没有三元/merge**,界面文字只能靠单行 `if` 二选一

  ! ⚠ This frontend has **no ternary/merge**, so UI text can only pick between two options with a single-line `if`

  !   (sokoban 本来就在用这个写法,见下面的 `if (key == 37) nc = pc - 1`)。

  !   (sokoban already used this style, see `if (key == 37) nc = pc - 1` below).

  lang = ui_scr_get_language()

  if (lang == 0) k = ui_win_open('推箱子', 0, 0)
  if (lang /= 0) k = ui_win_open('Sokoban', 0, 0)

  w = ui_scr_get_w()
  h = ui_scr_get_h()
  if (w <= 0) then
    w = 360
  end if
  if (h <= 0) then
    h = 620
  end if

  cell = (w - 40) / 8
  ! ⚠ 本前端在 `if` 条件里解析不了「紧跟括号的除法」(`Unexpected token: Div(/)`)⇒ 先算进变量

  ! ⚠ This frontend cannot parse "a division right after a parenthesis" in an `if` condition (`Unexpected token: Div(/)`) => compute it into a variable first

  k = (h - 150) / 8
  if (k < cell) then
    cell = k
  end if
  if (cell < 8) then
    cell = 8
  end if
  ox = (w - cell * 8) / 2
  oy = 90

  ! ── 关卡:边框是墙,两个目标点、两个箱子 ──

  ! -- Level: the border is walls, with two targets and two boxes --

  ! 地图用代码建(不用 64 个字面量),内部障碍摆两块

  ! The map is built in code (not with 64 literals), with two internal obstacles placed in it

  do i = 1, 64
    map(i) = 0
    obj(i) = 0
  end do

  ! 边框:第 1/8 行、第 1/8 列

  ! Border: rows 1/8, columns 1/8

  do i = 1, 8
    map(i) = 1                 ! 顶行

    ! top row

    map(56 + i) = 1            ! 底行

    ! bottom row

    map((i - 1) * 8 + 1) = 1   ! 左列

    ! left column

    map((i - 1) * 8 + 8) = 1   ! 右列

    ! right column

  end do
  ! 两块内部障碍

  ! Two internal obstacles

  map(3 * 8 + 4 + 1) = 1
  map(5 * 8 + 4 + 1) = 1

  ! 目标点:(行,列) = (3,3) 与 (5,6)

  ! Targets: (row,column) = (3,3) and (5,6)

  map((3 - 1) * 8 + 3 + 1) = 2
  map((5 - 1) * 8 + 6 + 1) = 2
  ! 箱子:(2,2) 与 (3,6)

  ! Boxes: (2,2) and (3,6)

  obj((2 - 1) * 8 + 2 + 1) = 1
  obj((3 - 1) * 8 + 6 + 1) = 1
  ! 玩家:(5,3)

  ! Player: (5,3)

  pr = 5
  pc = 3
  obj((pr - 1) * 8 + pc + 1) = 2

  k = ui_scr_set_keep_on(1)
  done = 0
  tid = ui_timer_set(120, 0)

  do while (ui_win_is_closed() == 0)

    ui_sfx_tick()
    ! ── 过关判定:目标点上都有箱子 ──

    ! -- Win detection: every target cell has a box --

    goals = 0
    do i = 1, 64
      if (map(i) == 2) then
        if (obj(i) == 1) then
          goals = goals + 1
        end if
      end if
    end do
    if (goals == 2) then
      done = 1
    end if

    ! ── draw ──

    k = ui_2d_clear(-15723504)
    ! 用显式两层循环画(比一维反解行号清楚)

    ! Draw with an explicit two-level loop (clearer than reverse-solving row numbers from a flat index)

    do nr = 1, 8
      do nc = 1, 8
        ni = (nr - 1) * 8 + nc
        if (map(ni) == 1) then
          k = ui_2d_rect(ox + (nc - 1) * cell, oy + (nr - 1) * cell, cell, cell, -11841664, 1, 0, 0)
        else
          k = ui_2d_rect(ox + (nc - 1) * cell, oy + (nr - 1) * cell, cell, cell, -15719856, 1, 0, 1)
        end if
        if (map(ni) == 2) then
          k = ui_2d_rect(ox + (nc - 1) * cell + cell / 4, oy + (nr - 1) * cell + cell / 4, cell / 2, cell / 2, -11513776, 0, 3, 0)
        end if
        if (obj(ni) == 1) then
          if (map(ni) == 2) then
            k = ui_2d_rect(ox + (nc - 1) * cell + 3, oy + (nr - 1) * cell + 3, cell - 6, cell - 6, -131246, 1, 0, 2)
          else
            k = ui_2d_rect(ox + (nc - 1) * cell + 3, oy + (nr - 1) * cell + 3, cell - 6, cell - 6, -11409298, 1, 0, 2)
          end if
        end if
        if (obj(ni) == 2) then
          k = ui_2d_circle(ox + (nc - 1) * cell + cell / 2, oy + (nr - 1) * cell + cell / 2, cell / 2 - 6, -63488, 1, 0)
        end if
      end do
    end do

    if (done == 0) then
      if (lang == 0) k = ui_2d_text(8, 8, '推箱子:把两个箱子推到空心格', -6643536, 13, 0)
      if (lang /= 0) k = ui_2d_text(8, 8, 'Push boxes onto the hollow targets', -6643536, 13, 0)
    else
      if (lang == 0) k = ui_2d_text(8, 8, '过关!回车再来一次', -131246, 16, 0)
      if (lang /= 0) k = ui_2d_text(8, 8, 'Cleared! Press Enter to restart', -131246, 16, 0)
    end if
    k = ui_2d_present()

    t = ui_msg_fetch_wait(0)
    if (t == 10) then
      exit
    end if

    if (t == 1) then
      key = ui_msg_get_a()
      if (key == 27) then
        exit
      end if
      if (key == 13) then
        ! 重开:把箱子与玩家放回原位

        ! Restart: put the boxes and the player back in their original places

        obj((2 - 1) * 8 + 2 + 1) = 1
        obj((3 - 1) * 8 + 6 + 1) = 1
        obj((pr - 1) * 8 + pc + 1) = 0
        pr = 5
        pc = 3
        obj((pr - 1) * 8 + pc + 1) = 2
        done = 0
      end if

      ! 方向键 → 目标格

      ! Arrow key -> target cell

      nr = pr
      nc = pc
      if (key == 37) nc = pc - 1
      if (key == 39) nc = pc + 1
      if (key == 38) nr = pr - 1
      if (key == 40) nr = pr + 1
      if (nr /= pr .or. nc /= pc) then
        ni = (nr - 1) * 8 + nc
        if (map(ni) /= 1) then
          if (obj(ni) == 0) then
            ! 空地:走过去

            ! Empty floor: step onto it

            obj((pr - 1) * 8 + pc + 1) = 0
            pr = nr
            pc = nc
            obj((pr - 1) * 8 + pc + 1) = 2
          else
            if (obj(ni) == 1) then
              ! 箱子:看它后面那格能不能放

              ! A box: check whether the cell behind it is free

              over = (nr - pr) + (nc - pc)
              if (over == -1) then
                if (map((nr - 1) * 8 + nc - 1) == 1) over = 0
                if (obj((nr - 1) * 8 + nc - 1) /= 0) over = 0
                if (over /= 0) then
                  obj((nr - 1) * 8 + nc - 1) = 1
                end if
              end if
              if (over == 1) then
                if (map((nr - 1) * 8 + nc + 1) == 1) over = 0
                if (obj((nr - 1) * 8 + nc + 1) /= 0) over = 0
                if (over /= 0) then
                  obj((nr - 1) * 8 + nc + 1) = 1
                end if
              end if
              if (over == -8) then
                if (map((nr - 2) * 8 + nc) == 1) over = 0
                if (obj((nr - 2) * 8 + nc) /= 0) over = 0
                if (over /= 0) then
                  obj((nr - 2) * 8 + nc) = 1
                end if
              end if
              if (over == 8) then
                if (map(nr * 8 + nc) == 1) over = 0
                if (obj(nr * 8 + nc) /= 0) over = 0
                if (over /= 0) then
                  obj(nr * 8 + nc) = 1
                end if
              end if
              ! 推得动才走

              ! Only move if it can be pushed

              if (over /= 0) then
                obj(ni) = 0
                obj((pr - 1) * 8 + pc + 1) = 0
                pr = nr
                pc = nc
                obj((pr - 1) * 8 + pc + 1) = 2
                call ui_sfx_add(0, 84, 0, 2, 75, 1)      ! 推到位:上行两音

                ! Pushed into place: two rising notes

                call ui_sfx_add(1, 91, 1, 4, 70, 1)
              else
                call ui_sfx_add(2, 48, 0, 2, 80, 2)   ! 推不动:低闷

                ! Cannot push: low and muffled

              end if
            end if
          end if
        else
          call ui_sfx_add(2, 48, 0, 2, 80, 2)   ! 推不动:低闷

          ! Cannot push: low and muffled

        end if
      end if
    end if
  end do

  k = ui_timer_kill(tid)
  k = ui_scr_set_keep_on(0)
  k = ui_win_close()
end program sokoban