diff options
Diffstat (limited to 'ipl/gprogs/hb.icn')
-rw-r--r-- | ipl/gprogs/hb.icn | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/ipl/gprogs/hb.icn b/ipl/gprogs/hb.icn new file mode 100644 index 0000000..077c3c2 --- /dev/null +++ b/ipl/gprogs/hb.icn @@ -0,0 +1,334 @@ +############################################################################ +# +# File: hb.icn +# +# Subject: Program for Hearts & Bones game +# +# Author: Robert J. Alexander +# +# Date: March 10, 1998 +# +############################################################################ +# +# This file is in the public domain. +# +############################################################################ +# +# Hearts & Bones +# +# Usage: hb [-h <board height>] [-w <board width>] [-b <# bones>] [-B] +# +# -B says to print the actual number of bones placed. +# +# For best results, use odd board heights and widths, and even +# square heights and widths. +# +# Defaults: board height = 9, board width = 13, # bones = 25. +# +# --- Game Play --- +# +# Hit "q" to quit, "r" to start a new game. +# +# The object is to visit all the safe squares without stepping on a +# bone. +# +# You *visit* a square by clicking the left mouse button in it. If the +# square is safe, a number is posted in it that reveals the number of +# squares in the eight neighboring squares the contain bones. Squares +# containing hearts (represented by $) are always safe. +# +# You can only visit squares that are adjacent to squares already +# visited. At the start of a game, the upper left square (a heart +# square) is pre-visited for you. If a visited square has no +# neighbors, its adjacent squares are automatically visited for you, as +# a convenience. +# +# At any time you can *mark* a square that you believe has a bone by +# clicking the right (or center) mouse button on it. This is a memory +# aid only -- if you visit it later (and you were right), you're dead. +# There is no confirmation whether a square you have marked really +# contains a bone, although you will probably find out later when it +# causes you to make a mistake. A right-button click on a marked +# square unmarks it. +# +# The game ends when you have visited all safe squares or stepped on a +# bone. (Presently, there is no automatic detection of a winning board +# -- you just have to notice that for yourself). +# +# NOTE: If you use the command line options to alter the setup +# parameters (e.g. increasing the number of squares, or *decreasing* +# the number of bones), you might get a stack overflow due, I think, to +# deep recursion. I have found that setting the environment variable +# MSTKSIZE=30000 works well. +# +############################################################################ +# +# Links: options, random, wopen +# +############################################################################ +# +# Requires: Version 9 graphics +# +############################################################################ + +link options +link random +link wopen + +global height, width, nbr_bones, x1, y1, sq, print_bone_count + + +procedure main(arg) + initialize(arg) + play() + return +end + + +procedure draw_board(win) + local x, y, x2, y2 + x2 := x1 + width * sq + y2 := y1 + height * sq + x := x1 + every 1 to width + 1 do { + DrawLine(win, x, y1, x, y2) + x +:= sq + } + y := y1 + every 1 to height + 1 do { + DrawLine(win, x1, y, x2, y) + y +:= sq + } + return +end + + +procedure set_up_board(win, visited) + local board, pt + EraseArea(win) + board := make_board() + set_bones(board, nbr_bones) + calc_neighbors(board) + draw_board(win) + draw_hearts(win) + every pt := spread_zeros(board, 1, 1) do { + write_to_square(win, pt[1], pt[2], pt[3]) + visited[pt[1], pt[2]] := 1 + } + return board +end + + +procedure draw_hearts(win) + local pt + every pt := generate_heart_squares() do + write_to_square(win, pt[1], pt[2], "$") + return +end + + +procedure legal_move(x, y, visited) + local xx, yy + every xx := x - 1 to x + 1 & yy := y - 1 to y + 1 do + if \visited[xx, yy] then { + visited[x, y] := 1 + return + } +end + + +procedure play() + local win, x, y, evt, mark, marks, board, visited, pt, value + sq := (if match("OS/2", &host) then 30 else 20) + x1 := 10 + y1 := 10 + win := WOpen("label=HB", "size=" || width * sq + 2 * x1 || "," || + height * sq + 2 * y1) + repeat { + visited := make_board() + board := set_up_board(win, visited) + marks := make_board(" ") + repeat { + evt := Event(win) + case type(evt) of { + "string": case map(evt) of { + "q": exit() + "r": break next + } + "integer": { + if evt = &lrelease then { + x := (&x - x1) / sq + 1 + y := (&y - y1) / sq + 1 + if legal_move(x, y, visited) then { + value := board[x, y] + if value ~=== "X" then { + # + # Visited a safe square. + # + if value = 0 then + every pt := spread_zeros(board, x, y) do { + write_to_square(win, pt[1], pt[2], pt[3]) + visited[pt[1], pt[2]] := 1 + } + else write_to_square(win, x, y, value) + } + else { + # + # Stepped on a bone -- game over. + # + every x := 1 to width & y := 1 to height do { + value := board[x, y] + write_to_square(win, x, y, "X" === value) + } + draw_hearts(win) + repeat { + evt := Event(win) + case type(evt) of { + "integer": if evt = &lrelease then break + "string": case map(evt) of { + "q": exit() + "r": break + } + } + } + break + } + } + } + else if evt = (&mrelease | &rrelease) then { + x := (&x - x1) / sq + 1 + y := (&y - y1) / sq + 1 + mark := marks[x, y] := if marks[x, y] == " " then "#" else " " + write_to_square(win, x, y, mark) + } + } + } + } + } +end + + +procedure spread_zeros(board, x, y, doneset) + local xx, yy, v, donekey + /doneset := set() + donekey := x || "," || y + if member(doneset, donekey) then fail + insert(doneset, donekey) + (v := board[x, y]) | fail + suspend [x, y, v] + if v === 0 then { + every xx := x - 1 to x + 1 & yy := y - 1 to y + 1 do + if not(x = xx & y = yy) & board[xx, yy] then + suspend spread_zeros(board, xx, yy, doneset) + } +end + + +procedure write_to_square(win, x, y, s) + WAttrib(win, + "x=" || x1 + (x - 1) * sq + sq / 2 - 2, + "y=" || y1 + (y - 1) * sq + sq / 2 + 4) + return writes(win, s) +end + + +procedure get_options(arg) + local opt + opt := options(arg, "h+w+b+B") + height := \opt["h"] | 9 + width := \opt["w"] | 15 + nbr_bones := \opt["b"] | (height * width - 9) / 5 + print_bone_count := opt["B"] + width <:= 5 + height <:= 5 + nbr_bones >:= height * width * 2 / 3 + return opt +end + + +procedure initialize(arg) + randomize() + get_options(arg) + return +end + + +procedure make_board(init_value) + local board + board := list(width) + every !board := list(height, init_value) + return board +end + + +procedure generate_heart_squares() + suspend [1 | (width + 1) / 2 | width, 1 | (height + 1) / 2 | height] +end + + +procedure set_bones(board, nbr_bones) + local i, j, pt, bone_count + every pt := generate_heart_squares() do board[pt[1], pt[2]] := "$" + board[1, 2] := board[2, 1] := board[2, 2] := "$" + bone_count := 0 + every 1 to nbr_bones do { + # + # Loop to find a spot with a path back to the start. If we don't + # find one after several tries, quit placing bones. + # + (every 1 to 20 do { + i := ?width + j := ?height + if /board[i, j] then { + board[i, j] := "X" + if hearts_reachable(board) then { + bone_count +:= 1 + break + } + else board[i, j] := &null + } + }) | break + } + if \print_bone_count then write(&errout, bone_count, " bones") + return +end + + +procedure calc_neighbors(board) + local i, j, ii, jj, neighbors + every i := 1 to width & j := 1 to height do { + if board[i, j] ~=== "X" then { + neighbors := 0 + every ii := i - 1 to i + 1 & jj := j - 1 to j + 1 do { + if board[ii, jj] === "X" then neighbors +:= 1 + } + board[i, j] := neighbors + } + } + return +end + + +procedure hearts_reachable(board) + local pt + every pt := generate_heart_squares() do { + if not path_to_start(pt[1], pt[2], board) then fail + } + return +end + + +procedure path_to_start(x, y, board, doneset) + local xx, yy, donekey + /doneset := set() + if not(board[x, y] ~=== "X") then fail + if x = 1 & y = 1 then return + donekey := x || "," || y + if member(doneset, donekey) then fail + insert(doneset, donekey) + every xx := x - 1 to x + 1 & yy := y - 1 to y + 1 do { + if x = xx & y == yy then next + if path_to_start(xx, yy, board, doneset) then return + } +end + |